pulse-js-framework 1.7.30 → 1.7.32

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.
@@ -26,29 +26,32 @@
26
26
 
27
27
  import { pulse, computed, batch, onCleanup } from './pulse.js';
28
28
  import { createVersionedAsync } from './async.js';
29
- import { RuntimeError, createErrorMessage, getDocsUrl } from './errors.js';
29
+ import { ClientError } from './errors.js';
30
30
  import { loggers } from './logger.js';
31
+ import { MessageInterceptorManager } from './interceptor-manager.js';
31
32
 
32
33
  // ============================================================================
33
34
  // WebSocket Error Class
34
35
  // ============================================================================
35
36
 
36
37
  /**
37
- * WebSocket-specific error suggestions
38
+ * WebSocket Error with connection and reconnection context.
39
+ * Extends ClientError for consistent error handling patterns.
38
40
  */
39
- const WEBSOCKET_SUGGESTIONS = {
40
- CONNECT_FAILED: 'Check the WebSocket URL and ensure the server is running. Verify CORS settings if connecting cross-origin.',
41
- CLOSE: 'The connection was closed. Check close code for reason. Common codes: 1000 (normal), 1001 (going away), 1006 (abnormal).',
42
- TIMEOUT: 'Connection timed out. Check network conditions or increase the timeout value.',
43
- PARSE_ERROR: 'Failed to parse incoming message. Check message format matches expected type (JSON/binary).',
44
- SEND_FAILED: 'Failed to send message. Connection may be closed or message exceeds size limit.',
45
- RECONNECT_EXHAUSTED: 'Maximum reconnection attempts reached. Consider increasing maxRetries or checking server availability.'
46
- };
41
+ export class WebSocketError extends ClientError {
42
+ static suggestions = {
43
+ CONNECT_FAILED: 'Check the WebSocket URL and ensure the server is running. Verify CORS settings if connecting cross-origin.',
44
+ CLOSE: 'The connection was closed. Check close code for reason. Common codes: 1000 (normal), 1001 (going away), 1006 (abnormal).',
45
+ TIMEOUT: 'Connection timed out. Check network conditions or increase the timeout value.',
46
+ PARSE_ERROR: 'Failed to parse incoming message. Check message format matches expected type (JSON/binary).',
47
+ SEND_FAILED: 'Failed to send message. Connection may be closed or message exceeds size limit.',
48
+ RECONNECT_EXHAUSTED: 'Maximum reconnection attempts reached. Consider increasing maxRetries or checking server availability.'
49
+ };
50
+
51
+ static errorName = 'WebSocketError';
52
+ static defaultCode = 'WEBSOCKET_ERROR';
53
+ static markerProperty = 'isWebSocketError';
47
54
 
48
- /**
49
- * WebSocket Error with connection context
50
- */
51
- export class WebSocketError extends RuntimeError {
52
55
  /**
53
56
  * @param {string} message - Error message
54
57
  * @param {Object} [options={}] - Error options
@@ -57,25 +60,21 @@ export class WebSocketError extends RuntimeError {
57
60
  * @param {number} [options.closeCode] - WebSocket close code
58
61
  * @param {string} [options.closeReason] - WebSocket close reason
59
62
  * @param {Event} [options.event] - Original event
63
+ * @param {number} [options.reconnectAttempt] - Current reconnection attempt number
64
+ * @param {number} [options.maxRetries] - Maximum reconnection retries configured
65
+ * @param {number} [options.nextRetryDelay] - Delay until next retry in ms
60
66
  */
61
67
  constructor(message, options = {}) {
62
- const code = options.code || 'WEBSOCKET_ERROR';
63
- const formattedMessage = createErrorMessage({
64
- code,
65
- message,
66
- context: options.context,
67
- suggestion: options.suggestion || WEBSOCKET_SUGGESTIONS[code]
68
- });
69
-
70
- super(formattedMessage, { code });
71
-
72
- this.name = 'WebSocketError';
73
- this.code = code;
68
+ super(message, options);
74
69
  this.url = options.url || null;
75
70
  this.closeCode = options.closeCode || null;
76
71
  this.closeReason = options.closeReason || null;
77
72
  this.event = options.event || null;
78
- this.isWebSocketError = true;
73
+
74
+ // Reconnection context
75
+ this.reconnectAttempt = typeof options.reconnectAttempt === 'number' ? options.reconnectAttempt : null;
76
+ this.maxRetries = typeof options.maxRetries === 'number' ? options.maxRetries : null;
77
+ this.nextRetryDelay = typeof options.nextRetryDelay === 'number' ? options.nextRetryDelay : null;
79
78
  }
80
79
 
81
80
  /**
@@ -89,67 +88,45 @@ export class WebSocketError extends RuntimeError {
89
88
 
90
89
  isConnectFailed() { return this.code === 'CONNECT_FAILED'; }
91
90
  isClose() { return this.code === 'CLOSE'; }
92
- isTimeout() { return this.code === 'TIMEOUT'; }
93
91
  isParseError() { return this.code === 'PARSE_ERROR'; }
94
92
  isSendFailed() { return this.code === 'SEND_FAILED'; }
95
- }
96
-
97
- // ============================================================================
98
- // Message Interceptor Manager
99
- // ============================================================================
100
-
101
- /**
102
- * Manages message interceptors for incoming/outgoing messages
103
- */
104
- class MessageInterceptorManager {
105
- #handlers = new Map();
106
- #idCounter = 0;
107
-
108
- /**
109
- * Add a message interceptor
110
- * @param {Function} onMessage - Transform message data
111
- * @param {Function} [onError] - Handle interceptor errors
112
- * @returns {number} Interceptor ID
113
- */
114
- use(onMessage, onError) {
115
- const id = this.#idCounter++;
116
- this.#handlers.set(id, { onMessage, onError });
117
- return id;
118
- }
93
+ isReconnectExhausted() { return this.code === 'RECONNECT_EXHAUSTED'; }
119
94
 
120
95
  /**
121
- * Remove an interceptor by ID
122
- * @param {number} id - The interceptor ID
123
- */
124
- eject(id) {
125
- this.#handlers.delete(id);
126
- }
127
-
128
- /**
129
- * Remove all interceptors
96
+ * Check if this error occurred during reconnection
97
+ * @returns {boolean}
130
98
  */
131
- clear() {
132
- this.#handlers.clear();
99
+ isReconnecting() {
100
+ return this.reconnectAttempt !== null && this.reconnectAttempt > 0;
133
101
  }
134
102
 
135
103
  /**
136
- * Get the number of interceptors
137
- * @returns {number}
104
+ * Check if more reconnection attempts are available
105
+ * @returns {boolean}
138
106
  */
139
- get size() {
140
- return this.#handlers.size;
107
+ canRetry() {
108
+ if (this.maxRetries === null || this.reconnectAttempt === null) {
109
+ return false;
110
+ }
111
+ return this.reconnectAttempt < this.maxRetries;
141
112
  }
142
113
 
143
114
  /**
144
- * Iterate through handlers
115
+ * Get remaining reconnection attempts
116
+ * @returns {number|null}
145
117
  */
146
- *[Symbol.iterator]() {
147
- for (const handler of this.#handlers.values()) {
148
- yield handler;
118
+ getRemainingRetries() {
119
+ if (this.maxRetries === null || this.reconnectAttempt === null) {
120
+ return null;
149
121
  }
122
+ return Math.max(0, this.maxRetries - this.reconnectAttempt);
150
123
  }
151
124
  }
152
125
 
126
+ // ============================================================================
127
+ // Message Interceptor Factory
128
+ // ============================================================================
129
+
153
130
  // ============================================================================
154
131
  // Internal Helpers
155
132
  // ============================================================================
@@ -487,10 +464,13 @@ export function createWebSocket(url, options = {}) {
487
464
 
488
465
  function handleSocketError(event, ctx) {
489
466
  ctx.ifCurrent(() => {
467
+ const attempt = reconnectAttempt.get();
490
468
  const wsError = new WebSocketError('WebSocket error', {
491
469
  code: 'CONNECT_FAILED',
492
470
  url,
493
- event
471
+ event,
472
+ reconnectAttempt: attempt > 0 ? attempt : null,
473
+ maxRetries: opts.reconnect ? opts.maxRetries : null
494
474
  });
495
475
  error.set(wsError);
496
476
  eventEmitter.emit('error', wsError);
@@ -501,6 +481,12 @@ export function createWebSocket(url, options = {}) {
501
481
 
502
482
  function handleError(wsError, ctx) {
503
483
  ctx.ifCurrent(() => {
484
+ // Add reconnection context to error if not already present
485
+ if (wsError.reconnectAttempt === null && opts.reconnect) {
486
+ wsError.reconnectAttempt = reconnectAttempt.get();
487
+ wsError.maxRetries = opts.maxRetries;
488
+ }
489
+
504
490
  error.set(wsError);
505
491
  state.set('closed');
506
492
  eventEmitter.emit('error', wsError);
@@ -560,7 +546,9 @@ export function createWebSocket(url, options = {}) {
560
546
  if (opts.maxRetries > 0 && attempt >= opts.maxRetries) {
561
547
  error.set(new WebSocketError('Max reconnection attempts reached', {
562
548
  code: 'RECONNECT_EXHAUSTED',
563
- url
549
+ url,
550
+ reconnectAttempt: attempt,
551
+ maxRetries: opts.maxRetries
564
552
  }));
565
553
  reconnecting.set(false);
566
554
  return;
@@ -864,6 +852,7 @@ export function useWebSocket(url, options = {}) {
864
852
  // Exports
865
853
  // ============================================================================
866
854
 
855
+ // Re-export MessageInterceptorManager from shared module
867
856
  export { MessageInterceptorManager };
868
857
 
869
858
  export default {