@vielzeug/mesh 3.1.0 → 26.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/_base45.cjs +1 -1
  2. package/dist/_base45.cjs.map +1 -1
  3. package/dist/_base45.js +1 -37
  4. package/dist/_base45.js.map +1 -1
  5. package/dist/_defaults.cjs +2 -0
  6. package/dist/_defaults.cjs.map +1 -0
  7. package/dist/_defaults.d.ts +6 -0
  8. package/dist/_defaults.d.ts.map +1 -0
  9. package/dist/_defaults.js +2 -0
  10. package/dist/_defaults.js.map +1 -0
  11. package/dist/_node.cjs +1 -1
  12. package/dist/_node.cjs.map +1 -1
  13. package/dist/_node.d.ts +4 -11
  14. package/dist/_node.d.ts.map +1 -1
  15. package/dist/_node.js +1 -194
  16. package/dist/_node.js.map +1 -1
  17. package/dist/_proof.cjs +1 -1
  18. package/dist/_proof.cjs.map +1 -1
  19. package/dist/_proof.d.ts +8 -5
  20. package/dist/_proof.d.ts.map +1 -1
  21. package/dist/_proof.js +1 -21
  22. package/dist/_proof.js.map +1 -1
  23. package/dist/_random.cjs +1 -1
  24. package/dist/_random.cjs.map +1 -1
  25. package/dist/_random.d.ts.map +1 -1
  26. package/dist/_random.js +1 -15
  27. package/dist/_random.js.map +1 -1
  28. package/dist/_rtc.cjs +2 -2
  29. package/dist/_rtc.cjs.map +1 -1
  30. package/dist/_rtc.d.ts +7 -6
  31. package/dist/_rtc.d.ts.map +1 -1
  32. package/dist/_rtc.js +3 -41
  33. package/dist/_rtc.js.map +1 -1
  34. package/dist/_sdp.cjs +2 -2
  35. package/dist/_sdp.cjs.map +1 -1
  36. package/dist/_sdp.js +2 -54
  37. package/dist/_sdp.js.map +1 -1
  38. package/dist/_wire.cjs +1 -1
  39. package/dist/_wire.cjs.map +1 -1
  40. package/dist/_wire.js +1 -20
  41. package/dist/_wire.js.map +1 -1
  42. package/dist/codec.cjs +1 -1
  43. package/dist/codec.cjs.map +1 -1
  44. package/dist/codec.d.ts +14 -9
  45. package/dist/codec.d.ts.map +1 -1
  46. package/dist/codec.js +1 -34
  47. package/dist/codec.js.map +1 -1
  48. package/dist/errors.cjs +1 -1
  49. package/dist/errors.cjs.map +1 -1
  50. package/dist/errors.js +1 -18
  51. package/dist/errors.js.map +1 -1
  52. package/dist/guest.cjs +1 -1
  53. package/dist/guest.cjs.map +1 -1
  54. package/dist/guest.d.ts.map +1 -1
  55. package/dist/guest.js +1 -164
  56. package/dist/guest.js.map +1 -1
  57. package/dist/host.cjs +1 -1
  58. package/dist/host.cjs.map +1 -1
  59. package/dist/host.d.ts.map +1 -1
  60. package/dist/host.js +1 -240
  61. package/dist/host.js.map +1 -1
  62. package/dist/index.d.ts +2 -2
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +1 -6
  65. package/dist/mesh.cjs +3 -3
  66. package/dist/mesh.cjs.map +1 -1
  67. package/dist/mesh.iife.js +3 -3
  68. package/dist/mesh.iife.js.map +1 -1
  69. package/dist/mesh.js +3 -3
  70. package/dist/mesh.js.map +1 -1
  71. package/dist/qr-codec.cjs +1 -1
  72. package/dist/qr-codec.cjs.map +1 -1
  73. package/dist/qr-codec.d.ts +3 -3
  74. package/dist/qr-codec.d.ts.map +1 -1
  75. package/dist/qr-codec.js +1 -67
  76. package/dist/qr-codec.js.map +1 -1
  77. package/dist/testing/fake-rtc.cjs +2 -0
  78. package/dist/testing/fake-rtc.cjs.map +1 -0
  79. package/dist/testing/fake-rtc.d.ts +32 -0
  80. package/dist/testing/fake-rtc.d.ts.map +1 -0
  81. package/dist/testing/fake-rtc.js +2 -0
  82. package/dist/testing/fake-rtc.js.map +1 -0
  83. package/dist/testing/index.d.ts +2 -0
  84. package/dist/testing/index.d.ts.map +1 -0
  85. package/dist/testing.cjs +1 -0
  86. package/dist/testing.js +1 -0
  87. package/dist/types.d.ts +19 -37
  88. package/dist/types.d.ts.map +1 -1
  89. package/package.json +17 -5
  90. package/dist/_base64.cjs +0 -2
  91. package/dist/_base64.cjs.map +0 -1
  92. package/dist/_base64.d.ts +0 -9
  93. package/dist/_base64.d.ts.map +0 -1
  94. package/dist/_base64.js +0 -33
  95. package/dist/_base64.js.map +0 -1
package/dist/host.js CHANGED
@@ -1,241 +1,2 @@
1
- import { bytesToBase64Url as e } from "./_base64.js";
2
- import { MeshConnectionError as t, MeshDisposedError as n, MeshPairingError as r } from "./errors.js";
3
- import { randomBytes as i, randomId as a } from "./_random.js";
4
- import { createMessenger as o, createNodeCore as s, createPeerRecord as c } from "./_node.js";
5
- import { createProof as l } from "./_proof.js";
6
- import { nativeRtcFactory as u, normalizeSdp as d, waitChannelOpen as f, waitIceGathering as p } from "./_rtc.js";
7
- //#region src/host.ts
8
- var m = 65536, h = 3e5, g = 6e4, _ = 5e3;
9
- function v(v = {}) {
10
- let y = v.rtc ?? u, b = v.clock ?? (() => Date.now()), x = v.serialize ?? JSON.stringify, S = v.deserialize ?? JSON.parse, C = v.maxMessageBytes ?? m, w = v.invitationTtlMs ?? h, T = v.channelOpenTimeoutMs ?? g, E = v.iceGatheringTimeoutMs ?? _, D = {
11
- label: "mesh",
12
- ordered: !0,
13
- ...v.channel
14
- }, O = s(a(v.random), v.signal), k = o({
15
- clock: b,
16
- deserialize: S,
17
- emitTap: O.emitTap,
18
- maxMessageBytes: C,
19
- peerIdOf: (e) => e.id,
20
- random: v.random,
21
- serialize: x
22
- }), A = /* @__PURE__ */ new Map(), j = /* @__PURE__ */ new Map(), M = /* @__PURE__ */ new Set(), N = /* @__PURE__ */ new Set(), P = !1, F = !1, I = !1;
23
- function L(e) {
24
- for (let n of N) try {
25
- n(e);
26
- } catch (e) {
27
- O.emitTap({
28
- error: new t("Peer listener threw", null, { cause: e }),
29
- type: "error"
30
- });
31
- }
32
- }
33
- function R() {
34
- let e = j.size > 0 ? "pairing" : P ? "disconnected" : F ? "failed" : "idle";
35
- for (let t of A.values()) {
36
- if (t.status === "connected") {
37
- e = "connected";
38
- break;
39
- }
40
- t.status === "connecting" && (e = "connecting");
41
- }
42
- O.setStatus(e);
43
- }
44
- function z() {
45
- I || (I = !0, O.emitTap({
46
- reason: "SubtleCrypto unavailable — proof uses a non-cryptographic hash",
47
- type: "security-downgrade"
48
- }));
49
- }
50
- function B(e) {
51
- let t = j.get(e);
52
- t && (j.delete(e), M.add(e), clearTimeout(t.expiryTimer), t.pc.close(), O.emitTap({
53
- sessionId: e,
54
- type: "invitation-expired"
55
- }), R());
56
- }
57
- function V(e, t) {
58
- e.status !== t && (e.status = t, O.emitTap({
59
- peerId: e.id,
60
- status: t,
61
- type: "status-change"
62
- }), L({
63
- peer: e.publicPeer,
64
- type: "status-change"
65
- }), R());
66
- }
67
- function H(e, t, n = !1) {
68
- A.has(e.id) && (A.delete(e.id), e.status = n ? "failed" : "disconnected", e.pc?.close(), e.dc?.close(), O.emitTap({
69
- peerId: e.id,
70
- status: e.status,
71
- type: "status-change"
72
- }), O.emitTap({
73
- peerId: e.id,
74
- reason: t,
75
- type: "peer-left"
76
- }), L({
77
- peer: e.publicPeer,
78
- reason: t,
79
- type: "left"
80
- }), n || (P = !0), F ||= n, R());
81
- }
82
- function U(e, t, n) {
83
- e.pc = t, e.dc = n, n.addEventListener("message", (t) => {
84
- O.disposed || k.handleInbound(e, t.data);
85
- }), n.addEventListener("close", () => {
86
- O.disposed || H(e, "channel closed");
87
- }), n.addEventListener("error", () => {
88
- O.disposed || H(e, "channel error", !0);
89
- }), t.addEventListener("iceconnectionstatechange", () => {
90
- if (O.disposed) return;
91
- let n = t.iceConnectionState;
92
- O.emitTap({
93
- peerId: e.id,
94
- state: n,
95
- type: "ice-state"
96
- }), n === "failed" || n === "closed" ? H(e, `ice ${n}`, n === "failed") : n === "disconnected" && e.status === "connected" && V(e, "disconnected");
97
- });
98
- }
99
- function W(e) {
100
- P = !0, A.set(e.id, e), V(e, "connected"), O.emitTap({
101
- peerId: e.id,
102
- type: "peer-joined"
103
- }), L({
104
- peer: e.publicPeer,
105
- type: "joined"
106
- });
107
- }
108
- return {
109
- async acceptAnswer(e) {
110
- if (O.ensureLive(), e?.v !== 1) throw new r("Unsupported answer version");
111
- let t = j.get(e.sessionId);
112
- if (!t) throw new r(M.has(e.sessionId) ? "Invitation expired" : `Unknown session "${e.sessionId}"`);
113
- if (t.answered) throw new r("Invitation already answered");
114
- if (b() >= t.expiresAt) throw B(e.sessionId), new r("Invitation expired");
115
- let i = await l(t.secret, e.sdp, z);
116
- if (e.proof !== i) throw new r("Proof mismatch");
117
- if (!(await v.approvePeer?.(e.peer) ?? !0)) throw O.emitTap({
118
- peerId: e.peer.id,
119
- type: "peer-rejected"
120
- }), new r(`Peer "${e.peer.id}" rejected by host`);
121
- O.emitTap({
122
- peerId: e.peer.id,
123
- type: "peer-approved"
124
- }), t.answered = !0, j.delete(e.sessionId), clearTimeout(t.expiryTimer);
125
- let a = c(e.peer.id, e.peer.name, "guest"), o = f(t.dc, t.pc, T, a.id, O.disposalCtrl.signal);
126
- U(a, t.pc, t.dc), A.set(a.id, a), R();
127
- try {
128
- await t.pc.setRemoteDescription({
129
- sdp: d(e.sdp),
130
- type: "answer"
131
- });
132
- } catch (e) {
133
- throw H(a, "invalid answer sdp", !0), new r("Invalid answer SDP", { cause: e });
134
- }
135
- try {
136
- await o;
137
- } catch (e) {
138
- let t = !(e instanceof n);
139
- throw H(a, e instanceof Error ? e.message : "open failed", t), e;
140
- }
141
- return W(a), a.publicPeer;
142
- },
143
- broadcast(e, t, n) {
144
- O.ensureLive();
145
- for (let r of A.values()) r.status !== "connected" || n?.except?.includes(r.id) || k.sendTo(r, e, t);
146
- },
147
- async createInvitation(r) {
148
- O.ensureLive();
149
- let o = y.createPeerConnection({ iceServers: [...v.iceServers ?? []] }), s = o.createDataChannel(D.label, {
150
- maxRetransmits: D.maxRetransmits,
151
- ordered: D.ordered
152
- }), c = a(v.random), l = e(i(32, v.random)), u = b() + w;
153
- o.addEventListener("iceconnectionstatechange", () => {
154
- O.disposed || O.emitTap({
155
- peerId: c,
156
- state: o.iceConnectionState,
157
- type: "ice-state"
158
- });
159
- });
160
- try {
161
- await o.setLocalDescription(await o.createOffer()), await p(o, E);
162
- } catch (e) {
163
- throw o.close(), new t("Failed to create offer", null, { cause: e });
164
- }
165
- if (O.disposed) throw o.close(), new n();
166
- let d = o.localDescription?.sdp;
167
- if (!d) throw o.close(), new t("Local description has no SDP");
168
- let f = setTimeout(() => B(c), w);
169
- return j.set(c, {
170
- answered: !1,
171
- dc: s,
172
- expiresAt: u,
173
- expiryTimer: f,
174
- pc: o,
175
- secret: l,
176
- sessionId: c
177
- }), O.emitTap({
178
- sessionId: c,
179
- type: "invitation-created"
180
- }), R(), {
181
- expiresAt: u,
182
- sdp: d,
183
- secret: l,
184
- sessionId: c,
185
- v: 1
186
- };
187
- },
188
- get disposalSignal() {
189
- return O.disposalCtrl.signal;
190
- },
191
- dispose() {
192
- if (!O.disposed) {
193
- O.markDisposed();
194
- for (let e of j.values()) clearTimeout(e.expiryTimer), e.pc.close();
195
- j.clear();
196
- for (let e of A.values()) e.dc?.close(), e.pc?.close(), e.status = "disposed";
197
- A.clear(), N.clear(), k.clear();
198
- }
199
- },
200
- get disposed() {
201
- return O.disposed;
202
- },
203
- get id() {
204
- return O.id;
205
- },
206
- kick(e, n) {
207
- O.ensureLive();
208
- let r = A.get(e);
209
- if (!r) throw new t(`Unknown peer "${e}"`, e);
210
- H(r, n ?? "kicked");
211
- },
212
- on(e, t) {
213
- return O.ensureLive(), k.on(e, t);
214
- },
215
- onPeer(e) {
216
- return O.ensureLive(), N.add(e), () => N.delete(e);
217
- },
218
- get peers() {
219
- return A;
220
- },
221
- send(e, n, r) {
222
- O.ensureLive();
223
- let i = A.get(e);
224
- if (!i) throw new t(`Unknown peer "${e}"`, e);
225
- k.sendTo(i, n, r);
226
- },
227
- get status() {
228
- return O.status;
229
- },
230
- tap(e, t) {
231
- return O.tap(e, t);
232
- },
233
- [Symbol.dispose]() {
234
- this.dispose();
235
- }
236
- };
237
- }
238
- //#endregion
239
- export { v as createMeshHost };
240
-
1
+ import{MeshConnectionError as e,MeshDisposedError as t,MeshPairingError as n}from"./errors.js";import"./_defaults.js";import{randomBytes as r,randomId as i}from"./_random.js";import{createMessenger as a,createNodeCore as o,createPeerRecord as s}from"./_node.js";import{createProof as c}from"./_proof.js";import{nativeRtcFactory as l,normalizeSdp as u,waitChannelOpen as d,waitIceGathering as f}from"./_rtc.js";import{bytesToBase64Url as p}from"@vielzeug/arsenal";function createMeshHost(m={}){let h=m.rtc??l,g=m.clock??(()=>Date.now()),_=m.maxMessageBytes??65536,v=m.invitationTtlMs??3e5,y=m.channelOpenTimeoutMs??6e4,b=m.iceGatheringTimeoutMs??5e3,x={label:`mesh`,ordered:!0,...m.channel},S=o(m.signal),C=a({clock:g,emitTap:S.emitTap,maxMessageBytes:_,random:m.random}),w=new Map,T=new Map,E=!1,D=!1;function recomputeStatus(){let e=T.size>0?`pairing`:E?`disconnected`:D?`failed`:`idle`;for(let t of w.values()){if(t.status===`connected`){e=`connected`;break}t.status===`connecting`&&(e=`connecting`)}S.setStatus(e)}function expireInvitation(e){let t=T.get(e);t&&(T.delete(e),clearTimeout(t.expiryTimer),t.pc.close(),S.emitTap({sessionId:e,type:`invitation-expired`}),recomputeStatus())}function setPeerStatus(e,t){e.status!==t&&(e.status=t,S.emitTap({peerId:e.id,status:t,type:`peer-status-change`}),recomputeStatus())}function leavePeer(e,t,n=!1){w.has(e.id)&&(w.delete(e.id),e.status=n?`failed`:`disconnected`,e.pc?.close(),e.dc?.close(),S.emitTap({peerId:e.id,status:e.status,type:`peer-status-change`}),S.emitTap({peer:e.publicPeer,reason:t,type:`peer-left`}),n||(E=!0),D||=n,recomputeStatus())}function wirePeerTransport(e,t,n){e.pc=t,e.dc=n,n.addEventListener(`message`,t=>{S.disposed||C.handleInbound(e,t.data)}),n.addEventListener(`close`,()=>{S.disposed||leavePeer(e,`channel closed`)}),n.addEventListener(`error`,()=>{S.disposed||leavePeer(e,`channel error`,!0)}),t.addEventListener(`iceconnectionstatechange`,()=>{if(S.disposed)return;let n=t.iceConnectionState;S.emitTap({peerId:e.id,state:n,type:`ice-state`}),n===`failed`||n===`closed`?leavePeer(e,`ice ${n}`,n===`failed`):n===`disconnected`&&e.status===`connected`&&setPeerStatus(e,`disconnected`)})}function markPeerConnected(e){E=!0,w.set(e.id,e),setPeerStatus(e,`connected`),S.emitTap({peer:e,type:`peer-joined`})}return{async acceptAnswer(e){if(S.ensureLive(),e?.v!==1)throw new n(`Unsupported answer version`);if(!e.peer?.id)throw new n(`Answer is missing a peer id`);let r=T.get(e.sessionId);if(!r)throw new n(`Unknown or expired session`);if(r.answered)throw new n(`Invitation already answered`);if(w.has(e.peer.id))throw new n(`Peer "${e.peer.id}" is already connected`);if(g()>=r.expiresAt)throw expireInvitation(e.sessionId),new n(`Invitation expired`);let i=await c(r.secret,e.sdp);if(e.proof!==i)throw new n(`Proof mismatch`);if(!(await m.approvePeer?.(e.peer)??!0))throw S.emitTap({peerId:e.peer.id,type:`peer-rejected`}),new n(`Peer "${e.peer.id}" rejected by host`);if(S.emitTap({peerId:e.peer.id,type:`peer-approved`}),w.has(e.peer.id))throw new n(`Peer "${e.peer.id}" is already connected`);r.answered=!0,T.delete(e.sessionId),clearTimeout(r.expiryTimer);let a=s(e.peer.id,e.peer.name,`guest`),o=d(r.dc,r.pc,y,a.id,S.disposalCtrl.signal);wirePeerTransport(a,r.pc,r.dc),w.set(a.id,a),recomputeStatus();try{await r.pc.setRemoteDescription({sdp:u(e.sdp),type:`answer`})}catch(e){throw leavePeer(a,`invalid answer sdp`,!0),new n(`Invalid answer SDP`,{cause:e})}try{await o}catch(e){let n=!(e instanceof t);throw leavePeer(a,e instanceof Error?e.message:`open failed`,n),e}return markPeerConnected(a),a.publicPeer},broadcast(e,t,n){S.ensureLive();for(let r of w.values())r.status!==`connected`||n?.except?.includes(r.id)||C.sendTo(r,e,t)},async createInvitation(n){S.ensureLive();let a=h.createPeerConnection({iceServers:[...m.iceServers??[]]}),o=a.createDataChannel(x.label,{maxRetransmits:x.maxRetransmits,ordered:x.ordered}),s=i(m.random),c=p(r(32,m.random)),l=g()+v;try{await a.setLocalDescription(await a.createOffer()),await f(a,b)}catch(t){throw a.close(),new e(`Failed to create offer`,null,{cause:t})}if(S.disposed)throw a.close(),new t;let u=a.localDescription?.sdp;if(!u)throw a.close(),new e(`Local description has no SDP`);let d=setTimeout(()=>expireInvitation(s),v);return T.set(s,{answered:!1,dc:o,expiresAt:l,expiryTimer:d,pc:a,secret:c,sessionId:s}),S.emitTap({sessionId:s,type:`invitation-created`}),recomputeStatus(),{expiresAt:l,sdp:u,secret:c,sessionId:s,v:1,...n?.name===void 0?{}:{hostName:n.name}}},get disposalSignal(){return S.disposalCtrl.signal},dispose(){if(!S.disposed){S.markDisposed();for(let e of T.values())clearTimeout(e.expiryTimer),e.pc.close();T.clear();for(let e of w.values())e.dc?.close(),e.pc?.close();w.clear(),C.clear()}},get disposed(){return S.disposed},kick(t,n){S.ensureLive();let r=w.get(t);if(!r)throw new e(`Unknown peer "${t}"`,t);leavePeer(r,n??`kicked`)},on(e,t){return S.ensureLive(),C.on(e,t)},get peers(){return w},send(t,n,r){S.ensureLive();let i=w.get(t);if(!i)throw new e(`Unknown peer "${t}"`,t);C.sendTo(i,n,r)},get status(){return S.status},tap(e,t){return S.tap(e,t)},[Symbol.dispose](){this.dispose()}}}export{createMeshHost};
241
2
  //# sourceMappingURL=host.js.map
package/dist/host.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"host.js","names":[],"sources":["../src/host.ts"],"sourcesContent":["import { bytesToBase64Url } from './_base64';\nimport { createMessenger, createNodeCore, createPeerRecord, type PeerRecord } from './_node';\nimport { createProof } from './_proof';\nimport { randomBytes, randomId } from './_random';\nimport { nativeRtcFactory, normalizeSdp, waitChannelOpen, waitIceGathering } from './_rtc';\nimport { MeshConnectionError, MeshDisposedError, MeshPairingError } from './errors';\nimport type {\n MeshHost,\n MeshHostOptions,\n MeshInbound,\n MeshPeer,\n MeshPeerEvent,\n MeshProtocol,\n MeshStatus,\n RTCDataChannelLike,\n RTCPeerConnectionLike,\n} from './types';\n\ninterface PendingInvitation {\n answered: boolean;\n readonly dc: RTCDataChannelLike;\n readonly expiresAt: number;\n readonly expiryTimer: ReturnType<typeof setTimeout>;\n readonly pc: RTCPeerConnectionLike;\n readonly secret: string;\n readonly sessionId: string;\n}\n\nconst DEFAULT_MAX_MESSAGE_BYTES = 65_536;\nconst DEFAULT_INVITATION_TTL_MS = 300_000;\nconst DEFAULT_CHANNEL_OPEN_TIMEOUT_MS = 60_000;\nconst DEFAULT_ICE_GATHERING_TIMEOUT_MS = 5_000;\n\n/**\n * Create a host-authoritative mesh node. The host holds one peer connection\n * per guest and pairs each one through a manual invitation/answer exchange.\n */\nexport function createMeshHost<P extends MeshProtocol>(options: MeshHostOptions = {}): MeshHost<P> {\n const rtc = options.rtc ?? nativeRtcFactory;\n const clock = options.clock ?? (() => Date.now());\n const serialize = options.serialize ?? JSON.stringify;\n const deserialize = options.deserialize ?? JSON.parse;\n const maxMessageBytes = options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES;\n const invitationTtlMs = options.invitationTtlMs ?? DEFAULT_INVITATION_TTL_MS;\n const channelOpenTimeoutMs = options.channelOpenTimeoutMs ?? DEFAULT_CHANNEL_OPEN_TIMEOUT_MS;\n const iceGatheringTimeoutMs = options.iceGatheringTimeoutMs ?? DEFAULT_ICE_GATHERING_TIMEOUT_MS;\n const channelInit = { label: 'mesh', ordered: true, ...options.channel };\n\n const core = createNodeCore(randomId(options.random), options.signal);\n const messenger = createMessenger({\n clock,\n deserialize,\n emitTap: core.emitTap,\n maxMessageBytes,\n peerIdOf: (peer) => peer.id,\n random: options.random,\n serialize,\n });\n\n const peers = new Map<string, PeerRecord>();\n const pending = new Map<string, PendingInvitation>();\n const expired = new Set<string>();\n const peerListeners = new Set<(event: MeshPeerEvent) => void>();\n\n let sawPeer = false;\n let sawFailure = false;\n let downgradeNotified = false;\n\n function emitPeer(event: MeshPeerEvent): void {\n for (const listener of peerListeners) {\n try {\n listener(event);\n } catch (cause) {\n core.emitTap({ error: new MeshConnectionError('Peer listener threw', null, { cause }), type: 'error' });\n }\n }\n }\n\n function recomputeStatus(): void {\n let next: MeshStatus = pending.size > 0 ? 'pairing' : sawPeer ? 'disconnected' : sawFailure ? 'failed' : 'idle';\n for (const peer of peers.values()) {\n if (peer.status === 'connected') {\n next = 'connected';\n break;\n }\n if (peer.status === 'connecting') next = 'connecting';\n }\n core.setStatus(next);\n }\n\n function onSecurityDowngrade(): void {\n if (downgradeNotified) return;\n downgradeNotified = true;\n core.emitTap({\n reason: 'SubtleCrypto unavailable — proof uses a non-cryptographic hash',\n type: 'security-downgrade',\n });\n }\n\n function expireInvitation(sessionId: string): void {\n const invitation = pending.get(sessionId);\n if (!invitation) return;\n pending.delete(sessionId);\n expired.add(sessionId);\n clearTimeout(invitation.expiryTimer);\n invitation.pc.close();\n core.emitTap({ sessionId, type: 'invitation-expired' });\n recomputeStatus();\n }\n\n function setPeerStatus(peer: PeerRecord, status: PeerRecord['status']): void {\n if (peer.status === status) return;\n peer.status = status;\n core.emitTap({ peerId: peer.id, status, type: 'status-change' });\n emitPeer({ peer: peer.publicPeer, type: 'status-change' });\n recomputeStatus();\n }\n\n function leavePeer(peer: PeerRecord, reason: string | undefined, failed = false): void {\n if (!peers.has(peer.id)) return;\n peers.delete(peer.id);\n peer.status = failed ? 'failed' : 'disconnected';\n peer.pc?.close();\n peer.dc?.close();\n core.emitTap({ peerId: peer.id, status: peer.status, type: 'status-change' });\n core.emitTap({ peerId: peer.id, reason, type: 'peer-left' });\n emitPeer({ peer: peer.publicPeer, reason, type: 'left' });\n if (!failed) sawPeer = true;\n sawFailure = sawFailure || failed;\n recomputeStatus();\n }\n\n function wirePeerTransport(peer: PeerRecord, pc: RTCPeerConnectionLike, dc: RTCDataChannelLike): void {\n peer.pc = pc;\n peer.dc = dc;\n\n dc.addEventListener('message', (event) => {\n if (core.disposed) return;\n messenger.handleInbound(peer, event.data);\n });\n dc.addEventListener('close', () => {\n if (core.disposed) return;\n leavePeer(peer, 'channel closed');\n });\n dc.addEventListener('error', () => {\n if (core.disposed) return;\n leavePeer(peer, 'channel error', true);\n });\n\n pc.addEventListener('iceconnectionstatechange', () => {\n if (core.disposed) return;\n const state = pc.iceConnectionState;\n core.emitTap({ peerId: peer.id, state, type: 'ice-state' });\n if (state === 'failed' || state === 'closed') {\n leavePeer(peer, `ice ${state}`, state === 'failed');\n } else if (state === 'disconnected' && peer.status === 'connected') {\n setPeerStatus(peer, 'disconnected');\n }\n });\n }\n\n function markPeerConnected(peer: PeerRecord): void {\n sawPeer = true;\n peers.set(peer.id, peer);\n setPeerStatus(peer, 'connected');\n core.emitTap({ peerId: peer.id, type: 'peer-joined' });\n emitPeer({ peer: peer.publicPeer, type: 'joined' });\n }\n\n const host: MeshHost<P> = {\n async acceptAnswer(answer) {\n core.ensureLive();\n\n if (answer?.v !== 1) throw new MeshPairingError('Unsupported answer version');\n const invitation = pending.get(answer.sessionId);\n if (!invitation) {\n throw new MeshPairingError(\n expired.has(answer.sessionId) ? 'Invitation expired' : `Unknown session \"${answer.sessionId}\"`,\n );\n }\n if (invitation.answered) throw new MeshPairingError('Invitation already answered');\n if (clock() >= invitation.expiresAt) {\n expireInvitation(answer.sessionId);\n throw new MeshPairingError('Invitation expired');\n }\n\n const expected = await createProof(invitation.secret, answer.sdp, onSecurityDowngrade);\n if (answer.proof !== expected) throw new MeshPairingError('Proof mismatch');\n\n const approved = (await options.approvePeer?.(answer.peer)) ?? true;\n if (!approved) {\n core.emitTap({ peerId: answer.peer.id, type: 'peer-rejected' });\n throw new MeshPairingError(`Peer \"${answer.peer.id}\" rejected by host`);\n }\n core.emitTap({ peerId: answer.peer.id, type: 'peer-approved' });\n\n invitation.answered = true;\n pending.delete(answer.sessionId);\n clearTimeout(invitation.expiryTimer);\n\n const peer = createPeerRecord(answer.peer.id, answer.peer.name, 'guest');\n const opened = waitChannelOpen(\n invitation.dc,\n invitation.pc,\n channelOpenTimeoutMs,\n peer.id,\n core.disposalCtrl.signal,\n );\n wirePeerTransport(peer, invitation.pc, invitation.dc);\n peers.set(peer.id, peer);\n recomputeStatus();\n\n try {\n await invitation.pc.setRemoteDescription({ sdp: normalizeSdp(answer.sdp), type: 'answer' });\n } catch (cause) {\n leavePeer(peer, 'invalid answer sdp', true);\n throw new MeshPairingError('Invalid answer SDP', { cause });\n }\n\n try {\n await opened;\n } catch (error) {\n const failed = !(error instanceof MeshDisposedError);\n leavePeer(peer, error instanceof Error ? error.message : 'open failed', failed);\n throw error;\n }\n\n markPeerConnected(peer);\n return peer.publicPeer;\n },\n\n broadcast(type, payload, broadcastOptions) {\n core.ensureLive();\n for (const peer of peers.values()) {\n if (peer.status !== 'connected' || broadcastOptions?.except?.includes(peer.id)) continue;\n messenger.sendTo(peer, type, payload);\n }\n },\n\n async createInvitation(meta) {\n core.ensureLive();\n\n const pc = rtc.createPeerConnection({ iceServers: [...(options.iceServers ?? [])] });\n const dc = pc.createDataChannel(channelInit.label, {\n maxRetransmits: channelInit.maxRetransmits,\n ordered: channelInit.ordered,\n });\n\n const sessionId = randomId(options.random);\n const secret = bytesToBase64Url(randomBytes(32, options.random));\n const expiresAt = clock() + invitationTtlMs;\n\n pc.addEventListener('iceconnectionstatechange', () => {\n if (core.disposed) return;\n core.emitTap({ peerId: sessionId, state: pc.iceConnectionState, type: 'ice-state' });\n });\n\n try {\n await pc.setLocalDescription(await pc.createOffer());\n await waitIceGathering(pc, iceGatheringTimeoutMs);\n } catch (cause) {\n pc.close();\n throw new MeshConnectionError('Failed to create offer', null, { cause });\n }\n if (core.disposed) {\n pc.close();\n throw new MeshDisposedError();\n }\n\n const sdp = pc.localDescription?.sdp;\n if (!sdp) {\n pc.close();\n throw new MeshConnectionError('Local description has no SDP');\n }\n\n const expiryTimer = setTimeout(() => expireInvitation(sessionId), invitationTtlMs);\n pending.set(sessionId, { answered: false, dc, expiresAt, expiryTimer, pc, secret, sessionId });\n core.emitTap({ sessionId, type: 'invitation-created' });\n recomputeStatus();\n void meta;\n return { expiresAt, sdp, secret, sessionId, v: 1 };\n },\n get disposalSignal() {\n return core.disposalCtrl.signal;\n },\n\n dispose() {\n if (core.disposed) return;\n core.markDisposed();\n for (const invitation of pending.values()) {\n clearTimeout(invitation.expiryTimer);\n invitation.pc.close();\n }\n pending.clear();\n for (const peer of peers.values()) {\n peer.dc?.close();\n peer.pc?.close();\n peer.status = 'disposed';\n }\n peers.clear();\n peerListeners.clear();\n messenger.clear();\n },\n get disposed() {\n return core.disposed;\n },\n get id() {\n return core.id;\n },\n\n kick(peerId, reason) {\n core.ensureLive();\n const peer = peers.get(peerId);\n if (!peer) throw new MeshConnectionError(`Unknown peer \"${peerId}\"`, peerId);\n leavePeer(peer, reason ?? 'kicked');\n },\n\n on(type, handler) {\n core.ensureLive();\n return messenger.on(type, handler as (message: MeshInbound<unknown>) => void);\n },\n\n onPeer(handler) {\n core.ensureLive();\n peerListeners.add(handler);\n return () => peerListeners.delete(handler);\n },\n get peers() {\n return peers as ReadonlyMap<string, MeshPeer>;\n },\n\n send(peerId, type, payload) {\n core.ensureLive();\n const peer = peers.get(peerId);\n if (!peer) throw new MeshConnectionError(`Unknown peer \"${peerId}\"`, peerId);\n messenger.sendTo(peer, type, payload);\n },\n get status() {\n return core.status;\n },\n\n tap(handler, tapOptions) {\n return core.tap(handler, tapOptions);\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n\n return host;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"host.js","names":[],"sources":["../src/host.ts"],"sourcesContent":["import { bytesToBase64Url } from '@vielzeug/arsenal';\nimport {\n DEFAULT_CHANNEL_OPEN_TIMEOUT_MS,\n DEFAULT_ICE_GATHERING_TIMEOUT_MS,\n DEFAULT_INVITATION_TTL_MS,\n DEFAULT_MAX_MESSAGE_BYTES,\n} from './_defaults';\nimport { createMessenger, createNodeCore, createPeerRecord, type PeerRecord } from './_node';\nimport { createProof } from './_proof';\nimport { randomBytes, randomId } from './_random';\nimport { nativeRtcFactory, normalizeSdp, waitChannelOpen, waitIceGathering } from './_rtc';\nimport { MeshConnectionError, MeshDisposedError, MeshPairingError } from './errors';\nimport type {\n MeshHost,\n MeshHostOptions,\n MeshInbound,\n MeshPeer,\n MeshProtocol,\n MeshStatus,\n RTCDataChannelLike,\n RTCPeerConnectionLike,\n} from './types';\n\ninterface PendingInvitation {\n answered: boolean;\n readonly dc: RTCDataChannelLike;\n readonly expiresAt: number;\n readonly expiryTimer: ReturnType<typeof setTimeout>;\n readonly pc: RTCPeerConnectionLike;\n readonly secret: string;\n readonly sessionId: string;\n}\n\n/**\n * Create a host-authoritative mesh node. The host holds one peer connection\n * per guest and pairs each one through a manual invitation/answer exchange.\n */\nexport function createMeshHost<P extends MeshProtocol>(options: MeshHostOptions = {}): MeshHost<P> {\n const rtc = options.rtc ?? nativeRtcFactory;\n const clock = options.clock ?? (() => Date.now());\n const maxMessageBytes = options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES;\n const invitationTtlMs = options.invitationTtlMs ?? DEFAULT_INVITATION_TTL_MS;\n const channelOpenTimeoutMs = options.channelOpenTimeoutMs ?? DEFAULT_CHANNEL_OPEN_TIMEOUT_MS;\n const iceGatheringTimeoutMs = options.iceGatheringTimeoutMs ?? DEFAULT_ICE_GATHERING_TIMEOUT_MS;\n const channelInit = { label: 'mesh', ordered: true, ...options.channel };\n\n const core = createNodeCore(options.signal);\n const messenger = createMessenger({\n clock,\n emitTap: core.emitTap,\n maxMessageBytes,\n random: options.random,\n });\n\n const peers = new Map<string, PeerRecord>();\n const pending = new Map<string, PendingInvitation>();\n\n let sawPeer = false;\n let sawFailure = false;\n\n function recomputeStatus(): void {\n let next: MeshStatus = pending.size > 0 ? 'pairing' : sawPeer ? 'disconnected' : sawFailure ? 'failed' : 'idle';\n for (const peer of peers.values()) {\n if (peer.status === 'connected') {\n next = 'connected';\n break;\n }\n if (peer.status === 'connecting') next = 'connecting';\n }\n core.setStatus(next);\n }\n\n function expireInvitation(sessionId: string): void {\n const invitation = pending.get(sessionId);\n if (!invitation) return;\n pending.delete(sessionId);\n clearTimeout(invitation.expiryTimer);\n invitation.pc.close();\n core.emitTap({ sessionId, type: 'invitation-expired' });\n recomputeStatus();\n }\n\n function setPeerStatus(peer: PeerRecord, status: PeerRecord['status']): void {\n if (peer.status === status) return;\n peer.status = status;\n core.emitTap({ peerId: peer.id, status, type: 'peer-status-change' });\n recomputeStatus();\n }\n\n function leavePeer(peer: PeerRecord, reason: string | undefined, failed = false): void {\n if (!peers.has(peer.id)) return;\n peers.delete(peer.id);\n peer.status = failed ? 'failed' : 'disconnected';\n peer.pc?.close();\n peer.dc?.close();\n core.emitTap({ peerId: peer.id, status: peer.status, type: 'peer-status-change' });\n core.emitTap({ peer: peer.publicPeer, reason, type: 'peer-left' });\n if (!failed) sawPeer = true;\n sawFailure = sawFailure || failed;\n recomputeStatus();\n }\n\n function wirePeerTransport(peer: PeerRecord, pc: RTCPeerConnectionLike, dc: RTCDataChannelLike): void {\n peer.pc = pc;\n peer.dc = dc;\n\n dc.addEventListener('message', (event) => {\n if (core.disposed) return;\n messenger.handleInbound(peer, event.data);\n });\n dc.addEventListener('close', () => {\n if (core.disposed) return;\n leavePeer(peer, 'channel closed');\n });\n dc.addEventListener('error', () => {\n if (core.disposed) return;\n leavePeer(peer, 'channel error', true);\n });\n\n pc.addEventListener('iceconnectionstatechange', () => {\n if (core.disposed) return;\n const state = pc.iceConnectionState;\n core.emitTap({ peerId: peer.id, state, type: 'ice-state' });\n if (state === 'failed' || state === 'closed') {\n leavePeer(peer, `ice ${state}`, state === 'failed');\n } else if (state === 'disconnected' && peer.status === 'connected') {\n setPeerStatus(peer, 'disconnected');\n }\n });\n }\n\n function markPeerConnected(peer: PeerRecord): void {\n sawPeer = true;\n peers.set(peer.id, peer);\n setPeerStatus(peer, 'connected');\n core.emitTap({ peer, type: 'peer-joined' });\n }\n\n const host: MeshHost<P> = {\n async acceptAnswer(answer) {\n core.ensureLive();\n\n if (answer?.v !== 1) throw new MeshPairingError('Unsupported answer version');\n if (!answer.peer?.id) throw new MeshPairingError('Answer is missing a peer id');\n const invitation = pending.get(answer.sessionId);\n if (!invitation) throw new MeshPairingError('Unknown or expired session');\n if (invitation.answered) throw new MeshPairingError('Invitation already answered');\n if (peers.has(answer.peer.id)) throw new MeshPairingError(`Peer \"${answer.peer.id}\" is already connected`);\n if (clock() >= invitation.expiresAt) {\n expireInvitation(answer.sessionId);\n throw new MeshPairingError('Invitation expired');\n }\n\n const expected = await createProof(invitation.secret, answer.sdp);\n if (answer.proof !== expected) throw new MeshPairingError('Proof mismatch');\n\n const approved = (await options.approvePeer?.(answer.peer)) ?? true;\n if (!approved) {\n core.emitTap({ peerId: answer.peer.id, type: 'peer-rejected' });\n throw new MeshPairingError(`Peer \"${answer.peer.id}\" rejected by host`);\n }\n core.emitTap({ peerId: answer.peer.id, type: 'peer-approved' });\n\n // Re-check after the proof/approval awaits: concurrent answers for\n // different sessions could both have passed the check above.\n if (peers.has(answer.peer.id)) throw new MeshPairingError(`Peer \"${answer.peer.id}\" is already connected`);\n\n invitation.answered = true;\n pending.delete(answer.sessionId);\n clearTimeout(invitation.expiryTimer);\n\n const peer = createPeerRecord(answer.peer.id, answer.peer.name, 'guest');\n const opened = waitChannelOpen(\n invitation.dc,\n invitation.pc,\n channelOpenTimeoutMs,\n peer.id,\n core.disposalCtrl.signal,\n );\n wirePeerTransport(peer, invitation.pc, invitation.dc);\n peers.set(peer.id, peer);\n recomputeStatus();\n\n try {\n await invitation.pc.setRemoteDescription({ sdp: normalizeSdp(answer.sdp), type: 'answer' });\n } catch (cause) {\n leavePeer(peer, 'invalid answer sdp', true);\n throw new MeshPairingError('Invalid answer SDP', { cause });\n }\n\n try {\n await opened;\n } catch (error) {\n const failed = !(error instanceof MeshDisposedError);\n leavePeer(peer, error instanceof Error ? error.message : 'open failed', failed);\n throw error;\n }\n\n markPeerConnected(peer);\n return peer.publicPeer;\n },\n\n broadcast(type, payload, broadcastOptions) {\n core.ensureLive();\n for (const peer of peers.values()) {\n if (peer.status !== 'connected' || broadcastOptions?.except?.includes(peer.id)) continue;\n messenger.sendTo(peer, type, payload);\n }\n },\n\n async createInvitation(meta) {\n core.ensureLive();\n\n const pc = rtc.createPeerConnection({ iceServers: [...(options.iceServers ?? [])] });\n const dc = pc.createDataChannel(channelInit.label, {\n maxRetransmits: channelInit.maxRetransmits,\n ordered: channelInit.ordered,\n });\n\n const sessionId = randomId(options.random);\n const secret = bytesToBase64Url(randomBytes(32, options.random));\n const expiresAt = clock() + invitationTtlMs;\n\n try {\n await pc.setLocalDescription(await pc.createOffer());\n await waitIceGathering(pc, iceGatheringTimeoutMs);\n } catch (cause) {\n pc.close();\n throw new MeshConnectionError('Failed to create offer', null, { cause });\n }\n if (core.disposed) {\n pc.close();\n throw new MeshDisposedError();\n }\n\n const sdp = pc.localDescription?.sdp;\n if (!sdp) {\n pc.close();\n throw new MeshConnectionError('Local description has no SDP');\n }\n\n const expiryTimer = setTimeout(() => expireInvitation(sessionId), invitationTtlMs);\n pending.set(sessionId, { answered: false, dc, expiresAt, expiryTimer, pc, secret, sessionId });\n core.emitTap({ sessionId, type: 'invitation-created' });\n recomputeStatus();\n return { expiresAt, sdp, secret, sessionId, v: 1, ...(meta?.name === undefined ? {} : { hostName: meta.name }) };\n },\n get disposalSignal() {\n return core.disposalCtrl.signal;\n },\n\n dispose() {\n if (core.disposed) return;\n core.markDisposed();\n for (const invitation of pending.values()) {\n clearTimeout(invitation.expiryTimer);\n invitation.pc.close();\n }\n pending.clear();\n for (const peer of peers.values()) {\n peer.dc?.close();\n peer.pc?.close();\n }\n peers.clear();\n messenger.clear();\n },\n get disposed() {\n return core.disposed;\n },\n\n kick(peerId, reason) {\n core.ensureLive();\n const peer = peers.get(peerId);\n if (!peer) throw new MeshConnectionError(`Unknown peer \"${peerId}\"`, peerId);\n leavePeer(peer, reason ?? 'kicked');\n },\n\n on(type, handler) {\n core.ensureLive();\n return messenger.on(type, handler as (message: MeshInbound<unknown>) => void);\n },\n get peers() {\n return peers as ReadonlyMap<string, MeshPeer>;\n },\n\n send(peerId, type, payload) {\n core.ensureLive();\n const peer = peers.get(peerId);\n if (!peer) throw new MeshConnectionError(`Unknown peer \"${peerId}\"`, peerId);\n messenger.sendTo(peer, type, payload);\n },\n get status() {\n return core.status;\n },\n\n tap(handler, tapOptions) {\n return core.tap(handler, tapOptions);\n },\n\n [Symbol.dispose]() {\n this.dispose();\n },\n };\n\n return host;\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,6 @@ export { meshCodec } from './codec';
2
2
  export { MeshConnectionError, MeshDisposedError, MeshError, MeshPairingError, MeshPayloadError, MeshTimeoutError, MeshUnsupportedError, } from './errors';
3
3
  export { createMeshGuest } from './guest';
4
4
  export { createMeshHost } from './host';
5
- export { type MeshAsyncCodec, meshQrCodec } from './qr-codec';
6
- export type { MeshAnswer, MeshChannelOptions, MeshCodec, MeshEvent, MeshGuest, MeshGuestOptions, MeshHost, MeshHostOptions, MeshInbound, MeshInvitation, MeshMessageMap, MeshNode, MeshOptions, MeshPeer, MeshPeerEvent, MeshPeerInfo, MeshProtocol, MeshRtcEvent, MeshRtcFactory, MeshStatus, RTCDataChannelLike, RTCPeerConnectionLike, RTCSessionDescriptionLike, Unsubscribe, } from './types';
5
+ export { meshQrCodec } from './qr-codec';
6
+ export type { MeshAnswer, MeshChannelOptions, MeshEvent, MeshGuest, MeshGuestOptions, MeshHost, MeshHostOptions, MeshInbound, MeshInvitation, MeshMessageMap, MeshNode, MeshOptions, MeshPeer, MeshPeerInfo, MeshPeerStatus, MeshProtocol, MeshRtcEvent, MeshRtcFactory, MeshStatus, RTCDataChannelLike, RTCPeerConnectionLike, RTCSessionDescriptionLike, Unsubscribe, } from './types';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,KAAK,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9D,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,SAAS,EACT,SAAS,EACT,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,eAAe,EACf,WAAW,EACX,cAAc,EACd,cAAc,EACd,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,qBAAqB,EACrB,yBAAyB,EACzB,WAAW,GACZ,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,SAAS,EACT,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,eAAe,EACf,WAAW,EACX,cAAc,EACd,cAAc,EACd,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,qBAAqB,EACrB,yBAAyB,EACzB,WAAW,GACZ,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1 @@
1
- import { MeshConnectionError as e, MeshDisposedError as t, MeshError as n, MeshPairingError as r, MeshPayloadError as i, MeshTimeoutError as a, MeshUnsupportedError as o } from "./errors.js";
2
- import { meshCodec as s } from "./codec.js";
3
- import { createMeshGuest as c } from "./guest.js";
4
- import { createMeshHost as l } from "./host.js";
5
- import { meshQrCodec as u } from "./qr-codec.js";
6
- export { e as MeshConnectionError, t as MeshDisposedError, n as MeshError, r as MeshPairingError, i as MeshPayloadError, a as MeshTimeoutError, o as MeshUnsupportedError, c as createMeshGuest, l as createMeshHost, s as meshCodec, u as meshQrCodec };
1
+ import{MeshConnectionError as e,MeshDisposedError as t,MeshError as n,MeshPairingError as r,MeshPayloadError as i,MeshTimeoutError as a,MeshUnsupportedError as o}from"./errors.js";import{meshCodec as s}from"./codec.js";import{createMeshGuest as c}from"./guest.js";import{createMeshHost as l}from"./host.js";import{meshQrCodec as u}from"./qr-codec.js";export{e as MeshConnectionError,t as MeshDisposedError,n as MeshError,r as MeshPairingError,i as MeshPayloadError,a as MeshTimeoutError,o as MeshUnsupportedError,c as createMeshGuest,l as createMeshHost,s as meshCodec,u as meshQrCodec};
package/dist/mesh.cjs CHANGED
@@ -1,5 +1,5 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@vielzeug/arsenal");var t=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_`,n=new Map([...t].map((e,t)=>[e,t]));function r(e){let n=``;for(let r=0;r<e.length;r+=3){let i=e[r],a=e[r+1],o=e[r+2];n+=t[i>>2],n+=t[(i&3)<<4|(a??0)>>4],a!==void 0&&(n+=t[(a&15)<<2|(o??0)>>6]),o!==void 0&&(n+=t[o&63])}return n}function i(e){let t=[],r=0,i=0;for(let a of e){let e=n.get(a);if(e===void 0)throw Error(`Invalid base64url character "${a}"`);r=r<<6|e,i+=6,i>=8&&(i-=8,t.push(r>>i&255))}return new Uint8Array(t)}var a=new TextEncoder,o=new TextDecoder;function s(e){return r(a.encode(e))}function c(e){return o.decode(i(e))}function l(e){return a.encode(e).length}var u=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},d=class extends u{},f=class extends u{peerId;constructor(e,t=null,n){super(e,n),this.peerId=t}},p=class extends u{},m=class extends u{},h=class extends u{constructor(e=`Mesh node`,t){super(`${e} is disposed`,t)}},g=class extends u{};function _(e){return typeof e==`object`&&!!e}function v(e){return e.v===1&&typeof e.sessionId==`string`&&typeof e.secret==`string`&&typeof e.expiresAt==`number`&&typeof e.sdp==`string`}function y(e){return e.v===1&&typeof e.sessionId==`string`&&typeof e.proof==`string`&&_(e.peer)&&typeof e.peer.id==`string`&&(e.peer.name===void 0||typeof e.peer.name==`string`)&&typeof e.sdp==`string`}function b(e){if(!_(e)||e.v!==1)throw new d(`Unsupported pairing payload version`);if(v(e)||y(e))return e;throw new d(`Unrecognized pairing payload shape`)}var x={decode(e){let t;try{t=JSON.parse(c(e.trim()))}catch(e){throw new d(`Malformed pairing payload`,{cause:e})}return b(t)},encode(e){return s(JSON.stringify(e))}};function S(t,n){let r=new Uint8Array(t);if(n)for(let i=0;i<t;i++)r[i]=Math.floor((0,e.randomFloat)(n)*256);else crypto.getRandomValues(r);return r}function C(e){return r(S(16,e))}function w(e){if(typeof e!=`object`||!e)return null;let t=e;return t.v!==1||typeof t.id!=`string`||typeof t.t!=`string`||typeof t.ts!=`number`?null:e}var T=class{seen=new Set;order=[];capacity;constructor(e=256){this.capacity=e}check(e){return!this.seen.has(e)&&(this.seen.add(e),this.order.push(e),this.order.length>this.capacity&&this.seen.delete(this.order.shift()),!0)}};function E(e,t){let n=new Set,r=new AbortController,i={disposalCtrl:r,disposed:!1,emitTap(e){if(n.size!==0)for(let t of n)try{t(e)}catch{}},ensureLive(){if(i.disposed)throw new h},id:e,markDisposed(){i.disposed||(i.disposed=!0,i.status=`disposed`,i.emitTap({peerId:null,status:`disposed`,type:`status-change`}),i.emitTap({type:`dispose`}),n.clear(),r.abort())},setStatus(e){i.disposed||i.status===e||(i.status=e,i.emitTap({peerId:null,status:e,type:`status-change`}))},status:`idle`,tap(e,t){if(i.disposed)return()=>{};n.add(e);let r=()=>n.delete(e);if(t?.signal){if(t.signal.aborted)return r(),()=>{};let e=()=>r();return t.signal.addEventListener(`abort`,e,{once:!0}),()=>{r(),t.signal?.removeEventListener(`abort`,e)}}return r}};return t&&(t.aborted?i.markDisposed():t.addEventListener(`abort`,()=>i.markDisposed(),{once:!0})),i}function D(e,t,n){let r={dc:null,dedupe:new T,id:e,name:t,pc:null,publicPeer:{get id(){return r.id},get name(){return r.name},get role(){return r.role},get status(){return r.status}},role:n,status:`connecting`};return r}function O(e){let t=new Map;function n(t,n){e.emitTap({peerId:t,reason:n,type:`message-rejected`})}return{clear(){t.clear()},handleInbound(r,i){let a=e.peerIdOf(r);if(typeof i!=`string`){n(a,`malformed`);return}let o=l(i);if(o>e.maxMessageBytes){n(a,`too-large`);return}let s;try{s=e.deserialize(i)}catch{n(a,`malformed`);return}let c=w(s);if(!c){n(a,`malformed`);return}if(!r.dedupe.check(c.id)){n(a,`duplicate`);return}let d=t.get(c.t);if(!d||d.size===0){n(a,`unknown-type`);return}e.emitTap({bytes:o,messageType:c.t,peerId:a,type:`message-received`});let f={messageId:c.id,payload:c.p,peerId:a,sentAt:c.ts};for(let t of d)try{t(f)}catch(t){e.emitTap({error:new u(`Message handler threw`,{cause:t}),type:`error`})}},hasListeners(e){return(t.get(e)?.size??0)>0},on(e,n){let r=t.get(e);return r||(r=new Set,t.set(e,r)),r.add(n),()=>{r.delete(n),r.size===0&&t.delete(e)}},sendTo(t,n,r){let i=t.dc;if(!i||i.readyState!==`open`||t.status!==`connected`)throw new f(`Peer "${t.id}" is not connected`,t.id);let a={id:C(e.random),p:r,t:n,ts:e.clock(),v:1},o;try{o=e.serialize(a)}catch(e){throw new p(`Failed to serialize message "${n}"`,{cause:e})}let s=l(o);if(s>e.maxMessageBytes)throw new p(`Message "${n}" is ${s} bytes (max ${e.maxMessageBytes})`);try{i.send(o)}catch(e){throw new f(`Failed to send message "${n}"`,t.id,{cause:e})}e.emitTap({bytes:s,messageType:n,peerId:t.id,type:`message-sent`})},wireBytes(t,n){let r={id:C(e.random),p:n,t,ts:e.clock(),v:1};return l(e.serialize(r))}}}function k(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.u==`string`&&typeof t.p==`string`&&typeof t.f==`string`&&typeof t.s==`string`&&typeof t.m==`string`&&Array.isArray(t.c)&&t.c.every(e=>typeof e==`string`)}function A(e){if(typeof e==`string`)return e;if(k(e))return N(e);throw new d(`Malformed pairing payload`)}function j(e){let t=``,n=``,r=``,i=``,a=`0`,o,s,c=[],l=0;for(let u of e.split(/\r?\n/))u.startsWith(`m=`)?l+=1:u.startsWith(`a=ice-ufrag:`)?t=u.slice(12):u.startsWith(`a=ice-pwd:`)?n=u.slice(10):u.startsWith(`a=fingerprint:sha-256 `)?r=u.slice(22).replaceAll(`:`,``).toUpperCase():u.startsWith(`a=setup:`)?i=u.slice(8):u.startsWith(`a=mid:`)?a=u.slice(6):u.startsWith(`a=sctp-port:`)?o=Number(u.slice(12)):u.startsWith(`a=max-message-size:`)?s=Number(u.slice(19)):u.startsWith(`a=candidate:`)&&c.push(u.slice(12));return l!==1||!t||!n||!r?null:{c,f:r,m:a,p:n,s:i||`actpass`,...s?{mm:s}:{},...o?{sp:o}:{},u:t}}function M(e){let t=j(e);return t?JSON.stringify(t):e}function N(e){let t=e.f.replace(/(..)/g,`$1:`).slice(0,-1);return`${[`v=0`,`o=- 0 2 IN IP4 0.0.0.0`,`s=-`,`t=0 0`,`a=group:BUNDLE ${e.m}`,`m=application 9 UDP/DTLS/SCTP webrtc-datachannel`,`c=IN IP4 0.0.0.0`,`a=ice-ufrag:${e.u}`,`a=ice-pwd:${e.p}`,`a=fingerprint:sha-256 ${t}`,`a=setup:${e.s}`,`a=mid:${e.m}`,`a=sctp-port:${e.sp??5e3}`,...e.mm?[`a=max-message-size:${e.mm}`]:[],...e.c.map(e=>`a=candidate:${e}`),``].join(`\r
2
- `)}`}async function P(t,n,i){let a=M(n),o=globalThis.crypto?.subtle;if(o){let e=new TextEncoder,n=await o.importKey(`raw`,e.encode(t),{hash:`SHA-256`,name:`HMAC`},!1,[`sign`]),i=await o.sign(`HMAC`,n,e.encode(a));return r(new Uint8Array(i))}return i(),(0,e.hash)({sdp:a,secret:t})}var F={createPeerConnection(e){let t=globalThis.RTCPeerConnection;if(!t)throw new g("WebRTC is unavailable in this environment — inject an `rtc` factory");return new t(e)}};function I(e){let t=e.replace(/\r?\n/g,`\r
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@vielzeug/arsenal");var MeshError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},MeshPairingError=class extends MeshError{},MeshConnectionError=class extends MeshError{peerId;constructor(e,t=null,n){super(e,n),this.peerId=t}},MeshPayloadError=class extends MeshError{},MeshTimeoutError=class extends MeshError{},MeshDisposedError=class extends MeshError{constructor(e=`Mesh node`,t){super(`${e} is disposed`,t)}},MeshUnsupportedError=class extends MeshError{};function isRecord(e){return typeof e==`object`&&!!e}function isInvitation(e){return e.v===1&&typeof e.sessionId==`string`&&typeof e.secret==`string`&&typeof e.expiresAt==`number`&&typeof e.sdp==`string`}function isAnswer(e){return e.v===1&&typeof e.sessionId==`string`&&typeof e.proof==`string`&&isRecord(e.peer)&&typeof e.peer.id==`string`&&e.peer.id!==``&&(e.peer.name===void 0||typeof e.peer.name==`string`)&&typeof e.sdp==`string`}function parsePairingPayload(e){if(!isRecord(e)||e.v!==1)throw new MeshPairingError(`Unsupported pairing payload version`);if(isInvitation(e)||isAnswer(e))return e;throw new MeshPairingError(`Unrecognized pairing payload shape`)}var t={decode(t){let n;try{n=JSON.parse((0,e.base64UrlToText)(t.trim()))}catch(e){throw new MeshPairingError(`Malformed pairing payload`,{cause:e})}return parsePairingPayload(n)},encode(t){return(0,e.textToBase64Url)(JSON.stringify(t))}};function randomBytes(t,n){let r=new Uint8Array(t);if(n)for(let i=0;i<t;i++)r[i]=Math.floor((0,e.randomFloat)(n)*256);else crypto.getRandomValues(r);return r}function randomId(t){return(0,e.bytesToBase64Url)(randomBytes(16,t))}function toEnvelope(e){if(typeof e!=`object`||!e)return null;let t=e;return t.v!==1||typeof t.id!=`string`||typeof t.t!=`string`||typeof t.ts!=`number`?null:e}var MessageDedupe=class{seen=new Set;order=[];capacity;constructor(e=256){this.capacity=e}check(e){return!this.seen.has(e)&&(this.seen.add(e),this.order.push(e),this.order.length>this.capacity&&this.seen.delete(this.order.shift()),!0)}};function createNodeCore(t){let n=(0,e.tapper)(),r=new AbortController,i={disposalCtrl:r,disposed:!1,emitTap(e){n.emit(e)},ensureLive(){if(i.disposed)throw new MeshDisposedError},markDisposed(){i.disposed||(i.disposed=!0,i.status=`disposed`,i.emitTap({status:`disposed`,type:`status-change`}),i.emitTap({type:`dispose`}),n.clear(),r.abort())},setStatus(e){i.disposed||i.status===e||(i.status=e,i.emitTap({status:e,type:`status-change`}))},status:`idle`,tap(e,t){return i.disposed?()=>{}:n.tap(e,t)}};return t&&(t.aborted?i.markDisposed():t.addEventListener(`abort`,()=>i.markDisposed(),{once:!0})),i}function createPeerRecord(e,t,n){let r={dc:null,dedupe:new MessageDedupe,id:e,name:t,pc:null,publicPeer:{get id(){return r.id},get name(){return r.name},get role(){return r.role},get status(){return r.status}},role:n,status:`connecting`};return r}function createMessenger(t){let n=new Map;function reject(e,n){t.emitTap({peerId:e,reason:n,type:`message-rejected`})}return{clear(){n.clear()},handleInbound(r,i){let a=r.id;if(typeof i!=`string`){reject(a,`malformed`);return}let o=(0,e.utf8Bytes)(i);if(o>t.maxMessageBytes){reject(a,`too-large`);return}let s;try{s=JSON.parse(i)}catch{reject(a,`malformed`);return}let c=toEnvelope(s);if(!c){reject(a,`malformed`);return}if(!r.dedupe.check(c.id)){reject(a,`duplicate`);return}let l=n.get(c.t);if(!l||l.size===0){reject(a,`unknown-type`);return}t.emitTap({bytes:o,messageType:c.t,peerId:a,type:`message-received`});let u={messageId:c.id,payload:c.p,peerId:a,sentAt:c.ts};for(let e of l)try{e(u)}catch(e){t.emitTap({error:new MeshError(`Message handler threw`,{cause:e}),type:`error`})}},on(e,t){let r=n.get(e);return r||(r=new Set,n.set(e,r)),r.add(t),()=>{r.delete(t),r.size===0&&n.delete(e)}},sendTo(n,r,i){let a=n.dc;if(!a||a.readyState!==`open`||n.status!==`connected`)throw new MeshConnectionError(`Peer "${n.id}" is not connected`,n.id);let o={id:randomId(t.random),p:i,t:r,ts:t.clock(),v:1},s;try{s=JSON.stringify(o)}catch(e){throw new MeshPayloadError(`Failed to serialize message "${r}"`,{cause:e})}let c=(0,e.utf8Bytes)(s);if(c>t.maxMessageBytes)throw new MeshPayloadError(`Message "${r}" is ${c} bytes (max ${t.maxMessageBytes})`);try{a.send(s)}catch(e){throw new MeshConnectionError(`Failed to send message "${r}"`,n.id,{cause:e})}t.emitTap({bytes:c,messageType:r,peerId:n.id,type:`message-sent`})}}}function isCompactSdp(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.u==`string`&&typeof t.p==`string`&&typeof t.f==`string`&&typeof t.s==`string`&&typeof t.m==`string`&&Array.isArray(t.c)&&t.c.every(e=>typeof e==`string`)}function restoreSdp(e){if(typeof e==`string`)return e;if(isCompactSdp(e))return expandSdp(e);throw new MeshPairingError(`Malformed pairing payload`)}function compactSdp(e){let t=``,n=``,r=``,i=``,a=`0`,o,s,c=[],l=0;for(let u of e.split(/\r?\n/))u.startsWith(`m=`)?l+=1:u.startsWith(`a=ice-ufrag:`)?t=u.slice(12):u.startsWith(`a=ice-pwd:`)?n=u.slice(10):u.startsWith(`a=fingerprint:sha-256 `)?r=u.slice(22).replaceAll(`:`,``).toUpperCase():u.startsWith(`a=setup:`)?i=u.slice(8):u.startsWith(`a=mid:`)?a=u.slice(6):u.startsWith(`a=sctp-port:`)?o=Number(u.slice(12)):u.startsWith(`a=max-message-size:`)?s=Number(u.slice(19)):u.startsWith(`a=candidate:`)&&c.push(u.slice(12));return l!==1||!t||!n||!r?null:{c,f:r,m:a,p:n,s:i||`actpass`,...s?{mm:s}:{},...o?{sp:o}:{},u:t}}function canonicalSdp(e){let t=compactSdp(e);return t?JSON.stringify(t):e}function expandSdp(e){let t=e.f.replace(/(..)/g,`$1:`).slice(0,-1);return`${[`v=0`,`o=- 0 2 IN IP4 0.0.0.0`,`s=-`,`t=0 0`,`a=group:BUNDLE ${e.m}`,`m=application 9 UDP/DTLS/SCTP webrtc-datachannel`,`c=IN IP4 0.0.0.0`,`a=ice-ufrag:${e.u}`,`a=ice-pwd:${e.p}`,`a=fingerprint:sha-256 ${t}`,`a=setup:${e.s}`,`a=mid:${e.m}`,`a=sctp-port:${e.sp??5e3}`,...e.mm?[`a=max-message-size:${e.mm}`]:[],...e.c.map(e=>`a=candidate:${e}`),``].join(`\r
2
+ `)}`}async function createProof(t,n){let r=globalThis.crypto?.subtle;if(!r)throw new MeshUnsupportedError(`WebCrypto is unavailable — pairing requires a secure context`);let i=new TextEncoder,a=await r.importKey(`raw`,i.encode(t),{hash:`SHA-256`,name:`HMAC`},!1,[`sign`]),o=await r.sign(`HMAC`,a,i.encode(canonicalSdp(n)));return(0,e.bytesToBase64Url)(new Uint8Array(o))}var n={createPeerConnection(e){let t=globalThis.RTCPeerConnection;if(!t)throw new MeshUnsupportedError("WebRTC is unavailable in this environment — inject an `rtc` factory");return new t(e)}};function normalizeSdp(e){let t=e.replace(/\r?\n/g,`\r
3
3
  `);return t.endsWith(`\r
4
- `)?t:`${t}\r\n`}function L(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 R(e,t,n,r,i){return e.readyState===`open`?Promise.resolve():new Promise((a,o)=>{let s=e=>{c(),o(e)},c=()=>{clearTimeout(g),e.removeEventListener(`open`,l),e.removeEventListener(`close`,u),t.removeEventListener(`iceconnectionstatechange`,d),i.removeEventListener(`abort`,p)},l=()=>{c(),a()},u=()=>s(new f(`Data channel closed before opening`,r)),d=()=>{let e=t.iceConnectionState;(e===`failed`||e===`closed`||e===`disconnected`)&&s(new f(`ICE connection ${e} before channel open`,r))},p=()=>s(new h),g=setTimeout(()=>s(new m(`Timed out waiting ${n}ms for the data channel to open`)),n);e.addEventListener(`open`,l),e.addEventListener(`close`,u),t.addEventListener(`iceconnectionstatechange`,d),i.addEventListener(`abort`,p,{once:!0})})}var z=6e4,B=65536,V=5e3;function H(e={}){let t=e.rtc??F,n=e.clock??(()=>Date.now()),r=e.serialize??JSON.stringify,i=e.deserialize??JSON.parse,a=e.maxMessageBytes??B,o=e.channelOpenTimeoutMs??z,s=e.iceGatheringTimeoutMs??V,c=E(C(e.random),e.signal),l=O({clock:n,deserialize:i,emitTap:c.emitTap,maxMessageBytes:a,peerIdOf:e=>e.id,random:e.random,serialize:r}),u=null,p=null,g=!1;function _(){g||(g=!0,c.emitTap({reason:`SubtleCrypto unavailable — proof uses a non-cryptographic hash`,type:`security-downgrade`}))}function v(e,t){c.disposed||e.status===`failed`||(e.status=`failed`,p&&=(clearTimeout(p),null),setTimeout(()=>{e.dc?.close(),e.pc?.close()},0),c.emitTap({peerId:e.id,status:`failed`,type:`status-change`}),c.emitTap({error:t,type:`error`}),c.emitTap({peerId:e.id,reason:t.message,type:`peer-left`}),c.setStatus(`failed`))}function y(e,t){c.disposed||e.status===`disconnected`||e.status===`failed`||(e.status=`disconnected`,e.pc?.close(),c.emitTap({peerId:e.id,status:`disconnected`,type:`status-change`}),c.emitTap({peerId:e.id,reason:t,type:`peer-left`}),c.setStatus(`disconnected`))}function b(e,t,n){e.dc=n,n.addEventListener(`open`,()=>{c.disposed||(p&&=(clearTimeout(p),null),e.status=`connected`,c.emitTap({peerId:e.id,status:`connected`,type:`status-change`}),c.emitTap({peerId:e.id,type:`peer-joined`}),c.setStatus(`connected`))}),n.addEventListener(`message`,t=>{c.disposed||l.handleInbound(e,t.data)}),n.addEventListener(`close`,()=>{c.disposed||(e.status===`connecting`?v(e,p?new m(`Timed out waiting for the data channel to open`):new f(`channel closed before opening`,e.id)):y(e,`channel closed`))}),n.addEventListener(`error`,()=>{c.disposed||v(e,new f(`channel error`,e.id))}),t.addEventListener(`iceconnectionstatechange`,()=>{if(c.disposed)return;let n=t.iceConnectionState;c.emitTap({peerId:e.id,state:n,type:`ice-state`}),n===`failed`||n===`closed`?v(e,new f(`ice ${n}`,e.id)):n===`disconnected`&&e.status===`connected`&&y(e,`ice disconnected`)})}return{async acceptInvitation(r,i){if(c.ensureLive(),u)throw new d(`Guest is already paired`);if(r?.v!==1)throw new d(`Unsupported invitation version`);if(n()>=r.expiresAt)throw new d(`Invitation expired`);c.setStatus(`pairing`);let a;try{a=t.createPeerConnection({iceServers:[...e.iceServers??[]]})}catch(e){throw c.setStatus(`idle`),e}let l=D(r.sessionId,void 0,`host`);l.pc=a,u=l,a.addEventListener(`datachannel`,e=>{b(l,a,e.channel)}),a.addEventListener(`iceconnectionstatechange`,()=>{c.disposed||c.emitTap({peerId:l.id,state:a.iceConnectionState,type:`ice-state`})});try{await a.setRemoteDescription({sdp:I(r.sdp),type:`offer`})}catch(e){throw u=null,c.setStatus(`idle`),new d(`Invalid invitation SDP`,{cause:e})}let g;try{await a.setLocalDescription(await a.createAnswer()),await L(a,s),g=a.localDescription?.sdp??``}catch(e){throw u=null,a.close(),c.setStatus(`idle`),new f(`Failed to create answer`,null,{cause:e})}if(c.disposed)throw a.close(),new h;if(!g)throw u=null,a.close(),c.setStatus(`idle`),new f(`Local description has no SDP`);let y=C(e.random),x=await P(r.secret,g,_);return c.setStatus(`connecting`),p=setTimeout(()=>{u?.status===`connecting`&&v(u,new m(`Timed out waiting for the data channel to open`))},o),{peer:i?.name===void 0?{id:y}:{id:y,name:i.name},proof:x,sdp:g,sessionId:r.sessionId,v:1}},get disposalSignal(){return c.disposalCtrl.signal},dispose(){c.disposed||(c.markDisposed(),p&&=(clearTimeout(p),null),u&&(u.dc?.close(),u.pc?.close(),u.status=`disposed`),l.clear())},get disposed(){return c.disposed},get host(){return u?.publicPeer??null},get id(){return c.id},on(e,t){return c.ensureLive(),l.on(e,t)},send(e,t){if(c.ensureLive(),!u)throw new f(`Guest is not paired`);l.sendTo(u,e,t)},get status(){return c.status},tap(e,t){return c.tap(e,t)},[Symbol.dispose](){this.dispose()}}}var U=65536,W=3e5,G=6e4,K=5e3;function q(e={}){let t=e.rtc??F,n=e.clock??(()=>Date.now()),i=e.serialize??JSON.stringify,a=e.deserialize??JSON.parse,o=e.maxMessageBytes??U,s=e.invitationTtlMs??W,c=e.channelOpenTimeoutMs??G,l=e.iceGatheringTimeoutMs??K,u={label:`mesh`,ordered:!0,...e.channel},p=E(C(e.random),e.signal),m=O({clock:n,deserialize:a,emitTap:p.emitTap,maxMessageBytes:o,peerIdOf:e=>e.id,random:e.random,serialize:i}),g=new Map,_=new Map,v=new Set,y=new Set,b=!1,x=!1,w=!1;function T(e){for(let t of y)try{t(e)}catch(e){p.emitTap({error:new f(`Peer listener threw`,null,{cause:e}),type:`error`})}}function k(){let e=_.size>0?`pairing`:b?`disconnected`:x?`failed`:`idle`;for(let t of g.values()){if(t.status===`connected`){e=`connected`;break}t.status===`connecting`&&(e=`connecting`)}p.setStatus(e)}function A(){w||(w=!0,p.emitTap({reason:`SubtleCrypto unavailable — proof uses a non-cryptographic hash`,type:`security-downgrade`}))}function j(e){let t=_.get(e);t&&(_.delete(e),v.add(e),clearTimeout(t.expiryTimer),t.pc.close(),p.emitTap({sessionId:e,type:`invitation-expired`}),k())}function M(e,t){e.status!==t&&(e.status=t,p.emitTap({peerId:e.id,status:t,type:`status-change`}),T({peer:e.publicPeer,type:`status-change`}),k())}function N(e,t,n=!1){g.has(e.id)&&(g.delete(e.id),e.status=n?`failed`:`disconnected`,e.pc?.close(),e.dc?.close(),p.emitTap({peerId:e.id,status:e.status,type:`status-change`}),p.emitTap({peerId:e.id,reason:t,type:`peer-left`}),T({peer:e.publicPeer,reason:t,type:`left`}),n||(b=!0),x||=n,k())}function z(e,t,n){e.pc=t,e.dc=n,n.addEventListener(`message`,t=>{p.disposed||m.handleInbound(e,t.data)}),n.addEventListener(`close`,()=>{p.disposed||N(e,`channel closed`)}),n.addEventListener(`error`,()=>{p.disposed||N(e,`channel error`,!0)}),t.addEventListener(`iceconnectionstatechange`,()=>{if(p.disposed)return;let n=t.iceConnectionState;p.emitTap({peerId:e.id,state:n,type:`ice-state`}),n===`failed`||n===`closed`?N(e,`ice ${n}`,n===`failed`):n===`disconnected`&&e.status===`connected`&&M(e,`disconnected`)})}function B(e){b=!0,g.set(e.id,e),M(e,`connected`),p.emitTap({peerId:e.id,type:`peer-joined`}),T({peer:e.publicPeer,type:`joined`})}return{async acceptAnswer(t){if(p.ensureLive(),t?.v!==1)throw new d(`Unsupported answer version`);let r=_.get(t.sessionId);if(!r)throw new d(v.has(t.sessionId)?`Invitation expired`:`Unknown session "${t.sessionId}"`);if(r.answered)throw new d(`Invitation already answered`);if(n()>=r.expiresAt)throw j(t.sessionId),new d(`Invitation expired`);let i=await P(r.secret,t.sdp,A);if(t.proof!==i)throw new d(`Proof mismatch`);if(!(await e.approvePeer?.(t.peer)??!0))throw p.emitTap({peerId:t.peer.id,type:`peer-rejected`}),new d(`Peer "${t.peer.id}" rejected by host`);p.emitTap({peerId:t.peer.id,type:`peer-approved`}),r.answered=!0,_.delete(t.sessionId),clearTimeout(r.expiryTimer);let a=D(t.peer.id,t.peer.name,`guest`),o=R(r.dc,r.pc,c,a.id,p.disposalCtrl.signal);z(a,r.pc,r.dc),g.set(a.id,a),k();try{await r.pc.setRemoteDescription({sdp:I(t.sdp),type:`answer`})}catch(e){throw N(a,`invalid answer sdp`,!0),new d(`Invalid answer SDP`,{cause:e})}try{await o}catch(e){let t=!(e instanceof h);throw N(a,e instanceof Error?e.message:`open failed`,t),e}return B(a),a.publicPeer},broadcast(e,t,n){p.ensureLive();for(let r of g.values())r.status!==`connected`||n?.except?.includes(r.id)||m.sendTo(r,e,t)},async createInvitation(i){p.ensureLive();let a=t.createPeerConnection({iceServers:[...e.iceServers??[]]}),o=a.createDataChannel(u.label,{maxRetransmits:u.maxRetransmits,ordered:u.ordered}),c=C(e.random),d=r(S(32,e.random)),m=n()+s;a.addEventListener(`iceconnectionstatechange`,()=>{p.disposed||p.emitTap({peerId:c,state:a.iceConnectionState,type:`ice-state`})});try{await a.setLocalDescription(await a.createOffer()),await L(a,l)}catch(e){throw a.close(),new f(`Failed to create offer`,null,{cause:e})}if(p.disposed)throw a.close(),new h;let g=a.localDescription?.sdp;if(!g)throw a.close(),new f(`Local description has no SDP`);let v=setTimeout(()=>j(c),s);return _.set(c,{answered:!1,dc:o,expiresAt:m,expiryTimer:v,pc:a,secret:d,sessionId:c}),p.emitTap({sessionId:c,type:`invitation-created`}),k(),{expiresAt:m,sdp:g,secret:d,sessionId:c,v:1}},get disposalSignal(){return p.disposalCtrl.signal},dispose(){if(!p.disposed){p.markDisposed();for(let e of _.values())clearTimeout(e.expiryTimer),e.pc.close();_.clear();for(let e of g.values())e.dc?.close(),e.pc?.close(),e.status=`disposed`;g.clear(),y.clear(),m.clear()}},get disposed(){return p.disposed},get id(){return p.id},kick(e,t){p.ensureLive();let n=g.get(e);if(!n)throw new f(`Unknown peer "${e}"`,e);N(n,t??`kicked`)},on(e,t){return p.ensureLive(),m.on(e,t)},onPeer(e){return p.ensureLive(),y.add(e),()=>y.delete(e)},get peers(){return g},send(e,t,n){p.ensureLive();let r=g.get(e);if(!r)throw new f(`Unknown peer "${e}"`,e);m.sendTo(r,t,n)},get status(){return p.status},tap(e,t){return p.tap(e,t)},[Symbol.dispose](){this.dispose()}}}var J=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:`,ee=new Map([...J].map((e,t)=>[e,t]));function te(e){let t=``;for(let n=0;n+1<e.length;n+=2){let r=e[n]*256+e[n+1];t+=J[r%45]+J[Math.floor(r/45)%45]+J[Math.floor(r/2025)]}if(e.length%2){let n=e[e.length-1];t+=J[n%45]+J[Math.floor(n/45)]}return t}function ne(e){let t=[],n=0;for(;n+2<e.length;){let r=Y(e,n)+45*Y(e,n+1)+2025*Y(e,n+2);if(r>65535)throw new d(`Malformed base45 payload`);t.push(r>>8,r&255),n+=3}if(e.length-n===2){let r=Y(e,n)+45*Y(e,n+1);if(r>255)throw new d(`Malformed base45 payload`);t.push(r)}else if(e.length-n===1)throw new d(`Malformed base45 payload`);return new Uint8Array(t)}function Y(e,t){let n=ee.get(e[t]);if(n===void 0)throw new d(`Malformed base45 payload`);return n}var re=`mq1.`,X=`mq2.`,ie=new TextEncoder,Z=new TextDecoder;function ae(e){if(typeof globalThis.CompressionStream!=`function`)throw new g(`CompressionStream is not available in this environment`);return new globalThis.CompressionStream(e)}function Q(e){if(typeof globalThis.DecompressionStream!=`function`)throw new g(`DecompressionStream is not available in this environment`);return new globalThis.DecompressionStream(e)}async function $(e,t){let n=t.writable.getWriter(),r=n.write(e).then(()=>n.close()).catch(()=>{}),i=[],a=t.readable.getReader();for(;;){let{done:e,value:t}=await a.read();if(e)break;i.push(t)}await r;let o=new Uint8Array(i.reduce((e,t)=>e+t.length,0)),s=0;for(let e of i)o.set(e,s),s+=e.length;return o}function oe(e){return typeof e==`object`&&!!e}var se={async decode(e){let t=e.trim();if(t.startsWith(X)){let e;try{let n=ne(t.slice(4));e=JSON.parse(Z.decode(await $(n,Q(`deflate-raw`))))}catch(e){throw e instanceof g||e instanceof d?e:new d(`Malformed pairing payload`,{cause:e})}return oe(e)&&(e={...e,sdp:A(e.sdp)}),b(e)}if(!t.startsWith(re))return x.decode(t);let n;try{let e=i(t.slice(4));n=JSON.parse(Z.decode(await $(e,Q(`deflate-raw`))))}catch(e){throw e instanceof g?e:new d(`Malformed pairing payload`,{cause:e})}return b(n)},async encode(e){let t=j(e.sdp);return X+te(await $(ie.encode(JSON.stringify(t?{...e,sdp:t}:e)),ae(`deflate-raw`)))}};exports.MeshConnectionError=f,exports.MeshDisposedError=h,exports.MeshError=u,exports.MeshPairingError=d,exports.MeshPayloadError=p,exports.MeshTimeoutError=m,exports.MeshUnsupportedError=g,exports.createMeshGuest=H,exports.createMeshHost=q,exports.meshCodec=x,exports.meshQrCodec=se;
4
+ `)?t:`${t}\r\n`}function waitIceGathering(e,t){return e.iceGatheringState===`complete`?Promise.resolve():new Promise(n=>{let cleanup=()=>{clearTimeout(r),e.removeEventListener(`icegatheringstatechange`,onChange)},onChange=()=>{e.iceGatheringState===`complete`&&(cleanup(),n())},r=setTimeout(()=>{cleanup(),n()},t);e.addEventListener(`icegatheringstatechange`,onChange)})}function waitChannelOpen(e,t,n,r,i){return e.readyState===`open`?Promise.resolve():new Promise((a,o)=>{let s=null,timeout=()=>new MeshTimeoutError(`Timed out waiting ${n}ms for the data channel to open`),fail=e=>{cleanup(),o(e)},cleanup=()=>{s&&clearTimeout(s),s=null,e.removeEventListener(`open`,onOpen),e.removeEventListener(`close`,onClose),t.removeEventListener(`iceconnectionstatechange`,onIce),i.removeEventListener(`abort`,onDispose)},onOpen=()=>{cleanup(),a()},onClose=()=>fail(timeout()),onIce=()=>{let e=t.iceConnectionState;(e===`failed`||e===`closed`||e===`disconnected`)&&fail(new MeshConnectionError(`ICE connection ${e} before channel open`,r))},onDispose=()=>fail(new MeshDisposedError);s=setTimeout(()=>fail(timeout()),n),e.addEventListener(`open`,onOpen),e.addEventListener(`close`,onClose),t.addEventListener(`iceconnectionstatechange`,onIce),i.addEventListener(`abort`,onDispose,{once:!0})})}function createMeshGuest(e={}){let t=e.rtc??n,r=e.clock??(()=>Date.now()),i=e.maxMessageBytes??65536,a=e.channelOpenTimeoutMs??6e4,o=e.iceGatheringTimeoutMs??5e3,s=createNodeCore(e.signal),c=createMessenger({clock:r,emitTap:s.emitTap,maxMessageBytes:i,random:e.random}),l=null,u=null,openTimeoutError=()=>new MeshTimeoutError(`Timed out waiting ${a}ms for the data channel to open`);function failPeer(e,t){s.disposed||e!==l||e.status===`failed`||(e.status=`failed`,u&&=(clearTimeout(u),null),setTimeout(()=>{e.dc?.close(),e.pc?.close()},0),s.emitTap({peerId:e.id,status:`failed`,type:`peer-status-change`}),s.emitTap({error:t,type:`error`}),s.emitTap({peer:e.publicPeer,reason:t.message,type:`peer-left`}),s.setStatus(`failed`))}function disconnectPeer(e,t){s.disposed||e!==l||e.status!==`disconnected`&&e.status!==`failed`&&(e.status=`disconnected`,e.pc?.close(),s.emitTap({peerId:e.id,status:`disconnected`,type:`peer-status-change`}),s.emitTap({peer:e.publicPeer,reason:t,type:`peer-left`}),s.setStatus(`disconnected`))}function wireChannel(e,t,n){e.dc=n,n.addEventListener(`open`,()=>{s.disposed||(u&&=(clearTimeout(u),null),e.status=`connected`,s.emitTap({peerId:e.id,status:`connected`,type:`peer-status-change`}),s.emitTap({peer:e.publicPeer,type:`peer-joined`}),s.setStatus(`connected`))}),n.addEventListener(`message`,t=>{s.disposed||c.handleInbound(e,t.data)}),n.addEventListener(`close`,()=>{s.disposed||(e.status===`connecting`?failPeer(e,u?openTimeoutError():new MeshConnectionError(`channel closed before opening`,e.id)):disconnectPeer(e,`channel closed`))}),n.addEventListener(`error`,()=>{s.disposed||failPeer(e,new MeshConnectionError(`channel error`,e.id))}),t.addEventListener(`iceconnectionstatechange`,()=>{if(s.disposed)return;let n=t.iceConnectionState;n===`failed`||n===`closed`?failPeer(e,new MeshConnectionError(`ice ${n}`,e.id)):n===`disconnected`&&e.status===`connected`&&disconnectPeer(e,`ice disconnected`)})}return{async acceptInvitation(n,i){if(s.ensureLive(),l&&l.status!==`failed`&&l.status!==`disconnected`)throw new MeshPairingError(`Guest is already paired`);if(n?.v!==1)throw new MeshPairingError(`Unsupported invitation version`);if(r()>=n.expiresAt)throw new MeshPairingError(`Invitation expired`);l&&=(u&&=(clearTimeout(u),null),l.pc?.close(),null),s.setStatus(`pairing`);let c;try{c=t.createPeerConnection({iceServers:[...e.iceServers??[]]})}catch(e){throw s.setStatus(`idle`),e}let d=createPeerRecord(n.sessionId,n.hostName,`host`);d.pc=c,l=d,c.addEventListener(`datachannel`,e=>{wireChannel(d,c,e.channel)}),c.addEventListener(`iceconnectionstatechange`,()=>{s.disposed||s.emitTap({peerId:d.id,state:c.iceConnectionState,type:`ice-state`})});try{await c.setRemoteDescription({sdp:normalizeSdp(n.sdp),type:`offer`})}catch(e){throw l=null,s.setStatus(`idle`),new MeshPairingError(`Invalid invitation SDP`,{cause:e})}let f;try{await c.setLocalDescription(await c.createAnswer()),await waitIceGathering(c,o),f=c.localDescription?.sdp??``}catch(e){throw l=null,c.close(),s.setStatus(`idle`),new MeshConnectionError(`Failed to create answer`,null,{cause:e})}if(s.disposed)throw c.close(),new MeshDisposedError;if(!f)throw l=null,c.close(),s.setStatus(`idle`),new MeshConnectionError(`Local description has no SDP`);let p=randomId(e.random),m;try{m=await createProof(n.secret,f)}catch(e){throw l=null,c.close(),s.setStatus(`idle`),e}return s.setStatus(`connecting`),u=setTimeout(()=>{l?.status===`connecting`&&failPeer(l,openTimeoutError())},a),{peer:i?.name===void 0?{id:p}:{id:p,name:i.name},proof:m,sdp:f,sessionId:n.sessionId,v:1}},get disposalSignal(){return s.disposalCtrl.signal},dispose(){s.disposed||(s.markDisposed(),u&&=(clearTimeout(u),null),l&&(l.dc?.close(),l.pc?.close()),c.clear())},get disposed(){return s.disposed},get host(){return l?.publicPeer??null},on(e,t){return s.ensureLive(),c.on(e,t)},send(e,t){if(s.ensureLive(),!l)throw new MeshConnectionError(`Guest is not paired`);c.sendTo(l,e,t)},get status(){return s.status},tap(e,t){return s.tap(e,t)},[Symbol.dispose](){this.dispose()}}}function createMeshHost(t={}){let r=t.rtc??n,i=t.clock??(()=>Date.now()),a=t.maxMessageBytes??65536,o=t.invitationTtlMs??3e5,s=t.channelOpenTimeoutMs??6e4,c=t.iceGatheringTimeoutMs??5e3,l={label:`mesh`,ordered:!0,...t.channel},u=createNodeCore(t.signal),d=createMessenger({clock:i,emitTap:u.emitTap,maxMessageBytes:a,random:t.random}),f=new Map,p=new Map,m=!1,h=!1;function recomputeStatus(){let e=p.size>0?`pairing`:m?`disconnected`:h?`failed`:`idle`;for(let t of f.values()){if(t.status===`connected`){e=`connected`;break}t.status===`connecting`&&(e=`connecting`)}u.setStatus(e)}function expireInvitation(e){let t=p.get(e);t&&(p.delete(e),clearTimeout(t.expiryTimer),t.pc.close(),u.emitTap({sessionId:e,type:`invitation-expired`}),recomputeStatus())}function setPeerStatus(e,t){e.status!==t&&(e.status=t,u.emitTap({peerId:e.id,status:t,type:`peer-status-change`}),recomputeStatus())}function leavePeer(e,t,n=!1){f.has(e.id)&&(f.delete(e.id),e.status=n?`failed`:`disconnected`,e.pc?.close(),e.dc?.close(),u.emitTap({peerId:e.id,status:e.status,type:`peer-status-change`}),u.emitTap({peer:e.publicPeer,reason:t,type:`peer-left`}),n||(m=!0),h||=n,recomputeStatus())}function wirePeerTransport(e,t,n){e.pc=t,e.dc=n,n.addEventListener(`message`,t=>{u.disposed||d.handleInbound(e,t.data)}),n.addEventListener(`close`,()=>{u.disposed||leavePeer(e,`channel closed`)}),n.addEventListener(`error`,()=>{u.disposed||leavePeer(e,`channel error`,!0)}),t.addEventListener(`iceconnectionstatechange`,()=>{if(u.disposed)return;let n=t.iceConnectionState;u.emitTap({peerId:e.id,state:n,type:`ice-state`}),n===`failed`||n===`closed`?leavePeer(e,`ice ${n}`,n===`failed`):n===`disconnected`&&e.status===`connected`&&setPeerStatus(e,`disconnected`)})}function markPeerConnected(e){m=!0,f.set(e.id,e),setPeerStatus(e,`connected`),u.emitTap({peer:e,type:`peer-joined`})}return{async acceptAnswer(e){if(u.ensureLive(),e?.v!==1)throw new MeshPairingError(`Unsupported answer version`);if(!e.peer?.id)throw new MeshPairingError(`Answer is missing a peer id`);let n=p.get(e.sessionId);if(!n)throw new MeshPairingError(`Unknown or expired session`);if(n.answered)throw new MeshPairingError(`Invitation already answered`);if(f.has(e.peer.id))throw new MeshPairingError(`Peer "${e.peer.id}" is already connected`);if(i()>=n.expiresAt)throw expireInvitation(e.sessionId),new MeshPairingError(`Invitation expired`);let r=await createProof(n.secret,e.sdp);if(e.proof!==r)throw new MeshPairingError(`Proof mismatch`);if(!(await t.approvePeer?.(e.peer)??!0))throw u.emitTap({peerId:e.peer.id,type:`peer-rejected`}),new MeshPairingError(`Peer "${e.peer.id}" rejected by host`);if(u.emitTap({peerId:e.peer.id,type:`peer-approved`}),f.has(e.peer.id))throw new MeshPairingError(`Peer "${e.peer.id}" is already connected`);n.answered=!0,p.delete(e.sessionId),clearTimeout(n.expiryTimer);let a=createPeerRecord(e.peer.id,e.peer.name,`guest`),o=waitChannelOpen(n.dc,n.pc,s,a.id,u.disposalCtrl.signal);wirePeerTransport(a,n.pc,n.dc),f.set(a.id,a),recomputeStatus();try{await n.pc.setRemoteDescription({sdp:normalizeSdp(e.sdp),type:`answer`})}catch(e){throw leavePeer(a,`invalid answer sdp`,!0),new MeshPairingError(`Invalid answer SDP`,{cause:e})}try{await o}catch(e){let t=!(e instanceof MeshDisposedError);throw leavePeer(a,e instanceof Error?e.message:`open failed`,t),e}return markPeerConnected(a),a.publicPeer},broadcast(e,t,n){u.ensureLive();for(let r of f.values())r.status!==`connected`||n?.except?.includes(r.id)||d.sendTo(r,e,t)},async createInvitation(n){u.ensureLive();let a=r.createPeerConnection({iceServers:[...t.iceServers??[]]}),s=a.createDataChannel(l.label,{maxRetransmits:l.maxRetransmits,ordered:l.ordered}),d=randomId(t.random),f=(0,e.bytesToBase64Url)(randomBytes(32,t.random)),m=i()+o;try{await a.setLocalDescription(await a.createOffer()),await waitIceGathering(a,c)}catch(e){throw a.close(),new MeshConnectionError(`Failed to create offer`,null,{cause:e})}if(u.disposed)throw a.close(),new MeshDisposedError;let h=a.localDescription?.sdp;if(!h)throw a.close(),new MeshConnectionError(`Local description has no SDP`);let g=setTimeout(()=>expireInvitation(d),o);return p.set(d,{answered:!1,dc:s,expiresAt:m,expiryTimer:g,pc:a,secret:f,sessionId:d}),u.emitTap({sessionId:d,type:`invitation-created`}),recomputeStatus(),{expiresAt:m,sdp:h,secret:f,sessionId:d,v:1,...n?.name===void 0?{}:{hostName:n.name}}},get disposalSignal(){return u.disposalCtrl.signal},dispose(){if(!u.disposed){u.markDisposed();for(let e of p.values())clearTimeout(e.expiryTimer),e.pc.close();p.clear();for(let e of f.values())e.dc?.close(),e.pc?.close();f.clear(),d.clear()}},get disposed(){return u.disposed},kick(e,t){u.ensureLive();let n=f.get(e);if(!n)throw new MeshConnectionError(`Unknown peer "${e}"`,e);leavePeer(n,t??`kicked`)},on(e,t){return u.ensureLive(),d.on(e,t)},get peers(){return f},send(e,t,n){u.ensureLive();let r=f.get(e);if(!r)throw new MeshConnectionError(`Unknown peer "${e}"`,e);d.sendTo(r,t,n)},get status(){return u.status},tap(e,t){return u.tap(e,t)},[Symbol.dispose](){this.dispose()}}}var r=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:`,i=new Map([...r].map((e,t)=>[e,t]));function bytesToBase45(e){let t=``;for(let n=0;n+1<e.length;n+=2){let i=e[n]*256+e[n+1];t+=r[i%45]+r[Math.floor(i/45)%45]+r[Math.floor(i/2025)]}if(e.length%2){let n=e[e.length-1];t+=r[n%45]+r[Math.floor(n/45)]}return t}function base45ToBytes(e){let t=[],n=0;for(;n+2<e.length;){let r=digit(e,n)+45*digit(e,n+1)+2025*digit(e,n+2);if(r>65535)throw new MeshPairingError(`Malformed base45 payload`);t.push(r>>8,r&255),n+=3}if(e.length-n===2){let r=digit(e,n)+45*digit(e,n+1);if(r>255)throw new MeshPairingError(`Malformed base45 payload`);t.push(r)}else if(e.length-n===1)throw new MeshPairingError(`Malformed base45 payload`);return new Uint8Array(t)}function digit(e,t){let n=i.get(e[t]);if(n===void 0)throw new MeshPairingError(`Malformed base45 payload`);return n}var a=`mq2.`,o=new TextEncoder,s=new TextDecoder;function decompressionStream(e){if(typeof globalThis.DecompressionStream!=`function`)throw new MeshUnsupportedError(`DecompressionStream is not available in this environment`);return new globalThis.DecompressionStream(e)}async function pump(e,t){let n=t.writable.getWriter(),r=n.write(e).then(()=>n.close()).catch(()=>{}),i=[],a=t.readable.getReader();for(;;){let{done:e,value:t}=await a.read();if(e)break;i.push(t)}await r;let o=new Uint8Array(i.reduce((e,t)=>e+t.length,0)),s=0;for(let e of i)o.set(e,s),s+=e.length;return o}var c={async decode(e){let n=e.trim();if(n.startsWith(a)){let e;try{let t=base45ToBytes(n.slice(4));e=JSON.parse(s.decode(await pump(t,decompressionStream(`deflate-raw`))))}catch(e){throw e instanceof MeshUnsupportedError||e instanceof MeshPairingError?e:new MeshPairingError(`Malformed pairing payload`,{cause:e})}return isRecord(e)&&(e={...e,sdp:restoreSdp(e.sdp)}),parsePairingPayload(e)}return t.decode(n)},async encode(e){if(typeof globalThis.CompressionStream!=`function`)return t.encode(e);let n=compactSdp(e.sdp);return a+bytesToBase45(await pump(o.encode(JSON.stringify(n?{...e,sdp:n}:e)),new globalThis.CompressionStream(`deflate-raw`)))}};exports.MeshConnectionError=MeshConnectionError,exports.MeshDisposedError=MeshDisposedError,exports.MeshError=MeshError,exports.MeshPairingError=MeshPairingError,exports.MeshPayloadError=MeshPayloadError,exports.MeshTimeoutError=MeshTimeoutError,exports.MeshUnsupportedError=MeshUnsupportedError,exports.createMeshGuest=createMeshGuest,exports.createMeshHost=createMeshHost,exports.meshCodec=t,exports.meshQrCodec=c;
5
5
  //# sourceMappingURL=mesh.cjs.map