jmri-client 4.2.0-beta.2 → 5.0.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.
Files changed (58) hide show
  1. package/README.md +3 -1
  2. package/dist/cjs/index.js +2382 -31
  3. package/dist/esm/index.js +2333 -17
  4. package/docs/BROWSER.md +4 -4
  5. package/docs/MIGRATION.md +30 -1
  6. package/package.json +17 -18
  7. package/dist/cjs/client.js +0 -366
  8. package/dist/cjs/core/connection-state-manager.js +0 -84
  9. package/dist/cjs/core/heartbeat-manager.js +0 -79
  10. package/dist/cjs/core/index.js +0 -25
  11. package/dist/cjs/core/message-queue.js +0 -59
  12. package/dist/cjs/core/reconnection-manager.js +0 -97
  13. package/dist/cjs/core/websocket-adapter.js +0 -135
  14. package/dist/cjs/core/websocket-client.js +0 -388
  15. package/dist/cjs/managers/index.js +0 -25
  16. package/dist/cjs/managers/light-manager.js +0 -111
  17. package/dist/cjs/managers/power-manager.js +0 -90
  18. package/dist/cjs/managers/roster-manager.js +0 -118
  19. package/dist/cjs/managers/system-connections-manager.js +0 -28
  20. package/dist/cjs/managers/throttle-manager.js +0 -233
  21. package/dist/cjs/managers/turnout-manager.js +0 -111
  22. package/dist/cjs/mocks/index.js +0 -12
  23. package/dist/cjs/mocks/mock-data.js +0 -237
  24. package/dist/cjs/mocks/mock-response-manager.js +0 -290
  25. package/dist/cjs/types/client-options.js +0 -66
  26. package/dist/cjs/types/events.js +0 -16
  27. package/dist/cjs/types/index.js +0 -23
  28. package/dist/cjs/types/jmri-messages.js +0 -95
  29. package/dist/cjs/types/throttle.js +0 -19
  30. package/dist/cjs/utils/exponential-backoff.js +0 -40
  31. package/dist/cjs/utils/index.js +0 -21
  32. package/dist/cjs/utils/message-id.js +0 -40
  33. package/dist/esm/client.js +0 -362
  34. package/dist/esm/core/connection-state-manager.js +0 -80
  35. package/dist/esm/core/heartbeat-manager.js +0 -75
  36. package/dist/esm/core/index.js +0 -9
  37. package/dist/esm/core/message-queue.js +0 -55
  38. package/dist/esm/core/reconnection-manager.js +0 -93
  39. package/dist/esm/core/websocket-adapter.js +0 -98
  40. package/dist/esm/core/websocket-client.js +0 -384
  41. package/dist/esm/managers/index.js +0 -9
  42. package/dist/esm/managers/light-manager.js +0 -107
  43. package/dist/esm/managers/power-manager.js +0 -86
  44. package/dist/esm/managers/roster-manager.js +0 -114
  45. package/dist/esm/managers/system-connections-manager.js +0 -24
  46. package/dist/esm/managers/throttle-manager.js +0 -229
  47. package/dist/esm/managers/turnout-manager.js +0 -107
  48. package/dist/esm/mocks/index.js +0 -6
  49. package/dist/esm/mocks/mock-data.js +0 -234
  50. package/dist/esm/mocks/mock-response-manager.js +0 -286
  51. package/dist/esm/types/client-options.js +0 -62
  52. package/dist/esm/types/events.js +0 -13
  53. package/dist/esm/types/index.js +0 -7
  54. package/dist/esm/types/jmri-messages.js +0 -89
  55. package/dist/esm/types/throttle.js +0 -15
  56. package/dist/esm/utils/exponential-backoff.js +0 -36
  57. package/dist/esm/utils/index.js +0 -5
  58. package/dist/esm/utils/message-id.js +0 -36
@@ -1,59 +0,0 @@
1
- "use strict";
2
- /**
3
- * Message queue for offline message handling
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.MessageQueue = void 0;
7
- /**
8
- * Queue for storing messages when disconnected
9
- * Messages are sent when connection is restored
10
- */
11
- class MessageQueue {
12
- constructor(maxSize = 100) {
13
- this.queue = [];
14
- this.maxSize = maxSize;
15
- }
16
- /**
17
- * Add message to queue
18
- * If queue is full, oldest message is removed
19
- */
20
- enqueue(message) {
21
- if (this.queue.length >= this.maxSize) {
22
- this.queue.shift(); // Remove oldest
23
- }
24
- this.queue.push(message);
25
- }
26
- /**
27
- * Get all queued messages and clear queue
28
- */
29
- flush() {
30
- const messages = [...this.queue];
31
- this.queue = [];
32
- return messages;
33
- }
34
- /**
35
- * Clear all queued messages without returning them
36
- */
37
- clear() {
38
- this.queue = [];
39
- }
40
- /**
41
- * Get number of queued messages
42
- */
43
- size() {
44
- return this.queue.length;
45
- }
46
- /**
47
- * Check if queue is empty
48
- */
49
- isEmpty() {
50
- return this.queue.length === 0;
51
- }
52
- /**
53
- * Check if queue is full
54
- */
55
- isFull() {
56
- return this.queue.length >= this.maxSize;
57
- }
58
- }
59
- exports.MessageQueue = MessageQueue;
@@ -1,97 +0,0 @@
1
- "use strict";
2
- /**
3
- * Automatic reconnection management with exponential backoff
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ReconnectionManager = void 0;
7
- const eventemitter3_1 = require("eventemitter3");
8
- const exponential_backoff_js_1 = require("../utils/exponential-backoff.js");
9
- /**
10
- * Manages automatic reconnection with exponential backoff
11
- */
12
- class ReconnectionManager extends eventemitter3_1.EventEmitter {
13
- constructor(options) {
14
- super();
15
- this.currentAttempt = 0;
16
- this.isReconnecting = false;
17
- this.options = options;
18
- }
19
- /**
20
- * Start reconnection process
21
- * @param reconnect - Callback to attempt reconnection
22
- */
23
- start(reconnect) {
24
- if (!this.options.enabled || this.isReconnecting) {
25
- return;
26
- }
27
- this.isReconnecting = true;
28
- this.currentAttempt = 0;
29
- this.scheduleNextAttempt(reconnect);
30
- }
31
- /**
32
- * Schedule next reconnection attempt
33
- */
34
- scheduleNextAttempt(reconnect) {
35
- this.currentAttempt++;
36
- // Check if should continue trying
37
- if (!(0, exponential_backoff_js_1.shouldReconnect)(this.currentAttempt, this.options.maxAttempts)) {
38
- this.emit('maxAttemptsReached', this.currentAttempt - 1);
39
- this.stop();
40
- return;
41
- }
42
- // Calculate delay with backoff
43
- const delay = (0, exponential_backoff_js_1.calculateBackoffDelay)(this.currentAttempt, this.options);
44
- this.emit('attemptScheduled', this.currentAttempt, delay);
45
- // Schedule attempt
46
- this.reconnectTimeout = setTimeout(async () => {
47
- this.emit('attempting', this.currentAttempt);
48
- try {
49
- await reconnect();
50
- // Success - stop reconnection process
51
- this.stop();
52
- this.emit('success', this.currentAttempt);
53
- }
54
- catch (error) {
55
- // Failure - schedule next attempt
56
- this.emit('failed', this.currentAttempt, error);
57
- this.scheduleNextAttempt(reconnect);
58
- }
59
- }, delay);
60
- }
61
- /**
62
- * Stop reconnection process
63
- */
64
- stop() {
65
- this.isReconnecting = false;
66
- if (this.reconnectTimeout) {
67
- clearTimeout(this.reconnectTimeout);
68
- this.reconnectTimeout = undefined;
69
- }
70
- }
71
- /**
72
- * Reset attempt counter
73
- */
74
- reset() {
75
- this.stop();
76
- this.currentAttempt = 0;
77
- }
78
- /**
79
- * Check if currently reconnecting
80
- */
81
- reconnecting() {
82
- return this.isReconnecting;
83
- }
84
- /**
85
- * Get current attempt number
86
- */
87
- getAttempt() {
88
- return this.currentAttempt;
89
- }
90
- /**
91
- * Update reconnection options
92
- */
93
- updateOptions(options) {
94
- this.options = { ...this.options, ...options };
95
- }
96
- }
97
- exports.ReconnectionManager = ReconnectionManager;
@@ -1,135 +0,0 @@
1
- "use strict";
2
- /**
3
- * WebSocket Adapter
4
- * Provides a unified interface for WebSocket connections in both Node.js and browser environments
5
- */
6
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
- if (k2 === undefined) k2 = k;
8
- var desc = Object.getOwnPropertyDescriptor(m, k);
9
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
- desc = { enumerable: true, get: function() { return m[k]; } };
11
- }
12
- Object.defineProperty(o, k2, desc);
13
- }) : (function(o, m, k, k2) {
14
- if (k2 === undefined) k2 = k;
15
- o[k2] = m[k];
16
- }));
17
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
- Object.defineProperty(o, "default", { enumerable: true, value: v });
19
- }) : function(o, v) {
20
- o["default"] = v;
21
- });
22
- var __importStar = (this && this.__importStar) || (function () {
23
- var ownKeys = function(o) {
24
- ownKeys = Object.getOwnPropertyNames || function (o) {
25
- var ar = [];
26
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
- return ar;
28
- };
29
- return ownKeys(o);
30
- };
31
- return function (mod) {
32
- if (mod && mod.__esModule) return mod;
33
- var result = {};
34
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
- __setModuleDefault(result, mod);
36
- return result;
37
- };
38
- })();
39
- Object.defineProperty(exports, "__esModule", { value: true });
40
- exports.ReadyState = void 0;
41
- exports.createWebSocketAdapter = createWebSocketAdapter;
42
- const eventemitter3_1 = require("eventemitter3");
43
- /**
44
- * WebSocket ready states
45
- */
46
- var ReadyState;
47
- (function (ReadyState) {
48
- ReadyState[ReadyState["CONNECTING"] = 0] = "CONNECTING";
49
- ReadyState[ReadyState["OPEN"] = 1] = "OPEN";
50
- ReadyState[ReadyState["CLOSING"] = 2] = "CLOSING";
51
- ReadyState[ReadyState["CLOSED"] = 3] = "CLOSED";
52
- })(ReadyState || (exports.ReadyState = ReadyState = {}));
53
- /**
54
- * Create a WebSocket adapter for the current environment
55
- */
56
- async function createWebSocketAdapter(url, protocols) {
57
- // Detect environment
58
- const isBrowser = typeof window !== 'undefined' && typeof window.WebSocket !== 'undefined';
59
- if (isBrowser) {
60
- return new BrowserWebSocketAdapter(url, protocols);
61
- }
62
- else {
63
- return await NodeWebSocketAdapter.create(url, protocols);
64
- }
65
- }
66
- /**
67
- * Browser WebSocket adapter
68
- * Wraps native browser WebSocket with EventEmitter interface
69
- */
70
- class BrowserWebSocketAdapter extends eventemitter3_1.EventEmitter {
71
- constructor(url, protocols) {
72
- super();
73
- this.ws = new WebSocket(url, protocols);
74
- this.ws.onopen = () => {
75
- this.emit('open');
76
- };
77
- this.ws.onmessage = (event) => {
78
- this.emit('message', event.data);
79
- };
80
- this.ws.onerror = () => {
81
- this.emit('error', new Error('WebSocket error'));
82
- };
83
- this.ws.onclose = (event) => {
84
- this.emit('close', event.code, event.reason);
85
- };
86
- }
87
- send(data) {
88
- this.ws.send(data);
89
- }
90
- close(code, reason) {
91
- this.ws.close(code, reason);
92
- }
93
- get readyState() {
94
- return this.ws.readyState;
95
- }
96
- }
97
- /**
98
- * Node.js WebSocket adapter
99
- * Wraps ws package with consistent interface
100
- */
101
- class NodeWebSocketAdapter extends eventemitter3_1.EventEmitter {
102
- constructor(ws) {
103
- super();
104
- this.ws = ws;
105
- this.ws.on('open', () => {
106
- this.emit('open');
107
- });
108
- this.ws.on('message', (data) => {
109
- // Convert Buffer to string if needed
110
- const message = typeof data === 'string' ? data : data.toString();
111
- this.emit('message', message);
112
- });
113
- this.ws.on('error', (error) => {
114
- this.emit('error', error);
115
- });
116
- this.ws.on('close', (code, reason) => {
117
- this.emit('close', code, reason);
118
- });
119
- }
120
- static async create(url, protocols) {
121
- // Use dynamic import for ESM compatibility
122
- const { default: WebSocket } = await Promise.resolve().then(() => __importStar(require('ws')));
123
- const ws = new WebSocket(url, protocols);
124
- return new NodeWebSocketAdapter(ws);
125
- }
126
- send(data) {
127
- this.ws.send(data);
128
- }
129
- close(code, reason) {
130
- this.ws.close(code, reason);
131
- }
132
- get readyState() {
133
- return this.ws.readyState;
134
- }
135
- }
@@ -1,388 +0,0 @@
1
- "use strict";
2
- /**
3
- * Core WebSocket client for JMRI communication
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.WebSocketClient = void 0;
7
- const eventemitter3_1 = require("eventemitter3");
8
- const websocket_adapter_js_1 = require("./websocket-adapter.js");
9
- const events_js_1 = require("../types/events.js");
10
- const message_id_js_1 = require("../utils/message-id.js");
11
- const message_queue_js_1 = require("./message-queue.js");
12
- const connection_state_manager_js_1 = require("./connection-state-manager.js");
13
- const heartbeat_manager_js_1 = require("./heartbeat-manager.js");
14
- const reconnection_manager_js_1 = require("./reconnection-manager.js");
15
- const index_js_1 = require("../mocks/index.js");
16
- /**
17
- * Core WebSocket client
18
- */
19
- class WebSocketClient extends eventemitter3_1.EventEmitter {
20
- constructor(options) {
21
- super();
22
- // Request/response tracking
23
- this.pendingRequests = new Map();
24
- // Connection state
25
- this.isManualDisconnect = false;
26
- this.options = options;
27
- this.url = `${options.protocol}://${options.host}:${options.port}/json/`;
28
- // Initialize sub-managers
29
- this.messageIdGen = new message_id_js_1.MessageIdGenerator();
30
- this.messageQueue = new message_queue_js_1.MessageQueue(options.messageQueueSize);
31
- this.stateManager = new connection_state_manager_js_1.ConnectionStateManager();
32
- this.heartbeatManager = new heartbeat_manager_js_1.HeartbeatManager(options.heartbeat);
33
- this.reconnectionManager = new reconnection_manager_js_1.ReconnectionManager(options.reconnection);
34
- // Initialize mock manager if mock mode is enabled
35
- if (options.mock.enabled) {
36
- this.mockManager = new index_js_1.MockResponseManager({
37
- responseDelay: options.mock.responseDelay
38
- });
39
- }
40
- // Wire up state manager events
41
- this.stateManager.on('stateChanged', (newState, prevState) => {
42
- this.emit('connectionStateChanged', newState, prevState);
43
- });
44
- // Wire up heartbeat events
45
- this.heartbeatManager.on('timeout', () => {
46
- this.emit('heartbeat:timeout');
47
- this.handleHeartbeatTimeout();
48
- });
49
- this.heartbeatManager.on('pingSent', () => {
50
- this.emit('heartbeat:sent');
51
- });
52
- // Wire up reconnection events
53
- this.reconnectionManager.on('attemptScheduled', (attempt, delay) => {
54
- this.emit('reconnecting', attempt, delay);
55
- });
56
- this.reconnectionManager.on('success', () => {
57
- this.emit('reconnected');
58
- });
59
- this.reconnectionManager.on('maxAttemptsReached', (attempts) => {
60
- this.emit('reconnectionFailed', attempts);
61
- });
62
- this.reconnectionManager.on('debug', (message) => {
63
- this.emit('debug', message);
64
- });
65
- }
66
- /**
67
- * Connect to JMRI WebSocket server (or mock)
68
- */
69
- async connect() {
70
- if (this.stateManager.isConnected() || this.stateManager.isConnecting()) {
71
- return;
72
- }
73
- this.isManualDisconnect = false;
74
- this.stateManager.transition(events_js_1.ConnectionState.CONNECTING);
75
- // Mock mode - simulate connection
76
- if (this.mockManager) {
77
- return this.connectMock();
78
- }
79
- // Real WebSocket connection
80
- return new Promise(async (resolve, reject) => {
81
- try {
82
- this.ws = await (0, websocket_adapter_js_1.createWebSocketAdapter)(this.url);
83
- this.ws.on('open', () => {
84
- this.handleOpen();
85
- resolve();
86
- });
87
- this.ws.on('message', (data) => {
88
- this.handleMessage(data);
89
- });
90
- this.ws.on('close', (code, reason) => {
91
- // If close happens during connection attempt, treat as connection failure
92
- if (this.stateManager.isConnecting()) {
93
- // Transition to disconnected state so reconnection can proceed
94
- this.stateManager.transition(events_js_1.ConnectionState.DISCONNECTED);
95
- const error = new Error(`WebSocket connection failed (code: ${code}${reason ? ', reason: ' + reason : ''})`);
96
- this.emit('error', error);
97
- reject(error);
98
- // Still need to handle close to trigger reconnection
99
- this.handleClose(code, reason);
100
- }
101
- else {
102
- this.handleClose(code, reason);
103
- }
104
- });
105
- this.ws.on('error', (error) => {
106
- this.emit('error', error);
107
- if (this.stateManager.isConnecting()) {
108
- this.stateManager.transition(events_js_1.ConnectionState.DISCONNECTED);
109
- reject(error);
110
- }
111
- });
112
- }
113
- catch (error) {
114
- this.stateManager.transition(events_js_1.ConnectionState.DISCONNECTED);
115
- reject(error);
116
- }
117
- });
118
- }
119
- /**
120
- * Simulate connection in mock mode
121
- */
122
- async connectMock() {
123
- // Simulate connection delay
124
- await this.delay(10);
125
- // Transition to connected state
126
- this.handleOpen();
127
- // Send hello message in mock mode
128
- const helloResponse = await this.mockManager.getMockResponse({ type: 'hello' });
129
- if (helloResponse) {
130
- this.processMessage(helloResponse);
131
- }
132
- }
133
- /**
134
- * Disconnect from JMRI WebSocket server
135
- */
136
- async disconnect() {
137
- // Already disconnected, nothing to do
138
- if (this.stateManager.isDisconnected()) {
139
- return;
140
- }
141
- this.isManualDisconnect = true;
142
- this.reconnectionManager.stop();
143
- this.heartbeatManager.stop();
144
- // Send goodbye message
145
- if (this.stateManager.isConnected()) {
146
- try {
147
- await this.sendGoodbye();
148
- }
149
- catch (error) {
150
- // Ignore errors during goodbye
151
- }
152
- }
153
- // Close WebSocket
154
- if (this.ws) {
155
- this.ws.close();
156
- this.ws = undefined;
157
- }
158
- // Reject all pending requests
159
- this.rejectAllPendingRequests(new Error('Client disconnected'));
160
- if (!this.stateManager.isDisconnected()) {
161
- this.stateManager.transition(events_js_1.ConnectionState.DISCONNECTED);
162
- }
163
- }
164
- /**
165
- * Send message to JMRI (or mock)
166
- */
167
- send(message) {
168
- if (!this.stateManager.isConnected()) {
169
- // Queue message for later
170
- this.messageQueue.enqueue(message);
171
- return;
172
- }
173
- // Mock mode - send doesn't need to do anything
174
- // Responses are generated in request()
175
- if (this.mockManager) {
176
- this.emit('message:sent', message);
177
- return;
178
- }
179
- if (!this.ws) {
180
- throw new Error('WebSocket not initialized');
181
- }
182
- const json = JSON.stringify(message);
183
- this.ws.send(json);
184
- this.emit('message:sent', message);
185
- }
186
- /**
187
- * Send request and wait for response (or get mock response)
188
- */
189
- async request(message, timeout) {
190
- // Mock mode - get response from mock manager
191
- if (this.mockManager) {
192
- const response = await this.mockManager.getMockResponse(message);
193
- this.emit('message:sent', message);
194
- if (response) {
195
- this.processMessage(response);
196
- }
197
- return response;
198
- }
199
- // Real mode - send request and wait for response
200
- // Assign message ID
201
- const id = this.messageIdGen.next();
202
- message.id = id;
203
- return new Promise((resolve, reject) => {
204
- // Set up timeout
205
- const timeoutMs = timeout || this.options.requestTimeout;
206
- const timeoutHandle = setTimeout(() => {
207
- this.pendingRequests.delete(id);
208
- reject(new Error(`Request timeout after ${timeoutMs}ms`));
209
- }, timeoutMs);
210
- // Track pending request with metadata for matching responses without IDs
211
- const pendingRequest = {
212
- resolve,
213
- reject,
214
- timeout: timeoutHandle,
215
- messageType: message.type
216
- };
217
- // For throttle requests, store the throttle name for matching
218
- if (message.type === 'throttle' && message.data && 'name' in message.data) {
219
- pendingRequest.matchKey = message.data.name;
220
- }
221
- this.pendingRequests.set(id, pendingRequest);
222
- // Send message
223
- try {
224
- this.send(message);
225
- }
226
- catch (error) {
227
- this.pendingRequests.delete(id);
228
- clearTimeout(timeoutHandle);
229
- reject(error);
230
- }
231
- });
232
- }
233
- /**
234
- * Get current connection state
235
- */
236
- getState() {
237
- return this.stateManager.getState();
238
- }
239
- /**
240
- * Check if connected
241
- */
242
- isConnected() {
243
- return this.stateManager.isConnected();
244
- }
245
- /**
246
- * Handle WebSocket open event
247
- */
248
- handleOpen() {
249
- this.stateManager.transition(events_js_1.ConnectionState.CONNECTED);
250
- this.emit('connected');
251
- // Start heartbeat
252
- if (this.options.heartbeat.enabled) {
253
- this.heartbeatManager.start(() => this.sendPing());
254
- }
255
- // Flush queued messages
256
- const queuedMessages = this.messageQueue.flush();
257
- for (const message of queuedMessages) {
258
- this.send(message);
259
- }
260
- }
261
- /**
262
- * Handle WebSocket message event
263
- */
264
- handleMessage(data) {
265
- try {
266
- const message = JSON.parse(data);
267
- this.processMessage(message);
268
- }
269
- catch (error) {
270
- this.emit('error', new Error(`Failed to parse message: ${error}`));
271
- }
272
- }
273
- /**
274
- * Process a parsed message (called by both real and mock mode)
275
- */
276
- processMessage(message) {
277
- this.emit('message:received', message);
278
- // Handle pong
279
- if (message.type === 'pong') {
280
- this.heartbeatManager.receivedPong();
281
- return;
282
- }
283
- // Handle hello
284
- if (message.type === 'hello') {
285
- this.emit('hello', message.data);
286
- return;
287
- }
288
- // Handle response to request
289
- if (message.id !== undefined) {
290
- const pending = this.pendingRequests.get(message.id);
291
- if (pending) {
292
- clearTimeout(pending.timeout);
293
- this.pendingRequests.delete(message.id);
294
- pending.resolve(message);
295
- return;
296
- }
297
- }
298
- // Handle responses without ID (like throttle responses from JMRI)
299
- // Match by message type and data
300
- if (message.id === undefined) {
301
- for (const [id, pending] of this.pendingRequests.entries()) {
302
- // Match by type
303
- if (pending.messageType === message.type) {
304
- // For throttle messages, also match by throttle name
305
- if (message.type === 'throttle' && pending.matchKey) {
306
- const throttleName = message.data?.throttle || message.data?.name;
307
- if (throttleName === pending.matchKey) {
308
- clearTimeout(pending.timeout);
309
- this.pendingRequests.delete(id);
310
- pending.resolve(message);
311
- return;
312
- }
313
- }
314
- else {
315
- // For other message types, just match by type
316
- clearTimeout(pending.timeout);
317
- this.pendingRequests.delete(id);
318
- pending.resolve(message);
319
- return;
320
- }
321
- }
322
- }
323
- }
324
- // Handle unsolicited updates (auto-subscriptions)
325
- this.emit('update', message);
326
- }
327
- /**
328
- * Handle WebSocket close event
329
- */
330
- handleClose(code, reason) {
331
- this.heartbeatManager.stop();
332
- const wasConnected = this.stateManager.isConnected();
333
- const isReconnecting = this.reconnectionManager.reconnecting();
334
- if (this.stateManager.isConnected() || this.stateManager.isConnecting()) {
335
- this.stateManager.transition(events_js_1.ConnectionState.DISCONNECTED);
336
- }
337
- this.emit('disconnected', reason || `Connection closed (code: ${code})`);
338
- // Reject all pending requests
339
- this.rejectAllPendingRequests(new Error('Connection closed'));
340
- // Attempt reconnection if not manual disconnect
341
- // Continue reconnecting if we were connected OR reconnection manager is already active
342
- if (!this.isManualDisconnect && (wasConnected || isReconnecting) && this.options.reconnection.enabled) {
343
- this.stateManager.forceState(events_js_1.ConnectionState.RECONNECTING);
344
- this.reconnectionManager.start(() => this.connect());
345
- }
346
- }
347
- /**
348
- * Handle heartbeat timeout
349
- */
350
- handleHeartbeatTimeout() {
351
- // Connection appears dead, force reconnect
352
- if (this.ws) {
353
- this.ws.close();
354
- }
355
- }
356
- /**
357
- * Send ping message
358
- */
359
- sendPing() {
360
- this.send({ type: 'ping' });
361
- }
362
- /**
363
- * Send goodbye message
364
- */
365
- async sendGoodbye() {
366
- const message = { type: 'goodbye' };
367
- this.send(message);
368
- // Give it a moment to send
369
- await new Promise(resolve => setTimeout(resolve, 100));
370
- }
371
- /**
372
- * Reject all pending requests
373
- */
374
- rejectAllPendingRequests(error) {
375
- for (const pending of this.pendingRequests.values()) {
376
- clearTimeout(pending.timeout);
377
- pending.reject(error);
378
- }
379
- this.pendingRequests.clear();
380
- }
381
- /**
382
- * Delay helper
383
- */
384
- delay(ms) {
385
- return new Promise(resolve => setTimeout(resolve, ms));
386
- }
387
- }
388
- exports.WebSocketClient = WebSocketClient;