@metamask-previews/core-backend 12.3.0-preview-4a8d4a2 → 12.3.0-preview-0f1d8eed9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/dist/index.d.ts +4 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +8 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/ws/ChannelSubscriptionManager.d.ts +92 -0
  7. package/dist/ws/ChannelSubscriptionManager.d.ts.map +1 -0
  8. package/dist/ws/ChannelSubscriptionManager.js +390 -0
  9. package/dist/ws/ChannelSubscriptionManager.js.map +1 -0
  10. package/dist/ws/launchpad/LaunchpadTokenDetailsService-method-action-types.d.ts +35 -0
  11. package/dist/ws/launchpad/LaunchpadTokenDetailsService-method-action-types.d.ts.map +1 -0
  12. package/dist/ws/launchpad/LaunchpadTokenDetailsService-method-action-types.js +6 -0
  13. package/dist/ws/launchpad/LaunchpadTokenDetailsService-method-action-types.js.map +1 -0
  14. package/dist/ws/launchpad/LaunchpadTokenDetailsService.d.ts +115 -0
  15. package/dist/ws/launchpad/LaunchpadTokenDetailsService.d.ts.map +1 -0
  16. package/dist/ws/launchpad/LaunchpadTokenDetailsService.js +193 -0
  17. package/dist/ws/launchpad/LaunchpadTokenDetailsService.js.map +1 -0
  18. package/dist/ws/launchpad/index.d.ts +6 -0
  19. package/dist/ws/launchpad/index.d.ts.map +1 -0
  20. package/dist/ws/launchpad/index.js +4 -0
  21. package/dist/ws/launchpad/index.js.map +1 -0
  22. package/dist/ws/launchpad/normalize.d.ts +37 -0
  23. package/dist/ws/launchpad/normalize.d.ts.map +1 -0
  24. package/dist/ws/launchpad/normalize.js +125 -0
  25. package/dist/ws/launchpad/normalize.js.map +1 -0
  26. package/dist/ws/launchpad/types.d.ts +132 -0
  27. package/dist/ws/launchpad/types.d.ts.map +1 -0
  28. package/dist/ws/launchpad/types.js +11 -0
  29. package/dist/ws/launchpad/types.js.map +1 -0
  30. package/dist/ws/ohlcv/OHLCVService.d.ts +5 -2
  31. package/dist/ws/ohlcv/OHLCVService.d.ts.map +1 -1
  32. package/dist/ws/ohlcv/OHLCVService.js +40 -319
  33. package/dist/ws/ohlcv/OHLCVService.js.map +1 -1
  34. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -9,10 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Added
11
11
 
12
+ - Add `LaunchpadTokenDetailsService` for real-time launchpad token detail streaming over the shared backend WebSocket, exposing `subscribe`/`unsubscribe` by `assetId` and publishing `tokenUpdated`, `statusChanged`, and `subscriptionError` events, along with `LAUNCHPAD_TDP_SERVICE_ALLOWED_ACTIONS`/`LAUNCHPAD_TDP_SERVICE_ALLOWED_EVENTS` and launchpad event types/normalizers (`parseBatch`, `normalizeEvent`, `buildChannel`) ([#10884](https://github.com/MetaMask/core/pull/10884))
13
+ - Add `ChannelSubscriptionManager`, a reusable per-channel WebSocket subscription lifecycle helper (reference counting, grace-period unsubscribe, unsubscribe retry, and resubscribe on reconnect) shared by `OHLCVService` and `LaunchpadTokenDetailsService` ([#10884](https://github.com/MetaMask/core/pull/10884))
12
14
  - Add support for Node.js 26 ([#10981](https://github.com/MetaMask/core/pull/10981))
13
15
 
14
16
  ### Changed
15
17
 
18
+ - Refactor `OHLCVService` to delegate its subscription lifecycle to the shared `ChannelSubscriptionManager` via composition, with no change to its public API or published events ([#10884](https://github.com/MetaMask/core/pull/10884))
16
19
  - Stop publishing the `api/test-utils` test helper, which was never reachable through the package's `exports` field ([#10916](https://github.com/MetaMask/core/pull/10916))
17
20
  - Bump `@metamask/account-tree-controller` from `^11.0.1` to `^11.2.0` ([#10891](https://github.com/MetaMask/core/pull/10891), [#10925](https://github.com/MetaMask/core/pull/10925), [#11092](https://github.com/MetaMask/core/pull/11092))
18
21
  - Bump `@metamask/profile-sync-controller` from `^34.0.3` to `^36.0.0` ([#10905](https://github.com/MetaMask/core/pull/10905), [#10925](https://github.com/MetaMask/core/pull/10925), [#11092](https://github.com/MetaMask/core/pull/11092))
package/dist/index.d.ts CHANGED
@@ -7,6 +7,10 @@ export { ApiPlatformClientService, apiPlatformClientServiceName, } from './ApiPl
7
7
  export type { ApiPlatformClientServiceOptions, ApiPlatformClientServiceActions, ApiPlatformClientServiceEvents, ApiPlatformClientServiceMessenger, } from './ApiPlatformClientService.js';
8
8
  export { OHLCVService, OHLCV_SERVICE_ALLOWED_ACTIONS, OHLCV_SERVICE_ALLOWED_EVENTS, } from './ws/ohlcv/index.js';
9
9
  export type { OHLCVBar, OHLCVSubscriptionOptions, OHLCVSystemNotificationData, OHLCVServiceOptions, OHLCVServiceActions, OHLCVServiceAllowedActions, OHLCVServiceBarUpdatedEvent, OHLCVServiceChainStatusChangedEvent, OHLCVServiceSubscriptionErrorEvent, OHLCVServiceEvents, OHLCVServiceAllowedEvents, OHLCVServiceMessenger, } from './ws/ohlcv/index.js';
10
+ export { ChannelSubscriptionManager } from './ws/ChannelSubscriptionManager.js';
11
+ export type { ChannelSubscriptionManagerMessenger, ChannelSubscriptionManagerOptions, ChannelSubscriptionOperation, } from './ws/ChannelSubscriptionManager.js';
12
+ export { LaunchpadTokenDetailsService, LAUNCHPAD_TDP_SERVICE_ALLOWED_ACTIONS, LAUNCHPAD_TDP_SERVICE_ALLOWED_EVENTS, buildChannel, normalizeEvent, parseBatch, LAUNCHPAD_SUBSCRIPTION_NAMESPACE, } from './ws/launchpad/index.js';
13
+ export type { LaunchpadSystemNotificationData, LaunchpadTokenDetailsServiceOptions, LaunchpadTokenDetailsServiceActions, LaunchpadTokenDetailsServiceAllowedActions, LaunchpadTokenDetailsServiceTokenUpdatedEvent, LaunchpadTokenDetailsServiceStatusChangedEvent, LaunchpadTokenDetailsServiceSubscriptionErrorEvent, LaunchpadTokenDetailsServiceEvents, LaunchpadTokenDetailsServiceAllowedEvents, LaunchpadTokenDetailsServiceMessenger, LaunchpadEventType, NumericLike, RawLaunchpadTokenEvent, LaunchpadTokenCreatedEvent, LaunchpadTokenStatsEvent, LaunchpadTokenMigratedEvent, LaunchpadTokenEvent, LaunchpadTokenDetailsSubscriptionOptions, } from './ws/launchpad/index.js';
10
14
  export { ApiPlatformClient, createApiPlatformClient, AccountsApiClient, PricesApiClient, TokenApiClient, TokensApiClient, API_URLS, STALE_TIMES, GC_TIMES, V6_DEFI_POSITION_TYPES, calculateRetryDelay, getQueryOptionsOverrides, shouldRetry, HttpError, } from './api/index.js';
11
15
  export type { ApiPlatformClientOptions, ApiUrls, FetchOptions, PageInfo, SupportedCurrency, MarketDataDetails, V5BalanceItem, V5BalancesResponse, V2BalanceItem, V2BalancesResponse, V4BalancesResponse, V6VsCurrency, V6DeFiPositionType, V6BalanceMetadata, V6StellarTokenBalanceMetadata, V6TokenBalanceMetadata, V6BalanceItem, V6BalancesResponse, V1SupportedNetworksResponse, V2SupportedNetworksResponse, V2ActiveNetworksResponse, V1TransactionByHashResponse, V1AccountTransactionsResponse, V4MultiAccountTransactionsResponse, ValueTransfer, V1AccountRelationshipResult, NftItem, V2NftsResponse, TokenDiscoveryItem, V2TokensResponse, V3SpotPricesResponse, CoinGeckoSpotPrice, ExchangeRateInfo, V1ExchangeRatesResponse, PriceSupportedNetworksResponse, PriceV1SupportedNetworksResponse, PriceV2SupportedNetworksResponse, V1HistoricalPricesResponse, V3HistoricalPricesResponse, TokenMetadata, TokenListQueryOptions, TokenV2SupportedNetworksResponse, V1TokenDescriptionResponse, NetworkInfo, TopAsset, TrendingSortBy, TrendingToken, TopGainersSortOption, TrendingSortOption, V1SuggestedOccurrenceFloorsResponse, V1TokenSupportedNetworksResponse, V2TokenSupportedNetworksResponse, V3AssetResponse, } from './api/index.js';
12
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,cAAc,EACd,kBAAkB,GACnB,MAAM,iCAAiC,CAAC;AAEzC,YAAY,EACV,8BAA8B,EAC9B,oBAAoB,EACpB,qBAAqB,EACrB,yBAAyB,EACzB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,uBAAuB,EACvB,8BAA8B,EAC9B,kDAAkD,EAClD,6BAA6B,EAC7B,gCAAgC,GACjC,MAAM,iCAAiC,CAAC;AAMzC,OAAO,EACL,sBAAsB,EACtB,wCAAwC,EACxC,uCAAuC,GACxC,MAAM,gCAAgC,CAAC;AAExC,YAAY,EACV,sBAAsB,EACtB,mBAAmB,EACnB,6BAA6B,EAC7B,6BAA6B,EAC7B,cAAc,IAAI,oCAAoC,EACtD,6CAA6C,EAC7C,yCAAyC,EACzC,4CAA4C,EAC5C,wCAAwC,EACxC,4BAA4B,EAC5B,aAAa,IAAI,mCAAmC,EACpD,+BAA+B,GAChC,MAAM,gCAAgC,CAAC;AAMxC,YAAY,EACV,WAAW,EACX,KAAK,EACL,OAAO,EACP,QAAQ,EACR,aAAa,EACb,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAMpB,OAAO,EACL,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,+BAA+B,CAAC;AAEvC,YAAY,EACV,+BAA+B,EAC/B,+BAA+B,EAC/B,8BAA8B,EAC9B,iCAAiC,GAClC,MAAM,+BAA+B,CAAC;AAMvC,OAAO,EACL,YAAY,EACZ,6BAA6B,EAC7B,4BAA4B,GAC7B,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACV,QAAQ,EACR,wBAAwB,EACxB,2BAA2B,EAC3B,mBAAmB,EACnB,mBAAmB,EACnB,0BAA0B,EAC1B,2BAA2B,EAC3B,mCAAmC,EACnC,kCAAkC,EAClC,kBAAkB,EAClB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAM7B,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EAEvB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,eAAe,EAEf,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,sBAAsB,EAEtB,mBAAmB,EACnB,wBAAwB,EACxB,WAAW,EAEX,SAAS,GACV,MAAM,gBAAgB,CAAC;AAMxB,YAAY,EAEV,wBAAwB,EACxB,OAAO,EACP,YAAY,EAEZ,QAAQ,EACR,iBAAiB,EACjB,iBAAiB,EAEjB,aAAa,EACb,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,kBAAkB,EAClB,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,6BAA6B,EAC7B,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAClB,2BAA2B,EAC3B,2BAA2B,EAC3B,wBAAwB,EACxB,2BAA2B,EAC3B,6BAA6B,EAC7B,kCAAkC,EAClC,aAAa,EACb,2BAA2B,EAC3B,OAAO,EACP,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAEhB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,uBAAuB,EACvB,8BAA8B,EAC9B,gCAAgC,EAChC,gCAAgC,EAChC,0BAA0B,EAC1B,0BAA0B,EAE1B,aAAa,EACb,qBAAqB,EACrB,gCAAgC,EAChC,0BAA0B,EAC1B,WAAW,EACX,QAAQ,EACR,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,mCAAmC,EAEnC,gCAAgC,EAChC,gCAAgC,EAChC,eAAe,GAChB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -16,6 +16,14 @@ export { ApiPlatformClientService, apiPlatformClientServiceName, } from './ApiPl
16
16
  // ============================================================================
17
17
  export { OHLCVService, OHLCV_SERVICE_ALLOWED_ACTIONS, OHLCV_SERVICE_ALLOWED_EVENTS, } from './ws/ohlcv/index.js';
18
18
  // ============================================================================
19
+ // CHANNEL SUBSCRIPTION MANAGER
20
+ // ============================================================================
21
+ export { ChannelSubscriptionManager } from './ws/ChannelSubscriptionManager.js';
22
+ // ============================================================================
23
+ // LAUNCHPAD TOKEN DETAILS SERVICE
24
+ // ============================================================================
25
+ export { LaunchpadTokenDetailsService, LAUNCHPAD_TDP_SERVICE_ALLOWED_ACTIONS, LAUNCHPAD_TDP_SERVICE_ALLOWED_EVENTS, buildChannel, normalizeEvent, parseBatch, LAUNCHPAD_SUBSCRIPTION_NAMESPACE, } from './ws/launchpad/index.js';
26
+ // ============================================================================
19
27
  // API PLATFORM CLIENT
20
28
  // ============================================================================
21
29
  export { ApiPlatformClient, createApiPlatformClient,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAE/B,+EAA+E;AAC/E,4BAA4B;AAC5B,+EAA+E;AAE/E,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,cAAc,EACd,kBAAkB,GACnB,MAAM,iCAAiC,CAAC;AAiBzC,+EAA+E;AAC/E,2BAA2B;AAC3B,+EAA+E;AAE/E,OAAO,EACL,sBAAsB,EACtB,wCAAwC,EACxC,uCAAuC,GACxC,MAAM,gCAAgC,CAAC;AA8BxC,+EAA+E;AAC/E,8BAA8B;AAC9B,+EAA+E;AAE/E,OAAO,EACL,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,+BAA+B,CAAC;AASvC,+EAA+E;AAC/E,gBAAgB;AAChB,+EAA+E;AAE/E,OAAO,EACL,YAAY,EACZ,6BAA6B,EAC7B,4BAA4B,GAC7B,MAAM,qBAAqB,CAAC;AAiB7B,+EAA+E;AAC/E,sBAAsB;AACtB,+EAA+E;AAE/E,OAAO,EACL,iBAAiB,EACjB,uBAAuB;AACvB,yBAAyB;AACzB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,eAAe;AACf,YAAY;AACZ,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,sBAAsB;AACtB,UAAU;AACV,mBAAmB,EACnB,wBAAwB,EACxB,WAAW;AACX,SAAS;AACT,SAAS,GACV,MAAM,gBAAgB,CAAC","sourcesContent":["// Core Backend Package Exports\n\n// ============================================================================\n// BACKEND WEBSOCKET SERVICE\n// ============================================================================\n\nexport {\n BackendWebSocketService,\n getCloseReason,\n WebSocketState,\n WebSocketEventType,\n} from './ws/BackendWebSocketService.js';\n\nexport type {\n BackendWebSocketServiceOptions,\n ClientRequestMessage,\n ServerResponseMessage,\n ServerNotificationMessage,\n WebSocketMessage,\n ChannelCallback,\n WebSocketSubscription,\n WebSocketConnectionInfo,\n BackendWebSocketServiceActions,\n BackendWebSocketServiceConnectionStateChangedEvent,\n BackendWebSocketServiceEvents,\n BackendWebSocketServiceMessenger,\n} from './ws/BackendWebSocketService.js';\n\n// ============================================================================\n// ACCOUNT ACTIVITY SERVICE\n// ============================================================================\n\nexport {\n AccountActivityService,\n ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS,\n ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS,\n} from './ws/AccountActivityService.js';\n\nexport type {\n SystemNotificationData,\n SubscriptionOptions,\n AccountActivityServiceOptions,\n AccountActivityServiceActions,\n AllowedActions as AccountActivityServiceAllowedActions,\n AccountActivityServiceTransactionUpdatedEvent,\n AccountActivityServiceBalanceUpdatedEvent,\n AccountActivityServiceSubscriptionErrorEvent,\n AccountActivityServiceStatusChangedEvent,\n AccountActivityServiceEvents,\n AllowedEvents as AccountActivityServiceAllowedEvents,\n AccountActivityServiceMessenger,\n} from './ws/AccountActivityService.js';\n\n// ============================================================================\n// SHARED TYPES\n// ============================================================================\n\nexport type {\n Transaction,\n Asset,\n Balance,\n Transfer,\n BalanceUpdate,\n AccountActivityMessage,\n} from './types.js';\n\n// ============================================================================\n// API PLATFORM CLIENT SERVICE\n// ============================================================================\n\nexport {\n ApiPlatformClientService,\n apiPlatformClientServiceName,\n} from './ApiPlatformClientService.js';\n\nexport type {\n ApiPlatformClientServiceOptions,\n ApiPlatformClientServiceActions,\n ApiPlatformClientServiceEvents,\n ApiPlatformClientServiceMessenger,\n} from './ApiPlatformClientService.js';\n\n// ============================================================================\n// OHLCV SERVICE\n// ============================================================================\n\nexport {\n OHLCVService,\n OHLCV_SERVICE_ALLOWED_ACTIONS,\n OHLCV_SERVICE_ALLOWED_EVENTS,\n} from './ws/ohlcv/index.js';\n\nexport type {\n OHLCVBar,\n OHLCVSubscriptionOptions,\n OHLCVSystemNotificationData,\n OHLCVServiceOptions,\n OHLCVServiceActions,\n OHLCVServiceAllowedActions,\n OHLCVServiceBarUpdatedEvent,\n OHLCVServiceChainStatusChangedEvent,\n OHLCVServiceSubscriptionErrorEvent,\n OHLCVServiceEvents,\n OHLCVServiceAllowedEvents,\n OHLCVServiceMessenger,\n} from './ws/ohlcv/index.js';\n\n// ============================================================================\n// API PLATFORM CLIENT\n// ============================================================================\n\nexport {\n ApiPlatformClient,\n createApiPlatformClient,\n // Individual API clients\n AccountsApiClient,\n PricesApiClient,\n TokenApiClient,\n TokensApiClient,\n // Constants\n API_URLS,\n STALE_TIMES,\n GC_TIMES,\n V6_DEFI_POSITION_TYPES,\n // Helpers\n calculateRetryDelay,\n getQueryOptionsOverrides,\n shouldRetry,\n // Errors\n HttpError,\n} from './api/index.js';\n\n// ============================================================================\n// API PLATFORM CLIENT TYPES\n// ============================================================================\n\nexport type {\n // Client options\n ApiPlatformClientOptions,\n ApiUrls,\n FetchOptions,\n // Shared types\n PageInfo,\n SupportedCurrency,\n MarketDataDetails,\n // Accounts API types\n V5BalanceItem,\n V5BalancesResponse,\n V2BalanceItem,\n V2BalancesResponse,\n V4BalancesResponse,\n V6VsCurrency,\n V6DeFiPositionType,\n V6BalanceMetadata,\n V6StellarTokenBalanceMetadata,\n V6TokenBalanceMetadata,\n V6BalanceItem,\n V6BalancesResponse,\n V1SupportedNetworksResponse,\n V2SupportedNetworksResponse,\n V2ActiveNetworksResponse,\n V1TransactionByHashResponse,\n V1AccountTransactionsResponse,\n V4MultiAccountTransactionsResponse,\n ValueTransfer,\n V1AccountRelationshipResult,\n NftItem,\n V2NftsResponse,\n TokenDiscoveryItem,\n V2TokensResponse,\n // Prices API types\n V3SpotPricesResponse,\n CoinGeckoSpotPrice,\n ExchangeRateInfo,\n V1ExchangeRatesResponse,\n PriceSupportedNetworksResponse,\n PriceV1SupportedNetworksResponse,\n PriceV2SupportedNetworksResponse,\n V1HistoricalPricesResponse,\n V3HistoricalPricesResponse,\n // Token API types\n TokenMetadata,\n TokenListQueryOptions,\n TokenV2SupportedNetworksResponse,\n V1TokenDescriptionResponse,\n NetworkInfo,\n TopAsset,\n TrendingSortBy,\n TrendingToken,\n TopGainersSortOption,\n TrendingSortOption,\n V1SuggestedOccurrenceFloorsResponse,\n // Tokens API types\n V1TokenSupportedNetworksResponse,\n V2TokenSupportedNetworksResponse,\n V3AssetResponse,\n} from './api/index.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAE/B,+EAA+E;AAC/E,4BAA4B;AAC5B,+EAA+E;AAE/E,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,cAAc,EACd,kBAAkB,GACnB,MAAM,iCAAiC,CAAC;AAiBzC,+EAA+E;AAC/E,2BAA2B;AAC3B,+EAA+E;AAE/E,OAAO,EACL,sBAAsB,EACtB,wCAAwC,EACxC,uCAAuC,GACxC,MAAM,gCAAgC,CAAC;AA8BxC,+EAA+E;AAC/E,8BAA8B;AAC9B,+EAA+E;AAE/E,OAAO,EACL,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,+BAA+B,CAAC;AASvC,+EAA+E;AAC/E,gBAAgB;AAChB,+EAA+E;AAE/E,OAAO,EACL,YAAY,EACZ,6BAA6B,EAC7B,4BAA4B,GAC7B,MAAM,qBAAqB,CAAC;AAiB7B,+EAA+E;AAC/E,+BAA+B;AAC/B,+EAA+E;AAE/E,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AAQhF,+EAA+E;AAC/E,kCAAkC;AAClC,+EAA+E;AAE/E,OAAO,EACL,4BAA4B,EAC5B,qCAAqC,EACrC,oCAAoC,EACpC,YAAY,EACZ,cAAc,EACd,UAAU,EACV,gCAAgC,GACjC,MAAM,yBAAyB,CAAC;AAuBjC,+EAA+E;AAC/E,sBAAsB;AACtB,+EAA+E;AAE/E,OAAO,EACL,iBAAiB,EACjB,uBAAuB;AACvB,yBAAyB;AACzB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,eAAe;AACf,YAAY;AACZ,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,sBAAsB;AACtB,UAAU;AACV,mBAAmB,EACnB,wBAAwB,EACxB,WAAW;AACX,SAAS;AACT,SAAS,GACV,MAAM,gBAAgB,CAAC","sourcesContent":["// Core Backend Package Exports\n\n// ============================================================================\n// BACKEND WEBSOCKET SERVICE\n// ============================================================================\n\nexport {\n BackendWebSocketService,\n getCloseReason,\n WebSocketState,\n WebSocketEventType,\n} from './ws/BackendWebSocketService.js';\n\nexport type {\n BackendWebSocketServiceOptions,\n ClientRequestMessage,\n ServerResponseMessage,\n ServerNotificationMessage,\n WebSocketMessage,\n ChannelCallback,\n WebSocketSubscription,\n WebSocketConnectionInfo,\n BackendWebSocketServiceActions,\n BackendWebSocketServiceConnectionStateChangedEvent,\n BackendWebSocketServiceEvents,\n BackendWebSocketServiceMessenger,\n} from './ws/BackendWebSocketService.js';\n\n// ============================================================================\n// ACCOUNT ACTIVITY SERVICE\n// ============================================================================\n\nexport {\n AccountActivityService,\n ACCOUNT_ACTIVITY_SERVICE_ALLOWED_ACTIONS,\n ACCOUNT_ACTIVITY_SERVICE_ALLOWED_EVENTS,\n} from './ws/AccountActivityService.js';\n\nexport type {\n SystemNotificationData,\n SubscriptionOptions,\n AccountActivityServiceOptions,\n AccountActivityServiceActions,\n AllowedActions as AccountActivityServiceAllowedActions,\n AccountActivityServiceTransactionUpdatedEvent,\n AccountActivityServiceBalanceUpdatedEvent,\n AccountActivityServiceSubscriptionErrorEvent,\n AccountActivityServiceStatusChangedEvent,\n AccountActivityServiceEvents,\n AllowedEvents as AccountActivityServiceAllowedEvents,\n AccountActivityServiceMessenger,\n} from './ws/AccountActivityService.js';\n\n// ============================================================================\n// SHARED TYPES\n// ============================================================================\n\nexport type {\n Transaction,\n Asset,\n Balance,\n Transfer,\n BalanceUpdate,\n AccountActivityMessage,\n} from './types.js';\n\n// ============================================================================\n// API PLATFORM CLIENT SERVICE\n// ============================================================================\n\nexport {\n ApiPlatformClientService,\n apiPlatformClientServiceName,\n} from './ApiPlatformClientService.js';\n\nexport type {\n ApiPlatformClientServiceOptions,\n ApiPlatformClientServiceActions,\n ApiPlatformClientServiceEvents,\n ApiPlatformClientServiceMessenger,\n} from './ApiPlatformClientService.js';\n\n// ============================================================================\n// OHLCV SERVICE\n// ============================================================================\n\nexport {\n OHLCVService,\n OHLCV_SERVICE_ALLOWED_ACTIONS,\n OHLCV_SERVICE_ALLOWED_EVENTS,\n} from './ws/ohlcv/index.js';\n\nexport type {\n OHLCVBar,\n OHLCVSubscriptionOptions,\n OHLCVSystemNotificationData,\n OHLCVServiceOptions,\n OHLCVServiceActions,\n OHLCVServiceAllowedActions,\n OHLCVServiceBarUpdatedEvent,\n OHLCVServiceChainStatusChangedEvent,\n OHLCVServiceSubscriptionErrorEvent,\n OHLCVServiceEvents,\n OHLCVServiceAllowedEvents,\n OHLCVServiceMessenger,\n} from './ws/ohlcv/index.js';\n\n// ============================================================================\n// CHANNEL SUBSCRIPTION MANAGER\n// ============================================================================\n\nexport { ChannelSubscriptionManager } from './ws/ChannelSubscriptionManager.js';\n\nexport type {\n ChannelSubscriptionManagerMessenger,\n ChannelSubscriptionManagerOptions,\n ChannelSubscriptionOperation,\n} from './ws/ChannelSubscriptionManager.js';\n\n// ============================================================================\n// LAUNCHPAD TOKEN DETAILS SERVICE\n// ============================================================================\n\nexport {\n LaunchpadTokenDetailsService,\n LAUNCHPAD_TDP_SERVICE_ALLOWED_ACTIONS,\n LAUNCHPAD_TDP_SERVICE_ALLOWED_EVENTS,\n buildChannel,\n normalizeEvent,\n parseBatch,\n LAUNCHPAD_SUBSCRIPTION_NAMESPACE,\n} from './ws/launchpad/index.js';\n\nexport type {\n LaunchpadSystemNotificationData,\n LaunchpadTokenDetailsServiceOptions,\n LaunchpadTokenDetailsServiceActions,\n LaunchpadTokenDetailsServiceAllowedActions,\n LaunchpadTokenDetailsServiceTokenUpdatedEvent,\n LaunchpadTokenDetailsServiceStatusChangedEvent,\n LaunchpadTokenDetailsServiceSubscriptionErrorEvent,\n LaunchpadTokenDetailsServiceEvents,\n LaunchpadTokenDetailsServiceAllowedEvents,\n LaunchpadTokenDetailsServiceMessenger,\n LaunchpadEventType,\n NumericLike,\n RawLaunchpadTokenEvent,\n LaunchpadTokenCreatedEvent,\n LaunchpadTokenStatsEvent,\n LaunchpadTokenMigratedEvent,\n LaunchpadTokenEvent,\n LaunchpadTokenDetailsSubscriptionOptions,\n} from './ws/launchpad/index.js';\n\n// ============================================================================\n// API PLATFORM CLIENT\n// ============================================================================\n\nexport {\n ApiPlatformClient,\n createApiPlatformClient,\n // Individual API clients\n AccountsApiClient,\n PricesApiClient,\n TokenApiClient,\n TokensApiClient,\n // Constants\n API_URLS,\n STALE_TIMES,\n GC_TIMES,\n V6_DEFI_POSITION_TYPES,\n // Helpers\n calculateRetryDelay,\n getQueryOptionsOverrides,\n shouldRetry,\n // Errors\n HttpError,\n} from './api/index.js';\n\n// ============================================================================\n// API PLATFORM CLIENT TYPES\n// ============================================================================\n\nexport type {\n // Client options\n ApiPlatformClientOptions,\n ApiUrls,\n FetchOptions,\n // Shared types\n PageInfo,\n SupportedCurrency,\n MarketDataDetails,\n // Accounts API types\n V5BalanceItem,\n V5BalancesResponse,\n V2BalanceItem,\n V2BalancesResponse,\n V4BalancesResponse,\n V6VsCurrency,\n V6DeFiPositionType,\n V6BalanceMetadata,\n V6StellarTokenBalanceMetadata,\n V6TokenBalanceMetadata,\n V6BalanceItem,\n V6BalancesResponse,\n V1SupportedNetworksResponse,\n V2SupportedNetworksResponse,\n V2ActiveNetworksResponse,\n V1TransactionByHashResponse,\n V1AccountTransactionsResponse,\n V4MultiAccountTransactionsResponse,\n ValueTransfer,\n V1AccountRelationshipResult,\n NftItem,\n V2NftsResponse,\n TokenDiscoveryItem,\n V2TokensResponse,\n // Prices API types\n V3SpotPricesResponse,\n CoinGeckoSpotPrice,\n ExchangeRateInfo,\n V1ExchangeRatesResponse,\n PriceSupportedNetworksResponse,\n PriceV1SupportedNetworksResponse,\n PriceV2SupportedNetworksResponse,\n V1HistoricalPricesResponse,\n V3HistoricalPricesResponse,\n // Token API types\n TokenMetadata,\n TokenListQueryOptions,\n TokenV2SupportedNetworksResponse,\n V1TokenDescriptionResponse,\n NetworkInfo,\n TopAsset,\n TrendingSortBy,\n TrendingToken,\n TopGainersSortOption,\n TrendingSortOption,\n V1SuggestedOccurrenceFloorsResponse,\n // Tokens API types\n V1TokenSupportedNetworksResponse,\n V2TokenSupportedNetworksResponse,\n V3AssetResponse,\n} from './api/index.js';\n"]}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Shared per-channel WebSocket subscription lifecycle manager.
3
+ *
4
+ * Extracts the channel-agnostic subscription machinery that was originally part
5
+ * of {@link OHLCVService}: reference counting, grace-period unsubscribe,
6
+ * grace-period flush, unsubscribe retry with backoff + force reconnection,
7
+ * idempotency checks, resubscribe-on-reconnect, and optional system-notification
8
+ * channel registration.
9
+ *
10
+ * It is consumed by **composition** (not inheritance). Each domain service
11
+ * (e.g. OHLCV market data, Launchpad token details) constructs a manager,
12
+ * provides the channel-type namespace and a per-message callback, and keeps its
13
+ * own domain concerns (channel-string construction, payload parsing, published
14
+ * events). The manager talks to {@link BackendWebSocketService} exclusively
15
+ * through the messenger — no direct import of the class.
16
+ */
17
+ import type { Messenger } from '@metamask/messenger';
18
+ import type { BackendWebSocketServiceMethodActions } from './BackendWebSocketService-method-action-types.js';
19
+ import type { WebSocketConnectionInfo, BackendWebSocketServiceConnectionStateChangedEvent, ServerNotificationMessage } from './BackendWebSocketService.js';
20
+ /**
21
+ * Minimal messenger shape the manager requires: the `BackendWebSocketService:*`
22
+ * actions and the `connectionStateChanged` event. A host service's messenger
23
+ * (which has these delegated plus its own domain actions/events) satisfies this.
24
+ */
25
+ export type ChannelSubscriptionManagerMessenger = Messenger<string, BackendWebSocketServiceMethodActions, BackendWebSocketServiceConnectionStateChangedEvent>;
26
+ /** The kind of operation that failed, forwarded to `onSubscriptionError`. */
27
+ export type ChannelSubscriptionOperation = 'subscribe' | 'unsubscribe';
28
+ /**
29
+ * Configuration for a {@link ChannelSubscriptionManager}.
30
+ */
31
+ export type ChannelSubscriptionManagerOptions = {
32
+ /** Messenger used to reach {@link BackendWebSocketService}. */
33
+ messenger: ChannelSubscriptionManagerMessenger;
34
+ /** Channel type/namespace passed to `BackendWebSocketService:subscribe`. */
35
+ channelType: string;
36
+ /** Called for each subscription notification on a managed channel. */
37
+ onMessage: (channel: string, notification: ServerNotificationMessage) => void;
38
+ /** Called when a subscribe/unsubscribe operation fails. */
39
+ onSubscriptionError?: (channel: string, error: string, operation: ChannelSubscriptionOperation) => void;
40
+ /** Optional system-notifications channel to register a callback for on `init()`. */
41
+ systemNotificationsChannel?: string;
42
+ /** Handler invoked for messages on the system-notifications channel. */
43
+ onSystemNotification?: (notification: ServerNotificationMessage) => void;
44
+ /**
45
+ * Called on every connection state change, after the manager performs its own
46
+ * resubscribe-on-CONNECTED. Lets the host run domain reactions (e.g. marking
47
+ * tracked chains down on DISCONNECTED).
48
+ */
49
+ onConnectionStateChange?: (connectionInfo: WebSocketConnectionInfo) => void;
50
+ /** Prefix used in log lines (defaults to the channel type). */
51
+ logPrefix?: string;
52
+ /** Override the grace period (primarily for tests). */
53
+ gracePeriodMs?: number;
54
+ /** Override the unsubscribe retry backoff delays (primarily for tests). */
55
+ unsubRetryDelaysMs?: number[];
56
+ };
57
+ /**
58
+ * Manages the lifecycle of per-channel WebSocket subscriptions on top of the
59
+ * shared {@link BackendWebSocketService}.
60
+ */
61
+ export declare class ChannelSubscriptionManager {
62
+ #private;
63
+ constructor(options: ChannelSubscriptionManagerOptions);
64
+ /**
65
+ * Register the system-notifications channel callback, if configured.
66
+ */
67
+ init(): void;
68
+ /**
69
+ * Subscribe to a channel. If this is the first subscriber a WebSocket
70
+ * subscription is created; additional calls for the same channel only bump the
71
+ * reference count.
72
+ *
73
+ * @param channel - The fully-qualified channel name.
74
+ * @returns A promise that resolves once the subscription is established.
75
+ */
76
+ subscribe(channel: string): Promise<void>;
77
+ /**
78
+ * Unsubscribe from a channel. Decrements the reference count and, when it
79
+ * reaches zero, starts a grace-period timer before actually unsubscribing from
80
+ * the WebSocket to absorb rapid navigation patterns.
81
+ *
82
+ * @param channel - The fully-qualified channel name.
83
+ * @returns A promise that resolves once the unsubscription is processed.
84
+ */
85
+ unsubscribe(channel: string): Promise<void>;
86
+ /**
87
+ * Tear down all channel timers and, if configured, remove the
88
+ * system-notifications channel callback.
89
+ */
90
+ destroy(): void;
91
+ }
92
+ //# sourceMappingURL=ChannelSubscriptionManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChannelSubscriptionManager.d.ts","sourceRoot":"","sources":["../../src/ws/ChannelSubscriptionManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAKrD,OAAO,KAAK,EAAE,oCAAoC,EAAE,MAAM,kDAAkD,CAAC;AAC7G,OAAO,KAAK,EACV,uBAAuB,EACvB,kDAAkD,EAClD,yBAAyB,EAC1B,MAAM,8BAA8B,CAAC;AAuBtC;;;;GAIG;AACH,MAAM,MAAM,mCAAmC,GAAG,SAAS,CACzD,MAAM,EACN,oCAAoC,EACpC,kDAAkD,CACnD,CAAC;AAEF,6EAA6E;AAC7E,MAAM,MAAM,4BAA4B,GAAG,WAAW,GAAG,aAAa,CAAC;AAEvE;;GAEG;AACH,MAAM,MAAM,iCAAiC,GAAG;IAC9C,+DAA+D;IAC/D,SAAS,EAAE,mCAAmC,CAAC;IAC/C,4EAA4E;IAC5E,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,yBAAyB,KAAK,IAAI,CAAC;IAC9E,2DAA2D;IAC3D,mBAAmB,CAAC,EAAE,CACpB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,4BAA4B,KACpC,IAAI,CAAC;IACV,oFAAoF;IACpF,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,wEAAwE;IACxE,oBAAoB,CAAC,EAAE,CAAC,YAAY,EAAE,yBAAyB,KAAK,IAAI,CAAC;IACzE;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,CAAC,cAAc,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAC5E,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B,CAAC;AAMF;;;GAGG;AACH,qBAAa,0BAA0B;;IAiBrC,YAAY,OAAO,EAAE,iCAAiC,EAgCrD;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,CAWX;IAMD;;;;;;;OAOG;IACG,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO9C;IA2FD;;;;;;;OAOG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhD;IAiPD;;;OAGG;IACH,OAAO,IAAI,IAAI,CAad;CACF"}
@@ -0,0 +1,390 @@
1
+ /**
2
+ * Shared per-channel WebSocket subscription lifecycle manager.
3
+ *
4
+ * Extracts the channel-agnostic subscription machinery that was originally part
5
+ * of {@link OHLCVService}: reference counting, grace-period unsubscribe,
6
+ * grace-period flush, unsubscribe retry with backoff + force reconnection,
7
+ * idempotency checks, resubscribe-on-reconnect, and optional system-notification
8
+ * channel registration.
9
+ *
10
+ * It is consumed by **composition** (not inheritance). Each domain service
11
+ * (e.g. OHLCV market data, Launchpad token details) constructs a manager,
12
+ * provides the channel-type namespace and a per-message callback, and keeps its
13
+ * own domain concerns (channel-string construction, payload parsing, published
14
+ * events). The manager talks to {@link BackendWebSocketService} exclusively
15
+ * through the messenger — no direct import of the class.
16
+ */
17
+ import { Mutex } from 'async-mutex';
18
+ import { handleAll, IterableBackoff, retry } from 'cockatiel';
19
+ import { projectLogger, createModuleLogger } from '../logger.js';
20
+ import { WebSocketState } from './BackendWebSocketService.js';
21
+ // =============================================================================
22
+ // Constants
23
+ // =============================================================================
24
+ /** Default delay before actually unsubscribing after refCount reaches 0. */
25
+ const DEFAULT_GRACE_PERIOD_MS = 3_000;
26
+ /** Default backoff delays between failed WebSocket unsubscribe attempts. */
27
+ const DEFAULT_UNSUB_RETRY_DELAYS_MS = [1_000, 2_000, 4_000];
28
+ // =============================================================================
29
+ // Class
30
+ // =============================================================================
31
+ /**
32
+ * Manages the lifecycle of per-channel WebSocket subscriptions on top of the
33
+ * shared {@link BackendWebSocketService}.
34
+ */
35
+ export class ChannelSubscriptionManager {
36
+ #messenger;
37
+ #options;
38
+ #channelType;
39
+ #channels = new Map();
40
+ #mutex = new Mutex();
41
+ #gracePeriodMs;
42
+ #log;
43
+ #unsubRetryPolicy;
44
+ constructor(options) {
45
+ this.#options = options;
46
+ this.#messenger = options.messenger;
47
+ this.#channelType = options.channelType;
48
+ this.#gracePeriodMs = options.gracePeriodMs ?? DEFAULT_GRACE_PERIOD_MS;
49
+ const retryDelays = options.unsubRetryDelaysMs ?? [
50
+ ...DEFAULT_UNSUB_RETRY_DELAYS_MS,
51
+ ];
52
+ this.#unsubRetryPolicy = retry(handleAll, {
53
+ // Cockatiel stops retrying once the failure index reaches `maxAttempts`,
54
+ // so `length` yields one initial attempt plus the delayed retries.
55
+ maxAttempts: retryDelays.length,
56
+ backoff: new IterableBackoff([...retryDelays]),
57
+ });
58
+ const prefix = options.logPrefix ?? options.channelType;
59
+ const moduleLog = createModuleLogger(projectLogger, 'ChannelSubscriptionManager');
60
+ this.#log = (message, data) => data === undefined
61
+ ? moduleLog(`${prefix}: ${message}`)
62
+ : moduleLog(`${prefix}: ${message}`, data);
63
+ this.#messenger.subscribe('BackendWebSocketService:connectionStateChanged',
64
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
65
+ (connectionInfo) => this.#handleWebSocketStateChange(connectionInfo));
66
+ }
67
+ /**
68
+ * Register the system-notifications channel callback, if configured.
69
+ */
70
+ init() {
71
+ const { systemNotificationsChannel, onSystemNotification } = this.#options;
72
+ if (!systemNotificationsChannel || !onSystemNotification) {
73
+ return;
74
+ }
75
+ this.#log('Initializing — registering system-notifications callback');
76
+ this.#messenger.call('BackendWebSocketService:addChannelCallback', {
77
+ channelName: systemNotificationsChannel,
78
+ callback: (notification) => onSystemNotification(notification),
79
+ });
80
+ }
81
+ // =============================================================================
82
+ // Public — Subscribe / Unsubscribe
83
+ // =============================================================================
84
+ /**
85
+ * Subscribe to a channel. If this is the first subscriber a WebSocket
86
+ * subscription is created; additional calls for the same channel only bump the
87
+ * reference count.
88
+ *
89
+ * @param channel - The fully-qualified channel name.
90
+ * @returns A promise that resolves once the subscription is established.
91
+ */
92
+ async subscribe(channel) {
93
+ const releaseLock = await this.#mutex.acquire();
94
+ try {
95
+ await this.#subscribeInner(channel);
96
+ }
97
+ finally {
98
+ releaseLock();
99
+ }
100
+ }
101
+ async #subscribeInner(channel) {
102
+ const entry = this.#channels.get(channel);
103
+ if (entry?.retryAbort) {
104
+ entry.retryAbort.abort();
105
+ entry.retryAbort = undefined;
106
+ entry.refCount = 1;
107
+ if (this.#messenger.call('BackendWebSocketService:channelHasSubscription', channel)) {
108
+ await this.#flushOtherChannels(channel);
109
+ this.#log('Cancelled unsubscribe retry — reusing WS subscription', {
110
+ channel,
111
+ });
112
+ return;
113
+ }
114
+ // WS subscription was lost — fall through to recreate it.
115
+ }
116
+ else if (entry?.gracePeriodTimer) {
117
+ clearTimeout(entry.gracePeriodTimer);
118
+ entry.gracePeriodTimer = undefined;
119
+ this.#log('Cancelled grace-period unsubscribe', {
120
+ channel,
121
+ });
122
+ if (this.#messenger.call('BackendWebSocketService:channelHasSubscription', channel)) {
123
+ await this.#flushOtherChannels(channel);
124
+ entry.refCount += 1;
125
+ this.#log('WS subscription still alive, bumped refCount', {
126
+ channel,
127
+ refCount: entry.refCount,
128
+ });
129
+ return;
130
+ }
131
+ // WS subscription was lost (e.g. after disconnect/reconnect) — fall
132
+ // through to recreate it. refCount is bumped only after success below.
133
+ }
134
+ else if (entry && entry.refCount > 0) {
135
+ entry.refCount += 1;
136
+ return;
137
+ }
138
+ await this.#flushOtherChannels(channel);
139
+ try {
140
+ await this.#messenger.call('BackendWebSocketService:connect');
141
+ if (this.#messenger.call('BackendWebSocketService:channelHasSubscription', channel)) {
142
+ this.#log('Channel already has WS subscription (idempotency), skipping', {
143
+ channel,
144
+ });
145
+ this.#channels.set(channel, { refCount: 1 });
146
+ return;
147
+ }
148
+ await this.#messenger.call('BackendWebSocketService:subscribe', {
149
+ channels: [channel],
150
+ channelType: this.#channelType,
151
+ callback: (notification) => {
152
+ this.#options.onMessage(channel, notification);
153
+ },
154
+ });
155
+ this.#channels.set(channel, { refCount: 1 });
156
+ this.#log('Subscribe succeeded — new WS subscription created', {
157
+ channel,
158
+ });
159
+ }
160
+ catch (error) {
161
+ this.#log('Subscription failed', { channel, error });
162
+ this.#channels.delete(channel);
163
+ this.#options.onSubscriptionError?.(channel, String(error), 'subscribe');
164
+ }
165
+ }
166
+ /**
167
+ * Unsubscribe from a channel. Decrements the reference count and, when it
168
+ * reaches zero, starts a grace-period timer before actually unsubscribing from
169
+ * the WebSocket to absorb rapid navigation patterns.
170
+ *
171
+ * @param channel - The fully-qualified channel name.
172
+ * @returns A promise that resolves once the unsubscription is processed.
173
+ */
174
+ async unsubscribe(channel) {
175
+ const releaseLock = await this.#mutex.acquire();
176
+ try {
177
+ await this.#unsubscribeInner(channel);
178
+ }
179
+ finally {
180
+ releaseLock();
181
+ }
182
+ }
183
+ async #unsubscribeInner(channel) {
184
+ const entry = this.#channels.get(channel);
185
+ if (!entry || entry.refCount <= 0) {
186
+ return;
187
+ }
188
+ entry.refCount -= 1;
189
+ if (entry.refCount > 0) {
190
+ return;
191
+ }
192
+ entry.gracePeriodTimer = setTimeout(() => {
193
+ entry.gracePeriodTimer = undefined;
194
+ this.#performUnsubscribe(channel).catch(() => {
195
+ // no-op: retry scheduling and force-reconnection are handled internally
196
+ });
197
+ }, this.#gracePeriodMs);
198
+ }
199
+ // =============================================================================
200
+ // Private — WebSocket Subscription Helpers
201
+ // =============================================================================
202
+ /**
203
+ * Immediately unsubscribe other channels in grace or failed-cleanup state.
204
+ * Called while the subscribe mutex is held before opening a new channel.
205
+ *
206
+ * @param exceptChannel - Channel being subscribed; excluded from flush.
207
+ */
208
+ async #flushOtherChannels(exceptChannel) {
209
+ for (const [channel, channelEntry] of this.#channels.entries()) {
210
+ if (channel === exceptChannel || channelEntry.refCount > 0) {
211
+ continue;
212
+ }
213
+ this.#clearChannelTimers(channelEntry);
214
+ this.#log('Flushing grace-period channel before new subscribe', {
215
+ flushedChannel: channel,
216
+ newChannel: exceptChannel,
217
+ });
218
+ const success = await this.#unsubscribeChannelOnServer(channel);
219
+ if (success) {
220
+ this.#channels.delete(channel);
221
+ }
222
+ else {
223
+ this.#scheduleUnsubscribeRetry(channel);
224
+ }
225
+ }
226
+ }
227
+ #clearChannelTimers(entry) {
228
+ if (entry.gracePeriodTimer) {
229
+ clearTimeout(entry.gracePeriodTimer);
230
+ entry.gracePeriodTimer = undefined;
231
+ }
232
+ entry.retryAbort?.abort();
233
+ entry.retryAbort = undefined;
234
+ }
235
+ async #unsubscribeChannelOnServer(channel) {
236
+ try {
237
+ const subscriptions = this.#messenger.call('BackendWebSocketService:getSubscriptionsByChannel', channel);
238
+ for (const sub of subscriptions) {
239
+ await sub.unsubscribe();
240
+ }
241
+ return true;
242
+ }
243
+ catch (error) {
244
+ this.#log('Unsubscription failed', { channel, error });
245
+ this.#options.onSubscriptionError?.(channel, String(error), 'unsubscribe');
246
+ return false;
247
+ }
248
+ }
249
+ #scheduleUnsubscribeRetry(channel) {
250
+ const entry = this.#channels.get(channel);
251
+ if (!entry) {
252
+ // The channel was removed (e.g. destroy() or reconnect cleanup) while the
253
+ // unsubscribe was in flight. Do not resurrect it or start a new retry loop
254
+ // with an AbortController that teardown can no longer cancel — that could
255
+ // force a reconnection on the shared WebSocket after teardown.
256
+ return;
257
+ }
258
+ entry.retryAbort?.abort();
259
+ entry.retryAbort = new AbortController();
260
+ const { signal } = entry.retryAbort;
261
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
262
+ this.#runUnsubRetryLoop(channel, signal);
263
+ }
264
+ async #runUnsubRetryLoop(channel, signal) {
265
+ try {
266
+ await this.#unsubRetryPolicy.execute(async () => {
267
+ const releaseLock = await this.#mutex.acquire();
268
+ try {
269
+ const current = this.#channels.get(channel);
270
+ if (current && current.refCount > 0) {
271
+ return;
272
+ }
273
+ const success = await this.#unsubscribeChannelOnServer(channel);
274
+ if (!success) {
275
+ throw new Error('unsubscribe failed');
276
+ }
277
+ this.#channels.delete(channel);
278
+ this.#log('WS unsubscribe completed', { channel });
279
+ }
280
+ finally {
281
+ releaseLock();
282
+ }
283
+ }, signal);
284
+ }
285
+ catch {
286
+ if (signal.aborted) {
287
+ return;
288
+ }
289
+ this.#log('Unsubscribe retries exhausted — forcing reconnection', {
290
+ channel,
291
+ });
292
+ this.#channels.delete(channel);
293
+ // Last resort: reconnects the shared BackendWebSocketService instance
294
+ // (other consumers share this connection). They resubscribe on CONNECTED;
295
+ // this manager only resubscribes channels with refCount > 0.
296
+ await this.#messenger
297
+ .call('BackendWebSocketService:forceReconnection')
298
+ .catch(() => {
299
+ // no-op
300
+ });
301
+ }
302
+ }
303
+ async #performUnsubscribe(channel) {
304
+ const releaseLock = await this.#mutex.acquire();
305
+ try {
306
+ const entry = this.#channels.get(channel);
307
+ if (entry && entry.refCount > 0) {
308
+ this.#log('Skipping unsubscribe — new subscriber arrived while queued', { channel, refCount: entry.refCount });
309
+ return;
310
+ }
311
+ this.#log('Grace period expired — performing actual WS unsubscribe', {
312
+ channel,
313
+ });
314
+ this.#clearChannelTimers(this.#channels.get(channel) ?? { refCount: 0 });
315
+ }
316
+ finally {
317
+ releaseLock();
318
+ }
319
+ this.#scheduleUnsubscribeRetry(channel);
320
+ }
321
+ /**
322
+ * Resubscribe all channels that were active before a disconnect.
323
+ * Called when WebSocket transitions to CONNECTED.
324
+ */
325
+ async #resubscribeActiveChannels() {
326
+ const releaseLock = await this.#mutex.acquire();
327
+ try {
328
+ const channelCount = this.#channels.size;
329
+ this.#log('Resubscribing active channels after reconnect', {
330
+ count: channelCount,
331
+ });
332
+ for (const [channel, entry] of [...this.#channels.entries()]) {
333
+ if (entry.refCount === 0) {
334
+ this.#clearChannelTimers(entry);
335
+ this.#channels.delete(channel);
336
+ continue;
337
+ }
338
+ try {
339
+ if (this.#messenger.call('BackendWebSocketService:channelHasSubscription', channel)) {
340
+ this.#log('Channel already subscribed on server, skipping resubscribe', {
341
+ channel,
342
+ });
343
+ continue;
344
+ }
345
+ await this.#messenger.call('BackendWebSocketService:subscribe', {
346
+ channels: [channel],
347
+ channelType: this.#channelType,
348
+ callback: (notification) => {
349
+ this.#options.onMessage(channel, notification);
350
+ },
351
+ });
352
+ this.#log('Resubscription succeeded', { channel });
353
+ }
354
+ catch (error) {
355
+ this.#log('Resubscription failed for channel', {
356
+ channel,
357
+ error,
358
+ });
359
+ }
360
+ }
361
+ }
362
+ finally {
363
+ releaseLock();
364
+ }
365
+ }
366
+ async #handleWebSocketStateChange(connectionInfo) {
367
+ if (connectionInfo.state === WebSocketState.CONNECTED) {
368
+ await this.#resubscribeActiveChannels();
369
+ }
370
+ this.#options.onConnectionStateChange?.(connectionInfo);
371
+ }
372
+ // =============================================================================
373
+ // Public — Cleanup
374
+ // =============================================================================
375
+ /**
376
+ * Tear down all channel timers and, if configured, remove the
377
+ * system-notifications channel callback.
378
+ */
379
+ destroy() {
380
+ for (const entry of this.#channels.values()) {
381
+ this.#clearChannelTimers(entry);
382
+ }
383
+ this.#channels.clear();
384
+ const { systemNotificationsChannel } = this.#options;
385
+ if (systemNotificationsChannel) {
386
+ this.#messenger.call('BackendWebSocketService:removeChannelCallback', systemNotificationsChannel);
387
+ }
388
+ }
389
+ }
390
+ //# sourceMappingURL=ChannelSubscriptionManager.js.map