-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat(SDKConnectV2): RPC Message Handling #19823
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
base: main
Are you sure you want to change the base?
Changes from 33 commits
d45777e
ecbcb51
29f423f
855538d
e41952f
d334490
9694bb5
bb91e79
d7d5d15
9ecdc20
cae9e08
a1094e3
d5218ae
d6597b5
6f8e949
bafb39c
c10bdfa
36f594b
2b54787
b4175ad
b279bad
0e3b230
80315ac
a9564ab
d3bef95
135242a
4d080d5
ac874d3
3bf4f40
b4621ea
f09ea0c
ed1e9ef
275177e
6a93ad6
720be35
6e324e7
a9b33f3
cf346cc
b358a86
347a5e1
f2abed5
20dcf70
478041c
fd27ebe
071750a
511d26a
a768428
07b03b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,172 @@ | ||||
import EventEmitter from 'eventemitter2'; | ||||
import BackgroundBridge from '../../BackgroundBridge/BackgroundBridge'; | ||||
import { Connection } from '../services/connection'; | ||||
import { IRPCBridgeAdapter } from '../types/rpc-bridge-adapter'; | ||||
import Engine from '../../Engine'; | ||||
import AppConstants from '../../AppConstants'; | ||||
import getRpcMethodMiddleware from '../../RPCMethods/RPCMethodMiddleware'; | ||||
import { ImageSourcePropType } from 'react-native'; | ||||
|
||||
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); | ||||
|
||||
export class RPCBridgeAdapter | ||||
extends EventEmitter | ||||
implements IRPCBridgeAdapter | ||||
{ | ||||
private client: BackgroundBridge | null = null; | ||||
private queue: unknown[] = []; | ||||
private processing = false; | ||||
private connection: Connection; | ||||
|
||||
constructor(connection: Connection) { | ||||
super(); | ||||
this.connection = connection; | ||||
this.initialize(); | ||||
} | ||||
|
||||
/** | ||||
* Sends a request to the background bridge. | ||||
*/ | ||||
public send(request: unknown): void { | ||||
this.queue.push(request); | ||||
this.processQueue(); // Attempt to process the request immediately | ||||
} | ||||
|
||||
/** | ||||
* Asynchronously sets up listeners for wallet state changes. | ||||
*/ | ||||
private async initialize() { | ||||
while (!Engine.context?.KeyringController) { | ||||
await wait(10); | ||||
} | ||||
|
||||
const messenger = Engine.controllerMessenger; | ||||
messenger.subscribe('KeyringController:lock', this.onLock); | ||||
messenger.subscribe('KeyringController:unlock', this.onUnlock); | ||||
} | ||||
|
||||
/** | ||||
* Handles the wallet lock event. | ||||
*/ | ||||
private onLock = () => { | ||||
console.warn('[SDKConnectV2] Wallet locked.'); | ||||
// TODO: What do we do here? Do we reject the queue? Do we discard the background bridge client? | ||||
}; | ||||
|
||||
/** | ||||
* Handles the wallet unlock event. | ||||
*/ | ||||
private onUnlock = () => { | ||||
console.warn( | ||||
'[SDKConnectV2] Wallet unlocked. Attempting to process queue.', | ||||
); | ||||
// The unlock event is a signal to try processing whatever is in the queue. | ||||
this.processQueue(); | ||||
}; | ||||
|
||||
/** | ||||
* The processQueue will process the queue of requests in a FIFO manner. | ||||
*/ | ||||
private async processQueue(): Promise<void> { | ||||
// Gate 1: Don't run if already processing or if the queue is empty | ||||
if (this.processing || this.queue.length === 0) { | ||||
console.warn( | ||||
'[SDKConnectV2] Processing halted: Queue is empty or already processing.', | ||||
); | ||||
return; | ||||
} | ||||
|
||||
// Gate 2: Don't process requests if the wallet is locked | ||||
if (!Engine.context.KeyringController.isUnlocked()) { | ||||
console.warn('[SDKConnectV2] Processing halted: Wallet is locked.'); | ||||
return; | ||||
} | ||||
|
||||
this.processing = true; | ||||
|
||||
try { | ||||
if (!this.client) { | ||||
this.client = this.createClient(); | ||||
} | ||||
|
||||
while (this.queue.length > 0) { | ||||
const request = this.queue.shift(); | ||||
if (this.client && request) { | ||||
this.client.onMessage({ | ||||
name: 'metamask-provider', | ||||
data: request, | ||||
}); | ||||
} | ||||
} | ||||
} catch (error) { | ||||
console.error('[SDKConnectV2] Error during queue processing:', error); | ||||
// TODO: What do we do here? Do we reject the request? Do we reject the full queue? Do we wait or return an error to the dapp? | ||||
// TODO: Do we use "@metamask/rpc-errors" here? | ||||
} finally { | ||||
this.processing = false; | ||||
} | ||||
} | ||||
chakra-guy marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
|
||||
/** | ||||
* Creates a new BackgroundBridge instance configured for our use case. | ||||
*/ | ||||
private createClient(): BackgroundBridge { | ||||
console.warn('[SDKConnectV2] Creating BackgroundBridge client.'); | ||||
const middlewareHostname = `${AppConstants.MM_SDK.SDK_REMOTE_ORIGIN}${this.connection.id}`; | ||||
|
||||
return new BackgroundBridge({ | ||||
webview: null, | ||||
isRemoteConn: true, | ||||
isMMSDK: true, | ||||
channelId: this.connection.id, | ||||
url: this.connection.metadata.dapp.url, | ||||
remoteConnHost: this.connection.metadata.dapp.url, | ||||
sendMessage: (response: unknown) => { | ||||
this.emit('response', response); | ||||
}, | ||||
getRpcMethodMiddleware: ({ | ||||
getProviderState, | ||||
}: { | ||||
getProviderState: any; | ||||
}) => | ||||
getRpcMethodMiddleware({ | ||||
hostname: middlewareHostname, | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this be the origin value? previously There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sadly I don't think we can do that. A bunch of old code relies on the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More context: a bunch of routing and UI code relies on this prefix There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm... Well I've made this change to setupBridge and I don't think I saw any issues come out of it. Does this seem right or wrong? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! If that works we should be fine (OR we might have regressions on the sdk v1 we don't know about). Will try this out though! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update: it looks like
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update: In the end I decided to not shoehorn, but the have a clean separation between v1 and v2. |
||||
channelId: this.connection.id, | ||||
getProviderState, | ||||
isMMSDK: true, | ||||
url: { current: this.connection.metadata.dapp.url }, | ||||
title: { current: this.connection.metadata.dapp.name }, | ||||
icon: { | ||||
current: this.connection.metadata.dapp.icon as ImageSourcePropType, | ||||
}, | ||||
navigation: null, | ||||
isHomepage: () => false, | ||||
fromHomepage: { current: false }, | ||||
tabId: '', | ||||
isWalletConnect: false, | ||||
analytics: { | ||||
isRemoteConn: true, | ||||
platform: | ||||
this.connection.metadata.sdk.platform ?? | ||||
AppConstants.MM_SDK.UNKNOWN_PARAM, | ||||
}, | ||||
toggleUrlModal: () => null, | ||||
injectHomePageScripts: () => null, | ||||
}), | ||||
isMainFrame: true, | ||||
getApprovedHosts: () => ({ | ||||
[this.connection.metadata.dapp.url]: true, | ||||
}), | ||||
isWalletConnect: false, | ||||
wcRequestActions: undefined, | ||||
}); | ||||
} | ||||
|
||||
public dispose(): void { | ||||
const messenger = Engine.controllerMessenger; | ||||
messenger.unsubscribe('KeyringController:lock', this.onLock); | ||||
messenger.unsubscribe('KeyringController:unlock', this.onUnlock); | ||||
this.client?.onDisconnect(); | ||||
this.removeAllListeners(); | ||||
} | ||||
} |
Uh oh!
There was an error while loading. Please reload this page.