@utsp/network-client 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Thomas Piquet
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # @utsp/network-client
2
+
3
+ > ⚠️ **PROTOTYPE - NOT READY FOR PRODUCTION**
4
+ >
5
+ > This package is currently in early development and should **NOT** be used in production.
6
+ > The API is unstable and subject to breaking changes without notice.
7
+
8
+ Client-side network communication layer for UTSP (Universal Text Stream Protocol).
9
+
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
11
+
12
+ ## ⚠️ Development Status
13
+
14
+ **This is a prototype package under active development.**
15
+
16
+ - ❌ No stable API
17
+ - ❌ No documentation available yet
18
+ - ❌ Breaking changes expected
19
+ - ❌ Not recommended for production use
20
+
21
+ **Please check back later for updates or watch the repository for release announcements.**
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install @utsp/network-client
27
+ ```
28
+
29
+ ## Repository
30
+
31
+ - [GitHub](https://github.com/thp-software/utsp)
32
+ - [Issues](https://github.com/thp-software/utsp/issues)
33
+
34
+ ## License
35
+
36
+ MIT © 2025 Thomas Piquet
package/dist/index.cjs ADDED
@@ -0,0 +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});
@@ -0,0 +1,310 @@
1
+ import { INetworkClient, NetworkClientOptions, NetworkState, NetworkEventHandler } from '@utsp/types';
2
+ export { AnyNetworkMessage, ChatMessage, ClientInfo, ErrorMessage, INetworkClient, InputMessage, JoinMessage, JoinResponseMessage, LeaveMessage, LoadMessage, MessageType, NetworkClientOptions, NetworkEventHandler, NetworkMessage, NetworkState, PingMessage, PongMessage, UpdateMessage } from '@utsp/types';
3
+
4
+ /**
5
+ * Socket.IO Client Implementation
6
+ *
7
+ * Production-ready Socket.IO client for real-time communication with UTSP servers.
8
+ * Implements the INetworkClient interface with automatic reconnection, latency monitoring,
9
+ * and comprehensive error handling. Browser-safe with no Node.js dependencies.
10
+ *
11
+ * @example Basic Usage
12
+ * ```typescript
13
+ * import { SocketIOClient } from '@utsp/network-client';
14
+ *
15
+ * const client = new SocketIOClient({
16
+ * url: 'http://localhost:3000',
17
+ * autoReconnect: true,
18
+ * debug: false
19
+ * });
20
+ *
21
+ * await client.connect();
22
+ * console.log('Connected:', client.isConnected());
23
+ *
24
+ * client.on('update', (data) => {
25
+ * console.log('Received update:', data);
26
+ * });
27
+ *
28
+ * client.send('input', { key: 'ArrowUp' });
29
+ * ```
30
+ *
31
+ * @example With Authentication
32
+ * ```typescript
33
+ * const client = new SocketIOClient({
34
+ * url: 'https://game.example.com',
35
+ * auth: {
36
+ * token: 'user-auth-token',
37
+ * userId: '12345'
38
+ * }
39
+ * });
40
+ *
41
+ * await client.connect();
42
+ * ```
43
+ *
44
+ * @example Request-Response Pattern
45
+ * ```typescript
46
+ * try {
47
+ * const response = await client.request('getPlayerStats', { playerId: '123' }, 5000);
48
+ * console.log('Stats:', response);
49
+ * } catch (error) {
50
+ * console.error('Request failed:', error);
51
+ * }
52
+ * ```
53
+ */
54
+
55
+ /**
56
+ * Socket.IO client implementation for UTSP network communication
57
+ *
58
+ * Features:
59
+ * - Automatic reconnection with exponential backoff
60
+ * - Real-time latency monitoring (ping/pong)
61
+ * - Request-response pattern with timeout
62
+ * - Type-safe event handling
63
+ * - Connection state management
64
+ * - Debug logging
65
+ *
66
+ * @implements {INetworkClient}
67
+ */
68
+ declare class SocketIOClient implements INetworkClient {
69
+ private socket;
70
+ private options;
71
+ private _state;
72
+ private pingTimestamp;
73
+ private latency;
74
+ private pingInterval;
75
+ /**
76
+ * Create a new Socket.IO client instance
77
+ *
78
+ * @param options - Client configuration options
79
+ * @throws {Error} If URL is invalid or options are malformed
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * const client = new SocketIOClient({
84
+ * url: 'http://localhost:3000',
85
+ * autoReconnect: true,
86
+ * reconnectDelay: 1000,
87
+ * maxReconnectAttempts: 10,
88
+ * timeout: 5000,
89
+ * debug: false
90
+ * });
91
+ * ```
92
+ */
93
+ constructor(options: NetworkClientOptions);
94
+ /**
95
+ * Get current connection state
96
+ *
97
+ * @returns Current network state (Disconnected, Connecting, Connected, Reconnecting, Error)
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * console.log('State:', client.state); // NetworkState.Connected
102
+ * ```
103
+ */
104
+ get state(): NetworkState;
105
+ /**
106
+ * Check if client is currently connected
107
+ *
108
+ * @returns true if connected and socket is active, false otherwise
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * if (client.isConnected()) {
113
+ * client.send('message', data);
114
+ * }
115
+ * ```
116
+ */
117
+ isConnected(): boolean;
118
+ /**
119
+ * Connect to the server
120
+ *
121
+ * Establishes a WebSocket connection to the configured server URL.
122
+ * Automatically sets up reconnection logic if enabled.
123
+ *
124
+ * @returns Promise that resolves when connected, rejects on connection error
125
+ * @throws {Error} If connection fails or times out
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * try {
130
+ * await client.connect();
131
+ * console.log('Connected successfully');
132
+ * } catch (error) {
133
+ * console.error('Connection failed:', error);
134
+ * }
135
+ * ```
136
+ */
137
+ connect(): Promise<void>;
138
+ /**
139
+ * Disconnect from the server
140
+ *
141
+ * Gracefully closes the WebSocket connection and stops all monitoring.
142
+ * Safe to call multiple times.
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * client.disconnect();
147
+ * console.log('Disconnected');
148
+ * ```
149
+ */
150
+ disconnect(): void;
151
+ /**
152
+ * Send an event to the server
153
+ *
154
+ * Uses volatile emit to prevent message queuing during disconnection.
155
+ * Events sent while disconnected will be silently dropped.
156
+ *
157
+ * @param event - Event name to emit
158
+ * @param data - Event payload (any serializable data)
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * client.send('input', { key: 'ArrowUp', timestamp: Date.now() });
163
+ * client.send('chat', { message: 'Hello world!' });
164
+ * ```
165
+ */
166
+ send(event: string, data: any): void;
167
+ /**
168
+ * Register an event listener
169
+ *
170
+ * Listens for events from the server. Multiple handlers can be registered
171
+ * for the same event.
172
+ *
173
+ * @param event - Event name to listen for
174
+ * @param handler - Callback function to handle event data
175
+ *
176
+ * @example
177
+ * ```typescript
178
+ * client.on<UpdateMessage>('update', (data) => {
179
+ * console.log('Game update:', data);
180
+ * });
181
+ *
182
+ * client.on('chat', (message) => {
183
+ * console.log('Chat:', message);
184
+ * });
185
+ * ```
186
+ */
187
+ on<T = any>(event: string, handler: NetworkEventHandler<T>): void;
188
+ /**
189
+ * Unregister a specific event listener
190
+ *
191
+ * Removes a previously registered handler for an event. The handler reference
192
+ * must be the exact same function object that was registered.
193
+ *
194
+ * @param event - Event name
195
+ * @param handler - Handler function to remove (must be same reference)
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * const handler = (data) => console.log(data);
200
+ * client.on('update', handler);
201
+ * // ... later
202
+ * client.off('update', handler);
203
+ * ```
204
+ */
205
+ off<T = any>(event: string, handler: NetworkEventHandler<T>): void;
206
+ /**
207
+ * Remove all listeners for an event or all events
208
+ *
209
+ * If event name is provided, removes all listeners for that specific event.
210
+ * If no event name, removes all listeners for all events.
211
+ *
212
+ * @param event - Optional event name. If omitted, removes all listeners.
213
+ *
214
+ * @example
215
+ * ```typescript
216
+ * // Remove all 'update' listeners
217
+ * client.removeAllListeners('update');
218
+ *
219
+ * // Remove ALL listeners
220
+ * client.removeAllListeners();
221
+ * ```
222
+ */
223
+ removeAllListeners(event?: string): void;
224
+ /**
225
+ * Send a request and wait for response (request-response pattern)
226
+ *
227
+ * Implements a request-response pattern on top of Socket.IO events.
228
+ * Automatically listens for `${event}_response` and handles timeout.
229
+ *
230
+ * @param event - Request event name
231
+ * @param data - Request payload
232
+ * @param timeout - Timeout in milliseconds (default: 5000ms)
233
+ * @returns Promise resolving to the response data
234
+ * @throws {Error} If not connected, request times out, or server returns error
235
+ *
236
+ * @example
237
+ * ```typescript
238
+ * try {
239
+ * const stats = await client.request<PlayerStats>(
240
+ * 'getPlayerStats',
241
+ * { playerId: '123' },
242
+ * 3000
243
+ * );
244
+ * console.log('Stats:', stats);
245
+ * } catch (error) {
246
+ * console.error('Request failed:', error);
247
+ * }
248
+ * ```
249
+ */
250
+ request<T = any>(event: string, data: any, timeout?: number): Promise<T>;
251
+ /**
252
+ * Get current network latency (ping time)
253
+ *
254
+ * Returns the round-trip time in milliseconds of the most recent ping.
255
+ * Updated every 2 seconds while connected.
256
+ *
257
+ * @returns Latency in milliseconds, or 0 if not yet measured
258
+ *
259
+ * @example
260
+ * ```typescript
261
+ * console.log('Latency:', client.getPing(), 'ms');
262
+ * ```
263
+ */
264
+ getPing(): number;
265
+ /**
266
+ * Destroy the client and clean up all resources
267
+ *
268
+ * Disconnects from server, removes all listeners, and releases resources.
269
+ * Client instance cannot be reused after calling destroy().
270
+ *
271
+ * @example
272
+ * ```typescript
273
+ * // Cleanup before page unload
274
+ * window.addEventListener('beforeunload', () => {
275
+ * client.destroy();
276
+ * });
277
+ * ```
278
+ */
279
+ destroy(): void;
280
+ /**
281
+ * Start ping monitoring for latency measurement
282
+ *
283
+ * Sends a ping every 2 seconds to measure round-trip time.
284
+ * Only runs while connected.
285
+ *
286
+ * @private
287
+ */
288
+ private startPingMonitor;
289
+ /**
290
+ * Stop ping monitoring
291
+ *
292
+ * Clears the ping interval timer.
293
+ *
294
+ * @private
295
+ */
296
+ private stopPingMonitor;
297
+ /**
298
+ * Debug logging utility
299
+ *
300
+ * Logs messages when debug mode is enabled. Uses console.warn
301
+ * to ensure visibility in production environments.
302
+ *
303
+ * @param message - Log message
304
+ * @param data - Optional data to log
305
+ * @private
306
+ */
307
+ private log;
308
+ }
309
+
310
+ export { SocketIOClient, SocketIOClient as SocketIOClientType };
package/dist/index.mjs ADDED
@@ -0,0 +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};
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@utsp/network-client",
3
+ "version": "0.1.1",
4
+ "description": "UTSP Network Client - Client-side communication adapters",
5
+ "author": "Thomas Piquet",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.mjs",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/thp-software/utsp.git",
21
+ "directory": "packages/network-client"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/thp-software/utsp/issues"
25
+ },
26
+ "homepage": "https://github.com/thp-software/utsp/tree/master/packages/network-client#readme",
27
+ "keywords": [
28
+ "utsp",
29
+ "network",
30
+ "client",
31
+ "websocket",
32
+ "socket.io",
33
+ "communication",
34
+ "multi-user",
35
+ "real-time",
36
+ "terminal"
37
+ ],
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ },
41
+ "sideEffects": false,
42
+ "files": [
43
+ "dist",
44
+ "README.md",
45
+ "LICENSE"
46
+ ],
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "dependencies": {
51
+ "socket.io-client": "^4.7.2",
52
+ "@utsp/types": "0.1.1"
53
+ },
54
+ "devDependencies": {
55
+ "typescript": "^5.6.3"
56
+ },
57
+ "scripts": {
58
+ "build": "node ../../scripts/build-package.mjs packages/network-client",
59
+ "dev": "tsc --watch",
60
+ "clean": "rimraf dist",
61
+ "lint": "eslint \"src/**/*.ts\" --max-warnings 0",
62
+ "lint:fix": "eslint \"src/**/*.ts\" --fix",
63
+ "typecheck": "tsc --noEmit"
64
+ }
65
+ }