@vielzeug/mesh 26.10.15 → 26.10.16

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 (50) hide show
  1. package/dist/_base45.cjs.map +1 -1
  2. package/dist/_base45.js +37 -1
  3. package/dist/_base45.js.map +1 -1
  4. package/dist/_defaults.cjs.map +1 -1
  5. package/dist/_defaults.js +5 -1
  6. package/dist/_defaults.js.map +1 -1
  7. package/dist/_node.cjs.map +1 -1
  8. package/dist/_node.js +166 -1
  9. package/dist/_node.js.map +1 -1
  10. package/dist/_proof.cjs.map +1 -1
  11. package/dist/_proof.js +16 -1
  12. package/dist/_proof.js.map +1 -1
  13. package/dist/_random.cjs.map +1 -1
  14. package/dist/_random.js +14 -1
  15. package/dist/_random.js.map +1 -1
  16. package/dist/_rtc.cjs.map +1 -1
  17. package/dist/_rtc.js +41 -3
  18. package/dist/_rtc.js.map +1 -1
  19. package/dist/_sdp.cjs.map +1 -1
  20. package/dist/_sdp.js +54 -2
  21. package/dist/_sdp.js.map +1 -1
  22. package/dist/_wire.cjs.map +1 -1
  23. package/dist/_wire.js +20 -1
  24. package/dist/_wire.js.map +1 -1
  25. package/dist/codec.cjs.map +1 -1
  26. package/dist/codec.js +34 -1
  27. package/dist/codec.js.map +1 -1
  28. package/dist/errors.cjs.map +1 -1
  29. package/dist/errors.js +18 -1
  30. package/dist/errors.js.map +1 -1
  31. package/dist/guest.cjs.map +1 -1
  32. package/dist/guest.js +153 -1
  33. package/dist/guest.js.map +1 -1
  34. package/dist/host.cjs.map +1 -1
  35. package/dist/host.js +202 -1
  36. package/dist/host.js.map +1 -1
  37. package/dist/index.js +6 -1
  38. package/dist/mesh.cjs.map +1 -1
  39. package/dist/mesh.iife.js.map +1 -1
  40. package/dist/mesh.js +756 -4
  41. package/dist/mesh.js.map +1 -1
  42. package/dist/qr-codec.cjs.map +1 -1
  43. package/dist/qr-codec.js +52 -1
  44. package/dist/qr-codec.js.map +1 -1
  45. package/dist/testing/fake-rtc.cjs +1 -1
  46. package/dist/testing/fake-rtc.cjs.map +1 -1
  47. package/dist/testing/fake-rtc.js +153 -1
  48. package/dist/testing/fake-rtc.js.map +1 -1
  49. package/dist/testing.js +1 -1
  50. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"_base45.cjs","names":[],"sources":["../src/_base45.ts"],"sourcesContent":["import { MeshPairingError } from './errors';\n\n/**\n * Base45 (RFC 9285): two bytes → three chars, one leftover byte → two chars.\n * The alphabet sits inside the QR alphanumeric charset, so QR encoders can use\n * alphanumeric mode (~5.5 bits/char) instead of byte mode: about a third\n * denser than base64 for the same payload.\n */\n\nconst ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';\nconst INDEX = new Map([...ALPHABET].map((ch, i) => [ch, i]));\n\nexport function bytesToBase45(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i + 1 < bytes.length; i += 2) {\n const value = bytes[i] * 256 + bytes[i + 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45) % 45] + ALPHABET[Math.floor(value / 2025)];\n }\n if (bytes.length % 2) {\n const value = bytes[bytes.length - 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45)];\n }\n return out;\n}\n\nexport function base45ToBytes(text: string): Uint8Array {\n const out: number[] = [];\n let i = 0;\n while (i + 2 < text.length) {\n const value = digit(text, i) + 45 * digit(text, i + 1) + 2025 * digit(text, i + 2);\n if (value > 0xffff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value >> 8, value & 0xff);\n i += 3;\n }\n if (text.length - i === 2) {\n const value = digit(text, i) + 45 * digit(text, i + 1);\n if (value > 0xff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value);\n } else if (text.length - i === 1) {\n throw new MeshPairingError('Malformed base45 payload');\n }\n return new Uint8Array(out);\n}\n\nfunction digit(text: string, at: number): number {\n const value = INDEX.get(text[at]);\n if (value === undefined) throw new MeshPairingError('Malformed base45 payload');\n return value;\n}\n"],"mappings":"gCASA,IAAM,EAAW,gDACX,EAAQ,IAAI,IAAI,CAAC,GAAG,CAAQ,CAAC,CAAC,KAAK,EAAI,IAAM,CAAC,EAAI,CAAC,CAAC,CAAC,EAE3D,SAAgB,cAAc,EAA2B,CACvD,IAAI,EAAM,GACV,IAAK,IAAI,EAAI,EAAG,EAAI,EAAI,EAAM,OAAQ,GAAK,EAAG,CAC5C,IAAM,EAAQ,EAAM,GAAK,IAAM,EAAM,EAAI,GACzC,GAAO,EAAS,EAAQ,IAAM,EAAS,KAAK,MAAM,EAAQ,EAAE,EAAI,IAAM,EAAS,KAAK,MAAM,EAAQ,IAAI,EACxG,CACA,GAAI,EAAM,OAAS,EAAG,CACpB,IAAM,EAAQ,EAAM,EAAM,OAAS,GACnC,GAAO,EAAS,EAAQ,IAAM,EAAS,KAAK,MAAM,EAAQ,EAAE,EAC9D,CACA,OAAO,CACT,CAEA,SAAgB,cAAc,EAA0B,CACtD,IAAM,EAAgB,CAAC,EACnB,EAAI,EACR,KAAO,EAAI,EAAI,EAAK,QAAQ,CAC1B,IAAM,EAAQ,MAAM,EAAM,CAAC,EAAI,GAAK,MAAM,EAAM,EAAI,CAAC,EAAI,KAAO,MAAM,EAAM,EAAI,CAAC,EACjF,GAAI,EAAQ,MAAQ,MAAM,IAAI,EAAA,iBAAiB,0BAA0B,EACzE,EAAI,KAAK,GAAS,EAAG,EAAQ,GAAI,EACjC,GAAK,CACP,CACA,GAAI,EAAK,OAAS,IAAM,EAAG,CACzB,IAAM,EAAQ,MAAM,EAAM,CAAC,EAAI,GAAK,MAAM,EAAM,EAAI,CAAC,EACrD,GAAI,EAAQ,IAAM,MAAM,IAAI,EAAA,iBAAiB,0BAA0B,EACvE,EAAI,KAAK,CAAK,CAChB,MAAO,GAAI,EAAK,OAAS,IAAM,EAC7B,MAAM,IAAI,EAAA,iBAAiB,0BAA0B,EAEvD,OAAO,IAAI,WAAW,CAAG,CAC3B,CAEA,SAAS,MAAM,EAAc,EAAoB,CAC/C,IAAM,EAAQ,EAAM,IAAI,EAAK,EAAG,EAChC,GAAI,IAAU,IAAA,GAAW,MAAM,IAAI,EAAA,iBAAiB,0BAA0B,EAC9E,OAAO,CACT"}
1
+ {"version":3,"file":"_base45.cjs","names":["MeshPairingError","require_errors","ALPHABET","INDEX","ch","i","out","bytes","value","text","at"],"sources":["../src/_base45.ts"],"sourcesContent":["import { MeshPairingError } from './errors';\n\n/**\n * Base45 (RFC 9285): two bytes → three chars, one leftover byte → two chars.\n * The alphabet sits inside the QR alphanumeric charset, so QR encoders can use\n * alphanumeric mode (~5.5 bits/char) instead of byte mode: about a third\n * denser than base64 for the same payload.\n */\n\nconst ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';\nconst INDEX = new Map([...ALPHABET].map((ch, i) => [ch, i]));\n\nexport function bytesToBase45(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i + 1 < bytes.length; i += 2) {\n const value = bytes[i] * 256 + bytes[i + 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45) % 45] + ALPHABET[Math.floor(value / 2025)];\n }\n if (bytes.length % 2) {\n const value = bytes[bytes.length - 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45)];\n }\n return out;\n}\n\nexport function base45ToBytes(text: string): Uint8Array {\n const out: number[] = [];\n let i = 0;\n while (i + 2 < text.length) {\n const value = digit(text, i) + 45 * digit(text, i + 1) + 2025 * digit(text, i + 2);\n if (value > 0xffff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value >> 8, value & 0xff);\n i += 3;\n }\n if (text.length - i === 2) {\n const value = digit(text, i) + 45 * digit(text, i + 1);\n if (value > 0xff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value);\n } else if (text.length - i === 1) {\n throw new MeshPairingError('Malformed base45 payload');\n }\n return new Uint8Array(out);\n}\n\nfunction digit(text: string, at: number): number {\n const value = INDEX.get(text[at]);\n if (value === undefined) throw new MeshPairingError('Malformed base45 payload');\n return value;\n}\n"],"mappings":"A,gCASA,IAAME,EAAW,gDACXC,EAAQ,IAAI,IAAI,CAAC,GAAGD,CAAQ,CAAC,CAAC,KAAK,EAAI,IAAM,CAACE,EAAIC,CAAC,CAAC,CAAC,EAE3D,SAAgB,cAAc,EAA2B,CACvD,IAAIC,EAAM,GACV,IAAK,IAAID,EAAI,EAAGA,EAAI,EAAIE,EAAM,OAAQ,GAAK,EAAG,CAC5C,IAAMC,EAAQD,EAAMF,GAAK,IAAME,EAAMF,EAAI,GACzC,GAAOH,EAASM,EAAQ,IAAMN,EAAS,KAAK,MAAMM,EAAQ,EAAE,EAAI,IAAMN,EAAS,KAAK,MAAMM,EAAQ,IAAI,EACxG,CACA,GAAID,EAAM,OAAS,EAAG,CACpB,IAAMC,EAAQD,EAAMA,EAAM,OAAS,GACnC,GAAOL,EAASM,EAAQ,IAAMN,EAAS,KAAK,MAAMM,EAAQ,EAAE,EAC9D,CACA,OAAOF,CACT,CAEA,SAAgB,cAAc,EAA0B,CACtD,IAAMA,EAAgB,CAAC,EACnBD,EAAI,EACR,KAAOA,EAAI,EAAII,EAAK,QAAQ,CAC1B,IAAMD,EAAQ,MAAMC,EAAMJ,CAAC,EAAI,GAAK,MAAMI,EAAMJ,EAAI,CAAC,EAAI,KAAO,MAAMI,EAAMJ,EAAI,CAAC,EACjF,GAAIG,EAAQ,MAAQ,MAAM,IAAIR,EAAAA,iBAAiB,0BAA0B,EACzEM,EAAI,KAAKE,GAAS,EAAGA,EAAQ,GAAI,EACjC,GAAK,CACP,CACA,GAAIC,EAAK,OAASJ,IAAM,EAAG,CACzB,IAAMG,EAAQ,MAAMC,EAAMJ,CAAC,EAAI,GAAK,MAAMI,EAAMJ,EAAI,CAAC,EACrD,GAAIG,EAAQ,IAAM,MAAM,IAAIR,EAAAA,iBAAiB,0BAA0B,EACvEM,EAAI,KAAKE,CAAK,CAChB,MAAO,GAAIC,EAAK,OAASJ,IAAM,EAC7B,MAAM,IAAIL,EAAAA,iBAAiB,0BAA0B,EAEvD,OAAO,IAAI,WAAWM,CAAG,CAC3B,CAEA,SAAS,MAAM,EAAc,EAAoB,CAC/C,IAAME,EAAQL,EAAM,IAAIM,EAAKC,EAAG,EAChC,GAAIF,IAAU,IAAA,GAAW,MAAM,IAAIR,EAAAA,iBAAiB,0BAA0B,EAC9E,OAAOQ,CACT,C"}
package/dist/_base45.js CHANGED
@@ -1,2 +1,38 @@
1
- import{MeshPairingError as e}from"./errors.js";var t=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:`,n=new Map([...t].map((e,t)=>[e,t]));function bytesToBase45(e){let n=``;for(let r=0;r+1<e.length;r+=2){let i=e[r]*256+e[r+1];n+=t[i%45]+t[Math.floor(i/45)%45]+t[Math.floor(i/2025)]}if(e.length%2){let r=e[e.length-1];n+=t[r%45]+t[Math.floor(r/45)]}return n}function base45ToBytes(t){let n=[],r=0;for(;r+2<t.length;){let i=digit(t,r)+45*digit(t,r+1)+2025*digit(t,r+2);if(i>65535)throw new e(`Malformed base45 payload`);n.push(i>>8,i&255),r+=3}if(t.length-r===2){let i=digit(t,r)+45*digit(t,r+1);if(i>255)throw new e(`Malformed base45 payload`);n.push(i)}else if(t.length-r===1)throw new e(`Malformed base45 payload`);return new Uint8Array(n)}function digit(t,r){let i=n.get(t[r]);if(i===void 0)throw new e(`Malformed base45 payload`);return i}export{base45ToBytes,bytesToBase45};
1
+ import { MeshPairingError as e } from "./errors.js";
2
+ //#region src/_base45.ts
3
+ var t = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:", n = new Map([...t].map((e, t) => [e, t]));
4
+ function bytesToBase45(e) {
5
+ let n = "";
6
+ for (let r = 0; r + 1 < e.length; r += 2) {
7
+ let i = e[r] * 256 + e[r + 1];
8
+ n += t[i % 45] + t[Math.floor(i / 45) % 45] + t[Math.floor(i / 2025)];
9
+ }
10
+ if (e.length % 2) {
11
+ let r = e[e.length - 1];
12
+ n += t[r % 45] + t[Math.floor(r / 45)];
13
+ }
14
+ return n;
15
+ }
16
+ function base45ToBytes(t) {
17
+ let n = [], r = 0;
18
+ for (; r + 2 < t.length;) {
19
+ let i = digit(t, r) + 45 * digit(t, r + 1) + 2025 * digit(t, r + 2);
20
+ if (i > 65535) throw new e("Malformed base45 payload");
21
+ n.push(i >> 8, i & 255), r += 3;
22
+ }
23
+ if (t.length - r === 2) {
24
+ let i = digit(t, r) + 45 * digit(t, r + 1);
25
+ if (i > 255) throw new e("Malformed base45 payload");
26
+ n.push(i);
27
+ } else if (t.length - r === 1) throw new e("Malformed base45 payload");
28
+ return new Uint8Array(n);
29
+ }
30
+ function digit(t, r) {
31
+ let i = n.get(t[r]);
32
+ if (i === void 0) throw new e("Malformed base45 payload");
33
+ return i;
34
+ }
35
+ //#endregion
36
+ export { base45ToBytes, bytesToBase45 };
37
+
2
38
  //# sourceMappingURL=_base45.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"_base45.js","names":[],"sources":["../src/_base45.ts"],"sourcesContent":["import { MeshPairingError } from './errors';\n\n/**\n * Base45 (RFC 9285): two bytes → three chars, one leftover byte → two chars.\n * The alphabet sits inside the QR alphanumeric charset, so QR encoders can use\n * alphanumeric mode (~5.5 bits/char) instead of byte mode: about a third\n * denser than base64 for the same payload.\n */\n\nconst ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';\nconst INDEX = new Map([...ALPHABET].map((ch, i) => [ch, i]));\n\nexport function bytesToBase45(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i + 1 < bytes.length; i += 2) {\n const value = bytes[i] * 256 + bytes[i + 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45) % 45] + ALPHABET[Math.floor(value / 2025)];\n }\n if (bytes.length % 2) {\n const value = bytes[bytes.length - 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45)];\n }\n return out;\n}\n\nexport function base45ToBytes(text: string): Uint8Array {\n const out: number[] = [];\n let i = 0;\n while (i + 2 < text.length) {\n const value = digit(text, i) + 45 * digit(text, i + 1) + 2025 * digit(text, i + 2);\n if (value > 0xffff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value >> 8, value & 0xff);\n i += 3;\n }\n if (text.length - i === 2) {\n const value = digit(text, i) + 45 * digit(text, i + 1);\n if (value > 0xff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value);\n } else if (text.length - i === 1) {\n throw new MeshPairingError('Malformed base45 payload');\n }\n return new Uint8Array(out);\n}\n\nfunction digit(text: string, at: number): number {\n const value = INDEX.get(text[at]);\n if (value === undefined) throw new MeshPairingError('Malformed base45 payload');\n return value;\n}\n"],"mappings":"+CASA,IAAM,EAAW,gDACX,EAAQ,IAAI,IAAI,CAAC,GAAG,CAAQ,CAAC,CAAC,KAAK,EAAI,IAAM,CAAC,EAAI,CAAC,CAAC,CAAC,EAE3D,SAAgB,cAAc,EAA2B,CACvD,IAAI,EAAM,GACV,IAAK,IAAI,EAAI,EAAG,EAAI,EAAI,EAAM,OAAQ,GAAK,EAAG,CAC5C,IAAM,EAAQ,EAAM,GAAK,IAAM,EAAM,EAAI,GACzC,GAAO,EAAS,EAAQ,IAAM,EAAS,KAAK,MAAM,EAAQ,EAAE,EAAI,IAAM,EAAS,KAAK,MAAM,EAAQ,IAAI,EACxG,CACA,GAAI,EAAM,OAAS,EAAG,CACpB,IAAM,EAAQ,EAAM,EAAM,OAAS,GACnC,GAAO,EAAS,EAAQ,IAAM,EAAS,KAAK,MAAM,EAAQ,EAAE,EAC9D,CACA,OAAO,CACT,CAEA,SAAgB,cAAc,EAA0B,CACtD,IAAM,EAAgB,CAAC,EACnB,EAAI,EACR,KAAO,EAAI,EAAI,EAAK,QAAQ,CAC1B,IAAM,EAAQ,MAAM,EAAM,CAAC,EAAI,GAAK,MAAM,EAAM,EAAI,CAAC,EAAI,KAAO,MAAM,EAAM,EAAI,CAAC,EACjF,GAAI,EAAQ,MAAQ,MAAM,IAAI,EAAiB,0BAA0B,EACzE,EAAI,KAAK,GAAS,EAAG,EAAQ,GAAI,EACjC,GAAK,CACP,CACA,GAAI,EAAK,OAAS,IAAM,EAAG,CACzB,IAAM,EAAQ,MAAM,EAAM,CAAC,EAAI,GAAK,MAAM,EAAM,EAAI,CAAC,EACrD,GAAI,EAAQ,IAAM,MAAM,IAAI,EAAiB,0BAA0B,EACvE,EAAI,KAAK,CAAK,CAChB,MAAO,GAAI,EAAK,OAAS,IAAM,EAC7B,MAAM,IAAI,EAAiB,0BAA0B,EAEvD,OAAO,IAAI,WAAW,CAAG,CAC3B,CAEA,SAAS,MAAM,EAAc,EAAoB,CAC/C,IAAM,EAAQ,EAAM,IAAI,EAAK,EAAG,EAChC,GAAI,IAAU,IAAA,GAAW,MAAM,IAAI,EAAiB,0BAA0B,EAC9E,OAAO,CACT"}
1
+ {"version":3,"file":"_base45.js","names":["ALPHABET","INDEX","ch","i","out","bytes","value","text","MeshPairingError","at"],"sources":["../src/_base45.ts"],"sourcesContent":["import { MeshPairingError } from './errors';\n\n/**\n * Base45 (RFC 9285): two bytes → three chars, one leftover byte → two chars.\n * The alphabet sits inside the QR alphanumeric charset, so QR encoders can use\n * alphanumeric mode (~5.5 bits/char) instead of byte mode: about a third\n * denser than base64 for the same payload.\n */\n\nconst ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';\nconst INDEX = new Map([...ALPHABET].map((ch, i) => [ch, i]));\n\nexport function bytesToBase45(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i + 1 < bytes.length; i += 2) {\n const value = bytes[i] * 256 + bytes[i + 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45) % 45] + ALPHABET[Math.floor(value / 2025)];\n }\n if (bytes.length % 2) {\n const value = bytes[bytes.length - 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45)];\n }\n return out;\n}\n\nexport function base45ToBytes(text: string): Uint8Array {\n const out: number[] = [];\n let i = 0;\n while (i + 2 < text.length) {\n const value = digit(text, i) + 45 * digit(text, i + 1) + 2025 * digit(text, i + 2);\n if (value > 0xffff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value >> 8, value & 0xff);\n i += 3;\n }\n if (text.length - i === 2) {\n const value = digit(text, i) + 45 * digit(text, i + 1);\n if (value > 0xff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value);\n } else if (text.length - i === 1) {\n throw new MeshPairingError('Malformed base45 payload');\n }\n return new Uint8Array(out);\n}\n\nfunction digit(text: string, at: number): number {\n const value = INDEX.get(text[at]);\n if (value === undefined) throw new MeshPairingError('Malformed base45 payload');\n return value;\n}\n"],"mappings":";;AASA,IAAMA,IAAW,iDACXC,IAAQ,IAAI,IAAI,CAAC,GAAGD,CAAQ,CAAC,CAAC,KAAK,GAAI,MAAM,CAACE,GAAIC,CAAC,CAAC,CAAC;AAE3D,SAAgB,cAAc,GAA2B;CACvD,IAAIC,IAAM;CACV,KAAK,IAAID,IAAI,GAAGA,IAAI,IAAIE,EAAM,QAAQ,KAAK,GAAG;EAC5C,IAAMC,IAAQD,EAAMF,KAAK,MAAME,EAAMF,IAAI;EACzC,KAAOH,EAASM,IAAQ,MAAMN,EAAS,KAAK,MAAMM,IAAQ,EAAE,IAAI,MAAMN,EAAS,KAAK,MAAMM,IAAQ,IAAI;CACxG;CACA,IAAID,EAAM,SAAS,GAAG;EACpB,IAAMC,IAAQD,EAAMA,EAAM,SAAS;EACnC,KAAOL,EAASM,IAAQ,MAAMN,EAAS,KAAK,MAAMM,IAAQ,EAAE;CAC9D;CACA,OAAOF;AACT;AAEA,SAAgB,cAAc,GAA0B;CACtD,IAAMA,IAAgB,CAAC,GACnBD,IAAI;CACR,OAAOA,IAAI,IAAII,EAAK,SAAQ;EAC1B,IAAMD,IAAQ,MAAMC,GAAMJ,CAAC,IAAI,KAAK,MAAMI,GAAMJ,IAAI,CAAC,IAAI,OAAO,MAAMI,GAAMJ,IAAI,CAAC;EACjF,IAAIG,IAAQ,OAAQ,MAAM,IAAIE,EAAiB,0BAA0B;EAEzE,AADAJ,EAAI,KAAKE,KAAS,GAAGA,IAAQ,GAAI,GACjC,KAAK;CACP;CACA,IAAIC,EAAK,SAASJ,MAAM,GAAG;EACzB,IAAMG,IAAQ,MAAMC,GAAMJ,CAAC,IAAI,KAAK,MAAMI,GAAMJ,IAAI,CAAC;EACrD,IAAIG,IAAQ,KAAM,MAAM,IAAIE,EAAiB,0BAA0B;EACvE,EAAI,KAAKF,CAAK;CAChB,OAAO,IAAIC,EAAK,SAASJ,MAAM,GAC7B,MAAM,IAAIK,EAAiB,0BAA0B;CAEvD,OAAO,IAAI,WAAWJ,CAAG;AAC3B;AAEA,SAAS,MAAM,GAAc,GAAoB;CAC/C,IAAME,IAAQL,EAAM,IAAIM,EAAKE,EAAG;CAChC,IAAIH,MAAU,KAAA,GAAW,MAAM,IAAIE,EAAiB,0BAA0B;CAC9E,OAAOF;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"_defaults.cjs","names":[],"sources":["../src/_defaults.ts"],"sourcesContent":["/** Option defaults shared by host and guest so the two never drift. */\nexport const DEFAULT_CHANNEL_OPEN_TIMEOUT_MS = 60_000;\nexport const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 5_000;\nexport const DEFAULT_INVITATION_TTL_MS = 300_000;\nexport const DEFAULT_MAX_MESSAGE_BYTES = 65_536;\n"],"mappings":"AACA,IAAa,EAAkC,IAClC,EAAmC,IACnC,EAA4B,IAC5B,EAA4B"}
1
+ {"version":3,"file":"_defaults.cjs","names":["DEFAULT_CHANNEL_OPEN_TIMEOUT_MS","DEFAULT_ICE_GATHERING_TIMEOUT_MS","DEFAULT_INVITATION_TTL_MS","DEFAULT_MAX_MESSAGE_BYTES"],"sources":["../src/_defaults.ts"],"sourcesContent":["/** Option defaults shared by host and guest so the two never drift. */\nexport const DEFAULT_CHANNEL_OPEN_TIMEOUT_MS = 60_000;\nexport const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 5_000;\nexport const DEFAULT_INVITATION_TTL_MS = 300_000;\nexport const DEFAULT_MAX_MESSAGE_BYTES = 65_536;\n"],"mappings":"AACA,IAAaA,EAAkC,IAClCC,EAAmC,IACnCC,EAA4B,IAC5BC,EAA4B,M"}
package/dist/_defaults.js CHANGED
@@ -1,2 +1,6 @@
1
- var e=6e4,t=5e3,n=3e5,r=65536;export{e as DEFAULT_CHANNEL_OPEN_TIMEOUT_MS,t as DEFAULT_ICE_GATHERING_TIMEOUT_MS,n as DEFAULT_INVITATION_TTL_MS,r as DEFAULT_MAX_MESSAGE_BYTES};
1
+ //#region src/_defaults.ts
2
+ var e = 6e4, t = 5e3, n = 3e5, r = 65536;
3
+ //#endregion
4
+ export { e as DEFAULT_CHANNEL_OPEN_TIMEOUT_MS, t as DEFAULT_ICE_GATHERING_TIMEOUT_MS, n as DEFAULT_INVITATION_TTL_MS, r as DEFAULT_MAX_MESSAGE_BYTES };
5
+
2
6
  //# sourceMappingURL=_defaults.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"_defaults.js","names":[],"sources":["../src/_defaults.ts"],"sourcesContent":["/** Option defaults shared by host and guest so the two never drift. */\nexport const DEFAULT_CHANNEL_OPEN_TIMEOUT_MS = 60_000;\nexport const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 5_000;\nexport const DEFAULT_INVITATION_TTL_MS = 300_000;\nexport const DEFAULT_MAX_MESSAGE_BYTES = 65_536;\n"],"mappings":"AACA,IAAa,EAAkC,IAClC,EAAmC,IACnC,EAA4B,IAC5B,EAA4B"}
1
+ {"version":3,"file":"_defaults.js","names":["DEFAULT_CHANNEL_OPEN_TIMEOUT_MS","DEFAULT_ICE_GATHERING_TIMEOUT_MS","DEFAULT_INVITATION_TTL_MS","DEFAULT_MAX_MESSAGE_BYTES"],"sources":["../src/_defaults.ts"],"sourcesContent":["/** Option defaults shared by host and guest so the two never drift. */\nexport const DEFAULT_CHANNEL_OPEN_TIMEOUT_MS = 60_000;\nexport const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 5_000;\nexport const DEFAULT_INVITATION_TTL_MS = 300_000;\nexport const DEFAULT_MAX_MESSAGE_BYTES = 65_536;\n"],"mappings":";AACA,IAAaA,IAAkC,KAClCC,IAAmC,KACnCC,IAA4B,KAC5BC,IAA4B"}
@@ -1 +1 @@
1
- {"version":3,"file":"_node.cjs","names":[],"sources":["../src/_node.ts"],"sourcesContent":["import { type RandomSource, tapper, utf8Bytes } from '@vielzeug/arsenal';\nimport { randomId } from './_random';\nimport { MessageDedupe, toEnvelope } from './_wire';\nimport { MeshConnectionError, MeshDisposedError, MeshError, MeshPayloadError } from './errors';\nimport type {\n MeshEvent,\n MeshInbound,\n MeshPeer,\n MeshPeerStatus,\n MeshStatus,\n RTCDataChannelLike,\n RTCPeerConnectionLike,\n Unsubscribe,\n} from './types';\n\ntype Tapper = (event: MeshEvent) => void;\n\n/** Shared lifecycle core: status, tappers, disposal. */\nexport interface NodeCore {\n readonly disposalCtrl: AbortController;\n disposed: boolean;\n emitTap(event: MeshEvent): void;\n ensureLive(): void;\n /** Emits the final status-change + dispose events, clears tappers, aborts. */\n markDisposed(): void;\n setStatus(next: MeshStatus): void;\n status: MeshStatus;\n tap(handler: Tapper, options?: { readonly signal?: AbortSignal }): Unsubscribe;\n}\n\nexport function createNodeCore(signal?: AbortSignal): NodeCore {\n const tappers = tapper<MeshEvent>();\n const disposalCtrl = new AbortController();\n\n const core: NodeCore = {\n disposalCtrl,\n disposed: false,\n\n emitTap(event) {\n tappers.emit(event);\n },\n\n ensureLive() {\n if (core.disposed) throw new MeshDisposedError();\n },\n\n markDisposed() {\n if (core.disposed) return;\n core.disposed = true;\n core.status = 'disposed';\n core.emitTap({ status: 'disposed', type: 'status-change' });\n core.emitTap({ type: 'dispose' });\n tappers.clear();\n disposalCtrl.abort();\n },\n\n setStatus(next) {\n if (core.disposed || core.status === next) return;\n core.status = next;\n core.emitTap({ status: next, type: 'status-change' });\n },\n status: 'idle',\n\n tap(handler, options) {\n if (core.disposed) return () => {};\n return tappers.tap(handler, options);\n },\n };\n\n if (signal) {\n if (signal.aborted) {\n core.markDisposed();\n } else {\n signal.addEventListener('abort', () => core.markDisposed(), { once: true });\n }\n }\n\n return core;\n}\n\n// ─── Peers ───────────────────────────────────────────────────────────────────\n\n/** Internal peer record; `publicPeer` exposes live status through getters. */\nexport interface PeerRecord {\n dc: RTCDataChannelLike | null;\n readonly dedupe: MessageDedupe;\n readonly id: string;\n readonly name: string | undefined;\n pc: RTCPeerConnectionLike | null;\n readonly publicPeer: MeshPeer;\n readonly role: 'host' | 'guest';\n status: MeshPeerStatus;\n}\n\nexport function createPeerRecord(id: string, name: string | undefined, role: 'host' | 'guest'): PeerRecord {\n const record: PeerRecord = {\n dc: null,\n dedupe: new MessageDedupe(),\n id,\n name,\n pc: null,\n publicPeer: {\n get id() {\n return record.id;\n },\n get name() {\n return record.name;\n },\n get role() {\n return record.role;\n },\n get status() {\n return record.status;\n },\n },\n role,\n status: 'connecting',\n };\n return record;\n}\n\n// ─── Messenger ───────────────────────────────────────────────────────────────\n\ntype InboundHandler = (message: MeshInbound<unknown>) => void;\n\nexport interface MessengerOptions {\n readonly clock: () => number;\n readonly emitTap: (event: MeshEvent) => void;\n readonly maxMessageBytes: number;\n readonly random?: RandomSource;\n}\n\nexport interface Messenger {\n clear(): void;\n /** Entry point for `dc` 'message' events. */\n handleInbound(peer: PeerRecord, data: unknown): void;\n on(type: string, handler: InboundHandler): Unsubscribe;\n /** Sends a typed envelope to a peer. Throws `MeshConnectionError`/`MeshPayloadError`. */\n sendTo(peer: PeerRecord, type: string, payload: unknown): void;\n}\n\nexport function createMessenger(options: MessengerOptions): Messenger {\n const handlers = new Map<string, Set<InboundHandler>>();\n\n function reject(peerId: string, reason: 'too-large' | 'duplicate' | 'malformed' | 'unknown-type'): void {\n options.emitTap({ peerId, reason, type: 'message-rejected' });\n }\n\n return {\n clear() {\n handlers.clear();\n },\n\n handleInbound(peer, data) {\n const peerId = peer.id;\n\n if (typeof data !== 'string') {\n reject(peerId, 'malformed');\n return;\n }\n\n const bytes = utf8Bytes(data);\n if (bytes > options.maxMessageBytes) {\n reject(peerId, 'too-large');\n return;\n }\n\n let decoded: unknown;\n try {\n decoded = JSON.parse(data);\n } catch {\n reject(peerId, 'malformed');\n return;\n }\n\n const envelope = toEnvelope(decoded);\n if (!envelope) {\n reject(peerId, 'malformed');\n return;\n }\n\n if (!peer.dedupe.check(envelope.id)) {\n reject(peerId, 'duplicate');\n return;\n }\n\n const set = handlers.get(envelope.t);\n if (!set || set.size === 0) {\n reject(peerId, 'unknown-type');\n return;\n }\n\n options.emitTap({ bytes, messageType: envelope.t, peerId, type: 'message-received' });\n const message: MeshInbound<unknown> = {\n messageId: envelope.id,\n payload: envelope.p,\n peerId,\n sentAt: envelope.ts,\n };\n for (const handler of set) {\n try {\n handler(message);\n } catch (cause) {\n options.emitTap({ error: new MeshError('Message handler threw', { cause }), type: 'error' });\n }\n }\n },\n\n on(type, handler) {\n let set = handlers.get(type);\n if (!set) {\n set = new Set();\n handlers.set(type, set);\n }\n set.add(handler);\n return () => {\n set.delete(handler);\n if (set.size === 0) handlers.delete(type);\n };\n },\n\n sendTo(peer, type, payload) {\n const dc = peer.dc;\n if (!dc || dc.readyState !== 'open' || peer.status !== 'connected') {\n throw new MeshConnectionError(`Peer \"${peer.id}\" is not connected`, peer.id);\n }\n\n const envelope = { id: randomId(options.random), p: payload, t: type, ts: options.clock(), v: 1 as const };\n let text: string;\n try {\n text = JSON.stringify(envelope);\n } catch (cause) {\n throw new MeshPayloadError(`Failed to serialize message \"${type}\"`, { cause });\n }\n\n const bytes = utf8Bytes(text);\n if (bytes > options.maxMessageBytes) {\n throw new MeshPayloadError(`Message \"${type}\" is ${bytes} bytes (max ${options.maxMessageBytes})`);\n }\n\n try {\n dc.send(text);\n } catch (cause) {\n throw new MeshConnectionError(`Failed to send message \"${type}\"`, peer.id, { cause });\n }\n options.emitTap({ bytes, messageType: type, peerId: peer.id, type: 'message-sent' });\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"_node.cjs","names":["tapper","MeshDisposedError","MessageDedupe","utf8Bytes","toEnvelope","MeshError","MeshConnectionError","randomId","MeshPayloadError","require_errors","require__random","require__wire","_vielzeug_arsenal","tappers","disposalCtrl","core","event","next","handler","options","signal","record","handlers","peerId","peer","data","bytes","decoded","envelope","set","message","cause","type","dc","payload","text"],"sources":["../src/_node.ts"],"sourcesContent":["import { type RandomSource, tapper, utf8Bytes } from '@vielzeug/arsenal';\nimport { randomId } from './_random';\nimport { MessageDedupe, toEnvelope } from './_wire';\nimport { MeshConnectionError, MeshDisposedError, MeshError, MeshPayloadError } from './errors';\nimport type {\n MeshEvent,\n MeshInbound,\n MeshPeer,\n MeshPeerStatus,\n MeshStatus,\n RTCDataChannelLike,\n RTCPeerConnectionLike,\n Unsubscribe,\n} from './types';\n\ntype Tapper = (event: MeshEvent) => void;\n\n/** Shared lifecycle core: status, tappers, disposal. */\nexport interface NodeCore {\n readonly disposalCtrl: AbortController;\n disposed: boolean;\n emitTap(event: MeshEvent): void;\n ensureLive(): void;\n /** Emits the final status-change + dispose events, clears tappers, aborts. */\n markDisposed(): void;\n setStatus(next: MeshStatus): void;\n status: MeshStatus;\n tap(handler: Tapper, options?: { readonly signal?: AbortSignal }): Unsubscribe;\n}\n\nexport function createNodeCore(signal?: AbortSignal): NodeCore {\n const tappers = tapper<MeshEvent>();\n const disposalCtrl = new AbortController();\n\n const core: NodeCore = {\n disposalCtrl,\n disposed: false,\n\n emitTap(event) {\n tappers.emit(event);\n },\n\n ensureLive() {\n if (core.disposed) throw new MeshDisposedError();\n },\n\n markDisposed() {\n if (core.disposed) return;\n core.disposed = true;\n core.status = 'disposed';\n core.emitTap({ status: 'disposed', type: 'status-change' });\n core.emitTap({ type: 'dispose' });\n tappers.clear();\n disposalCtrl.abort();\n },\n\n setStatus(next) {\n if (core.disposed || core.status === next) return;\n core.status = next;\n core.emitTap({ status: next, type: 'status-change' });\n },\n status: 'idle',\n\n tap(handler, options) {\n if (core.disposed) return () => {};\n return tappers.tap(handler, options);\n },\n };\n\n if (signal) {\n if (signal.aborted) {\n core.markDisposed();\n } else {\n signal.addEventListener('abort', () => core.markDisposed(), { once: true });\n }\n }\n\n return core;\n}\n\n// ─── Peers ───────────────────────────────────────────────────────────────────\n\n/** Internal peer record; `publicPeer` exposes live status through getters. */\nexport interface PeerRecord {\n dc: RTCDataChannelLike | null;\n readonly dedupe: MessageDedupe;\n readonly id: string;\n readonly name: string | undefined;\n pc: RTCPeerConnectionLike | null;\n readonly publicPeer: MeshPeer;\n readonly role: 'host' | 'guest';\n status: MeshPeerStatus;\n}\n\nexport function createPeerRecord(id: string, name: string | undefined, role: 'host' | 'guest'): PeerRecord {\n const record: PeerRecord = {\n dc: null,\n dedupe: new MessageDedupe(),\n id,\n name,\n pc: null,\n publicPeer: {\n get id() {\n return record.id;\n },\n get name() {\n return record.name;\n },\n get role() {\n return record.role;\n },\n get status() {\n return record.status;\n },\n },\n role,\n status: 'connecting',\n };\n return record;\n}\n\n// ─── Messenger ───────────────────────────────────────────────────────────────\n\ntype InboundHandler = (message: MeshInbound<unknown>) => void;\n\nexport interface MessengerOptions {\n readonly clock: () => number;\n readonly emitTap: (event: MeshEvent) => void;\n readonly maxMessageBytes: number;\n readonly random?: RandomSource;\n}\n\nexport interface Messenger {\n clear(): void;\n /** Entry point for `dc` 'message' events. */\n handleInbound(peer: PeerRecord, data: unknown): void;\n on(type: string, handler: InboundHandler): Unsubscribe;\n /** Sends a typed envelope to a peer. Throws `MeshConnectionError`/`MeshPayloadError`. */\n sendTo(peer: PeerRecord, type: string, payload: unknown): void;\n}\n\nexport function createMessenger(options: MessengerOptions): Messenger {\n const handlers = new Map<string, Set<InboundHandler>>();\n\n function reject(peerId: string, reason: 'too-large' | 'duplicate' | 'malformed' | 'unknown-type'): void {\n options.emitTap({ peerId, reason, type: 'message-rejected' });\n }\n\n return {\n clear() {\n handlers.clear();\n },\n\n handleInbound(peer, data) {\n const peerId = peer.id;\n\n if (typeof data !== 'string') {\n reject(peerId, 'malformed');\n return;\n }\n\n const bytes = utf8Bytes(data);\n if (bytes > options.maxMessageBytes) {\n reject(peerId, 'too-large');\n return;\n }\n\n let decoded: unknown;\n try {\n decoded = JSON.parse(data);\n } catch {\n reject(peerId, 'malformed');\n return;\n }\n\n const envelope = toEnvelope(decoded);\n if (!envelope) {\n reject(peerId, 'malformed');\n return;\n }\n\n if (!peer.dedupe.check(envelope.id)) {\n reject(peerId, 'duplicate');\n return;\n }\n\n const set = handlers.get(envelope.t);\n if (!set || set.size === 0) {\n reject(peerId, 'unknown-type');\n return;\n }\n\n options.emitTap({ bytes, messageType: envelope.t, peerId, type: 'message-received' });\n const message: MeshInbound<unknown> = {\n messageId: envelope.id,\n payload: envelope.p,\n peerId,\n sentAt: envelope.ts,\n };\n for (const handler of set) {\n try {\n handler(message);\n } catch (cause) {\n options.emitTap({ error: new MeshError('Message handler threw', { cause }), type: 'error' });\n }\n }\n },\n\n on(type, handler) {\n let set = handlers.get(type);\n if (!set) {\n set = new Set();\n handlers.set(type, set);\n }\n set.add(handler);\n return () => {\n set.delete(handler);\n if (set.size === 0) handlers.delete(type);\n };\n },\n\n sendTo(peer, type, payload) {\n const dc = peer.dc;\n if (!dc || dc.readyState !== 'open' || peer.status !== 'connected') {\n throw new MeshConnectionError(`Peer \"${peer.id}\" is not connected`, peer.id);\n }\n\n const envelope = { id: randomId(options.random), p: payload, t: type, ts: options.clock(), v: 1 as const };\n let text: string;\n try {\n text = JSON.stringify(envelope);\n } catch (cause) {\n throw new MeshPayloadError(`Failed to serialize message \"${type}\"`, { cause });\n }\n\n const bytes = utf8Bytes(text);\n if (bytes > options.maxMessageBytes) {\n throw new MeshPayloadError(`Message \"${type}\" is ${bytes} bytes (max ${options.maxMessageBytes})`);\n }\n\n try {\n dc.send(text);\n } catch (cause) {\n throw new MeshConnectionError(`Failed to send message \"${type}\"`, peer.id, { cause });\n }\n options.emitTap({ bytes, messageType: type, peerId: peer.id, type: 'message-sent' });\n },\n };\n}\n"],"mappings":"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"}
package/dist/_node.js CHANGED
@@ -1,2 +1,167 @@
1
- import{MeshConnectionError as e,MeshDisposedError as t,MeshError as n,MeshPayloadError as r}from"./errors.js";import{randomId as i}from"./_random.js";import{MessageDedupe as a,toEnvelope as o}from"./_wire.js";import{tapper as s,utf8Bytes as c}from"@vielzeug/arsenal";function createNodeCore(e){let n=s(),r=new AbortController,i={disposalCtrl:r,disposed:!1,emitTap(e){n.emit(e)},ensureLive(){if(i.disposed)throw new t},markDisposed(){i.disposed||(i.disposed=!0,i.status=`disposed`,i.emitTap({status:`disposed`,type:`status-change`}),i.emitTap({type:`dispose`}),n.clear(),r.abort())},setStatus(e){i.disposed||i.status===e||(i.status=e,i.emitTap({status:e,type:`status-change`}))},status:`idle`,tap(e,t){return i.disposed?()=>{}:n.tap(e,t)}};return e&&(e.aborted?i.markDisposed():e.addEventListener(`abort`,()=>i.markDisposed(),{once:!0})),i}function createPeerRecord(e,t,n){let r={dc:null,dedupe:new a,id:e,name:t,pc:null,publicPeer:{get id(){return r.id},get name(){return r.name},get role(){return r.role},get status(){return r.status}},role:n,status:`connecting`};return r}function createMessenger(t){let a=new Map;function reject(e,n){t.emitTap({peerId:e,reason:n,type:`message-rejected`})}return{clear(){a.clear()},handleInbound(e,r){let i=e.id;if(typeof r!=`string`){reject(i,`malformed`);return}let s=c(r);if(s>t.maxMessageBytes){reject(i,`too-large`);return}let l;try{l=JSON.parse(r)}catch{reject(i,`malformed`);return}let u=o(l);if(!u){reject(i,`malformed`);return}if(!e.dedupe.check(u.id)){reject(i,`duplicate`);return}let d=a.get(u.t);if(!d||d.size===0){reject(i,`unknown-type`);return}t.emitTap({bytes:s,messageType:u.t,peerId:i,type:`message-received`});let f={messageId:u.id,payload:u.p,peerId:i,sentAt:u.ts};for(let e of d)try{e(f)}catch(e){t.emitTap({error:new n(`Message handler threw`,{cause:e}),type:`error`})}},on(e,t){let n=a.get(e);return n||(n=new Set,a.set(e,n)),n.add(t),()=>{n.delete(t),n.size===0&&a.delete(e)}},sendTo(n,a,o){let s=n.dc;if(!s||s.readyState!==`open`||n.status!==`connected`)throw new e(`Peer "${n.id}" is not connected`,n.id);let l={id:i(t.random),p:o,t:a,ts:t.clock(),v:1},u;try{u=JSON.stringify(l)}catch(e){throw new r(`Failed to serialize message "${a}"`,{cause:e})}let d=c(u);if(d>t.maxMessageBytes)throw new r(`Message "${a}" is ${d} bytes (max ${t.maxMessageBytes})`);try{s.send(u)}catch(t){throw new e(`Failed to send message "${a}"`,n.id,{cause:t})}t.emitTap({bytes:d,messageType:a,peerId:n.id,type:`message-sent`})}}}export{createMessenger,createNodeCore,createPeerRecord};
1
+ import { MeshConnectionError as e, MeshDisposedError as t, MeshError as n, MeshPayloadError as r } from "./errors.js";
2
+ import { randomId as i } from "./_random.js";
3
+ import { MessageDedupe as a, toEnvelope as o } from "./_wire.js";
4
+ import { tapper as s, utf8Bytes as c } from "@vielzeug/arsenal";
5
+ //#region src/_node.ts
6
+ function createNodeCore(e) {
7
+ let n = s(), r = new AbortController(), i = {
8
+ disposalCtrl: r,
9
+ disposed: !1,
10
+ emitTap(e) {
11
+ n.emit(e);
12
+ },
13
+ ensureLive() {
14
+ if (i.disposed) throw new t();
15
+ },
16
+ markDisposed() {
17
+ i.disposed || (i.disposed = !0, i.status = "disposed", i.emitTap({
18
+ status: "disposed",
19
+ type: "status-change"
20
+ }), i.emitTap({ type: "dispose" }), n.clear(), r.abort());
21
+ },
22
+ setStatus(e) {
23
+ i.disposed || i.status === e || (i.status = e, i.emitTap({
24
+ status: e,
25
+ type: "status-change"
26
+ }));
27
+ },
28
+ status: "idle",
29
+ tap(e, t) {
30
+ return i.disposed ? () => {} : n.tap(e, t);
31
+ }
32
+ };
33
+ return e && (e.aborted ? i.markDisposed() : e.addEventListener("abort", () => i.markDisposed(), { once: !0 })), i;
34
+ }
35
+ function createPeerRecord(e, t, n) {
36
+ let r = {
37
+ dc: null,
38
+ dedupe: new a(),
39
+ id: e,
40
+ name: t,
41
+ pc: null,
42
+ publicPeer: {
43
+ get id() {
44
+ return r.id;
45
+ },
46
+ get name() {
47
+ return r.name;
48
+ },
49
+ get role() {
50
+ return r.role;
51
+ },
52
+ get status() {
53
+ return r.status;
54
+ }
55
+ },
56
+ role: n,
57
+ status: "connecting"
58
+ };
59
+ return r;
60
+ }
61
+ function createMessenger(t) {
62
+ let a = /* @__PURE__ */ new Map();
63
+ function reject(e, n) {
64
+ t.emitTap({
65
+ peerId: e,
66
+ reason: n,
67
+ type: "message-rejected"
68
+ });
69
+ }
70
+ return {
71
+ clear() {
72
+ a.clear();
73
+ },
74
+ handleInbound(e, r) {
75
+ let i = e.id;
76
+ if (typeof r != "string") {
77
+ reject(i, "malformed");
78
+ return;
79
+ }
80
+ let s = c(r);
81
+ if (s > t.maxMessageBytes) {
82
+ reject(i, "too-large");
83
+ return;
84
+ }
85
+ let l;
86
+ try {
87
+ l = JSON.parse(r);
88
+ } catch {
89
+ reject(i, "malformed");
90
+ return;
91
+ }
92
+ let u = o(l);
93
+ if (!u) {
94
+ reject(i, "malformed");
95
+ return;
96
+ }
97
+ if (!e.dedupe.check(u.id)) {
98
+ reject(i, "duplicate");
99
+ return;
100
+ }
101
+ let d = a.get(u.t);
102
+ if (!d || d.size === 0) {
103
+ reject(i, "unknown-type");
104
+ return;
105
+ }
106
+ t.emitTap({
107
+ bytes: s,
108
+ messageType: u.t,
109
+ peerId: i,
110
+ type: "message-received"
111
+ });
112
+ let f = {
113
+ messageId: u.id,
114
+ payload: u.p,
115
+ peerId: i,
116
+ sentAt: u.ts
117
+ };
118
+ for (let e of d) try {
119
+ e(f);
120
+ } catch (e) {
121
+ t.emitTap({
122
+ error: new n("Message handler threw", { cause: e }),
123
+ type: "error"
124
+ });
125
+ }
126
+ },
127
+ on(e, t) {
128
+ let n = a.get(e);
129
+ return n || (n = /* @__PURE__ */ new Set(), a.set(e, n)), n.add(t), () => {
130
+ n.delete(t), n.size === 0 && a.delete(e);
131
+ };
132
+ },
133
+ sendTo(n, a, o) {
134
+ let s = n.dc;
135
+ if (!s || s.readyState !== "open" || n.status !== "connected") throw new e(`Peer "${n.id}" is not connected`, n.id);
136
+ let l = {
137
+ id: i(t.random),
138
+ p: o,
139
+ t: a,
140
+ ts: t.clock(),
141
+ v: 1
142
+ }, u;
143
+ try {
144
+ u = JSON.stringify(l);
145
+ } catch (e) {
146
+ throw new r(`Failed to serialize message "${a}"`, { cause: e });
147
+ }
148
+ let d = c(u);
149
+ if (d > t.maxMessageBytes) throw new r(`Message "${a}" is ${d} bytes (max ${t.maxMessageBytes})`);
150
+ try {
151
+ s.send(u);
152
+ } catch (t) {
153
+ throw new e(`Failed to send message "${a}"`, n.id, { cause: t });
154
+ }
155
+ t.emitTap({
156
+ bytes: d,
157
+ messageType: a,
158
+ peerId: n.id,
159
+ type: "message-sent"
160
+ });
161
+ }
162
+ };
163
+ }
164
+ //#endregion
165
+ export { createMessenger, createNodeCore, createPeerRecord };
166
+
2
167
  //# sourceMappingURL=_node.js.map
package/dist/_node.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"_node.js","names":[],"sources":["../src/_node.ts"],"sourcesContent":["import { type RandomSource, tapper, utf8Bytes } from '@vielzeug/arsenal';\nimport { randomId } from './_random';\nimport { MessageDedupe, toEnvelope } from './_wire';\nimport { MeshConnectionError, MeshDisposedError, MeshError, MeshPayloadError } from './errors';\nimport type {\n MeshEvent,\n MeshInbound,\n MeshPeer,\n MeshPeerStatus,\n MeshStatus,\n RTCDataChannelLike,\n RTCPeerConnectionLike,\n Unsubscribe,\n} from './types';\n\ntype Tapper = (event: MeshEvent) => void;\n\n/** Shared lifecycle core: status, tappers, disposal. */\nexport interface NodeCore {\n readonly disposalCtrl: AbortController;\n disposed: boolean;\n emitTap(event: MeshEvent): void;\n ensureLive(): void;\n /** Emits the final status-change + dispose events, clears tappers, aborts. */\n markDisposed(): void;\n setStatus(next: MeshStatus): void;\n status: MeshStatus;\n tap(handler: Tapper, options?: { readonly signal?: AbortSignal }): Unsubscribe;\n}\n\nexport function createNodeCore(signal?: AbortSignal): NodeCore {\n const tappers = tapper<MeshEvent>();\n const disposalCtrl = new AbortController();\n\n const core: NodeCore = {\n disposalCtrl,\n disposed: false,\n\n emitTap(event) {\n tappers.emit(event);\n },\n\n ensureLive() {\n if (core.disposed) throw new MeshDisposedError();\n },\n\n markDisposed() {\n if (core.disposed) return;\n core.disposed = true;\n core.status = 'disposed';\n core.emitTap({ status: 'disposed', type: 'status-change' });\n core.emitTap({ type: 'dispose' });\n tappers.clear();\n disposalCtrl.abort();\n },\n\n setStatus(next) {\n if (core.disposed || core.status === next) return;\n core.status = next;\n core.emitTap({ status: next, type: 'status-change' });\n },\n status: 'idle',\n\n tap(handler, options) {\n if (core.disposed) return () => {};\n return tappers.tap(handler, options);\n },\n };\n\n if (signal) {\n if (signal.aborted) {\n core.markDisposed();\n } else {\n signal.addEventListener('abort', () => core.markDisposed(), { once: true });\n }\n }\n\n return core;\n}\n\n// ─── Peers ───────────────────────────────────────────────────────────────────\n\n/** Internal peer record; `publicPeer` exposes live status through getters. */\nexport interface PeerRecord {\n dc: RTCDataChannelLike | null;\n readonly dedupe: MessageDedupe;\n readonly id: string;\n readonly name: string | undefined;\n pc: RTCPeerConnectionLike | null;\n readonly publicPeer: MeshPeer;\n readonly role: 'host' | 'guest';\n status: MeshPeerStatus;\n}\n\nexport function createPeerRecord(id: string, name: string | undefined, role: 'host' | 'guest'): PeerRecord {\n const record: PeerRecord = {\n dc: null,\n dedupe: new MessageDedupe(),\n id,\n name,\n pc: null,\n publicPeer: {\n get id() {\n return record.id;\n },\n get name() {\n return record.name;\n },\n get role() {\n return record.role;\n },\n get status() {\n return record.status;\n },\n },\n role,\n status: 'connecting',\n };\n return record;\n}\n\n// ─── Messenger ───────────────────────────────────────────────────────────────\n\ntype InboundHandler = (message: MeshInbound<unknown>) => void;\n\nexport interface MessengerOptions {\n readonly clock: () => number;\n readonly emitTap: (event: MeshEvent) => void;\n readonly maxMessageBytes: number;\n readonly random?: RandomSource;\n}\n\nexport interface Messenger {\n clear(): void;\n /** Entry point for `dc` 'message' events. */\n handleInbound(peer: PeerRecord, data: unknown): void;\n on(type: string, handler: InboundHandler): Unsubscribe;\n /** Sends a typed envelope to a peer. Throws `MeshConnectionError`/`MeshPayloadError`. */\n sendTo(peer: PeerRecord, type: string, payload: unknown): void;\n}\n\nexport function createMessenger(options: MessengerOptions): Messenger {\n const handlers = new Map<string, Set<InboundHandler>>();\n\n function reject(peerId: string, reason: 'too-large' | 'duplicate' | 'malformed' | 'unknown-type'): void {\n options.emitTap({ peerId, reason, type: 'message-rejected' });\n }\n\n return {\n clear() {\n handlers.clear();\n },\n\n handleInbound(peer, data) {\n const peerId = peer.id;\n\n if (typeof data !== 'string') {\n reject(peerId, 'malformed');\n return;\n }\n\n const bytes = utf8Bytes(data);\n if (bytes > options.maxMessageBytes) {\n reject(peerId, 'too-large');\n return;\n }\n\n let decoded: unknown;\n try {\n decoded = JSON.parse(data);\n } catch {\n reject(peerId, 'malformed');\n return;\n }\n\n const envelope = toEnvelope(decoded);\n if (!envelope) {\n reject(peerId, 'malformed');\n return;\n }\n\n if (!peer.dedupe.check(envelope.id)) {\n reject(peerId, 'duplicate');\n return;\n }\n\n const set = handlers.get(envelope.t);\n if (!set || set.size === 0) {\n reject(peerId, 'unknown-type');\n return;\n }\n\n options.emitTap({ bytes, messageType: envelope.t, peerId, type: 'message-received' });\n const message: MeshInbound<unknown> = {\n messageId: envelope.id,\n payload: envelope.p,\n peerId,\n sentAt: envelope.ts,\n };\n for (const handler of set) {\n try {\n handler(message);\n } catch (cause) {\n options.emitTap({ error: new MeshError('Message handler threw', { cause }), type: 'error' });\n }\n }\n },\n\n on(type, handler) {\n let set = handlers.get(type);\n if (!set) {\n set = new Set();\n handlers.set(type, set);\n }\n set.add(handler);\n return () => {\n set.delete(handler);\n if (set.size === 0) handlers.delete(type);\n };\n },\n\n sendTo(peer, type, payload) {\n const dc = peer.dc;\n if (!dc || dc.readyState !== 'open' || peer.status !== 'connected') {\n throw new MeshConnectionError(`Peer \"${peer.id}\" is not connected`, peer.id);\n }\n\n const envelope = { id: randomId(options.random), p: payload, t: type, ts: options.clock(), v: 1 as const };\n let text: string;\n try {\n text = JSON.stringify(envelope);\n } catch (cause) {\n throw new MeshPayloadError(`Failed to serialize message \"${type}\"`, { cause });\n }\n\n const bytes = utf8Bytes(text);\n if (bytes > options.maxMessageBytes) {\n throw new MeshPayloadError(`Message \"${type}\" is ${bytes} bytes (max ${options.maxMessageBytes})`);\n }\n\n try {\n dc.send(text);\n } catch (cause) {\n throw new MeshConnectionError(`Failed to send message \"${type}\"`, peer.id, { cause });\n }\n options.emitTap({ bytes, messageType: type, peerId: peer.id, type: 'message-sent' });\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"_node.js","names":["tappers","tapper","disposalCtrl","core","event","MeshDisposedError","next","handler","options","signal","record","MessageDedupe","handlers","peerId","peer","data","bytes","utf8Bytes","decoded","envelope","toEnvelope","set","message","cause","MeshError","type","dc","MeshConnectionError","randomId","payload","text","MeshPayloadError"],"sources":["../src/_node.ts"],"sourcesContent":["import { type RandomSource, tapper, utf8Bytes } from '@vielzeug/arsenal';\nimport { randomId } from './_random';\nimport { MessageDedupe, toEnvelope } from './_wire';\nimport { MeshConnectionError, MeshDisposedError, MeshError, MeshPayloadError } from './errors';\nimport type {\n MeshEvent,\n MeshInbound,\n MeshPeer,\n MeshPeerStatus,\n MeshStatus,\n RTCDataChannelLike,\n RTCPeerConnectionLike,\n Unsubscribe,\n} from './types';\n\ntype Tapper = (event: MeshEvent) => void;\n\n/** Shared lifecycle core: status, tappers, disposal. */\nexport interface NodeCore {\n readonly disposalCtrl: AbortController;\n disposed: boolean;\n emitTap(event: MeshEvent): void;\n ensureLive(): void;\n /** Emits the final status-change + dispose events, clears tappers, aborts. */\n markDisposed(): void;\n setStatus(next: MeshStatus): void;\n status: MeshStatus;\n tap(handler: Tapper, options?: { readonly signal?: AbortSignal }): Unsubscribe;\n}\n\nexport function createNodeCore(signal?: AbortSignal): NodeCore {\n const tappers = tapper<MeshEvent>();\n const disposalCtrl = new AbortController();\n\n const core: NodeCore = {\n disposalCtrl,\n disposed: false,\n\n emitTap(event) {\n tappers.emit(event);\n },\n\n ensureLive() {\n if (core.disposed) throw new MeshDisposedError();\n },\n\n markDisposed() {\n if (core.disposed) return;\n core.disposed = true;\n core.status = 'disposed';\n core.emitTap({ status: 'disposed', type: 'status-change' });\n core.emitTap({ type: 'dispose' });\n tappers.clear();\n disposalCtrl.abort();\n },\n\n setStatus(next) {\n if (core.disposed || core.status === next) return;\n core.status = next;\n core.emitTap({ status: next, type: 'status-change' });\n },\n status: 'idle',\n\n tap(handler, options) {\n if (core.disposed) return () => {};\n return tappers.tap(handler, options);\n },\n };\n\n if (signal) {\n if (signal.aborted) {\n core.markDisposed();\n } else {\n signal.addEventListener('abort', () => core.markDisposed(), { once: true });\n }\n }\n\n return core;\n}\n\n// ─── Peers ───────────────────────────────────────────────────────────────────\n\n/** Internal peer record; `publicPeer` exposes live status through getters. */\nexport interface PeerRecord {\n dc: RTCDataChannelLike | null;\n readonly dedupe: MessageDedupe;\n readonly id: string;\n readonly name: string | undefined;\n pc: RTCPeerConnectionLike | null;\n readonly publicPeer: MeshPeer;\n readonly role: 'host' | 'guest';\n status: MeshPeerStatus;\n}\n\nexport function createPeerRecord(id: string, name: string | undefined, role: 'host' | 'guest'): PeerRecord {\n const record: PeerRecord = {\n dc: null,\n dedupe: new MessageDedupe(),\n id,\n name,\n pc: null,\n publicPeer: {\n get id() {\n return record.id;\n },\n get name() {\n return record.name;\n },\n get role() {\n return record.role;\n },\n get status() {\n return record.status;\n },\n },\n role,\n status: 'connecting',\n };\n return record;\n}\n\n// ─── Messenger ───────────────────────────────────────────────────────────────\n\ntype InboundHandler = (message: MeshInbound<unknown>) => void;\n\nexport interface MessengerOptions {\n readonly clock: () => number;\n readonly emitTap: (event: MeshEvent) => void;\n readonly maxMessageBytes: number;\n readonly random?: RandomSource;\n}\n\nexport interface Messenger {\n clear(): void;\n /** Entry point for `dc` 'message' events. */\n handleInbound(peer: PeerRecord, data: unknown): void;\n on(type: string, handler: InboundHandler): Unsubscribe;\n /** Sends a typed envelope to a peer. Throws `MeshConnectionError`/`MeshPayloadError`. */\n sendTo(peer: PeerRecord, type: string, payload: unknown): void;\n}\n\nexport function createMessenger(options: MessengerOptions): Messenger {\n const handlers = new Map<string, Set<InboundHandler>>();\n\n function reject(peerId: string, reason: 'too-large' | 'duplicate' | 'malformed' | 'unknown-type'): void {\n options.emitTap({ peerId, reason, type: 'message-rejected' });\n }\n\n return {\n clear() {\n handlers.clear();\n },\n\n handleInbound(peer, data) {\n const peerId = peer.id;\n\n if (typeof data !== 'string') {\n reject(peerId, 'malformed');\n return;\n }\n\n const bytes = utf8Bytes(data);\n if (bytes > options.maxMessageBytes) {\n reject(peerId, 'too-large');\n return;\n }\n\n let decoded: unknown;\n try {\n decoded = JSON.parse(data);\n } catch {\n reject(peerId, 'malformed');\n return;\n }\n\n const envelope = toEnvelope(decoded);\n if (!envelope) {\n reject(peerId, 'malformed');\n return;\n }\n\n if (!peer.dedupe.check(envelope.id)) {\n reject(peerId, 'duplicate');\n return;\n }\n\n const set = handlers.get(envelope.t);\n if (!set || set.size === 0) {\n reject(peerId, 'unknown-type');\n return;\n }\n\n options.emitTap({ bytes, messageType: envelope.t, peerId, type: 'message-received' });\n const message: MeshInbound<unknown> = {\n messageId: envelope.id,\n payload: envelope.p,\n peerId,\n sentAt: envelope.ts,\n };\n for (const handler of set) {\n try {\n handler(message);\n } catch (cause) {\n options.emitTap({ error: new MeshError('Message handler threw', { cause }), type: 'error' });\n }\n }\n },\n\n on(type, handler) {\n let set = handlers.get(type);\n if (!set) {\n set = new Set();\n handlers.set(type, set);\n }\n set.add(handler);\n return () => {\n set.delete(handler);\n if (set.size === 0) handlers.delete(type);\n };\n },\n\n sendTo(peer, type, payload) {\n const dc = peer.dc;\n if (!dc || dc.readyState !== 'open' || peer.status !== 'connected') {\n throw new MeshConnectionError(`Peer \"${peer.id}\" is not connected`, peer.id);\n }\n\n const envelope = { id: randomId(options.random), p: payload, t: type, ts: options.clock(), v: 1 as const };\n let text: string;\n try {\n text = JSON.stringify(envelope);\n } catch (cause) {\n throw new MeshPayloadError(`Failed to serialize message \"${type}\"`, { cause });\n }\n\n const bytes = utf8Bytes(text);\n if (bytes > options.maxMessageBytes) {\n throw new MeshPayloadError(`Message \"${type}\" is ${bytes} bytes (max ${options.maxMessageBytes})`);\n }\n\n try {\n dc.send(text);\n } catch (cause) {\n throw new MeshConnectionError(`Failed to send message \"${type}\"`, peer.id, { cause });\n }\n options.emitTap({ bytes, messageType: type, peerId: peer.id, type: 'message-sent' });\n },\n };\n}\n"],"mappings":";;;;;AA8BA,SAAgB,eAAe,GAAgC;CAC7D,IAAMA,IAAUC,EAAkB,GAC5BC,IAAe,IAAI,gBAAgB,GAEnCC,IAAiB;EACrB;EACA,UAAU;EAEV,QAAQ,GAAO;GACb,EAAQ,KAAKC,CAAK;EACpB;EAEA,aAAa;GACX,IAAID,EAAK,UAAU,MAAM,IAAIE,EAAkB;EACjD;EAEA,eAAe;GACb,AAAIF,EAAK,aACT,EAAK,WAAW,IAChB,EAAK,SAAS,YACdA,EAAK,QAAQ;IAAE,QAAQ;IAAY,MAAM;GAAgB,CAAC,GAC1DA,EAAK,QAAQ,EAAE,MAAM,UAAU,CAAC,GAChCH,EAAQ,MAAM,GACdE,EAAa,MAAM;EACrB;EAEA,UAAU,GAAM;GACd,AAAIC,EAAK,YAAYA,EAAK,WAAWG,MACrC,EAAK,SAASA,GACdH,EAAK,QAAQ;IAAE,QAAQG;IAAM,MAAM;GAAgB,CAAC;EACtD;EACA,QAAQ;EAER,IAAI,GAAS,GAAS;GAEpB,OADIH,EAAK,iBAAuB,CAAC,IAC1BH,EAAQ,IAAIO,GAASC,CAAO;EACrC;CACF;CAUA,OARIC,MACEA,EAAO,UACTN,EAAK,aAAa,IAElBM,EAAO,iBAAiB,eAAeN,EAAK,aAAa,GAAG,EAAE,MAAM,GAAK,CAAC,IAIvEA;AACT;AAgBA,SAAgB,iBAAiB,GAAY,GAA0B,GAAoC;CACzG,IAAMO,IAAqB;EACzB,IAAI;EACJ,QAAQ,IAAIC,EAAc;EAC1B;EACA;EACA,IAAI;EACJ,YAAY;GACV,IAAI,KAAK;IACP,OAAOD,EAAO;GAChB;GACA,IAAI,OAAO;IACT,OAAOA,EAAO;GAChB;GACA,IAAI,OAAO;IACT,OAAOA,EAAO;GAChB;GACA,IAAI,SAAS;IACX,OAAOA,EAAO;GAChB;EACF;EACA;EACA,QAAQ;CACV;CACA,OAAOA;AACT;AAsBA,SAAgB,gBAAgB,GAAsC;CACpE,IAAME,oBAAW,IAAI,IAAiC;CAEtD,SAAS,OAAO,GAAgB,GAAwE;EACtG,EAAQ,QAAQ;GAAE;GAAQ;GAAQ,MAAM;EAAmB,CAAC;CAC9D;CAEA,OAAO;EACL,QAAQ;GACN,EAAS,MAAM;EACjB;EAEA,cAAc,GAAM,GAAM;GACxB,IAAMC,IAASC,EAAK;GAEpB,IAAI,OAAOC,KAAS,UAAU;IAC5B,OAAOF,GAAQ,WAAW;IAC1B;GACF;GAEA,IAAMG,IAAQC,EAAUF,CAAI;GAC5B,IAAIC,IAAQR,EAAQ,iBAAiB;IACnC,OAAOK,GAAQ,WAAW;IAC1B;GACF;GAEA,IAAIK;GACJ,IAAI;IACF,IAAU,KAAK,MAAMH,CAAI;GAC3B,QAAQ;IACN,OAAOF,GAAQ,WAAW;IAC1B;GACF;GAEA,IAAMM,IAAWC,EAAWF,CAAO;GACnC,IAAI,CAACC,GAAU;IACb,OAAON,GAAQ,WAAW;IAC1B;GACF;GAEA,IAAI,CAACC,EAAK,OAAO,MAAMK,EAAS,EAAE,GAAG;IACnC,OAAON,GAAQ,WAAW;IAC1B;GACF;GAEA,IAAMQ,IAAMT,EAAS,IAAIO,EAAS,CAAC;GACnC,IAAI,CAACE,KAAOA,EAAI,SAAS,GAAG;IAC1B,OAAOR,GAAQ,cAAc;IAC7B;GACF;GAEA,EAAQ,QAAQ;IAAE;IAAO,aAAaM,EAAS;IAAG;IAAQ,MAAM;GAAmB,CAAC;GACpF,IAAMG,IAAgC;IACpC,WAAWH,EAAS;IACpB,SAASA,EAAS;IAClB;IACA,QAAQA,EAAS;GACnB;GACA,KAAK,IAAMZ,KAAWc,GACpB,IAAI;IACF,EAAQC,CAAO;GACjB,SAASC,GAAO;IACd,EAAQ,QAAQ;KAAE,OAAO,IAAIC,EAAU,yBAAyB,EAAE,SAAM,CAAC;KAAG,MAAM;IAAQ,CAAC;GAC7F;EAEJ;EAEA,GAAG,GAAM,GAAS;GAChB,IAAIH,IAAMT,EAAS,IAAIa,CAAI;GAM3B,OALKJ,MACH,oBAAM,IAAI,IAAI,GACdT,EAAS,IAAIa,GAAMJ,CAAG,IAExBA,EAAI,IAAId,CAAO,SACF;IAEX,AADAc,EAAI,OAAOd,CAAO,GACdc,EAAI,SAAS,KAAGT,EAAS,OAAOa,CAAI;GAC1C;EACF;EAEA,OAAO,GAAM,GAAM,GAAS;GAC1B,IAAMC,IAAKZ,EAAK;GAChB,IAAI,CAACY,KAAMA,EAAG,eAAe,UAAUZ,EAAK,WAAW,aACrD,MAAM,IAAIa,EAAoB,SAASb,EAAK,GAAG,qBAAqBA,EAAK,EAAE;GAG7E,IAAMK,IAAW;IAAE,IAAIS,EAASpB,EAAQ,MAAM;IAAG,GAAGqB;IAAS,GAAGJ;IAAM,IAAIjB,EAAQ,MAAM;IAAG,GAAG;GAAW,GACrGsB;GACJ,IAAI;IACF,IAAO,KAAK,UAAUX,CAAQ;GAChC,SAASI,GAAO;IACd,MAAM,IAAIQ,EAAiB,gCAAgCN,EAAK,IAAI,EAAE,SAAM,CAAC;GAC/E;GAEA,IAAMT,IAAQC,EAAUa,CAAI;GAC5B,IAAId,IAAQR,EAAQ,iBAClB,MAAM,IAAIuB,EAAiB,YAAYN,EAAK,OAAOT,EAAM,cAAcR,EAAQ,gBAAgB,EAAE;GAGnG,IAAI;IACF,EAAG,KAAKsB,CAAI;GACd,SAASP,GAAO;IACd,MAAM,IAAII,EAAoB,2BAA2BF,EAAK,IAAIX,EAAK,IAAI,EAAE,SAAM,CAAC;GACtF;GACA,EAAQ,QAAQ;IAAE;IAAO,aAAaW;IAAM,QAAQX,EAAK;IAAI,MAAM;GAAe,CAAC;EACrF;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"_proof.cjs","names":[],"sources":["../src/_proof.ts"],"sourcesContent":["import { bytesToBase64Url } from '@vielzeug/arsenal';\nimport { canonicalSdp } from './_sdp';\nimport { MeshUnsupportedError } from './errors';\n\n/**\n * Proof of invitation possession: HMAC-SHA-256 of the guest SDP keyed by the\n * invitation secret. The SDP is canonicalized first so the proof is stable\n * across pairing codecs that rebuild the description (see `mq2.`). The host\n * recomputes the proof without receiving the secret back.\n *\n * WebCrypto is required: a non-cryptographic fallback would make the proof\n * forgeable, which defeats its purpose. Environments without `SubtleCrypto`\n * (insecure contexts) must pair over `https://` or `localhost` instead.\n *\n * @throws {MeshUnsupportedError} When `SubtleCrypto` is unavailable.\n */\nexport async function createProof(secret: string, sdp: string): Promise<string> {\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) {\n throw new MeshUnsupportedError('WebCrypto is unavailable: pairing requires a secure context');\n }\n const encoder = new TextEncoder();\n const key = await subtle.importKey('raw', encoder.encode(secret), { hash: 'SHA-256', name: 'HMAC' }, false, ['sign']);\n const signature = await subtle.sign('HMAC', key, encoder.encode(canonicalSdp(sdp)));\n return bytesToBase64Url(new Uint8Array(signature));\n}\n"],"mappings":"2FAgBA,eAAsB,YAAY,EAAgB,EAA8B,CAC9E,IAAM,EAAS,WAAW,QAAQ,OAClC,GAAI,CAAC,EACH,MAAM,IAAI,EAAA,qBAAqB,6DAA6D,EAE9F,IAAM,EAAU,IAAI,YACd,EAAM,MAAM,EAAO,UAAU,MAAO,EAAQ,OAAO,CAAM,EAAG,CAAE,KAAM,UAAW,KAAM,MAAO,EAAG,GAAO,CAAC,MAAM,CAAC,EAC9G,EAAY,MAAM,EAAO,KAAK,OAAQ,EAAK,EAAQ,OAAO,EAAA,aAAa,CAAG,CAAC,CAAC,EAClF,OAAA,EAAO,EAAA,iBAAA,CAAiB,IAAI,WAAW,CAAS,CAAC,CACnD"}
1
+ {"version":3,"file":"_proof.cjs","names":["MeshUnsupportedError","canonicalSdp","bytesToBase64Url","require_errors","require__sdp","_vielzeug_arsenal","subtle","encoder","key","secret","signature","sdp"],"sources":["../src/_proof.ts"],"sourcesContent":["import { bytesToBase64Url } from '@vielzeug/arsenal';\nimport { canonicalSdp } from './_sdp';\nimport { MeshUnsupportedError } from './errors';\n\n/**\n * Proof of invitation possession: HMAC-SHA-256 of the guest SDP keyed by the\n * invitation secret. The SDP is canonicalized first so the proof is stable\n * across pairing codecs that rebuild the description (see `mq2.`). The host\n * recomputes the proof without receiving the secret back.\n *\n * WebCrypto is required: a non-cryptographic fallback would make the proof\n * forgeable, which defeats its purpose. Environments without `SubtleCrypto`\n * (insecure contexts) must pair over `https://` or `localhost` instead.\n *\n * @throws {MeshUnsupportedError} When `SubtleCrypto` is unavailable.\n */\nexport async function createProof(secret: string, sdp: string): Promise<string> {\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) {\n throw new MeshUnsupportedError('WebCrypto is unavailable: pairing requires a secure context');\n }\n const encoder = new TextEncoder();\n const key = await subtle.importKey('raw', encoder.encode(secret), { hash: 'SHA-256', name: 'HMAC' }, false, ['sign']);\n const signature = await subtle.sign('HMAC', key, encoder.encode(canonicalSdp(sdp)));\n return bytesToBase64Url(new Uint8Array(signature));\n}\n"],"mappings":"A,2FAgBA,eAAsB,YAAY,EAAgB,EAA8B,CAC9E,IAAMM,EAAS,WAAW,QAAQ,OAClC,GAAI,CAACA,EACH,MAAM,IAAIN,EAAAA,qBAAqB,6DAA6D,EAE9F,IAAMO,EAAU,IAAI,YACdC,EAAM,MAAMF,EAAO,UAAU,MAAOC,EAAQ,OAAOE,CAAM,EAAG,CAAE,KAAM,UAAW,KAAM,MAAO,EAAG,GAAO,CAAC,MAAM,CAAC,EAC9GC,EAAY,MAAMJ,EAAO,KAAK,OAAQE,EAAKD,EAAQ,OAAON,EAAAA,aAAaU,CAAG,CAAC,CAAC,EAClF,OAAA,EAAOT,EAAAA,iBAAAA,CAAiB,IAAI,WAAWQ,CAAS,CAAC,CACnD,C"}
package/dist/_proof.js CHANGED
@@ -1,2 +1,17 @@
1
- import{MeshUnsupportedError as e}from"./errors.js";import{canonicalSdp as t}from"./_sdp.js";import{bytesToBase64Url as n}from"@vielzeug/arsenal";async function createProof(r,i){let a=globalThis.crypto?.subtle;if(!a)throw new e(`WebCrypto is unavailable: pairing requires a secure context`);let o=new TextEncoder,s=await a.importKey(`raw`,o.encode(r),{hash:`SHA-256`,name:`HMAC`},!1,[`sign`]),c=await a.sign(`HMAC`,s,o.encode(t(i)));return n(new Uint8Array(c))}export{createProof};
1
+ import { MeshUnsupportedError as e } from "./errors.js";
2
+ import { canonicalSdp as t } from "./_sdp.js";
3
+ import { bytesToBase64Url as n } from "@vielzeug/arsenal";
4
+ //#region src/_proof.ts
5
+ async function createProof(r, i) {
6
+ let a = globalThis.crypto?.subtle;
7
+ if (!a) throw new e("WebCrypto is unavailable: pairing requires a secure context");
8
+ let o = new TextEncoder(), s = await a.importKey("raw", o.encode(r), {
9
+ hash: "SHA-256",
10
+ name: "HMAC"
11
+ }, !1, ["sign"]), c = await a.sign("HMAC", s, o.encode(t(i)));
12
+ return n(new Uint8Array(c));
13
+ }
14
+ //#endregion
15
+ export { createProof };
16
+
2
17
  //# sourceMappingURL=_proof.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"_proof.js","names":[],"sources":["../src/_proof.ts"],"sourcesContent":["import { bytesToBase64Url } from '@vielzeug/arsenal';\nimport { canonicalSdp } from './_sdp';\nimport { MeshUnsupportedError } from './errors';\n\n/**\n * Proof of invitation possession: HMAC-SHA-256 of the guest SDP keyed by the\n * invitation secret. The SDP is canonicalized first so the proof is stable\n * across pairing codecs that rebuild the description (see `mq2.`). The host\n * recomputes the proof without receiving the secret back.\n *\n * WebCrypto is required: a non-cryptographic fallback would make the proof\n * forgeable, which defeats its purpose. Environments without `SubtleCrypto`\n * (insecure contexts) must pair over `https://` or `localhost` instead.\n *\n * @throws {MeshUnsupportedError} When `SubtleCrypto` is unavailable.\n */\nexport async function createProof(secret: string, sdp: string): Promise<string> {\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) {\n throw new MeshUnsupportedError('WebCrypto is unavailable: pairing requires a secure context');\n }\n const encoder = new TextEncoder();\n const key = await subtle.importKey('raw', encoder.encode(secret), { hash: 'SHA-256', name: 'HMAC' }, false, ['sign']);\n const signature = await subtle.sign('HMAC', key, encoder.encode(canonicalSdp(sdp)));\n return bytesToBase64Url(new Uint8Array(signature));\n}\n"],"mappings":"iJAgBA,eAAsB,YAAY,EAAgB,EAA8B,CAC9E,IAAM,EAAS,WAAW,QAAQ,OAClC,GAAI,CAAC,EACH,MAAM,IAAI,EAAqB,6DAA6D,EAE9F,IAAM,EAAU,IAAI,YACd,EAAM,MAAM,EAAO,UAAU,MAAO,EAAQ,OAAO,CAAM,EAAG,CAAE,KAAM,UAAW,KAAM,MAAO,EAAG,GAAO,CAAC,MAAM,CAAC,EAC9G,EAAY,MAAM,EAAO,KAAK,OAAQ,EAAK,EAAQ,OAAO,EAAa,CAAG,CAAC,CAAC,EAClF,OAAO,EAAiB,IAAI,WAAW,CAAS,CAAC,CACnD"}
1
+ {"version":3,"file":"_proof.js","names":["subtle","MeshUnsupportedError","encoder","key","secret","signature","canonicalSdp","sdp","bytesToBase64Url"],"sources":["../src/_proof.ts"],"sourcesContent":["import { bytesToBase64Url } from '@vielzeug/arsenal';\nimport { canonicalSdp } from './_sdp';\nimport { MeshUnsupportedError } from './errors';\n\n/**\n * Proof of invitation possession: HMAC-SHA-256 of the guest SDP keyed by the\n * invitation secret. The SDP is canonicalized first so the proof is stable\n * across pairing codecs that rebuild the description (see `mq2.`). The host\n * recomputes the proof without receiving the secret back.\n *\n * WebCrypto is required: a non-cryptographic fallback would make the proof\n * forgeable, which defeats its purpose. Environments without `SubtleCrypto`\n * (insecure contexts) must pair over `https://` or `localhost` instead.\n *\n * @throws {MeshUnsupportedError} When `SubtleCrypto` is unavailable.\n */\nexport async function createProof(secret: string, sdp: string): Promise<string> {\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) {\n throw new MeshUnsupportedError('WebCrypto is unavailable: pairing requires a secure context');\n }\n const encoder = new TextEncoder();\n const key = await subtle.importKey('raw', encoder.encode(secret), { hash: 'SHA-256', name: 'HMAC' }, false, ['sign']);\n const signature = await subtle.sign('HMAC', key, encoder.encode(canonicalSdp(sdp)));\n return bytesToBase64Url(new Uint8Array(signature));\n}\n"],"mappings":";;;;AAgBA,eAAsB,YAAY,GAAgB,GAA8B;CAC9E,IAAMA,IAAS,WAAW,QAAQ;CAClC,IAAI,CAACA,GACH,MAAM,IAAIC,EAAqB,6DAA6D;CAE9F,IAAMC,IAAU,IAAI,YAAY,GAC1BC,IAAM,MAAMH,EAAO,UAAU,OAAOE,EAAQ,OAAOE,CAAM,GAAG;EAAE,MAAM;EAAW,MAAM;CAAO,GAAG,IAAO,CAAC,MAAM,CAAC,GAC9GC,IAAY,MAAML,EAAO,KAAK,QAAQG,GAAKD,EAAQ,OAAOI,EAAaC,CAAG,CAAC,CAAC;CAClF,OAAOC,EAAiB,IAAI,WAAWH,CAAS,CAAC;AACnD"}
@@ -1 +1 @@
1
- {"version":3,"file":"_random.cjs","names":[],"sources":["../src/_random.ts"],"sourcesContent":["import { bytesToBase64Url, type RandomSource, randomFloat } from '@vielzeug/arsenal';\n\n/**\n * Random bytes from `crypto.getRandomValues`, or from an injected\n * `RandomSource` for deterministic tests.\n */\nexport function randomBytes(length: number, source?: RandomSource): Uint8Array {\n const bytes = new Uint8Array(length);\n if (source) {\n for (let index = 0; index < length; index++) bytes[index] = Math.floor(randomFloat(source) * 256);\n } else {\n crypto.getRandomValues(bytes);\n }\n return bytes;\n}\n\n/** Random 128-bit base64url id: used for peer ids, session ids, and message ids. */\nexport function randomId(source?: RandomSource): string {\n return bytesToBase64Url(randomBytes(16, source));\n}\n"],"mappings":"mCAMA,SAAgB,YAAY,EAAgB,EAAmC,CAC7E,IAAM,EAAQ,IAAI,WAAW,CAAM,EACnC,GAAI,EACF,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAQ,IAAS,EAAM,GAAS,KAAK,OAAA,EAAM,EAAA,YAAA,CAAY,CAAM,EAAI,GAAG,OAEhG,OAAO,gBAAgB,CAAK,EAE9B,OAAO,CACT,CAGA,SAAgB,SAAS,EAA+B,CACtD,OAAA,EAAO,EAAA,iBAAA,CAAiB,YAAY,GAAI,CAAM,CAAC,CACjD"}
1
+ {"version":3,"file":"_random.cjs","names":["randomFloat","bytesToBase64Url","_vielzeug_arsenal","bytes","length","source","index"],"sources":["../src/_random.ts"],"sourcesContent":["import { bytesToBase64Url, type RandomSource, randomFloat } from '@vielzeug/arsenal';\n\n/**\n * Random bytes from `crypto.getRandomValues`, or from an injected\n * `RandomSource` for deterministic tests.\n */\nexport function randomBytes(length: number, source?: RandomSource): Uint8Array {\n const bytes = new Uint8Array(length);\n if (source) {\n for (let index = 0; index < length; index++) bytes[index] = Math.floor(randomFloat(source) * 256);\n } else {\n crypto.getRandomValues(bytes);\n }\n return bytes;\n}\n\n/** Random 128-bit base64url id: used for peer ids, session ids, and message ids. */\nexport function randomId(source?: RandomSource): string {\n return bytesToBase64Url(randomBytes(16, source));\n}\n"],"mappings":"A,mCAMA,SAAgB,YAAY,EAAgB,EAAmC,CAC7E,IAAMG,EAAQ,IAAI,WAAWC,CAAM,EACnC,GAAIC,EACF,IAAK,IAAIC,EAAQ,EAAGA,EAAQF,EAAQ,IAAS,EAAME,GAAS,KAAK,OAAA,EAAMN,EAAAA,YAAAA,CAAYK,CAAM,EAAI,GAAG,OAEhG,OAAO,gBAAgBF,CAAK,EAE9B,OAAOA,CACT,CAGA,SAAgB,SAAS,EAA+B,CACtD,OAAA,EAAOF,EAAAA,iBAAAA,CAAiB,YAAY,GAAII,CAAM,CAAC,CACjD,C"}
package/dist/_random.js CHANGED
@@ -1,2 +1,15 @@
1
- import{bytesToBase64Url as e,randomFloat as t}from"@vielzeug/arsenal";function randomBytes(e,n){let r=new Uint8Array(e);if(n)for(let i=0;i<e;i++)r[i]=Math.floor(t(n)*256);else crypto.getRandomValues(r);return r}function randomId(t){return e(randomBytes(16,t))}export{randomBytes,randomId};
1
+ import { bytesToBase64Url as e, randomFloat as t } from "@vielzeug/arsenal";
2
+ //#region src/_random.ts
3
+ function randomBytes(e, n) {
4
+ let r = new Uint8Array(e);
5
+ if (n) for (let i = 0; i < e; i++) r[i] = Math.floor(t(n) * 256);
6
+ else crypto.getRandomValues(r);
7
+ return r;
8
+ }
9
+ function randomId(t) {
10
+ return e(randomBytes(16, t));
11
+ }
12
+ //#endregion
13
+ export { randomBytes, randomId };
14
+
2
15
  //# sourceMappingURL=_random.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"_random.js","names":[],"sources":["../src/_random.ts"],"sourcesContent":["import { bytesToBase64Url, type RandomSource, randomFloat } from '@vielzeug/arsenal';\n\n/**\n * Random bytes from `crypto.getRandomValues`, or from an injected\n * `RandomSource` for deterministic tests.\n */\nexport function randomBytes(length: number, source?: RandomSource): Uint8Array {\n const bytes = new Uint8Array(length);\n if (source) {\n for (let index = 0; index < length; index++) bytes[index] = Math.floor(randomFloat(source) * 256);\n } else {\n crypto.getRandomValues(bytes);\n }\n return bytes;\n}\n\n/** Random 128-bit base64url id: used for peer ids, session ids, and message ids. */\nexport function randomId(source?: RandomSource): string {\n return bytesToBase64Url(randomBytes(16, source));\n}\n"],"mappings":"sEAMA,SAAgB,YAAY,EAAgB,EAAmC,CAC7E,IAAM,EAAQ,IAAI,WAAW,CAAM,EACnC,GAAI,EACF,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAQ,IAAS,EAAM,GAAS,KAAK,MAAM,EAAY,CAAM,EAAI,GAAG,OAEhG,OAAO,gBAAgB,CAAK,EAE9B,OAAO,CACT,CAGA,SAAgB,SAAS,EAA+B,CACtD,OAAO,EAAiB,YAAY,GAAI,CAAM,CAAC,CACjD"}
1
+ {"version":3,"file":"_random.js","names":["bytes","length","source","index","randomFloat","bytesToBase64Url"],"sources":["../src/_random.ts"],"sourcesContent":["import { bytesToBase64Url, type RandomSource, randomFloat } from '@vielzeug/arsenal';\n\n/**\n * Random bytes from `crypto.getRandomValues`, or from an injected\n * `RandomSource` for deterministic tests.\n */\nexport function randomBytes(length: number, source?: RandomSource): Uint8Array {\n const bytes = new Uint8Array(length);\n if (source) {\n for (let index = 0; index < length; index++) bytes[index] = Math.floor(randomFloat(source) * 256);\n } else {\n crypto.getRandomValues(bytes);\n }\n return bytes;\n}\n\n/** Random 128-bit base64url id: used for peer ids, session ids, and message ids. */\nexport function randomId(source?: RandomSource): string {\n return bytesToBase64Url(randomBytes(16, source));\n}\n"],"mappings":";;AAMA,SAAgB,YAAY,GAAgB,GAAmC;CAC7E,IAAMA,IAAQ,IAAI,WAAWC,CAAM;CACnC,IAAIC,GACF,KAAK,IAAIC,IAAQ,GAAGA,IAAQF,GAAQ,KAAS,EAAME,KAAS,KAAK,MAAMC,EAAYF,CAAM,IAAI,GAAG;MAEhG,OAAO,gBAAgBF,CAAK;CAE9B,OAAOA;AACT;AAGA,SAAgB,SAAS,GAA+B;CACtD,OAAOK,EAAiB,YAAY,IAAIH,CAAM,CAAC;AACjD"}
package/dist/_rtc.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"_rtc.cjs","names":[],"sources":["../src/_rtc.ts"],"sourcesContent":["import { MeshConnectionError, MeshDisposedError, MeshTimeoutError, MeshUnsupportedError } from './errors';\nimport type { MeshRtcFactory, RTCDataChannelLike, RTCPeerConnectionLike } from './types';\n\n/**\n * Wraps `globalThis.RTCPeerConnection`. Evaluated lazily at first use so\n * importing the package never throws in non-WebRTC environments.\n */\nexport const nativeRtcFactory: MeshRtcFactory = {\n createPeerConnection(config) {\n const Ctor = (globalThis as { RTCPeerConnection?: typeof RTCPeerConnection }).RTCPeerConnection;\n if (!Ctor) {\n throw new MeshUnsupportedError('WebRTC is unavailable in this environment: inject an `rtc` factory');\n }\n return new Ctor(config);\n },\n};\n\n/**\n * Textareas, clipboards, and share sheets normalize CRLF→LF;\n * `setRemoteDescription` requires CRLF line endings.\n */\nexport function normalizeSdp(sdp: string): string {\n const normalized = sdp.replace(/\\r?\\n/g, '\\r\\n');\n return normalized.endsWith('\\r\\n') ? normalized : `${normalized}\\r\\n`;\n}\n\n/**\n * Resolves when ICE gathering finishes or after `timeoutMs`: the caller\n * proceeds with whatever candidates were gathered (non-trickle semantics).\n */\nexport function waitIceGathering(pc: RTCPeerConnectionLike, timeoutMs: number): Promise<void> {\n if (pc.iceGatheringState === 'complete') return Promise.resolve();\n return new Promise((resolve) => {\n const cleanup = () => {\n clearTimeout(timer);\n pc.removeEventListener('icegatheringstatechange', onChange);\n };\n const onChange = () => {\n if (pc.iceGatheringState === 'complete') {\n cleanup();\n resolve();\n }\n };\n const timer = setTimeout(() => {\n cleanup();\n resolve();\n }, timeoutMs);\n pc.addEventListener('icegatheringstatechange', onChange);\n });\n}\n\n/**\n * Resolves when the data channel opens. Rejects with `MeshTimeoutError` after\n * `timeoutMs`, or when the channel closes while that deadline is still\n * pending, since a peer tearing down a never-opened channel races the same\n * deadline: `MeshConnectionError` when the ICE transport dies first, and\n * `MeshDisposedError` when `disposalSignal` aborts.\n */\nexport function waitChannelOpen(\n dc: RTCDataChannelLike,\n pc: RTCPeerConnectionLike,\n timeoutMs: number,\n peerId: string,\n disposalSignal: AbortSignal,\n): Promise<void> {\n if (dc.readyState === 'open') return Promise.resolve();\n return new Promise((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | null = null;\n const timeout = () => new MeshTimeoutError(`Timed out waiting ${timeoutMs}ms for the data channel to open`);\n const fail = (error: Error) => {\n cleanup();\n reject(error);\n };\n const cleanup = () => {\n if (timer) clearTimeout(timer);\n timer = null;\n dc.removeEventListener('open', onOpen);\n dc.removeEventListener('close', onClose);\n pc.removeEventListener('iceconnectionstatechange', onIce);\n disposalSignal.removeEventListener('abort', onDispose);\n };\n const onOpen = () => {\n cleanup();\n resolve();\n };\n // A close while our open deadline is still armed is the timeout it\n // effectively is; mirror the guest's open-timer guard so both ends agree\n // on the cause instead of racing to report whichever event landed first.\n const onClose = () => fail(timeout());\n const onIce = () => {\n const state = pc.iceConnectionState;\n if (state === 'failed' || state === 'closed' || state === 'disconnected') {\n fail(new MeshConnectionError(`ICE connection ${state} before channel open`, peerId));\n }\n };\n const onDispose = () => fail(new MeshDisposedError());\n timer = setTimeout(() => fail(timeout()), timeoutMs);\n dc.addEventListener('open', onOpen);\n dc.addEventListener('close', onClose);\n pc.addEventListener('iceconnectionstatechange', onIce);\n disposalSignal.addEventListener('abort', onDispose, { once: true });\n });\n}\n"],"mappings":"gCAOA,IAAa,EAAmC,CAC9C,qBAAqB,EAAQ,CAC3B,IAAM,EAAQ,WAAgE,kBAC9E,GAAI,CAAC,EACH,MAAM,IAAI,EAAA,qBAAqB,oEAAoE,EAErG,OAAO,IAAI,EAAK,CAAM,CACxB,CACF,EAMA,SAAgB,aAAa,EAAqB,CAChD,IAAM,EAAa,EAAI,QAAQ,SAAU;CAAM,EAC/C,OAAO,EAAW,SAAS;CAAM,EAAI,EAAa,GAAG,EAAW,KAClE,CAMA,SAAgB,iBAAiB,EAA2B,EAAkC,CAE5F,OADI,EAAG,oBAAsB,WAAmB,QAAQ,QAAQ,EACzD,IAAI,QAAS,GAAY,CAC9B,IAAM,YAAgB,CACpB,aAAa,CAAK,EAClB,EAAG,oBAAoB,0BAA2B,QAAQ,CAC5D,EACM,aAAiB,CACjB,EAAG,oBAAsB,aAC3B,QAAQ,EACR,EAAQ,EAEZ,EACM,EAAQ,eAAiB,CAC7B,QAAQ,EACR,EAAQ,CACV,EAAG,CAAS,EACZ,EAAG,iBAAiB,0BAA2B,QAAQ,CACzD,CAAC,CACH,CASA,SAAgB,gBACd,EACA,EACA,EACA,EACA,EACe,CAEf,OADI,EAAG,aAAe,OAAe,QAAQ,QAAQ,EAC9C,IAAI,SAAS,EAAS,IAAW,CACtC,IAAI,EAA8C,KAC5C,YAAgB,IAAI,EAAA,iBAAiB,qBAAqB,EAAU,gCAAgC,EACpG,KAAQ,GAAiB,CAC7B,QAAQ,EACR,EAAO,CAAK,CACd,EACM,YAAgB,CAChB,GAAO,aAAa,CAAK,EAC7B,EAAQ,KACR,EAAG,oBAAoB,OAAQ,MAAM,EACrC,EAAG,oBAAoB,QAAS,OAAO,EACvC,EAAG,oBAAoB,2BAA4B,KAAK,EACxD,EAAe,oBAAoB,QAAS,SAAS,CACvD,EACM,WAAe,CACnB,QAAQ,EACR,EAAQ,CACV,EAIM,YAAgB,KAAK,QAAQ,CAAC,EAC9B,UAAc,CAClB,IAAM,EAAQ,EAAG,oBACb,IAAU,UAAY,IAAU,UAAY,IAAU,iBACxD,KAAK,IAAI,EAAA,oBAAoB,kBAAkB,EAAM,sBAAuB,CAAM,CAAC,CAEvF,EACM,cAAkB,KAAK,IAAI,EAAA,iBAAmB,EACpD,EAAQ,eAAiB,KAAK,QAAQ,CAAC,EAAG,CAAS,EACnD,EAAG,iBAAiB,OAAQ,MAAM,EAClC,EAAG,iBAAiB,QAAS,OAAO,EACpC,EAAG,iBAAiB,2BAA4B,KAAK,EACrD,EAAe,iBAAiB,QAAS,UAAW,CAAE,KAAM,EAAK,CAAC,CACpE,CAAC,CACH"}
1
+ {"version":3,"file":"_rtc.cjs","names":["MeshUnsupportedError","MeshTimeoutError","MeshConnectionError","MeshDisposedError","require_errors","nativeRtcFactory","Ctor","config","normalized","sdp","pc","timer","resolve","timeoutMs","dc","reject","error","disposalSignal","state","peerId"],"sources":["../src/_rtc.ts"],"sourcesContent":["import { MeshConnectionError, MeshDisposedError, MeshTimeoutError, MeshUnsupportedError } from './errors';\nimport type { MeshRtcFactory, RTCDataChannelLike, RTCPeerConnectionLike } from './types';\n\n/**\n * Wraps `globalThis.RTCPeerConnection`. Evaluated lazily at first use so\n * importing the package never throws in non-WebRTC environments.\n */\nexport const nativeRtcFactory: MeshRtcFactory = {\n createPeerConnection(config) {\n const Ctor = (globalThis as { RTCPeerConnection?: typeof RTCPeerConnection }).RTCPeerConnection;\n if (!Ctor) {\n throw new MeshUnsupportedError('WebRTC is unavailable in this environment: inject an `rtc` factory');\n }\n return new Ctor(config);\n },\n};\n\n/**\n * Textareas, clipboards, and share sheets normalize CRLF→LF;\n * `setRemoteDescription` requires CRLF line endings.\n */\nexport function normalizeSdp(sdp: string): string {\n const normalized = sdp.replace(/\\r?\\n/g, '\\r\\n');\n return normalized.endsWith('\\r\\n') ? normalized : `${normalized}\\r\\n`;\n}\n\n/**\n * Resolves when ICE gathering finishes or after `timeoutMs`: the caller\n * proceeds with whatever candidates were gathered (non-trickle semantics).\n */\nexport function waitIceGathering(pc: RTCPeerConnectionLike, timeoutMs: number): Promise<void> {\n if (pc.iceGatheringState === 'complete') return Promise.resolve();\n return new Promise((resolve) => {\n const cleanup = () => {\n clearTimeout(timer);\n pc.removeEventListener('icegatheringstatechange', onChange);\n };\n const onChange = () => {\n if (pc.iceGatheringState === 'complete') {\n cleanup();\n resolve();\n }\n };\n const timer = setTimeout(() => {\n cleanup();\n resolve();\n }, timeoutMs);\n pc.addEventListener('icegatheringstatechange', onChange);\n });\n}\n\n/**\n * Resolves when the data channel opens. Rejects with `MeshTimeoutError` after\n * `timeoutMs`, or when the channel closes while that deadline is still\n * pending, since a peer tearing down a never-opened channel races the same\n * deadline: `MeshConnectionError` when the ICE transport dies first, and\n * `MeshDisposedError` when `disposalSignal` aborts.\n */\nexport function waitChannelOpen(\n dc: RTCDataChannelLike,\n pc: RTCPeerConnectionLike,\n timeoutMs: number,\n peerId: string,\n disposalSignal: AbortSignal,\n): Promise<void> {\n if (dc.readyState === 'open') return Promise.resolve();\n return new Promise((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | null = null;\n const timeout = () => new MeshTimeoutError(`Timed out waiting ${timeoutMs}ms for the data channel to open`);\n const fail = (error: Error) => {\n cleanup();\n reject(error);\n };\n const cleanup = () => {\n if (timer) clearTimeout(timer);\n timer = null;\n dc.removeEventListener('open', onOpen);\n dc.removeEventListener('close', onClose);\n pc.removeEventListener('iceconnectionstatechange', onIce);\n disposalSignal.removeEventListener('abort', onDispose);\n };\n const onOpen = () => {\n cleanup();\n resolve();\n };\n // A close while our open deadline is still armed is the timeout it\n // effectively is; mirror the guest's open-timer guard so both ends agree\n // on the cause instead of racing to report whichever event landed first.\n const onClose = () => fail(timeout());\n const onIce = () => {\n const state = pc.iceConnectionState;\n if (state === 'failed' || state === 'closed' || state === 'disconnected') {\n fail(new MeshConnectionError(`ICE connection ${state} before channel open`, peerId));\n }\n };\n const onDispose = () => fail(new MeshDisposedError());\n timer = setTimeout(() => fail(timeout()), timeoutMs);\n dc.addEventListener('open', onOpen);\n dc.addEventListener('close', onClose);\n pc.addEventListener('iceconnectionstatechange', onIce);\n disposalSignal.addEventListener('abort', onDispose, { once: true });\n });\n}\n"],"mappings":"A,gCAOA,IAAaK,EAAmC,CAC9C,qBAAqB,EAAQ,CAC3B,IAAMC,EAAQ,WAAgE,kBAC9E,GAAI,CAACA,EACH,MAAM,IAAIN,EAAAA,qBAAqB,oEAAoE,EAErG,OAAO,IAAIM,EAAKC,CAAM,CACxB,CACF,EAMA,SAAgB,aAAa,EAAqB,CAChD,IAAMC,EAAaC,EAAI,QAAQ,SAAU;CAAM,EAC/C,OAAOD,EAAW,SAAS;CAAM,EAAIA,EAAa,GAAGA,EAAW,KAClE,CAMA,SAAgB,iBAAiB,EAA2B,EAAkC,CAE5F,OADIE,EAAG,oBAAsB,WAAmB,QAAQ,QAAQ,EACzD,IAAI,QAAS,GAAY,CAC9B,IAAM,YAAgB,CACpB,aAAaC,CAAK,EAClBD,EAAG,oBAAoB,0BAA2B,QAAQ,CAC5D,EACM,aAAiB,CACjBA,EAAG,oBAAsB,aAC3B,QAAQ,EACRE,EAAQ,EAEZ,EACMD,EAAQ,eAAiB,CAC7B,QAAQ,EACRC,EAAQ,CACV,EAAGC,CAAS,EACZH,EAAG,iBAAiB,0BAA2B,QAAQ,CACzD,CAAC,CACH,CASA,SAAgB,gBACd,EACA,EACA,EACA,EACA,EACe,CAEf,OADII,EAAG,aAAe,OAAe,QAAQ,QAAQ,EAC9C,IAAI,SAAS,EAAS,IAAW,CACtC,IAAIH,EAA8C,KAC5C,YAAgB,IAAIV,EAAAA,iBAAiB,qBAAqBY,EAAU,gCAAgC,EACpG,KAAQ,GAAiB,CAC7B,QAAQ,EACRE,EAAOC,CAAK,CACd,EACM,YAAgB,CAChBL,GAAO,aAAaA,CAAK,EAC7B,EAAQ,KACRG,EAAG,oBAAoB,OAAQ,MAAM,EACrCA,EAAG,oBAAoB,QAAS,OAAO,EACvCJ,EAAG,oBAAoB,2BAA4B,KAAK,EACxDO,EAAe,oBAAoB,QAAS,SAAS,CACvD,EACM,WAAe,CACnB,QAAQ,EACRL,EAAQ,CACV,EAIM,YAAgB,KAAK,QAAQ,CAAC,EAC9B,UAAc,CAClB,IAAMM,EAAQR,EAAG,oBACbQ,IAAU,UAAYA,IAAU,UAAYA,IAAU,iBACxD,KAAK,IAAIhB,EAAAA,oBAAoB,kBAAkBgB,EAAM,sBAAuBC,CAAM,CAAC,CAEvF,EACM,cAAkB,KAAK,IAAIhB,EAAAA,iBAAmB,EACpD,EAAQ,eAAiB,KAAK,QAAQ,CAAC,EAAGU,CAAS,EACnDC,EAAG,iBAAiB,OAAQ,MAAM,EAClCA,EAAG,iBAAiB,QAAS,OAAO,EACpCJ,EAAG,iBAAiB,2BAA4B,KAAK,EACrDO,EAAe,iBAAiB,QAAS,UAAW,CAAE,KAAM,EAAK,CAAC,CACpE,CAAC,CACH,C"}
package/dist/_rtc.js CHANGED
@@ -1,4 +1,42 @@
1
- import{MeshConnectionError as e,MeshDisposedError as t,MeshTimeoutError as n,MeshUnsupportedError as r}from"./errors.js";var i={createPeerConnection(e){let t=globalThis.RTCPeerConnection;if(!t)throw new r("WebRTC is unavailable in this environment: inject an `rtc` factory");return new t(e)}};function normalizeSdp(e){let t=e.replace(/\r?\n/g,`\r
2
- `);return t.endsWith(`\r
3
- `)?t:`${t}\r\n`}function waitIceGathering(e,t){return e.iceGatheringState===`complete`?Promise.resolve():new Promise(n=>{let cleanup=()=>{clearTimeout(r),e.removeEventListener(`icegatheringstatechange`,onChange)},onChange=()=>{e.iceGatheringState===`complete`&&(cleanup(),n())},r=setTimeout(()=>{cleanup(),n()},t);e.addEventListener(`icegatheringstatechange`,onChange)})}function waitChannelOpen(r,i,a,o,s){return r.readyState===`open`?Promise.resolve():new Promise((c,l)=>{let u=null,timeout=()=>new n(`Timed out waiting ${a}ms for the data channel to open`),fail=e=>{cleanup(),l(e)},cleanup=()=>{u&&clearTimeout(u),u=null,r.removeEventListener(`open`,onOpen),r.removeEventListener(`close`,onClose),i.removeEventListener(`iceconnectionstatechange`,onIce),s.removeEventListener(`abort`,onDispose)},onOpen=()=>{cleanup(),c()},onClose=()=>fail(timeout()),onIce=()=>{let t=i.iceConnectionState;(t===`failed`||t===`closed`||t===`disconnected`)&&fail(new e(`ICE connection ${t} before channel open`,o))},onDispose=()=>fail(new t);u=setTimeout(()=>fail(timeout()),a),r.addEventListener(`open`,onOpen),r.addEventListener(`close`,onClose),i.addEventListener(`iceconnectionstatechange`,onIce),s.addEventListener(`abort`,onDispose,{once:!0})})}export{i as nativeRtcFactory,normalizeSdp,waitChannelOpen,waitIceGathering};
1
+ import { MeshConnectionError as e, MeshDisposedError as t, MeshTimeoutError as n, MeshUnsupportedError as r } from "./errors.js";
2
+ //#region src/_rtc.ts
3
+ var i = { createPeerConnection(e) {
4
+ let t = globalThis.RTCPeerConnection;
5
+ if (!t) throw new r("WebRTC is unavailable in this environment: inject an `rtc` factory");
6
+ return new t(e);
7
+ } };
8
+ function normalizeSdp(e) {
9
+ let t = e.replace(/\r?\n/g, "\r\n");
10
+ return t.endsWith("\r\n") ? t : `${t}\r\n`;
11
+ }
12
+ function waitIceGathering(e, t) {
13
+ return e.iceGatheringState === "complete" ? Promise.resolve() : new Promise((n) => {
14
+ let cleanup = () => {
15
+ clearTimeout(r), e.removeEventListener("icegatheringstatechange", onChange);
16
+ }, onChange = () => {
17
+ e.iceGatheringState === "complete" && (cleanup(), n());
18
+ }, r = setTimeout(() => {
19
+ cleanup(), n();
20
+ }, t);
21
+ e.addEventListener("icegatheringstatechange", onChange);
22
+ });
23
+ }
24
+ function waitChannelOpen(r, i, a, o, s) {
25
+ return r.readyState === "open" ? Promise.resolve() : new Promise((c, l) => {
26
+ let u = null, timeout = () => new n(`Timed out waiting ${a}ms for the data channel to open`), fail = (e) => {
27
+ cleanup(), l(e);
28
+ }, cleanup = () => {
29
+ u && clearTimeout(u), u = null, r.removeEventListener("open", onOpen), r.removeEventListener("close", onClose), i.removeEventListener("iceconnectionstatechange", onIce), s.removeEventListener("abort", onDispose);
30
+ }, onOpen = () => {
31
+ cleanup(), c();
32
+ }, onClose = () => fail(timeout()), onIce = () => {
33
+ let t = i.iceConnectionState;
34
+ (t === "failed" || t === "closed" || t === "disconnected") && fail(new e(`ICE connection ${t} before channel open`, o));
35
+ }, onDispose = () => fail(new t());
36
+ u = setTimeout(() => fail(timeout()), a), r.addEventListener("open", onOpen), r.addEventListener("close", onClose), i.addEventListener("iceconnectionstatechange", onIce), s.addEventListener("abort", onDispose, { once: !0 });
37
+ });
38
+ }
39
+ //#endregion
40
+ export { i as nativeRtcFactory, normalizeSdp, waitChannelOpen, waitIceGathering };
41
+
4
42
  //# sourceMappingURL=_rtc.js.map
package/dist/_rtc.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"_rtc.js","names":[],"sources":["../src/_rtc.ts"],"sourcesContent":["import { MeshConnectionError, MeshDisposedError, MeshTimeoutError, MeshUnsupportedError } from './errors';\nimport type { MeshRtcFactory, RTCDataChannelLike, RTCPeerConnectionLike } from './types';\n\n/**\n * Wraps `globalThis.RTCPeerConnection`. Evaluated lazily at first use so\n * importing the package never throws in non-WebRTC environments.\n */\nexport const nativeRtcFactory: MeshRtcFactory = {\n createPeerConnection(config) {\n const Ctor = (globalThis as { RTCPeerConnection?: typeof RTCPeerConnection }).RTCPeerConnection;\n if (!Ctor) {\n throw new MeshUnsupportedError('WebRTC is unavailable in this environment: inject an `rtc` factory');\n }\n return new Ctor(config);\n },\n};\n\n/**\n * Textareas, clipboards, and share sheets normalize CRLF→LF;\n * `setRemoteDescription` requires CRLF line endings.\n */\nexport function normalizeSdp(sdp: string): string {\n const normalized = sdp.replace(/\\r?\\n/g, '\\r\\n');\n return normalized.endsWith('\\r\\n') ? normalized : `${normalized}\\r\\n`;\n}\n\n/**\n * Resolves when ICE gathering finishes or after `timeoutMs`: the caller\n * proceeds with whatever candidates were gathered (non-trickle semantics).\n */\nexport function waitIceGathering(pc: RTCPeerConnectionLike, timeoutMs: number): Promise<void> {\n if (pc.iceGatheringState === 'complete') return Promise.resolve();\n return new Promise((resolve) => {\n const cleanup = () => {\n clearTimeout(timer);\n pc.removeEventListener('icegatheringstatechange', onChange);\n };\n const onChange = () => {\n if (pc.iceGatheringState === 'complete') {\n cleanup();\n resolve();\n }\n };\n const timer = setTimeout(() => {\n cleanup();\n resolve();\n }, timeoutMs);\n pc.addEventListener('icegatheringstatechange', onChange);\n });\n}\n\n/**\n * Resolves when the data channel opens. Rejects with `MeshTimeoutError` after\n * `timeoutMs`, or when the channel closes while that deadline is still\n * pending, since a peer tearing down a never-opened channel races the same\n * deadline: `MeshConnectionError` when the ICE transport dies first, and\n * `MeshDisposedError` when `disposalSignal` aborts.\n */\nexport function waitChannelOpen(\n dc: RTCDataChannelLike,\n pc: RTCPeerConnectionLike,\n timeoutMs: number,\n peerId: string,\n disposalSignal: AbortSignal,\n): Promise<void> {\n if (dc.readyState === 'open') return Promise.resolve();\n return new Promise((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | null = null;\n const timeout = () => new MeshTimeoutError(`Timed out waiting ${timeoutMs}ms for the data channel to open`);\n const fail = (error: Error) => {\n cleanup();\n reject(error);\n };\n const cleanup = () => {\n if (timer) clearTimeout(timer);\n timer = null;\n dc.removeEventListener('open', onOpen);\n dc.removeEventListener('close', onClose);\n pc.removeEventListener('iceconnectionstatechange', onIce);\n disposalSignal.removeEventListener('abort', onDispose);\n };\n const onOpen = () => {\n cleanup();\n resolve();\n };\n // A close while our open deadline is still armed is the timeout it\n // effectively is; mirror the guest's open-timer guard so both ends agree\n // on the cause instead of racing to report whichever event landed first.\n const onClose = () => fail(timeout());\n const onIce = () => {\n const state = pc.iceConnectionState;\n if (state === 'failed' || state === 'closed' || state === 'disconnected') {\n fail(new MeshConnectionError(`ICE connection ${state} before channel open`, peerId));\n }\n };\n const onDispose = () => fail(new MeshDisposedError());\n timer = setTimeout(() => fail(timeout()), timeoutMs);\n dc.addEventListener('open', onOpen);\n dc.addEventListener('close', onClose);\n pc.addEventListener('iceconnectionstatechange', onIce);\n disposalSignal.addEventListener('abort', onDispose, { once: true });\n });\n}\n"],"mappings":"yHAOA,IAAa,EAAmC,CAC9C,qBAAqB,EAAQ,CAC3B,IAAM,EAAQ,WAAgE,kBAC9E,GAAI,CAAC,EACH,MAAM,IAAI,EAAqB,oEAAoE,EAErG,OAAO,IAAI,EAAK,CAAM,CACxB,CACF,EAMA,SAAgB,aAAa,EAAqB,CAChD,IAAM,EAAa,EAAI,QAAQ,SAAU;CAAM,EAC/C,OAAO,EAAW,SAAS;CAAM,EAAI,EAAa,GAAG,EAAW,KAClE,CAMA,SAAgB,iBAAiB,EAA2B,EAAkC,CAE5F,OADI,EAAG,oBAAsB,WAAmB,QAAQ,QAAQ,EACzD,IAAI,QAAS,GAAY,CAC9B,IAAM,YAAgB,CACpB,aAAa,CAAK,EAClB,EAAG,oBAAoB,0BAA2B,QAAQ,CAC5D,EACM,aAAiB,CACjB,EAAG,oBAAsB,aAC3B,QAAQ,EACR,EAAQ,EAEZ,EACM,EAAQ,eAAiB,CAC7B,QAAQ,EACR,EAAQ,CACV,EAAG,CAAS,EACZ,EAAG,iBAAiB,0BAA2B,QAAQ,CACzD,CAAC,CACH,CASA,SAAgB,gBACd,EACA,EACA,EACA,EACA,EACe,CAEf,OADI,EAAG,aAAe,OAAe,QAAQ,QAAQ,EAC9C,IAAI,SAAS,EAAS,IAAW,CACtC,IAAI,EAA8C,KAC5C,YAAgB,IAAI,EAAiB,qBAAqB,EAAU,gCAAgC,EACpG,KAAQ,GAAiB,CAC7B,QAAQ,EACR,EAAO,CAAK,CACd,EACM,YAAgB,CAChB,GAAO,aAAa,CAAK,EAC7B,EAAQ,KACR,EAAG,oBAAoB,OAAQ,MAAM,EACrC,EAAG,oBAAoB,QAAS,OAAO,EACvC,EAAG,oBAAoB,2BAA4B,KAAK,EACxD,EAAe,oBAAoB,QAAS,SAAS,CACvD,EACM,WAAe,CACnB,QAAQ,EACR,EAAQ,CACV,EAIM,YAAgB,KAAK,QAAQ,CAAC,EAC9B,UAAc,CAClB,IAAM,EAAQ,EAAG,oBACb,IAAU,UAAY,IAAU,UAAY,IAAU,iBACxD,KAAK,IAAI,EAAoB,kBAAkB,EAAM,sBAAuB,CAAM,CAAC,CAEvF,EACM,cAAkB,KAAK,IAAI,CAAmB,EACpD,EAAQ,eAAiB,KAAK,QAAQ,CAAC,EAAG,CAAS,EACnD,EAAG,iBAAiB,OAAQ,MAAM,EAClC,EAAG,iBAAiB,QAAS,OAAO,EACpC,EAAG,iBAAiB,2BAA4B,KAAK,EACrD,EAAe,iBAAiB,QAAS,UAAW,CAAE,KAAM,EAAK,CAAC,CACpE,CAAC,CACH"}
1
+ {"version":3,"file":"_rtc.js","names":["nativeRtcFactory","Ctor","MeshUnsupportedError","config","normalized","sdp","pc","timer","resolve","timeoutMs","dc","MeshTimeoutError","reject","error","disposalSignal","state","MeshConnectionError","peerId","MeshDisposedError"],"sources":["../src/_rtc.ts"],"sourcesContent":["import { MeshConnectionError, MeshDisposedError, MeshTimeoutError, MeshUnsupportedError } from './errors';\nimport type { MeshRtcFactory, RTCDataChannelLike, RTCPeerConnectionLike } from './types';\n\n/**\n * Wraps `globalThis.RTCPeerConnection`. Evaluated lazily at first use so\n * importing the package never throws in non-WebRTC environments.\n */\nexport const nativeRtcFactory: MeshRtcFactory = {\n createPeerConnection(config) {\n const Ctor = (globalThis as { RTCPeerConnection?: typeof RTCPeerConnection }).RTCPeerConnection;\n if (!Ctor) {\n throw new MeshUnsupportedError('WebRTC is unavailable in this environment: inject an `rtc` factory');\n }\n return new Ctor(config);\n },\n};\n\n/**\n * Textareas, clipboards, and share sheets normalize CRLF→LF;\n * `setRemoteDescription` requires CRLF line endings.\n */\nexport function normalizeSdp(sdp: string): string {\n const normalized = sdp.replace(/\\r?\\n/g, '\\r\\n');\n return normalized.endsWith('\\r\\n') ? normalized : `${normalized}\\r\\n`;\n}\n\n/**\n * Resolves when ICE gathering finishes or after `timeoutMs`: the caller\n * proceeds with whatever candidates were gathered (non-trickle semantics).\n */\nexport function waitIceGathering(pc: RTCPeerConnectionLike, timeoutMs: number): Promise<void> {\n if (pc.iceGatheringState === 'complete') return Promise.resolve();\n return new Promise((resolve) => {\n const cleanup = () => {\n clearTimeout(timer);\n pc.removeEventListener('icegatheringstatechange', onChange);\n };\n const onChange = () => {\n if (pc.iceGatheringState === 'complete') {\n cleanup();\n resolve();\n }\n };\n const timer = setTimeout(() => {\n cleanup();\n resolve();\n }, timeoutMs);\n pc.addEventListener('icegatheringstatechange', onChange);\n });\n}\n\n/**\n * Resolves when the data channel opens. Rejects with `MeshTimeoutError` after\n * `timeoutMs`, or when the channel closes while that deadline is still\n * pending, since a peer tearing down a never-opened channel races the same\n * deadline: `MeshConnectionError` when the ICE transport dies first, and\n * `MeshDisposedError` when `disposalSignal` aborts.\n */\nexport function waitChannelOpen(\n dc: RTCDataChannelLike,\n pc: RTCPeerConnectionLike,\n timeoutMs: number,\n peerId: string,\n disposalSignal: AbortSignal,\n): Promise<void> {\n if (dc.readyState === 'open') return Promise.resolve();\n return new Promise((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | null = null;\n const timeout = () => new MeshTimeoutError(`Timed out waiting ${timeoutMs}ms for the data channel to open`);\n const fail = (error: Error) => {\n cleanup();\n reject(error);\n };\n const cleanup = () => {\n if (timer) clearTimeout(timer);\n timer = null;\n dc.removeEventListener('open', onOpen);\n dc.removeEventListener('close', onClose);\n pc.removeEventListener('iceconnectionstatechange', onIce);\n disposalSignal.removeEventListener('abort', onDispose);\n };\n const onOpen = () => {\n cleanup();\n resolve();\n };\n // A close while our open deadline is still armed is the timeout it\n // effectively is; mirror the guest's open-timer guard so both ends agree\n // on the cause instead of racing to report whichever event landed first.\n const onClose = () => fail(timeout());\n const onIce = () => {\n const state = pc.iceConnectionState;\n if (state === 'failed' || state === 'closed' || state === 'disconnected') {\n fail(new MeshConnectionError(`ICE connection ${state} before channel open`, peerId));\n }\n };\n const onDispose = () => fail(new MeshDisposedError());\n timer = setTimeout(() => fail(timeout()), timeoutMs);\n dc.addEventListener('open', onOpen);\n dc.addEventListener('close', onClose);\n pc.addEventListener('iceconnectionstatechange', onIce);\n disposalSignal.addEventListener('abort', onDispose, { once: true });\n });\n}\n"],"mappings":";;AAOA,IAAaA,IAAmC,EAC9C,qBAAqB,GAAQ;CAC3B,IAAMC,IAAQ,WAAgE;CAC9E,IAAI,CAACA,GACH,MAAM,IAAIC,EAAqB,oEAAoE;CAErG,OAAO,IAAID,EAAKE,CAAM;AACxB,EACF;AAMA,SAAgB,aAAa,GAAqB;CAChD,IAAMC,IAAaC,EAAI,QAAQ,UAAU,MAAM;CAC/C,OAAOD,EAAW,SAAS,MAAM,IAAIA,IAAa,GAAGA,EAAW;AAClE;AAMA,SAAgB,iBAAiB,GAA2B,GAAkC;CAE5F,OADIE,EAAG,sBAAsB,aAAmB,QAAQ,QAAQ,IACzD,IAAI,SAAS,MAAY;EAC9B,IAAM,gBAAgB;GAEpB,AADA,aAAaC,CAAK,GAClBD,EAAG,oBAAoB,2BAA2B,QAAQ;EAC5D,GACM,iBAAiB;GACrB,AAAIA,EAAG,sBAAsB,eAC3B,QAAQ,GACRE,EAAQ;EAEZ,GACMD,IAAQ,iBAAiB;GAE7B,AADA,QAAQ,GACRC,EAAQ;EACV,GAAGC,CAAS;EACZ,EAAG,iBAAiB,2BAA2B,QAAQ;CACzD,CAAC;AACH;AASA,SAAgB,gBACd,GACA,GACA,GACA,GACA,GACe;CAEf,OADIC,EAAG,eAAe,SAAe,QAAQ,QAAQ,IAC9C,IAAI,SAAS,GAAS,MAAW;EACtC,IAAIH,IAA8C,MAC5C,gBAAgB,IAAII,EAAiB,qBAAqBF,EAAU,gCAAgC,GACpG,QAAQ,MAAiB;GAE7B,AADA,QAAQ,GACRG,EAAOC,CAAK;EACd,GACM,gBAAgB;GAMpB,AALIN,KAAO,aAAaA,CAAK,GAC7B,IAAQ,MACRG,EAAG,oBAAoB,QAAQ,MAAM,GACrCA,EAAG,oBAAoB,SAAS,OAAO,GACvCJ,EAAG,oBAAoB,4BAA4B,KAAK,GACxDQ,EAAe,oBAAoB,SAAS,SAAS;EACvD,GACM,eAAe;GAEnB,AADA,QAAQ,GACRN,EAAQ;EACV,GAIM,gBAAgB,KAAK,QAAQ,CAAC,GAC9B,cAAc;GAClB,IAAMO,IAAQT,EAAG;GACjB,CAAIS,MAAU,YAAYA,MAAU,YAAYA,MAAU,mBACxD,KAAK,IAAIC,EAAoB,kBAAkBD,EAAM,uBAAuBE,CAAM,CAAC;EAEvF,GACM,kBAAkB,KAAK,IAAIC,EAAkB,CAAC;EAKpD,AAJA,IAAQ,iBAAiB,KAAK,QAAQ,CAAC,GAAGT,CAAS,GACnDC,EAAG,iBAAiB,QAAQ,MAAM,GAClCA,EAAG,iBAAiB,SAAS,OAAO,GACpCJ,EAAG,iBAAiB,4BAA4B,KAAK,GACrDQ,EAAe,iBAAiB,SAAS,WAAW,EAAE,MAAM,GAAK,CAAC;CACpE,CAAC;AACH"}