-
-
Notifications
You must be signed in to change notification settings - Fork 252
feat(core-backend): update websocket behavior #6819
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
61d7fa7
to
1a6bf0c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approving for the assets codeowner part.
8f51798
to
e7dac94
Compare
@metamaskbot publish-preview |
Preview builds have been published. See these instructions for more information about preview builds. Expand for full list of packages and versions.
|
e7dac94
to
0189b42
Compare
@metamaskbot publish-preview |
Preview builds have been published. See these instructions for more information about preview builds. Expand for full list of packages and versions.
|
Version tested in Extension MetaMask/metamask-extension#36819 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some comments about the changelog and a couple of minor places in the code.
Also since you added a new peer dependency would you mind running yarn update-readme-content
?
this.#isEnabled = options.isEnabled; | ||
// Default to no-op trace function to keep core platform-agnostic | ||
this.#trace = | ||
options.traceFn ?? (((_request, fn) => fn?.()) as TraceCallback); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need a type assertion here? We should hardly ever need type assertions especially for defining default values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I put (((_request, fn) => fn?.()) as any) I got a linter issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, that's an indication that it ought to be written a different way, no?
But not a big deal, we can address this in a future PR.
}); | ||
this.#scheduleReconnect(); | ||
} | ||
// For any unexpected disconnects, attempt reconnection |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Were we already reconnecting upon unexpected disconnect? Is the only connection-related change in this PR the fact that disconnect
will now disable automatic reconnect, or is there something I'm missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Were we already reconnecting upon unexpected disconnect?
Yes we were but if the server disconnected with the event code 1000 then no. I realized it by having the WebSocket open for more than 1 hour. The server disconnected, but didn't reconnect. Which is undesirable because we want MetaMask to reconnect back. I have tested it and it works well now.
disconnect will now disable automatic reconnect
yes that is the only case where we dont try to not re-connect, i.e. only when we close the app.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, thanks for clarifying.
Feature tested in mobile with MetaMask/metamask-mobile#21111 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Went through tests. Most are minor, but seems like some things could be cleaned up nonetheless.
ea0d770
to
e6764b2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes are ok with assets, they are non-breaking as they force lowercase, for TokenBalancesController state, which is what we already use.
This approval is only for the assets-controllers changes.
e6764b2
to
25cab83
Compare
@metamaskbot publish-preview |
Preview builds have been published. See these instructions for more information about preview builds. Expand for full list of packages and versions.
|
25cab83
to
dc2b77e
Compare
917096f
to
99cc00a
Compare
@Kriys94 The overall changes look good to me, thanks for handling the reviews. |
99cc00a
to
00e978c
Compare
00e978c
to
cf661ec
Compare
if ( | ||
this.#allIgnoredTokens?.[chainId]?.[account.toLowerCase()]?.some( | ||
(addr) => addr.toLowerCase() === normalizedAddress, | ||
(token) => token === tokenAddress, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Token Tracking Case Sensitivity Issue
The #isTokenTracked
method now uses case-sensitive address comparison, which can cause tokens to be incorrectly identified as untracked or not ignored. This happens because stored addresses in allTokens
and allIgnoredTokens
may not consistently match the casing of the input tokenAddress
.
const subscription = { | ||
subscriptionId, | ||
channels: [...channels], | ||
channelType, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Subscription Failures Not Properly Handled
The subscribe
method no longer validates subscriptionResponse.failed
, causing subscription failures to be silently ignored. This can lead to clients incorrectly assuming full subscription, resulting in missed notifications or unexpected application behavior.
cf661ec
to
16e8b32
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Done. Thanks for the review @cryptodev-2s @mcmire @bergarces |
if ( | ||
this.#allIgnoredTokens?.[chainId]?.[account.toLowerCase()]?.some( | ||
(addr) => addr.toLowerCase() === normalizedAddress, | ||
(token) => token === tokenAddress, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Token Address Matching Case Sensitivity Issue
The #isTokenTracked
method changed from case-insensitive to case-sensitive address comparison. This causes it to incorrectly return false
for tracked or ignored tokens when there's a casing mismatch between the input tokenAddress
and addresses stored in allTokens
or allIgnoredTokens
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approve from assets perspective, as forcing lowercase on state is not a breaking change, but a fix.
## Explanation This release includes major version bumps for 4 packages, primarily driven by breaking changes in `@metamask/core-backend` that introduce automatic WebSocket connection management and several API improvements. **Note:** While these are marked as breaking changes, they should not affect MetaMask Extension or MetaMask Mobile at this time, as WebSocket integration has not been implemented in these clients yet. The breaking changes are primarily API improvements that will be relevant once WebSocket functionality is adopted. ### 📦 Packages Included - `@metamask/core-backend`: **1.0.1 → 2.0.0** - `@metamask/assets-controllers`: **80.0.0 → 81.0.0** - `@metamask/bridge-controller`: **51.0.0 → 52.0.0** - `@metamask/bridge-status-controller`: **50.1.0 → 51.0.0** ### Current State and Why It Needs to Change The `@metamask/core-backend` package required several breaking changes to improve WebSocket connection management, type safety, and API consistency. The package needed to automatically manage WebSocket connections based on wallet lock state, and the type definitions needed improvements for better developer experience. ### Solution #### @metamask/core-backend (1.0.1 → 2.0.0) **Breaking Changes:** - Added required `channelType` argument to `BackendWebSocketService.subscribe` method for better subscription management - Updated `Asset` type to require `decimals` field for proper token amount formatting - Implemented automatic WebSocket connection management based on wallet lock state (requires `KeyringController:lock` and `KeyringController:unlock` events) - Renamed `Transaction.hash` to `Transaction.id` for consistency with backend API - Added new peer dependency on `@metamask/keyring-controller` (^23.0.0) - Removed `getSupportedChains` method from `AccountActivityService` (replaced with system notification-driven chain tracking) **Non-Breaking Additions:** - Added optional `traceFn` parameter for performance tracing integration (e.g., Sentry) - Added optional `timestamp` property to various notification types #### @metamask/assets-controllers (80.0.0 → 81.0.0) **Changes:** - **BREAKING:** Bump dependency `@metamask/core-backend` from `^1.0.1` to `^2.0.0` - **BREAKING:** Bump peer dependency `@metamask/core-backend` from `^1.0.1` to `^2.0.0` - **Fixed:** Address casing in WebSocket-based token balance updates to ensure consistency #### @metamask/bridge-controller (51.0.0 → 52.0.0) **Changes:** - **BREAKING:** Bump dependency `@metamask/assets-controllers` from `^80.0.0` to `^81.0.0` - **BREAKING:** Bump peer dependency `@metamask/assets-controllers` from `^80.0.0` to `^81.0.0` #### @metamask/bridge-status-controller (50.1.0 → 51.0.0) **Changes:** - **BREAKING:** Bump dependency `@metamask/bridge-controller` from `^51.0.0` to `^52.0.0` - **BREAKING:** Bump peer dependency `@metamask/bridge-controller` from `^51.0.0` to `^52.0.0` ### Why Cascade Updates Were Necessary The breaking changes in `@metamask/core-backend` required a major version bump. Since `@metamask/assets-controllers` has `@metamask/core-backend` as both a dependency and peer dependency, it needed to be updated to accept the new version. This cascaded to `@metamask/bridge-controller` (which depends on `@metamask/assets-controllers`) and `@metamask/bridge-status-controller` (which depends on `@metamask/bridge-controller`). ### Migration Guide for Consumers 1. **Update `subscribe` calls** to include `channelType`: ```typescript // Before await messenger.call('BackendWebSocketService:subscribe', { account: '0x123...', }); // After await messenger.call('BackendWebSocketService:subscribe', { account: '0x123...', channelType: 'balance', // or 'transaction' }); ``` 2. **Add KeyringController events** to your messenger: ```typescript AllowedEvents: [ 'KeyringController:lock', 'KeyringController:unlock', // ... other events ] ``` 3. **Update Asset type usage** to include `decimals`: ```typescript const asset: Asset = { address: '0x...', symbol: 'TOKEN', decimals: 18, // now required }; ``` 4. **Update Transaction references** from `hash` to `id`: ```typescript // Before: transaction.hash // After: transaction.id ``` 5. **Add @metamask/keyring-controller** peer dependency: ```json { "peerDependencies": { "@metamask/keyring-controller": "^23.0.0" } } ``` 6. **Remove `getSupportedChains` calls** - chain tracking is now automatic via system notifications ## References - Primary PR: #6819 - WebSocket connection management improvements - Related: #6818, #6824 - Release PR: #6834 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/contributing.md#updating-changelogs), highlighting breaking changes as necessary - [ ] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Releases core-backend v2 with breaking WebSocket and type changes, and cascades required peer/dependency bumps across assets and bridge packages. > > - **Backend** > - `@metamask/core-backend@2.0.0` (major): > - Requires `channelType` in `BackendWebSocketService.subscribe` > - Adds required `Asset.decimals`; renames transaction `hash` → `id` > - Auto WebSocket connection management tied to lock/unlock; new peer dep `@metamask/keyring-controller` > - Optional `traceFn`; optional `timestamp` fields in notifications/events > - **Assets** > - `@metamask/assets-controllers@81.0.0` (major): bump peer/dev dep `@metamask/core-backend` to `^2.0.0`. > - **Bridge** > - `@metamask/bridge-controller@52.0.0` (major): bump peer/dev dep `@metamask/assets-controllers` to `^81.0.0`. > - `@metamask/bridge-status-controller@51.0.0` (major): bump peer/dev dep `@metamask/bridge-controller` to `^52.0.0`. > - **Repo** > - Monorepo version `618.0.0` → `619.0.0`; lockfile updated. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 9e01bed. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Explanation
Current State & Motivation
The
core-backend
package had several architectural issues that needed to be addressed:Inflexible Chain Management:
AccountActivityService
was proactively fetching supported chains from the Accounts API (/v2/supportedNetworks
) with a hardcoded fallback list. This approach was:Incomplete Connection Management:
BackendWebSocketService
only subscribed toAuthenticationController:stateChange
events but didn't directly respond to wallet lock/unlock events fromKeyringController
. This created a dependency where connection management relied solely on authentication state changes.Reconnection Logic Based on Close Codes: The service used WebSocket close codes to determine reconnection behavior, which was fragile and didn't properly distinguish between manual disconnects (user action) and unexpected disconnects (network issues).
Type Mismatches: The
Transaction
andAsset
types didn't match the backend API contract, causing potential integration issues.No Performance Monitoring: There was no way to trace WebSocket operations for performance analysis in production environments.
Inconsistent Address Casing: WebSocket-based token balance and detection updates had inconsistent address normalization, causing issues with state lookups and controller integration.
Solution
1. System Notification-Driven Chain Tracking (
AccountActivityService
)Before: Client proactively fetches and caches supported chains
After: Client reacts to backend system notifications
Benefits:
Flow:
{chainIds: ['eip155:1', 'eip155:137'], status: 'up'}
2. Simplified Reconnection Logic (
BackendWebSocketService
)Before: Reconnection based on close codes
After: Reconnection based on manual disconnect flag
Benefits:
3. KeyringController Event Integration
Added direct subscriptions to wallet lock state:
Connection Requirements (all must be true):
isEnabled()
callback)AuthenticationController.isSignedIn
)KeyringController
state)4. Type Alignment with Backend API
Transaction.hash
→Transaction.id
(matches backend field name)Asset
now requiresdecimals: number
(needed for proper token formatting)5. Performance Tracing Integration
Added optional
traceFn
parameter to enable performance monitoring:Benefits:
Integration:
trace
fromshared/lib/trace.ts
(Sentry-backed)trace
fromapp/util/trace.ts
(Sentry-backed)Example usage:
6. Address Normalization for WebSocket Updates (
assets-controllers
)Fixed inconsistent address casing in WebSocket-based token balance and detection updates to ensure proper state lookups and controller integration.
TokenBalancesController (
#onAccountActivityBalanceUpdate
):tokenBalances
state lookupstokenBalances
state storageAccountTrackerController:updateNativeBalances
TokensController:addTokens
TokenDetectionController (
addDetectedTokensViaWs
):TokensController:addTokens
tokensChainsCache
lookupstokensChainsCache
at the start of the method to prevent stale dataBenefits:
Example:
Breaking Changes Impact
For
AccountActivityService
consumers:getSupportedChains()
method - consumers should subscribe toAccountActivityService:statusChanged
events insteadFor type definitions:
Transaction
objects must useid
instead ofhash
Asset
objects must includedecimals
fieldTesting Updates
nock
dependency (no longer fetching from external API)TokenDetectionController
tests to expect checksummed addresses inaddTokens
callsReferences
Checklist
Note
Revamps core-backend WebSocket lifecycle (keyring lock/unlock, manual disconnect, tracing, new params/types, system notification–driven chains) and fixes assets controllers to normalize addresses for WebSocket token updates; docs/tests updated.
@metamask/core-backend
):KeyringController:lock/unlock
; exposes richergetConnectionInfo
.BackendWebSocketService.subscribe
now requireschannelType
.Transaction.hash
→id
;Asset
adds requireddecimals
;ServerNotificationMessage
includestimestamp
.@metamask/keyring-controller
.traceFn
to instrument connect/disconnect/notifications.getSupportedChains
; chains tracked via system notifications; publishesstatusChanged
with optionaltimestamp
.addDetectedTokensViaWs
; metrics use checksummed addresses.@metamask/core-backend
to package list and dependency graph; minor graph edge additions.Written by Cursor Bugbot for commit 16e8b32. This will update automatically on new commits. Configure here.