Skip to content

Commit a0e4306

Browse files
committed
wip
1 parent fca69a9 commit a0e4306

6 files changed

Lines changed: 164 additions & 1 deletion

File tree

asterisk/agi/src/Agi/Webhook/WebhookEventPublisher.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ public function __construct(
1818

1919
public function publish(string $event): void
2020
{
21+
// Disabled: the realtime webhook dispatcher (subscribed to Kamailio
22+
// pubsub) is now the single source of webhook events.
23+
return;
24+
2125
try {
2226
$brandId = (int) $this->agi->getVariable('BRANDID');
2327
if ($brandId === 0) {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
use Symfony\Component\Console\Input\ArgvInput;
5+
use Symfony\Component\HttpFoundation\Request;
6+
7+
require __DIR__.'/../config/bootstrap.php';
8+
9+
$input = new ArgvInput();
10+
$env = $input->getParameterOption(['--env', '-e'], getenv('APP_ENV') ?: 'dev');
11+
12+
$kernel = new Kernel($env, false);
13+
$request = new Request([], [], [], [], [], ['REQUEST_URI' => '/realtime-webhook-dispatcher']);
14+
15+
$response = $kernel->handle($request);
16+
$response->send();
17+
$kernel->terminate($request, $response);
18+
19+
if ($response->getStatusCode() >= 300) {
20+
die(1);
21+
}

microservices/workers/config/routes.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,8 @@ asterisk-hint-update:
3636

3737
webhook-dispatcher:
3838
path: /webhook-dispatcher
39-
controller: Worker\Webhooks::dispatch
39+
controller: Worker\Webhooks::dispatch
40+
41+
realtime-webhook-dispatcher:
42+
path: /realtime-webhook-dispatcher
43+
controller: Worker\RealtimeWebhooks::dispatch

microservices/workers/config/services.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,8 @@ services:
7474
$redisDb: '%redis_jobs_db%'
7575
$redisTimeout: '%redis_timeout%'
7676

77+
Worker\RealtimeWebhooks:
78+
public: true
79+
arguments:
80+
$logger: '@monolog.logger.workers'
81+
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
namespace Worker;
4+
5+
use Ivoz\Core\Infrastructure\Persistence\Redis\RedisMasterFactory;
6+
use Ivoz\Provider\Domain\Job\WebhookJobInterface;
7+
use Ivoz\Provider\Domain\Model\Webhook\Payload\WebhookEventPayload;
8+
use Psr\Log\LoggerInterface;
9+
use Symfony\Component\HttpFoundation\Response;
10+
11+
class RealtimeWebhooks
12+
{
13+
private const REDIS_REALTIME_DB = 1;
14+
private const CHANNEL_PATTERN = 'users:*';
15+
16+
private const EVENT_MAP = [
17+
'Trying' => 'start',
18+
'Proceeding' => 'ring',
19+
'Early' => 'ring',
20+
'Confirmed' => 'answer',
21+
'Terminated' => 'end',
22+
];
23+
24+
/** @var array<string, array{Party: ?string, Direction: ?string, Owner: ?string}> */
25+
private array $callCache = [];
26+
27+
public function __construct(
28+
private RedisMasterFactory $redisMasterFactory,
29+
private WebhookJobInterface $webhookJob,
30+
private LoggerInterface $logger,
31+
) {
32+
}
33+
34+
public function dispatch(): Response
35+
{
36+
$redis = $this->redisMasterFactory->create(self::REDIS_REALTIME_DB);
37+
38+
$redis->pSubscribe(
39+
[self::CHANNEL_PATTERN],
40+
function ($redis, $pattern, $channel, $message): void {
41+
try {
42+
$this->processMessage($channel, $message);
43+
} catch (\Throwable $e) {
44+
$this->logger->error(
45+
'[RT-WEBHOOK] Error processing message on ' . $channel . ': ' . $e->getMessage()
46+
);
47+
}
48+
}
49+
);
50+
51+
return new Response('', 500);
52+
}
53+
54+
private function processMessage(string $channel, string $message): void
55+
{
56+
if (!preg_match('/^users:b(\d+):c(\d+):/', $channel, $matches)) {
57+
return;
58+
}
59+
60+
$brandId = (int) $matches[1];
61+
$companyId = (int) $matches[2];
62+
63+
/** @var array<string, mixed>|null $data */
64+
$data = json_decode($message, true);
65+
if (!is_array($data) || !isset($data['Event'])) {
66+
return;
67+
}
68+
69+
$kamEvent = (string) $data['Event'];
70+
$callId = isset($data['Call-ID']) ? (string) $data['Call-ID'] : null;
71+
72+
if ($kamEvent === 'UpdateCLID') {
73+
return;
74+
}
75+
76+
$webhookEvent = self::EVENT_MAP[$kamEvent] ?? null;
77+
if ($webhookEvent === null) {
78+
return;
79+
}
80+
81+
if ($kamEvent === 'Trying' && $callId !== null) {
82+
$this->callCache[$callId] = [
83+
'Party' => isset($data['Party']) ? (string) $data['Party'] : null,
84+
'Direction' => isset($data['Direction']) ? (string) $data['Direction'] : null,
85+
'Owner' => isset($data['Owner']) ? (string) $data['Owner'] : null,
86+
];
87+
}
88+
89+
$cached = ($callId !== null && isset($this->callCache[$callId]))
90+
? $this->callCache[$callId]
91+
: ['Party' => null, 'Direction' => null, 'Owner' => null];
92+
93+
$party = isset($data['Party']) ? (string) $data['Party'] : $cached['Party'];
94+
$direction = $cached['Direction'];
95+
96+
$payload = new WebhookEventPayload(
97+
event: $webhookEvent,
98+
brandId: $brandId,
99+
companyId: $companyId,
100+
ddiId: null,
101+
ddiE164: null,
102+
callId: $callId,
103+
uniqueId: isset($data['ID']) ? (string) $data['ID'] : null,
104+
caller: ($direction === 'inbound') ? $party : null,
105+
callee: ($direction === 'outbound') ? $party : null,
106+
dialStatus: null,
107+
timestamp: isset($data['Time'])
108+
? date('Y-m-d\TH:i:s.v\Z', (int) $data['Time'])
109+
: null,
110+
);
111+
112+
$this->webhookJob
113+
->setData($payload)
114+
->send();
115+
116+
if ($kamEvent === 'Terminated' && $callId !== null) {
117+
unset($this->callCache[$callId]);
118+
}
119+
}
120+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[program:realtime-webhook-dispatcher]
2+
command=/usr/bin/php /opt/irontec/ivozprovider/microservices/workers/bin/realtime-webhook-dispatcher
3+
autorestart=true
4+
autostart=true
5+
user=www-data
6+
startretries=5
7+
process_name=%(program_name)s
8+
numprocs=1
9+
stopsignal=KILL

0 commit comments

Comments
 (0)