@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/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
- 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 WebSocketFactory, type WebSocketLike, createDesktopClient };
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
- 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 WebSocketFactory, type WebSocketLike, createDesktopClient };
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
  };