feat: passive wallet_sessionChanged listening for extension#198
feat: passive wallet_sessionChanged listening for extension#198
Conversation
…pDefaultTransport
… and emiteSessionChanged when passive listening
packages/connect-multichain/src/multichain/transports/default/index.ts
Outdated
Show resolved
Hide resolved
packages/connect-multichain/src/multichain/transports/default/index.ts
Outdated
Show resolved
Hide resolved
| this.#notifyCallbacks({ | ||
| method: 'wallet_sessionChanged', | ||
| params: walletSession, | ||
| }); |
There was a problem hiding this comment.
This will not happen anymore in the refactor since its only happening in the public init and not the private one. This doesn't seem intentional?
| const response = await this.request({ method: 'wallet_getSession' }); | ||
| const { sessionScopes } = response.result as SessionData; | ||
|
|
||
| if (Object.keys(sessionScopes).length > 0) { | ||
| return; | ||
| } | ||
|
|
||
| this.#notificationCallbacks.clear(); | ||
|
|
||
| // Remove the message listener when disconnecting | ||
| if (this.#handleResponseListener) { | ||
| // eslint-disable-next-line no-restricted-globals | ||
| window.removeEventListener('message', this.#handleResponseListener); | ||
| this.#handleResponseListener = undefined; | ||
| } | ||
|
|
||
| // Remove the notification listener when disconnecting | ||
| if (this.#handleNotificationListener) { | ||
| // eslint-disable-next-line no-restricted-globals | ||
| window.removeEventListener('message', this.#handleNotificationListener); | ||
| this.#handleNotificationListener = undefined; | ||
| } | ||
|
|
||
| // Reject all pending requests | ||
| for (const [, request] of this.#pendingRequests) { | ||
| clearTimeout(request.timeout); | ||
| request.reject(new Error('Transport disconnected')); | ||
| } | ||
| this.#pendingRequests.clear(); |
There was a problem hiding this comment.
Help me understand why we don't need any of this cleanup anymore?
| ); | ||
| walletSession = sessionRequest.result as SessionData; | ||
| } catch { | ||
| // wallet_getSession may fail if extension is not ready; treat as no session |
There was a problem hiding this comment.
should add a logger here?
| await this.#setupDefaultTransport(); | ||
| // We don't actually want getStoredTransport to return this transport | ||
| // since we aren't connecting it to the wallet with a CAIP session, only | ||
| // listening for wallet_sessionChanged events. | ||
| await this.storage.removeTransport(); |
There was a problem hiding this comment.
nit: Alternatively we could add a passive parameter to #setupDefaultTransport to skip the storage write?
| // Passive init may fail if extension transport isn't ready | ||
| } | ||
| } | ||
| this.status = 'loaded'; |
There was a problem hiding this comment.
Heads up — there's a status flicker here. When init() fires, it emits wallet_sessionChanged synchronously through #notifyCallbacks, which hits #onTransportNotification and sets this.status to 'connected' or 'disconnected' (depending on scopes). Then this line immediately overwrites it with 'loaded'. Since the status setter emits stateChanged to consumers on every write, they'll see a quick connected → loaded (or disconnected → loaded) flash — two state change notifications in rapid succession where the first one is misleading.
Explanation
Uses the DefaultTransport on init if no existing transport is stored, extension is found, and extensionIsPreferred. Ensures that wallet_sessionChanged events are listened to and MultichainClient.status is correctly updated.
To test, use
window.ethereum.request({method: 'eth_requestAccounts})in console to trigger the permission flow outside of the MMC lifecycleReferences
Checklist