zero-query 1.0.9 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2 -0
  3. package/cli/args.js +33 -33
  4. package/cli/commands/build-api.js +443 -0
  5. package/cli/commands/build.js +254 -216
  6. package/cli/commands/bundle.js +1228 -1183
  7. package/cli/commands/create.js +137 -121
  8. package/cli/commands/dev/devtools/index.js +56 -56
  9. package/cli/commands/dev/devtools/js/components.js +49 -49
  10. package/cli/commands/dev/devtools/js/core.js +423 -423
  11. package/cli/commands/dev/devtools/js/elements.js +421 -421
  12. package/cli/commands/dev/devtools/js/network.js +166 -166
  13. package/cli/commands/dev/devtools/js/performance.js +73 -73
  14. package/cli/commands/dev/devtools/js/router.js +105 -105
  15. package/cli/commands/dev/devtools/js/source.js +132 -132
  16. package/cli/commands/dev/devtools/js/stats.js +35 -35
  17. package/cli/commands/dev/devtools/js/tabs.js +79 -79
  18. package/cli/commands/dev/devtools/panel.html +95 -95
  19. package/cli/commands/dev/devtools/styles.css +244 -244
  20. package/cli/commands/dev/index.js +107 -107
  21. package/cli/commands/dev/logger.js +75 -75
  22. package/cli/commands/dev/overlay.js +858 -858
  23. package/cli/commands/dev/server.js +220 -167
  24. package/cli/commands/dev/validator.js +94 -94
  25. package/cli/commands/dev/watcher.js +172 -172
  26. package/cli/help.js +114 -112
  27. package/cli/index.js +52 -52
  28. package/cli/scaffold/default/LICENSE +21 -21
  29. package/cli/scaffold/default/app/app.js +207 -207
  30. package/cli/scaffold/default/app/components/about.js +201 -201
  31. package/cli/scaffold/default/app/components/api-demo.js +143 -143
  32. package/cli/scaffold/default/app/components/contact-card.js +231 -231
  33. package/cli/scaffold/default/app/components/contacts/contacts.css +706 -706
  34. package/cli/scaffold/default/app/components/contacts/contacts.html +200 -200
  35. package/cli/scaffold/default/app/components/contacts/contacts.js +196 -196
  36. package/cli/scaffold/default/app/components/counter.js +127 -127
  37. package/cli/scaffold/default/app/components/home.js +249 -249
  38. package/cli/scaffold/default/app/components/not-found.js +16 -16
  39. package/cli/scaffold/default/app/components/playground/playground.css +115 -115
  40. package/cli/scaffold/default/app/components/playground/playground.html +161 -161
  41. package/cli/scaffold/default/app/components/playground/playground.js +116 -116
  42. package/cli/scaffold/default/app/components/todos.js +225 -225
  43. package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -97
  44. package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -146
  45. package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -280
  46. package/cli/scaffold/default/app/routes.js +15 -15
  47. package/cli/scaffold/default/app/store.js +101 -101
  48. package/cli/scaffold/default/global.css +552 -552
  49. package/cli/scaffold/default/index.html +99 -99
  50. package/cli/scaffold/minimal/app/app.js +85 -85
  51. package/cli/scaffold/minimal/app/components/about.js +68 -68
  52. package/cli/scaffold/minimal/app/components/counter.js +122 -122
  53. package/cli/scaffold/minimal/app/components/home.js +68 -68
  54. package/cli/scaffold/minimal/app/components/not-found.js +16 -16
  55. package/cli/scaffold/minimal/app/routes.js +9 -9
  56. package/cli/scaffold/minimal/app/store.js +36 -36
  57. package/cli/scaffold/minimal/global.css +300 -300
  58. package/cli/scaffold/minimal/index.html +44 -44
  59. package/cli/scaffold/ssr/app/app.js +41 -41
  60. package/cli/scaffold/ssr/app/components/about.js +55 -55
  61. package/cli/scaffold/ssr/app/components/blog/index.js +65 -65
  62. package/cli/scaffold/ssr/app/components/blog/post.js +86 -86
  63. package/cli/scaffold/ssr/app/components/home.js +37 -37
  64. package/cli/scaffold/ssr/app/components/not-found.js +15 -15
  65. package/cli/scaffold/ssr/app/routes.js +8 -8
  66. package/cli/scaffold/ssr/global.css +228 -228
  67. package/cli/scaffold/ssr/index.html +37 -37
  68. package/cli/scaffold/ssr/package.json +8 -8
  69. package/cli/scaffold/ssr/server/data/posts.js +144 -144
  70. package/cli/scaffold/ssr/server/index.js +213 -213
  71. package/cli/scaffold/webrtc/app/app.js +11 -0
  72. package/cli/scaffold/webrtc/app/components/video-room.js +295 -0
  73. package/cli/scaffold/webrtc/app/lib/room.js +252 -0
  74. package/cli/scaffold/webrtc/assets/.gitkeep +0 -0
  75. package/cli/scaffold/webrtc/global.css +250 -0
  76. package/cli/scaffold/webrtc/index.html +21 -0
  77. package/cli/utils.js +305 -287
  78. package/dist/API.md +7264 -0
  79. package/dist/zquery.dist.zip +0 -0
  80. package/dist/zquery.js +10313 -6252
  81. package/dist/zquery.min.js +8 -601
  82. package/index.d.ts +570 -365
  83. package/index.js +311 -232
  84. package/package.json +76 -69
  85. package/src/component.js +1709 -1454
  86. package/src/core.js +921 -921
  87. package/src/diff.js +497 -497
  88. package/src/errors.js +209 -209
  89. package/src/expression.js +922 -922
  90. package/src/http.js +242 -242
  91. package/src/package.json +1 -1
  92. package/src/reactive.js +255 -254
  93. package/src/router.js +843 -773
  94. package/src/ssr.js +418 -418
  95. package/src/store.js +318 -272
  96. package/src/utils.js +515 -515
  97. package/src/webrtc/e2ee.js +351 -0
  98. package/src/webrtc/errors.js +116 -0
  99. package/src/webrtc/ice.js +301 -0
  100. package/src/webrtc/index.js +131 -0
  101. package/src/webrtc/joinToken.js +119 -0
  102. package/src/webrtc/observe.js +172 -0
  103. package/src/webrtc/peer.js +351 -0
  104. package/src/webrtc/reactive.js +268 -0
  105. package/src/webrtc/room.js +625 -0
  106. package/src/webrtc/sdp.js +302 -0
  107. package/src/webrtc/sfu/index.js +43 -0
  108. package/src/webrtc/sfu/livekit.js +131 -0
  109. package/src/webrtc/sfu/mediasoup.js +150 -0
  110. package/src/webrtc/signaling.js +373 -0
  111. package/src/webrtc/turn.js +237 -0
  112. package/tests/_helpers/webrtcFakes.js +289 -0
  113. package/tests/audit.test.js +4158 -4158
  114. package/tests/cli.test.js +1136 -1023
  115. package/tests/compare.test.js +497 -0
  116. package/tests/component.test.js +3969 -3938
  117. package/tests/core.test.js +1910 -1910
  118. package/tests/dev-server.test.js +489 -0
  119. package/tests/diff.test.js +1416 -1416
  120. package/tests/docs.test.js +1664 -0
  121. package/tests/electron-features.test.js +864 -0
  122. package/tests/errors.test.js +619 -619
  123. package/tests/expression.test.js +1056 -1056
  124. package/tests/http.test.js +648 -648
  125. package/tests/reactive.test.js +819 -819
  126. package/tests/router.test.js +2327 -2327
  127. package/tests/ssr.test.js +870 -870
  128. package/tests/store.test.js +830 -830
  129. package/tests/test-minifier.js +153 -153
  130. package/tests/test-ssr.js +27 -27
  131. package/tests/utils.test.js +1377 -1377
  132. package/tests/webrtc/e2ee.test.js +283 -0
  133. package/tests/webrtc/ice.test.js +202 -0
  134. package/tests/webrtc/joinToken.test.js +89 -0
  135. package/tests/webrtc/observe.test.js +111 -0
  136. package/tests/webrtc/peer.test.js +373 -0
  137. package/tests/webrtc/reactive.test.js +235 -0
  138. package/tests/webrtc/room.test.js +406 -0
  139. package/tests/webrtc/sdp.test.js +151 -0
  140. package/tests/webrtc/sfu-livekit.test.js +119 -0
  141. package/tests/webrtc/sfu.test.js +160 -0
  142. package/tests/webrtc/signaling.test.js +251 -0
  143. package/tests/webrtc/turn.test.js +256 -0
  144. package/types/collection.d.ts +383 -383
  145. package/types/component.d.ts +186 -186
  146. package/types/errors.d.ts +135 -135
  147. package/types/http.d.ts +92 -92
  148. package/types/misc.d.ts +201 -201
  149. package/types/reactive.d.ts +98 -98
  150. package/types/router.d.ts +190 -190
  151. package/types/ssr.d.ts +102 -102
  152. package/types/store.d.ts +146 -145
  153. package/types/utils.d.ts +245 -245
  154. package/types/webrtc.d.ts +653 -0
@@ -0,0 +1,351 @@
1
+ /**
2
+ * src/webrtc/e2ee.js - SFrame-style end-to-end encryption
3
+ *
4
+ * Provides a small AES-GCM SFrame implementation suitable for
5
+ * `RTCRtpScriptTransform` / Encoded Transforms wiring. Frames are wrapped
6
+ * as `[1-byte epoch][12-byte IV][N-byte ciphertext+tag]` so receivers can
7
+ * route a frame to the correct key without an out-of-band signal.
8
+ *
9
+ * Key derivation: PBKDF2(passphrase, salt) -> HKDF -> AES-GCM-128. The
10
+ * salt is intended to be a room id so two clients of the same room with
11
+ * the same passphrase derive the same key.
12
+ *
13
+ * The actual `RTCRtpScriptTransform` wiring lives in `attachE2ee()`; the
14
+ * core encryptFrame / decryptFrame helpers are pure and run anywhere
15
+ * WebCrypto is available (browsers, jsdom, Node 18+).
16
+ */
17
+
18
+ import { E2eeError } from './errors.js';
19
+
20
+
21
+ const AES_GCM_KEY_BITS = 128;
22
+ const IV_BYTES = 12;
23
+ const HEADER_BYTES = 1 + IV_BYTES; // 1-byte epoch + 12-byte IV
24
+ const PBKDF2_ITERATIONS = 100_000;
25
+ const HKDF_INFO = new TextEncoder().encode('zquery-sframe-v1');
26
+
27
+
28
+ /**
29
+ * @returns {SubtleCrypto}
30
+ */
31
+ function _subtle() {
32
+ const subtle = typeof crypto !== 'undefined' && crypto.subtle ? crypto.subtle : null;
33
+ if (!subtle) {
34
+ throw new E2eeError('WebCrypto SubtleCrypto is not available in this environment', {
35
+ code: 'ZQ_WEBRTC_E2EE_NO_WEBCRYPTO',
36
+ });
37
+ }
38
+ return subtle;
39
+ }
40
+
41
+ function _randomBytes(n) {
42
+ const buf = new Uint8Array(n);
43
+ if (typeof crypto === 'undefined' || typeof crypto.getRandomValues !== 'function') {
44
+ throw new E2eeError('crypto.getRandomValues is not available in this environment', {
45
+ code: 'ZQ_WEBRTC_E2EE_NO_RANDOM',
46
+ });
47
+ }
48
+ crypto.getRandomValues(buf);
49
+ return buf;
50
+ }
51
+
52
+ function _asUint8(input) {
53
+ if (input instanceof Uint8Array) return input;
54
+ if (input instanceof ArrayBuffer) return new Uint8Array(input);
55
+ if (ArrayBuffer.isView(input)) return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
56
+ // Handle cross-realm ArrayBuffer (jsdom / VM contexts).
57
+ if (input && typeof input === 'object' && typeof input.byteLength === 'number'
58
+ && Object.prototype.toString.call(input) === '[object ArrayBuffer]') {
59
+ return new Uint8Array(input);
60
+ }
61
+ throw new E2eeError('expected a BufferSource (Uint8Array | ArrayBuffer | typed array)', {
62
+ code: 'ZQ_WEBRTC_E2EE_BAD_INPUT',
63
+ });
64
+ }
65
+
66
+
67
+ /**
68
+ * Derive an AES-GCM-128 SFrame key from a passphrase + salt (typically
69
+ * the room id). Two clients calling this with the same inputs produce
70
+ * the same key.
71
+ *
72
+ * @param {string} passphrase
73
+ * @param {string} salt
74
+ * @returns {Promise<CryptoKey>}
75
+ */
76
+ export async function deriveSFrameKey(passphrase, salt) {
77
+ if (typeof passphrase !== 'string' || !passphrase) {
78
+ throw new E2eeError('deriveSFrameKey: passphrase must be a non-empty string', {
79
+ code: 'ZQ_WEBRTC_E2EE_BAD_PASSPHRASE',
80
+ });
81
+ }
82
+ if (typeof salt !== 'string' || !salt) {
83
+ throw new E2eeError('deriveSFrameKey: salt must be a non-empty string', {
84
+ code: 'ZQ_WEBRTC_E2EE_BAD_SALT',
85
+ });
86
+ }
87
+ const subtle = _subtle();
88
+ const enc = new TextEncoder();
89
+ const baseKey = await subtle.importKey(
90
+ 'raw', enc.encode(passphrase), { name: 'PBKDF2' }, false, ['deriveBits']
91
+ );
92
+ const pbkdfBits = await subtle.deriveBits(
93
+ { name: 'PBKDF2', hash: 'SHA-256', salt: enc.encode(salt), iterations: PBKDF2_ITERATIONS },
94
+ baseKey,
95
+ 256
96
+ );
97
+ const hkdfKey = await subtle.importKey(
98
+ 'raw', pbkdfBits, { name: 'HKDF' }, false, ['deriveKey']
99
+ );
100
+ return subtle.deriveKey(
101
+ { name: 'HKDF', hash: 'SHA-256', salt: enc.encode(salt), info: HKDF_INFO },
102
+ hkdfKey,
103
+ { name: 'AES-GCM', length: AES_GCM_KEY_BITS },
104
+ false,
105
+ ['encrypt', 'decrypt']
106
+ );
107
+ }
108
+
109
+
110
+ /**
111
+ * Generate a fresh random AES-GCM-128 SFrame key.
112
+ *
113
+ * @returns {Promise<CryptoKey>}
114
+ */
115
+ export async function generateSFrameKey() {
116
+ return _subtle().generateKey(
117
+ { name: 'AES-GCM', length: AES_GCM_KEY_BITS },
118
+ true,
119
+ ['encrypt', 'decrypt']
120
+ );
121
+ }
122
+
123
+
124
+ /**
125
+ * Holds the current key + epoch for an SFrame transform pair. Receivers
126
+ * keep a sliding window of accepted epochs (old keys retained briefly so
127
+ * in-flight frames decode after a rotation; oldest evicted on each
128
+ * `setKey`).
129
+ */
130
+ export class SFrameContext {
131
+ /**
132
+ * @param {{maxEpochs?: number}} [opts]
133
+ */
134
+ constructor(opts) {
135
+ const max = opts && Number.isFinite(opts.maxEpochs) ? opts.maxEpochs : 4;
136
+ /** @private */ this._keys = new Map(); // epoch -> CryptoKey
137
+ /** @private */ this._maxEpochs = Math.max(1, max);
138
+ /** @public */ this.currentEpoch = 0;
139
+ }
140
+
141
+ /**
142
+ * Install `key` for `epoch` and mark it as the encrypt key. Evicts the
143
+ * oldest epoch when more than `maxEpochs` are tracked.
144
+ *
145
+ * @param {number} epoch
146
+ * @param {CryptoKey} key
147
+ */
148
+ setKey(epoch, key) {
149
+ if (!Number.isInteger(epoch) || epoch < 0 || epoch > 255) {
150
+ throw new E2eeError('SFrameContext.setKey: epoch must be an integer in [0, 255]', {
151
+ code: 'ZQ_WEBRTC_E2EE_BAD_EPOCH',
152
+ });
153
+ }
154
+ if (!key) {
155
+ throw new E2eeError('SFrameContext.setKey: key required', {
156
+ code: 'ZQ_WEBRTC_E2EE_BAD_KEY',
157
+ });
158
+ }
159
+ this._keys.set(epoch, key);
160
+ this.currentEpoch = epoch;
161
+ while (this._keys.size > this._maxEpochs) {
162
+ const oldest = this._keys.keys().next().value;
163
+ this._keys.delete(oldest);
164
+ }
165
+ }
166
+
167
+ /** Drop a previously installed epoch (e.g. forward-secret evict on peer-leave). */
168
+ removeEpoch(epoch) {
169
+ this._keys.delete(epoch);
170
+ }
171
+
172
+ /** Return the key for `epoch`, or `null` if unknown / evicted. */
173
+ getKey(epoch) {
174
+ return this._keys.get(epoch) || null;
175
+ }
176
+
177
+ /** Number of epochs currently tracked. */
178
+ get epochCount() {
179
+ return this._keys.size;
180
+ }
181
+ }
182
+
183
+
184
+ /**
185
+ * Encrypt one frame using the context's current epoch key.
186
+ *
187
+ * Output layout: `[1-byte epoch][12-byte IV][ciphertext + 16-byte tag]`.
188
+ *
189
+ * @param {SFrameContext} ctx
190
+ * @param {BufferSource} payload
191
+ * @returns {Promise<Uint8Array>}
192
+ */
193
+ export async function encryptFrame(ctx, payload) {
194
+ if (!(ctx instanceof SFrameContext)) {
195
+ throw new E2eeError('encryptFrame: ctx must be an SFrameContext', {
196
+ code: 'ZQ_WEBRTC_E2EE_BAD_CTX',
197
+ });
198
+ }
199
+ const key = ctx.getKey(ctx.currentEpoch);
200
+ if (!key) {
201
+ throw new E2eeError(`encryptFrame: no key installed for epoch ${ctx.currentEpoch}`, {
202
+ code: 'ZQ_WEBRTC_E2EE_NO_KEY',
203
+ context: { epoch: ctx.currentEpoch },
204
+ });
205
+ }
206
+ const plain = _asUint8(payload);
207
+ const iv = _randomBytes(IV_BYTES);
208
+ const cipher = new Uint8Array(await _subtle().encrypt({ name: 'AES-GCM', iv }, key, plain));
209
+ const out = new Uint8Array(HEADER_BYTES + cipher.byteLength);
210
+ out[0] = ctx.currentEpoch & 0xff;
211
+ out.set(iv, 1);
212
+ out.set(cipher, HEADER_BYTES);
213
+ return out;
214
+ }
215
+
216
+
217
+ /**
218
+ * Decrypt one frame produced by `encryptFrame`. Returns the plaintext as a
219
+ * `Uint8Array`. Throws `E2eeError` if the epoch is unknown or AES-GCM
220
+ * authentication fails.
221
+ *
222
+ * @param {SFrameContext} ctx
223
+ * @param {BufferSource} frame
224
+ * @returns {Promise<Uint8Array>}
225
+ */
226
+ export async function decryptFrame(ctx, frame) {
227
+ if (!(ctx instanceof SFrameContext)) {
228
+ throw new E2eeError('decryptFrame: ctx must be an SFrameContext', {
229
+ code: 'ZQ_WEBRTC_E2EE_BAD_CTX',
230
+ });
231
+ }
232
+ const bytes = _asUint8(frame);
233
+ if (bytes.byteLength <= HEADER_BYTES) {
234
+ throw new E2eeError('decryptFrame: frame too short for SFrame header', {
235
+ code: 'ZQ_WEBRTC_E2EE_SHORT_FRAME',
236
+ });
237
+ }
238
+ const epoch = bytes[0];
239
+ const key = ctx.getKey(epoch);
240
+ if (!key) {
241
+ throw new E2eeError(`decryptFrame: no key for epoch ${epoch}`, {
242
+ code: 'ZQ_WEBRTC_E2EE_UNKNOWN_EPOCH',
243
+ context: { epoch },
244
+ });
245
+ }
246
+ const iv = bytes.subarray(1, HEADER_BYTES);
247
+ const cipher = bytes.subarray(HEADER_BYTES);
248
+ let plain;
249
+ try {
250
+ plain = new Uint8Array(await _subtle().decrypt({ name: 'AES-GCM', iv }, key, cipher));
251
+ } catch (err) {
252
+ throw new E2eeError('decryptFrame: AES-GCM authentication failed', {
253
+ code: 'ZQ_WEBRTC_E2EE_AUTH_FAILED',
254
+ cause: err instanceof Error ? err : undefined,
255
+ context: { epoch },
256
+ });
257
+ }
258
+ return plain;
259
+ }
260
+
261
+
262
+ /**
263
+ * Attach SFrame encrypt/decrypt transforms to every existing and future
264
+ * RTP sender/receiver on `pc`. Uses `RTCRtpScriptTransform` where
265
+ * available, falls back to the legacy `createEncodedStreams()` API on
266
+ * older engines.
267
+ *
268
+ * @param {RTCPeerConnection} pc
269
+ * @param {SFrameContext} ctx
270
+ * @returns {{refresh(): void, detach(): void}}
271
+ */
272
+ export function attachE2ee(pc, ctx) {
273
+ if (!pc || typeof pc.getSenders !== 'function' || typeof pc.getReceivers !== 'function') {
274
+ throw new E2eeError('attachE2ee: pc must look like an RTCPeerConnection', {
275
+ code: 'ZQ_WEBRTC_E2EE_BAD_PC',
276
+ });
277
+ }
278
+ if (!(ctx instanceof SFrameContext)) {
279
+ throw new E2eeError('attachE2ee: ctx must be an SFrameContext', {
280
+ code: 'ZQ_WEBRTC_E2EE_BAD_CTX',
281
+ });
282
+ }
283
+
284
+ const wired = new WeakSet();
285
+ let detached = false;
286
+
287
+ function wireSender(sender) {
288
+ if (detached || wired.has(sender)) return;
289
+ wired.add(sender);
290
+ const stream = _maybeEncodedStreams(sender);
291
+ if (!stream) return;
292
+ const transformer = new TransformStream({
293
+ async transform(chunk, controller) {
294
+ try {
295
+ const payload = _asUint8(chunk.data);
296
+ const out = await encryptFrame(ctx, payload);
297
+ chunk.data = out.buffer;
298
+ controller.enqueue(chunk);
299
+ } catch (_) {
300
+ // drop frame on encrypt failure (no key yet, etc.)
301
+ }
302
+ },
303
+ });
304
+ stream.readable.pipeThrough(transformer).pipeTo(stream.writable).catch(() => {});
305
+ }
306
+
307
+ function wireReceiver(receiver) {
308
+ if (detached || wired.has(receiver)) return;
309
+ wired.add(receiver);
310
+ const stream = _maybeEncodedStreams(receiver);
311
+ if (!stream) return;
312
+ const transformer = new TransformStream({
313
+ async transform(chunk, controller) {
314
+ try {
315
+ const payload = _asUint8(chunk.data);
316
+ const out = await decryptFrame(ctx, payload);
317
+ chunk.data = out.buffer;
318
+ controller.enqueue(chunk);
319
+ } catch (_) {
320
+ // drop undecryptable frame
321
+ }
322
+ },
323
+ });
324
+ stream.readable.pipeThrough(transformer).pipeTo(stream.writable).catch(() => {});
325
+ }
326
+
327
+ function refresh() {
328
+ if (detached) return;
329
+ for (const s of pc.getSenders()) wireSender(s);
330
+ for (const r of pc.getReceivers()) wireReceiver(r);
331
+ }
332
+
333
+ refresh();
334
+
335
+ return {
336
+ refresh,
337
+ detach() { detached = true; },
338
+ };
339
+ }
340
+
341
+
342
+ function _maybeEncodedStreams(senderOrReceiver) {
343
+ if (typeof senderOrReceiver.createEncodedStreams === 'function') {
344
+ try {
345
+ return senderOrReceiver.createEncodedStreams();
346
+ } catch (_) {
347
+ return null;
348
+ }
349
+ }
350
+ return null;
351
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * src/webrtc/errors.js - WebRTC error family
3
+ *
4
+ * All WebRTC-specific errors derive from `WebRtcError`, which itself
5
+ * derives from `ZQueryError` so they participate in the same
6
+ * `$.onError(handler)` reporting pipeline as the rest of the library.
7
+ *
8
+ * Each subclass has a sensible default `code` string; callers may override
9
+ * via the constructor's options bag (`{ code, context, cause }`). The codes
10
+ * intentionally mirror the families used by the matching `@zero-server/webrtc`
11
+ * package so cross-stack error reporting stays consistent.
12
+ */
13
+
14
+ import { ZQueryError } from '../errors.js';
15
+
16
+
17
+ /**
18
+ * Base class for every WebRTC client error. Extends `ZQueryError` so it
19
+ * shows up in `$.onError(handler)` like any other library error.
20
+ *
21
+ * throw new WebRtcError('peer connection failed');
22
+ * throw new WebRtcError('peer connection failed', { code: 'PC_FAILED', context: { peerId } });
23
+ */
24
+ export class WebRtcError extends ZQueryError {
25
+ /**
26
+ * @param {string} message - human-readable description.
27
+ * @param {object} [options]
28
+ * @param {string} [options.code] - stable error code (defaults per subclass).
29
+ * @param {object} [options.context] - extra structured context.
30
+ * @param {Error} [options.cause] - original error, if any.
31
+ */
32
+ constructor(message, options = {}) {
33
+ const code = options.code || 'ZQ_WEBRTC';
34
+ const context = options.context || {};
35
+ super(code, message, context, options.cause);
36
+ this.name = 'WebRtcError';
37
+ }
38
+ }
39
+
40
+
41
+ /** Signaling-channel error (WebSocket transport, protocol framing, etc.). */
42
+ export class SignalingError extends WebRtcError {
43
+ /**
44
+ * @param {string} message
45
+ * @param {object} [options] - same shape as `WebRtcError`.
46
+ */
47
+ constructor(message, options = {}) {
48
+ super(message, { code: options.code || 'ZQ_WEBRTC_SIGNALING', context: options.context, cause: options.cause });
49
+ this.name = 'SignalingError';
50
+ }
51
+ }
52
+
53
+
54
+ /** ICE candidate / gathering / connectivity error. */
55
+ export class IceError extends WebRtcError {
56
+ /**
57
+ * @param {string} message
58
+ * @param {object} [options]
59
+ */
60
+ constructor(message, options = {}) {
61
+ super(message, { code: options.code || 'ZQ_WEBRTC_ICE', context: options.context, cause: options.cause });
62
+ this.name = 'IceError';
63
+ }
64
+ }
65
+
66
+
67
+ /** SDP parse / validate / mangle error. */
68
+ export class SdpError extends WebRtcError {
69
+ /**
70
+ * @param {string} message
71
+ * @param {object} [options]
72
+ */
73
+ constructor(message, options = {}) {
74
+ super(message, { code: options.code || 'ZQ_WEBRTC_SDP', context: options.context, cause: options.cause });
75
+ this.name = 'SdpError';
76
+ }
77
+ }
78
+
79
+
80
+ /** TURN credential fetch / refresh error. */
81
+ export class TurnError extends WebRtcError {
82
+ /**
83
+ * @param {string} message
84
+ * @param {object} [options]
85
+ */
86
+ constructor(message, options = {}) {
87
+ super(message, { code: options.code || 'ZQ_WEBRTC_TURN', context: options.context, cause: options.cause });
88
+ this.name = 'TurnError';
89
+ }
90
+ }
91
+
92
+
93
+ /** End-to-end encryption (SFrame / key exchange) error. */
94
+ export class E2eeError extends WebRtcError {
95
+ /**
96
+ * @param {string} message
97
+ * @param {object} [options]
98
+ */
99
+ constructor(message, options = {}) {
100
+ super(message, { code: options.code || 'ZQ_WEBRTC_E2EE', context: options.context, cause: options.cause });
101
+ this.name = 'E2eeError';
102
+ }
103
+ }
104
+
105
+
106
+ /** SFU adapter (mediasoup / LiveKit) error. */
107
+ export class SfuError extends WebRtcError {
108
+ /**
109
+ * @param {string} message
110
+ * @param {object} [options]
111
+ */
112
+ constructor(message, options = {}) {
113
+ super(message, { code: options.code || 'ZQ_WEBRTC_SFU', context: options.context, cause: options.cause });
114
+ this.name = 'SfuError';
115
+ }
116
+ }