@vielzeug/mesh 26.11.5 → 26.11.6

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/dist/guest.js CHANGED
@@ -1,17 +1,17 @@
1
- import { MeshConnectionError as e, MeshDisposedError as t, MeshPairingError as n, MeshTimeoutError as r } from "./errors.js";
2
1
  import "./_defaults.js";
2
+ import { createMessenger as e, createNodeCore as t, createPeerRecord as n } from "./_node.js";
3
+ import { createProof as r } from "./_proof.js";
3
4
  import { randomId as i } from "./_random.js";
4
- import { createMessenger as a, createNodeCore as o, createPeerRecord as s } from "./_node.js";
5
- import { createProof as c } from "./_proof.js";
6
- import { nativeRtcFactory as l, normalizeSdp as u, waitIceGathering as d } from "./_rtc.js";
7
- //#region src/guest.ts
5
+ import { nativeRtcFactory as a, normalizeSdp as o, waitIceGathering as s } from "./_rtc.js";
6
+ import { MeshConnectionError as c, MeshDisposedError as l, MeshPairingError as u, MeshTimeoutError as d } from "./errors.js";
7
+ // src/guest.ts
8
8
  function createMeshGuest(f = {}) {
9
- let p = f.rtc ?? l, m = f.clock ?? (() => Date.now()), h = f.maxMessageBytes ?? 65536, g = f.channelOpenTimeoutMs ?? 6e4, _ = f.iceGatheringTimeoutMs ?? 5e3, v = o(f.signal), y = a({
9
+ let p = f.rtc ?? a, m = f.clock ?? (() => Date.now()), h = f.maxMessageBytes ?? 65536, g = f.channelOpenTimeoutMs ?? 6e4, _ = f.iceGatheringTimeoutMs ?? 5e3, v = t(f.signal), y = e({
10
10
  clock: m,
11
11
  emitTap: v.emitTap,
12
12
  maxMessageBytes: h,
13
13
  random: f.random
14
- }), b = null, x = null, openTimeoutError = () => new r(`Timed out waiting ${g}ms for the data channel to open`);
14
+ }), b = null, x = null, openTimeoutError = () => new d(`Timed out waiting ${g}ms for the data channel to open`);
15
15
  function failPeer(e, t) {
16
16
  v.disposed || e !== b || e.status === "failed" || (e.status = "failed", x &&= (clearTimeout(x), null), setTimeout(() => {
17
17
  e.dc?.close(), e.pc?.close();
@@ -39,82 +39,82 @@ function createMeshGuest(f = {}) {
39
39
  type: "peer-left"
40
40
  }), v.setStatus("disconnected"));
41
41
  }
42
- function wireChannel(t, n, r) {
43
- t.dc = r, r.addEventListener("open", () => {
44
- v.disposed || (x &&= (clearTimeout(x), null), t.status = "connected", v.emitTap({
45
- peerId: t.id,
42
+ function wireChannel(e, t, n) {
43
+ e.dc = n, n.addEventListener("open", () => {
44
+ v.disposed || (x &&= (clearTimeout(x), null), e.status = "connected", v.emitTap({
45
+ peerId: e.id,
46
46
  status: "connected",
47
47
  type: "peer-status-change"
48
48
  }), v.emitTap({
49
- peer: t.publicPeer,
49
+ peer: e.publicPeer,
50
50
  type: "peer-joined"
51
51
  }), v.setStatus("connected"));
52
- }), r.addEventListener("message", (e) => {
53
- v.disposed || y.handleInbound(t, e.data);
54
- }), r.addEventListener("close", () => {
55
- v.disposed || (t.status === "connecting" ? failPeer(t, x ? openTimeoutError() : new e("channel closed before opening", t.id)) : disconnectPeer(t, "channel closed"));
56
- }), r.addEventListener("error", () => {
57
- v.disposed || failPeer(t, new e("channel error", t.id));
58
- }), n.addEventListener("iceconnectionstatechange", () => {
52
+ }), n.addEventListener("message", (t) => {
53
+ v.disposed || y.handleInbound(e, t.data);
54
+ }), n.addEventListener("close", () => {
55
+ v.disposed || (e.status === "connecting" ? failPeer(e, x ? openTimeoutError() : new c("channel closed before opening", e.id)) : disconnectPeer(e, "channel closed"));
56
+ }), n.addEventListener("error", () => {
57
+ v.disposed || failPeer(e, new c("channel error", e.id));
58
+ }), t.addEventListener("iceconnectionstatechange", () => {
59
59
  if (v.disposed) return;
60
- let r = n.iceConnectionState;
61
- r === "failed" || r === "closed" ? failPeer(t, new e(`ice ${r}`, t.id)) : r === "disconnected" && t.status === "connected" && disconnectPeer(t, "ice disconnected");
60
+ let n = t.iceConnectionState;
61
+ n === "failed" || n === "closed" ? failPeer(e, new c(`ice ${n}`, e.id)) : n === "disconnected" && e.status === "connected" && disconnectPeer(e, "ice disconnected");
62
62
  });
63
63
  }
64
64
  return {
65
- async acceptInvitation(r, a) {
66
- if (v.ensureLive(), b && b.status !== "failed" && b.status !== "disconnected") throw new n("Guest is already paired");
67
- if (r?.v !== 1) throw new n("Unsupported invitation version");
68
- if (m() >= r.expiresAt) throw new n("Invitation expired");
65
+ async acceptInvitation(e, t) {
66
+ if (v.ensureLive(), b && b.status !== "failed" && b.status !== "disconnected") throw new u("Guest is already paired");
67
+ if (e?.v !== 1) throw new u("Unsupported invitation version");
68
+ if (m() >= e.expiresAt) throw new u("Invitation expired");
69
69
  b &&= (x &&= (clearTimeout(x), null), b.pc?.close(), null), v.setStatus("pairing");
70
- let o;
70
+ let a;
71
71
  try {
72
- o = p.createPeerConnection({ iceServers: [...f.iceServers ?? []] });
72
+ a = p.createPeerConnection({ iceServers: [...f.iceServers ?? []] });
73
73
  } catch (e) {
74
74
  throw v.setStatus("idle"), e;
75
75
  }
76
- let l = s(r.sessionId, r.hostName, "host");
77
- l.pc = o, b = l, o.addEventListener("datachannel", (e) => {
78
- wireChannel(l, o, e.channel);
79
- }), o.addEventListener("iceconnectionstatechange", () => {
76
+ let d = n(e.sessionId, e.hostName, "host");
77
+ d.pc = a, b = d, a.addEventListener("datachannel", (e) => {
78
+ wireChannel(d, a, e.channel);
79
+ }), a.addEventListener("iceconnectionstatechange", () => {
80
80
  v.disposed || v.emitTap({
81
- peerId: l.id,
82
- state: o.iceConnectionState,
81
+ peerId: d.id,
82
+ state: a.iceConnectionState,
83
83
  type: "ice-state"
84
84
  });
85
85
  });
86
86
  try {
87
- await o.setRemoteDescription({
88
- sdp: u(r.sdp),
87
+ await a.setRemoteDescription({
88
+ sdp: o(e.sdp),
89
89
  type: "offer"
90
90
  });
91
91
  } catch (e) {
92
- throw b = null, v.setStatus("idle"), new n("Invalid invitation SDP", { cause: e });
92
+ throw b = null, v.setStatus("idle"), new u("Invalid invitation SDP", { cause: e });
93
93
  }
94
94
  let h;
95
95
  try {
96
- await o.setLocalDescription(await o.createAnswer()), await d(o, _), h = o.localDescription?.sdp ?? "";
97
- } catch (t) {
98
- throw b = null, o.close(), v.setStatus("idle"), new e("Failed to create answer", null, { cause: t });
96
+ await a.setLocalDescription(await a.createAnswer()), await s(a, _), h = a.localDescription?.sdp ?? "";
97
+ } catch (e) {
98
+ throw b = null, a.close(), v.setStatus("idle"), new c("Failed to create answer", null, { cause: e });
99
99
  }
100
- if (v.disposed) throw o.close(), new t();
101
- if (!h) throw b = null, o.close(), v.setStatus("idle"), new e("Local description has no SDP");
100
+ if (v.disposed) throw a.close(), new l();
101
+ if (!h) throw b = null, a.close(), v.setStatus("idle"), new c("Local description has no SDP");
102
102
  let y = i(f.random), S;
103
103
  try {
104
- S = await c(r.secret, h);
104
+ S = await r(e.secret, h);
105
105
  } catch (e) {
106
- throw b = null, o.close(), v.setStatus("idle"), e;
106
+ throw b = null, a.close(), v.setStatus("idle"), e;
107
107
  }
108
108
  return v.setStatus("connecting"), x = setTimeout(() => {
109
109
  b?.status === "connecting" && failPeer(b, openTimeoutError());
110
110
  }, g), {
111
- peer: a?.name === void 0 ? { id: y } : {
111
+ peer: t?.name === void 0 ? { id: y } : {
112
112
  id: y,
113
- name: a.name
113
+ name: t.name
114
114
  },
115
115
  proof: S,
116
116
  sdp: h,
117
- sessionId: r.sessionId,
117
+ sessionId: e.sessionId,
118
118
  v: 1
119
119
  };
120
120
  },
@@ -133,9 +133,9 @@ function createMeshGuest(f = {}) {
133
133
  on(e, t) {
134
134
  return v.ensureLive(), y.on(e, t);
135
135
  },
136
- send(t, n) {
137
- if (v.ensureLive(), !b) throw new e("Guest is not paired");
138
- y.sendTo(b, t, n);
136
+ send(e, t) {
137
+ if (v.ensureLive(), !b) throw new c("Guest is not paired");
138
+ y.sendTo(b, e, t);
139
139
  },
140
140
  get status() {
141
141
  return v.status;
@@ -148,7 +148,6 @@ function createMeshGuest(f = {}) {
148
148
  }
149
149
  };
150
150
  }
151
- //#endregion
152
151
  export { createMeshGuest };
153
152
 
154
153
  //# sourceMappingURL=guest.js.map
package/dist/host.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs");require("./_defaults.cjs");const t=require("./_random.cjs"),n=require("./_node.cjs"),r=require("./_proof.cjs"),i=require("./_rtc.cjs");let a=require("@vielzeug/arsenal");function createMeshHost(o={}){let s=o.rtc??i.nativeRtcFactory,c=o.clock??(()=>Date.now()),l=o.maxMessageBytes??65536,u=o.invitationTtlMs??3e5,d=o.channelOpenTimeoutMs??6e4,f=o.iceGatheringTimeoutMs??5e3,p={label:`mesh`,ordered:!0,...o.channel},m=n.createNodeCore(o.signal),h=n.createMessenger({clock:c,emitTap:m.emitTap,maxMessageBytes:l,random:o.random}),g=new Map,_=new Map,v=!1,y=!1;function recomputeStatus(){let e=_.size>0?`pairing`:v?`disconnected`:y?`failed`:`idle`;for(let t of g.values()){if(t.status===`connected`){e=`connected`;break}t.status===`connecting`&&(e=`connecting`)}m.setStatus(e)}function expireInvitation(e){let t=_.get(e);t&&(_.delete(e),clearTimeout(t.expiryTimer),t.pc.close(),m.emitTap({sessionId:e,type:`invitation-expired`}),recomputeStatus())}function setPeerStatus(e,t){e.status!==t&&(e.status=t,m.emitTap({peerId:e.id,status:t,type:`peer-status-change`}),recomputeStatus())}function leavePeer(e,t,n=!1){g.has(e.id)&&(g.delete(e.id),e.status=n?`failed`:`disconnected`,e.pc?.close(),e.dc?.close(),m.emitTap({peerId:e.id,status:e.status,type:`peer-status-change`}),m.emitTap({peer:e.publicPeer,reason:t,type:`peer-left`}),n||(v=!0),y||=n,recomputeStatus())}function wirePeerTransport(e,t,n){e.pc=t,e.dc=n,n.addEventListener(`message`,t=>{m.disposed||h.handleInbound(e,t.data)}),n.addEventListener(`close`,()=>{m.disposed||leavePeer(e,`channel closed`)}),n.addEventListener(`error`,()=>{m.disposed||leavePeer(e,`channel error`,!0)}),t.addEventListener(`iceconnectionstatechange`,()=>{if(m.disposed)return;let n=t.iceConnectionState;m.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){v=!0,g.set(e.id,e),setPeerStatus(e,`connected`),m.emitTap({peer:e,type:`peer-joined`})}return{async acceptAnswer(t){if(m.ensureLive(),t?.v!==1)throw new e.MeshPairingError(`Unsupported answer version`);if(!t.peer?.id)throw new e.MeshPairingError(`Answer is missing a peer id`);let a=_.get(t.sessionId);if(!a)throw new e.MeshPairingError(`Unknown or expired session`);if(a.answered)throw new e.MeshPairingError(`Invitation already answered`);if(g.has(t.peer.id))throw new e.MeshPairingError(`Peer "${t.peer.id}" is already connected`);if(c()>=a.expiresAt)throw expireInvitation(t.sessionId),new e.MeshPairingError(`Invitation expired`);let s=await r.createProof(a.secret,t.sdp);if(t.proof!==s)throw new e.MeshPairingError(`Proof mismatch`);if(!(await o.approvePeer?.(t.peer)??!0))throw m.emitTap({peerId:t.peer.id,type:`peer-rejected`}),new e.MeshPairingError(`Peer "${t.peer.id}" rejected by host`);if(m.emitTap({peerId:t.peer.id,type:`peer-approved`}),g.has(t.peer.id))throw new e.MeshPairingError(`Peer "${t.peer.id}" is already connected`);a.answered=!0,_.delete(t.sessionId),clearTimeout(a.expiryTimer);let l=n.createPeerRecord(t.peer.id,t.peer.name,`guest`),u=i.waitChannelOpen(a.dc,a.pc,d,l.id,m.disposalCtrl.signal);wirePeerTransport(l,a.pc,a.dc),g.set(l.id,l),recomputeStatus();try{await a.pc.setRemoteDescription({sdp:i.normalizeSdp(t.sdp),type:`answer`})}catch(t){throw leavePeer(l,`invalid answer sdp`,!0),new e.MeshPairingError(`Invalid answer SDP`,{cause:t})}try{await u}catch(t){let n=!(t instanceof e.MeshDisposedError);throw leavePeer(l,t instanceof Error?t.message:`open failed`,n),t}return markPeerConnected(l),l.publicPeer},broadcast(e,t,n){m.ensureLive();for(let r of g.values())r.status!==`connected`||n?.except?.includes(r.id)||h.sendTo(r,e,t)},async createInvitation(n){m.ensureLive();let r=s.createPeerConnection({iceServers:[...o.iceServers??[]]}),l=r.createDataChannel(p.label,{maxRetransmits:p.maxRetransmits,ordered:p.ordered}),d=t.randomId(o.random),h=(0,a.bytesToBase64Url)(t.randomBytes(32,o.random)),g=c()+u;try{await r.setLocalDescription(await r.createOffer()),await i.waitIceGathering(r,f)}catch(t){throw r.close(),new e.MeshConnectionError(`Failed to create offer`,null,{cause:t})}if(m.disposed)throw r.close(),new e.MeshDisposedError;let v=r.localDescription?.sdp;if(!v)throw r.close(),new e.MeshConnectionError(`Local description has no SDP`);let y=setTimeout(()=>expireInvitation(d),u);return _.set(d,{answered:!1,dc:l,expiresAt:g,expiryTimer:y,pc:r,secret:h,sessionId:d}),m.emitTap({sessionId:d,type:`invitation-created`}),recomputeStatus(),{expiresAt:g,sdp:v,secret:h,sessionId:d,v:1,...n?.name===void 0?{}:{hostName:n.name}}},get disposalSignal(){return m.disposalCtrl.signal},dispose(){if(!m.disposed){m.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();g.clear(),h.clear()}},get disposed(){return m.disposed},kick(t,n){m.ensureLive();let r=g.get(t);if(!r)throw new e.MeshConnectionError(`Unknown peer "${t}"`,t);leavePeer(r,n??`kicked`)},on(e,t){return m.ensureLive(),h.on(e,t)},get peers(){return g},send(t,n,r){m.ensureLive();let i=g.get(t);if(!i)throw new e.MeshConnectionError(`Unknown peer "${t}"`,t);h.sendTo(i,n,r)},get status(){return m.status},tap(e,t){return m.tap(e,t)},[Symbol.dispose](){this.dispose()}}}exports.createMeshHost=createMeshHost;
1
+ require("./_defaults.cjs");const e=require("./_node.cjs"),t=require("./_proof.cjs"),n=require("./_random.cjs"),r=require("./_rtc.cjs"),i=require("./errors.cjs");let a=require("@vielzeug/arsenal");function createMeshHost(o={}){let s=o.rtc??r.nativeRtcFactory,c=o.clock??(()=>Date.now()),l=o.maxMessageBytes??65536,u=o.invitationTtlMs??3e5,d=o.channelOpenTimeoutMs??6e4,f=o.iceGatheringTimeoutMs??5e3,p={label:`mesh`,ordered:!0,...o.channel},m=e.createNodeCore(o.signal),h=e.createMessenger({clock:c,emitTap:m.emitTap,maxMessageBytes:l,random:o.random}),g=new Map,_=new Map,v=!1,y=!1;function recomputeStatus(){let e=_.size>0?`pairing`:v?`disconnected`:y?`failed`:`idle`;for(let t of g.values()){if(t.status===`connected`){e=`connected`;break}t.status===`connecting`&&(e=`connecting`)}m.setStatus(e)}function expireInvitation(e){let t=_.get(e);t&&(_.delete(e),clearTimeout(t.expiryTimer),t.pc.close(),m.emitTap({sessionId:e,type:`invitation-expired`}),recomputeStatus())}function setPeerStatus(e,t){e.status!==t&&(e.status=t,m.emitTap({peerId:e.id,status:t,type:`peer-status-change`}),recomputeStatus())}function leavePeer(e,t,n=!1){g.has(e.id)&&(g.delete(e.id),e.status=n?`failed`:`disconnected`,e.pc?.close(),e.dc?.close(),m.emitTap({peerId:e.id,status:e.status,type:`peer-status-change`}),m.emitTap({peer:e.publicPeer,reason:t,type:`peer-left`}),n||(v=!0),y||=n,recomputeStatus())}function wirePeerTransport(e,t,n){e.pc=t,e.dc=n,n.addEventListener(`message`,t=>{m.disposed||h.handleInbound(e,t.data)}),n.addEventListener(`close`,()=>{m.disposed||leavePeer(e,`channel closed`)}),n.addEventListener(`error`,()=>{m.disposed||leavePeer(e,`channel error`,!0)}),t.addEventListener(`iceconnectionstatechange`,()=>{if(m.disposed)return;let n=t.iceConnectionState;m.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){v=!0,g.set(e.id,e),setPeerStatus(e,`connected`),m.emitTap({peer:e,type:`peer-joined`})}return{async acceptAnswer(n){if(m.ensureLive(),n?.v!==1)throw new i.MeshPairingError(`Unsupported answer version`);if(!n.peer?.id)throw new i.MeshPairingError(`Answer is missing a peer id`);let a=_.get(n.sessionId);if(!a)throw new i.MeshPairingError(`Unknown or expired session`);if(a.answered)throw new i.MeshPairingError(`Invitation already answered`);if(g.has(n.peer.id))throw new i.MeshPairingError(`Peer "${n.peer.id}" is already connected`);if(c()>=a.expiresAt)throw expireInvitation(n.sessionId),new i.MeshPairingError(`Invitation expired`);let s=await t.createProof(a.secret,n.sdp);if(n.proof!==s)throw new i.MeshPairingError(`Proof mismatch`);if(!(await o.approvePeer?.(n.peer)??!0))throw m.emitTap({peerId:n.peer.id,type:`peer-rejected`}),new i.MeshPairingError(`Peer "${n.peer.id}" rejected by host`);if(m.emitTap({peerId:n.peer.id,type:`peer-approved`}),g.has(n.peer.id))throw new i.MeshPairingError(`Peer "${n.peer.id}" is already connected`);a.answered=!0,_.delete(n.sessionId),clearTimeout(a.expiryTimer);let l=e.createPeerRecord(n.peer.id,n.peer.name,`guest`),u=r.waitChannelOpen(a.dc,a.pc,d,l.id,m.disposalCtrl.signal);wirePeerTransport(l,a.pc,a.dc),g.set(l.id,l),recomputeStatus();try{await a.pc.setRemoteDescription({sdp:r.normalizeSdp(n.sdp),type:`answer`})}catch(e){throw leavePeer(l,`invalid answer sdp`,!0),new i.MeshPairingError(`Invalid answer SDP`,{cause:e})}try{await u}catch(e){let t=!(e instanceof i.MeshDisposedError);throw leavePeer(l,e instanceof Error?e.message:`open failed`,t),e}return markPeerConnected(l),l.publicPeer},broadcast(e,t,n){m.ensureLive();for(let r of g.values())r.status!==`connected`||n?.except?.includes(r.id)||h.sendTo(r,e,t)},async createInvitation(e){m.ensureLive();let t=s.createPeerConnection({iceServers:[...o.iceServers??[]]}),l=t.createDataChannel(p.label,{maxRetransmits:p.maxRetransmits,ordered:p.ordered}),d=n.randomId(o.random),h=(0,a.bytesToBase64Url)(n.randomBytes(32,o.random)),g=c()+u;try{await t.setLocalDescription(await t.createOffer()),await r.waitIceGathering(t,f)}catch(e){throw t.close(),new i.MeshConnectionError(`Failed to create offer`,null,{cause:e})}if(m.disposed)throw t.close(),new i.MeshDisposedError;let v=t.localDescription?.sdp;if(!v)throw t.close(),new i.MeshConnectionError(`Local description has no SDP`);let y=setTimeout(()=>expireInvitation(d),u);return _.set(d,{answered:!1,dc:l,expiresAt:g,expiryTimer:y,pc:t,secret:h,sessionId:d}),m.emitTap({sessionId:d,type:`invitation-created`}),recomputeStatus(),{expiresAt:g,sdp:v,secret:h,sessionId:d,v:1,...e?.name===void 0?{}:{hostName:e.name}}},get disposalSignal(){return m.disposalCtrl.signal},dispose(){if(!m.disposed){m.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();g.clear(),h.clear()}},get disposed(){return m.disposed},kick(e,t){m.ensureLive();let n=g.get(e);if(!n)throw new i.MeshConnectionError(`Unknown peer "${e}"`,e);leavePeer(n,t??`kicked`)},on(e,t){return m.ensureLive(),h.on(e,t)},get peers(){return g},send(e,t,n){m.ensureLive();let r=g.get(e);if(!r)throw new i.MeshConnectionError(`Unknown peer "${e}"`,e);h.sendTo(r,t,n)},get status(){return m.status},tap(e,t){return m.tap(e,t)},[Symbol.dispose](){this.dispose()}}}exports.createMeshHost=createMeshHost;
2
2
  //# sourceMappingURL=host.cjs.map
package/dist/host.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"host.cjs","names":["nativeRtcFactory","createNodeCore","createMessenger","MeshPairingError","createProof","createPeerRecord","waitChannelOpen","normalizeSdp","MeshDisposedError","randomId","bytesToBase64Url","randomBytes","waitIceGathering","MeshConnectionError","require_errors","require__random","require__node","require__proof","require__rtc","_vielzeug_arsenal","rtc","options","clock","maxMessageBytes","invitationTtlMs","channelOpenTimeoutMs","iceGatheringTimeoutMs","channelInit","core","messenger","peers","pending","sawPeer","sawFailure","next","peer","invitation","sessionId","status","failed","pc","dc","event","state","answer","expected","opened","cause","error","broadcastOptions","type","payload","secret","expiresAt","sdp","expiryTimer","meta","peerId","reason","handler","tapOptions"],"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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"}
1
+ {"version":3,"file":"host.cjs","names":["nativeRtcFactory","createNodeCore","createMessenger","MeshPairingError","createProof","createPeerRecord","waitChannelOpen","normalizeSdp","MeshDisposedError","randomId","bytesToBase64Url","randomBytes","waitIceGathering","MeshConnectionError","require__node","require__proof","require__random","require__rtc","require_errors","_vielzeug_arsenal","rtc","options","clock","maxMessageBytes","invitationTtlMs","channelOpenTimeoutMs","iceGatheringTimeoutMs","channelInit","core","messenger","peers","pending","sawPeer","sawFailure","next","peer","invitation","sessionId","status","failed","pc","dc","event","state","answer","expected","opened","cause","error","broadcastOptions","type","payload","secret","expiresAt","sdp","expiryTimer","meta","peerId","reason","handler","tapOptions"],"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/host.js CHANGED
@@ -1,17 +1,17 @@
1
- import { MeshConnectionError as e, MeshDisposedError as t, MeshPairingError as n } from "./errors.js";
2
1
  import "./_defaults.js";
3
- import { randomBytes as r, randomId as i } from "./_random.js";
4
- import { createMessenger as a, createNodeCore as o, createPeerRecord as s } from "./_node.js";
5
- import { createProof as c } from "./_proof.js";
6
- import { nativeRtcFactory as l, normalizeSdp as u, waitChannelOpen as d, waitIceGathering as f } from "./_rtc.js";
2
+ import { createMessenger as e, createNodeCore as t, createPeerRecord as n } from "./_node.js";
3
+ import { createProof as r } from "./_proof.js";
4
+ import { randomBytes as i, randomId as a } from "./_random.js";
5
+ import { nativeRtcFactory as o, normalizeSdp as s, waitChannelOpen as c, waitIceGathering as l } from "./_rtc.js";
6
+ import { MeshConnectionError as u, MeshDisposedError as d, MeshPairingError as f } from "./errors.js";
7
7
  import { bytesToBase64Url as p } from "@vielzeug/arsenal";
8
- //#region src/host.ts
8
+ // src/host.ts
9
9
  function createMeshHost(m = {}) {
10
- 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 = {
10
+ let h = m.rtc ?? o, g = m.clock ?? (() => Date.now()), _ = m.maxMessageBytes ?? 65536, v = m.invitationTtlMs ?? 3e5, y = m.channelOpenTimeoutMs ?? 6e4, b = m.iceGatheringTimeoutMs ?? 5e3, x = {
11
11
  label: "mesh",
12
12
  ordered: !0,
13
13
  ...m.channel
14
- }, S = o(m.signal), C = a({
14
+ }, S = t(m.signal), C = e({
15
15
  clock: g,
16
16
  emitTap: S.emitTap,
17
17
  maxMessageBytes: _,
@@ -78,39 +78,39 @@ function createMeshHost(m = {}) {
78
78
  }
79
79
  return {
80
80
  async acceptAnswer(e) {
81
- if (S.ensureLive(), e?.v !== 1) throw new n("Unsupported answer version");
82
- if (!e.peer?.id) throw new n("Answer is missing a peer id");
83
- let r = T.get(e.sessionId);
84
- if (!r) throw new n("Unknown or expired session");
85
- if (r.answered) throw new n("Invitation already answered");
86
- if (w.has(e.peer.id)) throw new n(`Peer "${e.peer.id}" is already connected`);
87
- if (g() >= r.expiresAt) throw expireInvitation(e.sessionId), new n("Invitation expired");
88
- let i = await c(r.secret, e.sdp);
89
- if (e.proof !== i) throw new n("Proof mismatch");
81
+ if (S.ensureLive(), e?.v !== 1) throw new f("Unsupported answer version");
82
+ if (!e.peer?.id) throw new f("Answer is missing a peer id");
83
+ let t = T.get(e.sessionId);
84
+ if (!t) throw new f("Unknown or expired session");
85
+ if (t.answered) throw new f("Invitation already answered");
86
+ if (w.has(e.peer.id)) throw new f(`Peer "${e.peer.id}" is already connected`);
87
+ if (g() >= t.expiresAt) throw expireInvitation(e.sessionId), new f("Invitation expired");
88
+ let i = await r(t.secret, e.sdp);
89
+ if (e.proof !== i) throw new f("Proof mismatch");
90
90
  if (!(await m.approvePeer?.(e.peer) ?? !0)) throw S.emitTap({
91
91
  peerId: e.peer.id,
92
92
  type: "peer-rejected"
93
- }), new n(`Peer "${e.peer.id}" rejected by host`);
93
+ }), new f(`Peer "${e.peer.id}" rejected by host`);
94
94
  if (S.emitTap({
95
95
  peerId: e.peer.id,
96
96
  type: "peer-approved"
97
- }), w.has(e.peer.id)) throw new n(`Peer "${e.peer.id}" is already connected`);
98
- r.answered = !0, T.delete(e.sessionId), clearTimeout(r.expiryTimer);
99
- let a = s(e.peer.id, e.peer.name, "guest"), o = d(r.dc, r.pc, y, a.id, S.disposalCtrl.signal);
100
- wirePeerTransport(a, r.pc, r.dc), w.set(a.id, a), recomputeStatus();
97
+ }), w.has(e.peer.id)) throw new f(`Peer "${e.peer.id}" is already connected`);
98
+ t.answered = !0, T.delete(e.sessionId), clearTimeout(t.expiryTimer);
99
+ let a = n(e.peer.id, e.peer.name, "guest"), o = c(t.dc, t.pc, y, a.id, S.disposalCtrl.signal);
100
+ wirePeerTransport(a, t.pc, t.dc), w.set(a.id, a), recomputeStatus();
101
101
  try {
102
- await r.pc.setRemoteDescription({
103
- sdp: u(e.sdp),
102
+ await t.pc.setRemoteDescription({
103
+ sdp: s(e.sdp),
104
104
  type: "answer"
105
105
  });
106
106
  } catch (e) {
107
- throw leavePeer(a, "invalid answer sdp", !0), new n("Invalid answer SDP", { cause: e });
107
+ throw leavePeer(a, "invalid answer sdp", !0), new f("Invalid answer SDP", { cause: e });
108
108
  }
109
109
  try {
110
110
  await o;
111
111
  } catch (e) {
112
- let n = !(e instanceof t);
113
- throw leavePeer(a, e instanceof Error ? e.message : "open failed", n), e;
112
+ let t = !(e instanceof d);
113
+ throw leavePeer(a, e instanceof Error ? e.message : "open failed", t), e;
114
114
  }
115
115
  return markPeerConnected(a), a.publicPeer;
116
116
  },
@@ -118,39 +118,39 @@ function createMeshHost(m = {}) {
118
118
  S.ensureLive();
119
119
  for (let r of w.values()) r.status !== "connected" || n?.except?.includes(r.id) || C.sendTo(r, e, t);
120
120
  },
121
- async createInvitation(n) {
121
+ async createInvitation(e) {
122
122
  S.ensureLive();
123
- let a = h.createPeerConnection({ iceServers: [...m.iceServers ?? []] }), o = a.createDataChannel(x.label, {
123
+ let t = h.createPeerConnection({ iceServers: [...m.iceServers ?? []] }), n = t.createDataChannel(x.label, {
124
124
  maxRetransmits: x.maxRetransmits,
125
125
  ordered: x.ordered
126
- }), s = i(m.random), c = p(r(32, m.random)), l = g() + v;
126
+ }), r = a(m.random), o = p(i(32, m.random)), s = g() + v;
127
127
  try {
128
- await a.setLocalDescription(await a.createOffer()), await f(a, b);
129
- } catch (t) {
130
- throw a.close(), new e("Failed to create offer", null, { cause: t });
128
+ await t.setLocalDescription(await t.createOffer()), await l(t, b);
129
+ } catch (e) {
130
+ throw t.close(), new u("Failed to create offer", null, { cause: e });
131
131
  }
132
- if (S.disposed) throw a.close(), new t();
133
- let u = a.localDescription?.sdp;
134
- if (!u) throw a.close(), new e("Local description has no SDP");
135
- let d = setTimeout(() => expireInvitation(s), v);
136
- return T.set(s, {
132
+ if (S.disposed) throw t.close(), new d();
133
+ let c = t.localDescription?.sdp;
134
+ if (!c) throw t.close(), new u("Local description has no SDP");
135
+ let f = setTimeout(() => expireInvitation(r), v);
136
+ return T.set(r, {
137
137
  answered: !1,
138
- dc: o,
139
- expiresAt: l,
140
- expiryTimer: d,
141
- pc: a,
142
- secret: c,
143
- sessionId: s
138
+ dc: n,
139
+ expiresAt: s,
140
+ expiryTimer: f,
141
+ pc: t,
142
+ secret: o,
143
+ sessionId: r
144
144
  }), S.emitTap({
145
- sessionId: s,
145
+ sessionId: r,
146
146
  type: "invitation-created"
147
147
  }), recomputeStatus(), {
148
- expiresAt: l,
149
- sdp: u,
150
- secret: c,
151
- sessionId: s,
148
+ expiresAt: s,
149
+ sdp: c,
150
+ secret: o,
151
+ sessionId: r,
152
152
  v: 1,
153
- ...n?.name === void 0 ? {} : { hostName: n.name }
153
+ ...e?.name === void 0 ? {} : { hostName: e.name }
154
154
  };
155
155
  },
156
156
  get disposalSignal() {
@@ -168,11 +168,11 @@ function createMeshHost(m = {}) {
168
168
  get disposed() {
169
169
  return S.disposed;
170
170
  },
171
- kick(t, n) {
171
+ kick(e, t) {
172
172
  S.ensureLive();
173
- let r = w.get(t);
174
- if (!r) throw new e(`Unknown peer "${t}"`, t);
175
- leavePeer(r, n ?? "kicked");
173
+ let n = w.get(e);
174
+ if (!n) throw new u(`Unknown peer "${e}"`, e);
175
+ leavePeer(n, t ?? "kicked");
176
176
  },
177
177
  on(e, t) {
178
178
  return S.ensureLive(), C.on(e, t);
@@ -180,11 +180,11 @@ function createMeshHost(m = {}) {
180
180
  get peers() {
181
181
  return w;
182
182
  },
183
- send(t, n, r) {
183
+ send(e, t, n) {
184
184
  S.ensureLive();
185
- let i = w.get(t);
186
- if (!i) throw new e(`Unknown peer "${t}"`, t);
187
- C.sendTo(i, n, r);
185
+ let r = w.get(e);
186
+ if (!r) throw new u(`Unknown peer "${e}"`, e);
187
+ C.sendTo(r, t, n);
188
188
  },
189
189
  get status() {
190
190
  return S.status;
@@ -197,7 +197,6 @@ function createMeshHost(m = {}) {
197
197
  }
198
198
  };
199
199
  }
200
- //#endregion
201
200
  export { createMeshHost };
202
201
 
203
202
  //# sourceMappingURL=host.js.map
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./codec.cjs"),n=require("./guest.cjs"),r=require("./host.cjs"),i=require("./qr-codec.cjs");exports.MeshConnectionError=e.MeshConnectionError,exports.MeshDisposedError=e.MeshDisposedError,exports.MeshError=e.MeshError,exports.MeshPairingError=e.MeshPairingError,exports.MeshPayloadError=e.MeshPayloadError,exports.MeshTimeoutError=e.MeshTimeoutError,exports.MeshUnsupportedError=e.MeshUnsupportedError,exports.createMeshGuest=n.createMeshGuest,exports.createMeshHost=r.createMeshHost,exports.meshCodec=t.meshCodec,exports.meshQrCodec=i.meshQrCodec;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./codec.cjs"),t=require("./errors.cjs"),n=require("./guest.cjs"),r=require("./host.cjs"),i=require("./qr-codec.cjs");exports.MeshConnectionError=t.MeshConnectionError,exports.MeshDisposedError=t.MeshDisposedError,exports.MeshError=t.MeshError,exports.MeshPairingError=t.MeshPairingError,exports.MeshPayloadError=t.MeshPayloadError,exports.MeshTimeoutError=t.MeshTimeoutError,exports.MeshUnsupportedError=t.MeshUnsupportedError,exports.createMeshGuest=n.createMeshGuest,exports.createMeshHost=r.createMeshHost,exports.meshCodec=e.meshCodec,exports.meshQrCodec=i.meshQrCodec;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
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";
1
+ import { meshCodec as e } from "./codec.js";
2
+ import { MeshConnectionError as t, MeshDisposedError as n, MeshError as r, MeshPairingError as i, MeshPayloadError as a, MeshTimeoutError as o, MeshUnsupportedError as s } from "./errors.js";
3
3
  import { createMeshGuest as c } from "./guest.js";
4
4
  import { createMeshHost as l } from "./host.js";
5
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 };
6
+ export { t as MeshConnectionError, n as MeshDisposedError, r as MeshError, i as MeshPairingError, a as MeshPayloadError, o as MeshTimeoutError, s as MeshUnsupportedError, c as createMeshGuest, l as createMeshHost, e as meshCodec, u as meshQrCodec };
package/dist/mesh.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { base64UrlToText as e, bytesToBase64Url as t, randomFloat as n, tapper as r, textToBase64Url as i, utf8Bytes as a } from "@vielzeug/arsenal";
2
- //#region src/errors.ts
2
+ // src/errors.ts
3
3
  var MeshError = class extends Error {
4
4
  constructor(e, t) {
5
5
  super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
@@ -14,8 +14,7 @@ var MeshError = class extends Error {
14
14
  super(`${e} is disposed`, t);
15
15
  }
16
16
  }, MeshUnsupportedError = class extends MeshError {};
17
- //#endregion
18
- //#region src/codec.ts
17
+ // src/codec.ts
19
18
  function isRecord(e) {
20
19
  return typeof e == "object" && !!e;
21
20
  }
@@ -44,8 +43,7 @@ var o = {
44
43
  return i(JSON.stringify(e));
45
44
  }
46
45
  };
47
- //#endregion
48
- //#region src/_random.ts
46
+ // src/_random.ts
49
47
  function randomBytes(e, t) {
50
48
  let r = new Uint8Array(e);
51
49
  if (t) for (let i = 0; i < e; i++) r[i] = Math.floor(n(t) * 256);
@@ -55,8 +53,7 @@ function randomBytes(e, t) {
55
53
  function randomId(e) {
56
54
  return t(randomBytes(16, e));
57
55
  }
58
- //#endregion
59
- //#region src/_wire.ts
56
+ // src/_wire.ts
60
57
  function toEnvelope(e) {
61
58
  if (typeof e != "object" || !e) return null;
62
59
  let t = e;
@@ -73,8 +70,7 @@ var MessageDedupe = class {
73
70
  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);
74
71
  }
75
72
  };
76
- //#endregion
77
- //#region src/_node.ts
73
+ // src/_node.ts
78
74
  function createNodeCore(e) {
79
75
  let t = r(), n = new AbortController(), i = {
80
76
  disposalCtrl: n,
@@ -233,8 +229,7 @@ function createMessenger(e) {
233
229
  }
234
230
  };
235
231
  }
236
- //#endregion
237
- //#region src/_sdp.ts
232
+ // src/_sdp.ts
238
233
  function isCompactSdp(e) {
239
234
  if (typeof e != "object" || !e) return !1;
240
235
  let t = e;
@@ -284,8 +279,7 @@ function expandSdp(e) {
284
279
  ""
285
280
  ].join("\r\n")}`;
286
281
  }
287
- //#endregion
288
- //#region src/_proof.ts
282
+ // src/_proof.ts
289
283
  async function createProof(e, n) {
290
284
  let r = globalThis.crypto?.subtle;
291
285
  if (!r) throw new MeshUnsupportedError("WebCrypto is unavailable: pairing requires a secure context");
@@ -295,8 +289,7 @@ async function createProof(e, n) {
295
289
  }, !1, ["sign"]), o = await r.sign("HMAC", a, i.encode(canonicalSdp(n)));
296
290
  return t(new Uint8Array(o));
297
291
  }
298
- //#endregion
299
- //#region src/_rtc.ts
292
+ // src/_rtc.ts
300
293
  var s = { createPeerConnection(e) {
301
294
  let t = globalThis.RTCPeerConnection;
302
295
  if (!t) throw new MeshUnsupportedError("WebRTC is unavailable in this environment: inject an `rtc` factory");
@@ -333,8 +326,7 @@ function waitChannelOpen(e, t, n, r, i) {
333
326
  s = setTimeout(() => fail(timeout()), n), e.addEventListener("open", onOpen), e.addEventListener("close", onClose), t.addEventListener("iceconnectionstatechange", onIce), i.addEventListener("abort", onDispose, { once: !0 });
334
327
  });
335
328
  }
336
- //#endregion
337
- //#region src/guest.ts
329
+ // src/guest.ts
338
330
  function createMeshGuest(e = {}) {
339
331
  let t = e.rtc ?? s, n = e.clock ?? (() => Date.now()), r = e.maxMessageBytes ?? 65536, i = e.channelOpenTimeoutMs ?? 6e4, a = e.iceGatheringTimeoutMs ?? 5e3, o = createNodeCore(e.signal), c = createMessenger({
340
332
  clock: n,
@@ -478,8 +470,7 @@ function createMeshGuest(e = {}) {
478
470
  }
479
471
  };
480
472
  }
481
- //#endregion
482
- //#region src/host.ts
473
+ // src/host.ts
483
474
  function createMeshHost(e = {}) {
484
475
  let n = e.rtc ?? s, r = e.clock ?? (() => Date.now()), i = e.maxMessageBytes ?? 65536, a = e.invitationTtlMs ?? 3e5, o = e.channelOpenTimeoutMs ?? 6e4, c = e.iceGatheringTimeoutMs ?? 5e3, l = {
485
476
  label: "mesh",
@@ -671,8 +662,7 @@ function createMeshHost(e = {}) {
671
662
  }
672
663
  };
673
664
  }
674
- //#endregion
675
- //#region src/_base45.ts
665
+ // src/_base45.ts
676
666
  var c = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:", l = new Map([...c].map((e, t) => [e, t]));
677
667
  function bytesToBase45(e) {
678
668
  let t = "";
@@ -705,8 +695,7 @@ function digit(e, t) {
705
695
  if (n === void 0) throw new MeshPairingError("Malformed base45 payload");
706
696
  return n;
707
697
  }
708
- //#endregion
709
- //#region src/qr-codec.ts
698
+ // src/qr-codec.ts
710
699
  var u = "mq2.", d = 1048576, f = new TextEncoder(), p = new TextDecoder();
711
700
  function decompressionStream(e) {
712
701
  if (typeof globalThis.DecompressionStream != "function") throw new MeshUnsupportedError("DecompressionStream is not available in this environment");
@@ -752,7 +741,6 @@ var m = {
752
741
  } : e)), new globalThis.CompressionStream("deflate-raw")));
753
742
  }
754
743
  };
755
- //#endregion
756
744
  export { MeshConnectionError, MeshDisposedError, MeshError, MeshPairingError, MeshPayloadError, MeshTimeoutError, MeshUnsupportedError, createMeshGuest, createMeshHost, o as meshCodec, m as meshQrCodec };
757
745
 
758
746
  //# sourceMappingURL=mesh.js.map