Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
46.15% covered (danger)
46.15%
18 / 39
87.50% covered (success)
87.50%
7 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ClientsService
46.15% covered (danger)
46.15%
18 / 39
87.50% covered (success)
87.50%
7 / 8
55.97
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 refresh
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 create
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 delete
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 update
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 cancel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 notify
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 unload
32.26% covered (danger)
32.26%
10 / 31
0.00% covered (danger)
0.00%
0 / 1
34.18
1<?php
2
3declare(strict_types=1);
4
5namespace UppServices;
6
7/**
8 * Controls connection state for sync clients (create, refresh, update, delete, cancel, notify, unload).
9 * Delegates to App/Services/legacy/model/conncontrol.php, Legacy logout, and RegstService for unload.
10 */
11class ClientsService
12{
13    public function __construct(
14        private readonly RegstService $regstService,
15    ) {
16        require_once __DIR__.'/legacy/model/conncontrol.php';
17        require_once __DIR__.'/../Legacy/common/logout.php';
18    }
19
20    /**
21     * Refresh connection status for a session (polling). Updates TTL and returns status.
22     *
23     * @param \conx $conx Legacy db connection (must be connected when using DB backend).
24     * @param string $session Session id.
25     * @param string $refresh Output: 'UPDATED'|'REFRESH'|'EXPIRED'|'CANCELLED'.
26     * @return int ApplicationError code.
27     */
28    public function refresh(\conx $conx, string $session, string &$refresh): int
29    {
30        return \connection_refresh($conx, $session, $refresh);
31    }
32
33    /**
34     * Create or update connection entry for the session (initial sync registration).
35     *
36     * @param \conx $conx Legacy db connection.
37     * @param string $session Session id.
38     * @param array<string, mixed> $config Client config (incl. triggers).
39     * @return int ApplicationError code.
40     */
41    public function create(\conx $conx, string $session, array $config): int
42    {
43        return \connection_create($conx, $session, $config);
44    }
45
46    /**
47     * Delete connection entry (e.g. on logout).
48     *
49     * @param \conx $conx Legacy db connection.
50     * @param string $session Session id.
51     * @return int ApplicationError code.
52     */
53    public function delete(\conx $conx, string $session): int
54    {
55        return \connection_delete($conx, $session);
56    }
57
58    /**
59     * Update connection config (e.g. triggers).
60     *
61     * @param \conx $conx Legacy db connection.
62     * @param string $session Session id.
63     * @param array<string, mixed> $config Client config.
64     * @return int ApplicationError code.
65     */
66    public function update(\conx $conx, string $session, array $config): int
67    {
68        return \connection_update($conx, $session, $config);
69    }
70
71    /**
72     * Cancel connection (e.g. wake from another device).
73     *
74     * @param \conx $conx Legacy db connection.
75     * @param string $source Session id to cancel.
76     * @return int ApplicationError code.
77     */
78    public function cancel(\conx $conx, string $source): int
79    {
80        return \connection_cancel($conx, $source);
81    }
82
83    /**
84     * Notify connected sessions that match the given updates (trigger-based).
85     *
86     * @param \ConxHelper $conx Connection helper (uses ->global for central DB).
87     * @param string $origin Session id of the request origin.
88     * @param array<int, array{table: string, objid: mixed}> $updates List of table/objid changes.
89     * @param bool $toorigin Whether to force notify the origin as well.
90     * @return int ApplicationError code.
91     */
92    public function notify(\ConxHelper $conx, string $origin, array &$updates, bool $toorigin): int
93    {
94        return \connection_notify($conx, $origin, $updates, $toorigin);
95    }
96
97    /**
98     * Unload (logout) session: drop CONNECTED, release SESSION to DE, register events.
99     *
100     * Deletes the wait entry first (best-effort) so an in-flight sync wait does not
101     * continue and race with release. Client also halts sync on pagehide before this
102     * beacon. A later check_session may revive DE→AC within RefreshTime (window refresh).
103     *
104     * @param \ConxHelper $conx Connection helper (uses ->global for central DB).
105     * @param array{session: string, place?: mixed, table?: mixed} $body Payload with session id and optional place/table for registry.
106     * @return int ApplicationError code.
107     */
108    public function unload(\ConxHelper $conx, array $body): int
109    {
110        $global = $conx->global;
111        if ($global === null) {
112            return \ApplicationError::Database;
113        }
114        $when = $global->now();
115        $sessionId = (string) $body['session'];
116
117        // Best-effort: end long-poll before release so peers are not the only wake path.
118        $deleted = \connection_delete($global, $sessionId);
119        if (!\success($deleted)) {
120            \addlog(__FILE__, \LogLevel::Warning, "ClientsService::unload: connection_delete failed for session [".$sessionId."]; continuing with release.");
121        }
122
123        $retval = \logout_session_and_notify($conx, $when, $sessionId);
124        if (!\success($retval)) {
125            return $retval;
126        }
127        $results = [];
128        $res = $global->query('SESSION', ['id' => $body['session']], $results);
129        if (!$res || count($results) === 0) {
130            return \ApplicationError::NotFound;
131        }
132        
133        $sessionObjid = $results[0]['objid'];
134        if (!empty($body['place'])) {
135            $event = [
136                'created' => $global->now(),
137                'place' => $body['place'],
138                'login' => '0',
139            ];
140            $retval = $this->regstService->registerLoginEvent($conx, (string) $sessionObjid, $event);
141        }
142        
143        if (\success($retval) && !empty($body['table'])) {
144            $event = [
145                'created' => $global->now(),
146                'qrcode' => $body['table'],
147                'login' => '0',
148            ];
149            $retval = $this->regstService->registerGuestEvent($conx, (string) $sessionObjid, $event);
150        }
151        return $retval;
152    }
153}