@vielzeug/mesh 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/dist/_base45.cjs +2 -0
- package/dist/_base45.cjs.map +1 -0
- package/dist/_base45.d.ts +3 -0
- package/dist/_base45.d.ts.map +1 -0
- package/dist/_base45.js +38 -0
- package/dist/_base45.js.map +1 -0
- package/dist/_base64.cjs +2 -0
- package/dist/_base64.cjs.map +1 -0
- package/dist/_base64.d.ts +9 -0
- package/dist/_base64.d.ts.map +1 -0
- package/dist/_base64.js +33 -0
- package/dist/_base64.js.map +1 -0
- package/dist/_node.cjs +2 -0
- package/dist/_node.cjs.map +1 -0
- package/dist/_node.d.ts +56 -0
- package/dist/_node.d.ts.map +1 -0
- package/dist/_node.js +195 -0
- package/dist/_node.js.map +1 -0
- package/dist/_proof.cjs +2 -0
- package/dist/_proof.cjs.map +1 -0
- package/dist/_proof.d.ts +11 -0
- package/dist/_proof.d.ts.map +1 -0
- package/dist/_proof.js +22 -0
- package/dist/_proof.js.map +1 -0
- package/dist/_random.cjs +2 -0
- package/dist/_random.cjs.map +1 -0
- package/dist/_random.d.ts +9 -0
- package/dist/_random.d.ts.map +1 -0
- package/dist/_random.js +16 -0
- package/dist/_random.js.map +1 -0
- package/dist/_rtc.cjs +4 -0
- package/dist/_rtc.cjs.map +1 -0
- package/dist/_rtc.d.ts +24 -0
- package/dist/_rtc.d.ts.map +1 -0
- package/dist/_rtc.js +42 -0
- package/dist/_rtc.js.map +1 -0
- package/dist/_sdp.cjs +3 -0
- package/dist/_sdp.cjs.map +1 -0
- package/dist/_sdp.d.ts +40 -0
- package/dist/_sdp.d.ts.map +1 -0
- package/dist/_sdp.js +55 -0
- package/dist/_sdp.js.map +1 -0
- package/dist/_wire.cjs +2 -0
- package/dist/_wire.cjs.map +1 -0
- package/dist/_wire.d.ts +20 -0
- package/dist/_wire.d.ts.map +1 -0
- package/dist/_wire.js +21 -0
- package/dist/_wire.js.map +1 -0
- package/dist/codec.cjs +2 -0
- package/dist/codec.cjs.map +1 -0
- package/dist/codec.d.ts +25 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +35 -0
- package/dist/codec.js.map +1 -0
- package/dist/errors.cjs +2 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.ts +47 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +19 -0
- package/dist/errors.js.map +1 -0
- package/dist/guest.cjs +2 -0
- package/dist/guest.cjs.map +1 -0
- package/dist/guest.d.ts +7 -0
- package/dist/guest.d.ts.map +1 -0
- package/dist/guest.js +165 -0
- package/dist/guest.js.map +1 -0
- package/dist/host.cjs +2 -0
- package/dist/host.cjs.map +1 -0
- package/dist/host.d.ts +7 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +241 -0
- package/dist/host.js.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/mesh.cjs +5 -0
- package/dist/mesh.cjs.map +1 -0
- package/dist/mesh.iife.js +5 -0
- package/dist/mesh.iife.js.map +1 -0
- package/dist/mesh.js +5 -0
- package/dist/mesh.js.map +1 -0
- package/dist/qr-codec.cjs +2 -0
- package/dist/qr-codec.cjs.map +1 -0
- package/dist/qr-codec.d.ts +8 -0
- package/dist/qr-codec.d.ts.map +1 -0
- package/dist/qr-codec.js +68 -0
- package/dist/qr-codec.js.map +1 -0
- package/dist/types.d.ts +300 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# @vielzeug/mesh
|
|
2
|
+
|
|
3
|
+
> Backendless peer-to-peer session transport over WebRTC data channels with manual pairing and host-authoritative star topology
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @vielzeug/mesh
|
|
9
|
+
npm install @vielzeug/mesh
|
|
10
|
+
yarn add @vielzeug/mesh
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createMeshGuest, createMeshHost, meshCodec, type MeshProtocol } from '@vielzeug/mesh';
|
|
17
|
+
|
|
18
|
+
interface AppProtocol extends MeshProtocol {
|
|
19
|
+
toHost: { note: string };
|
|
20
|
+
toGuest: { ack: { ok: boolean } };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const host = createMeshHost<AppProtocol>();
|
|
24
|
+
const guest = createMeshGuest<AppProtocol>();
|
|
25
|
+
|
|
26
|
+
host.on('note', (m) => host.send(m.peerId, 'ack', { ok: true }));
|
|
27
|
+
|
|
28
|
+
// Two-leg manual pairing: invitation to the guest, answer back to the host.
|
|
29
|
+
const invitationText = meshCodec.encode(await host.createInvitation());
|
|
30
|
+
const answerText = meshCodec.encode(await guest.acceptInvitation(meshCodec.decode(invitationText)));
|
|
31
|
+
|
|
32
|
+
await host.acceptAnswer(meshCodec.decode(answerText)); // resolves when the channel opens
|
|
33
|
+
guest.send('note', 'hello over WebRTC');
|
|
34
|
+
|
|
35
|
+
host.dispose();
|
|
36
|
+
guest.dispose();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Pairing payloads travel out-of-band — copy/paste, `navigator.share`, or any channel you control. `RTCPeerConnection` requires a secure context (`https:` or `localhost`).
|
|
40
|
+
|
|
41
|
+
## Documentation
|
|
42
|
+
|
|
43
|
+
- [Overview](https://vielzeug.dev/mesh/)
|
|
44
|
+
- [Usage Guide](https://vielzeug.dev/mesh/usage)
|
|
45
|
+
- [API Reference](https://vielzeug.dev/mesh/api)
|
|
46
|
+
- [Examples](https://vielzeug.dev/mesh/examples)
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
MIT © [Helmuth Saatkamp](https://github.com/helmuthdu) — part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
|
package/dist/_base45.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./errors.cjs");var t=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:`,n=new Map([...t].map((e,t)=>[e,t]));function r(e){let n=``;for(let r=0;r+1<e.length;r+=2){let i=e[r]*256+e[r+1];n+=t[i%45]+t[Math.floor(i/45)%45]+t[Math.floor(i/2025)]}if(e.length%2){let r=e[e.length-1];n+=t[r%45]+t[Math.floor(r/45)]}return n}function i(t){let n=[],r=0;for(;r+2<t.length;){let i=a(t,r)+45*a(t,r+1)+2025*a(t,r+2);if(i>65535)throw new e.MeshPairingError(`Malformed base45 payload`);n.push(i>>8,i&255),r+=3}if(t.length-r===2){let i=a(t,r)+45*a(t,r+1);if(i>255)throw new e.MeshPairingError(`Malformed base45 payload`);n.push(i)}else if(t.length-r===1)throw new e.MeshPairingError(`Malformed base45 payload`);return new Uint8Array(n)}function a(t,r){let i=n.get(t[r]);if(i===void 0)throw new e.MeshPairingError(`Malformed base45 payload`);return i}exports.base45ToBytes=i,exports.bytesToBase45=r;
|
|
2
|
+
//# sourceMappingURL=_base45.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_base45.cjs","names":[],"sources":["../src/_base45.ts"],"sourcesContent":["import { MeshPairingError } from './errors';\n\n/**\n * Base45 (RFC 9285): two bytes → three chars, one leftover byte → two chars.\n * The alphabet sits inside the QR alphanumeric charset, so QR encoders can use\n * alphanumeric mode (~5.5 bits/char) instead of byte mode — about a third\n * denser than base64 for the same payload.\n */\n\nconst ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';\nconst INDEX = new Map([...ALPHABET].map((ch, i) => [ch, i]));\n\nexport function bytesToBase45(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i + 1 < bytes.length; i += 2) {\n const value = bytes[i] * 256 + bytes[i + 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45) % 45] + ALPHABET[Math.floor(value / 2025)];\n }\n if (bytes.length % 2) {\n const value = bytes[bytes.length - 1];\n out += ALPHABET[value % 45] + ALPHABET[Math.floor(value / 45)];\n }\n return out;\n}\n\nexport function base45ToBytes(text: string): Uint8Array {\n const out: number[] = [];\n let i = 0;\n while (i + 2 < text.length) {\n const value = digit(text, i) + 45 * digit(text, i + 1) + 2025 * digit(text, i + 2);\n if (value > 0xffff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value >> 8, value & 0xff);\n i += 3;\n }\n if (text.length - i === 2) {\n const value = digit(text, i) + 45 * digit(text, i + 1);\n if (value > 0xff) throw new MeshPairingError('Malformed base45 payload');\n out.push(value);\n } else if (text.length - i === 1) {\n throw new MeshPairingError('Malformed base45 payload');\n }\n return new Uint8Array(out);\n}\n\nfunction digit(text: string, at: number): number {\n const value = INDEX.get(text[at]);\n if (value === undefined) throw new MeshPairingError('Malformed base45 payload');\n return value;\n}\n"],"mappings":"gCASA,IAAM,EAAW,gDACX,EAAQ,IAAI,IAAI,CAAC,GAAG,CAAQ,CAAC,CAAC,KAAK,EAAI,IAAM,CAAC,EAAI,CAAC,CAAC,CAAC,EAE3D,SAAgB,EAAc,EAA2B,CACvD,IAAI,EAAM,GACV,IAAK,IAAI,EAAI,EAAG,EAAI,EAAI,EAAM,OAAQ,GAAK,EAAG,CAC5C,IAAM,EAAQ,EAAM,GAAK,IAAM,EAAM,EAAI,GACzC,GAAO,EAAS,EAAQ,IAAM,EAAS,KAAK,MAAM,EAAQ,EAAE,EAAI,IAAM,EAAS,KAAK,MAAM,EAAQ,IAAI,EACxG,CACA,GAAI,EAAM,OAAS,EAAG,CACpB,IAAM,EAAQ,EAAM,EAAM,OAAS,GACnC,GAAO,EAAS,EAAQ,IAAM,EAAS,KAAK,MAAM,EAAQ,EAAE,EAC9D,CACA,OAAO,CACT,CAEA,SAAgB,EAAc,EAA0B,CACtD,IAAM,EAAgB,CAAC,EACnB,EAAI,EACR,KAAO,EAAI,EAAI,EAAK,QAAQ,CAC1B,IAAM,EAAQ,EAAM,EAAM,CAAC,EAAI,GAAK,EAAM,EAAM,EAAI,CAAC,EAAI,KAAO,EAAM,EAAM,EAAI,CAAC,EACjF,GAAI,EAAQ,MAAQ,MAAM,IAAI,EAAA,iBAAiB,0BAA0B,EACzE,EAAI,KAAK,GAAS,EAAG,EAAQ,GAAI,EACjC,GAAK,CACP,CACA,GAAI,EAAK,OAAS,IAAM,EAAG,CACzB,IAAM,EAAQ,EAAM,EAAM,CAAC,EAAI,GAAK,EAAM,EAAM,EAAI,CAAC,EACrD,GAAI,EAAQ,IAAM,MAAM,IAAI,EAAA,iBAAiB,0BAA0B,EACvE,EAAI,KAAK,CAAK,CAChB,MAAO,GAAI,EAAK,OAAS,IAAM,EAC7B,MAAM,IAAI,EAAA,iBAAiB,0BAA0B,EAEvD,OAAO,IAAI,WAAW,CAAG,CAC3B,CAEA,SAAS,EAAM,EAAc,EAAoB,CAC/C,IAAM,EAAQ,EAAM,IAAI,EAAK,EAAG,EAChC,GAAI,IAAU,IAAA,GAAW,MAAM,IAAI,EAAA,iBAAiB,0BAA0B,EAC9E,OAAO,CACT"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_base45.d.ts","sourceRoot":"","sources":["../src/_base45.ts"],"names":[],"mappings":"AAYA,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAWvD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAiBtD"}
|
package/dist/_base45.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
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 r(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 i(t) {
|
|
17
|
+
let n = [], r = 0;
|
|
18
|
+
for (; r + 2 < t.length;) {
|
|
19
|
+
let i = a(t, r) + 45 * a(t, r + 1) + 2025 * a(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 = a(t, r) + 45 * a(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 a(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 { i as base45ToBytes, r as bytesToBase45 };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=_base45.js.map
|
|
@@ -0,0 +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":";;AASA,IAAM,IAAW,iDACX,IAAQ,IAAI,IAAI,CAAC,GAAG,CAAQ,CAAC,CAAC,KAAK,GAAI,MAAM,CAAC,GAAI,CAAC,CAAC,CAAC;AAE3D,SAAgB,EAAc,GAA2B;CACvD,IAAI,IAAM;CACV,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,EAAM,QAAQ,KAAK,GAAG;EAC5C,IAAM,IAAQ,EAAM,KAAK,MAAM,EAAM,IAAI;EACzC,KAAO,EAAS,IAAQ,MAAM,EAAS,KAAK,MAAM,IAAQ,EAAE,IAAI,MAAM,EAAS,KAAK,MAAM,IAAQ,IAAI;CACxG;CACA,IAAI,EAAM,SAAS,GAAG;EACpB,IAAM,IAAQ,EAAM,EAAM,SAAS;EACnC,KAAO,EAAS,IAAQ,MAAM,EAAS,KAAK,MAAM,IAAQ,EAAE;CAC9D;CACA,OAAO;AACT;AAEA,SAAgB,EAAc,GAA0B;CACtD,IAAM,IAAgB,CAAC,GACnB,IAAI;CACR,OAAO,IAAI,IAAI,EAAK,SAAQ;EAC1B,IAAM,IAAQ,EAAM,GAAM,CAAC,IAAI,KAAK,EAAM,GAAM,IAAI,CAAC,IAAI,OAAO,EAAM,GAAM,IAAI,CAAC;EACjF,IAAI,IAAQ,OAAQ,MAAM,IAAI,EAAiB,0BAA0B;EAEzE,AADA,EAAI,KAAK,KAAS,GAAG,IAAQ,GAAI,GACjC,KAAK;CACP;CACA,IAAI,EAAK,SAAS,MAAM,GAAG;EACzB,IAAM,IAAQ,EAAM,GAAM,CAAC,IAAI,KAAK,EAAM,GAAM,IAAI,CAAC;EACrD,IAAI,IAAQ,KAAM,MAAM,IAAI,EAAiB,0BAA0B;EACvE,EAAI,KAAK,CAAK;CAChB,OAAO,IAAI,EAAK,SAAS,MAAM,GAC7B,MAAM,IAAI,EAAiB,0BAA0B;CAEvD,OAAO,IAAI,WAAW,CAAG;AAC3B;AAEA,SAAS,EAAM,GAAc,GAAoB;CAC/C,IAAM,IAAQ,EAAM,IAAI,EAAK,EAAG;CAChC,IAAI,MAAU,KAAA,GAAW,MAAM,IAAI,EAAiB,0BAA0B;CAC9E,OAAO;AACT"}
|
package/dist/_base64.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_`,t=new Map([...e].map((e,t)=>[e,t]));function n(t){let n=``;for(let r=0;r<t.length;r+=3){let i=t[r],a=t[r+1],o=t[r+2];n+=e[i>>2],n+=e[(i&3)<<4|(a??0)>>4],a!==void 0&&(n+=e[(a&15)<<2|(o??0)>>6]),o!==void 0&&(n+=e[o&63])}return n}function r(e){let n=[],r=0,i=0;for(let a of e){let e=t.get(a);if(e===void 0)throw Error(`Invalid base64url character "${a}"`);r=r<<6|e,i+=6,i>=8&&(i-=8,n.push(r>>i&255))}return new Uint8Array(n)}var i=new TextEncoder,a=new TextDecoder;function o(e){return n(i.encode(e))}function s(e){return a.decode(r(e))}function c(e){return i.encode(e).length}exports.base64UrlToBytes=r,exports.base64UrlToText=s,exports.bytesToBase64Url=n,exports.textToBase64Url=o,exports.utf8Bytes=c;
|
|
2
|
+
//# sourceMappingURL=_base64.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_base64.cjs","names":[],"sources":["../src/_base64.ts"],"sourcesContent":["const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';\nconst LOOKUP = new Map([...ALPHABET].map((char, index) => [char, index]));\n\n/** Environment-independent base64url encoding (no `btoa`/`Buffer` dependency). */\nexport function bytesToBase64Url(bytes: Uint8Array): string {\n let output = '';\n for (let index = 0; index < bytes.length; index += 3) {\n const a = bytes[index]!;\n const b = bytes[index + 1];\n const c = bytes[index + 2];\n output += ALPHABET[a >> 2]!;\n output += ALPHABET[((a & 0b11) << 4) | ((b ?? 0) >> 4)]!;\n if (b !== undefined) output += ALPHABET[((b & 0b1111) << 2) | ((c ?? 0) >> 6)]!;\n if (c !== undefined) output += ALPHABET[c & 0b111111]!;\n }\n return output;\n}\n\n/** Decodes base64url text. Throws on characters outside the base64url alphabet. */\nexport function base64UrlToBytes(text: string): Uint8Array {\n const bytes: number[] = [];\n let buffer = 0;\n let bits = 0;\n for (const char of text) {\n const value = LOOKUP.get(char);\n if (value === undefined) throw new Error(`Invalid base64url character \"${char}\"`);\n buffer = (buffer << 6) | value;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n bytes.push((buffer >> bits) & 0xff);\n }\n }\n return new Uint8Array(bytes);\n}\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nexport function textToBase64Url(text: string): string {\n return bytesToBase64Url(encoder.encode(text));\n}\n\nexport function base64UrlToText(text: string): string {\n return decoder.decode(base64UrlToBytes(text));\n}\n\n/** Byte length of a UTF-8 string — used for `maxMessageBytes` checks. */\nexport function utf8Bytes(text: string): number {\n return encoder.encode(text).length;\n}\n"],"mappings":"AAAA,IAAM,EAAW,mEACX,EAAS,IAAI,IAAI,CAAC,GAAG,CAAQ,CAAC,CAAC,KAAK,EAAM,IAAU,CAAC,EAAM,CAAK,CAAC,CAAC,EAGxE,SAAgB,EAAiB,EAA2B,CAC1D,IAAI,EAAS,GACb,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAM,OAAQ,GAAS,EAAG,CACpD,IAAM,EAAI,EAAM,GACV,EAAI,EAAM,EAAQ,GAClB,EAAI,EAAM,EAAQ,GACxB,GAAU,EAAS,GAAK,GACxB,GAAU,GAAW,EAAI,IAAS,GAAO,GAAK,IAAM,GAChD,IAAM,IAAA,KAAW,GAAU,GAAW,EAAI,KAAW,GAAO,GAAK,IAAM,IACvE,IAAM,IAAA,KAAW,GAAU,EAAS,EAAI,IAC9C,CACA,OAAO,CACT,CAGA,SAAgB,EAAiB,EAA0B,CACzD,IAAM,EAAkB,CAAC,EACrB,EAAS,EACT,EAAO,EACX,IAAK,IAAM,KAAQ,EAAM,CACvB,IAAM,EAAQ,EAAO,IAAI,CAAI,EAC7B,GAAI,IAAU,IAAA,GAAW,MAAU,MAAM,gCAAgC,EAAK,EAAE,EAChF,EAAU,GAAU,EAAK,EACzB,GAAQ,EACJ,GAAQ,IACV,GAAQ,EACR,EAAM,KAAM,GAAU,EAAQ,GAAI,EAEtC,CACA,OAAO,IAAI,WAAW,CAAK,CAC7B,CAEA,IAAM,EAAU,IAAI,YACd,EAAU,IAAI,YAEpB,SAAgB,EAAgB,EAAsB,CACpD,OAAO,EAAiB,EAAQ,OAAO,CAAI,CAAC,CAC9C,CAEA,SAAgB,EAAgB,EAAsB,CACpD,OAAO,EAAQ,OAAO,EAAiB,CAAI,CAAC,CAC9C,CAGA,SAAgB,EAAU,EAAsB,CAC9C,OAAO,EAAQ,OAAO,CAAI,CAAC,CAAC,MAC9B"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Environment-independent base64url encoding (no `btoa`/`Buffer` dependency). */
|
|
2
|
+
export declare function bytesToBase64Url(bytes: Uint8Array): string;
|
|
3
|
+
/** Decodes base64url text. Throws on characters outside the base64url alphabet. */
|
|
4
|
+
export declare function base64UrlToBytes(text: string): Uint8Array;
|
|
5
|
+
export declare function textToBase64Url(text: string): string;
|
|
6
|
+
export declare function base64UrlToText(text: string): string;
|
|
7
|
+
/** Byte length of a UTF-8 string — used for `maxMessageBytes` checks. */
|
|
8
|
+
export declare function utf8Bytes(text: string): number;
|
|
9
|
+
//# sourceMappingURL=_base64.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_base64.d.ts","sourceRoot":"","sources":["../src/_base64.ts"],"names":[],"mappings":"AAGA,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAY1D;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAezD;AAKD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,yEAAyE;AACzE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C"}
|
package/dist/_base64.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region src/_base64.ts
|
|
2
|
+
var e = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", t = new Map([...e].map((e, t) => [e, t]));
|
|
3
|
+
function n(t) {
|
|
4
|
+
let n = "";
|
|
5
|
+
for (let r = 0; r < t.length; r += 3) {
|
|
6
|
+
let i = t[r], a = t[r + 1], o = t[r + 2];
|
|
7
|
+
n += e[i >> 2], n += e[(i & 3) << 4 | (a ?? 0) >> 4], a !== void 0 && (n += e[(a & 15) << 2 | (o ?? 0) >> 6]), o !== void 0 && (n += e[o & 63]);
|
|
8
|
+
}
|
|
9
|
+
return n;
|
|
10
|
+
}
|
|
11
|
+
function r(e) {
|
|
12
|
+
let n = [], r = 0, i = 0;
|
|
13
|
+
for (let a of e) {
|
|
14
|
+
let e = t.get(a);
|
|
15
|
+
if (e === void 0) throw Error(`Invalid base64url character "${a}"`);
|
|
16
|
+
r = r << 6 | e, i += 6, i >= 8 && (i -= 8, n.push(r >> i & 255));
|
|
17
|
+
}
|
|
18
|
+
return new Uint8Array(n);
|
|
19
|
+
}
|
|
20
|
+
var i = new TextEncoder(), a = new TextDecoder();
|
|
21
|
+
function o(e) {
|
|
22
|
+
return n(i.encode(e));
|
|
23
|
+
}
|
|
24
|
+
function s(e) {
|
|
25
|
+
return a.decode(r(e));
|
|
26
|
+
}
|
|
27
|
+
function c(e) {
|
|
28
|
+
return i.encode(e).length;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { r as base64UrlToBytes, s as base64UrlToText, n as bytesToBase64Url, o as textToBase64Url, c as utf8Bytes };
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=_base64.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_base64.js","names":[],"sources":["../src/_base64.ts"],"sourcesContent":["const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';\nconst LOOKUP = new Map([...ALPHABET].map((char, index) => [char, index]));\n\n/** Environment-independent base64url encoding (no `btoa`/`Buffer` dependency). */\nexport function bytesToBase64Url(bytes: Uint8Array): string {\n let output = '';\n for (let index = 0; index < bytes.length; index += 3) {\n const a = bytes[index]!;\n const b = bytes[index + 1];\n const c = bytes[index + 2];\n output += ALPHABET[a >> 2]!;\n output += ALPHABET[((a & 0b11) << 4) | ((b ?? 0) >> 4)]!;\n if (b !== undefined) output += ALPHABET[((b & 0b1111) << 2) | ((c ?? 0) >> 6)]!;\n if (c !== undefined) output += ALPHABET[c & 0b111111]!;\n }\n return output;\n}\n\n/** Decodes base64url text. Throws on characters outside the base64url alphabet. */\nexport function base64UrlToBytes(text: string): Uint8Array {\n const bytes: number[] = [];\n let buffer = 0;\n let bits = 0;\n for (const char of text) {\n const value = LOOKUP.get(char);\n if (value === undefined) throw new Error(`Invalid base64url character \"${char}\"`);\n buffer = (buffer << 6) | value;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n bytes.push((buffer >> bits) & 0xff);\n }\n }\n return new Uint8Array(bytes);\n}\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nexport function textToBase64Url(text: string): string {\n return bytesToBase64Url(encoder.encode(text));\n}\n\nexport function base64UrlToText(text: string): string {\n return decoder.decode(base64UrlToBytes(text));\n}\n\n/** Byte length of a UTF-8 string — used for `maxMessageBytes` checks. */\nexport function utf8Bytes(text: string): number {\n return encoder.encode(text).length;\n}\n"],"mappings":";AAAA,IAAM,IAAW,oEACX,IAAS,IAAI,IAAI,CAAC,GAAG,CAAQ,CAAC,CAAC,KAAK,GAAM,MAAU,CAAC,GAAM,CAAK,CAAC,CAAC;AAGxE,SAAgB,EAAiB,GAA2B;CAC1D,IAAI,IAAS;CACb,KAAK,IAAI,IAAQ,GAAG,IAAQ,EAAM,QAAQ,KAAS,GAAG;EACpD,IAAM,IAAI,EAAM,IACV,IAAI,EAAM,IAAQ,IAClB,IAAI,EAAM,IAAQ;EAIxB,AAHA,KAAU,EAAS,KAAK,IACxB,KAAU,GAAW,IAAI,MAAS,KAAO,KAAK,MAAM,IAChD,MAAM,KAAA,MAAW,KAAU,GAAW,IAAI,OAAW,KAAO,KAAK,MAAM,KACvE,MAAM,KAAA,MAAW,KAAU,EAAS,IAAI;CAC9C;CACA,OAAO;AACT;AAGA,SAAgB,EAAiB,GAA0B;CACzD,IAAM,IAAkB,CAAC,GACrB,IAAS,GACT,IAAO;CACX,KAAK,IAAM,KAAQ,GAAM;EACvB,IAAM,IAAQ,EAAO,IAAI,CAAI;EAC7B,IAAI,MAAU,KAAA,GAAW,MAAU,MAAM,gCAAgC,EAAK,EAAE;EAGhF,AAFA,IAAU,KAAU,IAAK,GACzB,KAAQ,GACJ,KAAQ,MACV,KAAQ,GACR,EAAM,KAAM,KAAU,IAAQ,GAAI;CAEtC;CACA,OAAO,IAAI,WAAW,CAAK;AAC7B;AAEA,IAAM,IAAU,IAAI,YAAY,GAC1B,IAAU,IAAI,YAAY;AAEhC,SAAgB,EAAgB,GAAsB;CACpD,OAAO,EAAiB,EAAQ,OAAO,CAAI,CAAC;AAC9C;AAEA,SAAgB,EAAgB,GAAsB;CACpD,OAAO,EAAQ,OAAO,EAAiB,CAAI,CAAC;AAC9C;AAGA,SAAgB,EAAU,GAAsB;CAC9C,OAAO,EAAQ,OAAO,CAAI,CAAC,CAAC;AAC9B"}
|
package/dist/_node.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./_base64.cjs"),t=require("./errors.cjs"),n=require("./_random.cjs"),r=require("./_wire.cjs");function i(e,n){let r=new Set,i=new AbortController,a={disposalCtrl:i,disposed:!1,emitTap(e){if(r.size!==0)for(let t of r)try{t(e)}catch{}},ensureLive(){if(a.disposed)throw new t.MeshDisposedError},id:e,markDisposed(){a.disposed||(a.disposed=!0,a.status=`disposed`,a.emitTap({peerId:null,status:`disposed`,type:`status-change`}),a.emitTap({type:`dispose`}),r.clear(),i.abort())},setStatus(e){a.disposed||a.status===e||(a.status=e,a.emitTap({peerId:null,status:e,type:`status-change`}))},status:`idle`,tap(e,t){if(a.disposed)return()=>{};r.add(e);let n=()=>r.delete(e);if(t?.signal){if(t.signal.aborted)return n(),()=>{};let e=()=>n();return t.signal.addEventListener(`abort`,e,{once:!0}),()=>{n(),t.signal?.removeEventListener(`abort`,e)}}return n}};return n&&(n.aborted?a.markDisposed():n.addEventListener(`abort`,()=>a.markDisposed(),{once:!0})),a}function a(e,t,n){let i={dc:null,dedupe:new r.MessageDedupe,id:e,name:t,pc:null,publicPeer:{get id(){return i.id},get name(){return i.name},get role(){return i.role},get status(){return i.status}},role:n,status:`connecting`};return i}function o(i){let a=new Map;function o(e,t){i.emitTap({peerId:e,reason:t,type:`message-rejected`})}return{clear(){a.clear()},handleInbound(n,s){let c=i.peerIdOf(n);if(typeof s!=`string`){o(c,`malformed`);return}let l=e.utf8Bytes(s);if(l>i.maxMessageBytes){o(c,`too-large`);return}let u;try{u=i.deserialize(s)}catch{o(c,`malformed`);return}let d=r.toEnvelope(u);if(!d){o(c,`malformed`);return}if(!n.dedupe.check(d.id)){o(c,`duplicate`);return}let f=a.get(d.t);if(!f||f.size===0){o(c,`unknown-type`);return}i.emitTap({bytes:l,messageType:d.t,peerId:c,type:`message-received`});let p={messageId:d.id,payload:d.p,peerId:c,sentAt:d.ts};for(let e of f)try{e(p)}catch(e){i.emitTap({error:new t.MeshError(`Message handler threw`,{cause:e}),type:`error`})}},hasListeners(e){return(a.get(e)?.size??0)>0},on(e,t){let n=a.get(e);return n||(n=new Set,a.set(e,n)),n.add(t),()=>{n.delete(t),n.size===0&&a.delete(e)}},sendTo(r,a,o){let s=r.dc;if(!s||s.readyState!==`open`||r.status!==`connected`)throw new t.MeshConnectionError(`Peer "${r.id}" is not connected`,r.id);let c={id:n.randomId(i.random),p:o,t:a,ts:i.clock(),v:1},l;try{l=i.serialize(c)}catch(e){throw new t.MeshPayloadError(`Failed to serialize message "${a}"`,{cause:e})}let u=e.utf8Bytes(l);if(u>i.maxMessageBytes)throw new t.MeshPayloadError(`Message "${a}" is ${u} bytes (max ${i.maxMessageBytes})`);try{s.send(l)}catch(e){throw new t.MeshConnectionError(`Failed to send message "${a}"`,r.id,{cause:e})}i.emitTap({bytes:u,messageType:a,peerId:r.id,type:`message-sent`})},wireBytes(t,r){let a={id:n.randomId(i.random),p:r,t,ts:i.clock(),v:1};return e.utf8Bytes(i.serialize(a))}}}exports.createMessenger=o,exports.createNodeCore=i,exports.createPeerRecord=a;
|
|
2
|
+
//# sourceMappingURL=_node.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_node.cjs","names":[],"sources":["../src/_node.ts"],"sourcesContent":["import type { RandomSource } from '@vielzeug/arsenal';\nimport { utf8Bytes } from './_base64';\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 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 readonly id: string;\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(id: string, signal?: AbortSignal): NodeCore {\n const tappers = new Set<Tapper>();\n const disposalCtrl = new AbortController();\n\n const core: NodeCore = {\n disposalCtrl,\n disposed: false,\n\n emitTap(event) {\n if (tappers.size === 0) return;\n for (const tapper of tappers) {\n try {\n tapper(event);\n } catch {\n // Observability must not affect mesh behavior.\n }\n }\n },\n\n ensureLive() {\n if (core.disposed) throw new MeshDisposedError();\n },\n id,\n\n markDisposed() {\n if (core.disposed) return;\n core.disposed = true;\n core.status = 'disposed';\n core.emitTap({ peerId: null, 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({ peerId: null, status: next, type: 'status-change' });\n },\n status: 'idle',\n\n tap(handler, options) {\n if (core.disposed) return () => {};\n tappers.add(handler);\n\n const detach = () => tappers.delete(handler);\n if (options?.signal) {\n if (options.signal.aborted) {\n detach();\n return () => {};\n }\n const onAbort = () => detach();\n options.signal.addEventListener('abort', onAbort, { once: true });\n return () => {\n detach();\n options.signal?.removeEventListener('abort', onAbort);\n };\n }\n return detach;\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: MeshStatus;\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 deserialize: (text: string) => unknown;\n readonly emitTap: (event: MeshEvent) => void;\n readonly maxMessageBytes: number;\n readonly peerIdOf: (peer: PeerRecord) => string;\n readonly random?: RandomSource;\n readonly serialize: (value: unknown) => string;\n}\n\nexport interface Messenger {\n clear(): void;\n /** Entry point for `dc` 'message' events. */\n handleInbound(peer: PeerRecord, data: unknown): void;\n hasListeners(type: string): boolean;\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 /** Serialized-byte size of an outbound message — used by broadcast pre-checks. */\n wireBytes(type: string, payload: unknown): number;\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 = options.peerIdOf(peer);\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 = options.deserialize(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 hasListeners(type) {\n return (handlers.get(type)?.size ?? 0) > 0;\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 = options.serialize(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 wireBytes(type, payload) {\n const envelope = { id: randomId(options.random), p: payload, t: type, ts: options.clock(), v: 1 as const };\n return utf8Bytes(options.serialize(envelope));\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/_node.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { RandomSource } from '@vielzeug/arsenal';
|
|
2
|
+
import { MessageDedupe } from './_wire';
|
|
3
|
+
import type { MeshEvent, MeshInbound, MeshPeer, MeshStatus, RTCDataChannelLike, RTCPeerConnectionLike, Unsubscribe } from './types';
|
|
4
|
+
type Tapper = (event: MeshEvent) => void;
|
|
5
|
+
/** Shared lifecycle core: status, tappers, disposal. */
|
|
6
|
+
export interface NodeCore {
|
|
7
|
+
readonly disposalCtrl: AbortController;
|
|
8
|
+
disposed: boolean;
|
|
9
|
+
emitTap(event: MeshEvent): void;
|
|
10
|
+
ensureLive(): void;
|
|
11
|
+
readonly id: string;
|
|
12
|
+
/** Emits the final status-change + dispose events, clears tappers, aborts. */
|
|
13
|
+
markDisposed(): void;
|
|
14
|
+
setStatus(next: MeshStatus): void;
|
|
15
|
+
status: MeshStatus;
|
|
16
|
+
tap(handler: Tapper, options?: {
|
|
17
|
+
readonly signal?: AbortSignal;
|
|
18
|
+
}): Unsubscribe;
|
|
19
|
+
}
|
|
20
|
+
export declare function createNodeCore(id: string, signal?: AbortSignal): NodeCore;
|
|
21
|
+
/** Internal peer record; `publicPeer` exposes live status through getters. */
|
|
22
|
+
export interface PeerRecord {
|
|
23
|
+
dc: RTCDataChannelLike | null;
|
|
24
|
+
readonly dedupe: MessageDedupe;
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly name: string | undefined;
|
|
27
|
+
pc: RTCPeerConnectionLike | null;
|
|
28
|
+
readonly publicPeer: MeshPeer;
|
|
29
|
+
readonly role: 'host' | 'guest';
|
|
30
|
+
status: MeshStatus;
|
|
31
|
+
}
|
|
32
|
+
export declare function createPeerRecord(id: string, name: string | undefined, role: 'host' | 'guest'): PeerRecord;
|
|
33
|
+
type InboundHandler = (message: MeshInbound<unknown>) => void;
|
|
34
|
+
export interface MessengerOptions {
|
|
35
|
+
readonly clock: () => number;
|
|
36
|
+
readonly deserialize: (text: string) => unknown;
|
|
37
|
+
readonly emitTap: (event: MeshEvent) => void;
|
|
38
|
+
readonly maxMessageBytes: number;
|
|
39
|
+
readonly peerIdOf: (peer: PeerRecord) => string;
|
|
40
|
+
readonly random?: RandomSource;
|
|
41
|
+
readonly serialize: (value: unknown) => string;
|
|
42
|
+
}
|
|
43
|
+
export interface Messenger {
|
|
44
|
+
clear(): void;
|
|
45
|
+
/** Entry point for `dc` 'message' events. */
|
|
46
|
+
handleInbound(peer: PeerRecord, data: unknown): void;
|
|
47
|
+
hasListeners(type: string): boolean;
|
|
48
|
+
on(type: string, handler: InboundHandler): Unsubscribe;
|
|
49
|
+
/** Sends a typed envelope to a peer. Throws `MeshConnectionError`/`MeshPayloadError`. */
|
|
50
|
+
sendTo(peer: PeerRecord, type: string, payload: unknown): void;
|
|
51
|
+
/** Serialized-byte size of an outbound message — used by broadcast pre-checks. */
|
|
52
|
+
wireBytes(type: string, payload: unknown): number;
|
|
53
|
+
}
|
|
54
|
+
export declare function createMessenger(options: MessengerOptions): Messenger;
|
|
55
|
+
export {};
|
|
56
|
+
//# sourceMappingURL=_node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_node.d.ts","sourceRoot":"","sources":["../src/_node.ts"],"names":[],"mappings":"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"}
|
package/dist/_node.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { utf8Bytes as e } from "./_base64.js";
|
|
2
|
+
import { MeshConnectionError as t, MeshDisposedError as n, MeshError as r, MeshPayloadError as i } from "./errors.js";
|
|
3
|
+
import { randomId as a } from "./_random.js";
|
|
4
|
+
import { MessageDedupe as o, toEnvelope as s } from "./_wire.js";
|
|
5
|
+
//#region src/_node.ts
|
|
6
|
+
function c(e, t) {
|
|
7
|
+
let r = /* @__PURE__ */ new Set(), i = new AbortController(), a = {
|
|
8
|
+
disposalCtrl: i,
|
|
9
|
+
disposed: !1,
|
|
10
|
+
emitTap(e) {
|
|
11
|
+
if (r.size !== 0) for (let t of r) try {
|
|
12
|
+
t(e);
|
|
13
|
+
} catch {}
|
|
14
|
+
},
|
|
15
|
+
ensureLive() {
|
|
16
|
+
if (a.disposed) throw new n();
|
|
17
|
+
},
|
|
18
|
+
id: e,
|
|
19
|
+
markDisposed() {
|
|
20
|
+
a.disposed || (a.disposed = !0, a.status = "disposed", a.emitTap({
|
|
21
|
+
peerId: null,
|
|
22
|
+
status: "disposed",
|
|
23
|
+
type: "status-change"
|
|
24
|
+
}), a.emitTap({ type: "dispose" }), r.clear(), i.abort());
|
|
25
|
+
},
|
|
26
|
+
setStatus(e) {
|
|
27
|
+
a.disposed || a.status === e || (a.status = e, a.emitTap({
|
|
28
|
+
peerId: null,
|
|
29
|
+
status: e,
|
|
30
|
+
type: "status-change"
|
|
31
|
+
}));
|
|
32
|
+
},
|
|
33
|
+
status: "idle",
|
|
34
|
+
tap(e, t) {
|
|
35
|
+
if (a.disposed) return () => {};
|
|
36
|
+
r.add(e);
|
|
37
|
+
let n = () => r.delete(e);
|
|
38
|
+
if (t?.signal) {
|
|
39
|
+
if (t.signal.aborted) return n(), () => {};
|
|
40
|
+
let e = () => n();
|
|
41
|
+
return t.signal.addEventListener("abort", e, { once: !0 }), () => {
|
|
42
|
+
n(), t.signal?.removeEventListener("abort", e);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return n;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
return t && (t.aborted ? a.markDisposed() : t.addEventListener("abort", () => a.markDisposed(), { once: !0 })), a;
|
|
49
|
+
}
|
|
50
|
+
function l(e, t, n) {
|
|
51
|
+
let r = {
|
|
52
|
+
dc: null,
|
|
53
|
+
dedupe: new o(),
|
|
54
|
+
id: e,
|
|
55
|
+
name: t,
|
|
56
|
+
pc: null,
|
|
57
|
+
publicPeer: {
|
|
58
|
+
get id() {
|
|
59
|
+
return r.id;
|
|
60
|
+
},
|
|
61
|
+
get name() {
|
|
62
|
+
return r.name;
|
|
63
|
+
},
|
|
64
|
+
get role() {
|
|
65
|
+
return r.role;
|
|
66
|
+
},
|
|
67
|
+
get status() {
|
|
68
|
+
return r.status;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
role: n,
|
|
72
|
+
status: "connecting"
|
|
73
|
+
};
|
|
74
|
+
return r;
|
|
75
|
+
}
|
|
76
|
+
function u(n) {
|
|
77
|
+
let o = /* @__PURE__ */ new Map();
|
|
78
|
+
function c(e, t) {
|
|
79
|
+
n.emitTap({
|
|
80
|
+
peerId: e,
|
|
81
|
+
reason: t,
|
|
82
|
+
type: "message-rejected"
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
clear() {
|
|
87
|
+
o.clear();
|
|
88
|
+
},
|
|
89
|
+
handleInbound(t, i) {
|
|
90
|
+
let a = n.peerIdOf(t);
|
|
91
|
+
if (typeof i != "string") {
|
|
92
|
+
c(a, "malformed");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
let l = e(i);
|
|
96
|
+
if (l > n.maxMessageBytes) {
|
|
97
|
+
c(a, "too-large");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
let u;
|
|
101
|
+
try {
|
|
102
|
+
u = n.deserialize(i);
|
|
103
|
+
} catch {
|
|
104
|
+
c(a, "malformed");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
let d = s(u);
|
|
108
|
+
if (!d) {
|
|
109
|
+
c(a, "malformed");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (!t.dedupe.check(d.id)) {
|
|
113
|
+
c(a, "duplicate");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
let f = o.get(d.t);
|
|
117
|
+
if (!f || f.size === 0) {
|
|
118
|
+
c(a, "unknown-type");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
n.emitTap({
|
|
122
|
+
bytes: l,
|
|
123
|
+
messageType: d.t,
|
|
124
|
+
peerId: a,
|
|
125
|
+
type: "message-received"
|
|
126
|
+
});
|
|
127
|
+
let p = {
|
|
128
|
+
messageId: d.id,
|
|
129
|
+
payload: d.p,
|
|
130
|
+
peerId: a,
|
|
131
|
+
sentAt: d.ts
|
|
132
|
+
};
|
|
133
|
+
for (let e of f) try {
|
|
134
|
+
e(p);
|
|
135
|
+
} catch (e) {
|
|
136
|
+
n.emitTap({
|
|
137
|
+
error: new r("Message handler threw", { cause: e }),
|
|
138
|
+
type: "error"
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
hasListeners(e) {
|
|
143
|
+
return (o.get(e)?.size ?? 0) > 0;
|
|
144
|
+
},
|
|
145
|
+
on(e, t) {
|
|
146
|
+
let n = o.get(e);
|
|
147
|
+
return n || (n = /* @__PURE__ */ new Set(), o.set(e, n)), n.add(t), () => {
|
|
148
|
+
n.delete(t), n.size === 0 && o.delete(e);
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
sendTo(r, o, s) {
|
|
152
|
+
let c = r.dc;
|
|
153
|
+
if (!c || c.readyState !== "open" || r.status !== "connected") throw new t(`Peer "${r.id}" is not connected`, r.id);
|
|
154
|
+
let l = {
|
|
155
|
+
id: a(n.random),
|
|
156
|
+
p: s,
|
|
157
|
+
t: o,
|
|
158
|
+
ts: n.clock(),
|
|
159
|
+
v: 1
|
|
160
|
+
}, u;
|
|
161
|
+
try {
|
|
162
|
+
u = n.serialize(l);
|
|
163
|
+
} catch (e) {
|
|
164
|
+
throw new i(`Failed to serialize message "${o}"`, { cause: e });
|
|
165
|
+
}
|
|
166
|
+
let d = e(u);
|
|
167
|
+
if (d > n.maxMessageBytes) throw new i(`Message "${o}" is ${d} bytes (max ${n.maxMessageBytes})`);
|
|
168
|
+
try {
|
|
169
|
+
c.send(u);
|
|
170
|
+
} catch (e) {
|
|
171
|
+
throw new t(`Failed to send message "${o}"`, r.id, { cause: e });
|
|
172
|
+
}
|
|
173
|
+
n.emitTap({
|
|
174
|
+
bytes: d,
|
|
175
|
+
messageType: o,
|
|
176
|
+
peerId: r.id,
|
|
177
|
+
type: "message-sent"
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
wireBytes(t, r) {
|
|
181
|
+
let i = {
|
|
182
|
+
id: a(n.random),
|
|
183
|
+
p: r,
|
|
184
|
+
t,
|
|
185
|
+
ts: n.clock(),
|
|
186
|
+
v: 1
|
|
187
|
+
};
|
|
188
|
+
return e(n.serialize(i));
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//#endregion
|
|
193
|
+
export { u as createMessenger, c as createNodeCore, l as createPeerRecord };
|
|
194
|
+
|
|
195
|
+
//# sourceMappingURL=_node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_node.js","names":[],"sources":["../src/_node.ts"],"sourcesContent":["import type { RandomSource } from '@vielzeug/arsenal';\nimport { utf8Bytes } from './_base64';\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 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 readonly id: string;\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(id: string, signal?: AbortSignal): NodeCore {\n const tappers = new Set<Tapper>();\n const disposalCtrl = new AbortController();\n\n const core: NodeCore = {\n disposalCtrl,\n disposed: false,\n\n emitTap(event) {\n if (tappers.size === 0) return;\n for (const tapper of tappers) {\n try {\n tapper(event);\n } catch {\n // Observability must not affect mesh behavior.\n }\n }\n },\n\n ensureLive() {\n if (core.disposed) throw new MeshDisposedError();\n },\n id,\n\n markDisposed() {\n if (core.disposed) return;\n core.disposed = true;\n core.status = 'disposed';\n core.emitTap({ peerId: null, 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({ peerId: null, status: next, type: 'status-change' });\n },\n status: 'idle',\n\n tap(handler, options) {\n if (core.disposed) return () => {};\n tappers.add(handler);\n\n const detach = () => tappers.delete(handler);\n if (options?.signal) {\n if (options.signal.aborted) {\n detach();\n return () => {};\n }\n const onAbort = () => detach();\n options.signal.addEventListener('abort', onAbort, { once: true });\n return () => {\n detach();\n options.signal?.removeEventListener('abort', onAbort);\n };\n }\n return detach;\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: MeshStatus;\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 deserialize: (text: string) => unknown;\n readonly emitTap: (event: MeshEvent) => void;\n readonly maxMessageBytes: number;\n readonly peerIdOf: (peer: PeerRecord) => string;\n readonly random?: RandomSource;\n readonly serialize: (value: unknown) => string;\n}\n\nexport interface Messenger {\n clear(): void;\n /** Entry point for `dc` 'message' events. */\n handleInbound(peer: PeerRecord, data: unknown): void;\n hasListeners(type: string): boolean;\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 /** Serialized-byte size of an outbound message — used by broadcast pre-checks. */\n wireBytes(type: string, payload: unknown): number;\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 = options.peerIdOf(peer);\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 = options.deserialize(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 hasListeners(type) {\n return (handlers.get(type)?.size ?? 0) > 0;\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 = options.serialize(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 wireBytes(type, payload) {\n const envelope = { id: randomId(options.random), p: payload, t: type, ts: options.clock(), v: 1 as const };\n return utf8Bytes(options.serialize(envelope));\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/_proof.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./_base64.cjs"),t=require("./_sdp.cjs");let n=require("@vielzeug/arsenal");async function r(r,i,a){let o=t.canonicalSdp(i),s=globalThis.crypto?.subtle;if(s){let t=new TextEncoder,n=await s.importKey(`raw`,t.encode(r),{hash:`SHA-256`,name:`HMAC`},!1,[`sign`]),i=await s.sign(`HMAC`,n,t.encode(o));return e.bytesToBase64Url(new Uint8Array(i))}return a(),(0,n.hash)({sdp:o,secret:r})}exports.createProof=r;
|
|
2
|
+
//# sourceMappingURL=_proof.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_proof.cjs","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":"4FAaA,eAAsB,EAAY,EAAgB,EAAa,EAA0C,CACvG,IAAM,EAAY,EAAA,aAAa,CAAG,EAC5B,EAAS,WAAW,QAAQ,OAClC,GAAI,EAAQ,CACV,IAAM,EAAU,IAAI,YACd,EAAM,MAAM,EAAO,UAAU,MAAO,EAAQ,OAAO,CAAM,EAAG,CAAE,KAAM,UAAW,KAAM,MAAO,EAAG,GAAO,CAC1G,MACF,CAAC,EACK,EAAY,MAAM,EAAO,KAAK,OAAQ,EAAK,EAAQ,OAAO,CAAS,CAAC,EAC1E,OAAO,EAAA,iBAAiB,IAAI,WAAW,CAAS,CAAC,CACnD,CAGA,OADA,EAAY,GACZ,EAAO,EAAA,KAAA,CAAK,CAAE,IAAK,EAAW,QAAO,CAAC,CACxC"}
|
package/dist/_proof.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof of invitation possession: HMAC-SHA-256 of the guest SDP keyed by the
|
|
3
|
+
* invitation secret. The SDP is canonicalized first so the proof is stable
|
|
4
|
+
* across pairing codecs that rebuild the description (see `mq2.`). The host
|
|
5
|
+
* recomputes the proof without receiving the secret back. Falls back to a
|
|
6
|
+
* deterministic non-cryptographic hash when `SubtleCrypto` is unavailable —
|
|
7
|
+
* the caller reports a `'security-downgrade'` tap so consumers can refuse
|
|
8
|
+
* degraded environments.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createProof(secret: string, sdp: string, onDowngrade: () => void): Promise<string>;
|
|
11
|
+
//# sourceMappingURL=_proof.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_proof.d.ts","sourceRoot":"","sources":["../src/_proof.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAcvG"}
|
package/dist/_proof.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { bytesToBase64Url as e } from "./_base64.js";
|
|
2
|
+
import { canonicalSdp as t } from "./_sdp.js";
|
|
3
|
+
import { hash as n } from "@vielzeug/arsenal";
|
|
4
|
+
//#region src/_proof.ts
|
|
5
|
+
async function r(r, i, a) {
|
|
6
|
+
let o = t(i), s = globalThis.crypto?.subtle;
|
|
7
|
+
if (s) {
|
|
8
|
+
let t = new TextEncoder(), n = await s.importKey("raw", t.encode(r), {
|
|
9
|
+
hash: "SHA-256",
|
|
10
|
+
name: "HMAC"
|
|
11
|
+
}, !1, ["sign"]), i = await s.sign("HMAC", n, t.encode(o));
|
|
12
|
+
return e(new Uint8Array(i));
|
|
13
|
+
}
|
|
14
|
+
return a(), n({
|
|
15
|
+
sdp: o,
|
|
16
|
+
secret: r
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { r as createProof };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=_proof.js.map
|
|
@@ -0,0 +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"}
|
package/dist/_random.cjs
ADDED
|
@@ -0,0 +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;
|
|
2
|
+
//# sourceMappingURL=_random.cjs.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type RandomSource } from '@vielzeug/arsenal';
|
|
2
|
+
/**
|
|
3
|
+
* Random bytes from `crypto.getRandomValues`, or from an injected
|
|
4
|
+
* `RandomSource` for deterministic tests.
|
|
5
|
+
*/
|
|
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. */
|
|
8
|
+
export declare function randomId(source?: RandomSource): string;
|
|
9
|
+
//# sourceMappingURL=_random.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/_random.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
|
|
16
|
+
//# sourceMappingURL=_random.js.map
|
|
@@ -0,0 +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"}
|
package/dist/_rtc.cjs
ADDED
|
@@ -0,0 +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
|
|
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;
|
|
4
|
+
//# sourceMappingURL=_rtc.cjs.map
|