@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 +1 -1
- package/dist/index.d.ts +85 -0
- package/dist/index.mjs +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
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
|
|
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
|
|
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
|
-
"
|
|
52
|
-
"
|
|
51
|
+
"socket.io-client": "^4.7.2",
|
|
52
|
+
"@utsp/types": "0.8.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"typescript": "^5.6.3"
|