@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/_base45.js +1 -2
- package/dist/_defaults.js +1 -2
- package/dist/_node.cjs +1 -1
- package/dist/_node.cjs.map +1 -1
- package/dist/_node.js +51 -52
- package/dist/_proof.cjs +1 -1
- package/dist/_proof.cjs.map +1 -1
- package/dist/_proof.js +5 -6
- package/dist/_random.js +1 -2
- package/dist/_rtc.js +1 -2
- package/dist/_sdp.js +1 -2
- package/dist/_wire.js +1 -2
- package/dist/codec.js +1 -2
- package/dist/errors.js +1 -2
- package/dist/guest.cjs +1 -1
- package/dist/guest.cjs.map +1 -1
- package/dist/guest.js +49 -50
- package/dist/host.cjs +1 -1
- package/dist/host.cjs.map +1 -1
- package/dist/host.js +58 -59
- package/dist/index.cjs +1 -1
- package/dist/index.js +3 -3
- package/dist/mesh.js +12 -24
- package/dist/mesh.js.map +1 -1
- package/dist/qr-codec.cjs +1 -1
- package/dist/qr-codec.cjs.map +1 -1
- package/dist/qr-codec.js +32 -33
- package/dist/testing/fake-rtc.js +1 -2
- package/package.json +7 -7
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 {
|
|
5
|
-
import {
|
|
6
|
-
|
|
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 ??
|
|
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
|
|
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
|
|
43
|
-
|
|
44
|
-
v.disposed || (x &&= (clearTimeout(x), null),
|
|
45
|
-
peerId:
|
|
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:
|
|
49
|
+
peer: e.publicPeer,
|
|
50
50
|
type: "peer-joined"
|
|
51
51
|
}), v.setStatus("connected"));
|
|
52
|
-
}),
|
|
53
|
-
v.disposed || y.handleInbound(
|
|
54
|
-
}),
|
|
55
|
-
v.disposed || (
|
|
56
|
-
}),
|
|
57
|
-
v.disposed || failPeer(
|
|
58
|
-
}),
|
|
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
|
|
61
|
-
|
|
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(
|
|
66
|
-
if (v.ensureLive(), b && b.status !== "failed" && b.status !== "disconnected") throw new
|
|
67
|
-
if (
|
|
68
|
-
if (m() >=
|
|
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
|
|
70
|
+
let a;
|
|
71
71
|
try {
|
|
72
|
-
|
|
72
|
+
a = p.createPeerConnection({ iceServers: [...f.iceServers ?? []] });
|
|
73
73
|
} catch (e) {
|
|
74
74
|
throw v.setStatus("idle"), e;
|
|
75
75
|
}
|
|
76
|
-
let
|
|
77
|
-
|
|
78
|
-
wireChannel(
|
|
79
|
-
}),
|
|
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:
|
|
82
|
-
state:
|
|
81
|
+
peerId: d.id,
|
|
82
|
+
state: a.iceConnectionState,
|
|
83
83
|
type: "ice-state"
|
|
84
84
|
});
|
|
85
85
|
});
|
|
86
86
|
try {
|
|
87
|
-
await
|
|
88
|
-
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
|
|
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
|
|
97
|
-
} catch (
|
|
98
|
-
throw b = null,
|
|
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
|
|
101
|
-
if (!h) throw b = null,
|
|
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
|
|
104
|
+
S = await r(e.secret, h);
|
|
105
105
|
} catch (e) {
|
|
106
|
-
throw b = null,
|
|
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:
|
|
111
|
+
peer: t?.name === void 0 ? { id: y } : {
|
|
112
112
|
id: y,
|
|
113
|
-
name:
|
|
113
|
+
name: t.name
|
|
114
114
|
},
|
|
115
115
|
proof: S,
|
|
116
116
|
sdp: h,
|
|
117
|
-
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(
|
|
137
|
-
if (v.ensureLive(), !b) throw new
|
|
138
|
-
y.sendTo(b,
|
|
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
|
-
|
|
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 {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { nativeRtcFactory as
|
|
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
|
-
|
|
8
|
+
// src/host.ts
|
|
9
9
|
function createMeshHost(m = {}) {
|
|
10
|
-
let h = m.rtc ??
|
|
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 =
|
|
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
|
|
82
|
-
if (!e.peer?.id) throw new
|
|
83
|
-
let
|
|
84
|
-
if (!
|
|
85
|
-
if (
|
|
86
|
-
if (w.has(e.peer.id)) throw new
|
|
87
|
-
if (g() >=
|
|
88
|
-
let i = await
|
|
89
|
-
if (e.proof !== i) throw new
|
|
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
|
|
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
|
|
98
|
-
|
|
99
|
-
let a =
|
|
100
|
-
wirePeerTransport(a,
|
|
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
|
|
103
|
-
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
|
|
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
|
|
113
|
-
throw leavePeer(a, e instanceof Error ? e.message : "open failed",
|
|
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(
|
|
121
|
+
async createInvitation(e) {
|
|
122
122
|
S.ensureLive();
|
|
123
|
-
let
|
|
123
|
+
let t = h.createPeerConnection({ iceServers: [...m.iceServers ?? []] }), n = t.createDataChannel(x.label, {
|
|
124
124
|
maxRetransmits: x.maxRetransmits,
|
|
125
125
|
ordered: x.ordered
|
|
126
|
-
}),
|
|
126
|
+
}), r = a(m.random), o = p(i(32, m.random)), s = g() + v;
|
|
127
127
|
try {
|
|
128
|
-
await
|
|
129
|
-
} catch (
|
|
130
|
-
throw
|
|
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
|
|
133
|
-
let
|
|
134
|
-
if (!
|
|
135
|
-
let
|
|
136
|
-
return T.set(
|
|
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:
|
|
139
|
-
expiresAt:
|
|
140
|
-
expiryTimer:
|
|
141
|
-
pc:
|
|
142
|
-
secret:
|
|
143
|
-
sessionId:
|
|
138
|
+
dc: n,
|
|
139
|
+
expiresAt: s,
|
|
140
|
+
expiryTimer: f,
|
|
141
|
+
pc: t,
|
|
142
|
+
secret: o,
|
|
143
|
+
sessionId: r
|
|
144
144
|
}), S.emitTap({
|
|
145
|
-
sessionId:
|
|
145
|
+
sessionId: r,
|
|
146
146
|
type: "invitation-created"
|
|
147
147
|
}), recomputeStatus(), {
|
|
148
|
-
expiresAt:
|
|
149
|
-
sdp:
|
|
150
|
-
secret:
|
|
151
|
-
sessionId:
|
|
148
|
+
expiresAt: s,
|
|
149
|
+
sdp: c,
|
|
150
|
+
secret: o,
|
|
151
|
+
sessionId: r,
|
|
152
152
|
v: 1,
|
|
153
|
-
...
|
|
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(
|
|
171
|
+
kick(e, t) {
|
|
172
172
|
S.ensureLive();
|
|
173
|
-
let
|
|
174
|
-
if (!
|
|
175
|
-
leavePeer(
|
|
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
|
|
183
|
+
send(e, t, n) {
|
|
184
184
|
S.ensureLive();
|
|
185
|
-
let
|
|
186
|
-
if (!
|
|
187
|
-
C.sendTo(
|
|
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("./
|
|
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 {
|
|
2
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|