@afterrealism/dendri-client 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -1
- package/dist/chunk-G3PMV62Z.js +33 -0
- package/dist/chunk-G3PMV62Z.js.map +1 -0
- package/dist/{chunk-3CE674DE.js → chunk-YLWDCKUW.js} +5 -33
- package/dist/chunk-YLWDCKUW.js.map +1 -0
- package/dist/dendri.browser.global.js +2 -2
- package/dist/dendri.cjs +2 -2
- package/dist/dendri.js +3 -2
- package/dist/dendri.js.map +1 -1
- package/dist/dendri.min.global.js +2 -2
- package/dist/react.cjs +16 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +25 -0
- package/dist/react.d.ts +25 -0
- package/dist/react.js +14 -0
- package/dist/react.js.map +1 -0
- package/dist/store.cjs +2 -2
- package/dist/store.js +2 -1
- package/dist/svelte.cjs +15 -0
- package/dist/svelte.cjs.map +1 -0
- package/dist/svelte.d.cts +29 -0
- package/dist/svelte.d.ts +29 -0
- package/dist/svelte.js +15 -0
- package/dist/svelte.js.map +1 -0
- package/dist/vue.cjs +19 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.d.cts +25 -0
- package/dist/vue.d.ts +25 -0
- package/dist/vue.js +17 -0
- package/dist/vue.js.map +1 -0
- package/package.json +48 -2
- package/dist/chunk-3CE674DE.js.map +0 -1
|
@@ -38,9 +38,9 @@ a=extmap-allow-mixed`)!==-1){let o=i.sdp.split(`
|
|
|
38
38
|
isIOS:${this.isIOS}
|
|
39
39
|
isWebRTCSupported:${this.isWebRTCSupported()}
|
|
40
40
|
isBrowserSupported:${this.isBrowserSupported()}
|
|
41
|
-
isUnifiedPlanSupported:${this.isUnifiedPlanSupported()}`}};var ie=()=>Math.random().toString(36).slice(2);var _t=r=>!r||/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.test(r);var gt={iceServers:[{urls:"stun:stun.l.google.com:19302"},{urls:"stun:stun1.l.google.com:19302"}]},et=class extends R{noop(){}CLOUD_HOST="signal.dendri.dev";CLOUD_PORT=443;chunkedBrowsers={Chrome:1,chrome:1};defaultConfig=gt;browser=D.getBrowser();browserVersion=D.getVersion();get isSafari(){return typeof navigator>"u"?!1:/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}get isIOS(){return typeof navigator>"u"?!1:/iPad|iPhone|iPod/.test(navigator.userAgent)||navigator.platform==="MacIntel"&&navigator.maxTouchPoints>1}pack=q;unpack=G;supports=(()=>{let e={browser:D.isBrowserSupported(),webRTC:D.isWebRTCSupported(),audioVideo:!1,data:!1,binaryBlob:!1,reliable:!1};if(!e.webRTC)return e;let t;try{t=new RTCPeerConnection(gt),e.audioVideo=!0;let n;try{n=t.createDataChannel("_DENDRITEST",{ordered:!0}),e.data=!0,e.reliable=!!n.ordered;try{n.binaryType="blob",e.binaryBlob=!D.isIOS}catch{}}catch{}finally{n&&n.close()}}catch{}finally{t&&t.close()}return e})();validateId=_t;randomToken=ie;blobToArrayBuffer(e,t){let n=new FileReader;return n.onload=i=>{i.target&&t(i.target.result)},n.readAsArrayBuffer(e),n}binaryStringToArrayBuffer(e){let t=new Uint8Array(e.length);for(let n=0;n<e.length;n++)t[n]=e.charCodeAt(n)&255;return t.buffer}isSecure(){return typeof location<"u"&&location.protocol==="https:"}},_=new et;var Bt="2.5.0",oe=class r{constructor(e){this._options=e}_options;static FETCH_TIMEOUT=1e4;_buildRequest(e){let t=this._options.secure?"https":"http",{host:n,port:i,path:o,key:s}=this._options,a=new URL(`${t}://${n}:${i}${o}${s}/${e}`);a.searchParams.set("ts",`${Date.now()}${Math.random()}`),a.searchParams.set("version",Bt),this._options.apiKey&&a.searchParams.set("api_key",this._options.apiKey);let c=new AbortController,p=setTimeout(()=>c.abort(),r.FETCH_TIMEOUT);return fetch(a.href,{referrerPolicy:this._options.referrerPolicy,signal:c.signal}).finally(()=>clearTimeout(p))}async retrieveId(){try{let e=await this._buildRequest("id");if(e.status!==200)throw new Error(`Error. Status:${e.status}`);return e.text()}catch(e){l.error("Error retrieving ID",e);let t="";throw this._options.path==="/"&&this._options.host!==_.CLOUD_HOST&&(t=" If you passed in a `path` to your self-hosted Dendri server, you'll also need to pass in that same path when creating a new Dendri instance."),new Error(`Could not get an ID from the server.${t}`)}}async getTurnCredentials(){let e=this._options.secure?"https":"http",{host:t,port:n,path:i,key:o}=this._options,s=new URL(`${e}://${t}:${n}${i}${o}/turn-credentials`);this._options.apiKey&&s.searchParams.set("api_key",this._options.apiKey);try{let a=new AbortController,c=setTimeout(()=>a.abort(),r.FETCH_TIMEOUT),p=await fetch(s.href,{referrerPolicy:this._options.referrerPolicy,signal:a.signal}).finally(()=>clearTimeout(c));if(!p.ok)return[];let h=(await p.json()).iceServers;return h?Array.isArray(h)?h:[h]:[]}catch(a){return l.error("Error fetching TURN credentials",a),[]}}async listAllPeers(){try{let e=await this._buildRequest("peers");if(e.status!==200)throw e.status===401?new Error("It doesn't look like you have permission to list peers IDs. Check your server configuration and ensure allow_discovery is enabled."):new Error(`Error. Status:${e.status}`);return e.json()}catch(e){throw l.error("Error retrieving list peers",e),new Error("Could not get list peers from the server. "+(e instanceof Error?e.message:e))}}};var vt=L(ae(),1);var M=class extends vt.EventEmitter{emitError(e,t,n=!1){l.error("Error:",t);let i=new nt(`${e}`,t);i.retryable=n,this.emit("error",i)}},nt=class extends Error{type;retryable;details;constructor(e,t){typeof t=="string"?super(t):(super(t.message),this.stack=t.stack),this.type=e,this.retryable=!1}setRetryable(e){return this.retryable=e,this}setDetails(e){return this.details=e,this}};var I=class extends M{constructor(t,n,i){super();this.peer=t;this.provider=n;this.options=i;this.metadata=i.metadata}peer;provider;options;_open=!1;metadata;connectionId;peerConnection;dataChannel=null;label;get open(){return this._open}};var A=class{constructor(e){this.connection=e}connection;_pendingCandidates=[];_iceCandidateFilter=null;startConnection(e){let t=this._startPeerConnection();if(this.connection.peerConnection=t,this.connection.type==="media"&&e._stream&&(this._addTracksToConnection(e._stream,t),this._setCodecPreferences(t)),e.originator){let n=this.connection,i={ordered:!!e.reliable},o=t.createDataChannel(n.label,i);n._initializeDataChannel(o),this._makeOffer()}else this.handleSDP("OFFER",e.sdp)}_startPeerConnection(){l.log("Creating RTCPeerConnection.");let e=new RTCPeerConnection(this.connection.provider?.options.config);if(this.connection.provider?.options.ipPolicy==="public"){let t=n=>!(n.candidate??"").includes("typ host");this._iceCandidateFilter=t}return this._setupListeners(e),e}_setupListeners(e){let t=this.connection.peer,n=this.connection.connectionId,i=this.connection.type,o=this.connection.provider;l.log("Listening for ICE candidates."),e.onicecandidate=s=>{s.candidate?.candidate&&(this._iceCandidateFilter&&!this._iceCandidateFilter(s.candidate)||(l.log(`Received ICE candidates for ${t}:`,s.candidate),o.socket.send({type:"CANDIDATE",payload:{candidate:s.candidate,type:i,connectionId:n},dst:t})))},e.oniceconnectionstatechange=()=>{switch(e.iceConnectionState){case"failed":l.log(`iceConnectionState is failed, closing connections to ${t}`),this.connection.emitError("negotiation-failed",`Negotiation of connection to ${t} failed.`),this.connection.close();break;case"closed":l.log(`iceConnectionState is closed, closing connections to ${t}`),this.connection.emitError("connection-closed",`Connection to ${t} closed.`),this.connection.close();break;case"disconnected":l.log(`iceConnectionState changed to disconnected on the connection with ${t}`);break;case"completed":e.onicecandidate=()=>{};break}this.connection.emit("iceStateChanged",e.iceConnectionState)},l.log("Listening for data channel"),e.ondatachannel=s=>{l.log("Received data channel");let a=s.channel,c=o.getConnection(t,n);if(!c){l.warn(`Received data channel for non-existent connection ${n}`);return}c._initializeDataChannel(a)},l.log("Listening for remote stream"),e.ontrack=s=>{l.log("Received remote stream");let a=s.streams[0],c=o.getConnection(t,n);if(!c){l.warn(`Received remote stream for non-existent connection ${n}`);return}if(c.type==="media"){let p=c;this._addStreamToMediaConnection(a,p)}}}cleanup(){l.log(`Cleaning up PeerConnection to ${this.connection.peer}`);let e=this.connection.peerConnection;if(!e)return;this.connection.peerConnection=null,this._pendingCandidates=[],e.onicecandidate=e.oniceconnectionstatechange=e.ondatachannel=e.ontrack=()=>{};let t=e.signalingState!=="closed",n=!1,i=this.connection.dataChannel;i&&(n=!!i.readyState&&i.readyState!=="closed",n&&i.close()),(t||n)&&e.close()}async _makeOffer(){let e=this.connection.peerConnection,t=this.connection.provider;try{let n=await e.createOffer(this.connection.options.constraints);if(!this.connection.peerConnection){l.log("PeerConnection closed during createOffer");return}l.log("Created offer."),this.connection.options.sdpTransform&&typeof this.connection.options.sdpTransform=="function"&&(n.sdp=this.connection.options.sdpTransform(n.sdp)||n.sdp),this.connection.type==="media"&&n.sdp&&(n.sdp=this._applyH264SdpFallback(n.sdp,e));try{if(await e.setLocalDescription(n),!this.connection.peerConnection){l.log("PeerConnection closed during setLocalDescription");return}l.log("Set localDescription:",n,`for:${this.connection.peer}`);let i={sdp:n,type:this.connection.type,connectionId:this.connection.connectionId,metadata:this.connection.metadata};if(this.connection.type==="data"){let o=this.connection;i={...i,label:o.label,reliable:o.reliable,serialization:o.serialization}}t.socket.send({type:"OFFER",payload:i,dst:this.connection.peer})}catch(i){i!=="OperationError: Failed to set local offer sdp: Called in wrong state: kHaveRemoteOffer"&&(t.emitError("webrtc",i instanceof Error?i:String(i)),l.log("Failed to setLocalDescription, ",i))}}catch(n){t.emitError("webrtc",n instanceof Error?n:String(n)),l.log("Failed to createOffer, ",n)}}async _makeAnswer(){let e=this.connection.peerConnection,t=this.connection.provider;try{let n=await e.createAnswer();if(!this.connection.peerConnection){l.log("PeerConnection closed during createAnswer");return}l.log("Created answer."),this.connection.options.sdpTransform&&typeof this.connection.options.sdpTransform=="function"&&(n.sdp=this.connection.options.sdpTransform(n.sdp)||n.sdp),this.connection.type==="media"&&n.sdp&&(n.sdp=this._applyH264SdpFallback(n.sdp,e));try{if(await e.setLocalDescription(n),!this.connection.peerConnection){l.log("PeerConnection closed during setLocalDescription");return}l.log("Set localDescription:",n,`for:${this.connection.peer}`),t.socket.send({type:"ANSWER",payload:{sdp:n,type:this.connection.type,connectionId:this.connection.connectionId},dst:this.connection.peer})}catch(i){t.emitError("webrtc",i instanceof Error?i:String(i)),l.log("Failed to setLocalDescription, ",i)}}catch(n){t.emitError("webrtc",n instanceof Error?n:String(n)),l.log("Failed to create answer, ",n)}}async handleSDP(e,t){let n=this.connection.peerConnection,i=this.connection.provider;l.log("Setting remote description",t);try{if(await n.setRemoteDescription(t),!this.connection.peerConnection){l.log("PeerConnection closed during setRemoteDescription");return}if(l.log(`Set remoteDescription:${e} for:${this.connection.peer}`),this._pendingCandidates.length>0){l.log(`Flushing ${this._pendingCandidates.length} pending ICE candidates`);let o=this._pendingCandidates;this._pendingCandidates=[];for(let s of o)await this.handleCandidate(s)}e==="OFFER"&&await this._makeAnswer()}catch(o){i.emitError("webrtc",o instanceof Error?o:String(o)),l.log("Failed to setRemoteDescription, ",o)}}async handleCandidate(e){l.log("handleCandidate:",e);let t=this.connection.peerConnection;if(!t){l.warn(`PeerConnection not set for ${this.connection.peer}, cannot add ICE candidate`);return}if(!t.remoteDescription){l.log("Queueing ICE candidate (no remote description yet)"),this._pendingCandidates.push(e);return}try{await t.addIceCandidate(e),l.log(`Added ICE candidate for:${this.connection.peer}`)}catch(n){this.connection.provider?.emitError("webrtc",n instanceof Error?n:String(n)),l.log("Failed to handleCandidate, ",n)}}_setCodecPreferences(e){if(e.getTransceivers){for(let t of e.getTransceivers())if(t.sender?.track?.kind==="video"){let n=typeof RTCRtpReceiver<"u"?RTCRtpReceiver.getCapabilities?.("video")?.codecs:void 0;if(!n)continue;let i=n.filter(s=>s.mimeType==="video/H264"),o=n.filter(s=>s.mimeType!=="video/H264");if(i.length>0&&typeof t.setCodecPreferences=="function")try{t.setCodecPreferences([...i,...o])}catch{}}}}_preferH264InSdp(e){let t=e.split(`\r
|
|
41
|
+
isUnifiedPlanSupported:${this.isUnifiedPlanSupported()}`}};var ie=()=>Math.random().toString(36).slice(2);var _t=r=>!r||/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.test(r);var gt={iceServers:[{urls:"stun:stun.l.google.com:19302"},{urls:"stun:stun1.l.google.com:19302"}]},et=class extends R{noop(){}CLOUD_HOST="signal.dendri.dev";CLOUD_PORT=443;chunkedBrowsers={Chrome:1,chrome:1};defaultConfig=gt;browser=D.getBrowser();browserVersion=D.getVersion();get isSafari(){return typeof navigator>"u"?!1:/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}get isIOS(){return typeof navigator>"u"?!1:/iPad|iPhone|iPod/.test(navigator.userAgent)||navigator.platform==="MacIntel"&&navigator.maxTouchPoints>1}pack=q;unpack=G;supports=(()=>{let e={browser:D.isBrowserSupported(),webRTC:D.isWebRTCSupported(),audioVideo:!1,data:!1,binaryBlob:!1,reliable:!1};if(!e.webRTC)return e;let t;try{t=new RTCPeerConnection(gt),e.audioVideo=!0;let n;try{n=t.createDataChannel("_DENDRITEST",{ordered:!0}),e.data=!0,e.reliable=!!n.ordered;try{n.binaryType="blob",e.binaryBlob=!D.isIOS}catch{}}catch{}finally{n&&n.close()}}catch{}finally{t&&t.close()}return e})();validateId=_t;randomToken=ie;blobToArrayBuffer(e,t){let n=new FileReader;return n.onload=i=>{i.target&&t(i.target.result)},n.readAsArrayBuffer(e),n}binaryStringToArrayBuffer(e){let t=new Uint8Array(e.length);for(let n=0;n<e.length;n++)t[n]=e.charCodeAt(n)&255;return t.buffer}isSecure(){return typeof location<"u"&&location.protocol==="https:"}},_=new et;var Bt="2.6.0",oe=class r{constructor(e){this._options=e}_options;static FETCH_TIMEOUT=1e4;_buildRequest(e){let t=this._options.secure?"https":"http",{host:n,port:i,path:o,key:s}=this._options,a=new URL(`${t}://${n}:${i}${o}${s}/${e}`);a.searchParams.set("ts",`${Date.now()}${Math.random()}`),a.searchParams.set("version",Bt),this._options.apiKey&&a.searchParams.set("api_key",this._options.apiKey);let c=new AbortController,p=setTimeout(()=>c.abort(),r.FETCH_TIMEOUT);return fetch(a.href,{referrerPolicy:this._options.referrerPolicy,signal:c.signal}).finally(()=>clearTimeout(p))}async retrieveId(){try{let e=await this._buildRequest("id");if(e.status!==200)throw new Error(`Error. Status:${e.status}`);return e.text()}catch(e){l.error("Error retrieving ID",e);let t="";throw this._options.path==="/"&&this._options.host!==_.CLOUD_HOST&&(t=" If you passed in a `path` to your self-hosted Dendri server, you'll also need to pass in that same path when creating a new Dendri instance."),new Error(`Could not get an ID from the server.${t}`)}}async getTurnCredentials(){let e=this._options.secure?"https":"http",{host:t,port:n,path:i,key:o}=this._options,s=new URL(`${e}://${t}:${n}${i}${o}/turn-credentials`);this._options.apiKey&&s.searchParams.set("api_key",this._options.apiKey);try{let a=new AbortController,c=setTimeout(()=>a.abort(),r.FETCH_TIMEOUT),p=await fetch(s.href,{referrerPolicy:this._options.referrerPolicy,signal:a.signal}).finally(()=>clearTimeout(c));if(!p.ok)return[];let h=(await p.json()).iceServers;return h?Array.isArray(h)?h:[h]:[]}catch(a){return l.error("Error fetching TURN credentials",a),[]}}async listAllPeers(){try{let e=await this._buildRequest("peers");if(e.status!==200)throw e.status===401?new Error("It doesn't look like you have permission to list peers IDs. Check your server configuration and ensure allow_discovery is enabled."):new Error(`Error. Status:${e.status}`);return e.json()}catch(e){throw l.error("Error retrieving list peers",e),new Error("Could not get list peers from the server. "+(e instanceof Error?e.message:e))}}};var vt=L(ae(),1);var M=class extends vt.EventEmitter{emitError(e,t,n=!1){l.error("Error:",t);let i=new nt(`${e}`,t);i.retryable=n,this.emit("error",i)}},nt=class extends Error{type;retryable;details;constructor(e,t){typeof t=="string"?super(t):(super(t.message),this.stack=t.stack),this.type=e,this.retryable=!1}setRetryable(e){return this.retryable=e,this}setDetails(e){return this.details=e,this}};var I=class extends M{constructor(t,n,i){super();this.peer=t;this.provider=n;this.options=i;this.metadata=i.metadata}peer;provider;options;_open=!1;metadata;connectionId;peerConnection;dataChannel=null;label;get open(){return this._open}};var A=class{constructor(e){this.connection=e}connection;_pendingCandidates=[];_iceCandidateFilter=null;startConnection(e){let t=this._startPeerConnection();if(this.connection.peerConnection=t,this.connection.type==="media"&&e._stream&&(this._addTracksToConnection(e._stream,t),this._setCodecPreferences(t)),e.originator){let n=this.connection,i={ordered:!!e.reliable},o=t.createDataChannel(n.label,i);n._initializeDataChannel(o),this._makeOffer()}else this.handleSDP("OFFER",e.sdp)}_startPeerConnection(){l.log("Creating RTCPeerConnection.");let e=new RTCPeerConnection(this.connection.provider?.options.config);if(this.connection.provider?.options.ipPolicy==="public"){let t=n=>!(n.candidate??"").includes("typ host");this._iceCandidateFilter=t}return this._setupListeners(e),e}_setupListeners(e){let t=this.connection.peer,n=this.connection.connectionId,i=this.connection.type,o=this.connection.provider;l.log("Listening for ICE candidates."),e.onicecandidate=s=>{s.candidate?.candidate&&(this._iceCandidateFilter&&!this._iceCandidateFilter(s.candidate)||(l.log(`Received ICE candidates for ${t}:`,s.candidate),o.socket.send({type:"CANDIDATE",payload:{candidate:s.candidate,type:i,connectionId:n},dst:t})))},e.oniceconnectionstatechange=()=>{switch(e.iceConnectionState){case"failed":l.log(`iceConnectionState is failed, closing connections to ${t}`),this.connection.emitError("negotiation-failed",`Negotiation of connection to ${t} failed.`),this.connection.close();break;case"closed":l.log(`iceConnectionState is closed, closing connections to ${t}`),this.connection.emitError("connection-closed",`Connection to ${t} closed.`),this.connection.close();break;case"disconnected":l.log(`iceConnectionState changed to disconnected on the connection with ${t}`);break;case"completed":e.onicecandidate=()=>{};break}this.connection.emit("iceStateChanged",e.iceConnectionState)},l.log("Listening for data channel"),e.ondatachannel=s=>{l.log("Received data channel");let a=s.channel,c=o.getConnection(t,n);if(!c){l.warn(`Received data channel for non-existent connection ${n}`);return}c._initializeDataChannel(a)},l.log("Listening for remote stream"),e.ontrack=s=>{l.log("Received remote stream");let a=s.streams[0],c=o.getConnection(t,n);if(!c){l.warn(`Received remote stream for non-existent connection ${n}`);return}if(c.type==="media"){let p=c;this._addStreamToMediaConnection(a,p)}}}cleanup(){l.log(`Cleaning up PeerConnection to ${this.connection.peer}`);let e=this.connection.peerConnection;if(!e)return;this.connection.peerConnection=null,this._pendingCandidates=[],e.onicecandidate=e.oniceconnectionstatechange=e.ondatachannel=e.ontrack=()=>{};let t=e.signalingState!=="closed",n=!1,i=this.connection.dataChannel;i&&(n=!!i.readyState&&i.readyState!=="closed",n&&i.close()),(t||n)&&e.close()}async _makeOffer(){let e=this.connection.peerConnection,t=this.connection.provider;try{let n=await e.createOffer(this.connection.options.constraints);if(!this.connection.peerConnection){l.log("PeerConnection closed during createOffer");return}l.log("Created offer."),this.connection.options.sdpTransform&&typeof this.connection.options.sdpTransform=="function"&&(n.sdp=this.connection.options.sdpTransform(n.sdp)||n.sdp),this.connection.type==="media"&&n.sdp&&(n.sdp=this._applyH264SdpFallback(n.sdp,e));try{if(await e.setLocalDescription(n),!this.connection.peerConnection){l.log("PeerConnection closed during setLocalDescription");return}l.log("Set localDescription:",n,`for:${this.connection.peer}`);let i={sdp:n,type:this.connection.type,connectionId:this.connection.connectionId,metadata:this.connection.metadata};if(this.connection.type==="data"){let o=this.connection;i={...i,label:o.label,reliable:o.reliable,serialization:o.serialization}}t.socket.send({type:"OFFER",payload:i,dst:this.connection.peer})}catch(i){i!=="OperationError: Failed to set local offer sdp: Called in wrong state: kHaveRemoteOffer"&&(t.emitError("webrtc",i instanceof Error?i:String(i)),l.log("Failed to setLocalDescription, ",i))}}catch(n){t.emitError("webrtc",n instanceof Error?n:String(n)),l.log("Failed to createOffer, ",n)}}async _makeAnswer(){let e=this.connection.peerConnection,t=this.connection.provider;try{let n=await e.createAnswer();if(!this.connection.peerConnection){l.log("PeerConnection closed during createAnswer");return}l.log("Created answer."),this.connection.options.sdpTransform&&typeof this.connection.options.sdpTransform=="function"&&(n.sdp=this.connection.options.sdpTransform(n.sdp)||n.sdp),this.connection.type==="media"&&n.sdp&&(n.sdp=this._applyH264SdpFallback(n.sdp,e));try{if(await e.setLocalDescription(n),!this.connection.peerConnection){l.log("PeerConnection closed during setLocalDescription");return}l.log("Set localDescription:",n,`for:${this.connection.peer}`),t.socket.send({type:"ANSWER",payload:{sdp:n,type:this.connection.type,connectionId:this.connection.connectionId},dst:this.connection.peer})}catch(i){t.emitError("webrtc",i instanceof Error?i:String(i)),l.log("Failed to setLocalDescription, ",i)}}catch(n){t.emitError("webrtc",n instanceof Error?n:String(n)),l.log("Failed to create answer, ",n)}}async handleSDP(e,t){let n=this.connection.peerConnection,i=this.connection.provider;l.log("Setting remote description",t);try{if(await n.setRemoteDescription(t),!this.connection.peerConnection){l.log("PeerConnection closed during setRemoteDescription");return}if(l.log(`Set remoteDescription:${e} for:${this.connection.peer}`),this._pendingCandidates.length>0){l.log(`Flushing ${this._pendingCandidates.length} pending ICE candidates`);let o=this._pendingCandidates;this._pendingCandidates=[];for(let s of o)await this.handleCandidate(s)}e==="OFFER"&&await this._makeAnswer()}catch(o){i.emitError("webrtc",o instanceof Error?o:String(o)),l.log("Failed to setRemoteDescription, ",o)}}async handleCandidate(e){l.log("handleCandidate:",e);let t=this.connection.peerConnection;if(!t){l.warn(`PeerConnection not set for ${this.connection.peer}, cannot add ICE candidate`);return}if(!t.remoteDescription){l.log("Queueing ICE candidate (no remote description yet)"),this._pendingCandidates.push(e);return}try{await t.addIceCandidate(e),l.log(`Added ICE candidate for:${this.connection.peer}`)}catch(n){this.connection.provider?.emitError("webrtc",n instanceof Error?n:String(n)),l.log("Failed to handleCandidate, ",n)}}_setCodecPreferences(e){if(e.getTransceivers){for(let t of e.getTransceivers())if(t.sender?.track?.kind==="video"){let n=typeof RTCRtpReceiver<"u"?RTCRtpReceiver.getCapabilities?.("video")?.codecs:void 0;if(!n)continue;let i=n.filter(s=>s.mimeType==="video/H264"),o=n.filter(s=>s.mimeType!=="video/H264");if(i.length>0&&typeof t.setCodecPreferences=="function")try{t.setCodecPreferences([...i,...o])}catch{}}}}_preferH264InSdp(e){let t=e.split(`\r
|
|
42
42
|
`),n=[],i=[];for(let o of t)if(o.includes("a=rtpmap:")&&o.toLowerCase().includes("h264")){let s=o.match(/a=rtpmap:(\d+)/);s&&i.push(s[1])}for(let o of t){if(o.startsWith("m=video")&&i.length>0){let s=o.split(" "),a=s.slice(0,3).join(" "),c=s.slice(3),p=[...i,...c.filter(d=>!i.includes(d))];n.push(`${a} ${p.join(" ")}`);continue}n.push(o)}return n.join(`\r
|
|
43
|
-
`)}_applyH264SdpFallback(e,t){return t.getTransceivers&&t.getTransceivers().some(i=>typeof i.setCodecPreferences=="function")?e:this._preferH264InSdp(e)}_addTracksToConnection(e,t){if(l.log(`add tracks from stream ${e.id} to peer connection`),!t.addTrack){l.error("Your browser doesn't support RTCPeerConnection#addTrack. Ignored.");return}e.getTracks().forEach(n=>{t.addTrack(n,e)})}_addStreamToMediaConnection(e,t){l.log(`add stream ${e.id} to media connection ${t.connectionId}`),t.addStream(e)}};var N=class r extends I{static ID_PREFIX="dc_";static MAX_BUFFERED_AMOUNT=8*1024*1024;_negotiator;reliable;get type(){return"data"}constructor(e,t,n){super(e,t,n),this.connectionId=this.options.connectionId||r.ID_PREFIX+ie(),this.label=this.options.label||this.connectionId,this.reliable=!!this.options.reliable,this._negotiator=new A(this),this._negotiator.startConnection(this.options._payload||{originator:!0,reliable:this.reliable})}_initializeDataChannel(e){this.dataChannel=e,this.dataChannel.onopen=()=>{l.log(`DC#${this.connectionId} dc connection success`),this._open=!0,this._applyAdaptiveBuffer(e),this.emit("open")},this.dataChannel.onclose=()=>{l.log(`DC#${this.connectionId} dc closed for:`,this.peer),this.close()}}_applyAdaptiveBuffer(e){let t=this.peerConnection;!t||typeof t.getStats!="function"||t.getStats().then(n=>{let i=null;if(n.forEach(o=>{o.type==="candidate-pair"&&o.state==="succeeded"&&o.currentRoundTripTime&&(i=o.currentRoundTripTime*1e3)}),i!==null){let o=13107200*(i/1e3),s=Math.max(1*1024*1024,Math.min(32*1024*1024,Math.ceil(o)));e.bufferedAmountLowThreshold=s}}).catch(()=>{})}_flushCloseTimeout=null;close(e){if(e?.flush){this.send({__peerData:{type:"close"}}),this._flushCloseTimeout=setTimeout(()=>{this._flushCloseTimeout=null,this.close()},5e3);return}this._flushCloseTimeout&&(clearTimeout(this._flushCloseTimeout),this._flushCloseTimeout=null),this._negotiator&&(this._negotiator.cleanup(),this._negotiator=null),this.provider&&(this.provider._removeConnection(this),this.provider=null),this.dataChannel&&(this.dataChannel.onopen=null,this.dataChannel.onmessage=null,this.dataChannel.onclose=null,this.dataChannel=null),this.open&&(this._open=!1,super.emit("close"),this.removeAllListeners())}send(e,t=!1){if(!this.open){this.emitError("not-open-yet","Connection is not open. You should listen for the `open` event before sending messages.");return}return this._send(e,t)}async handleMessage(e){let t=e.payload;switch(e.type){case"ANSWER":this._negotiator&&await this._negotiator.handleSDP(e.type,t.sdp);break;case"CANDIDATE":this._negotiator&&await this._negotiator.handleCandidate(t.candidate);break;default:l.warn("Unrecognized message type:",e.type,"from peer:",this.peer);break}}};var S=class extends N{_buffer=[];_bufferSize=0;_buffering=!1;_bufferTimer=null;_messageHandler=null;get bufferSize(){return this._bufferSize}_initializeDataChannel(e){super._initializeDataChannel(e),this.dataChannel.binaryType="arraybuffer",this._messageHandler=t=>this._handleDataMessage(t),this.dataChannel?.addEventListener("message",this._messageHandler)}_bufferedSend(e){(this._buffering||!this._trySend(e))&&(this._buffer.push(e),this._bufferSize=this._buffer.length)}_trySend(e){if(!this.open)return!1;if((this.dataChannel?.bufferedAmount??0)>N.MAX_BUFFERED_AMOUNT)return this._buffering=!0,this._bufferTimer=setTimeout(()=>{this._bufferTimer=null,this._buffering=!1,this._tryBuffer()},50),!1;try{this.dataChannel?.send(e)}catch(t){return l.error(`DC#:${this.connectionId} Error when sending:`,t),this._buffering=!0,this.close(),!1}return!0}_tryBuffer(){for(;this.open&&this._buffer.length>0;){let e=this._buffer[0];if(!this._trySend(e))break;this._buffer.shift(),this._bufferSize=this._buffer.length}}close(e){if(e?.flush){this.send({__peerData:{type:"close"}});return}this._bufferTimer&&(clearTimeout(this._bufferTimer),this._bufferTimer=null),this.dataChannel&&this._messageHandler&&(this.dataChannel.removeEventListener("message",this._messageHandler),this._messageHandler=null),this._buffer=[],this._bufferSize=0,super.close()}};var O=class r extends S{chunker=new R;serialization="binary";static MAX_CHUNKED_SETS=256;_chunkedData={};close(e){this._chunkedData={},super.close(e)}_handleDataMessage({data:e}){let t;try{t=G(e)}catch(i){l.error(`DC#${this.connectionId} Failed to unpack data:`,i),this.emitError("not-open-yet","Failed to deserialize received data");return}let n=t.__peerData;if(n){if(n.type==="close"){this.close();return}this._handleChunk(t);return}this.emit("data",t)}_handleChunk(e){let t=e.__peerData;if(e.n<0||e.n>=e.total){l.warn(`DC#${this.connectionId} Invalid chunk index ${e.n} of ${e.total}, dropping`);return}let n=this._chunkedData[t]||{data:[],count:0,total:e.total};if(n.data[e.n]=new Uint8Array(e.data),n.count++,this._chunkedData[t]=n,n.total===n.count){delete this._chunkedData[t];let i=it(n.data);this._handleDataMessage({data:i})}else{let i=Object.keys(this._chunkedData);if(i.length>r.MAX_CHUNKED_SETS){let o=i[0];l.warn(`DC#${this.connectionId} Too many pending chunk sets (${i.length}), dropping oldest`),delete this._chunkedData[Number(o)]}}}_send(e,t){let n=q(e);if(n instanceof Promise)return this._send_blob(n);if(!t&&n.byteLength>this.chunker.chunkedMTU){this._sendChunks(n);return}this._bufferedSend(n)}async _send_blob(e){let t=await e;if(!this.open){l.warn(`DC#${this.connectionId} Connection closed during async send, dropping message`);return}if(t.byteLength>this.chunker.chunkedMTU){this._sendChunks(t);return}this._bufferedSend(t)}_sendChunks(e){let t=this.chunker.chunk(e);l.log(`DC#${this.connectionId} Try to send ${t.length} chunks...`);for(let n of t)this.send(n,!0)}};var ce=class extends S{serialization="json";encoder=new TextEncoder;decoder=new TextDecoder;stringify=JSON.stringify;parse=JSON.parse;_handleDataMessage({data:e}){let t;try{t=this.parse(this.decoder.decode(e))}catch(i){l.error(`DC#${this.connectionId} Failed to parse JSON data:`,i),this.emitError("not-open-yet","Failed to parse received JSON data");return}let n=t.__peerData;if(n&&n.type==="close"){this.close();return}this.emit("data",t)}_send(e,t){let n=this.encoder.encode(this.stringify(e));if(n.byteLength>=_.chunkedMTU){this.emitError("message-too-big","Message too big for JSON channel");return}this._bufferedSend(n.buffer)}};var pe=class extends S{serialization="raw";_handleDataMessage({data:e}){super.emit("data",e)}_send(e,t){this._bufferedSend(e)}};var bt=L(ae(),1);var de=class{_pending=new Map;_counter=0;nextId(){return`ack_${++this._counter}_${Date.now()}`}waitForAck(e,t=5e3,n){return new Promise((i,o)=>{let s=setTimeout(()=>{this._pending.delete(e),o(new Error(`ACK timeout for ${e}`))},t);this._pending.set(e,{resolve:i,reject:o,timer:s,peerId:n})})}handleAck(e){let t=this._pending.get(e);return t?(clearTimeout(t.timer),t.resolve(),this._pending.delete(e),!0):!1}rejectAllForPeer(e){let t=0;for(let[n,i]of this._pending)i.peerId===e&&(clearTimeout(i.timer),i.reject(new Error(`Peer ${e} disconnected`)),this._pending.delete(n),t++);return t}clear(){for(let[,e]of this._pending)clearTimeout(e.timer),e.reject(new Error("Connection closed"));this._pending.clear()}get pendingCount(){return this._pending.size}};var le=class{_keyPair=null;_sharedKey=null;_ready=!1;get ready(){return this._ready}async generateKeyPair(){return this._keyPair=await crypto.subtle.generateKey({name:"ECDH",namedCurve:"P-256"},!0,["deriveKey"]),crypto.subtle.exportKey("jwk",this._keyPair.publicKey)}async deriveSharedKey(e){if(!this._keyPair)throw new Error("Key pair not generated");let t=await crypto.subtle.importKey("jwk",e,{name:"ECDH",namedCurve:"P-256"},!1,[]);this._sharedKey=await crypto.subtle.deriveKey({name:"ECDH",public:t},this._keyPair.privateKey,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"]),this._ready=!0}async encrypt(e){if(!this._sharedKey)throw new Error("Shared key not derived");let t=crypto.getRandomValues(new Uint8Array(12)),n=new TextEncoder().encode(e),i=await crypto.subtle.encrypt({name:"AES-GCM",iv:t},this._sharedKey,n);return{iv:btoa(String.fromCharCode(...t)),ciphertext:btoa(String.fromCharCode(...new Uint8Array(i)))}}async decrypt(e){if(!this._sharedKey)throw new Error("Shared key not derived");let t=Uint8Array.from(atob(e.iv),o=>o.charCodeAt(0)),n=Uint8Array.from(atob(e.ciphertext),o=>o.charCodeAt(0)),i=await crypto.subtle.decrypt({name:"AES-GCM",iv:t},this._sharedKey,n);return new TextDecoder().decode(i)}clear(){this._keyPair=null,this._sharedKey=null,this._ready=!1}};function Ct(r){return typeof r=="object"&&r!==null&&"__topic"in r&&typeof r.__topic=="string"&&"__data"in r}var he=class{_handlers=new Map;_globalHandlers=new Set;subscribe(e,t){return this._handlers.has(e)||this._handlers.set(e,new Set),this._handlers.get(e).add(t),()=>{this._handlers.get(e)?.delete(t)}}subscribeAll(e){return this._globalHandlers.add(e),()=>{this._globalHandlers.delete(e)}}dispatch(e,t,n){let i=!1;if(e!==void 0&&this._handlers.has(e))for(let o of this._handlers.get(e))o(t,n),i=!0;for(let o of this._globalHandlers)o(t,n),i=!0;return i}clear(){this._handlers.clear(),this._globalHandlers.clear()}};var fe=class extends bt.EventEmitter{peer;_provider;_options;_dataConnection=null;_mode="reconnecting";_iceTimer=null;_upgradeTimer=null;_upgradeAttempts=0;_open=!1;_closed=!1;_ackManager=new de;_topics=new he;_encryption=new le;_encryptRelay;_keyExchangeSent=!1;_pendingRelayQueue=[];_expectedSeq=null;_reorderBuffer=new Map;_reorderTimeout=500;_reorderTimers=new Set;constructor(e,t,n={}){super(),this.peer=e,this._provider=t,this._options=Object.freeze({...n}),this._encryptRelay=n.encryptRelay??!0}get mode(){return this._mode}get open(){return this._open}start(){if(!this._closed){if(l.log(`HybridConnection: start peer=${this.peer} iceTimeout=${this._options.iceTimeout??1e4}ms encryptRelay=${this._encryptRelay}`),typeof this._provider?.on!="function"){this._attemptWebRTC();return}this._tryConnectionReversal().then(e=>{e||this._attemptWebRTC()})}}send(e,t){if(this._closed){this.emit("error",new Error("Connection is closed."));return}if(!this._open){this.emit("error",new Error("Connection is not open. Listen for the `open` event before sending."));return}let n=t?.topic,i=n?{__topic:n,__data:e}:e;this._mode==="webrtc"&&this._dataConnection?.open?this._dataConnection.send(i):this._mode==="ws-relay"?this._sendRelay(i):this.emit("error",new Error(`No transport available (current mode: ${this._mode}).`))}async sendWithAck(e,t){if(this._closed)throw new Error("Connection is closed.");if(!this._open)throw new Error("Connection is not open. Listen for the `open` event before sending.");let n=this._ackManager.nextId();if(this._mode==="webrtc"&&this._dataConnection?.open)this._dataConnection.send({__ackId:n,data:e});else if(this._mode==="ws-relay")this._sendRelay({__ackId:n,data:e});else throw new Error(`No transport available (current mode: ${this._mode}).`);return this._ackManager.waitForAck(n,t)}get ackManager(){return this._ackManager}subscribe(e,t){return this._topics.subscribe(e,t)}onData(e){return this._topics.subscribeAll(e)}handleRelayData(e,t){if(this._encryptRelay&&this._encryption.ready&&e!==null&&typeof e=="object"&&"__encrypted"in e){let n=e;this._encryption.decrypt(n.__encrypted).then(i=>{this._deliverInOrder(t,JSON.parse(i))}).catch(i=>{this.emit("error",new Error(`Relay decryption failed: ${String(i)}`))});return}this._deliverInOrder(t,e)}close(){if(!this._closed){this._closed=!0,this._clearIceTimer(),this._clearUpgradeTimer(),this._ackManager.clear(),this._topics.clear(),this._encryption.clear(),this._pendingRelayQueue=[],this._keyExchangeSent=!1,this._reorderBuffer.clear(),this._expectedSeq=null;for(let e of this._reorderTimers)clearTimeout(e);this._reorderTimers.clear(),this._dataConnection&&(this._dataConnection.close(),this._dataConnection=null),this._open&&(this._open=!1,this.emit("close")),this.removeAllListeners()}}initiateKeyExchange(){!this._encryptRelay||this._keyExchangeSent||(this._keyExchangeSent=!0,this._encryption.generateKeyPair().then(e=>{this._provider.socket.send({type:"KEY-EXCHANGE",dst:this.peer,payload:{publicKey:e}})}).catch(e=>{this._keyExchangeSent=!1,this.emit("error",new Error(`Key exchange initiation failed: ${String(e)}`))}))}handleKeyExchange(e){if(!this._encryptRelay)return;let t=n=>{this._encryption.deriveSharedKey(n).then(()=>{l.log(`HybridConnection: E2E encryption established with ${this.peer}`),this._flushPendingRelayQueue()}).catch(i=>{this.emit("error",new Error(`Key derivation failed: ${String(i)}`))})};this._keyExchangeSent?t(e.publicKey):(this._keyExchangeSent=!0,this._encryption.generateKeyPair().then(n=>{this._provider.socket.send({type:"KEY-EXCHANGE",dst:this.peer,payload:{publicKey:n}}),t(e.publicKey)}).catch(n=>{this._keyExchangeSent=!1,this.emit("error",new Error(`Key exchange response failed: ${String(n)}`))}))}_sendRelay(e){if(!this._encryptRelay){this._provider.socket.send({type:"DATA",dst:this.peer,payload:e});return}if(!this._encryption.ready){this._pendingRelayQueue.push(e);return}let t=JSON.stringify(e);this._encryption.encrypt(t).then(n=>{this._provider.socket.send({type:"DATA",dst:this.peer,payload:{__encrypted:n}})}).catch(n=>{this.emit("error",new Error(`Relay encryption failed: ${String(n)}`))})}_flushPendingRelayQueue(){let e=[...this._pendingRelayQueue];this._pendingRelayQueue=[];for(let t of e)this._sendRelay(t)}_deliverInOrder(e,t){if(e===void 0||!this._encryptRelay){this._dispatchIncoming(t);return}if(this._expectedSeq===null){this._dispatchIncoming(t),this._expectedSeq=e+1;return}if(e===this._expectedSeq)this._dispatchIncoming(t),this._expectedSeq=e+1,this._flushReorderBuffer();else if(e>this._expectedSeq){this._reorderBuffer.set(e,t);let n=setTimeout(()=>{this._reorderTimers.delete(n),!this._closed&&this._reorderBuffer.has(e)&&this._forceFlushReorderBuffer()},this._reorderTimeout);this._reorderTimers.add(n)}}_flushReorderBuffer(){if(this._expectedSeq!==null)for(;this._reorderBuffer.has(this._expectedSeq);)this._dispatchIncoming(this._reorderBuffer.get(this._expectedSeq)),this._reorderBuffer.delete(this._expectedSeq),this._expectedSeq++}_forceFlushReorderBuffer(){if(this._reorderBuffer.size===0)return;let e=Number.MAX_SAFE_INTEGER;for(let t of this._reorderBuffer.keys())t<e&&(e=t);this._expectedSeq=e,this._flushReorderBuffer()}_dispatchIncoming(e){Ct(e)?(this._topics.dispatch(e.__topic,e.__data,this.peer),this.emit("data",e.__data)):(this._topics.dispatch(void 0,e,this.peer),this.emit("data",e))}_attemptWebRTC(){if(this._closed)return;let e=this._options.iceTimeout??1e4,t=this._provider.connect(this.peer,{reliable:!0});if(!t){this._fallbackToRelay();return}this._dataConnection=t,this._iceTimer=setTimeout(()=>{this._mode!=="webrtc"&&(l.warn(`HybridConnection: ICE timeout after ${e}ms for ${this.peer}, falling back to relay`),this._fallbackToRelay())},e),this._dataConnection.on("open",()=>{l.log(`HybridConnection: WebRTC opened to ${this.peer} (attempt ${this._upgradeAttempts+1})`),this._clearIceTimer(),this._clearUpgradeTimer(),this._upgradeAttempts=0,this._setMode("webrtc"),this._open||(this._open=!0,this.emit("open"))}),this._dataConnection.on("data",n=>{this._dispatchIncoming(n)}),this._dataConnection.on("close",()=>{this._open&&!this._closed&&(l.log(`HybridConnection: WebRTC to ${this.peer} closed, falling back to relay`),this._dataConnection=null,this._fallbackToRelay())}),this._dataConnection.on("error",n=>{this.emit("error",n instanceof Error?n:new Error(String(n)))})}_fallbackToRelay(){this._closed||(this._clearIceTimer(),this._setMode("ws-relay"),this._open||(this._open=!0,this.emit("open")),this.initiateKeyExchange(),this._scheduleUpgrade())}_setMode(e){this._mode!==e&&(l.log(`HybridConnection: transport ${this._mode} -> ${e} for ${this.peer}`),this._mode=e,this.emit("transportChanged",e))}_clearIceTimer(){this._iceTimer!==null&&(clearTimeout(this._iceTimer),this._iceTimer=null)}_clearUpgradeTimer(){this._upgradeTimer!==null&&(clearInterval(this._upgradeTimer),this._upgradeTimer=null)}_scheduleUpgrade(){if(!(this._options.autoUpgrade??!0))return;this._clearUpgradeTimer();let e=this._options.upgradeInterval??6e4,t=this._options.maxUpgradeAttempts??5;this._upgradeTimer=setInterval(()=>{if(this._closed){this._clearUpgradeTimer();return}if(this._upgradeAttempts>=t){this._clearUpgradeTimer();return}this._upgradeAttempts++,this._attemptWebRTC()},e)}async _tryConnectionReversal(){if(typeof this._provider?.on!="function")return!1;try{if(!(await new Promise((o,s)=>{let a=setTimeout(()=>s(new Error("timeout")),3e3),c=p=>{clearTimeout(a),this._provider.off("CONNECT-REQUEST",c),o(p)};this._provider.on("CONNECT-REQUEST",c),this._provider.socket.send({type:"CONNECT-REQUEST",payload:{peer:this.peer}})}))?.address)return!1;let n=new RTCPeerConnection(this._provider.options.config),i=n.createDataChannel("probe",{id:0});return await new Promise((o,s)=>{let a=setTimeout(()=>{n.close(),s(new Error("direct-dial-timeout"))},2500);i.onopen=()=>{clearTimeout(a),o()},i.onerror=()=>{clearTimeout(a),n.close(),s(new Error("dc-error"))}}),this._dataConnection=void 0,this._setMode("webrtc"),n.close(),!0}catch{return!1}}async _gatherSrflxCandidates(){let e=new RTCPeerConnection({iceServers:this._provider.options.config?.iceServers});e.createDataChannel("probe");let t=await e.createOffer();await e.setLocalDescription(t);let n=[];return await new Promise(i=>{let o=setTimeout(i,2e3);e.onicecandidate=s=>{if(!s.candidate){clearTimeout(o),i();return}s.candidate.candidate.includes("typ host")||n.push(s.candidate.candidate)}}),e.close(),n}async _dcutrHolePunch(){try{let e=await this._gatherSrflxCandidates(),t=performance.now(),n=await new Promise((o,s)=>{let a=setTimeout(()=>s(new Error("dcutr-timeout")),8e3),c=p=>{clearTimeout(a),this._provider.off("DCUTR-CONNECT",c),o(p?.addresses??[])};this._provider.on("DCUTR-CONNECT",c),this._provider.socket.send({type:"DCUTR-CONNECT",payload:{addresses:e}})}),i=performance.now()-t;this._provider.socket.send({type:"DCUTR-SYNC",payload:{}}),await new Promise(o=>setTimeout(o,i/2));for(let o=0;o<Math.min(n.length,4);o++)try{let s=new RTCPeerConnection(this._provider.options.config);return await new Promise((a,c)=>{let p=setTimeout(()=>{s.close(),c(new Error("dc-dial-timeout"))},5e3),d=s.createDataChannel("dcutr");d.onopen=()=>{clearTimeout(p),a()}}),this._dataConnection=void 0,this._setMode("webrtc"),s.close(),!0}catch{}return!1}catch{return!1}}};var K=class r extends I{static ID_PREFIX="mc_";_negotiator;_localStream;_remoteStream=null;get type(){return"media"}get localStream(){return this._localStream}get remoteStream(){return this._remoteStream}constructor(e,t,n){super(e,t,n),this._localStream=this.options._stream,this.connectionId=this.options.connectionId||r.ID_PREFIX+_.randomToken(),this._negotiator=new A(this),this._localStream&&this._negotiator.startConnection({_stream:this._localStream,originator:!0})}_initializeDataChannel(e){this.dataChannel=e,this.dataChannel.onopen=()=>{l.log(`DC#${this.connectionId} dc connection success`),this.emit("willCloseOnRemote")},this.dataChannel.onclose=()=>{l.log(`DC#${this.connectionId} dc closed for:`,this.peer),this.close()}}addStream(e){l.log("Receiving stream",e),this._remoteStream=e,super.emit("stream",e)}handleMessage(e){let t=e.type,n=e.payload;switch(e.type){case"ANSWER":this._negotiator&&this._negotiator.handleSDP(t,n.sdp).then(()=>{this._open=!0});break;case"CANDIDATE":this._negotiator&&this._negotiator.handleCandidate(n.candidate);break;default:l.warn(`Unrecognized message type:${t} from peer:${this.peer}`);break}}answer(e,t={}){if(this._localStream){l.warn("Local stream already exists on this MediaConnection. Are you answering a call twice?");return}if(!this._negotiator||!this.provider){l.warn("Cannot answer a connection that has already been closed.");return}this._localStream=e??null,t?.sdpTransform&&(this.options.sdpTransform=t.sdpTransform),this._negotiator.startConnection({...this.options._payload,_stream:e});let n=this.provider._getMessages(this.connectionId);for(let i of n)this.handleMessage(i);this._open=!0}close(){this._negotiator&&(this._negotiator.cleanup(),this._negotiator=null),this._localStream=null,this._remoteStream=null,this.provider&&(this.provider._removeConnection(this),this.provider=null),this.options?._stream&&(this.options._stream=null),this.open&&(this._open=!1,super.emit("close"),this.removeAllListeners())}};var St=L(ae(),1),T=class extends St.EventEmitter{};var ue=class r extends T{_connected=!1;_disconnected=!0;_id;_token;_messagesQueue=[];_polling=!1;_autoReconnect=!0;_reconnectAttempt=0;_reconnectTimer;_heartbeatTimer;_lastSeq=0;_baseUrl;_key;_jwt;_apiKey;_pingInterval;_abortController;static BACKOFF_SCHEDULE=[0,1e3,2e3,4e3,8e3,16e3,3e4];static BACKOFF_JITTER=500;constructor(e,t,n,i,o,s=5e3,a,c){super();let p=e?"https://":"http://";this._baseUrl=`${p+t}:${n}${i}`,this._pingInterval=s,this._key=o,this._jwt=a,this._apiKey=c}_applyAuthParams(e){e.set("key",this._key),this._jwt&&e.set("jwt",this._jwt),this._apiKey&&e.set("api_key",this._apiKey)}get reconnectAttempt(){return this._reconnectAttempt}start(e,t){this._id=e,this._token=t,this._disconnected=!1,this._connected=!0,this._reconnectAttempt=0,this._startPolling().catch(n=>{l.error("Polling start failed:",n),!this._disconnected&&this._autoReconnect&&this._scheduleReconnect()}),this._startHeartbeat(),this._sendQueuedMessages()}async _startPolling(){if(!(this._polling||this._disconnected)){for(this._polling=!0;this._polling&&!this._disconnected;)try{this._abortController=new AbortController;let e=new URLSearchParams({id:this._id,token:this._token});this._applyAuthParams(e),this._lastSeq>0&&e.set("last_seq",String(this._lastSeq));let t=await fetch(`${this._baseUrl}http/poll?${e}`,{signal:this._abortController.signal});if(!t.ok)throw new Error(`Poll failed: ${t.status}`);let n=await t.json();for(let i of n)typeof i.seq=="number"&&(this._lastSeq=i.seq),this.emit("message",i)}catch(e){if(e?.name==="AbortError")break;l.error("Poll error:",e),await new Promise(t=>setTimeout(t,1e3))}this._polling=!1,!this._disconnected&&this._autoReconnect&&this._scheduleReconnect()}}send(e){if(!this._disconnected){if(!this._id||!this._connected){this._messagesQueue.push(e);return}if(!e.type){this.emit("error","Invalid message");return}this._postMessage(e)}}async _postMessage(e){let t=new URLSearchParams({id:this._id,token:this._token});this._applyAuthParams(t);try{await fetch(`${this._baseUrl}http/send?${t}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}catch{this._messagesQueue.push(e)}}close(){this._autoReconnect=!1,this._cleanup(),this._disconnected=!0}_getReconnectDelay(){let e=r.BACKOFF_SCHEDULE,t=Math.min(this._reconnectAttempt,e.length-1),n=e[t],i=Math.random()*r.BACKOFF_JITTER*2-r.BACKOFF_JITTER;return Math.max(0,n+i)}_scheduleReconnect(){this._reconnectAttempt++,this.emit("reconnect-attempt",this._reconnectAttempt);let e=this._getReconnectDelay();this._reconnectTimer=setTimeout(()=>{this._startPolling().catch(t=>{l.error("Polling reconnect failed:",t),!this._disconnected&&this._autoReconnect&&this._scheduleReconnect()})},Math.max(0,e))}_startHeartbeat(){this._heartbeatTimer=setInterval(()=>{this._connected&&this._postMessage({type:"HEARTBEAT"})},this._pingInterval)}_sendQueuedMessages(){let e=[...this._messagesQueue];this._messagesQueue=[];for(let t of e)this.send(t)}_cleanup(){this._polling=!1,this._abortController?.abort(),this._abortController=void 0,clearTimeout(this._reconnectTimer),this._reconnectTimer=void 0,clearInterval(this._heartbeatTimer),this._heartbeatTimer=void 0,this._connected=!1}};var Tt="2.5.0",me=class r extends T{constructor(t,n,i,o,s,a=5e3,c,p){super();this.pingInterval=a;let d=t?"wss://":"ws://";this._baseUrl=`${d+n}:${i}${o}dendri?key=${s}`,p&&(this._baseUrl+=`&api_key=${encodeURIComponent(p)}`),this._jwt=c}pingInterval;_disconnected=!0;_id;_messagesQueue=[];_socket;_wsPingTimer;_heartbeatWorker=null;_heartbeatWorkerUrl=void 0;_visibilityHandler;_baseUrl;_autoReconnect=!0;_lastSeq=0;_reconnectAttempt=0;_reconnectTimer;_token;_jwt;static BACKOFF_SCHEDULE=[0,1e3,2e3,4e3,8e3,16e3,3e4];static BACKOFF_JITTER=500;start(t,n){this._id=t,this._token=n;let i=`${this._baseUrl}&id=${t}&token=${n}`;this._jwt&&(i+=`&jwt=${encodeURIComponent(this._jwt)}`),!(this._socket||!this._disconnected)&&(this._socket=new WebSocket(`${i}&version=${Tt}`),this._disconnected=!1,this._socket.onmessage=o=>{let s;try{s=JSON.parse(o.data),l.log("Server message received:",s)}catch{l.log("Invalid server message",o.data);return}s!==null&&typeof s=="object"&&"seq"in s&&typeof s.seq=="number"&&(this._lastSeq=s.seq),this.emit("message",s)},this._socket.onclose=o=>{this._disconnected||(l.log("Socket closed.",o),this._cleanup(),this._disconnected=!0,this._autoReconnect&&this._id&&this._token?this._scheduleReconnect():this.emit("disconnected"))},this._socket.onerror=o=>{l.error("Socket error:",o)},this._socket.onopen=()=>{if(this._disconnected)return;let o=this._reconnectAttempt>0;this._reconnectAttempt=0,this._sendQueuedMessages(),l.log("Socket open"),this._createHeartbeatWorker(),this._startWorkerHeartbeat(),o&&this.emit("reconnected")},typeof document<"u"&&(this._visibilityHandler=()=>{document.visibilityState==="hidden"?clearTimeout(this._wsPingTimer):this._wsOpen()?this._sendHeartbeat():this._autoReconnect&&!this._disconnected&&this._scheduleReconnect()},document.addEventListener("visibilitychange",this._visibilityHandler)))}_getReconnectDelay(){let t=r.BACKOFF_SCHEDULE,n=Math.min(this._reconnectAttempt,t.length-1),i=t[n],o=Math.random()*r.BACKOFF_JITTER*2-r.BACKOFF_JITTER;return Math.max(0,i+o)}get reconnectAttempt(){return this._reconnectAttempt}_scheduleReconnect(){let t=this._getReconnectDelay();this._reconnectAttempt++,this.emit("reconnect-attempt",this._reconnectAttempt),l.log(`Scheduling reconnect attempt ${this._reconnectAttempt} in ${Math.round(t)}ms`),this._reconnectTimer=setTimeout(()=>{if(this._reconnectTimer=void 0,!this._autoReconnect||!this._id||!this._token)return;let n=`${this._baseUrl}&id=${this._id}&token=${this._token}&version=${Tt}&last_seq=${this._lastSeq}`;this._jwt&&(n+=`&jwt=${encodeURIComponent(this._jwt)}`),this._disconnected=!1,this._socket=new WebSocket(n),this._socket.onmessage=i=>{let o;try{o=JSON.parse(i.data),l.log("Server message received:",o)}catch{l.log("Invalid server message",i.data);return}o!==null&&typeof o=="object"&&"seq"in o&&typeof o.seq=="number"&&(this._lastSeq=o.seq),this.emit("message",o)},this._socket.onclose=i=>{this._disconnected||(l.log("Socket closed during reconnect.",i),this._cleanup(),this._disconnected=!0,this._autoReconnect&&this._id&&this._token?this._scheduleReconnect():this.emit("disconnected"))},this._socket.onerror=i=>{l.error("Socket error during reconnect:",i)},this._socket.onopen=()=>{this._disconnected||(this._reconnectAttempt=0,this._sendQueuedMessages(),l.log("Socket reconnected"),this._createHeartbeatWorker(),this._startWorkerHeartbeat(),this.emit("reconnected"))}},t)}_createHeartbeatWorker(){if(!(typeof Worker>"u"||typeof Blob>"u"))try{let t=`
|
|
43
|
+
`)}_applyH264SdpFallback(e,t){return t.getTransceivers&&t.getTransceivers().some(i=>typeof i.setCodecPreferences=="function")?e:this._preferH264InSdp(e)}_addTracksToConnection(e,t){if(l.log(`add tracks from stream ${e.id} to peer connection`),!t.addTrack){l.error("Your browser doesn't support RTCPeerConnection#addTrack. Ignored.");return}e.getTracks().forEach(n=>{t.addTrack(n,e)})}_addStreamToMediaConnection(e,t){l.log(`add stream ${e.id} to media connection ${t.connectionId}`),t.addStream(e)}};var N=class r extends I{static ID_PREFIX="dc_";static MAX_BUFFERED_AMOUNT=8*1024*1024;_negotiator;reliable;get type(){return"data"}constructor(e,t,n){super(e,t,n),this.connectionId=this.options.connectionId||r.ID_PREFIX+ie(),this.label=this.options.label||this.connectionId,this.reliable=!!this.options.reliable,this._negotiator=new A(this),this._negotiator.startConnection(this.options._payload||{originator:!0,reliable:this.reliable})}_initializeDataChannel(e){this.dataChannel=e,this.dataChannel.onopen=()=>{l.log(`DC#${this.connectionId} dc connection success`),this._open=!0,this._applyAdaptiveBuffer(e),this.emit("open")},this.dataChannel.onclose=()=>{l.log(`DC#${this.connectionId} dc closed for:`,this.peer),this.close()}}_applyAdaptiveBuffer(e){let t=this.peerConnection;!t||typeof t.getStats!="function"||t.getStats().then(n=>{let i=null;if(n.forEach(o=>{o.type==="candidate-pair"&&o.state==="succeeded"&&o.currentRoundTripTime&&(i=o.currentRoundTripTime*1e3)}),i!==null){let o=13107200*(i/1e3),s=Math.max(1*1024*1024,Math.min(32*1024*1024,Math.ceil(o)));e.bufferedAmountLowThreshold=s}}).catch(()=>{})}_flushCloseTimeout=null;close(e){if(e?.flush){this.send({__peerData:{type:"close"}}),this._flushCloseTimeout=setTimeout(()=>{this._flushCloseTimeout=null,this.close()},5e3);return}this._flushCloseTimeout&&(clearTimeout(this._flushCloseTimeout),this._flushCloseTimeout=null),this._negotiator&&(this._negotiator.cleanup(),this._negotiator=null),this.provider&&(this.provider._removeConnection(this),this.provider=null),this.dataChannel&&(this.dataChannel.onopen=null,this.dataChannel.onmessage=null,this.dataChannel.onclose=null,this.dataChannel=null),this.open&&(this._open=!1,super.emit("close"),this.removeAllListeners())}send(e,t=!1){if(!this.open){this.emitError("not-open-yet","Connection is not open. You should listen for the `open` event before sending messages.");return}return this._send(e,t)}async handleMessage(e){let t=e.payload;switch(e.type){case"ANSWER":this._negotiator&&await this._negotiator.handleSDP(e.type,t.sdp);break;case"CANDIDATE":this._negotiator&&await this._negotiator.handleCandidate(t.candidate);break;default:l.warn("Unrecognized message type:",e.type,"from peer:",this.peer);break}}};var S=class extends N{_buffer=[];_bufferSize=0;_buffering=!1;_bufferTimer=null;_messageHandler=null;get bufferSize(){return this._bufferSize}_initializeDataChannel(e){super._initializeDataChannel(e),this.dataChannel.binaryType="arraybuffer",this._messageHandler=t=>this._handleDataMessage(t),this.dataChannel?.addEventListener("message",this._messageHandler)}_bufferedSend(e){(this._buffering||!this._trySend(e))&&(this._buffer.push(e),this._bufferSize=this._buffer.length)}_trySend(e){if(!this.open)return!1;if((this.dataChannel?.bufferedAmount??0)>N.MAX_BUFFERED_AMOUNT)return this._buffering=!0,this._bufferTimer=setTimeout(()=>{this._bufferTimer=null,this._buffering=!1,this._tryBuffer()},50),!1;try{this.dataChannel?.send(e)}catch(t){return l.error(`DC#:${this.connectionId} Error when sending:`,t),this._buffering=!0,this.close(),!1}return!0}_tryBuffer(){for(;this.open&&this._buffer.length>0;){let e=this._buffer[0];if(!this._trySend(e))break;this._buffer.shift(),this._bufferSize=this._buffer.length}}close(e){if(e?.flush){this.send({__peerData:{type:"close"}});return}this._bufferTimer&&(clearTimeout(this._bufferTimer),this._bufferTimer=null),this.dataChannel&&this._messageHandler&&(this.dataChannel.removeEventListener("message",this._messageHandler),this._messageHandler=null),this._buffer=[],this._bufferSize=0,super.close()}};var O=class r extends S{chunker=new R;serialization="binary";static MAX_CHUNKED_SETS=256;_chunkedData={};close(e){this._chunkedData={},super.close(e)}_handleDataMessage({data:e}){let t;try{t=G(e)}catch(i){l.error(`DC#${this.connectionId} Failed to unpack data:`,i),this.emitError("not-open-yet","Failed to deserialize received data");return}let n=t.__peerData;if(n){if(n.type==="close"){this.close();return}this._handleChunk(t);return}this.emit("data",t)}_handleChunk(e){let t=e.__peerData;if(e.n<0||e.n>=e.total){l.warn(`DC#${this.connectionId} Invalid chunk index ${e.n} of ${e.total}, dropping`);return}let n=this._chunkedData[t]||{data:[],count:0,total:e.total};if(n.data[e.n]=new Uint8Array(e.data),n.count++,this._chunkedData[t]=n,n.total===n.count){delete this._chunkedData[t];let i=it(n.data);this._handleDataMessage({data:i})}else{let i=Object.keys(this._chunkedData);if(i.length>r.MAX_CHUNKED_SETS){let o=i[0];l.warn(`DC#${this.connectionId} Too many pending chunk sets (${i.length}), dropping oldest`),delete this._chunkedData[Number(o)]}}}_send(e,t){let n=q(e);if(n instanceof Promise)return this._send_blob(n);if(!t&&n.byteLength>this.chunker.chunkedMTU){this._sendChunks(n);return}this._bufferedSend(n)}async _send_blob(e){let t=await e;if(!this.open){l.warn(`DC#${this.connectionId} Connection closed during async send, dropping message`);return}if(t.byteLength>this.chunker.chunkedMTU){this._sendChunks(t);return}this._bufferedSend(t)}_sendChunks(e){let t=this.chunker.chunk(e);l.log(`DC#${this.connectionId} Try to send ${t.length} chunks...`);for(let n of t)this.send(n,!0)}};var ce=class extends S{serialization="json";encoder=new TextEncoder;decoder=new TextDecoder;stringify=JSON.stringify;parse=JSON.parse;_handleDataMessage({data:e}){let t;try{t=this.parse(this.decoder.decode(e))}catch(i){l.error(`DC#${this.connectionId} Failed to parse JSON data:`,i),this.emitError("not-open-yet","Failed to parse received JSON data");return}let n=t.__peerData;if(n&&n.type==="close"){this.close();return}this.emit("data",t)}_send(e,t){let n=this.encoder.encode(this.stringify(e));if(n.byteLength>=_.chunkedMTU){this.emitError("message-too-big","Message too big for JSON channel");return}this._bufferedSend(n.buffer)}};var pe=class extends S{serialization="raw";_handleDataMessage({data:e}){super.emit("data",e)}_send(e,t){this._bufferedSend(e)}};var bt=L(ae(),1);var de=class{_pending=new Map;_counter=0;nextId(){return`ack_${++this._counter}_${Date.now()}`}waitForAck(e,t=5e3,n){return new Promise((i,o)=>{let s=setTimeout(()=>{this._pending.delete(e),o(new Error(`ACK timeout for ${e}`))},t);this._pending.set(e,{resolve:i,reject:o,timer:s,peerId:n})})}handleAck(e){let t=this._pending.get(e);return t?(clearTimeout(t.timer),t.resolve(),this._pending.delete(e),!0):!1}rejectAllForPeer(e){let t=0;for(let[n,i]of this._pending)i.peerId===e&&(clearTimeout(i.timer),i.reject(new Error(`Peer ${e} disconnected`)),this._pending.delete(n),t++);return t}clear(){for(let[,e]of this._pending)clearTimeout(e.timer),e.reject(new Error("Connection closed"));this._pending.clear()}get pendingCount(){return this._pending.size}};var le=class{_keyPair=null;_sharedKey=null;_ready=!1;get ready(){return this._ready}async generateKeyPair(){return this._keyPair=await crypto.subtle.generateKey({name:"ECDH",namedCurve:"P-256"},!0,["deriveKey"]),crypto.subtle.exportKey("jwk",this._keyPair.publicKey)}async deriveSharedKey(e){if(!this._keyPair)throw new Error("Key pair not generated");let t=await crypto.subtle.importKey("jwk",e,{name:"ECDH",namedCurve:"P-256"},!1,[]);this._sharedKey=await crypto.subtle.deriveKey({name:"ECDH",public:t},this._keyPair.privateKey,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"]),this._ready=!0}async encrypt(e){if(!this._sharedKey)throw new Error("Shared key not derived");let t=crypto.getRandomValues(new Uint8Array(12)),n=new TextEncoder().encode(e),i=await crypto.subtle.encrypt({name:"AES-GCM",iv:t},this._sharedKey,n);return{iv:btoa(String.fromCharCode(...t)),ciphertext:btoa(String.fromCharCode(...new Uint8Array(i)))}}async decrypt(e){if(!this._sharedKey)throw new Error("Shared key not derived");let t=Uint8Array.from(atob(e.iv),o=>o.charCodeAt(0)),n=Uint8Array.from(atob(e.ciphertext),o=>o.charCodeAt(0)),i=await crypto.subtle.decrypt({name:"AES-GCM",iv:t},this._sharedKey,n);return new TextDecoder().decode(i)}clear(){this._keyPair=null,this._sharedKey=null,this._ready=!1}};function Ct(r){return typeof r=="object"&&r!==null&&"__topic"in r&&typeof r.__topic=="string"&&"__data"in r}var he=class{_handlers=new Map;_globalHandlers=new Set;subscribe(e,t){return this._handlers.has(e)||this._handlers.set(e,new Set),this._handlers.get(e).add(t),()=>{this._handlers.get(e)?.delete(t)}}subscribeAll(e){return this._globalHandlers.add(e),()=>{this._globalHandlers.delete(e)}}dispatch(e,t,n){let i=!1;if(e!==void 0&&this._handlers.has(e))for(let o of this._handlers.get(e))o(t,n),i=!0;for(let o of this._globalHandlers)o(t,n),i=!0;return i}clear(){this._handlers.clear(),this._globalHandlers.clear()}};var fe=class extends bt.EventEmitter{peer;_provider;_options;_dataConnection=null;_mode="reconnecting";_iceTimer=null;_upgradeTimer=null;_upgradeAttempts=0;_open=!1;_closed=!1;_ackManager=new de;_topics=new he;_encryption=new le;_encryptRelay;_keyExchangeSent=!1;_pendingRelayQueue=[];_expectedSeq=null;_reorderBuffer=new Map;_reorderTimeout=500;_reorderTimers=new Set;constructor(e,t,n={}){super(),this.peer=e,this._provider=t,this._options=Object.freeze({...n}),this._encryptRelay=n.encryptRelay??!0}get mode(){return this._mode}get open(){return this._open}start(){if(!this._closed){if(l.log(`HybridConnection: start peer=${this.peer} iceTimeout=${this._options.iceTimeout??1e4}ms encryptRelay=${this._encryptRelay}`),typeof this._provider?.on!="function"){this._attemptWebRTC();return}this._tryConnectionReversal().then(e=>{e||this._attemptWebRTC()})}}send(e,t){if(this._closed){this.emit("error",new Error("Connection is closed."));return}if(!this._open){this.emit("error",new Error("Connection is not open. Listen for the `open` event before sending."));return}let n=t?.topic,i=n?{__topic:n,__data:e}:e;this._mode==="webrtc"&&this._dataConnection?.open?this._dataConnection.send(i):this._mode==="ws-relay"?this._sendRelay(i):this.emit("error",new Error(`No transport available (current mode: ${this._mode}).`))}async sendWithAck(e,t){if(this._closed)throw new Error("Connection is closed.");if(!this._open)throw new Error("Connection is not open. Listen for the `open` event before sending.");let n=this._ackManager.nextId();if(this._mode==="webrtc"&&this._dataConnection?.open)this._dataConnection.send({__ackId:n,data:e});else if(this._mode==="ws-relay")this._sendRelay({__ackId:n,data:e});else throw new Error(`No transport available (current mode: ${this._mode}).`);return this._ackManager.waitForAck(n,t)}get ackManager(){return this._ackManager}subscribe(e,t){return this._topics.subscribe(e,t)}onData(e){return this._topics.subscribeAll(e)}handleRelayData(e,t){if(this._encryptRelay&&this._encryption.ready&&e!==null&&typeof e=="object"&&"__encrypted"in e){let n=e;this._encryption.decrypt(n.__encrypted).then(i=>{this._deliverInOrder(t,JSON.parse(i))}).catch(i=>{this.emit("error",new Error(`Relay decryption failed: ${String(i)}`))});return}this._deliverInOrder(t,e)}close(){if(!this._closed){this._closed=!0,this._clearIceTimer(),this._clearUpgradeTimer(),this._ackManager.clear(),this._topics.clear(),this._encryption.clear(),this._pendingRelayQueue=[],this._keyExchangeSent=!1,this._reorderBuffer.clear(),this._expectedSeq=null;for(let e of this._reorderTimers)clearTimeout(e);this._reorderTimers.clear(),this._dataConnection&&(this._dataConnection.close(),this._dataConnection=null),this._open&&(this._open=!1,this.emit("close")),this.removeAllListeners()}}initiateKeyExchange(){!this._encryptRelay||this._keyExchangeSent||(this._keyExchangeSent=!0,this._encryption.generateKeyPair().then(e=>{this._provider.socket.send({type:"KEY-EXCHANGE",dst:this.peer,payload:{publicKey:e}})}).catch(e=>{this._keyExchangeSent=!1,this.emit("error",new Error(`Key exchange initiation failed: ${String(e)}`))}))}handleKeyExchange(e){if(!this._encryptRelay)return;let t=n=>{this._encryption.deriveSharedKey(n).then(()=>{l.log(`HybridConnection: E2E encryption established with ${this.peer}`),this._flushPendingRelayQueue()}).catch(i=>{this.emit("error",new Error(`Key derivation failed: ${String(i)}`))})};this._keyExchangeSent?t(e.publicKey):(this._keyExchangeSent=!0,this._encryption.generateKeyPair().then(n=>{this._provider.socket.send({type:"KEY-EXCHANGE",dst:this.peer,payload:{publicKey:n}}),t(e.publicKey)}).catch(n=>{this._keyExchangeSent=!1,this.emit("error",new Error(`Key exchange response failed: ${String(n)}`))}))}_sendRelay(e){if(!this._encryptRelay){this._provider.socket.send({type:"DATA",dst:this.peer,payload:e});return}if(!this._encryption.ready){this._pendingRelayQueue.push(e);return}let t=JSON.stringify(e);this._encryption.encrypt(t).then(n=>{this._provider.socket.send({type:"DATA",dst:this.peer,payload:{__encrypted:n}})}).catch(n=>{this.emit("error",new Error(`Relay encryption failed: ${String(n)}`))})}_flushPendingRelayQueue(){let e=[...this._pendingRelayQueue];this._pendingRelayQueue=[];for(let t of e)this._sendRelay(t)}_deliverInOrder(e,t){if(e===void 0||!this._encryptRelay){this._dispatchIncoming(t);return}if(this._expectedSeq===null){this._dispatchIncoming(t),this._expectedSeq=e+1;return}if(e===this._expectedSeq)this._dispatchIncoming(t),this._expectedSeq=e+1,this._flushReorderBuffer();else if(e>this._expectedSeq){this._reorderBuffer.set(e,t);let n=setTimeout(()=>{this._reorderTimers.delete(n),!this._closed&&this._reorderBuffer.has(e)&&this._forceFlushReorderBuffer()},this._reorderTimeout);this._reorderTimers.add(n)}}_flushReorderBuffer(){if(this._expectedSeq!==null)for(;this._reorderBuffer.has(this._expectedSeq);)this._dispatchIncoming(this._reorderBuffer.get(this._expectedSeq)),this._reorderBuffer.delete(this._expectedSeq),this._expectedSeq++}_forceFlushReorderBuffer(){if(this._reorderBuffer.size===0)return;let e=Number.MAX_SAFE_INTEGER;for(let t of this._reorderBuffer.keys())t<e&&(e=t);this._expectedSeq=e,this._flushReorderBuffer()}_dispatchIncoming(e){Ct(e)?(this._topics.dispatch(e.__topic,e.__data,this.peer),this.emit("data",e.__data)):(this._topics.dispatch(void 0,e,this.peer),this.emit("data",e))}_attemptWebRTC(){if(this._closed)return;let e=this._options.iceTimeout??1e4,t=this._provider.connect(this.peer,{reliable:!0});if(!t){this._fallbackToRelay();return}this._dataConnection=t,this._iceTimer=setTimeout(()=>{this._mode!=="webrtc"&&(l.warn(`HybridConnection: ICE timeout after ${e}ms for ${this.peer}, falling back to relay`),this._fallbackToRelay())},e),this._dataConnection.on("open",()=>{l.log(`HybridConnection: WebRTC opened to ${this.peer} (attempt ${this._upgradeAttempts+1})`),this._clearIceTimer(),this._clearUpgradeTimer(),this._upgradeAttempts=0,this._setMode("webrtc"),this._open||(this._open=!0,this.emit("open"))}),this._dataConnection.on("data",n=>{this._dispatchIncoming(n)}),this._dataConnection.on("close",()=>{this._open&&!this._closed&&(l.log(`HybridConnection: WebRTC to ${this.peer} closed, falling back to relay`),this._dataConnection=null,this._fallbackToRelay())}),this._dataConnection.on("error",n=>{this.emit("error",n instanceof Error?n:new Error(String(n)))})}_fallbackToRelay(){this._closed||(this._clearIceTimer(),this._setMode("ws-relay"),this._open||(this._open=!0,this.emit("open")),this.initiateKeyExchange(),this._scheduleUpgrade())}_setMode(e){this._mode!==e&&(l.log(`HybridConnection: transport ${this._mode} -> ${e} for ${this.peer}`),this._mode=e,this.emit("transportChanged",e))}_clearIceTimer(){this._iceTimer!==null&&(clearTimeout(this._iceTimer),this._iceTimer=null)}_clearUpgradeTimer(){this._upgradeTimer!==null&&(clearInterval(this._upgradeTimer),this._upgradeTimer=null)}_scheduleUpgrade(){if(!(this._options.autoUpgrade??!0))return;this._clearUpgradeTimer();let e=this._options.upgradeInterval??6e4,t=this._options.maxUpgradeAttempts??5;this._upgradeTimer=setInterval(()=>{if(this._closed){this._clearUpgradeTimer();return}if(this._upgradeAttempts>=t){this._clearUpgradeTimer();return}this._upgradeAttempts++,this._attemptWebRTC()},e)}async _tryConnectionReversal(){if(typeof this._provider?.on!="function")return!1;try{if(!(await new Promise((o,s)=>{let a=setTimeout(()=>s(new Error("timeout")),3e3),c=p=>{clearTimeout(a),this._provider.off("CONNECT-REQUEST",c),o(p)};this._provider.on("CONNECT-REQUEST",c),this._provider.socket.send({type:"CONNECT-REQUEST",payload:{peer:this.peer}})}))?.address)return!1;let n=new RTCPeerConnection(this._provider.options.config),i=n.createDataChannel("probe",{id:0});return await new Promise((o,s)=>{let a=setTimeout(()=>{n.close(),s(new Error("direct-dial-timeout"))},2500);i.onopen=()=>{clearTimeout(a),o()},i.onerror=()=>{clearTimeout(a),n.close(),s(new Error("dc-error"))}}),this._dataConnection=void 0,this._setMode("webrtc"),n.close(),!0}catch{return!1}}async _gatherSrflxCandidates(){let e=new RTCPeerConnection({iceServers:this._provider.options.config?.iceServers});e.createDataChannel("probe");let t=await e.createOffer();await e.setLocalDescription(t);let n=[];return await new Promise(i=>{let o=setTimeout(i,2e3);e.onicecandidate=s=>{if(!s.candidate){clearTimeout(o),i();return}s.candidate.candidate.includes("typ host")||n.push(s.candidate.candidate)}}),e.close(),n}async _dcutrHolePunch(){try{let e=await this._gatherSrflxCandidates(),t=performance.now(),n=await new Promise((o,s)=>{let a=setTimeout(()=>s(new Error("dcutr-timeout")),8e3),c=p=>{clearTimeout(a),this._provider.off("DCUTR-CONNECT",c),o(p?.addresses??[])};this._provider.on("DCUTR-CONNECT",c),this._provider.socket.send({type:"DCUTR-CONNECT",payload:{addresses:e}})}),i=performance.now()-t;this._provider.socket.send({type:"DCUTR-SYNC",payload:{}}),await new Promise(o=>setTimeout(o,i/2));for(let o=0;o<Math.min(n.length,4);o++)try{let s=new RTCPeerConnection(this._provider.options.config);return await new Promise((a,c)=>{let p=setTimeout(()=>{s.close(),c(new Error("dc-dial-timeout"))},5e3),d=s.createDataChannel("dcutr");d.onopen=()=>{clearTimeout(p),a()}}),this._dataConnection=void 0,this._setMode("webrtc"),s.close(),!0}catch{}return!1}catch{return!1}}};var K=class r extends I{static ID_PREFIX="mc_";_negotiator;_localStream;_remoteStream=null;get type(){return"media"}get localStream(){return this._localStream}get remoteStream(){return this._remoteStream}constructor(e,t,n){super(e,t,n),this._localStream=this.options._stream,this.connectionId=this.options.connectionId||r.ID_PREFIX+_.randomToken(),this._negotiator=new A(this),this._localStream&&this._negotiator.startConnection({_stream:this._localStream,originator:!0})}_initializeDataChannel(e){this.dataChannel=e,this.dataChannel.onopen=()=>{l.log(`DC#${this.connectionId} dc connection success`),this.emit("willCloseOnRemote")},this.dataChannel.onclose=()=>{l.log(`DC#${this.connectionId} dc closed for:`,this.peer),this.close()}}addStream(e){l.log("Receiving stream",e),this._remoteStream=e,super.emit("stream",e)}handleMessage(e){let t=e.type,n=e.payload;switch(e.type){case"ANSWER":this._negotiator&&this._negotiator.handleSDP(t,n.sdp).then(()=>{this._open=!0});break;case"CANDIDATE":this._negotiator&&this._negotiator.handleCandidate(n.candidate);break;default:l.warn(`Unrecognized message type:${t} from peer:${this.peer}`);break}}answer(e,t={}){if(this._localStream){l.warn("Local stream already exists on this MediaConnection. Are you answering a call twice?");return}if(!this._negotiator||!this.provider){l.warn("Cannot answer a connection that has already been closed.");return}this._localStream=e??null,t?.sdpTransform&&(this.options.sdpTransform=t.sdpTransform),this._negotiator.startConnection({...this.options._payload,_stream:e});let n=this.provider._getMessages(this.connectionId);for(let i of n)this.handleMessage(i);this._open=!0}close(){this._negotiator&&(this._negotiator.cleanup(),this._negotiator=null),this._localStream=null,this._remoteStream=null,this.provider&&(this.provider._removeConnection(this),this.provider=null),this.options?._stream&&(this.options._stream=null),this.open&&(this._open=!1,super.emit("close"),this.removeAllListeners())}};var St=L(ae(),1),T=class extends St.EventEmitter{};var ue=class r extends T{_connected=!1;_disconnected=!0;_id;_token;_messagesQueue=[];_polling=!1;_autoReconnect=!0;_reconnectAttempt=0;_reconnectTimer;_heartbeatTimer;_lastSeq=0;_baseUrl;_key;_jwt;_apiKey;_pingInterval;_abortController;static BACKOFF_SCHEDULE=[0,1e3,2e3,4e3,8e3,16e3,3e4];static BACKOFF_JITTER=500;constructor(e,t,n,i,o,s=5e3,a,c){super();let p=e?"https://":"http://";this._baseUrl=`${p+t}:${n}${i}`,this._pingInterval=s,this._key=o,this._jwt=a,this._apiKey=c}_applyAuthParams(e){e.set("key",this._key),this._jwt&&e.set("jwt",this._jwt),this._apiKey&&e.set("api_key",this._apiKey)}get reconnectAttempt(){return this._reconnectAttempt}start(e,t){this._id=e,this._token=t,this._disconnected=!1,this._connected=!0,this._reconnectAttempt=0,this._startPolling().catch(n=>{l.error("Polling start failed:",n),!this._disconnected&&this._autoReconnect&&this._scheduleReconnect()}),this._startHeartbeat(),this._sendQueuedMessages()}async _startPolling(){if(!(this._polling||this._disconnected)){for(this._polling=!0;this._polling&&!this._disconnected;)try{this._abortController=new AbortController;let e=new URLSearchParams({id:this._id,token:this._token});this._applyAuthParams(e),this._lastSeq>0&&e.set("last_seq",String(this._lastSeq));let t=await fetch(`${this._baseUrl}http/poll?${e}`,{signal:this._abortController.signal});if(!t.ok)throw new Error(`Poll failed: ${t.status}`);let n=await t.json();for(let i of n)typeof i.seq=="number"&&(this._lastSeq=i.seq),this.emit("message",i)}catch(e){if(e?.name==="AbortError")break;l.error("Poll error:",e),await new Promise(t=>setTimeout(t,1e3))}this._polling=!1,!this._disconnected&&this._autoReconnect&&this._scheduleReconnect()}}send(e){if(!this._disconnected){if(!this._id||!this._connected){this._messagesQueue.push(e);return}if(!e.type){this.emit("error","Invalid message");return}this._postMessage(e)}}async _postMessage(e){let t=new URLSearchParams({id:this._id,token:this._token});this._applyAuthParams(t);try{await fetch(`${this._baseUrl}http/send?${t}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}catch{this._messagesQueue.push(e)}}close(){this._autoReconnect=!1,this._cleanup(),this._disconnected=!0}_getReconnectDelay(){let e=r.BACKOFF_SCHEDULE,t=Math.min(this._reconnectAttempt,e.length-1),n=e[t],i=Math.random()*r.BACKOFF_JITTER*2-r.BACKOFF_JITTER;return Math.max(0,n+i)}_scheduleReconnect(){this._reconnectAttempt++,this.emit("reconnect-attempt",this._reconnectAttempt);let e=this._getReconnectDelay();this._reconnectTimer=setTimeout(()=>{this._startPolling().catch(t=>{l.error("Polling reconnect failed:",t),!this._disconnected&&this._autoReconnect&&this._scheduleReconnect()})},Math.max(0,e))}_startHeartbeat(){this._heartbeatTimer=setInterval(()=>{this._connected&&this._postMessage({type:"HEARTBEAT"})},this._pingInterval)}_sendQueuedMessages(){let e=[...this._messagesQueue];this._messagesQueue=[];for(let t of e)this.send(t)}_cleanup(){this._polling=!1,this._abortController?.abort(),this._abortController=void 0,clearTimeout(this._reconnectTimer),this._reconnectTimer=void 0,clearInterval(this._heartbeatTimer),this._heartbeatTimer=void 0,this._connected=!1}};var Tt="2.6.0",me=class r extends T{constructor(t,n,i,o,s,a=5e3,c,p){super();this.pingInterval=a;let d=t?"wss://":"ws://";this._baseUrl=`${d+n}:${i}${o}dendri?key=${s}`,p&&(this._baseUrl+=`&api_key=${encodeURIComponent(p)}`),this._jwt=c}pingInterval;_disconnected=!0;_id;_messagesQueue=[];_socket;_wsPingTimer;_heartbeatWorker=null;_heartbeatWorkerUrl=void 0;_visibilityHandler;_baseUrl;_autoReconnect=!0;_lastSeq=0;_reconnectAttempt=0;_reconnectTimer;_token;_jwt;static BACKOFF_SCHEDULE=[0,1e3,2e3,4e3,8e3,16e3,3e4];static BACKOFF_JITTER=500;start(t,n){this._id=t,this._token=n;let i=`${this._baseUrl}&id=${t}&token=${n}`;this._jwt&&(i+=`&jwt=${encodeURIComponent(this._jwt)}`),!(this._socket||!this._disconnected)&&(this._socket=new WebSocket(`${i}&version=${Tt}`),this._disconnected=!1,this._socket.onmessage=o=>{let s;try{s=JSON.parse(o.data),l.log("Server message received:",s)}catch{l.log("Invalid server message",o.data);return}s!==null&&typeof s=="object"&&"seq"in s&&typeof s.seq=="number"&&(this._lastSeq=s.seq),this.emit("message",s)},this._socket.onclose=o=>{this._disconnected||(l.log("Socket closed.",o),this._cleanup(),this._disconnected=!0,this._autoReconnect&&this._id&&this._token?this._scheduleReconnect():this.emit("disconnected"))},this._socket.onerror=o=>{l.error("Socket error:",o)},this._socket.onopen=()=>{if(this._disconnected)return;let o=this._reconnectAttempt>0;this._reconnectAttempt=0,this._sendQueuedMessages(),l.log("Socket open"),this._createHeartbeatWorker(),this._startWorkerHeartbeat(),o&&this.emit("reconnected")},typeof document<"u"&&(this._visibilityHandler=()=>{document.visibilityState==="hidden"?clearTimeout(this._wsPingTimer):this._wsOpen()?this._sendHeartbeat():this._autoReconnect&&!this._disconnected&&this._scheduleReconnect()},document.addEventListener("visibilitychange",this._visibilityHandler)))}_getReconnectDelay(){let t=r.BACKOFF_SCHEDULE,n=Math.min(this._reconnectAttempt,t.length-1),i=t[n],o=Math.random()*r.BACKOFF_JITTER*2-r.BACKOFF_JITTER;return Math.max(0,i+o)}get reconnectAttempt(){return this._reconnectAttempt}_scheduleReconnect(){let t=this._getReconnectDelay();this._reconnectAttempt++,this.emit("reconnect-attempt",this._reconnectAttempt),l.log(`Scheduling reconnect attempt ${this._reconnectAttempt} in ${Math.round(t)}ms`),this._reconnectTimer=setTimeout(()=>{if(this._reconnectTimer=void 0,!this._autoReconnect||!this._id||!this._token)return;let n=`${this._baseUrl}&id=${this._id}&token=${this._token}&version=${Tt}&last_seq=${this._lastSeq}`;this._jwt&&(n+=`&jwt=${encodeURIComponent(this._jwt)}`),this._disconnected=!1,this._socket=new WebSocket(n),this._socket.onmessage=i=>{let o;try{o=JSON.parse(i.data),l.log("Server message received:",o)}catch{l.log("Invalid server message",i.data);return}o!==null&&typeof o=="object"&&"seq"in o&&typeof o.seq=="number"&&(this._lastSeq=o.seq),this.emit("message",o)},this._socket.onclose=i=>{this._disconnected||(l.log("Socket closed during reconnect.",i),this._cleanup(),this._disconnected=!0,this._autoReconnect&&this._id&&this._token?this._scheduleReconnect():this.emit("disconnected"))},this._socket.onerror=i=>{l.error("Socket error during reconnect:",i)},this._socket.onopen=()=>{this._disconnected||(this._reconnectAttempt=0,this._sendQueuedMessages(),l.log("Socket reconnected"),this._createHeartbeatWorker(),this._startWorkerHeartbeat(),this.emit("reconnected"))}},t)}_createHeartbeatWorker(){if(!(typeof Worker>"u"||typeof Blob>"u"))try{let t=`
|
|
44
44
|
let interval = null;
|
|
45
45
|
self.onmessage = function(e) {
|
|
46
46
|
if (e.data.type === 'start') {
|
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
// src/adapters/react.ts
|
|
6
|
+
function useDendriStore(store) {
|
|
7
|
+
return react.useSyncExternalStore(
|
|
8
|
+
(listener) => store.subscribe(listener),
|
|
9
|
+
() => store.getSnapshot(),
|
|
10
|
+
() => store.getSnapshot()
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.useDendriStore = useDendriStore;
|
|
15
|
+
//# sourceMappingURL=react.cjs.map
|
|
16
|
+
//# sourceMappingURL=react.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/react.ts"],"names":["useSyncExternalStore"],"mappings":";;;;;AAsBO,SAAS,eAAe,KAAA,EAAyC;AACvE,EAAA,OAAOA,0BAAA;AAAA,IACN,CAAC,QAAA,KAAa,KAAA,CAAM,SAAA,CAAU,QAAQ,CAAA;AAAA,IACtC,MAAM,MAAM,WAAA,EAAY;AAAA,IACxB,MAAM,MAAM,WAAA;AAAY,GACzB;AACD","file":"react.cjs","sourcesContent":["import { useSyncExternalStore } from \"react\";\nimport type { DendriStore, DendriStoreSnapshot } from \"../store\";\n\n/**\n * Subscribe a React component to a Dendri store.\n *\n * ```tsx\n * import { createDendriStore } from \"@afterrealism/dendri-client\";\n * import { useDendriStore } from \"@afterrealism/dendri-client/react\";\n *\n * const store = createDendriStore({ url: \"wss://signal.example.com\" });\n *\n * function Room() {\n * const { connectionState, peers } = useDendriStore(store);\n * return <div>{connectionState} — {peers.length} peers</div>;\n * }\n * ```\n *\n * The component re-renders whenever the store changes. Snapshots are\n * referentially stable between changes, so this is safe for\n * `useSyncExternalStore` and React 18 concurrent rendering.\n */\nexport function useDendriStore(store: DendriStore): DendriStoreSnapshot {\n\treturn useSyncExternalStore(\n\t\t(listener) => store.subscribe(listener),\n\t\t() => store.getSnapshot(),\n\t\t() => store.getSnapshot(),\n\t);\n}\n"]}
|
package/dist/react.d.cts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { n as DendriStore, p as DendriStoreSnapshot } from './store-C3Nwl62R.cjs';
|
|
2
|
+
import 'eventemitter3';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Subscribe a React component to a Dendri store.
|
|
6
|
+
*
|
|
7
|
+
* ```tsx
|
|
8
|
+
* import { createDendriStore } from "@afterrealism/dendri-client";
|
|
9
|
+
* import { useDendriStore } from "@afterrealism/dendri-client/react";
|
|
10
|
+
*
|
|
11
|
+
* const store = createDendriStore({ url: "wss://signal.example.com" });
|
|
12
|
+
*
|
|
13
|
+
* function Room() {
|
|
14
|
+
* const { connectionState, peers } = useDendriStore(store);
|
|
15
|
+
* return <div>{connectionState} — {peers.length} peers</div>;
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* The component re-renders whenever the store changes. Snapshots are
|
|
20
|
+
* referentially stable between changes, so this is safe for
|
|
21
|
+
* `useSyncExternalStore` and React 18 concurrent rendering.
|
|
22
|
+
*/
|
|
23
|
+
declare function useDendriStore(store: DendriStore): DendriStoreSnapshot;
|
|
24
|
+
|
|
25
|
+
export { useDendriStore };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { n as DendriStore, p as DendriStoreSnapshot } from './store-C3Nwl62R.js';
|
|
2
|
+
import 'eventemitter3';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Subscribe a React component to a Dendri store.
|
|
6
|
+
*
|
|
7
|
+
* ```tsx
|
|
8
|
+
* import { createDendriStore } from "@afterrealism/dendri-client";
|
|
9
|
+
* import { useDendriStore } from "@afterrealism/dendri-client/react";
|
|
10
|
+
*
|
|
11
|
+
* const store = createDendriStore({ url: "wss://signal.example.com" });
|
|
12
|
+
*
|
|
13
|
+
* function Room() {
|
|
14
|
+
* const { connectionState, peers } = useDendriStore(store);
|
|
15
|
+
* return <div>{connectionState} — {peers.length} peers</div>;
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* The component re-renders whenever the store changes. Snapshots are
|
|
20
|
+
* referentially stable between changes, so this is safe for
|
|
21
|
+
* `useSyncExternalStore` and React 18 concurrent rendering.
|
|
22
|
+
*/
|
|
23
|
+
declare function useDendriStore(store: DendriStore): DendriStoreSnapshot;
|
|
24
|
+
|
|
25
|
+
export { useDendriStore };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import './chunk-G3PMV62Z.js';
|
|
2
|
+
import { useSyncExternalStore } from 'react';
|
|
3
|
+
|
|
4
|
+
function useDendriStore(store) {
|
|
5
|
+
return useSyncExternalStore(
|
|
6
|
+
(listener) => store.subscribe(listener),
|
|
7
|
+
() => store.getSnapshot(),
|
|
8
|
+
() => store.getSnapshot()
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { useDendriStore };
|
|
13
|
+
//# sourceMappingURL=react.js.map
|
|
14
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/react.ts"],"names":[],"mappings":";;;AAsBO,SAAS,eAAe,KAAA,EAAyC;AACvE,EAAA,OAAO,oBAAA;AAAA,IACN,CAAC,QAAA,KAAa,KAAA,CAAM,SAAA,CAAU,QAAQ,CAAA;AAAA,IACtC,MAAM,MAAM,WAAA,EAAY;AAAA,IACxB,MAAM,MAAM,WAAA;AAAY,GACzB;AACD","file":"react.js","sourcesContent":["import { useSyncExternalStore } from \"react\";\nimport type { DendriStore, DendriStoreSnapshot } from \"../store\";\n\n/**\n * Subscribe a React component to a Dendri store.\n *\n * ```tsx\n * import { createDendriStore } from \"@afterrealism/dendri-client\";\n * import { useDendriStore } from \"@afterrealism/dendri-client/react\";\n *\n * const store = createDendriStore({ url: \"wss://signal.example.com\" });\n *\n * function Room() {\n * const { connectionState, peers } = useDendriStore(store);\n * return <div>{connectionState} — {peers.length} peers</div>;\n * }\n * ```\n *\n * The component re-renders whenever the store changes. Snapshots are\n * referentially stable between changes, so this is safe for\n * `useSyncExternalStore` and React 18 concurrent rendering.\n */\nexport function useDendriStore(store: DendriStore): DendriStoreSnapshot {\n\treturn useSyncExternalStore(\n\t\t(listener) => store.subscribe(listener),\n\t\t() => store.getSnapshot(),\n\t\t() => store.getSnapshot(),\n\t);\n}\n"]}
|
package/dist/store.cjs
CHANGED
|
@@ -3531,7 +3531,7 @@ var Util = class extends BinaryPackChunker {
|
|
|
3531
3531
|
var util = new Util();
|
|
3532
3532
|
|
|
3533
3533
|
// src/api.ts
|
|
3534
|
-
var version = "2.
|
|
3534
|
+
var version = "2.6.0";
|
|
3535
3535
|
var API = class _API {
|
|
3536
3536
|
constructor(_options) {
|
|
3537
3537
|
this._options = _options;
|
|
@@ -5458,7 +5458,7 @@ var PollingTransport = class _PollingTransport extends SignalingTransport {
|
|
|
5458
5458
|
};
|
|
5459
5459
|
|
|
5460
5460
|
// src/socket.ts
|
|
5461
|
-
var version2 = "2.
|
|
5461
|
+
var version2 = "2.6.0";
|
|
5462
5462
|
var Socket = class _Socket extends SignalingTransport {
|
|
5463
5463
|
constructor(secure, host, port, path, key, pingInterval = 5e3, jwt, apiKey) {
|
|
5464
5464
|
super();
|
package/dist/store.js
CHANGED
package/dist/svelte.cjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/adapters/svelte.ts
|
|
4
|
+
function toSvelteStore(store) {
|
|
5
|
+
return {
|
|
6
|
+
subscribe(run) {
|
|
7
|
+
run(store.getSnapshot());
|
|
8
|
+
return store.subscribe(() => run(store.getSnapshot()));
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
exports.toSvelteStore = toSvelteStore;
|
|
14
|
+
//# sourceMappingURL=svelte.cjs.map
|
|
15
|
+
//# sourceMappingURL=svelte.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/svelte.ts"],"names":[],"mappings":";;;AA0BO,SAAS,cAAc,KAAA,EAAyD;AACtF,EAAA,OAAO;AAAA,IACN,UAAU,GAAA,EAAK;AACd,MAAA,GAAA,CAAI,KAAA,CAAM,aAAa,CAAA;AACvB,MAAA,OAAO,MAAM,SAAA,CAAU,MAAM,IAAI,KAAA,CAAM,WAAA,EAAa,CAAC,CAAA;AAAA,IACtD;AAAA,GACD;AACD","file":"svelte.cjs","sourcesContent":["import type { DendriStore, DendriStoreSnapshot } from \"../store\";\n\n/** Svelte store contract (readable). Works with `$store` auto-subscription in Svelte 4 and 5. */\nexport interface SvelteReadable<T> {\n\tsubscribe(run: (value: T) => void): () => void;\n}\n\n/**\n * Wrap a Dendri store in Svelte's store contract.\n *\n * ```svelte\n * <script>\n * import { createDendriStore } from \"@afterrealism/dendri-client\";\n * import { toSvelteStore } from \"@afterrealism/dendri-client/svelte\";\n *\n * const store = createDendriStore({ url: \"wss://signal.example.com\" });\n * const snapshot = toSvelteStore(store);\n * </script>\n *\n * {$snapshot.connectionState} — {$snapshot.peers.length} peers\n * ```\n *\n * Plain store contract — no Svelte compiler or dependency involved, so it\n * also works from `.js`/`.ts` modules and with Svelte 5 runes code via\n * `fromStore()` if preferred.\n */\nexport function toSvelteStore(store: DendriStore): SvelteReadable<DendriStoreSnapshot> {\n\treturn {\n\t\tsubscribe(run) {\n\t\t\trun(store.getSnapshot());\n\t\t\treturn store.subscribe(() => run(store.getSnapshot()));\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { n as DendriStore, p as DendriStoreSnapshot } from './store-C3Nwl62R.cjs';
|
|
2
|
+
import 'eventemitter3';
|
|
3
|
+
|
|
4
|
+
/** Svelte store contract (readable). Works with `$store` auto-subscription in Svelte 4 and 5. */
|
|
5
|
+
interface SvelteReadable<T> {
|
|
6
|
+
subscribe(run: (value: T) => void): () => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Wrap a Dendri store in Svelte's store contract.
|
|
10
|
+
*
|
|
11
|
+
* ```svelte
|
|
12
|
+
* <script>
|
|
13
|
+
* import { createDendriStore } from "@afterrealism/dendri-client";
|
|
14
|
+
* import { toSvelteStore } from "@afterrealism/dendri-client/svelte";
|
|
15
|
+
*
|
|
16
|
+
* const store = createDendriStore({ url: "wss://signal.example.com" });
|
|
17
|
+
* const snapshot = toSvelteStore(store);
|
|
18
|
+
* </script>
|
|
19
|
+
*
|
|
20
|
+
* {$snapshot.connectionState} — {$snapshot.peers.length} peers
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Plain store contract — no Svelte compiler or dependency involved, so it
|
|
24
|
+
* also works from `.js`/`.ts` modules and with Svelte 5 runes code via
|
|
25
|
+
* `fromStore()` if preferred.
|
|
26
|
+
*/
|
|
27
|
+
declare function toSvelteStore(store: DendriStore): SvelteReadable<DendriStoreSnapshot>;
|
|
28
|
+
|
|
29
|
+
export { type SvelteReadable, toSvelteStore };
|
package/dist/svelte.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { n as DendriStore, p as DendriStoreSnapshot } from './store-C3Nwl62R.js';
|
|
2
|
+
import 'eventemitter3';
|
|
3
|
+
|
|
4
|
+
/** Svelte store contract (readable). Works with `$store` auto-subscription in Svelte 4 and 5. */
|
|
5
|
+
interface SvelteReadable<T> {
|
|
6
|
+
subscribe(run: (value: T) => void): () => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Wrap a Dendri store in Svelte's store contract.
|
|
10
|
+
*
|
|
11
|
+
* ```svelte
|
|
12
|
+
* <script>
|
|
13
|
+
* import { createDendriStore } from "@afterrealism/dendri-client";
|
|
14
|
+
* import { toSvelteStore } from "@afterrealism/dendri-client/svelte";
|
|
15
|
+
*
|
|
16
|
+
* const store = createDendriStore({ url: "wss://signal.example.com" });
|
|
17
|
+
* const snapshot = toSvelteStore(store);
|
|
18
|
+
* </script>
|
|
19
|
+
*
|
|
20
|
+
* {$snapshot.connectionState} — {$snapshot.peers.length} peers
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Plain store contract — no Svelte compiler or dependency involved, so it
|
|
24
|
+
* also works from `.js`/`.ts` modules and with Svelte 5 runes code via
|
|
25
|
+
* `fromStore()` if preferred.
|
|
26
|
+
*/
|
|
27
|
+
declare function toSvelteStore(store: DendriStore): SvelteReadable<DendriStoreSnapshot>;
|
|
28
|
+
|
|
29
|
+
export { type SvelteReadable, toSvelteStore };
|
package/dist/svelte.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import './chunk-G3PMV62Z.js';
|
|
2
|
+
|
|
3
|
+
// src/adapters/svelte.ts
|
|
4
|
+
function toSvelteStore(store) {
|
|
5
|
+
return {
|
|
6
|
+
subscribe(run) {
|
|
7
|
+
run(store.getSnapshot());
|
|
8
|
+
return store.subscribe(() => run(store.getSnapshot()));
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { toSvelteStore };
|
|
14
|
+
//# sourceMappingURL=svelte.js.map
|
|
15
|
+
//# sourceMappingURL=svelte.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/svelte.ts"],"names":[],"mappings":";;;AA0BO,SAAS,cAAc,KAAA,EAAyD;AACtF,EAAA,OAAO;AAAA,IACN,UAAU,GAAA,EAAK;AACd,MAAA,GAAA,CAAI,KAAA,CAAM,aAAa,CAAA;AACvB,MAAA,OAAO,MAAM,SAAA,CAAU,MAAM,IAAI,KAAA,CAAM,WAAA,EAAa,CAAC,CAAA;AAAA,IACtD;AAAA,GACD;AACD","file":"svelte.js","sourcesContent":["import type { DendriStore, DendriStoreSnapshot } from \"../store\";\n\n/** Svelte store contract (readable). Works with `$store` auto-subscription in Svelte 4 and 5. */\nexport interface SvelteReadable<T> {\n\tsubscribe(run: (value: T) => void): () => void;\n}\n\n/**\n * Wrap a Dendri store in Svelte's store contract.\n *\n * ```svelte\n * <script>\n * import { createDendriStore } from \"@afterrealism/dendri-client\";\n * import { toSvelteStore } from \"@afterrealism/dendri-client/svelte\";\n *\n * const store = createDendriStore({ url: \"wss://signal.example.com\" });\n * const snapshot = toSvelteStore(store);\n * </script>\n *\n * {$snapshot.connectionState} — {$snapshot.peers.length} peers\n * ```\n *\n * Plain store contract — no Svelte compiler or dependency involved, so it\n * also works from `.js`/`.ts` modules and with Svelte 5 runes code via\n * `fromStore()` if preferred.\n */\nexport function toSvelteStore(store: DendriStore): SvelteReadable<DendriStoreSnapshot> {\n\treturn {\n\t\tsubscribe(run) {\n\t\t\trun(store.getSnapshot());\n\t\t\treturn store.subscribe(() => run(store.getSnapshot()));\n\t\t},\n\t};\n}\n"]}
|
package/dist/vue.cjs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var vue = require('vue');
|
|
4
|
+
|
|
5
|
+
// src/adapters/vue.ts
|
|
6
|
+
function useDendriStore(store) {
|
|
7
|
+
const snapshot = vue.shallowRef(store.getSnapshot());
|
|
8
|
+
const unsubscribe = store.subscribe(() => {
|
|
9
|
+
snapshot.value = store.getSnapshot();
|
|
10
|
+
});
|
|
11
|
+
if (vue.getCurrentScope()) {
|
|
12
|
+
vue.onScopeDispose(unsubscribe);
|
|
13
|
+
}
|
|
14
|
+
return snapshot;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
exports.useDendriStore = useDendriStore;
|
|
18
|
+
//# sourceMappingURL=vue.cjs.map
|
|
19
|
+
//# sourceMappingURL=vue.cjs.map
|
package/dist/vue.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/vue.ts"],"names":["shallowRef","getCurrentScope","onScopeDispose"],"mappings":";;;;;AAqBO,SAAS,eAAe,KAAA,EAA+D;AAC7F,EAAA,MAAM,QAAA,GAAWA,cAAA,CAAW,KAAA,CAAM,WAAA,EAAa,CAAA;AAC/C,EAAA,MAAM,WAAA,GAAc,KAAA,CAAM,SAAA,CAAU,MAAM;AACzC,IAAA,QAAA,CAAS,KAAA,GAAQ,MAAM,WAAA,EAAY;AAAA,EACpC,CAAC,CAAA;AACD,EAAA,IAAIC,qBAAgB,EAAG;AACtB,IAAAC,kBAAA,CAAe,WAAW,CAAA;AAAA,EAC3B;AACA,EAAA,OAAO,QAAA;AACR","file":"vue.cjs","sourcesContent":["import { getCurrentScope, onScopeDispose, type ShallowRef, shallowRef } from \"vue\";\nimport type { DendriStore, DendriStoreSnapshot } from \"../store\";\n\n/**\n * Subscribe a Vue setup scope to a Dendri store.\n *\n * ```vue\n * <script setup>\n * import { createDendriStore } from \"@afterrealism/dendri-client\";\n * import { useDendriStore } from \"@afterrealism/dendri-client/vue\";\n *\n * const store = createDendriStore({ url: \"wss://signal.example.com\" });\n * const snapshot = useDendriStore(store);\n * </script>\n *\n * <template>{{ snapshot.connectionState }}</template>\n * ```\n *\n * The ref updates on every store change and unsubscribes automatically when\n * the component (effect scope) is torn down.\n */\nexport function useDendriStore(store: DendriStore): Readonly<ShallowRef<DendriStoreSnapshot>> {\n\tconst snapshot = shallowRef(store.getSnapshot());\n\tconst unsubscribe = store.subscribe(() => {\n\t\tsnapshot.value = store.getSnapshot();\n\t});\n\tif (getCurrentScope()) {\n\t\tonScopeDispose(unsubscribe);\n\t}\n\treturn snapshot;\n}\n"]}
|
package/dist/vue.d.cts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ShallowRef } from 'vue';
|
|
2
|
+
import { n as DendriStore, p as DendriStoreSnapshot } from './store-C3Nwl62R.cjs';
|
|
3
|
+
import 'eventemitter3';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Subscribe a Vue setup scope to a Dendri store.
|
|
7
|
+
*
|
|
8
|
+
* ```vue
|
|
9
|
+
* <script setup>
|
|
10
|
+
* import { createDendriStore } from "@afterrealism/dendri-client";
|
|
11
|
+
* import { useDendriStore } from "@afterrealism/dendri-client/vue";
|
|
12
|
+
*
|
|
13
|
+
* const store = createDendriStore({ url: "wss://signal.example.com" });
|
|
14
|
+
* const snapshot = useDendriStore(store);
|
|
15
|
+
* </script>
|
|
16
|
+
*
|
|
17
|
+
* <template>{{ snapshot.connectionState }}</template>
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* The ref updates on every store change and unsubscribes automatically when
|
|
21
|
+
* the component (effect scope) is torn down.
|
|
22
|
+
*/
|
|
23
|
+
declare function useDendriStore(store: DendriStore): Readonly<ShallowRef<DendriStoreSnapshot>>;
|
|
24
|
+
|
|
25
|
+
export { useDendriStore };
|
package/dist/vue.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ShallowRef } from 'vue';
|
|
2
|
+
import { n as DendriStore, p as DendriStoreSnapshot } from './store-C3Nwl62R.js';
|
|
3
|
+
import 'eventemitter3';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Subscribe a Vue setup scope to a Dendri store.
|
|
7
|
+
*
|
|
8
|
+
* ```vue
|
|
9
|
+
* <script setup>
|
|
10
|
+
* import { createDendriStore } from "@afterrealism/dendri-client";
|
|
11
|
+
* import { useDendriStore } from "@afterrealism/dendri-client/vue";
|
|
12
|
+
*
|
|
13
|
+
* const store = createDendriStore({ url: "wss://signal.example.com" });
|
|
14
|
+
* const snapshot = useDendriStore(store);
|
|
15
|
+
* </script>
|
|
16
|
+
*
|
|
17
|
+
* <template>{{ snapshot.connectionState }}</template>
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* The ref updates on every store change and unsubscribes automatically when
|
|
21
|
+
* the component (effect scope) is torn down.
|
|
22
|
+
*/
|
|
23
|
+
declare function useDendriStore(store: DendriStore): Readonly<ShallowRef<DendriStoreSnapshot>>;
|
|
24
|
+
|
|
25
|
+
export { useDendriStore };
|
package/dist/vue.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import './chunk-G3PMV62Z.js';
|
|
2
|
+
import { shallowRef, getCurrentScope, onScopeDispose } from 'vue';
|
|
3
|
+
|
|
4
|
+
function useDendriStore(store) {
|
|
5
|
+
const snapshot = shallowRef(store.getSnapshot());
|
|
6
|
+
const unsubscribe = store.subscribe(() => {
|
|
7
|
+
snapshot.value = store.getSnapshot();
|
|
8
|
+
});
|
|
9
|
+
if (getCurrentScope()) {
|
|
10
|
+
onScopeDispose(unsubscribe);
|
|
11
|
+
}
|
|
12
|
+
return snapshot;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export { useDendriStore };
|
|
16
|
+
//# sourceMappingURL=vue.js.map
|
|
17
|
+
//# sourceMappingURL=vue.js.map
|
package/dist/vue.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/vue.ts"],"names":[],"mappings":";;;AAqBO,SAAS,eAAe,KAAA,EAA+D;AAC7F,EAAA,MAAM,QAAA,GAAW,UAAA,CAAW,KAAA,CAAM,WAAA,EAAa,CAAA;AAC/C,EAAA,MAAM,WAAA,GAAc,KAAA,CAAM,SAAA,CAAU,MAAM;AACzC,IAAA,QAAA,CAAS,KAAA,GAAQ,MAAM,WAAA,EAAY;AAAA,EACpC,CAAC,CAAA;AACD,EAAA,IAAI,iBAAgB,EAAG;AACtB,IAAA,cAAA,CAAe,WAAW,CAAA;AAAA,EAC3B;AACA,EAAA,OAAO,QAAA;AACR","file":"vue.js","sourcesContent":["import { getCurrentScope, onScopeDispose, type ShallowRef, shallowRef } from \"vue\";\nimport type { DendriStore, DendriStoreSnapshot } from \"../store\";\n\n/**\n * Subscribe a Vue setup scope to a Dendri store.\n *\n * ```vue\n * <script setup>\n * import { createDendriStore } from \"@afterrealism/dendri-client\";\n * import { useDendriStore } from \"@afterrealism/dendri-client/vue\";\n *\n * const store = createDendriStore({ url: \"wss://signal.example.com\" });\n * const snapshot = useDendriStore(store);\n * </script>\n *\n * <template>{{ snapshot.connectionState }}</template>\n * ```\n *\n * The ref updates on every store change and unsubscribes automatically when\n * the component (effect scope) is torn down.\n */\nexport function useDendriStore(store: DendriStore): Readonly<ShallowRef<DendriStoreSnapshot>> {\n\tconst snapshot = shallowRef(store.getSnapshot());\n\tconst unsubscribe = store.subscribe(() => {\n\t\tsnapshot.value = store.getSnapshot();\n\t});\n\tif (getCurrentScope()) {\n\t\tonScopeDispose(unsubscribe);\n\t}\n\treturn snapshot;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@afterrealism/dendri-client",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"packageManager": "pnpm@10.30.1",
|
|
6
6
|
"description": "Dendri client WebRTC P2P signaling library",
|
|
@@ -54,6 +54,36 @@
|
|
|
54
54
|
"types": "./dist/serializer.msgpack.d.cts",
|
|
55
55
|
"default": "./dist/serializer.msgpack.cjs"
|
|
56
56
|
}
|
|
57
|
+
},
|
|
58
|
+
"./react": {
|
|
59
|
+
"import": {
|
|
60
|
+
"types": "./dist/react.d.ts",
|
|
61
|
+
"default": "./dist/react.js"
|
|
62
|
+
},
|
|
63
|
+
"require": {
|
|
64
|
+
"types": "./dist/react.d.cts",
|
|
65
|
+
"default": "./dist/react.cjs"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"./vue": {
|
|
69
|
+
"import": {
|
|
70
|
+
"types": "./dist/vue.d.ts",
|
|
71
|
+
"default": "./dist/vue.js"
|
|
72
|
+
},
|
|
73
|
+
"require": {
|
|
74
|
+
"types": "./dist/vue.d.cts",
|
|
75
|
+
"default": "./dist/vue.cjs"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"./svelte": {
|
|
79
|
+
"import": {
|
|
80
|
+
"types": "./dist/svelte.d.ts",
|
|
81
|
+
"default": "./dist/svelte.js"
|
|
82
|
+
},
|
|
83
|
+
"require": {
|
|
84
|
+
"types": "./dist/svelte.d.cts",
|
|
85
|
+
"default": "./dist/svelte.cjs"
|
|
86
|
+
}
|
|
57
87
|
}
|
|
58
88
|
},
|
|
59
89
|
"files": [
|
|
@@ -84,6 +114,18 @@
|
|
|
84
114
|
"prepublishOnly": "npm run build",
|
|
85
115
|
"clean": "rm -rf dist"
|
|
86
116
|
},
|
|
117
|
+
"peerDependencies": {
|
|
118
|
+
"react": ">=18",
|
|
119
|
+
"vue": ">=3.3"
|
|
120
|
+
},
|
|
121
|
+
"peerDependenciesMeta": {
|
|
122
|
+
"react": {
|
|
123
|
+
"optional": true
|
|
124
|
+
},
|
|
125
|
+
"vue": {
|
|
126
|
+
"optional": true
|
|
127
|
+
}
|
|
128
|
+
},
|
|
87
129
|
"dependencies": {
|
|
88
130
|
"@msgpack/msgpack": "^2.8.0",
|
|
89
131
|
"eventemitter3": "^4.0.7",
|
|
@@ -94,13 +136,17 @@
|
|
|
94
136
|
"devDependencies": {
|
|
95
137
|
"@arethetypeswrong/cli": "^0.17.0",
|
|
96
138
|
"@biomejs/biome": "^2.0.0",
|
|
139
|
+
"@types/react": "^19.2.17",
|
|
97
140
|
"@vitest/coverage-v8": "^3.0.0",
|
|
98
141
|
"fast-check": "^4.6.0",
|
|
99
142
|
"jsdom": "^25.0.1",
|
|
100
143
|
"mock-socket": "^9.0.0",
|
|
101
144
|
"publint": "^0.3.0",
|
|
145
|
+
"react": "^19.2.7",
|
|
146
|
+
"react-dom": "^19.2.7",
|
|
102
147
|
"tsup": "^8.0.0",
|
|
103
148
|
"typescript": "^5.7.0",
|
|
104
|
-
"vitest": "^3.0.0"
|
|
149
|
+
"vitest": "^3.0.0",
|
|
150
|
+
"vue": "^3.5.39"
|
|
105
151
|
}
|
|
106
152
|
}
|