@drift-labs/common 1.0.35 → 1.0.37

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.
@@ -216,8 +216,8 @@ class MultiplexWebSocket {
216
216
  }
217
217
  }
218
218
  }
219
- // Restart the websocket connection on error
220
- this.refreshWebSocket();
219
+ // Note: onclose always fires after onerror, so reconnection is handled there
220
+ // to avoid double-counting reconnection attempts.
221
221
  };
222
222
  __classPrivateFieldSet(this, _MultiplexWebSocket_webSocket, webSocket, "f");
223
223
  }
@@ -386,7 +386,22 @@ class MultiplexWebSocket {
386
386
  if (this.heartbeatMonitoringEnabled) {
387
387
  this.stopHeartbeatMonitoring();
388
388
  }
389
- const { shouldReconnect, delay } = this.reconnectionManager.attemptReconnection(this.wsUrl);
389
+ let shouldReconnect;
390
+ let delay;
391
+ try {
392
+ ({ shouldReconnect, delay } =
393
+ this.reconnectionManager.attemptReconnection(this.wsUrl));
394
+ }
395
+ catch (error) {
396
+ // Max reconnect attempts exceeded — close gracefully and notify all subscribers
397
+ console.error('WebSocket reconnection failed', error);
398
+ // Forward error to all subscriptions before closing
399
+ for (const [, subscription] of this.subscriptions.entries()) {
400
+ subscription.onError(error);
401
+ }
402
+ this.close();
403
+ return;
404
+ }
390
405
  if (!shouldReconnect) {
391
406
  return;
392
407
  }
@@ -1 +1 @@
1
- {"version":3,"file":"MultiplexWebSocket.js","sourceRoot":"","sources":["../../src/utils/MultiplexWebSocket.ts"],"names":[],"mappings":"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","sourcesContent":["import { Subject, Subscription, catchError } from 'rxjs';\nimport WebSocket, { MessageEvent } from 'isomorphic-ws';\n\ntype WebSocketMessage<T = Record<string, unknown>> = T;\n\ntype WebSocketSubscriptionProps<T = Record<string, unknown>> = {\n\twsUrl: string;\n\tsubscriptionId: string;\n\tsubscribeMessage: string;\n\tunsubscribeMessage: string;\n\tonError: (err?: any) => void;\n\tonMessage: (message: WebSocketMessage<T>) => void;\n\tmessageFilter?: (message: WebSocketMessage<T>) => boolean;\n\terrorMessageFilter?: (message: WebSocketMessage<T>) => boolean;\n\tonClose?: () => void;\n\tenableHeartbeatMonitoring?: boolean;\n\theartbeatTimeoutMs?: number;\n};\n\ntype WebSocketSubscriptionState<T = Record<string, unknown>> =\n\tWebSocketSubscriptionProps<T> & {\n\t\thasSentSubscribeMessage?: boolean;\n\t\tsubjectSubscription?: Subscription;\n\t};\n\ntype WebSocketUrl = string;\ntype SubscriptionId = string;\n\nconst DEFAULT_MAX_RECONNECT_ATTEMPTS = 5;\nconst DEFAULT_MAX_RECONNECT_WINDOW_MS = 60 * 1000;\nconst DEFAULT_CONNECTION_CLOSE_DELAY_MS = 2 * 1000; // 2 seconds delay before closing connection\nconst DEFAULT_HEARTBEAT_TIMEOUT_MS = 11 * 1000; // Consider connection dead if no heartbeat within 11 seconds (a little more than twice of 5 seconds, the interval Drift servers use)\n\n/**\n * Manages reconnection logic for WebSocket connections with exponential backoff and rate limiting.\n *\n * Features:\n * - Tracks reconnection attempts within a time window\n * - Implements exponential backoff (1s, 2s, 4s, 8s max)\n * - Resets attempt counter after configurable time window\n * - Throws error when max attempts exceeded\n * - Provides configurable limits for attempts and time window\n *\n * @example\n * ```ts\n * const manager = new ReconnectionManager(5, 60000); // 5 attempts in 60s\n * const { shouldReconnect, delay } = manager.attemptReconnection('ws://example.com');\n * if (shouldReconnect) {\n * setTimeout(() => reconnect(), delay);\n * }\n * ```\n */\nclass ReconnectionManager {\n\tprivate reconnectAttempts: number = 0;\n\tprivate lastReconnectWindow: number = Date.now();\n\tprivate maxAttemptsCount: number;\n\tprivate maxAttemptsWindowMs: number;\n\n\tconstructor(\n\t\tmaxAttemptsCount = DEFAULT_MAX_RECONNECT_ATTEMPTS,\n\t\tmaxAttemptsWindowMs = DEFAULT_MAX_RECONNECT_WINDOW_MS\n\t) {\n\t\tthis.maxAttemptsCount = maxAttemptsCount;\n\t\tthis.maxAttemptsWindowMs = maxAttemptsWindowMs;\n\t}\n\n\tpublic attemptReconnection(wsUrl: string): {\n\t\tshouldReconnect: boolean;\n\t\tdelay: number;\n\t} {\n\t\tconst now = Date.now();\n\n\t\t// Reset reconnect attempts if more than a minute has passed\n\t\tif (now - this.lastReconnectWindow > this.maxAttemptsWindowMs) {\n\t\t\tthis.reconnectAttempts = 0;\n\t\t\tthis.lastReconnectWindow = now;\n\t\t}\n\n\t\t// Check if we've exceeded the maximum reconnect attempts\n\t\tif (this.reconnectAttempts >= this.maxAttemptsCount) {\n\t\t\tthrow new Error(\n\t\t\t\t`WebSocket reconnection failed: Maximum reconnect attempts (${this.maxAttemptsCount}) exceeded within ${this.maxAttemptsWindowMs}ms for ${wsUrl}`\n\t\t\t);\n\t\t}\n\n\t\tthis.reconnectAttempts++;\n\n\t\t// Calculate exponential backoff delay: 1s, 2s, 4s, 8s, etc. (capped at 8s)\n\t\tconst backoffDelay = Math.min(\n\t\t\t1000 * Math.pow(2, this.reconnectAttempts - 1),\n\t\t\t8000\n\t\t);\n\n\t\treturn {\n\t\t\tshouldReconnect: true,\n\t\t\tdelay: backoffDelay,\n\t\t};\n\t}\n\n\tpublic reset(): void {\n\t\tthis.reconnectAttempts = 0;\n\t\tthis.lastReconnectWindow = Date.now();\n\t}\n}\n\nenum WebSocketConnectionState {\n\tCONNECTING,\n\tCONNECTED,\n\tDISCONNECTING,\n\tDISCONNECTED,\n}\n\ntype IMultiplexWebSocket<T = Record<string, unknown>> = {\n\twsUrl: WebSocketUrl;\n\twebSocket: WebSocket;\n\tcustomConnectionState: WebSocketConnectionState;\n\tsubject: Subject<WebSocketMessage<T>>;\n\tsubscriptions: Map<\n\t\tSubscriptionId,\n\t\tOmit<WebSocketSubscriptionProps<T>, 'wsUrl' | 'subscriptionId'>\n\t>;\n};\n\n/**\n * MultiplexWebSocket allows for multiple subscriptions to a single websocket of the same URL, improving efficiency and reducing the number of open connections.\n *\n * This implementation assumes the following:\n * - All websocket streams are treated equally - reconnection attempts are performed at the same standards\n * - All messages returned are in the `WebSocketMessage` format\n * - A single instance of the websocket manager is created for each websocket URL - this means all subscriptions to the same websocket URL will share the same websocket instance\n *\n * Internal implementation details:\n * - The websocket is closed when the number of subscriptions is 0\n * - The websocket will be refreshed (new instance) when it disconnects unexpectedly or errors, until it reaches the maximum number of reconnect attempts\n */\nexport class MultiplexWebSocket<T = Record<string, unknown>>\n\timplements IMultiplexWebSocket<T>\n{\n\t/**\n\t * A lookup of all websockets by their URL.\n\t */\n\tprivate static URL_TO_WEBSOCKETS_LOOKUP = new Map<\n\t\tWebSocketUrl,\n\t\tMultiplexWebSocket<any>\n\t>();\n\n\t/**\n\t * A lookup from websocket URL to all subscription IDs for that URL.\n\t */\n\tprivate static URL_TO_SUBSCRIPTION_IDS_LOOKUP = new Map<\n\t\tWebSocketUrl,\n\t\tSet<SubscriptionId>\n\t>();\n\n\twsUrl: WebSocketUrl;\n\t#webSocket: WebSocket;\n\tcustomConnectionState: WebSocketConnectionState;\n\tsubject: Subject<WebSocketMessage<T>>;\n\tsubscriptions: Map<\n\t\tSubscriptionId,\n\t\tOmit<WebSocketSubscriptionState<T>, 'wsUrl' | 'subscriptionId'>\n\t>;\n\tprivate reconnectionManager: ReconnectionManager;\n\tprivate closeTimeout: NodeJS.Timeout | null = null;\n\tprivate heartbeatTimeout: NodeJS.Timeout | null = null;\n\tprivate heartbeatMonitoringEnabled: boolean = false;\n\tprivate heartbeatTimeoutMs: number = DEFAULT_HEARTBEAT_TIMEOUT_MS;\n\n\tprivate constructor(\n\t\twsUrl: WebSocketUrl,\n\t\tenableHeartbeatMonitoring: boolean = false,\n\t\theartbeatTimeoutMs: number = DEFAULT_HEARTBEAT_TIMEOUT_MS\n\t) {\n\t\tif (MultiplexWebSocket.URL_TO_WEBSOCKETS_LOOKUP.has(wsUrl)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Attempting to create a new websocket for ${wsUrl}, but it already exists`\n\t\t\t);\n\t\t}\n\n\t\tthis.wsUrl = wsUrl;\n\t\tthis.customConnectionState = WebSocketConnectionState.CONNECTING;\n\t\tthis.subject = new Subject<WebSocketMessage<T>>();\n\t\tthis.subscriptions = new Map<\n\t\t\tSubscriptionId,\n\t\t\tOmit<WebSocketSubscriptionProps<T>, 'wsUrl' | 'subscriptionId'>\n\t\t>();\n\n\t\tthis.reconnectionManager = new ReconnectionManager();\n\t\tthis.heartbeatMonitoringEnabled = enableHeartbeatMonitoring;\n\t\tthis.heartbeatTimeoutMs = heartbeatTimeoutMs;\n\n\t\tthis.webSocket = new WebSocket(wsUrl);\n\n\t\tMultiplexWebSocket.URL_TO_WEBSOCKETS_LOOKUP.set(wsUrl, this);\n\t}\n\n\t/**\n\t * Creates a new virtual websocket subscription. If an existing websocket for the given URL exists, the subscription will be added to the existing websocket.\n\t * Returns a function that can be called to unsubscribe from the subscription.\n\t */\n\tpublic static createWebSocketSubscription<T = Record<string, unknown>>(\n\t\tprops: WebSocketSubscriptionProps<T>\n\t): { unsubscribe: () => void } {\n\t\tconst { wsUrl } = props;\n\n\t\tconst doesWebSocketForWsUrlExist =\n\t\t\tMultiplexWebSocket.URL_TO_WEBSOCKETS_LOOKUP.has(wsUrl);\n\n\t\tif (doesWebSocketForWsUrlExist) {\n\t\t\treturn this.handleNewSubForExistingWsUrl<T>(props);\n\t\t} else {\n\t\t\t// Create new websocket for new URL or reopen previously closed websocket\n\t\t\treturn this.handleNewSubForNewWsUrl<T>(props);\n\t\t}\n\t}\n\n\tprivate static handleNewSubForNewWsUrl<T = Record<string, unknown>>(\n\t\tnewSubscriptionProps: WebSocketSubscriptionProps<T>\n\t) {\n\t\tconst newMWS = new MultiplexWebSocket<T>(\n\t\t\tnewSubscriptionProps.wsUrl,\n\t\t\tnewSubscriptionProps.enableHeartbeatMonitoring ?? false,\n\t\t\tnewSubscriptionProps.heartbeatTimeoutMs ?? DEFAULT_HEARTBEAT_TIMEOUT_MS\n\t\t);\n\n\t\tnewMWS.subscribe(newSubscriptionProps);\n\n\t\treturn {\n\t\t\tunsubscribe: () => {\n\t\t\t\tnewMWS.unsubscribe(newSubscriptionProps.subscriptionId);\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate static handleNewSubForExistingWsUrl<T = Record<string, unknown>>(\n\t\tnewSubscriptionProps: WebSocketSubscriptionProps<T>\n\t) {\n\t\tconst { wsUrl, subscriptionId } = newSubscriptionProps;\n\n\t\tif (!MultiplexWebSocket.URL_TO_WEBSOCKETS_LOOKUP.has(wsUrl)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Attempting to subscribe to ${subscriptionId} on websocket ${wsUrl}, but websocket does not exist yet`\n\t\t\t);\n\t\t}\n\n\t\tconst existingMWS = MultiplexWebSocket.URL_TO_WEBSOCKETS_LOOKUP.get(\n\t\t\twsUrl\n\t\t) as MultiplexWebSocket<T>;\n\n\t\t// Check if heartbeat monitoring settings match\n\t\tconst requestedHeartbeat =\n\t\t\tnewSubscriptionProps.enableHeartbeatMonitoring ?? false;\n\t\tif (existingMWS.heartbeatMonitoringEnabled !== requestedHeartbeat) {\n\t\t\tconsole.warn(\n\t\t\t\t`WebSocket for ${wsUrl} already exists with heartbeat monitoring ${\n\t\t\t\t\texistingMWS.heartbeatMonitoringEnabled ? 'enabled' : 'disabled'\n\t\t\t\t}, ` +\n\t\t\t\t\t`but new subscription requests ${\n\t\t\t\t\t\trequestedHeartbeat ? 'enabled' : 'disabled'\n\t\t\t\t\t}. Using existing setting.`\n\t\t\t);\n\t\t}\n\n\t\t// Check if heartbeat timeout settings match for the same websocket URL (note that this assumes that all types of subscriptions from the same websocket URL are expected to have the same heartbeat interval)\n\t\tconst requestedTimeout =\n\t\t\tnewSubscriptionProps.heartbeatTimeoutMs ?? DEFAULT_HEARTBEAT_TIMEOUT_MS;\n\t\tif (existingMWS.heartbeatTimeoutMs !== requestedTimeout) {\n\t\t\tconsole.warn(\n\t\t\t\t`WebSocket for ${wsUrl} already exists with heartbeat timeout ${existingMWS.heartbeatTimeoutMs}ms, ` +\n\t\t\t\t\t`but new subscription requests ${requestedTimeout}ms. Using existing setting.`\n\t\t\t);\n\t\t}\n\n\t\t// Track new subscription for existing websocket\n\t\texistingMWS.subscribe(newSubscriptionProps);\n\n\t\treturn {\n\t\t\tunsubscribe: () => {\n\t\t\t\texistingMWS.unsubscribe(newSubscriptionProps.subscriptionId);\n\t\t\t},\n\t\t};\n\t}\n\n\tget webSocket() {\n\t\treturn this.#webSocket;\n\t}\n\n\t/**\n\t * Setting the WebSocket instance will automatically add event handlers to the WebSocket instance.\n\t * When the WebSocket is connected, all existing subscriptions will be subscribed to.\n\t */\n\tset webSocket(webSocket: WebSocket) {\n\t\twebSocket.onopen = () => {\n\t\t\tthis.customConnectionState = WebSocketConnectionState.CONNECTED;\n\t\t\tthis.reconnectionManager.reset(); // Reset reconnection attempts on successful connection\n\n\t\t\t// Start heartbeat monitoring if enabled\n\t\t\tif (this.heartbeatMonitoringEnabled) {\n\t\t\t\tthis.startHeartbeatMonitoring();\n\t\t\t}\n\n\t\t\t// sends subscription message for each subscription for those that are added before the websocket is connected\n\t\t\tfor (const [subscriptionId] of this.subscriptions.entries()) {\n\t\t\t\tthis.subscribeToWebSocket(subscriptionId);\n\t\t\t}\n\t\t};\n\n\t\twebSocket.onmessage = (messageEvent: MessageEvent) => {\n\t\t\tconst message = JSON.parse(\n\t\t\t\tmessageEvent.data as string\n\t\t\t) as WebSocketMessage<T>;\n\n\t\t\t// Check for heartbeat message from server (only if heartbeat monitoring is enabled)\n\t\t\tif (this.heartbeatMonitoringEnabled && this.isHeartbeatMessage(message)) {\n\t\t\t\tthis.handleHeartbeat(message);\n\t\t\t\treturn; // Don't forward heartbeat messages to subscriptions\n\t\t\t}\n\n\t\t\tthis.subject.next(message);\n\t\t};\n\n\t\twebSocket.onclose = (event) => {\n\t\t\tthis.customConnectionState = WebSocketConnectionState.DISCONNECTED;\n\n\t\t\t// Stop heartbeat monitoring when connection closes (if enabled)\n\t\t\tif (this.heartbeatMonitoringEnabled) {\n\t\t\t\tthis.stopHeartbeatMonitoring();\n\t\t\t}\n\n\t\t\t// Restart websocket if it was closed unexpectedly (not by us)\n\t\t\tif (!event.wasClean && this.subscriptions.size > 0) {\n\t\t\t\tconsole.log('WebSocket closed unexpectedly, restarting...', event);\n\t\t\t\tthis.refreshWebSocket();\n\t\t\t}\n\t\t};\n\n\t\twebSocket.onerror = (error) => {\n\t\t\tconsole.error('MultiplexWebSocket Error', { error, webSocket });\n\n\t\t\t// Forward error to all subscriptions for this websocket URL\n\t\t\tconst subscriptionIds =\n\t\t\t\tMultiplexWebSocket.URL_TO_SUBSCRIPTION_IDS_LOOKUP.get(this.wsUrl);\n\t\t\tif (subscriptionIds) {\n\t\t\t\tfor (const subscriptionId of subscriptionIds) {\n\t\t\t\t\tconst subscription = this.subscriptions.get(subscriptionId);\n\t\t\t\t\tif (subscription) {\n\t\t\t\t\t\tsubscription.onError(error);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Restart the websocket connection on error\n\t\t\tthis.refreshWebSocket();\n\t\t};\n\n\t\tthis.#webSocket = webSocket;\n\t}\n\n\tprivate subscribeToWebSocket(subscriptionId: SubscriptionId) {\n\t\tconst subscriptionState = this.subscriptions.get(subscriptionId);\n\n\t\tconst {\n\t\t\tsubscribeMessage,\n\t\t\tonError,\n\t\t\tonMessage,\n\t\t\tmessageFilter,\n\t\t\terrorMessageFilter,\n\t\t\tonClose,\n\t\t} = subscriptionState;\n\n\t\tthis.webSocket.send(subscribeMessage);\n\t\tif (subscriptionState) {\n\t\t\tsubscriptionState.hasSentSubscribeMessage = true;\n\t\t}\n\n\t\t// Create internal subscription for message handling\n\t\tconst subjectSubscription = this.subject\n\t\t\t.pipe(\n\t\t\t\tcatchError((err) => {\n\t\t\t\t\tconsole.error('Caught websocket error', err);\n\t\t\t\t\tonError();\n\t\t\t\t\treturn [];\n\t\t\t\t})\n\t\t\t)\n\t\t\t.subscribe({\n\t\t\t\tnext: (message: WebSocketMessage<T>) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (messageFilter && !messageFilter(message)) return;\n\n\t\t\t\t\t\tif (errorMessageFilter && errorMessageFilter(message)) {\n\t\t\t\t\t\t\tonError();\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tonMessage(message);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconsole.error('Error parsing websocket message', err);\n\t\t\t\t\t\tonError();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\terror: (err) => {\n\t\t\t\t\tconsole.error('Error subscribing to websocket', err);\n\t\t\t\t\tonError();\n\t\t\t\t},\n\t\t\t\tcomplete: () => {\n\t\t\t\t\tif (onClose) {\n\t\t\t\t\t\tonClose();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t});\n\n\t\tsubscriptionState.subjectSubscription = subjectSubscription;\n\t}\n\n\tprivate subscribe(props: WebSocketSubscriptionProps<T>) {\n\t\tconst {\n\t\t\tsubscriptionId,\n\t\t\tsubscribeMessage,\n\t\t\tunsubscribeMessage,\n\t\t\tonError,\n\t\t\tonMessage,\n\t\t\tmessageFilter,\n\t\t\terrorMessageFilter,\n\t\t\tonClose,\n\t\t} = props;\n\n\t\tif (this.subscriptions.get(subscriptionId)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Attempting to subscribe to ${subscriptionId} on websocket ${this.wsUrl}, but subscription already exists`\n\t\t\t);\n\t\t}\n\n\t\t// Cancel any pending delayed close since we're adding a new subscription\n\t\tthis.cancelDelayedClose();\n\n\t\tthis.subscriptions.set(subscriptionId, {\n\t\t\tsubscribeMessage,\n\t\t\tunsubscribeMessage,\n\t\t\tonError,\n\t\t\tonMessage,\n\t\t\tmessageFilter,\n\t\t\terrorMessageFilter,\n\t\t\tonClose,\n\t\t\thasSentSubscribeMessage: false,\n\t\t});\n\n\t\t// Update URL to subscription IDs lookup\n\t\tif (!MultiplexWebSocket.URL_TO_SUBSCRIPTION_IDS_LOOKUP.has(this.wsUrl)) {\n\t\t\tMultiplexWebSocket.URL_TO_SUBSCRIPTION_IDS_LOOKUP.set(\n\t\t\t\tthis.wsUrl,\n\t\t\t\tnew Set()\n\t\t\t);\n\t\t}\n\t\tMultiplexWebSocket.URL_TO_SUBSCRIPTION_IDS_LOOKUP.get(this.wsUrl)!.add(\n\t\t\tsubscriptionId\n\t\t);\n\n\t\tif (this.customConnectionState === WebSocketConnectionState.CONNECTED) {\n\t\t\tthis.subscribeToWebSocket(subscriptionId);\n\t\t} else if (\n\t\t\tthis.customConnectionState === WebSocketConnectionState.CONNECTING\n\t\t) {\n\t\t\t// do nothing, subscription will automatically start when websocket is connected\n\t\t} else {\n\t\t\t// handle case where websocket is disconnecting/disconnected\n\t\t\tthis.refreshWebSocket();\n\t\t}\n\t}\n\n\tprivate unsubscribe(subscriptionId: SubscriptionId) {\n\t\tconst subscriptionState = this.subscriptions.get(subscriptionId);\n\t\tif (subscriptionState) {\n\t\t\tsubscriptionState.subjectSubscription?.unsubscribe();\n\n\t\t\t// Only send unsubscribe message if websocket is connected and ready to send.\n\t\t\t//// Otherwise, when the websocket DOES connect we don't have to worry about this subscription because we are deleting it from the subscriptions map. (Which only trigger their connections once the websocket becomes connected)\n\t\t\tif (\n\t\t\t\tthis.customConnectionState === WebSocketConnectionState.CONNECTED &&\n\t\t\t\tthis.webSocket.readyState === WebSocket.OPEN\n\t\t\t) {\n\t\t\t\tthis.webSocket.send(subscriptionState.unsubscribeMessage);\n\t\t\t}\n\n\t\t\tthis.subscriptions.delete(subscriptionId);\n\n\t\t\t// Update URL to subscription IDs lookup\n\t\t\tconst subscriptionIds =\n\t\t\t\tMultiplexWebSocket.URL_TO_SUBSCRIPTION_IDS_LOOKUP.get(this.wsUrl);\n\t\t\tif (subscriptionIds) {\n\t\t\t\tsubscriptionIds.delete(subscriptionId);\n\t\t\t\tif (subscriptionIds.size === 0) {\n\t\t\t\t\tMultiplexWebSocket.URL_TO_SUBSCRIPTION_IDS_LOOKUP.delete(this.wsUrl);\n\t\t\t\t\t// Schedule delayed close when last subscriber unsubscribes\n\t\t\t\t\tthis.scheduleDelayedClose();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate scheduleDelayedClose() {\n\t\t// Cancel any existing delayed close timeout\n\t\tthis.cancelDelayedClose();\n\n\t\t// Schedule new delayed close\n\t\tthis.closeTimeout = setTimeout(() => {\n\t\t\tthis.close();\n\t\t}, DEFAULT_CONNECTION_CLOSE_DELAY_MS);\n\t}\n\n\tprivate cancelDelayedClose() {\n\t\tif (this.closeTimeout) {\n\t\t\tclearTimeout(this.closeTimeout);\n\t\t\tthis.closeTimeout = null;\n\t\t}\n\t}\n\n\tprivate close() {\n\t\t// Cancel any pending delayed close\n\t\tthis.cancelDelayedClose();\n\n\t\t// Stop heartbeat monitoring (if enabled)\n\t\tif (this.heartbeatMonitoringEnabled) {\n\t\t\tthis.stopHeartbeatMonitoring();\n\t\t}\n\n\t\tfor (const [subscriptionId] of this.subscriptions.entries()) {\n\t\t\tthis.unsubscribe(subscriptionId);\n\t\t}\n\n\t\tthis.subscriptions.clear();\n\t\tthis.webSocket.close();\n\t\tMultiplexWebSocket.URL_TO_WEBSOCKETS_LOOKUP.delete(this.wsUrl);\n\t\tMultiplexWebSocket.URL_TO_SUBSCRIPTION_IDS_LOOKUP.delete(this.wsUrl);\n\t\tthis.reconnectionManager.reset();\n\t}\n\n\tprivate startHeartbeatMonitoring() {\n\t\t// Start the heartbeat timeout - if we don't receive a heartbeat message within the timeout, refresh connection\n\t\tthis.resetHeartbeatTimeout();\n\t}\n\n\tprivate stopHeartbeatMonitoring() {\n\t\tif (this.heartbeatTimeout) {\n\t\t\tclearTimeout(this.heartbeatTimeout);\n\t\t\tthis.heartbeatTimeout = null;\n\t\t}\n\t}\n\n\tprivate resetHeartbeatTimeout() {\n\t\t// Clear existing timeout\n\t\tif (this.heartbeatTimeout) {\n\t\t\tclearTimeout(this.heartbeatTimeout);\n\t\t}\n\n\t\t// Set new timeout\n\t\tthis.heartbeatTimeout = setTimeout(() => {\n\t\t\tconsole.warn(\n\t\t\t\t`No heartbeat received within ${this.heartbeatTimeoutMs}ms - connection appears dead`\n\t\t\t);\n\t\t\tthis.refreshWebSocket();\n\t\t}, this.heartbeatTimeoutMs);\n\t}\n\n\tprivate isHeartbeatMessage(message: WebSocketMessage<T>): boolean {\n\t\t// Check if message is a heartbeat message from server\n\t\treturn (message as any)?.channel === 'heartbeat';\n\t}\n\n\tprivate handleHeartbeat(_message: WebSocketMessage<T>) {\n\t\t// Reset the heartbeat timeout\n\t\tthis.resetHeartbeatTimeout();\n\t}\n\n\tprivate refreshWebSocket() {\n\t\t// Cancel any pending delayed close since we're refreshing\n\t\tthis.cancelDelayedClose();\n\n\t\t// Reset heartbeat monitoring during refresh (if enabled) - it will restart when the new connection opens\n\t\tif (this.heartbeatMonitoringEnabled) {\n\t\t\tthis.stopHeartbeatMonitoring();\n\t\t}\n\n\t\tconst { shouldReconnect, delay } =\n\t\t\tthis.reconnectionManager.attemptReconnection(this.wsUrl);\n\n\t\tif (!shouldReconnect) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Clean up current websocket\n\t\tconst currentWebSocket = this.webSocket;\n\t\tif (currentWebSocket) {\n\t\t\tcurrentWebSocket.onerror = () => {};\n\t\t\tcurrentWebSocket.onclose = () => {};\n\t\t\tcurrentWebSocket.onmessage = () => {};\n\t\t\tcurrentWebSocket.onopen = () => {\n\t\t\t\t// in the event where the websocket has yet to connect, we close the connection after it is connected\n\t\t\t\tcurrentWebSocket.close();\n\t\t\t};\n\t\t\tcurrentWebSocket.close();\n\t\t}\n\n\t\t// Reset subscription states\n\t\tthis.subscriptions.forEach((subscription) => {\n\t\t\tsubscription.hasSentSubscribeMessage = false;\n\t\t});\n\n\t\t// Use exponential backoff before attempting to reconnect\n\t\tsetTimeout(() => {\n\t\t\tthis.webSocket = new WebSocket(this.wsUrl);\n\t\t}, delay);\n\t}\n}\n"]}
1
+ {"version":3,"file":"MultiplexWebSocket.js","sourceRoot":"","sources":["../../src/utils/MultiplexWebSocket.ts"],"names":[],"mappings":"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","sourcesContent":["import { Subject, Subscription, catchError } from 'rxjs';\nimport WebSocket, { MessageEvent } from 'isomorphic-ws';\n\ntype WebSocketMessage<T = Record<string, unknown>> = T;\n\ntype WebSocketSubscriptionProps<T = Record<string, unknown>> = {\n\twsUrl: string;\n\tsubscriptionId: string;\n\tsubscribeMessage: string;\n\tunsubscribeMessage: string;\n\tonError: (err?: any) => void;\n\tonMessage: (message: WebSocketMessage<T>) => void;\n\tmessageFilter?: (message: WebSocketMessage<T>) => boolean;\n\terrorMessageFilter?: (message: WebSocketMessage<T>) => boolean;\n\tonClose?: () => void;\n\tenableHeartbeatMonitoring?: boolean;\n\theartbeatTimeoutMs?: number;\n};\n\ntype WebSocketSubscriptionState<T = Record<string, unknown>> =\n\tWebSocketSubscriptionProps<T> & {\n\t\thasSentSubscribeMessage?: boolean;\n\t\tsubjectSubscription?: Subscription;\n\t};\n\ntype WebSocketUrl = string;\ntype SubscriptionId = string;\n\nconst DEFAULT_MAX_RECONNECT_ATTEMPTS = 5;\nconst DEFAULT_MAX_RECONNECT_WINDOW_MS = 60 * 1000;\nconst DEFAULT_CONNECTION_CLOSE_DELAY_MS = 2 * 1000; // 2 seconds delay before closing connection\nconst DEFAULT_HEARTBEAT_TIMEOUT_MS = 11 * 1000; // Consider connection dead if no heartbeat within 11 seconds (a little more than twice of 5 seconds, the interval Drift servers use)\n\n/**\n * Manages reconnection logic for WebSocket connections with exponential backoff and rate limiting.\n *\n * Features:\n * - Tracks reconnection attempts within a time window\n * - Implements exponential backoff (1s, 2s, 4s, 8s max)\n * - Resets attempt counter after configurable time window\n * - Throws error when max attempts exceeded\n * - Provides configurable limits for attempts and time window\n *\n * @example\n * ```ts\n * const manager = new ReconnectionManager(5, 60000); // 5 attempts in 60s\n * const { shouldReconnect, delay } = manager.attemptReconnection('ws://example.com');\n * if (shouldReconnect) {\n * setTimeout(() => reconnect(), delay);\n * }\n * ```\n */\nclass ReconnectionManager {\n\tprivate reconnectAttempts: number = 0;\n\tprivate lastReconnectWindow: number = Date.now();\n\tprivate maxAttemptsCount: number;\n\tprivate maxAttemptsWindowMs: number;\n\n\tconstructor(\n\t\tmaxAttemptsCount = DEFAULT_MAX_RECONNECT_ATTEMPTS,\n\t\tmaxAttemptsWindowMs = DEFAULT_MAX_RECONNECT_WINDOW_MS\n\t) {\n\t\tthis.maxAttemptsCount = maxAttemptsCount;\n\t\tthis.maxAttemptsWindowMs = maxAttemptsWindowMs;\n\t}\n\n\tpublic attemptReconnection(wsUrl: string): {\n\t\tshouldReconnect: boolean;\n\t\tdelay: number;\n\t} {\n\t\tconst now = Date.now();\n\n\t\t// Reset reconnect attempts if more than a minute has passed\n\t\tif (now - this.lastReconnectWindow > this.maxAttemptsWindowMs) {\n\t\t\tthis.reconnectAttempts = 0;\n\t\t\tthis.lastReconnectWindow = now;\n\t\t}\n\n\t\t// Check if we've exceeded the maximum reconnect attempts\n\t\tif (this.reconnectAttempts >= this.maxAttemptsCount) {\n\t\t\tthrow new Error(\n\t\t\t\t`WebSocket reconnection failed: Maximum reconnect attempts (${this.maxAttemptsCount}) exceeded within ${this.maxAttemptsWindowMs}ms for ${wsUrl}`\n\t\t\t);\n\t\t}\n\n\t\tthis.reconnectAttempts++;\n\n\t\t// Calculate exponential backoff delay: 1s, 2s, 4s, 8s, etc. (capped at 8s)\n\t\tconst backoffDelay = Math.min(\n\t\t\t1000 * Math.pow(2, this.reconnectAttempts - 1),\n\t\t\t8000\n\t\t);\n\n\t\treturn {\n\t\t\tshouldReconnect: true,\n\t\t\tdelay: backoffDelay,\n\t\t};\n\t}\n\n\tpublic reset(): void {\n\t\tthis.reconnectAttempts = 0;\n\t\tthis.lastReconnectWindow = Date.now();\n\t}\n}\n\nenum WebSocketConnectionState {\n\tCONNECTING,\n\tCONNECTED,\n\tDISCONNECTING,\n\tDISCONNECTED,\n}\n\ntype IMultiplexWebSocket<T = Record<string, unknown>> = {\n\twsUrl: WebSocketUrl;\n\twebSocket: WebSocket;\n\tcustomConnectionState: WebSocketConnectionState;\n\tsubject: Subject<WebSocketMessage<T>>;\n\tsubscriptions: Map<\n\t\tSubscriptionId,\n\t\tOmit<WebSocketSubscriptionProps<T>, 'wsUrl' | 'subscriptionId'>\n\t>;\n};\n\n/**\n * MultiplexWebSocket allows for multiple subscriptions to a single websocket of the same URL, improving efficiency and reducing the number of open connections.\n *\n * This implementation assumes the following:\n * - All websocket streams are treated equally - reconnection attempts are performed at the same standards\n * - All messages returned are in the `WebSocketMessage` format\n * - A single instance of the websocket manager is created for each websocket URL - this means all subscriptions to the same websocket URL will share the same websocket instance\n *\n * Internal implementation details:\n * - The websocket is closed when the number of subscriptions is 0\n * - The websocket will be refreshed (new instance) when it disconnects unexpectedly or errors, until it reaches the maximum number of reconnect attempts\n */\nexport class MultiplexWebSocket<T = Record<string, unknown>>\n\timplements IMultiplexWebSocket<T>\n{\n\t/**\n\t * A lookup of all websockets by their URL.\n\t */\n\tprivate static URL_TO_WEBSOCKETS_LOOKUP = new Map<\n\t\tWebSocketUrl,\n\t\tMultiplexWebSocket<any>\n\t>();\n\n\t/**\n\t * A lookup from websocket URL to all subscription IDs for that URL.\n\t */\n\tprivate static URL_TO_SUBSCRIPTION_IDS_LOOKUP = new Map<\n\t\tWebSocketUrl,\n\t\tSet<SubscriptionId>\n\t>();\n\n\twsUrl: WebSocketUrl;\n\t#webSocket: WebSocket;\n\tcustomConnectionState: WebSocketConnectionState;\n\tsubject: Subject<WebSocketMessage<T>>;\n\tsubscriptions: Map<\n\t\tSubscriptionId,\n\t\tOmit<WebSocketSubscriptionState<T>, 'wsUrl' | 'subscriptionId'>\n\t>;\n\tprivate reconnectionManager: ReconnectionManager;\n\tprivate closeTimeout: NodeJS.Timeout | null = null;\n\tprivate heartbeatTimeout: NodeJS.Timeout | null = null;\n\tprivate heartbeatMonitoringEnabled: boolean = false;\n\tprivate heartbeatTimeoutMs: number = DEFAULT_HEARTBEAT_TIMEOUT_MS;\n\n\tprivate constructor(\n\t\twsUrl: WebSocketUrl,\n\t\tenableHeartbeatMonitoring: boolean = false,\n\t\theartbeatTimeoutMs: number = DEFAULT_HEARTBEAT_TIMEOUT_MS\n\t) {\n\t\tif (MultiplexWebSocket.URL_TO_WEBSOCKETS_LOOKUP.has(wsUrl)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Attempting to create a new websocket for ${wsUrl}, but it already exists`\n\t\t\t);\n\t\t}\n\n\t\tthis.wsUrl = wsUrl;\n\t\tthis.customConnectionState = WebSocketConnectionState.CONNECTING;\n\t\tthis.subject = new Subject<WebSocketMessage<T>>();\n\t\tthis.subscriptions = new Map<\n\t\t\tSubscriptionId,\n\t\t\tOmit<WebSocketSubscriptionProps<T>, 'wsUrl' | 'subscriptionId'>\n\t\t>();\n\n\t\tthis.reconnectionManager = new ReconnectionManager();\n\t\tthis.heartbeatMonitoringEnabled = enableHeartbeatMonitoring;\n\t\tthis.heartbeatTimeoutMs = heartbeatTimeoutMs;\n\n\t\tthis.webSocket = new WebSocket(wsUrl);\n\n\t\tMultiplexWebSocket.URL_TO_WEBSOCKETS_LOOKUP.set(wsUrl, this);\n\t}\n\n\t/**\n\t * Creates a new virtual websocket subscription. If an existing websocket for the given URL exists, the subscription will be added to the existing websocket.\n\t * Returns a function that can be called to unsubscribe from the subscription.\n\t */\n\tpublic static createWebSocketSubscription<T = Record<string, unknown>>(\n\t\tprops: WebSocketSubscriptionProps<T>\n\t): { unsubscribe: () => void } {\n\t\tconst { wsUrl } = props;\n\n\t\tconst doesWebSocketForWsUrlExist =\n\t\t\tMultiplexWebSocket.URL_TO_WEBSOCKETS_LOOKUP.has(wsUrl);\n\n\t\tif (doesWebSocketForWsUrlExist) {\n\t\t\treturn this.handleNewSubForExistingWsUrl<T>(props);\n\t\t} else {\n\t\t\t// Create new websocket for new URL or reopen previously closed websocket\n\t\t\treturn this.handleNewSubForNewWsUrl<T>(props);\n\t\t}\n\t}\n\n\tprivate static handleNewSubForNewWsUrl<T = Record<string, unknown>>(\n\t\tnewSubscriptionProps: WebSocketSubscriptionProps<T>\n\t) {\n\t\tconst newMWS = new MultiplexWebSocket<T>(\n\t\t\tnewSubscriptionProps.wsUrl,\n\t\t\tnewSubscriptionProps.enableHeartbeatMonitoring ?? false,\n\t\t\tnewSubscriptionProps.heartbeatTimeoutMs ?? DEFAULT_HEARTBEAT_TIMEOUT_MS\n\t\t);\n\n\t\tnewMWS.subscribe(newSubscriptionProps);\n\n\t\treturn {\n\t\t\tunsubscribe: () => {\n\t\t\t\tnewMWS.unsubscribe(newSubscriptionProps.subscriptionId);\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate static handleNewSubForExistingWsUrl<T = Record<string, unknown>>(\n\t\tnewSubscriptionProps: WebSocketSubscriptionProps<T>\n\t) {\n\t\tconst { wsUrl, subscriptionId } = newSubscriptionProps;\n\n\t\tif (!MultiplexWebSocket.URL_TO_WEBSOCKETS_LOOKUP.has(wsUrl)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Attempting to subscribe to ${subscriptionId} on websocket ${wsUrl}, but websocket does not exist yet`\n\t\t\t);\n\t\t}\n\n\t\tconst existingMWS = MultiplexWebSocket.URL_TO_WEBSOCKETS_LOOKUP.get(\n\t\t\twsUrl\n\t\t) as MultiplexWebSocket<T>;\n\n\t\t// Check if heartbeat monitoring settings match\n\t\tconst requestedHeartbeat =\n\t\t\tnewSubscriptionProps.enableHeartbeatMonitoring ?? false;\n\t\tif (existingMWS.heartbeatMonitoringEnabled !== requestedHeartbeat) {\n\t\t\tconsole.warn(\n\t\t\t\t`WebSocket for ${wsUrl} already exists with heartbeat monitoring ${\n\t\t\t\t\texistingMWS.heartbeatMonitoringEnabled ? 'enabled' : 'disabled'\n\t\t\t\t}, ` +\n\t\t\t\t\t`but new subscription requests ${\n\t\t\t\t\t\trequestedHeartbeat ? 'enabled' : 'disabled'\n\t\t\t\t\t}. Using existing setting.`\n\t\t\t);\n\t\t}\n\n\t\t// Check if heartbeat timeout settings match for the same websocket URL (note that this assumes that all types of subscriptions from the same websocket URL are expected to have the same heartbeat interval)\n\t\tconst requestedTimeout =\n\t\t\tnewSubscriptionProps.heartbeatTimeoutMs ?? DEFAULT_HEARTBEAT_TIMEOUT_MS;\n\t\tif (existingMWS.heartbeatTimeoutMs !== requestedTimeout) {\n\t\t\tconsole.warn(\n\t\t\t\t`WebSocket for ${wsUrl} already exists with heartbeat timeout ${existingMWS.heartbeatTimeoutMs}ms, ` +\n\t\t\t\t\t`but new subscription requests ${requestedTimeout}ms. Using existing setting.`\n\t\t\t);\n\t\t}\n\n\t\t// Track new subscription for existing websocket\n\t\texistingMWS.subscribe(newSubscriptionProps);\n\n\t\treturn {\n\t\t\tunsubscribe: () => {\n\t\t\t\texistingMWS.unsubscribe(newSubscriptionProps.subscriptionId);\n\t\t\t},\n\t\t};\n\t}\n\n\tget webSocket() {\n\t\treturn this.#webSocket;\n\t}\n\n\t/**\n\t * Setting the WebSocket instance will automatically add event handlers to the WebSocket instance.\n\t * When the WebSocket is connected, all existing subscriptions will be subscribed to.\n\t */\n\tset webSocket(webSocket: WebSocket) {\n\t\twebSocket.onopen = () => {\n\t\t\tthis.customConnectionState = WebSocketConnectionState.CONNECTED;\n\t\t\tthis.reconnectionManager.reset(); // Reset reconnection attempts on successful connection\n\n\t\t\t// Start heartbeat monitoring if enabled\n\t\t\tif (this.heartbeatMonitoringEnabled) {\n\t\t\t\tthis.startHeartbeatMonitoring();\n\t\t\t}\n\n\t\t\t// sends subscription message for each subscription for those that are added before the websocket is connected\n\t\t\tfor (const [subscriptionId] of this.subscriptions.entries()) {\n\t\t\t\tthis.subscribeToWebSocket(subscriptionId);\n\t\t\t}\n\t\t};\n\n\t\twebSocket.onmessage = (messageEvent: MessageEvent) => {\n\t\t\tconst message = JSON.parse(\n\t\t\t\tmessageEvent.data as string\n\t\t\t) as WebSocketMessage<T>;\n\n\t\t\t// Check for heartbeat message from server (only if heartbeat monitoring is enabled)\n\t\t\tif (this.heartbeatMonitoringEnabled && this.isHeartbeatMessage(message)) {\n\t\t\t\tthis.handleHeartbeat(message);\n\t\t\t\treturn; // Don't forward heartbeat messages to subscriptions\n\t\t\t}\n\n\t\t\tthis.subject.next(message);\n\t\t};\n\n\t\twebSocket.onclose = (event) => {\n\t\t\tthis.customConnectionState = WebSocketConnectionState.DISCONNECTED;\n\n\t\t\t// Stop heartbeat monitoring when connection closes (if enabled)\n\t\t\tif (this.heartbeatMonitoringEnabled) {\n\t\t\t\tthis.stopHeartbeatMonitoring();\n\t\t\t}\n\n\t\t\t// Restart websocket if it was closed unexpectedly (not by us)\n\t\t\tif (!event.wasClean && this.subscriptions.size > 0) {\n\t\t\t\tconsole.log('WebSocket closed unexpectedly, restarting...', event);\n\t\t\t\tthis.refreshWebSocket();\n\t\t\t}\n\t\t};\n\n\t\twebSocket.onerror = (error) => {\n\t\t\tconsole.error('MultiplexWebSocket Error', { error, webSocket });\n\n\t\t\t// Forward error to all subscriptions for this websocket URL\n\t\t\tconst subscriptionIds =\n\t\t\t\tMultiplexWebSocket.URL_TO_SUBSCRIPTION_IDS_LOOKUP.get(this.wsUrl);\n\t\t\tif (subscriptionIds) {\n\t\t\t\tfor (const subscriptionId of subscriptionIds) {\n\t\t\t\t\tconst subscription = this.subscriptions.get(subscriptionId);\n\t\t\t\t\tif (subscription) {\n\t\t\t\t\t\tsubscription.onError(error);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Note: onclose always fires after onerror, so reconnection is handled there\n\t\t\t// to avoid double-counting reconnection attempts.\n\t\t};\n\n\t\tthis.#webSocket = webSocket;\n\t}\n\n\tprivate subscribeToWebSocket(subscriptionId: SubscriptionId) {\n\t\tconst subscriptionState = this.subscriptions.get(subscriptionId);\n\n\t\tconst {\n\t\t\tsubscribeMessage,\n\t\t\tonError,\n\t\t\tonMessage,\n\t\t\tmessageFilter,\n\t\t\terrorMessageFilter,\n\t\t\tonClose,\n\t\t} = subscriptionState;\n\n\t\tthis.webSocket.send(subscribeMessage);\n\t\tif (subscriptionState) {\n\t\t\tsubscriptionState.hasSentSubscribeMessage = true;\n\t\t}\n\n\t\t// Create internal subscription for message handling\n\t\tconst subjectSubscription = this.subject\n\t\t\t.pipe(\n\t\t\t\tcatchError((err) => {\n\t\t\t\t\tconsole.error('Caught websocket error', err);\n\t\t\t\t\tonError();\n\t\t\t\t\treturn [];\n\t\t\t\t})\n\t\t\t)\n\t\t\t.subscribe({\n\t\t\t\tnext: (message: WebSocketMessage<T>) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tif (messageFilter && !messageFilter(message)) return;\n\n\t\t\t\t\t\tif (errorMessageFilter && errorMessageFilter(message)) {\n\t\t\t\t\t\t\tonError();\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tonMessage(message);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconsole.error('Error parsing websocket message', err);\n\t\t\t\t\t\tonError();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\terror: (err) => {\n\t\t\t\t\tconsole.error('Error subscribing to websocket', err);\n\t\t\t\t\tonError();\n\t\t\t\t},\n\t\t\t\tcomplete: () => {\n\t\t\t\t\tif (onClose) {\n\t\t\t\t\t\tonClose();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t});\n\n\t\tsubscriptionState.subjectSubscription = subjectSubscription;\n\t}\n\n\tprivate subscribe(props: WebSocketSubscriptionProps<T>) {\n\t\tconst {\n\t\t\tsubscriptionId,\n\t\t\tsubscribeMessage,\n\t\t\tunsubscribeMessage,\n\t\t\tonError,\n\t\t\tonMessage,\n\t\t\tmessageFilter,\n\t\t\terrorMessageFilter,\n\t\t\tonClose,\n\t\t} = props;\n\n\t\tif (this.subscriptions.get(subscriptionId)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Attempting to subscribe to ${subscriptionId} on websocket ${this.wsUrl}, but subscription already exists`\n\t\t\t);\n\t\t}\n\n\t\t// Cancel any pending delayed close since we're adding a new subscription\n\t\tthis.cancelDelayedClose();\n\n\t\tthis.subscriptions.set(subscriptionId, {\n\t\t\tsubscribeMessage,\n\t\t\tunsubscribeMessage,\n\t\t\tonError,\n\t\t\tonMessage,\n\t\t\tmessageFilter,\n\t\t\terrorMessageFilter,\n\t\t\tonClose,\n\t\t\thasSentSubscribeMessage: false,\n\t\t});\n\n\t\t// Update URL to subscription IDs lookup\n\t\tif (!MultiplexWebSocket.URL_TO_SUBSCRIPTION_IDS_LOOKUP.has(this.wsUrl)) {\n\t\t\tMultiplexWebSocket.URL_TO_SUBSCRIPTION_IDS_LOOKUP.set(\n\t\t\t\tthis.wsUrl,\n\t\t\t\tnew Set()\n\t\t\t);\n\t\t}\n\t\tMultiplexWebSocket.URL_TO_SUBSCRIPTION_IDS_LOOKUP.get(this.wsUrl)!.add(\n\t\t\tsubscriptionId\n\t\t);\n\n\t\tif (this.customConnectionState === WebSocketConnectionState.CONNECTED) {\n\t\t\tthis.subscribeToWebSocket(subscriptionId);\n\t\t} else if (\n\t\t\tthis.customConnectionState === WebSocketConnectionState.CONNECTING\n\t\t) {\n\t\t\t// do nothing, subscription will automatically start when websocket is connected\n\t\t} else {\n\t\t\t// handle case where websocket is disconnecting/disconnected\n\t\t\tthis.refreshWebSocket();\n\t\t}\n\t}\n\n\tprivate unsubscribe(subscriptionId: SubscriptionId) {\n\t\tconst subscriptionState = this.subscriptions.get(subscriptionId);\n\t\tif (subscriptionState) {\n\t\t\tsubscriptionState.subjectSubscription?.unsubscribe();\n\n\t\t\t// Only send unsubscribe message if websocket is connected and ready to send.\n\t\t\t//// Otherwise, when the websocket DOES connect we don't have to worry about this subscription because we are deleting it from the subscriptions map. (Which only trigger their connections once the websocket becomes connected)\n\t\t\tif (\n\t\t\t\tthis.customConnectionState === WebSocketConnectionState.CONNECTED &&\n\t\t\t\tthis.webSocket.readyState === WebSocket.OPEN\n\t\t\t) {\n\t\t\t\tthis.webSocket.send(subscriptionState.unsubscribeMessage);\n\t\t\t}\n\n\t\t\tthis.subscriptions.delete(subscriptionId);\n\n\t\t\t// Update URL to subscription IDs lookup\n\t\t\tconst subscriptionIds =\n\t\t\t\tMultiplexWebSocket.URL_TO_SUBSCRIPTION_IDS_LOOKUP.get(this.wsUrl);\n\t\t\tif (subscriptionIds) {\n\t\t\t\tsubscriptionIds.delete(subscriptionId);\n\t\t\t\tif (subscriptionIds.size === 0) {\n\t\t\t\t\tMultiplexWebSocket.URL_TO_SUBSCRIPTION_IDS_LOOKUP.delete(this.wsUrl);\n\t\t\t\t\t// Schedule delayed close when last subscriber unsubscribes\n\t\t\t\t\tthis.scheduleDelayedClose();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate scheduleDelayedClose() {\n\t\t// Cancel any existing delayed close timeout\n\t\tthis.cancelDelayedClose();\n\n\t\t// Schedule new delayed close\n\t\tthis.closeTimeout = setTimeout(() => {\n\t\t\tthis.close();\n\t\t}, DEFAULT_CONNECTION_CLOSE_DELAY_MS);\n\t}\n\n\tprivate cancelDelayedClose() {\n\t\tif (this.closeTimeout) {\n\t\t\tclearTimeout(this.closeTimeout);\n\t\t\tthis.closeTimeout = null;\n\t\t}\n\t}\n\n\tprivate close() {\n\t\t// Cancel any pending delayed close\n\t\tthis.cancelDelayedClose();\n\n\t\t// Stop heartbeat monitoring (if enabled)\n\t\tif (this.heartbeatMonitoringEnabled) {\n\t\t\tthis.stopHeartbeatMonitoring();\n\t\t}\n\n\t\tfor (const [subscriptionId] of this.subscriptions.entries()) {\n\t\t\tthis.unsubscribe(subscriptionId);\n\t\t}\n\n\t\tthis.subscriptions.clear();\n\t\tthis.webSocket.close();\n\t\tMultiplexWebSocket.URL_TO_WEBSOCKETS_LOOKUP.delete(this.wsUrl);\n\t\tMultiplexWebSocket.URL_TO_SUBSCRIPTION_IDS_LOOKUP.delete(this.wsUrl);\n\t\tthis.reconnectionManager.reset();\n\t}\n\n\tprivate startHeartbeatMonitoring() {\n\t\t// Start the heartbeat timeout - if we don't receive a heartbeat message within the timeout, refresh connection\n\t\tthis.resetHeartbeatTimeout();\n\t}\n\n\tprivate stopHeartbeatMonitoring() {\n\t\tif (this.heartbeatTimeout) {\n\t\t\tclearTimeout(this.heartbeatTimeout);\n\t\t\tthis.heartbeatTimeout = null;\n\t\t}\n\t}\n\n\tprivate resetHeartbeatTimeout() {\n\t\t// Clear existing timeout\n\t\tif (this.heartbeatTimeout) {\n\t\t\tclearTimeout(this.heartbeatTimeout);\n\t\t}\n\n\t\t// Set new timeout\n\t\tthis.heartbeatTimeout = setTimeout(() => {\n\t\t\tconsole.warn(\n\t\t\t\t`No heartbeat received within ${this.heartbeatTimeoutMs}ms - connection appears dead`\n\t\t\t);\n\t\t\tthis.refreshWebSocket();\n\t\t}, this.heartbeatTimeoutMs);\n\t}\n\n\tprivate isHeartbeatMessage(message: WebSocketMessage<T>): boolean {\n\t\t// Check if message is a heartbeat message from server\n\t\treturn (message as any)?.channel === 'heartbeat';\n\t}\n\n\tprivate handleHeartbeat(_message: WebSocketMessage<T>) {\n\t\t// Reset the heartbeat timeout\n\t\tthis.resetHeartbeatTimeout();\n\t}\n\n\tprivate refreshWebSocket() {\n\t\t// Cancel any pending delayed close since we're refreshing\n\t\tthis.cancelDelayedClose();\n\n\t\t// Reset heartbeat monitoring during refresh (if enabled) - it will restart when the new connection opens\n\t\tif (this.heartbeatMonitoringEnabled) {\n\t\t\tthis.stopHeartbeatMonitoring();\n\t\t}\n\n\t\tlet shouldReconnect: boolean;\n\t\tlet delay: number;\n\n\t\ttry {\n\t\t\t({ shouldReconnect, delay } =\n\t\t\t\tthis.reconnectionManager.attemptReconnection(this.wsUrl));\n\t\t} catch (error) {\n\t\t\t// Max reconnect attempts exceeded — close gracefully and notify all subscribers\n\t\t\tconsole.error('WebSocket reconnection failed', error);\n\n\t\t\t// Forward error to all subscriptions before closing\n\t\t\tfor (const [, subscription] of this.subscriptions.entries()) {\n\t\t\t\tsubscription.onError(error);\n\t\t\t}\n\n\t\t\tthis.close();\n\t\t\treturn;\n\t\t}\n\n\t\tif (!shouldReconnect) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Clean up current websocket\n\t\tconst currentWebSocket = this.webSocket;\n\t\tif (currentWebSocket) {\n\t\t\tcurrentWebSocket.onerror = () => {};\n\t\t\tcurrentWebSocket.onclose = () => {};\n\t\t\tcurrentWebSocket.onmessage = () => {};\n\t\t\tcurrentWebSocket.onopen = () => {\n\t\t\t\t// in the event where the websocket has yet to connect, we close the connection after it is connected\n\t\t\t\tcurrentWebSocket.close();\n\t\t\t};\n\t\t\tcurrentWebSocket.close();\n\t\t}\n\n\t\t// Reset subscription states\n\t\tthis.subscriptions.forEach((subscription) => {\n\t\t\tsubscription.hasSentSubscribeMessage = false;\n\t\t});\n\n\t\t// Use exponential backoff before attempting to reconnect\n\t\tsetTimeout(() => {\n\t\t\tthis.webSocket = new WebSocket(this.wsUrl);\n\t\t}, delay);\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/common",
3
- "version": "1.0.35",
3
+ "version": "1.0.37",
4
4
  "description": "Common functions for Drift",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -83,7 +83,7 @@
83
83
  "chai": "4.3.10",
84
84
  "crypto-browserify": "^3.12.1",
85
85
  "encoding": "0.1.13",
86
- "esbuild": "0.27.3",
86
+ "esbuild": "0.27.2",
87
87
  "jest": "29.7.0",
88
88
  "madge": "^8.0.0",
89
89
  "mocha": "10.2.0",