| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 |
- <?php
- // Long-poll endpoint. Holds the connection open for up to ~28 seconds until
- // either (a) the job_history or messages tables receive a row beyond the
- // caller's since markers, or (b) it times out. Either way returns the current
- // max IDs so the client can update its trackers.
- //
- // One PHP-FPM worker is parked per connected client while this runs. For 2-3
- // users that's fine; if more users start using this app, raise pm.max_children.
- require_once __DIR__ . '/../lib/identity.php';
- [$actor, $vendor_id] = resolve_request_actor();
- $since_h = (int) ($_GET['since_h'] ?? 0);
- // Messages "since" markers.
- // Vendor caller: a single int (their own vendor's last seen message id).
- // ICG caller: "vid:id,vid:id" pairs (one per vendor thread on PDQ.php).
- $since_m = [];
- if ($actor === 'ICG') {
- foreach (array_filter(explode(',', (string) ($_GET['since_m'] ?? ''))) as $pair) {
- $p = explode(':', $pair);
- if (count($p) === 2) $since_m[(int) $p[0]] = (int) $p[1];
- }
- } else {
- $since_m[$vendor_id] = (int) ($_GET['since_m'] ?? 0);
- }
- // PHP's default max_execution_time is 30s; give ourselves slightly more so the
- // loop can finish writing the response before the deadline.
- @set_time_limit(45);
- $pdo = db();
- $vfilter = $actor === 'ICG' ? null : $vendor_id;
- $deadline = microtime(true) + 28.0;
- header('Content-Type: application/json');
- while (true) {
- [$cur_h, $cur_m] = events_snapshot($pdo, $vfilter);
- $history_changed = $cur_h > $since_h;
- $changed_vendors = [];
- foreach ($cur_m as $vid => $maxid) {
- if ($maxid > ($since_m[$vid] ?? 0)) $changed_vendors[] = $vid;
- }
- if ($history_changed || $changed_vendors) {
- echo json_encode([
- 'history' => $cur_h,
- 'messages' => $cur_m,
- 'history_changed' => $history_changed,
- 'changed_vendors' => $changed_vendors,
- ]);
- return;
- }
- if (microtime(true) >= $deadline) {
- echo json_encode([
- 'history' => $cur_h,
- 'messages' => $cur_m,
- 'timeout' => true,
- ]);
- return;
- }
- // Bail early if the client has gone away (cheap check — flush updates the
- // connection state). Otherwise sleep 1s and loop.
- if (connection_aborted()) return;
- usleep(1000000);
- }
- function events_snapshot(PDO $pdo, ?int $vendor_filter): array {
- if ($vendor_filter === null) {
- $h = (int) $pdo->query('SELECT COALESCE(MAX(id), 0) FROM job_history')->fetchColumn();
- $m = [];
- foreach ($pdo->query('SELECT vendor_id, COALESCE(MAX(id), 0) AS maxid FROM messages GROUP BY vendor_id')->fetchAll() as $r) {
- $m[(int) $r['vendor_id']] = (int) $r['maxid'];
- }
- // Vendors with zero messages get a 0 entry so the client tracks them.
- foreach ($pdo->query('SELECT id FROM vendors WHERE active = 1')->fetchAll() as $v) {
- if (!isset($m[(int) $v['id']])) $m[(int) $v['id']] = 0;
- }
- } else {
- $stmt = $pdo->prepare(
- 'SELECT COALESCE(MAX(h.id), 0)
- FROM job_history h
- JOIN jobs j ON j.id = h.job_id
- WHERE j.vendor_id = ?'
- );
- $stmt->execute([$vendor_filter]);
- $h = (int) $stmt->fetchColumn();
- $stmt = $pdo->prepare('SELECT COALESCE(MAX(id), 0) FROM messages WHERE vendor_id = ?');
- $stmt->execute([$vendor_filter]);
- $m = [$vendor_filter => (int) $stmt->fetchColumn()];
- }
- return [$h, $m];
- }
|