@vielzeug/mesh 3.1.0 → 26.10.1

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 (98) hide show
  1. package/README.md +2 -2
  2. package/dist/_base45.cjs +1 -1
  3. package/dist/_base45.cjs.map +1 -1
  4. package/dist/_base45.js +1 -37
  5. package/dist/_base45.js.map +1 -1
  6. package/dist/_defaults.cjs +2 -0
  7. package/dist/_defaults.cjs.map +1 -0
  8. package/dist/_defaults.d.ts +6 -0
  9. package/dist/_defaults.d.ts.map +1 -0
  10. package/dist/_defaults.js +2 -0
  11. package/dist/_defaults.js.map +1 -0
  12. package/dist/_node.cjs +1 -1
  13. package/dist/_node.cjs.map +1 -1
  14. package/dist/_node.d.ts +4 -11
  15. package/dist/_node.d.ts.map +1 -1
  16. package/dist/_node.js +1 -194
  17. package/dist/_node.js.map +1 -1
  18. package/dist/_proof.cjs +1 -1
  19. package/dist/_proof.cjs.map +1 -1
  20. package/dist/_proof.d.ts +8 -5
  21. package/dist/_proof.d.ts.map +1 -1
  22. package/dist/_proof.js +1 -21
  23. package/dist/_proof.js.map +1 -1
  24. package/dist/_random.cjs +1 -1
  25. package/dist/_random.cjs.map +1 -1
  26. package/dist/_random.d.ts +1 -1
  27. package/dist/_random.d.ts.map +1 -1
  28. package/dist/_random.js +1 -15
  29. package/dist/_random.js.map +1 -1
  30. package/dist/_rtc.cjs +2 -2
  31. package/dist/_rtc.cjs.map +1 -1
  32. package/dist/_rtc.d.ts +7 -6
  33. package/dist/_rtc.d.ts.map +1 -1
  34. package/dist/_rtc.js +3 -41
  35. package/dist/_rtc.js.map +1 -1
  36. package/dist/_sdp.cjs +2 -2
  37. package/dist/_sdp.cjs.map +1 -1
  38. package/dist/_sdp.d.ts +1 -1
  39. package/dist/_sdp.js +2 -54
  40. package/dist/_sdp.js.map +1 -1
  41. package/dist/_wire.cjs +1 -1
  42. package/dist/_wire.cjs.map +1 -1
  43. package/dist/_wire.js +1 -20
  44. package/dist/_wire.js.map +1 -1
  45. package/dist/codec.cjs +1 -1
  46. package/dist/codec.cjs.map +1 -1
  47. package/dist/codec.d.ts +14 -9
  48. package/dist/codec.d.ts.map +1 -1
  49. package/dist/codec.js +1 -34
  50. package/dist/codec.js.map +1 -1
  51. package/dist/errors.cjs +1 -1
  52. package/dist/errors.cjs.map +1 -1
  53. package/dist/errors.js +1 -18
  54. package/dist/errors.js.map +1 -1
  55. package/dist/guest.cjs +1 -1
  56. package/dist/guest.cjs.map +1 -1
  57. package/dist/guest.d.ts.map +1 -1
  58. package/dist/guest.js +1 -164
  59. package/dist/guest.js.map +1 -1
  60. package/dist/host.cjs +1 -1
  61. package/dist/host.cjs.map +1 -1
  62. package/dist/host.d.ts.map +1 -1
  63. package/dist/host.js +1 -240
  64. package/dist/host.js.map +1 -1
  65. package/dist/index.d.ts +2 -2
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +1 -6
  68. package/dist/mesh.cjs +3 -3
  69. package/dist/mesh.cjs.map +1 -1
  70. package/dist/mesh.iife.js +3 -3
  71. package/dist/mesh.iife.js.map +1 -1
  72. package/dist/mesh.js +3 -3
  73. package/dist/mesh.js.map +1 -1
  74. package/dist/qr-codec.cjs +1 -1
  75. package/dist/qr-codec.cjs.map +1 -1
  76. package/dist/qr-codec.d.ts +3 -3
  77. package/dist/qr-codec.d.ts.map +1 -1
  78. package/dist/qr-codec.js +1 -67
  79. package/dist/qr-codec.js.map +1 -1
  80. package/dist/testing/fake-rtc.cjs +2 -0
  81. package/dist/testing/fake-rtc.cjs.map +1 -0
  82. package/dist/testing/fake-rtc.d.ts +32 -0
  83. package/dist/testing/fake-rtc.d.ts.map +1 -0
  84. package/dist/testing/fake-rtc.js +2 -0
  85. package/dist/testing/fake-rtc.js.map +1 -0
  86. package/dist/testing/index.d.ts +2 -0
  87. package/dist/testing/index.d.ts.map +1 -0
  88. package/dist/testing.cjs +1 -0
  89. package/dist/testing.js +1 -0
  90. package/dist/types.d.ts +22 -40
  91. package/dist/types.d.ts.map +1 -1
  92. package/package.json +17 -5
  93. package/dist/_base64.cjs +0 -2
  94. package/dist/_base64.cjs.map +0 -1
  95. package/dist/_base64.d.ts +0 -9
  96. package/dist/_base64.d.ts.map +0 -1
  97. package/dist/_base64.js +0 -33
  98. package/dist/_base64.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"_proof.js","names":[],"sources":["../src/_proof.ts"],"sourcesContent":["import { hash } from '@vielzeug/arsenal';\nimport { bytesToBase64Url } from './_base64';\nimport { canonicalSdp } from './_sdp';\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. Falls back to a\n * deterministic non-cryptographic hash when `SubtleCrypto` is unavailable —\n * the caller reports a `'security-downgrade'` tap so consumers can refuse\n * degraded environments.\n */\nexport async function createProof(secret: string, sdp: string, onDowngrade: () => void): Promise<string> {\n const canonical = canonicalSdp(sdp);\n const subtle = globalThis.crypto?.subtle;\n if (subtle) {\n const encoder = new TextEncoder();\n const key = await subtle.importKey('raw', encoder.encode(secret), { hash: 'SHA-256', name: 'HMAC' }, false, [\n 'sign',\n ]);\n const signature = await subtle.sign('HMAC', key, encoder.encode(canonical));\n return bytesToBase64Url(new Uint8Array(signature));\n }\n\n onDowngrade();\n return hash({ sdp: canonical, secret });\n}\n"],"mappings":";;;;AAaA,eAAsB,EAAY,GAAgB,GAAa,GAA0C;CACvG,IAAM,IAAY,EAAa,CAAG,GAC5B,IAAS,WAAW,QAAQ;CAClC,IAAI,GAAQ;EACV,IAAM,IAAU,IAAI,YAAY,GAC1B,IAAM,MAAM,EAAO,UAAU,OAAO,EAAQ,OAAO,CAAM,GAAG;GAAE,MAAM;GAAW,MAAM;EAAO,GAAG,IAAO,CAC1G,MACF,CAAC,GACK,IAAY,MAAM,EAAO,KAAK,QAAQ,GAAK,EAAQ,OAAO,CAAS,CAAC;EAC1E,OAAO,EAAiB,IAAI,WAAW,CAAS,CAAC;CACnD;CAGA,OADA,EAAY,GACL,EAAK;EAAE,KAAK;EAAW;CAAO,CAAC;AACxC"}
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"}
package/dist/_random.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./_base64.cjs");let t=require("@vielzeug/arsenal");function n(e,n){let r=new Uint8Array(e);if(n)for(let i=0;i<e;i++)r[i]=Math.floor((0,t.randomFloat)(n)*256);else crypto.getRandomValues(r);return r}function r(t){return e.bytesToBase64Url(n(16,t))}exports.randomBytes=n,exports.randomId=r;
1
+ let e=require("@vielzeug/arsenal");function randomBytes(t,n){let r=new Uint8Array(t);if(n)for(let i=0;i<t;i++)r[i]=Math.floor((0,e.randomFloat)(n)*256);else crypto.getRandomValues(r);return r}function randomId(t){return(0,e.bytesToBase64Url)(randomBytes(16,t))}exports.randomBytes=randomBytes,exports.randomId=randomId;
2
2
  //# sourceMappingURL=_random.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_random.cjs","names":[],"sources":["../src/_random.ts"],"sourcesContent":["import { type RandomSource, randomFloat } from '@vielzeug/arsenal';\nimport { bytesToBase64Url } from './_base64';\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":"oEAOA,SAAgB,EAAY,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,EAAS,EAA+B,CACtD,OAAO,EAAA,iBAAiB,EAAY,GAAI,CAAM,CAAC,CACjD"}
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"}
package/dist/_random.d.ts CHANGED
@@ -4,6 +4,6 @@ import { type RandomSource } from '@vielzeug/arsenal';
4
4
  * `RandomSource` for deterministic tests.
5
5
  */
6
6
  export declare function randomBytes(length: number, source?: RandomSource): Uint8Array;
7
- /** Random 128-bit base64url id — used for peer ids, session ids, and message ids. */
7
+ /** Random 128-bit base64url id: used for peer ids, session ids, and message ids. */
8
8
  export declare function randomId(source?: RandomSource): string;
9
9
  //# sourceMappingURL=_random.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"_random.d.ts","sourceRoot":"","sources":["../src/_random.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAe,MAAM,mBAAmB,CAAC;AAGnE;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,UAAU,CAQ7E;AAED,qFAAqF;AACrF,wBAAgB,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,CAEtD"}
1
+ {"version":3,"file":"_random.d.ts","sourceRoot":"","sources":["../src/_random.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,YAAY,EAAe,MAAM,mBAAmB,CAAC;AAErF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,UAAU,CAQ7E;AAED,oFAAoF;AACpF,wBAAgB,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,CAEtD"}
package/dist/_random.js CHANGED
@@ -1,16 +1,2 @@
1
- import { bytesToBase64Url as e } from "./_base64.js";
2
- import { randomFloat as t } from "@vielzeug/arsenal";
3
- //#region src/_random.ts
4
- function n(e, n) {
5
- let r = new Uint8Array(e);
6
- if (n) for (let i = 0; i < e; i++) r[i] = Math.floor(t(n) * 256);
7
- else crypto.getRandomValues(r);
8
- return r;
9
- }
10
- function r(t) {
11
- return e(n(16, t));
12
- }
13
- //#endregion
14
- export { n as randomBytes, r as randomId };
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};
16
2
  //# sourceMappingURL=_random.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"_random.js","names":[],"sources":["../src/_random.ts"],"sourcesContent":["import { type RandomSource, randomFloat } from '@vielzeug/arsenal';\nimport { bytesToBase64Url } from './_base64';\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":";;;AAOA,SAAgB,EAAY,GAAgB,GAAmC;CAC7E,IAAM,IAAQ,IAAI,WAAW,CAAM;CACnC,IAAI,GACF,KAAK,IAAI,IAAQ,GAAG,IAAQ,GAAQ,KAAS,EAAM,KAAS,KAAK,MAAM,EAAY,CAAM,IAAI,GAAG;MAEhG,OAAO,gBAAgB,CAAK;CAE9B,OAAO;AACT;AAGA,SAAgB,EAAS,GAA+B;CACtD,OAAO,EAAiB,EAAY,IAAI,CAAM,CAAC;AACjD"}
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"}
package/dist/_rtc.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const e=require("./errors.cjs");var t={createPeerConnection(t){let n=globalThis.RTCPeerConnection;if(!n)throw new e.MeshUnsupportedError("WebRTC is unavailable in this environment — inject an `rtc` factory");return new n(t)}};function n(e){let t=e.replace(/\r?\n/g,`\r
1
+ const e=require("./errors.cjs");var t={createPeerConnection(t){let n=globalThis.RTCPeerConnection;if(!n)throw new e.MeshUnsupportedError("WebRTC is unavailable in this environment: inject an `rtc` factory");return new n(t)}};function normalizeSdp(e){let t=e.replace(/\r?\n/g,`\r
2
2
  `);return t.endsWith(`\r
3
- `)?t:`${t}\r\n`}function r(e,t){return e.iceGatheringState===`complete`?Promise.resolve(`complete`):new Promise(n=>{let r=()=>{clearTimeout(a),e.removeEventListener(`icegatheringstatechange`,i)},i=()=>{e.iceGatheringState===`complete`&&(r(),n(`complete`))},a=setTimeout(()=>{r(),n(`timeout`)},t);e.addEventListener(`icegatheringstatechange`,i)})}function i(t,n,r,i,a){return t.readyState===`open`?Promise.resolve():new Promise((o,s)=>{let c=e=>{l(),s(e)},l=()=>{clearTimeout(m),t.removeEventListener(`open`,u),t.removeEventListener(`close`,d),n.removeEventListener(`iceconnectionstatechange`,f),a.removeEventListener(`abort`,p)},u=()=>{l(),o()},d=()=>c(new e.MeshConnectionError(`Data channel closed before opening`,i)),f=()=>{let t=n.iceConnectionState;(t===`failed`||t===`closed`||t===`disconnected`)&&c(new e.MeshConnectionError(`ICE connection ${t} before channel open`,i))},p=()=>c(new e.MeshDisposedError),m=setTimeout(()=>c(new e.MeshTimeoutError(`Timed out waiting ${r}ms for the data channel to open`)),r);t.addEventListener(`open`,u),t.addEventListener(`close`,d),n.addEventListener(`iceconnectionstatechange`,f),a.addEventListener(`abort`,p,{once:!0})})}exports.nativeRtcFactory=t,exports.normalizeSdp=n,exports.waitChannelOpen=i,exports.waitIceGathering=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(t,n,r,i,a){return t.readyState===`open`?Promise.resolve():new Promise((o,s)=>{let c=null,timeout=()=>new e.MeshTimeoutError(`Timed out waiting ${r}ms for the data channel to open`),fail=e=>{cleanup(),s(e)},cleanup=()=>{c&&clearTimeout(c),c=null,t.removeEventListener(`open`,onOpen),t.removeEventListener(`close`,onClose),n.removeEventListener(`iceconnectionstatechange`,onIce),a.removeEventListener(`abort`,onDispose)},onOpen=()=>{cleanup(),o()},onClose=()=>fail(timeout()),onIce=()=>{let t=n.iceConnectionState;(t===`failed`||t===`closed`||t===`disconnected`)&&fail(new e.MeshConnectionError(`ICE connection ${t} before channel open`,i))},onDispose=()=>fail(new e.MeshDisposedError);c=setTimeout(()=>fail(timeout()),r),t.addEventListener(`open`,onOpen),t.addEventListener(`close`,onClose),n.addEventListener(`iceconnectionstatechange`,onIce),a.addEventListener(`abort`,onDispose,{once:!0})})}exports.nativeRtcFactory=t,exports.normalizeSdp=normalizeSdp,exports.waitChannelOpen=waitChannelOpen,exports.waitIceGathering=waitIceGathering;
4
4
  //# sourceMappingURL=_rtc.cjs.map
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 `'complete'` when ICE gathering finishes, or `'timeout'` after\n * `timeoutMs` — the caller proceeds with whatever candidates were gathered\n * (non-trickle semantics).\n */\nexport function waitIceGathering(pc: RTCPeerConnectionLike, timeoutMs: number): Promise<'complete' | 'timeout'> {\n if (pc.iceGatheringState === 'complete') return Promise.resolve('complete');\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('complete');\n }\n };\n const timer = setTimeout(() => {\n cleanup();\n resolve('timeout');\n }, timeoutMs);\n pc.addEventListener('icegatheringstatechange', onChange);\n });\n}\n\n/**\n * Resolves when the data channel opens. Rejects with `MeshTimeoutError` after\n * `timeoutMs`, `MeshConnectionError` when the channel or ICE transport dies\n * first, and `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 const fail = (error: Error) => {\n cleanup();\n reject(error);\n };\n const cleanup = () => {\n clearTimeout(timer);\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 const onClose = () => fail(new MeshConnectionError('Data channel closed before opening', peerId));\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 const timer = setTimeout(\n () => fail(new MeshTimeoutError(`Timed out waiting ${timeoutMs}ms for the data channel to open`)),\n timeoutMs,\n );\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,qEAAqE,EAEtG,OAAO,IAAI,EAAK,CAAM,CACxB,CACF,EAMA,SAAgB,EAAa,EAAqB,CAChD,IAAM,EAAa,EAAI,QAAQ,SAAU;CAAM,EAC/C,OAAO,EAAW,SAAS;CAAM,EAAI,EAAa,GAAG,EAAW,KAClE,CAOA,SAAgB,EAAiB,EAA2B,EAAoD,CAE9G,OADI,EAAG,oBAAsB,WAAmB,QAAQ,QAAQ,UAAU,EACnE,IAAI,QAAS,GAAY,CAC9B,IAAM,MAAgB,CACpB,aAAa,CAAK,EAClB,EAAG,oBAAoB,0BAA2B,CAAQ,CAC5D,EACM,MAAiB,CACjB,EAAG,oBAAsB,aAC3B,EAAQ,EACR,EAAQ,UAAU,EAEtB,EACM,EAAQ,eAAiB,CAC7B,EAAQ,EACR,EAAQ,SAAS,CACnB,EAAG,CAAS,EACZ,EAAG,iBAAiB,0BAA2B,CAAQ,CACzD,CAAC,CACH,CAOA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACe,CAEf,OADI,EAAG,aAAe,OAAe,QAAQ,QAAQ,EAC9C,IAAI,SAAS,EAAS,IAAW,CACtC,IAAM,EAAQ,GAAiB,CAC7B,EAAQ,EACR,EAAO,CAAK,CACd,EACM,MAAgB,CACpB,aAAa,CAAK,EAClB,EAAG,oBAAoB,OAAQ,CAAM,EACrC,EAAG,oBAAoB,QAAS,CAAO,EACvC,EAAG,oBAAoB,2BAA4B,CAAK,EACxD,EAAe,oBAAoB,QAAS,CAAS,CACvD,EACM,MAAe,CACnB,EAAQ,EACR,EAAQ,CACV,EACM,MAAgB,EAAK,IAAI,EAAA,oBAAoB,qCAAsC,CAAM,CAAC,EAC1F,MAAc,CAClB,IAAM,EAAQ,EAAG,oBACb,IAAU,UAAY,IAAU,UAAY,IAAU,iBACxD,EAAK,IAAI,EAAA,oBAAoB,kBAAkB,EAAM,sBAAuB,CAAM,CAAC,CAEvF,EACM,MAAkB,EAAK,IAAI,EAAA,iBAAmB,EAC9C,EAAQ,eACN,EAAK,IAAI,EAAA,iBAAiB,qBAAqB,EAAU,gCAAgC,CAAC,EAChG,CACF,EACA,EAAG,iBAAiB,OAAQ,CAAM,EAClC,EAAG,iBAAiB,QAAS,CAAO,EACpC,EAAG,iBAAiB,2BAA4B,CAAK,EACrD,EAAe,iBAAiB,QAAS,EAAW,CAAE,KAAM,EAAK,CAAC,CACpE,CAAC,CACH"}
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"}
package/dist/_rtc.d.ts CHANGED
@@ -10,15 +10,16 @@ export declare const nativeRtcFactory: MeshRtcFactory;
10
10
  */
11
11
  export declare function normalizeSdp(sdp: string): string;
12
12
  /**
13
- * Resolves `'complete'` when ICE gathering finishes, or `'timeout'` after
14
- * `timeoutMs` — the caller proceeds with whatever candidates were gathered
15
- * (non-trickle semantics).
13
+ * Resolves when ICE gathering finishes or after `timeoutMs`: the caller
14
+ * proceeds with whatever candidates were gathered (non-trickle semantics).
16
15
  */
17
- export declare function waitIceGathering(pc: RTCPeerConnectionLike, timeoutMs: number): Promise<'complete' | 'timeout'>;
16
+ export declare function waitIceGathering(pc: RTCPeerConnectionLike, timeoutMs: number): Promise<void>;
18
17
  /**
19
18
  * Resolves when the data channel opens. Rejects with `MeshTimeoutError` after
20
- * `timeoutMs`, `MeshConnectionError` when the channel or ICE transport dies
21
- * first, and `MeshDisposedError` when `disposalSignal` aborts.
19
+ * `timeoutMs`, or when the channel closes while that deadline is still
20
+ * pending, since a peer tearing down a never-opened channel races the same
21
+ * deadline: `MeshConnectionError` when the ICE transport dies first, and
22
+ * `MeshDisposedError` when `disposalSignal` aborts.
22
23
  */
23
24
  export declare function waitChannelOpen(dc: RTCDataChannelLike, pc: RTCPeerConnectionLike, timeoutMs: number, peerId: string, disposalSignal: AbortSignal): Promise<void>;
24
25
  //# sourceMappingURL=_rtc.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"_rtc.d.ts","sourceRoot":"","sources":["../src/_rtc.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAEzF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAQ9B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAmB9G;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,kBAAkB,EACtB,EAAE,EAAE,qBAAqB,EACzB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,WAAW,GAC1B,OAAO,CAAC,IAAI,CAAC,CAmCf"}
1
+ {"version":3,"file":"_rtc.d.ts","sourceRoot":"","sources":["../src/_rtc.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAEzF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAQ9B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB5F;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,kBAAkB,EACtB,EAAE,EAAE,qBAAqB,EACzB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,WAAW,GAC1B,OAAO,CAAC,IAAI,CAAC,CAsCf"}
package/dist/_rtc.js CHANGED
@@ -1,42 +1,4 @@
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 a(e) {
9
- let t = e.replace(/\r?\n/g, "\r\n");
10
- return t.endsWith("\r\n") ? t : `${t}\r\n`;
11
- }
12
- function o(e, t) {
13
- return e.iceGatheringState === "complete" ? Promise.resolve("complete") : new Promise((n) => {
14
- let r = () => {
15
- clearTimeout(a), e.removeEventListener("icegatheringstatechange", i);
16
- }, i = () => {
17
- e.iceGatheringState === "complete" && (r(), n("complete"));
18
- }, a = setTimeout(() => {
19
- r(), n("timeout");
20
- }, t);
21
- e.addEventListener("icegatheringstatechange", i);
22
- });
23
- }
24
- function s(r, i, a, o, s) {
25
- return r.readyState === "open" ? Promise.resolve() : new Promise((c, l) => {
26
- let u = (e) => {
27
- d(), l(e);
28
- }, d = () => {
29
- clearTimeout(g), r.removeEventListener("open", f), r.removeEventListener("close", p), i.removeEventListener("iceconnectionstatechange", m), s.removeEventListener("abort", h);
30
- }, f = () => {
31
- d(), c();
32
- }, p = () => u(new e("Data channel closed before opening", o)), m = () => {
33
- let t = i.iceConnectionState;
34
- (t === "failed" || t === "closed" || t === "disconnected") && u(new e(`ICE connection ${t} before channel open`, o));
35
- }, h = () => u(new t()), g = setTimeout(() => u(new n(`Timed out waiting ${a}ms for the data channel to open`)), a);
36
- r.addEventListener("open", f), r.addEventListener("close", p), i.addEventListener("iceconnectionstatechange", m), s.addEventListener("abort", h, { once: !0 });
37
- });
38
- }
39
- //#endregion
40
- export { i as nativeRtcFactory, a as normalizeSdp, s as waitChannelOpen, o as waitIceGathering };
41
-
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};
42
4
  //# 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 `'complete'` when ICE gathering finishes, or `'timeout'` after\n * `timeoutMs` — the caller proceeds with whatever candidates were gathered\n * (non-trickle semantics).\n */\nexport function waitIceGathering(pc: RTCPeerConnectionLike, timeoutMs: number): Promise<'complete' | 'timeout'> {\n if (pc.iceGatheringState === 'complete') return Promise.resolve('complete');\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('complete');\n }\n };\n const timer = setTimeout(() => {\n cleanup();\n resolve('timeout');\n }, timeoutMs);\n pc.addEventListener('icegatheringstatechange', onChange);\n });\n}\n\n/**\n * Resolves when the data channel opens. Rejects with `MeshTimeoutError` after\n * `timeoutMs`, `MeshConnectionError` when the channel or ICE transport dies\n * first, and `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 const fail = (error: Error) => {\n cleanup();\n reject(error);\n };\n const cleanup = () => {\n clearTimeout(timer);\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 const onClose = () => fail(new MeshConnectionError('Data channel closed before opening', peerId));\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 const timer = setTimeout(\n () => fail(new MeshTimeoutError(`Timed out waiting ${timeoutMs}ms for the data channel to open`)),\n timeoutMs,\n );\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,IAAa,IAAmC,EAC9C,qBAAqB,GAAQ;CAC3B,IAAM,IAAQ,WAAgE;CAC9E,IAAI,CAAC,GACH,MAAM,IAAI,EAAqB,qEAAqE;CAEtG,OAAO,IAAI,EAAK,CAAM;AACxB,EACF;AAMA,SAAgB,EAAa,GAAqB;CAChD,IAAM,IAAa,EAAI,QAAQ,UAAU,MAAM;CAC/C,OAAO,EAAW,SAAS,MAAM,IAAI,IAAa,GAAG,EAAW;AAClE;AAOA,SAAgB,EAAiB,GAA2B,GAAoD;CAE9G,OADI,EAAG,sBAAsB,aAAmB,QAAQ,QAAQ,UAAU,IACnE,IAAI,SAAS,MAAY;EAC9B,IAAM,UAAgB;GAEpB,AADA,aAAa,CAAK,GAClB,EAAG,oBAAoB,2BAA2B,CAAQ;EAC5D,GACM,UAAiB;GACrB,AAAI,EAAG,sBAAsB,eAC3B,EAAQ,GACR,EAAQ,UAAU;EAEtB,GACM,IAAQ,iBAAiB;GAE7B,AADA,EAAQ,GACR,EAAQ,SAAS;EACnB,GAAG,CAAS;EACZ,EAAG,iBAAiB,2BAA2B,CAAQ;CACzD,CAAC;AACH;AAOA,SAAgB,EACd,GACA,GACA,GACA,GACA,GACe;CAEf,OADI,EAAG,eAAe,SAAe,QAAQ,QAAQ,IAC9C,IAAI,SAAS,GAAS,MAAW;EACtC,IAAM,KAAQ,MAAiB;GAE7B,AADA,EAAQ,GACR,EAAO,CAAK;EACd,GACM,UAAgB;GAKpB,AAJA,aAAa,CAAK,GAClB,EAAG,oBAAoB,QAAQ,CAAM,GACrC,EAAG,oBAAoB,SAAS,CAAO,GACvC,EAAG,oBAAoB,4BAA4B,CAAK,GACxD,EAAe,oBAAoB,SAAS,CAAS;EACvD,GACM,UAAe;GAEnB,AADA,EAAQ,GACR,EAAQ;EACV,GACM,UAAgB,EAAK,IAAI,EAAoB,sCAAsC,CAAM,CAAC,GAC1F,UAAc;GAClB,IAAM,IAAQ,EAAG;GACjB,CAAI,MAAU,YAAY,MAAU,YAAY,MAAU,mBACxD,EAAK,IAAI,EAAoB,kBAAkB,EAAM,uBAAuB,CAAM,CAAC;EAEvF,GACM,UAAkB,EAAK,IAAI,EAAkB,CAAC,GAC9C,IAAQ,iBACN,EAAK,IAAI,EAAiB,qBAAqB,EAAU,gCAAgC,CAAC,GAChG,CACF;EAIA,AAHA,EAAG,iBAAiB,QAAQ,CAAM,GAClC,EAAG,iBAAiB,SAAS,CAAO,GACpC,EAAG,iBAAiB,4BAA4B,CAAK,GACrD,EAAe,iBAAiB,SAAS,GAAW,EAAE,MAAM,GAAK,CAAC;CACpE,CAAC;AACH"}
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"}
package/dist/_sdp.cjs CHANGED
@@ -1,3 +1,3 @@
1
- const e=require("./errors.cjs");function t(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.u==`string`&&typeof t.p==`string`&&typeof t.f==`string`&&typeof t.s==`string`&&typeof t.m==`string`&&Array.isArray(t.c)&&t.c.every(e=>typeof e==`string`)}function n(n){if(typeof n==`string`)return n;if(t(n))return a(n);throw new e.MeshPairingError(`Malformed pairing payload`)}function r(e){let t=``,n=``,r=``,i=``,a=`0`,o,s,c=[],l=0;for(let u of e.split(/\r?\n/))u.startsWith(`m=`)?l+=1:u.startsWith(`a=ice-ufrag:`)?t=u.slice(12):u.startsWith(`a=ice-pwd:`)?n=u.slice(10):u.startsWith(`a=fingerprint:sha-256 `)?r=u.slice(22).replaceAll(`:`,``).toUpperCase():u.startsWith(`a=setup:`)?i=u.slice(8):u.startsWith(`a=mid:`)?a=u.slice(6):u.startsWith(`a=sctp-port:`)?o=Number(u.slice(12)):u.startsWith(`a=max-message-size:`)?s=Number(u.slice(19)):u.startsWith(`a=candidate:`)&&c.push(u.slice(12));return l!==1||!t||!n||!r?null:{c,f:r,m:a,p:n,s:i||`actpass`,...s?{mm:s}:{},...o?{sp:o}:{},u:t}}function i(e){let t=r(e);return t?JSON.stringify(t):e}function a(e){let t=e.f.replace(/(..)/g,`$1:`).slice(0,-1);return`${[`v=0`,`o=- 0 2 IN IP4 0.0.0.0`,`s=-`,`t=0 0`,`a=group:BUNDLE ${e.m}`,`m=application 9 UDP/DTLS/SCTP webrtc-datachannel`,`c=IN IP4 0.0.0.0`,`a=ice-ufrag:${e.u}`,`a=ice-pwd:${e.p}`,`a=fingerprint:sha-256 ${t}`,`a=setup:${e.s}`,`a=mid:${e.m}`,`a=sctp-port:${e.sp??5e3}`,...e.mm?[`a=max-message-size:${e.mm}`]:[],...e.c.map(e=>`a=candidate:${e}`),``].join(`\r
2
- `)}`}exports.canonicalSdp=i,exports.compactSdp=r,exports.expandSdp=a,exports.restoreSdp=n;
1
+ const e=require("./errors.cjs");function isCompactSdp(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.u==`string`&&typeof t.p==`string`&&typeof t.f==`string`&&typeof t.s==`string`&&typeof t.m==`string`&&Array.isArray(t.c)&&t.c.every(e=>typeof e==`string`)}function restoreSdp(t){if(typeof t==`string`)return t;if(isCompactSdp(t))return expandSdp(t);throw new e.MeshPairingError(`Malformed pairing payload`)}function compactSdp(e){let t=``,n=``,r=``,i=``,a=`0`,o,s,c=[],l=0;for(let u of e.split(/\r?\n/))u.startsWith(`m=`)?l+=1:u.startsWith(`a=ice-ufrag:`)?t=u.slice(12):u.startsWith(`a=ice-pwd:`)?n=u.slice(10):u.startsWith(`a=fingerprint:sha-256 `)?r=u.slice(22).replaceAll(`:`,``).toUpperCase():u.startsWith(`a=setup:`)?i=u.slice(8):u.startsWith(`a=mid:`)?a=u.slice(6):u.startsWith(`a=sctp-port:`)?o=Number(u.slice(12)):u.startsWith(`a=max-message-size:`)?s=Number(u.slice(19)):u.startsWith(`a=candidate:`)&&c.push(u.slice(12));return l!==1||!t||!n||!r?null:{c,f:r,m:a,p:n,s:i||`actpass`,...s?{mm:s}:{},...o?{sp:o}:{},u:t}}function canonicalSdp(e){let t=compactSdp(e);return t?JSON.stringify(t):e}function expandSdp(e){let t=e.f.replace(/(..)/g,`$1:`).slice(0,-1);return`${[`v=0`,`o=- 0 2 IN IP4 0.0.0.0`,`s=-`,`t=0 0`,`a=group:BUNDLE ${e.m}`,`m=application 9 UDP/DTLS/SCTP webrtc-datachannel`,`c=IN IP4 0.0.0.0`,`a=ice-ufrag:${e.u}`,`a=ice-pwd:${e.p}`,`a=fingerprint:sha-256 ${t}`,`a=setup:${e.s}`,`a=mid:${e.m}`,`a=sctp-port:${e.sp??5e3}`,...e.mm?[`a=max-message-size:${e.mm}`]:[],...e.c.map(e=>`a=candidate:${e}`),``].join(`\r
2
+ `)}`}exports.canonicalSdp=canonicalSdp,exports.compactSdp=compactSdp,exports.expandSdp=expandSdp,exports.restoreSdp=restoreSdp;
3
3
  //# sourceMappingURL=_sdp.cjs.map
package/dist/_sdp.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"_sdp.cjs","names":[],"sources":["../src/_sdp.ts"],"sourcesContent":["import { MeshPairingError } from './errors';\n\n/**\n * Compact SDP serialization for pairing payloads. A data-channel-only\n * SDP is mostly boilerplate; the remote side only needs the ICE credentials,\n * DTLS fingerprint, setup role, mid, and candidates. `compactSdp` extracts\n * those fields and `expandSdp` rebuilds a minimal valid SDP for\n * `setRemoteDescription`.\n *\n * `compactSdp` returns `null` for shapes it cannot safely represent\n * (multiple m-lines, missing credentials), letting callers keep the raw SDP.\n */\n\nexport interface CompactSdp {\n /** `a=candidate:` lines, prefix stripped. */\n c: string[];\n /** sha-256 fingerprint, hex without colons. */\n f: string;\n /** `a=mid`. */\n m: string;\n /** `a=max-message-size`. */\n mm?: number;\n /** `a=ice-pwd`. */\n p: string;\n /** `a=setup` role. */\n s: string;\n /** `a=sctp-port`. */\n sp?: number;\n /** `a=ice-ufrag`. */\n u: string;\n}\n\nfunction isCompactSdp(value: unknown): value is CompactSdp {\n if (typeof value !== 'object' || value === null) return false;\n const c = value as Record<string, unknown>;\n return (\n typeof c.u === 'string' &&\n typeof c.p === 'string' &&\n typeof c.f === 'string' &&\n typeof c.s === 'string' &&\n typeof c.m === 'string' &&\n Array.isArray(c.c) &&\n c.c.every((line) => typeof line === 'string')\n );\n}\n\n/** Returns the expanded SDP unchanged when `value` is already a string. */\nexport function restoreSdp(value: unknown): string {\n if (typeof value === 'string') return value;\n if (isCompactSdp(value)) return expandSdp(value);\n throw new MeshPairingError('Malformed pairing payload');\n}\n\nexport function compactSdp(sdp: string): CompactSdp | null {\n let ufrag = '';\n let pwd = '';\n let fingerprint = '';\n let setup = '';\n let mid = '0';\n let sctpPort: number | undefined;\n let maxMessageSize: number | undefined;\n const candidates: string[] = [];\n let mLines = 0;\n\n for (const line of sdp.split(/\\r?\\n/)) {\n if (line.startsWith('m=')) mLines += 1;\n else if (line.startsWith('a=ice-ufrag:')) ufrag = line.slice(12);\n else if (line.startsWith('a=ice-pwd:')) pwd = line.slice(10);\n else if (line.startsWith('a=fingerprint:sha-256 ')) fingerprint = line.slice(22).replaceAll(':', '').toUpperCase();\n else if (line.startsWith('a=setup:')) setup = line.slice(8);\n else if (line.startsWith('a=mid:')) mid = line.slice(6);\n else if (line.startsWith('a=sctp-port:')) sctpPort = Number(line.slice(12));\n else if (line.startsWith('a=max-message-size:')) maxMessageSize = Number(line.slice(19));\n else if (line.startsWith('a=candidate:')) candidates.push(line.slice(12));\n }\n\n if (mLines !== 1 || !ufrag || !pwd || !fingerprint) return null;\n return {\n c: candidates,\n f: fingerprint,\n m: mid,\n p: pwd,\n s: setup || 'actpass',\n ...(maxMessageSize ? { mm: maxMessageSize } : {}),\n ...(sctpPort ? { sp: sctpPort } : {}),\n u: ufrag,\n };\n}\n\n/**\n * Canonical signing form: the compact serialization when the SDP is\n * recognizable, else the raw text. `compactSdp` is lossless over the fields\n * it keeps, so the rebuilt `mq2.` SDP canonicalizes identically to the\n * original — a proof computed over one verifies against the other.\n */\nexport function canonicalSdp(sdp: string): string {\n const compact = compactSdp(sdp);\n return compact ? JSON.stringify(compact) : sdp;\n}\n\nexport function expandSdp(compact: CompactSdp): string {\n const colonHex = compact.f.replace(/(..)/g, '$1:').slice(0, -1);\n return `${[\n 'v=0',\n 'o=- 0 2 IN IP4 0.0.0.0',\n 's=-',\n 't=0 0',\n `a=group:BUNDLE ${compact.m}`,\n 'm=application 9 UDP/DTLS/SCTP webrtc-datachannel',\n 'c=IN IP4 0.0.0.0',\n `a=ice-ufrag:${compact.u}`,\n `a=ice-pwd:${compact.p}`,\n `a=fingerprint:sha-256 ${colonHex}`,\n `a=setup:${compact.s}`,\n `a=mid:${compact.m}`,\n `a=sctp-port:${compact.sp ?? 5000}`,\n ...(compact.mm ? [`a=max-message-size:${compact.mm}`] : []),\n ...compact.c.map((line) => `a=candidate:${line}`),\n '',\n ].join('\\r\\n')}`;\n}\n"],"mappings":"gCAgCA,SAAS,EAAa,EAAqC,CACzD,GAAI,OAAO,GAAU,WAAY,EAAgB,MAAO,GACxD,IAAM,EAAI,EACV,OACE,OAAO,EAAE,GAAM,UACf,OAAO,EAAE,GAAM,UACf,OAAO,EAAE,GAAM,UACf,OAAO,EAAE,GAAM,UACf,OAAO,EAAE,GAAM,UACf,MAAM,QAAQ,EAAE,CAAC,GACjB,EAAE,EAAE,MAAO,GAAS,OAAO,GAAS,QAAQ,CAEhD,CAGA,SAAgB,EAAW,EAAwB,CACjD,GAAI,OAAO,GAAU,SAAU,OAAO,EACtC,GAAI,EAAa,CAAK,EAAG,OAAO,EAAU,CAAK,EAC/C,MAAM,IAAI,EAAA,iBAAiB,2BAA2B,CACxD,CAEA,SAAgB,EAAW,EAAgC,CACzD,IAAI,EAAQ,GACR,EAAM,GACN,EAAc,GACd,EAAQ,GACR,EAAM,IACN,EACA,EACE,EAAuB,CAAC,EAC1B,EAAS,EAEb,IAAK,IAAM,KAAQ,EAAI,MAAM,OAAO,EAC9B,EAAK,WAAW,IAAI,EAAG,GAAU,EAC5B,EAAK,WAAW,cAAc,EAAG,EAAQ,EAAK,MAAM,EAAE,EACtD,EAAK,WAAW,YAAY,EAAG,EAAM,EAAK,MAAM,EAAE,EAClD,EAAK,WAAW,wBAAwB,EAAG,EAAc,EAAK,MAAM,EAAE,CAAC,CAAC,WAAW,IAAK,EAAE,CAAC,CAAC,YAAY,EACxG,EAAK,WAAW,UAAU,EAAG,EAAQ,EAAK,MAAM,CAAC,EACjD,EAAK,WAAW,QAAQ,EAAG,EAAM,EAAK,MAAM,CAAC,EAC7C,EAAK,WAAW,cAAc,EAAG,EAAW,OAAO,EAAK,MAAM,EAAE,CAAC,EACjE,EAAK,WAAW,qBAAqB,EAAG,EAAiB,OAAO,EAAK,MAAM,EAAE,CAAC,EAC9E,EAAK,WAAW,cAAc,GAAG,EAAW,KAAK,EAAK,MAAM,EAAE,CAAC,EAI1E,OADI,IAAW,GAAK,CAAC,GAAS,CAAC,GAAO,CAAC,EAAoB,KACpD,CACF,EACH,EAAG,EACH,EAAG,EACH,EAAG,EACH,EAAG,GAAS,UACZ,GAAI,EAAiB,CAAE,GAAI,CAAe,EAAI,CAAC,EAC/C,GAAI,EAAW,CAAE,GAAI,CAAS,EAAI,CAAC,EACnC,EAAG,CACL,CACF,CAQA,SAAgB,EAAa,EAAqB,CAChD,IAAM,EAAU,EAAW,CAAG,EAC9B,OAAO,EAAU,KAAK,UAAU,CAAO,EAAI,CAC7C,CAEA,SAAgB,EAAU,EAA6B,CACrD,IAAM,EAAW,EAAQ,EAAE,QAAQ,QAAS,KAAK,CAAC,CAAC,MAAM,EAAG,EAAE,EAC9D,MAAO,GAAG,CACR,MACA,yBACA,MACA,QACA,kBAAkB,EAAQ,IAC1B,mDACA,mBACA,eAAe,EAAQ,IACvB,aAAa,EAAQ,IACrB,yBAAyB,IACzB,WAAW,EAAQ,IACnB,SAAS,EAAQ,IACjB,eAAe,EAAQ,IAAM,MAC7B,GAAI,EAAQ,GAAK,CAAC,sBAAsB,EAAQ,IAAI,EAAI,CAAC,EACzD,GAAG,EAAQ,EAAE,IAAK,GAAS,eAAe,GAAM,EAChD,EACF,CAAC,CAAC,KAAK;CAAM,GACf"}
1
+ {"version":3,"file":"_sdp.cjs","names":[],"sources":["../src/_sdp.ts"],"sourcesContent":["import { MeshPairingError } from './errors';\n\n/**\n * Compact SDP serialization for pairing payloads. A data-channel-only\n * SDP is mostly boilerplate; the remote side only needs the ICE credentials,\n * DTLS fingerprint, setup role, mid, and candidates. `compactSdp` extracts\n * those fields and `expandSdp` rebuilds a minimal valid SDP for\n * `setRemoteDescription`.\n *\n * `compactSdp` returns `null` for shapes it cannot safely represent\n * (multiple m-lines, missing credentials), letting callers keep the raw SDP.\n */\n\nexport interface CompactSdp {\n /** `a=candidate:` lines, prefix stripped. */\n c: string[];\n /** sha-256 fingerprint, hex without colons. */\n f: string;\n /** `a=mid`. */\n m: string;\n /** `a=max-message-size`. */\n mm?: number;\n /** `a=ice-pwd`. */\n p: string;\n /** `a=setup` role. */\n s: string;\n /** `a=sctp-port`. */\n sp?: number;\n /** `a=ice-ufrag`. */\n u: string;\n}\n\nfunction isCompactSdp(value: unknown): value is CompactSdp {\n if (typeof value !== 'object' || value === null) return false;\n const c = value as Record<string, unknown>;\n return (\n typeof c.u === 'string' &&\n typeof c.p === 'string' &&\n typeof c.f === 'string' &&\n typeof c.s === 'string' &&\n typeof c.m === 'string' &&\n Array.isArray(c.c) &&\n c.c.every((line) => typeof line === 'string')\n );\n}\n\n/** Returns the expanded SDP unchanged when `value` is already a string. */\nexport function restoreSdp(value: unknown): string {\n if (typeof value === 'string') return value;\n if (isCompactSdp(value)) return expandSdp(value);\n throw new MeshPairingError('Malformed pairing payload');\n}\n\nexport function compactSdp(sdp: string): CompactSdp | null {\n let ufrag = '';\n let pwd = '';\n let fingerprint = '';\n let setup = '';\n let mid = '0';\n let sctpPort: number | undefined;\n let maxMessageSize: number | undefined;\n const candidates: string[] = [];\n let mLines = 0;\n\n for (const line of sdp.split(/\\r?\\n/)) {\n if (line.startsWith('m=')) mLines += 1;\n else if (line.startsWith('a=ice-ufrag:')) ufrag = line.slice(12);\n else if (line.startsWith('a=ice-pwd:')) pwd = line.slice(10);\n else if (line.startsWith('a=fingerprint:sha-256 ')) fingerprint = line.slice(22).replaceAll(':', '').toUpperCase();\n else if (line.startsWith('a=setup:')) setup = line.slice(8);\n else if (line.startsWith('a=mid:')) mid = line.slice(6);\n else if (line.startsWith('a=sctp-port:')) sctpPort = Number(line.slice(12));\n else if (line.startsWith('a=max-message-size:')) maxMessageSize = Number(line.slice(19));\n else if (line.startsWith('a=candidate:')) candidates.push(line.slice(12));\n }\n\n if (mLines !== 1 || !ufrag || !pwd || !fingerprint) return null;\n return {\n c: candidates,\n f: fingerprint,\n m: mid,\n p: pwd,\n s: setup || 'actpass',\n ...(maxMessageSize ? { mm: maxMessageSize } : {}),\n ...(sctpPort ? { sp: sctpPort } : {}),\n u: ufrag,\n };\n}\n\n/**\n * Canonical signing form: the compact serialization when the SDP is\n * recognizable, else the raw text. `compactSdp` is lossless over the fields\n * it keeps, so the rebuilt `mq2.` SDP canonicalizes identically to the\n * original: a proof computed over one verifies against the other.\n */\nexport function canonicalSdp(sdp: string): string {\n const compact = compactSdp(sdp);\n return compact ? JSON.stringify(compact) : sdp;\n}\n\nexport function expandSdp(compact: CompactSdp): string {\n const colonHex = compact.f.replace(/(..)/g, '$1:').slice(0, -1);\n return `${[\n 'v=0',\n 'o=- 0 2 IN IP4 0.0.0.0',\n 's=-',\n 't=0 0',\n `a=group:BUNDLE ${compact.m}`,\n 'm=application 9 UDP/DTLS/SCTP webrtc-datachannel',\n 'c=IN IP4 0.0.0.0',\n `a=ice-ufrag:${compact.u}`,\n `a=ice-pwd:${compact.p}`,\n `a=fingerprint:sha-256 ${colonHex}`,\n `a=setup:${compact.s}`,\n `a=mid:${compact.m}`,\n `a=sctp-port:${compact.sp ?? 5000}`,\n ...(compact.mm ? [`a=max-message-size:${compact.mm}`] : []),\n ...compact.c.map((line) => `a=candidate:${line}`),\n '',\n ].join('\\r\\n')}`;\n}\n"],"mappings":"gCAgCA,SAAS,aAAa,EAAqC,CACzD,GAAI,OAAO,GAAU,WAAY,EAAgB,MAAO,GACxD,IAAM,EAAI,EACV,OACE,OAAO,EAAE,GAAM,UACf,OAAO,EAAE,GAAM,UACf,OAAO,EAAE,GAAM,UACf,OAAO,EAAE,GAAM,UACf,OAAO,EAAE,GAAM,UACf,MAAM,QAAQ,EAAE,CAAC,GACjB,EAAE,EAAE,MAAO,GAAS,OAAO,GAAS,QAAQ,CAEhD,CAGA,SAAgB,WAAW,EAAwB,CACjD,GAAI,OAAO,GAAU,SAAU,OAAO,EACtC,GAAI,aAAa,CAAK,EAAG,OAAO,UAAU,CAAK,EAC/C,MAAM,IAAI,EAAA,iBAAiB,2BAA2B,CACxD,CAEA,SAAgB,WAAW,EAAgC,CACzD,IAAI,EAAQ,GACR,EAAM,GACN,EAAc,GACd,EAAQ,GACR,EAAM,IACN,EACA,EACE,EAAuB,CAAC,EAC1B,EAAS,EAEb,IAAK,IAAM,KAAQ,EAAI,MAAM,OAAO,EAC9B,EAAK,WAAW,IAAI,EAAG,GAAU,EAC5B,EAAK,WAAW,cAAc,EAAG,EAAQ,EAAK,MAAM,EAAE,EACtD,EAAK,WAAW,YAAY,EAAG,EAAM,EAAK,MAAM,EAAE,EAClD,EAAK,WAAW,wBAAwB,EAAG,EAAc,EAAK,MAAM,EAAE,CAAC,CAAC,WAAW,IAAK,EAAE,CAAC,CAAC,YAAY,EACxG,EAAK,WAAW,UAAU,EAAG,EAAQ,EAAK,MAAM,CAAC,EACjD,EAAK,WAAW,QAAQ,EAAG,EAAM,EAAK,MAAM,CAAC,EAC7C,EAAK,WAAW,cAAc,EAAG,EAAW,OAAO,EAAK,MAAM,EAAE,CAAC,EACjE,EAAK,WAAW,qBAAqB,EAAG,EAAiB,OAAO,EAAK,MAAM,EAAE,CAAC,EAC9E,EAAK,WAAW,cAAc,GAAG,EAAW,KAAK,EAAK,MAAM,EAAE,CAAC,EAI1E,OADI,IAAW,GAAK,CAAC,GAAS,CAAC,GAAO,CAAC,EAAoB,KACpD,CACF,EACH,EAAG,EACH,EAAG,EACH,EAAG,EACH,EAAG,GAAS,UACZ,GAAI,EAAiB,CAAE,GAAI,CAAe,EAAI,CAAC,EAC/C,GAAI,EAAW,CAAE,GAAI,CAAS,EAAI,CAAC,EACnC,EAAG,CACL,CACF,CAQA,SAAgB,aAAa,EAAqB,CAChD,IAAM,EAAU,WAAW,CAAG,EAC9B,OAAO,EAAU,KAAK,UAAU,CAAO,EAAI,CAC7C,CAEA,SAAgB,UAAU,EAA6B,CACrD,IAAM,EAAW,EAAQ,EAAE,QAAQ,QAAS,KAAK,CAAC,CAAC,MAAM,EAAG,EAAE,EAC9D,MAAO,GAAG,CACR,MACA,yBACA,MACA,QACA,kBAAkB,EAAQ,IAC1B,mDACA,mBACA,eAAe,EAAQ,IACvB,aAAa,EAAQ,IACrB,yBAAyB,IACzB,WAAW,EAAQ,IACnB,SAAS,EAAQ,IACjB,eAAe,EAAQ,IAAM,MAC7B,GAAI,EAAQ,GAAK,CAAC,sBAAsB,EAAQ,IAAI,EAAI,CAAC,EACzD,GAAG,EAAQ,EAAE,IAAK,GAAS,eAAe,GAAM,EAChD,EACF,CAAC,CAAC,KAAK;CAAM,GACf"}
package/dist/_sdp.d.ts CHANGED
@@ -33,7 +33,7 @@ export declare function compactSdp(sdp: string): CompactSdp | null;
33
33
  * Canonical signing form: the compact serialization when the SDP is
34
34
  * recognizable, else the raw text. `compactSdp` is lossless over the fields
35
35
  * it keeps, so the rebuilt `mq2.` SDP canonicalizes identically to the
36
- * original — a proof computed over one verifies against the other.
36
+ * original: a proof computed over one verifies against the other.
37
37
  */
38
38
  export declare function canonicalSdp(sdp: string): string;
39
39
  export declare function expandSdp(compact: CompactSdp): string;
package/dist/_sdp.js CHANGED
@@ -1,55 +1,3 @@
1
- import { MeshPairingError as e } from "./errors.js";
2
- //#region src/_sdp.ts
3
- function t(e) {
4
- if (typeof e != "object" || !e) return !1;
5
- let t = e;
6
- return typeof t.u == "string" && typeof t.p == "string" && typeof t.f == "string" && typeof t.s == "string" && typeof t.m == "string" && Array.isArray(t.c) && t.c.every((e) => typeof e == "string");
7
- }
8
- function n(n) {
9
- if (typeof n == "string") return n;
10
- if (t(n)) return a(n);
11
- throw new e("Malformed pairing payload");
12
- }
13
- function r(e) {
14
- let t = "", n = "", r = "", i = "", a = "0", o, s, c = [], l = 0;
15
- for (let u of e.split(/\r?\n/)) u.startsWith("m=") ? l += 1 : u.startsWith("a=ice-ufrag:") ? t = u.slice(12) : u.startsWith("a=ice-pwd:") ? n = u.slice(10) : u.startsWith("a=fingerprint:sha-256 ") ? r = u.slice(22).replaceAll(":", "").toUpperCase() : u.startsWith("a=setup:") ? i = u.slice(8) : u.startsWith("a=mid:") ? a = u.slice(6) : u.startsWith("a=sctp-port:") ? o = Number(u.slice(12)) : u.startsWith("a=max-message-size:") ? s = Number(u.slice(19)) : u.startsWith("a=candidate:") && c.push(u.slice(12));
16
- return l !== 1 || !t || !n || !r ? null : {
17
- c,
18
- f: r,
19
- m: a,
20
- p: n,
21
- s: i || "actpass",
22
- ...s ? { mm: s } : {},
23
- ...o ? { sp: o } : {},
24
- u: t
25
- };
26
- }
27
- function i(e) {
28
- let t = r(e);
29
- return t ? JSON.stringify(t) : e;
30
- }
31
- function a(e) {
32
- let t = e.f.replace(/(..)/g, "$1:").slice(0, -1);
33
- return `${[
34
- "v=0",
35
- "o=- 0 2 IN IP4 0.0.0.0",
36
- "s=-",
37
- "t=0 0",
38
- `a=group:BUNDLE ${e.m}`,
39
- "m=application 9 UDP/DTLS/SCTP webrtc-datachannel",
40
- "c=IN IP4 0.0.0.0",
41
- `a=ice-ufrag:${e.u}`,
42
- `a=ice-pwd:${e.p}`,
43
- `a=fingerprint:sha-256 ${t}`,
44
- `a=setup:${e.s}`,
45
- `a=mid:${e.m}`,
46
- `a=sctp-port:${e.sp ?? 5e3}`,
47
- ...e.mm ? [`a=max-message-size:${e.mm}`] : [],
48
- ...e.c.map((e) => `a=candidate:${e}`),
49
- ""
50
- ].join("\r\n")}`;
51
- }
52
- //#endregion
53
- export { i as canonicalSdp, r as compactSdp, a as expandSdp, n as restoreSdp };
54
-
1
+ import{MeshPairingError as e}from"./errors.js";function isCompactSdp(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.u==`string`&&typeof t.p==`string`&&typeof t.f==`string`&&typeof t.s==`string`&&typeof t.m==`string`&&Array.isArray(t.c)&&t.c.every(e=>typeof e==`string`)}function restoreSdp(t){if(typeof t==`string`)return t;if(isCompactSdp(t))return expandSdp(t);throw new e(`Malformed pairing payload`)}function compactSdp(e){let t=``,n=``,r=``,i=``,a=`0`,o,s,c=[],l=0;for(let u of e.split(/\r?\n/))u.startsWith(`m=`)?l+=1:u.startsWith(`a=ice-ufrag:`)?t=u.slice(12):u.startsWith(`a=ice-pwd:`)?n=u.slice(10):u.startsWith(`a=fingerprint:sha-256 `)?r=u.slice(22).replaceAll(`:`,``).toUpperCase():u.startsWith(`a=setup:`)?i=u.slice(8):u.startsWith(`a=mid:`)?a=u.slice(6):u.startsWith(`a=sctp-port:`)?o=Number(u.slice(12)):u.startsWith(`a=max-message-size:`)?s=Number(u.slice(19)):u.startsWith(`a=candidate:`)&&c.push(u.slice(12));return l!==1||!t||!n||!r?null:{c,f:r,m:a,p:n,s:i||`actpass`,...s?{mm:s}:{},...o?{sp:o}:{},u:t}}function canonicalSdp(e){let t=compactSdp(e);return t?JSON.stringify(t):e}function expandSdp(e){let t=e.f.replace(/(..)/g,`$1:`).slice(0,-1);return`${[`v=0`,`o=- 0 2 IN IP4 0.0.0.0`,`s=-`,`t=0 0`,`a=group:BUNDLE ${e.m}`,`m=application 9 UDP/DTLS/SCTP webrtc-datachannel`,`c=IN IP4 0.0.0.0`,`a=ice-ufrag:${e.u}`,`a=ice-pwd:${e.p}`,`a=fingerprint:sha-256 ${t}`,`a=setup:${e.s}`,`a=mid:${e.m}`,`a=sctp-port:${e.sp??5e3}`,...e.mm?[`a=max-message-size:${e.mm}`]:[],...e.c.map(e=>`a=candidate:${e}`),``].join(`\r
2
+ `)}`}export{canonicalSdp,compactSdp,expandSdp,restoreSdp};
55
3
  //# sourceMappingURL=_sdp.js.map
package/dist/_sdp.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"_sdp.js","names":[],"sources":["../src/_sdp.ts"],"sourcesContent":["import { MeshPairingError } from './errors';\n\n/**\n * Compact SDP serialization for pairing payloads. A data-channel-only\n * SDP is mostly boilerplate; the remote side only needs the ICE credentials,\n * DTLS fingerprint, setup role, mid, and candidates. `compactSdp` extracts\n * those fields and `expandSdp` rebuilds a minimal valid SDP for\n * `setRemoteDescription`.\n *\n * `compactSdp` returns `null` for shapes it cannot safely represent\n * (multiple m-lines, missing credentials), letting callers keep the raw SDP.\n */\n\nexport interface CompactSdp {\n /** `a=candidate:` lines, prefix stripped. */\n c: string[];\n /** sha-256 fingerprint, hex without colons. */\n f: string;\n /** `a=mid`. */\n m: string;\n /** `a=max-message-size`. */\n mm?: number;\n /** `a=ice-pwd`. */\n p: string;\n /** `a=setup` role. */\n s: string;\n /** `a=sctp-port`. */\n sp?: number;\n /** `a=ice-ufrag`. */\n u: string;\n}\n\nfunction isCompactSdp(value: unknown): value is CompactSdp {\n if (typeof value !== 'object' || value === null) return false;\n const c = value as Record<string, unknown>;\n return (\n typeof c.u === 'string' &&\n typeof c.p === 'string' &&\n typeof c.f === 'string' &&\n typeof c.s === 'string' &&\n typeof c.m === 'string' &&\n Array.isArray(c.c) &&\n c.c.every((line) => typeof line === 'string')\n );\n}\n\n/** Returns the expanded SDP unchanged when `value` is already a string. */\nexport function restoreSdp(value: unknown): string {\n if (typeof value === 'string') return value;\n if (isCompactSdp(value)) return expandSdp(value);\n throw new MeshPairingError('Malformed pairing payload');\n}\n\nexport function compactSdp(sdp: string): CompactSdp | null {\n let ufrag = '';\n let pwd = '';\n let fingerprint = '';\n let setup = '';\n let mid = '0';\n let sctpPort: number | undefined;\n let maxMessageSize: number | undefined;\n const candidates: string[] = [];\n let mLines = 0;\n\n for (const line of sdp.split(/\\r?\\n/)) {\n if (line.startsWith('m=')) mLines += 1;\n else if (line.startsWith('a=ice-ufrag:')) ufrag = line.slice(12);\n else if (line.startsWith('a=ice-pwd:')) pwd = line.slice(10);\n else if (line.startsWith('a=fingerprint:sha-256 ')) fingerprint = line.slice(22).replaceAll(':', '').toUpperCase();\n else if (line.startsWith('a=setup:')) setup = line.slice(8);\n else if (line.startsWith('a=mid:')) mid = line.slice(6);\n else if (line.startsWith('a=sctp-port:')) sctpPort = Number(line.slice(12));\n else if (line.startsWith('a=max-message-size:')) maxMessageSize = Number(line.slice(19));\n else if (line.startsWith('a=candidate:')) candidates.push(line.slice(12));\n }\n\n if (mLines !== 1 || !ufrag || !pwd || !fingerprint) return null;\n return {\n c: candidates,\n f: fingerprint,\n m: mid,\n p: pwd,\n s: setup || 'actpass',\n ...(maxMessageSize ? { mm: maxMessageSize } : {}),\n ...(sctpPort ? { sp: sctpPort } : {}),\n u: ufrag,\n };\n}\n\n/**\n * Canonical signing form: the compact serialization when the SDP is\n * recognizable, else the raw text. `compactSdp` is lossless over the fields\n * it keeps, so the rebuilt `mq2.` SDP canonicalizes identically to the\n * original — a proof computed over one verifies against the other.\n */\nexport function canonicalSdp(sdp: string): string {\n const compact = compactSdp(sdp);\n return compact ? JSON.stringify(compact) : sdp;\n}\n\nexport function expandSdp(compact: CompactSdp): string {\n const colonHex = compact.f.replace(/(..)/g, '$1:').slice(0, -1);\n return `${[\n 'v=0',\n 'o=- 0 2 IN IP4 0.0.0.0',\n 's=-',\n 't=0 0',\n `a=group:BUNDLE ${compact.m}`,\n 'm=application 9 UDP/DTLS/SCTP webrtc-datachannel',\n 'c=IN IP4 0.0.0.0',\n `a=ice-ufrag:${compact.u}`,\n `a=ice-pwd:${compact.p}`,\n `a=fingerprint:sha-256 ${colonHex}`,\n `a=setup:${compact.s}`,\n `a=mid:${compact.m}`,\n `a=sctp-port:${compact.sp ?? 5000}`,\n ...(compact.mm ? [`a=max-message-size:${compact.mm}`] : []),\n ...compact.c.map((line) => `a=candidate:${line}`),\n '',\n ].join('\\r\\n')}`;\n}\n"],"mappings":";;AAgCA,SAAS,EAAa,GAAqC;CACzD,IAAI,OAAO,KAAU,aAAY,GAAgB,OAAO;CACxD,IAAM,IAAI;CACV,OACE,OAAO,EAAE,KAAM,YACf,OAAO,EAAE,KAAM,YACf,OAAO,EAAE,KAAM,YACf,OAAO,EAAE,KAAM,YACf,OAAO,EAAE,KAAM,YACf,MAAM,QAAQ,EAAE,CAAC,KACjB,EAAE,EAAE,OAAO,MAAS,OAAO,KAAS,QAAQ;AAEhD;AAGA,SAAgB,EAAW,GAAwB;CACjD,IAAI,OAAO,KAAU,UAAU,OAAO;CACtC,IAAI,EAAa,CAAK,GAAG,OAAO,EAAU,CAAK;CAC/C,MAAM,IAAI,EAAiB,2BAA2B;AACxD;AAEA,SAAgB,EAAW,GAAgC;CACzD,IAAI,IAAQ,IACR,IAAM,IACN,IAAc,IACd,IAAQ,IACR,IAAM,KACN,GACA,GACE,IAAuB,CAAC,GAC1B,IAAS;CAEb,KAAK,IAAM,KAAQ,EAAI,MAAM,OAAO,GAClC,AAAI,EAAK,WAAW,IAAI,IAAG,KAAU,IAC5B,EAAK,WAAW,cAAc,IAAG,IAAQ,EAAK,MAAM,EAAE,IACtD,EAAK,WAAW,YAAY,IAAG,IAAM,EAAK,MAAM,EAAE,IAClD,EAAK,WAAW,wBAAwB,IAAG,IAAc,EAAK,MAAM,EAAE,CAAC,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC,YAAY,IACxG,EAAK,WAAW,UAAU,IAAG,IAAQ,EAAK,MAAM,CAAC,IACjD,EAAK,WAAW,QAAQ,IAAG,IAAM,EAAK,MAAM,CAAC,IAC7C,EAAK,WAAW,cAAc,IAAG,IAAW,OAAO,EAAK,MAAM,EAAE,CAAC,IACjE,EAAK,WAAW,qBAAqB,IAAG,IAAiB,OAAO,EAAK,MAAM,EAAE,CAAC,IAC9E,EAAK,WAAW,cAAc,KAAG,EAAW,KAAK,EAAK,MAAM,EAAE,CAAC;CAI1E,OADI,MAAW,KAAK,CAAC,KAAS,CAAC,KAAO,CAAC,IAAoB,OACpD;EACF;EACH,GAAG;EACH,GAAG;EACH,GAAG;EACH,GAAG,KAAS;EACZ,GAAI,IAAiB,EAAE,IAAI,EAAe,IAAI,CAAC;EAC/C,GAAI,IAAW,EAAE,IAAI,EAAS,IAAI,CAAC;EACnC,GAAG;CACL;AACF;AAQA,SAAgB,EAAa,GAAqB;CAChD,IAAM,IAAU,EAAW,CAAG;CAC9B,OAAO,IAAU,KAAK,UAAU,CAAO,IAAI;AAC7C;AAEA,SAAgB,EAAU,GAA6B;CACrD,IAAM,IAAW,EAAQ,EAAE,QAAQ,SAAS,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE;CAC9D,OAAO,GAAG;EACR;EACA;EACA;EACA;EACA,kBAAkB,EAAQ;EAC1B;EACA;EACA,eAAe,EAAQ;EACvB,aAAa,EAAQ;EACrB,yBAAyB;EACzB,WAAW,EAAQ;EACnB,SAAS,EAAQ;EACjB,eAAe,EAAQ,MAAM;EAC7B,GAAI,EAAQ,KAAK,CAAC,sBAAsB,EAAQ,IAAI,IAAI,CAAC;EACzD,GAAG,EAAQ,EAAE,KAAK,MAAS,eAAe,GAAM;EAChD;CACF,CAAC,CAAC,KAAK,MAAM;AACf"}
1
+ {"version":3,"file":"_sdp.js","names":[],"sources":["../src/_sdp.ts"],"sourcesContent":["import { MeshPairingError } from './errors';\n\n/**\n * Compact SDP serialization for pairing payloads. A data-channel-only\n * SDP is mostly boilerplate; the remote side only needs the ICE credentials,\n * DTLS fingerprint, setup role, mid, and candidates. `compactSdp` extracts\n * those fields and `expandSdp` rebuilds a minimal valid SDP for\n * `setRemoteDescription`.\n *\n * `compactSdp` returns `null` for shapes it cannot safely represent\n * (multiple m-lines, missing credentials), letting callers keep the raw SDP.\n */\n\nexport interface CompactSdp {\n /** `a=candidate:` lines, prefix stripped. */\n c: string[];\n /** sha-256 fingerprint, hex without colons. */\n f: string;\n /** `a=mid`. */\n m: string;\n /** `a=max-message-size`. */\n mm?: number;\n /** `a=ice-pwd`. */\n p: string;\n /** `a=setup` role. */\n s: string;\n /** `a=sctp-port`. */\n sp?: number;\n /** `a=ice-ufrag`. */\n u: string;\n}\n\nfunction isCompactSdp(value: unknown): value is CompactSdp {\n if (typeof value !== 'object' || value === null) return false;\n const c = value as Record<string, unknown>;\n return (\n typeof c.u === 'string' &&\n typeof c.p === 'string' &&\n typeof c.f === 'string' &&\n typeof c.s === 'string' &&\n typeof c.m === 'string' &&\n Array.isArray(c.c) &&\n c.c.every((line) => typeof line === 'string')\n );\n}\n\n/** Returns the expanded SDP unchanged when `value` is already a string. */\nexport function restoreSdp(value: unknown): string {\n if (typeof value === 'string') return value;\n if (isCompactSdp(value)) return expandSdp(value);\n throw new MeshPairingError('Malformed pairing payload');\n}\n\nexport function compactSdp(sdp: string): CompactSdp | null {\n let ufrag = '';\n let pwd = '';\n let fingerprint = '';\n let setup = '';\n let mid = '0';\n let sctpPort: number | undefined;\n let maxMessageSize: number | undefined;\n const candidates: string[] = [];\n let mLines = 0;\n\n for (const line of sdp.split(/\\r?\\n/)) {\n if (line.startsWith('m=')) mLines += 1;\n else if (line.startsWith('a=ice-ufrag:')) ufrag = line.slice(12);\n else if (line.startsWith('a=ice-pwd:')) pwd = line.slice(10);\n else if (line.startsWith('a=fingerprint:sha-256 ')) fingerprint = line.slice(22).replaceAll(':', '').toUpperCase();\n else if (line.startsWith('a=setup:')) setup = line.slice(8);\n else if (line.startsWith('a=mid:')) mid = line.slice(6);\n else if (line.startsWith('a=sctp-port:')) sctpPort = Number(line.slice(12));\n else if (line.startsWith('a=max-message-size:')) maxMessageSize = Number(line.slice(19));\n else if (line.startsWith('a=candidate:')) candidates.push(line.slice(12));\n }\n\n if (mLines !== 1 || !ufrag || !pwd || !fingerprint) return null;\n return {\n c: candidates,\n f: fingerprint,\n m: mid,\n p: pwd,\n s: setup || 'actpass',\n ...(maxMessageSize ? { mm: maxMessageSize } : {}),\n ...(sctpPort ? { sp: sctpPort } : {}),\n u: ufrag,\n };\n}\n\n/**\n * Canonical signing form: the compact serialization when the SDP is\n * recognizable, else the raw text. `compactSdp` is lossless over the fields\n * it keeps, so the rebuilt `mq2.` SDP canonicalizes identically to the\n * original: a proof computed over one verifies against the other.\n */\nexport function canonicalSdp(sdp: string): string {\n const compact = compactSdp(sdp);\n return compact ? JSON.stringify(compact) : sdp;\n}\n\nexport function expandSdp(compact: CompactSdp): string {\n const colonHex = compact.f.replace(/(..)/g, '$1:').slice(0, -1);\n return `${[\n 'v=0',\n 'o=- 0 2 IN IP4 0.0.0.0',\n 's=-',\n 't=0 0',\n `a=group:BUNDLE ${compact.m}`,\n 'm=application 9 UDP/DTLS/SCTP webrtc-datachannel',\n 'c=IN IP4 0.0.0.0',\n `a=ice-ufrag:${compact.u}`,\n `a=ice-pwd:${compact.p}`,\n `a=fingerprint:sha-256 ${colonHex}`,\n `a=setup:${compact.s}`,\n `a=mid:${compact.m}`,\n `a=sctp-port:${compact.sp ?? 5000}`,\n ...(compact.mm ? [`a=max-message-size:${compact.mm}`] : []),\n ...compact.c.map((line) => `a=candidate:${line}`),\n '',\n ].join('\\r\\n')}`;\n}\n"],"mappings":"+CAgCA,SAAS,aAAa,EAAqC,CACzD,GAAI,OAAO,GAAU,WAAY,EAAgB,MAAO,GACxD,IAAM,EAAI,EACV,OACE,OAAO,EAAE,GAAM,UACf,OAAO,EAAE,GAAM,UACf,OAAO,EAAE,GAAM,UACf,OAAO,EAAE,GAAM,UACf,OAAO,EAAE,GAAM,UACf,MAAM,QAAQ,EAAE,CAAC,GACjB,EAAE,EAAE,MAAO,GAAS,OAAO,GAAS,QAAQ,CAEhD,CAGA,SAAgB,WAAW,EAAwB,CACjD,GAAI,OAAO,GAAU,SAAU,OAAO,EACtC,GAAI,aAAa,CAAK,EAAG,OAAO,UAAU,CAAK,EAC/C,MAAM,IAAI,EAAiB,2BAA2B,CACxD,CAEA,SAAgB,WAAW,EAAgC,CACzD,IAAI,EAAQ,GACR,EAAM,GACN,EAAc,GACd,EAAQ,GACR,EAAM,IACN,EACA,EACE,EAAuB,CAAC,EAC1B,EAAS,EAEb,IAAK,IAAM,KAAQ,EAAI,MAAM,OAAO,EAC9B,EAAK,WAAW,IAAI,EAAG,GAAU,EAC5B,EAAK,WAAW,cAAc,EAAG,EAAQ,EAAK,MAAM,EAAE,EACtD,EAAK,WAAW,YAAY,EAAG,EAAM,EAAK,MAAM,EAAE,EAClD,EAAK,WAAW,wBAAwB,EAAG,EAAc,EAAK,MAAM,EAAE,CAAC,CAAC,WAAW,IAAK,EAAE,CAAC,CAAC,YAAY,EACxG,EAAK,WAAW,UAAU,EAAG,EAAQ,EAAK,MAAM,CAAC,EACjD,EAAK,WAAW,QAAQ,EAAG,EAAM,EAAK,MAAM,CAAC,EAC7C,EAAK,WAAW,cAAc,EAAG,EAAW,OAAO,EAAK,MAAM,EAAE,CAAC,EACjE,EAAK,WAAW,qBAAqB,EAAG,EAAiB,OAAO,EAAK,MAAM,EAAE,CAAC,EAC9E,EAAK,WAAW,cAAc,GAAG,EAAW,KAAK,EAAK,MAAM,EAAE,CAAC,EAI1E,OADI,IAAW,GAAK,CAAC,GAAS,CAAC,GAAO,CAAC,EAAoB,KACpD,CACF,EACH,EAAG,EACH,EAAG,EACH,EAAG,EACH,EAAG,GAAS,UACZ,GAAI,EAAiB,CAAE,GAAI,CAAe,EAAI,CAAC,EAC/C,GAAI,EAAW,CAAE,GAAI,CAAS,EAAI,CAAC,EACnC,EAAG,CACL,CACF,CAQA,SAAgB,aAAa,EAAqB,CAChD,IAAM,EAAU,WAAW,CAAG,EAC9B,OAAO,EAAU,KAAK,UAAU,CAAO,EAAI,CAC7C,CAEA,SAAgB,UAAU,EAA6B,CACrD,IAAM,EAAW,EAAQ,EAAE,QAAQ,QAAS,KAAK,CAAC,CAAC,MAAM,EAAG,EAAE,EAC9D,MAAO,GAAG,CACR,MACA,yBACA,MACA,QACA,kBAAkB,EAAQ,IAC1B,mDACA,mBACA,eAAe,EAAQ,IACvB,aAAa,EAAQ,IACrB,yBAAyB,IACzB,WAAW,EAAQ,IACnB,SAAS,EAAQ,IACjB,eAAe,EAAQ,IAAM,MAC7B,GAAI,EAAQ,GAAK,CAAC,sBAAsB,EAAQ,IAAI,EAAI,CAAC,EACzD,GAAG,EAAQ,EAAE,IAAK,GAAS,eAAe,GAAM,EAChD,EACF,CAAC,CAAC,KAAK;CAAM,GACf"}
package/dist/_wire.cjs CHANGED
@@ -1,2 +1,2 @@
1
- function e(e){if(typeof e!=`object`||!e)return null;let t=e;return t.v!==1||typeof t.id!=`string`||typeof t.t!=`string`||typeof t.ts!=`number`?null:e}var t=class{seen=new Set;order=[];capacity;constructor(e=256){this.capacity=e}check(e){return!this.seen.has(e)&&(this.seen.add(e),this.order.push(e),this.order.length>this.capacity&&this.seen.delete(this.order.shift()),!0)}};exports.MessageDedupe=t,exports.toEnvelope=e;
1
+ function toEnvelope(e){if(typeof e!=`object`||!e)return null;let t=e;return t.v!==1||typeof t.id!=`string`||typeof t.t!=`string`||typeof t.ts!=`number`?null:e}var MessageDedupe=class{seen=new Set;order=[];capacity;constructor(e=256){this.capacity=e}check(e){return!this.seen.has(e)&&(this.seen.add(e),this.order.push(e),this.order.length>this.capacity&&this.seen.delete(this.order.shift()),!0)}};exports.MessageDedupe=MessageDedupe,exports.toEnvelope=toEnvelope;
2
2
  //# sourceMappingURL=_wire.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_wire.cjs","names":[],"sources":["../src/_wire.ts"],"sourcesContent":["/** Wire envelope serialized through the configured `serialize` function. */\nexport interface WireEnvelope {\n readonly id: string;\n readonly p: unknown;\n readonly t: string;\n readonly ts: number;\n readonly v: 1;\n}\n\n/** Returns the envelope, or `null` when the value violates the wire shape. */\nexport function toEnvelope(value: unknown): WireEnvelope | null {\n if (typeof value !== 'object' || value === null) return null;\n const env = value as Record<string, unknown>;\n if (env.v !== 1 || typeof env.id !== 'string' || typeof env.t !== 'string' || typeof env.ts !== 'number') return null;\n return value as WireEnvelope;\n}\n\n/** Per-peer duplicate guard: remembers the last `capacity` message ids (LRU). */\nexport class MessageDedupe {\n private readonly seen = new Set<string>();\n private readonly order: string[] = [];\n private readonly capacity: number;\n\n constructor(capacity = 256) {\n this.capacity = capacity;\n }\n\n /** `true` when the id is new (and records it); `false` for duplicates. */\n check(id: string): boolean {\n if (this.seen.has(id)) return false;\n this.seen.add(id);\n this.order.push(id);\n if (this.order.length > this.capacity) this.seen.delete(this.order.shift()!);\n return true;\n }\n}\n"],"mappings":"AAUA,SAAgB,EAAW,EAAqC,CAC9D,GAAI,OAAO,GAAU,WAAY,EAAgB,OAAO,KACxD,IAAM,EAAM,EAEZ,OADI,EAAI,IAAM,GAAK,OAAO,EAAI,IAAO,UAAY,OAAO,EAAI,GAAM,UAAY,OAAO,EAAI,IAAO,SAAiB,KAC1G,CACT,CAGA,IAAa,EAAb,KAA2B,CACzB,KAAwB,IAAI,IAC5B,MAAmC,CAAC,EACpC,SAEA,YAAY,EAAW,IAAK,CAC1B,KAAK,SAAW,CAClB,CAGA,MAAM,EAAqB,CAKzB,MAJA,CAAI,KAAK,KAAK,IAAI,CAAE,IACpB,KAAK,KAAK,IAAI,CAAE,EAChB,KAAK,MAAM,KAAK,CAAE,EACd,KAAK,MAAM,OAAS,KAAK,UAAU,KAAK,KAAK,OAAO,KAAK,MAAM,MAAM,CAAE,EACpE,GACT,CACF"}
1
+ {"version":3,"file":"_wire.cjs","names":[],"sources":["../src/_wire.ts"],"sourcesContent":["/** Wire envelope serialized through the configured `serialize` function. */\nexport interface WireEnvelope {\n readonly id: string;\n readonly p: unknown;\n readonly t: string;\n readonly ts: number;\n readonly v: 1;\n}\n\n/** Returns the envelope, or `null` when the value violates the wire shape. */\nexport function toEnvelope(value: unknown): WireEnvelope | null {\n if (typeof value !== 'object' || value === null) return null;\n const env = value as Record<string, unknown>;\n if (env.v !== 1 || typeof env.id !== 'string' || typeof env.t !== 'string' || typeof env.ts !== 'number') return null;\n return value as WireEnvelope;\n}\n\n/** Per-peer duplicate guard: remembers the last `capacity` message ids (LRU). */\nexport class MessageDedupe {\n private readonly seen = new Set<string>();\n private readonly order: string[] = [];\n private readonly capacity: number;\n\n constructor(capacity = 256) {\n this.capacity = capacity;\n }\n\n /** `true` when the id is new (and records it); `false` for duplicates. */\n check(id: string): boolean {\n if (this.seen.has(id)) return false;\n this.seen.add(id);\n this.order.push(id);\n if (this.order.length > this.capacity) this.seen.delete(this.order.shift()!);\n return true;\n }\n}\n"],"mappings":"AAUA,SAAgB,WAAW,EAAqC,CAC9D,GAAI,OAAO,GAAU,WAAY,EAAgB,OAAO,KACxD,IAAM,EAAM,EAEZ,OADI,EAAI,IAAM,GAAK,OAAO,EAAI,IAAO,UAAY,OAAO,EAAI,GAAM,UAAY,OAAO,EAAI,IAAO,SAAiB,KAC1G,CACT,CAGA,IAAa,cAAb,KAA2B,CACzB,KAAwB,IAAI,IAC5B,MAAmC,CAAC,EACpC,SAEA,YAAY,EAAW,IAAK,CAC1B,KAAK,SAAW,CAClB,CAGA,MAAM,EAAqB,CAKzB,MAJA,CAAI,KAAK,KAAK,IAAI,CAAE,IACpB,KAAK,KAAK,IAAI,CAAE,EAChB,KAAK,MAAM,KAAK,CAAE,EACd,KAAK,MAAM,OAAS,KAAK,UAAU,KAAK,KAAK,OAAO,KAAK,MAAM,MAAM,CAAE,EACpE,GACT,CACF"}
package/dist/_wire.js CHANGED
@@ -1,21 +1,2 @@
1
- //#region src/_wire.ts
2
- function e(e) {
3
- if (typeof e != "object" || !e) return null;
4
- let t = e;
5
- return t.v !== 1 || typeof t.id != "string" || typeof t.t != "string" || typeof t.ts != "number" ? null : e;
6
- }
7
- var t = class {
8
- seen = /* @__PURE__ */ new Set();
9
- order = [];
10
- capacity;
11
- constructor(e = 256) {
12
- this.capacity = e;
13
- }
14
- check(e) {
15
- return !this.seen.has(e) && (this.seen.add(e), this.order.push(e), this.order.length > this.capacity && this.seen.delete(this.order.shift()), !0);
16
- }
17
- };
18
- //#endregion
19
- export { t as MessageDedupe, e as toEnvelope };
20
-
1
+ function toEnvelope(e){if(typeof e!=`object`||!e)return null;let t=e;return t.v!==1||typeof t.id!=`string`||typeof t.t!=`string`||typeof t.ts!=`number`?null:e}var MessageDedupe=class{seen=new Set;order=[];capacity;constructor(e=256){this.capacity=e}check(e){return!this.seen.has(e)&&(this.seen.add(e),this.order.push(e),this.order.length>this.capacity&&this.seen.delete(this.order.shift()),!0)}};export{MessageDedupe,toEnvelope};
21
2
  //# sourceMappingURL=_wire.js.map
package/dist/_wire.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"_wire.js","names":[],"sources":["../src/_wire.ts"],"sourcesContent":["/** Wire envelope serialized through the configured `serialize` function. */\nexport interface WireEnvelope {\n readonly id: string;\n readonly p: unknown;\n readonly t: string;\n readonly ts: number;\n readonly v: 1;\n}\n\n/** Returns the envelope, or `null` when the value violates the wire shape. */\nexport function toEnvelope(value: unknown): WireEnvelope | null {\n if (typeof value !== 'object' || value === null) return null;\n const env = value as Record<string, unknown>;\n if (env.v !== 1 || typeof env.id !== 'string' || typeof env.t !== 'string' || typeof env.ts !== 'number') return null;\n return value as WireEnvelope;\n}\n\n/** Per-peer duplicate guard: remembers the last `capacity` message ids (LRU). */\nexport class MessageDedupe {\n private readonly seen = new Set<string>();\n private readonly order: string[] = [];\n private readonly capacity: number;\n\n constructor(capacity = 256) {\n this.capacity = capacity;\n }\n\n /** `true` when the id is new (and records it); `false` for duplicates. */\n check(id: string): boolean {\n if (this.seen.has(id)) return false;\n this.seen.add(id);\n this.order.push(id);\n if (this.order.length > this.capacity) this.seen.delete(this.order.shift()!);\n return true;\n }\n}\n"],"mappings":";AAUA,SAAgB,EAAW,GAAqC;CAC9D,IAAI,OAAO,KAAU,aAAY,GAAgB,OAAO;CACxD,IAAM,IAAM;CAEZ,OADI,EAAI,MAAM,KAAK,OAAO,EAAI,MAAO,YAAY,OAAO,EAAI,KAAM,YAAY,OAAO,EAAI,MAAO,WAAiB,OAC1G;AACT;AAGA,IAAa,IAAb,MAA2B;CACzB,uBAAwB,IAAI,IAAY;CACxC,QAAmC,CAAC;CACpC;CAEA,YAAY,IAAW,KAAK;EAC1B,KAAK,WAAW;CAClB;CAGA,MAAM,GAAqB;EAKzB,OAJA,CAAI,KAAK,KAAK,IAAI,CAAE,MACpB,KAAK,KAAK,IAAI,CAAE,GAChB,KAAK,MAAM,KAAK,CAAE,GACd,KAAK,MAAM,SAAS,KAAK,YAAU,KAAK,KAAK,OAAO,KAAK,MAAM,MAAM,CAAE,GACpE;CACT;AACF"}
1
+ {"version":3,"file":"_wire.js","names":[],"sources":["../src/_wire.ts"],"sourcesContent":["/** Wire envelope serialized through the configured `serialize` function. */\nexport interface WireEnvelope {\n readonly id: string;\n readonly p: unknown;\n readonly t: string;\n readonly ts: number;\n readonly v: 1;\n}\n\n/** Returns the envelope, or `null` when the value violates the wire shape. */\nexport function toEnvelope(value: unknown): WireEnvelope | null {\n if (typeof value !== 'object' || value === null) return null;\n const env = value as Record<string, unknown>;\n if (env.v !== 1 || typeof env.id !== 'string' || typeof env.t !== 'string' || typeof env.ts !== 'number') return null;\n return value as WireEnvelope;\n}\n\n/** Per-peer duplicate guard: remembers the last `capacity` message ids (LRU). */\nexport class MessageDedupe {\n private readonly seen = new Set<string>();\n private readonly order: string[] = [];\n private readonly capacity: number;\n\n constructor(capacity = 256) {\n this.capacity = capacity;\n }\n\n /** `true` when the id is new (and records it); `false` for duplicates. */\n check(id: string): boolean {\n if (this.seen.has(id)) return false;\n this.seen.add(id);\n this.order.push(id);\n if (this.order.length > this.capacity) this.seen.delete(this.order.shift()!);\n return true;\n }\n}\n"],"mappings":"AAUA,SAAgB,WAAW,EAAqC,CAC9D,GAAI,OAAO,GAAU,WAAY,EAAgB,OAAO,KACxD,IAAM,EAAM,EAEZ,OADI,EAAI,IAAM,GAAK,OAAO,EAAI,IAAO,UAAY,OAAO,EAAI,GAAM,UAAY,OAAO,EAAI,IAAO,SAAiB,KAC1G,CACT,CAGA,IAAa,cAAb,KAA2B,CACzB,KAAwB,IAAI,IAC5B,MAAmC,CAAC,EACpC,SAEA,YAAY,EAAW,IAAK,CAC1B,KAAK,SAAW,CAClB,CAGA,MAAM,EAAqB,CAKzB,MAJA,CAAI,KAAK,KAAK,IAAI,CAAE,IACpB,KAAK,KAAK,IAAI,CAAE,EAChB,KAAK,MAAM,KAAK,CAAE,EACd,KAAK,MAAM,OAAS,KAAK,UAAU,KAAK,KAAK,OAAO,KAAK,MAAM,MAAM,CAAE,EACpE,GACT,CACF"}
package/dist/codec.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./_base64.cjs"),t=require("./errors.cjs");function n(e){return typeof e==`object`&&!!e}function r(e){return e.v===1&&typeof e.sessionId==`string`&&typeof e.secret==`string`&&typeof e.expiresAt==`number`&&typeof e.sdp==`string`}function i(e){return e.v===1&&typeof e.sessionId==`string`&&typeof e.proof==`string`&&n(e.peer)&&typeof e.peer.id==`string`&&(e.peer.name===void 0||typeof e.peer.name==`string`)&&typeof e.sdp==`string`}function a(e){if(!n(e)||e.v!==1)throw new t.MeshPairingError(`Unsupported pairing payload version`);if(r(e)||i(e))return e;throw new t.MeshPairingError(`Unrecognized pairing payload shape`)}var o={decode(n){let r;try{r=JSON.parse(e.base64UrlToText(n.trim()))}catch(e){throw new t.MeshPairingError(`Malformed pairing payload`,{cause:e})}return a(r)},encode(t){return e.textToBase64Url(JSON.stringify(t))}};exports.meshCodec=o,exports.parsePairingPayload=a;
1
+ const e=require("./errors.cjs");let t=require("@vielzeug/arsenal");function isRecord(e){return typeof e==`object`&&!!e}function isInvitation(e){return e.v===1&&typeof e.sessionId==`string`&&typeof e.secret==`string`&&typeof e.expiresAt==`number`&&typeof e.sdp==`string`}function isAnswer(e){return e.v===1&&typeof e.sessionId==`string`&&typeof e.proof==`string`&&isRecord(e.peer)&&typeof e.peer.id==`string`&&e.peer.id!==``&&(e.peer.name===void 0||typeof e.peer.name==`string`)&&typeof e.sdp==`string`}function parsePairingPayload(t){if(!isRecord(t)||t.v!==1)throw new e.MeshPairingError(`Unsupported pairing payload version`);if(isInvitation(t)||isAnswer(t))return t;throw new e.MeshPairingError(`Unrecognized pairing payload shape`)}var n={decode(n){let r;try{r=JSON.parse((0,t.base64UrlToText)(n.trim()))}catch(t){throw new e.MeshPairingError(`Malformed pairing payload`,{cause:t})}return parsePairingPayload(r)},encode(e){return(0,t.textToBase64Url)(JSON.stringify(e))}};exports.isRecord=isRecord,exports.meshCodec=n,exports.parsePairingPayload=parsePairingPayload;
2
2
  //# sourceMappingURL=codec.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"codec.cjs","names":[],"sources":["../src/codec.ts"],"sourcesContent":["import { base64UrlToText, textToBase64Url } from './_base64';\nimport { MeshPairingError } from './errors';\nimport type { MeshAnswer, MeshCodec, MeshInvitation } from './types';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isInvitation(value: Record<string, unknown>): value is Record<string, unknown> & MeshInvitation {\n return (\n value.v === 1 &&\n typeof value.sessionId === 'string' &&\n typeof value.secret === 'string' &&\n typeof value.expiresAt === 'number' &&\n typeof value.sdp === 'string'\n );\n}\n\nfunction isAnswer(value: Record<string, unknown>): value is Record<string, unknown> & MeshAnswer {\n return (\n value.v === 1 &&\n typeof value.sessionId === 'string' &&\n typeof value.proof === 'string' &&\n isRecord(value.peer) &&\n typeof value.peer.id === 'string' &&\n (value.peer.name === undefined || typeof value.peer.name === 'string') &&\n typeof value.sdp === 'string'\n );\n}\n\n/**\n * Compact JSON → base64url codec for pairing payloads. Pure and transport-free\n * so alternative encodings (compressed, QR-optimized) can be added later\n * without touching the transport.\n *\n * @example\n * ```ts\n * const invitation = await host.createInvitation();\n * const text = meshCodec.encode(invitation); // copy/paste or navigator.share\n * const answer = await guest.acceptInvitation(meshCodec.decode(text));\n * await host.acceptAnswer(meshCodec.decode(meshCodec.encode(answer)));\n * ```\n *\n * @throws {MeshPairingError} On malformed text or unsupported payload shape/version.\n */\n/**\n * Validate a decoded pairing payload. Shared by `meshCodec` and `meshQrCodec`\n * so both encodings enforce the same shape/version contract.\n *\n * @throws {MeshPairingError} On unsupported version or unrecognized shape.\n */\nexport function parsePairingPayload(parsed: unknown): MeshInvitation | MeshAnswer {\n if (!isRecord(parsed) || parsed.v !== 1) {\n throw new MeshPairingError('Unsupported pairing payload version');\n }\n if (isInvitation(parsed) || isAnswer(parsed)) {\n return parsed;\n }\n throw new MeshPairingError('Unrecognized pairing payload shape');\n}\n\nexport const meshCodec: MeshCodec = {\n decode(text) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(base64UrlToText(text.trim()));\n } catch (cause) {\n throw new MeshPairingError('Malformed pairing payload', { cause });\n }\n return parsePairingPayload(parsed);\n },\n encode(payload) {\n return textToBase64Url(JSON.stringify(payload));\n },\n};\n"],"mappings":"2DAIA,SAAS,EAAS,EAAkD,CAClE,OAAO,OAAO,GAAU,YAAY,CACtC,CAEA,SAAS,EAAa,EAAmF,CACvG,OACE,EAAM,IAAM,GACZ,OAAO,EAAM,WAAc,UAC3B,OAAO,EAAM,QAAW,UACxB,OAAO,EAAM,WAAc,UAC3B,OAAO,EAAM,KAAQ,QAEzB,CAEA,SAAS,EAAS,EAA+E,CAC/F,OACE,EAAM,IAAM,GACZ,OAAO,EAAM,WAAc,UAC3B,OAAO,EAAM,OAAU,UACvB,EAAS,EAAM,IAAI,GACnB,OAAO,EAAM,KAAK,IAAO,WACxB,EAAM,KAAK,OAAS,IAAA,IAAa,OAAO,EAAM,KAAK,MAAS,WAC7D,OAAO,EAAM,KAAQ,QAEzB,CAuBA,SAAgB,EAAoB,EAA8C,CAChF,GAAI,CAAC,EAAS,CAAM,GAAK,EAAO,IAAM,EACpC,MAAM,IAAI,EAAA,iBAAiB,qCAAqC,EAElE,GAAI,EAAa,CAAM,GAAK,EAAS,CAAM,EACzC,OAAO,EAET,MAAM,IAAI,EAAA,iBAAiB,oCAAoC,CACjE,CAEA,IAAa,EAAuB,CAClC,OAAO,EAAM,CACX,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,EAAA,gBAAgB,EAAK,KAAK,CAAC,CAAC,CAClD,OAAS,EAAO,CACd,MAAM,IAAI,EAAA,iBAAiB,4BAA6B,CAAE,OAAM,CAAC,CACnE,CACA,OAAO,EAAoB,CAAM,CACnC,EACA,OAAO,EAAS,CACd,OAAO,EAAA,gBAAgB,KAAK,UAAU,CAAO,CAAC,CAChD,CACF"}
1
+ {"version":3,"file":"codec.cjs","names":[],"sources":["../src/codec.ts"],"sourcesContent":["import { base64UrlToText, textToBase64Url } from '@vielzeug/arsenal';\nimport { MeshPairingError } from './errors';\nimport type { MeshAnswer, MeshInvitation } from './types';\n\n/** Structural guard shared by the plain and QR pairing codecs. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isInvitation(value: Record<string, unknown>): value is Record<string, unknown> & MeshInvitation {\n return (\n value.v === 1 &&\n typeof value.sessionId === 'string' &&\n typeof value.secret === 'string' &&\n typeof value.expiresAt === 'number' &&\n typeof value.sdp === 'string'\n );\n}\n\nfunction isAnswer(value: Record<string, unknown>): value is Record<string, unknown> & MeshAnswer {\n return (\n value.v === 1 &&\n typeof value.sessionId === 'string' &&\n typeof value.proof === 'string' &&\n isRecord(value.peer) &&\n typeof value.peer.id === 'string' &&\n value.peer.id !== '' &&\n (value.peer.name === undefined || typeof value.peer.name === 'string') &&\n typeof value.sdp === 'string'\n );\n}\n\n/**\n * Validate a decoded pairing payload. Shared by `meshCodec` and `meshQrCodec`\n * so both encodings enforce the same shape/version contract.\n *\n * @throws {MeshPairingError} On unsupported version or unrecognized shape.\n */\nexport function parsePairingPayload(parsed: unknown): MeshInvitation | MeshAnswer {\n if (!isRecord(parsed) || parsed.v !== 1) {\n throw new MeshPairingError('Unsupported pairing payload version');\n }\n if (isInvitation(parsed) || isAnswer(parsed)) {\n return parsed;\n }\n throw new MeshPairingError('Unrecognized pairing payload shape');\n}\n\n/**\n * Compact JSON → base64url codec for pairing payloads. Pure and transport-free\n * so alternative encodings (compressed, QR-optimized) can be added later\n * without touching the transport.\n *\n * @example\n * ```ts\n * const invitation = await host.createInvitation();\n * const text = meshCodec.encode(invitation); // copy/paste or navigator.share\n * const answer = await guest.acceptInvitation(meshCodec.decode(text));\n * await host.acceptAnswer(meshCodec.decode(meshCodec.encode(answer)));\n * ```\n *\n * @throws {MeshPairingError} On malformed text or unsupported payload shape/version.\n */\nexport const meshCodec = {\n decode(text: string): MeshInvitation | MeshAnswer {\n let parsed: unknown;\n try {\n parsed = JSON.parse(base64UrlToText(text.trim()));\n } catch (cause) {\n throw new MeshPairingError('Malformed pairing payload', { cause });\n }\n return parsePairingPayload(parsed);\n },\n encode(payload: MeshInvitation | MeshAnswer): string {\n return textToBase64Url(JSON.stringify(payload));\n },\n};\n"],"mappings":"mEAKA,SAAgB,SAAS,EAAkD,CACzE,OAAO,OAAO,GAAU,YAAY,CACtC,CAEA,SAAS,aAAa,EAAmF,CACvG,OACE,EAAM,IAAM,GACZ,OAAO,EAAM,WAAc,UAC3B,OAAO,EAAM,QAAW,UACxB,OAAO,EAAM,WAAc,UAC3B,OAAO,EAAM,KAAQ,QAEzB,CAEA,SAAS,SAAS,EAA+E,CAC/F,OACE,EAAM,IAAM,GACZ,OAAO,EAAM,WAAc,UAC3B,OAAO,EAAM,OAAU,UACvB,SAAS,EAAM,IAAI,GACnB,OAAO,EAAM,KAAK,IAAO,UACzB,EAAM,KAAK,KAAO,KACjB,EAAM,KAAK,OAAS,IAAA,IAAa,OAAO,EAAM,KAAK,MAAS,WAC7D,OAAO,EAAM,KAAQ,QAEzB,CAQA,SAAgB,oBAAoB,EAA8C,CAChF,GAAI,CAAC,SAAS,CAAM,GAAK,EAAO,IAAM,EACpC,MAAM,IAAI,EAAA,iBAAiB,qCAAqC,EAElE,GAAI,aAAa,CAAM,GAAK,SAAS,CAAM,EACzC,OAAO,EAET,MAAM,IAAI,EAAA,iBAAiB,oCAAoC,CACjE,CAiBA,IAAa,EAAY,CACvB,OAAO,EAA2C,CAChD,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,OAAA,EAAM,EAAA,gBAAA,CAAgB,EAAK,KAAK,CAAC,CAAC,CAClD,OAAS,EAAO,CACd,MAAM,IAAI,EAAA,iBAAiB,4BAA6B,CAAE,OAAM,CAAC,CACnE,CACA,OAAO,oBAAoB,CAAM,CACnC,EACA,OAAO,EAA8C,CACnD,OAAA,EAAO,EAAA,gBAAA,CAAgB,KAAK,UAAU,CAAO,CAAC,CAChD,CACF"}
package/dist/codec.d.ts CHANGED
@@ -1,4 +1,13 @@
1
- import type { MeshAnswer, MeshCodec, MeshInvitation } from './types';
1
+ import type { MeshAnswer, MeshInvitation } from './types';
2
+ /** Structural guard shared by the plain and QR pairing codecs. */
3
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
4
+ /**
5
+ * Validate a decoded pairing payload. Shared by `meshCodec` and `meshQrCodec`
6
+ * so both encodings enforce the same shape/version contract.
7
+ *
8
+ * @throws {MeshPairingError} On unsupported version or unrecognized shape.
9
+ */
10
+ export declare function parsePairingPayload(parsed: unknown): MeshInvitation | MeshAnswer;
2
11
  /**
3
12
  * Compact JSON → base64url codec for pairing payloads. Pure and transport-free
4
13
  * so alternative encodings (compressed, QR-optimized) can be added later
@@ -14,12 +23,8 @@ import type { MeshAnswer, MeshCodec, MeshInvitation } from './types';
14
23
  *
15
24
  * @throws {MeshPairingError} On malformed text or unsupported payload shape/version.
16
25
  */
17
- /**
18
- * Validate a decoded pairing payload. Shared by `meshCodec` and `meshQrCodec`
19
- * so both encodings enforce the same shape/version contract.
20
- *
21
- * @throws {MeshPairingError} On unsupported version or unrecognized shape.
22
- */
23
- export declare function parsePairingPayload(parsed: unknown): MeshInvitation | MeshAnswer;
24
- export declare const meshCodec: MeshCodec;
26
+ export declare const meshCodec: {
27
+ decode(text: string): MeshInvitation | MeshAnswer;
28
+ encode(payload: MeshInvitation | MeshAnswer): string;
29
+ };
25
30
  //# sourceMappingURL=codec.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../src/codec.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AA4BrE;;;;;;;;;;;;;;GAcG;AACH;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,cAAc,GAAG,UAAU,CAQhF;AAED,eAAO,MAAM,SAAS,EAAE,SAavB,CAAC"}
1
+ {"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../src/codec.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE1D,kEAAkE;AAClE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAyBD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,cAAc,GAAG,UAAU,CAQhF;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS;iBACP,MAAM,GAAG,cAAc,GAAG,UAAU;oBASjC,cAAc,GAAG,UAAU,GAAG,MAAM;CAGrD,CAAC"}
package/dist/codec.js CHANGED
@@ -1,35 +1,2 @@
1
- import { base64UrlToText as e, textToBase64Url as t } from "./_base64.js";
2
- import { MeshPairingError as n } from "./errors.js";
3
- //#region src/codec.ts
4
- function r(e) {
5
- return typeof e == "object" && !!e;
6
- }
7
- function i(e) {
8
- return e.v === 1 && typeof e.sessionId == "string" && typeof e.secret == "string" && typeof e.expiresAt == "number" && typeof e.sdp == "string";
9
- }
10
- function a(e) {
11
- return e.v === 1 && typeof e.sessionId == "string" && typeof e.proof == "string" && r(e.peer) && typeof e.peer.id == "string" && (e.peer.name === void 0 || typeof e.peer.name == "string") && typeof e.sdp == "string";
12
- }
13
- function o(e) {
14
- if (!r(e) || e.v !== 1) throw new n("Unsupported pairing payload version");
15
- if (i(e) || a(e)) return e;
16
- throw new n("Unrecognized pairing payload shape");
17
- }
18
- var s = {
19
- decode(t) {
20
- let r;
21
- try {
22
- r = JSON.parse(e(t.trim()));
23
- } catch (e) {
24
- throw new n("Malformed pairing payload", { cause: e });
25
- }
26
- return o(r);
27
- },
28
- encode(e) {
29
- return t(JSON.stringify(e));
30
- }
31
- };
32
- //#endregion
33
- export { s as meshCodec, o as parsePairingPayload };
34
-
1
+ import{MeshPairingError as e}from"./errors.js";import{base64UrlToText as t,textToBase64Url as n}from"@vielzeug/arsenal";function isRecord(e){return typeof e==`object`&&!!e}function isInvitation(e){return e.v===1&&typeof e.sessionId==`string`&&typeof e.secret==`string`&&typeof e.expiresAt==`number`&&typeof e.sdp==`string`}function isAnswer(e){return e.v===1&&typeof e.sessionId==`string`&&typeof e.proof==`string`&&isRecord(e.peer)&&typeof e.peer.id==`string`&&e.peer.id!==``&&(e.peer.name===void 0||typeof e.peer.name==`string`)&&typeof e.sdp==`string`}function parsePairingPayload(t){if(!isRecord(t)||t.v!==1)throw new e(`Unsupported pairing payload version`);if(isInvitation(t)||isAnswer(t))return t;throw new e(`Unrecognized pairing payload shape`)}var r={decode(n){let r;try{r=JSON.parse(t(n.trim()))}catch(t){throw new e(`Malformed pairing payload`,{cause:t})}return parsePairingPayload(r)},encode(e){return n(JSON.stringify(e))}};export{isRecord,r as meshCodec,parsePairingPayload};
35
2
  //# sourceMappingURL=codec.js.map