@utsp/network-client 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 c=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var C=(o,t,e)=>t in o?c(o,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):o[t]=e;var g=(o,t)=>c(o,"name",{value:t,configurable:!0});var I=(o,t)=>{for(var e in t)c(o,e,{get:t[e],enumerable:!0})},$=(o,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of y(t))!v.call(o,s)&&s!==e&&c(o,s,{get:()=>t[s],enumerable:!(n=w(t,s))||n.enumerable});return o};var R=o=>$(c({},"__esModule",{value:!0}),o);var r=(o,t,e)=>(C(o,typeof t!="symbol"?t+"":t,e),e);var b={};I(b,{NetworkState:()=>d.NetworkState,SocketIOClient:()=>a});module.exports=R(b);var d=require("@utsp/types");var u=require("socket.io-client"),i=require("@utsp/types");var m=class m{constructor(t){r(this,"socket",null);r(this,"options");r(this,"_state",i.NetworkState.Disconnected);r(this,"pingTimestamp",0);r(this,"latency",0);r(this,"pingInterval",null);if(!t.url||typeof t.url!="string")throw new Error("SocketIOClient: Invalid URL - must be a non-empty string");try{if(!new globalThis.URL(t.url).protocol)throw new Error("Invalid protocol")}catch{throw new Error(`SocketIOClient: Invalid URL format - ${t.url}`)}if(t.reconnectDelay!==void 0&&(t.reconnectDelay<0||!Number.isFinite(t.reconnectDelay)))throw new Error(`SocketIOClient: Invalid reconnectDelay - must be a positive number (got ${t.reconnectDelay})`);if(t.maxReconnectAttempts!==void 0&&(!Number.isInteger(t.maxReconnectAttempts)||t.maxReconnectAttempts<0))throw new Error(`SocketIOClient: Invalid maxReconnectAttempts - must be a positive integer (got ${t.maxReconnectAttempts})`);if(t.timeout!==void 0&&(t.timeout<=0||!Number.isFinite(t.timeout)))throw new Error(`SocketIOClient: Invalid timeout - must be a positive number (got ${t.timeout})`);this.options={url:t.url,autoReconnect:t.autoReconnect??!0,reconnectDelay:t.reconnectDelay??1e3,maxReconnectAttempts:t.maxReconnectAttempts??10,timeout:t.timeout??5e3,auth:t.auth??{},debug:t.debug??!1},this.log("Client initialized",{url:this.options.url,autoReconnect:this.options.autoReconnect})}get state(){return this._state}isConnected(){return this._state===i.NetworkState.Connected&&this.socket?.connected===!0}async connect(){if(this.socket&&this.isConnected()){this.log("Already connected");return}return this.log(`Connecting to ${this.options.url}...`),this._state=i.NetworkState.Connecting,new Promise((t,e)=>{this.socket=(0,u.io)(this.options.url,{auth:this.options.auth,reconnection:this.options.autoReconnect,reconnectionDelay:this.options.reconnectDelay,reconnectionAttempts:this.options.maxReconnectAttempts,timeout:this.options.timeout,transports:["websocket"]}),this.socket.on("connect",()=>{this.log(`Connected with ID: ${this.socket?.id}`),this._state=i.NetworkState.Connected,this.startPingMonitor(),t()}),this.socket.on("connect_error",n=>{this.log(`Connection error: ${n.message}`),this._state=i.NetworkState.Error,e(n)}),this.socket.on("disconnect",n=>{this.log(`Disconnected: ${n}`),this._state=i.NetworkState.Disconnected,this.stopPingMonitor()}),this.socket.on("reconnect_attempt",n=>{this.log(`Reconnection attempt ${n}...`),this._state=i.NetworkState.Reconnecting}),this.socket.on("reconnect",n=>{this.log(`Reconnected after ${n} attempts`),this._state=i.NetworkState.Connected,this.startPingMonitor()}),this.socket.on("reconnect_failed",()=>{this.log("Reconnection failed"),this._state=i.NetworkState.Error}),this.socket.on("pong",()=>{this.latency=Date.now()-this.pingTimestamp,this.log(`Ping: ${this.latency}ms`)})})}disconnect(){this.socket&&(this.log("Disconnecting..."),this.stopPingMonitor(),this.socket.disconnect(),this._state=i.NetworkState.Disconnected)}send(t,e){if(!t||typeof t!="string"){this.log(`Cannot send: invalid event name (${typeof t})`);return}if(!this.isConnected()||!this.socket){this.log(`Cannot send '${t}': not connected`);return}try{this.socket.volatile.emit(t,e)}catch(n){this.log(`Error sending '${t}':`,n)}}on(t,e){if(!t||typeof t!="string")throw new Error(`Invalid event name: ${t}`);if(typeof e!="function")throw new Error(`Invalid handler for event '${t}': must be a function`);if(!this.socket){this.log(`Cannot listen to '${t}': socket not initialized`);return}this.socket.on(t,e),this.log(`Listening to '${t}'`)}off(t,e){this.socket&&(this.socket.off(t,e),this.log(`Stopped listening to '${t}'`))}removeAllListeners(t){this.socket&&(t?(this.socket.removeAllListeners(t),this.log(`Removed all listeners for '${t}'`)):(this.socket.removeAllListeners(),this.log("Removed all listeners")))}async request(t,e,n=5e3){if(!t||typeof t!="string")throw new Error(`Invalid event name: ${t}`);if(!Number.isFinite(n)||n<=0)throw new Error(`Invalid timeout: ${n} (must be a positive number)`);if(!this.isConnected())throw new Error("Cannot send request: not connected to server");return new Promise((s,p)=>{let l=`${t}_response`,f=setTimeout(()=>{this.off(l,h),p(new Error(`Request timeout after ${n}ms: ${t}`))},n),h=g(k=>{clearTimeout(f),this.off(l,h),s(k)},"responseHandler");this.on(l,h),this.send(t,e),this.log(`Request sent: ${t} (timeout: ${n}ms)`)})}getPing(){return this.latency}destroy(){this.log("Destroying client..."),this.stopPingMonitor(),this.socket&&(this.socket.removeAllListeners(),this.socket.disconnect(),this.socket=null),this._state=i.NetworkState.Disconnected,this.log("Client destroyed")}startPingMonitor(){this.pingInterval||(this.pingInterval=setInterval(()=>{this.isConnected()&&this.socket&&(this.pingTimestamp=Date.now(),this.socket.emit("ping"))},2e3),this.log("Ping monitor started"))}stopPingMonitor(){this.pingInterval&&(clearInterval(this.pingInterval),this.pingInterval=null,this.log("Ping monitor stopped"))}log(t,e){this.options.debug&&(e!==void 0?console.warn(`[SocketIOClient] ${t}`,e):console.warn(`[SocketIOClient] ${t}`))}};g(m,"SocketIOClient");var a=m;0&&(module.exports={NetworkState,SocketIOClient});
1
+ "use strict";var l=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var y=Object.prototype.hasOwnProperty;var b=(n,t,e)=>t in n?l(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e;var u=(n,t)=>l(n,"name",{value:t,configurable:!0});var $=(n,t)=>{for(var e in t)l(n,e,{get:t[e],enumerable:!0})},C=(n,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of w(t))!y.call(n,s)&&s!==e&&l(n,s,{get:()=>t[s],enumerable:!(i=v(t,s))||i.enumerable});return n};var I=n=>C(l({},"__esModule",{value:!0}),n);var r=(n,t,e)=>(b(n,typeof t!="symbol"?t+"":t,e),e);var R={};$(R,{NetworkState:()=>f.NetworkState,SocketIOClient:()=>d});module.exports=I(R);var f=require("@utsp/types");var m=require("socket.io-client"),o=require("@utsp/types");var p=class p{constructor(t){r(this,"socket",null);r(this,"options");r(this,"_state",o.NetworkState.Disconnected);r(this,"pingTimestamp",0);r(this,"latency",0);r(this,"pingInterval",null);r(this,"bridgeHandlers",new Map);r(this,"bridgeSetup",!1);if(!t.url||typeof t.url!="string")throw new Error("SocketIOClient: Invalid URL - must be a non-empty string");try{if(!new globalThis.URL(t.url).protocol)throw new Error("Invalid protocol")}catch{throw new Error(`SocketIOClient: Invalid URL format - ${t.url}`)}if(t.reconnectDelay!==void 0&&(t.reconnectDelay<0||!Number.isFinite(t.reconnectDelay)))throw new Error(`SocketIOClient: Invalid reconnectDelay - must be a positive number (got ${t.reconnectDelay})`);if(t.maxReconnectAttempts!==void 0&&(!Number.isInteger(t.maxReconnectAttempts)||t.maxReconnectAttempts<0))throw new Error(`SocketIOClient: Invalid maxReconnectAttempts - must be a positive integer (got ${t.maxReconnectAttempts})`);if(t.timeout!==void 0&&(t.timeout<=0||!Number.isFinite(t.timeout)))throw new Error(`SocketIOClient: Invalid timeout - must be a positive number (got ${t.timeout})`);this.options={url:t.url,autoReconnect:t.autoReconnect??!0,reconnectDelay:t.reconnectDelay??1e3,maxReconnectAttempts:t.maxReconnectAttempts??10,timeout:t.timeout??5e3,auth:t.auth??{},debug:t.debug??!1},this.log("Client initialized",{url:this.options.url,autoReconnect:this.options.autoReconnect})}get state(){return this._state}isConnected(){return this._state===o.NetworkState.Connected&&this.socket?.connected===!0}async connect(){if(this.socket&&this.isConnected()){this.log("Already connected");return}return this.log(`Connecting to ${this.options.url}...`),this._state=o.NetworkState.Connecting,this.bridgeSetup=!1,new Promise((t,e)=>{this.socket=(0,m.io)(this.options.url,{auth:this.options.auth,reconnection:this.options.autoReconnect,reconnectionDelay:this.options.reconnectDelay,reconnectionAttempts:this.options.maxReconnectAttempts,timeout:this.options.timeout,transports:["websocket"]}),this.socket.on("connect",()=>{this.log(`Connected with ID: ${this.socket?.id}`),this._state=o.NetworkState.Connected,this.startPingMonitor(),this.bridgeHandlers.size>0&&this.setupBridgeListener(),t()}),this.socket.on("connect_error",i=>{this.log(`Connection error: ${i.message}`),this._state=o.NetworkState.Error,e(i)}),this.socket.on("disconnect",i=>{this.log(`Disconnected: ${i}`),this._state=o.NetworkState.Disconnected,this.stopPingMonitor()}),this.socket.on("reconnect_attempt",i=>{this.log(`Reconnection attempt ${i}...`),this._state=o.NetworkState.Reconnecting}),this.socket.on("reconnect",i=>{this.log(`Reconnected after ${i} attempts`),this._state=o.NetworkState.Connected,this.startPingMonitor()}),this.socket.on("reconnect_failed",()=>{this.log("Reconnection failed"),this._state=o.NetworkState.Error}),this.socket.on("pong",()=>{this.latency=Date.now()-this.pingTimestamp,this.log(`Ping: ${this.latency}ms`)})})}disconnect(){this.socket&&(this.log("Disconnecting..."),this.stopPingMonitor(),this.socket.disconnect(),this._state=o.NetworkState.Disconnected)}send(t,e){if(!t||typeof t!="string"){this.log(`Cannot send: invalid event name (${typeof t})`);return}if(!this.isConnected()||!this.socket){this.log(`Cannot send '${t}': not connected`);return}try{this.socket.volatile.emit(t,e)}catch(i){this.log(`Error sending '${t}':`,i)}}on(t,e){if(!t||typeof t!="string")throw new Error(`Invalid event name: ${t}`);if(typeof e!="function")throw new Error(`Invalid handler for event '${t}': must be a function`);if(!this.socket){this.log(`Cannot listen to '${t}': socket not initialized`);return}this.socket.on(t,e),this.log(`Listening to '${t}'`)}off(t,e){this.socket&&(this.socket.off(t,e),this.log(`Stopped listening to '${t}'`))}removeAllListeners(t){this.socket&&(t?(this.socket.removeAllListeners(t),this.log(`Removed all listeners for '${t}'`)):(this.socket.removeAllListeners(),this.log("Removed all listeners")))}async request(t,e,i=5e3){if(!t||typeof t!="string")throw new Error(`Invalid event name: ${t}`);if(!Number.isFinite(i)||i<=0)throw new Error(`Invalid timeout: ${i} (must be a positive number)`);if(!this.isConnected())throw new Error("Cannot send request: not connected to server");return new Promise((s,a)=>{let c=`${t}_response`,h=setTimeout(()=>{this.off(c,g),a(new Error(`Request timeout after ${i}ms: ${t}`))},i),g=u(k=>{clearTimeout(h),this.off(c,g),s(k)},"responseHandler");this.on(c,g),this.send(t,e),this.log(`Request sent: ${t} (timeout: ${i}ms)`)})}getPing(){return this.latency}sendBridge(t,e){if(!this.isConnected()||!this.socket){this.log(`Cannot send bridge '${t}': not connected`);return}try{let i={channel:t,data:JSON.stringify(e)};this.socket.emit("bridge",i),this.log(`Bridge sent on channel '${t}'`)}catch(i){this.log(`Error sending bridge: ${i}`)}}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}'`),this.setupBridgeListener()}offBridge(t,e){let i=this.bridgeHandlers.get(t);if(i){let s=i.indexOf(e);s!==-1&&(i.splice(s,1),this.log(`Bridge handler removed for channel '${t}'`))}}removeAllBridgeHandlers(t){t?(this.bridgeHandlers.delete(t),this.log(`All bridge handlers removed for channel '${t}'`)):(this.bridgeHandlers.clear(),this.log("All bridge handlers removed"))}setupBridgeListener(){this.bridgeSetup||!this.socket||(this.socket.on("bridge",t=>{this.handleBridgeMessage(t)}),this.bridgeSetup=!0,this.log("Bridge listener setup"))}handleBridgeMessage(t){try{let{channel:e,data:i}=t,s=JSON.parse(i),a=this.bridgeHandlers.get(e);a&&a.length>0?a.forEach(c=>{try{c(s)}catch(h){this.log(`Error in bridge handler for '${e}': ${h}`)}}):this.log(`No bridge handler for channel '${e}'`)}catch(e){this.log(`Error parsing bridge message: ${e}`)}}destroy(){this.log("Destroying client..."),this.stopPingMonitor(),this.bridgeHandlers.clear(),this.bridgeSetup=!1,this.socket&&(this.socket.removeAllListeners(),this.socket.disconnect(),this.socket=null),this._state=o.NetworkState.Disconnected,this.log("Client destroyed")}startPingMonitor(){this.pingInterval||(this.pingInterval=setInterval(()=>{this.isConnected()&&this.socket&&(this.pingTimestamp=Date.now(),this.socket.emit("ping"))},2e3),this.log("Ping monitor started"))}stopPingMonitor(){this.pingInterval&&(clearInterval(this.pingInterval),this.pingInterval=null,this.log("Ping monitor stopped"))}log(t,e){this.options.debug&&(e!==void 0?console.warn(`[SocketIOClient] ${t}`,e):console.warn(`[SocketIOClient] ${t}`))}};u(p,"SocketIOClient");var d=p;0&&(module.exports={NetworkState,SocketIOClient});
package/dist/index.d.ts CHANGED
@@ -262,6 +262,91 @@ declare class SocketIOClient implements INetworkClient {
262
262
  * ```
263
263
  */
264
264
  getPing(): number;
265
+ private bridgeHandlers;
266
+ private bridgeSetup;
267
+ /**
268
+ * Send data to the server through the bridge channel
269
+ *
270
+ * The bridge channel is a separate communication channel that bypasses
271
+ * the UTSP protocol. It allows sending arbitrary JSON data from the client
272
+ * application (React, HTML, etc.) to the server.
273
+ *
274
+ * Use cases:
275
+ * - Send button clicks from React UI to server
276
+ * - External menu/pause controls
277
+ * - Custom commands from the parent application
278
+ * - Analytics or telemetry data
279
+ *
280
+ * @param channel - Channel name (e.g., 'action', 'menu', 'custom')
281
+ * @param data - Any JSON-serializable data
282
+ *
283
+ * @example
284
+ * ```typescript
285
+ * // React button click
286
+ * <button onClick={() => client.sendBridge('action', { type: 'jump' })}>
287
+ * Jump
288
+ * </button>
289
+ *
290
+ * // External menu
291
+ * client.sendBridge('menu', { action: 'pause' });
292
+ *
293
+ * // Custom data
294
+ * client.sendBridge('analytics', { event: 'level_complete', level: 5 });
295
+ * ```
296
+ */
297
+ sendBridge(channel: string, data: unknown): void;
298
+ /**
299
+ * Register a handler for incoming bridge messages from the server
300
+ *
301
+ * Allows the client application (React, HTML) to receive custom data
302
+ * from the server through the bridge channel.
303
+ *
304
+ * @param channel - Channel name to listen for
305
+ * @param handler - Callback function receiving the parsed data
306
+ *
307
+ * @example
308
+ * ```typescript
309
+ * // Handle background changes from server
310
+ * client.onBridge('background', (data) => {
311
+ * document.body.style.backgroundImage = `url(${data.image})`;
312
+ * });
313
+ *
314
+ * // Handle score updates
315
+ * client.onBridge('score', (data) => {
316
+ * setScore(data.value); // React state
317
+ * setCombo(data.combo);
318
+ * });
319
+ *
320
+ * // Handle notifications
321
+ * client.onBridge('notification', (data) => {
322
+ * showToast(data.title, data.message);
323
+ * });
324
+ * ```
325
+ */
326
+ onBridge<T = unknown>(channel: string, handler: (data: T) => void): void;
327
+ /**
328
+ * Remove a bridge handler for a specific channel
329
+ *
330
+ * @param channel - Channel name
331
+ * @param handler - The exact handler function to remove
332
+ */
333
+ offBridge<T = unknown>(channel: string, handler: (data: T) => void): void;
334
+ /**
335
+ * Remove all bridge handlers for a specific channel or all channels
336
+ *
337
+ * @param channel - Optional channel name. If omitted, removes all handlers.
338
+ */
339
+ removeAllBridgeHandlers(channel?: string): void;
340
+ /**
341
+ * Internal: Setup the bridge socket listener
342
+ * @private
343
+ */
344
+ private setupBridgeListener;
345
+ /**
346
+ * Internal: Handle incoming bridge message from server
347
+ * @private
348
+ */
349
+ private handleBridgeMessage;
265
350
  /**
266
351
  * Destroy the client and clean up all resources
267
352
  *
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- var g=Object.defineProperty;var f=(i,t,e)=>t in i?g(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e;var a=(i,t)=>g(i,"name",{value:t,configurable:!0});var s=(i,t,e)=>(f(i,typeof t!="symbol"?t+"":t,e),e);import{NetworkState as E}from"@utsp/types";import{io as k}from"socket.io-client";import{NetworkState as o}from"@utsp/types";var h=class h{constructor(t){s(this,"socket",null);s(this,"options");s(this,"_state",o.Disconnected);s(this,"pingTimestamp",0);s(this,"latency",0);s(this,"pingInterval",null);if(!t.url||typeof t.url!="string")throw new Error("SocketIOClient: Invalid URL - must be a non-empty string");try{if(!new globalThis.URL(t.url).protocol)throw new Error("Invalid protocol")}catch{throw new Error(`SocketIOClient: Invalid URL format - ${t.url}`)}if(t.reconnectDelay!==void 0&&(t.reconnectDelay<0||!Number.isFinite(t.reconnectDelay)))throw new Error(`SocketIOClient: Invalid reconnectDelay - must be a positive number (got ${t.reconnectDelay})`);if(t.maxReconnectAttempts!==void 0&&(!Number.isInteger(t.maxReconnectAttempts)||t.maxReconnectAttempts<0))throw new Error(`SocketIOClient: Invalid maxReconnectAttempts - must be a positive integer (got ${t.maxReconnectAttempts})`);if(t.timeout!==void 0&&(t.timeout<=0||!Number.isFinite(t.timeout)))throw new Error(`SocketIOClient: Invalid timeout - must be a positive number (got ${t.timeout})`);this.options={url:t.url,autoReconnect:t.autoReconnect??!0,reconnectDelay:t.reconnectDelay??1e3,maxReconnectAttempts:t.maxReconnectAttempts??10,timeout:t.timeout??5e3,auth:t.auth??{},debug:t.debug??!1},this.log("Client initialized",{url:this.options.url,autoReconnect:this.options.autoReconnect})}get state(){return this._state}isConnected(){return this._state===o.Connected&&this.socket?.connected===!0}async connect(){if(this.socket&&this.isConnected()){this.log("Already connected");return}return this.log(`Connecting to ${this.options.url}...`),this._state=o.Connecting,new Promise((t,e)=>{this.socket=k(this.options.url,{auth:this.options.auth,reconnection:this.options.autoReconnect,reconnectionDelay:this.options.reconnectDelay,reconnectionAttempts:this.options.maxReconnectAttempts,timeout:this.options.timeout,transports:["websocket"]}),this.socket.on("connect",()=>{this.log(`Connected with ID: ${this.socket?.id}`),this._state=o.Connected,this.startPingMonitor(),t()}),this.socket.on("connect_error",n=>{this.log(`Connection error: ${n.message}`),this._state=o.Error,e(n)}),this.socket.on("disconnect",n=>{this.log(`Disconnected: ${n}`),this._state=o.Disconnected,this.stopPingMonitor()}),this.socket.on("reconnect_attempt",n=>{this.log(`Reconnection attempt ${n}...`),this._state=o.Reconnecting}),this.socket.on("reconnect",n=>{this.log(`Reconnected after ${n} attempts`),this._state=o.Connected,this.startPingMonitor()}),this.socket.on("reconnect_failed",()=>{this.log("Reconnection failed"),this._state=o.Error}),this.socket.on("pong",()=>{this.latency=Date.now()-this.pingTimestamp,this.log(`Ping: ${this.latency}ms`)})})}disconnect(){this.socket&&(this.log("Disconnecting..."),this.stopPingMonitor(),this.socket.disconnect(),this._state=o.Disconnected)}send(t,e){if(!t||typeof t!="string"){this.log(`Cannot send: invalid event name (${typeof t})`);return}if(!this.isConnected()||!this.socket){this.log(`Cannot send '${t}': not connected`);return}try{this.socket.volatile.emit(t,e)}catch(n){this.log(`Error sending '${t}':`,n)}}on(t,e){if(!t||typeof t!="string")throw new Error(`Invalid event name: ${t}`);if(typeof e!="function")throw new Error(`Invalid handler for event '${t}': must be a function`);if(!this.socket){this.log(`Cannot listen to '${t}': socket not initialized`);return}this.socket.on(t,e),this.log(`Listening to '${t}'`)}off(t,e){this.socket&&(this.socket.off(t,e),this.log(`Stopped listening to '${t}'`))}removeAllListeners(t){this.socket&&(t?(this.socket.removeAllListeners(t),this.log(`Removed all listeners for '${t}'`)):(this.socket.removeAllListeners(),this.log("Removed all listeners")))}async request(t,e,n=5e3){if(!t||typeof t!="string")throw new Error(`Invalid event name: ${t}`);if(!Number.isFinite(n)||n<=0)throw new Error(`Invalid timeout: ${n} (must be a positive number)`);if(!this.isConnected())throw new Error("Cannot send request: not connected to server");return new Promise((m,u)=>{let r=`${t}_response`,d=setTimeout(()=>{this.off(r,c),u(new Error(`Request timeout after ${n}ms: ${t}`))},n),c=a(p=>{clearTimeout(d),this.off(r,c),m(p)},"responseHandler");this.on(r,c),this.send(t,e),this.log(`Request sent: ${t} (timeout: ${n}ms)`)})}getPing(){return this.latency}destroy(){this.log("Destroying client..."),this.stopPingMonitor(),this.socket&&(this.socket.removeAllListeners(),this.socket.disconnect(),this.socket=null),this._state=o.Disconnected,this.log("Client destroyed")}startPingMonitor(){this.pingInterval||(this.pingInterval=setInterval(()=>{this.isConnected()&&this.socket&&(this.pingTimestamp=Date.now(),this.socket.emit("ping"))},2e3),this.log("Ping monitor started"))}stopPingMonitor(){this.pingInterval&&(clearInterval(this.pingInterval),this.pingInterval=null,this.log("Ping monitor stopped"))}log(t,e){this.options.debug&&(e!==void 0?console.warn(`[SocketIOClient] ${t}`,e):console.warn(`[SocketIOClient] ${t}`))}};a(h,"SocketIOClient");var l=h;export{E as NetworkState,l as SocketIOClient};
1
+ var p=Object.defineProperty;var f=(o,t,e)=>t in o?p(o,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):o[t]=e;var h=(o,t)=>p(o,"name",{value:t,configurable:!0});var s=(o,t,e)=>(f(o,typeof t!="symbol"?t+"":t,e),e);import{NetworkState as E}from"@utsp/types";import{io as k}from"socket.io-client";import{NetworkState as n}from"@utsp/types";var u=class u{constructor(t){s(this,"socket",null);s(this,"options");s(this,"_state",n.Disconnected);s(this,"pingTimestamp",0);s(this,"latency",0);s(this,"pingInterval",null);s(this,"bridgeHandlers",new Map);s(this,"bridgeSetup",!1);if(!t.url||typeof t.url!="string")throw new Error("SocketIOClient: Invalid URL - must be a non-empty string");try{if(!new globalThis.URL(t.url).protocol)throw new Error("Invalid protocol")}catch{throw new Error(`SocketIOClient: Invalid URL format - ${t.url}`)}if(t.reconnectDelay!==void 0&&(t.reconnectDelay<0||!Number.isFinite(t.reconnectDelay)))throw new Error(`SocketIOClient: Invalid reconnectDelay - must be a positive number (got ${t.reconnectDelay})`);if(t.maxReconnectAttempts!==void 0&&(!Number.isInteger(t.maxReconnectAttempts)||t.maxReconnectAttempts<0))throw new Error(`SocketIOClient: Invalid maxReconnectAttempts - must be a positive integer (got ${t.maxReconnectAttempts})`);if(t.timeout!==void 0&&(t.timeout<=0||!Number.isFinite(t.timeout)))throw new Error(`SocketIOClient: Invalid timeout - must be a positive number (got ${t.timeout})`);this.options={url:t.url,autoReconnect:t.autoReconnect??!0,reconnectDelay:t.reconnectDelay??1e3,maxReconnectAttempts:t.maxReconnectAttempts??10,timeout:t.timeout??5e3,auth:t.auth??{},debug:t.debug??!1},this.log("Client initialized",{url:this.options.url,autoReconnect:this.options.autoReconnect})}get state(){return this._state}isConnected(){return this._state===n.Connected&&this.socket?.connected===!0}async connect(){if(this.socket&&this.isConnected()){this.log("Already connected");return}return this.log(`Connecting to ${this.options.url}...`),this._state=n.Connecting,this.bridgeSetup=!1,new Promise((t,e)=>{this.socket=k(this.options.url,{auth:this.options.auth,reconnection:this.options.autoReconnect,reconnectionDelay:this.options.reconnectDelay,reconnectionAttempts:this.options.maxReconnectAttempts,timeout:this.options.timeout,transports:["websocket"]}),this.socket.on("connect",()=>{this.log(`Connected with ID: ${this.socket?.id}`),this._state=n.Connected,this.startPingMonitor(),this.bridgeHandlers.size>0&&this.setupBridgeListener(),t()}),this.socket.on("connect_error",i=>{this.log(`Connection error: ${i.message}`),this._state=n.Error,e(i)}),this.socket.on("disconnect",i=>{this.log(`Disconnected: ${i}`),this._state=n.Disconnected,this.stopPingMonitor()}),this.socket.on("reconnect_attempt",i=>{this.log(`Reconnection attempt ${i}...`),this._state=n.Reconnecting}),this.socket.on("reconnect",i=>{this.log(`Reconnected after ${i} attempts`),this._state=n.Connected,this.startPingMonitor()}),this.socket.on("reconnect_failed",()=>{this.log("Reconnection failed"),this._state=n.Error}),this.socket.on("pong",()=>{this.latency=Date.now()-this.pingTimestamp,this.log(`Ping: ${this.latency}ms`)})})}disconnect(){this.socket&&(this.log("Disconnecting..."),this.stopPingMonitor(),this.socket.disconnect(),this._state=n.Disconnected)}send(t,e){if(!t||typeof t!="string"){this.log(`Cannot send: invalid event name (${typeof t})`);return}if(!this.isConnected()||!this.socket){this.log(`Cannot send '${t}': not connected`);return}try{this.socket.volatile.emit(t,e)}catch(i){this.log(`Error sending '${t}':`,i)}}on(t,e){if(!t||typeof t!="string")throw new Error(`Invalid event name: ${t}`);if(typeof e!="function")throw new Error(`Invalid handler for event '${t}': must be a function`);if(!this.socket){this.log(`Cannot listen to '${t}': socket not initialized`);return}this.socket.on(t,e),this.log(`Listening to '${t}'`)}off(t,e){this.socket&&(this.socket.off(t,e),this.log(`Stopped listening to '${t}'`))}removeAllListeners(t){this.socket&&(t?(this.socket.removeAllListeners(t),this.log(`Removed all listeners for '${t}'`)):(this.socket.removeAllListeners(),this.log("Removed all listeners")))}async request(t,e,i=5e3){if(!t||typeof t!="string")throw new Error(`Invalid event name: ${t}`);if(!Number.isFinite(i)||i<=0)throw new Error(`Invalid timeout: ${i} (must be a positive number)`);if(!this.isConnected())throw new Error("Cannot send request: not connected to server");return new Promise((r,a)=>{let c=`${t}_response`,l=setTimeout(()=>{this.off(c,d),a(new Error(`Request timeout after ${i}ms: ${t}`))},i),d=h(m=>{clearTimeout(l),this.off(c,d),r(m)},"responseHandler");this.on(c,d),this.send(t,e),this.log(`Request sent: ${t} (timeout: ${i}ms)`)})}getPing(){return this.latency}sendBridge(t,e){if(!this.isConnected()||!this.socket){this.log(`Cannot send bridge '${t}': not connected`);return}try{let i={channel:t,data:JSON.stringify(e)};this.socket.emit("bridge",i),this.log(`Bridge sent on channel '${t}'`)}catch(i){this.log(`Error sending bridge: ${i}`)}}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}'`),this.setupBridgeListener()}offBridge(t,e){let i=this.bridgeHandlers.get(t);if(i){let r=i.indexOf(e);r!==-1&&(i.splice(r,1),this.log(`Bridge handler removed for channel '${t}'`))}}removeAllBridgeHandlers(t){t?(this.bridgeHandlers.delete(t),this.log(`All bridge handlers removed for channel '${t}'`)):(this.bridgeHandlers.clear(),this.log("All bridge handlers removed"))}setupBridgeListener(){this.bridgeSetup||!this.socket||(this.socket.on("bridge",t=>{this.handleBridgeMessage(t)}),this.bridgeSetup=!0,this.log("Bridge listener setup"))}handleBridgeMessage(t){try{let{channel:e,data:i}=t,r=JSON.parse(i),a=this.bridgeHandlers.get(e);a&&a.length>0?a.forEach(c=>{try{c(r)}catch(l){this.log(`Error in bridge handler for '${e}': ${l}`)}}):this.log(`No bridge handler for channel '${e}'`)}catch(e){this.log(`Error parsing bridge message: ${e}`)}}destroy(){this.log("Destroying client..."),this.stopPingMonitor(),this.bridgeHandlers.clear(),this.bridgeSetup=!1,this.socket&&(this.socket.removeAllListeners(),this.socket.disconnect(),this.socket=null),this._state=n.Disconnected,this.log("Client destroyed")}startPingMonitor(){this.pingInterval||(this.pingInterval=setInterval(()=>{this.isConnected()&&this.socket&&(this.pingTimestamp=Date.now(),this.socket.emit("ping"))},2e3),this.log("Ping monitor started"))}stopPingMonitor(){this.pingInterval&&(clearInterval(this.pingInterval),this.pingInterval=null,this.log("Ping monitor stopped"))}log(t,e){this.options.debug&&(e!==void 0?console.warn(`[SocketIOClient] ${t}`,e):console.warn(`[SocketIOClient] ${t}`))}};h(u,"SocketIOClient");var g=u;export{E as NetworkState,g as SocketIOClient};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utsp/network-client",
3
- "version": "0.8.0-nightly.20251209024647.83e7069",
3
+ "version": "0.8.0",
4
4
  "description": "UTSP Network Client - Client-side communication adapters",
5
5
  "author": "THP Software",
6
6
  "license": "MIT",
@@ -48,8 +48,8 @@
48
48
  "access": "public"
49
49
  },
50
50
  "dependencies": {
51
- "@utsp/types": "0.8.0-nightly.20251209024647.83e7069",
52
- "socket.io-client": "^4.7.2"
51
+ "socket.io-client": "^4.7.2",
52
+ "@utsp/types": "0.8.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "typescript": "^5.6.3"