@vielzeug/mesh 26.11.4 → 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 CHANGED
@@ -1,5 +1,5 @@
1
1
  import { MeshPairingError as e } from "./errors.js";
2
- //#region src/_base45.ts
2
+ // src/_base45.ts
3
3
  var t = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:", n = new Map([...t].map((e, t) => [e, t]));
4
4
  function bytesToBase45(e) {
5
5
  let n = "";
@@ -32,7 +32,6 @@ function digit(t, r) {
32
32
  if (i === void 0) throw new e("Malformed base45 payload");
33
33
  return i;
34
34
  }
35
- //#endregion
36
35
  export { base45ToBytes, bytesToBase45 };
37
36
 
38
37
  //# sourceMappingURL=_base45.js.map
package/dist/_defaults.js CHANGED
@@ -1,6 +1,5 @@
1
- //#region src/_defaults.ts
1
+ // src/_defaults.ts
2
2
  var e = 6e4, t = 5e3, n = 3e5, r = 65536;
3
- //#endregion
4
3
  export { e as DEFAULT_CHANNEL_OPEN_TIMEOUT_MS, t as DEFAULT_ICE_GATHERING_TIMEOUT_MS, n as DEFAULT_INVITATION_TTL_MS, r as DEFAULT_MAX_MESSAGE_BYTES };
5
4
 
6
5
  //# sourceMappingURL=_defaults.js.map
package/dist/_node.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs"),t=require("./_random.cjs"),n=require("./_wire.cjs");let r=require("@vielzeug/arsenal");function createNodeCore(t){let n=(0,r.tapper)(),i=new AbortController,a={disposalCtrl:i,disposed:!1,emitTap(e){n.emit(e)},ensureLive(){if(a.disposed)throw new e.MeshDisposedError},markDisposed(){a.disposed||(a.disposed=!0,a.status=`disposed`,a.emitTap({status:`disposed`,type:`status-change`}),a.emitTap({type:`dispose`}),n.clear(),i.abort())},setStatus(e){a.disposed||a.status===e||(a.status=e,a.emitTap({status:e,type:`status-change`}))},status:`idle`,tap(e,t){return a.disposed?()=>{}:n.tap(e,t)}};return t&&(t.aborted?a.markDisposed():t.addEventListener(`abort`,()=>a.markDisposed(),{once:!0})),a}function createPeerRecord(e,t,r){let i={dc:null,dedupe:new n.MessageDedupe,id:e,name:t,pc:null,publicPeer:{get id(){return i.id},get name(){return i.name},get role(){return i.role},get status(){return i.status}},role:r,status:`connecting`};return i}function createMessenger(i){let a=new Map;function reject(e,t){i.emitTap({peerId:e,reason:t,type:`message-rejected`})}return{clear(){a.clear()},handleInbound(t,o){let s=t.id;if(typeof o!=`string`){reject(s,`malformed`);return}let c=(0,r.utf8Bytes)(o);if(c>i.maxMessageBytes){reject(s,`too-large`);return}let l;try{l=JSON.parse(o)}catch{reject(s,`malformed`);return}let u=n.toEnvelope(l);if(!u){reject(s,`malformed`);return}if(!t.dedupe.check(u.id)){reject(s,`duplicate`);return}let d=a.get(u.t);if(!d||d.size===0){reject(s,`unknown-type`);return}i.emitTap({bytes:c,messageType:u.t,peerId:s,type:`message-received`});let f={messageId:u.id,payload:u.p,peerId:s,sentAt:u.ts};for(let t of d)try{t(f)}catch(t){i.emitTap({error:new e.MeshError(`Message handler threw`,{cause:t}),type:`error`})}},on(e,t){let n=a.get(e);return n||(n=new Set,a.set(e,n)),n.add(t),()=>{n.delete(t),n.size===0&&a.delete(e)}},sendTo(n,a,o){let s=n.dc;if(!s||s.readyState!==`open`||n.status!==`connected`)throw new e.MeshConnectionError(`Peer "${n.id}" is not connected`,n.id);let c={id:t.randomId(i.random),p:o,t:a,ts:i.clock(),v:1},l;try{l=JSON.stringify(c)}catch(t){throw new e.MeshPayloadError(`Failed to serialize message "${a}"`,{cause:t})}let u=(0,r.utf8Bytes)(l);if(u>i.maxMessageBytes)throw new e.MeshPayloadError(`Message "${a}" is ${u} bytes (max ${i.maxMessageBytes})`);try{s.send(l)}catch(t){throw new e.MeshConnectionError(`Failed to send message "${a}"`,n.id,{cause:t})}i.emitTap({bytes:u,messageType:a,peerId:n.id,type:`message-sent`})}}}exports.createMessenger=createMessenger,exports.createNodeCore=createNodeCore,exports.createPeerRecord=createPeerRecord;
1
+ const e=require("./_random.cjs"),t=require("./_wire.cjs"),n=require("./errors.cjs");let r=require("@vielzeug/arsenal");function createNodeCore(e){let t=(0,r.tapper)(),i=new AbortController,a={disposalCtrl:i,disposed:!1,emitTap(e){t.emit(e)},ensureLive(){if(a.disposed)throw new n.MeshDisposedError},markDisposed(){a.disposed||(a.disposed=!0,a.status=`disposed`,a.emitTap({status:`disposed`,type:`status-change`}),a.emitTap({type:`dispose`}),t.clear(),i.abort())},setStatus(e){a.disposed||a.status===e||(a.status=e,a.emitTap({status:e,type:`status-change`}))},status:`idle`,tap(e,n){return a.disposed?()=>{}:t.tap(e,n)}};return e&&(e.aborted?a.markDisposed():e.addEventListener(`abort`,()=>a.markDisposed(),{once:!0})),a}function createPeerRecord(e,n,r){let i={dc:null,dedupe:new t.MessageDedupe,id:e,name:n,pc:null,publicPeer:{get id(){return i.id},get name(){return i.name},get role(){return i.role},get status(){return i.status}},role:r,status:`connecting`};return i}function createMessenger(i){let a=new Map;function reject(e,t){i.emitTap({peerId:e,reason:t,type:`message-rejected`})}return{clear(){a.clear()},handleInbound(e,o){let s=e.id;if(typeof o!=`string`){reject(s,`malformed`);return}let c=(0,r.utf8Bytes)(o);if(c>i.maxMessageBytes){reject(s,`too-large`);return}let l;try{l=JSON.parse(o)}catch{reject(s,`malformed`);return}let u=t.toEnvelope(l);if(!u){reject(s,`malformed`);return}if(!e.dedupe.check(u.id)){reject(s,`duplicate`);return}let d=a.get(u.t);if(!d||d.size===0){reject(s,`unknown-type`);return}i.emitTap({bytes:c,messageType:u.t,peerId:s,type:`message-received`});let f={messageId:u.id,payload:u.p,peerId:s,sentAt:u.ts};for(let e of d)try{e(f)}catch(e){i.emitTap({error:new n.MeshError(`Message handler threw`,{cause:e}),type:`error`})}},on(e,t){let n=a.get(e);return n||(n=new Set,a.set(e,n)),n.add(t),()=>{n.delete(t),n.size===0&&a.delete(e)}},sendTo(t,a,o){let s=t.dc;if(!s||s.readyState!==`open`||t.status!==`connected`)throw new n.MeshConnectionError(`Peer "${t.id}" is not connected`,t.id);let c={id:e.randomId(i.random),p:o,t:a,ts:i.clock(),v:1},l;try{l=JSON.stringify(c)}catch(e){throw new n.MeshPayloadError(`Failed to serialize message "${a}"`,{cause:e})}let u=(0,r.utf8Bytes)(l);if(u>i.maxMessageBytes)throw new n.MeshPayloadError(`Message "${a}" is ${u} bytes (max ${i.maxMessageBytes})`);try{s.send(l)}catch(e){throw new n.MeshConnectionError(`Failed to send message "${a}"`,t.id,{cause:e})}i.emitTap({bytes:u,messageType:a,peerId:t.id,type:`message-sent`})}}}exports.createMessenger=createMessenger,exports.createNodeCore=createNodeCore,exports.createPeerRecord=createPeerRecord;
2
2
  //# sourceMappingURL=_node.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_node.cjs","names":["tapper","MeshDisposedError","MessageDedupe","utf8Bytes","toEnvelope","MeshError","MeshConnectionError","randomId","MeshPayloadError","require_errors","require__random","require__wire","_vielzeug_arsenal","tappers","disposalCtrl","core","event","next","handler","options","signal","record","handlers","peerId","peer","data","bytes","decoded","envelope","set","message","cause","type","dc","payload","text"],"sources":["../src/_node.ts"],"sourcesContent":["import { type RandomSource, tapper, utf8Bytes } from '@vielzeug/arsenal';\nimport { randomId } from './_random';\nimport { MessageDedupe, toEnvelope } from './_wire';\nimport { MeshConnectionError, MeshDisposedError, MeshError, MeshPayloadError } from './errors';\nimport type {\n MeshEvent,\n MeshInbound,\n MeshPeer,\n MeshPeerStatus,\n MeshStatus,\n RTCDataChannelLike,\n RTCPeerConnectionLike,\n Unsubscribe,\n} from './types';\n\ntype Tapper = (event: MeshEvent) => void;\n\n/** Shared lifecycle core: status, tappers, disposal. */\nexport interface NodeCore {\n readonly disposalCtrl: AbortController;\n disposed: boolean;\n emitTap(event: MeshEvent): void;\n ensureLive(): void;\n /** Emits the final status-change + dispose events, clears tappers, aborts. */\n markDisposed(): void;\n setStatus(next: MeshStatus): void;\n status: MeshStatus;\n tap(handler: Tapper, options?: { readonly signal?: AbortSignal }): Unsubscribe;\n}\n\nexport function createNodeCore(signal?: AbortSignal): NodeCore {\n const tappers = tapper<MeshEvent>();\n const disposalCtrl = new AbortController();\n\n const core: NodeCore = {\n disposalCtrl,\n disposed: false,\n\n emitTap(event) {\n tappers.emit(event);\n },\n\n ensureLive() {\n if (core.disposed) throw new MeshDisposedError();\n },\n\n markDisposed() {\n if (core.disposed) return;\n core.disposed = true;\n core.status = 'disposed';\n core.emitTap({ status: 'disposed', type: 'status-change' });\n core.emitTap({ type: 'dispose' });\n tappers.clear();\n disposalCtrl.abort();\n },\n\n setStatus(next) {\n if (core.disposed || core.status === next) return;\n core.status = next;\n core.emitTap({ status: next, type: 'status-change' });\n },\n status: 'idle',\n\n tap(handler, options) {\n if (core.disposed) return () => {};\n return tappers.tap(handler, options);\n },\n };\n\n if (signal) {\n if (signal.aborted) {\n core.markDisposed();\n } else {\n signal.addEventListener('abort', () => core.markDisposed(), { once: true });\n }\n }\n\n return core;\n}\n\n// ─── Peers ───────────────────────────────────────────────────────────────────\n\n/** Internal peer record; `publicPeer` exposes live status through getters. */\nexport interface PeerRecord {\n dc: RTCDataChannelLike | null;\n readonly dedupe: MessageDedupe;\n readonly id: string;\n readonly name: string | undefined;\n pc: RTCPeerConnectionLike | null;\n readonly publicPeer: MeshPeer;\n readonly role: 'host' | 'guest';\n status: MeshPeerStatus;\n}\n\nexport function createPeerRecord(id: string, name: string | undefined, role: 'host' | 'guest'): PeerRecord {\n const record: PeerRecord = {\n dc: null,\n dedupe: new MessageDedupe(),\n id,\n name,\n pc: null,\n publicPeer: {\n get id() {\n return record.id;\n },\n get name() {\n return record.name;\n },\n get role() {\n return record.role;\n },\n get status() {\n return record.status;\n },\n },\n role,\n status: 'connecting',\n };\n return record;\n}\n\n// ─── Messenger ───────────────────────────────────────────────────────────────\n\ntype InboundHandler = (message: MeshInbound<unknown>) => void;\n\nexport interface MessengerOptions {\n readonly clock: () => number;\n readonly emitTap: (event: MeshEvent) => void;\n readonly maxMessageBytes: number;\n readonly random?: RandomSource;\n}\n\nexport interface Messenger {\n clear(): void;\n /** Entry point for `dc` 'message' events. */\n handleInbound(peer: PeerRecord, data: unknown): void;\n on(type: string, handler: InboundHandler): Unsubscribe;\n /** Sends a typed envelope to a peer. Throws `MeshConnectionError`/`MeshPayloadError`. */\n sendTo(peer: PeerRecord, type: string, payload: unknown): void;\n}\n\nexport function createMessenger(options: MessengerOptions): Messenger {\n const handlers = new Map<string, Set<InboundHandler>>();\n\n function reject(peerId: string, reason: 'too-large' | 'duplicate' | 'malformed' | 'unknown-type'): void {\n options.emitTap({ peerId, reason, type: 'message-rejected' });\n }\n\n return {\n clear() {\n handlers.clear();\n },\n\n handleInbound(peer, data) {\n const peerId = peer.id;\n\n if (typeof data !== 'string') {\n reject(peerId, 'malformed');\n return;\n }\n\n const bytes = utf8Bytes(data);\n if (bytes > options.maxMessageBytes) {\n reject(peerId, 'too-large');\n return;\n }\n\n let decoded: unknown;\n try {\n decoded = JSON.parse(data);\n } catch {\n reject(peerId, 'malformed');\n return;\n }\n\n const envelope = toEnvelope(decoded);\n if (!envelope) {\n reject(peerId, 'malformed');\n return;\n }\n\n if (!peer.dedupe.check(envelope.id)) {\n reject(peerId, 'duplicate');\n return;\n }\n\n const set = handlers.get(envelope.t);\n if (!set || set.size === 0) {\n reject(peerId, 'unknown-type');\n return;\n }\n\n options.emitTap({ bytes, messageType: envelope.t, peerId, type: 'message-received' });\n const message: MeshInbound<unknown> = {\n messageId: envelope.id,\n payload: envelope.p,\n peerId,\n sentAt: envelope.ts,\n };\n for (const handler of set) {\n try {\n handler(message);\n } catch (cause) {\n options.emitTap({ error: new MeshError('Message handler threw', { cause }), type: 'error' });\n }\n }\n },\n\n on(type, handler) {\n let set = handlers.get(type);\n if (!set) {\n set = new Set();\n handlers.set(type, set);\n }\n set.add(handler);\n return () => {\n set.delete(handler);\n if (set.size === 0) handlers.delete(type);\n };\n },\n\n sendTo(peer, type, payload) {\n const dc = peer.dc;\n if (!dc || dc.readyState !== 'open' || peer.status !== 'connected') {\n throw new MeshConnectionError(`Peer \"${peer.id}\" is not connected`, peer.id);\n }\n\n const envelope = { id: randomId(options.random), p: payload, t: type, ts: options.clock(), v: 1 as const };\n let text: string;\n try {\n text = JSON.stringify(envelope);\n } catch (cause) {\n throw new MeshPayloadError(`Failed to serialize message \"${type}\"`, { cause });\n }\n\n const bytes = utf8Bytes(text);\n if (bytes > options.maxMessageBytes) {\n throw new MeshPayloadError(`Message \"${type}\" is ${bytes} bytes (max ${options.maxMessageBytes})`);\n }\n\n try {\n dc.send(text);\n } catch (cause) {\n throw new MeshConnectionError(`Failed to send message \"${type}\"`, peer.id, { cause });\n }\n options.emitTap({ bytes, messageType: type, peerId: peer.id, type: 'message-sent' });\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"_node.cjs","names":["tapper","MeshDisposedError","MessageDedupe","utf8Bytes","toEnvelope","MeshError","MeshConnectionError","randomId","MeshPayloadError","require__random","require__wire","require_errors","_vielzeug_arsenal","tappers","disposalCtrl","core","event","next","handler","options","signal","record","handlers","peerId","peer","data","bytes","decoded","envelope","set","message","cause","type","dc","payload","text"],"sources":["../src/_node.ts"],"sourcesContent":["import { type RandomSource, tapper, utf8Bytes } from '@vielzeug/arsenal';\nimport { randomId } from './_random';\nimport { MessageDedupe, toEnvelope } from './_wire';\nimport { MeshConnectionError, MeshDisposedError, MeshError, MeshPayloadError } from './errors';\nimport type {\n MeshEvent,\n MeshInbound,\n MeshPeer,\n MeshPeerStatus,\n MeshStatus,\n RTCDataChannelLike,\n RTCPeerConnectionLike,\n Unsubscribe,\n} from './types';\n\ntype Tapper = (event: MeshEvent) => void;\n\n/** Shared lifecycle core: status, tappers, disposal. */\nexport interface NodeCore {\n readonly disposalCtrl: AbortController;\n disposed: boolean;\n emitTap(event: MeshEvent): void;\n ensureLive(): void;\n /** Emits the final status-change + dispose events, clears tappers, aborts. */\n markDisposed(): void;\n setStatus(next: MeshStatus): void;\n status: MeshStatus;\n tap(handler: Tapper, options?: { readonly signal?: AbortSignal }): Unsubscribe;\n}\n\nexport function createNodeCore(signal?: AbortSignal): NodeCore {\n const tappers = tapper<MeshEvent>();\n const disposalCtrl = new AbortController();\n\n const core: NodeCore = {\n disposalCtrl,\n disposed: false,\n\n emitTap(event) {\n tappers.emit(event);\n },\n\n ensureLive() {\n if (core.disposed) throw new MeshDisposedError();\n },\n\n markDisposed() {\n if (core.disposed) return;\n core.disposed = true;\n core.status = 'disposed';\n core.emitTap({ status: 'disposed', type: 'status-change' });\n core.emitTap({ type: 'dispose' });\n tappers.clear();\n disposalCtrl.abort();\n },\n\n setStatus(next) {\n if (core.disposed || core.status === next) return;\n core.status = next;\n core.emitTap({ status: next, type: 'status-change' });\n },\n status: 'idle',\n\n tap(handler, options) {\n if (core.disposed) return () => {};\n return tappers.tap(handler, options);\n },\n };\n\n if (signal) {\n if (signal.aborted) {\n core.markDisposed();\n } else {\n signal.addEventListener('abort', () => core.markDisposed(), { once: true });\n }\n }\n\n return core;\n}\n\n// ─── Peers ───────────────────────────────────────────────────────────────────\n\n/** Internal peer record; `publicPeer` exposes live status through getters. */\nexport interface PeerRecord {\n dc: RTCDataChannelLike | null;\n readonly dedupe: MessageDedupe;\n readonly id: string;\n readonly name: string | undefined;\n pc: RTCPeerConnectionLike | null;\n readonly publicPeer: MeshPeer;\n readonly role: 'host' | 'guest';\n status: MeshPeerStatus;\n}\n\nexport function createPeerRecord(id: string, name: string | undefined, role: 'host' | 'guest'): PeerRecord {\n const record: PeerRecord = {\n dc: null,\n dedupe: new MessageDedupe(),\n id,\n name,\n pc: null,\n publicPeer: {\n get id() {\n return record.id;\n },\n get name() {\n return record.name;\n },\n get role() {\n return record.role;\n },\n get status() {\n return record.status;\n },\n },\n role,\n status: 'connecting',\n };\n return record;\n}\n\n// ─── Messenger ───────────────────────────────────────────────────────────────\n\ntype InboundHandler = (message: MeshInbound<unknown>) => void;\n\nexport interface MessengerOptions {\n readonly clock: () => number;\n readonly emitTap: (event: MeshEvent) => void;\n readonly maxMessageBytes: number;\n readonly random?: RandomSource;\n}\n\nexport interface Messenger {\n clear(): void;\n /** Entry point for `dc` 'message' events. */\n handleInbound(peer: PeerRecord, data: unknown): void;\n on(type: string, handler: InboundHandler): Unsubscribe;\n /** Sends a typed envelope to a peer. Throws `MeshConnectionError`/`MeshPayloadError`. */\n sendTo(peer: PeerRecord, type: string, payload: unknown): void;\n}\n\nexport function createMessenger(options: MessengerOptions): Messenger {\n const handlers = new Map<string, Set<InboundHandler>>();\n\n function reject(peerId: string, reason: 'too-large' | 'duplicate' | 'malformed' | 'unknown-type'): void {\n options.emitTap({ peerId, reason, type: 'message-rejected' });\n }\n\n return {\n clear() {\n handlers.clear();\n },\n\n handleInbound(peer, data) {\n const peerId = peer.id;\n\n if (typeof data !== 'string') {\n reject(peerId, 'malformed');\n return;\n }\n\n const bytes = utf8Bytes(data);\n if (bytes > options.maxMessageBytes) {\n reject(peerId, 'too-large');\n return;\n }\n\n let decoded: unknown;\n try {\n decoded = JSON.parse(data);\n } catch {\n reject(peerId, 'malformed');\n return;\n }\n\n const envelope = toEnvelope(decoded);\n if (!envelope) {\n reject(peerId, 'malformed');\n return;\n }\n\n if (!peer.dedupe.check(envelope.id)) {\n reject(peerId, 'duplicate');\n return;\n }\n\n const set = handlers.get(envelope.t);\n if (!set || set.size === 0) {\n reject(peerId, 'unknown-type');\n return;\n }\n\n options.emitTap({ bytes, messageType: envelope.t, peerId, type: 'message-received' });\n const message: MeshInbound<unknown> = {\n messageId: envelope.id,\n payload: envelope.p,\n peerId,\n sentAt: envelope.ts,\n };\n for (const handler of set) {\n try {\n handler(message);\n } catch (cause) {\n options.emitTap({ error: new MeshError('Message handler threw', { cause }), type: 'error' });\n }\n }\n },\n\n on(type, handler) {\n let set = handlers.get(type);\n if (!set) {\n set = new Set();\n handlers.set(type, set);\n }\n set.add(handler);\n return () => {\n set.delete(handler);\n if (set.size === 0) handlers.delete(type);\n };\n },\n\n sendTo(peer, type, payload) {\n const dc = peer.dc;\n if (!dc || dc.readyState !== 'open' || peer.status !== 'connected') {\n throw new MeshConnectionError(`Peer \"${peer.id}\" is not connected`, peer.id);\n }\n\n const envelope = { id: randomId(options.random), p: payload, t: type, ts: options.clock(), v: 1 as const };\n let text: string;\n try {\n text = JSON.stringify(envelope);\n } catch (cause) {\n throw new MeshPayloadError(`Failed to serialize message \"${type}\"`, { cause });\n }\n\n const bytes = utf8Bytes(text);\n if (bytes > options.maxMessageBytes) {\n throw new MeshPayloadError(`Message \"${type}\" is ${bytes} bytes (max ${options.maxMessageBytes})`);\n }\n\n try {\n dc.send(text);\n } catch (cause) {\n throw new MeshConnectionError(`Failed to send message \"${type}\"`, peer.id, { cause });\n }\n options.emitTap({ bytes, messageType: type, peerId: peer.id, type: 'message-sent' });\n },\n };\n}\n"],"mappings":"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"}
package/dist/_node.js CHANGED
@@ -1,65 +1,65 @@
1
- import { MeshConnectionError as e, MeshDisposedError as t, MeshError as n, MeshPayloadError as r } from "./errors.js";
2
- import { randomId as i } from "./_random.js";
3
- import { MessageDedupe as a, toEnvelope as o } from "./_wire.js";
1
+ import { randomId as e } from "./_random.js";
2
+ import { MessageDedupe as t, toEnvelope as n } from "./_wire.js";
3
+ import { MeshConnectionError as r, MeshDisposedError as i, MeshError as a, MeshPayloadError as o } from "./errors.js";
4
4
  import { tapper as s, utf8Bytes as c } from "@vielzeug/arsenal";
5
- //#region src/_node.ts
5
+ // src/_node.ts
6
6
  function createNodeCore(e) {
7
- let n = s(), r = new AbortController(), i = {
8
- disposalCtrl: r,
7
+ let t = s(), n = new AbortController(), r = {
8
+ disposalCtrl: n,
9
9
  disposed: !1,
10
10
  emitTap(e) {
11
- n.emit(e);
11
+ t.emit(e);
12
12
  },
13
13
  ensureLive() {
14
- if (i.disposed) throw new t();
14
+ if (r.disposed) throw new i();
15
15
  },
16
16
  markDisposed() {
17
- i.disposed || (i.disposed = !0, i.status = "disposed", i.emitTap({
17
+ r.disposed || (r.disposed = !0, r.status = "disposed", r.emitTap({
18
18
  status: "disposed",
19
19
  type: "status-change"
20
- }), i.emitTap({ type: "dispose" }), n.clear(), r.abort());
20
+ }), r.emitTap({ type: "dispose" }), t.clear(), n.abort());
21
21
  },
22
22
  setStatus(e) {
23
- i.disposed || i.status === e || (i.status = e, i.emitTap({
23
+ r.disposed || r.status === e || (r.status = e, r.emitTap({
24
24
  status: e,
25
25
  type: "status-change"
26
26
  }));
27
27
  },
28
28
  status: "idle",
29
- tap(e, t) {
30
- return i.disposed ? () => {} : n.tap(e, t);
29
+ tap(e, n) {
30
+ return r.disposed ? () => {} : t.tap(e, n);
31
31
  }
32
32
  };
33
- return e && (e.aborted ? i.markDisposed() : e.addEventListener("abort", () => i.markDisposed(), { once: !0 })), i;
33
+ return e && (e.aborted ? r.markDisposed() : e.addEventListener("abort", () => r.markDisposed(), { once: !0 })), r;
34
34
  }
35
- function createPeerRecord(e, t, n) {
36
- let r = {
35
+ function createPeerRecord(e, n, r) {
36
+ let i = {
37
37
  dc: null,
38
- dedupe: new a(),
38
+ dedupe: new t(),
39
39
  id: e,
40
- name: t,
40
+ name: n,
41
41
  pc: null,
42
42
  publicPeer: {
43
43
  get id() {
44
- return r.id;
44
+ return i.id;
45
45
  },
46
46
  get name() {
47
- return r.name;
47
+ return i.name;
48
48
  },
49
49
  get role() {
50
- return r.role;
50
+ return i.role;
51
51
  },
52
52
  get status() {
53
- return r.status;
53
+ return i.status;
54
54
  }
55
55
  },
56
- role: n,
56
+ role: r,
57
57
  status: "connecting"
58
58
  };
59
- return r;
59
+ return i;
60
60
  }
61
61
  function createMessenger(t) {
62
- let a = /* @__PURE__ */ new Map();
62
+ let i = /* @__PURE__ */ new Map();
63
63
  function reject(e, n) {
64
64
  t.emitTap({
65
65
  peerId: e,
@@ -69,99 +69,98 @@ function createMessenger(t) {
69
69
  }
70
70
  return {
71
71
  clear() {
72
- a.clear();
72
+ i.clear();
73
73
  },
74
74
  handleInbound(e, r) {
75
- let i = e.id;
75
+ let o = e.id;
76
76
  if (typeof r != "string") {
77
- reject(i, "malformed");
77
+ reject(o, "malformed");
78
78
  return;
79
79
  }
80
80
  let s = c(r);
81
81
  if (s > t.maxMessageBytes) {
82
- reject(i, "too-large");
82
+ reject(o, "too-large");
83
83
  return;
84
84
  }
85
85
  let l;
86
86
  try {
87
87
  l = JSON.parse(r);
88
88
  } catch {
89
- reject(i, "malformed");
89
+ reject(o, "malformed");
90
90
  return;
91
91
  }
92
- let u = o(l);
92
+ let u = n(l);
93
93
  if (!u) {
94
- reject(i, "malformed");
94
+ reject(o, "malformed");
95
95
  return;
96
96
  }
97
97
  if (!e.dedupe.check(u.id)) {
98
- reject(i, "duplicate");
98
+ reject(o, "duplicate");
99
99
  return;
100
100
  }
101
- let d = a.get(u.t);
101
+ let d = i.get(u.t);
102
102
  if (!d || d.size === 0) {
103
- reject(i, "unknown-type");
103
+ reject(o, "unknown-type");
104
104
  return;
105
105
  }
106
106
  t.emitTap({
107
107
  bytes: s,
108
108
  messageType: u.t,
109
- peerId: i,
109
+ peerId: o,
110
110
  type: "message-received"
111
111
  });
112
112
  let f = {
113
113
  messageId: u.id,
114
114
  payload: u.p,
115
- peerId: i,
115
+ peerId: o,
116
116
  sentAt: u.ts
117
117
  };
118
118
  for (let e of d) try {
119
119
  e(f);
120
120
  } catch (e) {
121
121
  t.emitTap({
122
- error: new n("Message handler threw", { cause: e }),
122
+ error: new a("Message handler threw", { cause: e }),
123
123
  type: "error"
124
124
  });
125
125
  }
126
126
  },
127
127
  on(e, t) {
128
- let n = a.get(e);
129
- return n || (n = /* @__PURE__ */ new Set(), a.set(e, n)), n.add(t), () => {
130
- n.delete(t), n.size === 0 && a.delete(e);
128
+ let n = i.get(e);
129
+ return n || (n = /* @__PURE__ */ new Set(), i.set(e, n)), n.add(t), () => {
130
+ n.delete(t), n.size === 0 && i.delete(e);
131
131
  };
132
132
  },
133
- sendTo(n, a, o) {
133
+ sendTo(n, i, a) {
134
134
  let s = n.dc;
135
- if (!s || s.readyState !== "open" || n.status !== "connected") throw new e(`Peer "${n.id}" is not connected`, n.id);
135
+ if (!s || s.readyState !== "open" || n.status !== "connected") throw new r(`Peer "${n.id}" is not connected`, n.id);
136
136
  let l = {
137
- id: i(t.random),
138
- p: o,
139
- t: a,
137
+ id: e(t.random),
138
+ p: a,
139
+ t: i,
140
140
  ts: t.clock(),
141
141
  v: 1
142
142
  }, u;
143
143
  try {
144
144
  u = JSON.stringify(l);
145
145
  } catch (e) {
146
- throw new r(`Failed to serialize message "${a}"`, { cause: e });
146
+ throw new o(`Failed to serialize message "${i}"`, { cause: e });
147
147
  }
148
148
  let d = c(u);
149
- if (d > t.maxMessageBytes) throw new r(`Message "${a}" is ${d} bytes (max ${t.maxMessageBytes})`);
149
+ if (d > t.maxMessageBytes) throw new o(`Message "${i}" is ${d} bytes (max ${t.maxMessageBytes})`);
150
150
  try {
151
151
  s.send(u);
152
- } catch (t) {
153
- throw new e(`Failed to send message "${a}"`, n.id, { cause: t });
152
+ } catch (e) {
153
+ throw new r(`Failed to send message "${i}"`, n.id, { cause: e });
154
154
  }
155
155
  t.emitTap({
156
156
  bytes: d,
157
- messageType: a,
157
+ messageType: i,
158
158
  peerId: n.id,
159
159
  type: "message-sent"
160
160
  });
161
161
  }
162
162
  };
163
163
  }
164
- //#endregion
165
164
  export { createMessenger, createNodeCore, createPeerRecord };
166
165
 
167
166
  //# sourceMappingURL=_node.js.map
package/dist/_proof.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs"),t=require("./_sdp.cjs");let n=require("@vielzeug/arsenal");async function createProof(r,i){let a=globalThis.crypto?.subtle;if(!a)throw new e.MeshUnsupportedError(`WebCrypto is unavailable: pairing requires a secure context`);let o=new TextEncoder,s=await a.importKey(`raw`,o.encode(r),{hash:`SHA-256`,name:`HMAC`},!1,[`sign`]),c=await a.sign(`HMAC`,s,o.encode(t.canonicalSdp(i)));return(0,n.bytesToBase64Url)(new Uint8Array(c))}exports.createProof=createProof;
1
+ const e=require("./_sdp.cjs"),t=require("./errors.cjs");let n=require("@vielzeug/arsenal");async function createProof(r,i){let a=globalThis.crypto?.subtle;if(!a)throw new t.MeshUnsupportedError(`WebCrypto is unavailable: pairing requires a secure context`);let o=new TextEncoder,s=await a.importKey(`raw`,o.encode(r),{hash:`SHA-256`,name:`HMAC`},!1,[`sign`]),c=await a.sign(`HMAC`,s,o.encode(e.canonicalSdp(i)));return(0,n.bytesToBase64Url)(new Uint8Array(c))}exports.createProof=createProof;
2
2
  //# sourceMappingURL=_proof.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_proof.cjs","names":["MeshUnsupportedError","canonicalSdp","bytesToBase64Url","require_errors","require__sdp","_vielzeug_arsenal","subtle","encoder","key","secret","signature","sdp"],"sources":["../src/_proof.ts"],"sourcesContent":["import { bytesToBase64Url } from '@vielzeug/arsenal';\nimport { canonicalSdp } from './_sdp';\nimport { MeshUnsupportedError } from './errors';\n\n/**\n * Proof of invitation possession: HMAC-SHA-256 of the guest SDP keyed by the\n * invitation secret. The SDP is canonicalized first so the proof is stable\n * across pairing codecs that rebuild the description (see `mq2.`). The host\n * recomputes the proof without receiving the secret back.\n *\n * WebCrypto is required: a non-cryptographic fallback would make the proof\n * forgeable, which defeats its purpose. Environments without `SubtleCrypto`\n * (insecure contexts) must pair over `https://` or `localhost` instead.\n *\n * @throws {MeshUnsupportedError} When `SubtleCrypto` is unavailable.\n */\nexport async function createProof(secret: string, sdp: string): Promise<string> {\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) {\n throw new MeshUnsupportedError('WebCrypto is unavailable: pairing requires a secure context');\n }\n const encoder = new TextEncoder();\n const key = await subtle.importKey('raw', encoder.encode(secret), { hash: 'SHA-256', name: 'HMAC' }, false, ['sign']);\n const signature = await subtle.sign('HMAC', key, encoder.encode(canonicalSdp(sdp)));\n return bytesToBase64Url(new Uint8Array(signature));\n}\n"],"mappings":"A,2FAgBA,eAAsB,YAAY,EAAgB,EAA8B,CAC9E,IAAMM,EAAS,WAAW,QAAQ,OAClC,GAAI,CAACA,EACH,MAAM,IAAIN,EAAAA,qBAAqB,6DAA6D,EAE9F,IAAMO,EAAU,IAAI,YACdC,EAAM,MAAMF,EAAO,UAAU,MAAOC,EAAQ,OAAOE,CAAM,EAAG,CAAE,KAAM,UAAW,KAAM,MAAO,EAAG,GAAO,CAAC,MAAM,CAAC,EAC9GC,EAAY,MAAMJ,EAAO,KAAK,OAAQE,EAAKD,EAAQ,OAAON,EAAAA,aAAaU,CAAG,CAAC,CAAC,EAClF,OAAA,EAAOT,EAAAA,iBAAAA,CAAiB,IAAI,WAAWQ,CAAS,CAAC,CACnD,C"}
1
+ {"version":3,"file":"_proof.cjs","names":["MeshUnsupportedError","canonicalSdp","bytesToBase64Url","require__sdp","require_errors","_vielzeug_arsenal","subtle","encoder","key","secret","signature","sdp"],"sources":["../src/_proof.ts"],"sourcesContent":["import { bytesToBase64Url } from '@vielzeug/arsenal';\nimport { canonicalSdp } from './_sdp';\nimport { MeshUnsupportedError } from './errors';\n\n/**\n * Proof of invitation possession: HMAC-SHA-256 of the guest SDP keyed by the\n * invitation secret. The SDP is canonicalized first so the proof is stable\n * across pairing codecs that rebuild the description (see `mq2.`). The host\n * recomputes the proof without receiving the secret back.\n *\n * WebCrypto is required: a non-cryptographic fallback would make the proof\n * forgeable, which defeats its purpose. Environments without `SubtleCrypto`\n * (insecure contexts) must pair over `https://` or `localhost` instead.\n *\n * @throws {MeshUnsupportedError} When `SubtleCrypto` is unavailable.\n */\nexport async function createProof(secret: string, sdp: string): Promise<string> {\n const subtle = globalThis.crypto?.subtle;\n if (!subtle) {\n throw new MeshUnsupportedError('WebCrypto is unavailable: pairing requires a secure context');\n }\n const encoder = new TextEncoder();\n const key = await subtle.importKey('raw', encoder.encode(secret), { hash: 'SHA-256', name: 'HMAC' }, false, ['sign']);\n const signature = await subtle.sign('HMAC', key, encoder.encode(canonicalSdp(sdp)));\n return bytesToBase64Url(new Uint8Array(signature));\n}\n"],"mappings":"A,2FAgBA,eAAsB,YAAY,EAAgB,EAA8B,CAC9E,IAAMM,EAAS,WAAW,QAAQ,OAClC,GAAI,CAACA,EACH,MAAM,IAAIN,EAAAA,qBAAqB,6DAA6D,EAE9F,IAAMO,EAAU,IAAI,YACdC,EAAM,MAAMF,EAAO,UAAU,MAAOC,EAAQ,OAAOE,CAAM,EAAG,CAAE,KAAM,UAAW,KAAM,MAAO,EAAG,GAAO,CAAC,MAAM,CAAC,EAC9GC,EAAY,MAAMJ,EAAO,KAAK,OAAQE,EAAKD,EAAQ,OAAON,EAAAA,aAAaU,CAAG,CAAC,CAAC,EAClF,OAAA,EAAOT,EAAAA,iBAAAA,CAAiB,IAAI,WAAWQ,CAAS,CAAC,CACnD,C"}
package/dist/_proof.js CHANGED
@@ -1,17 +1,16 @@
1
- import { MeshUnsupportedError as e } from "./errors.js";
2
- import { canonicalSdp as t } from "./_sdp.js";
1
+ import { canonicalSdp as e } from "./_sdp.js";
2
+ import { MeshUnsupportedError as t } from "./errors.js";
3
3
  import { bytesToBase64Url as n } from "@vielzeug/arsenal";
4
- //#region src/_proof.ts
4
+ // src/_proof.ts
5
5
  async function createProof(r, i) {
6
6
  let a = globalThis.crypto?.subtle;
7
- if (!a) throw new e("WebCrypto is unavailable: pairing requires a secure context");
7
+ if (!a) throw new t("WebCrypto is unavailable: pairing requires a secure context");
8
8
  let o = new TextEncoder(), s = await a.importKey("raw", o.encode(r), {
9
9
  hash: "SHA-256",
10
10
  name: "HMAC"
11
- }, !1, ["sign"]), c = await a.sign("HMAC", s, o.encode(t(i)));
11
+ }, !1, ["sign"]), c = await a.sign("HMAC", s, o.encode(e(i)));
12
12
  return n(new Uint8Array(c));
13
13
  }
14
- //#endregion
15
14
  export { createProof };
16
15
 
17
16
  //# sourceMappingURL=_proof.js.map
package/dist/_random.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { bytesToBase64Url as e, randomFloat as t } from "@vielzeug/arsenal";
2
- //#region src/_random.ts
2
+ // src/_random.ts
3
3
  function randomBytes(e, n) {
4
4
  let r = new Uint8Array(e);
5
5
  if (n) for (let i = 0; i < e; i++) r[i] = Math.floor(t(n) * 256);
@@ -9,7 +9,6 @@ function randomBytes(e, n) {
9
9
  function randomId(t) {
10
10
  return e(randomBytes(16, t));
11
11
  }
12
- //#endregion
13
12
  export { randomBytes, randomId };
14
13
 
15
14
  //# sourceMappingURL=_random.js.map
package/dist/_rtc.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { MeshConnectionError as e, MeshDisposedError as t, MeshTimeoutError as n, MeshUnsupportedError as r } from "./errors.js";
2
- //#region src/_rtc.ts
2
+ // src/_rtc.ts
3
3
  var i = { createPeerConnection(e) {
4
4
  let t = globalThis.RTCPeerConnection;
5
5
  if (!t) throw new r("WebRTC is unavailable in this environment: inject an `rtc` factory");
@@ -36,7 +36,6 @@ function waitChannelOpen(r, i, a, o, s) {
36
36
  u = setTimeout(() => fail(timeout()), a), r.addEventListener("open", onOpen), r.addEventListener("close", onClose), i.addEventListener("iceconnectionstatechange", onIce), s.addEventListener("abort", onDispose, { once: !0 });
37
37
  });
38
38
  }
39
- //#endregion
40
39
  export { i as nativeRtcFactory, normalizeSdp, waitChannelOpen, waitIceGathering };
41
40
 
42
41
  //# sourceMappingURL=_rtc.js.map
package/dist/_sdp.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { MeshPairingError as e } from "./errors.js";
2
- //#region src/_sdp.ts
2
+ // src/_sdp.ts
3
3
  function isCompactSdp(e) {
4
4
  if (typeof e != "object" || !e) return !1;
5
5
  let t = e;
@@ -49,7 +49,6 @@ function expandSdp(e) {
49
49
  ""
50
50
  ].join("\r\n")}`;
51
51
  }
52
- //#endregion
53
52
  export { canonicalSdp, compactSdp, expandSdp, restoreSdp };
54
53
 
55
54
  //# sourceMappingURL=_sdp.js.map
package/dist/_wire.js CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/_wire.ts
1
+ // src/_wire.ts
2
2
  function toEnvelope(e) {
3
3
  if (typeof e != "object" || !e) return null;
4
4
  let t = e;
@@ -15,7 +15,6 @@ var MessageDedupe = class {
15
15
  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);
16
16
  }
17
17
  };
18
- //#endregion
19
18
  export { MessageDedupe, toEnvelope };
20
19
 
21
20
  //# sourceMappingURL=_wire.js.map
package/dist/codec.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { MeshPairingError as e } from "./errors.js";
2
2
  import { base64UrlToText as t, textToBase64Url as n } from "@vielzeug/arsenal";
3
- //#region src/codec.ts
3
+ // src/codec.ts
4
4
  function isRecord(e) {
5
5
  return typeof e == "object" && !!e;
6
6
  }
@@ -29,7 +29,6 @@ var r = {
29
29
  return n(JSON.stringify(e));
30
30
  }
31
31
  };
32
- //#endregion
33
32
  export { isRecord, r as meshCodec, parsePairingPayload };
34
33
 
35
34
  //# sourceMappingURL=codec.js.map
package/dist/errors.js CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/errors.ts
1
+ // src/errors.ts
2
2
  var MeshError = class extends Error {
3
3
  constructor(e, t) {
4
4
  super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
@@ -13,7 +13,6 @@ var MeshError = class extends Error {
13
13
  super(`${e} is disposed`, t);
14
14
  }
15
15
  }, MeshUnsupportedError = class extends MeshError {};
16
- //#endregion
17
16
  export { MeshConnectionError, MeshDisposedError, MeshError, MeshPairingError, MeshPayloadError, MeshTimeoutError, MeshUnsupportedError };
18
17
 
19
18
  //# sourceMappingURL=errors.js.map
package/dist/guest.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");function createMeshGuest(a={}){let o=a.rtc??i.nativeRtcFactory,s=a.clock??(()=>Date.now()),c=a.maxMessageBytes??65536,l=a.channelOpenTimeoutMs??6e4,u=a.iceGatheringTimeoutMs??5e3,d=n.createNodeCore(a.signal),f=n.createMessenger({clock:s,emitTap:d.emitTap,maxMessageBytes:c,random:a.random}),p=null,m=null,openTimeoutError=()=>new e.MeshTimeoutError(`Timed out waiting ${l}ms for the data channel to open`);function failPeer(e,t){d.disposed||e!==p||e.status===`failed`||(e.status=`failed`,m&&=(clearTimeout(m),null),setTimeout(()=>{e.dc?.close(),e.pc?.close()},0),d.emitTap({peerId:e.id,status:`failed`,type:`peer-status-change`}),d.emitTap({error:t,type:`error`}),d.emitTap({peer:e.publicPeer,reason:t.message,type:`peer-left`}),d.setStatus(`failed`))}function disconnectPeer(e,t){d.disposed||e!==p||e.status!==`disconnected`&&e.status!==`failed`&&(e.status=`disconnected`,e.pc?.close(),d.emitTap({peerId:e.id,status:`disconnected`,type:`peer-status-change`}),d.emitTap({peer:e.publicPeer,reason:t,type:`peer-left`}),d.setStatus(`disconnected`))}function wireChannel(t,n,r){t.dc=r,r.addEventListener(`open`,()=>{d.disposed||(m&&=(clearTimeout(m),null),t.status=`connected`,d.emitTap({peerId:t.id,status:`connected`,type:`peer-status-change`}),d.emitTap({peer:t.publicPeer,type:`peer-joined`}),d.setStatus(`connected`))}),r.addEventListener(`message`,e=>{d.disposed||f.handleInbound(t,e.data)}),r.addEventListener(`close`,()=>{d.disposed||(t.status===`connecting`?failPeer(t,m?openTimeoutError():new e.MeshConnectionError(`channel closed before opening`,t.id)):disconnectPeer(t,`channel closed`))}),r.addEventListener(`error`,()=>{d.disposed||failPeer(t,new e.MeshConnectionError(`channel error`,t.id))}),n.addEventListener(`iceconnectionstatechange`,()=>{if(d.disposed)return;let r=n.iceConnectionState;r===`failed`||r===`closed`?failPeer(t,new e.MeshConnectionError(`ice ${r}`,t.id)):r===`disconnected`&&t.status===`connected`&&disconnectPeer(t,`ice disconnected`)})}return{async acceptInvitation(c,f){if(d.ensureLive(),p&&p.status!==`failed`&&p.status!==`disconnected`)throw new e.MeshPairingError(`Guest is already paired`);if(c?.v!==1)throw new e.MeshPairingError(`Unsupported invitation version`);if(s()>=c.expiresAt)throw new e.MeshPairingError(`Invitation expired`);p&&=(m&&=(clearTimeout(m),null),p.pc?.close(),null),d.setStatus(`pairing`);let h;try{h=o.createPeerConnection({iceServers:[...a.iceServers??[]]})}catch(e){throw d.setStatus(`idle`),e}let g=n.createPeerRecord(c.sessionId,c.hostName,`host`);g.pc=h,p=g,h.addEventListener(`datachannel`,e=>{wireChannel(g,h,e.channel)}),h.addEventListener(`iceconnectionstatechange`,()=>{d.disposed||d.emitTap({peerId:g.id,state:h.iceConnectionState,type:`ice-state`})});try{await h.setRemoteDescription({sdp:i.normalizeSdp(c.sdp),type:`offer`})}catch(t){throw p=null,d.setStatus(`idle`),new e.MeshPairingError(`Invalid invitation SDP`,{cause:t})}let _;try{await h.setLocalDescription(await h.createAnswer()),await i.waitIceGathering(h,u),_=h.localDescription?.sdp??``}catch(t){throw p=null,h.close(),d.setStatus(`idle`),new e.MeshConnectionError(`Failed to create answer`,null,{cause:t})}if(d.disposed)throw h.close(),new e.MeshDisposedError;if(!_)throw p=null,h.close(),d.setStatus(`idle`),new e.MeshConnectionError(`Local description has no SDP`);let v=t.randomId(a.random),y;try{y=await r.createProof(c.secret,_)}catch(e){throw p=null,h.close(),d.setStatus(`idle`),e}return d.setStatus(`connecting`),m=setTimeout(()=>{p?.status===`connecting`&&failPeer(p,openTimeoutError())},l),{peer:f?.name===void 0?{id:v}:{id:v,name:f.name},proof:y,sdp:_,sessionId:c.sessionId,v:1}},get disposalSignal(){return d.disposalCtrl.signal},dispose(){d.disposed||(d.markDisposed(),m&&=(clearTimeout(m),null),p&&(p.dc?.close(),p.pc?.close()),f.clear())},get disposed(){return d.disposed},get host(){return p?.publicPeer??null},on(e,t){return d.ensureLive(),f.on(e,t)},send(t,n){if(d.ensureLive(),!p)throw new e.MeshConnectionError(`Guest is not paired`);f.sendTo(p,t,n)},get status(){return d.status},tap(e,t){return d.tap(e,t)},[Symbol.dispose](){this.dispose()}}}exports.createMeshGuest=createMeshGuest;
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");function createMeshGuest(a={}){let o=a.rtc??r.nativeRtcFactory,s=a.clock??(()=>Date.now()),c=a.maxMessageBytes??65536,l=a.channelOpenTimeoutMs??6e4,u=a.iceGatheringTimeoutMs??5e3,d=e.createNodeCore(a.signal),f=e.createMessenger({clock:s,emitTap:d.emitTap,maxMessageBytes:c,random:a.random}),p=null,m=null,openTimeoutError=()=>new i.MeshTimeoutError(`Timed out waiting ${l}ms for the data channel to open`);function failPeer(e,t){d.disposed||e!==p||e.status===`failed`||(e.status=`failed`,m&&=(clearTimeout(m),null),setTimeout(()=>{e.dc?.close(),e.pc?.close()},0),d.emitTap({peerId:e.id,status:`failed`,type:`peer-status-change`}),d.emitTap({error:t,type:`error`}),d.emitTap({peer:e.publicPeer,reason:t.message,type:`peer-left`}),d.setStatus(`failed`))}function disconnectPeer(e,t){d.disposed||e!==p||e.status!==`disconnected`&&e.status!==`failed`&&(e.status=`disconnected`,e.pc?.close(),d.emitTap({peerId:e.id,status:`disconnected`,type:`peer-status-change`}),d.emitTap({peer:e.publicPeer,reason:t,type:`peer-left`}),d.setStatus(`disconnected`))}function wireChannel(e,t,n){e.dc=n,n.addEventListener(`open`,()=>{d.disposed||(m&&=(clearTimeout(m),null),e.status=`connected`,d.emitTap({peerId:e.id,status:`connected`,type:`peer-status-change`}),d.emitTap({peer:e.publicPeer,type:`peer-joined`}),d.setStatus(`connected`))}),n.addEventListener(`message`,t=>{d.disposed||f.handleInbound(e,t.data)}),n.addEventListener(`close`,()=>{d.disposed||(e.status===`connecting`?failPeer(e,m?openTimeoutError():new i.MeshConnectionError(`channel closed before opening`,e.id)):disconnectPeer(e,`channel closed`))}),n.addEventListener(`error`,()=>{d.disposed||failPeer(e,new i.MeshConnectionError(`channel error`,e.id))}),t.addEventListener(`iceconnectionstatechange`,()=>{if(d.disposed)return;let n=t.iceConnectionState;n===`failed`||n===`closed`?failPeer(e,new i.MeshConnectionError(`ice ${n}`,e.id)):n===`disconnected`&&e.status===`connected`&&disconnectPeer(e,`ice disconnected`)})}return{async acceptInvitation(c,f){if(d.ensureLive(),p&&p.status!==`failed`&&p.status!==`disconnected`)throw new i.MeshPairingError(`Guest is already paired`);if(c?.v!==1)throw new i.MeshPairingError(`Unsupported invitation version`);if(s()>=c.expiresAt)throw new i.MeshPairingError(`Invitation expired`);p&&=(m&&=(clearTimeout(m),null),p.pc?.close(),null),d.setStatus(`pairing`);let h;try{h=o.createPeerConnection({iceServers:[...a.iceServers??[]]})}catch(e){throw d.setStatus(`idle`),e}let g=e.createPeerRecord(c.sessionId,c.hostName,`host`);g.pc=h,p=g,h.addEventListener(`datachannel`,e=>{wireChannel(g,h,e.channel)}),h.addEventListener(`iceconnectionstatechange`,()=>{d.disposed||d.emitTap({peerId:g.id,state:h.iceConnectionState,type:`ice-state`})});try{await h.setRemoteDescription({sdp:r.normalizeSdp(c.sdp),type:`offer`})}catch(e){throw p=null,d.setStatus(`idle`),new i.MeshPairingError(`Invalid invitation SDP`,{cause:e})}let _;try{await h.setLocalDescription(await h.createAnswer()),await r.waitIceGathering(h,u),_=h.localDescription?.sdp??``}catch(e){throw p=null,h.close(),d.setStatus(`idle`),new i.MeshConnectionError(`Failed to create answer`,null,{cause:e})}if(d.disposed)throw h.close(),new i.MeshDisposedError;if(!_)throw p=null,h.close(),d.setStatus(`idle`),new i.MeshConnectionError(`Local description has no SDP`);let v=n.randomId(a.random),y;try{y=await t.createProof(c.secret,_)}catch(e){throw p=null,h.close(),d.setStatus(`idle`),e}return d.setStatus(`connecting`),m=setTimeout(()=>{p?.status===`connecting`&&failPeer(p,openTimeoutError())},l),{peer:f?.name===void 0?{id:v}:{id:v,name:f.name},proof:y,sdp:_,sessionId:c.sessionId,v:1}},get disposalSignal(){return d.disposalCtrl.signal},dispose(){d.disposed||(d.markDisposed(),m&&=(clearTimeout(m),null),p&&(p.dc?.close(),p.pc?.close()),f.clear())},get disposed(){return d.disposed},get host(){return p?.publicPeer??null},on(e,t){return d.ensureLive(),f.on(e,t)},send(e,t){if(d.ensureLive(),!p)throw new i.MeshConnectionError(`Guest is not paired`);f.sendTo(p,e,t)},get status(){return d.status},tap(e,t){return d.tap(e,t)},[Symbol.dispose](){this.dispose()}}}exports.createMeshGuest=createMeshGuest;
2
2
  //# sourceMappingURL=guest.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"guest.cjs","names":["nativeRtcFactory","createNodeCore","createMessenger","MeshTimeoutError","MeshConnectionError","MeshPairingError","createPeerRecord","normalizeSdp","waitIceGathering","MeshDisposedError","randomId","createProof","require_errors","require__random","require__node","require__proof","require__rtc","rtc","options","clock","maxMessageBytes","channelOpenTimeoutMs","iceGatheringTimeoutMs","core","messenger","hostPeer","openTimer","peer","error","dc","event","pc","state","invitation","cause","answerSdp","peerId","proof","meta","type","handler","payload","tapOptions"],"sources":["../src/guest.ts"],"sourcesContent":["import {\n DEFAULT_CHANNEL_OPEN_TIMEOUT_MS,\n DEFAULT_ICE_GATHERING_TIMEOUT_MS,\n DEFAULT_MAX_MESSAGE_BYTES,\n} from './_defaults';\nimport { createMessenger, createNodeCore, createPeerRecord, type PeerRecord } from './_node';\nimport { createProof } from './_proof';\nimport { randomId } from './_random';\nimport { nativeRtcFactory, normalizeSdp, waitIceGathering } from './_rtc';\nimport { MeshConnectionError, MeshDisposedError, type MeshError, MeshPairingError, MeshTimeoutError } from './errors';\nimport type {\n MeshGuest,\n MeshGuestOptions,\n MeshInbound,\n MeshPeer,\n MeshProtocol,\n RTCDataChannelLike,\n RTCPeerConnectionLike,\n} from './types';\n\n/**\n * Create a guest mesh node. The guest pairs with one host by consuming an\n * out-of-band invitation and returning an answer.\n */\nexport function createMeshGuest<P extends MeshProtocol>(options: MeshGuestOptions = {}): MeshGuest<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 channelOpenTimeoutMs = options.channelOpenTimeoutMs ?? DEFAULT_CHANNEL_OPEN_TIMEOUT_MS;\n const iceGatheringTimeoutMs = options.iceGatheringTimeoutMs ?? DEFAULT_ICE_GATHERING_TIMEOUT_MS;\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 let hostPeer: PeerRecord | null = null;\n let openTimer: ReturnType<typeof setTimeout> | null = null;\n\n const openTimeoutError = (): MeshTimeoutError =>\n new MeshTimeoutError(`Timed out waiting ${channelOpenTimeoutMs}ms for the data channel to open`);\n\n function failPeer(peer: PeerRecord, error: MeshError): void {\n // Ignore events from a superseded peer after re-pairing replaced hostPeer.\n if (core.disposed || peer !== hostPeer || peer.status === 'failed') return;\n peer.status = 'failed';\n if (openTimer) {\n clearTimeout(openTimer);\n openTimer = null;\n }\n // Teardown is deferred a macrotask so a peer racing its own open\n // deadline reports its timeout rather than this hangup.\n setTimeout(() => {\n peer.dc?.close();\n peer.pc?.close();\n }, 0);\n core.emitTap({ peerId: peer.id, status: 'failed', type: 'peer-status-change' });\n core.emitTap({ error, type: 'error' });\n core.emitTap({ peer: peer.publicPeer, reason: error.message, type: 'peer-left' });\n core.setStatus('failed');\n }\n\n function disconnectPeer(peer: PeerRecord, reason: string): void {\n if (core.disposed || peer !== hostPeer) return;\n if (peer.status === 'disconnected' || peer.status === 'failed') return;\n peer.status = 'disconnected';\n peer.pc?.close();\n core.emitTap({ peerId: peer.id, status: 'disconnected', type: 'peer-status-change' });\n core.emitTap({ peer: peer.publicPeer, reason, type: 'peer-left' });\n core.setStatus('disconnected');\n }\n\n function wireChannel(peer: PeerRecord, pc: RTCPeerConnectionLike, dc: RTCDataChannelLike): void {\n peer.dc = dc;\n\n dc.addEventListener('open', () => {\n if (core.disposed) return;\n if (openTimer) {\n clearTimeout(openTimer);\n openTimer = null;\n }\n peer.status = 'connected';\n core.emitTap({ peerId: peer.id, status: 'connected', type: 'peer-status-change' });\n core.emitTap({ peer: peer.publicPeer, type: 'peer-joined' });\n core.setStatus('connected');\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 if (peer.status === 'connecting')\n failPeer(\n peer,\n openTimer ? openTimeoutError() : new MeshConnectionError('channel closed before opening', peer.id),\n );\n else disconnectPeer(peer, 'channel closed');\n });\n dc.addEventListener('error', () => {\n if (core.disposed) return;\n failPeer(peer, new MeshConnectionError('channel error', peer.id));\n });\n\n // The ice-state emission lives on the pairing-scoped listener above; this\n // one owns channel-lifecycle transitions only, so each ICE change emits\n // exactly one ice-state event.\n pc.addEventListener('iceconnectionstatechange', () => {\n if (core.disposed) return;\n const state = pc.iceConnectionState;\n if (state === 'failed' || state === 'closed') {\n failPeer(peer, new MeshConnectionError(`ice ${state}`, peer.id));\n } else if (state === 'disconnected' && peer.status === 'connected') {\n disconnectPeer(peer, 'ice disconnected');\n }\n });\n }\n\n const guest: MeshGuest<P> = {\n async acceptInvitation(invitation, meta) {\n core.ensureLive();\n // A terminal host peer (failed/disconnected) may be replaced by a fresh\n // pairing on the same node: listeners stay attached and keep working.\n if (hostPeer && hostPeer.status !== 'failed' && hostPeer.status !== 'disconnected') {\n throw new MeshPairingError('Guest is already paired');\n }\n if (invitation?.v !== 1) throw new MeshPairingError('Unsupported invitation version');\n if (clock() >= invitation.expiresAt) throw new MeshPairingError('Invitation expired');\n\n if (hostPeer) {\n if (openTimer) {\n clearTimeout(openTimer);\n openTimer = null;\n }\n hostPeer.pc?.close();\n hostPeer = null;\n }\n\n core.setStatus('pairing');\n let pc: RTCPeerConnectionLike;\n try {\n pc = rtc.createPeerConnection({ iceServers: [...(options.iceServers ?? [])] });\n } catch (error) {\n core.setStatus('idle');\n throw error;\n }\n\n // The invitation carries no host identity beyond an optional display\n // name: the session id doubles as the host-side peer id for inbound\n // metadata and tap events.\n const peer = createPeerRecord(invitation.sessionId, invitation.hostName, 'host');\n peer.pc = pc;\n hostPeer = peer;\n\n pc.addEventListener('datachannel', (event) => {\n wireChannel(peer, pc, event.channel);\n });\n pc.addEventListener('iceconnectionstatechange', () => {\n if (core.disposed) return;\n core.emitTap({ peerId: peer.id, state: pc.iceConnectionState, type: 'ice-state' });\n });\n\n try {\n await pc.setRemoteDescription({ sdp: normalizeSdp(invitation.sdp), type: 'offer' });\n } catch (cause) {\n hostPeer = null;\n core.setStatus('idle');\n throw new MeshPairingError('Invalid invitation SDP', { cause });\n }\n\n let answerSdp: string;\n try {\n await pc.setLocalDescription(await pc.createAnswer());\n await waitIceGathering(pc, iceGatheringTimeoutMs);\n answerSdp = pc.localDescription?.sdp ?? '';\n } catch (cause) {\n hostPeer = null;\n pc.close();\n core.setStatus('idle');\n throw new MeshConnectionError('Failed to create answer', null, { cause });\n }\n if (core.disposed) {\n pc.close();\n throw new MeshDisposedError();\n }\n if (!answerSdp) {\n hostPeer = null;\n pc.close();\n core.setStatus('idle');\n throw new MeshConnectionError('Local description has no SDP');\n }\n\n const peerId = randomId(options.random);\n let proof: string;\n try {\n proof = await createProof(invitation.secret, answerSdp);\n } catch (cause) {\n hostPeer = null;\n pc.close();\n core.setStatus('idle');\n throw cause;\n }\n core.setStatus('connecting');\n\n // If the channel never opens (host never accepts the answer), fail loudly\n // instead of hanging in 'connecting' forever. The deadline must cover\n // the human carry-back of the answer, not just machine negotiation.\n openTimer = setTimeout(() => {\n if (hostPeer?.status === 'connecting') {\n failPeer(hostPeer, openTimeoutError());\n }\n }, channelOpenTimeoutMs);\n\n return {\n peer: meta?.name === undefined ? { id: peerId } : { id: peerId, name: meta.name },\n proof,\n sdp: answerSdp,\n sessionId: invitation.sessionId,\n v: 1,\n };\n },\n get disposalSignal() {\n return core.disposalCtrl.signal;\n },\n\n dispose() {\n if (core.disposed) return;\n core.markDisposed();\n if (openTimer) {\n clearTimeout(openTimer);\n openTimer = null;\n }\n if (hostPeer) {\n hostPeer.dc?.close();\n hostPeer.pc?.close();\n }\n messenger.clear();\n },\n get disposed() {\n return core.disposed;\n },\n get host(): MeshPeer | null {\n return hostPeer?.publicPeer ?? null;\n },\n\n on(type, handler) {\n core.ensureLive();\n return messenger.on(type, handler as (message: MeshInbound<unknown>) => void);\n },\n\n send(type, payload) {\n core.ensureLive();\n if (!hostPeer) throw new MeshConnectionError('Guest is not paired');\n messenger.sendTo(hostPeer, 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 guest;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"guest.cjs","names":["nativeRtcFactory","createNodeCore","createMessenger","MeshTimeoutError","MeshConnectionError","MeshPairingError","createPeerRecord","normalizeSdp","waitIceGathering","MeshDisposedError","randomId","createProof","require__node","require__proof","require__random","require__rtc","require_errors","rtc","options","clock","maxMessageBytes","channelOpenTimeoutMs","iceGatheringTimeoutMs","core","messenger","hostPeer","openTimer","peer","error","dc","event","pc","state","invitation","cause","answerSdp","peerId","proof","meta","type","handler","payload","tapOptions"],"sources":["../src/guest.ts"],"sourcesContent":["import {\n DEFAULT_CHANNEL_OPEN_TIMEOUT_MS,\n DEFAULT_ICE_GATHERING_TIMEOUT_MS,\n DEFAULT_MAX_MESSAGE_BYTES,\n} from './_defaults';\nimport { createMessenger, createNodeCore, createPeerRecord, type PeerRecord } from './_node';\nimport { createProof } from './_proof';\nimport { randomId } from './_random';\nimport { nativeRtcFactory, normalizeSdp, waitIceGathering } from './_rtc';\nimport { MeshConnectionError, MeshDisposedError, type MeshError, MeshPairingError, MeshTimeoutError } from './errors';\nimport type {\n MeshGuest,\n MeshGuestOptions,\n MeshInbound,\n MeshPeer,\n MeshProtocol,\n RTCDataChannelLike,\n RTCPeerConnectionLike,\n} from './types';\n\n/**\n * Create a guest mesh node. The guest pairs with one host by consuming an\n * out-of-band invitation and returning an answer.\n */\nexport function createMeshGuest<P extends MeshProtocol>(options: MeshGuestOptions = {}): MeshGuest<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 channelOpenTimeoutMs = options.channelOpenTimeoutMs ?? DEFAULT_CHANNEL_OPEN_TIMEOUT_MS;\n const iceGatheringTimeoutMs = options.iceGatheringTimeoutMs ?? DEFAULT_ICE_GATHERING_TIMEOUT_MS;\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 let hostPeer: PeerRecord | null = null;\n let openTimer: ReturnType<typeof setTimeout> | null = null;\n\n const openTimeoutError = (): MeshTimeoutError =>\n new MeshTimeoutError(`Timed out waiting ${channelOpenTimeoutMs}ms for the data channel to open`);\n\n function failPeer(peer: PeerRecord, error: MeshError): void {\n // Ignore events from a superseded peer after re-pairing replaced hostPeer.\n if (core.disposed || peer !== hostPeer || peer.status === 'failed') return;\n peer.status = 'failed';\n if (openTimer) {\n clearTimeout(openTimer);\n openTimer = null;\n }\n // Teardown is deferred a macrotask so a peer racing its own open\n // deadline reports its timeout rather than this hangup.\n setTimeout(() => {\n peer.dc?.close();\n peer.pc?.close();\n }, 0);\n core.emitTap({ peerId: peer.id, status: 'failed', type: 'peer-status-change' });\n core.emitTap({ error, type: 'error' });\n core.emitTap({ peer: peer.publicPeer, reason: error.message, type: 'peer-left' });\n core.setStatus('failed');\n }\n\n function disconnectPeer(peer: PeerRecord, reason: string): void {\n if (core.disposed || peer !== hostPeer) return;\n if (peer.status === 'disconnected' || peer.status === 'failed') return;\n peer.status = 'disconnected';\n peer.pc?.close();\n core.emitTap({ peerId: peer.id, status: 'disconnected', type: 'peer-status-change' });\n core.emitTap({ peer: peer.publicPeer, reason, type: 'peer-left' });\n core.setStatus('disconnected');\n }\n\n function wireChannel(peer: PeerRecord, pc: RTCPeerConnectionLike, dc: RTCDataChannelLike): void {\n peer.dc = dc;\n\n dc.addEventListener('open', () => {\n if (core.disposed) return;\n if (openTimer) {\n clearTimeout(openTimer);\n openTimer = null;\n }\n peer.status = 'connected';\n core.emitTap({ peerId: peer.id, status: 'connected', type: 'peer-status-change' });\n core.emitTap({ peer: peer.publicPeer, type: 'peer-joined' });\n core.setStatus('connected');\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 if (peer.status === 'connecting')\n failPeer(\n peer,\n openTimer ? openTimeoutError() : new MeshConnectionError('channel closed before opening', peer.id),\n );\n else disconnectPeer(peer, 'channel closed');\n });\n dc.addEventListener('error', () => {\n if (core.disposed) return;\n failPeer(peer, new MeshConnectionError('channel error', peer.id));\n });\n\n // The ice-state emission lives on the pairing-scoped listener above; this\n // one owns channel-lifecycle transitions only, so each ICE change emits\n // exactly one ice-state event.\n pc.addEventListener('iceconnectionstatechange', () => {\n if (core.disposed) return;\n const state = pc.iceConnectionState;\n if (state === 'failed' || state === 'closed') {\n failPeer(peer, new MeshConnectionError(`ice ${state}`, peer.id));\n } else if (state === 'disconnected' && peer.status === 'connected') {\n disconnectPeer(peer, 'ice disconnected');\n }\n });\n }\n\n const guest: MeshGuest<P> = {\n async acceptInvitation(invitation, meta) {\n core.ensureLive();\n // A terminal host peer (failed/disconnected) may be replaced by a fresh\n // pairing on the same node: listeners stay attached and keep working.\n if (hostPeer && hostPeer.status !== 'failed' && hostPeer.status !== 'disconnected') {\n throw new MeshPairingError('Guest is already paired');\n }\n if (invitation?.v !== 1) throw new MeshPairingError('Unsupported invitation version');\n if (clock() >= invitation.expiresAt) throw new MeshPairingError('Invitation expired');\n\n if (hostPeer) {\n if (openTimer) {\n clearTimeout(openTimer);\n openTimer = null;\n }\n hostPeer.pc?.close();\n hostPeer = null;\n }\n\n core.setStatus('pairing');\n let pc: RTCPeerConnectionLike;\n try {\n pc = rtc.createPeerConnection({ iceServers: [...(options.iceServers ?? [])] });\n } catch (error) {\n core.setStatus('idle');\n throw error;\n }\n\n // The invitation carries no host identity beyond an optional display\n // name: the session id doubles as the host-side peer id for inbound\n // metadata and tap events.\n const peer = createPeerRecord(invitation.sessionId, invitation.hostName, 'host');\n peer.pc = pc;\n hostPeer = peer;\n\n pc.addEventListener('datachannel', (event) => {\n wireChannel(peer, pc, event.channel);\n });\n pc.addEventListener('iceconnectionstatechange', () => {\n if (core.disposed) return;\n core.emitTap({ peerId: peer.id, state: pc.iceConnectionState, type: 'ice-state' });\n });\n\n try {\n await pc.setRemoteDescription({ sdp: normalizeSdp(invitation.sdp), type: 'offer' });\n } catch (cause) {\n hostPeer = null;\n core.setStatus('idle');\n throw new MeshPairingError('Invalid invitation SDP', { cause });\n }\n\n let answerSdp: string;\n try {\n await pc.setLocalDescription(await pc.createAnswer());\n await waitIceGathering(pc, iceGatheringTimeoutMs);\n answerSdp = pc.localDescription?.sdp ?? '';\n } catch (cause) {\n hostPeer = null;\n pc.close();\n core.setStatus('idle');\n throw new MeshConnectionError('Failed to create answer', null, { cause });\n }\n if (core.disposed) {\n pc.close();\n throw new MeshDisposedError();\n }\n if (!answerSdp) {\n hostPeer = null;\n pc.close();\n core.setStatus('idle');\n throw new MeshConnectionError('Local description has no SDP');\n }\n\n const peerId = randomId(options.random);\n let proof: string;\n try {\n proof = await createProof(invitation.secret, answerSdp);\n } catch (cause) {\n hostPeer = null;\n pc.close();\n core.setStatus('idle');\n throw cause;\n }\n core.setStatus('connecting');\n\n // If the channel never opens (host never accepts the answer), fail loudly\n // instead of hanging in 'connecting' forever. The deadline must cover\n // the human carry-back of the answer, not just machine negotiation.\n openTimer = setTimeout(() => {\n if (hostPeer?.status === 'connecting') {\n failPeer(hostPeer, openTimeoutError());\n }\n }, channelOpenTimeoutMs);\n\n return {\n peer: meta?.name === undefined ? { id: peerId } : { id: peerId, name: meta.name },\n proof,\n sdp: answerSdp,\n sessionId: invitation.sessionId,\n v: 1,\n };\n },\n get disposalSignal() {\n return core.disposalCtrl.signal;\n },\n\n dispose() {\n if (core.disposed) return;\n core.markDisposed();\n if (openTimer) {\n clearTimeout(openTimer);\n openTimer = null;\n }\n if (hostPeer) {\n hostPeer.dc?.close();\n hostPeer.pc?.close();\n }\n messenger.clear();\n },\n get disposed() {\n return core.disposed;\n },\n get host(): MeshPeer | null {\n return hostPeer?.publicPeer ?? null;\n },\n\n on(type, handler) {\n core.ensureLive();\n return messenger.on(type, handler as (message: MeshInbound<unknown>) => void);\n },\n\n send(type, payload) {\n core.ensureLive();\n if (!hostPeer) throw new MeshConnectionError('Guest is not paired');\n messenger.sendTo(hostPeer, 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 guest;\n}\n"],"mappings":"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"}