@metamask-previews/core-backend 12.3.0-preview-9fd631f7d → 12.3.0-preview-d0145b59e

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 +7 -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 +105 -0
  7. package/dist/ws/ChannelSubscriptionManager.d.ts.map +1 -0
  8. package/dist/ws/ChannelSubscriptionManager.js +413 -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 +185 -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 +38 -0
  23. package/dist/ws/launchpad/normalize.d.ts.map +1 -0
  24. package/dist/ws/launchpad/normalize.js +134 -0
  25. package/dist/ws/launchpad/normalize.js.map +1 -0
  26. package/dist/ws/launchpad/types.d.ts +141 -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 +7 -4
  31. package/dist/ws/ohlcv/OHLCVService.d.ts.map +1 -1
  32. package/dist/ws/ohlcv/OHLCVService.js +42 -329
  33. package/dist/ws/ohlcv/OHLCVService.js.map +1 -1
  34. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -9,14 +9,21 @@ 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
+ - **BREAKING:** Remove the unused `BackendWebSocketService:getConnectionInfo` and `BackendWebSocketService:findSubscriptionsByChannelPrefix` actions from `OHLCV_SERVICE_ALLOWED_ACTIONS` ([#10884](https://github.com/MetaMask/core/pull/10884))
19
+ - `OHLCV_SERVICE_ALLOWED_ACTIONS` / `OHLCV_SERVICE_ALLOWED_EVENTS` and `LAUNCHPAD_TDP_SERVICE_ALLOWED_ACTIONS` / `LAUNCHPAD_TDP_SERVICE_ALLOWED_EVENTS` are now sourced from a single shared set (the `BackendWebSocketService` actions/events the `ChannelSubscriptionManager` actually calls), so both services grant identical, minimal permissions.
20
+ - If you build the `OHLCVService` restricted messenger from `OHLCV_SERVICE_ALLOWED_ACTIONS`, it will no longer be granted those two actions. Neither action was ever called by `OHLCVService`, so no runtime behavior changes; grant them explicitly only if your own code calls them through that messenger.
21
+ - Refactor `OHLCVService` to delegate its subscription lifecycle to the shared `ChannelSubscriptionManager` via composition, with no change to its public methods or published events ([#10884](https://github.com/MetaMask/core/pull/10884))
16
22
  - 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
23
  - 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
24
  - 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))
19
25
  - Bump `@metamask/utils` from `^12.0.0` to `^12.0.1` ([#11107](https://github.com/MetaMask/core/pull/11107))
26
+ - Bump `uuid` from `^11.1.1` to `^14.0.0` ([#10941](https://github.com/MetaMask/core/pull/10941))
20
27
 
21
28
  ## [12.3.0]
22
29
 
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, 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;AAsBjC,+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 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,105 @@
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
+ * The `BackendWebSocketService:*` actions the manager calls through its host
22
+ * service's messenger. A domain service must grant (at least) these to its
23
+ * restricted messenger, since the manager borrows that messenger and performs
24
+ * all WebSocket calls on the service's behalf. Services should source their own
25
+ * `*_ALLOWED_ACTIONS` export from this list rather than re-declaring it.
26
+ */
27
+ export declare const CHANNEL_SUBSCRIPTION_MANAGER_ALLOWED_ACTIONS: readonly ['BackendWebSocketService:connect', 'BackendWebSocketService:forceReconnection', 'BackendWebSocketService:subscribe', 'BackendWebSocketService:channelHasSubscription', 'BackendWebSocketService:getSubscriptionsByChannel', 'BackendWebSocketService:addChannelCallback', 'BackendWebSocketService:removeChannelCallback'];
28
+ /**
29
+ * The `BackendWebSocketService:*` events the manager subscribes to through its
30
+ * host service's messenger.
31
+ */
32
+ export declare const CHANNEL_SUBSCRIPTION_MANAGER_ALLOWED_EVENTS: readonly ['BackendWebSocketService:connectionStateChanged'];
33
+ /**
34
+ * Minimal messenger shape the manager requires: the `BackendWebSocketService:*`
35
+ * actions and the `connectionStateChanged` event. A host service's messenger
36
+ * (which has these delegated plus its own domain actions/events) satisfies this.
37
+ */
38
+ export type ChannelSubscriptionManagerMessenger = Messenger<string, BackendWebSocketServiceMethodActions, BackendWebSocketServiceConnectionStateChangedEvent>;
39
+ /** The kind of operation that failed, forwarded to `onSubscriptionError`. */
40
+ export type ChannelSubscriptionOperation = 'subscribe' | 'unsubscribe';
41
+ /**
42
+ * Configuration for a {@link ChannelSubscriptionManager}.
43
+ */
44
+ export type ChannelSubscriptionManagerOptions = {
45
+ /** Messenger used to reach {@link BackendWebSocketService}. */
46
+ messenger: ChannelSubscriptionManagerMessenger;
47
+ /** Channel type/namespace passed to `BackendWebSocketService:subscribe`. */
48
+ channelType: string;
49
+ /** Called for each subscription notification on a managed channel. */
50
+ onMessage: (channel: string, notification: ServerNotificationMessage) => void;
51
+ /** Called when a subscribe/unsubscribe operation fails. */
52
+ onSubscriptionError?: (channel: string, error: string, operation: ChannelSubscriptionOperation) => void;
53
+ /** Optional system-notifications channel to register a callback for on `init()`. */
54
+ systemNotificationsChannel?: string;
55
+ /** Handler invoked for messages on the system-notifications channel. */
56
+ onSystemNotification?: (notification: ServerNotificationMessage) => void;
57
+ /**
58
+ * Called on every connection state change, after the manager performs its own
59
+ * resubscribe-on-CONNECTED. Lets the host run domain reactions (e.g. marking
60
+ * tracked chains down on DISCONNECTED).
61
+ */
62
+ onConnectionStateChange?: (connectionInfo: WebSocketConnectionInfo) => void;
63
+ /** Prefix used in log lines (defaults to the channel type). */
64
+ logPrefix?: string;
65
+ /** Override the grace period (primarily for tests). */
66
+ gracePeriodMs?: number;
67
+ /** Override the unsubscribe retry backoff delays (primarily for tests). */
68
+ unsubRetryDelaysMs?: number[];
69
+ };
70
+ /**
71
+ * Manages the lifecycle of per-channel WebSocket subscriptions on top of the
72
+ * shared {@link BackendWebSocketService}.
73
+ */
74
+ export declare class ChannelSubscriptionManager {
75
+ #private;
76
+ constructor(options: ChannelSubscriptionManagerOptions);
77
+ /**
78
+ * Register the system-notifications channel callback, if configured.
79
+ */
80
+ init(): void;
81
+ /**
82
+ * Subscribe to a channel. If this is the first subscriber a WebSocket
83
+ * subscription is created; additional calls for the same channel only bump the
84
+ * reference count.
85
+ *
86
+ * @param channel - The fully-qualified channel name.
87
+ * @returns A promise that resolves once the subscription is established.
88
+ */
89
+ subscribe(channel: string): Promise<void>;
90
+ /**
91
+ * Unsubscribe from a channel. Decrements the reference count and, when it
92
+ * reaches zero, starts a grace-period timer before actually unsubscribing from
93
+ * the WebSocket to absorb rapid navigation patterns.
94
+ *
95
+ * @param channel - The fully-qualified channel name.
96
+ * @returns A promise that resolves once the unsubscription is processed.
97
+ */
98
+ unsubscribe(channel: string): Promise<void>;
99
+ /**
100
+ * Tear down all channel timers and, if configured, remove the
101
+ * system-notifications channel callback.
102
+ */
103
+ destroy(): void;
104
+ }
105
+ //# 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;AAatC;;;;;;GAMG;AACH,eAAO,MAAM,4CAA4C,YACvD,iCAAiC,EACjC,2CAA2C,EAC3C,mCAAmC,EACnC,gDAAgD,EAChD,mDAAmD,EACnD,4CAA4C,EAC5C,+CAA+C,CACvC,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,2CAA2C,YACtD,gDAAgD,CACxC,CAAC;AAYX;;;;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,413 @@
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
+ * The `BackendWebSocketService:*` actions the manager calls through its host
30
+ * service's messenger. A domain service must grant (at least) these to its
31
+ * restricted messenger, since the manager borrows that messenger and performs
32
+ * all WebSocket calls on the service's behalf. Services should source their own
33
+ * `*_ALLOWED_ACTIONS` export from this list rather than re-declaring it.
34
+ */
35
+ export const CHANNEL_SUBSCRIPTION_MANAGER_ALLOWED_ACTIONS = [
36
+ 'BackendWebSocketService:connect',
37
+ 'BackendWebSocketService:forceReconnection',
38
+ 'BackendWebSocketService:subscribe',
39
+ 'BackendWebSocketService:channelHasSubscription',
40
+ 'BackendWebSocketService:getSubscriptionsByChannel',
41
+ 'BackendWebSocketService:addChannelCallback',
42
+ 'BackendWebSocketService:removeChannelCallback',
43
+ ];
44
+ /**
45
+ * The `BackendWebSocketService:*` events the manager subscribes to through its
46
+ * host service's messenger.
47
+ */
48
+ export const CHANNEL_SUBSCRIPTION_MANAGER_ALLOWED_EVENTS = [
49
+ 'BackendWebSocketService:connectionStateChanged',
50
+ ];
51
+ // =============================================================================
52
+ // Class
53
+ // =============================================================================
54
+ /**
55
+ * Manages the lifecycle of per-channel WebSocket subscriptions on top of the
56
+ * shared {@link BackendWebSocketService}.
57
+ */
58
+ export class ChannelSubscriptionManager {
59
+ #messenger;
60
+ #options;
61
+ #channelType;
62
+ #channels = new Map();
63
+ #mutex = new Mutex();
64
+ #gracePeriodMs;
65
+ #log;
66
+ #unsubRetryPolicy;
67
+ constructor(options) {
68
+ this.#options = options;
69
+ this.#messenger = options.messenger;
70
+ this.#channelType = options.channelType;
71
+ this.#gracePeriodMs = options.gracePeriodMs ?? DEFAULT_GRACE_PERIOD_MS;
72
+ const retryDelays = options.unsubRetryDelaysMs ?? [
73
+ ...DEFAULT_UNSUB_RETRY_DELAYS_MS,
74
+ ];
75
+ this.#unsubRetryPolicy = retry(handleAll, {
76
+ // Cockatiel stops retrying once the failure index reaches `maxAttempts`,
77
+ // so `length` yields one initial attempt plus the delayed retries.
78
+ maxAttempts: retryDelays.length,
79
+ backoff: new IterableBackoff([...retryDelays]),
80
+ });
81
+ const prefix = options.logPrefix ?? options.channelType;
82
+ const moduleLog = createModuleLogger(projectLogger, 'ChannelSubscriptionManager');
83
+ this.#log = (message, data) => data === undefined
84
+ ? moduleLog(`${prefix}: ${message}`)
85
+ : moduleLog(`${prefix}: ${message}`, data);
86
+ this.#messenger.subscribe('BackendWebSocketService:connectionStateChanged',
87
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
88
+ (connectionInfo) => this.#handleWebSocketStateChange(connectionInfo));
89
+ }
90
+ /**
91
+ * Register the system-notifications channel callback, if configured.
92
+ */
93
+ init() {
94
+ const { systemNotificationsChannel, onSystemNotification } = this.#options;
95
+ if (!systemNotificationsChannel || !onSystemNotification) {
96
+ return;
97
+ }
98
+ this.#log('Initializing — registering system-notifications callback');
99
+ this.#messenger.call('BackendWebSocketService:addChannelCallback', {
100
+ channelName: systemNotificationsChannel,
101
+ callback: (notification) => onSystemNotification(notification),
102
+ });
103
+ }
104
+ // =============================================================================
105
+ // Public — Subscribe / Unsubscribe
106
+ // =============================================================================
107
+ /**
108
+ * Subscribe to a channel. If this is the first subscriber a WebSocket
109
+ * subscription is created; additional calls for the same channel only bump the
110
+ * reference count.
111
+ *
112
+ * @param channel - The fully-qualified channel name.
113
+ * @returns A promise that resolves once the subscription is established.
114
+ */
115
+ async subscribe(channel) {
116
+ const releaseLock = await this.#mutex.acquire();
117
+ try {
118
+ await this.#subscribeInner(channel);
119
+ }
120
+ finally {
121
+ releaseLock();
122
+ }
123
+ }
124
+ async #subscribeInner(channel) {
125
+ const entry = this.#channels.get(channel);
126
+ if (entry?.retryAbort) {
127
+ entry.retryAbort.abort();
128
+ entry.retryAbort = undefined;
129
+ entry.refCount = 1;
130
+ if (this.#messenger.call('BackendWebSocketService:channelHasSubscription', channel)) {
131
+ await this.#flushOtherChannels(channel);
132
+ this.#log('Cancelled unsubscribe retry — reusing WS subscription', {
133
+ channel,
134
+ });
135
+ return;
136
+ }
137
+ // WS subscription was lost — fall through to recreate it.
138
+ }
139
+ else if (entry?.gracePeriodTimer) {
140
+ clearTimeout(entry.gracePeriodTimer);
141
+ entry.gracePeriodTimer = undefined;
142
+ this.#log('Cancelled grace-period unsubscribe', {
143
+ channel,
144
+ });
145
+ if (this.#messenger.call('BackendWebSocketService:channelHasSubscription', channel)) {
146
+ await this.#flushOtherChannels(channel);
147
+ entry.refCount += 1;
148
+ this.#log('WS subscription still alive, bumped refCount', {
149
+ channel,
150
+ refCount: entry.refCount,
151
+ });
152
+ return;
153
+ }
154
+ // WS subscription was lost (e.g. after disconnect/reconnect) — fall
155
+ // through to recreate it. refCount is bumped only after success below.
156
+ }
157
+ else if (entry && entry.refCount > 0) {
158
+ entry.refCount += 1;
159
+ return;
160
+ }
161
+ await this.#flushOtherChannels(channel);
162
+ try {
163
+ await this.#messenger.call('BackendWebSocketService:connect');
164
+ if (this.#messenger.call('BackendWebSocketService:channelHasSubscription', channel)) {
165
+ this.#log('Channel already has WS subscription (idempotency), skipping', {
166
+ channel,
167
+ });
168
+ this.#channels.set(channel, { refCount: 1 });
169
+ return;
170
+ }
171
+ await this.#messenger.call('BackendWebSocketService:subscribe', {
172
+ channels: [channel],
173
+ channelType: this.#channelType,
174
+ callback: (notification) => {
175
+ this.#options.onMessage(channel, notification);
176
+ },
177
+ });
178
+ this.#channels.set(channel, { refCount: 1 });
179
+ this.#log('Subscribe succeeded — new WS subscription created', {
180
+ channel,
181
+ });
182
+ }
183
+ catch (error) {
184
+ this.#log('Subscription failed', { channel, error });
185
+ this.#channels.delete(channel);
186
+ this.#options.onSubscriptionError?.(channel, String(error), 'subscribe');
187
+ }
188
+ }
189
+ /**
190
+ * Unsubscribe from a channel. Decrements the reference count and, when it
191
+ * reaches zero, starts a grace-period timer before actually unsubscribing from
192
+ * the WebSocket to absorb rapid navigation patterns.
193
+ *
194
+ * @param channel - The fully-qualified channel name.
195
+ * @returns A promise that resolves once the unsubscription is processed.
196
+ */
197
+ async unsubscribe(channel) {
198
+ const releaseLock = await this.#mutex.acquire();
199
+ try {
200
+ await this.#unsubscribeInner(channel);
201
+ }
202
+ finally {
203
+ releaseLock();
204
+ }
205
+ }
206
+ async #unsubscribeInner(channel) {
207
+ const entry = this.#channels.get(channel);
208
+ if (!entry || entry.refCount <= 0) {
209
+ return;
210
+ }
211
+ entry.refCount -= 1;
212
+ if (entry.refCount > 0) {
213
+ return;
214
+ }
215
+ entry.gracePeriodTimer = setTimeout(() => {
216
+ entry.gracePeriodTimer = undefined;
217
+ this.#performUnsubscribe(channel).catch(() => {
218
+ // no-op: retry scheduling and force-reconnection are handled internally
219
+ });
220
+ }, this.#gracePeriodMs);
221
+ }
222
+ // =============================================================================
223
+ // Private — WebSocket Subscription Helpers
224
+ // =============================================================================
225
+ /**
226
+ * Immediately unsubscribe other channels in grace or failed-cleanup state.
227
+ * Called while the subscribe mutex is held before opening a new channel.
228
+ *
229
+ * @param exceptChannel - Channel being subscribed; excluded from flush.
230
+ */
231
+ async #flushOtherChannels(exceptChannel) {
232
+ for (const [channel, channelEntry] of this.#channels.entries()) {
233
+ if (channel === exceptChannel || channelEntry.refCount > 0) {
234
+ continue;
235
+ }
236
+ this.#clearChannelTimers(channelEntry);
237
+ this.#log('Flushing grace-period channel before new subscribe', {
238
+ flushedChannel: channel,
239
+ newChannel: exceptChannel,
240
+ });
241
+ const success = await this.#unsubscribeChannelOnServer(channel);
242
+ if (success) {
243
+ this.#channels.delete(channel);
244
+ }
245
+ else {
246
+ this.#scheduleUnsubscribeRetry(channel);
247
+ }
248
+ }
249
+ }
250
+ #clearChannelTimers(entry) {
251
+ if (entry.gracePeriodTimer) {
252
+ clearTimeout(entry.gracePeriodTimer);
253
+ entry.gracePeriodTimer = undefined;
254
+ }
255
+ entry.retryAbort?.abort();
256
+ entry.retryAbort = undefined;
257
+ }
258
+ async #unsubscribeChannelOnServer(channel) {
259
+ try {
260
+ const subscriptions = this.#messenger.call('BackendWebSocketService:getSubscriptionsByChannel', channel);
261
+ for (const sub of subscriptions) {
262
+ await sub.unsubscribe();
263
+ }
264
+ return true;
265
+ }
266
+ catch (error) {
267
+ this.#log('Unsubscription failed', { channel, error });
268
+ this.#options.onSubscriptionError?.(channel, String(error), 'unsubscribe');
269
+ return false;
270
+ }
271
+ }
272
+ #scheduleUnsubscribeRetry(channel) {
273
+ const entry = this.#channels.get(channel);
274
+ if (!entry) {
275
+ // The channel was removed (e.g. destroy() or reconnect cleanup) while the
276
+ // unsubscribe was in flight. Do not resurrect it or start a new retry loop
277
+ // with an AbortController that teardown can no longer cancel — that could
278
+ // force a reconnection on the shared WebSocket after teardown.
279
+ return;
280
+ }
281
+ entry.retryAbort?.abort();
282
+ entry.retryAbort = new AbortController();
283
+ const { signal } = entry.retryAbort;
284
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
285
+ this.#runUnsubRetryLoop(channel, signal);
286
+ }
287
+ async #runUnsubRetryLoop(channel, signal) {
288
+ try {
289
+ await this.#unsubRetryPolicy.execute(async () => {
290
+ const releaseLock = await this.#mutex.acquire();
291
+ try {
292
+ const current = this.#channels.get(channel);
293
+ if (current && current.refCount > 0) {
294
+ return;
295
+ }
296
+ const success = await this.#unsubscribeChannelOnServer(channel);
297
+ if (!success) {
298
+ throw new Error('unsubscribe failed');
299
+ }
300
+ this.#channels.delete(channel);
301
+ this.#log('WS unsubscribe completed', { channel });
302
+ }
303
+ finally {
304
+ releaseLock();
305
+ }
306
+ }, signal);
307
+ }
308
+ catch {
309
+ if (signal.aborted) {
310
+ return;
311
+ }
312
+ this.#log('Unsubscribe retries exhausted — forcing reconnection', {
313
+ channel,
314
+ });
315
+ this.#channels.delete(channel);
316
+ // Last resort: reconnects the shared BackendWebSocketService instance
317
+ // (other consumers share this connection). They resubscribe on CONNECTED;
318
+ // this manager only resubscribes channels with refCount > 0.
319
+ await this.#messenger
320
+ .call('BackendWebSocketService:forceReconnection')
321
+ .catch(() => {
322
+ // no-op
323
+ });
324
+ }
325
+ }
326
+ async #performUnsubscribe(channel) {
327
+ const releaseLock = await this.#mutex.acquire();
328
+ try {
329
+ const entry = this.#channels.get(channel);
330
+ if (entry && entry.refCount > 0) {
331
+ this.#log('Skipping unsubscribe — new subscriber arrived while queued', { channel, refCount: entry.refCount });
332
+ return;
333
+ }
334
+ this.#log('Grace period expired — performing actual WS unsubscribe', {
335
+ channel,
336
+ });
337
+ this.#clearChannelTimers(this.#channels.get(channel) ?? { refCount: 0 });
338
+ }
339
+ finally {
340
+ releaseLock();
341
+ }
342
+ this.#scheduleUnsubscribeRetry(channel);
343
+ }
344
+ /**
345
+ * Resubscribe all channels that were active before a disconnect.
346
+ * Called when WebSocket transitions to CONNECTED.
347
+ */
348
+ async #resubscribeActiveChannels() {
349
+ const releaseLock = await this.#mutex.acquire();
350
+ try {
351
+ const channelCount = this.#channels.size;
352
+ this.#log('Resubscribing active channels after reconnect', {
353
+ count: channelCount,
354
+ });
355
+ for (const [channel, entry] of [...this.#channels.entries()]) {
356
+ if (entry.refCount === 0) {
357
+ this.#clearChannelTimers(entry);
358
+ this.#channels.delete(channel);
359
+ continue;
360
+ }
361
+ try {
362
+ if (this.#messenger.call('BackendWebSocketService:channelHasSubscription', channel)) {
363
+ this.#log('Channel already subscribed on server, skipping resubscribe', {
364
+ channel,
365
+ });
366
+ continue;
367
+ }
368
+ await this.#messenger.call('BackendWebSocketService:subscribe', {
369
+ channels: [channel],
370
+ channelType: this.#channelType,
371
+ callback: (notification) => {
372
+ this.#options.onMessage(channel, notification);
373
+ },
374
+ });
375
+ this.#log('Resubscription succeeded', { channel });
376
+ }
377
+ catch (error) {
378
+ this.#log('Resubscription failed for channel', {
379
+ channel,
380
+ error,
381
+ });
382
+ }
383
+ }
384
+ }
385
+ finally {
386
+ releaseLock();
387
+ }
388
+ }
389
+ async #handleWebSocketStateChange(connectionInfo) {
390
+ if (connectionInfo.state === WebSocketState.CONNECTED) {
391
+ await this.#resubscribeActiveChannels();
392
+ }
393
+ this.#options.onConnectionStateChange?.(connectionInfo);
394
+ }
395
+ // =============================================================================
396
+ // Public — Cleanup
397
+ // =============================================================================
398
+ /**
399
+ * Tear down all channel timers and, if configured, remove the
400
+ * system-notifications channel callback.
401
+ */
402
+ destroy() {
403
+ for (const entry of this.#channels.values()) {
404
+ this.#clearChannelTimers(entry);
405
+ }
406
+ this.#channels.clear();
407
+ const { systemNotificationsChannel } = this.#options;
408
+ if (systemNotificationsChannel) {
409
+ this.#messenger.call('BackendWebSocketService:removeChannelCallback', systemNotificationsChannel);
410
+ }
411
+ }
412
+ }
413
+ //# sourceMappingURL=ChannelSubscriptionManager.js.map