@metamask-previews/core-backend 8.1.0-preview-5a03e1b92 → 8.1.0-preview-943313fc9

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/CHANGELOG.md CHANGED
@@ -7,10 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ### Fixed
11
-
12
- - `OHLCVService` now flushes grace-period channels when subscribing to a different asset/interval, retries failed WebSocket unsubscribes with backoff before forcing reconnection, and only removes channel tracking after a successful unsubscribe ([#9678](https://github.com/MetaMask/core/pull/9678))
13
-
14
10
  ## [8.1.0]
15
11
 
16
12
  ### Changed
@@ -18,11 +18,10 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
18
18
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
19
19
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
20
20
  };
21
- var _OHLCVService_instances, _OHLCVService_messenger, _OHLCVService_trace, _OHLCVService_channels, _OHLCVService_mutex, _OHLCVService_chainsUp, _OHLCVService_subscribeInner, _OHLCVService_unsubscribeInner, _OHLCVService_flushOtherChannels, _OHLCVService_clearChannelTimers, _OHLCVService_unsubscribeChannelOnServer, _OHLCVService_scheduleUnsubscribeRetry, _OHLCVService_runUnsubRetryLoop, _OHLCVService_performUnsubscribe, _OHLCVService_resubscribeActiveChannels, _OHLCVService_handleBarUpdate, _OHLCVService_handleSystemNotification, _OHLCVService_handleWebSocketStateChange, _OHLCVService_buildChannel;
21
+ var _OHLCVService_instances, _OHLCVService_messenger, _OHLCVService_trace, _OHLCVService_channels, _OHLCVService_mutex, _OHLCVService_chainsUp, _OHLCVService_subscribeInner, _OHLCVService_unsubscribeInner, _OHLCVService_performUnsubscribe, _OHLCVService_resubscribeActiveChannels, _OHLCVService_handleBarUpdate, _OHLCVService_handleSystemNotification, _OHLCVService_handleWebSocketStateChange, _OHLCVService_buildChannel;
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
23
  exports.OHLCVService = exports.OHLCV_SERVICE_ALLOWED_EVENTS = exports.OHLCV_SERVICE_ALLOWED_ACTIONS = void 0;
24
24
  const async_mutex_1 = require("async-mutex");
25
- const cockatiel_1 = require("cockatiel");
26
25
  const logger_js_1 = require("../../logger.cjs");
27
26
  const BackendWebSocketService_js_1 = require("../BackendWebSocketService.cjs");
28
27
  // =============================================================================
@@ -35,14 +34,6 @@ const SUBSCRIPTION_NAMESPACE = 'market-data.v1';
35
34
  const SYSTEM_NOTIFICATIONS_CHANNEL = `system-notifications.v1.${SUBSCRIPTION_NAMESPACE}`;
36
35
  /** Delay before actually unsubscribing from a channel after refCount reaches 0. */
37
36
  const GRACE_PERIOD_MS = 3000;
38
- /** Backoff delays between failed WebSocket unsubscribe attempts. */
39
- const UNSUB_RETRY_DELAYS_MS = [1000, 2000, 4000];
40
- const unsubRetryPolicy = (0, cockatiel_1.retry)(cockatiel_1.handleAll, {
41
- // Cockatiel stops retrying once the failure index reaches `maxAttempts`, so
42
- // `length` here yields one initial attempt plus three delayed retries (4 total).
43
- maxAttempts: UNSUB_RETRY_DELAYS_MS.length,
44
- backoff: new cockatiel_1.IterableBackoff([...UNSUB_RETRY_DELAYS_MS]),
45
- });
46
37
  exports.OHLCV_SERVICE_ALLOWED_ACTIONS = [
47
38
  'BackendWebSocketService:connect',
48
39
  'BackendWebSocketService:forceReconnection',
@@ -67,9 +58,7 @@ exports.OHLCV_SERVICE_ALLOWED_EVENTS = [
67
58
  *
68
59
  * Features:
69
60
  * - Reference counting: multiple UI consumers share one WebSocket subscription
70
- * - Grace-period unsubscribe: reuses same-channel subs on rapid back navigation
71
- * - Grace-period flush: immediately unsubscribes other channels on navigation
72
- * - Unsubscribe retry: retries failed unsubs with backoff before force reconnect
61
+ * - Grace-period unsubscribe: avoids rapid unsub/resub during navigation
73
62
  * - Idempotency: duplicate subscribe calls for the same channel are no-ops
74
63
  * - Reconnect resilience: resubscribes all active channels on reconnect
75
64
  * - Chain-status forwarding: listens to system-notifications for chain up/down
@@ -153,7 +142,9 @@ class OHLCVService {
153
142
  */
154
143
  destroy() {
155
144
  for (const entry of __classPrivateFieldGet(this, _OHLCVService_channels, "f").values()) {
156
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_clearChannelTimers).call(this, entry);
145
+ if (entry.gracePeriodTimer) {
146
+ clearTimeout(entry.gracePeriodTimer);
147
+ }
157
148
  }
158
149
  __classPrivateFieldGet(this, _OHLCVService_channels, "f").clear();
159
150
  __classPrivateFieldGet(this, _OHLCVService_chainsUp, "f").clear();
@@ -163,27 +154,13 @@ class OHLCVService {
163
154
  exports.OHLCVService = OHLCVService;
164
155
  _OHLCVService_messenger = new WeakMap(), _OHLCVService_trace = new WeakMap(), _OHLCVService_channels = new WeakMap(), _OHLCVService_mutex = new WeakMap(), _OHLCVService_chainsUp = new WeakMap(), _OHLCVService_instances = new WeakSet(), _OHLCVService_subscribeInner = async function _OHLCVService_subscribeInner(channel) {
165
156
  const entry = __classPrivateFieldGet(this, _OHLCVService_channels, "f").get(channel);
166
- if (entry?.retryAbort) {
167
- entry.retryAbort.abort();
168
- entry.retryAbort = undefined;
169
- entry.refCount = 1;
170
- if (__classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:channelHasSubscription', channel)) {
171
- await __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_flushOtherChannels).call(this, channel);
172
- log('OHLCV-WS: Cancelled unsubscribe retry — reusing WS subscription', {
173
- channel,
174
- });
175
- return;
176
- }
177
- // WS subscription was lost — fall through to recreate it.
178
- }
179
- else if (entry?.gracePeriodTimer) {
157
+ if (entry?.gracePeriodTimer) {
180
158
  clearTimeout(entry.gracePeriodTimer);
181
159
  entry.gracePeriodTimer = undefined;
182
160
  log('OHLCV-WS: Cancelled grace-period unsubscribe', {
183
161
  channel,
184
162
  });
185
163
  if (__classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:channelHasSubscription', channel)) {
186
- await __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_flushOtherChannels).call(this, channel);
187
164
  entry.refCount += 1;
188
165
  log('OHLCV-WS: WS subscription still alive, bumped refCount', {
189
166
  channel,
@@ -198,7 +175,6 @@ _OHLCVService_messenger = new WeakMap(), _OHLCVService_trace = new WeakMap(), _O
198
175
  entry.refCount += 1;
199
176
  return;
200
177
  }
201
- await __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_flushOtherChannels).call(this, channel);
202
178
  try {
203
179
  await __classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:connect');
204
180
  if (__classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:channelHasSubscription', channel)) {
@@ -240,108 +216,15 @@ _OHLCVService_messenger = new WeakMap(), _OHLCVService_trace = new WeakMap(), _O
240
216
  }
241
217
  entry.gracePeriodTimer = setTimeout(() => {
242
218
  entry.gracePeriodTimer = undefined;
243
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
244
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_performUnsubscribe).call(this, channel);
219
+ __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_performUnsubscribe).call(this, channel).catch(() => {
220
+ // no-op
221
+ });
245
222
  }, GRACE_PERIOD_MS);
246
- }, _OHLCVService_flushOtherChannels =
223
+ }, _OHLCVService_performUnsubscribe =
247
224
  // =============================================================================
248
225
  // Private — WebSocket Subscription Helpers
249
226
  // =============================================================================
250
- /**
251
- * Immediately unsubscribe other channels in grace or failed-cleanup state.
252
- * Called while the subscribe mutex is held before opening a new channel.
253
- *
254
- * @param exceptChannel - Channel being subscribed; excluded from flush.
255
- */
256
- async function _OHLCVService_flushOtherChannels(exceptChannel) {
257
- for (const [channel, channelEntry] of __classPrivateFieldGet(this, _OHLCVService_channels, "f").entries()) {
258
- if (channel === exceptChannel || channelEntry.refCount > 0) {
259
- continue;
260
- }
261
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_clearChannelTimers).call(this, channelEntry);
262
- log('OHLCV-WS: Flushing grace-period channel before new subscribe', {
263
- flushedChannel: channel,
264
- newChannel: exceptChannel,
265
- });
266
- const success = await __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_unsubscribeChannelOnServer).call(this, channel);
267
- if (success) {
268
- __classPrivateFieldGet(this, _OHLCVService_channels, "f").delete(channel);
269
- }
270
- else {
271
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_scheduleUnsubscribeRetry).call(this, channel);
272
- }
273
- }
274
- }, _OHLCVService_clearChannelTimers = function _OHLCVService_clearChannelTimers(entry) {
275
- if (entry.gracePeriodTimer) {
276
- clearTimeout(entry.gracePeriodTimer);
277
- entry.gracePeriodTimer = undefined;
278
- }
279
- entry.retryAbort?.abort();
280
- entry.retryAbort = undefined;
281
- }, _OHLCVService_unsubscribeChannelOnServer = async function _OHLCVService_unsubscribeChannelOnServer(channel) {
282
- try {
283
- const subscriptions = __classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:getSubscriptionsByChannel', channel);
284
- for (const sub of subscriptions) {
285
- await sub.unsubscribe();
286
- }
287
- return true;
288
- }
289
- catch (error) {
290
- log('OHLCV-WS: Unsubscription failed', { channel, error });
291
- __classPrivateFieldGet(this, _OHLCVService_messenger, "f").publish('OHLCVService:subscriptionError', {
292
- channel,
293
- error: String(error),
294
- operation: 'unsubscribe',
295
- });
296
- return false;
297
- }
298
- }, _OHLCVService_scheduleUnsubscribeRetry = function _OHLCVService_scheduleUnsubscribeRetry(channel) {
299
- const entry = __classPrivateFieldGet(this, _OHLCVService_channels, "f").get(channel) ?? { refCount: 0 };
300
- entry.retryAbort?.abort();
301
- entry.retryAbort = new AbortController();
302
- __classPrivateFieldGet(this, _OHLCVService_channels, "f").set(channel, entry);
303
- const { signal } = entry.retryAbort;
304
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
305
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_runUnsubRetryLoop).call(this, channel, signal);
306
- }, _OHLCVService_runUnsubRetryLoop = async function _OHLCVService_runUnsubRetryLoop(channel, signal) {
307
- try {
308
- await unsubRetryPolicy.execute(async () => {
309
- const releaseLock = await __classPrivateFieldGet(this, _OHLCVService_mutex, "f").acquire();
310
- try {
311
- const current = __classPrivateFieldGet(this, _OHLCVService_channels, "f").get(channel);
312
- if (current && current.refCount > 0) {
313
- return;
314
- }
315
- const success = await __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_unsubscribeChannelOnServer).call(this, channel);
316
- if (!success) {
317
- throw new Error('unsubscribe failed');
318
- }
319
- __classPrivateFieldGet(this, _OHLCVService_channels, "f").delete(channel);
320
- log('OHLCV-WS: WS unsubscribe completed', { channel });
321
- }
322
- finally {
323
- releaseLock();
324
- }
325
- }, signal);
326
- }
327
- catch {
328
- if (signal.aborted) {
329
- return;
330
- }
331
- log('OHLCV-WS: Unsubscribe retries exhausted — forcing reconnection', {
332
- channel,
333
- });
334
- __classPrivateFieldGet(this, _OHLCVService_channels, "f").delete(channel);
335
- // Last resort: reconnects the shared BackendWebSocketService instance
336
- // (AccountActivityService and other consumers share this connection).
337
- // They resubscribe on CONNECTED; OHLCV only resubscribes refCount > 0.
338
- await __classPrivateFieldGet(this, _OHLCVService_messenger, "f")
339
- .call('BackendWebSocketService:forceReconnection')
340
- .catch(() => {
341
- // no-op
342
- });
343
- }
344
- }, _OHLCVService_performUnsubscribe = async function _OHLCVService_performUnsubscribe(channel) {
227
+ async function _OHLCVService_performUnsubscribe(channel) {
345
228
  const releaseLock = await __classPrivateFieldGet(this, _OHLCVService_mutex, "f").acquire();
346
229
  try {
347
230
  const entry = __classPrivateFieldGet(this, _OHLCVService_channels, "f").get(channel);
@@ -352,12 +235,26 @@ async function _OHLCVService_flushOtherChannels(exceptChannel) {
352
235
  log('OHLCV-WS: Grace period expired — performing actual WS unsubscribe', {
353
236
  channel,
354
237
  });
355
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_clearChannelTimers).call(this, __classPrivateFieldGet(this, _OHLCVService_channels, "f").get(channel) ?? { refCount: 0 });
238
+ __classPrivateFieldGet(this, _OHLCVService_channels, "f").delete(channel);
239
+ try {
240
+ const subscriptions = __classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:getSubscriptionsByChannel', channel);
241
+ for (const sub of subscriptions) {
242
+ await sub.unsubscribe();
243
+ }
244
+ log('OHLCV-WS: WS unsubscribe completed', { channel });
245
+ }
246
+ catch (error) {
247
+ log('OHLCV-WS: Unsubscription failed', { channel, error });
248
+ __classPrivateFieldGet(this, _OHLCVService_messenger, "f").publish('OHLCVService:subscriptionError', {
249
+ channel,
250
+ error: String(error),
251
+ operation: 'unsubscribe',
252
+ });
253
+ }
356
254
  }
357
255
  finally {
358
256
  releaseLock();
359
257
  }
360
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_scheduleUnsubscribeRetry).call(this, channel);
361
258
  }, _OHLCVService_resubscribeActiveChannels =
362
259
  /**
363
260
  * Resubscribe all channels that were active before a disconnect.
@@ -370,10 +267,8 @@ async function _OHLCVService_resubscribeActiveChannels() {
370
267
  log('OHLCV-WS: Resubscribing active channels after reconnect', {
371
268
  count: channelCount,
372
269
  });
373
- for (const [channel, entry] of [...__classPrivateFieldGet(this, _OHLCVService_channels, "f").entries()]) {
270
+ for (const [channel, entry] of __classPrivateFieldGet(this, _OHLCVService_channels, "f").entries()) {
374
271
  if (entry.refCount === 0) {
375
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_clearChannelTimers).call(this, entry);
376
- __classPrivateFieldGet(this, _OHLCVService_channels, "f").delete(channel);
377
272
  continue;
378
273
  }
379
274
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"OHLCVService.cjs","sourceRoot":"","sources":["../../../src/ws/ohlcv/OHLCVService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OHLCV Service for real-time candlestick data streaming via WebSocket.\n *\n * Wraps {@link BackendWebSocketService} through the messenger pattern to\n * provide subscribe/unsubscribe semantics for OHLCV market-data channels.\n * Includes reference counting, grace-period unsubscribe, idempotency checks,\n * chain-status forwarding, and automatic resubscription on reconnect.\n */\n\nimport type {\n TraceCallback,\n TraceContext,\n TraceRequest,\n} from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport { Mutex } from 'async-mutex';\nimport { handleAll, IterableBackoff, retry } from 'cockatiel';\n\nimport { projectLogger, createModuleLogger } from '../../logger.js';\nimport type { BackendWebSocketServiceMethodActions } from '../BackendWebSocketService-method-action-types.js';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from '../BackendWebSocketService.js';\nimport { WebSocketState } from '../BackendWebSocketService.js';\nimport type { OHLCVServiceMethodActions } from './OHLCVService-method-action-types.js';\nimport type { OHLCVBar, OHLCVSubscriptionOptions } from './types.js';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst SERVICE_NAME = 'OHLCVService';\n\nconst log = createModuleLogger(projectLogger, SERVICE_NAME);\n\nconst MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'market-data.v1';\n\nconst SYSTEM_NOTIFICATIONS_CHANNEL = `system-notifications.v1.${SUBSCRIPTION_NAMESPACE}`;\n\n/** Delay before actually unsubscribing from a channel after refCount reaches 0. */\nconst GRACE_PERIOD_MS = 3_000;\n\n/** Backoff delays between failed WebSocket unsubscribe attempts. */\nconst UNSUB_RETRY_DELAYS_MS = [1_000, 2_000, 4_000] as const;\n\nconst unsubRetryPolicy = retry(handleAll, {\n // Cockatiel stops retrying once the failure index reaches `maxAttempts`, so\n // `length` here yields one initial attempt plus three delayed retries (4 total).\n maxAttempts: UNSUB_RETRY_DELAYS_MS.length,\n backoff: new IterableBackoff([...UNSUB_RETRY_DELAYS_MS]),\n});\n\n// =============================================================================\n// Types — Channel Tracking\n// =============================================================================\n\ntype ChannelEntry = {\n refCount: number;\n gracePeriodTimer?: ReturnType<typeof setTimeout>;\n retryAbort?: AbortController;\n};\n\n// =============================================================================\n// Types — System Notifications\n// =============================================================================\n\n/**\n * System notification data for chain status updates on market-data channels.\n */\nexport type OHLCVSystemNotificationData = {\n chainIds: string[];\n status: 'down' | 'up';\n timestamp?: number;\n};\n\n// =============================================================================\n// Types — Service Options\n// =============================================================================\n\n/**\n * Configuration options for the OHLCV service.\n */\nexport type OHLCVServiceOptions = {\n /** Optional callback to trace performance of OHLCV operations (default: no-op) */\n traceFn?: TraceCallback;\n};\n\n// =============================================================================\n// Action and Event Types\n// =============================================================================\n\nexport type OHLCVServiceActions = OHLCVServiceMethodActions;\n\nexport const OHLCV_SERVICE_ALLOWED_ACTIONS = [\n 'BackendWebSocketService:connect',\n 'BackendWebSocketService:forceReconnection',\n 'BackendWebSocketService:subscribe',\n 'BackendWebSocketService:getConnectionInfo',\n 'BackendWebSocketService:channelHasSubscription',\n 'BackendWebSocketService:getSubscriptionsByChannel',\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n 'BackendWebSocketService:addChannelCallback',\n 'BackendWebSocketService:removeChannelCallback',\n] as const;\n\nexport const OHLCV_SERVICE_ALLOWED_EVENTS = [\n 'BackendWebSocketService:connectionStateChanged',\n] as const;\n\nexport type AllowedActions = BackendWebSocketServiceMethodActions;\n\n// Events published by OHLCVService\n\nexport type OHLCVServiceBarUpdatedEvent = {\n type: `OHLCVService:barUpdated`;\n payload: [{ channel: string; bar: OHLCVBar }];\n};\n\nexport type OHLCVServiceChainStatusChangedEvent = {\n type: `OHLCVService:chainStatusChanged`;\n payload: [{ chainIds: string[]; status: 'up' | 'down'; timestamp?: number }];\n};\n\nexport type OHLCVServiceSubscriptionErrorEvent = {\n type: `OHLCVService:subscriptionError`;\n payload: [{ channel: string; error: string; operation: string }];\n};\n\nexport type OHLCVServiceEvents =\n | OHLCVServiceBarUpdatedEvent\n | OHLCVServiceChainStatusChangedEvent\n | OHLCVServiceSubscriptionErrorEvent;\n\nexport type AllowedEvents = BackendWebSocketServiceConnectionStateChangedEvent;\n\nexport type OHLCVServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n OHLCVServiceActions | AllowedActions,\n OHLCVServiceEvents | AllowedEvents\n>;\n\n// =============================================================================\n// Main Service Class\n// =============================================================================\n\n/**\n * Service for real-time OHLCV candlestick streaming via the backend WebSocket\n * gateway. Communicates with {@link BackendWebSocketService} exclusively\n * through the messenger — no direct import of the class.\n *\n * Features:\n * - Reference counting: multiple UI consumers share one WebSocket subscription\n * - Grace-period unsubscribe: reuses same-channel subs on rapid back navigation\n * - Grace-period flush: immediately unsubscribes other channels on navigation\n * - Unsubscribe retry: retries failed unsubs with backoff before force reconnect\n * - Idempotency: duplicate subscribe calls for the same channel are no-ops\n * - Reconnect resilience: resubscribes all active channels on reconnect\n * - Chain-status forwarding: listens to system-notifications for chain up/down\n *\n */\nexport class OHLCVService {\n readonly name = SERVICE_NAME;\n\n readonly #messenger: OHLCVServiceMessenger;\n\n readonly #trace: TraceCallback;\n\n readonly #channels = new Map<string, ChannelEntry>();\n\n readonly #mutex = new Mutex();\n\n readonly #chainsUp = new Set<string>();\n\n // =============================================================================\n // Constructor\n // =============================================================================\n\n constructor(\n options: OHLCVServiceOptions & { messenger: OHLCVServiceMessenger },\n ) {\n this.#messenger = options.messenger;\n\n this.#trace =\n options.traceFn ??\n ((<Result>(\n _request: TraceRequest,\n fn?: (context?: TraceContext) => Result,\n ) => fn?.()) as TraceCallback);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n }\n\n /**\n * Register the system-notifications channel callback.\n */\n init(): void {\n log('OHLCV-WS: Initializing — registering system-notifications callback');\n this.#messenger.call('BackendWebSocketService:addChannelCallback', {\n channelName: SYSTEM_NOTIFICATIONS_CHANNEL,\n callback: (notification: ServerNotificationMessage) =>\n this.#handleSystemNotification(notification),\n });\n }\n\n // =============================================================================\n // Public — Subscribe / Unsubscribe\n // =============================================================================\n\n /**\n * Subscribe to an OHLCV channel. If this is the first subscriber for the\n * given asset/interval/currency combination a WebSocket subscription is\n * created. Additional calls for the same combination only bump the reference\n * count.\n *\n * @param options - The subscription parameters.\n * @returns A promise that resolves once the subscription is established.\n */\n async subscribe(options: OHLCVSubscriptionOptions): Promise<void> {\n const channel = this.#buildChannel(options);\n const releaseLock = await this.#mutex.acquire();\n try {\n await this.#subscribeInner(channel);\n } finally {\n releaseLock();\n }\n }\n\n async #subscribeInner(channel: string): Promise<void> {\n const entry = this.#channels.get(channel);\n\n if (entry?.retryAbort) {\n entry.retryAbort.abort();\n entry.retryAbort = undefined;\n entry.refCount = 1;\n\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n await this.#flushOtherChannels(channel);\n log('OHLCV-WS: Cancelled unsubscribe retry — reusing WS subscription', {\n channel,\n });\n return;\n }\n // WS subscription was lost — fall through to recreate it.\n } else if (entry?.gracePeriodTimer) {\n clearTimeout(entry.gracePeriodTimer);\n entry.gracePeriodTimer = undefined;\n log('OHLCV-WS: Cancelled grace-period unsubscribe', {\n channel,\n });\n\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n await this.#flushOtherChannels(channel);\n entry.refCount += 1;\n log('OHLCV-WS: WS subscription still alive, bumped refCount', {\n channel,\n refCount: entry.refCount,\n });\n return;\n }\n // WS subscription was lost (e.g. after disconnect/reconnect) — fall\n // through to recreate it. refCount is bumped only after success below.\n } else if (entry && entry.refCount > 0) {\n entry.refCount += 1;\n return;\n }\n\n await this.#flushOtherChannels(channel);\n\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n log(\n 'OHLCV-WS: Channel already has WS subscription (idempotency), skipping',\n {\n channel,\n },\n );\n this.#channels.set(channel, { refCount: 1 });\n return;\n }\n\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: SUBSCRIPTION_NAMESPACE,\n callback: (notification: ServerNotificationMessage) => {\n this.#handleBarUpdate(channel, notification);\n },\n });\n\n this.#channels.set(channel, { refCount: 1 });\n log('OHLCV-WS: Subscribe succeeded — new WS subscription created', {\n channel,\n });\n } catch (error) {\n log('OHLCV-WS: Subscription failed', { channel, error });\n this.#channels.delete(channel);\n this.#messenger.publish('OHLCVService:subscriptionError', {\n channel,\n error: String(error),\n operation: 'subscribe',\n });\n }\n }\n\n /**\n * Unsubscribe from an OHLCV channel. Decrements the reference count and,\n * when it reaches zero, starts a grace-period timer before actually\n * unsubscribing from the WebSocket to absorb rapid navigation patterns.\n *\n * @param options - The subscription parameters to unsubscribe from.\n * @returns A promise that resolves once the unsubscription is processed.\n */\n async unsubscribe(options: OHLCVSubscriptionOptions): Promise<void> {\n const channel = this.#buildChannel(options);\n const releaseLock = await this.#mutex.acquire();\n try {\n await this.#unsubscribeInner(channel);\n } finally {\n releaseLock();\n }\n }\n\n async #unsubscribeInner(channel: string): Promise<void> {\n const entry = this.#channels.get(channel);\n\n if (!entry || entry.refCount <= 0) {\n return;\n }\n\n entry.refCount -= 1;\n\n if (entry.refCount > 0) {\n return;\n }\n\n entry.gracePeriodTimer = setTimeout(() => {\n entry.gracePeriodTimer = undefined;\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#performUnsubscribe(channel);\n }, GRACE_PERIOD_MS);\n }\n\n // =============================================================================\n // Private — WebSocket Subscription Helpers\n // =============================================================================\n\n /**\n * Immediately unsubscribe other channels in grace or failed-cleanup state.\n * Called while the subscribe mutex is held before opening a new channel.\n *\n * @param exceptChannel - Channel being subscribed; excluded from flush.\n */\n async #flushOtherChannels(exceptChannel: string): Promise<void> {\n for (const [channel, channelEntry] of this.#channels.entries()) {\n if (channel === exceptChannel || channelEntry.refCount > 0) {\n continue;\n }\n\n this.#clearChannelTimers(channelEntry);\n log('OHLCV-WS: Flushing grace-period channel before new subscribe', {\n flushedChannel: channel,\n newChannel: exceptChannel,\n });\n\n const success = await this.#unsubscribeChannelOnServer(channel);\n if (success) {\n this.#channels.delete(channel);\n } else {\n this.#scheduleUnsubscribeRetry(channel);\n }\n }\n }\n\n #clearChannelTimers(entry: ChannelEntry): void {\n if (entry.gracePeriodTimer) {\n clearTimeout(entry.gracePeriodTimer);\n entry.gracePeriodTimer = undefined;\n }\n entry.retryAbort?.abort();\n entry.retryAbort = undefined;\n }\n\n async #unsubscribeChannelOnServer(channel: string): Promise<boolean> {\n try {\n const subscriptions = this.#messenger.call(\n 'BackendWebSocketService:getSubscriptionsByChannel',\n channel,\n );\n\n for (const sub of subscriptions) {\n await sub.unsubscribe();\n }\n return true;\n } catch (error) {\n log('OHLCV-WS: Unsubscription failed', { channel, error });\n this.#messenger.publish('OHLCVService:subscriptionError', {\n channel,\n error: String(error),\n operation: 'unsubscribe',\n });\n return false;\n }\n }\n\n #scheduleUnsubscribeRetry(channel: string): void {\n const entry = this.#channels.get(channel) ?? { refCount: 0 };\n entry.retryAbort?.abort();\n entry.retryAbort = new AbortController();\n this.#channels.set(channel, entry);\n\n const { signal } = entry.retryAbort;\n\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#runUnsubRetryLoop(channel, signal);\n }\n\n async #runUnsubRetryLoop(\n channel: string,\n signal: AbortSignal,\n ): Promise<void> {\n try {\n await unsubRetryPolicy.execute(async () => {\n const releaseLock = await this.#mutex.acquire();\n try {\n const current = this.#channels.get(channel);\n if (current && current.refCount > 0) {\n return;\n }\n\n const success = await this.#unsubscribeChannelOnServer(channel);\n if (!success) {\n throw new Error('unsubscribe failed');\n }\n\n this.#channels.delete(channel);\n log('OHLCV-WS: WS unsubscribe completed', { channel });\n } finally {\n releaseLock();\n }\n }, signal);\n } catch {\n if (signal.aborted) {\n return;\n }\n\n log('OHLCV-WS: Unsubscribe retries exhausted — forcing reconnection', {\n channel,\n });\n this.#channels.delete(channel);\n // Last resort: reconnects the shared BackendWebSocketService instance\n // (AccountActivityService and other consumers share this connection).\n // They resubscribe on CONNECTED; OHLCV only resubscribes refCount > 0.\n await this.#messenger\n .call('BackendWebSocketService:forceReconnection')\n .catch(() => {\n // no-op\n });\n }\n }\n\n async #performUnsubscribe(channel: string): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n try {\n const entry = this.#channels.get(channel);\n if (entry && entry.refCount > 0) {\n log(\n 'OHLCV-WS: Skipping unsubscribe — new subscriber arrived while queued',\n { channel, refCount: entry.refCount },\n );\n return;\n }\n\n log('OHLCV-WS: Grace period expired — performing actual WS unsubscribe', {\n channel,\n });\n\n this.#clearChannelTimers(this.#channels.get(channel) ?? { refCount: 0 });\n } finally {\n releaseLock();\n }\n\n this.#scheduleUnsubscribeRetry(channel);\n }\n\n /**\n * Resubscribe all channels that were active before a disconnect.\n * Called when WebSocket transitions to CONNECTED.\n */\n async #resubscribeActiveChannels(): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n try {\n const channelCount = this.#channels.size;\n log('OHLCV-WS: Resubscribing active channels after reconnect', {\n count: channelCount,\n });\n\n for (const [channel, entry] of [...this.#channels.entries()]) {\n if (entry.refCount === 0) {\n this.#clearChannelTimers(entry);\n this.#channels.delete(channel);\n continue;\n }\n\n try {\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n log(\n 'OHLCV-WS: Channel already subscribed on server, skipping resubscribe',\n {\n channel,\n },\n );\n continue;\n }\n\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: SUBSCRIPTION_NAMESPACE,\n callback: (notification: ServerNotificationMessage) => {\n this.#handleBarUpdate(channel, notification);\n },\n });\n log('OHLCV-WS: Resubscription succeeded', { channel });\n } catch (error) {\n log('OHLCV-WS: Resubscription failed for channel', {\n channel,\n error,\n });\n }\n }\n } finally {\n releaseLock();\n }\n }\n\n // =============================================================================\n // Private — Message Handlers\n // =============================================================================\n\n #handleBarUpdate(\n channel: string,\n notification: ServerNotificationMessage,\n ): void {\n const bar = notification.data as OHLCVBar;\n\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#trace(\n {\n name: `${SERVICE_NAME} Bar Update`,\n data: { channel, timestamp: bar.timestamp },\n tags: { service: SERVICE_NAME },\n },\n () => {\n this.#messenger.publish('OHLCVService:barUpdated', { channel, bar });\n },\n );\n }\n\n #handleSystemNotification(notification: ServerNotificationMessage): void {\n const data = notification.data as OHLCVSystemNotificationData;\n const { timestamp } = notification;\n\n if (!data.chainIds || !Array.isArray(data.chainIds) || !data.status) {\n throw new Error(\n 'Invalid system notification data: missing chainIds or status',\n );\n }\n\n if (data.status === 'up') {\n for (const chainId of data.chainIds) {\n this.#chainsUp.add(chainId);\n }\n } else {\n for (const chainId of data.chainIds) {\n this.#chainsUp.delete(chainId);\n }\n }\n\n this.#messenger.publish('OHLCVService:chainStatusChanged', {\n chainIds: data.chainIds,\n status: data.status,\n timestamp,\n });\n\n log(`OHLCV-WS: Chain status change: ${data.status}`, {\n chains: data.chainIds,\n status: data.status,\n });\n }\n\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n await this.#resubscribeActiveChannels();\n } else if (state === WebSocketState.DISCONNECTED) {\n const chainsToMarkDown = Array.from(this.#chainsUp);\n\n if (chainsToMarkDown.length > 0) {\n this.#messenger.publish('OHLCVService:chainStatusChanged', {\n chainIds: chainsToMarkDown,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log(\n 'OHLCV-WS: WebSocket disconnection — marked tracked chains as down',\n {\n count: chainsToMarkDown.length,\n chains: chainsToMarkDown,\n },\n );\n\n this.#chainsUp.clear();\n }\n }\n }\n\n // =============================================================================\n // Private — Utility\n // =============================================================================\n\n #buildChannel(options: OHLCVSubscriptionOptions): string {\n return `${SUBSCRIPTION_NAMESPACE}.${options.assetId}.${options.interval}.${options.currency}`;\n }\n\n // =============================================================================\n // Public — Cleanup\n // =============================================================================\n\n /**\n * Destroy the service and clean up all resources.\n */\n destroy(): void {\n for (const entry of this.#channels.values()) {\n this.#clearChannelTimers(entry);\n }\n this.#channels.clear();\n this.#chainsUp.clear();\n\n this.#messenger.call(\n 'BackendWebSocketService:removeChannelCallback',\n SYSTEM_NOTIFICATIONS_CHANNEL,\n );\n }\n}\n"]}
1
+ {"version":3,"file":"OHLCVService.cjs","sourceRoot":"","sources":["../../../src/ws/ohlcv/OHLCVService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OHLCV Service for real-time candlestick data streaming via WebSocket.\n *\n * Wraps {@link BackendWebSocketService} through the messenger pattern to\n * provide subscribe/unsubscribe semantics for OHLCV market-data channels.\n * Includes reference counting, grace-period unsubscribe, idempotency checks,\n * chain-status forwarding, and automatic resubscription on reconnect.\n */\n\nimport type {\n TraceCallback,\n TraceContext,\n TraceRequest,\n} from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport { Mutex } from 'async-mutex';\n\nimport { projectLogger, createModuleLogger } from '../../logger.js';\nimport type { BackendWebSocketServiceMethodActions } from '../BackendWebSocketService-method-action-types.js';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from '../BackendWebSocketService.js';\nimport { WebSocketState } from '../BackendWebSocketService.js';\nimport type { OHLCVServiceMethodActions } from './OHLCVService-method-action-types.js';\nimport type { OHLCVBar, OHLCVSubscriptionOptions } from './types.js';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst SERVICE_NAME = 'OHLCVService';\n\nconst log = createModuleLogger(projectLogger, SERVICE_NAME);\n\nconst MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'market-data.v1';\n\nconst SYSTEM_NOTIFICATIONS_CHANNEL = `system-notifications.v1.${SUBSCRIPTION_NAMESPACE}`;\n\n/** Delay before actually unsubscribing from a channel after refCount reaches 0. */\nconst GRACE_PERIOD_MS = 3_000;\n\n// =============================================================================\n// Types — Channel Tracking\n// =============================================================================\n\ntype ChannelEntry = {\n refCount: number;\n gracePeriodTimer?: ReturnType<typeof setTimeout>;\n};\n\n// =============================================================================\n// Types — System Notifications\n// =============================================================================\n\n/**\n * System notification data for chain status updates on market-data channels.\n */\nexport type OHLCVSystemNotificationData = {\n chainIds: string[];\n status: 'down' | 'up';\n timestamp?: number;\n};\n\n// =============================================================================\n// Types — Service Options\n// =============================================================================\n\n/**\n * Configuration options for the OHLCV service.\n */\nexport type OHLCVServiceOptions = {\n /** Optional callback to trace performance of OHLCV operations (default: no-op) */\n traceFn?: TraceCallback;\n};\n\n// =============================================================================\n// Action and Event Types\n// =============================================================================\n\nexport type OHLCVServiceActions = OHLCVServiceMethodActions;\n\nexport const OHLCV_SERVICE_ALLOWED_ACTIONS = [\n 'BackendWebSocketService:connect',\n 'BackendWebSocketService:forceReconnection',\n 'BackendWebSocketService:subscribe',\n 'BackendWebSocketService:getConnectionInfo',\n 'BackendWebSocketService:channelHasSubscription',\n 'BackendWebSocketService:getSubscriptionsByChannel',\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n 'BackendWebSocketService:addChannelCallback',\n 'BackendWebSocketService:removeChannelCallback',\n] as const;\n\nexport const OHLCV_SERVICE_ALLOWED_EVENTS = [\n 'BackendWebSocketService:connectionStateChanged',\n] as const;\n\nexport type AllowedActions = BackendWebSocketServiceMethodActions;\n\n// Events published by OHLCVService\n\nexport type OHLCVServiceBarUpdatedEvent = {\n type: `OHLCVService:barUpdated`;\n payload: [{ channel: string; bar: OHLCVBar }];\n};\n\nexport type OHLCVServiceChainStatusChangedEvent = {\n type: `OHLCVService:chainStatusChanged`;\n payload: [{ chainIds: string[]; status: 'up' | 'down'; timestamp?: number }];\n};\n\nexport type OHLCVServiceSubscriptionErrorEvent = {\n type: `OHLCVService:subscriptionError`;\n payload: [{ channel: string; error: string; operation: string }];\n};\n\nexport type OHLCVServiceEvents =\n | OHLCVServiceBarUpdatedEvent\n | OHLCVServiceChainStatusChangedEvent\n | OHLCVServiceSubscriptionErrorEvent;\n\nexport type AllowedEvents = BackendWebSocketServiceConnectionStateChangedEvent;\n\nexport type OHLCVServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n OHLCVServiceActions | AllowedActions,\n OHLCVServiceEvents | AllowedEvents\n>;\n\n// =============================================================================\n// Main Service Class\n// =============================================================================\n\n/**\n * Service for real-time OHLCV candlestick streaming via the backend WebSocket\n * gateway. Communicates with {@link BackendWebSocketService} exclusively\n * through the messenger — no direct import of the class.\n *\n * Features:\n * - Reference counting: multiple UI consumers share one WebSocket subscription\n * - Grace-period unsubscribe: avoids rapid unsub/resub during navigation\n * - Idempotency: duplicate subscribe calls for the same channel are no-ops\n * - Reconnect resilience: resubscribes all active channels on reconnect\n * - Chain-status forwarding: listens to system-notifications for chain up/down\n *\n */\nexport class OHLCVService {\n readonly name = SERVICE_NAME;\n\n readonly #messenger: OHLCVServiceMessenger;\n\n readonly #trace: TraceCallback;\n\n readonly #channels = new Map<string, ChannelEntry>();\n\n readonly #mutex = new Mutex();\n\n readonly #chainsUp = new Set<string>();\n\n // =============================================================================\n // Constructor\n // =============================================================================\n\n constructor(\n options: OHLCVServiceOptions & { messenger: OHLCVServiceMessenger },\n ) {\n this.#messenger = options.messenger;\n\n this.#trace =\n options.traceFn ??\n ((<Result>(\n _request: TraceRequest,\n fn?: (context?: TraceContext) => Result,\n ) => fn?.()) as TraceCallback);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n }\n\n /**\n * Register the system-notifications channel callback.\n */\n init(): void {\n log('OHLCV-WS: Initializing — registering system-notifications callback');\n this.#messenger.call('BackendWebSocketService:addChannelCallback', {\n channelName: SYSTEM_NOTIFICATIONS_CHANNEL,\n callback: (notification: ServerNotificationMessage) =>\n this.#handleSystemNotification(notification),\n });\n }\n\n // =============================================================================\n // Public — Subscribe / Unsubscribe\n // =============================================================================\n\n /**\n * Subscribe to an OHLCV channel. If this is the first subscriber for the\n * given asset/interval/currency combination a WebSocket subscription is\n * created. Additional calls for the same combination only bump the reference\n * count.\n *\n * @param options - The subscription parameters.\n * @returns A promise that resolves once the subscription is established.\n */\n async subscribe(options: OHLCVSubscriptionOptions): Promise<void> {\n const channel = this.#buildChannel(options);\n const releaseLock = await this.#mutex.acquire();\n try {\n await this.#subscribeInner(channel);\n } finally {\n releaseLock();\n }\n }\n\n async #subscribeInner(channel: string): Promise<void> {\n const entry = this.#channels.get(channel);\n\n if (entry?.gracePeriodTimer) {\n clearTimeout(entry.gracePeriodTimer);\n entry.gracePeriodTimer = undefined;\n log('OHLCV-WS: Cancelled grace-period unsubscribe', {\n channel,\n });\n\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n entry.refCount += 1;\n log('OHLCV-WS: WS subscription still alive, bumped refCount', {\n channel,\n refCount: entry.refCount,\n });\n return;\n }\n // WS subscription was lost (e.g. after disconnect/reconnect) — fall\n // through to recreate it. refCount is bumped only after success below.\n } else if (entry && entry.refCount > 0) {\n entry.refCount += 1;\n return;\n }\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n log(\n 'OHLCV-WS: Channel already has WS subscription (idempotency), skipping',\n {\n channel,\n },\n );\n this.#channels.set(channel, { refCount: 1 });\n return;\n }\n\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: SUBSCRIPTION_NAMESPACE,\n callback: (notification: ServerNotificationMessage) => {\n this.#handleBarUpdate(channel, notification);\n },\n });\n\n this.#channels.set(channel, { refCount: 1 });\n log('OHLCV-WS: Subscribe succeeded — new WS subscription created', {\n channel,\n });\n } catch (error) {\n log('OHLCV-WS: Subscription failed', { channel, error });\n this.#channels.delete(channel);\n this.#messenger.publish('OHLCVService:subscriptionError', {\n channel,\n error: String(error),\n operation: 'subscribe',\n });\n }\n }\n\n /**\n * Unsubscribe from an OHLCV channel. Decrements the reference count and,\n * when it reaches zero, starts a grace-period timer before actually\n * unsubscribing from the WebSocket to absorb rapid navigation patterns.\n *\n * @param options - The subscription parameters to unsubscribe from.\n * @returns A promise that resolves once the unsubscription is processed.\n */\n async unsubscribe(options: OHLCVSubscriptionOptions): Promise<void> {\n const channel = this.#buildChannel(options);\n const releaseLock = await this.#mutex.acquire();\n try {\n await this.#unsubscribeInner(channel);\n } finally {\n releaseLock();\n }\n }\n\n async #unsubscribeInner(channel: string): Promise<void> {\n const entry = this.#channels.get(channel);\n\n if (!entry || entry.refCount <= 0) {\n return;\n }\n\n entry.refCount -= 1;\n\n if (entry.refCount > 0) {\n return;\n }\n\n entry.gracePeriodTimer = setTimeout(() => {\n entry.gracePeriodTimer = undefined;\n this.#performUnsubscribe(channel).catch(() => {\n // no-op\n });\n }, GRACE_PERIOD_MS);\n }\n\n // =============================================================================\n // Private — WebSocket Subscription Helpers\n // =============================================================================\n\n async #performUnsubscribe(channel: string): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n try {\n const entry = this.#channels.get(channel);\n if (entry && entry.refCount > 0) {\n log(\n 'OHLCV-WS: Skipping unsubscribe — new subscriber arrived while queued',\n { channel, refCount: entry.refCount },\n );\n return;\n }\n\n log('OHLCV-WS: Grace period expired — performing actual WS unsubscribe', {\n channel,\n });\n this.#channels.delete(channel);\n\n try {\n const subscriptions = this.#messenger.call(\n 'BackendWebSocketService:getSubscriptionsByChannel',\n channel,\n );\n\n for (const sub of subscriptions) {\n await sub.unsubscribe();\n }\n log('OHLCV-WS: WS unsubscribe completed', { channel });\n } catch (error) {\n log('OHLCV-WS: Unsubscription failed', { channel, error });\n this.#messenger.publish('OHLCVService:subscriptionError', {\n channel,\n error: String(error),\n operation: 'unsubscribe',\n });\n }\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Resubscribe all channels that were active before a disconnect.\n * Called when WebSocket transitions to CONNECTED.\n */\n async #resubscribeActiveChannels(): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n try {\n const channelCount = this.#channels.size;\n log('OHLCV-WS: Resubscribing active channels after reconnect', {\n count: channelCount,\n });\n\n for (const [channel, entry] of this.#channels.entries()) {\n if (entry.refCount === 0) {\n continue;\n }\n\n try {\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n log(\n 'OHLCV-WS: Channel already subscribed on server, skipping resubscribe',\n {\n channel,\n },\n );\n continue;\n }\n\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: SUBSCRIPTION_NAMESPACE,\n callback: (notification: ServerNotificationMessage) => {\n this.#handleBarUpdate(channel, notification);\n },\n });\n log('OHLCV-WS: Resubscription succeeded', { channel });\n } catch (error) {\n log('OHLCV-WS: Resubscription failed for channel', {\n channel,\n error,\n });\n }\n }\n } finally {\n releaseLock();\n }\n }\n\n // =============================================================================\n // Private — Message Handlers\n // =============================================================================\n\n #handleBarUpdate(\n channel: string,\n notification: ServerNotificationMessage,\n ): void {\n const bar = notification.data as OHLCVBar;\n\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#trace(\n {\n name: `${SERVICE_NAME} Bar Update`,\n data: { channel, timestamp: bar.timestamp },\n tags: { service: SERVICE_NAME },\n },\n () => {\n this.#messenger.publish('OHLCVService:barUpdated', { channel, bar });\n },\n );\n }\n\n #handleSystemNotification(notification: ServerNotificationMessage): void {\n const data = notification.data as OHLCVSystemNotificationData;\n const { timestamp } = notification;\n\n if (!data.chainIds || !Array.isArray(data.chainIds) || !data.status) {\n throw new Error(\n 'Invalid system notification data: missing chainIds or status',\n );\n }\n\n if (data.status === 'up') {\n for (const chainId of data.chainIds) {\n this.#chainsUp.add(chainId);\n }\n } else {\n for (const chainId of data.chainIds) {\n this.#chainsUp.delete(chainId);\n }\n }\n\n this.#messenger.publish('OHLCVService:chainStatusChanged', {\n chainIds: data.chainIds,\n status: data.status,\n timestamp,\n });\n\n log(`OHLCV-WS: Chain status change: ${data.status}`, {\n chains: data.chainIds,\n status: data.status,\n });\n }\n\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n await this.#resubscribeActiveChannels();\n } else if (state === WebSocketState.DISCONNECTED) {\n const chainsToMarkDown = Array.from(this.#chainsUp);\n\n if (chainsToMarkDown.length > 0) {\n this.#messenger.publish('OHLCVService:chainStatusChanged', {\n chainIds: chainsToMarkDown,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log(\n 'OHLCV-WS: WebSocket disconnection — marked tracked chains as down',\n {\n count: chainsToMarkDown.length,\n chains: chainsToMarkDown,\n },\n );\n\n this.#chainsUp.clear();\n }\n }\n }\n\n // =============================================================================\n // Private — Utility\n // =============================================================================\n\n #buildChannel(options: OHLCVSubscriptionOptions): string {\n return `${SUBSCRIPTION_NAMESPACE}.${options.assetId}.${options.interval}.${options.currency}`;\n }\n\n // =============================================================================\n // Public — Cleanup\n // =============================================================================\n\n /**\n * Destroy the service and clean up all resources.\n */\n destroy(): void {\n for (const entry of this.#channels.values()) {\n if (entry.gracePeriodTimer) {\n clearTimeout(entry.gracePeriodTimer);\n }\n }\n this.#channels.clear();\n this.#chainsUp.clear();\n\n this.#messenger.call(\n 'BackendWebSocketService:removeChannelCallback',\n SYSTEM_NOTIFICATIONS_CHANNEL,\n );\n }\n}\n"]}
@@ -65,9 +65,7 @@ export type OHLCVServiceMessenger = Messenger<typeof SERVICE_NAME, OHLCVServiceA
65
65
  *
66
66
  * Features:
67
67
  * - Reference counting: multiple UI consumers share one WebSocket subscription
68
- * - Grace-period unsubscribe: reuses same-channel subs on rapid back navigation
69
- * - Grace-period flush: immediately unsubscribes other channels on navigation
70
- * - Unsubscribe retry: retries failed unsubs with backoff before force reconnect
68
+ * - Grace-period unsubscribe: avoids rapid unsub/resub during navigation
71
69
  * - Idempotency: duplicate subscribe calls for the same channel are no-ops
72
70
  * - Reconnect resilience: resubscribes all active channels on reconnect
73
71
  * - Chain-status forwarding: listens to system-notifications for chain up/down
@@ -1 +1 @@
1
- {"version":3,"file":"OHLCVService.d.cts","sourceRoot":"","sources":["../../../src/ws/ohlcv/OHLCVService.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,aAAa,EAGd,mCAAmC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAKrD,OAAO,KAAK,EAAE,oCAAoC,EAAE,2DAA0D;AAC9G,OAAO,KAAK,EAEV,kDAAkD,EAEnD,uCAAsC;AAEvC,OAAO,KAAK,EAAE,yBAAyB,EAAE,+CAA8C;AACvF,OAAO,KAAK,EAAE,QAAQ,EAAE,wBAAwB,EAAE,oBAAmB;AAMrE,QAAA,MAAM,YAAY,iBAAiB,CAAC;AAqCpC;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAMF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,kFAAkF;IAClF,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB,CAAC;AAMF,MAAM,MAAM,mBAAmB,GAAG,yBAAyB,CAAC;AAE5D,eAAO,MAAM,6BAA6B,+aAUhC,CAAC;AAEX,eAAO,MAAM,4BAA4B,6DAE/B,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,oCAAoC,CAAC;AAIlE,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG;IAChD,IAAI,EAAE,iCAAiC,CAAC;IACxC,OAAO,EAAE,CAAC;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9E,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,IAAI,EAAE,gCAAgC,CAAC;IACvC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B,2BAA2B,GAC3B,mCAAmC,GACnC,kCAAkC,CAAC;AAEvC,MAAM,MAAM,aAAa,GAAG,kDAAkD,CAAC;AAE/E,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAC3C,OAAO,YAAY,EACnB,mBAAmB,GAAG,cAAc,EACpC,kBAAkB,GAAG,aAAa,CACnC,CAAC;AAMF;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAY;;IACvB,QAAQ,CAAC,IAAI,kBAAgB;gBAiB3B,OAAO,EAAE,mBAAmB,GAAG;QAAE,SAAS,EAAE,qBAAqB,CAAA;KAAE;IAwBrE;;OAEG;IACH,IAAI,IAAI,IAAI;IAaZ;;;;;;;;OAQG;IACG,SAAS,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuGjE;;;;;;;OAOG;IACG,WAAW,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoUnE;;OAEG;IACH,OAAO,IAAI,IAAI;CAYhB"}
1
+ {"version":3,"file":"OHLCVService.d.cts","sourceRoot":"","sources":["../../../src/ws/ohlcv/OHLCVService.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,aAAa,EAGd,mCAAmC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAIrD,OAAO,KAAK,EAAE,oCAAoC,EAAE,2DAA0D;AAC9G,OAAO,KAAK,EAEV,kDAAkD,EAEnD,uCAAsC;AAEvC,OAAO,KAAK,EAAE,yBAAyB,EAAE,+CAA8C;AACvF,OAAO,KAAK,EAAE,QAAQ,EAAE,wBAAwB,EAAE,oBAAmB;AAMrE,QAAA,MAAM,YAAY,iBAAiB,CAAC;AA0BpC;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAMF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,kFAAkF;IAClF,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB,CAAC;AAMF,MAAM,MAAM,mBAAmB,GAAG,yBAAyB,CAAC;AAE5D,eAAO,MAAM,6BAA6B,+aAUhC,CAAC;AAEX,eAAO,MAAM,4BAA4B,6DAE/B,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,oCAAoC,CAAC;AAIlE,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG;IAChD,IAAI,EAAE,iCAAiC,CAAC;IACxC,OAAO,EAAE,CAAC;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9E,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,IAAI,EAAE,gCAAgC,CAAC;IACvC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B,2BAA2B,GAC3B,mCAAmC,GACnC,kCAAkC,CAAC;AAEvC,MAAM,MAAM,aAAa,GAAG,kDAAkD,CAAC;AAE/E,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAC3C,OAAO,YAAY,EACnB,mBAAmB,GAAG,cAAc,EACpC,kBAAkB,GAAG,aAAa,CACnC,CAAC;AAMF;;;;;;;;;;;;GAYG;AACH,qBAAa,YAAY;;IACvB,QAAQ,CAAC,IAAI,kBAAgB;gBAiB3B,OAAO,EAAE,mBAAmB,GAAG;QAAE,SAAS,EAAE,qBAAqB,CAAA;KAAE;IAwBrE;;OAEG;IACH,IAAI,IAAI,IAAI;IAaZ;;;;;;;;OAQG;IACG,SAAS,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiFjE;;;;;;;OAOG;IACG,WAAW,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiOnE;;OAEG;IACH,OAAO,IAAI,IAAI;CAchB"}
@@ -65,9 +65,7 @@ export type OHLCVServiceMessenger = Messenger<typeof SERVICE_NAME, OHLCVServiceA
65
65
  *
66
66
  * Features:
67
67
  * - Reference counting: multiple UI consumers share one WebSocket subscription
68
- * - Grace-period unsubscribe: reuses same-channel subs on rapid back navigation
69
- * - Grace-period flush: immediately unsubscribes other channels on navigation
70
- * - Unsubscribe retry: retries failed unsubs with backoff before force reconnect
68
+ * - Grace-period unsubscribe: avoids rapid unsub/resub during navigation
71
69
  * - Idempotency: duplicate subscribe calls for the same channel are no-ops
72
70
  * - Reconnect resilience: resubscribes all active channels on reconnect
73
71
  * - Chain-status forwarding: listens to system-notifications for chain up/down
@@ -1 +1 @@
1
- {"version":3,"file":"OHLCVService.d.mts","sourceRoot":"","sources":["../../../src/ws/ohlcv/OHLCVService.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,aAAa,EAGd,mCAAmC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAKrD,OAAO,KAAK,EAAE,oCAAoC,EAAE,2DAA0D;AAC9G,OAAO,KAAK,EAEV,kDAAkD,EAEnD,uCAAsC;AAEvC,OAAO,KAAK,EAAE,yBAAyB,EAAE,+CAA8C;AACvF,OAAO,KAAK,EAAE,QAAQ,EAAE,wBAAwB,EAAE,oBAAmB;AAMrE,QAAA,MAAM,YAAY,iBAAiB,CAAC;AAqCpC;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAMF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,kFAAkF;IAClF,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB,CAAC;AAMF,MAAM,MAAM,mBAAmB,GAAG,yBAAyB,CAAC;AAE5D,eAAO,MAAM,6BAA6B,+aAUhC,CAAC;AAEX,eAAO,MAAM,4BAA4B,6DAE/B,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,oCAAoC,CAAC;AAIlE,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG;IAChD,IAAI,EAAE,iCAAiC,CAAC;IACxC,OAAO,EAAE,CAAC;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9E,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,IAAI,EAAE,gCAAgC,CAAC;IACvC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B,2BAA2B,GAC3B,mCAAmC,GACnC,kCAAkC,CAAC;AAEvC,MAAM,MAAM,aAAa,GAAG,kDAAkD,CAAC;AAE/E,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAC3C,OAAO,YAAY,EACnB,mBAAmB,GAAG,cAAc,EACpC,kBAAkB,GAAG,aAAa,CACnC,CAAC;AAMF;;;;;;;;;;;;;;GAcG;AACH,qBAAa,YAAY;;IACvB,QAAQ,CAAC,IAAI,kBAAgB;gBAiB3B,OAAO,EAAE,mBAAmB,GAAG;QAAE,SAAS,EAAE,qBAAqB,CAAA;KAAE;IAwBrE;;OAEG;IACH,IAAI,IAAI,IAAI;IAaZ;;;;;;;;OAQG;IACG,SAAS,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuGjE;;;;;;;OAOG;IACG,WAAW,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoUnE;;OAEG;IACH,OAAO,IAAI,IAAI;CAYhB"}
1
+ {"version":3,"file":"OHLCVService.d.mts","sourceRoot":"","sources":["../../../src/ws/ohlcv/OHLCVService.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,aAAa,EAGd,mCAAmC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAIrD,OAAO,KAAK,EAAE,oCAAoC,EAAE,2DAA0D;AAC9G,OAAO,KAAK,EAEV,kDAAkD,EAEnD,uCAAsC;AAEvC,OAAO,KAAK,EAAE,yBAAyB,EAAE,+CAA8C;AACvF,OAAO,KAAK,EAAE,QAAQ,EAAE,wBAAwB,EAAE,oBAAmB;AAMrE,QAAA,MAAM,YAAY,iBAAiB,CAAC;AA0BpC;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAMF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,kFAAkF;IAClF,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB,CAAC;AAMF,MAAM,MAAM,mBAAmB,GAAG,yBAAyB,CAAC;AAE5D,eAAO,MAAM,6BAA6B,+aAUhC,CAAC;AAEX,eAAO,MAAM,4BAA4B,6DAE/B,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,oCAAoC,CAAC;AAIlE,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG;IAChD,IAAI,EAAE,iCAAiC,CAAC;IACxC,OAAO,EAAE,CAAC;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9E,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,IAAI,EAAE,gCAAgC,CAAC;IACvC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B,2BAA2B,GAC3B,mCAAmC,GACnC,kCAAkC,CAAC;AAEvC,MAAM,MAAM,aAAa,GAAG,kDAAkD,CAAC;AAE/E,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAC3C,OAAO,YAAY,EACnB,mBAAmB,GAAG,cAAc,EACpC,kBAAkB,GAAG,aAAa,CACnC,CAAC;AAMF;;;;;;;;;;;;GAYG;AACH,qBAAa,YAAY;;IACvB,QAAQ,CAAC,IAAI,kBAAgB;gBAiB3B,OAAO,EAAE,mBAAmB,GAAG;QAAE,SAAS,EAAE,qBAAqB,CAAA;KAAE;IAwBrE;;OAEG;IACH,IAAI,IAAI,IAAI;IAaZ;;;;;;;;OAQG;IACG,SAAS,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiFjE;;;;;;;OAOG;IACG,WAAW,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiOnE;;OAEG;IACH,OAAO,IAAI,IAAI;CAchB"}
@@ -17,9 +17,8 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
17
17
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
18
18
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
19
19
  };
20
- var _OHLCVService_instances, _OHLCVService_messenger, _OHLCVService_trace, _OHLCVService_channels, _OHLCVService_mutex, _OHLCVService_chainsUp, _OHLCVService_subscribeInner, _OHLCVService_unsubscribeInner, _OHLCVService_flushOtherChannels, _OHLCVService_clearChannelTimers, _OHLCVService_unsubscribeChannelOnServer, _OHLCVService_scheduleUnsubscribeRetry, _OHLCVService_runUnsubRetryLoop, _OHLCVService_performUnsubscribe, _OHLCVService_resubscribeActiveChannels, _OHLCVService_handleBarUpdate, _OHLCVService_handleSystemNotification, _OHLCVService_handleWebSocketStateChange, _OHLCVService_buildChannel;
20
+ var _OHLCVService_instances, _OHLCVService_messenger, _OHLCVService_trace, _OHLCVService_channels, _OHLCVService_mutex, _OHLCVService_chainsUp, _OHLCVService_subscribeInner, _OHLCVService_unsubscribeInner, _OHLCVService_performUnsubscribe, _OHLCVService_resubscribeActiveChannels, _OHLCVService_handleBarUpdate, _OHLCVService_handleSystemNotification, _OHLCVService_handleWebSocketStateChange, _OHLCVService_buildChannel;
21
21
  import { Mutex } from "async-mutex";
22
- import { handleAll, IterableBackoff, retry } from "cockatiel";
23
22
  import { projectLogger, createModuleLogger } from "../../logger.mjs";
24
23
  import { WebSocketState } from "../BackendWebSocketService.mjs";
25
24
  // =============================================================================
@@ -32,14 +31,6 @@ const SUBSCRIPTION_NAMESPACE = 'market-data.v1';
32
31
  const SYSTEM_NOTIFICATIONS_CHANNEL = `system-notifications.v1.${SUBSCRIPTION_NAMESPACE}`;
33
32
  /** Delay before actually unsubscribing from a channel after refCount reaches 0. */
34
33
  const GRACE_PERIOD_MS = 3000;
35
- /** Backoff delays between failed WebSocket unsubscribe attempts. */
36
- const UNSUB_RETRY_DELAYS_MS = [1000, 2000, 4000];
37
- const unsubRetryPolicy = retry(handleAll, {
38
- // Cockatiel stops retrying once the failure index reaches `maxAttempts`, so
39
- // `length` here yields one initial attempt plus three delayed retries (4 total).
40
- maxAttempts: UNSUB_RETRY_DELAYS_MS.length,
41
- backoff: new IterableBackoff([...UNSUB_RETRY_DELAYS_MS]),
42
- });
43
34
  export const OHLCV_SERVICE_ALLOWED_ACTIONS = [
44
35
  'BackendWebSocketService:connect',
45
36
  'BackendWebSocketService:forceReconnection',
@@ -64,9 +55,7 @@ export const OHLCV_SERVICE_ALLOWED_EVENTS = [
64
55
  *
65
56
  * Features:
66
57
  * - Reference counting: multiple UI consumers share one WebSocket subscription
67
- * - Grace-period unsubscribe: reuses same-channel subs on rapid back navigation
68
- * - Grace-period flush: immediately unsubscribes other channels on navigation
69
- * - Unsubscribe retry: retries failed unsubs with backoff before force reconnect
58
+ * - Grace-period unsubscribe: avoids rapid unsub/resub during navigation
70
59
  * - Idempotency: duplicate subscribe calls for the same channel are no-ops
71
60
  * - Reconnect resilience: resubscribes all active channels on reconnect
72
61
  * - Chain-status forwarding: listens to system-notifications for chain up/down
@@ -150,7 +139,9 @@ export class OHLCVService {
150
139
  */
151
140
  destroy() {
152
141
  for (const entry of __classPrivateFieldGet(this, _OHLCVService_channels, "f").values()) {
153
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_clearChannelTimers).call(this, entry);
142
+ if (entry.gracePeriodTimer) {
143
+ clearTimeout(entry.gracePeriodTimer);
144
+ }
154
145
  }
155
146
  __classPrivateFieldGet(this, _OHLCVService_channels, "f").clear();
156
147
  __classPrivateFieldGet(this, _OHLCVService_chainsUp, "f").clear();
@@ -159,27 +150,13 @@ export class OHLCVService {
159
150
  }
160
151
  _OHLCVService_messenger = new WeakMap(), _OHLCVService_trace = new WeakMap(), _OHLCVService_channels = new WeakMap(), _OHLCVService_mutex = new WeakMap(), _OHLCVService_chainsUp = new WeakMap(), _OHLCVService_instances = new WeakSet(), _OHLCVService_subscribeInner = async function _OHLCVService_subscribeInner(channel) {
161
152
  const entry = __classPrivateFieldGet(this, _OHLCVService_channels, "f").get(channel);
162
- if (entry?.retryAbort) {
163
- entry.retryAbort.abort();
164
- entry.retryAbort = undefined;
165
- entry.refCount = 1;
166
- if (__classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:channelHasSubscription', channel)) {
167
- await __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_flushOtherChannels).call(this, channel);
168
- log('OHLCV-WS: Cancelled unsubscribe retry — reusing WS subscription', {
169
- channel,
170
- });
171
- return;
172
- }
173
- // WS subscription was lost — fall through to recreate it.
174
- }
175
- else if (entry?.gracePeriodTimer) {
153
+ if (entry?.gracePeriodTimer) {
176
154
  clearTimeout(entry.gracePeriodTimer);
177
155
  entry.gracePeriodTimer = undefined;
178
156
  log('OHLCV-WS: Cancelled grace-period unsubscribe', {
179
157
  channel,
180
158
  });
181
159
  if (__classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:channelHasSubscription', channel)) {
182
- await __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_flushOtherChannels).call(this, channel);
183
160
  entry.refCount += 1;
184
161
  log('OHLCV-WS: WS subscription still alive, bumped refCount', {
185
162
  channel,
@@ -194,7 +171,6 @@ _OHLCVService_messenger = new WeakMap(), _OHLCVService_trace = new WeakMap(), _O
194
171
  entry.refCount += 1;
195
172
  return;
196
173
  }
197
- await __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_flushOtherChannels).call(this, channel);
198
174
  try {
199
175
  await __classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:connect');
200
176
  if (__classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:channelHasSubscription', channel)) {
@@ -236,108 +212,15 @@ _OHLCVService_messenger = new WeakMap(), _OHLCVService_trace = new WeakMap(), _O
236
212
  }
237
213
  entry.gracePeriodTimer = setTimeout(() => {
238
214
  entry.gracePeriodTimer = undefined;
239
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
240
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_performUnsubscribe).call(this, channel);
215
+ __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_performUnsubscribe).call(this, channel).catch(() => {
216
+ // no-op
217
+ });
241
218
  }, GRACE_PERIOD_MS);
242
- }, _OHLCVService_flushOtherChannels =
219
+ }, _OHLCVService_performUnsubscribe =
243
220
  // =============================================================================
244
221
  // Private — WebSocket Subscription Helpers
245
222
  // =============================================================================
246
- /**
247
- * Immediately unsubscribe other channels in grace or failed-cleanup state.
248
- * Called while the subscribe mutex is held before opening a new channel.
249
- *
250
- * @param exceptChannel - Channel being subscribed; excluded from flush.
251
- */
252
- async function _OHLCVService_flushOtherChannels(exceptChannel) {
253
- for (const [channel, channelEntry] of __classPrivateFieldGet(this, _OHLCVService_channels, "f").entries()) {
254
- if (channel === exceptChannel || channelEntry.refCount > 0) {
255
- continue;
256
- }
257
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_clearChannelTimers).call(this, channelEntry);
258
- log('OHLCV-WS: Flushing grace-period channel before new subscribe', {
259
- flushedChannel: channel,
260
- newChannel: exceptChannel,
261
- });
262
- const success = await __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_unsubscribeChannelOnServer).call(this, channel);
263
- if (success) {
264
- __classPrivateFieldGet(this, _OHLCVService_channels, "f").delete(channel);
265
- }
266
- else {
267
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_scheduleUnsubscribeRetry).call(this, channel);
268
- }
269
- }
270
- }, _OHLCVService_clearChannelTimers = function _OHLCVService_clearChannelTimers(entry) {
271
- if (entry.gracePeriodTimer) {
272
- clearTimeout(entry.gracePeriodTimer);
273
- entry.gracePeriodTimer = undefined;
274
- }
275
- entry.retryAbort?.abort();
276
- entry.retryAbort = undefined;
277
- }, _OHLCVService_unsubscribeChannelOnServer = async function _OHLCVService_unsubscribeChannelOnServer(channel) {
278
- try {
279
- const subscriptions = __classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:getSubscriptionsByChannel', channel);
280
- for (const sub of subscriptions) {
281
- await sub.unsubscribe();
282
- }
283
- return true;
284
- }
285
- catch (error) {
286
- log('OHLCV-WS: Unsubscription failed', { channel, error });
287
- __classPrivateFieldGet(this, _OHLCVService_messenger, "f").publish('OHLCVService:subscriptionError', {
288
- channel,
289
- error: String(error),
290
- operation: 'unsubscribe',
291
- });
292
- return false;
293
- }
294
- }, _OHLCVService_scheduleUnsubscribeRetry = function _OHLCVService_scheduleUnsubscribeRetry(channel) {
295
- const entry = __classPrivateFieldGet(this, _OHLCVService_channels, "f").get(channel) ?? { refCount: 0 };
296
- entry.retryAbort?.abort();
297
- entry.retryAbort = new AbortController();
298
- __classPrivateFieldGet(this, _OHLCVService_channels, "f").set(channel, entry);
299
- const { signal } = entry.retryAbort;
300
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
301
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_runUnsubRetryLoop).call(this, channel, signal);
302
- }, _OHLCVService_runUnsubRetryLoop = async function _OHLCVService_runUnsubRetryLoop(channel, signal) {
303
- try {
304
- await unsubRetryPolicy.execute(async () => {
305
- const releaseLock = await __classPrivateFieldGet(this, _OHLCVService_mutex, "f").acquire();
306
- try {
307
- const current = __classPrivateFieldGet(this, _OHLCVService_channels, "f").get(channel);
308
- if (current && current.refCount > 0) {
309
- return;
310
- }
311
- const success = await __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_unsubscribeChannelOnServer).call(this, channel);
312
- if (!success) {
313
- throw new Error('unsubscribe failed');
314
- }
315
- __classPrivateFieldGet(this, _OHLCVService_channels, "f").delete(channel);
316
- log('OHLCV-WS: WS unsubscribe completed', { channel });
317
- }
318
- finally {
319
- releaseLock();
320
- }
321
- }, signal);
322
- }
323
- catch {
324
- if (signal.aborted) {
325
- return;
326
- }
327
- log('OHLCV-WS: Unsubscribe retries exhausted — forcing reconnection', {
328
- channel,
329
- });
330
- __classPrivateFieldGet(this, _OHLCVService_channels, "f").delete(channel);
331
- // Last resort: reconnects the shared BackendWebSocketService instance
332
- // (AccountActivityService and other consumers share this connection).
333
- // They resubscribe on CONNECTED; OHLCV only resubscribes refCount > 0.
334
- await __classPrivateFieldGet(this, _OHLCVService_messenger, "f")
335
- .call('BackendWebSocketService:forceReconnection')
336
- .catch(() => {
337
- // no-op
338
- });
339
- }
340
- }, _OHLCVService_performUnsubscribe = async function _OHLCVService_performUnsubscribe(channel) {
223
+ async function _OHLCVService_performUnsubscribe(channel) {
341
224
  const releaseLock = await __classPrivateFieldGet(this, _OHLCVService_mutex, "f").acquire();
342
225
  try {
343
226
  const entry = __classPrivateFieldGet(this, _OHLCVService_channels, "f").get(channel);
@@ -348,12 +231,26 @@ async function _OHLCVService_flushOtherChannels(exceptChannel) {
348
231
  log('OHLCV-WS: Grace period expired — performing actual WS unsubscribe', {
349
232
  channel,
350
233
  });
351
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_clearChannelTimers).call(this, __classPrivateFieldGet(this, _OHLCVService_channels, "f").get(channel) ?? { refCount: 0 });
234
+ __classPrivateFieldGet(this, _OHLCVService_channels, "f").delete(channel);
235
+ try {
236
+ const subscriptions = __classPrivateFieldGet(this, _OHLCVService_messenger, "f").call('BackendWebSocketService:getSubscriptionsByChannel', channel);
237
+ for (const sub of subscriptions) {
238
+ await sub.unsubscribe();
239
+ }
240
+ log('OHLCV-WS: WS unsubscribe completed', { channel });
241
+ }
242
+ catch (error) {
243
+ log('OHLCV-WS: Unsubscription failed', { channel, error });
244
+ __classPrivateFieldGet(this, _OHLCVService_messenger, "f").publish('OHLCVService:subscriptionError', {
245
+ channel,
246
+ error: String(error),
247
+ operation: 'unsubscribe',
248
+ });
249
+ }
352
250
  }
353
251
  finally {
354
252
  releaseLock();
355
253
  }
356
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_scheduleUnsubscribeRetry).call(this, channel);
357
254
  }, _OHLCVService_resubscribeActiveChannels =
358
255
  /**
359
256
  * Resubscribe all channels that were active before a disconnect.
@@ -366,10 +263,8 @@ async function _OHLCVService_resubscribeActiveChannels() {
366
263
  log('OHLCV-WS: Resubscribing active channels after reconnect', {
367
264
  count: channelCount,
368
265
  });
369
- for (const [channel, entry] of [...__classPrivateFieldGet(this, _OHLCVService_channels, "f").entries()]) {
266
+ for (const [channel, entry] of __classPrivateFieldGet(this, _OHLCVService_channels, "f").entries()) {
370
267
  if (entry.refCount === 0) {
371
- __classPrivateFieldGet(this, _OHLCVService_instances, "m", _OHLCVService_clearChannelTimers).call(this, entry);
372
- __classPrivateFieldGet(this, _OHLCVService_channels, "f").delete(channel);
373
268
  continue;
374
269
  }
375
270
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"OHLCVService.mjs","sourceRoot":"","sources":["../../../src/ws/ohlcv/OHLCVService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OHLCV Service for real-time candlestick data streaming via WebSocket.\n *\n * Wraps {@link BackendWebSocketService} through the messenger pattern to\n * provide subscribe/unsubscribe semantics for OHLCV market-data channels.\n * Includes reference counting, grace-period unsubscribe, idempotency checks,\n * chain-status forwarding, and automatic resubscription on reconnect.\n */\n\nimport type {\n TraceCallback,\n TraceContext,\n TraceRequest,\n} from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport { Mutex } from 'async-mutex';\nimport { handleAll, IterableBackoff, retry } from 'cockatiel';\n\nimport { projectLogger, createModuleLogger } from '../../logger.js';\nimport type { BackendWebSocketServiceMethodActions } from '../BackendWebSocketService-method-action-types.js';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from '../BackendWebSocketService.js';\nimport { WebSocketState } from '../BackendWebSocketService.js';\nimport type { OHLCVServiceMethodActions } from './OHLCVService-method-action-types.js';\nimport type { OHLCVBar, OHLCVSubscriptionOptions } from './types.js';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst SERVICE_NAME = 'OHLCVService';\n\nconst log = createModuleLogger(projectLogger, SERVICE_NAME);\n\nconst MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'market-data.v1';\n\nconst SYSTEM_NOTIFICATIONS_CHANNEL = `system-notifications.v1.${SUBSCRIPTION_NAMESPACE}`;\n\n/** Delay before actually unsubscribing from a channel after refCount reaches 0. */\nconst GRACE_PERIOD_MS = 3_000;\n\n/** Backoff delays between failed WebSocket unsubscribe attempts. */\nconst UNSUB_RETRY_DELAYS_MS = [1_000, 2_000, 4_000] as const;\n\nconst unsubRetryPolicy = retry(handleAll, {\n // Cockatiel stops retrying once the failure index reaches `maxAttempts`, so\n // `length` here yields one initial attempt plus three delayed retries (4 total).\n maxAttempts: UNSUB_RETRY_DELAYS_MS.length,\n backoff: new IterableBackoff([...UNSUB_RETRY_DELAYS_MS]),\n});\n\n// =============================================================================\n// Types — Channel Tracking\n// =============================================================================\n\ntype ChannelEntry = {\n refCount: number;\n gracePeriodTimer?: ReturnType<typeof setTimeout>;\n retryAbort?: AbortController;\n};\n\n// =============================================================================\n// Types — System Notifications\n// =============================================================================\n\n/**\n * System notification data for chain status updates on market-data channels.\n */\nexport type OHLCVSystemNotificationData = {\n chainIds: string[];\n status: 'down' | 'up';\n timestamp?: number;\n};\n\n// =============================================================================\n// Types — Service Options\n// =============================================================================\n\n/**\n * Configuration options for the OHLCV service.\n */\nexport type OHLCVServiceOptions = {\n /** Optional callback to trace performance of OHLCV operations (default: no-op) */\n traceFn?: TraceCallback;\n};\n\n// =============================================================================\n// Action and Event Types\n// =============================================================================\n\nexport type OHLCVServiceActions = OHLCVServiceMethodActions;\n\nexport const OHLCV_SERVICE_ALLOWED_ACTIONS = [\n 'BackendWebSocketService:connect',\n 'BackendWebSocketService:forceReconnection',\n 'BackendWebSocketService:subscribe',\n 'BackendWebSocketService:getConnectionInfo',\n 'BackendWebSocketService:channelHasSubscription',\n 'BackendWebSocketService:getSubscriptionsByChannel',\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n 'BackendWebSocketService:addChannelCallback',\n 'BackendWebSocketService:removeChannelCallback',\n] as const;\n\nexport const OHLCV_SERVICE_ALLOWED_EVENTS = [\n 'BackendWebSocketService:connectionStateChanged',\n] as const;\n\nexport type AllowedActions = BackendWebSocketServiceMethodActions;\n\n// Events published by OHLCVService\n\nexport type OHLCVServiceBarUpdatedEvent = {\n type: `OHLCVService:barUpdated`;\n payload: [{ channel: string; bar: OHLCVBar }];\n};\n\nexport type OHLCVServiceChainStatusChangedEvent = {\n type: `OHLCVService:chainStatusChanged`;\n payload: [{ chainIds: string[]; status: 'up' | 'down'; timestamp?: number }];\n};\n\nexport type OHLCVServiceSubscriptionErrorEvent = {\n type: `OHLCVService:subscriptionError`;\n payload: [{ channel: string; error: string; operation: string }];\n};\n\nexport type OHLCVServiceEvents =\n | OHLCVServiceBarUpdatedEvent\n | OHLCVServiceChainStatusChangedEvent\n | OHLCVServiceSubscriptionErrorEvent;\n\nexport type AllowedEvents = BackendWebSocketServiceConnectionStateChangedEvent;\n\nexport type OHLCVServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n OHLCVServiceActions | AllowedActions,\n OHLCVServiceEvents | AllowedEvents\n>;\n\n// =============================================================================\n// Main Service Class\n// =============================================================================\n\n/**\n * Service for real-time OHLCV candlestick streaming via the backend WebSocket\n * gateway. Communicates with {@link BackendWebSocketService} exclusively\n * through the messenger — no direct import of the class.\n *\n * Features:\n * - Reference counting: multiple UI consumers share one WebSocket subscription\n * - Grace-period unsubscribe: reuses same-channel subs on rapid back navigation\n * - Grace-period flush: immediately unsubscribes other channels on navigation\n * - Unsubscribe retry: retries failed unsubs with backoff before force reconnect\n * - Idempotency: duplicate subscribe calls for the same channel are no-ops\n * - Reconnect resilience: resubscribes all active channels on reconnect\n * - Chain-status forwarding: listens to system-notifications for chain up/down\n *\n */\nexport class OHLCVService {\n readonly name = SERVICE_NAME;\n\n readonly #messenger: OHLCVServiceMessenger;\n\n readonly #trace: TraceCallback;\n\n readonly #channels = new Map<string, ChannelEntry>();\n\n readonly #mutex = new Mutex();\n\n readonly #chainsUp = new Set<string>();\n\n // =============================================================================\n // Constructor\n // =============================================================================\n\n constructor(\n options: OHLCVServiceOptions & { messenger: OHLCVServiceMessenger },\n ) {\n this.#messenger = options.messenger;\n\n this.#trace =\n options.traceFn ??\n ((<Result>(\n _request: TraceRequest,\n fn?: (context?: TraceContext) => Result,\n ) => fn?.()) as TraceCallback);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n }\n\n /**\n * Register the system-notifications channel callback.\n */\n init(): void {\n log('OHLCV-WS: Initializing — registering system-notifications callback');\n this.#messenger.call('BackendWebSocketService:addChannelCallback', {\n channelName: SYSTEM_NOTIFICATIONS_CHANNEL,\n callback: (notification: ServerNotificationMessage) =>\n this.#handleSystemNotification(notification),\n });\n }\n\n // =============================================================================\n // Public — Subscribe / Unsubscribe\n // =============================================================================\n\n /**\n * Subscribe to an OHLCV channel. If this is the first subscriber for the\n * given asset/interval/currency combination a WebSocket subscription is\n * created. Additional calls for the same combination only bump the reference\n * count.\n *\n * @param options - The subscription parameters.\n * @returns A promise that resolves once the subscription is established.\n */\n async subscribe(options: OHLCVSubscriptionOptions): Promise<void> {\n const channel = this.#buildChannel(options);\n const releaseLock = await this.#mutex.acquire();\n try {\n await this.#subscribeInner(channel);\n } finally {\n releaseLock();\n }\n }\n\n async #subscribeInner(channel: string): Promise<void> {\n const entry = this.#channels.get(channel);\n\n if (entry?.retryAbort) {\n entry.retryAbort.abort();\n entry.retryAbort = undefined;\n entry.refCount = 1;\n\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n await this.#flushOtherChannels(channel);\n log('OHLCV-WS: Cancelled unsubscribe retry — reusing WS subscription', {\n channel,\n });\n return;\n }\n // WS subscription was lost — fall through to recreate it.\n } else if (entry?.gracePeriodTimer) {\n clearTimeout(entry.gracePeriodTimer);\n entry.gracePeriodTimer = undefined;\n log('OHLCV-WS: Cancelled grace-period unsubscribe', {\n channel,\n });\n\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n await this.#flushOtherChannels(channel);\n entry.refCount += 1;\n log('OHLCV-WS: WS subscription still alive, bumped refCount', {\n channel,\n refCount: entry.refCount,\n });\n return;\n }\n // WS subscription was lost (e.g. after disconnect/reconnect) — fall\n // through to recreate it. refCount is bumped only after success below.\n } else if (entry && entry.refCount > 0) {\n entry.refCount += 1;\n return;\n }\n\n await this.#flushOtherChannels(channel);\n\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n log(\n 'OHLCV-WS: Channel already has WS subscription (idempotency), skipping',\n {\n channel,\n },\n );\n this.#channels.set(channel, { refCount: 1 });\n return;\n }\n\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: SUBSCRIPTION_NAMESPACE,\n callback: (notification: ServerNotificationMessage) => {\n this.#handleBarUpdate(channel, notification);\n },\n });\n\n this.#channels.set(channel, { refCount: 1 });\n log('OHLCV-WS: Subscribe succeeded — new WS subscription created', {\n channel,\n });\n } catch (error) {\n log('OHLCV-WS: Subscription failed', { channel, error });\n this.#channels.delete(channel);\n this.#messenger.publish('OHLCVService:subscriptionError', {\n channel,\n error: String(error),\n operation: 'subscribe',\n });\n }\n }\n\n /**\n * Unsubscribe from an OHLCV channel. Decrements the reference count and,\n * when it reaches zero, starts a grace-period timer before actually\n * unsubscribing from the WebSocket to absorb rapid navigation patterns.\n *\n * @param options - The subscription parameters to unsubscribe from.\n * @returns A promise that resolves once the unsubscription is processed.\n */\n async unsubscribe(options: OHLCVSubscriptionOptions): Promise<void> {\n const channel = this.#buildChannel(options);\n const releaseLock = await this.#mutex.acquire();\n try {\n await this.#unsubscribeInner(channel);\n } finally {\n releaseLock();\n }\n }\n\n async #unsubscribeInner(channel: string): Promise<void> {\n const entry = this.#channels.get(channel);\n\n if (!entry || entry.refCount <= 0) {\n return;\n }\n\n entry.refCount -= 1;\n\n if (entry.refCount > 0) {\n return;\n }\n\n entry.gracePeriodTimer = setTimeout(() => {\n entry.gracePeriodTimer = undefined;\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#performUnsubscribe(channel);\n }, GRACE_PERIOD_MS);\n }\n\n // =============================================================================\n // Private — WebSocket Subscription Helpers\n // =============================================================================\n\n /**\n * Immediately unsubscribe other channels in grace or failed-cleanup state.\n * Called while the subscribe mutex is held before opening a new channel.\n *\n * @param exceptChannel - Channel being subscribed; excluded from flush.\n */\n async #flushOtherChannels(exceptChannel: string): Promise<void> {\n for (const [channel, channelEntry] of this.#channels.entries()) {\n if (channel === exceptChannel || channelEntry.refCount > 0) {\n continue;\n }\n\n this.#clearChannelTimers(channelEntry);\n log('OHLCV-WS: Flushing grace-period channel before new subscribe', {\n flushedChannel: channel,\n newChannel: exceptChannel,\n });\n\n const success = await this.#unsubscribeChannelOnServer(channel);\n if (success) {\n this.#channels.delete(channel);\n } else {\n this.#scheduleUnsubscribeRetry(channel);\n }\n }\n }\n\n #clearChannelTimers(entry: ChannelEntry): void {\n if (entry.gracePeriodTimer) {\n clearTimeout(entry.gracePeriodTimer);\n entry.gracePeriodTimer = undefined;\n }\n entry.retryAbort?.abort();\n entry.retryAbort = undefined;\n }\n\n async #unsubscribeChannelOnServer(channel: string): Promise<boolean> {\n try {\n const subscriptions = this.#messenger.call(\n 'BackendWebSocketService:getSubscriptionsByChannel',\n channel,\n );\n\n for (const sub of subscriptions) {\n await sub.unsubscribe();\n }\n return true;\n } catch (error) {\n log('OHLCV-WS: Unsubscription failed', { channel, error });\n this.#messenger.publish('OHLCVService:subscriptionError', {\n channel,\n error: String(error),\n operation: 'unsubscribe',\n });\n return false;\n }\n }\n\n #scheduleUnsubscribeRetry(channel: string): void {\n const entry = this.#channels.get(channel) ?? { refCount: 0 };\n entry.retryAbort?.abort();\n entry.retryAbort = new AbortController();\n this.#channels.set(channel, entry);\n\n const { signal } = entry.retryAbort;\n\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#runUnsubRetryLoop(channel, signal);\n }\n\n async #runUnsubRetryLoop(\n channel: string,\n signal: AbortSignal,\n ): Promise<void> {\n try {\n await unsubRetryPolicy.execute(async () => {\n const releaseLock = await this.#mutex.acquire();\n try {\n const current = this.#channels.get(channel);\n if (current && current.refCount > 0) {\n return;\n }\n\n const success = await this.#unsubscribeChannelOnServer(channel);\n if (!success) {\n throw new Error('unsubscribe failed');\n }\n\n this.#channels.delete(channel);\n log('OHLCV-WS: WS unsubscribe completed', { channel });\n } finally {\n releaseLock();\n }\n }, signal);\n } catch {\n if (signal.aborted) {\n return;\n }\n\n log('OHLCV-WS: Unsubscribe retries exhausted — forcing reconnection', {\n channel,\n });\n this.#channels.delete(channel);\n // Last resort: reconnects the shared BackendWebSocketService instance\n // (AccountActivityService and other consumers share this connection).\n // They resubscribe on CONNECTED; OHLCV only resubscribes refCount > 0.\n await this.#messenger\n .call('BackendWebSocketService:forceReconnection')\n .catch(() => {\n // no-op\n });\n }\n }\n\n async #performUnsubscribe(channel: string): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n try {\n const entry = this.#channels.get(channel);\n if (entry && entry.refCount > 0) {\n log(\n 'OHLCV-WS: Skipping unsubscribe — new subscriber arrived while queued',\n { channel, refCount: entry.refCount },\n );\n return;\n }\n\n log('OHLCV-WS: Grace period expired — performing actual WS unsubscribe', {\n channel,\n });\n\n this.#clearChannelTimers(this.#channels.get(channel) ?? { refCount: 0 });\n } finally {\n releaseLock();\n }\n\n this.#scheduleUnsubscribeRetry(channel);\n }\n\n /**\n * Resubscribe all channels that were active before a disconnect.\n * Called when WebSocket transitions to CONNECTED.\n */\n async #resubscribeActiveChannels(): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n try {\n const channelCount = this.#channels.size;\n log('OHLCV-WS: Resubscribing active channels after reconnect', {\n count: channelCount,\n });\n\n for (const [channel, entry] of [...this.#channels.entries()]) {\n if (entry.refCount === 0) {\n this.#clearChannelTimers(entry);\n this.#channels.delete(channel);\n continue;\n }\n\n try {\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n log(\n 'OHLCV-WS: Channel already subscribed on server, skipping resubscribe',\n {\n channel,\n },\n );\n continue;\n }\n\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: SUBSCRIPTION_NAMESPACE,\n callback: (notification: ServerNotificationMessage) => {\n this.#handleBarUpdate(channel, notification);\n },\n });\n log('OHLCV-WS: Resubscription succeeded', { channel });\n } catch (error) {\n log('OHLCV-WS: Resubscription failed for channel', {\n channel,\n error,\n });\n }\n }\n } finally {\n releaseLock();\n }\n }\n\n // =============================================================================\n // Private — Message Handlers\n // =============================================================================\n\n #handleBarUpdate(\n channel: string,\n notification: ServerNotificationMessage,\n ): void {\n const bar = notification.data as OHLCVBar;\n\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#trace(\n {\n name: `${SERVICE_NAME} Bar Update`,\n data: { channel, timestamp: bar.timestamp },\n tags: { service: SERVICE_NAME },\n },\n () => {\n this.#messenger.publish('OHLCVService:barUpdated', { channel, bar });\n },\n );\n }\n\n #handleSystemNotification(notification: ServerNotificationMessage): void {\n const data = notification.data as OHLCVSystemNotificationData;\n const { timestamp } = notification;\n\n if (!data.chainIds || !Array.isArray(data.chainIds) || !data.status) {\n throw new Error(\n 'Invalid system notification data: missing chainIds or status',\n );\n }\n\n if (data.status === 'up') {\n for (const chainId of data.chainIds) {\n this.#chainsUp.add(chainId);\n }\n } else {\n for (const chainId of data.chainIds) {\n this.#chainsUp.delete(chainId);\n }\n }\n\n this.#messenger.publish('OHLCVService:chainStatusChanged', {\n chainIds: data.chainIds,\n status: data.status,\n timestamp,\n });\n\n log(`OHLCV-WS: Chain status change: ${data.status}`, {\n chains: data.chainIds,\n status: data.status,\n });\n }\n\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n await this.#resubscribeActiveChannels();\n } else if (state === WebSocketState.DISCONNECTED) {\n const chainsToMarkDown = Array.from(this.#chainsUp);\n\n if (chainsToMarkDown.length > 0) {\n this.#messenger.publish('OHLCVService:chainStatusChanged', {\n chainIds: chainsToMarkDown,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log(\n 'OHLCV-WS: WebSocket disconnection — marked tracked chains as down',\n {\n count: chainsToMarkDown.length,\n chains: chainsToMarkDown,\n },\n );\n\n this.#chainsUp.clear();\n }\n }\n }\n\n // =============================================================================\n // Private — Utility\n // =============================================================================\n\n #buildChannel(options: OHLCVSubscriptionOptions): string {\n return `${SUBSCRIPTION_NAMESPACE}.${options.assetId}.${options.interval}.${options.currency}`;\n }\n\n // =============================================================================\n // Public — Cleanup\n // =============================================================================\n\n /**\n * Destroy the service and clean up all resources.\n */\n destroy(): void {\n for (const entry of this.#channels.values()) {\n this.#clearChannelTimers(entry);\n }\n this.#channels.clear();\n this.#chainsUp.clear();\n\n this.#messenger.call(\n 'BackendWebSocketService:removeChannelCallback',\n SYSTEM_NOTIFICATIONS_CHANNEL,\n );\n }\n}\n"]}
1
+ {"version":3,"file":"OHLCVService.mjs","sourceRoot":"","sources":["../../../src/ws/ohlcv/OHLCVService.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OHLCV Service for real-time candlestick data streaming via WebSocket.\n *\n * Wraps {@link BackendWebSocketService} through the messenger pattern to\n * provide subscribe/unsubscribe semantics for OHLCV market-data channels.\n * Includes reference counting, grace-period unsubscribe, idempotency checks,\n * chain-status forwarding, and automatic resubscription on reconnect.\n */\n\nimport type {\n TraceCallback,\n TraceContext,\n TraceRequest,\n} from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport { Mutex } from 'async-mutex';\n\nimport { projectLogger, createModuleLogger } from '../../logger.js';\nimport type { BackendWebSocketServiceMethodActions } from '../BackendWebSocketService-method-action-types.js';\nimport type {\n WebSocketConnectionInfo,\n BackendWebSocketServiceConnectionStateChangedEvent,\n ServerNotificationMessage,\n} from '../BackendWebSocketService.js';\nimport { WebSocketState } from '../BackendWebSocketService.js';\nimport type { OHLCVServiceMethodActions } from './OHLCVService-method-action-types.js';\nimport type { OHLCVBar, OHLCVSubscriptionOptions } from './types.js';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst SERVICE_NAME = 'OHLCVService';\n\nconst log = createModuleLogger(projectLogger, SERVICE_NAME);\n\nconst MESSENGER_EXPOSED_METHODS = ['subscribe', 'unsubscribe'] as const;\n\nconst SUBSCRIPTION_NAMESPACE = 'market-data.v1';\n\nconst SYSTEM_NOTIFICATIONS_CHANNEL = `system-notifications.v1.${SUBSCRIPTION_NAMESPACE}`;\n\n/** Delay before actually unsubscribing from a channel after refCount reaches 0. */\nconst GRACE_PERIOD_MS = 3_000;\n\n// =============================================================================\n// Types — Channel Tracking\n// =============================================================================\n\ntype ChannelEntry = {\n refCount: number;\n gracePeriodTimer?: ReturnType<typeof setTimeout>;\n};\n\n// =============================================================================\n// Types — System Notifications\n// =============================================================================\n\n/**\n * System notification data for chain status updates on market-data channels.\n */\nexport type OHLCVSystemNotificationData = {\n chainIds: string[];\n status: 'down' | 'up';\n timestamp?: number;\n};\n\n// =============================================================================\n// Types — Service Options\n// =============================================================================\n\n/**\n * Configuration options for the OHLCV service.\n */\nexport type OHLCVServiceOptions = {\n /** Optional callback to trace performance of OHLCV operations (default: no-op) */\n traceFn?: TraceCallback;\n};\n\n// =============================================================================\n// Action and Event Types\n// =============================================================================\n\nexport type OHLCVServiceActions = OHLCVServiceMethodActions;\n\nexport const OHLCV_SERVICE_ALLOWED_ACTIONS = [\n 'BackendWebSocketService:connect',\n 'BackendWebSocketService:forceReconnection',\n 'BackendWebSocketService:subscribe',\n 'BackendWebSocketService:getConnectionInfo',\n 'BackendWebSocketService:channelHasSubscription',\n 'BackendWebSocketService:getSubscriptionsByChannel',\n 'BackendWebSocketService:findSubscriptionsByChannelPrefix',\n 'BackendWebSocketService:addChannelCallback',\n 'BackendWebSocketService:removeChannelCallback',\n] as const;\n\nexport const OHLCV_SERVICE_ALLOWED_EVENTS = [\n 'BackendWebSocketService:connectionStateChanged',\n] as const;\n\nexport type AllowedActions = BackendWebSocketServiceMethodActions;\n\n// Events published by OHLCVService\n\nexport type OHLCVServiceBarUpdatedEvent = {\n type: `OHLCVService:barUpdated`;\n payload: [{ channel: string; bar: OHLCVBar }];\n};\n\nexport type OHLCVServiceChainStatusChangedEvent = {\n type: `OHLCVService:chainStatusChanged`;\n payload: [{ chainIds: string[]; status: 'up' | 'down'; timestamp?: number }];\n};\n\nexport type OHLCVServiceSubscriptionErrorEvent = {\n type: `OHLCVService:subscriptionError`;\n payload: [{ channel: string; error: string; operation: string }];\n};\n\nexport type OHLCVServiceEvents =\n | OHLCVServiceBarUpdatedEvent\n | OHLCVServiceChainStatusChangedEvent\n | OHLCVServiceSubscriptionErrorEvent;\n\nexport type AllowedEvents = BackendWebSocketServiceConnectionStateChangedEvent;\n\nexport type OHLCVServiceMessenger = Messenger<\n typeof SERVICE_NAME,\n OHLCVServiceActions | AllowedActions,\n OHLCVServiceEvents | AllowedEvents\n>;\n\n// =============================================================================\n// Main Service Class\n// =============================================================================\n\n/**\n * Service for real-time OHLCV candlestick streaming via the backend WebSocket\n * gateway. Communicates with {@link BackendWebSocketService} exclusively\n * through the messenger — no direct import of the class.\n *\n * Features:\n * - Reference counting: multiple UI consumers share one WebSocket subscription\n * - Grace-period unsubscribe: avoids rapid unsub/resub during navigation\n * - Idempotency: duplicate subscribe calls for the same channel are no-ops\n * - Reconnect resilience: resubscribes all active channels on reconnect\n * - Chain-status forwarding: listens to system-notifications for chain up/down\n *\n */\nexport class OHLCVService {\n readonly name = SERVICE_NAME;\n\n readonly #messenger: OHLCVServiceMessenger;\n\n readonly #trace: TraceCallback;\n\n readonly #channels = new Map<string, ChannelEntry>();\n\n readonly #mutex = new Mutex();\n\n readonly #chainsUp = new Set<string>();\n\n // =============================================================================\n // Constructor\n // =============================================================================\n\n constructor(\n options: OHLCVServiceOptions & { messenger: OHLCVServiceMessenger },\n ) {\n this.#messenger = options.messenger;\n\n this.#trace =\n options.traceFn ??\n ((<Result>(\n _request: TraceRequest,\n fn?: (context?: TraceContext) => Result,\n ) => fn?.()) as TraceCallback);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n\n this.#messenger.subscribe(\n 'BackendWebSocketService:connectionStateChanged',\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n (connectionInfo: WebSocketConnectionInfo) =>\n this.#handleWebSocketStateChange(connectionInfo),\n );\n }\n\n /**\n * Register the system-notifications channel callback.\n */\n init(): void {\n log('OHLCV-WS: Initializing — registering system-notifications callback');\n this.#messenger.call('BackendWebSocketService:addChannelCallback', {\n channelName: SYSTEM_NOTIFICATIONS_CHANNEL,\n callback: (notification: ServerNotificationMessage) =>\n this.#handleSystemNotification(notification),\n });\n }\n\n // =============================================================================\n // Public — Subscribe / Unsubscribe\n // =============================================================================\n\n /**\n * Subscribe to an OHLCV channel. If this is the first subscriber for the\n * given asset/interval/currency combination a WebSocket subscription is\n * created. Additional calls for the same combination only bump the reference\n * count.\n *\n * @param options - The subscription parameters.\n * @returns A promise that resolves once the subscription is established.\n */\n async subscribe(options: OHLCVSubscriptionOptions): Promise<void> {\n const channel = this.#buildChannel(options);\n const releaseLock = await this.#mutex.acquire();\n try {\n await this.#subscribeInner(channel);\n } finally {\n releaseLock();\n }\n }\n\n async #subscribeInner(channel: string): Promise<void> {\n const entry = this.#channels.get(channel);\n\n if (entry?.gracePeriodTimer) {\n clearTimeout(entry.gracePeriodTimer);\n entry.gracePeriodTimer = undefined;\n log('OHLCV-WS: Cancelled grace-period unsubscribe', {\n channel,\n });\n\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n entry.refCount += 1;\n log('OHLCV-WS: WS subscription still alive, bumped refCount', {\n channel,\n refCount: entry.refCount,\n });\n return;\n }\n // WS subscription was lost (e.g. after disconnect/reconnect) — fall\n // through to recreate it. refCount is bumped only after success below.\n } else if (entry && entry.refCount > 0) {\n entry.refCount += 1;\n return;\n }\n try {\n await this.#messenger.call('BackendWebSocketService:connect');\n\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n log(\n 'OHLCV-WS: Channel already has WS subscription (idempotency), skipping',\n {\n channel,\n },\n );\n this.#channels.set(channel, { refCount: 1 });\n return;\n }\n\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: SUBSCRIPTION_NAMESPACE,\n callback: (notification: ServerNotificationMessage) => {\n this.#handleBarUpdate(channel, notification);\n },\n });\n\n this.#channels.set(channel, { refCount: 1 });\n log('OHLCV-WS: Subscribe succeeded — new WS subscription created', {\n channel,\n });\n } catch (error) {\n log('OHLCV-WS: Subscription failed', { channel, error });\n this.#channels.delete(channel);\n this.#messenger.publish('OHLCVService:subscriptionError', {\n channel,\n error: String(error),\n operation: 'subscribe',\n });\n }\n }\n\n /**\n * Unsubscribe from an OHLCV channel. Decrements the reference count and,\n * when it reaches zero, starts a grace-period timer before actually\n * unsubscribing from the WebSocket to absorb rapid navigation patterns.\n *\n * @param options - The subscription parameters to unsubscribe from.\n * @returns A promise that resolves once the unsubscription is processed.\n */\n async unsubscribe(options: OHLCVSubscriptionOptions): Promise<void> {\n const channel = this.#buildChannel(options);\n const releaseLock = await this.#mutex.acquire();\n try {\n await this.#unsubscribeInner(channel);\n } finally {\n releaseLock();\n }\n }\n\n async #unsubscribeInner(channel: string): Promise<void> {\n const entry = this.#channels.get(channel);\n\n if (!entry || entry.refCount <= 0) {\n return;\n }\n\n entry.refCount -= 1;\n\n if (entry.refCount > 0) {\n return;\n }\n\n entry.gracePeriodTimer = setTimeout(() => {\n entry.gracePeriodTimer = undefined;\n this.#performUnsubscribe(channel).catch(() => {\n // no-op\n });\n }, GRACE_PERIOD_MS);\n }\n\n // =============================================================================\n // Private — WebSocket Subscription Helpers\n // =============================================================================\n\n async #performUnsubscribe(channel: string): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n try {\n const entry = this.#channels.get(channel);\n if (entry && entry.refCount > 0) {\n log(\n 'OHLCV-WS: Skipping unsubscribe — new subscriber arrived while queued',\n { channel, refCount: entry.refCount },\n );\n return;\n }\n\n log('OHLCV-WS: Grace period expired — performing actual WS unsubscribe', {\n channel,\n });\n this.#channels.delete(channel);\n\n try {\n const subscriptions = this.#messenger.call(\n 'BackendWebSocketService:getSubscriptionsByChannel',\n channel,\n );\n\n for (const sub of subscriptions) {\n await sub.unsubscribe();\n }\n log('OHLCV-WS: WS unsubscribe completed', { channel });\n } catch (error) {\n log('OHLCV-WS: Unsubscription failed', { channel, error });\n this.#messenger.publish('OHLCVService:subscriptionError', {\n channel,\n error: String(error),\n operation: 'unsubscribe',\n });\n }\n } finally {\n releaseLock();\n }\n }\n\n /**\n * Resubscribe all channels that were active before a disconnect.\n * Called when WebSocket transitions to CONNECTED.\n */\n async #resubscribeActiveChannels(): Promise<void> {\n const releaseLock = await this.#mutex.acquire();\n try {\n const channelCount = this.#channels.size;\n log('OHLCV-WS: Resubscribing active channels after reconnect', {\n count: channelCount,\n });\n\n for (const [channel, entry] of this.#channels.entries()) {\n if (entry.refCount === 0) {\n continue;\n }\n\n try {\n if (\n this.#messenger.call(\n 'BackendWebSocketService:channelHasSubscription',\n channel,\n )\n ) {\n log(\n 'OHLCV-WS: Channel already subscribed on server, skipping resubscribe',\n {\n channel,\n },\n );\n continue;\n }\n\n await this.#messenger.call('BackendWebSocketService:subscribe', {\n channels: [channel],\n channelType: SUBSCRIPTION_NAMESPACE,\n callback: (notification: ServerNotificationMessage) => {\n this.#handleBarUpdate(channel, notification);\n },\n });\n log('OHLCV-WS: Resubscription succeeded', { channel });\n } catch (error) {\n log('OHLCV-WS: Resubscription failed for channel', {\n channel,\n error,\n });\n }\n }\n } finally {\n releaseLock();\n }\n }\n\n // =============================================================================\n // Private — Message Handlers\n // =============================================================================\n\n #handleBarUpdate(\n channel: string,\n notification: ServerNotificationMessage,\n ): void {\n const bar = notification.data as OHLCVBar;\n\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n this.#trace(\n {\n name: `${SERVICE_NAME} Bar Update`,\n data: { channel, timestamp: bar.timestamp },\n tags: { service: SERVICE_NAME },\n },\n () => {\n this.#messenger.publish('OHLCVService:barUpdated', { channel, bar });\n },\n );\n }\n\n #handleSystemNotification(notification: ServerNotificationMessage): void {\n const data = notification.data as OHLCVSystemNotificationData;\n const { timestamp } = notification;\n\n if (!data.chainIds || !Array.isArray(data.chainIds) || !data.status) {\n throw new Error(\n 'Invalid system notification data: missing chainIds or status',\n );\n }\n\n if (data.status === 'up') {\n for (const chainId of data.chainIds) {\n this.#chainsUp.add(chainId);\n }\n } else {\n for (const chainId of data.chainIds) {\n this.#chainsUp.delete(chainId);\n }\n }\n\n this.#messenger.publish('OHLCVService:chainStatusChanged', {\n chainIds: data.chainIds,\n status: data.status,\n timestamp,\n });\n\n log(`OHLCV-WS: Chain status change: ${data.status}`, {\n chains: data.chainIds,\n status: data.status,\n });\n }\n\n async #handleWebSocketStateChange(\n connectionInfo: WebSocketConnectionInfo,\n ): Promise<void> {\n const { state } = connectionInfo;\n\n if (state === WebSocketState.CONNECTED) {\n await this.#resubscribeActiveChannels();\n } else if (state === WebSocketState.DISCONNECTED) {\n const chainsToMarkDown = Array.from(this.#chainsUp);\n\n if (chainsToMarkDown.length > 0) {\n this.#messenger.publish('OHLCVService:chainStatusChanged', {\n chainIds: chainsToMarkDown,\n status: 'down',\n timestamp: Date.now(),\n });\n\n log(\n 'OHLCV-WS: WebSocket disconnection — marked tracked chains as down',\n {\n count: chainsToMarkDown.length,\n chains: chainsToMarkDown,\n },\n );\n\n this.#chainsUp.clear();\n }\n }\n }\n\n // =============================================================================\n // Private — Utility\n // =============================================================================\n\n #buildChannel(options: OHLCVSubscriptionOptions): string {\n return `${SUBSCRIPTION_NAMESPACE}.${options.assetId}.${options.interval}.${options.currency}`;\n }\n\n // =============================================================================\n // Public — Cleanup\n // =============================================================================\n\n /**\n * Destroy the service and clean up all resources.\n */\n destroy(): void {\n for (const entry of this.#channels.values()) {\n if (entry.gracePeriodTimer) {\n clearTimeout(entry.gracePeriodTimer);\n }\n }\n this.#channels.clear();\n this.#chainsUp.clear();\n\n this.#messenger.call(\n 'BackendWebSocketService:removeChannelCallback',\n SYSTEM_NOTIFICATIONS_CHANNEL,\n );\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/core-backend",
3
- "version": "8.1.0-preview-5a03e1b92",
3
+ "version": "8.1.0-preview-943313fc9",
4
4
  "description": "Core backend services for MetaMask",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -64,7 +64,6 @@
64
64
  "@metamask/utils": "^11.11.0",
65
65
  "@tanstack/query-core": "^5.62.16",
66
66
  "async-mutex": "^0.5.0",
67
- "cockatiel": "^3.1.2",
68
67
  "uuid": "^8.3.2"
69
68
  },
70
69
  "devDependencies": {