@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/dist/client.d.cts
CHANGED
|
@@ -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
|
@@ -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
|
@@ -732,7 +732,9 @@ function createDesktopClient(options) {
|
|
|
732
732
|
let helloId = null;
|
|
733
733
|
let reconnectTimer = null;
|
|
734
734
|
let heartbeatTimer = null;
|
|
735
|
+
let probeTimer = null;
|
|
735
736
|
let lastHeardAt = 0;
|
|
737
|
+
let heardCount = 0;
|
|
736
738
|
let closedByUser = false;
|
|
737
739
|
const newId = (prefix) => `${prefix}${(++nextId).toString(36)}`;
|
|
738
740
|
function setState(patch) {
|
|
@@ -787,6 +789,7 @@ function createDesktopClient(options) {
|
|
|
787
789
|
ws.onmessage = (event) => {
|
|
788
790
|
if (generation !== socketGeneration) return;
|
|
789
791
|
lastHeardAt = timers.now();
|
|
792
|
+
heardCount++;
|
|
790
793
|
onMessage(event.data);
|
|
791
794
|
};
|
|
792
795
|
ws.onerror = () => {
|
|
@@ -828,6 +831,10 @@ function createDesktopClient(options) {
|
|
|
828
831
|
timers.clearTimeout(heartbeatTimer);
|
|
829
832
|
heartbeatTimer = null;
|
|
830
833
|
}
|
|
834
|
+
if (probeTimer !== null) {
|
|
835
|
+
timers.clearTimeout(probeTimer);
|
|
836
|
+
probeTimer = null;
|
|
837
|
+
}
|
|
831
838
|
const terminal = code === null ? void 0 : TERMINAL_CLOSE.get(code);
|
|
832
839
|
const lost = error ?? (terminal ? new DesktopProtocolError(terminal[0], terminal[1]) : new DesktopProtocolError("DEVICE_OFFLINE", "Connection lost", { retryable: true }));
|
|
833
840
|
for (const [id, p] of pending) {
|
|
@@ -1204,15 +1211,16 @@ function createDesktopClient(options) {
|
|
|
1204
1211
|
});
|
|
1205
1212
|
result.catch(() => void 0);
|
|
1206
1213
|
done.catch(() => void 0);
|
|
1214
|
+
const attach = op === "session.attach" ? params : null;
|
|
1207
1215
|
const s = {
|
|
1208
1216
|
op,
|
|
1209
1217
|
handlers,
|
|
1210
1218
|
options: opts,
|
|
1211
1219
|
id: newId("s"),
|
|
1212
1220
|
sid: null,
|
|
1213
|
-
resourceId: null,
|
|
1214
|
-
lastSeq: 0,
|
|
1215
|
-
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,
|
|
1216
1224
|
windowBytes: opts.windowBytes ?? DEFAULT_WINDOW_BYTES,
|
|
1217
1225
|
consumedUncredited: 0,
|
|
1218
1226
|
sendWindow: DEFAULT_WINDOW_BYTES,
|
|
@@ -1301,6 +1309,34 @@ function createDesktopClient(options) {
|
|
|
1301
1309
|
function reauth(token) {
|
|
1302
1310
|
sendJson({ type: "relay", op: "reauth", token });
|
|
1303
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
|
+
}
|
|
1304
1340
|
return {
|
|
1305
1341
|
connect,
|
|
1306
1342
|
close,
|
|
@@ -1315,12 +1351,35 @@ function createDesktopClient(options) {
|
|
|
1315
1351
|
request,
|
|
1316
1352
|
stream,
|
|
1317
1353
|
on,
|
|
1318
|
-
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);
|
|
1319
1377
|
};
|
|
1320
1378
|
}
|
|
1321
1379
|
export {
|
|
1322
1380
|
DesktopProtocolError,
|
|
1323
1381
|
RECONNECT_ALARM_ATTEMPTS,
|
|
1382
|
+
bindDesktopWake,
|
|
1324
1383
|
createDesktopClient,
|
|
1325
1384
|
isDesktopProtocolError
|
|
1326
1385
|
};
|