devframe 0.8.2 → 0.9.0-beta.10

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.
Files changed (127) hide show
  1. package/dist/_shared-BPmX9fqK.d.mts +41 -0
  2. package/dist/_shared-CbZ_yhbz.mjs +68 -0
  3. package/dist/adapters/build.d.mts +10 -12
  4. package/dist/adapters/build.mjs +26 -29
  5. package/dist/adapters/cac.d.mts +1 -1
  6. package/dist/adapters/cac.mjs +156 -1
  7. package/dist/adapters/dev.d.mts +38 -46
  8. package/dist/adapters/dev.mjs +2 -1
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/initiate.d.mts +224 -0
  11. package/dist/adapters/initiate.mjs +2 -0
  12. package/dist/adapters/mcp.d.mts +24 -7
  13. package/dist/adapters/mcp.mjs +2 -2
  14. package/dist/{agent-tool-name-C3b5vEwJ.mjs → agent-tool-name-EgfoFO8C.mjs} +0 -3
  15. package/dist/cli/main.d.mts +0 -2
  16. package/dist/cli/main.mjs +4 -8
  17. package/dist/client/index.d.mts +71 -7
  18. package/dist/client/index.mjs +720 -1540
  19. package/dist/constants.d.mts +28 -7
  20. package/dist/constants.mjs +28 -7
  21. package/dist/{context-DoSozfvx.d.mts → context-B3YUu_95.d.mts} +19 -1
  22. package/dist/{host-h3-D2hYZvDK.mjs → context-CEe4PBz1.mjs} +25 -557
  23. package/dist/{context-CBg8iRbQ.mjs → context-xdynGSLP.mjs} +10 -1
  24. package/dist/dev--rAPzbvO.mjs +239 -0
  25. package/dist/{devframe-mQ4qZAuo.d.mts → devframe-DJMg0WLe.d.mts} +181 -134
  26. package/dist/{diagnostics-hwjXp_UV.mjs → diagnostics-CD8nlgll.mjs} +1 -3
  27. package/dist/{diagnostics-D7wM2cwM.mjs → diagnostics-DOQPnQmX.mjs} +51 -5
  28. package/dist/dist-eNI9qM_I.mjs +228 -0
  29. package/dist/host-agent-xLCSe65D.mjs +264 -0
  30. package/dist/host-h3-Crkzm42q.mjs +26 -0
  31. package/dist/{fetch-BKaD0Vb3.mjs → http-DYpK0cUi.mjs} +42 -11
  32. package/dist/{index-b64Uuhy2.d.mts → index-CB-fSUyn.d.mts} +2 -2
  33. package/dist/{index-Dbw1p5ch.d.mts → index-tdrk1bzV.d.mts} +3 -32
  34. package/dist/index.d.mts +12 -5
  35. package/dist/index.mjs +8 -1
  36. package/dist/{instance-registry-BnnA2Eoh.mjs → instance-registry-WvQkt42E.mjs} +9 -8
  37. package/dist/instance-shell-CMFyszWJ.mjs +486 -0
  38. package/dist/instance-shell-CljiKq1c.d.mts +255 -0
  39. package/dist/internal/index.d.mts +397 -0
  40. package/dist/internal/index.mjs +9 -0
  41. package/dist/node/auth.d.mts +2 -2
  42. package/dist/node/auth.mjs +1 -1
  43. package/dist/node/hub-internals.d.mts +2 -14
  44. package/dist/node/hub-internals.mjs +2 -2
  45. package/dist/node/index.d.mts +4 -268
  46. package/dist/node/index.mjs +3 -5
  47. package/dist/nostics-CzECRXpE.mjs +59 -0
  48. package/dist/nostics-SPGDgHEP.d.mts +33 -0
  49. package/dist/recipes/common-rpc-functions.d.mts +4 -4
  50. package/dist/recipes/interactive-auth.d.mts +3 -3
  51. package/dist/recipes/interactive-auth.mjs +8 -8
  52. package/dist/remote-assets-Bg4gCUZ_.d.mts +123 -0
  53. package/dist/remote-assets-nTTxY2MU.mjs +636 -0
  54. package/dist/rolldown-runtime-JspESFgx.mjs +35 -0
  55. package/dist/rpc/dump.d.mts +77 -1
  56. package/dist/rpc/dump.mjs +1 -1
  57. package/dist/rpc/index.d.mts +3 -3
  58. package/dist/rpc/index.mjs +4 -18
  59. package/dist/rpc/transports/sse-client.d.mts +2 -0
  60. package/dist/rpc/transports/sse-client.mjs +142 -0
  61. package/dist/rpc/transports/sse-server.d.mts +61 -0
  62. package/dist/rpc/transports/sse-server.mjs +209 -0
  63. package/dist/rpc/transports/ws-bun.d.mts +35 -0
  64. package/dist/rpc/transports/ws-bun.mjs +31 -0
  65. package/dist/rpc/transports/ws-client.d.mts +1 -1
  66. package/dist/rpc/transports/ws-client.mjs +10 -18
  67. package/dist/rpc/transports/ws-server.d.mts +2 -2
  68. package/dist/rpc/transports/ws-server.mjs +2 -235
  69. package/dist/rpc-core-Cc4lucI0.d.mts +48 -0
  70. package/dist/rpc-core-DMTe-hLo.mjs +81 -0
  71. package/dist/{serialization-C8Mnw9hK.mjs → serialization-BGzEwAdr.mjs} +2 -2
  72. package/dist/{shared-state-6kHolYZy.mjs → shared-state-fRRbRUtD.mjs} +1 -1
  73. package/dist/sse-client-CFBOIaUi.d.mts +37 -0
  74. package/dist/state-Cc1IeSmQ.mjs +1291 -0
  75. package/dist/{dump-CgZShDRB.mjs → static-BPZ1_OPA.mjs} +3 -266
  76. package/dist/storage-CYye7P7L.mjs +137 -0
  77. package/dist/types/index.d.mts +6 -4
  78. package/dist/types/index.mjs +1 -6
  79. package/dist/{types-CnJSgRVa.d.mts → types-Djq7CBXh.d.mts} +0 -7
  80. package/dist/utils/agent-tool-name.d.mts +0 -3
  81. package/dist/utils/agent-tool-name.mjs +0 -3
  82. package/dist/utils/events.d.mts +1 -1
  83. package/dist/utils/get-port.d.mts +17 -0
  84. package/dist/utils/get-port.mjs +2 -0
  85. package/dist/utils/launch-editor.mjs +1 -25
  86. package/dist/utils/nostics.d.mts +2 -0
  87. package/dist/utils/nostics.mjs +2 -0
  88. package/dist/utils/open.d.mts +2 -10
  89. package/dist/utils/open.mjs +56 -1
  90. package/dist/utils/remote-assets.d.mts +15 -0
  91. package/dist/utils/remote-assets.mjs +2 -0
  92. package/dist/utils/serve-static.d.mts +13 -5
  93. package/dist/utils/serve-static.mjs +119 -9
  94. package/dist/utils/shared-state.d.mts +1 -1
  95. package/dist/utils/shared-state.mjs +1 -1
  96. package/dist/utils/simple-schema.d.mts +101 -1
  97. package/dist/utils/streaming-channel.d.mts +1 -1
  98. package/dist/validation-CpXFB6Dz.mjs +266 -0
  99. package/dist/wire-codec-0K-o5MYW.mjs +56 -0
  100. package/dist/{ws-client-B6Tnr0ic.d.mts → ws-client-DhjNyQo3.d.mts} +7 -2
  101. package/dist/ws-server-BdSLrhxE.mjs +267 -0
  102. package/dist/{ws-server-uKQcClcJ.d.mts → ws-server-D1d3QM9f.d.mts} +90 -9
  103. package/package.json +13 -12
  104. package/skills/devframe/SKILL.md +91 -93
  105. package/dist/_shared-bWRzeSa0.mjs +0 -19
  106. package/dist/adapters/cli.d.mts +0 -11
  107. package/dist/adapters/cli.mjs +0 -6
  108. package/dist/cac-D4_9ok0z.mjs +0 -157
  109. package/dist/dev-Bc49u3qg.mjs +0 -439
  110. package/dist/diagnostics-reporter-CsIG85Q5.mjs +0 -38
  111. package/dist/helpers/vite.d.mts +0 -100
  112. package/dist/helpers/vite.mjs +0 -105
  113. package/dist/http-C4T68YV6.mjs +0 -38
  114. package/dist/index-vAX6bYuc.d.mts +0 -78
  115. package/dist/open-Deb5xmIT.mjs +0 -547
  116. package/dist/recipes/open-helpers.d.mts +0 -6
  117. package/dist/recipes/open-helpers.mjs +0 -6
  118. package/dist/server-CD8XmBXl.mjs +0 -167
  119. package/dist/server-UHWae43Q.d.mts +0 -149
  120. package/dist/simple-schema-BDzLeJDk.d.mts +0 -102
  121. package/dist/state-BeUHDDjk.mjs +0 -108
  122. package/dist/storage-CTNVbuFB.mjs +0 -1122
  123. package/dist/utils/promise.d.mts +0 -8
  124. package/dist/utils/promise.mjs +0 -15
  125. package/dist/utils/scope.d.mts +0 -11
  126. package/dist/utils/scope.mjs +0 -15
  127. package/skills/devframe/templates/spa-devframe.ts +0 -38
@@ -1,11 +1,9 @@
1
1
  import { t as hash } from "../hash-KtDZYXDN.mjs";
2
2
  import { colors } from "../utils/colors.mjs";
3
3
  import { createEventEmitter } from "../utils/events.mjs";
4
- import { t as createSharedState } from "../shared-state-6kHolYZy.mjs";
4
+ import { t as createSharedState } from "../shared-state-fRRbRUtD.mjs";
5
5
  import { i as structuredCloneStringify, n as structuredCloneParse, t as structuredCloneDeserialize } from "../structured-clone-CgtQ61SA.mjs";
6
6
  import { createStreamReader, createStreamSink } from "../utils/streaming-channel.mjs";
7
- import { promiseWithResolver } from "../utils/promise.mjs";
8
- import { isQualifiedName, qualifyName } from "../utils/scope.mjs";
9
7
  import { defineDiagnostics } from "nostics";
10
8
  import { ansiFormatter } from "nostics/formatters/ansi";
11
9
  import { withBase, withProtocol } from "ufo";
@@ -18,6 +16,13 @@ const DEVFRAME_CONNECTION_META_FILENAME = "__connection.json";
18
16
  * connection from another JavaScript realm.
19
17
  */
20
18
  const DEVFRAME_CONNECTION_KEY = "__DEVFRAME_CONNECTION__";
19
+ /**
20
+ * Request header carrying the SSE session id on every RPC `POST`. The
21
+ * server mints the id as the stream's first event (`event: session`);
22
+ * the client echoes it here so the POST joins that stream's birpc
23
+ * channel. Mirrors birpc's own SSE helpers for wire compatibility.
24
+ */
25
+ const DEVFRAME_SSE_SESSION_HEADER = "x-birpc-session";
21
26
  const DEVFRAME_RPC_DUMP_MANIFEST_FILENAME = "__rpc-dump/index.json";
22
27
  /**
23
28
  * Page-URL **fragment** parameter carrying a one-time authentication code (OTP)
@@ -43,16 +48,74 @@ const DEVFRAME_VIEWER_ORIGIN_QUERY_PARAM = "devframe_viewer_origin";
43
48
  /** Token that authorizes an external viewer origin registration. */
44
49
  const DEVFRAME_VIEWER_ORIGIN_TOKEN_QUERY_PARAM = "devframe_viewer_origin_token";
45
50
  //#endregion
46
- //#region src/utils/diagnostics-reporter.ts
51
+ //#region src/rpc/cache.ts
52
+ /**
53
+ * @experimental API is expected to change.
54
+ */
55
+ var RpcCacheManager = class {
56
+ cacheMap = /* @__PURE__ */ new Map();
57
+ options;
58
+ keySerializer;
59
+ constructor(options) {
60
+ this.options = options;
61
+ this.keySerializer = options.keySerializer || ((args) => hash(args));
62
+ }
63
+ updateOptions(options) {
64
+ this.options = {
65
+ ...this.options,
66
+ ...options
67
+ };
68
+ }
69
+ cached(m, a) {
70
+ const methodCache = this.cacheMap.get(m);
71
+ if (methodCache) return methodCache.get(this.keySerializer(a));
72
+ }
73
+ has(m, a) {
74
+ return this.cacheMap.get(m)?.has(this.keySerializer(a)) ?? false;
75
+ }
76
+ apply(req, res) {
77
+ const methodCache = this.cacheMap.get(req.m) || /* @__PURE__ */ new Map();
78
+ methodCache.set(this.keySerializer(req.a), res);
79
+ this.cacheMap.set(req.m, methodCache);
80
+ }
81
+ validate(m) {
82
+ return this.options.functions.includes(m);
83
+ }
84
+ clear(fn) {
85
+ if (fn) this.cacheMap.delete(fn);
86
+ else this.cacheMap.clear();
87
+ }
88
+ };
89
+ //#endregion
90
+ //#region src/utils/nostics.ts
47
91
  const formatAnsi = ansiFormatter(colors);
92
+ /**
93
+ * The reporter every {@link defineDiagnostics} call below wires in ahead of
94
+ * any caller-supplied ones: prints the diagnostic through devframe's own
95
+ * ANSI colors via `console[method]` (default `'warn'`).
96
+ */
48
97
  function devframeReporter(d, { method = "warn" } = {}) {
49
98
  console[method](formatAnsi(d));
50
99
  }
100
+ /**
101
+ * Drop-in replacement for `nostics`'s `defineDiagnostics()` with devframe's
102
+ * ANSI console reporter pre-wired ahead of any `reporters` passed in. Every
103
+ * `diagnostics.ts` in devframe core, `@devframes/hub`, `@devframes/json-render`,
104
+ * and the built-in plugins defines its codes through this instead of
105
+ * `nostics`'s own `defineDiagnostics` — the reporter registration lives
106
+ * here, once, so none of them need to build their own reporter (`colors`,
107
+ * `ansiFormatter`) or take a direct dependency on `nostics` themselves.
108
+ */
109
+ function defineDiagnostics$1(options) {
110
+ return defineDiagnostics({
111
+ ...options,
112
+ reporters: [devframeReporter, ...options.reporters ?? []]
113
+ });
114
+ }
51
115
  //#endregion
52
116
  //#region src/rpc/diagnostics.ts
53
- const diagnostics = defineDiagnostics({
117
+ const diagnostics = defineDiagnostics$1({
54
118
  docsBase: "https://devfra.me/errors",
55
- reporters: [devframeReporter],
56
119
  codes: {
57
120
  DF0019: {
58
121
  why: (p) => `RPC function "${p.name}" has \`agent\` set but \`jsonSerializable\` is not \`true\` — MCP requires JSON-serializable data.`,
@@ -87,26 +150,6 @@ const diagnostics = defineDiagnostics({
87
150
  }
88
151
  });
89
152
  //#endregion
90
- //#region src/rpc/dump/error.ts
91
- /**
92
- * Inverse of {@link serializeDumpError}: rebuild a thrown `Error` from
93
- * the plain object stored in a dump record. Preserves `cause`, restores
94
- * the original `name`, and re-attaches any custom own properties.
95
- */
96
- function reviveDumpError(stored) {
97
- const cause = stored.cause instanceof Error ? stored.cause : isPlainErrorShape(stored.cause) ? reviveDumpError(stored.cause) : stored.cause;
98
- const error = cause !== void 0 ? new Error(stored.message, { cause }) : new Error(stored.message);
99
- error.name = stored.name;
100
- for (const key of Object.keys(stored)) {
101
- if (key === "name" || key === "message" || key === "cause") continue;
102
- error[key] = stored[key];
103
- }
104
- return error;
105
- }
106
- function isPlainErrorShape(value) {
107
- return typeof value === "object" && value !== null && typeof value.message === "string" && typeof value.name === "string";
108
- }
109
- //#endregion
110
153
  //#region src/rpc/validate-io.ts
111
154
  /**
112
155
  * Run a single [Standard Schema](https://standardschema.dev) validator,
@@ -215,45 +258,6 @@ async function getRpcHandler(definition, context) {
215
258
  return validating;
216
259
  }
217
260
  //#endregion
218
- //#region src/rpc/cache.ts
219
- /**
220
- * @experimental API is expected to change.
221
- */
222
- var RpcCacheManager = class {
223
- cacheMap = /* @__PURE__ */ new Map();
224
- options;
225
- keySerializer;
226
- constructor(options) {
227
- this.options = options;
228
- this.keySerializer = options.keySerializer || ((args) => hash(args));
229
- }
230
- updateOptions(options) {
231
- this.options = {
232
- ...this.options,
233
- ...options
234
- };
235
- }
236
- cached(m, a) {
237
- const methodCache = this.cacheMap.get(m);
238
- if (methodCache) return methodCache.get(this.keySerializer(a));
239
- }
240
- has(m, a) {
241
- return this.cacheMap.get(m)?.has(this.keySerializer(a)) ?? false;
242
- }
243
- apply(req, res) {
244
- const methodCache = this.cacheMap.get(req.m) || /* @__PURE__ */ new Map();
245
- methodCache.set(this.keySerializer(req.a), res);
246
- this.cacheMap.set(req.m, methodCache);
247
- }
248
- validate(m) {
249
- return this.options.functions.includes(m);
250
- }
251
- clear(fn) {
252
- if (fn) this.cacheMap.delete(fn);
253
- else this.cacheMap.clear();
254
- }
255
- };
256
- //#endregion
257
261
  //#region src/rpc/collector.ts
258
262
  var RpcFunctionsCollectorBase = class {
259
263
  context;
@@ -706,112 +710,590 @@ function createRpcSharedStateClientHost(rpc) {
706
710
  };
707
711
  }
708
712
  //#endregion
709
- //#region src/client/static-rpc.ts
710
- function isStaticEntry(value) {
711
- return typeof value === "object" && value !== null && value.type === "static" && typeof value.path === "string";
712
- }
713
- function isQueryEntry(value) {
714
- return typeof value === "object" && value !== null && value.type === "query" && typeof value.records === "object" && value.records !== null;
715
- }
716
- function isRecord(value) {
717
- return typeof value === "object" && value !== null && ("output" in value || "error" in value);
718
- }
719
- function resolveRecordOutput(record) {
720
- if (record.error) throw reviveDumpError(record.error);
721
- return record.output;
722
- }
723
- function hasMeaningfulArgs(args) {
724
- return args.some((arg) => arg !== null && arg !== void 0);
725
- }
726
- function unwrapEnvelope(raw) {
727
- if (raw !== null && typeof raw === "object" && "serialization" in raw && "data" in raw) return raw.data;
728
- return raw;
713
+ //#region src/rpc/wire-codec.ts
714
+ const EMPTY_WIRE_DEFS = /* @__PURE__ */ new Map();
715
+ /**
716
+ * Build the per-connection wire codec every live transport (WS server, WS
717
+ * client, SSE server, SSE client) shares: per-method dispatch between strict
718
+ * JSON (methods declared `jsonSerializable: true`) and `s:`-prefixed
719
+ * structured-clone (everything else, including all error envelopes), with a
720
+ * request-id → method map so a response independently picks the same
721
+ * encoder as its request. One codec per connection — request-id spaces
722
+ * don't collide across connections.
723
+ *
724
+ * @internal
725
+ */
726
+ function createRpcWireCodec(definitions = EMPTY_WIRE_DEFS) {
727
+ const pendingRequestMethods = /* @__PURE__ */ new Map();
728
+ return {
729
+ serialize: (msg) => {
730
+ let method;
731
+ if (msg.t === "q") method = msg.m;
732
+ else {
733
+ method = pendingRequestMethods.get(msg.i);
734
+ pendingRequestMethods.delete(msg.i);
735
+ }
736
+ if (!(msg.t === "s" && "e" in msg) && !!method && definitions.get(method)?.jsonSerializable === true) return strictJsonStringify(msg, method ?? "");
737
+ return `s:${structuredCloneStringify(msg)}`;
738
+ },
739
+ deserialize: (raw) => {
740
+ const msg = raw.startsWith("s:") ? structuredCloneParse(raw.slice(2)) : JSON.parse(raw);
741
+ if (msg.t === "q" && msg.i && msg.m) pendingRequestMethods.set(msg.i, msg.m);
742
+ return msg;
743
+ }
744
+ };
729
745
  }
730
- function createStaticRpcCaller(manifest, fetchJson) {
731
- const staticCache = /* @__PURE__ */ new Map();
732
- const queryRecordCache = /* @__PURE__ */ new Map();
733
- function reviveIfStructuredClone(value, serialization) {
734
- if (serialization === "structured-clone" && Array.isArray(value)) return structuredCloneDeserialize(value);
735
- return value;
736
- }
737
- function decode(raw, serialization) {
738
- return reviveIfStructuredClone(unwrapEnvelope(raw), serialization);
739
- }
740
- async function loadStatic(entry) {
741
- if (!staticCache.has(entry.path)) staticCache.set(entry.path, fetchJson(entry.path).then((raw) => decode(raw, entry.serialization)));
742
- const data = await staticCache.get(entry.path);
743
- if (isRecord(data)) return resolveRecordOutput(data);
744
- return data;
746
+ //#endregion
747
+ //#region src/rpc/transports/sse-client.ts
748
+ function NOOP$1() {}
749
+ /**
750
+ * Build a birpc `ChannelOptions` object backed by an SSE stream (server →
751
+ * client) and HTTP `POST` (client → server) — the WebSocket-free counterpart
752
+ * to `createWsRpcChannel`, wire-compatible with `attachSseRpcTransport`.
753
+ *
754
+ * The stream is consumed via `fetch` streaming (not `EventSource`), so it
755
+ * works in browsers and server runtimes alike. The server's first frame
756
+ * (`event: session`) carries the session id; every `POST` echoes it in the
757
+ * `x-birpc-session` header. A response to a client-initiated request comes
758
+ * back in the `POST`'s own body and is re-injected into the channel; a
759
+ * dropped stream terminates the channel — there is no reconnect, matching
760
+ * the WS channel's closed-is-done semantics.
761
+ */
762
+ function createSseRpcChannel(options) {
763
+ const { onConnected = NOOP$1, onError = NOOP$1, onDisconnected = NOOP$1, definitions, fetch: fetchImpl = globalThis.fetch.bind(globalThis) } = options;
764
+ let url = options.url;
765
+ if (options.authToken) url = `${url}${url.includes("?") ? "&" : "?"}${DEVFRAME_AUTH_TOKEN_QUERY_PARAM}=${encodeURIComponent(options.authToken)}`;
766
+ const codec = createRpcWireCodec(definitions);
767
+ const abort = new AbortController();
768
+ let closed = false;
769
+ let onMessage;
770
+ let activeReader;
771
+ let resolveSession;
772
+ let rejectSession;
773
+ const sessionReady = new Promise((resolve, reject) => {
774
+ resolveSession = resolve;
775
+ rejectSession = reject;
776
+ });
777
+ sessionReady.catch(() => {});
778
+ function fail(error) {
779
+ if (closed) return;
780
+ closed = true;
781
+ rejectSession(error);
782
+ onError(error);
783
+ onDisconnected();
745
784
  }
746
- async function loadQueryRecord(path, serialization) {
747
- if (!queryRecordCache.has(path)) queryRecordCache.set(path, fetchJson(path).then((raw) => decode(raw, serialization)));
748
- return await queryRecordCache.get(path);
785
+ function end() {
786
+ if (closed) return;
787
+ closed = true;
788
+ rejectSession(/* @__PURE__ */ new Error("Devframe SSE stream closed"));
789
+ onDisconnected();
749
790
  }
750
- async function call(functionName, args) {
751
- if (!(functionName in manifest)) throw new Error(`[devframe-rpc] Function "${functionName}" not found in dump store`);
752
- const entry = manifest[functionName];
753
- if (isStaticEntry(entry)) {
754
- if (hasMeaningfulArgs(args)) throw new Error(`[devframe-rpc] No dump match for "${functionName}" with args: ${JSON.stringify(args)}`);
755
- return await loadStatic(entry);
791
+ function dispatch(event, data) {
792
+ if (event === "session") {
793
+ resolveSession(data);
794
+ onConnected();
795
+ return;
756
796
  }
757
- if (isQueryEntry(entry)) {
758
- const argsHash = hash(args);
759
- const recordPath = entry.records[argsHash];
760
- if (recordPath) return resolveRecordOutput(await loadQueryRecord(recordPath, entry.serialization));
761
- if (entry.fallback) return resolveRecordOutput(await loadQueryRecord(entry.fallback, entry.serialization));
762
- throw new Error(`[devframe-rpc] No dump match for "${functionName}" with args: ${JSON.stringify(args)}`);
797
+ onMessage?.(data);
798
+ }
799
+ /** Parse the SSE byte stream frame by frame and dispatch each event. */
800
+ async function consume(stream) {
801
+ const reader = stream.getReader();
802
+ activeReader = reader;
803
+ const decoder = new TextDecoder();
804
+ let buffer = "";
805
+ for (;;) {
806
+ const { done, value } = await reader.read();
807
+ if (done) break;
808
+ buffer += decoder.decode(value, { stream: true });
809
+ for (;;) {
810
+ const boundary = buffer.search(/\n\n|\r\n\r\n/);
811
+ if (boundary < 0) break;
812
+ const frame = buffer.slice(0, boundary);
813
+ buffer = buffer.slice(boundary + (buffer[boundary] === "\r" ? 4 : 2));
814
+ let event = "message";
815
+ const data = [];
816
+ for (const rawLine of frame.split(/\r?\n/)) {
817
+ if (rawLine.startsWith(":")) continue;
818
+ if (rawLine.startsWith("event:")) event = rawLine.slice(6).trimStart();
819
+ else if (rawLine.startsWith("data:")) data.push(rawLine.slice(5).replace(/^ /, ""));
820
+ }
821
+ if (data.length > 0) dispatch(event, data.join("\n"));
822
+ }
763
823
  }
764
- if (!hasMeaningfulArgs(args)) return entry;
765
- throw new Error(`[devframe-rpc] No dump match for "${functionName}" with args: ${JSON.stringify(args)}`);
824
+ end();
766
825
  }
826
+ (async () => {
827
+ try {
828
+ const response = await fetchImpl(url, {
829
+ headers: { accept: "text/event-stream" },
830
+ signal: abort.signal
831
+ });
832
+ if (!response.ok || !response.body) throw new Error(`Devframe SSE stream request failed: ${response.status}`);
833
+ await consume(response.body);
834
+ } catch (error) {
835
+ if (abort.signal.aborted) {
836
+ end();
837
+ return;
838
+ }
839
+ fail(error instanceof Error ? error : new Error(String(error)));
840
+ }
841
+ })();
767
842
  return {
768
- call: async (functionName, args) => await call(functionName, args),
769
- callOptional: async (functionName, args) => {
770
- if (!(functionName in manifest)) return void 0;
771
- return await call(functionName, args);
843
+ close: () => {
844
+ closed = true;
845
+ abort.abort();
846
+ activeReader?.cancel().catch(() => {});
772
847
  },
773
- callEvent: async (_functionName, _args) => {}
848
+ on: (handler) => {
849
+ onMessage = handler;
850
+ },
851
+ post: async (data) => {
852
+ let sessionId;
853
+ try {
854
+ sessionId = await sessionReady;
855
+ } catch {
856
+ return;
857
+ }
858
+ if (closed) {
859
+ onError(/* @__PURE__ */ new Error("Devframe SSE channel is closed; message dropped"));
860
+ return;
861
+ }
862
+ try {
863
+ const response = await fetchImpl(url, {
864
+ method: "POST",
865
+ headers: {
866
+ "content-type": "text/plain; charset=utf-8",
867
+ [DEVFRAME_SSE_SESSION_HEADER]: sessionId
868
+ },
869
+ body: data
870
+ });
871
+ if (response.status === 200) {
872
+ const body = await response.text();
873
+ if (body) onMessage?.(body);
874
+ return;
875
+ }
876
+ if (!response.ok) throw new Error(`Devframe SSE POST failed: ${response.status}`);
877
+ } catch (error) {
878
+ onError(error instanceof Error ? error : new Error(String(error)));
879
+ }
880
+ },
881
+ serialize: codec.serialize,
882
+ deserialize: codec.deserialize
774
883
  };
775
884
  }
776
885
  //#endregion
777
- //#region src/client/rpc-static.ts
778
- async function createStaticRpcClientMode(options) {
779
- const staticCaller = createStaticRpcCaller(await options.fetchJsonFromBases(DEVFRAME_RPC_DUMP_MANIFEST_FILENAME), options.fetchJsonFromBases);
886
+ //#region src/rpc/client.ts
887
+ function createRpcClient(functions, options) {
888
+ const { channel, rpcOptions = {} } = options;
889
+ return createBirpc(functions, {
890
+ ...channel,
891
+ timeout: -1,
892
+ ...rpcOptions,
893
+ proxify: false
894
+ });
895
+ }
896
+ //#endregion
897
+ //#region src/utils/promise.ts
898
+ function promiseWithResolver() {
899
+ let resolve;
900
+ let reject;
780
901
  return {
781
- isTrusted: true,
782
- status: "connected",
783
- connectionError: null,
784
- requestTrust: async () => true,
785
- requestTrustWithToken: async () => true,
786
- requestTrustWithCode: async () => null,
787
- ensureTrusted: async () => true,
788
- call: (...args) => staticCaller.call(args[0], args.slice(1)),
789
- callEvent: (...args) => staticCaller.callEvent(args[0], args.slice(1)),
790
- callOptional: (...args) => staticCaller.callOptional(args[0], args.slice(1))
902
+ promise: new Promise((_resolve, _reject) => {
903
+ resolve = _resolve;
904
+ reject = _reject;
905
+ }),
906
+ resolve,
907
+ reject
791
908
  };
792
909
  }
793
910
  //#endregion
794
- //#region src/client/rpc-streaming.ts
795
- const STREAM_KEY_SEPARATOR = "";
796
- function streamKey(channel, id) {
797
- return `${channel}${STREAM_KEY_SEPARATOR}${id}`;
798
- }
911
+ //#region src/client/rpc-live.ts
799
912
  /**
800
- * Client-side streaming host. Mirrors `createRpcSharedStateClientHost`:
801
- * registers the two `:chunk` / `:end` event handlers once, then per-stream
802
- * state lives in a `Map<streamKey, StreamReader>`.
913
+ * The transport-independent half of a live RPC client mode — connection
914
+ * status, pending-call guarding, and the trust handshake — shared by the
915
+ * WebSocket and SSE modes. Each transport supplies only its channel.
803
916
  */
804
- function createRpcStreamingClientHost(rpc) {
805
- const readers = /* @__PURE__ */ new Map();
806
- const uploads = /* @__PURE__ */ new Map();
807
- rpc.client.register({
808
- name: "devframe:streaming:chunk",
809
- type: "event",
810
- handler(channel, id, seq, chunk) {
811
- readers.get(streamKey(channel, id))?._push(seq, chunk);
812
- }
813
- });
814
- rpc.client.register({
917
+ function createLiveRpcClientMode(options) {
918
+ const { transport, authToken, connectionMeta, events, clientRpc, rpcOptions = {}, callTimeout = 0 } = options;
919
+ let isTrusted = false;
920
+ let status = "connecting";
921
+ let connectionError = null;
922
+ const trustedPromise = promiseWithResolver();
923
+ function setStatus(next, error = null) {
924
+ if (error) connectionError = error;
925
+ else if (next === "connected") connectionError = null;
926
+ if (next === status) return;
927
+ const previous = status;
928
+ status = next;
929
+ events.emit("connection:status", next, previous);
930
+ }
931
+ const pending = /* @__PURE__ */ new Set();
932
+ function rejectAllPending(error) {
933
+ for (const entry of [...pending]) entry.reject(error);
934
+ }
935
+ function terminalError() {
936
+ if (status === "disconnected" || status === "error") return new DevframeConnectionError("connection", "[devframe] Not connected to the devframe server", { cause: connectionError ?? void 0 });
937
+ if (status === "unauthorized") return new DevframeConnectionError("auth", "[devframe] Not authorized by the devframe server", { cause: connectionError ?? void 0 });
938
+ return null;
939
+ }
940
+ /**
941
+ * Wrap an in-flight call promise so it settles on server response, on an
942
+ * optional wall-clock timeout, or when the connection drops — never hangs.
943
+ */
944
+ function guardCall(promise, method) {
945
+ return new Promise((resolve, reject) => {
946
+ let settled = false;
947
+ let timer;
948
+ const entry = { reject(error) {
949
+ if (settled) return;
950
+ finish();
951
+ events.emit("rpc:error", error, method);
952
+ reject(error);
953
+ } };
954
+ function finish() {
955
+ settled = true;
956
+ pending.delete(entry);
957
+ if (timer) clearTimeout(timer);
958
+ }
959
+ pending.add(entry);
960
+ if (callTimeout > 0) timer = setTimeout(() => {
961
+ entry.reject(new DevframeConnectionError("timeout", `[devframe] RPC call "${method}" timed out after ${callTimeout}ms`));
962
+ }, callTimeout);
963
+ promise.then((value) => {
964
+ if (settled) return;
965
+ finish();
966
+ resolve(value);
967
+ }, (error) => {
968
+ if (settled) return;
969
+ finish();
970
+ const err = error instanceof Error ? error : new Error(String(error));
971
+ events.emit("rpc:error", err, method);
972
+ reject(err);
973
+ });
974
+ });
975
+ }
976
+ const definitions = /* @__PURE__ */ new Map();
977
+ for (const name of connectionMeta.jsonSerializableMethods ?? []) definitions.set(name, { jsonSerializable: true });
978
+ const channel = options.createChannel({
979
+ definitions,
980
+ onError(error) {
981
+ setStatus("error", error);
982
+ events.emit("connection:error", error);
983
+ rejectAllPending(new DevframeConnectionError("connection", "[devframe] Connection to the devframe server failed", { cause: error }));
984
+ },
985
+ onDisconnected() {
986
+ if (status !== "error") setStatus("disconnected");
987
+ rejectAllPending(new DevframeConnectionError("connection", "[devframe] Disconnected from the devframe server", { cause: connectionError ?? void 0 }));
988
+ }
989
+ });
990
+ const serverRpc = createRpcClient(clientRpc.functions, {
991
+ channel,
992
+ rpcOptions
993
+ });
994
+ clientRpc.register({
995
+ name: "devframe:auth:revoked",
996
+ type: "event",
997
+ handler: () => {
998
+ isTrusted = false;
999
+ const authError = new DevframeConnectionError("auth", "[devframe] The devframe server revoked this client's trust");
1000
+ setStatus("unauthorized", authError);
1001
+ events.emit("connection:error", authError);
1002
+ rejectAllPending(authError);
1003
+ events.emit("rpc:is-trusted:updated", false);
1004
+ }
1005
+ });
1006
+ let currentAuthToken = authToken;
1007
+ async function requestTrustWithToken(token) {
1008
+ currentAuthToken = token;
1009
+ const result = await serverRpc.$call("anonymous:devframe:auth", {
1010
+ authToken: token,
1011
+ ua: navigator.userAgent,
1012
+ origin: location.origin
1013
+ });
1014
+ isTrusted = result.isTrusted;
1015
+ if (isTrusted) {
1016
+ trustedPromise.resolve(true);
1017
+ setStatus("connected");
1018
+ } else {
1019
+ const authError = new DevframeConnectionError("auth", "[devframe] The devframe server refused this client's credentials");
1020
+ setStatus("unauthorized", authError);
1021
+ events.emit("connection:error", authError);
1022
+ }
1023
+ events.emit("rpc:is-trusted:updated", isTrusted);
1024
+ return result.isTrusted;
1025
+ }
1026
+ async function requestTrustWithCode(code) {
1027
+ const token = (await serverRpc.$call("anonymous:devframe:auth:exchange", {
1028
+ code,
1029
+ ua: navigator.userAgent,
1030
+ origin: location.origin
1031
+ }))?.authToken ?? null;
1032
+ if (token) {
1033
+ currentAuthToken = token;
1034
+ isTrusted = true;
1035
+ trustedPromise.resolve(true);
1036
+ setStatus("connected");
1037
+ events.emit("rpc:is-trusted:updated", true);
1038
+ }
1039
+ return token;
1040
+ }
1041
+ async function requestTrust() {
1042
+ if (isTrusted) return true;
1043
+ return requestTrustWithToken(currentAuthToken ?? "");
1044
+ }
1045
+ async function ensureTrusted(timeout = 6e4) {
1046
+ if (isTrusted) trustedPromise.resolve(true);
1047
+ if (timeout <= 0) return trustedPromise.promise;
1048
+ let clear = () => {};
1049
+ await Promise.race([trustedPromise.promise.then(clear), new Promise((resolve, reject) => {
1050
+ const id = setTimeout(() => {
1051
+ reject(/* @__PURE__ */ new Error("[devframe] Timeout waiting for rpc to be trusted"));
1052
+ }, timeout);
1053
+ clear = () => clearTimeout(id);
1054
+ })]);
1055
+ return isTrusted;
1056
+ }
1057
+ return {
1058
+ transport,
1059
+ get isTrusted() {
1060
+ return isTrusted;
1061
+ },
1062
+ get status() {
1063
+ return status;
1064
+ },
1065
+ get connectionError() {
1066
+ return connectionError;
1067
+ },
1068
+ requestTrust,
1069
+ requestTrustWithToken,
1070
+ requestTrustWithCode,
1071
+ ensureTrusted,
1072
+ call: (...args) => {
1073
+ const method = String(args[0]);
1074
+ const failFast = terminalError();
1075
+ if (failFast) {
1076
+ events.emit("rpc:error", failFast, method);
1077
+ return Promise.reject(failFast);
1078
+ }
1079
+ return guardCall(serverRpc.$call(...args), method);
1080
+ },
1081
+ callEvent: (...args) => {
1082
+ const failFast = terminalError();
1083
+ if (failFast) {
1084
+ events.emit("rpc:error", failFast, String(args[0]));
1085
+ return;
1086
+ }
1087
+ return serverRpc.$callEvent(...args);
1088
+ },
1089
+ callOptional: (...args) => {
1090
+ const method = String(args[0]);
1091
+ const failFast = terminalError();
1092
+ if (failFast) {
1093
+ events.emit("rpc:error", failFast, method);
1094
+ return Promise.reject(failFast);
1095
+ }
1096
+ return guardCall(serverRpc.$callOptional(...args), method);
1097
+ },
1098
+ close: () => {
1099
+ channel.close();
1100
+ }
1101
+ };
1102
+ }
1103
+ //#endregion
1104
+ //#region src/client/rpc-sse.ts
1105
+ /**
1106
+ * Resolve a {@link ConnectionMeta.sse} descriptor into a concrete
1107
+ * `http(s)://` URL, with the same proxy-safe rules as `resolveWsUrl`: the
1108
+ * object / relative-path forms resolve against where `__connection.json`
1109
+ * was loaded (the client trusts its own location, never a server-baked
1110
+ * hostname), while an explicit `port`/`host` (or a full `http(s)://` URL
1111
+ * string) opts into a cross-origin endpoint.
1112
+ */
1113
+ function resolveSseUrl(sse, metaBaseUrl, loc) {
1114
+ const base = (() => {
1115
+ try {
1116
+ return new URL(metaBaseUrl, loc.href);
1117
+ } catch {
1118
+ return new URL(loc.href);
1119
+ }
1120
+ })();
1121
+ if (sse && typeof sse === "object") {
1122
+ if (sse.host != null || sse.port != null) {
1123
+ const host = sse.host ?? `${base.hostname}:${sse.port}`;
1124
+ return new URL(sse.path ?? "/", `${base.protocol}//${host}`).href;
1125
+ }
1126
+ return new URL(sse.path ?? "", base).href;
1127
+ }
1128
+ const str = sse ?? "";
1129
+ if (/^https?:\/\//i.test(str)) return str;
1130
+ return new URL(str, base).href;
1131
+ }
1132
+ /**
1133
+ * The SSE-backed live client mode — picked by `connectDevframe` when the
1134
+ * server advertises SSE as its primary transport (`backend: 'sse'`) or when
1135
+ * the caller pins `transport: 'sse'`. Same status machine, call guarding,
1136
+ * and trust handshake as the WebSocket mode, over `createSseRpcChannel`.
1137
+ */
1138
+ function createSseRpcClientMode(options) {
1139
+ const { authToken, connectionMeta, metaBaseUrl, events, clientRpc, rpcOptions = {}, sseOptions = {}, callTimeout = 0 } = options;
1140
+ const url = resolveSseUrl(connectionMeta.sse, metaBaseUrl ?? "./", location);
1141
+ return createLiveRpcClientMode({
1142
+ transport: "sse",
1143
+ authToken,
1144
+ connectionMeta,
1145
+ events,
1146
+ clientRpc,
1147
+ rpcOptions,
1148
+ callTimeout,
1149
+ createChannel: (handlers) => createSseRpcChannel({
1150
+ url,
1151
+ authToken,
1152
+ definitions: handlers.definitions,
1153
+ ...sseOptions,
1154
+ onConnected() {
1155
+ sseOptions.onConnected?.();
1156
+ },
1157
+ onError(error) {
1158
+ handlers.onError(error);
1159
+ sseOptions.onError?.(error);
1160
+ },
1161
+ onDisconnected() {
1162
+ handlers.onDisconnected();
1163
+ sseOptions.onDisconnected?.();
1164
+ }
1165
+ })
1166
+ });
1167
+ }
1168
+ //#endregion
1169
+ //#region src/rpc/dump/error.ts
1170
+ /**
1171
+ * Inverse of {@link serializeDumpError}: rebuild a thrown `Error` from
1172
+ * the plain object stored in a dump record. Preserves `cause`, restores
1173
+ * the original `name`, and re-attaches any custom own properties.
1174
+ */
1175
+ function reviveDumpError(stored) {
1176
+ const cause = stored.cause instanceof Error ? stored.cause : isPlainErrorShape(stored.cause) ? reviveDumpError(stored.cause) : stored.cause;
1177
+ const error = cause !== void 0 ? new Error(stored.message, { cause }) : new Error(stored.message);
1178
+ error.name = stored.name;
1179
+ for (const key of Object.keys(stored)) {
1180
+ if (key === "name" || key === "message" || key === "cause") continue;
1181
+ error[key] = stored[key];
1182
+ }
1183
+ return error;
1184
+ }
1185
+ function isPlainErrorShape(value) {
1186
+ return typeof value === "object" && value !== null && typeof value.message === "string" && typeof value.name === "string";
1187
+ }
1188
+ //#endregion
1189
+ //#region src/client/static-rpc.ts
1190
+ function isStaticEntry(value) {
1191
+ return typeof value === "object" && value !== null && value.type === "static" && typeof value.path === "string";
1192
+ }
1193
+ function isQueryEntry(value) {
1194
+ return typeof value === "object" && value !== null && value.type === "query" && typeof value.records === "object" && value.records !== null;
1195
+ }
1196
+ function isRecord(value) {
1197
+ return typeof value === "object" && value !== null && ("output" in value || "error" in value);
1198
+ }
1199
+ function resolveRecordOutput(record) {
1200
+ if (record.error) throw reviveDumpError(record.error);
1201
+ return record.output;
1202
+ }
1203
+ function hasMeaningfulArgs(args) {
1204
+ return args.some((arg) => arg !== null && arg !== void 0);
1205
+ }
1206
+ function unwrapEnvelope(raw) {
1207
+ if (raw !== null && typeof raw === "object" && "serialization" in raw && "data" in raw) return raw.data;
1208
+ return raw;
1209
+ }
1210
+ function createStaticRpcCaller(manifest, fetchJson) {
1211
+ const staticCache = /* @__PURE__ */ new Map();
1212
+ const queryRecordCache = /* @__PURE__ */ new Map();
1213
+ function reviveIfStructuredClone(value, serialization) {
1214
+ if (serialization === "structured-clone" && Array.isArray(value)) return structuredCloneDeserialize(value);
1215
+ return value;
1216
+ }
1217
+ function decode(raw, serialization) {
1218
+ return reviveIfStructuredClone(unwrapEnvelope(raw), serialization);
1219
+ }
1220
+ async function loadStatic(entry) {
1221
+ if (!staticCache.has(entry.path)) staticCache.set(entry.path, fetchJson(entry.path).then((raw) => decode(raw, entry.serialization)));
1222
+ const data = await staticCache.get(entry.path);
1223
+ if (isRecord(data)) return resolveRecordOutput(data);
1224
+ return data;
1225
+ }
1226
+ async function loadQueryRecord(path, serialization) {
1227
+ if (!queryRecordCache.has(path)) queryRecordCache.set(path, fetchJson(path).then((raw) => decode(raw, serialization)));
1228
+ return await queryRecordCache.get(path);
1229
+ }
1230
+ async function call(functionName, args) {
1231
+ if (!(functionName in manifest)) throw new Error(`[devframe-rpc] Function "${functionName}" not found in dump store`);
1232
+ const entry = manifest[functionName];
1233
+ if (isStaticEntry(entry)) {
1234
+ if (hasMeaningfulArgs(args)) throw new Error(`[devframe-rpc] No dump match for "${functionName}" with args: ${JSON.stringify(args)}`);
1235
+ return await loadStatic(entry);
1236
+ }
1237
+ if (isQueryEntry(entry)) {
1238
+ const argsHash = hash(args);
1239
+ const recordPath = entry.records[argsHash];
1240
+ if (recordPath) return resolveRecordOutput(await loadQueryRecord(recordPath, entry.serialization));
1241
+ if (entry.fallback) return resolveRecordOutput(await loadQueryRecord(entry.fallback, entry.serialization));
1242
+ throw new Error(`[devframe-rpc] No dump match for "${functionName}" with args: ${JSON.stringify(args)}`);
1243
+ }
1244
+ if (!hasMeaningfulArgs(args)) return entry;
1245
+ throw new Error(`[devframe-rpc] No dump match for "${functionName}" with args: ${JSON.stringify(args)}`);
1246
+ }
1247
+ return {
1248
+ call: async (functionName, args) => await call(functionName, args),
1249
+ callOptional: async (functionName, args) => {
1250
+ if (!(functionName in manifest)) return void 0;
1251
+ return await call(functionName, args);
1252
+ },
1253
+ callEvent: async (_functionName, _args) => {}
1254
+ };
1255
+ }
1256
+ //#endregion
1257
+ //#region src/client/rpc-static.ts
1258
+ async function createStaticRpcClientMode(options) {
1259
+ const staticCaller = createStaticRpcCaller(await options.fetchJsonFromBases(DEVFRAME_RPC_DUMP_MANIFEST_FILENAME), options.fetchJsonFromBases);
1260
+ return {
1261
+ transport: "static",
1262
+ isTrusted: true,
1263
+ status: "connected",
1264
+ connectionError: null,
1265
+ requestTrust: async () => true,
1266
+ requestTrustWithToken: async () => true,
1267
+ requestTrustWithCode: async () => null,
1268
+ ensureTrusted: async () => true,
1269
+ call: (...args) => staticCaller.call(args[0], args.slice(1)),
1270
+ callEvent: (...args) => staticCaller.callEvent(args[0], args.slice(1)),
1271
+ callOptional: (...args) => staticCaller.callOptional(args[0], args.slice(1)),
1272
+ close: () => {}
1273
+ };
1274
+ }
1275
+ //#endregion
1276
+ //#region src/client/rpc-streaming.ts
1277
+ const STREAM_KEY_SEPARATOR = "";
1278
+ function streamKey(channel, id) {
1279
+ return `${channel}${STREAM_KEY_SEPARATOR}${id}`;
1280
+ }
1281
+ /**
1282
+ * Client-side streaming host. Mirrors `createRpcSharedStateClientHost`:
1283
+ * registers the two `:chunk` / `:end` event handlers once, then per-stream
1284
+ * state lives in a `Map<streamKey, StreamReader>`.
1285
+ */
1286
+ function createRpcStreamingClientHost(rpc) {
1287
+ const readers = /* @__PURE__ */ new Map();
1288
+ const uploads = /* @__PURE__ */ new Map();
1289
+ rpc.client.register({
1290
+ name: "devframe:streaming:chunk",
1291
+ type: "event",
1292
+ handler(channel, id, seq, chunk) {
1293
+ readers.get(streamKey(channel, id))?._push(seq, chunk);
1294
+ }
1295
+ });
1296
+ rpc.client.register({
815
1297
  name: "devframe:streaming:end",
816
1298
  type: "event",
817
1299
  handler(channel, id, error) {
@@ -881,26 +1363,15 @@ function createRpcStreamingClientHost(rpc) {
881
1363
  });
882
1364
  sink.events.on("end", (error) => {
883
1365
  rpc.callEvent("devframe:streaming:upload-end", channel, id, error);
884
- uploads.delete(key);
885
- });
886
- uploads.set(key, sink);
887
- return sink;
888
- }
889
- return {
890
- subscribe,
891
- upload
892
- };
893
- }
894
- //#endregion
895
- //#region src/rpc/client.ts
896
- function createRpcClient(functions, options) {
897
- const { channel, rpcOptions = {} } = options;
898
- return createBirpc(functions, {
899
- ...channel,
900
- timeout: -1,
901
- ...rpcOptions,
902
- proxify: false
903
- });
1366
+ uploads.delete(key);
1367
+ });
1368
+ uploads.set(key, sink);
1369
+ return sink;
1370
+ }
1371
+ return {
1372
+ subscribe,
1373
+ upload
1374
+ };
904
1375
  }
905
1376
  //#endregion
906
1377
  //#region src/rpc/transports/ws-client.ts
@@ -909,6 +1380,9 @@ const EMPTY_DEFS = /* @__PURE__ */ new Map();
909
1380
  /**
910
1381
  * Build a birpc `ChannelOptions` object backed by a browser `WebSocket`.
911
1382
  * Pass the result straight to `createRpcClient`'s `channel` option.
1383
+ *
1384
+ * Also returns `close()`, closing the underlying socket — mirroring the server transport's
1385
+ * existing `WsRpcTransport.close()`. `birpc`'s own `ChannelOptions` has no teardown of its own.
912
1386
  */
913
1387
  function createWsRpcChannel(options) {
914
1388
  let url = options.url;
@@ -925,8 +1399,11 @@ function createWsRpcChannel(options) {
925
1399
  ws.addEventListener("close", (e) => {
926
1400
  onDisconnected(e);
927
1401
  });
928
- const pendingRequestMethods = /* @__PURE__ */ new Map();
1402
+ const codec = createRpcWireCodec(definitions);
929
1403
  return {
1404
+ close: () => {
1405
+ ws.close();
1406
+ },
930
1407
  on: (handler) => {
931
1408
  ws.addEventListener("message", (e) => {
932
1409
  handler(e.data);
@@ -953,1183 +1430,8 @@ function createWsRpcChannel(options) {
953
1430
  }
954
1431
  onError(/* @__PURE__ */ new Error("Devframe WebSocket is not open; message dropped"));
955
1432
  },
956
- serialize: (msg) => {
957
- let method;
958
- if (msg.t === "q") method = msg.m;
959
- else {
960
- method = pendingRequestMethods.get(msg.i);
961
- pendingRequestMethods.delete(msg.i);
962
- }
963
- if (!(msg.t === "s" && "e" in msg) && !!method && definitions.get(method)?.jsonSerializable === true) return strictJsonStringify(msg, method ?? "");
964
- return `s:${structuredCloneStringify(msg)}`;
965
- },
966
- deserialize: (raw) => {
967
- const msg = raw.startsWith("s:") ? structuredCloneParse(raw.slice(2)) : JSON.parse(raw);
968
- if (msg.t === "q" && msg.i && msg.m) pendingRequestMethods.set(msg.i, msg.m);
969
- return msg;
970
- }
971
- };
972
- }
973
- //#endregion
974
- //#region ../../node_modules/.pnpm/ua-parser-modern@0.1.1/node_modules/ua-parser-modern/dist/index.mjs
975
- const EMPTY = "";
976
- const UNKNOWN = "?";
977
- const MAJOR = "major";
978
- const MODEL = "model";
979
- const NAME = "name";
980
- const TYPE = "type";
981
- const VENDOR = "vendor";
982
- const VERSION = "version";
983
- const ARCHITECTURE = "architecture";
984
- const CONSOLE = "console";
985
- const MOBILE = "mobile";
986
- const TABLET = "tablet";
987
- const SMARTTV = "smarttv";
988
- const WEARABLE = "wearable";
989
- const EMBEDDED = "embedded";
990
- const UA_MAX_LENGTH = 500;
991
- const AMAZON = "Amazon";
992
- const APPLE = "Apple";
993
- const ASUS = "ASUS";
994
- const BLACKBERRY = "BlackBerry";
995
- const BROWSER_LABEL = "Browser";
996
- const CHROME = "Chrome";
997
- const EDGE = "Edge";
998
- const FIREFOX = "Firefox";
999
- const GOOGLE = "Google";
1000
- const HUAWEI = "Huawei";
1001
- const LG = "LG";
1002
- const MICROSOFT = "Microsoft";
1003
- const MOTOROLA = "Motorola";
1004
- const OPERA = "Opera";
1005
- const SAMSUNG = "Samsung";
1006
- const SHARP = "Sharp";
1007
- const SONY = "Sony";
1008
- const XIAOMI = "Xiaomi";
1009
- const ZEBRA = "Zebra";
1010
- const FACEBOOK = "Facebook";
1011
- const CHROMIUM_OS = "Chromium OS";
1012
- const MAC_OS = "Mac OS";
1013
- function extend(regexes, extensions = {}) {
1014
- const mergedRegexes = {};
1015
- for (const key of Object.keys(regexes)) {
1016
- const extension = extensions[key];
1017
- if (extension && extension.length % 2 === 0) mergedRegexes[key] = [...extension, ...regexes[key]];
1018
- else mergedRegexes[key] = regexes[key];
1019
- }
1020
- return mergedRegexes;
1021
- }
1022
- function enumerize(arr) {
1023
- const enums = {};
1024
- for (let i = 0; i < arr.length; i++) enums[arr[i].toUpperCase()] = arr[i];
1025
- return enums;
1026
- }
1027
- function lowerize(str) {
1028
- return str.toLowerCase();
1029
- }
1030
- function has(str1, str2) {
1031
- return typeof str1 === "string" ? lowerize(str2).includes(lowerize(str1)) : false;
1032
- }
1033
- function majorize(version) {
1034
- return typeof version === "string" ? version.replace(/[^\d.]/g, EMPTY).split(".")[0] : void 0;
1035
- }
1036
- function trim(str, len) {
1037
- const result = str.replace(/^\s+/, EMPTY);
1038
- return typeof len === "undefined" ? result : result.substring(0, len);
1039
- }
1040
- function rgxMapper(ua, arrays, augment) {
1041
- const result = augment ?? {};
1042
- for (let i = 0; i < arrays.length; i += 2) {
1043
- const regex = arrays[i];
1044
- const props = arrays[i + 1];
1045
- for (let j = 0; j < regex.length; j++) {
1046
- const matcher = regex[j];
1047
- if (!matcher) break;
1048
- const matches = matcher.exec(ua);
1049
- if (!matches) continue;
1050
- for (let p = 0; p < props.length; p++) {
1051
- const match = matches[p + 1];
1052
- const q = props[p];
1053
- if (Array.isArray(q)) {
1054
- const key = q[0];
1055
- const length = q.length;
1056
- if (length === 2) {
1057
- const value = q[1];
1058
- if (typeof value === "function") result[key] = value(match, void 0, result);
1059
- else result[key] = value;
1060
- } else if (length === 3) {
1061
- const arg1 = q[1];
1062
- const arg2 = q[2];
1063
- if (typeof arg1 === "function" && !("exec" in arg1) && !("test" in arg1)) result[key] = match ? arg1(match, arg2, result) : void 0;
1064
- else result[key] = match ? match.replace(arg1, arg2) : void 0;
1065
- } else if (length === 4) result[key] = match ? q[3](match.replace(q[1], q[2]), void 0, result) : void 0;
1066
- } else result[q] = match || void 0;
1067
- }
1068
- return result;
1069
- }
1070
- }
1071
- return result;
1072
- }
1073
- function strMapper(str, map) {
1074
- for (const i in map) {
1075
- const value = map[i];
1076
- if (Array.isArray(value) && value.length > 0) {
1077
- for (let j = 0; j < value.length; j++) if (has(value[j], str)) return i === UNKNOWN ? void 0 : i;
1078
- } else if (has(value, str)) return i === UNKNOWN ? void 0 : i;
1079
- }
1080
- return str;
1081
- }
1082
- const oldSafariMap = {
1083
- "1.0": "/8",
1084
- "1.2": "/1",
1085
- "1.3": "/3",
1086
- "2.0": "/412",
1087
- "2.0.2": "/416",
1088
- "2.0.3": "/417",
1089
- "2.0.4": "/419",
1090
- "?": "/"
1091
- };
1092
- const windowsVersionMap = {
1093
- "ME": "4.90",
1094
- "NT 3.11": "NT3.51",
1095
- "NT 4.0": "NT4.0",
1096
- "2000": "NT 5.0",
1097
- "XP": ["NT 5.1", "NT 5.2"],
1098
- "Vista": "NT 6.0",
1099
- "7": "NT 6.1",
1100
- "8": "NT 6.2",
1101
- "8.1": "NT 6.3",
1102
- "10": ["NT 6.4", "NT 10.0"],
1103
- "RT": "ARM"
1104
- };
1105
- const regexes = {
1106
- browser: [
1107
- [/\b(?:crmo|crios)\/([\w.]+)/i],
1108
- [VERSION, [NAME, "Chrome"]],
1109
- [/edg(?:e|ios|a)?\/([\w.]+)/i],
1110
- [VERSION, [NAME, "Edge"]],
1111
- [
1112
- /(opera mini)\/([-\w.]+)/i,
1113
- /(opera [mobileta]{3,6})\b.+version\/([-\w.]+)/i,
1114
- /(opera)(?:.+version\/|[/ ]+)([\w.]+)/i
1115
- ],
1116
- [NAME, VERSION],
1117
- [/opios[/ ]+([\w.]+)/i],
1118
- [VERSION, [NAME, `${OPERA} Mini`]],
1119
- [/\bop(?:rg)?x\/([\w.]+)/i],
1120
- [VERSION, [NAME, `${OPERA} GX`]],
1121
- [/\bopr\/([\w.]+)/i],
1122
- [VERSION, [NAME, OPERA]],
1123
- [/\bb[ai]*d(?:uhd|[ub]*[aekoprswx]{5,6})[/ ]?([\w.]+)/i],
1124
- [VERSION, [NAME, "Baidu"]],
1125
- [
1126
- /(kindle)\/([\w.]+)/i,
1127
- /(lunascape|maxthon|netfront|jasmine|blazer)[/ ]?([\w.]*)/i,
1128
- /(avant|iemobile|slim)\s?(?:browser)?[/ ]?([\w.]*)/i,
1129
- /(?:ms|\()(ie) ([\w.]+)/i,
1130
- /(flock|rockmelt|midori|epiphany|silk|skyfire|bolt|iron|vivaldi|iridium|phantomjs|bowser|quark|qupzilla|falkon|rekonq|puffin|brave|whale(?!.+naver)|qqbrowserlite|qq|duckduckgo)\/([-\w.]+)/i,
1131
- /(heytap|ovi)browser\/([\d.]+)/i,
1132
- /(weibo)__([\d.]+)/i
1133
- ],
1134
- [NAME, VERSION],
1135
- [/\bddg\/([\w.]+)/i],
1136
- [VERSION, [NAME, "DuckDuckGo"]],
1137
- [/(?:\buc? ?browser|juc.+ucweb)[/ ]?([\w.]+)/i],
1138
- [VERSION, [NAME, `UC${BROWSER_LABEL}`]],
1139
- [
1140
- /microm.+\bqbcore\/([\w.]+)/i,
1141
- /\bqbcore\/([\w.]+).+microm/i,
1142
- /micromessenger\/([\w.]+)/i
1143
- ],
1144
- [VERSION, [NAME, "WeChat"]],
1145
- [/konqueror\/([\w.]+)/i],
1146
- [VERSION, [NAME, "Konqueror"]],
1147
- [/trident.+rv[: ]([\w.]{1,9})\b.+like gecko/i],
1148
- [VERSION, [NAME, "IE"]],
1149
- [/ya(?:search)?browser\/([\w.]+)/i],
1150
- [VERSION, [NAME, "Yandex"]],
1151
- [/slbrowser\/([\w.]+)/i],
1152
- [VERSION, [NAME, `Smart Lenovo ${BROWSER_LABEL}`]],
1153
- [/(avast|avg)\/([\w.]+)/i],
1154
- [[
1155
- NAME,
1156
- /(.+)/,
1157
- `$1 Secure ${BROWSER_LABEL}`
1158
- ], VERSION],
1159
- [/\bfocus\/([\w.]+)/i],
1160
- [VERSION, [NAME, `${FIREFOX} Focus`]],
1161
- [/\bopt\/([\w.]+)/i],
1162
- [VERSION, [NAME, `${OPERA} Touch`]],
1163
- [/coc_coc\w+\/([\w.]+)/i],
1164
- [VERSION, [NAME, "Coc Coc"]],
1165
- [/dolfin\/([\w.]+)/i],
1166
- [VERSION, [NAME, "Dolphin"]],
1167
- [/coast\/([\w.]+)/i],
1168
- [VERSION, [NAME, `${OPERA} Coast`]],
1169
- [/miuibrowser\/([\w.]+)/i],
1170
- [VERSION, [NAME, `MIUI ${BROWSER_LABEL}`]],
1171
- [/fxios\/([-\w.]+)/i],
1172
- [VERSION, [NAME, FIREFOX]],
1173
- [/\bqihu|(qi?ho{0,2}|360)browser/i],
1174
- [[NAME, `360 ${BROWSER_LABEL}`]],
1175
- [/(oculus|sailfish|huawei|vivo)browser\/([\w.]+)/i],
1176
- [[
1177
- NAME,
1178
- /(.+)/,
1179
- `$1 ${BROWSER_LABEL}`
1180
- ], VERSION],
1181
- [/samsungbrowser\/([\w.]+)/i],
1182
- [VERSION, [NAME, `${SAMSUNG} Internet`]],
1183
- [/(comodo_dragon)\/([\w.]+)/i],
1184
- [[
1185
- NAME,
1186
- /_/g,
1187
- " "
1188
- ], VERSION],
1189
- [/metasr[/ ]?([\d.]+)/i],
1190
- [VERSION, [NAME, "Sogou Explorer"]],
1191
- [/(sogou)mo\w+\/([\d.]+)/i],
1192
- [[NAME, "Sogou Mobile"], VERSION],
1193
- [
1194
- /(electron)\/([\w.]+) safari/i,
1195
- /(tesla)(?: qtcarbrowser|\/(20\d\d\.[-\w.]+))/i,
1196
- /m?(qqbrowser|2345Explorer)[/ ]?([\w.]+)/i
1197
- ],
1198
- [NAME, VERSION],
1199
- [/(lbbrowser)/i, /\[(linkedin)app\]/i],
1200
- [NAME],
1201
- [/((?:fban\/fbios|fb_iab\/fb4a)(?!.+fbav)|;fbav\/([\w.]+);)/i],
1202
- [[NAME, FACEBOOK], VERSION],
1203
- [
1204
- /(Klarna)\/([\w.]+)/i,
1205
- /(kakao(?:talk|story))[/ ]([\w.]+)/i,
1206
- /(naver)\(.*?(\d+\.[\w.]+).*\)/i,
1207
- /safari (line)\/([\w.]+)/i,
1208
- /\b(line)\/([\w.]+)\/iab/i,
1209
- /(alipay)client\/([\w.]+)/i,
1210
- /(twitter)(?:and| f.+e\/([\w.]+))/i,
1211
- /(chromium|instagram|snapchat)[/ ]([-\w.]+)/i
1212
- ],
1213
- [NAME, VERSION],
1214
- [/\bgsa\/([\w.]+) .*safari\//i],
1215
- [VERSION, [NAME, "GSA"]],
1216
- [/musical_ly(?:.+app_?version\/|_)([\w.]+)/i],
1217
- [VERSION, [NAME, "TikTok"]],
1218
- [/headlesschrome(?:\/([\w.]+)| )/i],
1219
- [VERSION, [NAME, `${CHROME} Headless`]],
1220
- [/ wv\).+(chrome)\/([\w.]+)/i],
1221
- [[NAME, `${CHROME} WebView`], VERSION],
1222
- [/droid.+ version\/([\w.]+)\b.+(?:mobile safari|safari)/i],
1223
- [VERSION, [NAME, `Android ${BROWSER_LABEL}`]],
1224
- [/(chrome|omniweb|arora|[tizenoka]{5} ?browser)\/v?([\w.]+)/i],
1225
- [NAME, VERSION],
1226
- [/version\/([\w.,]+) .*mobile\/\w+ (safari)/i],
1227
- [VERSION, [NAME, "Mobile Safari"]],
1228
- [/version\/([\w(.|,)]+) .*(mobile ?safari|safari)/i],
1229
- [VERSION, NAME],
1230
- [/webkit.+?(mobile ?safari|safari)(\/[\w.]+)/i],
1231
- [NAME, [
1232
- VERSION,
1233
- strMapper,
1234
- oldSafariMap
1235
- ]],
1236
- [/(webkit|khtml)\/([\w.]+)/i],
1237
- [NAME, VERSION],
1238
- [/(navigator|netscape\d?)\/([-\w.]+)/i],
1239
- [[NAME, "Netscape"], VERSION],
1240
- [/mobile vr; rv:([\w.]+)\).+firefox/i],
1241
- [VERSION, [NAME, `${FIREFOX} Reality`]],
1242
- [
1243
- /ekiohf.+(flow)\/([\w.]+)/i,
1244
- /(swiftfox)/i,
1245
- /(icedragon|iceweasel|camino|chimera|fennec|maemo browser|minimo|conkeror|klar)[/ ]?([\w.+]+)/i,
1246
- /(seamonkey|k-meleon|icecat|iceape|firebird|phoenix|palemoon|basilisk|waterfox)\/([-\w.]+)$/i,
1247
- /(firefox)\/([\w.]+)/i,
1248
- /(mozilla)\/([\w.]+) .+rv:.+gecko\/\d+/i,
1249
- /(polaris|lynx|dillo|icab|doris|amaya|w3m|netsurf|sleipnir|obigo|mosaic|(?:go|ice|up)[. ]?browser)[-/ ]?v?([\w.]+)/i,
1250
- /(links) \(([\w.]+)/i,
1251
- /panasonic;(viera)/i
1252
- ],
1253
- [NAME, VERSION],
1254
- [/(cobalt)\/([\w.]+)/i],
1255
- [NAME, [
1256
- VERSION,
1257
- /master.|lts./,
1258
- ""
1259
- ]]
1260
- ],
1261
- cpu: [
1262
- [/(amd|x(?:(?:86|64)[-_])?|wow|win)64[;)]/i],
1263
- [[ARCHITECTURE, "amd64"]],
1264
- [/(ia32(?=;))/i],
1265
- [[ARCHITECTURE, lowerize]],
1266
- [/((?:i[346]|x)86)[;)]/i],
1267
- [[ARCHITECTURE, "ia32"]],
1268
- [/\b(aarch64|arm(v?8e?l?|_?64))\b/i],
1269
- [[ARCHITECTURE, "arm64"]],
1270
- [/\b(arm(?:v[67])?ht?n?[fl]p?)\b/i],
1271
- [[ARCHITECTURE, "armhf"]],
1272
- [/windows (ce|mobile); ppc;/i],
1273
- [[ARCHITECTURE, "arm"]],
1274
- [/((?:ppc|powerpc)(?:64)?)(?: mac|;|\))/i],
1275
- [[
1276
- ARCHITECTURE,
1277
- /ower/,
1278
- EMPTY,
1279
- lowerize
1280
- ]],
1281
- [/(sun4\w)[;)]/i],
1282
- [[ARCHITECTURE, "sparc"]],
1283
- [/(avr32|ia64(?=;)|68k(?=\))|\barm(?=v(?:[1-7]|[5-7]1)l?|;|eabi)|(?=atmel )avr|(?:irix|mips|sparc)(?:64)?\b|pa-risc)/i],
1284
- [[ARCHITECTURE, lowerize]]
1285
- ],
1286
- device: [
1287
- [/\b(sch-i[89]0\d|shw-m380s|sm-[ptx]\w{2,4}|gt-[pn]\d{2,4}|sgh-t8[56]9|nexus 10)/i],
1288
- [
1289
- MODEL,
1290
- [VENDOR, SAMSUNG],
1291
- [TYPE, TABLET]
1292
- ],
1293
- [
1294
- /\b((?:s[cgp]h|gt|sm)-\w+|sc[g-]?\d+a?|galaxy nexus)/i,
1295
- /samsung[- ]([-\w]+)/i,
1296
- /sec-(sgh\w+)/i
1297
- ],
1298
- [
1299
- MODEL,
1300
- [VENDOR, SAMSUNG],
1301
- [TYPE, MOBILE]
1302
- ],
1303
- [/(?:\/|\()(ip(?:hone|od)[\w, ]*)(?:\/|;)/i],
1304
- [
1305
- MODEL,
1306
- [VENDOR, APPLE],
1307
- [TYPE, MOBILE]
1308
- ],
1309
- [
1310
- /\((ipad);[-\w),; ]+apple/i,
1311
- /applecoremedia\/[\w.]+ \((ipad)/i,
1312
- /\b(ipad)\d\d?,\d\d?[;\]].+ios/i
1313
- ],
1314
- [
1315
- MODEL,
1316
- [VENDOR, APPLE],
1317
- [TYPE, TABLET]
1318
- ],
1319
- [/(macintosh);/i],
1320
- [MODEL, [VENDOR, APPLE]],
1321
- [/\b(sh-?[altvz]?\d\d[a-ekm]?)/i],
1322
- [
1323
- MODEL,
1324
- [VENDOR, SHARP],
1325
- [TYPE, MOBILE]
1326
- ],
1327
- [/\b((?:ag[rs][23]?|bah2?|sht?|btv)-a?[lw]\d{2})\b(?!.+d\/s)/i],
1328
- [
1329
- MODEL,
1330
- [VENDOR, HUAWEI],
1331
- [TYPE, TABLET]
1332
- ],
1333
- [/(?:huawei|honor)([-\w ]+)[;)]/i, /\b(nexus 6p|\w{2,4}e?-[atu]?[ln][\dx][0-359c][adn]?)\b(?!.+d\/s)/i],
1334
- [
1335
- MODEL,
1336
- [VENDOR, HUAWEI],
1337
- [TYPE, MOBILE]
1338
- ],
1339
- [
1340
- /\b(poco[\w ]+|m2\d{3}j\d\d[a-z]{2})(?: bui|\))/i,
1341
- /\b; (\w+) build\/hm\1/i,
1342
- /\b(hm[-_ ]?note?[_ ]?(?:\d\w)?) bui/i,
1343
- /\b(redmi[\-_ ]?[\w ]+)(?: bui|\))/i,
1344
- /oid[^)]+; (m?[12][0-389][01]\w{3,6}[c-y])( bui|; wv|\))/i,
1345
- /\b(mi[-_ ]?(?:a\d|one|one[_ ]plus|note lte|max|cc)?[_ ]?\d?\w?[_ ]?(?:plus|se|lite)?)(?: bui|\))/i
1346
- ],
1347
- [
1348
- [
1349
- MODEL,
1350
- /_/g,
1351
- " "
1352
- ],
1353
- [VENDOR, XIAOMI],
1354
- [TYPE, MOBILE]
1355
- ],
1356
- [/oid[^)]+; (2\d{4}(283|rpbf)[cgl])( bui|\))/i, /\b(mi[-_ ]?pad[\w ]+)(?: bui|\))/i],
1357
- [
1358
- [
1359
- MODEL,
1360
- /_/g,
1361
- " "
1362
- ],
1363
- [VENDOR, XIAOMI],
1364
- [TYPE, TABLET]
1365
- ],
1366
- [/; (\w+) bui.+ oppo/i, /\b(cph[12]\d{3}|p(?:af|c[al]|d\w|e[ar])[mt]\d0|x9007|a101op)\b/i],
1367
- [
1368
- MODEL,
1369
- [VENDOR, "OPPO"],
1370
- [TYPE, MOBILE]
1371
- ],
1372
- [/\b(opd2\d{3}a?) bui/i],
1373
- [
1374
- MODEL,
1375
- [VENDOR, "OPPO"],
1376
- [TYPE, TABLET]
1377
- ],
1378
- [/vivo (\w+)(?: bui|\))/i, /\b(v[12]\d{3}\w?[at])(?: bui|;)/i],
1379
- [
1380
- MODEL,
1381
- [VENDOR, "Vivo"],
1382
- [TYPE, MOBILE]
1383
- ],
1384
- [/\b(rmx[1-3]\d{3})(?: bui|;|\))/i],
1385
- [
1386
- MODEL,
1387
- [VENDOR, "Realme"],
1388
- [TYPE, MOBILE]
1389
- ],
1390
- [
1391
- /\b(milestone|droid(?:[2-4x]| (?:bionic|x2|pro|razr))?:?( 4g)?)\b[\w ]+build\//i,
1392
- /\bmot(?:orola)?[- ](\w*)/i,
1393
- /((?:moto[\w() ]+|xt\d{3,4}|nexus 6)(?= bui|\)))/i
1394
- ],
1395
- [
1396
- MODEL,
1397
- [VENDOR, MOTOROLA],
1398
- [TYPE, MOBILE]
1399
- ],
1400
- [/\b(mz60\d|xoom[2 ]{0,2}) build\//i],
1401
- [
1402
- MODEL,
1403
- [VENDOR, MOTOROLA],
1404
- [TYPE, TABLET]
1405
- ],
1406
- [/((?=lg)?[vl]k-?\d{3}) bui| 3\.[-\w; ]{10}lg?-([06cv9]{3,4})/i],
1407
- [
1408
- MODEL,
1409
- [VENDOR, LG],
1410
- [TYPE, TABLET]
1411
- ],
1412
- [
1413
- /(lm(?:-?f100[nv]?|-[\w.]+)(?= bui|\))|nexus [45])/i,
1414
- /\blg[-e;/ ]+((?!browser|netcast|android tv)\w+)/i,
1415
- /\blg-?(\w+) bui/i
1416
- ],
1417
- [
1418
- MODEL,
1419
- [VENDOR, LG],
1420
- [TYPE, MOBILE]
1421
- ],
1422
- [/(ideatab[-\w ]+)/i, /lenovo ?(s[56]000[-\w]+|tab[\w ]+|yt[-\w]{6}|tb[-\w]{6})/i],
1423
- [
1424
- MODEL,
1425
- [VENDOR, "Lenovo"],
1426
- [TYPE, TABLET]
1427
- ],
1428
- [/(?:maemo|nokia).*(n900|lumia \d+)/i, /nokia[-_ ]?([-\w.]*)/i],
1429
- [
1430
- [
1431
- MODEL,
1432
- /_/g,
1433
- " "
1434
- ],
1435
- [VENDOR, "Nokia"],
1436
- [TYPE, MOBILE]
1437
- ],
1438
- [/(pixel c)\b/i],
1439
- [
1440
- MODEL,
1441
- [VENDOR, GOOGLE],
1442
- [TYPE, TABLET]
1443
- ],
1444
- [/droid.+; (pixel[\daxl ]{0,6})(?: bui|\))/i],
1445
- [
1446
- MODEL,
1447
- [VENDOR, GOOGLE],
1448
- [TYPE, MOBILE]
1449
- ],
1450
- [/droid.+ (a?\d[0-2]{2}so|[c-g]\d{4}|so[-gl]\w+|xq-a\w[4-7][12])(?= bui|\).+chrome\/(?![1-6]?\d\.))/i],
1451
- [
1452
- MODEL,
1453
- [VENDOR, SONY],
1454
- [TYPE, MOBILE]
1455
- ],
1456
- [/sony tablet [ps]/i, /\b(?:sony)?sgp\w+(?: bui|\))/i],
1457
- [
1458
- [MODEL, "Xperia Tablet"],
1459
- [VENDOR, SONY],
1460
- [TYPE, TABLET]
1461
- ],
1462
- [/ (kb2005|in20[12]5|be20[12][59])\b/i, /(?:one)?(?:plus)? (a\d0\d\d)(?: b|\))/i],
1463
- [
1464
- MODEL,
1465
- [VENDOR, "OnePlus"],
1466
- [TYPE, MOBILE]
1467
- ],
1468
- [
1469
- /(alexa)webm/i,
1470
- /(kf[a-z]{2}wi|aeo[c-r]{2})( bui|\))/i,
1471
- /(kf[a-z]+)( bui|\)).+silk\//i
1472
- ],
1473
- [
1474
- MODEL,
1475
- [VENDOR, AMAZON],
1476
- [TYPE, TABLET]
1477
- ],
1478
- [/((?:sd|kf)[0349hijor-uw]+)( bui|\)).+silk\//i],
1479
- [
1480
- [
1481
- MODEL,
1482
- /(.+)/g,
1483
- "Fire Phone $1"
1484
- ],
1485
- [VENDOR, AMAZON],
1486
- [TYPE, MOBILE]
1487
- ],
1488
- [/(playbook);[-\w),; ]+(rim)/i],
1489
- [
1490
- MODEL,
1491
- VENDOR,
1492
- [TYPE, TABLET]
1493
- ],
1494
- [/\b((?:bb[a-f]|st[hv])100-\d)/i, /\(bb10; (\w+)/i],
1495
- [
1496
- MODEL,
1497
- [VENDOR, BLACKBERRY],
1498
- [TYPE, MOBILE]
1499
- ],
1500
- [/(?:\b|asus_)(transfo[prime ]{4,10} \w+|eeepc|slider \w+|nexus 7|padfone|p00[cj])/i],
1501
- [
1502
- MODEL,
1503
- [VENDOR, ASUS],
1504
- [TYPE, TABLET]
1505
- ],
1506
- [/ (z[bes]6[027][012][km][ls]|zenfone \d\w?)\b/i],
1507
- [
1508
- MODEL,
1509
- [VENDOR, ASUS],
1510
- [TYPE, MOBILE]
1511
- ],
1512
- [/(nexus 9)/i],
1513
- [
1514
- MODEL,
1515
- [VENDOR, "HTC"],
1516
- [TYPE, TABLET]
1517
- ],
1518
- [
1519
- /(htc)[-;_ ]{1,2}([\w ]+(?=\)| bui)|\w+)/i,
1520
- /(zte)[- ]([\w ]+?)(?: bui|\/|\))/i,
1521
- /(alcatel|geeksphone|nexian|panasonic(?!;|\.)|sony(?!-bra))[-_ ]?([-\w]*)/i
1522
- ],
1523
- [
1524
- VENDOR,
1525
- [
1526
- MODEL,
1527
- /_/g,
1528
- " "
1529
- ],
1530
- [TYPE, MOBILE]
1531
- ],
1532
- [/droid.+; ([ab][1-7]-?[0178a]\d\d?)/i],
1533
- [
1534
- MODEL,
1535
- [VENDOR, "Acer"],
1536
- [TYPE, TABLET]
1537
- ],
1538
- [/droid.+; (m[1-5] note) bui/i, /\bmz-([-\w]{2,})/i],
1539
- [
1540
- MODEL,
1541
- [VENDOR, "Meizu"],
1542
- [TYPE, MOBILE]
1543
- ],
1544
- [/; ((?:power )?armor[\w ]{0,8})(?: bui|\))/i],
1545
- [
1546
- MODEL,
1547
- [VENDOR, "Ulefone"],
1548
- [TYPE, MOBILE]
1549
- ],
1550
- [
1551
- /(blackberry|benq|palm(?=-)|sonyericsson|acer|asus|dell|meizu|motorola|polytron|infinix|tecno)[-_ ]?([-\w]*)/i,
1552
- /(hp) ([\w ]+\w)/i,
1553
- /(asus)-?(\w+)/i,
1554
- /(microsoft); (lumia[\w ]+)/i,
1555
- /(lenovo)[-_ ]?([-\w]+)/i,
1556
- /(jolla)/i,
1557
- /(oppo) ?([\w ]+) bui/i
1558
- ],
1559
- [
1560
- VENDOR,
1561
- MODEL,
1562
- [TYPE, MOBILE]
1563
- ],
1564
- [
1565
- /(kobo)\s(ereader|touch)/i,
1566
- /(archos) (gamepad2?)/i,
1567
- /(hp).+(touchpad(?!.+tablet)|tablet)/i,
1568
- /(kindle)\/([\w.]+)/i,
1569
- /(nook)[\w ]+build\/(\w+)/i,
1570
- /(dell) (strea[kpr\d ]*[\dko])/i,
1571
- /(le[- ]+pan)[- ]+(\w{1,9}) bui/i,
1572
- /(trinity)[- ]*(t\d{3}) bui/i,
1573
- /(gigaset)[- ]+(q\w{1,9}) bui/i,
1574
- /(vodafone) ([\w ]+)(?:\)| bui)/i
1575
- ],
1576
- [
1577
- VENDOR,
1578
- MODEL,
1579
- [TYPE, TABLET]
1580
- ],
1581
- [/(surface duo)/i],
1582
- [
1583
- MODEL,
1584
- [VENDOR, MICROSOFT],
1585
- [TYPE, TABLET]
1586
- ],
1587
- [/droid [\d.]+; (fp\du?)(?: b|\))/i],
1588
- [
1589
- MODEL,
1590
- [VENDOR, "Fairphone"],
1591
- [TYPE, MOBILE]
1592
- ],
1593
- [/(u304aa)/i],
1594
- [
1595
- MODEL,
1596
- [VENDOR, "AT&T"],
1597
- [TYPE, MOBILE]
1598
- ],
1599
- [/\bsie-(\w*)/i],
1600
- [
1601
- MODEL,
1602
- [VENDOR, "Siemens"],
1603
- [TYPE, MOBILE]
1604
- ],
1605
- [/\b(rct\w+) b/i],
1606
- [
1607
- MODEL,
1608
- [VENDOR, "RCA"],
1609
- [TYPE, TABLET]
1610
- ],
1611
- [/\b(venue[\d ]{2,7}) b/i],
1612
- [
1613
- MODEL,
1614
- [VENDOR, "Dell"],
1615
- [TYPE, TABLET]
1616
- ],
1617
- [/\b(q(?:mv|ta)\w+) b/i],
1618
- [
1619
- MODEL,
1620
- [VENDOR, "Verizon"],
1621
- [TYPE, TABLET]
1622
- ],
1623
- [/\b(?:barnes[& ]+noble |bn[rt])([\w+ ]*) b/i],
1624
- [
1625
- MODEL,
1626
- [VENDOR, "Barnes & Noble"],
1627
- [TYPE, TABLET]
1628
- ],
1629
- [/\b(tm\d{3}\w+) b/i],
1630
- [
1631
- MODEL,
1632
- [VENDOR, "NuVision"],
1633
- [TYPE, TABLET]
1634
- ],
1635
- [/\b(k88) b/i],
1636
- [
1637
- MODEL,
1638
- [VENDOR, "ZTE"],
1639
- [TYPE, TABLET]
1640
- ],
1641
- [/\b(nx\d{3}j) b/i],
1642
- [
1643
- MODEL,
1644
- [VENDOR, "ZTE"],
1645
- [TYPE, MOBILE]
1646
- ],
1647
- [/\b(gen\d{3}) b.+49h/i],
1648
- [
1649
- MODEL,
1650
- [VENDOR, "Swiss"],
1651
- [TYPE, MOBILE]
1652
- ],
1653
- [/\b(zur\d{3}) b/i],
1654
- [
1655
- MODEL,
1656
- [VENDOR, "Swiss"],
1657
- [TYPE, TABLET]
1658
- ],
1659
- [/\b((zeki)?tb.*\b) b/i],
1660
- [
1661
- MODEL,
1662
- [VENDOR, "Zeki"],
1663
- [TYPE, TABLET]
1664
- ],
1665
- [/\b([yr]\d{2}) b/i, /\b(dragon[- ]+touch |dt)(\w{5}) b/i],
1666
- [
1667
- [VENDOR, "Dragon Touch"],
1668
- MODEL,
1669
- [TYPE, TABLET]
1670
- ],
1671
- [/\b(ns-?\w{0,9}) b/i],
1672
- [
1673
- MODEL,
1674
- [VENDOR, "Insignia"],
1675
- [TYPE, TABLET]
1676
- ],
1677
- [/\b((nxa|next)-?\w{0,9}) b/i],
1678
- [
1679
- MODEL,
1680
- [VENDOR, "NextBook"],
1681
- [TYPE, TABLET]
1682
- ],
1683
- [/\b(xtreme_)?(v(1[045]|2[015]|[3469]0|7[05])) b/i],
1684
- [
1685
- [VENDOR, "Voice"],
1686
- MODEL,
1687
- [TYPE, MOBILE]
1688
- ],
1689
- [/\b(lvtel-)?(v1[12]) b/i],
1690
- [
1691
- [VENDOR, "LvTel"],
1692
- MODEL,
1693
- [TYPE, MOBILE]
1694
- ],
1695
- [/\b(ph-1) /i],
1696
- [
1697
- MODEL,
1698
- [VENDOR, "Essential"],
1699
- [TYPE, MOBILE]
1700
- ],
1701
- [/\b(v(100md|700na|7011|917g).*\b) b/i],
1702
- [
1703
- MODEL,
1704
- [VENDOR, "Envizen"],
1705
- [TYPE, TABLET]
1706
- ],
1707
- [/\b(trio[-\w. ]+) b/i],
1708
- [
1709
- MODEL,
1710
- [VENDOR, "MachSpeed"],
1711
- [TYPE, TABLET]
1712
- ],
1713
- [/\btu_(1491) b/i],
1714
- [
1715
- MODEL,
1716
- [VENDOR, "Rotor"],
1717
- [TYPE, TABLET]
1718
- ],
1719
- [/(shield[\w ]+) b/i],
1720
- [
1721
- MODEL,
1722
- [VENDOR, "Nvidia"],
1723
- [TYPE, TABLET]
1724
- ],
1725
- [/(sprint) (\w+)/i],
1726
- [
1727
- VENDOR,
1728
- MODEL,
1729
- [TYPE, MOBILE]
1730
- ],
1731
- [/(kin\.[onetw]{3})/i],
1732
- [
1733
- [
1734
- MODEL,
1735
- /\./g,
1736
- " "
1737
- ],
1738
- [VENDOR, MICROSOFT],
1739
- [TYPE, MOBILE]
1740
- ],
1741
- [/droid.+; (cc6666?|et5[16]|mc[239][23]x?|vc8[03]x?)\)/i],
1742
- [
1743
- MODEL,
1744
- [VENDOR, ZEBRA],
1745
- [TYPE, TABLET]
1746
- ],
1747
- [/droid.+; (ec30|ps20|tc[2-8]\d[kx])\)/i],
1748
- [
1749
- MODEL,
1750
- [VENDOR, ZEBRA],
1751
- [TYPE, MOBILE]
1752
- ],
1753
- [/smart-tv.+(samsung)/i],
1754
- [VENDOR, [TYPE, SMARTTV]],
1755
- [/hbbtv.+maple;(\d+)/i],
1756
- [
1757
- [
1758
- MODEL,
1759
- /^/,
1760
- "SmartTV"
1761
- ],
1762
- [VENDOR, SAMSUNG],
1763
- [TYPE, SMARTTV]
1764
- ],
1765
- [/(nux; netcast.+smarttv|lg (netcast\.tv-201\d|android tv))/i],
1766
- [[VENDOR, LG], [TYPE, SMARTTV]],
1767
- [/(apple) ?tv/i],
1768
- [
1769
- VENDOR,
1770
- [MODEL, `${APPLE} TV`],
1771
- [TYPE, SMARTTV]
1772
- ],
1773
- [/crkey/i],
1774
- [
1775
- [MODEL, `${CHROME}cast`],
1776
- [VENDOR, GOOGLE],
1777
- [TYPE, SMARTTV]
1778
- ],
1779
- [/droid.+aft(\w+)( bui|\))/i],
1780
- [
1781
- MODEL,
1782
- [VENDOR, AMAZON],
1783
- [TYPE, SMARTTV]
1784
- ],
1785
- [/\(dtv[);].+(aquos)/i, /(aquos-tv[\w ]+)\)/i],
1786
- [
1787
- MODEL,
1788
- [VENDOR, SHARP],
1789
- [TYPE, SMARTTV]
1790
- ],
1791
- [/(bravia[\w ]+)( bui|\))/i],
1792
- [
1793
- MODEL,
1794
- [VENDOR, SONY],
1795
- [TYPE, SMARTTV]
1796
- ],
1797
- [/(mitv-\w{5}) bui/i],
1798
- [
1799
- MODEL,
1800
- [VENDOR, XIAOMI],
1801
- [TYPE, SMARTTV]
1802
- ],
1803
- [/Hbbtv.*(technisat) (.*);/i],
1804
- [
1805
- VENDOR,
1806
- MODEL,
1807
- [TYPE, SMARTTV]
1808
- ],
1809
- [/\b(roku)[\dx]*[)/]((?:dvp-)?[\d.]*)/i, /hbbtv\/\d+\.\d+\.\d+ +\([\w+ ]*; *(\w[^;]*);([^;]*)/i],
1810
- [
1811
- [VENDOR, trim],
1812
- [MODEL, trim],
1813
- [TYPE, SMARTTV]
1814
- ],
1815
- [/\b(android tv|smart[- ]?tv|opera tv|tv; rv:)\b/i],
1816
- [[TYPE, SMARTTV]],
1817
- [/(ouya)/i, /(nintendo) ([wids3utch]+)/i],
1818
- [
1819
- VENDOR,
1820
- MODEL,
1821
- [TYPE, CONSOLE]
1822
- ],
1823
- [/droid.+; (shield) bui/i],
1824
- [
1825
- MODEL,
1826
- [VENDOR, "Nvidia"],
1827
- [TYPE, CONSOLE]
1828
- ],
1829
- [/(playstation [345portablevi]+)/i],
1830
- [
1831
- MODEL,
1832
- [VENDOR, SONY],
1833
- [TYPE, CONSOLE]
1834
- ],
1835
- [/\b(xbox(?: one)?(?!; xbox))[); ]/i],
1836
- [
1837
- MODEL,
1838
- [VENDOR, MICROSOFT],
1839
- [TYPE, CONSOLE]
1840
- ],
1841
- [/((pebble))app/i],
1842
- [
1843
- VENDOR,
1844
- MODEL,
1845
- [TYPE, WEARABLE]
1846
- ],
1847
- [/(watch)(?: ?os[,/]|\d,\d\/)[\d.]+/i],
1848
- [
1849
- MODEL,
1850
- [VENDOR, APPLE],
1851
- [TYPE, WEARABLE]
1852
- ],
1853
- [/droid.+; (glass) \d/i],
1854
- [
1855
- MODEL,
1856
- [VENDOR, GOOGLE],
1857
- [TYPE, WEARABLE]
1858
- ],
1859
- [/droid.+; (wt63?0{2,3})\)/i],
1860
- [
1861
- MODEL,
1862
- [VENDOR, ZEBRA],
1863
- [TYPE, WEARABLE]
1864
- ],
1865
- [/(quest( \d| pro)?)/i],
1866
- [
1867
- MODEL,
1868
- [VENDOR, FACEBOOK],
1869
- [TYPE, WEARABLE]
1870
- ],
1871
- [/(tesla)(?: qtcarbrowser|\/[-\w.]+)/i],
1872
- [VENDOR, [TYPE, EMBEDDED]],
1873
- [/(aeobc)\b/i],
1874
- [
1875
- MODEL,
1876
- [VENDOR, AMAZON],
1877
- [TYPE, EMBEDDED]
1878
- ],
1879
- [/droid .+?; ([^;]+?)(?: bui|; wv\)|\) applew).+? mobile safari/i],
1880
- [MODEL, [TYPE, MOBILE]],
1881
- [/droid .+?; ([^;]+?)(?: bui|\) applew).+?(?! mobile) safari/i],
1882
- [MODEL, [TYPE, TABLET]],
1883
- [/\b((tablet|tab)[;/]|focus\/\d(?!.+mobile))/i],
1884
- [[TYPE, TABLET]],
1885
- [/(phone|mobile(?:[;/]| [ \w/.]*safari)|pda(?=.+windows ce))/i],
1886
- [[TYPE, MOBILE]],
1887
- [/(android[-\w. ]{0,9});.+buil/i],
1888
- [MODEL, [VENDOR, "Generic"]]
1889
- ],
1890
- engine: [
1891
- [/windows.+ edge\/([\w.]+)/i],
1892
- [VERSION, [NAME, `${EDGE}HTML`]],
1893
- [/webkit\/537\.36.+chrome\/(?!27)([\w.]+)/i],
1894
- [VERSION, [NAME, "Blink"]],
1895
- [
1896
- /(presto)\/([\w.]+)/i,
1897
- /(webkit|trident|netfront|netsurf|amaya|lynx|w3m|goanna)\/([\w.]+)/i,
1898
- /ekioh(flow)\/([\w.]+)/i,
1899
- /(khtml|tasman|links)[/ ]\(?([\w.]+)/i,
1900
- /(icab)[/ ]([23]\.[\d.]+)/i,
1901
- /\b(libweb)/i
1902
- ],
1903
- [NAME, VERSION],
1904
- [/rv:([\w.]{1,9})\b.+(gecko)/i],
1905
- [VERSION, NAME]
1906
- ],
1907
- os: [
1908
- [/microsoft (windows) (vista|xp)/i],
1909
- [NAME, VERSION],
1910
- [/(windows (?:phone(?: os)?|mobile))[/ ]?([.\w ]*)/i],
1911
- [NAME, [
1912
- VERSION,
1913
- strMapper,
1914
- windowsVersionMap
1915
- ]],
1916
- [
1917
- /windows nt 6\.2; (arm)/i,
1918
- /windows[/ ]?([ntce\d. ]+\w)(?!.+xbox)/i,
1919
- /(?:win(?=[39n])|win 9x )([nt\d.]+)/i
1920
- ],
1921
- [[
1922
- VERSION,
1923
- strMapper,
1924
- windowsVersionMap
1925
- ], [NAME, "Windows"]],
1926
- [
1927
- /ip[honead]{2,4}\b(?:.*os (\w+) like mac|; opera)/i,
1928
- /(?:ios;fbsv\/|iphone.+ios[/ ])([\d.]+)/i,
1929
- /cfnetwork\/.+darwin/i
1930
- ],
1931
- [[
1932
- VERSION,
1933
- /_/g,
1934
- "."
1935
- ], [NAME, "iOS"]],
1936
- [/(mac os x) ?([\w. ]*)/i, /(macintosh|mac_powerpc\b)(?!.+haiku)/i],
1937
- [[NAME, MAC_OS], [
1938
- VERSION,
1939
- /_/g,
1940
- "."
1941
- ]],
1942
- [/droid ([\w.]+)\b.+(android[- ]x86|harmonyos)/i],
1943
- [VERSION, NAME],
1944
- [
1945
- /(android|webos|qnx|bada|rim tablet os|maemo|meego|sailfish)[-/ ]?([\w.]*)/i,
1946
- /(blackberry)\w*\/([\w.]*)/i,
1947
- /(tizen|kaios)[/ ]([\w.]+)/i,
1948
- /\((series40);/i
1949
- ],
1950
- [NAME, VERSION],
1951
- [/\(bb(10);/i],
1952
- [VERSION, [NAME, BLACKBERRY]],
1953
- [/(?:symbian ?os|symbos|s60(?=;)|series60)[-/ ]?([\w.]*)/i],
1954
- [VERSION, [NAME, "Symbian"]],
1955
- [/mozilla\/[\d.]+ \((?:mobile|tablet|tv|mobile; [\w ]+); rv:.+ gecko\/([\w.]+)/i],
1956
- [VERSION, [NAME, `${FIREFOX} OS`]],
1957
- [/web0s;.+rt(tv)/i, /\b(?:hp)?wos(?:browser)?\/([\w.]+)/i],
1958
- [VERSION, [NAME, "webOS"]],
1959
- [/watch(?: ?os[,/]|\d,\d\/)([\d.]+)/i],
1960
- [VERSION, [NAME, "watchOS"]],
1961
- [/crkey\/([\d.]+)/i],
1962
- [VERSION, [NAME, `${CHROME}cast`]],
1963
- [/(cros) \w+(?:\)| ([\w.]+)\b)/i],
1964
- [[NAME, CHROMIUM_OS], VERSION],
1965
- [
1966
- /panasonic;(viera)/i,
1967
- /(netrange)mmh/i,
1968
- /(nettv)\/(\d+\.[\w.]+)/i,
1969
- /(nintendo|playstation) ([wids345portablevuch]+)/i,
1970
- /(xbox); +xbox ([^);]+)/i,
1971
- /\b(joli|palm)\b ?(?:os)?\/?([\w.]*)/i,
1972
- /(mint)[/() ]?(\w*)/i,
1973
- /(mageia|vectorlinux)[; ]/i,
1974
- /([kxln]?ubuntu|debian|suse|opensuse|gentoo|arch(?= linux)|slackware|fedora|mandriva|centos|pclinuxos|red ?hat|zenwalk|linpus|raspbian|plan 9|minix|risc os|contiki|deepin|manjaro|elementary os|sabayon|linspire)(?: gnu\/linux)?(?: enterprise)?(?:[- ]linux)?(?:-gnu)?[-/ ]?(?!chrom|package)([-\w.]*)/i,
1975
- /(hurd|linux) ?([\w.]*)/i,
1976
- /(gnu) ?([\w.]*)/i,
1977
- /\b([-e-hrntopcs]{0,5}bsd|dragonfly)[/ ]?(?!amd|[ix346]{1,2}86)([\w.]*)/i,
1978
- /(haiku) (\w+)/i
1979
- ],
1980
- [NAME, VERSION],
1981
- [/(sunos) ?([\w.]*)/i],
1982
- [[NAME, "Solaris"], VERSION],
1983
- [
1984
- /((?:open)?solaris)[-/ ]?([\w.]*)/i,
1985
- /(aix) ((\d)(?=[.) ])[\w.])*/i,
1986
- /\b(beos|os\/2|amigaos|morphos|openvms|fuchsia|hp-ux|serenityos)/i,
1987
- /(unix) ?([\w.]*)/i
1988
- ],
1989
- [NAME, VERSION]
1990
- ]
1991
- };
1992
- function isExtensionsInput(input) {
1993
- return typeof input === "object" && input !== null;
1994
- }
1995
- Object.freeze(enumerize([
1996
- NAME,
1997
- VERSION,
1998
- MAJOR
1999
- ]));
2000
- Object.freeze(enumerize([ARCHITECTURE]));
2001
- Object.freeze(enumerize([
2002
- MODEL,
2003
- VENDOR,
2004
- TYPE,
2005
- CONSOLE,
2006
- MOBILE,
2007
- SMARTTV,
2008
- TABLET,
2009
- WEARABLE,
2010
- EMBEDDED
2011
- ]));
2012
- Object.freeze(enumerize([NAME, VERSION]));
2013
- Object.freeze(enumerize([NAME, VERSION]));
2014
- function getRuntimeNavigator() {
2015
- if (typeof window === "undefined" || !window.navigator) return void 0;
2016
- return window.navigator;
2017
- }
2018
- function normalizeUA(ua) {
2019
- return ua.length > UA_MAX_LENGTH ? trim(ua, UA_MAX_LENGTH) : ua;
2020
- }
2021
- function createDefaultParserContext(uastring) {
2022
- if (typeof uastring === "string") return {
2023
- ua: normalizeUA(uastring),
2024
- isSelfNavigator: false,
2025
- navigator: void 0,
2026
- userAgentData: void 0,
2027
- regexMap: regexes
2028
- };
2029
- const navigator = getRuntimeNavigator();
2030
- const ua = normalizeUA(navigator && navigator.userAgent ? navigator.userAgent : EMPTY);
2031
- return {
2032
- ua,
2033
- isSelfNavigator: Boolean(navigator && navigator.userAgent === ua),
2034
- navigator,
2035
- userAgentData: navigator?.userAgentData,
2036
- regexMap: regexes
2037
- };
2038
- }
2039
- function createParserContext(uastringOrExtensions, extensions) {
2040
- if (!extensions && !isExtensionsInput(uastringOrExtensions)) return createDefaultParserContext(uastringOrExtensions);
2041
- const navigator = getRuntimeNavigator();
2042
- const uastring = isExtensionsInput(uastringOrExtensions) ? void 0 : uastringOrExtensions;
2043
- const resolvedExtensions = isExtensionsInput(uastringOrExtensions) ? uastringOrExtensions : extensions;
2044
- const ua = normalizeUA(uastring || (navigator && navigator.userAgent ? navigator.userAgent : EMPTY));
2045
- const userAgentData = navigator?.userAgentData;
2046
- return {
2047
- ua,
2048
- isSelfNavigator: Boolean(navigator && navigator.userAgent === ua),
2049
- navigator,
2050
- userAgentData,
2051
- regexMap: resolvedExtensions ? extend(regexes, resolvedExtensions) : regexes
2052
- };
2053
- }
2054
- function parseBrowserFromContext(context) {
2055
- const browser = {
2056
- name: void 0,
2057
- version: void 0,
2058
- major: void 0
2059
- };
2060
- rgxMapper(context.ua, context.regexMap.browser, browser);
2061
- browser.major = majorize(browser.version);
2062
- if (context.isSelfNavigator && context.navigator?.brave && typeof context.navigator.brave.isBrave === "function") browser.name = "Brave";
2063
- return browser;
2064
- }
2065
- function parseCPUFromContext(context) {
2066
- const cpu = { architecture: void 0 };
2067
- rgxMapper(context.ua, context.regexMap.cpu, cpu);
2068
- return cpu;
2069
- }
2070
- function parseDeviceFromContext(context) {
2071
- const device = {
2072
- vendor: void 0,
2073
- model: void 0,
2074
- type: void 0
2075
- };
2076
- rgxMapper(context.ua, context.regexMap.device, device);
2077
- if (context.isSelfNavigator && !device.type && context.userAgentData?.mobile) device.type = MOBILE;
2078
- if (context.isSelfNavigator && device.model === "Macintosh" && context.navigator && typeof context.navigator.standalone !== "undefined" && context.navigator.maxTouchPoints && context.navigator.maxTouchPoints > 2) {
2079
- device.model = "iPad";
2080
- device.type = TABLET;
2081
- }
2082
- return device;
2083
- }
2084
- function parseEngineFromContext(context) {
2085
- const engine = {
2086
- name: void 0,
2087
- version: void 0
2088
- };
2089
- rgxMapper(context.ua, context.regexMap.engine, engine);
2090
- return engine;
2091
- }
2092
- function parseOSFromContext(context) {
2093
- const os = {
2094
- name: void 0,
2095
- version: void 0
2096
- };
2097
- rgxMapper(context.ua, context.regexMap.os, os);
2098
- if (context.isSelfNavigator && !os.name && context.userAgentData?.platform && context.userAgentData.platform !== "Unknown") os.name = context.userAgentData.platform.replace(/chrome os/i, CHROMIUM_OS).replace(/macos/i, MAC_OS);
2099
- return os;
2100
- }
2101
- function parseUA(uastringOrExtensions, extensions) {
2102
- let context;
2103
- let browserCache;
2104
- let engineCache;
2105
- let osCache;
2106
- let deviceCache;
2107
- let cpuCache;
2108
- const getContext = () => context ||= createParserContext(uastringOrExtensions, extensions);
2109
- return {
2110
- get ua() {
2111
- return getContext().ua;
2112
- },
2113
- get browser() {
2114
- browserCache ||= parseBrowserFromContext(getContext());
2115
- return browserCache;
2116
- },
2117
- get engine() {
2118
- engineCache ||= parseEngineFromContext(getContext());
2119
- return engineCache;
2120
- },
2121
- get os() {
2122
- osCache ||= parseOSFromContext(getContext());
2123
- return osCache;
2124
- },
2125
- get device() {
2126
- deviceCache ||= parseDeviceFromContext(getContext());
2127
- return deviceCache;
2128
- },
2129
- get cpu() {
2130
- cpuCache ||= parseCPUFromContext(getContext());
2131
- return cpuCache;
2132
- }
1433
+ serialize: codec.serialize,
1434
+ deserialize: codec.deserialize
2133
1435
  };
2134
1436
  }
2135
1437
  //#endregion
@@ -2176,204 +1478,47 @@ function resolveWsUrl(websocket, metaBaseUrl, loc) {
2176
1478
  }
2177
1479
  function createWsRpcClientMode(options) {
2178
1480
  const { authToken, connectionMeta, metaBaseUrl, events, clientRpc, rpcOptions = {}, wsOptions = {}, callTimeout = 0 } = options;
2179
- let isTrusted = false;
2180
- let status = "connecting";
2181
- let connectionError = null;
2182
- const trustedPromise = promiseWithResolver();
2183
1481
  const url = resolveWsUrl(connectionMeta.websocket, metaBaseUrl ?? "./", location);
2184
- function setStatus(next, error = null) {
2185
- if (error) connectionError = error;
2186
- else if (next === "connected") connectionError = null;
2187
- if (next === status) return;
2188
- const previous = status;
2189
- status = next;
2190
- events.emit("connection:status", next, previous);
2191
- }
2192
- const pending = /* @__PURE__ */ new Set();
2193
- function rejectAllPending(error) {
2194
- for (const entry of [...pending]) entry.reject(error);
2195
- }
2196
- function terminalError() {
2197
- if (status === "disconnected" || status === "error") return new DevframeConnectionError("connection", "[devframe] Not connected to the devframe server", { cause: connectionError ?? void 0 });
2198
- if (status === "unauthorized") return new DevframeConnectionError("auth", "[devframe] Not authorized by the devframe server", { cause: connectionError ?? void 0 });
2199
- return null;
2200
- }
2201
- /**
2202
- * Wrap an in-flight call promise so it settles on server response, on an
2203
- * optional wall-clock timeout, or when the connection drops — never hangs.
2204
- */
2205
- function guardCall(promise, method) {
2206
- return new Promise((resolve, reject) => {
2207
- let settled = false;
2208
- let timer;
2209
- const entry = { reject(error) {
2210
- if (settled) return;
2211
- finish();
2212
- events.emit("rpc:error", error, method);
2213
- reject(error);
2214
- } };
2215
- function finish() {
2216
- settled = true;
2217
- pending.delete(entry);
2218
- if (timer) clearTimeout(timer);
2219
- }
2220
- pending.add(entry);
2221
- if (callTimeout > 0) timer = setTimeout(() => {
2222
- entry.reject(new DevframeConnectionError("timeout", `[devframe] RPC call "${method}" timed out after ${callTimeout}ms`));
2223
- }, callTimeout);
2224
- promise.then((value) => {
2225
- if (settled) return;
2226
- finish();
2227
- resolve(value);
2228
- }, (error) => {
2229
- if (settled) return;
2230
- finish();
2231
- const err = error instanceof Error ? error : new Error(String(error));
2232
- events.emit("rpc:error", err, method);
2233
- reject(err);
2234
- });
2235
- });
2236
- }
2237
- const definitions = /* @__PURE__ */ new Map();
2238
- for (const name of connectionMeta.jsonSerializableMethods ?? []) definitions.set(name, { jsonSerializable: true });
2239
- const serverRpc = createRpcClient(clientRpc.functions, {
2240
- channel: createWsRpcChannel({
1482
+ return createLiveRpcClientMode({
1483
+ transport: "websocket",
1484
+ authToken,
1485
+ connectionMeta,
1486
+ events,
1487
+ clientRpc,
1488
+ rpcOptions,
1489
+ callTimeout,
1490
+ createChannel: (handlers) => createWsRpcChannel({
2241
1491
  url,
2242
1492
  authToken,
2243
- definitions,
1493
+ definitions: handlers.definitions,
2244
1494
  ...wsOptions,
2245
1495
  onConnected(event) {
2246
1496
  wsOptions.onConnected?.(event);
2247
1497
  },
2248
1498
  onError(error) {
2249
- setStatus("error", error);
2250
- events.emit("connection:error", error);
2251
- rejectAllPending(new DevframeConnectionError("connection", "[devframe] Connection to the devframe server failed", { cause: error }));
1499
+ handlers.onError(error);
2252
1500
  wsOptions.onError?.(error);
2253
1501
  },
2254
1502
  onDisconnected(event) {
2255
- if (status !== "error") setStatus("disconnected");
2256
- rejectAllPending(new DevframeConnectionError("connection", "[devframe] Disconnected from the devframe server", { cause: connectionError ?? void 0 }));
1503
+ handlers.onDisconnected();
2257
1504
  wsOptions.onDisconnected?.(event);
2258
1505
  }
2259
- }),
2260
- rpcOptions
2261
- });
2262
- clientRpc.register({
2263
- name: "devframe:auth:revoked",
2264
- type: "event",
2265
- handler: () => {
2266
- isTrusted = false;
2267
- const authError = new DevframeConnectionError("auth", "[devframe] The devframe server revoked this client's trust");
2268
- setStatus("unauthorized", authError);
2269
- events.emit("connection:error", authError);
2270
- rejectAllPending(authError);
2271
- events.emit("rpc:is-trusted:updated", false);
2272
- }
1506
+ })
2273
1507
  });
2274
- let currentAuthToken = authToken;
2275
- function describeUA() {
2276
- const info = parseUA(navigator.userAgent);
2277
- return [
2278
- info.browser.name,
2279
- info.browser.version,
2280
- "|",
2281
- info.os.name,
2282
- info.os.version,
2283
- info.device.type
2284
- ].filter((i) => i).join(" ");
2285
- }
2286
- async function requestTrustWithToken(token) {
2287
- currentAuthToken = token;
2288
- const result = await serverRpc.$call("anonymous:devframe:auth", {
2289
- authToken: token,
2290
- ua: describeUA(),
2291
- origin: location.origin
2292
- });
2293
- isTrusted = result.isTrusted;
2294
- if (isTrusted) {
2295
- trustedPromise.resolve(true);
2296
- setStatus("connected");
2297
- } else {
2298
- const authError = new DevframeConnectionError("auth", "[devframe] The devframe server refused this client's credentials");
2299
- setStatus("unauthorized", authError);
2300
- events.emit("connection:error", authError);
2301
- }
2302
- events.emit("rpc:is-trusted:updated", isTrusted);
2303
- return result.isTrusted;
2304
- }
2305
- async function requestTrustWithCode(code) {
2306
- const token = (await serverRpc.$call("anonymous:devframe:auth:exchange", {
2307
- code,
2308
- ua: describeUA(),
2309
- origin: location.origin
2310
- }))?.authToken ?? null;
2311
- if (token) {
2312
- currentAuthToken = token;
2313
- isTrusted = true;
2314
- trustedPromise.resolve(true);
2315
- setStatus("connected");
2316
- events.emit("rpc:is-trusted:updated", true);
2317
- }
2318
- return token;
2319
- }
2320
- async function requestTrust() {
2321
- if (isTrusted) return true;
2322
- return requestTrustWithToken(currentAuthToken ?? "");
2323
- }
2324
- async function ensureTrusted(timeout = 6e4) {
2325
- if (isTrusted) trustedPromise.resolve(true);
2326
- if (timeout <= 0) return trustedPromise.promise;
2327
- let clear = () => {};
2328
- await Promise.race([trustedPromise.promise.then(clear), new Promise((resolve, reject) => {
2329
- const id = setTimeout(() => {
2330
- reject(/* @__PURE__ */ new Error("[devframe] Timeout waiting for rpc to be trusted"));
2331
- }, timeout);
2332
- clear = () => clearTimeout(id);
2333
- })]);
2334
- return isTrusted;
2335
- }
2336
- return {
2337
- get isTrusted() {
2338
- return isTrusted;
2339
- },
2340
- get status() {
2341
- return status;
2342
- },
2343
- get connectionError() {
2344
- return connectionError;
2345
- },
2346
- requestTrust,
2347
- requestTrustWithToken,
2348
- requestTrustWithCode,
2349
- ensureTrusted,
2350
- call: (...args) => {
2351
- const method = String(args[0]);
2352
- const failFast = terminalError();
2353
- if (failFast) {
2354
- events.emit("rpc:error", failFast, method);
2355
- return Promise.reject(failFast);
2356
- }
2357
- return guardCall(serverRpc.$call(...args), method);
2358
- },
2359
- callEvent: (...args) => {
2360
- const failFast = terminalError();
2361
- if (failFast) {
2362
- events.emit("rpc:error", failFast, String(args[0]));
2363
- return;
2364
- }
2365
- return serverRpc.$callEvent(...args);
2366
- },
2367
- callOptional: (...args) => {
2368
- const method = String(args[0]);
2369
- const failFast = terminalError();
2370
- if (failFast) {
2371
- events.emit("rpc:error", failFast, method);
2372
- return Promise.reject(failFast);
2373
- }
2374
- return guardCall(serverRpc.$callOptional(...args), method);
2375
- }
2376
- };
1508
+ }
1509
+ //#endregion
1510
+ //#region src/utils/scope.ts
1511
+ /** Whether a name is already namespaced (contains a `:` separator). */
1512
+ function isQualifiedName(name) {
1513
+ return name.includes(":");
1514
+ }
1515
+ /**
1516
+ * Prefix a bare name with `<namespace>:`. Names that already contain a
1517
+ * `:` are returned unchanged, so callers can reference another scope's
1518
+ * ids explicitly (e.g. `ctx.rpc.call('other-plugin:fn')`).
1519
+ */
1520
+ function qualifyName(namespace, name) {
1521
+ return isQualifiedName(name) ? name : `${namespace}:${name}`;
2377
1522
  }
2378
1523
  //#endregion
2379
1524
  //#region src/client/settings.ts
@@ -2451,6 +1596,30 @@ function createScopedClientContext(rpc, namespace) {
2451
1596
  }
2452
1597
  //#endregion
2453
1598
  //#region src/client/rpc.ts
1599
+ /**
1600
+ * Resolve the requested `transport` option against what the server
1601
+ * advertises. `'auto'` trusts the advertisement — the server's declared
1602
+ * primary (`backend`), preferring the WebSocket when both endpoints are
1603
+ * present; an explicit `'websocket'` / `'sse'` pins that transport and
1604
+ * throws when the server doesn't advertise it.
1605
+ */
1606
+ function resolveClientTransport(requested, meta) {
1607
+ if (meta.backend === "static") return "static";
1608
+ const hasWebsocket = meta.websocket !== void 0;
1609
+ const hasSse = meta.sse !== void 0;
1610
+ if (requested === "websocket") {
1611
+ if (!hasWebsocket) throw new Error("[devframe] transport: 'websocket' was requested, but this server does not advertise a WebSocket endpoint");
1612
+ return "websocket";
1613
+ }
1614
+ if (requested === "sse") {
1615
+ if (!hasSse) throw new Error("[devframe] transport: 'sse' was requested, but this server does not advertise an SSE endpoint");
1616
+ return "sse";
1617
+ }
1618
+ if (meta.backend === "sse" && hasSse) return "sse";
1619
+ if (hasWebsocket) return "websocket";
1620
+ if (hasSse) return "sse";
1621
+ throw new Error("[devframe] This server advertises no RPC transport (backend \"none\") — nothing to connect to. Enable the WebSocket or SSE endpoint on the server, or use its static/MCP surfaces instead.");
1622
+ }
2454
1623
  async function getDevframeRpcClient(options = {}) {
2455
1624
  const { baseURL = "./", rpcOptions = {}, cacheOptions = false } = options;
2456
1625
  const events = createEventEmitter();
@@ -2480,7 +1649,7 @@ async function getDevframeRpcClient(options = {}) {
2480
1649
  }
2481
1650
  throw new Error(`Failed to load ${path} from ${candidates.join(", ")}`, { cause: errors });
2482
1651
  }
2483
- const mode = connectionMeta.backend === "static" ? await createStaticRpcClientMode({ fetchJsonFromBases }) : createWsRpcClientMode({
1652
+ const liveModeOptions = {
2484
1653
  authToken,
2485
1654
  connectionMeta,
2486
1655
  metaBaseUrl,
@@ -2497,7 +1666,14 @@ async function getDevframeRpcClient(options = {}) {
2497
1666
  cacheManager.apply(req, res);
2498
1667
  } else await next(req);
2499
1668
  }
2500
- },
1669
+ }
1670
+ };
1671
+ const transport = resolveClientTransport(options.transport ?? "auto", connectionMeta);
1672
+ const mode = transport === "static" ? await createStaticRpcClientMode({ fetchJsonFromBases }) : transport === "sse" ? createSseRpcClientMode({
1673
+ ...liveModeOptions,
1674
+ sseOptions: options.sseOptions
1675
+ }) : createWsRpcClientMode({
1676
+ ...liveModeOptions,
2501
1677
  wsOptions: options.wsOptions
2502
1678
  });
2503
1679
  let authChannel;
@@ -2523,6 +1699,9 @@ async function getDevframeRpcClient(options = {}) {
2523
1699
  get connectionError() {
2524
1700
  return mode.connectionError;
2525
1701
  },
1702
+ get transport() {
1703
+ return mode.transport ?? transport;
1704
+ },
2526
1705
  get connection() {
2527
1706
  return connection;
2528
1707
  },
@@ -2560,7 +1739,8 @@ async function getDevframeRpcClient(options = {}) {
2560
1739
  sharedState: void 0,
2561
1740
  streaming: void 0,
2562
1741
  cacheManager,
2563
- scope: void 0
1742
+ scope: void 0,
1743
+ close: () => mode.close?.()
2564
1744
  };
2565
1745
  rpc.sharedState = createRpcSharedStateClientHost(rpc);
2566
1746
  rpc.streaming = createRpcStreamingClientHost(rpc);
@@ -2614,4 +1794,4 @@ async function getDevframeRpcClient(options = {}) {
2614
1794
  //#region src/client/index.ts
2615
1795
  const connectDevframe = getDevframeRpcClient;
2616
1796
  //#endregion
2617
- export { DevframeConnectionError, authenticateWithUrlOtp, connectDevframe, consumeOtpFromUrl, createClientSettings, createRpcStreamingClientHost, createScopedClientContext, getDevframeConnection, getDevframeRpcClient, isCallableStatus, readOtpFromUrl, registerDevframeViewerOrigin, resolveWsUrl, setupDevframeConnection };
1797
+ export { DevframeConnectionError, authenticateWithUrlOtp, connectDevframe, consumeOtpFromUrl, createClientSettings, createRpcStreamingClientHost, createScopedClientContext, getDevframeConnection, getDevframeRpcClient, isCallableStatus, readOtpFromUrl, registerDevframeViewerOrigin, resolveClientTransport, resolveSseUrl, resolveWsUrl, setupDevframeConnection };