@utsp/network-server 0.17.4 → 0.18.0-nightly.20260204225152.3333dbc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,3 +1,3 @@
1
- "use strict";var d=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var $=Object.prototype.hasOwnProperty;var N=(c,t,e)=>t in c?d(c,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):c[t]=e;var f=(c,t)=>d(c,"name",{value:t,configurable:!0});var D=(c,t)=>{for(var e in t)d(c,e,{get:t[e],enumerable:!0})},I=(c,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of k(t))!$.call(c,i)&&i!==e&&d(c,i,{get:()=>t[i],enumerable:!(n=R(t,i))||n.enumerable});return c};var O=c=>I(d({},"__esModule",{value:!0}),c);var l=(c,t,e)=>(N(c,typeof t!="symbol"?t+"":t,e),e);var B={};D(B,{SocketIOServer:()=>u,WebRTCServer:()=>v});module.exports=O(B);var C=require("socket.io"),b=require("http");var y=class y{constructor(t){l(this,"io",null);l(this,"httpServer",null);l(this,"options");l(this,"running",!1);l(this,"clients",new Map);l(this,"clientData",new Map);l(this,"connectionHandlers",[]);l(this,"disconnectionHandlers",[]);l(this,"eventHandlers",new Map);l(this,"stats",{totalConnections:0,startTime:0});l(this,"bridgeHandlers",new Map);if(!Number.isInteger(t.port)||t.port<0||t.port>65535)throw new Error(`SocketIOServer: Invalid port ${t.port} - must be an integer between 0 and 65535`);if(t.maxConnections!==void 0&&(!Number.isInteger(t.maxConnections)||t.maxConnections<=0))throw new Error(`SocketIOServer: Invalid maxConnections ${t.maxConnections} - must be a positive integer`);if(t.pingInterval!==void 0&&(!Number.isFinite(t.pingInterval)||t.pingInterval<=0))throw new Error(`SocketIOServer: Invalid pingInterval ${t.pingInterval} - must be a positive number`);if(t.pingTimeout!==void 0&&(!Number.isFinite(t.pingTimeout)||t.pingTimeout<=0))throw new Error(`SocketIOServer: Invalid pingTimeout ${t.pingTimeout} - must be a positive number`);this.options={port:t.port,host:t.host??"0.0.0.0",cors:t.cors??{origin:"*"},maxConnections:t.maxConnections??1e3,pingInterval:t.pingInterval??25e3,pingTimeout:t.pingTimeout??5e3,debug:t.debug??!1},this.log("Server initialized",{port:this.options.port,host:this.options.host,maxConnections:this.options.maxConnections})}isRunning(){return this.running}getHttpServer(){return this.httpServer}async start(){if(this.running){this.log("Server already running");return}return this.log(`Starting server on ${this.options.host}:${this.options.port}...`),new Promise((t,e)=>{try{this.httpServer=(0,b.createServer)(),this.io=new C.Server(this.httpServer,{cors:this.options.cors,pingInterval:this.options.pingInterval,pingTimeout:this.options.pingTimeout,maxHttpBufferSize:1e6}),this.io.on("connection",n=>{this.handleConnection(n)}),this.httpServer.listen(this.options.port,this.options.host,()=>{this.running=!0,this.stats.startTime=Date.now(),this.log(`Server started on ${this.options.host}:${this.options.port}`),t()}),this.httpServer.on("error",n=>{this.log(`Server error: ${n.message}`),e(n)})}catch(n){e(n)}})}async stop(){if(this.running)return this.log("Stopping server..."),new Promise(t=>{this.clients.forEach(e=>{e.disconnect(!0)}),this.clients.clear(),this.clientData.clear(),this.io&&(this.io.close(()=>{this.log("Socket.IO server closed")}),this.io=null),this.httpServer?(this.httpServer.close(()=>{this.log("HTTP server closed"),this.running=!1,t()}),this.httpServer=null):(this.running=!1,t())})}getSignalingInfo(){return{mode:"socketio",transport:"websocket",host:this.options.host,port:this.options.port}}getClients(){return Array.from(this.clients.keys())}getClientInfo(t){let e=this.clients.get(t);return e?{id:t,connectedAt:e.connectedAt||Date.now(),address:e.handshake.address,data:this.clientData.get(t)||{}}:null}sendToClient(t,e,n){let i=this.clients.get(t);if(!i){this.log(`Cannot send to client ${t}: not found`);return}i.emit(e,n)}sendToClientVolatile(t,e,n){let i=this.clients.get(t);if(!i){this.log(`Cannot send volatile to client ${t}: not found`);return}i.compress(!1).emit(e,n)}broadcast(t,e){this.io&&(this.io.emit(t,e),this.log(`Broadcast '${t}' to all clients`))}broadcastVolatile(t,e){this.io&&(this.io.volatile.emit(t,e),this.log(`Broadcast volatile '${t}' to all clients`))}broadcastExcept(t,e,n){let i=this.clients.get(t);i&&(i.broadcast.emit(e,n),this.log(`Broadcast '${e}' except ${t}`))}sendToRoom(t,e,n){this.io&&(this.io.to(t).emit(e,n),this.log(`Sent '${e}' to room '${t}'`))}joinRoom(t,e){let n=this.clients.get(t);n&&(n.join(e),this.log(`Client ${t} joined room '${e}'`))}leaveRoom(t,e){let n=this.clients.get(t);n&&(n.leave(e),this.log(`Client ${t} left room '${e}'`))}getRoomClients(t){if(!this.io)return[];let e=this.io.sockets.adapter.rooms.get(t);return e?Array.from(e):[]}disconnectClient(t,e="Server disconnect"){let n=this.clients.get(t);n&&(n.disconnect(!0),this.log(`Disconnected client ${t}: ${e}`))}on(t,e){let n=this.eventHandlers.get(t)||[];n.push(e),this.eventHandlers.set(t,n),this.log(`Registered handler for '${t}'`)}onConnect(t){this.connectionHandlers.push(t),this.log("Registered connection handler")}onDisconnect(t){this.disconnectionHandlers.push(t),this.log("Registered disconnection handler")}off(t,e){let n=this.eventHandlers.get(t);if(!n)return;let i=n.indexOf(e);i!==-1&&(n.splice(i,1),this.log(`Removed handler for '${t}'`))}setClientData(t,e,n){let i=this.clientData.get(t)||{};i[e]=n,this.clientData.set(t,i)}getClientData(t,e){let n=this.clientData.get(t);return n?n[e]:void 0}getStats(){return{connectedClients:this.clients.size,totalConnections:this.stats.totalConnections,uptime:Date.now()-this.stats.startTime}}sendBridge(t,e,n){let i=this.clients.get(t);if(!i){this.log(`Cannot send bridge to client ${t}: not found`);return}try{let r={channel:e,data:JSON.stringify(n)};i.emit("bridge",r),this.log(`Bridge sent to ${t} on channel '${e}'`)}catch(r){this.log(`Error sending bridge to ${t}: ${r}`)}}broadcastBridge(t,e){if(this.io)try{let n={channel:t,data:JSON.stringify(e)};this.io.emit("bridge",n),this.log(`Bridge broadcast on channel '${t}'`)}catch(n){this.log(`Error broadcasting bridge: ${n}`)}}onBridge(t,e){this.bridgeHandlers.has(t)||this.bridgeHandlers.set(t,[]),this.bridgeHandlers.get(t).push(e),this.log(`Bridge handler registered for channel '${t}'`)}offBridge(t,e){let n=this.bridgeHandlers.get(t);if(n){let i=n.indexOf(e);i!==-1&&(n.splice(i,1),this.log(`Bridge handler removed for channel '${t}'`))}}handleBridgeMessage(t,e){try{let{channel:n,data:i}=e,r=JSON.parse(i),s=this.bridgeHandlers.get(n);s&&s.length>0&&s.forEach(a=>{try{a(t,r)}catch(h){this.log(`Error in bridge handler for '${n}': ${h}`)}});let o=this.bridgeHandlers.get("*");o&&o.length>0&&o.forEach(a=>{try{a(t,{channel:n,data:r})}catch(h){this.log(`Error in wildcard bridge handler: ${h}`)}}),(!s||s.length===0)&&(!o||o.length===0)&&this.log(`No bridge handler for channel '${n}' from ${t}`)}catch(n){this.log(`Error parsing bridge message from ${t}: ${n}`)}}async destroy(){await this.stop(),this.connectionHandlers=[],this.disconnectionHandlers=[],this.eventHandlers.clear(),this.log("Server destroyed")}handleConnection(t){let e=t.id;if(this.log(`Client connected: ${e}`),this.clients.size>=this.options.maxConnections){this.log(`Max connections reached, rejecting ${e}`),t.emit("error",{code:"MAX_CONNECTIONS",message:"Server is full"}),t.disconnect(!0);return}this.clients.set(e,t),this.clientData.set(e,{}),t.connectedAt=Date.now(),this.stats.totalConnections++,this.setupClientHandlers(t),this.connectionHandlers.forEach(n=>{try{n(e)}catch(i){this.log(`Error in connection handler: ${i}`)}}),t.on("ping",()=>{t.emit("pong")})}setupClientHandlers(t){let e=t.id;t.on("disconnect",n=>{this.log(`Client disconnected: ${e} (${n})`),this.clients.delete(e),this.clientData.delete(e),this.disconnectionHandlers.forEach(i=>{try{i(e,n)}catch(r){this.log(`Error in disconnection handler: ${r}`)}})}),t.on("bridge",n=>{this.handleBridgeMessage(e,n)}),this.eventHandlers.forEach((n,i)=>{t.on(i,r=>{n.forEach(s=>{try{s(e,r)}catch(o){this.log(`Error in event handler for '${i}': ${o}`)}})})})}log(t,e){this.options.debug&&(e!==void 0?console.warn(`[SocketIOServer] ${t}`,e):console.warn(`[SocketIOServer] ${t}`))}};f(y,"SocketIOServer");var u=y;var T=require("socket.io"),w=require("socket.io-client"),S=require("werift"),H=require("http");var P={connecting:5e3,channels_opening:5e3,ready:5e3,joined:0,aborted:0},m=class m{constructor(t){l(this,"signalingServer",null);l(this,"httpServer",null);l(this,"relaySocket",null);l(this,"clients",new Map);l(this,"options");l(this,"running",!1);l(this,"certificates",[]);l(this,"connectionHandlers",[]);l(this,"disconnectionHandlers",[]);l(this,"eventHandlers",new Map);l(this,"bridgeHandlers",new Map);l(this,"rooms",new Map);this.options={port:t.port,host:t.host??"0.0.0.0",cors:t.cors??{origin:"*"},maxConnections:t.maxConnections??1e3,pingInterval:t.pingInterval??25e3,pingTimeout:t.pingTimeout??5e3,debug:t.debug??!1,iceServers:t.iceServers??[{urls:"stun:stun.l.google.com:19302"}],signalingPath:t.signalingPath??(t.signalUrl?"/socket.io":"/utsp-signal"),signalUrl:t.signalUrl,sessionId:t.sessionId},this.on("bridge",(e,n)=>{if(!n||!n.channel)return;let{channel:i,data:r}=n,s=this.bridgeHandlers.get(i);s&&s.forEach(a=>a(e,r));let o=this.bridgeHandlers.get("*");o&&o.forEach(a=>a(e,{channel:i,data:r}))})}isRunning(){return this.running}getHttpServer(){return this.httpServer}async start(){if(!this.running){if(this.log("Starting WebRTC Server..."),this.certificates.length===0){this.log("Warm-up: Generating WebRTC Certificates...");try{let t=await S.RTCDtlsTransport.SetupCertificate();t&&(this.certificates.push(t),this.log(`Warm-up: Certificate generated. Fingerprints: ${JSON.stringify(t.getFingerprints())}`),this.log("Warm-up: Done."))}catch{this.log("Warm-up: Static generateCertificate failed, falling back to on-demand generation.")}}if(this.options.signalUrl){this.log(`Connecting to PUBLIC RELAY: ${this.options.signalUrl} (Session: ${this.options.sessionId})`),await this.startRelayMode(),this.running=!0;return}return this.httpServer=(0,H.createServer)(),this.signalingServer=new T.Server(this.httpServer,{path:this.options.signalingPath,cors:this.options.cors,pingInterval:this.options.pingInterval,pingTimeout:this.options.pingTimeout,transports:["websocket","polling"]}),this.setupSignaling(),new Promise((t,e)=>{this.httpServer?.listen(this.options.port,this.options.host,()=>{this.running=!0,this.log(`WebRTC Server listening on ${this.options.host}:${this.options.port} (Signaling path: ${this.options.signalingPath})`),t()}),this.httpServer?.on("error",n=>{this.error("Failed to start server",n),e(n)})})}}getSignalingInfo(){return this.options.signalUrl?{mode:"webrtc",transport:"webrtc",signalUrl:this.options.signalUrl,sessionId:this.options.sessionId}:{mode:"webrtc",transport:"webrtc",host:this.options.host,port:this.options.port,path:this.options.signalingPath}}async startRelayMode(){return new Promise((t,e)=>{if(!this.options.signalUrl)return e("No relay URL");this.relaySocket=(0,w.io)(this.options.signalUrl,{path:this.options.signalingPath,transports:["websocket","polling"]}),this.relaySocket.on("connect",()=>{this.log(`Connected to Relay Server: ${this.options.signalUrl}`),this.relaySocket?.emit("register-host",this.options.sessionId)}),this.relaySocket.on("host-registered",n=>{n&&n.sessionId?(this.options.sessionId=n.sessionId,this.log(`\u2705 Host Registered! Share this Session ID with clients: ${this.options.sessionId}`),t()):(this.log("Host registered (No ID returned from relay)"),t())}),this.relaySocket.on("error",n=>{this.error("Relay connection error",n),this.running||e(n)}),this.relaySocket.on("client-ready",({clientId:n})=>{this.handleRelayClient(n)}),this.relaySocket.on("signal",({from:n,type:i,data:r})=>{let s=i;i==="offer"&&(s="rtc:offer"),i==="answer"&&(s="rtc:answer"),i==="candidate"&&(s="rtc:candidate"),this.log(`Received signal from relay (type: ${i}, from: ${n})`),this.handleRelaySignal(n,s,r)})})}handleRelayClient(t){this.log(`New Client via Relay: ${t}`);let e={id:t,connected:!0,emit:(n,i)=>{this.relaySocket&&this.relaySocket.emit("signal",{target:t,type:n,data:i})},disconnect:()=>{},handshake:{address:"relay"}};this.setupClient(t,e)}handleRelaySignal(t,e,n){let i=this.clients.get(t);i&&i.socket.trigger(e,n)}setupClient(t,e){let n=Date.now();if(!e.on){let o=new Map;e.on=(a,h)=>{o.has(a)||o.set(a,[]),o.get(a).push(h)},e.trigger=(a,h)=>{let p=o.get(a);p&&p.forEach(g=>g(h))}}let i=Date.now(),r=new S.RTCPeerConnection({iceServers:this.options.iceServers,dtls:this.certificates[0]?{keys:{certPem:this.certificates[0].certPem,keyPem:this.certificates[0].privateKey,signatureHash:this.certificates[0].signatureHash}}:void 0});this.log(`\u23F1\uFE0F [${t}] RTCPeerConnection created in ${Date.now()-i}ms`),this.certificates[0]?this.log(`Using cached certificate for client ${t}`):this.warn(`No cached certificate available for client ${t}, generating new one (LAG WARNING)`);let s={id:t,socket:e,peer:r,connectedAt:Date.now(),data:{},state:"connecting",pendingEvents:[]};this.clients.set(t,s),this.setStateTimeout(t,"connecting"),this.setupWebRTC(s),this.log(`\u23F1\uFE0F [${t}] setupClient total: ${Date.now()-n}ms`)}setupSignaling(){this.signalingServer&&this.signalingServer.on("connection",async t=>{let e=t.id;this.log(`Signaling connection: ${e}`),this.setupClient(e,t)})}async setupWebRTC(t){let{id:e,socket:n,peer:i}=t,r=i.createDataChannel("reliable",{ordered:!0});t.dataChannelReliable=r,this.setupChannel(e,r,!0);let s=i.createDataChannel("unreliable",{ordered:!1,maxRetransmits:0});t.dataChannelUnreliable=s,this.setupChannel(e,s,!1),i.onicecandidate=o=>{o.candidate&&n.emit("rtc:candidate",o.candidate)},n.on("rtc:answer",async o=>{if(!(!o||typeof o!="object"||!o.type||!o.sdp))try{let a=this.stripRelayFromSDP(o.sdp);await i.setRemoteDescription({type:"answer",sdp:a})}catch(a){this.error(`Error setting remote description for ${e}`,a)}}),n.on("rtc:candidate",async o=>{if(!(!o||typeof o!="object"||!o.candidate))try{if(this.parseCandidateType(o.candidate)==="relay")return;await i.addIceCandidate(o)}catch(a){this.error(`Error adding ICE candidate for ${e}`,a)}}),n.on("disconnect",o=>{t.state!=="joined"&&this.handleDisconnect(e,o)});try{let o=await i.createOffer();await i.setLocalDescription(o);let a=this.stripRelayFromSDP(o.sdp||"");n.emit("rtc:offer",{type:"offer",sdp:a})}catch{n.disconnect(!0)}}setupChannel(t,e,n){e.onopen=()=>{this.log(`Client ${t}: ${n?"Reliable":"Unreliable"} channel open`),this.checkClientReady(t)},e.onmessage=i=>{let r=i.data,s;Buffer.isBuffer(r)?s=r.toString("utf8"):s=r;try{let o=JSON.parse(s);if(Array.isArray(o)&&o.length>=1){let[a,h]=o;this.handleEvent(t,a,h)}}catch{}}}setStateTimeout(t,e){let n=this.clients.get(t);if(!n)return;n.stateTimeout&&(clearTimeout(n.stateTimeout),n.stateTimeout=void 0);let i=P[e];i!==0&&(n.stateTimeout=setTimeout(()=>{let r=this.clients.get(t);r&&r.state===e&&(this.warn(`Client ${t}: Timeout in state '${e}' - aborting connection`),this.transitionTo(t,"aborted"),this.handleDisconnect(t,`timeout_${e}`))},i))}transitionTo(t,e){let n=this.clients.get(t);if(!n)return;let i=n.state;this.log(`[State] ${t}: ${i} \u2192 ${e}`),n.state=e,this.setStateTimeout(t,e)}checkClientReady(t){let e=this.clients.get(t);if(!e)return;let n=e.dataChannelReliable?.readyState==="open",i=e.dataChannelUnreliable?.readyState==="open";if(e.state==="connecting"&&(n||i)&&this.transitionTo(t,"channels_opening"),e.state==="channels_opening"&&n&&i){this.transitionTo(t,"ready");for(let{event:r,data:s}of e.pendingEvents)this.processEvent(t,r,s);e.pendingEvents=[]}}processEvent(t,e,n){let i=this.clients.get(t);e==="join"&&i?.state==="ready"&&(this.transitionTo(t,"joined"),this.connectionHandlers.forEach(s=>s(t)),this.startStatsMonitor(t));let r=this.eventHandlers.get(e);r&&r.forEach(s=>s(t,n))}handleEvent(t,e,n){let i=this.clients.get(t);if(e==="ping"){this.sendSafe(i?.dataChannelReliable,"pong",null,!0);return}if(i&&i.state!=="ready"&&i.state!=="joined"){i.pendingEvents.push({event:e,data:n});return}this.processEvent(t,e,n)}handleDisconnect(t,e){let n=this.clients.get(t);if(n){n.stateTimeout&&clearTimeout(n.stateTimeout),this.stopStatsMonitor(t);try{n.peer.close()}catch{}this.clients.delete(t),this.rooms.forEach(i=>i.delete(t)),n.state==="joined"&&this.disconnectionHandlers.forEach(i=>i(t,e))}}async stop(){this.running=!1;for(let t of this.clients.values())t.peer.close(),t.socket.disconnect(!0);this.clients.clear(),this.signalingServer&&(this.signalingServer.close(),this.signalingServer=null),this.httpServer&&(this.httpServer.close(),this.httpServer=null)}preparePayload(t,e){return JSON.stringify([t,e],(n,i)=>{if(i&&typeof i=="object"){if(i.type==="Buffer"&&Array.isArray(i.data))return{_b64:Buffer.from(i.data).toString("base64")};if(i instanceof Uint8Array||i instanceof Int8Array||i instanceof Uint16Array||i instanceof Int16Array||i instanceof Uint32Array||i instanceof Int32Array||i instanceof Float32Array||i instanceof Float64Array)return{_b64:Buffer.from(i.buffer,i.byteOffset,i.byteLength).toString("base64")}}return i})}getChunks(t,e){let n=Math.ceil(t.length/e),i=new Array(n);for(let r=0,s=0;r<n;++r,s+=e)i[r]=t.substr(s,e);return i}prepareBinaryPacket(t,e){let n=Buffer.isBuffer(e)?e:Buffer.from(e.buffer,e.byteOffset,e.byteLength);if(typeof t=="number"){let s=Buffer.alloc(2);return s.writeUInt8(255,0),s.writeUInt8(t,1),Buffer.concat([s,n])}let i=Buffer.from(t,"utf8");if(i.length>=255)return this.error("Event name too long for binary fast path",t),null;let r=Buffer.alloc(1);return r.writeUInt8(i.length,0),Buffer.concat([r,i,n])}sendSafe(t,e,n,i=!1){if(!t||t.readyState!=="open"||!i&&t.bufferedAmount>16*1024)return;if(Buffer.isBuffer(n)||n instanceof Uint8Array){let s=this.prepareBinaryPacket(e,n);if(s){try{t.send(s)}catch{}return}}try{let s=this.preparePayload(String(e),n),o=1200;if(s.length<=o)t.send(s);else{let a=Math.random().toString(36).substring(2,15),h=this.getChunks(s,o),p=JSON.stringify(["__ch:start",{id:a,count:h.length}]);t.send(p);for(let g=0;g<h.length;g++){let E=JSON.stringify(["__ch:part",{id:a,idx:g,chunk:h[g]}]);t.send(E)}}}catch{}}sendToClient(t,e,n){let i=this.clients.get(t);if(i&&i.dataChannelReliable?.readyState==="open")try{this.sendSafe(i.dataChannelReliable,e,n,!0)}catch(r){this.error(`Failed to send to ${t}`,r)}}sendToClientVolatile(t,e,n){let i=this.clients.get(t),r=i?.dataChannelUnreliable?.readyState==="open"?i.dataChannelUnreliable:i?.dataChannelReliable;this.sendSafe(r,e,n,!1)}broadcast(t,e){for(let n of this.clients.values())n.dataChannelReliable?.readyState==="open"&&this.sendSafe(n.dataChannelReliable,t,e,!0)}broadcastVolatile(t,e){for(let n of this.clients.values()){let i=n.dataChannelUnreliable?.readyState==="open"?n.dataChannelUnreliable:n.dataChannelReliable;this.sendSafe(i,t,e,!1)}}broadcastExcept(t,e,n){for(let[i,r]of this.clients)i!==t&&r.dataChannelReliable?.readyState==="open"&&this.sendSafe(r.dataChannelReliable,e,n,!0)}sendToRoom(t,e,n){let i=this.rooms.get(t);if(i)for(let r of i){let s=this.clients.get(r);s&&s.dataChannelReliable?.readyState==="open"&&this.sendSafe(s.dataChannelReliable,e,n,!0)}}getClients(){return Array.from(this.clients.keys())}getClientInfo(t){let e=this.clients.get(t);return e?{id:e.id,connectedAt:e.connectedAt,address:e.socket.handshake.address,data:e.data}:null}getClientData(t,e){let n=this.clients.get(t);return n?n.data[e]:void 0}setClientData(t,e,n){let i=this.clients.get(t);i&&(i.data[e]=n)}disconnectClient(t,e="Server disconnected client"){let n=this.clients.get(t);n&&(n.socket.disconnect(!0),this.handleDisconnect(t,e))}joinRoom(t,e){this.clients.has(t)&&(this.rooms.has(e)||this.rooms.set(e,new Set),this.rooms.get(e).add(t))}leaveRoom(t,e){let n=this.rooms.get(e);n&&(n.delete(t),n.size===0&&this.rooms.delete(e))}getRoomClients(t){return this.rooms.has(t)?Array.from(this.rooms.get(t)):[]}on(t,e){this.eventHandlers.has(t)||this.eventHandlers.set(t,[]),this.eventHandlers.get(t).push(e)}off(t,e){let n=this.eventHandlers.get(t);if(n){let i=n.indexOf(e);i!==-1&&n.splice(i,1)}}onConnect(t){this.connectionHandlers.push(t)}onDisconnect(t){this.disconnectionHandlers.push(t)}getStats(){return{connectedClients:this.clients.size,totalConnections:0,uptime:process.uptime()}}sendBridge(t,e,n){this.sendToClient(t,"bridge",{channel:e,data:n})}broadcastBridge(t,e){this.broadcast("bridge",{channel:t,data:e})}onBridge(t,e){this.bridgeHandlers.has(t)||this.bridgeHandlers.set(t,[]),this.bridgeHandlers.get(t).push(e)}offBridge(t,e){let n=this.bridgeHandlers.get(t);if(n){let i=n.indexOf(e);i!==-1&&n.splice(i,1)}}async destroy(){return this.stop()}async startStatsMonitor(t){let e=this.clients.get(t);e&&(this.stopStatsMonitor(t),e.statsInterval=setInterval(async()=>{let n=this.clients.get(t);if(!(!n||!n.peer||n.state!=="joined"))try{let i=await n.peer.getStats(),r=null;for(let s of i.values())if(s.type==="candidate-pair"&&s.state==="succeeded"){r=s;break}if(r){let s=i.get(r.localCandidateId),o=i.get(r.remoteCandidateId);this.options.debug&&this.log(`Stats for ${t}: RTT=${r.currentRoundTripTime?Math.round(r.currentRoundTripTime*1e3):"N/A"}ms Path=${s?.candidateType||"?"}<->${o?.candidateType||"?"}`),(s?.candidateType==="relay"||o?.candidateType==="relay")&&(this.error(`STRICT P2P VIOLATION for ${t}: Detected relay path in stats. DISCONNECTING.`),this.handleDisconnect(t,"Strict P2P Violation"))}}catch{}},5e3))}stopStatsMonitor(t){let e=this.clients.get(t);e&&e.statsInterval&&(clearInterval(e.statsInterval),e.statsInterval=void 0)}parseCandidateType(t){return t?t.includes("typ host")?"host":t.includes("typ srflx")?"srflx":t.includes("typ relay")?"relay":"unknown":"unknown"}stripRelayFromSDP(t){return t.split(`
1
+ "use strict";var f=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames;var $=Object.prototype.hasOwnProperty;var N=(h,t,e)=>t in h?f(h,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):h[t]=e;var p=(h,t)=>f(h,"name",{value:t,configurable:!0});var D=(h,t)=>{for(var e in t)f(h,e,{get:t[e],enumerable:!0})},O=(h,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of H(t))!$.call(h,i)&&i!==e&&f(h,i,{get:()=>t[i],enumerable:!(n=E(t,i))||n.enumerable});return h};var B=h=>O(f({},"__esModule",{value:!0}),h);var l=(h,t,e)=>(N(h,typeof t!="symbol"?t+"":t,e),e);var P={};D(P,{SocketIOServer:()=>u,WebRTCServer:()=>v});module.exports=B(P);var b=require("socket.io"),T=require("http");var m=class m{constructor(t){l(this,"io",null);l(this,"httpServer",null);l(this,"options");l(this,"running",!1);l(this,"clients",new Map);l(this,"clientData",new Map);l(this,"connectionHandlers",[]);l(this,"disconnectionHandlers",[]);l(this,"eventHandlers",new Map);l(this,"stats",{totalConnections:0,startTime:0});l(this,"bridgeHandlers",new Map);if(!Number.isInteger(t.port)||t.port<0||t.port>65535)throw new Error(`SocketIOServer: Invalid port ${t.port} - must be an integer between 0 and 65535`);if(t.maxConnections!==void 0&&(!Number.isInteger(t.maxConnections)||t.maxConnections<=0))throw new Error(`SocketIOServer: Invalid maxConnections ${t.maxConnections} - must be a positive integer`);if(t.pingInterval!==void 0&&(!Number.isFinite(t.pingInterval)||t.pingInterval<=0))throw new Error(`SocketIOServer: Invalid pingInterval ${t.pingInterval} - must be a positive number`);if(t.pingTimeout!==void 0&&(!Number.isFinite(t.pingTimeout)||t.pingTimeout<=0))throw new Error(`SocketIOServer: Invalid pingTimeout ${t.pingTimeout} - must be a positive number`);this.options={port:t.port,host:t.host??"0.0.0.0",cors:t.cors??{origin:"*"},maxConnections:t.maxConnections??1e3,pingInterval:t.pingInterval??25e3,pingTimeout:t.pingTimeout??5e3,debug:t.debug??!1},this.log("Server initialized",{port:this.options.port,host:this.options.host,maxConnections:this.options.maxConnections})}isRunning(){return this.running}getHttpServer(){return this.httpServer}async start(){if(this.running){this.log("Server already running");return}return this.log(`Starting server on ${this.options.host}:${this.options.port}...`),new Promise((t,e)=>{try{this.httpServer=(0,T.createServer)(),this.io=new b.Server(this.httpServer,{cors:this.options.cors,pingInterval:this.options.pingInterval,pingTimeout:this.options.pingTimeout,maxHttpBufferSize:1e6}),this.io.on("connection",n=>{this.handleConnection(n)}),this.httpServer.listen(this.options.port,this.options.host,()=>{this.running=!0,this.stats.startTime=Date.now(),this.log(`Server started on ${this.options.host}:${this.options.port}`),t()}),this.httpServer.on("error",n=>{this.log(`Server error: ${n.message}`),e(n)})}catch(n){e(n)}})}async stop(){if(this.running)return this.log("Stopping server..."),new Promise(t=>{this.clients.forEach(e=>{e.disconnect(!0)}),this.clients.clear(),this.clientData.clear(),this.io&&(this.io.close(()=>{this.log("Socket.IO server closed")}),this.io=null),this.httpServer?(this.httpServer.close(()=>{this.log("HTTP server closed"),this.running=!1,t()}),this.httpServer=null):(this.running=!1,t())})}getSignalingInfo(){return{mode:"socketio",transport:"websocket",host:this.options.host,port:this.options.port}}getClients(){return Array.from(this.clients.keys())}getClientInfo(t){let e=this.clients.get(t);return e?{id:t,connectedAt:e.connectedAt||Date.now(),address:e.handshake.address,data:this.clientData.get(t)||{}}:null}sendToClient(t,e,n){let i=this.clients.get(t);if(!i){this.log(`Cannot send to client ${t}: not found`);return}i.emit(e,n)}sendToClientVolatile(t,e,n){let i=this.clients.get(t);if(!i){this.log(`Cannot send volatile to client ${t}: not found`);return}i.compress(!1).emit(e,n)}broadcast(t,e){this.io&&(this.io.emit(t,e),this.log(`Broadcast '${t}' to all clients`))}broadcastVolatile(t,e){this.io&&(this.io.volatile.emit(t,e),this.log(`Broadcast volatile '${t}' to all clients`))}broadcastExcept(t,e,n){let i=this.clients.get(t);i&&(i.broadcast.emit(e,n),this.log(`Broadcast '${e}' except ${t}`))}sendToRoom(t,e,n){this.io&&(this.io.to(t).emit(e,n),this.log(`Sent '${e}' to room '${t}'`))}joinRoom(t,e){let n=this.clients.get(t);n&&(n.join(e),this.log(`Client ${t} joined room '${e}'`))}leaveRoom(t,e){let n=this.clients.get(t);n&&(n.leave(e),this.log(`Client ${t} left room '${e}'`))}getRoomClients(t){if(!this.io)return[];let e=this.io.sockets.adapter.rooms.get(t);return e?Array.from(e):[]}disconnectClient(t,e="Server disconnect"){let n=this.clients.get(t);n&&(n.disconnect(!0),this.log(`Disconnected client ${t}: ${e}`))}on(t,e){let n=this.eventHandlers.get(t)||[];n.push(e),this.eventHandlers.set(t,n),this.log(`Registered handler for '${t}'`)}onConnect(t){this.connectionHandlers.push(t),this.log("Registered connection handler")}onDisconnect(t){this.disconnectionHandlers.push(t),this.log("Registered disconnection handler")}off(t,e){let n=this.eventHandlers.get(t);if(!n)return;let i=n.indexOf(e);i!==-1&&(n.splice(i,1),this.log(`Removed handler for '${t}'`))}setClientData(t,e,n){let i=this.clientData.get(t)||{};i[e]=n,this.clientData.set(t,i)}getClientData(t,e){let n=this.clientData.get(t);return n?n[e]:void 0}getStats(){return{connectedClients:this.clients.size,totalConnections:this.stats.totalConnections,uptime:Date.now()-this.stats.startTime}}sendBridge(t,e,n){let i=this.clients.get(t);if(!i){this.log(`Cannot send bridge to client ${t}: not found`);return}try{let r={channel:e,data:JSON.stringify(n)};i.emit("bridge",r),this.log(`Bridge sent to ${t} on channel '${e}'`)}catch(r){this.log(`Error sending bridge to ${t}: ${r}`)}}broadcastBridge(t,e){if(this.io)try{let n={channel:t,data:JSON.stringify(e)};this.io.emit("bridge",n),this.log(`Bridge broadcast on channel '${t}'`)}catch(n){this.log(`Error broadcasting bridge: ${n}`)}}onBridge(t,e){this.bridgeHandlers.has(t)||this.bridgeHandlers.set(t,[]),this.bridgeHandlers.get(t).push(e),this.log(`Bridge handler registered for channel '${t}'`)}offBridge(t,e){let n=this.bridgeHandlers.get(t);if(n){let i=n.indexOf(e);i!==-1&&(n.splice(i,1),this.log(`Bridge handler removed for channel '${t}'`))}}handleBridgeMessage(t,e){try{let{channel:n,data:i}=e,r=JSON.parse(i),s=this.bridgeHandlers.get(n);s&&s.length>0&&s.forEach(a=>{try{a(t,r)}catch(c){this.log(`Error in bridge handler for '${n}': ${c}`)}});let o=this.bridgeHandlers.get("*");o&&o.length>0&&o.forEach(a=>{try{a(t,{channel:n,data:r})}catch(c){this.log(`Error in wildcard bridge handler: ${c}`)}}),(!s||s.length===0)&&(!o||o.length===0)&&this.log(`No bridge handler for channel '${n}' from ${t}`)}catch(n){this.log(`Error parsing bridge message from ${t}: ${n}`)}}async destroy(){await this.stop(),this.connectionHandlers=[],this.disconnectionHandlers=[],this.eventHandlers.clear(),this.log("Server destroyed")}handleConnection(t){let e=t.id;if(this.log(`Client connected: ${e}`),this.clients.size>=this.options.maxConnections){this.log(`Max connections reached, rejecting ${e}`),t.emit("error",{code:"MAX_CONNECTIONS",message:"Server is full"}),t.disconnect(!0);return}this.clients.set(e,t),this.clientData.set(e,{}),t.connectedAt=Date.now(),this.stats.totalConnections++,this.setupClientHandlers(t),this.connectionHandlers.forEach(n=>{try{n(e)}catch(i){this.log(`Error in connection handler: ${i}`)}}),t.on("ping",()=>{t.emit("pong")})}setupClientHandlers(t){let e=t.id;t.on("disconnect",n=>{this.log(`Client disconnected: ${e} (${n})`),this.clients.delete(e),this.clientData.delete(e),this.disconnectionHandlers.forEach(i=>{try{i(e,n)}catch(r){this.log(`Error in disconnection handler: ${r}`)}})}),t.on("bridge",n=>{this.handleBridgeMessage(e,n)}),this.eventHandlers.forEach((n,i)=>{t.on(i,r=>{n.forEach(s=>{try{s(e,r)}catch(o){this.log(`Error in event handler for '${i}': ${o}`)}})})})}log(t,e){this.options.debug&&(e!==void 0?console.warn(`[SocketIOServer] ${t}`,e):console.warn(`[SocketIOServer] ${t}`))}};p(m,"SocketIOServer");var u=m;var w=require("socket.io"),k=require("socket.io-client"),S=require("werift"),R=require("http");var I={connecting:5e3,channels_opening:5e3,ready:5e3,joined:0,aborted:0},C=class C{constructor(t){l(this,"signalingServer",null);l(this,"httpServer",null);l(this,"relaySocket",null);l(this,"clients",new Map);l(this,"options");l(this,"running",!1);l(this,"certificates",[]);l(this,"connectionHandlers",[]);l(this,"disconnectionHandlers",[]);l(this,"eventHandlers",new Map);l(this,"bridgeHandlers",new Map);l(this,"rooms",new Map);l(this,"chunkBuffers",new Map);l(this,"clientsReassembling",new Set);this.options={port:t.port,host:t.host??"0.0.0.0",cors:t.cors??{origin:"*"},maxConnections:t.maxConnections??1e3,pingInterval:t.pingInterval??25e3,pingTimeout:t.pingTimeout??5e3,debug:t.debug??!1,iceServers:t.iceServers??[{urls:"stun:stun.l.google.com:19302"}],signalingPath:t.signalingPath??(t.signalUrl?"/socket.io":"/utsp-signal"),signalUrl:t.signalUrl,sessionId:t.sessionId},this.on("bridge",(e,n)=>{if(!n||!n.channel)return;let{channel:i,data:r}=n,s=this.bridgeHandlers.get(i);s&&s.forEach(a=>a(e,r));let o=this.bridgeHandlers.get("*");o&&o.forEach(a=>a(e,{channel:i,data:r}))})}isRunning(){return this.running}getHttpServer(){return this.httpServer}async start(){if(!this.running){if(this.log("Starting WebRTC Server..."),this.certificates.length===0){this.log("Warm-up: Generating WebRTC Certificates...");try{let t=await S.RTCDtlsTransport.SetupCertificate();t&&(this.certificates.push(t),this.log(`Warm-up: Certificate generated. Fingerprints: ${JSON.stringify(t.getFingerprints())}`),this.log("Warm-up: Done."))}catch{this.log("Warm-up: Static generateCertificate failed, falling back to on-demand generation.")}}if(this.options.signalUrl){this.log(`Connecting to PUBLIC RELAY: ${this.options.signalUrl} (Session: ${this.options.sessionId})`),await this.startRelayMode(),this.running=!0;return}return this.httpServer=(0,R.createServer)(),this.signalingServer=new w.Server(this.httpServer,{path:this.options.signalingPath,cors:this.options.cors,pingInterval:this.options.pingInterval,pingTimeout:this.options.pingTimeout,transports:["websocket","polling"]}),this.setupSignaling(),new Promise((t,e)=>{this.httpServer?.listen(this.options.port,this.options.host,()=>{this.running=!0,this.log(`WebRTC Server listening on ${this.options.host}:${this.options.port} (Signaling path: ${this.options.signalingPath})`),t()}),this.httpServer?.on("error",n=>{this.error("Failed to start server",n),e(n)})})}}getSignalingInfo(){return this.options.signalUrl?{mode:"webrtc",transport:"webrtc",signalUrl:this.options.signalUrl,sessionId:this.options.sessionId}:{mode:"webrtc",transport:"webrtc",host:this.options.host,port:this.options.port,path:this.options.signalingPath}}async startRelayMode(){return new Promise((t,e)=>{if(!this.options.signalUrl)return e("No relay URL");this.relaySocket=(0,k.io)(this.options.signalUrl,{path:this.options.signalingPath,transports:["websocket","polling"]}),this.relaySocket.on("connect",()=>{this.log(`Connected to Relay Server: ${this.options.signalUrl}`),this.relaySocket?.emit("register-host",this.options.sessionId)}),this.relaySocket.on("host-registered",n=>{n&&n.sessionId?(this.options.sessionId=n.sessionId,this.log(`\u2705 Host Registered! Share this Session ID with clients: ${this.options.sessionId}`),t()):(this.log("Host registered (No ID returned from relay)"),t())}),this.relaySocket.on("error",n=>{this.error("Relay connection error",n),this.running||e(n)}),this.relaySocket.on("client-ready",({clientId:n})=>{this.handleRelayClient(n)}),this.relaySocket.on("signal",({from:n,type:i,data:r})=>{let s=i;i==="offer"&&(s="rtc:offer"),i==="answer"&&(s="rtc:answer"),i==="candidate"&&(s="rtc:candidate"),this.log(`Received signal from relay (type: ${i}, from: ${n})`),this.handleRelaySignal(n,s,r)})})}handleRelayClient(t){this.log(`New Client via Relay: ${t}`);let e={id:t,connected:!0,emit:(n,i)=>{this.relaySocket&&this.relaySocket.emit("signal",{target:t,type:n,data:i})},disconnect:()=>{},handshake:{address:"relay"}};this.setupClient(t,e)}handleRelaySignal(t,e,n){let i=this.clients.get(t);i&&i.socket.trigger(e,n)}setupClient(t,e){let n=Date.now();if(!e.on){let o=new Map;e.on=(a,c)=>{o.has(a)||o.set(a,[]),o.get(a).push(c)},e.trigger=(a,c)=>{let d=o.get(a);d&&d.forEach(g=>g(c))}}let i=Date.now(),r=new S.RTCPeerConnection({iceServers:this.options.iceServers,dtls:this.certificates[0]?{keys:{certPem:this.certificates[0].certPem,keyPem:this.certificates[0].privateKey,signatureHash:this.certificates[0].signatureHash}}:void 0});this.log(`\u23F1\uFE0F [${t}] RTCPeerConnection created in ${Date.now()-i}ms`),this.certificates[0]?this.log(`Using cached certificate for client ${t}`):this.warn(`No cached certificate available for client ${t}, generating new one (LAG WARNING)`);let s={id:t,socket:e,peer:r,connectedAt:Date.now(),data:{},state:"connecting",pendingEvents:[]};this.clients.set(t,s),this.setStateTimeout(t,"connecting"),this.setupWebRTC(s),this.log(`\u23F1\uFE0F [${t}] setupClient total: ${Date.now()-n}ms`)}setupSignaling(){this.signalingServer&&this.signalingServer.on("connection",async t=>{let e=t.id;this.log(`Signaling connection: ${e}`),this.setupClient(e,t)})}async setupWebRTC(t){let{id:e,socket:n,peer:i}=t,r=i.createDataChannel("reliable",{ordered:!0});t.dataChannelReliable=r,this.setupChannel(e,r,!0);let s=i.createDataChannel("unreliable",{ordered:!1,maxRetransmits:0});t.dataChannelUnreliable=s,this.setupChannel(e,s,!1),i.onicecandidate=o=>{o.candidate&&n.emit("rtc:candidate",o.candidate)},n.on("rtc:answer",async o=>{if(!(!o||typeof o!="object"||!o.type||!o.sdp))try{let a=this.stripRelayFromSDP(o.sdp);await i.setRemoteDescription({type:"answer",sdp:a})}catch(a){this.error(`Error setting remote description for ${e}`,a)}}),n.on("rtc:candidate",async o=>{if(!(!o||typeof o!="object"||!o.candidate))try{if(this.parseCandidateType(o.candidate)==="relay")return;await i.addIceCandidate(o)}catch(a){this.error(`Error adding ICE candidate for ${e}`,a)}}),n.on("disconnect",o=>{t.state!=="joined"&&this.handleDisconnect(e,o)});try{let o=await i.createOffer();await i.setLocalDescription(o);let a=this.stripRelayFromSDP(o.sdp||"");n.emit("rtc:offer",{type:"offer",sdp:a})}catch{n.disconnect(!0)}}setupChannel(t,e,n){e.onopen=()=>{this.log(`Client ${t}: ${n?"Reliable":"Unreliable"} channel open`),this.checkClientReady(t)},e.onmessage=i=>{let r=i.data,s;Buffer.isBuffer(r)?s=r.toString("utf8"):s=r;try{let o=JSON.parse(s);if(Array.isArray(o)&&o.length>=1){let[a,c]=o;this.handleEvent(t,a,c)}}catch{}}}setStateTimeout(t,e){let n=this.clients.get(t);if(!n)return;n.stateTimeout&&(clearTimeout(n.stateTimeout),n.stateTimeout=void 0);let i=I[e];i!==0&&(n.stateTimeout=setTimeout(()=>{let r=this.clients.get(t);r&&r.state===e&&(this.warn(`Client ${t}: Timeout in state '${e}' - aborting connection`),this.transitionTo(t,"aborted"),this.handleDisconnect(t,`timeout_${e}`))},i))}transitionTo(t,e){let n=this.clients.get(t);if(!n)return;let i=n.state;this.log(`[State] ${t}: ${i} \u2192 ${e}`),n.state=e,this.setStateTimeout(t,e)}checkClientReady(t){let e=this.clients.get(t);if(!e)return;let n=e.dataChannelReliable?.readyState==="open",i=e.dataChannelUnreliable?.readyState==="open";if(e.state==="connecting"&&(n||i)&&this.transitionTo(t,"channels_opening"),e.state==="channels_opening"&&n&&i){this.transitionTo(t,"ready");for(let{event:r,data:s}of e.pendingEvents)this.processEvent(t,r,s);e.pendingEvents=[]}}processEvent(t,e,n){let i=this.clients.get(t);e==="join"&&i?.state==="ready"&&(this.transitionTo(t,"joined"),this.connectionHandlers.forEach(s=>s(t)),this.startStatsMonitor(t));let r=this.eventHandlers.get(e);r&&r.forEach(s=>s(t,n))}handleEvent(t,e,n){let i=this.clients.get(t);if(e==="ping"){this.sendSafe(i?.dataChannelReliable,"pong",null,!0);return}if(e==="__ch:start"){if(this.clientsReassembling.has(t))return;let{id:r,count:s}=n;this.chunkBuffers.has(t)||this.chunkBuffers.set(t,new Map),this.chunkBuffers.get(t).set(r,{count:s,parts:new Array(s),received:0});return}if(e==="__ch:part"){let{id:r,idx:s,chunk:o}=n,a=this.chunkBuffers.get(t),c=a?.get(r);if(c&&(c.parts[s]=o,c.received++,c.received===c.count)){a.delete(r);try{this.clientsReassembling.add(t);let d=c.parts.join(""),[g,y]=JSON.parse(d);this.handleEvent(t,g,y)}catch{this.error(`Client ${t}: Failed to reassemble chunked payload`)}finally{this.clientsReassembling.delete(t)}}return}if(i&&i.state!=="ready"&&i.state!=="joined"){i.pendingEvents.push({event:e,data:n});return}this.processEvent(t,e,n)}handleDisconnect(t,e){let n=this.clients.get(t);if(n){n.stateTimeout&&clearTimeout(n.stateTimeout),this.stopStatsMonitor(t);try{n.peer.close()}catch{}this.clients.delete(t),this.rooms.forEach(i=>i.delete(t)),this.chunkBuffers.delete(t),this.clientsReassembling.delete(t),n.state==="joined"&&this.disconnectionHandlers.forEach(i=>i(t,e))}}async stop(){this.running=!1;for(let t of this.clients.values())t.peer.close(),t.socket.disconnect(!0);this.clients.clear(),this.signalingServer&&(this.signalingServer.close(),this.signalingServer=null),this.httpServer&&(this.httpServer.close(),this.httpServer=null)}preparePayload(t,e){return JSON.stringify([t,e],(n,i)=>{if(i&&typeof i=="object"){if(i.type==="Buffer"&&Array.isArray(i.data))return{_b64:Buffer.from(i.data).toString("base64")};if(i instanceof Uint8Array||i instanceof Int8Array||i instanceof Uint16Array||i instanceof Int16Array||i instanceof Uint32Array||i instanceof Int32Array||i instanceof Float32Array||i instanceof Float64Array)return{_b64:Buffer.from(i.buffer,i.byteOffset,i.byteLength).toString("base64")}}return i})}getChunks(t,e){let n=Math.ceil(t.length/e),i=new Array(n);for(let r=0,s=0;r<n;++r,s+=e)i[r]=t.substr(s,e);return i}prepareBinaryPacket(t,e){let n=Buffer.isBuffer(e)?e:Buffer.from(e.buffer,e.byteOffset,e.byteLength);if(typeof t=="number"){let s=Buffer.alloc(2);return s.writeUInt8(255,0),s.writeUInt8(t,1),Buffer.concat([s,n])}let i=Buffer.from(t,"utf8");if(i.length>=255)return this.error("Event name too long for binary fast path",t),null;let r=Buffer.alloc(1);return r.writeUInt8(i.length,0),Buffer.concat([r,i,n])}sendSafe(t,e,n,i=!1){if(!t||t.readyState!=="open"||!i&&t.bufferedAmount>64*1024)return;let r=1200;if((Buffer.isBuffer(n)||n instanceof Uint8Array)&&n.byteLength<=r){let o=this.prepareBinaryPacket(e,n);if(o){try{t.send(o)}catch{}return}}try{let o=this.preparePayload(String(e),n);if(o.length<=r)t.send(o);else{let a=Math.random().toString(36).substring(2,15),c=this.getChunks(o,r),d=JSON.stringify(["__ch:start",{id:a,count:c.length}]);t.send(d);for(let g=0;g<c.length;g++){let y=JSON.stringify(["__ch:part",{id:a,idx:g,chunk:c[g]}]);t.send(y)}}}catch{}}sendToClient(t,e,n){let i=this.clients.get(t);if(i&&i.dataChannelReliable?.readyState==="open")try{this.sendSafe(i.dataChannelReliable,e,n,!0)}catch(r){this.error(`Failed to send to ${t}`,r)}}sendToClientVolatile(t,e,n){let i=this.clients.get(t),r=i?.dataChannelUnreliable?.readyState==="open"?i.dataChannelUnreliable:i?.dataChannelReliable;this.sendSafe(r,e,n,!1)}broadcast(t,e){for(let n of this.clients.values())n.dataChannelReliable?.readyState==="open"&&this.sendSafe(n.dataChannelReliable,t,e,!0)}broadcastVolatile(t,e){for(let n of this.clients.values()){let i=n.dataChannelUnreliable?.readyState==="open"?n.dataChannelUnreliable:n.dataChannelReliable;this.sendSafe(i,t,e,!1)}}broadcastExcept(t,e,n){for(let[i,r]of this.clients)i!==t&&r.dataChannelReliable?.readyState==="open"&&this.sendSafe(r.dataChannelReliable,e,n,!0)}sendToRoom(t,e,n){let i=this.rooms.get(t);if(i)for(let r of i){let s=this.clients.get(r);s&&s.dataChannelReliable?.readyState==="open"&&this.sendSafe(s.dataChannelReliable,e,n,!0)}}getClients(){return Array.from(this.clients.keys())}getClientInfo(t){let e=this.clients.get(t);return e?{id:e.id,connectedAt:e.connectedAt,address:e.socket.handshake.address,data:e.data}:null}getClientData(t,e){let n=this.clients.get(t);return n?n.data[e]:void 0}setClientData(t,e,n){let i=this.clients.get(t);i&&(i.data[e]=n)}disconnectClient(t,e="Server disconnected client"){let n=this.clients.get(t);n&&(n.socket.disconnect(!0),this.handleDisconnect(t,e))}joinRoom(t,e){this.clients.has(t)&&(this.rooms.has(e)||this.rooms.set(e,new Set),this.rooms.get(e).add(t))}leaveRoom(t,e){let n=this.rooms.get(e);n&&(n.delete(t),n.size===0&&this.rooms.delete(e))}getRoomClients(t){return this.rooms.has(t)?Array.from(this.rooms.get(t)):[]}on(t,e){this.eventHandlers.has(t)||this.eventHandlers.set(t,[]),this.eventHandlers.get(t).push(e)}off(t,e){let n=this.eventHandlers.get(t);if(n){let i=n.indexOf(e);i!==-1&&n.splice(i,1)}}onConnect(t){this.connectionHandlers.push(t)}onDisconnect(t){this.disconnectionHandlers.push(t)}getStats(){return{connectedClients:this.clients.size,totalConnections:0,uptime:process.uptime()}}sendBridge(t,e,n){this.sendToClient(t,"bridge",{channel:e,data:n})}broadcastBridge(t,e){this.broadcast("bridge",{channel:t,data:e})}onBridge(t,e){this.bridgeHandlers.has(t)||this.bridgeHandlers.set(t,[]),this.bridgeHandlers.get(t).push(e)}offBridge(t,e){let n=this.bridgeHandlers.get(t);if(n){let i=n.indexOf(e);i!==-1&&n.splice(i,1)}}async destroy(){return this.stop()}async startStatsMonitor(t){let e=this.clients.get(t);e&&(this.stopStatsMonitor(t),e.statsInterval=setInterval(async()=>{let n=this.clients.get(t);if(!(!n||!n.peer||n.state!=="joined"))try{let i=await n.peer.getStats(),r=null;for(let s of i.values())if(s.type==="candidate-pair"&&s.state==="succeeded"){r=s;break}if(r){let s=i.get(r.localCandidateId),o=i.get(r.remoteCandidateId);this.options.debug&&this.log(`Stats for ${t}: RTT=${r.currentRoundTripTime?Math.round(r.currentRoundTripTime*1e3):"N/A"}ms Path=${s?.candidateType||"?"}<->${o?.candidateType||"?"}`),(s?.candidateType==="relay"||o?.candidateType==="relay")&&(this.error(`STRICT P2P VIOLATION for ${t}: Detected relay path in stats. DISCONNECTING.`),this.handleDisconnect(t,"Strict P2P Violation"))}}catch{}},5e3))}stopStatsMonitor(t){let e=this.clients.get(t);e&&e.statsInterval&&(clearInterval(e.statsInterval),e.statsInterval=void 0)}parseCandidateType(t){return t?t.includes("typ host")?"host":t.includes("typ srflx")?"srflx":t.includes("typ relay")?"relay":"unknown":"unknown"}stripRelayFromSDP(t){return t.split(`
2
2
  `).filter(e=>!e.includes("typ relay")).join(`
3
- `)}log(t,...e){this.options.debug&&console.warn(`[WebRTC-Server] ${t}`,...e)}warn(t,...e){this.options.debug&&console.warn(`[WebRTC-Server] ${t}`,...e)}error(t,...e){console.error(`[WebRTC-Server] \u274C ${t}`,...e)}};f(m,"WebRTCServer");var v=m;0&&(module.exports={SocketIOServer,WebRTCServer});
3
+ `)}log(t,...e){this.options.debug&&console.warn(`[WebRTC-Server] ${t}`,...e)}warn(t,...e){this.options.debug&&console.warn(`[WebRTC-Server] ${t}`,...e)}error(t,...e){console.error(`[WebRTC-Server] \u274C ${t}`,...e)}};p(C,"WebRTCServer");var v=C;0&&(module.exports={SocketIOServer,WebRTCServer});
package/dist/index.d.ts CHANGED
@@ -718,6 +718,8 @@ declare class WebRTCServer implements INetworkServer {
718
718
  private eventHandlers;
719
719
  private bridgeHandlers;
720
720
  private rooms;
721
+ private chunkBuffers;
722
+ private clientsReassembling;
721
723
  constructor(options: WebRTCServerOptions);
722
724
  isRunning(): boolean;
723
725
  getHttpServer(): Server | null;
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- var y=Object.defineProperty;var C=(h,t,e)=>t in h?y(h,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):h[t]=e;var p=(h,t)=>y(h,"name",{value:t,configurable:!0});var l=(h,t,e)=>(C(h,typeof t!="symbol"?t+"":t,e),e);import{Server as b}from"socket.io";import{createServer as T}from"http";var u=class u{constructor(t){l(this,"io",null);l(this,"httpServer",null);l(this,"options");l(this,"running",!1);l(this,"clients",new Map);l(this,"clientData",new Map);l(this,"connectionHandlers",[]);l(this,"disconnectionHandlers",[]);l(this,"eventHandlers",new Map);l(this,"stats",{totalConnections:0,startTime:0});l(this,"bridgeHandlers",new Map);if(!Number.isInteger(t.port)||t.port<0||t.port>65535)throw new Error(`SocketIOServer: Invalid port ${t.port} - must be an integer between 0 and 65535`);if(t.maxConnections!==void 0&&(!Number.isInteger(t.maxConnections)||t.maxConnections<=0))throw new Error(`SocketIOServer: Invalid maxConnections ${t.maxConnections} - must be a positive integer`);if(t.pingInterval!==void 0&&(!Number.isFinite(t.pingInterval)||t.pingInterval<=0))throw new Error(`SocketIOServer: Invalid pingInterval ${t.pingInterval} - must be a positive number`);if(t.pingTimeout!==void 0&&(!Number.isFinite(t.pingTimeout)||t.pingTimeout<=0))throw new Error(`SocketIOServer: Invalid pingTimeout ${t.pingTimeout} - must be a positive number`);this.options={port:t.port,host:t.host??"0.0.0.0",cors:t.cors??{origin:"*"},maxConnections:t.maxConnections??1e3,pingInterval:t.pingInterval??25e3,pingTimeout:t.pingTimeout??5e3,debug:t.debug??!1},this.log("Server initialized",{port:this.options.port,host:this.options.host,maxConnections:this.options.maxConnections})}isRunning(){return this.running}getHttpServer(){return this.httpServer}async start(){if(this.running){this.log("Server already running");return}return this.log(`Starting server on ${this.options.host}:${this.options.port}...`),new Promise((t,e)=>{try{this.httpServer=T(),this.io=new b(this.httpServer,{cors:this.options.cors,pingInterval:this.options.pingInterval,pingTimeout:this.options.pingTimeout,maxHttpBufferSize:1e6}),this.io.on("connection",n=>{this.handleConnection(n)}),this.httpServer.listen(this.options.port,this.options.host,()=>{this.running=!0,this.stats.startTime=Date.now(),this.log(`Server started on ${this.options.host}:${this.options.port}`),t()}),this.httpServer.on("error",n=>{this.log(`Server error: ${n.message}`),e(n)})}catch(n){e(n)}})}async stop(){if(this.running)return this.log("Stopping server..."),new Promise(t=>{this.clients.forEach(e=>{e.disconnect(!0)}),this.clients.clear(),this.clientData.clear(),this.io&&(this.io.close(()=>{this.log("Socket.IO server closed")}),this.io=null),this.httpServer?(this.httpServer.close(()=>{this.log("HTTP server closed"),this.running=!1,t()}),this.httpServer=null):(this.running=!1,t())})}getSignalingInfo(){return{mode:"socketio",transport:"websocket",host:this.options.host,port:this.options.port}}getClients(){return Array.from(this.clients.keys())}getClientInfo(t){let e=this.clients.get(t);return e?{id:t,connectedAt:e.connectedAt||Date.now(),address:e.handshake.address,data:this.clientData.get(t)||{}}:null}sendToClient(t,e,n){let i=this.clients.get(t);if(!i){this.log(`Cannot send to client ${t}: not found`);return}i.emit(e,n)}sendToClientVolatile(t,e,n){let i=this.clients.get(t);if(!i){this.log(`Cannot send volatile to client ${t}: not found`);return}i.compress(!1).emit(e,n)}broadcast(t,e){this.io&&(this.io.emit(t,e),this.log(`Broadcast '${t}' to all clients`))}broadcastVolatile(t,e){this.io&&(this.io.volatile.emit(t,e),this.log(`Broadcast volatile '${t}' to all clients`))}broadcastExcept(t,e,n){let i=this.clients.get(t);i&&(i.broadcast.emit(e,n),this.log(`Broadcast '${e}' except ${t}`))}sendToRoom(t,e,n){this.io&&(this.io.to(t).emit(e,n),this.log(`Sent '${e}' to room '${t}'`))}joinRoom(t,e){let n=this.clients.get(t);n&&(n.join(e),this.log(`Client ${t} joined room '${e}'`))}leaveRoom(t,e){let n=this.clients.get(t);n&&(n.leave(e),this.log(`Client ${t} left room '${e}'`))}getRoomClients(t){if(!this.io)return[];let e=this.io.sockets.adapter.rooms.get(t);return e?Array.from(e):[]}disconnectClient(t,e="Server disconnect"){let n=this.clients.get(t);n&&(n.disconnect(!0),this.log(`Disconnected client ${t}: ${e}`))}on(t,e){let n=this.eventHandlers.get(t)||[];n.push(e),this.eventHandlers.set(t,n),this.log(`Registered handler for '${t}'`)}onConnect(t){this.connectionHandlers.push(t),this.log("Registered connection handler")}onDisconnect(t){this.disconnectionHandlers.push(t),this.log("Registered disconnection handler")}off(t,e){let n=this.eventHandlers.get(t);if(!n)return;let i=n.indexOf(e);i!==-1&&(n.splice(i,1),this.log(`Removed handler for '${t}'`))}setClientData(t,e,n){let i=this.clientData.get(t)||{};i[e]=n,this.clientData.set(t,i)}getClientData(t,e){let n=this.clientData.get(t);return n?n[e]:void 0}getStats(){return{connectedClients:this.clients.size,totalConnections:this.stats.totalConnections,uptime:Date.now()-this.stats.startTime}}sendBridge(t,e,n){let i=this.clients.get(t);if(!i){this.log(`Cannot send bridge to client ${t}: not found`);return}try{let r={channel:e,data:JSON.stringify(n)};i.emit("bridge",r),this.log(`Bridge sent to ${t} on channel '${e}'`)}catch(r){this.log(`Error sending bridge to ${t}: ${r}`)}}broadcastBridge(t,e){if(this.io)try{let n={channel:t,data:JSON.stringify(e)};this.io.emit("bridge",n),this.log(`Bridge broadcast on channel '${t}'`)}catch(n){this.log(`Error broadcasting bridge: ${n}`)}}onBridge(t,e){this.bridgeHandlers.has(t)||this.bridgeHandlers.set(t,[]),this.bridgeHandlers.get(t).push(e),this.log(`Bridge handler registered for channel '${t}'`)}offBridge(t,e){let n=this.bridgeHandlers.get(t);if(n){let i=n.indexOf(e);i!==-1&&(n.splice(i,1),this.log(`Bridge handler removed for channel '${t}'`))}}handleBridgeMessage(t,e){try{let{channel:n,data:i}=e,r=JSON.parse(i),s=this.bridgeHandlers.get(n);s&&s.length>0&&s.forEach(a=>{try{a(t,r)}catch(c){this.log(`Error in bridge handler for '${n}': ${c}`)}});let o=this.bridgeHandlers.get("*");o&&o.length>0&&o.forEach(a=>{try{a(t,{channel:n,data:r})}catch(c){this.log(`Error in wildcard bridge handler: ${c}`)}}),(!s||s.length===0)&&(!o||o.length===0)&&this.log(`No bridge handler for channel '${n}' from ${t}`)}catch(n){this.log(`Error parsing bridge message from ${t}: ${n}`)}}async destroy(){await this.stop(),this.connectionHandlers=[],this.disconnectionHandlers=[],this.eventHandlers.clear(),this.log("Server destroyed")}handleConnection(t){let e=t.id;if(this.log(`Client connected: ${e}`),this.clients.size>=this.options.maxConnections){this.log(`Max connections reached, rejecting ${e}`),t.emit("error",{code:"MAX_CONNECTIONS",message:"Server is full"}),t.disconnect(!0);return}this.clients.set(e,t),this.clientData.set(e,{}),t.connectedAt=Date.now(),this.stats.totalConnections++,this.setupClientHandlers(t),this.connectionHandlers.forEach(n=>{try{n(e)}catch(i){this.log(`Error in connection handler: ${i}`)}}),t.on("ping",()=>{t.emit("pong")})}setupClientHandlers(t){let e=t.id;t.on("disconnect",n=>{this.log(`Client disconnected: ${e} (${n})`),this.clients.delete(e),this.clientData.delete(e),this.disconnectionHandlers.forEach(i=>{try{i(e,n)}catch(r){this.log(`Error in disconnection handler: ${r}`)}})}),t.on("bridge",n=>{this.handleBridgeMessage(e,n)}),this.eventHandlers.forEach((n,i)=>{t.on(i,r=>{n.forEach(s=>{try{s(e,r)}catch(o){this.log(`Error in event handler for '${i}': ${o}`)}})})})}log(t,e){this.options.debug&&(e!==void 0?console.warn(`[SocketIOServer] ${t}`,e):console.warn(`[SocketIOServer] ${t}`))}};p(u,"SocketIOServer");var f=u;import{Server as w}from"socket.io";import{io as H}from"socket.io-client";import{RTCPeerConnection as E,RTCDtlsTransport as R}from"werift";import{createServer as k}from"http";var $={connecting:5e3,channels_opening:5e3,ready:5e3,joined:0,aborted:0},S=class S{constructor(t){l(this,"signalingServer",null);l(this,"httpServer",null);l(this,"relaySocket",null);l(this,"clients",new Map);l(this,"options");l(this,"running",!1);l(this,"certificates",[]);l(this,"connectionHandlers",[]);l(this,"disconnectionHandlers",[]);l(this,"eventHandlers",new Map);l(this,"bridgeHandlers",new Map);l(this,"rooms",new Map);this.options={port:t.port,host:t.host??"0.0.0.0",cors:t.cors??{origin:"*"},maxConnections:t.maxConnections??1e3,pingInterval:t.pingInterval??25e3,pingTimeout:t.pingTimeout??5e3,debug:t.debug??!1,iceServers:t.iceServers??[{urls:"stun:stun.l.google.com:19302"}],signalingPath:t.signalingPath??(t.signalUrl?"/socket.io":"/utsp-signal"),signalUrl:t.signalUrl,sessionId:t.sessionId},this.on("bridge",(e,n)=>{if(!n||!n.channel)return;let{channel:i,data:r}=n,s=this.bridgeHandlers.get(i);s&&s.forEach(a=>a(e,r));let o=this.bridgeHandlers.get("*");o&&o.forEach(a=>a(e,{channel:i,data:r}))})}isRunning(){return this.running}getHttpServer(){return this.httpServer}async start(){if(!this.running){if(this.log("Starting WebRTC Server..."),this.certificates.length===0){this.log("Warm-up: Generating WebRTC Certificates...");try{let t=await R.SetupCertificate();t&&(this.certificates.push(t),this.log(`Warm-up: Certificate generated. Fingerprints: ${JSON.stringify(t.getFingerprints())}`),this.log("Warm-up: Done."))}catch{this.log("Warm-up: Static generateCertificate failed, falling back to on-demand generation.")}}if(this.options.signalUrl){this.log(`Connecting to PUBLIC RELAY: ${this.options.signalUrl} (Session: ${this.options.sessionId})`),await this.startRelayMode(),this.running=!0;return}return this.httpServer=k(),this.signalingServer=new w(this.httpServer,{path:this.options.signalingPath,cors:this.options.cors,pingInterval:this.options.pingInterval,pingTimeout:this.options.pingTimeout,transports:["websocket","polling"]}),this.setupSignaling(),new Promise((t,e)=>{this.httpServer?.listen(this.options.port,this.options.host,()=>{this.running=!0,this.log(`WebRTC Server listening on ${this.options.host}:${this.options.port} (Signaling path: ${this.options.signalingPath})`),t()}),this.httpServer?.on("error",n=>{this.error("Failed to start server",n),e(n)})})}}getSignalingInfo(){return this.options.signalUrl?{mode:"webrtc",transport:"webrtc",signalUrl:this.options.signalUrl,sessionId:this.options.sessionId}:{mode:"webrtc",transport:"webrtc",host:this.options.host,port:this.options.port,path:this.options.signalingPath}}async startRelayMode(){return new Promise((t,e)=>{if(!this.options.signalUrl)return e("No relay URL");this.relaySocket=H(this.options.signalUrl,{path:this.options.signalingPath,transports:["websocket","polling"]}),this.relaySocket.on("connect",()=>{this.log(`Connected to Relay Server: ${this.options.signalUrl}`),this.relaySocket?.emit("register-host",this.options.sessionId)}),this.relaySocket.on("host-registered",n=>{n&&n.sessionId?(this.options.sessionId=n.sessionId,this.log(`\u2705 Host Registered! Share this Session ID with clients: ${this.options.sessionId}`),t()):(this.log("Host registered (No ID returned from relay)"),t())}),this.relaySocket.on("error",n=>{this.error("Relay connection error",n),this.running||e(n)}),this.relaySocket.on("client-ready",({clientId:n})=>{this.handleRelayClient(n)}),this.relaySocket.on("signal",({from:n,type:i,data:r})=>{let s=i;i==="offer"&&(s="rtc:offer"),i==="answer"&&(s="rtc:answer"),i==="candidate"&&(s="rtc:candidate"),this.log(`Received signal from relay (type: ${i}, from: ${n})`),this.handleRelaySignal(n,s,r)})})}handleRelayClient(t){this.log(`New Client via Relay: ${t}`);let e={id:t,connected:!0,emit:(n,i)=>{this.relaySocket&&this.relaySocket.emit("signal",{target:t,type:n,data:i})},disconnect:()=>{},handshake:{address:"relay"}};this.setupClient(t,e)}handleRelaySignal(t,e,n){let i=this.clients.get(t);i&&i.socket.trigger(e,n)}setupClient(t,e){let n=Date.now();if(!e.on){let o=new Map;e.on=(a,c)=>{o.has(a)||o.set(a,[]),o.get(a).push(c)},e.trigger=(a,c)=>{let d=o.get(a);d&&d.forEach(g=>g(c))}}let i=Date.now(),r=new E({iceServers:this.options.iceServers,dtls:this.certificates[0]?{keys:{certPem:this.certificates[0].certPem,keyPem:this.certificates[0].privateKey,signatureHash:this.certificates[0].signatureHash}}:void 0});this.log(`\u23F1\uFE0F [${t}] RTCPeerConnection created in ${Date.now()-i}ms`),this.certificates[0]?this.log(`Using cached certificate for client ${t}`):this.warn(`No cached certificate available for client ${t}, generating new one (LAG WARNING)`);let s={id:t,socket:e,peer:r,connectedAt:Date.now(),data:{},state:"connecting",pendingEvents:[]};this.clients.set(t,s),this.setStateTimeout(t,"connecting"),this.setupWebRTC(s),this.log(`\u23F1\uFE0F [${t}] setupClient total: ${Date.now()-n}ms`)}setupSignaling(){this.signalingServer&&this.signalingServer.on("connection",async t=>{let e=t.id;this.log(`Signaling connection: ${e}`),this.setupClient(e,t)})}async setupWebRTC(t){let{id:e,socket:n,peer:i}=t,r=i.createDataChannel("reliable",{ordered:!0});t.dataChannelReliable=r,this.setupChannel(e,r,!0);let s=i.createDataChannel("unreliable",{ordered:!1,maxRetransmits:0});t.dataChannelUnreliable=s,this.setupChannel(e,s,!1),i.onicecandidate=o=>{o.candidate&&n.emit("rtc:candidate",o.candidate)},n.on("rtc:answer",async o=>{if(!(!o||typeof o!="object"||!o.type||!o.sdp))try{let a=this.stripRelayFromSDP(o.sdp);await i.setRemoteDescription({type:"answer",sdp:a})}catch(a){this.error(`Error setting remote description for ${e}`,a)}}),n.on("rtc:candidate",async o=>{if(!(!o||typeof o!="object"||!o.candidate))try{if(this.parseCandidateType(o.candidate)==="relay")return;await i.addIceCandidate(o)}catch(a){this.error(`Error adding ICE candidate for ${e}`,a)}}),n.on("disconnect",o=>{t.state!=="joined"&&this.handleDisconnect(e,o)});try{let o=await i.createOffer();await i.setLocalDescription(o);let a=this.stripRelayFromSDP(o.sdp||"");n.emit("rtc:offer",{type:"offer",sdp:a})}catch{n.disconnect(!0)}}setupChannel(t,e,n){e.onopen=()=>{this.log(`Client ${t}: ${n?"Reliable":"Unreliable"} channel open`),this.checkClientReady(t)},e.onmessage=i=>{let r=i.data,s;Buffer.isBuffer(r)?s=r.toString("utf8"):s=r;try{let o=JSON.parse(s);if(Array.isArray(o)&&o.length>=1){let[a,c]=o;this.handleEvent(t,a,c)}}catch{}}}setStateTimeout(t,e){let n=this.clients.get(t);if(!n)return;n.stateTimeout&&(clearTimeout(n.stateTimeout),n.stateTimeout=void 0);let i=$[e];i!==0&&(n.stateTimeout=setTimeout(()=>{let r=this.clients.get(t);r&&r.state===e&&(this.warn(`Client ${t}: Timeout in state '${e}' - aborting connection`),this.transitionTo(t,"aborted"),this.handleDisconnect(t,`timeout_${e}`))},i))}transitionTo(t,e){let n=this.clients.get(t);if(!n)return;let i=n.state;this.log(`[State] ${t}: ${i} \u2192 ${e}`),n.state=e,this.setStateTimeout(t,e)}checkClientReady(t){let e=this.clients.get(t);if(!e)return;let n=e.dataChannelReliable?.readyState==="open",i=e.dataChannelUnreliable?.readyState==="open";if(e.state==="connecting"&&(n||i)&&this.transitionTo(t,"channels_opening"),e.state==="channels_opening"&&n&&i){this.transitionTo(t,"ready");for(let{event:r,data:s}of e.pendingEvents)this.processEvent(t,r,s);e.pendingEvents=[]}}processEvent(t,e,n){let i=this.clients.get(t);e==="join"&&i?.state==="ready"&&(this.transitionTo(t,"joined"),this.connectionHandlers.forEach(s=>s(t)),this.startStatsMonitor(t));let r=this.eventHandlers.get(e);r&&r.forEach(s=>s(t,n))}handleEvent(t,e,n){let i=this.clients.get(t);if(e==="ping"){this.sendSafe(i?.dataChannelReliable,"pong",null,!0);return}if(i&&i.state!=="ready"&&i.state!=="joined"){i.pendingEvents.push({event:e,data:n});return}this.processEvent(t,e,n)}handleDisconnect(t,e){let n=this.clients.get(t);if(n){n.stateTimeout&&clearTimeout(n.stateTimeout),this.stopStatsMonitor(t);try{n.peer.close()}catch{}this.clients.delete(t),this.rooms.forEach(i=>i.delete(t)),n.state==="joined"&&this.disconnectionHandlers.forEach(i=>i(t,e))}}async stop(){this.running=!1;for(let t of this.clients.values())t.peer.close(),t.socket.disconnect(!0);this.clients.clear(),this.signalingServer&&(this.signalingServer.close(),this.signalingServer=null),this.httpServer&&(this.httpServer.close(),this.httpServer=null)}preparePayload(t,e){return JSON.stringify([t,e],(n,i)=>{if(i&&typeof i=="object"){if(i.type==="Buffer"&&Array.isArray(i.data))return{_b64:Buffer.from(i.data).toString("base64")};if(i instanceof Uint8Array||i instanceof Int8Array||i instanceof Uint16Array||i instanceof Int16Array||i instanceof Uint32Array||i instanceof Int32Array||i instanceof Float32Array||i instanceof Float64Array)return{_b64:Buffer.from(i.buffer,i.byteOffset,i.byteLength).toString("base64")}}return i})}getChunks(t,e){let n=Math.ceil(t.length/e),i=new Array(n);for(let r=0,s=0;r<n;++r,s+=e)i[r]=t.substr(s,e);return i}prepareBinaryPacket(t,e){let n=Buffer.isBuffer(e)?e:Buffer.from(e.buffer,e.byteOffset,e.byteLength);if(typeof t=="number"){let s=Buffer.alloc(2);return s.writeUInt8(255,0),s.writeUInt8(t,1),Buffer.concat([s,n])}let i=Buffer.from(t,"utf8");if(i.length>=255)return this.error("Event name too long for binary fast path",t),null;let r=Buffer.alloc(1);return r.writeUInt8(i.length,0),Buffer.concat([r,i,n])}sendSafe(t,e,n,i=!1){if(!t||t.readyState!=="open"||!i&&t.bufferedAmount>16*1024)return;if(Buffer.isBuffer(n)||n instanceof Uint8Array){let s=this.prepareBinaryPacket(e,n);if(s){try{t.send(s)}catch{}return}}try{let s=this.preparePayload(String(e),n),o=1200;if(s.length<=o)t.send(s);else{let a=Math.random().toString(36).substring(2,15),c=this.getChunks(s,o),d=JSON.stringify(["__ch:start",{id:a,count:c.length}]);t.send(d);for(let g=0;g<c.length;g++){let m=JSON.stringify(["__ch:part",{id:a,idx:g,chunk:c[g]}]);t.send(m)}}}catch{}}sendToClient(t,e,n){let i=this.clients.get(t);if(i&&i.dataChannelReliable?.readyState==="open")try{this.sendSafe(i.dataChannelReliable,e,n,!0)}catch(r){this.error(`Failed to send to ${t}`,r)}}sendToClientVolatile(t,e,n){let i=this.clients.get(t),r=i?.dataChannelUnreliable?.readyState==="open"?i.dataChannelUnreliable:i?.dataChannelReliable;this.sendSafe(r,e,n,!1)}broadcast(t,e){for(let n of this.clients.values())n.dataChannelReliable?.readyState==="open"&&this.sendSafe(n.dataChannelReliable,t,e,!0)}broadcastVolatile(t,e){for(let n of this.clients.values()){let i=n.dataChannelUnreliable?.readyState==="open"?n.dataChannelUnreliable:n.dataChannelReliable;this.sendSafe(i,t,e,!1)}}broadcastExcept(t,e,n){for(let[i,r]of this.clients)i!==t&&r.dataChannelReliable?.readyState==="open"&&this.sendSafe(r.dataChannelReliable,e,n,!0)}sendToRoom(t,e,n){let i=this.rooms.get(t);if(i)for(let r of i){let s=this.clients.get(r);s&&s.dataChannelReliable?.readyState==="open"&&this.sendSafe(s.dataChannelReliable,e,n,!0)}}getClients(){return Array.from(this.clients.keys())}getClientInfo(t){let e=this.clients.get(t);return e?{id:e.id,connectedAt:e.connectedAt,address:e.socket.handshake.address,data:e.data}:null}getClientData(t,e){let n=this.clients.get(t);return n?n.data[e]:void 0}setClientData(t,e,n){let i=this.clients.get(t);i&&(i.data[e]=n)}disconnectClient(t,e="Server disconnected client"){let n=this.clients.get(t);n&&(n.socket.disconnect(!0),this.handleDisconnect(t,e))}joinRoom(t,e){this.clients.has(t)&&(this.rooms.has(e)||this.rooms.set(e,new Set),this.rooms.get(e).add(t))}leaveRoom(t,e){let n=this.rooms.get(e);n&&(n.delete(t),n.size===0&&this.rooms.delete(e))}getRoomClients(t){return this.rooms.has(t)?Array.from(this.rooms.get(t)):[]}on(t,e){this.eventHandlers.has(t)||this.eventHandlers.set(t,[]),this.eventHandlers.get(t).push(e)}off(t,e){let n=this.eventHandlers.get(t);if(n){let i=n.indexOf(e);i!==-1&&n.splice(i,1)}}onConnect(t){this.connectionHandlers.push(t)}onDisconnect(t){this.disconnectionHandlers.push(t)}getStats(){return{connectedClients:this.clients.size,totalConnections:0,uptime:process.uptime()}}sendBridge(t,e,n){this.sendToClient(t,"bridge",{channel:e,data:n})}broadcastBridge(t,e){this.broadcast("bridge",{channel:t,data:e})}onBridge(t,e){this.bridgeHandlers.has(t)||this.bridgeHandlers.set(t,[]),this.bridgeHandlers.get(t).push(e)}offBridge(t,e){let n=this.bridgeHandlers.get(t);if(n){let i=n.indexOf(e);i!==-1&&n.splice(i,1)}}async destroy(){return this.stop()}async startStatsMonitor(t){let e=this.clients.get(t);e&&(this.stopStatsMonitor(t),e.statsInterval=setInterval(async()=>{let n=this.clients.get(t);if(!(!n||!n.peer||n.state!=="joined"))try{let i=await n.peer.getStats(),r=null;for(let s of i.values())if(s.type==="candidate-pair"&&s.state==="succeeded"){r=s;break}if(r){let s=i.get(r.localCandidateId),o=i.get(r.remoteCandidateId);this.options.debug&&this.log(`Stats for ${t}: RTT=${r.currentRoundTripTime?Math.round(r.currentRoundTripTime*1e3):"N/A"}ms Path=${s?.candidateType||"?"}<->${o?.candidateType||"?"}`),(s?.candidateType==="relay"||o?.candidateType==="relay")&&(this.error(`STRICT P2P VIOLATION for ${t}: Detected relay path in stats. DISCONNECTING.`),this.handleDisconnect(t,"Strict P2P Violation"))}}catch{}},5e3))}stopStatsMonitor(t){let e=this.clients.get(t);e&&e.statsInterval&&(clearInterval(e.statsInterval),e.statsInterval=void 0)}parseCandidateType(t){return t?t.includes("typ host")?"host":t.includes("typ srflx")?"srflx":t.includes("typ relay")?"relay":"unknown":"unknown"}stripRelayFromSDP(t){return t.split(`
1
+ var m=Object.defineProperty;var C=(g,t,e)=>t in g?m(g,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):g[t]=e;var f=(g,t)=>m(g,"name",{value:t,configurable:!0});var l=(g,t,e)=>(C(g,typeof t!="symbol"?t+"":t,e),e);import{Server as b}from"socket.io";import{createServer as T}from"http";var v=class v{constructor(t){l(this,"io",null);l(this,"httpServer",null);l(this,"options");l(this,"running",!1);l(this,"clients",new Map);l(this,"clientData",new Map);l(this,"connectionHandlers",[]);l(this,"disconnectionHandlers",[]);l(this,"eventHandlers",new Map);l(this,"stats",{totalConnections:0,startTime:0});l(this,"bridgeHandlers",new Map);if(!Number.isInteger(t.port)||t.port<0||t.port>65535)throw new Error(`SocketIOServer: Invalid port ${t.port} - must be an integer between 0 and 65535`);if(t.maxConnections!==void 0&&(!Number.isInteger(t.maxConnections)||t.maxConnections<=0))throw new Error(`SocketIOServer: Invalid maxConnections ${t.maxConnections} - must be a positive integer`);if(t.pingInterval!==void 0&&(!Number.isFinite(t.pingInterval)||t.pingInterval<=0))throw new Error(`SocketIOServer: Invalid pingInterval ${t.pingInterval} - must be a positive number`);if(t.pingTimeout!==void 0&&(!Number.isFinite(t.pingTimeout)||t.pingTimeout<=0))throw new Error(`SocketIOServer: Invalid pingTimeout ${t.pingTimeout} - must be a positive number`);this.options={port:t.port,host:t.host??"0.0.0.0",cors:t.cors??{origin:"*"},maxConnections:t.maxConnections??1e3,pingInterval:t.pingInterval??25e3,pingTimeout:t.pingTimeout??5e3,debug:t.debug??!1},this.log("Server initialized",{port:this.options.port,host:this.options.host,maxConnections:this.options.maxConnections})}isRunning(){return this.running}getHttpServer(){return this.httpServer}async start(){if(this.running){this.log("Server already running");return}return this.log(`Starting server on ${this.options.host}:${this.options.port}...`),new Promise((t,e)=>{try{this.httpServer=T(),this.io=new b(this.httpServer,{cors:this.options.cors,pingInterval:this.options.pingInterval,pingTimeout:this.options.pingTimeout,maxHttpBufferSize:1e6}),this.io.on("connection",n=>{this.handleConnection(n)}),this.httpServer.listen(this.options.port,this.options.host,()=>{this.running=!0,this.stats.startTime=Date.now(),this.log(`Server started on ${this.options.host}:${this.options.port}`),t()}),this.httpServer.on("error",n=>{this.log(`Server error: ${n.message}`),e(n)})}catch(n){e(n)}})}async stop(){if(this.running)return this.log("Stopping server..."),new Promise(t=>{this.clients.forEach(e=>{e.disconnect(!0)}),this.clients.clear(),this.clientData.clear(),this.io&&(this.io.close(()=>{this.log("Socket.IO server closed")}),this.io=null),this.httpServer?(this.httpServer.close(()=>{this.log("HTTP server closed"),this.running=!1,t()}),this.httpServer=null):(this.running=!1,t())})}getSignalingInfo(){return{mode:"socketio",transport:"websocket",host:this.options.host,port:this.options.port}}getClients(){return Array.from(this.clients.keys())}getClientInfo(t){let e=this.clients.get(t);return e?{id:t,connectedAt:e.connectedAt||Date.now(),address:e.handshake.address,data:this.clientData.get(t)||{}}:null}sendToClient(t,e,n){let i=this.clients.get(t);if(!i){this.log(`Cannot send to client ${t}: not found`);return}i.emit(e,n)}sendToClientVolatile(t,e,n){let i=this.clients.get(t);if(!i){this.log(`Cannot send volatile to client ${t}: not found`);return}i.compress(!1).emit(e,n)}broadcast(t,e){this.io&&(this.io.emit(t,e),this.log(`Broadcast '${t}' to all clients`))}broadcastVolatile(t,e){this.io&&(this.io.volatile.emit(t,e),this.log(`Broadcast volatile '${t}' to all clients`))}broadcastExcept(t,e,n){let i=this.clients.get(t);i&&(i.broadcast.emit(e,n),this.log(`Broadcast '${e}' except ${t}`))}sendToRoom(t,e,n){this.io&&(this.io.to(t).emit(e,n),this.log(`Sent '${e}' to room '${t}'`))}joinRoom(t,e){let n=this.clients.get(t);n&&(n.join(e),this.log(`Client ${t} joined room '${e}'`))}leaveRoom(t,e){let n=this.clients.get(t);n&&(n.leave(e),this.log(`Client ${t} left room '${e}'`))}getRoomClients(t){if(!this.io)return[];let e=this.io.sockets.adapter.rooms.get(t);return e?Array.from(e):[]}disconnectClient(t,e="Server disconnect"){let n=this.clients.get(t);n&&(n.disconnect(!0),this.log(`Disconnected client ${t}: ${e}`))}on(t,e){let n=this.eventHandlers.get(t)||[];n.push(e),this.eventHandlers.set(t,n),this.log(`Registered handler for '${t}'`)}onConnect(t){this.connectionHandlers.push(t),this.log("Registered connection handler")}onDisconnect(t){this.disconnectionHandlers.push(t),this.log("Registered disconnection handler")}off(t,e){let n=this.eventHandlers.get(t);if(!n)return;let i=n.indexOf(e);i!==-1&&(n.splice(i,1),this.log(`Removed handler for '${t}'`))}setClientData(t,e,n){let i=this.clientData.get(t)||{};i[e]=n,this.clientData.set(t,i)}getClientData(t,e){let n=this.clientData.get(t);return n?n[e]:void 0}getStats(){return{connectedClients:this.clients.size,totalConnections:this.stats.totalConnections,uptime:Date.now()-this.stats.startTime}}sendBridge(t,e,n){let i=this.clients.get(t);if(!i){this.log(`Cannot send bridge to client ${t}: not found`);return}try{let r={channel:e,data:JSON.stringify(n)};i.emit("bridge",r),this.log(`Bridge sent to ${t} on channel '${e}'`)}catch(r){this.log(`Error sending bridge to ${t}: ${r}`)}}broadcastBridge(t,e){if(this.io)try{let n={channel:t,data:JSON.stringify(e)};this.io.emit("bridge",n),this.log(`Bridge broadcast on channel '${t}'`)}catch(n){this.log(`Error broadcasting bridge: ${n}`)}}onBridge(t,e){this.bridgeHandlers.has(t)||this.bridgeHandlers.set(t,[]),this.bridgeHandlers.get(t).push(e),this.log(`Bridge handler registered for channel '${t}'`)}offBridge(t,e){let n=this.bridgeHandlers.get(t);if(n){let i=n.indexOf(e);i!==-1&&(n.splice(i,1),this.log(`Bridge handler removed for channel '${t}'`))}}handleBridgeMessage(t,e){try{let{channel:n,data:i}=e,r=JSON.parse(i),s=this.bridgeHandlers.get(n);s&&s.length>0&&s.forEach(a=>{try{a(t,r)}catch(c){this.log(`Error in bridge handler for '${n}': ${c}`)}});let o=this.bridgeHandlers.get("*");o&&o.length>0&&o.forEach(a=>{try{a(t,{channel:n,data:r})}catch(c){this.log(`Error in wildcard bridge handler: ${c}`)}}),(!s||s.length===0)&&(!o||o.length===0)&&this.log(`No bridge handler for channel '${n}' from ${t}`)}catch(n){this.log(`Error parsing bridge message from ${t}: ${n}`)}}async destroy(){await this.stop(),this.connectionHandlers=[],this.disconnectionHandlers=[],this.eventHandlers.clear(),this.log("Server destroyed")}handleConnection(t){let e=t.id;if(this.log(`Client connected: ${e}`),this.clients.size>=this.options.maxConnections){this.log(`Max connections reached, rejecting ${e}`),t.emit("error",{code:"MAX_CONNECTIONS",message:"Server is full"}),t.disconnect(!0);return}this.clients.set(e,t),this.clientData.set(e,{}),t.connectedAt=Date.now(),this.stats.totalConnections++,this.setupClientHandlers(t),this.connectionHandlers.forEach(n=>{try{n(e)}catch(i){this.log(`Error in connection handler: ${i}`)}}),t.on("ping",()=>{t.emit("pong")})}setupClientHandlers(t){let e=t.id;t.on("disconnect",n=>{this.log(`Client disconnected: ${e} (${n})`),this.clients.delete(e),this.clientData.delete(e),this.disconnectionHandlers.forEach(i=>{try{i(e,n)}catch(r){this.log(`Error in disconnection handler: ${r}`)}})}),t.on("bridge",n=>{this.handleBridgeMessage(e,n)}),this.eventHandlers.forEach((n,i)=>{t.on(i,r=>{n.forEach(s=>{try{s(e,r)}catch(o){this.log(`Error in event handler for '${i}': ${o}`)}})})})}log(t,e){this.options.debug&&(e!==void 0?console.warn(`[SocketIOServer] ${t}`,e):console.warn(`[SocketIOServer] ${t}`))}};f(v,"SocketIOServer");var u=v;import{Server as w}from"socket.io";import{io as k}from"socket.io-client";import{RTCPeerConnection as R,RTCDtlsTransport as E}from"werift";import{createServer as H}from"http";var $={connecting:5e3,channels_opening:5e3,ready:5e3,joined:0,aborted:0},y=class y{constructor(t){l(this,"signalingServer",null);l(this,"httpServer",null);l(this,"relaySocket",null);l(this,"clients",new Map);l(this,"options");l(this,"running",!1);l(this,"certificates",[]);l(this,"connectionHandlers",[]);l(this,"disconnectionHandlers",[]);l(this,"eventHandlers",new Map);l(this,"bridgeHandlers",new Map);l(this,"rooms",new Map);l(this,"chunkBuffers",new Map);l(this,"clientsReassembling",new Set);this.options={port:t.port,host:t.host??"0.0.0.0",cors:t.cors??{origin:"*"},maxConnections:t.maxConnections??1e3,pingInterval:t.pingInterval??25e3,pingTimeout:t.pingTimeout??5e3,debug:t.debug??!1,iceServers:t.iceServers??[{urls:"stun:stun.l.google.com:19302"}],signalingPath:t.signalingPath??(t.signalUrl?"/socket.io":"/utsp-signal"),signalUrl:t.signalUrl,sessionId:t.sessionId},this.on("bridge",(e,n)=>{if(!n||!n.channel)return;let{channel:i,data:r}=n,s=this.bridgeHandlers.get(i);s&&s.forEach(a=>a(e,r));let o=this.bridgeHandlers.get("*");o&&o.forEach(a=>a(e,{channel:i,data:r}))})}isRunning(){return this.running}getHttpServer(){return this.httpServer}async start(){if(!this.running){if(this.log("Starting WebRTC Server..."),this.certificates.length===0){this.log("Warm-up: Generating WebRTC Certificates...");try{let t=await E.SetupCertificate();t&&(this.certificates.push(t),this.log(`Warm-up: Certificate generated. Fingerprints: ${JSON.stringify(t.getFingerprints())}`),this.log("Warm-up: Done."))}catch{this.log("Warm-up: Static generateCertificate failed, falling back to on-demand generation.")}}if(this.options.signalUrl){this.log(`Connecting to PUBLIC RELAY: ${this.options.signalUrl} (Session: ${this.options.sessionId})`),await this.startRelayMode(),this.running=!0;return}return this.httpServer=H(),this.signalingServer=new w(this.httpServer,{path:this.options.signalingPath,cors:this.options.cors,pingInterval:this.options.pingInterval,pingTimeout:this.options.pingTimeout,transports:["websocket","polling"]}),this.setupSignaling(),new Promise((t,e)=>{this.httpServer?.listen(this.options.port,this.options.host,()=>{this.running=!0,this.log(`WebRTC Server listening on ${this.options.host}:${this.options.port} (Signaling path: ${this.options.signalingPath})`),t()}),this.httpServer?.on("error",n=>{this.error("Failed to start server",n),e(n)})})}}getSignalingInfo(){return this.options.signalUrl?{mode:"webrtc",transport:"webrtc",signalUrl:this.options.signalUrl,sessionId:this.options.sessionId}:{mode:"webrtc",transport:"webrtc",host:this.options.host,port:this.options.port,path:this.options.signalingPath}}async startRelayMode(){return new Promise((t,e)=>{if(!this.options.signalUrl)return e("No relay URL");this.relaySocket=k(this.options.signalUrl,{path:this.options.signalingPath,transports:["websocket","polling"]}),this.relaySocket.on("connect",()=>{this.log(`Connected to Relay Server: ${this.options.signalUrl}`),this.relaySocket?.emit("register-host",this.options.sessionId)}),this.relaySocket.on("host-registered",n=>{n&&n.sessionId?(this.options.sessionId=n.sessionId,this.log(`\u2705 Host Registered! Share this Session ID with clients: ${this.options.sessionId}`),t()):(this.log("Host registered (No ID returned from relay)"),t())}),this.relaySocket.on("error",n=>{this.error("Relay connection error",n),this.running||e(n)}),this.relaySocket.on("client-ready",({clientId:n})=>{this.handleRelayClient(n)}),this.relaySocket.on("signal",({from:n,type:i,data:r})=>{let s=i;i==="offer"&&(s="rtc:offer"),i==="answer"&&(s="rtc:answer"),i==="candidate"&&(s="rtc:candidate"),this.log(`Received signal from relay (type: ${i}, from: ${n})`),this.handleRelaySignal(n,s,r)})})}handleRelayClient(t){this.log(`New Client via Relay: ${t}`);let e={id:t,connected:!0,emit:(n,i)=>{this.relaySocket&&this.relaySocket.emit("signal",{target:t,type:n,data:i})},disconnect:()=>{},handshake:{address:"relay"}};this.setupClient(t,e)}handleRelaySignal(t,e,n){let i=this.clients.get(t);i&&i.socket.trigger(e,n)}setupClient(t,e){let n=Date.now();if(!e.on){let o=new Map;e.on=(a,c)=>{o.has(a)||o.set(a,[]),o.get(a).push(c)},e.trigger=(a,c)=>{let d=o.get(a);d&&d.forEach(h=>h(c))}}let i=Date.now(),r=new R({iceServers:this.options.iceServers,dtls:this.certificates[0]?{keys:{certPem:this.certificates[0].certPem,keyPem:this.certificates[0].privateKey,signatureHash:this.certificates[0].signatureHash}}:void 0});this.log(`\u23F1\uFE0F [${t}] RTCPeerConnection created in ${Date.now()-i}ms`),this.certificates[0]?this.log(`Using cached certificate for client ${t}`):this.warn(`No cached certificate available for client ${t}, generating new one (LAG WARNING)`);let s={id:t,socket:e,peer:r,connectedAt:Date.now(),data:{},state:"connecting",pendingEvents:[]};this.clients.set(t,s),this.setStateTimeout(t,"connecting"),this.setupWebRTC(s),this.log(`\u23F1\uFE0F [${t}] setupClient total: ${Date.now()-n}ms`)}setupSignaling(){this.signalingServer&&this.signalingServer.on("connection",async t=>{let e=t.id;this.log(`Signaling connection: ${e}`),this.setupClient(e,t)})}async setupWebRTC(t){let{id:e,socket:n,peer:i}=t,r=i.createDataChannel("reliable",{ordered:!0});t.dataChannelReliable=r,this.setupChannel(e,r,!0);let s=i.createDataChannel("unreliable",{ordered:!1,maxRetransmits:0});t.dataChannelUnreliable=s,this.setupChannel(e,s,!1),i.onicecandidate=o=>{o.candidate&&n.emit("rtc:candidate",o.candidate)},n.on("rtc:answer",async o=>{if(!(!o||typeof o!="object"||!o.type||!o.sdp))try{let a=this.stripRelayFromSDP(o.sdp);await i.setRemoteDescription({type:"answer",sdp:a})}catch(a){this.error(`Error setting remote description for ${e}`,a)}}),n.on("rtc:candidate",async o=>{if(!(!o||typeof o!="object"||!o.candidate))try{if(this.parseCandidateType(o.candidate)==="relay")return;await i.addIceCandidate(o)}catch(a){this.error(`Error adding ICE candidate for ${e}`,a)}}),n.on("disconnect",o=>{t.state!=="joined"&&this.handleDisconnect(e,o)});try{let o=await i.createOffer();await i.setLocalDescription(o);let a=this.stripRelayFromSDP(o.sdp||"");n.emit("rtc:offer",{type:"offer",sdp:a})}catch{n.disconnect(!0)}}setupChannel(t,e,n){e.onopen=()=>{this.log(`Client ${t}: ${n?"Reliable":"Unreliable"} channel open`),this.checkClientReady(t)},e.onmessage=i=>{let r=i.data,s;Buffer.isBuffer(r)?s=r.toString("utf8"):s=r;try{let o=JSON.parse(s);if(Array.isArray(o)&&o.length>=1){let[a,c]=o;this.handleEvent(t,a,c)}}catch{}}}setStateTimeout(t,e){let n=this.clients.get(t);if(!n)return;n.stateTimeout&&(clearTimeout(n.stateTimeout),n.stateTimeout=void 0);let i=$[e];i!==0&&(n.stateTimeout=setTimeout(()=>{let r=this.clients.get(t);r&&r.state===e&&(this.warn(`Client ${t}: Timeout in state '${e}' - aborting connection`),this.transitionTo(t,"aborted"),this.handleDisconnect(t,`timeout_${e}`))},i))}transitionTo(t,e){let n=this.clients.get(t);if(!n)return;let i=n.state;this.log(`[State] ${t}: ${i} \u2192 ${e}`),n.state=e,this.setStateTimeout(t,e)}checkClientReady(t){let e=this.clients.get(t);if(!e)return;let n=e.dataChannelReliable?.readyState==="open",i=e.dataChannelUnreliable?.readyState==="open";if(e.state==="connecting"&&(n||i)&&this.transitionTo(t,"channels_opening"),e.state==="channels_opening"&&n&&i){this.transitionTo(t,"ready");for(let{event:r,data:s}of e.pendingEvents)this.processEvent(t,r,s);e.pendingEvents=[]}}processEvent(t,e,n){let i=this.clients.get(t);e==="join"&&i?.state==="ready"&&(this.transitionTo(t,"joined"),this.connectionHandlers.forEach(s=>s(t)),this.startStatsMonitor(t));let r=this.eventHandlers.get(e);r&&r.forEach(s=>s(t,n))}handleEvent(t,e,n){let i=this.clients.get(t);if(e==="ping"){this.sendSafe(i?.dataChannelReliable,"pong",null,!0);return}if(e==="__ch:start"){if(this.clientsReassembling.has(t))return;let{id:r,count:s}=n;this.chunkBuffers.has(t)||this.chunkBuffers.set(t,new Map),this.chunkBuffers.get(t).set(r,{count:s,parts:new Array(s),received:0});return}if(e==="__ch:part"){let{id:r,idx:s,chunk:o}=n,a=this.chunkBuffers.get(t),c=a?.get(r);if(c&&(c.parts[s]=o,c.received++,c.received===c.count)){a.delete(r);try{this.clientsReassembling.add(t);let d=c.parts.join(""),[h,p]=JSON.parse(d);this.handleEvent(t,h,p)}catch{this.error(`Client ${t}: Failed to reassemble chunked payload`)}finally{this.clientsReassembling.delete(t)}}return}if(i&&i.state!=="ready"&&i.state!=="joined"){i.pendingEvents.push({event:e,data:n});return}this.processEvent(t,e,n)}handleDisconnect(t,e){let n=this.clients.get(t);if(n){n.stateTimeout&&clearTimeout(n.stateTimeout),this.stopStatsMonitor(t);try{n.peer.close()}catch{}this.clients.delete(t),this.rooms.forEach(i=>i.delete(t)),this.chunkBuffers.delete(t),this.clientsReassembling.delete(t),n.state==="joined"&&this.disconnectionHandlers.forEach(i=>i(t,e))}}async stop(){this.running=!1;for(let t of this.clients.values())t.peer.close(),t.socket.disconnect(!0);this.clients.clear(),this.signalingServer&&(this.signalingServer.close(),this.signalingServer=null),this.httpServer&&(this.httpServer.close(),this.httpServer=null)}preparePayload(t,e){return JSON.stringify([t,e],(n,i)=>{if(i&&typeof i=="object"){if(i.type==="Buffer"&&Array.isArray(i.data))return{_b64:Buffer.from(i.data).toString("base64")};if(i instanceof Uint8Array||i instanceof Int8Array||i instanceof Uint16Array||i instanceof Int16Array||i instanceof Uint32Array||i instanceof Int32Array||i instanceof Float32Array||i instanceof Float64Array)return{_b64:Buffer.from(i.buffer,i.byteOffset,i.byteLength).toString("base64")}}return i})}getChunks(t,e){let n=Math.ceil(t.length/e),i=new Array(n);for(let r=0,s=0;r<n;++r,s+=e)i[r]=t.substr(s,e);return i}prepareBinaryPacket(t,e){let n=Buffer.isBuffer(e)?e:Buffer.from(e.buffer,e.byteOffset,e.byteLength);if(typeof t=="number"){let s=Buffer.alloc(2);return s.writeUInt8(255,0),s.writeUInt8(t,1),Buffer.concat([s,n])}let i=Buffer.from(t,"utf8");if(i.length>=255)return this.error("Event name too long for binary fast path",t),null;let r=Buffer.alloc(1);return r.writeUInt8(i.length,0),Buffer.concat([r,i,n])}sendSafe(t,e,n,i=!1){if(!t||t.readyState!=="open"||!i&&t.bufferedAmount>64*1024)return;let r=1200;if((Buffer.isBuffer(n)||n instanceof Uint8Array)&&n.byteLength<=r){let o=this.prepareBinaryPacket(e,n);if(o){try{t.send(o)}catch{}return}}try{let o=this.preparePayload(String(e),n);if(o.length<=r)t.send(o);else{let a=Math.random().toString(36).substring(2,15),c=this.getChunks(o,r),d=JSON.stringify(["__ch:start",{id:a,count:c.length}]);t.send(d);for(let h=0;h<c.length;h++){let p=JSON.stringify(["__ch:part",{id:a,idx:h,chunk:c[h]}]);t.send(p)}}}catch{}}sendToClient(t,e,n){let i=this.clients.get(t);if(i&&i.dataChannelReliable?.readyState==="open")try{this.sendSafe(i.dataChannelReliable,e,n,!0)}catch(r){this.error(`Failed to send to ${t}`,r)}}sendToClientVolatile(t,e,n){let i=this.clients.get(t),r=i?.dataChannelUnreliable?.readyState==="open"?i.dataChannelUnreliable:i?.dataChannelReliable;this.sendSafe(r,e,n,!1)}broadcast(t,e){for(let n of this.clients.values())n.dataChannelReliable?.readyState==="open"&&this.sendSafe(n.dataChannelReliable,t,e,!0)}broadcastVolatile(t,e){for(let n of this.clients.values()){let i=n.dataChannelUnreliable?.readyState==="open"?n.dataChannelUnreliable:n.dataChannelReliable;this.sendSafe(i,t,e,!1)}}broadcastExcept(t,e,n){for(let[i,r]of this.clients)i!==t&&r.dataChannelReliable?.readyState==="open"&&this.sendSafe(r.dataChannelReliable,e,n,!0)}sendToRoom(t,e,n){let i=this.rooms.get(t);if(i)for(let r of i){let s=this.clients.get(r);s&&s.dataChannelReliable?.readyState==="open"&&this.sendSafe(s.dataChannelReliable,e,n,!0)}}getClients(){return Array.from(this.clients.keys())}getClientInfo(t){let e=this.clients.get(t);return e?{id:e.id,connectedAt:e.connectedAt,address:e.socket.handshake.address,data:e.data}:null}getClientData(t,e){let n=this.clients.get(t);return n?n.data[e]:void 0}setClientData(t,e,n){let i=this.clients.get(t);i&&(i.data[e]=n)}disconnectClient(t,e="Server disconnected client"){let n=this.clients.get(t);n&&(n.socket.disconnect(!0),this.handleDisconnect(t,e))}joinRoom(t,e){this.clients.has(t)&&(this.rooms.has(e)||this.rooms.set(e,new Set),this.rooms.get(e).add(t))}leaveRoom(t,e){let n=this.rooms.get(e);n&&(n.delete(t),n.size===0&&this.rooms.delete(e))}getRoomClients(t){return this.rooms.has(t)?Array.from(this.rooms.get(t)):[]}on(t,e){this.eventHandlers.has(t)||this.eventHandlers.set(t,[]),this.eventHandlers.get(t).push(e)}off(t,e){let n=this.eventHandlers.get(t);if(n){let i=n.indexOf(e);i!==-1&&n.splice(i,1)}}onConnect(t){this.connectionHandlers.push(t)}onDisconnect(t){this.disconnectionHandlers.push(t)}getStats(){return{connectedClients:this.clients.size,totalConnections:0,uptime:process.uptime()}}sendBridge(t,e,n){this.sendToClient(t,"bridge",{channel:e,data:n})}broadcastBridge(t,e){this.broadcast("bridge",{channel:t,data:e})}onBridge(t,e){this.bridgeHandlers.has(t)||this.bridgeHandlers.set(t,[]),this.bridgeHandlers.get(t).push(e)}offBridge(t,e){let n=this.bridgeHandlers.get(t);if(n){let i=n.indexOf(e);i!==-1&&n.splice(i,1)}}async destroy(){return this.stop()}async startStatsMonitor(t){let e=this.clients.get(t);e&&(this.stopStatsMonitor(t),e.statsInterval=setInterval(async()=>{let n=this.clients.get(t);if(!(!n||!n.peer||n.state!=="joined"))try{let i=await n.peer.getStats(),r=null;for(let s of i.values())if(s.type==="candidate-pair"&&s.state==="succeeded"){r=s;break}if(r){let s=i.get(r.localCandidateId),o=i.get(r.remoteCandidateId);this.options.debug&&this.log(`Stats for ${t}: RTT=${r.currentRoundTripTime?Math.round(r.currentRoundTripTime*1e3):"N/A"}ms Path=${s?.candidateType||"?"}<->${o?.candidateType||"?"}`),(s?.candidateType==="relay"||o?.candidateType==="relay")&&(this.error(`STRICT P2P VIOLATION for ${t}: Detected relay path in stats. DISCONNECTING.`),this.handleDisconnect(t,"Strict P2P Violation"))}}catch{}},5e3))}stopStatsMonitor(t){let e=this.clients.get(t);e&&e.statsInterval&&(clearInterval(e.statsInterval),e.statsInterval=void 0)}parseCandidateType(t){return t?t.includes("typ host")?"host":t.includes("typ srflx")?"srflx":t.includes("typ relay")?"relay":"unknown":"unknown"}stripRelayFromSDP(t){return t.split(`
2
2
  `).filter(e=>!e.includes("typ relay")).join(`
3
- `)}log(t,...e){this.options.debug&&console.warn(`[WebRTC-Server] ${t}`,...e)}warn(t,...e){this.options.debug&&console.warn(`[WebRTC-Server] ${t}`,...e)}error(t,...e){console.error(`[WebRTC-Server] \u274C ${t}`,...e)}};p(S,"WebRTCServer");var v=S;export{f as SocketIOServer,v as WebRTCServer};
3
+ `)}log(t,...e){this.options.debug&&console.warn(`[WebRTC-Server] ${t}`,...e)}warn(t,...e){this.options.debug&&console.warn(`[WebRTC-Server] ${t}`,...e)}error(t,...e){console.error(`[WebRTC-Server] \u274C ${t}`,...e)}};f(y,"WebRTCServer");var S=y;export{u as SocketIOServer,S as WebRTCServer};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utsp/network-server",
3
- "version": "0.17.4",
3
+ "version": "0.18.0-nightly.20260204225152.3333dbc",
4
4
  "description": "UTSP Network Server - Server-side communication adapters",
5
5
  "author": "THP Software",
6
6
  "license": "MIT",
@@ -49,10 +49,10 @@
49
49
  "access": "public"
50
50
  },
51
51
  "dependencies": {
52
+ "@utsp/types": "0.18.0-nightly.20260204225152.3333dbc",
52
53
  "socket.io": "^4.7.2",
53
54
  "socket.io-client": "^4.7.2",
54
- "werift": "^0.22.2",
55
- "@utsp/types": "0.17.4"
55
+ "werift": "^0.22.2"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/node": "^20.0.0",