aboutsummaryrefslogtreecommitdiff
path: root/api/chat/poll.php
blob: b4aa9700edf9244ca11c289dc1b6f08c878cfe7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
// api/chat/poll.php — read back the caller's own thread. The thread comes from the
// token, never from a query parameter: no id to guess, no IDOR, no listing.
// Contract: _docs/chat-contract.md
require_once __DIR__ . '/lib.php';
// Note: poll is deliberately NOT rate limited. A 30 s poller is 120 GETs/hour;
// sharing the write window would make the client throttle itself into silence.

$token  = chat_token();
$thread = chat_thread($token);
$since  = isset($_GET['since']) ? (int)$_GET['since'] : 0;   // epoch seconds, numeric

$all = chat_read($thread);
$messages = $since > 0
    ? array_values(array_filter($all, fn($m) => (int)($m['epoch'] ?? 0) > $since))
    : $all;

$window = array_slice($messages, -50);
$truncated = count($messages) > count($window);
// The cursor is the last message the client is actually being handed. Returning the
// thread's real tail after a truncation would skip everything in between for good.
$lastSent = $window ? $window[count($window) - 1] : null;
$lastAll  = $all ? $all[count($all) - 1] : null;

json_response([
    'success'  => true,
    'thread'   => $thread,
    'messages' => $window,
    'truncated' => $truncated,
    // `awaiting` replaces `unread`: the server keeps no read state, so an unread
    // counter could never clear. Who spoke last is knowable.
    'awaiting' => (bool)($lastAll && ($lastAll['author'] ?? '') !== 'hermes'),
    'cursor'   => $lastSent ? (int)($lastSent['epoch'] ?? 0) : ($lastAll ? (int)($lastAll['epoch'] ?? 0) : 0),
]);