@vielzeug/mesh 26.10.15 → 26.10.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/_base45.cjs.map +1 -1
- package/dist/_base45.js +37 -1
- package/dist/_base45.js.map +1 -1
- package/dist/_defaults.cjs.map +1 -1
- package/dist/_defaults.js +5 -1
- package/dist/_defaults.js.map +1 -1
- package/dist/_node.cjs.map +1 -1
- package/dist/_node.js +166 -1
- package/dist/_node.js.map +1 -1
- package/dist/_proof.cjs.map +1 -1
- package/dist/_proof.js +16 -1
- package/dist/_proof.js.map +1 -1
- package/dist/_random.cjs.map +1 -1
- package/dist/_random.js +14 -1
- package/dist/_random.js.map +1 -1
- package/dist/_rtc.cjs.map +1 -1
- package/dist/_rtc.js +41 -3
- package/dist/_rtc.js.map +1 -1
- package/dist/_sdp.cjs.map +1 -1
- package/dist/_sdp.js +54 -2
- package/dist/_sdp.js.map +1 -1
- package/dist/_wire.cjs.map +1 -1
- package/dist/_wire.js +20 -1
- package/dist/_wire.js.map +1 -1
- package/dist/codec.cjs.map +1 -1
- package/dist/codec.js +34 -1
- package/dist/codec.js.map +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js +18 -1
- package/dist/errors.js.map +1 -1
- package/dist/guest.cjs.map +1 -1
- package/dist/guest.js +153 -1
- package/dist/guest.js.map +1 -1
- package/dist/host.cjs.map +1 -1
- package/dist/host.js +202 -1
- package/dist/host.js.map +1 -1
- package/dist/index.js +6 -1
- package/dist/mesh.cjs.map +1 -1
- package/dist/mesh.iife.js.map +1 -1
- package/dist/mesh.js +756 -4
- package/dist/mesh.js.map +1 -1
- package/dist/qr-codec.cjs.map +1 -1
- package/dist/qr-codec.js +52 -1
- package/dist/qr-codec.js.map +1 -1
- package/dist/testing/fake-rtc.cjs +1 -1
- package/dist/testing/fake-rtc.cjs.map +1 -1
- package/dist/testing/fake-rtc.js +153 -1
- package/dist/testing/fake-rtc.js.map +1 -1
- package/dist/testing.js +1 -1
- package/package.json +6 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Helmuth Saatkamp
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/_base45.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_base45.cjs","names":[],"sources":["../src/_base45.ts"],"sourcesContent":["import { MeshPairingError } from './errors';\n\n/**\n * Base45 (RFC 9285): two bytes → three chars, one leftover byte → two chars.\n * The alphabet sits inside the QR alphanumeric charset, so QR encoders can use\n * alphanumeric mode (~5.5 bits/char) instead of byte mode: about a third\n * denser than base64 for the same payload.\n */\n\nconst ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';\nconst INDEX = new Map([...ALPHABET].map((ch, i) => [ch, i]));\n\nexport function bytesToBase45(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i + 1 < bytes.length; i += 2) {\n const value = bytes[i] * 256 + bytes[i + 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45) % 45] + ALPHABET[Math.floor(value / 2025)];\n }\n if (bytes.length % 2) {\n const value = bytes[bytes.length - 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45)];\n }\n return out;\n}\n\nexport function base45ToBytes(text: string): Uint8Array {\n const out: number[] = [];\n let i = 0;\n while (i + 2 < text.length) {\n const value = digit(text, i) + 45 * digit(text, i + 1) + 2025 * digit(text, i + 2);\n if (value > 0xffff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value >> 8, value & 0xff);\n i += 3;\n }\n if (text.length - i === 2) {\n const value = digit(text, i) + 45 * digit(text, i + 1);\n if (value > 0xff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value);\n } else if (text.length - i === 1) {\n throw new MeshPairingError('Malformed base45 payload');\n }\n return new Uint8Array(out);\n}\n\nfunction digit(text: string, at: number): number {\n const value = INDEX.get(text[at]);\n if (value === undefined) throw new MeshPairingError('Malformed base45 payload');\n return value;\n}\n"],"mappings":"gCASA,
|
|
1
|
+
{"version":3,"file":"_base45.cjs","names":["MeshPairingError","require_errors","ALPHABET","INDEX","ch","i","out","bytes","value","text","at"],"sources":["../src/_base45.ts"],"sourcesContent":["import { MeshPairingError } from './errors';\n\n/**\n * Base45 (RFC 9285): two bytes → three chars, one leftover byte → two chars.\n * The alphabet sits inside the QR alphanumeric charset, so QR encoders can use\n * alphanumeric mode (~5.5 bits/char) instead of byte mode: about a third\n * denser than base64 for the same payload.\n */\n\nconst ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';\nconst INDEX = new Map([...ALPHABET].map((ch, i) => [ch, i]));\n\nexport function bytesToBase45(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i + 1 < bytes.length; i += 2) {\n const value = bytes[i] * 256 + bytes[i + 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45) % 45] + ALPHABET[Math.floor(value / 2025)];\n }\n if (bytes.length % 2) {\n const value = bytes[bytes.length - 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45)];\n }\n return out;\n}\n\nexport function base45ToBytes(text: string): Uint8Array {\n const out: number[] = [];\n let i = 0;\n while (i + 2 < text.length) {\n const value = digit(text, i) + 45 * digit(text, i + 1) + 2025 * digit(text, i + 2);\n if (value > 0xffff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value >> 8, value & 0xff);\n i += 3;\n }\n if (text.length - i === 2) {\n const value = digit(text, i) + 45 * digit(text, i + 1);\n if (value > 0xff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value);\n } else if (text.length - i === 1) {\n throw new MeshPairingError('Malformed base45 payload');\n }\n return new Uint8Array(out);\n}\n\nfunction digit(text: string, at: number): number {\n const value = INDEX.get(text[at]);\n if (value === undefined) throw new MeshPairingError('Malformed base45 payload');\n return value;\n}\n"],"mappings":"A,gCASA,IAAME,EAAW,gDACXC,EAAQ,IAAI,IAAI,CAAC,GAAGD,CAAQ,CAAC,CAAC,KAAK,EAAI,IAAM,CAACE,EAAIC,CAAC,CAAC,CAAC,EAE3D,SAAgB,cAAc,EAA2B,CACvD,IAAIC,EAAM,GACV,IAAK,IAAID,EAAI,EAAGA,EAAI,EAAIE,EAAM,OAAQ,GAAK,EAAG,CAC5C,IAAMC,EAAQD,EAAMF,GAAK,IAAME,EAAMF,EAAI,GACzC,GAAOH,EAASM,EAAQ,IAAMN,EAAS,KAAK,MAAMM,EAAQ,EAAE,EAAI,IAAMN,EAAS,KAAK,MAAMM,EAAQ,IAAI,EACxG,CACA,GAAID,EAAM,OAAS,EAAG,CACpB,IAAMC,EAAQD,EAAMA,EAAM,OAAS,GACnC,GAAOL,EAASM,EAAQ,IAAMN,EAAS,KAAK,MAAMM,EAAQ,EAAE,EAC9D,CACA,OAAOF,CACT,CAEA,SAAgB,cAAc,EAA0B,CACtD,IAAMA,EAAgB,CAAC,EACnBD,EAAI,EACR,KAAOA,EAAI,EAAII,EAAK,QAAQ,CAC1B,IAAMD,EAAQ,MAAMC,EAAMJ,CAAC,EAAI,GAAK,MAAMI,EAAMJ,EAAI,CAAC,EAAI,KAAO,MAAMI,EAAMJ,EAAI,CAAC,EACjF,GAAIG,EAAQ,MAAQ,MAAM,IAAIR,EAAAA,iBAAiB,0BAA0B,EACzEM,EAAI,KAAKE,GAAS,EAAGA,EAAQ,GAAI,EACjC,GAAK,CACP,CACA,GAAIC,EAAK,OAASJ,IAAM,EAAG,CACzB,IAAMG,EAAQ,MAAMC,EAAMJ,CAAC,EAAI,GAAK,MAAMI,EAAMJ,EAAI,CAAC,EACrD,GAAIG,EAAQ,IAAM,MAAM,IAAIR,EAAAA,iBAAiB,0BAA0B,EACvEM,EAAI,KAAKE,CAAK,CAChB,MAAO,GAAIC,EAAK,OAASJ,IAAM,EAC7B,MAAM,IAAIL,EAAAA,iBAAiB,0BAA0B,EAEvD,OAAO,IAAI,WAAWM,CAAG,CAC3B,CAEA,SAAS,MAAM,EAAc,EAAoB,CAC/C,IAAME,EAAQL,EAAM,IAAIM,EAAKC,EAAG,EAChC,GAAIF,IAAU,IAAA,GAAW,MAAM,IAAIR,EAAAA,iBAAiB,0BAA0B,EAC9E,OAAOQ,CACT,C"}
|
package/dist/_base45.js
CHANGED
|
@@ -1,2 +1,38 @@
|
|
|
1
|
-
import{MeshPairingError as e}from"./errors.js";
|
|
1
|
+
import { MeshPairingError as e } from "./errors.js";
|
|
2
|
+
//#region src/_base45.ts
|
|
3
|
+
var t = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:", n = new Map([...t].map((e, t) => [e, t]));
|
|
4
|
+
function bytesToBase45(e) {
|
|
5
|
+
let n = "";
|
|
6
|
+
for (let r = 0; r + 1 < e.length; r += 2) {
|
|
7
|
+
let i = e[r] * 256 + e[r + 1];
|
|
8
|
+
n += t[i % 45] + t[Math.floor(i / 45) % 45] + t[Math.floor(i / 2025)];
|
|
9
|
+
}
|
|
10
|
+
if (e.length % 2) {
|
|
11
|
+
let r = e[e.length - 1];
|
|
12
|
+
n += t[r % 45] + t[Math.floor(r / 45)];
|
|
13
|
+
}
|
|
14
|
+
return n;
|
|
15
|
+
}
|
|
16
|
+
function base45ToBytes(t) {
|
|
17
|
+
let n = [], r = 0;
|
|
18
|
+
for (; r + 2 < t.length;) {
|
|
19
|
+
let i = digit(t, r) + 45 * digit(t, r + 1) + 2025 * digit(t, r + 2);
|
|
20
|
+
if (i > 65535) throw new e("Malformed base45 payload");
|
|
21
|
+
n.push(i >> 8, i & 255), r += 3;
|
|
22
|
+
}
|
|
23
|
+
if (t.length - r === 2) {
|
|
24
|
+
let i = digit(t, r) + 45 * digit(t, r + 1);
|
|
25
|
+
if (i > 255) throw new e("Malformed base45 payload");
|
|
26
|
+
n.push(i);
|
|
27
|
+
} else if (t.length - r === 1) throw new e("Malformed base45 payload");
|
|
28
|
+
return new Uint8Array(n);
|
|
29
|
+
}
|
|
30
|
+
function digit(t, r) {
|
|
31
|
+
let i = n.get(t[r]);
|
|
32
|
+
if (i === void 0) throw new e("Malformed base45 payload");
|
|
33
|
+
return i;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { base45ToBytes, bytesToBase45 };
|
|
37
|
+
|
|
2
38
|
//# sourceMappingURL=_base45.js.map
|
package/dist/_base45.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_base45.js","names":[],"sources":["../src/_base45.ts"],"sourcesContent":["import { MeshPairingError } from './errors';\n\n/**\n * Base45 (RFC 9285): two bytes → three chars, one leftover byte → two chars.\n * The alphabet sits inside the QR alphanumeric charset, so QR encoders can use\n * alphanumeric mode (~5.5 bits/char) instead of byte mode: about a third\n * denser than base64 for the same payload.\n */\n\nconst ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';\nconst INDEX = new Map([...ALPHABET].map((ch, i) => [ch, i]));\n\nexport function bytesToBase45(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i + 1 < bytes.length; i += 2) {\n const value = bytes[i] * 256 + bytes[i + 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45) % 45] + ALPHABET[Math.floor(value / 2025)];\n }\n if (bytes.length % 2) {\n const value = bytes[bytes.length - 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45)];\n }\n return out;\n}\n\nexport function base45ToBytes(text: string): Uint8Array {\n const out: number[] = [];\n let i = 0;\n while (i + 2 < text.length) {\n const value = digit(text, i) + 45 * digit(text, i + 1) + 2025 * digit(text, i + 2);\n if (value > 0xffff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value >> 8, value & 0xff);\n i += 3;\n }\n if (text.length - i === 2) {\n const value = digit(text, i) + 45 * digit(text, i + 1);\n if (value > 0xff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value);\n } else if (text.length - i === 1) {\n throw new MeshPairingError('Malformed base45 payload');\n }\n return new Uint8Array(out);\n}\n\nfunction digit(text: string, at: number): number {\n const value = INDEX.get(text[at]);\n if (value === undefined) throw new MeshPairingError('Malformed base45 payload');\n return value;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"_base45.js","names":["ALPHABET","INDEX","ch","i","out","bytes","value","text","MeshPairingError","at"],"sources":["../src/_base45.ts"],"sourcesContent":["import { MeshPairingError } from './errors';\n\n/**\n * Base45 (RFC 9285): two bytes → three chars, one leftover byte → two chars.\n * The alphabet sits inside the QR alphanumeric charset, so QR encoders can use\n * alphanumeric mode (~5.5 bits/char) instead of byte mode: about a third\n * denser than base64 for the same payload.\n */\n\nconst ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';\nconst INDEX = new Map([...ALPHABET].map((ch, i) => [ch, i]));\n\nexport function bytesToBase45(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i + 1 < bytes.length; i += 2) {\n const value = bytes[i] * 256 + bytes[i + 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45) % 45] + ALPHABET[Math.floor(value / 2025)];\n }\n if (bytes.length % 2) {\n const value = bytes[bytes.length - 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45)];\n }\n return out;\n}\n\nexport function base45ToBytes(text: string): Uint8Array {\n const out: number[] = [];\n let i = 0;\n while (i + 2 < text.length) {\n const value = digit(text, i) + 45 * digit(text, i + 1) + 2025 * digit(text, i + 2);\n if (value > 0xffff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value >> 8, value & 0xff);\n i += 3;\n }\n if (text.length - i === 2) {\n const value = digit(text, i) + 45 * digit(text, i + 1);\n if (value > 0xff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value);\n } else if (text.length - i === 1) {\n throw new MeshPairingError('Malformed base45 payload');\n }\n return new Uint8Array(out);\n}\n\nfunction digit(text: string, at: number): number {\n const value = INDEX.get(text[at]);\n if (value === undefined) throw new MeshPairingError('Malformed base45 payload');\n return value;\n}\n"],"mappings":";;AASA,IAAMA,IAAW,iDACXC,IAAQ,IAAI,IAAI,CAAC,GAAGD,CAAQ,CAAC,CAAC,KAAK,GAAI,MAAM,CAACE,GAAIC,CAAC,CAAC,CAAC;AAE3D,SAAgB,cAAc,GAA2B;CACvD,IAAIC,IAAM;CACV,KAAK,IAAID,IAAI,GAAGA,IAAI,IAAIE,EAAM,QAAQ,KAAK,GAAG;EAC5C,IAAMC,IAAQD,EAAMF,KAAK,MAAME,EAAMF,IAAI;EACzC,KAAOH,EAASM,IAAQ,MAAMN,EAAS,KAAK,MAAMM,IAAQ,EAAE,IAAI,MAAMN,EAAS,KAAK,MAAMM,IAAQ,IAAI;CACxG;CACA,IAAID,EAAM,SAAS,GAAG;EACpB,IAAMC,IAAQD,EAAMA,EAAM,SAAS;EACnC,KAAOL,EAASM,IAAQ,MAAMN,EAAS,KAAK,MAAMM,IAAQ,EAAE;CAC9D;CACA,OAAOF;AACT;AAEA,SAAgB,cAAc,GAA0B;CACtD,IAAMA,IAAgB,CAAC,GACnBD,IAAI;CACR,OAAOA,IAAI,IAAII,EAAK,SAAQ;EAC1B,IAAMD,IAAQ,MAAMC,GAAMJ,CAAC,IAAI,KAAK,MAAMI,GAAMJ,IAAI,CAAC,IAAI,OAAO,MAAMI,GAAMJ,IAAI,CAAC;EACjF,IAAIG,IAAQ,OAAQ,MAAM,IAAIE,EAAiB,0BAA0B;EAEzE,AADAJ,EAAI,KAAKE,KAAS,GAAGA,IAAQ,GAAI,GACjC,KAAK;CACP;CACA,IAAIC,EAAK,SAASJ,MAAM,GAAG;EACzB,IAAMG,IAAQ,MAAMC,GAAMJ,CAAC,IAAI,KAAK,MAAMI,GAAMJ,IAAI,CAAC;EACrD,IAAIG,IAAQ,KAAM,MAAM,IAAIE,EAAiB,0BAA0B;EACvE,EAAI,KAAKF,CAAK;CAChB,OAAO,IAAIC,EAAK,SAASJ,MAAM,GAC7B,MAAM,IAAIK,EAAiB,0BAA0B;CAEvD,OAAO,IAAI,WAAWJ,CAAG;AAC3B;AAEA,SAAS,MAAM,GAAc,GAAoB;CAC/C,IAAME,IAAQL,EAAM,IAAIM,EAAKE,EAAG;CAChC,IAAIH,MAAU,KAAA,GAAW,MAAM,IAAIE,EAAiB,0BAA0B;CAC9E,OAAOF;AACT"}
|
package/dist/_defaults.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_defaults.cjs","names":[],"sources":["../src/_defaults.ts"],"sourcesContent":["/** Option defaults shared by host and guest so the two never drift. */\nexport const DEFAULT_CHANNEL_OPEN_TIMEOUT_MS = 60_000;\nexport const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 5_000;\nexport const DEFAULT_INVITATION_TTL_MS = 300_000;\nexport const DEFAULT_MAX_MESSAGE_BYTES = 65_536;\n"],"mappings":"AACA,
|
|
1
|
+
{"version":3,"file":"_defaults.cjs","names":["DEFAULT_CHANNEL_OPEN_TIMEOUT_MS","DEFAULT_ICE_GATHERING_TIMEOUT_MS","DEFAULT_INVITATION_TTL_MS","DEFAULT_MAX_MESSAGE_BYTES"],"sources":["../src/_defaults.ts"],"sourcesContent":["/** Option defaults shared by host and guest so the two never drift. */\nexport const DEFAULT_CHANNEL_OPEN_TIMEOUT_MS = 60_000;\nexport const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 5_000;\nexport const DEFAULT_INVITATION_TTL_MS = 300_000;\nexport const DEFAULT_MAX_MESSAGE_BYTES = 65_536;\n"],"mappings":"AACA,IAAaA,EAAkC,IAClCC,EAAmC,IACnCC,EAA4B,IAC5BC,EAA4B,M"}
|
package/dist/_defaults.js
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/_defaults.ts
|
|
2
|
+
var e = 6e4, t = 5e3, n = 3e5, r = 65536;
|
|
3
|
+
//#endregion
|
|
4
|
+
export { e as DEFAULT_CHANNEL_OPEN_TIMEOUT_MS, t as DEFAULT_ICE_GATHERING_TIMEOUT_MS, n as DEFAULT_INVITATION_TTL_MS, r as DEFAULT_MAX_MESSAGE_BYTES };
|
|
5
|
+
|
|
2
6
|
//# sourceMappingURL=_defaults.js.map
|
package/dist/_defaults.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_defaults.js","names":[],"sources":["../src/_defaults.ts"],"sourcesContent":["/** Option defaults shared by host and guest so the two never drift. */\nexport const DEFAULT_CHANNEL_OPEN_TIMEOUT_MS = 60_000;\nexport const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 5_000;\nexport const DEFAULT_INVITATION_TTL_MS = 300_000;\nexport const DEFAULT_MAX_MESSAGE_BYTES = 65_536;\n"],"mappings":"AACA,
|
|
1
|
+
{"version":3,"file":"_defaults.js","names":["DEFAULT_CHANNEL_OPEN_TIMEOUT_MS","DEFAULT_ICE_GATHERING_TIMEOUT_MS","DEFAULT_INVITATION_TTL_MS","DEFAULT_MAX_MESSAGE_BYTES"],"sources":["../src/_defaults.ts"],"sourcesContent":["/** Option defaults shared by host and guest so the two never drift. */\nexport const DEFAULT_CHANNEL_OPEN_TIMEOUT_MS = 60_000;\nexport const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 5_000;\nexport const DEFAULT_INVITATION_TTL_MS = 300_000;\nexport const DEFAULT_MAX_MESSAGE_BYTES = 65_536;\n"],"mappings":";AACA,IAAaA,IAAkC,KAClCC,IAAmC,KACnCC,IAA4B,KAC5BC,IAA4B"}
|
package/dist/_node.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_node.cjs","names":[],"sources":["../src/_node.ts"],"sourcesContent":["import { type RandomSource, tapper, utf8Bytes } from '@vielzeug/arsenal';\nimport { randomId } from './_random';\nimport { MessageDedupe, toEnvelope } from './_wire';\nimport { MeshConnectionError, MeshDisposedError, MeshError, MeshPayloadError } from './errors';\nimport type {\n MeshEvent,\n MeshInbound,\n MeshPeer,\n MeshPeerStatus,\n MeshStatus,\n RTCDataChannelLike,\n RTCPeerConnectionLike,\n Unsubscribe,\n} from './types';\n\ntype Tapper = (event: MeshEvent) => void;\n\n/** Shared lifecycle core: status, tappers, disposal. */\nexport interface NodeCore {\n readonly disposalCtrl: AbortController;\n disposed: boolean;\n emitTap(event: MeshEvent): void;\n ensureLive(): void;\n /** Emits the final status-change + dispose events, clears tappers, aborts. */\n markDisposed(): void;\n setStatus(next: MeshStatus): void;\n status: MeshStatus;\n tap(handler: Tapper, options?: { readonly signal?: AbortSignal }): Unsubscribe;\n}\n\nexport function createNodeCore(signal?: AbortSignal): NodeCore {\n const tappers = tapper<MeshEvent>();\n const disposalCtrl = new AbortController();\n\n const core: NodeCore = {\n disposalCtrl,\n disposed: false,\n\n emitTap(event) {\n tappers.emit(event);\n },\n\n ensureLive() {\n if (core.disposed) throw new MeshDisposedError();\n },\n\n markDisposed() {\n if (core.disposed) return;\n core.disposed = true;\n core.status = 'disposed';\n core.emitTap({ status: 'disposed', type: 'status-change' });\n core.emitTap({ type: 'dispose' });\n tappers.clear();\n disposalCtrl.abort();\n },\n\n setStatus(next) {\n if (core.disposed || core.status === next) return;\n core.status = next;\n core.emitTap({ status: next, type: 'status-change' });\n },\n status: 'idle',\n\n tap(handler, options) {\n if (core.disposed) return () => {};\n return tappers.tap(handler, options);\n },\n };\n\n if (signal) {\n if (signal.aborted) {\n core.markDisposed();\n } else {\n signal.addEventListener('abort', () => core.markDisposed(), { once: true });\n }\n }\n\n return core;\n}\n\n// ─── Peers ───────────────────────────────────────────────────────────────────\n\n/** Internal peer record; `publicPeer` exposes live status through getters. */\nexport interface PeerRecord {\n dc: RTCDataChannelLike | null;\n readonly dedupe: MessageDedupe;\n readonly id: string;\n readonly name: string | undefined;\n pc: RTCPeerConnectionLike | null;\n readonly publicPeer: MeshPeer;\n readonly role: 'host' | 'guest';\n status: MeshPeerStatus;\n}\n\nexport function createPeerRecord(id: string, name: string | undefined, role: 'host' | 'guest'): PeerRecord {\n const record: PeerRecord = {\n dc: null,\n dedupe: new MessageDedupe(),\n id,\n name,\n pc: null,\n publicPeer: {\n get id() {\n return record.id;\n },\n get name() {\n return record.name;\n },\n get role() {\n return record.role;\n },\n get status() {\n return record.status;\n },\n },\n role,\n status: 'connecting',\n };\n return record;\n}\n\n// ─── Messenger ───────────────────────────────────────────────────────────────\n\ntype InboundHandler = (message: MeshInbound<unknown>) => void;\n\nexport interface MessengerOptions {\n readonly clock: () => number;\n readonly emitTap: (event: MeshEvent) => void;\n readonly maxMessageBytes: number;\n readonly random?: RandomSource;\n}\n\nexport interface Messenger {\n clear(): void;\n /** Entry point for `dc` 'message' events. */\n handleInbound(peer: PeerRecord, data: unknown): void;\n on(type: string, handler: InboundHandler): Unsubscribe;\n /** Sends a typed envelope to a peer. Throws `MeshConnectionError`/`MeshPayloadError`. */\n sendTo(peer: PeerRecord, type: string, payload: unknown): void;\n}\n\nexport function createMessenger(options: MessengerOptions): Messenger {\n const handlers = new Map<string, Set<InboundHandler>>();\n\n function reject(peerId: string, reason: 'too-large' | 'duplicate' | 'malformed' | 'unknown-type'): void {\n options.emitTap({ peerId, reason, type: 'message-rejected' });\n }\n\n return {\n clear() {\n handlers.clear();\n },\n\n handleInbound(peer, data) {\n const peerId = peer.id;\n\n if (typeof data !== 'string') {\n reject(peerId, 'malformed');\n return;\n }\n\n const bytes = utf8Bytes(data);\n if (bytes > options.maxMessageBytes) {\n reject(peerId, 'too-large');\n return;\n }\n\n let decoded: unknown;\n try {\n decoded = JSON.parse(data);\n } catch {\n reject(peerId, 'malformed');\n return;\n }\n\n const envelope = toEnvelope(decoded);\n if (!envelope) {\n reject(peerId, 'malformed');\n return;\n }\n\n if (!peer.dedupe.check(envelope.id)) {\n reject(peerId, 'duplicate');\n return;\n }\n\n const set = handlers.get(envelope.t);\n if (!set || set.size === 0) {\n reject(peerId, 'unknown-type');\n return;\n }\n\n options.emitTap({ bytes, messageType: envelope.t, peerId, type: 'message-received' });\n const message: MeshInbound<unknown> = {\n messageId: envelope.id,\n payload: envelope.p,\n peerId,\n sentAt: envelope.ts,\n };\n for (const handler of set) {\n try {\n handler(message);\n } catch (cause) {\n options.emitTap({ error: new MeshError('Message handler threw', { cause }), type: 'error' });\n }\n }\n },\n\n on(type, handler) {\n let set = handlers.get(type);\n if (!set) {\n set = new Set();\n handlers.set(type, set);\n }\n set.add(handler);\n return () => {\n set.delete(handler);\n if (set.size === 0) handlers.delete(type);\n };\n },\n\n sendTo(peer, type, payload) {\n const dc = peer.dc;\n if (!dc || dc.readyState !== 'open' || peer.status !== 'connected') {\n throw new MeshConnectionError(`Peer \"${peer.id}\" is not connected`, peer.id);\n }\n\n const envelope = { id: randomId(options.random), p: payload, t: type, ts: options.clock(), v: 1 as const };\n let text: string;\n try {\n text = JSON.stringify(envelope);\n } catch (cause) {\n throw new MeshPayloadError(`Failed to serialize message \"${type}\"`, { cause });\n }\n\n const bytes = utf8Bytes(text);\n if (bytes > options.maxMessageBytes) {\n throw new MeshPayloadError(`Message \"${type}\" is ${bytes} bytes (max ${options.maxMessageBytes})`);\n }\n\n try {\n dc.send(text);\n } catch (cause) {\n throw new MeshConnectionError(`Failed to send message \"${type}\"`, peer.id, { cause });\n }\n options.emitTap({ bytes, messageType: type, peerId: peer.id, type: 'message-sent' });\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"_node.cjs","names":["tapper","MeshDisposedError","MessageDedupe","utf8Bytes","toEnvelope","MeshError","MeshConnectionError","randomId","MeshPayloadError","require_errors","require__random","require__wire","_vielzeug_arsenal","tappers","disposalCtrl","core","event","next","handler","options","signal","record","handlers","peerId","peer","data","bytes","decoded","envelope","set","message","cause","type","dc","payload","text"],"sources":["../src/_node.ts"],"sourcesContent":["import { type RandomSource, tapper, utf8Bytes } from '@vielzeug/arsenal';\nimport { randomId } from './_random';\nimport { MessageDedupe, toEnvelope } from './_wire';\nimport { MeshConnectionError, MeshDisposedError, MeshError, MeshPayloadError } from './errors';\nimport type {\n MeshEvent,\n MeshInbound,\n MeshPeer,\n MeshPeerStatus,\n MeshStatus,\n RTCDataChannelLike,\n RTCPeerConnectionLike,\n Unsubscribe,\n} from './types';\n\ntype Tapper = (event: MeshEvent) => void;\n\n/** Shared lifecycle core: status, tappers, disposal. */\nexport interface NodeCore {\n readonly disposalCtrl: AbortController;\n disposed: boolean;\n emitTap(event: MeshEvent): void;\n ensureLive(): void;\n /** Emits the final status-change + dispose events, clears tappers, aborts. */\n markDisposed(): void;\n setStatus(next: MeshStatus): void;\n status: MeshStatus;\n tap(handler: Tapper, options?: { readonly signal?: AbortSignal }): Unsubscribe;\n}\n\nexport function createNodeCore(signal?: AbortSignal): NodeCore {\n const tappers = tapper<MeshEvent>();\n const disposalCtrl = new AbortController();\n\n const core: NodeCore = {\n disposalCtrl,\n disposed: false,\n\n emitTap(event) {\n tappers.emit(event);\n },\n\n ensureLive() {\n if (core.disposed) throw new MeshDisposedError();\n },\n\n markDisposed() {\n if (core.disposed) return;\n core.disposed = true;\n core.status = 'disposed';\n core.emitTap({ status: 'disposed', type: 'status-change' });\n core.emitTap({ type: 'dispose' });\n tappers.clear();\n disposalCtrl.abort();\n },\n\n setStatus(next) {\n if (core.disposed || core.status === next) return;\n core.status = next;\n core.emitTap({ status: next, type: 'status-change' });\n },\n status: 'idle',\n\n tap(handler, options) {\n if (core.disposed) return () => {};\n return tappers.tap(handler, options);\n },\n };\n\n if (signal) {\n if (signal.aborted) {\n core.markDisposed();\n } else {\n signal.addEventListener('abort', () => core.markDisposed(), { once: true });\n }\n }\n\n return core;\n}\n\n// ─── Peers ───────────────────────────────────────────────────────────────────\n\n/** Internal peer record; `publicPeer` exposes live status through getters. */\nexport interface PeerRecord {\n dc: RTCDataChannelLike | null;\n readonly dedupe: MessageDedupe;\n readonly id: string;\n readonly name: string | undefined;\n pc: RTCPeerConnectionLike | null;\n readonly publicPeer: MeshPeer;\n readonly role: 'host' | 'guest';\n status: MeshPeerStatus;\n}\n\nexport function createPeerRecord(id: string, name: string | undefined, role: 'host' | 'guest'): PeerRecord {\n const record: PeerRecord = {\n dc: null,\n dedupe: new MessageDedupe(),\n id,\n name,\n pc: null,\n publicPeer: {\n get id() {\n return record.id;\n },\n get name() {\n return record.name;\n },\n get role() {\n return record.role;\n },\n get status() {\n return record.status;\n },\n },\n role,\n status: 'connecting',\n };\n return record;\n}\n\n// ─── Messenger ───────────────────────────────────────────────────────────────\n\ntype InboundHandler = (message: MeshInbound<unknown>) => void;\n\nexport interface MessengerOptions {\n readonly clock: () => number;\n readonly emitTap: (event: MeshEvent) => void;\n readonly maxMessageBytes: number;\n readonly random?: RandomSource;\n}\n\nexport interface Messenger {\n clear(): void;\n /** Entry point for `dc` 'message' events. */\n handleInbound(peer: PeerRecord, data: unknown): void;\n on(type: string, handler: InboundHandler): Unsubscribe;\n /** Sends a typed envelope to a peer. Throws `MeshConnectionError`/`MeshPayloadError`. */\n sendTo(peer: PeerRecord, type: string, payload: unknown): void;\n}\n\nexport function createMessenger(options: MessengerOptions): Messenger {\n const handlers = new Map<string, Set<InboundHandler>>();\n\n function reject(peerId: string, reason: 'too-large' | 'duplicate' | 'malformed' | 'unknown-type'): void {\n options.emitTap({ peerId, reason, type: 'message-rejected' });\n }\n\n return {\n clear() {\n handlers.clear();\n },\n\n handleInbound(peer, data) {\n const peerId = peer.id;\n\n if (typeof data !== 'string') {\n reject(peerId, 'malformed');\n return;\n }\n\n const bytes = utf8Bytes(data);\n if (bytes > options.maxMessageBytes) {\n reject(peerId, 'too-large');\n return;\n }\n\n let decoded: unknown;\n try {\n decoded = JSON.parse(data);\n } catch {\n reject(peerId, 'malformed');\n return;\n }\n\n const envelope = toEnvelope(decoded);\n if (!envelope) {\n reject(peerId, 'malformed');\n return;\n }\n\n if (!peer.dedupe.check(envelope.id)) {\n reject(peerId, 'duplicate');\n return;\n }\n\n const set = handlers.get(envelope.t);\n if (!set || set.size === 0) {\n reject(peerId, 'unknown-type');\n return;\n }\n\n options.emitTap({ bytes, messageType: envelope.t, peerId, type: 'message-received' });\n const message: MeshInbound<unknown> = {\n messageId: envelope.id,\n payload: envelope.p,\n peerId,\n sentAt: envelope.ts,\n };\n for (const handler of set) {\n try {\n handler(message);\n } catch (cause) {\n options.emitTap({ error: new MeshError('Message handler threw', { cause }), type: 'error' });\n }\n }\n },\n\n on(type, handler) {\n let set = handlers.get(type);\n if (!set) {\n set = new Set();\n handlers.set(type, set);\n }\n set.add(handler);\n return () => {\n set.delete(handler);\n if (set.size === 0) handlers.delete(type);\n };\n },\n\n sendTo(peer, type, payload) {\n const dc = peer.dc;\n if (!dc || dc.readyState !== 'open' || peer.status !== 'connected') {\n throw new MeshConnectionError(`Peer \"${peer.id}\" is not connected`, peer.id);\n }\n\n const envelope = { id: randomId(options.random), p: payload, t: type, ts: options.clock(), v: 1 as const };\n let text: string;\n try {\n text = JSON.stringify(envelope);\n } catch (cause) {\n throw new MeshPayloadError(`Failed to serialize message \"${type}\"`, { cause });\n }\n\n const bytes = utf8Bytes(text);\n if (bytes > options.maxMessageBytes) {\n throw new MeshPayloadError(`Message \"${type}\" is ${bytes} bytes (max ${options.maxMessageBytes})`);\n }\n\n try {\n dc.send(text);\n } catch (cause) {\n throw new MeshConnectionError(`Failed to send message \"${type}\"`, peer.id, { cause });\n }\n options.emitTap({ bytes, messageType: type, peerId: peer.id, type: 'message-sent' });\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/_node.js
CHANGED
|
@@ -1,2 +1,167 @@
|
|
|
1
|
-
import{MeshConnectionError as e,MeshDisposedError as t,MeshError as n,MeshPayloadError as r}
|
|
1
|
+
import { MeshConnectionError as e, MeshDisposedError as t, MeshError as n, MeshPayloadError as r } from "./errors.js";
|
|
2
|
+
import { randomId as i } from "./_random.js";
|
|
3
|
+
import { MessageDedupe as a, toEnvelope as o } from "./_wire.js";
|
|
4
|
+
import { tapper as s, utf8Bytes as c } from "@vielzeug/arsenal";
|
|
5
|
+
//#region src/_node.ts
|
|
6
|
+
function createNodeCore(e) {
|
|
7
|
+
let n = s(), r = new AbortController(), i = {
|
|
8
|
+
disposalCtrl: r,
|
|
9
|
+
disposed: !1,
|
|
10
|
+
emitTap(e) {
|
|
11
|
+
n.emit(e);
|
|
12
|
+
},
|
|
13
|
+
ensureLive() {
|
|
14
|
+
if (i.disposed) throw new t();
|
|
15
|
+
},
|
|
16
|
+
markDisposed() {
|
|
17
|
+
i.disposed || (i.disposed = !0, i.status = "disposed", i.emitTap({
|
|
18
|
+
status: "disposed",
|
|
19
|
+
type: "status-change"
|
|
20
|
+
}), i.emitTap({ type: "dispose" }), n.clear(), r.abort());
|
|
21
|
+
},
|
|
22
|
+
setStatus(e) {
|
|
23
|
+
i.disposed || i.status === e || (i.status = e, i.emitTap({
|
|
24
|
+
status: e,
|
|
25
|
+
type: "status-change"
|
|
26
|
+
}));
|
|
27
|
+
},
|
|
28
|
+
status: "idle",
|
|
29
|
+
tap(e, t) {
|
|
30
|
+
return i.disposed ? () => {} : n.tap(e, t);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
return e && (e.aborted ? i.markDisposed() : e.addEventListener("abort", () => i.markDisposed(), { once: !0 })), i;
|
|
34
|
+
}
|
|
35
|
+
function createPeerRecord(e, t, n) {
|
|
36
|
+
let r = {
|
|
37
|
+
dc: null,
|
|
38
|
+
dedupe: new a(),
|
|
39
|
+
id: e,
|
|
40
|
+
name: t,
|
|
41
|
+
pc: null,
|
|
42
|
+
publicPeer: {
|
|
43
|
+
get id() {
|
|
44
|
+
return r.id;
|
|
45
|
+
},
|
|
46
|
+
get name() {
|
|
47
|
+
return r.name;
|
|
48
|
+
},
|
|
49
|
+
get role() {
|
|
50
|
+
return r.role;
|
|
51
|
+
},
|
|
52
|
+
get status() {
|
|
53
|
+
return r.status;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
role: n,
|
|
57
|
+
status: "connecting"
|
|
58
|
+
};
|
|
59
|
+
return r;
|
|
60
|
+
}
|
|
61
|
+
function createMessenger(t) {
|
|
62
|
+
let a = /* @__PURE__ */ new Map();
|
|
63
|
+
function reject(e, n) {
|
|
64
|
+
t.emitTap({
|
|
65
|
+
peerId: e,
|
|
66
|
+
reason: n,
|
|
67
|
+
type: "message-rejected"
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
clear() {
|
|
72
|
+
a.clear();
|
|
73
|
+
},
|
|
74
|
+
handleInbound(e, r) {
|
|
75
|
+
let i = e.id;
|
|
76
|
+
if (typeof r != "string") {
|
|
77
|
+
reject(i, "malformed");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
let s = c(r);
|
|
81
|
+
if (s > t.maxMessageBytes) {
|
|
82
|
+
reject(i, "too-large");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
let l;
|
|
86
|
+
try {
|
|
87
|
+
l = JSON.parse(r);
|
|
88
|
+
} catch {
|
|
89
|
+
reject(i, "malformed");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
let u = o(l);
|
|
93
|
+
if (!u) {
|
|
94
|
+
reject(i, "malformed");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (!e.dedupe.check(u.id)) {
|
|
98
|
+
reject(i, "duplicate");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
let d = a.get(u.t);
|
|
102
|
+
if (!d || d.size === 0) {
|
|
103
|
+
reject(i, "unknown-type");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
t.emitTap({
|
|
107
|
+
bytes: s,
|
|
108
|
+
messageType: u.t,
|
|
109
|
+
peerId: i,
|
|
110
|
+
type: "message-received"
|
|
111
|
+
});
|
|
112
|
+
let f = {
|
|
113
|
+
messageId: u.id,
|
|
114
|
+
payload: u.p,
|
|
115
|
+
peerId: i,
|
|
116
|
+
sentAt: u.ts
|
|
117
|
+
};
|
|
118
|
+
for (let e of d) try {
|
|
119
|
+
e(f);
|
|
120
|
+
} catch (e) {
|
|
121
|
+
t.emitTap({
|
|
122
|
+
error: new n("Message handler threw", { cause: e }),
|
|
123
|
+
type: "error"
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
on(e, t) {
|
|
128
|
+
let n = a.get(e);
|
|
129
|
+
return n || (n = /* @__PURE__ */ new Set(), a.set(e, n)), n.add(t), () => {
|
|
130
|
+
n.delete(t), n.size === 0 && a.delete(e);
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
sendTo(n, a, o) {
|
|
134
|
+
let s = n.dc;
|
|
135
|
+
if (!s || s.readyState !== "open" || n.status !== "connected") throw new e(`Peer "${n.id}" is not connected`, n.id);
|
|
136
|
+
let l = {
|
|
137
|
+
id: i(t.random),
|
|
138
|
+
p: o,
|
|
139
|
+
t: a,
|
|
140
|
+
ts: t.clock(),
|
|
141
|
+
v: 1
|
|
142
|
+
}, u;
|
|
143
|
+
try {
|
|
144
|
+
u = JSON.stringify(l);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
throw new r(`Failed to serialize message "${a}"`, { cause: e });
|
|
147
|
+
}
|
|
148
|
+
let d = c(u);
|
|
149
|
+
if (d > t.maxMessageBytes) throw new r(`Message "${a}" is ${d} bytes (max ${t.maxMessageBytes})`);
|
|
150
|
+
try {
|
|
151
|
+
s.send(u);
|
|
152
|
+
} catch (t) {
|
|
153
|
+
throw new e(`Failed to send message "${a}"`, n.id, { cause: t });
|
|
154
|
+
}
|
|
155
|
+
t.emitTap({
|
|
156
|
+
bytes: d,
|
|
157
|
+
messageType: a,
|
|
158
|
+
peerId: n.id,
|
|
159
|
+
type: "message-sent"
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
export { createMessenger, createNodeCore, createPeerRecord };
|
|
166
|
+
|
|
2
167
|
//# sourceMappingURL=_node.js.map
|
package/dist/_node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_node.js","names":[],"sources":["../src/_node.ts"],"sourcesContent":["import { type RandomSource, tapper, utf8Bytes } from '@vielzeug/arsenal';\nimport { randomId } from './_random';\nimport { MessageDedupe, toEnvelope } from './_wire';\nimport { MeshConnectionError, MeshDisposedError, MeshError, MeshPayloadError } from './errors';\nimport type {\n MeshEvent,\n MeshInbound,\n MeshPeer,\n MeshPeerStatus,\n MeshStatus,\n RTCDataChannelLike,\n RTCPeerConnectionLike,\n Unsubscribe,\n} from './types';\n\ntype Tapper = (event: MeshEvent) => void;\n\n/** Shared lifecycle core: status, tappers, disposal. */\nexport interface NodeCore {\n readonly disposalCtrl: AbortController;\n disposed: boolean;\n emitTap(event: MeshEvent): void;\n ensureLive(): void;\n /** Emits the final status-change + dispose events, clears tappers, aborts. */\n markDisposed(): void;\n setStatus(next: MeshStatus): void;\n status: MeshStatus;\n tap(handler: Tapper, options?: { readonly signal?: AbortSignal }): Unsubscribe;\n}\n\nexport function createNodeCore(signal?: AbortSignal): NodeCore {\n const tappers = tapper<MeshEvent>();\n const disposalCtrl = new AbortController();\n\n const core: NodeCore = {\n disposalCtrl,\n disposed: false,\n\n emitTap(event) {\n tappers.emit(event);\n },\n\n ensureLive() {\n if (core.disposed) throw new MeshDisposedError();\n },\n\n markDisposed() {\n if (core.disposed) return;\n core.disposed = true;\n core.status = 'disposed';\n core.emitTap({ status: 'disposed', type: 'status-change' });\n core.emitTap({ type: 'dispose' });\n tappers.clear();\n disposalCtrl.abort();\n },\n\n setStatus(next) {\n if (core.disposed || core.status === next) return;\n core.status = next;\n core.emitTap({ status: next, type: 'status-change' });\n },\n status: 'idle',\n\n tap(handler, options) {\n if (core.disposed) return () => {};\n return tappers.tap(handler, options);\n },\n };\n\n if (signal) {\n if (signal.aborted) {\n core.markDisposed();\n } else {\n signal.addEventListener('abort', () => core.markDisposed(), { once: true });\n }\n }\n\n return core;\n}\n\n// ─── Peers ───────────────────────────────────────────────────────────────────\n\n/** Internal peer record; `publicPeer` exposes live status through getters. */\nexport interface PeerRecord {\n dc: RTCDataChannelLike | null;\n readonly dedupe: MessageDedupe;\n readonly id: string;\n readonly name: string | undefined;\n pc: RTCPeerConnectionLike | null;\n readonly publicPeer: MeshPeer;\n readonly role: 'host' | 'guest';\n status: MeshPeerStatus;\n}\n\nexport function createPeerRecord(id: string, name: string | undefined, role: 'host' | 'guest'): PeerRecord {\n const record: PeerRecord = {\n dc: null,\n dedupe: new MessageDedupe(),\n id,\n name,\n pc: null,\n publicPeer: {\n get id() {\n return record.id;\n },\n get name() {\n return record.name;\n },\n get role() {\n return record.role;\n },\n get status() {\n return record.status;\n },\n },\n role,\n status: 'connecting',\n };\n return record;\n}\n\n// ─── Messenger ───────────────────────────────────────────────────────────────\n\ntype InboundHandler = (message: MeshInbound<unknown>) => void;\n\nexport interface MessengerOptions {\n readonly clock: () => number;\n readonly emitTap: (event: MeshEvent) => void;\n readonly maxMessageBytes: number;\n readonly random?: RandomSource;\n}\n\nexport interface Messenger {\n clear(): void;\n /** Entry point for `dc` 'message' events. */\n handleInbound(peer: PeerRecord, data: unknown): void;\n on(type: string, handler: InboundHandler): Unsubscribe;\n /** Sends a typed envelope to a peer. Throws `MeshConnectionError`/`MeshPayloadError`. */\n sendTo(peer: PeerRecord, type: string, payload: unknown): void;\n}\n\nexport function createMessenger(options: MessengerOptions): Messenger {\n const handlers = new Map<string, Set<InboundHandler>>();\n\n function reject(peerId: string, reason: 'too-large' | 'duplicate' | 'malformed' | 'unknown-type'): void {\n options.emitTap({ peerId, reason, type: 'message-rejected' });\n }\n\n return {\n clear() {\n handlers.clear();\n },\n\n handleInbound(peer, data) {\n const peerId = peer.id;\n\n if (typeof data !== 'string') {\n reject(peerId, 'malformed');\n return;\n }\n\n const bytes = utf8Bytes(data);\n if (bytes > options.maxMessageBytes) {\n reject(peerId, 'too-large');\n return;\n }\n\n let decoded: unknown;\n try {\n decoded = JSON.parse(data);\n } catch {\n reject(peerId, 'malformed');\n return;\n }\n\n const envelope = toEnvelope(decoded);\n if (!envelope) {\n reject(peerId, 'malformed');\n return;\n }\n\n if (!peer.dedupe.check(envelope.id)) {\n reject(peerId, 'duplicate');\n return;\n }\n\n const set = handlers.get(envelope.t);\n if (!set || set.size === 0) {\n reject(peerId, 'unknown-type');\n return;\n }\n\n options.emitTap({ bytes, messageType: envelope.t, peerId, type: 'message-received' });\n const message: MeshInbound<unknown> = {\n messageId: envelope.id,\n payload: envelope.p,\n peerId,\n sentAt: envelope.ts,\n };\n for (const handler of set) {\n try {\n handler(message);\n } catch (cause) {\n options.emitTap({ error: new MeshError('Message handler threw', { cause }), type: 'error' });\n }\n }\n },\n\n on(type, handler) {\n let set = handlers.get(type);\n if (!set) {\n set = new Set();\n handlers.set(type, set);\n }\n set.add(handler);\n return () => {\n set.delete(handler);\n if (set.size === 0) handlers.delete(type);\n };\n },\n\n sendTo(peer, type, payload) {\n const dc = peer.dc;\n if (!dc || dc.readyState !== 'open' || peer.status !== 'connected') {\n throw new MeshConnectionError(`Peer \"${peer.id}\" is not connected`, peer.id);\n }\n\n const envelope = { id: randomId(options.random), p: payload, t: type, ts: options.clock(), v: 1 as const };\n let text: string;\n try {\n text = JSON.stringify(envelope);\n } catch (cause) {\n throw new MeshPayloadError(`Failed to serialize message \"${type}\"`, { cause });\n }\n\n const bytes = utf8Bytes(text);\n if (bytes > options.maxMessageBytes) {\n throw new MeshPayloadError(`Message \"${type}\" is ${bytes} bytes (max ${options.maxMessageBytes})`);\n }\n\n try {\n dc.send(text);\n } catch (cause) {\n throw new MeshConnectionError(`Failed to send message \"${type}\"`, peer.id, { cause });\n }\n options.emitTap({ bytes, messageType: type, peerId: peer.id, type: 'message-sent' });\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"_node.js","names":["tappers","tapper","disposalCtrl","core","event","MeshDisposedError","next","handler","options","signal","record","MessageDedupe","handlers","peerId","peer","data","bytes","utf8Bytes","decoded","envelope","toEnvelope","set","message","cause","MeshError","type","dc","MeshConnectionError","randomId","payload","text","MeshPayloadError"],"sources":["../src/_node.ts"],"sourcesContent":["import { type RandomSource, tapper, utf8Bytes } from '@vielzeug/arsenal';\nimport { randomId } from './_random';\nimport { MessageDedupe, toEnvelope } from './_wire';\nimport { MeshConnectionError, MeshDisposedError, MeshError, MeshPayloadError } from './errors';\nimport type {\n MeshEvent,\n MeshInbound,\n MeshPeer,\n MeshPeerStatus,\n MeshStatus,\n RTCDataChannelLike,\n RTCPeerConnectionLike,\n Unsubscribe,\n} from './types';\n\ntype Tapper = (event: MeshEvent) => void;\n\n/** Shared lifecycle core: status, tappers, disposal. */\nexport interface NodeCore {\n readonly disposalCtrl: AbortController;\n disposed: boolean;\n emitTap(event: MeshEvent): void;\n ensureLive(): void;\n /** Emits the final status-change + dispose events, clears tappers, aborts. */\n markDisposed(): void;\n setStatus(next: MeshStatus): void;\n status: MeshStatus;\n tap(handler: Tapper, options?: { readonly signal?: AbortSignal }): Unsubscribe;\n}\n\nexport function createNodeCore(signal?: AbortSignal): NodeCore {\n const tappers = tapper<MeshEvent>();\n const disposalCtrl = new AbortController();\n\n const core: NodeCore = {\n disposalCtrl,\n disposed: false,\n\n emitTap(event) {\n tappers.emit(event);\n },\n\n ensureLive() {\n if (core.disposed) throw new MeshDisposedError();\n },\n\n markDisposed() {\n if (core.disposed) return;\n core.disposed = true;\n core.status = 'disposed';\n core.emitTap({ status: 'disposed', type: 'status-change' });\n core.emitTap({ type: 'dispose' });\n tappers.clear();\n disposalCtrl.abort();\n },\n\n setStatus(next) {\n if (core.disposed || core.status === next) return;\n core.status = next;\n core.emitTap({ status: next, type: 'status-change' });\n },\n status: 'idle',\n\n tap(handler, options) {\n if (core.disposed) return () => {};\n return tappers.tap(handler, options);\n },\n };\n\n if (signal) {\n if (signal.aborted) {\n core.markDisposed();\n } else {\n signal.addEventListener('abort', () => core.markDisposed(), { once: true });\n }\n }\n\n return core;\n}\n\n// ─── Peers ───────────────────────────────────────────────────────────────────\n\n/** Internal peer record; `publicPeer` exposes live status through getters. */\nexport interface PeerRecord {\n dc: RTCDataChannelLike | null;\n readonly dedupe: MessageDedupe;\n readonly id: string;\n readonly name: string | undefined;\n pc: RTCPeerConnectionLike | null;\n readonly publicPeer: MeshPeer;\n readonly role: 'host' | 'guest';\n status: MeshPeerStatus;\n}\n\nexport function createPeerRecord(id: string, name: string | undefined, role: 'host' | 'guest'): PeerRecord {\n const record: PeerRecord = {\n dc: null,\n dedupe: new MessageDedupe(),\n id,\n name,\n pc: null,\n publicPeer: {\n get id() {\n return record.id;\n },\n get name() {\n return record.name;\n },\n get role() {\n return record.role;\n },\n get status() {\n return record.status;\n },\n },\n role,\n status: 'connecting',\n };\n return record;\n}\n\n// ─── Messenger ───────────────────────────────────────────────────────────────\n\ntype InboundHandler = (message: MeshInbound<unknown>) => void;\n\nexport interface MessengerOptions {\n readonly clock: () => number;\n readonly emitTap: (event: MeshEvent) => void;\n readonly maxMessageBytes: number;\n readonly random?: RandomSource;\n}\n\nexport interface Messenger {\n clear(): void;\n /** Entry point for `dc` 'message' events. */\n handleInbound(peer: PeerRecord, data: unknown): void;\n on(type: string, handler: InboundHandler): Unsubscribe;\n /** Sends a typed envelope to a peer. Throws `MeshConnectionError`/`MeshPayloadError`. */\n sendTo(peer: PeerRecord, type: string, payload: unknown): void;\n}\n\nexport function createMessenger(options: MessengerOptions): Messenger {\n const handlers = new Map<string, Set<InboundHandler>>();\n\n function reject(peerId: string, reason: 'too-large' | 'duplicate' | 'malformed' | 'unknown-type'): void {\n options.emitTap({ peerId, reason, type: 'message-rejected' });\n }\n\n return {\n clear() {\n handlers.clear();\n },\n\n handleInbound(peer, data) {\n const peerId = peer.id;\n\n if (typeof data !== 'string') {\n reject(peerId, 'malformed');\n return;\n }\n\n const bytes = utf8Bytes(data);\n if (bytes > options.maxMessageBytes) {\n reject(peerId, 'too-large');\n return;\n }\n\n let decoded: unknown;\n try {\n decoded = JSON.parse(data);\n } catch {\n reject(peerId, 'malformed');\n return;\n }\n\n const envelope = toEnvelope(decoded);\n if (!envelope) {\n reject(peerId, 'malformed');\n return;\n }\n\n if (!peer.dedupe.check(envelope.id)) {\n reject(peerId, 'duplicate');\n return;\n }\n\n const set = handlers.get(envelope.t);\n if (!set || set.size === 0) {\n reject(peerId, 'unknown-type');\n return;\n }\n\n options.emitTap({ bytes, messageType: envelope.t, peerId, type: 'message-received' });\n const message: MeshInbound<unknown> = {\n messageId: envelope.id,\n payload: envelope.p,\n peerId,\n sentAt: envelope.ts,\n };\n for (const handler of set) {\n try {\n handler(message);\n } catch (cause) {\n options.emitTap({ error: new MeshError('Message handler threw', { cause }), type: 'error' });\n }\n }\n },\n\n on(type, handler) {\n let set = handlers.get(type);\n if (!set) {\n set = new Set();\n handlers.set(type, set);\n }\n set.add(handler);\n return () => {\n set.delete(handler);\n if (set.size === 0) handlers.delete(type);\n };\n },\n\n sendTo(peer, type, payload) {\n const dc = peer.dc;\n if (!dc || dc.readyState !== 'open' || peer.status !== 'connected') {\n throw new MeshConnectionError(`Peer \"${peer.id}\" is not connected`, peer.id);\n }\n\n const envelope = { id: randomId(options.random), p: payload, t: type, ts: options.clock(), v: 1 as const };\n let text: string;\n try {\n text = JSON.stringify(envelope);\n } catch (cause) {\n throw new MeshPayloadError(`Failed to serialize message \"${type}\"`, { cause });\n }\n\n const bytes = utf8Bytes(text);\n if (bytes > options.maxMessageBytes) {\n throw new MeshPayloadError(`Message \"${type}\" is ${bytes} bytes (max ${options.maxMessageBytes})`);\n }\n\n try {\n dc.send(text);\n } catch (cause) {\n throw new MeshConnectionError(`Failed to send message \"${type}\"`, peer.id, { cause });\n }\n options.emitTap({ bytes, messageType: type, peerId: peer.id, type: 'message-sent' });\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/_proof.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_proof.cjs","names":[],"sources":["../src/_proof.ts"],"sourcesContent":["import { bytesToBase64Url } from '@vielzeug/arsenal';\nimport { canonicalSdp } from './_sdp';\nimport { MeshUnsupportedError } from './errors';\n\n/**\n * Proof of invitation possession: HMAC-SHA-256 of the guest SDP keyed by the\n * invitation secret. The SDP is canonicalized first so the proof is stable\n * across pairing codecs that rebuild the description (see `mq2.`). The host\n * recomputes the proof without receiving the secret back.\n *\n * WebCrypto is required: a non-cryptographic fallback would make the proof\n * forgeable, which defeats its purpose. Environments without `SubtleCrypto`\n * (insecure contexts) must pair over `https://` or `localhost` instead.\n *\n * @throws {MeshUnsupportedError} When `SubtleCrypto` is unavailable.\n */\nexport async function createProof(secret: string, sdp: string): Promise<string> {\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) {\n throw new MeshUnsupportedError('WebCrypto is unavailable: pairing requires a secure context');\n }\n const encoder = new TextEncoder();\n const key = await subtle.importKey('raw', encoder.encode(secret), { hash: 'SHA-256', name: 'HMAC' }, false, ['sign']);\n const signature = await subtle.sign('HMAC', key, encoder.encode(canonicalSdp(sdp)));\n return bytesToBase64Url(new Uint8Array(signature));\n}\n"],"mappings":"2FAgBA,eAAsB,YAAY,EAAgB,EAA8B,CAC9E,
|
|
1
|
+
{"version":3,"file":"_proof.cjs","names":["MeshUnsupportedError","canonicalSdp","bytesToBase64Url","require_errors","require__sdp","_vielzeug_arsenal","subtle","encoder","key","secret","signature","sdp"],"sources":["../src/_proof.ts"],"sourcesContent":["import { bytesToBase64Url } from '@vielzeug/arsenal';\nimport { canonicalSdp } from './_sdp';\nimport { MeshUnsupportedError } from './errors';\n\n/**\n * Proof of invitation possession: HMAC-SHA-256 of the guest SDP keyed by the\n * invitation secret. The SDP is canonicalized first so the proof is stable\n * across pairing codecs that rebuild the description (see `mq2.`). The host\n * recomputes the proof without receiving the secret back.\n *\n * WebCrypto is required: a non-cryptographic fallback would make the proof\n * forgeable, which defeats its purpose. Environments without `SubtleCrypto`\n * (insecure contexts) must pair over `https://` or `localhost` instead.\n *\n * @throws {MeshUnsupportedError} When `SubtleCrypto` is unavailable.\n */\nexport async function createProof(secret: string, sdp: string): Promise<string> {\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) {\n throw new MeshUnsupportedError('WebCrypto is unavailable: pairing requires a secure context');\n }\n const encoder = new TextEncoder();\n const key = await subtle.importKey('raw', encoder.encode(secret), { hash: 'SHA-256', name: 'HMAC' }, false, ['sign']);\n const signature = await subtle.sign('HMAC', key, encoder.encode(canonicalSdp(sdp)));\n return bytesToBase64Url(new Uint8Array(signature));\n}\n"],"mappings":"A,2FAgBA,eAAsB,YAAY,EAAgB,EAA8B,CAC9E,IAAMM,EAAS,WAAW,QAAQ,OAClC,GAAI,CAACA,EACH,MAAM,IAAIN,EAAAA,qBAAqB,6DAA6D,EAE9F,IAAMO,EAAU,IAAI,YACdC,EAAM,MAAMF,EAAO,UAAU,MAAOC,EAAQ,OAAOE,CAAM,EAAG,CAAE,KAAM,UAAW,KAAM,MAAO,EAAG,GAAO,CAAC,MAAM,CAAC,EAC9GC,EAAY,MAAMJ,EAAO,KAAK,OAAQE,EAAKD,EAAQ,OAAON,EAAAA,aAAaU,CAAG,CAAC,CAAC,EAClF,OAAA,EAAOT,EAAAA,iBAAAA,CAAiB,IAAI,WAAWQ,CAAS,CAAC,CACnD,C"}
|
package/dist/_proof.js
CHANGED
|
@@ -1,2 +1,17 @@
|
|
|
1
|
-
import{MeshUnsupportedError as e}from"./errors.js";
|
|
1
|
+
import { MeshUnsupportedError as e } from "./errors.js";
|
|
2
|
+
import { canonicalSdp as t } from "./_sdp.js";
|
|
3
|
+
import { bytesToBase64Url as n } from "@vielzeug/arsenal";
|
|
4
|
+
//#region src/_proof.ts
|
|
5
|
+
async function createProof(r, i) {
|
|
6
|
+
let a = globalThis.crypto?.subtle;
|
|
7
|
+
if (!a) throw new e("WebCrypto is unavailable: pairing requires a secure context");
|
|
8
|
+
let o = new TextEncoder(), s = await a.importKey("raw", o.encode(r), {
|
|
9
|
+
hash: "SHA-256",
|
|
10
|
+
name: "HMAC"
|
|
11
|
+
}, !1, ["sign"]), c = await a.sign("HMAC", s, o.encode(t(i)));
|
|
12
|
+
return n(new Uint8Array(c));
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { createProof };
|
|
16
|
+
|
|
2
17
|
//# sourceMappingURL=_proof.js.map
|
package/dist/_proof.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_proof.js","names":[],"sources":["../src/_proof.ts"],"sourcesContent":["import { bytesToBase64Url } from '@vielzeug/arsenal';\nimport { canonicalSdp } from './_sdp';\nimport { MeshUnsupportedError } from './errors';\n\n/**\n * Proof of invitation possession: HMAC-SHA-256 of the guest SDP keyed by the\n * invitation secret. The SDP is canonicalized first so the proof is stable\n * across pairing codecs that rebuild the description (see `mq2.`). The host\n * recomputes the proof without receiving the secret back.\n *\n * WebCrypto is required: a non-cryptographic fallback would make the proof\n * forgeable, which defeats its purpose. Environments without `SubtleCrypto`\n * (insecure contexts) must pair over `https://` or `localhost` instead.\n *\n * @throws {MeshUnsupportedError} When `SubtleCrypto` is unavailable.\n */\nexport async function createProof(secret: string, sdp: string): Promise<string> {\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) {\n throw new MeshUnsupportedError('WebCrypto is unavailable: pairing requires a secure context');\n }\n const encoder = new TextEncoder();\n const key = await subtle.importKey('raw', encoder.encode(secret), { hash: 'SHA-256', name: 'HMAC' }, false, ['sign']);\n const signature = await subtle.sign('HMAC', key, encoder.encode(canonicalSdp(sdp)));\n return bytesToBase64Url(new Uint8Array(signature));\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"_proof.js","names":["subtle","MeshUnsupportedError","encoder","key","secret","signature","canonicalSdp","sdp","bytesToBase64Url"],"sources":["../src/_proof.ts"],"sourcesContent":["import { bytesToBase64Url } from '@vielzeug/arsenal';\nimport { canonicalSdp } from './_sdp';\nimport { MeshUnsupportedError } from './errors';\n\n/**\n * Proof of invitation possession: HMAC-SHA-256 of the guest SDP keyed by the\n * invitation secret. The SDP is canonicalized first so the proof is stable\n * across pairing codecs that rebuild the description (see `mq2.`). The host\n * recomputes the proof without receiving the secret back.\n *\n * WebCrypto is required: a non-cryptographic fallback would make the proof\n * forgeable, which defeats its purpose. Environments without `SubtleCrypto`\n * (insecure contexts) must pair over `https://` or `localhost` instead.\n *\n * @throws {MeshUnsupportedError} When `SubtleCrypto` is unavailable.\n */\nexport async function createProof(secret: string, sdp: string): Promise<string> {\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) {\n throw new MeshUnsupportedError('WebCrypto is unavailable: pairing requires a secure context');\n }\n const encoder = new TextEncoder();\n const key = await subtle.importKey('raw', encoder.encode(secret), { hash: 'SHA-256', name: 'HMAC' }, false, ['sign']);\n const signature = await subtle.sign('HMAC', key, encoder.encode(canonicalSdp(sdp)));\n return bytesToBase64Url(new Uint8Array(signature));\n}\n"],"mappings":";;;;AAgBA,eAAsB,YAAY,GAAgB,GAA8B;CAC9E,IAAMA,IAAS,WAAW,QAAQ;CAClC,IAAI,CAACA,GACH,MAAM,IAAIC,EAAqB,6DAA6D;CAE9F,IAAMC,IAAU,IAAI,YAAY,GAC1BC,IAAM,MAAMH,EAAO,UAAU,OAAOE,EAAQ,OAAOE,CAAM,GAAG;EAAE,MAAM;EAAW,MAAM;CAAO,GAAG,IAAO,CAAC,MAAM,CAAC,GAC9GC,IAAY,MAAML,EAAO,KAAK,QAAQG,GAAKD,EAAQ,OAAOI,EAAaC,CAAG,CAAC,CAAC;CAClF,OAAOC,EAAiB,IAAI,WAAWH,CAAS,CAAC;AACnD"}
|
package/dist/_random.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_random.cjs","names":[],"sources":["../src/_random.ts"],"sourcesContent":["import { bytesToBase64Url, type RandomSource, randomFloat } from '@vielzeug/arsenal';\n\n/**\n * Random bytes from `crypto.getRandomValues`, or from an injected\n * `RandomSource` for deterministic tests.\n */\nexport function randomBytes(length: number, source?: RandomSource): Uint8Array {\n const bytes = new Uint8Array(length);\n if (source) {\n for (let index = 0; index < length; index++) bytes[index] = Math.floor(randomFloat(source) * 256);\n } else {\n crypto.getRandomValues(bytes);\n }\n return bytes;\n}\n\n/** Random 128-bit base64url id: used for peer ids, session ids, and message ids. */\nexport function randomId(source?: RandomSource): string {\n return bytesToBase64Url(randomBytes(16, source));\n}\n"],"mappings":"mCAMA,SAAgB,YAAY,EAAgB,EAAmC,CAC7E,
|
|
1
|
+
{"version":3,"file":"_random.cjs","names":["randomFloat","bytesToBase64Url","_vielzeug_arsenal","bytes","length","source","index"],"sources":["../src/_random.ts"],"sourcesContent":["import { bytesToBase64Url, type RandomSource, randomFloat } from '@vielzeug/arsenal';\n\n/**\n * Random bytes from `crypto.getRandomValues`, or from an injected\n * `RandomSource` for deterministic tests.\n */\nexport function randomBytes(length: number, source?: RandomSource): Uint8Array {\n const bytes = new Uint8Array(length);\n if (source) {\n for (let index = 0; index < length; index++) bytes[index] = Math.floor(randomFloat(source) * 256);\n } else {\n crypto.getRandomValues(bytes);\n }\n return bytes;\n}\n\n/** Random 128-bit base64url id: used for peer ids, session ids, and message ids. */\nexport function randomId(source?: RandomSource): string {\n return bytesToBase64Url(randomBytes(16, source));\n}\n"],"mappings":"A,mCAMA,SAAgB,YAAY,EAAgB,EAAmC,CAC7E,IAAMG,EAAQ,IAAI,WAAWC,CAAM,EACnC,GAAIC,EACF,IAAK,IAAIC,EAAQ,EAAGA,EAAQF,EAAQ,IAAS,EAAME,GAAS,KAAK,OAAA,EAAMN,EAAAA,YAAAA,CAAYK,CAAM,EAAI,GAAG,OAEhG,OAAO,gBAAgBF,CAAK,EAE9B,OAAOA,CACT,CAGA,SAAgB,SAAS,EAA+B,CACtD,OAAA,EAAOF,EAAAA,iBAAAA,CAAiB,YAAY,GAAII,CAAM,CAAC,CACjD,C"}
|
package/dist/_random.js
CHANGED
|
@@ -1,2 +1,15 @@
|
|
|
1
|
-
import{bytesToBase64Url as e,randomFloat as t}from"@vielzeug/arsenal";
|
|
1
|
+
import { bytesToBase64Url as e, randomFloat as t } from "@vielzeug/arsenal";
|
|
2
|
+
//#region src/_random.ts
|
|
3
|
+
function randomBytes(e, n) {
|
|
4
|
+
let r = new Uint8Array(e);
|
|
5
|
+
if (n) for (let i = 0; i < e; i++) r[i] = Math.floor(t(n) * 256);
|
|
6
|
+
else crypto.getRandomValues(r);
|
|
7
|
+
return r;
|
|
8
|
+
}
|
|
9
|
+
function randomId(t) {
|
|
10
|
+
return e(randomBytes(16, t));
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { randomBytes, randomId };
|
|
14
|
+
|
|
2
15
|
//# sourceMappingURL=_random.js.map
|
package/dist/_random.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_random.js","names":[],"sources":["../src/_random.ts"],"sourcesContent":["import { bytesToBase64Url, type RandomSource, randomFloat } from '@vielzeug/arsenal';\n\n/**\n * Random bytes from `crypto.getRandomValues`, or from an injected\n * `RandomSource` for deterministic tests.\n */\nexport function randomBytes(length: number, source?: RandomSource): Uint8Array {\n const bytes = new Uint8Array(length);\n if (source) {\n for (let index = 0; index < length; index++) bytes[index] = Math.floor(randomFloat(source) * 256);\n } else {\n crypto.getRandomValues(bytes);\n }\n return bytes;\n}\n\n/** Random 128-bit base64url id: used for peer ids, session ids, and message ids. */\nexport function randomId(source?: RandomSource): string {\n return bytesToBase64Url(randomBytes(16, source));\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"_random.js","names":["bytes","length","source","index","randomFloat","bytesToBase64Url"],"sources":["../src/_random.ts"],"sourcesContent":["import { bytesToBase64Url, type RandomSource, randomFloat } from '@vielzeug/arsenal';\n\n/**\n * Random bytes from `crypto.getRandomValues`, or from an injected\n * `RandomSource` for deterministic tests.\n */\nexport function randomBytes(length: number, source?: RandomSource): Uint8Array {\n const bytes = new Uint8Array(length);\n if (source) {\n for (let index = 0; index < length; index++) bytes[index] = Math.floor(randomFloat(source) * 256);\n } else {\n crypto.getRandomValues(bytes);\n }\n return bytes;\n}\n\n/** Random 128-bit base64url id: used for peer ids, session ids, and message ids. */\nexport function randomId(source?: RandomSource): string {\n return bytesToBase64Url(randomBytes(16, source));\n}\n"],"mappings":";;AAMA,SAAgB,YAAY,GAAgB,GAAmC;CAC7E,IAAMA,IAAQ,IAAI,WAAWC,CAAM;CACnC,IAAIC,GACF,KAAK,IAAIC,IAAQ,GAAGA,IAAQF,GAAQ,KAAS,EAAME,KAAS,KAAK,MAAMC,EAAYF,CAAM,IAAI,GAAG;MAEhG,OAAO,gBAAgBF,CAAK;CAE9B,OAAOA;AACT;AAGA,SAAgB,SAAS,GAA+B;CACtD,OAAOK,EAAiB,YAAY,IAAIH,CAAM,CAAC;AACjD"}
|
package/dist/_rtc.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_rtc.cjs","names":[],"sources":["../src/_rtc.ts"],"sourcesContent":["import { MeshConnectionError, MeshDisposedError, MeshTimeoutError, MeshUnsupportedError } from './errors';\nimport type { MeshRtcFactory, RTCDataChannelLike, RTCPeerConnectionLike } from './types';\n\n/**\n * Wraps `globalThis.RTCPeerConnection`. Evaluated lazily at first use so\n * importing the package never throws in non-WebRTC environments.\n */\nexport const nativeRtcFactory: MeshRtcFactory = {\n createPeerConnection(config) {\n const Ctor = (globalThis as { RTCPeerConnection?: typeof RTCPeerConnection }).RTCPeerConnection;\n if (!Ctor) {\n throw new MeshUnsupportedError('WebRTC is unavailable in this environment: inject an `rtc` factory');\n }\n return new Ctor(config);\n },\n};\n\n/**\n * Textareas, clipboards, and share sheets normalize CRLF→LF;\n * `setRemoteDescription` requires CRLF line endings.\n */\nexport function normalizeSdp(sdp: string): string {\n const normalized = sdp.replace(/\\r?\\n/g, '\\r\\n');\n return normalized.endsWith('\\r\\n') ? normalized : `${normalized}\\r\\n`;\n}\n\n/**\n * Resolves when ICE gathering finishes or after `timeoutMs`: the caller\n * proceeds with whatever candidates were gathered (non-trickle semantics).\n */\nexport function waitIceGathering(pc: RTCPeerConnectionLike, timeoutMs: number): Promise<void> {\n if (pc.iceGatheringState === 'complete') return Promise.resolve();\n return new Promise((resolve) => {\n const cleanup = () => {\n clearTimeout(timer);\n pc.removeEventListener('icegatheringstatechange', onChange);\n };\n const onChange = () => {\n if (pc.iceGatheringState === 'complete') {\n cleanup();\n resolve();\n }\n };\n const timer = setTimeout(() => {\n cleanup();\n resolve();\n }, timeoutMs);\n pc.addEventListener('icegatheringstatechange', onChange);\n });\n}\n\n/**\n * Resolves when the data channel opens. Rejects with `MeshTimeoutError` after\n * `timeoutMs`, or when the channel closes while that deadline is still\n * pending, since a peer tearing down a never-opened channel races the same\n * deadline: `MeshConnectionError` when the ICE transport dies first, and\n * `MeshDisposedError` when `disposalSignal` aborts.\n */\nexport function waitChannelOpen(\n dc: RTCDataChannelLike,\n pc: RTCPeerConnectionLike,\n timeoutMs: number,\n peerId: string,\n disposalSignal: AbortSignal,\n): Promise<void> {\n if (dc.readyState === 'open') return Promise.resolve();\n return new Promise((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | null = null;\n const timeout = () => new MeshTimeoutError(`Timed out waiting ${timeoutMs}ms for the data channel to open`);\n const fail = (error: Error) => {\n cleanup();\n reject(error);\n };\n const cleanup = () => {\n if (timer) clearTimeout(timer);\n timer = null;\n dc.removeEventListener('open', onOpen);\n dc.removeEventListener('close', onClose);\n pc.removeEventListener('iceconnectionstatechange', onIce);\n disposalSignal.removeEventListener('abort', onDispose);\n };\n const onOpen = () => {\n cleanup();\n resolve();\n };\n // A close while our open deadline is still armed is the timeout it\n // effectively is; mirror the guest's open-timer guard so both ends agree\n // on the cause instead of racing to report whichever event landed first.\n const onClose = () => fail(timeout());\n const onIce = () => {\n const state = pc.iceConnectionState;\n if (state === 'failed' || state === 'closed' || state === 'disconnected') {\n fail(new MeshConnectionError(`ICE connection ${state} before channel open`, peerId));\n }\n };\n const onDispose = () => fail(new MeshDisposedError());\n timer = setTimeout(() => fail(timeout()), timeoutMs);\n dc.addEventListener('open', onOpen);\n dc.addEventListener('close', onClose);\n pc.addEventListener('iceconnectionstatechange', onIce);\n disposalSignal.addEventListener('abort', onDispose, { once: true });\n });\n}\n"],"mappings":"gCAOA,
|
|
1
|
+
{"version":3,"file":"_rtc.cjs","names":["MeshUnsupportedError","MeshTimeoutError","MeshConnectionError","MeshDisposedError","require_errors","nativeRtcFactory","Ctor","config","normalized","sdp","pc","timer","resolve","timeoutMs","dc","reject","error","disposalSignal","state","peerId"],"sources":["../src/_rtc.ts"],"sourcesContent":["import { MeshConnectionError, MeshDisposedError, MeshTimeoutError, MeshUnsupportedError } from './errors';\nimport type { MeshRtcFactory, RTCDataChannelLike, RTCPeerConnectionLike } from './types';\n\n/**\n * Wraps `globalThis.RTCPeerConnection`. Evaluated lazily at first use so\n * importing the package never throws in non-WebRTC environments.\n */\nexport const nativeRtcFactory: MeshRtcFactory = {\n createPeerConnection(config) {\n const Ctor = (globalThis as { RTCPeerConnection?: typeof RTCPeerConnection }).RTCPeerConnection;\n if (!Ctor) {\n throw new MeshUnsupportedError('WebRTC is unavailable in this environment: inject an `rtc` factory');\n }\n return new Ctor(config);\n },\n};\n\n/**\n * Textareas, clipboards, and share sheets normalize CRLF→LF;\n * `setRemoteDescription` requires CRLF line endings.\n */\nexport function normalizeSdp(sdp: string): string {\n const normalized = sdp.replace(/\\r?\\n/g, '\\r\\n');\n return normalized.endsWith('\\r\\n') ? normalized : `${normalized}\\r\\n`;\n}\n\n/**\n * Resolves when ICE gathering finishes or after `timeoutMs`: the caller\n * proceeds with whatever candidates were gathered (non-trickle semantics).\n */\nexport function waitIceGathering(pc: RTCPeerConnectionLike, timeoutMs: number): Promise<void> {\n if (pc.iceGatheringState === 'complete') return Promise.resolve();\n return new Promise((resolve) => {\n const cleanup = () => {\n clearTimeout(timer);\n pc.removeEventListener('icegatheringstatechange', onChange);\n };\n const onChange = () => {\n if (pc.iceGatheringState === 'complete') {\n cleanup();\n resolve();\n }\n };\n const timer = setTimeout(() => {\n cleanup();\n resolve();\n }, timeoutMs);\n pc.addEventListener('icegatheringstatechange', onChange);\n });\n}\n\n/**\n * Resolves when the data channel opens. Rejects with `MeshTimeoutError` after\n * `timeoutMs`, or when the channel closes while that deadline is still\n * pending, since a peer tearing down a never-opened channel races the same\n * deadline: `MeshConnectionError` when the ICE transport dies first, and\n * `MeshDisposedError` when `disposalSignal` aborts.\n */\nexport function waitChannelOpen(\n dc: RTCDataChannelLike,\n pc: RTCPeerConnectionLike,\n timeoutMs: number,\n peerId: string,\n disposalSignal: AbortSignal,\n): Promise<void> {\n if (dc.readyState === 'open') return Promise.resolve();\n return new Promise((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | null = null;\n const timeout = () => new MeshTimeoutError(`Timed out waiting ${timeoutMs}ms for the data channel to open`);\n const fail = (error: Error) => {\n cleanup();\n reject(error);\n };\n const cleanup = () => {\n if (timer) clearTimeout(timer);\n timer = null;\n dc.removeEventListener('open', onOpen);\n dc.removeEventListener('close', onClose);\n pc.removeEventListener('iceconnectionstatechange', onIce);\n disposalSignal.removeEventListener('abort', onDispose);\n };\n const onOpen = () => {\n cleanup();\n resolve();\n };\n // A close while our open deadline is still armed is the timeout it\n // effectively is; mirror the guest's open-timer guard so both ends agree\n // on the cause instead of racing to report whichever event landed first.\n const onClose = () => fail(timeout());\n const onIce = () => {\n const state = pc.iceConnectionState;\n if (state === 'failed' || state === 'closed' || state === 'disconnected') {\n fail(new MeshConnectionError(`ICE connection ${state} before channel open`, peerId));\n }\n };\n const onDispose = () => fail(new MeshDisposedError());\n timer = setTimeout(() => fail(timeout()), timeoutMs);\n dc.addEventListener('open', onOpen);\n dc.addEventListener('close', onClose);\n pc.addEventListener('iceconnectionstatechange', onIce);\n disposalSignal.addEventListener('abort', onDispose, { once: true });\n });\n}\n"],"mappings":"A,gCAOA,IAAaK,EAAmC,CAC9C,qBAAqB,EAAQ,CAC3B,IAAMC,EAAQ,WAAgE,kBAC9E,GAAI,CAACA,EACH,MAAM,IAAIN,EAAAA,qBAAqB,oEAAoE,EAErG,OAAO,IAAIM,EAAKC,CAAM,CACxB,CACF,EAMA,SAAgB,aAAa,EAAqB,CAChD,IAAMC,EAAaC,EAAI,QAAQ,SAAU;CAAM,EAC/C,OAAOD,EAAW,SAAS;CAAM,EAAIA,EAAa,GAAGA,EAAW,KAClE,CAMA,SAAgB,iBAAiB,EAA2B,EAAkC,CAE5F,OADIE,EAAG,oBAAsB,WAAmB,QAAQ,QAAQ,EACzD,IAAI,QAAS,GAAY,CAC9B,IAAM,YAAgB,CACpB,aAAaC,CAAK,EAClBD,EAAG,oBAAoB,0BAA2B,QAAQ,CAC5D,EACM,aAAiB,CACjBA,EAAG,oBAAsB,aAC3B,QAAQ,EACRE,EAAQ,EAEZ,EACMD,EAAQ,eAAiB,CAC7B,QAAQ,EACRC,EAAQ,CACV,EAAGC,CAAS,EACZH,EAAG,iBAAiB,0BAA2B,QAAQ,CACzD,CAAC,CACH,CASA,SAAgB,gBACd,EACA,EACA,EACA,EACA,EACe,CAEf,OADII,EAAG,aAAe,OAAe,QAAQ,QAAQ,EAC9C,IAAI,SAAS,EAAS,IAAW,CACtC,IAAIH,EAA8C,KAC5C,YAAgB,IAAIV,EAAAA,iBAAiB,qBAAqBY,EAAU,gCAAgC,EACpG,KAAQ,GAAiB,CAC7B,QAAQ,EACRE,EAAOC,CAAK,CACd,EACM,YAAgB,CAChBL,GAAO,aAAaA,CAAK,EAC7B,EAAQ,KACRG,EAAG,oBAAoB,OAAQ,MAAM,EACrCA,EAAG,oBAAoB,QAAS,OAAO,EACvCJ,EAAG,oBAAoB,2BAA4B,KAAK,EACxDO,EAAe,oBAAoB,QAAS,SAAS,CACvD,EACM,WAAe,CACnB,QAAQ,EACRL,EAAQ,CACV,EAIM,YAAgB,KAAK,QAAQ,CAAC,EAC9B,UAAc,CAClB,IAAMM,EAAQR,EAAG,oBACbQ,IAAU,UAAYA,IAAU,UAAYA,IAAU,iBACxD,KAAK,IAAIhB,EAAAA,oBAAoB,kBAAkBgB,EAAM,sBAAuBC,CAAM,CAAC,CAEvF,EACM,cAAkB,KAAK,IAAIhB,EAAAA,iBAAmB,EACpD,EAAQ,eAAiB,KAAK,QAAQ,CAAC,EAAGU,CAAS,EACnDC,EAAG,iBAAiB,OAAQ,MAAM,EAClCA,EAAG,iBAAiB,QAAS,OAAO,EACpCJ,EAAG,iBAAiB,2BAA4B,KAAK,EACrDO,EAAe,iBAAiB,QAAS,UAAW,CAAE,KAAM,EAAK,CAAC,CACpE,CAAC,CACH,C"}
|
package/dist/_rtc.js
CHANGED
|
@@ -1,4 +1,42 @@
|
|
|
1
|
-
import{MeshConnectionError as e,MeshDisposedError as t,MeshTimeoutError as n,MeshUnsupportedError as r}from"./errors.js";
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { MeshConnectionError as e, MeshDisposedError as t, MeshTimeoutError as n, MeshUnsupportedError as r } from "./errors.js";
|
|
2
|
+
//#region src/_rtc.ts
|
|
3
|
+
var i = { createPeerConnection(e) {
|
|
4
|
+
let t = globalThis.RTCPeerConnection;
|
|
5
|
+
if (!t) throw new r("WebRTC is unavailable in this environment: inject an `rtc` factory");
|
|
6
|
+
return new t(e);
|
|
7
|
+
} };
|
|
8
|
+
function normalizeSdp(e) {
|
|
9
|
+
let t = e.replace(/\r?\n/g, "\r\n");
|
|
10
|
+
return t.endsWith("\r\n") ? t : `${t}\r\n`;
|
|
11
|
+
}
|
|
12
|
+
function waitIceGathering(e, t) {
|
|
13
|
+
return e.iceGatheringState === "complete" ? Promise.resolve() : new Promise((n) => {
|
|
14
|
+
let cleanup = () => {
|
|
15
|
+
clearTimeout(r), e.removeEventListener("icegatheringstatechange", onChange);
|
|
16
|
+
}, onChange = () => {
|
|
17
|
+
e.iceGatheringState === "complete" && (cleanup(), n());
|
|
18
|
+
}, r = setTimeout(() => {
|
|
19
|
+
cleanup(), n();
|
|
20
|
+
}, t);
|
|
21
|
+
e.addEventListener("icegatheringstatechange", onChange);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
function waitChannelOpen(r, i, a, o, s) {
|
|
25
|
+
return r.readyState === "open" ? Promise.resolve() : new Promise((c, l) => {
|
|
26
|
+
let u = null, timeout = () => new n(`Timed out waiting ${a}ms for the data channel to open`), fail = (e) => {
|
|
27
|
+
cleanup(), l(e);
|
|
28
|
+
}, cleanup = () => {
|
|
29
|
+
u && clearTimeout(u), u = null, r.removeEventListener("open", onOpen), r.removeEventListener("close", onClose), i.removeEventListener("iceconnectionstatechange", onIce), s.removeEventListener("abort", onDispose);
|
|
30
|
+
}, onOpen = () => {
|
|
31
|
+
cleanup(), c();
|
|
32
|
+
}, onClose = () => fail(timeout()), onIce = () => {
|
|
33
|
+
let t = i.iceConnectionState;
|
|
34
|
+
(t === "failed" || t === "closed" || t === "disconnected") && fail(new e(`ICE connection ${t} before channel open`, o));
|
|
35
|
+
}, onDispose = () => fail(new t());
|
|
36
|
+
u = setTimeout(() => fail(timeout()), a), r.addEventListener("open", onOpen), r.addEventListener("close", onClose), i.addEventListener("iceconnectionstatechange", onIce), s.addEventListener("abort", onDispose, { once: !0 });
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { i as nativeRtcFactory, normalizeSdp, waitChannelOpen, waitIceGathering };
|
|
41
|
+
|
|
4
42
|
//# sourceMappingURL=_rtc.js.map
|