@utsp/network-server 0.8.0-nightly.20251209024647.83e7069 → 0.8.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/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var o=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var m=Object.getOwnPropertyNames;var f=Object.prototype.hasOwnProperty;var S=(r,e,t)=>e in r?o(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t;var h=(r,e)=>o(r,"name",{value:e,configurable:!0});var C=(r,e)=>{for(var t in e)o(r,t,{get:e[t],enumerable:!0})},y=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of m(e))!f.call(r,i)&&i!==t&&o(r,i,{get:()=>e[i],enumerable:!(n=u(e,i))||n.enumerable});return r};var H=r=>y(o({},"__esModule",{value:!0}),r);var s=(r,e,t)=>(S(r,typeof e!="symbol"?e+"":e,t),t);var $={};C($,{SocketIOServer:()=>a});module.exports=H($);var d=require("socket.io"),g=require("http");var c=class c{constructor(e){s(this,"io",null);s(this,"httpServer",null);s(this,"options");s(this,"running",!1);s(this,"clients",new Map);s(this,"clientData",new Map);s(this,"connectionHandlers",[]);s(this,"disconnectionHandlers",[]);s(this,"eventHandlers",new Map);s(this,"stats",{totalConnections:0,startTime:0});if(!Number.isInteger(e.port)||e.port<0||e.port>65535)throw new Error(`SocketIOServer: Invalid port ${e.port} - must be an integer between 0 and 65535`);if(e.maxConnections!==void 0&&(!Number.isInteger(e.maxConnections)||e.maxConnections<=0))throw new Error(`SocketIOServer: Invalid maxConnections ${e.maxConnections} - must be a positive integer`);if(e.pingInterval!==void 0&&(!Number.isFinite(e.pingInterval)||e.pingInterval<=0))throw new Error(`SocketIOServer: Invalid pingInterval ${e.pingInterval} - must be a positive number`);if(e.pingTimeout!==void 0&&(!Number.isFinite(e.pingTimeout)||e.pingTimeout<=0))throw new Error(`SocketIOServer: Invalid pingTimeout ${e.pingTimeout} - must be a positive number`);this.options={port:e.port,host:e.host??"0.0.0.0",cors:e.cors??{origin:"*"},maxConnections:e.maxConnections??1e3,pingInterval:e.pingInterval??25e3,pingTimeout:e.pingTimeout??5e3,debug:e.debug??!1},this.log("Server initialized",{port:this.options.port,host:this.options.host,maxConnections:this.options.maxConnections})}isRunning(){return this.running}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((e,t)=>{try{this.httpServer=(0,g.createServer)(),this.io=new d.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}`),e()}),this.httpServer.on("error",n=>{this.log(`Server error: ${n.message}`),t(n)})}catch(n){t(n)}})}async stop(){if(this.running)return this.log("Stopping server..."),new Promise(e=>{this.clients.forEach(t=>{t.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,e()}),this.httpServer=null):(this.running=!1,e())})}getClients(){return Array.from(this.clients.keys())}getClientInfo(e){let t=this.clients.get(e);return t?{id:e,connectedAt:t.connectedAt||Date.now(),address:t.handshake.address,data:this.clientData.get(e)||{}}:null}sendToClient(e,t,n){let i=this.clients.get(e);if(!i){this.log(`Cannot send to client ${e}: not found`);return}i.emit(t,n)}sendToClientVolatile(e,t,n){let i=this.clients.get(e);if(!i){this.log(`Cannot send volatile to client ${e}: not found`);return}i.compress(!1).emit(t,n)}broadcast(e,t){this.io&&(this.io.emit(e,t),this.log(`Broadcast '${e}' to all clients`))}broadcastVolatile(e,t){this.io&&(this.io.volatile.emit(e,t),this.log(`Broadcast volatile '${e}' to all clients`))}broadcastExcept(e,t,n){let i=this.clients.get(e);i&&(i.broadcast.emit(t,n),this.log(`Broadcast '${t}' except ${e}`))}sendToRoom(e,t,n){this.io&&(this.io.to(e).emit(t,n),this.log(`Sent '${t}' to room '${e}'`))}joinRoom(e,t){let n=this.clients.get(e);n&&(n.join(t),this.log(`Client ${e} joined room '${t}'`))}leaveRoom(e,t){let n=this.clients.get(e);n&&(n.leave(t),this.log(`Client ${e} left room '${t}'`))}getRoomClients(e){if(!this.io)return[];let t=this.io.sockets.adapter.rooms.get(e);return t?Array.from(t):[]}disconnectClient(e,t="Server disconnect"){let n=this.clients.get(e);n&&(n.disconnect(!0),this.log(`Disconnected client ${e}: ${t}`))}on(e,t){let n=this.eventHandlers.get(e)||[];n.push(t),this.eventHandlers.set(e,n),this.log(`Registered handler for '${e}'`)}onConnect(e){this.connectionHandlers.push(e),this.log("Registered connection handler")}onDisconnect(e){this.disconnectionHandlers.push(e),this.log("Registered disconnection handler")}off(e,t){let n=this.eventHandlers.get(e);if(!n)return;let i=n.indexOf(t);i!==-1&&(n.splice(i,1),this.log(`Removed handler for '${e}'`))}setClientData(e,t,n){let i=this.clientData.get(e)||{};i[t]=n,this.clientData.set(e,i)}getClientData(e,t){let n=this.clientData.get(e);return n?n[t]:void 0}getStats(){return{connectedClients:this.clients.size,totalConnections:this.stats.totalConnections,uptime:Date.now()-this.stats.startTime}}async destroy(){await this.stop(),this.connectionHandlers=[],this.disconnectionHandlers=[],this.eventHandlers.clear(),this.log("Server destroyed")}handleConnection(e){let t=e.id;if(this.log(`Client connected: ${t}`),this.clients.size>=this.options.maxConnections){this.log(`Max connections reached, rejecting ${t}`),e.emit("error",{code:"MAX_CONNECTIONS",message:"Server is full"}),e.disconnect(!0);return}this.clients.set(t,e),this.clientData.set(t,{}),e.connectedAt=Date.now(),this.stats.totalConnections++,this.setupClientHandlers(e),this.connectionHandlers.forEach(n=>{try{n(t)}catch(i){this.log(`Error in connection handler: ${i}`)}}),e.on("ping",()=>{e.emit("pong")})}setupClientHandlers(e){let t=e.id;e.on("disconnect",n=>{this.log(`Client disconnected: ${t} (${n})`),this.clients.delete(t),this.clientData.delete(t),this.disconnectionHandlers.forEach(i=>{try{i(t,n)}catch(l){this.log(`Error in disconnection handler: ${l}`)}})}),this.eventHandlers.forEach((n,i)=>{e.on(i,l=>{n.forEach(v=>{try{v(t,l)}catch(p){this.log(`Error in event handler for '${i}': ${p}`)}})})})}log(e,t){this.options.debug&&(t!==void 0?console.warn(`[SocketIOServer] ${e}`,t):console.warn(`[SocketIOServer] ${e}`))}};h(c,"SocketIOServer");var a=c;0&&(module.exports={SocketIOServer});
1
+ "use strict";var d=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var $=Object.prototype.hasOwnProperty;var y=(r,t,e)=>t in r?d(r,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[t]=e;var p=(r,t)=>d(r,"name",{value:t,configurable:!0});var C=(r,t)=>{for(var e in t)d(r,e,{get:t[e],enumerable:!0})},H=(r,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of S(t))!$.call(r,i)&&i!==e&&d(r,i,{get:()=>t[i],enumerable:!(n=m(t,i))||n.enumerable});return r};var w=r=>H(d({},"__esModule",{value:!0}),r);var s=(r,t,e)=>(y(r,typeof t!="symbol"?t+"":t,e),e);var b={};C(b,{SocketIOServer:()=>c});module.exports=w(b);var u=require("socket.io"),f=require("http");var v=class v{constructor(t){s(this,"io",null);s(this,"httpServer",null);s(this,"options");s(this,"running",!1);s(this,"clients",new Map);s(this,"clientData",new Map);s(this,"connectionHandlers",[]);s(this,"disconnectionHandlers",[]);s(this,"eventHandlers",new Map);s(this,"stats",{totalConnections:0,startTime:0});s(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}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,f.createServer)(),this.io=new u.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())})}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 o={channel:e,data:JSON.stringify(n)};i.emit("bridge",o),this.log(`Bridge sent to ${t} on channel '${e}'`)}catch(o){this.log(`Error sending bridge to ${t}: ${o}`)}}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,o=JSON.parse(i),a=this.bridgeHandlers.get(n);a&&a.length>0&&a.forEach(g=>{try{g(t,o)}catch(h){this.log(`Error in bridge handler for '${n}': ${h}`)}});let l=this.bridgeHandlers.get("*");l&&l.length>0&&l.forEach(g=>{try{g(t,{channel:n,data:o})}catch(h){this.log(`Error in wildcard bridge handler: ${h}`)}}),(!a||a.length===0)&&(!l||l.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(o){this.log(`Error in disconnection handler: ${o}`)}})}),t.on("bridge",n=>{this.handleBridgeMessage(e,n)}),this.eventHandlers.forEach((n,i)=>{t.on(i,o=>{n.forEach(a=>{try{a(e,o)}catch(l){this.log(`Error in event handler for '${i}': ${l}`)}})})})}log(t,e){this.options.debug&&(e!==void 0?console.warn(`[SocketIOServer] ${t}`,e):console.warn(`[SocketIOServer] ${t}`))}};p(v,"SocketIOServer");var c=v;0&&(module.exports={SocketIOServer});
package/dist/index.d.ts CHANGED
@@ -516,6 +516,110 @@ declare class SocketIOServer implements INetworkServer {
516
516
  totalConnections: number;
517
517
  uptime: number;
518
518
  };
519
+ private bridgeHandlers;
520
+ /**
521
+ * Send data to a client through the bridge channel
522
+ *
523
+ * The bridge channel is a separate communication channel that bypasses
524
+ * the UTSP protocol. It allows sending arbitrary JSON data to the client
525
+ * application (React, HTML, etc.) that wraps the UTSP client.
526
+ *
527
+ * Use cases:
528
+ * - Change background image/video behind the terminal
529
+ * - Update external UI elements (scoreboards, notifications)
530
+ * - Trigger external audio/music players
531
+ * - Synchronize state with the parent application
532
+ *
533
+ * @param clientId - Client socket ID
534
+ * @param channel - Channel name (e.g., 'background', 'score', 'notification')
535
+ * @param data - Any JSON-serializable data
536
+ *
537
+ * @example
538
+ * ```typescript
539
+ * // Change background image
540
+ * server.sendBridge(clientId, 'background', { image: '/images/forest.jpg' });
541
+ *
542
+ * // Update external scoreboard
543
+ * server.sendBridge(clientId, 'score', { value: 1500, combo: 3 });
544
+ *
545
+ * // Trigger notification in parent app
546
+ * server.sendBridge(clientId, 'notification', {
547
+ * type: 'achievement',
548
+ * title: 'First Blood!',
549
+ * icon: '🏆'
550
+ * });
551
+ * ```
552
+ */
553
+ sendBridge(clientId: string, channel: string, data: unknown): void;
554
+ /**
555
+ * Broadcast data to all clients through the bridge channel
556
+ *
557
+ * @param channel - Channel name
558
+ * @param data - Any JSON-serializable data
559
+ *
560
+ * @example
561
+ * ```typescript
562
+ * // Change background for all clients
563
+ * server.broadcastBridge('background', { image: '/images/night.jpg' });
564
+ *
565
+ * // Global announcement
566
+ * server.broadcastBridge('announcement', {
567
+ * message: 'Server restarting in 5 minutes',
568
+ * type: 'warning'
569
+ * });
570
+ * ```
571
+ */
572
+ broadcastBridge(channel: string, data: unknown): void;
573
+ /**
574
+ * Register a handler for incoming bridge messages from clients
575
+ *
576
+ * Allows the server to receive custom data from the client application
577
+ * (React buttons, external controls, etc.) through the bridge channel.
578
+ *
579
+ * Use the special channel `'*'` (wildcard) to receive ALL bridge messages.
580
+ * When using wildcard, the handler receives `{ channel: string, data: T }` as the data.
581
+ *
582
+ * @param channel - Channel name to listen for, or '*' for all channels
583
+ * @param handler - Callback function receiving clientId and parsed data
584
+ *
585
+ * @example
586
+ * ```typescript
587
+ * // Handle custom action buttons from React UI
588
+ * server.onBridge('action', (clientId, data) => {
589
+ * if (data.type === 'jump') {
590
+ * game.playerJump(clientId);
591
+ * } else if (data.type === 'attack') {
592
+ * game.playerAttack(clientId);
593
+ * }
594
+ * });
595
+ *
596
+ * // Handle external menu interactions
597
+ * server.onBridge('menu', (clientId, data) => {
598
+ * if (data.action === 'pause') {
599
+ * game.pauseForPlayer(clientId);
600
+ * }
601
+ * });
602
+ *
603
+ * // Wildcard: receive ALL bridge messages
604
+ * server.onBridge('*', (clientId, envelope) => {
605
+ * const { channel, data } = envelope;
606
+ * console.log(`Bridge from ${clientId} on ${channel}:`, data);
607
+ * });
608
+ * ```
609
+ */
610
+ onBridge<T = unknown>(channel: string, handler: (clientId: string, data: T) => void): void;
611
+ /**
612
+ * Remove a bridge handler for a specific channel
613
+ *
614
+ * @param channel - Channel name
615
+ * @param handler - The exact handler function to remove
616
+ */
617
+ offBridge<T = unknown>(channel: string, handler: (clientId: string, data: T) => void): void;
618
+ /**
619
+ * Internal: Handle incoming bridge message from a client
620
+ * @private
621
+ */
622
+ private handleBridgeMessage;
519
623
  /**
520
624
  * Destroy the server and clean up all resources
521
625
  *
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- var c=Object.defineProperty;var v=(s,e,t)=>e in s?c(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t;var h=(s,e)=>c(s,"name",{value:e,configurable:!0});var r=(s,e,t)=>(v(s,typeof e!="symbol"?e+"":e,t),t);import{Server as p}from"socket.io";import{createServer as u}from"http";var l=class l{constructor(e){r(this,"io",null);r(this,"httpServer",null);r(this,"options");r(this,"running",!1);r(this,"clients",new Map);r(this,"clientData",new Map);r(this,"connectionHandlers",[]);r(this,"disconnectionHandlers",[]);r(this,"eventHandlers",new Map);r(this,"stats",{totalConnections:0,startTime:0});if(!Number.isInteger(e.port)||e.port<0||e.port>65535)throw new Error(`SocketIOServer: Invalid port ${e.port} - must be an integer between 0 and 65535`);if(e.maxConnections!==void 0&&(!Number.isInteger(e.maxConnections)||e.maxConnections<=0))throw new Error(`SocketIOServer: Invalid maxConnections ${e.maxConnections} - must be a positive integer`);if(e.pingInterval!==void 0&&(!Number.isFinite(e.pingInterval)||e.pingInterval<=0))throw new Error(`SocketIOServer: Invalid pingInterval ${e.pingInterval} - must be a positive number`);if(e.pingTimeout!==void 0&&(!Number.isFinite(e.pingTimeout)||e.pingTimeout<=0))throw new Error(`SocketIOServer: Invalid pingTimeout ${e.pingTimeout} - must be a positive number`);this.options={port:e.port,host:e.host??"0.0.0.0",cors:e.cors??{origin:"*"},maxConnections:e.maxConnections??1e3,pingInterval:e.pingInterval??25e3,pingTimeout:e.pingTimeout??5e3,debug:e.debug??!1},this.log("Server initialized",{port:this.options.port,host:this.options.host,maxConnections:this.options.maxConnections})}isRunning(){return this.running}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((e,t)=>{try{this.httpServer=u(),this.io=new p(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}`),e()}),this.httpServer.on("error",n=>{this.log(`Server error: ${n.message}`),t(n)})}catch(n){t(n)}})}async stop(){if(this.running)return this.log("Stopping server..."),new Promise(e=>{this.clients.forEach(t=>{t.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,e()}),this.httpServer=null):(this.running=!1,e())})}getClients(){return Array.from(this.clients.keys())}getClientInfo(e){let t=this.clients.get(e);return t?{id:e,connectedAt:t.connectedAt||Date.now(),address:t.handshake.address,data:this.clientData.get(e)||{}}:null}sendToClient(e,t,n){let i=this.clients.get(e);if(!i){this.log(`Cannot send to client ${e}: not found`);return}i.emit(t,n)}sendToClientVolatile(e,t,n){let i=this.clients.get(e);if(!i){this.log(`Cannot send volatile to client ${e}: not found`);return}i.compress(!1).emit(t,n)}broadcast(e,t){this.io&&(this.io.emit(e,t),this.log(`Broadcast '${e}' to all clients`))}broadcastVolatile(e,t){this.io&&(this.io.volatile.emit(e,t),this.log(`Broadcast volatile '${e}' to all clients`))}broadcastExcept(e,t,n){let i=this.clients.get(e);i&&(i.broadcast.emit(t,n),this.log(`Broadcast '${t}' except ${e}`))}sendToRoom(e,t,n){this.io&&(this.io.to(e).emit(t,n),this.log(`Sent '${t}' to room '${e}'`))}joinRoom(e,t){let n=this.clients.get(e);n&&(n.join(t),this.log(`Client ${e} joined room '${t}'`))}leaveRoom(e,t){let n=this.clients.get(e);n&&(n.leave(t),this.log(`Client ${e} left room '${t}'`))}getRoomClients(e){if(!this.io)return[];let t=this.io.sockets.adapter.rooms.get(e);return t?Array.from(t):[]}disconnectClient(e,t="Server disconnect"){let n=this.clients.get(e);n&&(n.disconnect(!0),this.log(`Disconnected client ${e}: ${t}`))}on(e,t){let n=this.eventHandlers.get(e)||[];n.push(t),this.eventHandlers.set(e,n),this.log(`Registered handler for '${e}'`)}onConnect(e){this.connectionHandlers.push(e),this.log("Registered connection handler")}onDisconnect(e){this.disconnectionHandlers.push(e),this.log("Registered disconnection handler")}off(e,t){let n=this.eventHandlers.get(e);if(!n)return;let i=n.indexOf(t);i!==-1&&(n.splice(i,1),this.log(`Removed handler for '${e}'`))}setClientData(e,t,n){let i=this.clientData.get(e)||{};i[t]=n,this.clientData.set(e,i)}getClientData(e,t){let n=this.clientData.get(e);return n?n[t]:void 0}getStats(){return{connectedClients:this.clients.size,totalConnections:this.stats.totalConnections,uptime:Date.now()-this.stats.startTime}}async destroy(){await this.stop(),this.connectionHandlers=[],this.disconnectionHandlers=[],this.eventHandlers.clear(),this.log("Server destroyed")}handleConnection(e){let t=e.id;if(this.log(`Client connected: ${t}`),this.clients.size>=this.options.maxConnections){this.log(`Max connections reached, rejecting ${t}`),e.emit("error",{code:"MAX_CONNECTIONS",message:"Server is full"}),e.disconnect(!0);return}this.clients.set(t,e),this.clientData.set(t,{}),e.connectedAt=Date.now(),this.stats.totalConnections++,this.setupClientHandlers(e),this.connectionHandlers.forEach(n=>{try{n(t)}catch(i){this.log(`Error in connection handler: ${i}`)}}),e.on("ping",()=>{e.emit("pong")})}setupClientHandlers(e){let t=e.id;e.on("disconnect",n=>{this.log(`Client disconnected: ${t} (${n})`),this.clients.delete(t),this.clientData.delete(t),this.disconnectionHandlers.forEach(i=>{try{i(t,n)}catch(o){this.log(`Error in disconnection handler: ${o}`)}})}),this.eventHandlers.forEach((n,i)=>{e.on(i,o=>{n.forEach(d=>{try{d(t,o)}catch(g){this.log(`Error in event handler for '${i}': ${g}`)}})})})}log(e,t){this.options.debug&&(t!==void 0?console.warn(`[SocketIOServer] ${e}`,t):console.warn(`[SocketIOServer] ${e}`))}};h(l,"SocketIOServer");var a=l;export{a as SocketIOServer};
1
+ var v=Object.defineProperty;var u=(o,t,e)=>t in o?v(o,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):o[t]=e;var p=(o,t)=>v(o,"name",{value:t,configurable:!0});var r=(o,t,e)=>(u(o,typeof t!="symbol"?t+"":t,e),e);import{Server as f}from"socket.io";import{createServer as m}from"http";var h=class h{constructor(t){r(this,"io",null);r(this,"httpServer",null);r(this,"options");r(this,"running",!1);r(this,"clients",new Map);r(this,"clientData",new Map);r(this,"connectionHandlers",[]);r(this,"disconnectionHandlers",[]);r(this,"eventHandlers",new Map);r(this,"stats",{totalConnections:0,startTime:0});r(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}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=m(),this.io=new f(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())})}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 s={channel:e,data:JSON.stringify(n)};i.emit("bridge",s),this.log(`Bridge sent to ${t} on channel '${e}'`)}catch(s){this.log(`Error sending bridge to ${t}: ${s}`)}}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,s=JSON.parse(i),a=this.bridgeHandlers.get(n);a&&a.length>0&&a.forEach(d=>{try{d(t,s)}catch(c){this.log(`Error in bridge handler for '${n}': ${c}`)}});let l=this.bridgeHandlers.get("*");l&&l.length>0&&l.forEach(d=>{try{d(t,{channel:n,data:s})}catch(c){this.log(`Error in wildcard bridge handler: ${c}`)}}),(!a||a.length===0)&&(!l||l.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(s){this.log(`Error in disconnection handler: ${s}`)}})}),t.on("bridge",n=>{this.handleBridgeMessage(e,n)}),this.eventHandlers.forEach((n,i)=>{t.on(i,s=>{n.forEach(a=>{try{a(e,s)}catch(l){this.log(`Error in event handler for '${i}': ${l}`)}})})})}log(t,e){this.options.debug&&(e!==void 0?console.warn(`[SocketIOServer] ${t}`,e):console.warn(`[SocketIOServer] ${t}`))}};p(h,"SocketIOServer");var g=h;export{g as SocketIOServer};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utsp/network-server",
3
- "version": "0.8.0-nightly.20251209024647.83e7069",
3
+ "version": "0.8.0",
4
4
  "description": "UTSP Network Server - Server-side communication adapters",
5
5
  "author": "THP Software",
6
6
  "license": "MIT",
@@ -49,8 +49,8 @@
49
49
  "access": "public"
50
50
  },
51
51
  "dependencies": {
52
- "@utsp/types": "0.8.0-nightly.20251209024647.83e7069",
53
- "socket.io": "^4.7.2"
52
+ "socket.io": "^4.7.2",
53
+ "@utsp/types": "0.8.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/node": "^20.0.0",