@ai-matrx/desktop-protocol 0.5.0 → 0.7.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
@@ -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-BeY9D7h0.cjs';
5
- export { i as isDesktopProtocolError } from './registry-BeY9D7h0.cjs';
4
+ import { W as Welcome, D as DesktopProtocolError, C as CloseOutcome, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, e as ClientType } from './registry-Duc_VFN8.cjs';
5
+ export { i as isDesktopProtocolError } from './registry-Duc_VFN8.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. */
@@ -49,6 +49,8 @@ interface DesktopClientState {
49
49
  retryInMs: number | null;
50
50
  lastError: DesktopProtocolError | null;
51
51
  lastCloseCode: number | null;
52
+ /** Why dialing stopped, when the far side refused us for good (CLOSE_RULES); null otherwise. */
53
+ outcome: CloseOutcome | null;
52
54
  }
53
55
  type DesktopClientDiagnostic = {
54
56
  kind: "malformed_message";
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-BeY9D7h0.js';
5
- export { i as isDesktopProtocolError } from './registry-BeY9D7h0.js';
4
+ import { W as Welcome, D as DesktopProtocolError, C as CloseOutcome, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, e as ClientType } from './registry-Duc_VFN8.js';
5
+ export { i as isDesktopProtocolError } from './registry-Duc_VFN8.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. */
@@ -49,6 +49,8 @@ interface DesktopClientState {
49
49
  retryInMs: number | null;
50
50
  lastError: DesktopProtocolError | null;
51
51
  lastCloseCode: number | null;
52
+ /** Why dialing stopped, when the far side refused us for good (CLOSE_RULES); null otherwise. */
53
+ outcome: CloseOutcome | null;
52
54
  }
53
55
  type DesktopClientDiagnostic = {
54
56
  kind: "malformed_message";
package/dist/client.js CHANGED
@@ -281,6 +281,29 @@ var CloseCode = {
281
281
  };
282
282
  var CloseCodeValue = z3.literal([1e3, 1001, 1006, 1012, 1013, 4001, 4003, 4004, 4008, 4009, 4010, 4400, 4426]);
283
283
 
284
+ // src/close-policy.ts
285
+ var CLOSE_RULES = {
286
+ [CloseCode.NORMAL]: { action: "reconnect", error: ["DEVICE_OFFLINE", "Connection closed"] },
287
+ [CloseCode.GOING_AWAY]: { action: "reconnect_now", error: ["DEVICE_OFFLINE", "Connection lost"] },
288
+ [CloseCode.ABNORMAL]: { action: "reconnect_now", error: ["DEVICE_OFFLINE", "Connection lost"] },
289
+ [CloseCode.DEVICE_RECONNECTED]: { action: "reconnect_now", error: ["DEVICE_OFFLINE", "Device reconnected"] },
290
+ [CloseCode.TRY_AGAIN_LATER]: { action: "reconnect", error: ["DEVICE_OFFLINE", "Relay busy; retrying"] },
291
+ [CloseCode.AUTH_INVALID]: { action: "reauth", outcome: "signed_out", error: ["AUTH_INVALID", "Sign-in was not accepted"] },
292
+ [CloseCode.AUTH_FORBIDDEN]: { action: "stop", outcome: "not_owned", error: ["AUTH_FORBIDDEN", "Not this device's owner"] },
293
+ [CloseCode.DEVICE_NOT_FOUND]: { action: "stop", outcome: "removed", error: ["NOT_FOUND", "Device not found"] },
294
+ [CloseCode.AUTH_EXPIRED]: { action: "reauth", outcome: "signed_out", error: ["AUTH_EXPIRED", "Sign-in expired"] },
295
+ [CloseCode.REPLACED]: { action: "stop", outcome: "replaced", error: ["CONFLICT", "Replaced by a newer connection"] },
296
+ [CloseCode.DEVICE_REVOKED]: { action: "stop", outcome: "revoked", error: ["AUTH_DEVICE_REVOKED", "This device was revoked"] },
297
+ // A malformed message, or a connection the relay lost track of: a fresh socket starts clean.
298
+ [CloseCode.PROTOCOL_ERROR]: { action: "reconnect", error: ["PROTOCOL_ERROR", "Connection reset by the relay"] },
299
+ [CloseCode.UPGRADE_REQUIRED]: { action: "stop", outcome: "upgrade_required", error: ["PROTOCOL_VERSION_UNSUPPORTED", "Protocol upgrade required"] }
300
+ };
301
+ var UNKNOWN = { action: "reconnect", error: ["DEVICE_OFFLINE", "Connection lost"] };
302
+ function closeRule(code) {
303
+ if (code === null || code === void 0) return CLOSE_RULES[CloseCode.ABNORMAL];
304
+ return CLOSE_RULES[code] ?? UNKNOWN;
305
+ }
306
+
284
307
  // src/schema/frame.ts
285
308
  import { z as z4 } from "zod";
286
309
  var FrameHeader = z4.strictObject({
@@ -616,7 +639,11 @@ var SessionControlChangedEvent = z8.strictObject({ controller_client_id: z8.stri
616
639
  import { z as z9 } from "zod";
617
640
  var RelayDeviceStatusEvent = z9.strictObject({
618
641
  online: z9.boolean(),
619
- since_ms: epochMs,
642
+ /**
643
+ * When the device last went online/offline; null = it has never connected (since 1.1 —
644
+ * NULL_SINCE: a 1.0 reader is sent 0, its old meaning).
645
+ */
646
+ since_ms: epochMs.nullable(),
620
647
  app_version: z9.string().max(32).nullable(),
621
648
  protocol_version: ProtocolVersion.nullable()
622
649
  });
@@ -698,13 +725,6 @@ var EVENTS = {
698
725
 
699
726
  // src/client/client.ts
700
727
  var WS_OPEN = 1;
701
- var TERMINAL_CLOSE = /* @__PURE__ */ new Map([
702
- [CloseCode.DEVICE_REVOKED, ["AUTH_DEVICE_REVOKED", "This device was revoked"]],
703
- [CloseCode.AUTH_FORBIDDEN, ["AUTH_FORBIDDEN", "Not this device's owner"]],
704
- [CloseCode.DEVICE_NOT_FOUND, ["NOT_FOUND", "Device not found"]],
705
- [CloseCode.REPLACED, ["CONFLICT", "Replaced by a newer connection"]],
706
- [CloseCode.UPGRADE_REQUIRED, ["PROTOCOL_VERSION_UNSUPPORTED", "Protocol upgrade required"]]
707
- ]);
708
728
  var defaultTimers = {
709
729
  setTimeout: (fn, ms) => globalThis.setTimeout(fn, ms),
710
730
  clearTimeout: (handle) => globalThis.clearTimeout(handle),
@@ -747,8 +767,10 @@ function createDesktopClient(options) {
747
767
  attempts: 0,
748
768
  retryInMs: null,
749
769
  lastError: null,
750
- lastCloseCode: null
770
+ lastCloseCode: null,
771
+ outcome: null
751
772
  };
773
+ let authRefusals = 0;
752
774
  const listeners = /* @__PURE__ */ new Set();
753
775
  const readyWaiters = /* @__PURE__ */ new Set();
754
776
  const pending = /* @__PURE__ */ new Map();
@@ -783,6 +805,7 @@ function createDesktopClient(options) {
783
805
  }
784
806
  function connect() {
785
807
  if (state.status === "connecting" || state.status === "handshaking" || state.status === "open") return;
808
+ if (state.status === "closed" || state.status === "idle") authRefusals = 0;
786
809
  closedByUser = false;
787
810
  if (reconnectTimer !== null) {
788
811
  timers.clearTimeout(reconnectTimer);
@@ -864,8 +887,11 @@ function createDesktopClient(options) {
864
887
  timers.clearTimeout(probeTimer);
865
888
  probeTimer = null;
866
889
  }
867
- const terminal = code === null ? void 0 : TERMINAL_CLOSE.get(code);
868
- const lost = error ?? (terminal ? new DesktopProtocolError(terminal[0], terminal[1]) : new DesktopProtocolError("DEVICE_OFFLINE", "Connection lost", { retryable: true }));
890
+ const rule = code === null ? null : closeRule(code);
891
+ if (rule?.action === "reauth") authRefusals++;
892
+ const stopped = rule !== null && (rule.action === "stop" || rule.action === "reauth" && authRefusals >= 2);
893
+ const terminal = stopped && rule ? rule : void 0;
894
+ const lost = error ?? (terminal ? new DesktopProtocolError(terminal.error[0], terminal.error[1]) : rule && rule.action === "reauth" ? new DesktopProtocolError(rule.error[0], rule.error[1], { retryable: true }) : new DesktopProtocolError("DEVICE_OFFLINE", "Connection lost", { retryable: true }));
869
895
  for (const [id, p] of pending) {
870
896
  pending.delete(id);
871
897
  p.cleanup();
@@ -882,7 +908,14 @@ function createDesktopClient(options) {
882
908
  endStream(s, null, lost);
883
909
  }
884
910
  if (closedByUser || terminal) {
885
- setState({ status: "closed", welcome: null, lastError: closedByUser ? state.lastError : lost, lastCloseCode: code, retryInMs: null });
911
+ setState({
912
+ status: "closed",
913
+ welcome: null,
914
+ lastError: closedByUser ? state.lastError : lost,
915
+ lastCloseCode: code,
916
+ retryInMs: null,
917
+ outcome: closedByUser ? null : terminal?.outcome ?? null
918
+ });
886
919
  rejectReadyWaiters(lost);
887
920
  return;
888
921
  }
@@ -1036,7 +1069,8 @@ function createDesktopClient(options) {
1036
1069
  }
1037
1070
  helloId = null;
1038
1071
  backoff.markConnected();
1039
- setState({ status: "open", welcome: welcome.data, attempts: 0, retryInMs: null, lastError: null });
1072
+ authRefusals = 0;
1073
+ setState({ status: "open", welcome: welcome.data, attempts: 0, retryInMs: null, lastError: null, outcome: null });
1040
1074
  for (const w of [...readyWaiters]) {
1041
1075
  readyWaiters.delete(w);
1042
1076
  w.resolve(welcome.data);