@ai-matrx/desktop-protocol 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +7 -1
- package/dist/client.cjs +63 -4
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +29 -1
- package/dist/client.d.ts +29 -1
- package/dist/client.js +63 -4
- package/dist/client.js.map +1 -1
- package/dist/react.cjs +28 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +20 -1
- package/dist/react.d.ts +20 -1
- package/dist/react.js +28 -1
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
The phone console (lane E) needs a client that comes back the instant the person does.
|
|
6
|
+
|
|
7
|
+
- `client.wake({ probeMs })` — reconnecting: dial now instead of waiting out the backoff; open:
|
|
8
|
+
send a ping and, if nothing is heard within `probeMs` (default 4000), treat the socket as dead
|
|
9
|
+
and redial at once. A phone resumed from sleep often holds a socket the OS already killed.
|
|
10
|
+
- `bindDesktopWake(client)` (`./client`) and `useDesktopWake()` (`./react`) call `wake()` on
|
|
11
|
+
`visibilitychange` → visible, `pageshow`, `online` and `focus`.
|
|
12
|
+
- A caller-started `session.attach` (a page reload reattaching by the resource id it kept in the
|
|
13
|
+
URL) is now a resource stream from its first byte: `resourceId` is set from the params,
|
|
14
|
+
`lastSeq` starts at `since_seq` (no false `seq_gap` on the first replayed frame), and the stream
|
|
15
|
+
reattaches after a later drop like the stream that created the resource.
|
|
16
|
+
|
|
17
|
+
Consumer action: none required. Phone and browser consoles should call `useDesktopWake()` (or
|
|
18
|
+
`bindDesktopWake`) once per client.
|
|
19
|
+
|
|
3
20
|
## 0.1.1
|
|
4
21
|
|
|
5
22
|
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ pnpm add @ai-matrx/desktop-protocol
|
|
|
14
14
|
| `@ai-matrx/desktop-protocol` | Schemas for the envelope and every `cap.op`, `OPS` / `EVENTS` registries, `ErrorCode`, close codes, `DesktopProtocolError`. Every schema name is also its inferred type. |
|
|
15
15
|
| `@ai-matrx/desktop-protocol/frame` | The 16-byte binary frame codec. Zod-free (the relay Worker's hot path). |
|
|
16
16
|
| `@ai-matrx/desktop-protocol/client` | `createDesktopClient` — reconnecting client with typed requests, credit-gated streams, transparent terminal reattach, heartbeat and relay reauth. |
|
|
17
|
-
| `@ai-matrx/desktop-protocol/react` | `DesktopClientProvider`, `useDesktopConnection`, `useDesktopRequest`, `useDesktopEvent`. |
|
|
17
|
+
| `@ai-matrx/desktop-protocol/react` | `DesktopClientProvider`, `useDesktopConnection`, `useDesktopRequest`, `useDesktopEvent`, `useDesktopWake`. |
|
|
18
18
|
|
|
19
19
|
## Client
|
|
20
20
|
|
|
@@ -54,6 +54,12 @@ term.write("ls\n");
|
|
|
54
54
|
`@ai-matrx/realtime` backoff (stability reset + jitter) — including `1006` (a relay deploy drops
|
|
55
55
|
every socket without a close frame) and `1012` (the relay's "the device reconnected": hello and
|
|
56
56
|
reattach against the new core session).
|
|
57
|
+
- **Coming back:** `client.wake()` (or `bindDesktopWake(client)` / `useDesktopWake()`, which call it
|
|
58
|
+
on `visibilitychange`, `pageshow`, `online` and `focus`) dials at once while reconnecting and
|
|
59
|
+
probes an open socket with a ping, redialing if nothing answers within 4 s.
|
|
60
|
+
- **Reload reattach:** keep `stream.resourceId` and `stream.lastSeq` (e.g. in the URL); after a reload
|
|
61
|
+
`client.stream("session.attach", { resource_id, since_seq })` replays the gap and then behaves
|
|
62
|
+
exactly like the original stream, including surviving later drops.
|
|
57
63
|
- Background problems (malformed messages, sequence gaps, heartbeat death, reconnect alarm) go to
|
|
58
64
|
`onDiagnostic` — loud by default (`console.warn`).
|
|
59
65
|
|
package/dist/client.cjs
CHANGED
|
@@ -22,6 +22,7 @@ var client_exports = {};
|
|
|
22
22
|
__export(client_exports, {
|
|
23
23
|
DesktopProtocolError: () => DesktopProtocolError,
|
|
24
24
|
RECONNECT_ALARM_ATTEMPTS: () => import_realtime.RECONNECT_ALARM_ATTEMPTS,
|
|
25
|
+
bindDesktopWake: () => bindDesktopWake,
|
|
25
26
|
createDesktopClient: () => createDesktopClient,
|
|
26
27
|
isDesktopProtocolError: () => isDesktopProtocolError
|
|
27
28
|
});
|
|
@@ -761,7 +762,9 @@ function createDesktopClient(options) {
|
|
|
761
762
|
let helloId = null;
|
|
762
763
|
let reconnectTimer = null;
|
|
763
764
|
let heartbeatTimer = null;
|
|
765
|
+
let probeTimer = null;
|
|
764
766
|
let lastHeardAt = 0;
|
|
767
|
+
let heardCount = 0;
|
|
765
768
|
let closedByUser = false;
|
|
766
769
|
const newId = (prefix) => `${prefix}${(++nextId).toString(36)}`;
|
|
767
770
|
function setState(patch) {
|
|
@@ -816,6 +819,7 @@ function createDesktopClient(options) {
|
|
|
816
819
|
ws.onmessage = (event) => {
|
|
817
820
|
if (generation !== socketGeneration) return;
|
|
818
821
|
lastHeardAt = timers.now();
|
|
822
|
+
heardCount++;
|
|
819
823
|
onMessage(event.data);
|
|
820
824
|
};
|
|
821
825
|
ws.onerror = () => {
|
|
@@ -857,6 +861,10 @@ function createDesktopClient(options) {
|
|
|
857
861
|
timers.clearTimeout(heartbeatTimer);
|
|
858
862
|
heartbeatTimer = null;
|
|
859
863
|
}
|
|
864
|
+
if (probeTimer !== null) {
|
|
865
|
+
timers.clearTimeout(probeTimer);
|
|
866
|
+
probeTimer = null;
|
|
867
|
+
}
|
|
860
868
|
const terminal = code === null ? void 0 : TERMINAL_CLOSE.get(code);
|
|
861
869
|
const lost = error ?? (terminal ? new DesktopProtocolError(terminal[0], terminal[1]) : new DesktopProtocolError("DEVICE_OFFLINE", "Connection lost", { retryable: true }));
|
|
862
870
|
for (const [id, p] of pending) {
|
|
@@ -1233,15 +1241,16 @@ function createDesktopClient(options) {
|
|
|
1233
1241
|
});
|
|
1234
1242
|
result.catch(() => void 0);
|
|
1235
1243
|
done.catch(() => void 0);
|
|
1244
|
+
const attach = op === "session.attach" ? params : null;
|
|
1236
1245
|
const s = {
|
|
1237
1246
|
op,
|
|
1238
1247
|
handlers,
|
|
1239
1248
|
options: opts,
|
|
1240
1249
|
id: newId("s"),
|
|
1241
1250
|
sid: null,
|
|
1242
|
-
resourceId: null,
|
|
1243
|
-
lastSeq: 0,
|
|
1244
|
-
expectSeq: 1,
|
|
1251
|
+
resourceId: attach ? attach.resource_id : null,
|
|
1252
|
+
lastSeq: attach?.since_seq ?? 0,
|
|
1253
|
+
expectSeq: attach ? attach.since_seq === void 0 ? null : attach.since_seq + 1 : 1,
|
|
1245
1254
|
windowBytes: opts.windowBytes ?? DEFAULT_WINDOW_BYTES,
|
|
1246
1255
|
consumedUncredited: 0,
|
|
1247
1256
|
sendWindow: DEFAULT_WINDOW_BYTES,
|
|
@@ -1330,6 +1339,34 @@ function createDesktopClient(options) {
|
|
|
1330
1339
|
function reauth(token) {
|
|
1331
1340
|
sendJson({ type: "relay", op: "reauth", token });
|
|
1332
1341
|
}
|
|
1342
|
+
function wake(opts = {}) {
|
|
1343
|
+
if (closedByUser || state.status === "closed") return;
|
|
1344
|
+
if (state.status === "idle" || state.status === "reconnecting") {
|
|
1345
|
+
connect();
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
if (state.status === "connecting" || !socket || probeTimer !== null) return;
|
|
1349
|
+
const generation = socketGeneration;
|
|
1350
|
+
const heardBefore = heardCount;
|
|
1351
|
+
try {
|
|
1352
|
+
socket.send(PING_TEXT);
|
|
1353
|
+
} catch {
|
|
1354
|
+
}
|
|
1355
|
+
probeTimer = timers.setTimeout(() => {
|
|
1356
|
+
probeTimer = null;
|
|
1357
|
+
if (generation !== socketGeneration || !socket) return;
|
|
1358
|
+
if (heardCount > heardBefore) return;
|
|
1359
|
+
const silentMs = timers.now() - lastHeardAt;
|
|
1360
|
+
diagnose({ kind: "heartbeat_timeout", silentMs });
|
|
1361
|
+
const dead = socket;
|
|
1362
|
+
onSocketGone(generation, null, new DesktopProtocolError("TIMEOUT", "Connection went silent", { retryable: true }));
|
|
1363
|
+
try {
|
|
1364
|
+
dead.close(CloseCode.NORMAL, "wake probe timeout");
|
|
1365
|
+
} catch {
|
|
1366
|
+
}
|
|
1367
|
+
if (state.status === "reconnecting") connect();
|
|
1368
|
+
}, opts.probeMs ?? 4e3);
|
|
1369
|
+
}
|
|
1333
1370
|
return {
|
|
1334
1371
|
connect,
|
|
1335
1372
|
close,
|
|
@@ -1344,7 +1381,29 @@ function createDesktopClient(options) {
|
|
|
1344
1381
|
request,
|
|
1345
1382
|
stream,
|
|
1346
1383
|
on,
|
|
1347
|
-
reauth
|
|
1384
|
+
reauth,
|
|
1385
|
+
wake
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// src/client/wake.ts
|
|
1390
|
+
function bindDesktopWake(client, options = {}) {
|
|
1391
|
+
const targets = options.targets ?? (typeof window !== "undefined" && typeof document !== "undefined" ? { window, document } : null);
|
|
1392
|
+
if (!targets) return () => void 0;
|
|
1393
|
+
const wakeOptions = options.probeMs === void 0 ? {} : { probeMs: options.probeMs };
|
|
1394
|
+
const onVisibility = () => {
|
|
1395
|
+
if (targets.document.visibilityState === "visible") client.wake(wakeOptions);
|
|
1396
|
+
};
|
|
1397
|
+
const onWake = () => client.wake(wakeOptions);
|
|
1398
|
+
targets.document.addEventListener("visibilitychange", onVisibility);
|
|
1399
|
+
targets.window.addEventListener("pageshow", onWake);
|
|
1400
|
+
targets.window.addEventListener("online", onWake);
|
|
1401
|
+
targets.window.addEventListener("focus", onWake);
|
|
1402
|
+
return () => {
|
|
1403
|
+
targets.document.removeEventListener("visibilitychange", onVisibility);
|
|
1404
|
+
targets.window.removeEventListener("pageshow", onWake);
|
|
1405
|
+
targets.window.removeEventListener("online", onWake);
|
|
1406
|
+
targets.window.removeEventListener("focus", onWake);
|
|
1348
1407
|
};
|
|
1349
1408
|
}
|
|
1350
1409
|
//# sourceMappingURL=client.cjs.map
|