@ai-matrx/desktop-protocol 0.1.0 → 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 +31 -0
- package/README.md +10 -2
- package/dist/client.cjs +105 -5
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +31 -3
- package/dist/client.d.ts +31 -3
- package/dist/client.js +105 -5
- package/dist/client.js.map +1 -1
- package/dist/frame.cjs +12 -0
- package/dist/frame.cjs.map +1 -1
- package/dist/frame.d.cts +11 -1
- package/dist/frame.d.ts +11 -1
- package/dist/frame.js +12 -0
- package/dist/frame.js.map +1 -1
- package/dist/index.cjs +67 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -3
- package/dist/index.d.ts +46 -3
- package/dist/index.js +67 -2
- package/dist/index.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/dist/{registry-DTcTGycI.d.cts → registry-DJbrsZ4u.d.cts} +42 -1
- package/dist/{registry-DTcTGycI.d.ts → registry-DJbrsZ4u.d.ts} +42 -1
- package/package.json +1 -1
package/dist/client.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { BackoffOptions } from '@ai-matrx/realtime';
|
|
2
2
|
export { RECONNECT_ALARM_ATTEMPTS } from '@ai-matrx/realtime';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import { W as Welcome, D as DesktopProtocolError, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, C as ClientType } from './registry-
|
|
5
|
-
export { i as isDesktopProtocolError } from './registry-
|
|
4
|
+
import { W as Welcome, D as DesktopProtocolError, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, C as ClientType } from './registry-DJbrsZ4u.cjs';
|
|
5
|
+
export { i as isDesktopProtocolError } from './registry-DJbrsZ4u.cjs';
|
|
6
6
|
import { FrameHeader } from './frame.cjs';
|
|
7
7
|
|
|
8
8
|
/** The subset of the WHATWG WebSocket the client uses — browser, React Native, `ws` and Workers all fit. */
|
|
@@ -167,6 +167,18 @@ interface DesktopClient {
|
|
|
167
167
|
on<E extends EventName>(name: E, handler: EventHandler<E>): () => void;
|
|
168
168
|
/** Hand the relay a fresh token without dropping the socket. */
|
|
169
169
|
reauth(token: string): void;
|
|
170
|
+
/**
|
|
171
|
+
* The app came back to the foreground or the network changed (`visibilitychange` → visible,
|
|
172
|
+
* `pageshow`, `online`). Reconnecting → dial now instead of waiting out the backoff. Open → probe:
|
|
173
|
+
* a ping must be answered within `probeMs` (default 4000) or the socket is treated as dead and
|
|
174
|
+
* redialed at once. A phone resumed from sleep often holds a socket the OS already killed, and two
|
|
175
|
+
* silent heartbeats would cost 50 s. Resource streams reattach with `since_seq` as usual.
|
|
176
|
+
*/
|
|
177
|
+
wake(options?: WakeOptions): void;
|
|
178
|
+
}
|
|
179
|
+
interface WakeOptions {
|
|
180
|
+
/** How long an open socket has to answer the probe ping. Default 4000. */
|
|
181
|
+
probeMs?: number;
|
|
170
182
|
}
|
|
171
183
|
|
|
172
184
|
/**
|
|
@@ -178,4 +190,20 @@ interface DesktopClient {
|
|
|
178
190
|
|
|
179
191
|
declare function createDesktopClient(options: DesktopClientOptions): DesktopClient;
|
|
180
192
|
|
|
181
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Wire a client's wake() to the browser events that mean "the person is back": the page becomes
|
|
195
|
+
* visible again (iOS Safari suspends a background tab's sockets), a back/forward-cache restore
|
|
196
|
+
* (`pageshow` with persisted), and the network returning (`online`). Framework-free; the React
|
|
197
|
+
* binding is `useDesktopWake` in `./react`.
|
|
198
|
+
*/
|
|
199
|
+
|
|
200
|
+
/** The subset of window/document the binding listens on — injectable for tests and non-DOM hosts. */
|
|
201
|
+
interface WakeTargets {
|
|
202
|
+
window: Pick<Window, "addEventListener" | "removeEventListener">;
|
|
203
|
+
document: Pick<Document, "addEventListener" | "removeEventListener" | "visibilityState">;
|
|
204
|
+
}
|
|
205
|
+
declare function bindDesktopWake(client: Pick<DesktopClient, "wake">, options?: WakeOptions & {
|
|
206
|
+
targets?: WakeTargets;
|
|
207
|
+
}): () => void;
|
|
208
|
+
|
|
209
|
+
export { type ClientTimers, type DesktopClient, type DesktopClientDiagnostic, type DesktopClientOptions, type DesktopClientState, type DesktopClientStatus, DesktopProtocolError, type DesktopStream, type EventHandler, type EventName, type EventPayload, OpName, OpParams, OpResult, type RequestOptions, type StreamHandlers, type StreamOpName, type StreamOptions, type WakeOptions, type WakeTargets, type WebSocketFactory, type WebSocketLike, bindDesktopWake, createDesktopClient };
|
package/dist/client.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { BackoffOptions } from '@ai-matrx/realtime';
|
|
2
2
|
export { RECONNECT_ALARM_ATTEMPTS } from '@ai-matrx/realtime';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import { W as Welcome, D as DesktopProtocolError, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, C as ClientType } from './registry-
|
|
5
|
-
export { i as isDesktopProtocolError } from './registry-
|
|
4
|
+
import { W as Welcome, D as DesktopProtocolError, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, C as ClientType } from './registry-DJbrsZ4u.js';
|
|
5
|
+
export { i as isDesktopProtocolError } from './registry-DJbrsZ4u.js';
|
|
6
6
|
import { FrameHeader } from './frame.js';
|
|
7
7
|
|
|
8
8
|
/** The subset of the WHATWG WebSocket the client uses — browser, React Native, `ws` and Workers all fit. */
|
|
@@ -167,6 +167,18 @@ interface DesktopClient {
|
|
|
167
167
|
on<E extends EventName>(name: E, handler: EventHandler<E>): () => void;
|
|
168
168
|
/** Hand the relay a fresh token without dropping the socket. */
|
|
169
169
|
reauth(token: string): void;
|
|
170
|
+
/**
|
|
171
|
+
* The app came back to the foreground or the network changed (`visibilitychange` → visible,
|
|
172
|
+
* `pageshow`, `online`). Reconnecting → dial now instead of waiting out the backoff. Open → probe:
|
|
173
|
+
* a ping must be answered within `probeMs` (default 4000) or the socket is treated as dead and
|
|
174
|
+
* redialed at once. A phone resumed from sleep often holds a socket the OS already killed, and two
|
|
175
|
+
* silent heartbeats would cost 50 s. Resource streams reattach with `since_seq` as usual.
|
|
176
|
+
*/
|
|
177
|
+
wake(options?: WakeOptions): void;
|
|
178
|
+
}
|
|
179
|
+
interface WakeOptions {
|
|
180
|
+
/** How long an open socket has to answer the probe ping. Default 4000. */
|
|
181
|
+
probeMs?: number;
|
|
170
182
|
}
|
|
171
183
|
|
|
172
184
|
/**
|
|
@@ -178,4 +190,20 @@ interface DesktopClient {
|
|
|
178
190
|
|
|
179
191
|
declare function createDesktopClient(options: DesktopClientOptions): DesktopClient;
|
|
180
192
|
|
|
181
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Wire a client's wake() to the browser events that mean "the person is back": the page becomes
|
|
195
|
+
* visible again (iOS Safari suspends a background tab's sockets), a back/forward-cache restore
|
|
196
|
+
* (`pageshow` with persisted), and the network returning (`online`). Framework-free; the React
|
|
197
|
+
* binding is `useDesktopWake` in `./react`.
|
|
198
|
+
*/
|
|
199
|
+
|
|
200
|
+
/** The subset of window/document the binding listens on — injectable for tests and non-DOM hosts. */
|
|
201
|
+
interface WakeTargets {
|
|
202
|
+
window: Pick<Window, "addEventListener" | "removeEventListener">;
|
|
203
|
+
document: Pick<Document, "addEventListener" | "removeEventListener" | "visibilityState">;
|
|
204
|
+
}
|
|
205
|
+
declare function bindDesktopWake(client: Pick<DesktopClient, "wake">, options?: WakeOptions & {
|
|
206
|
+
targets?: WakeTargets;
|
|
207
|
+
}): () => void;
|
|
208
|
+
|
|
209
|
+
export { type ClientTimers, type DesktopClient, type DesktopClientDiagnostic, type DesktopClientOptions, type DesktopClientState, type DesktopClientStatus, DesktopProtocolError, type DesktopStream, type EventHandler, type EventName, type EventPayload, OpName, OpParams, OpResult, type RequestOptions, type StreamHandlers, type StreamOpName, type StreamOptions, type WakeOptions, type WakeTargets, type WebSocketFactory, type WebSocketLike, bindDesktopWake, createDesktopClient };
|
package/dist/client.js
CHANGED
|
@@ -254,7 +254,15 @@ var CoreToClientMsg = z3.discriminatedUnion("type", [ResponseMsg, ErrorMsg, Stre
|
|
|
254
254
|
var CloseCode = {
|
|
255
255
|
NORMAL: 1e3,
|
|
256
256
|
GOING_AWAY: 1001,
|
|
257
|
-
// relay
|
|
257
|
+
// app quit; relay: the device was silent for 75 s — reconnect
|
|
258
|
+
/** Never sent by anyone: what a socket reports when it drops without a close frame. A relay
|
|
259
|
+
* deploy (every Durable Object restarts) arrives as this — reconnect immediately. */
|
|
260
|
+
ABNORMAL: 1006,
|
|
261
|
+
/** Relay → clients: the device (re)connected, so the core session they spoke to is gone —
|
|
262
|
+
* reconnect, hello, and reattach with since_seq. */
|
|
263
|
+
DEVICE_RECONNECTED: 1012,
|
|
264
|
+
TRY_AGAIN_LATER: 1013,
|
|
265
|
+
// relay: signing keys or the device registry unreachable — retry
|
|
258
266
|
AUTH_INVALID: 4001,
|
|
259
267
|
AUTH_FORBIDDEN: 4003,
|
|
260
268
|
DEVICE_NOT_FOUND: 4004,
|
|
@@ -267,6 +275,7 @@ var CloseCode = {
|
|
|
267
275
|
PROTOCOL_ERROR: 4400,
|
|
268
276
|
UPGRADE_REQUIRED: 4426
|
|
269
277
|
};
|
|
278
|
+
var CloseCodeValue = z3.literal([1e3, 1001, 1006, 1012, 1013, 4001, 4003, 4004, 4008, 4009, 4010, 4400, 4426]);
|
|
270
279
|
|
|
271
280
|
// src/schema/frame.ts
|
|
272
281
|
import { z as z4 } from "zod";
|
|
@@ -278,6 +287,22 @@ var FrameHeader = z4.strictObject({
|
|
|
278
287
|
sid: u32,
|
|
279
288
|
seq: u32
|
|
280
289
|
});
|
|
290
|
+
var RelayHopEnvelopeHeader = z4.strictObject({
|
|
291
|
+
version: z4.literal(FRAME_VERSION),
|
|
292
|
+
flags: z4.literal(0),
|
|
293
|
+
channel: z4.literal(255),
|
|
294
|
+
cid: u32,
|
|
295
|
+
sid: z4.literal(0),
|
|
296
|
+
seq: z4.literal(0)
|
|
297
|
+
});
|
|
298
|
+
var RelayHopConnectionEnd = z4.strictObject({
|
|
299
|
+
version: z4.literal(FRAME_VERSION),
|
|
300
|
+
flags: z4.literal(1),
|
|
301
|
+
channel: z4.literal(255),
|
|
302
|
+
cid: z4.int().min(1).max(4294967295),
|
|
303
|
+
sid: z4.literal(0),
|
|
304
|
+
seq: z4.literal(0)
|
|
305
|
+
});
|
|
281
306
|
var DEFAULT_WINDOW_BYTES = 262144;
|
|
282
307
|
var DEFAULT_MAX_FRAME_PAYLOAD_BYTES = 65536;
|
|
283
308
|
|
|
@@ -587,6 +612,22 @@ var RelayCallResponse = z9.discriminatedUnion("ok", [
|
|
|
587
612
|
z9.strictObject({ ok: z9.literal(true), result: z9.unknown() }),
|
|
588
613
|
z9.strictObject({ ok: z9.literal(false), error: ProtocolErrorBody })
|
|
589
614
|
]);
|
|
615
|
+
var RELAY_CALL_HELLO_ID = "hello";
|
|
616
|
+
var RELAY_CALL_REQUEST_ID = "call";
|
|
617
|
+
var RelayCallHello = z9.strictObject({
|
|
618
|
+
type: z9.literal("hello"),
|
|
619
|
+
id: z9.literal(RELAY_CALL_HELLO_ID),
|
|
620
|
+
protocolVersion: ProtocolVersion,
|
|
621
|
+
clientType: z9.literal("server"),
|
|
622
|
+
clientVersion: z9.string().min(1).max(32)
|
|
623
|
+
});
|
|
624
|
+
var RelayCallEnvelope = z9.strictObject({
|
|
625
|
+
id: z9.literal(RELAY_CALL_REQUEST_ID),
|
|
626
|
+
type: z9.literal("request"),
|
|
627
|
+
cap: z9.string().min(1).max(32),
|
|
628
|
+
op: z9.string().min(1).max(64),
|
|
629
|
+
params: z9.unknown()
|
|
630
|
+
});
|
|
590
631
|
|
|
591
632
|
// src/schema/registry.ts
|
|
592
633
|
var FS_ERR = ["NOT_FOUND", "PERMISSION_DENIED_OS", "INVALID_PARAMS", "RESOURCE_BUSY", "INTERNAL"];
|
|
@@ -691,7 +732,9 @@ function createDesktopClient(options) {
|
|
|
691
732
|
let helloId = null;
|
|
692
733
|
let reconnectTimer = null;
|
|
693
734
|
let heartbeatTimer = null;
|
|
735
|
+
let probeTimer = null;
|
|
694
736
|
let lastHeardAt = 0;
|
|
737
|
+
let heardCount = 0;
|
|
695
738
|
let closedByUser = false;
|
|
696
739
|
const newId = (prefix) => `${prefix}${(++nextId).toString(36)}`;
|
|
697
740
|
function setState(patch) {
|
|
@@ -746,6 +789,7 @@ function createDesktopClient(options) {
|
|
|
746
789
|
ws.onmessage = (event) => {
|
|
747
790
|
if (generation !== socketGeneration) return;
|
|
748
791
|
lastHeardAt = timers.now();
|
|
792
|
+
heardCount++;
|
|
749
793
|
onMessage(event.data);
|
|
750
794
|
};
|
|
751
795
|
ws.onerror = () => {
|
|
@@ -787,6 +831,10 @@ function createDesktopClient(options) {
|
|
|
787
831
|
timers.clearTimeout(heartbeatTimer);
|
|
788
832
|
heartbeatTimer = null;
|
|
789
833
|
}
|
|
834
|
+
if (probeTimer !== null) {
|
|
835
|
+
timers.clearTimeout(probeTimer);
|
|
836
|
+
probeTimer = null;
|
|
837
|
+
}
|
|
790
838
|
const terminal = code === null ? void 0 : TERMINAL_CLOSE.get(code);
|
|
791
839
|
const lost = error ?? (terminal ? new DesktopProtocolError(terminal[0], terminal[1]) : new DesktopProtocolError("DEVICE_OFFLINE", "Connection lost", { retryable: true }));
|
|
792
840
|
for (const [id, p] of pending) {
|
|
@@ -1163,15 +1211,16 @@ function createDesktopClient(options) {
|
|
|
1163
1211
|
});
|
|
1164
1212
|
result.catch(() => void 0);
|
|
1165
1213
|
done.catch(() => void 0);
|
|
1214
|
+
const attach = op === "session.attach" ? params : null;
|
|
1166
1215
|
const s = {
|
|
1167
1216
|
op,
|
|
1168
1217
|
handlers,
|
|
1169
1218
|
options: opts,
|
|
1170
1219
|
id: newId("s"),
|
|
1171
1220
|
sid: null,
|
|
1172
|
-
resourceId: null,
|
|
1173
|
-
lastSeq: 0,
|
|
1174
|
-
expectSeq: 1,
|
|
1221
|
+
resourceId: attach ? attach.resource_id : null,
|
|
1222
|
+
lastSeq: attach?.since_seq ?? 0,
|
|
1223
|
+
expectSeq: attach ? attach.since_seq === void 0 ? null : attach.since_seq + 1 : 1,
|
|
1175
1224
|
windowBytes: opts.windowBytes ?? DEFAULT_WINDOW_BYTES,
|
|
1176
1225
|
consumedUncredited: 0,
|
|
1177
1226
|
sendWindow: DEFAULT_WINDOW_BYTES,
|
|
@@ -1260,6 +1309,34 @@ function createDesktopClient(options) {
|
|
|
1260
1309
|
function reauth(token) {
|
|
1261
1310
|
sendJson({ type: "relay", op: "reauth", token });
|
|
1262
1311
|
}
|
|
1312
|
+
function wake(opts = {}) {
|
|
1313
|
+
if (closedByUser || state.status === "closed") return;
|
|
1314
|
+
if (state.status === "idle" || state.status === "reconnecting") {
|
|
1315
|
+
connect();
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
if (state.status === "connecting" || !socket || probeTimer !== null) return;
|
|
1319
|
+
const generation = socketGeneration;
|
|
1320
|
+
const heardBefore = heardCount;
|
|
1321
|
+
try {
|
|
1322
|
+
socket.send(PING_TEXT);
|
|
1323
|
+
} catch {
|
|
1324
|
+
}
|
|
1325
|
+
probeTimer = timers.setTimeout(() => {
|
|
1326
|
+
probeTimer = null;
|
|
1327
|
+
if (generation !== socketGeneration || !socket) return;
|
|
1328
|
+
if (heardCount > heardBefore) return;
|
|
1329
|
+
const silentMs = timers.now() - lastHeardAt;
|
|
1330
|
+
diagnose({ kind: "heartbeat_timeout", silentMs });
|
|
1331
|
+
const dead = socket;
|
|
1332
|
+
onSocketGone(generation, null, new DesktopProtocolError("TIMEOUT", "Connection went silent", { retryable: true }));
|
|
1333
|
+
try {
|
|
1334
|
+
dead.close(CloseCode.NORMAL, "wake probe timeout");
|
|
1335
|
+
} catch {
|
|
1336
|
+
}
|
|
1337
|
+
if (state.status === "reconnecting") connect();
|
|
1338
|
+
}, opts.probeMs ?? 4e3);
|
|
1339
|
+
}
|
|
1263
1340
|
return {
|
|
1264
1341
|
connect,
|
|
1265
1342
|
close,
|
|
@@ -1274,12 +1351,35 @@ function createDesktopClient(options) {
|
|
|
1274
1351
|
request,
|
|
1275
1352
|
stream,
|
|
1276
1353
|
on,
|
|
1277
|
-
reauth
|
|
1354
|
+
reauth,
|
|
1355
|
+
wake
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// src/client/wake.ts
|
|
1360
|
+
function bindDesktopWake(client, options = {}) {
|
|
1361
|
+
const targets = options.targets ?? (typeof window !== "undefined" && typeof document !== "undefined" ? { window, document } : null);
|
|
1362
|
+
if (!targets) return () => void 0;
|
|
1363
|
+
const wakeOptions = options.probeMs === void 0 ? {} : { probeMs: options.probeMs };
|
|
1364
|
+
const onVisibility = () => {
|
|
1365
|
+
if (targets.document.visibilityState === "visible") client.wake(wakeOptions);
|
|
1366
|
+
};
|
|
1367
|
+
const onWake = () => client.wake(wakeOptions);
|
|
1368
|
+
targets.document.addEventListener("visibilitychange", onVisibility);
|
|
1369
|
+
targets.window.addEventListener("pageshow", onWake);
|
|
1370
|
+
targets.window.addEventListener("online", onWake);
|
|
1371
|
+
targets.window.addEventListener("focus", onWake);
|
|
1372
|
+
return () => {
|
|
1373
|
+
targets.document.removeEventListener("visibilitychange", onVisibility);
|
|
1374
|
+
targets.window.removeEventListener("pageshow", onWake);
|
|
1375
|
+
targets.window.removeEventListener("online", onWake);
|
|
1376
|
+
targets.window.removeEventListener("focus", onWake);
|
|
1278
1377
|
};
|
|
1279
1378
|
}
|
|
1280
1379
|
export {
|
|
1281
1380
|
DesktopProtocolError,
|
|
1282
1381
|
RECONNECT_ALARM_ATTEMPTS,
|
|
1382
|
+
bindDesktopWake,
|
|
1283
1383
|
createDesktopClient,
|
|
1284
1384
|
isDesktopProtocolError
|
|
1285
1385
|
};
|