From bfec9bcaa2cdf1dfc691be797de7133fef71babf Mon Sep 17 00:00:00 2001 From: abretonc7s Date: Mon, 20 Nov 2023 20:43:01 +0800 Subject: [PATCH 1/7] feat: prioritize deeplink connection --- app/core/SDKConnect/SDKConnect.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/core/SDKConnect/SDKConnect.ts b/app/core/SDKConnect/SDKConnect.ts index 83e66e0fa58..ac3e0776559 100644 --- a/app/core/SDKConnect/SDKConnect.ts +++ b/app/core/SDKConnect/SDKConnect.ts @@ -421,8 +421,11 @@ export class SDKConnect extends EventEmitter2 { let interruptReason = ''; - if (connecting) { + if (connecting && trigger !== 'deeplink') { interruptReason = 'already connecting'; + } else if (connecting && trigger === 'deeplink') { + console.warn(`Priotity to deeplink - overwrite previous connection`); + this.removeChannel(channelId); } if (!this.connections[channelId]) { @@ -642,7 +645,6 @@ export class SDKConnect extends EventEmitter2 { delete this.connected[channelId]; delete this.connections[channelId]; - delete this.connecting[channelId]; delete this.approvedHosts[ AppConstants.MM_SDK.SDK_REMOTE_ORIGIN + channelId ]; @@ -662,6 +664,7 @@ export class SDKConnect extends EventEmitter2 { throw err; }); } + delete this.connecting[channelId]; this.emit('refresh'); } From 0cae1e2a1c20727992ca892ff1112c01f70dc2aa Mon Sep 17 00:00:00 2001 From: abretonc7s Date: Mon, 20 Nov 2023 20:45:11 +0800 Subject: [PATCH 2/7] feat: typings --- app/core/SDKConnect/SDKConnect.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/core/SDKConnect/SDKConnect.ts b/app/core/SDKConnect/SDKConnect.ts index ac3e0776559..361e65d16c6 100644 --- a/app/core/SDKConnect/SDKConnect.ts +++ b/app/core/SDKConnect/SDKConnect.ts @@ -109,7 +109,7 @@ export class SDKConnect extends EventEmitter2 { private disabledHosts: ApprovedHosts = {}; private rpcqueueManager = new RPCQueueManager(); private appStateListener: NativeEventSubscription | undefined; - private socketServerUrl = AppConstants.MM_SDK.SERVER_URL; // Allow to customize different socket server url + private socketServerUrl: string = AppConstants.MM_SDK.SERVER_URL; // Allow to customize different socket server url private SDKConnect() { // Keep empty to manage singleton From 8470379262ca31e0e0ef8ede947e1a1e9874f287 Mon Sep 17 00:00:00 2001 From: abretonc7s Date: Tue, 21 Nov 2023 21:38:36 +0800 Subject: [PATCH 3/7] feat: overwrite without destroing previous connection --- app/core/SDKConnect/SDKConnect.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/core/SDKConnect/SDKConnect.ts b/app/core/SDKConnect/SDKConnect.ts index 361e65d16c6..2ee7dc9a82a 100644 --- a/app/core/SDKConnect/SDKConnect.ts +++ b/app/core/SDKConnect/SDKConnect.ts @@ -425,7 +425,6 @@ export class SDKConnect extends EventEmitter2 { interruptReason = 'already connecting'; } else if (connecting && trigger === 'deeplink') { console.warn(`Priotity to deeplink - overwrite previous connection`); - this.removeChannel(channelId); } if (!this.connections[channelId]) { From af2c10ddbc672709627dbb89f884b3457d8a8cf4 Mon Sep 17 00:00:00 2001 From: abretonc7s Date: Thu, 23 Nov 2023 19:05:49 +0800 Subject: [PATCH 4/7] feat: refactor sdk Connection class --- app/core/DeeplinkManager.js | 2 +- app/core/SDKConnect/Connection.ts | 653 +----------------- app/core/SDKConnect/SDKConnect.ts | 29 +- .../SDKConnect/handlers/checkPermissions.ts | 98 +++ .../handlers/handleBatchRpcResponse.ts | 110 +++ .../handlers/handleConnectionMessage.ts | 219 ++++++ .../handlers/handleConnectionReady.ts | 191 +++++ .../{ => handlers}/handleDeeplink.ts | 10 +- .../SDKConnect/handlers/handleRpcOverwrite.ts | 22 + .../SDKConnect/handlers/handleSendMessage.ts | 106 +++ app/core/SDKConnect/handlers/setupBridge.ts | 104 +++ app/core/SDKConnect/utils/DevLogger.ts | 2 +- 12 files changed, 910 insertions(+), 636 deletions(-) create mode 100644 app/core/SDKConnect/handlers/checkPermissions.ts create mode 100644 app/core/SDKConnect/handlers/handleBatchRpcResponse.ts create mode 100644 app/core/SDKConnect/handlers/handleConnectionMessage.ts create mode 100644 app/core/SDKConnect/handlers/handleConnectionReady.ts rename app/core/SDKConnect/{ => handlers}/handleDeeplink.ts (92%) create mode 100644 app/core/SDKConnect/handlers/handleRpcOverwrite.ts create mode 100644 app/core/SDKConnect/handlers/handleSendMessage.ts create mode 100644 app/core/SDKConnect/handlers/setupBridge.ts diff --git a/app/core/DeeplinkManager.js b/app/core/DeeplinkManager.js index 07ac7100007..01fb82d21f7 100644 --- a/app/core/DeeplinkManager.js +++ b/app/core/DeeplinkManager.js @@ -25,7 +25,7 @@ import Engine from './Engine'; import { Minimizer } from './NativeModules'; import DevLogger from './SDKConnect/utils/DevLogger'; import WC2Manager from './WalletConnect/WalletConnectV2'; -import handleDeeplink from './SDKConnect/handleDeeplink'; +import handleDeeplink from './SDKConnect/handlers/handleDeeplink'; import Logger from '../../app/util/Logger'; class DeeplinkManager { diff --git a/app/core/SDKConnect/Connection.ts b/app/core/SDKConnect/Connection.ts index 0e7d9d412c0..6cc7e5fc587 100644 --- a/app/core/SDKConnect/Connection.ts +++ b/app/core/SDKConnect/Connection.ts @@ -1,16 +1,9 @@ -import { Platform } from 'react-native'; import Logger from '../../util/Logger'; import AppConstants from '../AppConstants'; import BackgroundBridge from '../BackgroundBridge/BackgroundBridge'; import Engine from '../Engine'; -import getRpcMethodMiddleware, { - ApprovalTypes, - RPCMethodsMiddleParameters, -} from '../RPCMethods/RPCMethodMiddleware'; -import { ApprovalController } from '@metamask/approval-controller'; import { KeyringController } from '@metamask/keyring-controller'; -import { PreferencesController } from '@metamask/preferences-controller'; import { CommunicationLayerMessage, CommunicationLayerPreference, @@ -19,31 +12,19 @@ import { OriginatorInfo, RemoteCommunication, } from '@metamask/sdk-communication-layer'; -import { Json } from '@metamask/utils'; import { NavigationContainerRef } from '@react-navigation/native'; -import { ethErrors } from 'eth-rpc-errors'; import { EventEmitter2 } from 'eventemitter2'; -import { PROTOCOLS } from '../../constants/deeplinks'; -import Routes from '../../constants/navigation/Routes'; -import { Minimizer } from '../NativeModules'; -import BatchRPCManager, { BatchRPCState } from './BatchRPCManager'; +import BatchRPCManager from './BatchRPCManager'; import RPCQueueManager from './RPCQueueManager'; import { ApprovedHosts, CONNECTION_LOADING_EVENT, - HOUR_IN_MS, - METHODS_TO_DELAY, - METHODS_TO_REDIRECT, approveHostProps, } from './SDKConnect'; +import { handleConnectionMessage } from './handlers/handleConnectionMessage'; +import handleConnectionReady from './handlers/handleConnectionReady'; import DevLogger from './utils/DevLogger'; -import generateOTP from './utils/generateOTP.util'; -import { - wait, - waitForConnectionReadiness, - waitForKeychainUnlocked, -} from './utils/wait.util'; -import Device from '../../util/device'; +import { waitForKeychainUnlocked } from './utils/wait.util'; export interface ConnectionProps { id: string; @@ -65,6 +46,7 @@ const { version } = require('../../../package.json'); export const RPC_METHODS = { METAMASK_GETPROVIDERSTATE: 'metamask_getProviderState', METAMASK_CONNECTSIGN: 'metamask_connectSign', + METAMASK_CONNECTWITH: 'metamask_connectWith', METAMASK_BATCH: 'metamask_batch', PERSONAL_SIGN: 'personal_sign', ETH_SIGN: 'eth_sign', @@ -122,17 +104,18 @@ export class Connection extends EventEmitter2 { */ otps?: number[]; + approvalPromise?: Promise; + /** * Should only be accesses via getter / setter. */ - private _loading = false; - private approvalPromise?: Promise; + _loading = false; - private rpcQueueManager: RPCQueueManager; + rpcQueueManager: RPCQueueManager; - private batchRPCManager: BatchRPCManager; + batchRPCManager: BatchRPCManager; - private socketServerUrl: string; + socketServerUrl: string; approveHost: ({ host, hostname }: approveHostProps) => void; getApprovedHosts: (context: string) => ApprovedHosts; @@ -240,8 +223,6 @@ export class Connection extends EventEmitter2 { this.requestsToRedirect = {}; - this.sendMessage = this.sendMessage.bind(this); - this.remote.on(EventType.CLIENTS_CONNECTED, () => { DevLogger.log( `Connection::CLIENTS_CONNECTED id=${this.channelId} receivedDisconnect=${this.receivedDisconnect} origin=${this.origin}`, @@ -311,293 +292,48 @@ export class Connection extends EventEmitter2 { this.remote.on( EventType.CLIENTS_READY, async (clientsReadyMsg: { originatorInfo: OriginatorInfo }) => { - const approvalController = ( - Engine.context as { ApprovalController: ApprovalController } - ).ApprovalController; - - // clients_ready may be sent multple time (from sdk <0.2.0). - const updatedOriginatorInfo = clientsReadyMsg?.originatorInfo; - const apiVersion = updatedOriginatorInfo?.apiVersion; - this.receivedClientsReady = true; - - // backward compatibility with older sdk -- always first request approval - if (!apiVersion) { - // clear previous pending approval - if (approvalController.get(this.channelId)) { - approvalController.reject( - this.channelId, - ethErrors.provider.userRejectedRequest(), - ); - } - - this.approvalPromise = undefined; - } - - DevLogger.log( - `SDKConnect::CLIENTS_READY id=${this.channelId} apiVersion=${apiVersion}`, - ); - if (!updatedOriginatorInfo) { - return; - } - - this.originatorInfo = updatedOriginatorInfo; - updateOriginatorInfos({ - channelId: this.channelId, - originatorInfo: updatedOriginatorInfo, - }); - - if (this.isReady) { - return; - } - - // TODO following logic blocks should be simplified (too many conditions) - // Should be done in a separate PR to avoid breaking changes and separate SDKConnect / Connection logic in different files. - if ( - this.initialConnection && - this.origin === AppConstants.DEEPLINKS.ORIGIN_QR_CODE - ) { - // Ask for authorisation? - // Always need to re-approve connection first. - await this.checkPermissions({ - lastAuthorized: this.lastAuthorized, - }); - - this.sendAuthorized(true); - } else if ( - !this.initialConnection && - this.origin === AppConstants.DEEPLINKS.ORIGIN_QR_CODE - ) { - const currentTime = Date.now(); - - const OTPExpirationDuration = - Number(process.env.OTP_EXPIRATION_DURATION_IN_MS) || HOUR_IN_MS; - - const channelWasActiveRecently = - !!this.lastAuthorized && - currentTime - this.lastAuthorized < OTPExpirationDuration; - - if (channelWasActiveRecently) { - this.approvalPromise = undefined; - - // Prevent auto approval if metamask is killed and restarted - disapprove(this.channelId); - - // Always need to re-approve connection first. - await this.checkPermissions({ - lastAuthorized: this.lastAuthorized, - }); - - this.sendAuthorized(true); - } else { - if (approvalController.get(this.channelId)) { - // cleaning previous pending approval - approvalController.reject( - this.channelId, - ethErrors.provider.userRejectedRequest(), - ); - } - this.approvalPromise = undefined; - - if (!this.otps) { - this.otps = generateOTP(); - } - this.sendMessage({ - type: MessageType.OTP, - otpAnswer: this.otps?.[0], - }).catch((err) => { - Logger.log(err, `SDKConnect:: Connection failed to send otp`); - }); - // Prevent auto approval if metamask is killed and restarted - disapprove(this.channelId); - - // Always need to re-approve connection first. - await this.checkPermissions(); - this.sendAuthorized(true); - this.lastAuthorized = Date.now(); - } - } else if ( - !this.initialConnection && - (this.origin === AppConstants.DEEPLINKS.ORIGIN_DEEPLINK || - this.trigger === 'deeplink') - ) { - // Deeplink channels are automatically approved on re-connection. - const hostname = - AppConstants.MM_SDK.SDK_REMOTE_ORIGIN + this.channelId; - approveHost({ - host: hostname, - hostname, - context: 'clients_ready', + try { + await handleConnectionReady({ + originatorInfo: clientsReadyMsg.originatorInfo, + Engine, + updateOriginatorInfos, + approveHost, + disapprove, + rpcQueueManager: this.rpcQueueManager, + batchRpcManager: this.batchRPCManager, + connection: this, }); - this.remote - .sendMessage({ type: 'authorized' as MessageType }) - .catch((err) => { - Logger.log(err, `Connection failed to send 'authorized`); - }); - } else if ( - this.initialConnection && - this.origin === AppConstants.DEEPLINKS.ORIGIN_DEEPLINK - ) { - // Should ask for confirmation to reconnect? - await this.checkPermissions(); - this.sendAuthorized(true); + } catch (error) { + Logger.error(error as Error, 'Connection not initialized'); + throw error; } - - this.setupBridge(updatedOriginatorInfo); - this.isReady = true; }, ); this.remote.on( EventType.MESSAGE, async (message: CommunicationLayerMessage) => { - // TODO should probably handle this in a separate EventType.TERMINATE event. - // handle termination message - if (message.type === MessageType.TERMINATE) { - // Delete connection from storage - this.onTerminate({ channelId: this.channelId }); - return; - } else if (message.type === 'ping') { - DevLogger.log(`Connection::ping id=${this.channelId}`); - return; - } - - // ignore anything other than RPC methods - if (!message.method || !message.id) { - return; - } - - const lcMethod = message.method.toLowerCase(); - let needsRedirect = METHODS_TO_REDIRECT[message?.method] ?? false; - - if (needsRedirect) { - this.requestsToRedirect[message?.id] = true; - } - - // Keep this section only for backward compatibility otherwise metamask doesn't redirect properly. if ( - !this.originatorInfo?.apiVersion && - !needsRedirect && - // this.originatorInfo?.platform !== 'unity' && - lcMethod === RPC_METHODS.METAMASK_GETPROVIDERSTATE.toLowerCase() + !this.backgroundBridge || + !this.rpcQueueManager || + !this.batchRPCManager ) { - // Manually force redirect if apiVersion isn't defined for backward compatibility - needsRedirect = true; - this.requestsToRedirect[message?.id] = true; + throw new Error('Connection not initialized'); } - // Wait for keychain to be unlocked before handling rpc calls. - const keyringController = ( - Engine.context as { KeyringController: KeyringController } - ).KeyringController; - await waitForKeychainUnlocked({ - keyringController, - context: 'connection::on_message', - }); - - this.setLoading(false); - - // Wait for bridge to be ready before handling messages. - // It will wait until user accept/reject the connection request. try { - await this.checkPermissions({ message }); - if (!this.receivedDisconnect) { - await waitForConnectionReadiness({ connection: this }); - this.sendAuthorized(); - } else { - // Reset state to continue communication after reconnection. - this.isReady = true; - this.receivedDisconnect = false; - } - } catch (error) { - // Approval failed - redirect to app with error. - this.sendMessage({ - data: { - error, - id: message.id, - jsonrpc: '2.0', - }, - name: 'metamask-provider', - }).catch(() => { - Logger.log(error, `Connection approval failed`); - }); - this.approvalPromise = undefined; - return; - } - - // Special case for metamask_connectSign - if (lcMethod === RPC_METHODS.METAMASK_CONNECTSIGN.toLowerCase()) { - // Replace with personal_sign - message.method = RPC_METHODS.PERSONAL_SIGN; - if ( - !( - message?.params && - Array.isArray(message.params) && - message.params.length > 0 - ) - ) { - throw new Error('Invalid message format'); - } - - if (Platform.OS === 'ios') { - // TODO: why does ios (older devices) requires a delay after request is initially approved? - await wait(1000); - } - // Append selected address to params - const preferencesController = ( - Engine.context as { - PreferencesController: PreferencesController; - } - ).PreferencesController; - const selectedAddress = preferencesController.state.selectedAddress; - message.params = [(message.params as string[])[0], selectedAddress]; - - Logger.log( - `metamask_connectSign selectedAddress=${selectedAddress}`, - message.params, - ); - } else if (lcMethod === RPC_METHODS.METAMASK_BATCH.toLowerCase()) { - DevLogger.log(`metamask_batch`, JSON.stringify(message, null, 2)); - if ( - !( - message?.params && - Array.isArray(message.params) && - message.params.length > 0 - ) - ) { - throw new Error('Invalid message format'); - } - const rpcs = message.params; - // Add rpcs to the batch manager - this.batchRPCManager.add({ id: message.id, rpcs }); - - // Send the first rpc method to the background bridge - const rpc = rpcs[0]; - rpc.id = message.id + `_0`; // Add index to id to keep track of the order - rpc.jsonrpc = '2.0'; - DevLogger.log( - `metamask_batch method=${rpc.method} id=${rpc.id}`, - rpc.params, - ); - - this.backgroundBridge?.onMessage({ - name: 'metamask-provider', - data: rpc, - origin: 'sdk', + await handleConnectionMessage({ + message, + Engine, + backgroundBridge: this.backgroundBridge, + rpcQueueManager: this.rpcQueueManager, + batchRpcManager: this.batchRPCManager, + connection: this, }); - - return; + } catch (error) { + Logger.error(error as Error, 'Connection not initialized'); + throw error; } - - this.rpcQueueManager.add({ - id: (message.id as string) ?? 'unknown', - method: message.method, - }); - - this.backgroundBridge?.onMessage({ - name: 'metamask-provider', - data: message, - origin: 'sdk', - }); }, ); } @@ -636,167 +372,6 @@ export class Connection extends EventEmitter2 { return this._loading; } - private setupBridge(originatorInfo: OriginatorInfo) { - if (this.backgroundBridge) { - return; - } - - this.backgroundBridge = new BackgroundBridge({ - webview: null, - isMMSDK: true, - // TODO: need to rewrite backgroundBridge to directly provide the origin instead of url format. - url: PROTOCOLS.METAMASK + '://' + AppConstants.MM_SDK.SDK_REMOTE_ORIGIN, - isRemoteConn: true, - sendMessage: this.sendMessage, - getApprovedHosts: () => this.getApprovedHosts('backgroundBridge'), - remoteConnHost: this.host, - getRpcMethodMiddleware: ({ - getProviderState, - }: RPCMethodsMiddleParameters) => { - DevLogger.log( - `getRpcMethodMiddleware hostname=${this.host} url=${originatorInfo.url} `, - ); - return getRpcMethodMiddleware({ - hostname: this.host, - getProviderState, - isMMSDK: true, - navigation: null, //props.navigation, - getApprovedHosts: () => this.getApprovedHosts('rpcMethodMiddleWare'), - setApprovedHosts: (hostname: string) => { - this.approveHost({ - host: hostname, - hostname, - context: 'setApprovedHosts', - }); - }, - approveHost: (approveHostname) => - this.approveHost({ - host: this.host, - hostname: approveHostname, - context: 'rpcMethodMiddleWare', - }), - // Website info - url: { - current: originatorInfo?.url, - }, - title: { - current: originatorInfo?.title, - }, - icon: { current: originatorInfo?.icon }, - // Bookmarks - isHomepage: () => false, - // Show autocomplete - fromHomepage: { current: false }, - // Wizard - wizardScrollAdjusted: { current: false }, - tabId: '', - isWalletConnect: false, - analytics: { - isRemoteConn: true, - platform: - originatorInfo?.platform ?? AppConstants.MM_SDK.UNKNOWN_PARAM, - }, - toggleUrlModal: () => null, - injectHomePageScripts: () => null, - }); - }, - isMainFrame: true, - isWalletConnect: false, - wcRequestActions: undefined, - }); - } - - /** - * Check if current channel has been allowed. - * - * @param message - * @returns {boolean} true when host is approved or user approved the request. - * @throws error if the user reject approval request. - */ - private async checkPermissions({ - // eslint-disable-next-line - message, - lastAuthorized, - }: { - message?: CommunicationLayerMessage; - lastAuthorized?: number; - } = {}): Promise { - const OTPExpirationDuration = - Number(process.env.OTP_EXPIRATION_DURATION_IN_MS) || HOUR_IN_MS; - - const channelWasActiveRecently = - !!lastAuthorized && Date.now() - lastAuthorized < OTPExpirationDuration; - - DevLogger.log( - `SDKConnect checkPermissions initialConnection=${this.initialConnection} lastAuthorized=${lastAuthorized} OTPExpirationDuration ${OTPExpirationDuration} channelWasActiveRecently ${channelWasActiveRecently}`, - ); - // only ask approval if needed - const approved = this.isApproved({ - channelId: this.channelId, - context: 'checkPermission', - }); - - const preferencesController = ( - Engine.context as { PreferencesController: PreferencesController } - ).PreferencesController; - const selectedAddress = preferencesController.state.selectedAddress; - - if (approved && selectedAddress) { - return true; - } - - const approvalController = ( - Engine.context as { ApprovalController: ApprovalController } - ).ApprovalController; - - if (this.approvalPromise) { - // Wait for result and clean the promise afterwards. - await this.approvalPromise; - this.approvalPromise = undefined; - return true; - } - - if (!this.initialConnection && AppConstants.DEEPLINKS.ORIGIN_DEEPLINK) { - this.revalidate({ channelId: this.channelId }); - } - - if (channelWasActiveRecently) { - return true; - } - - const approvalRequest = { - origin: this.origin, - type: ApprovalTypes.CONNECT_ACCOUNTS, - requestData: { - hostname: this.originatorInfo?.title ?? '', - pageMeta: { - channelId: this.channelId, - reconnect: !this.initialConnection, - origin: this.origin, - url: this.originatorInfo?.url ?? '', - title: this.originatorInfo?.title ?? '', - icon: this.originatorInfo?.icon ?? '', - otps: this.otps ?? [], - apiVersion: this.originatorInfo?.apiVersion, - analytics: { - request_source: AppConstants.REQUEST_SOURCES.SDK_REMOTE_CONN, - request_platform: - this.originatorInfo?.platform ?? - AppConstants.MM_SDK.UNKNOWN_PARAM, - }, - } as Json, - }, - id: this.channelId, - }; - this.approvalPromise = approvalController.add(approvalRequest); - - await this.approvalPromise; - // Clear previous permissions if already approved. - this.revalidate({ channelId: this.channelId }); - this.approvalPromise = undefined; - return true; - } - pause() { this.remote.pause(); } @@ -850,156 +425,4 @@ export class Connection extends EventEmitter2 { this.backgroundBridge?.onDisconnect(); this.setLoading(false); } - - async handleBatchRpcResponse({ - chainRpcs, - msg, - }: { - chainRpcs: BatchRPCState; - msg: any; - }) { - const isLastRpc = chainRpcs.index === chainRpcs.rpcs.length - 1; - const hasError = msg?.data?.error; - const origRpcId = parseInt(chainRpcs.baseId); - const result = chainRpcs.rpcs - .filter((rpc) => rpc.response !== undefined) - .map((rpc) => rpc.response); - result.push(msg?.data?.result); - - DevLogger.log( - `handleChainRpcResponse origRpcId=${origRpcId} isLastRpc=${isLastRpc} hasError=${hasError}`, - chainRpcs, - ); - - if (hasError) { - // Cancel the whole chain if any of the rpcs fails, send previous responses with current error - const data = { - id: origRpcId, - jsonrpc: '2.0', - result, - error: msg?.data?.error, - }; - const response = { - data, - name: 'metamask-provider', - }; - await this.sendMessage(response); - // Delete the chain from the chainRPCManager - this.batchRPCManager.remove(chainRpcs.baseId); - } else if (isLastRpc) { - // Respond to the original rpc call with the list of responses append the current response - DevLogger.log( - `handleChainRpcResponse id=${chainRpcs.baseId} result`, - result, - ); - const data = { - id: origRpcId, - jsonrpc: '2.0', - result, - }; - const response = { - data, - name: 'metamask-provider', - }; - await this.sendMessage(response); - // Delete the chain from the chainRPCManager - this.batchRPCManager.remove(chainRpcs.baseId); - } else { - // Save response and send the next rpc method - this.batchRPCManager.addResponse({ - id: chainRpcs.baseId, - index: chainRpcs.index, - response: msg?.data?.result, - }); - - // wait 1s before sending the next rpc method To give user time to process UI feedbacks - await wait(1000); - - // Send the next rpc method to the background bridge - const nextRpc = chainRpcs.rpcs[chainRpcs.index + 1]; - nextRpc.id = chainRpcs.baseId + `_${chainRpcs.index + 1}`; // Add index to id to keep track of the order - nextRpc.jsonrpc = '2.0'; - DevLogger.log( - `handleChainRpcResponse method=${nextRpc.method} id=${nextRpc.id}`, - nextRpc.params, - ); - - this.backgroundBridge?.onMessage({ - name: 'metamask-provider', - data: nextRpc, - origin: 'sdk', - }); - } - } - - async sendMessage(msg: any) { - const msgId = msg?.data?.id + ''; - const needsRedirect = this.requestsToRedirect[msgId] !== undefined; - const method = this.rpcQueueManager.getId(msgId); - - DevLogger.log(`Connection::sendMessage`, msg); - // handle multichain rpc call responses separately - const chainRPCs = this.batchRPCManager.getById(msgId); - if (chainRPCs) { - await this.handleBatchRpcResponse({ chainRpcs: chainRPCs, msg }); - return; - } - - if (msgId && method) { - this.rpcQueueManager.remove(msgId); - } - - this.remote.sendMessage(msg).catch((err) => { - Logger.log(err, `Connection::sendMessage failed to send`); - }); - - DevLogger.log( - `Connection::sendMessage method=${method} trigger=${this.trigger} id=${msgId} needsRedirect=${needsRedirect} origin=${this.origin}`, - ); - - if (!needsRedirect) { - return; - } - - delete this.requestsToRedirect[msgId]; - - // hide modal - this.setLoading(false); - - if (this.origin === AppConstants.DEEPLINKS.ORIGIN_QR_CODE) return; - - if (!this.rpcQueueManager.isEmpty()) { - DevLogger.log(`Connection::sendMessage NOT empty --- skip goBack()`); - return; - } - - if (this.trigger !== 'deeplink') { - DevLogger.log(`Connection::sendMessage NOT deeplink --- skip goBack()`); - return; - } - - try { - if (METHODS_TO_DELAY[method]) { - await wait(1200); - } - - DevLogger.log( - `Connection::sendMessage method=${method} trigger=${this.trigger} origin=${this.origin} id=${msgId} goBack()`, - ); - - // Check for iOS 17 and above to use a custom modal, as Minimizer.goBack() is incompatible with these versions - if (Device.isIos() && parseInt(Platform.Version as string) >= 17) { - this.navigation?.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.RETURN_TO_DAPP_MODAL, - }); - } else { - await Minimizer.goBack(); - } - } catch (err) { - Logger.log( - err, - `Connection::sendMessage error while waiting for empty rpc queue`, - ); - } - } } diff --git a/app/core/SDKConnect/SDKConnect.ts b/app/core/SDKConnect/SDKConnect.ts index 2ee7dc9a82a..085a3b8d9e7 100644 --- a/app/core/SDKConnect/SDKConnect.ts +++ b/app/core/SDKConnect/SDKConnect.ts @@ -281,22 +281,23 @@ export class SDKConnect extends EventEmitter2 { this.sdkLoadingState[channelId] = true; } else { delete this.sdkLoadingState[channelId]; + await this.hideLoadingState(); } - const loadingSessions = Object.keys(this.sdkLoadingState).length; - if (loadingSessions > 0) { - this.navigation?.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.SDK_LOADING, - }); - } else { - const currentRoute = this.navigation?.getCurrentRoute()?.name; - if (currentRoute === Routes.SHEET.SDK_LOADING) { - DevLogger.log(`updateSDKLoadingState - goBack`); - this.navigation?.goBack(); - } else { - DevLogger.log(`updateSDKLoadingState - currentRoute=${currentRoute}`); - } - } + // const loadingSessions = Object.keys(this.sdkLoadingState).length; + // if (loadingSessions > 0) { + // this.navigation?.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + // screen: Routes.SHEET.SDK_LOADING, + // }); + // } else { + // const currentRoute = this.navigation?.getCurrentRoute()?.name; + // if (currentRoute === Routes.SHEET.SDK_LOADING) { + // DevLogger.log(`updateSDKLoadingState - goBack`); + // this.navigation?.goBack(); + // } else { + // DevLogger.log(`updateSDKLoadingState - currentRoute=${currentRoute}`); + // } + // } } public async hideLoadingState() { diff --git a/app/core/SDKConnect/handlers/checkPermissions.ts b/app/core/SDKConnect/handlers/checkPermissions.ts new file mode 100644 index 00000000000..16d795a77e1 --- /dev/null +++ b/app/core/SDKConnect/handlers/checkPermissions.ts @@ -0,0 +1,98 @@ +import { CommunicationLayerMessage } from '@metamask/sdk-communication-layer'; +import { Connection } from '../Connection'; +import { HOUR_IN_MS } from '../SDKConnect'; +import DevLogger from '../utils/DevLogger'; +import AppConstants from '../../AppConstants'; +import { ApprovalTypes } from '../../RPCMethods/RPCMethodMiddleware'; +import { PreferencesController } from '@metamask/preferences-controller'; +import { ApprovalController } from '@metamask/approval-controller'; +import { Json } from '@metamask/utils'; + +// TODO: should be more generic and be used in wallet connect and android service as well +export const checkPermissions = async ({ + connection, + Engine, + lastAuthorized, +}: { + connection: Connection; + Engine: any; + message?: CommunicationLayerMessage; + lastAuthorized?: number; +}) => { + const OTPExpirationDuration = + Number(process.env.OTP_EXPIRATION_DURATION_IN_MS) || HOUR_IN_MS; + + const channelWasActiveRecently = + !!lastAuthorized && Date.now() - lastAuthorized < OTPExpirationDuration; + + DevLogger.log( + `SDKConnect checkPermissions initialConnection=${connection.initialConnection} lastAuthorized=${lastAuthorized} OTPExpirationDuration ${OTPExpirationDuration} channelWasActiveRecently ${channelWasActiveRecently}`, + ); + // only ask approval if needed + const approved = connection.isApproved({ + channelId: connection.channelId, + context: 'checkPermission', + }); + + const preferencesController = ( + Engine.context as { PreferencesController: PreferencesController } + ).PreferencesController; + const selectedAddress = preferencesController.state.selectedAddress; + + if (approved && selectedAddress) { + return true; + } + + const approvalController = ( + Engine.context as { ApprovalController: ApprovalController } + ).ApprovalController; + + if (connection.approvalPromise) { + // Wait for result and clean the promise afterwards. + await connection.approvalPromise; + connection.approvalPromise = undefined; + return true; + } + + if (!connection.initialConnection && AppConstants.DEEPLINKS.ORIGIN_DEEPLINK) { + connection.revalidate({ channelId: connection.channelId }); + } + + if (channelWasActiveRecently) { + return true; + } + + const approvalRequest = { + origin: connection.origin, + type: ApprovalTypes.CONNECT_ACCOUNTS, + requestData: { + hostname: connection.originatorInfo?.title ?? '', + pageMeta: { + channelId: connection.channelId, + reconnect: !connection.initialConnection, + origin: connection.origin, + url: connection.originatorInfo?.url ?? '', + title: connection.originatorInfo?.title ?? '', + icon: connection.originatorInfo?.icon ?? '', + otps: connection.otps ?? [], + apiVersion: connection.originatorInfo?.apiVersion, + analytics: { + request_source: AppConstants.REQUEST_SOURCES.SDK_REMOTE_CONN, + request_platform: + connection.originatorInfo?.platform ?? + AppConstants.MM_SDK.UNKNOWN_PARAM, + }, + } as Json, + }, + id: connection.channelId, + }; + connection.approvalPromise = approvalController.add(approvalRequest); + + await connection.approvalPromise; + // Clear previous permissions if already approved. + connection.revalidate({ channelId: connection.channelId }); + connection.approvalPromise = undefined; + return true; +}; + +export default checkPermissions; diff --git a/app/core/SDKConnect/handlers/handleBatchRpcResponse.ts b/app/core/SDKConnect/handlers/handleBatchRpcResponse.ts new file mode 100644 index 00000000000..29696c7cf4e --- /dev/null +++ b/app/core/SDKConnect/handlers/handleBatchRpcResponse.ts @@ -0,0 +1,110 @@ +import BackgroundBridge from 'app/core/BackgroundBridge/BackgroundBridge'; +import BatchRPCManager, { BatchRPCState } from '../BatchRPCManager'; +import { Connection } from '../Connection'; +import DevLogger from '../utils/DevLogger'; +import { wait } from '../utils/wait.util'; +import handleSendMessage from './handleSendMessage'; +import RPCQueueManager from '../RPCQueueManager'; + +export const handleBatchRpcResponse = async ({ + chainRpcs, + msg, + rpcQueueManager, + backgroundBridge, + batchRpcManager, + connection, +}: { + chainRpcs: BatchRPCState; + rpcQueueManager: RPCQueueManager; + backgroundBridge?: BackgroundBridge; + batchRpcManager: BatchRPCManager; + connection: Connection; + msg: any; +}): Promise => { + const isLastRpc = chainRpcs.index === chainRpcs.rpcs.length - 1; + const hasError = msg?.data?.error; + const origRpcId = parseInt(chainRpcs.baseId); + const result = chainRpcs.rpcs + .filter((rpc) => rpc.response !== undefined) + .map((rpc) => rpc.response); + result.push(msg?.data?.result); + + DevLogger.log( + `handleChainRpcResponse origRpcId=${origRpcId} isLastRpc=${isLastRpc} hasError=${hasError}`, + chainRpcs, + ); + + if (hasError) { + // Cancel the whole chain if any of the rpcs fails, send previous responses with current error + const data = { + id: `${origRpcId}`, + jsonrpc: '2.0', + result, + error: msg?.data?.error, + }; + const response = { + data, + name: 'metamask-provider', + }; + await handleSendMessage({ + msg: response, + connection, + backgroundBridge, + batchRpcManager, + rpcQueueManager, + }); + // Delete the chain from the chainRPCManager + batchRpcManager.remove(chainRpcs.baseId); + } else if (isLastRpc) { + // Respond to the original rpc call with the list of responses append the current response + DevLogger.log( + `handleChainRpcResponse id=${chainRpcs.baseId} result`, + result, + ); + const data = { + id: `${origRpcId}`, + jsonrpc: '2.0', + result, + }; + const response = { + data, + name: 'metamask-provider', + }; + await handleSendMessage({ + msg: response, + connection, + backgroundBridge, + batchRpcManager, + rpcQueueManager, + }); + // Delete the chain from the chainRPCManager + batchRpcManager.remove(chainRpcs.baseId); + } else { + // Save response and send the next rpc method + batchRpcManager.addResponse({ + id: chainRpcs.baseId, + index: chainRpcs.index, + response: msg?.data?.result, + }); + + // wait 1s before sending the next rpc method To give user time to process UI feedbacks + await wait(1000); + + // Send the next rpc method to the background bridge + const nextRpc = chainRpcs.rpcs[chainRpcs.index + 1]; + nextRpc.id = chainRpcs.baseId + `_${chainRpcs.index + 1}`; // Add index to id to keep track of the order + nextRpc.jsonrpc = '2.0'; + DevLogger.log( + `handleChainRpcResponse method=${nextRpc.method} id=${nextRpc.id}`, + nextRpc.params, + ); + + backgroundBridge?.onMessage({ + name: 'metamask-provider', + data: nextRpc, + origin: 'sdk', + }); + } +}; + +export default handleBatchRpcResponse; diff --git a/app/core/SDKConnect/handlers/handleConnectionMessage.ts b/app/core/SDKConnect/handlers/handleConnectionMessage.ts new file mode 100644 index 00000000000..8f98d3d0e84 --- /dev/null +++ b/app/core/SDKConnect/handlers/handleConnectionMessage.ts @@ -0,0 +1,219 @@ +import { KeyringController } from '@metamask/keyring-controller'; +import { PreferencesController } from '@metamask/preferences-controller'; +import { + CommunicationLayerMessage, + MessageType, +} from '@metamask/sdk-communication-layer'; +import { Platform } from 'react-native'; +import Logger from '../../../util/Logger'; +import BackgroundBridge from '../../BackgroundBridge/BackgroundBridge'; +import BatchRPCManager from '../BatchRPCManager'; +import { Connection, RPC_METHODS } from '../Connection'; +import RPCQueueManager from '../RPCQueueManager'; +import { METHODS_TO_REDIRECT } from '../SDKConnect'; +import DevLogger from '../utils/DevLogger'; +import { + wait, + waitForConnectionReadiness, + waitForKeychainUnlocked, +} from '../utils/wait.util'; +import checkPermissions from './checkPermissions'; +import handleSendMessage from './handleSendMessage'; + +export const handleConnectionMessage = async ({ + message, + Engine, + backgroundBridge, + rpcQueueManager, + batchRpcManager, + connection, +}: { + message: CommunicationLayerMessage; + Engine: any; + rpcQueueManager: RPCQueueManager; + backgroundBridge: BackgroundBridge; + batchRpcManager: BatchRPCManager; + connection: Connection; +}) => { + // TODO should probably handle this in a separate EventType.TERMINATE event. + // handle termination message + if (message.type === MessageType.TERMINATE) { + // Delete connection from storage + connection.onTerminate({ channelId: connection.channelId }); + return; + } else if (message.type === 'ping') { + DevLogger.log(`Connection::ping id=${connection.channelId}`); + return; + } + + // ignore anything other than RPC methods + if (!message.method || !message.id) { + return; + } + + const lcMethod = message.method.toLowerCase(); + let needsRedirect = METHODS_TO_REDIRECT[message?.method] ?? false; + + if (needsRedirect) { + connection.requestsToRedirect[message?.id] = true; + } + + // Keep this section only for backward compatibility otherwise metamask doesn't redirect properly. + if ( + !connection.originatorInfo?.apiVersion && + !needsRedirect && + // connection.originatorInfo?.platform !== 'unity' && + lcMethod === RPC_METHODS.METAMASK_GETPROVIDERSTATE.toLowerCase() + ) { + // Manually force redirect if apiVersion isn't defined for backward compatibility + needsRedirect = true; + connection.requestsToRedirect[message?.id] = true; + } + + // Wait for keychain to be unlocked before handling rpc calls. + const keyringController = ( + Engine.context as { KeyringController: KeyringController } + ).KeyringController; + await waitForKeychainUnlocked({ + keyringController, + context: 'connection::on_message', + }); + + connection.setLoading(false); + + const preferencesController = ( + Engine.context as { + PreferencesController: PreferencesController; + } + ).PreferencesController; + const selectedAddress = preferencesController.state.selectedAddress; + + // Wait for bridge to be ready before handling messages. + // It will wait until user accept/reject the connection request. + try { + await checkPermissions({ message, connection, Engine }); + if (!connection.receivedDisconnect) { + await waitForConnectionReadiness({ connection }); + connection.sendAuthorized(); + } else { + // Reset state to continue communication after reconnection. + connection.isReady = true; + connection.receivedDisconnect = false; + } + } catch (error) { + // Approval failed - redirect to app with error. + const msg = { + data: { + error, + id: message.id, + jsonrpc: '2.0', + }, + name: 'metamask-provider', + }; + handleSendMessage({ + msg, + backgroundBridge, + batchRpcManager, + connection, + rpcQueueManager, + }).catch(() => { + Logger.log(error, `Connection approval failed`); + }); + connection.approvalPromise = undefined; + return; + } + + // Special case for metamask_connectSign + if (lcMethod === RPC_METHODS.METAMASK_CONNECTWITH.toLowerCase()) { + // format of the message: + // { method: 'metamask_connectWith', params: [ { method: 'personalSign' | 'eth_sendTransaction', params: any[] ] } ] } } + if ( + !( + message?.params && + Array.isArray(message.params) && + message.params.length > 0 + ) + ) { + throw new Error('Invalid message format'); + } + + // Extract the rpc method from the params + const rpc = message.params[0]; + message.message = rpc.method; + message.params = rpc.params; + // Replace message.params with the selected address + + if (Platform.OS === 'ios') { + // TODO: why does ios (older devices) requires a delay after request is initially approved? + await wait(1000); + } + } else if (lcMethod === RPC_METHODS.METAMASK_CONNECTSIGN.toLowerCase()) { + // Replace with personal_sign + message.method = RPC_METHODS.PERSONAL_SIGN; + if ( + !( + message?.params && + Array.isArray(message.params) && + message.params.length > 0 + ) + ) { + throw new Error('Invalid message format'); + } + + if (Platform.OS === 'ios') { + // TODO: why does ios (older devices) requires a delay after request is initially approved? + await wait(1000); + } + + message.params = [(message.params as string[])[0], selectedAddress]; + + Logger.log( + `metamask_connectSign selectedAddress=${selectedAddress}`, + message.params, + ); + } else if (lcMethod === RPC_METHODS.METAMASK_BATCH.toLowerCase()) { + DevLogger.log(`metamask_batch`, JSON.stringify(message, null, 2)); + if ( + !( + message?.params && + Array.isArray(message.params) && + message.params.length > 0 + ) + ) { + throw new Error('Invalid message format'); + } + const rpcs = message.params; + // Add rpcs to the batch manager + batchRpcManager.add({ id: message.id, rpcs }); + + // Send the first rpc method to the background bridge + const rpc = rpcs[0]; + rpc.id = message.id + `_0`; // Add index to id to keep track of the order + rpc.jsonrpc = '2.0'; + DevLogger.log( + `metamask_batch method=${rpc.method} id=${rpc.id}`, + rpc.params, + ); + + connection.backgroundBridge?.onMessage({ + name: 'metamask-provider', + data: rpc, + origin: 'sdk', + }); + + return; + } + + rpcQueueManager.add({ + id: (message.id as string) ?? 'unknown', + method: message.method, + }); + + backgroundBridge.onMessage({ + name: 'metamask-provider', + data: message, + origin: 'sdk', + }); +}; + +export default handleConnectionMessage; diff --git a/app/core/SDKConnect/handlers/handleConnectionReady.ts b/app/core/SDKConnect/handlers/handleConnectionReady.ts new file mode 100644 index 00000000000..6777ba43364 --- /dev/null +++ b/app/core/SDKConnect/handlers/handleConnectionReady.ts @@ -0,0 +1,191 @@ +import { ApprovalController } from '@metamask/approval-controller'; +import { MessageType, OriginatorInfo } from '@metamask/sdk-communication-layer'; +import AppConstants from '../../../../app/core/AppConstants'; +import Logger from '../../../util/Logger'; +import BatchRPCManager from '../BatchRPCManager'; +import { Connection } from '../Connection'; +import RPCQueueManager from '../RPCQueueManager'; +import DevLogger from '../utils/DevLogger'; +import checkPermissions from './checkPermissions'; +import handleSendMessage from './handleSendMessage'; + +import { ethErrors } from 'eth-rpc-errors'; +import { HOUR_IN_MS, approveHostProps } from '../SDKConnect'; +import generateOTP from '../utils/generateOTP.util'; +import { setupBridge } from './setupBridge'; + +export const handleConnectionReady = async ({ + originatorInfo, + Engine, + rpcQueueManager, + batchRpcManager, + connection, + approveHost, + disapprove, + updateOriginatorInfos, +}: { + originatorInfo: OriginatorInfo; + Engine: any; + rpcQueueManager: RPCQueueManager; + batchRpcManager: BatchRPCManager; + connection: Connection; + approveHost: ({ host, hostname }: approveHostProps) => void; + disapprove: (channelId: string) => void; + updateOriginatorInfos: (params: { + channelId: string; + originatorInfo: OriginatorInfo; + }) => void; +}) => { + const approvalController = ( + Engine.context as { ApprovalController: ApprovalController } + ).ApprovalController; + + // clients_ready may be sent multple time (from sdk <0.2.0). + const apiVersion = originatorInfo?.apiVersion; + connection.receivedClientsReady = true; + + // backward compatibility with older sdk -- always first request approval + if (!apiVersion) { + // clear previous pending approval + if (approvalController.get(connection.channelId)) { + approvalController.reject( + connection.channelId, + ethErrors.provider.userRejectedRequest(), + ); + } + + connection.approvalPromise = undefined; + } + + DevLogger.log( + `SDKConnect::CLIENTS_READY id=${connection.channelId} apiVersion=${apiVersion}`, + ); + if (!originatorInfo) { + return; + } + + connection.originatorInfo = originatorInfo; + updateOriginatorInfos({ + channelId: connection.channelId, + originatorInfo, + }); + + if (connection.isReady) { + DevLogger.log(`SDKConnect::CLIENTS_READY already ready`); + return; + } + + // TODO following logic blocks should be simplified (too many conditions) + // Should be done in a separate PR to avoid breaking changes and separate SDKConnect / Connection logic in different files. + if ( + connection.initialConnection && + connection.origin === AppConstants.DEEPLINKS.ORIGIN_QR_CODE + ) { + // Ask for authorisation? + // Always need to re-approve connection first. + await checkPermissions({ + connection, + Engine, + lastAuthorized: connection.lastAuthorized, + }); + + connection.sendAuthorized(true); + } else if ( + !connection.initialConnection && + connection.origin === AppConstants.DEEPLINKS.ORIGIN_QR_CODE + ) { + const currentTime = Date.now(); + + const OTPExpirationDuration = + Number(process.env.OTP_EXPIRATION_DURATION_IN_MS) || HOUR_IN_MS; + + const channelWasActiveRecently = + !!connection.lastAuthorized && + currentTime - connection.lastAuthorized < OTPExpirationDuration; + + if (channelWasActiveRecently) { + connection.approvalPromise = undefined; + + // Prevent auto approval if metamask is killed and restarted + disapprove(connection.channelId); + + // Always need to re-approve connection first. + await checkPermissions({ + connection, + Engine, + lastAuthorized: connection.lastAuthorized, + }); + + connection.sendAuthorized(true); + } else { + if (approvalController.get(connection.channelId)) { + // cleaning previous pending approval + approvalController.reject( + connection.channelId, + ethErrors.provider.userRejectedRequest(), + ); + } + connection.approvalPromise = undefined; + + if (!connection.otps) { + connection.otps = generateOTP(); + } + + const msg = { + type: MessageType.OTP, + otpAnswer: connection.otps?.[0], + }; + handleSendMessage({ + msg, + connection, + batchRpcManager, + rpcQueueManager, + }).catch((err) => { + Logger.log(err, `SDKConnect:: Connection failed to send otp`); + }); + // Prevent auto approval if metamask is killed and restarted + disapprove(connection.channelId); + + // Always need to re-approve connection first. + await checkPermissions({ + connection, + Engine, + }); + connection.sendAuthorized(true); + connection.lastAuthorized = Date.now(); + } + } else if ( + !connection.initialConnection && + (connection.origin === AppConstants.DEEPLINKS.ORIGIN_DEEPLINK || + connection.trigger === 'deeplink') + ) { + // Deeplink channels are automatically approved on re-connection. + const hostname = + AppConstants.MM_SDK.SDK_REMOTE_ORIGIN + connection.channelId; + approveHost({ + host: hostname, + hostname, + context: 'clients_ready', + }); + connection.remote + .sendMessage({ type: 'authorized' as MessageType }) + .catch((err: Error) => { + Logger.log(err, `Connection failed to send 'authorized`); + }); + } else if ( + connection.initialConnection && + connection.origin === AppConstants.DEEPLINKS.ORIGIN_DEEPLINK + ) { + // Should ask for confirmation to reconnect? + await checkPermissions({ connection, Engine }); + connection.sendAuthorized(true); + } + + connection.backgroundBridge = setupBridge({ + originatorInfo, + connection, + }); + connection.isReady = true; +}; + +export default handleConnectionReady; diff --git a/app/core/SDKConnect/handleDeeplink.ts b/app/core/SDKConnect/handlers/handleDeeplink.ts similarity index 92% rename from app/core/SDKConnect/handleDeeplink.ts rename to app/core/SDKConnect/handlers/handleDeeplink.ts index 8f8e70bd31f..3cffd1aa4ae 100644 --- a/app/core/SDKConnect/handleDeeplink.ts +++ b/app/core/SDKConnect/handlers/handleDeeplink.ts @@ -1,8 +1,8 @@ -import AppConstants from '../AppConstants'; -import SDKConnect from './SDKConnect'; -import DevLogger from './utils/DevLogger'; -import { waitForCondition } from './utils/wait.util'; -import Logger from '../../util/Logger'; +import AppConstants from '../../AppConstants'; +import SDKConnect from '../SDKConnect'; +import DevLogger from '../utils/DevLogger'; +import { waitForCondition } from '../utils/wait.util'; +import Logger from '../../../util/Logger'; const QRCODE_PARAM_PATTERN = '&t=q'; diff --git a/app/core/SDKConnect/handlers/handleRpcOverwrite.ts b/app/core/SDKConnect/handlers/handleRpcOverwrite.ts new file mode 100644 index 00000000000..5c69b75b53e --- /dev/null +++ b/app/core/SDKConnect/handlers/handleRpcOverwrite.ts @@ -0,0 +1,22 @@ +import { RPC_METHODS } from '../Connection'; +import DevLogger from '../utils/DevLogger'; + +export const overwriteRPCWith = ({ + rpc, + accountAddress, +}: { + rpc: { method: string; params: any; [key: string]: any }; + accountAddress: string; +}) => { + // Handle + if (rpc.method.toLowerCase() === RPC_METHODS.PERSONAL_SIGN.toLowerCase()) { + // Replace address value with the selected address + rpc.params = [rpc.params[0], accountAddress]; + } else { + DevLogger.log(`overwriteRPCWith:: method=${rpc.method} not handled`); + } + + return rpc; +}; + +export default overwriteRPCWith; diff --git a/app/core/SDKConnect/handlers/handleSendMessage.ts b/app/core/SDKConnect/handlers/handleSendMessage.ts new file mode 100644 index 00000000000..1477b581a59 --- /dev/null +++ b/app/core/SDKConnect/handlers/handleSendMessage.ts @@ -0,0 +1,106 @@ +import Routes from '../../../../app/constants/navigation/Routes'; +import AppConstants from '../../../../app/core/AppConstants'; +import BackgroundBridge from '../../BackgroundBridge/BackgroundBridge'; +import { Platform } from 'react-native'; +import Logger from '../../../util/Logger'; +import Device from '../../../util/device'; +import { Minimizer } from '../../NativeModules'; +import BatchRPCManager from '../BatchRPCManager'; +import { Connection } from '../Connection'; +import RPCQueueManager from '../RPCQueueManager'; +import { METHODS_TO_DELAY } from '../SDKConnect'; +import DevLogger from '../utils/DevLogger'; +import { wait } from '../utils/wait.util'; +import handleBatchRpcResponse from './handleBatchRpcResponse'; + +export const handleSendMessage = async ({ + msg, + rpcQueueManager, + backgroundBridge, + batchRpcManager, + connection, +}: { + msg: any; + rpcQueueManager: RPCQueueManager; + backgroundBridge?: BackgroundBridge; + batchRpcManager: BatchRPCManager; + connection: Connection; +}) => { + const msgId = msg?.data?.id + ''; + const needsRedirect = connection.requestsToRedirect[msgId] !== undefined; + const method = rpcQueueManager.getId(msgId); + + DevLogger.log(`Connection::sendMessage`, msg); + // handle multichain rpc call responses separately + const chainRPCs = batchRpcManager.getById(msgId); + if (chainRPCs) { + await handleBatchRpcResponse({ + chainRpcs: chainRPCs, + msg, + connection, + batchRpcManager, + rpcQueueManager, + backgroundBridge, + }); + return; + } + + if (msgId && method) { + rpcQueueManager.remove(msgId); + } + + connection.remote.sendMessage(msg).catch((err) => { + Logger.log(err, `Connection::sendMessage failed to send`); + }); + + DevLogger.log( + `Connection::sendMessage method=${method} trigger=${connection.trigger} id=${msgId} needsRedirect=${needsRedirect} origin=${connection.origin}`, + ); + + if (!needsRedirect) { + return; + } + + delete connection.requestsToRedirect[msgId]; + + // hide modal + connection.setLoading(false); + + if (connection.origin === AppConstants.DEEPLINKS.ORIGIN_QR_CODE) return; + + if (!rpcQueueManager.isEmpty()) { + DevLogger.log(`Connection::sendMessage NOT empty --- skip goBack()`); + return; + } + + if (connection.trigger !== 'deeplink') { + DevLogger.log(`Connection::sendMessage NOT deeplink --- skip goBack()`); + return; + } + + try { + if (METHODS_TO_DELAY[method]) { + await wait(1200); + } + + DevLogger.log( + `Connection::sendMessage method=${method} trigger=${connection.trigger} origin=${connection.origin} id=${msgId} goBack()`, + ); + + // Check for iOS 17 and above to use a custom modal, as Minimizer.goBack() is incompatible with these versions + if (Device.isIos() && parseInt(Platform.Version as string) >= 17) { + connection.navigation?.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.RETURN_TO_DAPP_MODAL, + }); + } else { + await Minimizer.goBack(); + } + } catch (err) { + Logger.log( + err, + `Connection::sendMessage error while waiting for empty rpc queue`, + ); + } +}; + +export default handleSendMessage; diff --git a/app/core/SDKConnect/handlers/setupBridge.ts b/app/core/SDKConnect/handlers/setupBridge.ts new file mode 100644 index 00000000000..668dcf342cc --- /dev/null +++ b/app/core/SDKConnect/handlers/setupBridge.ts @@ -0,0 +1,104 @@ +import AppConstants from '../../AppConstants'; +import BackgroundBridge from '../../BackgroundBridge/BackgroundBridge'; +import getRpcMethodMiddleware, { + RPCMethodsMiddleParameters, +} from '../../RPCMethods/RPCMethodMiddleware'; + +import { OriginatorInfo } from '@metamask/sdk-communication-layer'; +import { PROTOCOLS } from '../../../constants/deeplinks'; +import DevLogger from '../utils/DevLogger'; +import { Connection } from '../Connection'; +import handleSendMessage from './handleSendMessage'; +import Logger from '../../../util/Logger'; + +export const setupBridge = ({ + originatorInfo, + connection, +}: { + originatorInfo: OriginatorInfo; + connection: Connection; +}): BackgroundBridge => { + if (connection.backgroundBridge) { + DevLogger.log(`setupBridge:: backgroundBridge already exists`); + return connection.backgroundBridge; + } + + const backgroundBridge = new BackgroundBridge({ + webview: null, + isMMSDK: true, + // TODO: need to rewrite backgroundBridge to directly provide the origin instead of url format. + url: PROTOCOLS.METAMASK + '://' + AppConstants.MM_SDK.SDK_REMOTE_ORIGIN, + isRemoteConn: true, + sendMessage: (msg: any) => { + handleSendMessage({ + msg, + connection, + batchRpcManager: connection.batchRPCManager, + rpcQueueManager: connection.rpcQueueManager, + backgroundBridge: connection.backgroundBridge, + }).catch((err) => { + Logger.error(err, 'Connection::sendMessage failed to send'); + }); + }, + getApprovedHosts: () => connection.getApprovedHosts('backgroundBridge'), + remoteConnHost: connection.host, + getRpcMethodMiddleware: ({ + getProviderState, + }: RPCMethodsMiddleParameters) => { + DevLogger.log( + `getRpcMethodMiddleware hostname=${connection.host} url=${originatorInfo.url} `, + ); + return getRpcMethodMiddleware({ + hostname: connection.host, + getProviderState, + isMMSDK: true, + navigation: null, //props.navigation, + getApprovedHosts: () => + connection.getApprovedHosts('rpcMethodMiddleWare'), + setApprovedHosts: (hostname: string) => { + connection.approveHost({ + host: hostname, + hostname, + context: 'setApprovedHosts', + }); + }, + approveHost: (approveHostname) => + connection.approveHost({ + host: connection.host, + hostname: approveHostname, + context: 'rpcMethodMiddleWare', + }), + // Website info + url: { + current: originatorInfo?.url, + }, + title: { + current: originatorInfo?.title, + }, + icon: { current: undefined }, + // Bookmarks + isHomepage: () => false, + // Show autocomplete + fromHomepage: { current: false }, + // Wizard + wizardScrollAdjusted: { current: false }, + tabId: '', + isWalletConnect: false, + analytics: { + isRemoteConn: true, + platform: + originatorInfo?.platform ?? AppConstants.MM_SDK.UNKNOWN_PARAM, + }, + toggleUrlModal: () => null, + injectHomePageScripts: () => null, + }); + }, + isMainFrame: true, + isWalletConnect: false, + wcRequestActions: undefined, + }); + + return backgroundBridge; +}; + +export default setupBridge; diff --git a/app/core/SDKConnect/utils/DevLogger.ts b/app/core/SDKConnect/utils/DevLogger.ts index de1afab7a60..461a7da7696 100644 --- a/app/core/SDKConnect/utils/DevLogger.ts +++ b/app/core/SDKConnect/utils/DevLogger.ts @@ -1,4 +1,4 @@ -const DevLogger = { +export const DevLogger = { log: (...args: any[]) => { if (process.env.SDK_DEV === 'DEV') { // eslint-disable-next-line no-console From 8f89cd92d975bd7a9406d797657ee8a9e47a6878 Mon Sep 17 00:00:00 2001 From: abretonc7s Date: Thu, 23 Nov 2023 20:36:38 +0800 Subject: [PATCH 5/7] feat: refactor Connection --- app/core/SDKConnect/Connection.ts | 15 +---- .../handlers/handleBatchRpcResponse.ts | 24 ++----- .../handlers/handleConnectionMessage.ts | 63 ++++++++----------- .../handlers/handleConnectionReady.ts | 16 +++-- .../SDKConnect/handlers/handleSendMessage.ts | 22 ++----- app/core/SDKConnect/handlers/setupBridge.ts | 8 +-- 6 files changed, 47 insertions(+), 101 deletions(-) diff --git a/app/core/SDKConnect/Connection.ts b/app/core/SDKConnect/Connection.ts index 6cc7e5fc587..9045c080b5c 100644 --- a/app/core/SDKConnect/Connection.ts +++ b/app/core/SDKConnect/Connection.ts @@ -299,12 +299,10 @@ export class Connection extends EventEmitter2 { updateOriginatorInfos, approveHost, disapprove, - rpcQueueManager: this.rpcQueueManager, - batchRpcManager: this.batchRPCManager, connection: this, }); } catch (error) { - Logger.error(error as Error, 'Connection not initialized'); + DevLogger.log(`Connection::CLIENTS_READY error`, error); throw error; } }, @@ -313,21 +311,10 @@ export class Connection extends EventEmitter2 { this.remote.on( EventType.MESSAGE, async (message: CommunicationLayerMessage) => { - if ( - !this.backgroundBridge || - !this.rpcQueueManager || - !this.batchRPCManager - ) { - throw new Error('Connection not initialized'); - } - try { await handleConnectionMessage({ message, Engine, - backgroundBridge: this.backgroundBridge, - rpcQueueManager: this.rpcQueueManager, - batchRpcManager: this.batchRPCManager, connection: this, }); } catch (error) { diff --git a/app/core/SDKConnect/handlers/handleBatchRpcResponse.ts b/app/core/SDKConnect/handlers/handleBatchRpcResponse.ts index 29696c7cf4e..540e0d427fc 100644 --- a/app/core/SDKConnect/handlers/handleBatchRpcResponse.ts +++ b/app/core/SDKConnect/handlers/handleBatchRpcResponse.ts @@ -1,23 +1,15 @@ -import BackgroundBridge from 'app/core/BackgroundBridge/BackgroundBridge'; -import BatchRPCManager, { BatchRPCState } from '../BatchRPCManager'; +import { BatchRPCState } from '../BatchRPCManager'; import { Connection } from '../Connection'; import DevLogger from '../utils/DevLogger'; import { wait } from '../utils/wait.util'; import handleSendMessage from './handleSendMessage'; -import RPCQueueManager from '../RPCQueueManager'; export const handleBatchRpcResponse = async ({ chainRpcs, msg, - rpcQueueManager, - backgroundBridge, - batchRpcManager, connection, }: { chainRpcs: BatchRPCState; - rpcQueueManager: RPCQueueManager; - backgroundBridge?: BackgroundBridge; - batchRpcManager: BatchRPCManager; connection: Connection; msg: any; }): Promise => { @@ -49,12 +41,9 @@ export const handleBatchRpcResponse = async ({ await handleSendMessage({ msg: response, connection, - backgroundBridge, - batchRpcManager, - rpcQueueManager, }); // Delete the chain from the chainRPCManager - batchRpcManager.remove(chainRpcs.baseId); + connection.batchRPCManager.remove(chainRpcs.baseId); } else if (isLastRpc) { // Respond to the original rpc call with the list of responses append the current response DevLogger.log( @@ -73,15 +62,12 @@ export const handleBatchRpcResponse = async ({ await handleSendMessage({ msg: response, connection, - backgroundBridge, - batchRpcManager, - rpcQueueManager, }); // Delete the chain from the chainRPCManager - batchRpcManager.remove(chainRpcs.baseId); + connection.batchRPCManager.remove(chainRpcs.baseId); } else { // Save response and send the next rpc method - batchRpcManager.addResponse({ + connection.batchRPCManager.addResponse({ id: chainRpcs.baseId, index: chainRpcs.index, response: msg?.data?.result, @@ -99,7 +85,7 @@ export const handleBatchRpcResponse = async ({ nextRpc.params, ); - backgroundBridge?.onMessage({ + connection.backgroundBridge?.onMessage({ name: 'metamask-provider', data: nextRpc, origin: 'sdk', diff --git a/app/core/SDKConnect/handlers/handleConnectionMessage.ts b/app/core/SDKConnect/handlers/handleConnectionMessage.ts index 8f98d3d0e84..063ab466916 100644 --- a/app/core/SDKConnect/handlers/handleConnectionMessage.ts +++ b/app/core/SDKConnect/handlers/handleConnectionMessage.ts @@ -6,10 +6,7 @@ import { } from '@metamask/sdk-communication-layer'; import { Platform } from 'react-native'; import Logger from '../../../util/Logger'; -import BackgroundBridge from '../../BackgroundBridge/BackgroundBridge'; -import BatchRPCManager from '../BatchRPCManager'; import { Connection, RPC_METHODS } from '../Connection'; -import RPCQueueManager from '../RPCQueueManager'; import { METHODS_TO_REDIRECT } from '../SDKConnect'; import DevLogger from '../utils/DevLogger'; import { @@ -23,16 +20,10 @@ import handleSendMessage from './handleSendMessage'; export const handleConnectionMessage = async ({ message, Engine, - backgroundBridge, - rpcQueueManager, - batchRpcManager, connection, }: { message: CommunicationLayerMessage; Engine: any; - rpcQueueManager: RPCQueueManager; - backgroundBridge: BackgroundBridge; - batchRpcManager: BatchRPCManager; connection: Connection; }) => { // TODO should probably handle this in a separate EventType.TERMINATE event. @@ -48,6 +39,7 @@ export const handleConnectionMessage = async ({ // ignore anything other than RPC methods if (!message.method || !message.id) { + DevLogger.log(`Connection::onMessage invalid message`, message); return; } @@ -88,6 +80,7 @@ export const handleConnectionMessage = async ({ ).PreferencesController; const selectedAddress = preferencesController.state.selectedAddress; + DevLogger.log(`Connection::onMessage`, message); // Wait for bridge to be ready before handling messages. // It will wait until user accept/reject the connection request. try { @@ -112,10 +105,7 @@ export const handleConnectionMessage = async ({ }; handleSendMessage({ msg, - backgroundBridge, - batchRpcManager, connection, - rpcQueueManager, }).catch(() => { Logger.log(error, `Connection approval failed`); }); @@ -125,28 +115,27 @@ export const handleConnectionMessage = async ({ // Special case for metamask_connectSign if (lcMethod === RPC_METHODS.METAMASK_CONNECTWITH.toLowerCase()) { - // format of the message: - // { method: 'metamask_connectWith', params: [ { method: 'personalSign' | 'eth_sendTransaction', params: any[] ] } ] } } - if ( - !( - message?.params && - Array.isArray(message.params) && - message.params.length > 0 - ) - ) { - throw new Error('Invalid message format'); - } - - // Extract the rpc method from the params - const rpc = message.params[0]; - message.message = rpc.method; - message.params = rpc.params; - // Replace message.params with the selected address - - if (Platform.OS === 'ios') { - // TODO: why does ios (older devices) requires a delay after request is initially approved? - await wait(1000); - } + // TODO activate once refactor is vetted. + // // format of the message: + // // { method: 'metamask_connectWith', params: [ { method: 'personalSign' | 'eth_sendTransaction', params: any[] ] } ] } } + // if ( + // !( + // message?.params && + // Array.isArray(message.params) && + // message.params.length > 0 + // ) + // ) { + // throw new Error('Invalid message format'); + // } + // // Extract the rpc method from the params + // const rpc = message.params[0]; + // message.message = rpc.method; + // message.params = rpc.params; + // // Replace message.params with the selected address + // if (Platform.OS === 'ios') { + // // TODO: why does ios (older devices) requires a delay after request is initially approved? + // await wait(1000); + // } } else if (lcMethod === RPC_METHODS.METAMASK_CONNECTSIGN.toLowerCase()) { // Replace with personal_sign message.method = RPC_METHODS.PERSONAL_SIGN; @@ -184,7 +173,7 @@ export const handleConnectionMessage = async ({ } const rpcs = message.params; // Add rpcs to the batch manager - batchRpcManager.add({ id: message.id, rpcs }); + connection.batchRPCManager.add({ id: message.id, rpcs }); // Send the first rpc method to the background bridge const rpc = rpcs[0]; @@ -204,12 +193,12 @@ export const handleConnectionMessage = async ({ return; } - rpcQueueManager.add({ + connection.rpcQueueManager.add({ id: (message.id as string) ?? 'unknown', method: message.method, }); - backgroundBridge.onMessage({ + connection.backgroundBridge?.onMessage({ name: 'metamask-provider', data: message, origin: 'sdk', diff --git a/app/core/SDKConnect/handlers/handleConnectionReady.ts b/app/core/SDKConnect/handlers/handleConnectionReady.ts index 6777ba43364..299dec495a1 100644 --- a/app/core/SDKConnect/handlers/handleConnectionReady.ts +++ b/app/core/SDKConnect/handlers/handleConnectionReady.ts @@ -2,9 +2,7 @@ import { ApprovalController } from '@metamask/approval-controller'; import { MessageType, OriginatorInfo } from '@metamask/sdk-communication-layer'; import AppConstants from '../../../../app/core/AppConstants'; import Logger from '../../../util/Logger'; -import BatchRPCManager from '../BatchRPCManager'; import { Connection } from '../Connection'; -import RPCQueueManager from '../RPCQueueManager'; import DevLogger from '../utils/DevLogger'; import checkPermissions from './checkPermissions'; import handleSendMessage from './handleSendMessage'; @@ -17,8 +15,6 @@ import { setupBridge } from './setupBridge'; export const handleConnectionReady = async ({ originatorInfo, Engine, - rpcQueueManager, - batchRpcManager, connection, approveHost, disapprove, @@ -26,8 +22,6 @@ export const handleConnectionReady = async ({ }: { originatorInfo: OriginatorInfo; Engine: any; - rpcQueueManager: RPCQueueManager; - batchRpcManager: BatchRPCManager; connection: Connection; approveHost: ({ host, hostname }: approveHostProps) => void; disapprove: (channelId: string) => void; @@ -58,7 +52,7 @@ export const handleConnectionReady = async ({ } DevLogger.log( - `SDKConnect::CLIENTS_READY id=${connection.channelId} apiVersion=${apiVersion}`, + `SDKConnect::CLIENTS_READY id=${connection.channelId} apiVersion=${apiVersion} origin=${connection.origin} trigger=${connection.trigger}`, ); if (!originatorInfo) { return; @@ -69,6 +63,10 @@ export const handleConnectionReady = async ({ channelId: connection.channelId, originatorInfo, }); + DevLogger.log( + `SDKConnect::CLIENTS_READY originatorInfo updated`, + originatorInfo, + ); if (connection.isReady) { DevLogger.log(`SDKConnect::CLIENTS_READY already ready`); @@ -119,6 +117,7 @@ export const handleConnectionReady = async ({ connection.sendAuthorized(true); } else { if (approvalController.get(connection.channelId)) { + DevLogger.log(`SDKConnect::CLIENTS_READY reject previous approval`); // cleaning previous pending approval approvalController.reject( connection.channelId, @@ -138,8 +137,6 @@ export const handleConnectionReady = async ({ handleSendMessage({ msg, connection, - batchRpcManager, - rpcQueueManager, }).catch((err) => { Logger.log(err, `SDKConnect:: Connection failed to send otp`); }); @@ -181,6 +178,7 @@ export const handleConnectionReady = async ({ connection.sendAuthorized(true); } + DevLogger.log(`SDKConnect::CLIENTS_READY setup bridge`); connection.backgroundBridge = setupBridge({ originatorInfo, connection, diff --git a/app/core/SDKConnect/handlers/handleSendMessage.ts b/app/core/SDKConnect/handlers/handleSendMessage.ts index 1477b581a59..f68bb6af775 100644 --- a/app/core/SDKConnect/handlers/handleSendMessage.ts +++ b/app/core/SDKConnect/handlers/handleSendMessage.ts @@ -1,13 +1,10 @@ +import { Platform } from 'react-native'; import Routes from '../../../../app/constants/navigation/Routes'; import AppConstants from '../../../../app/core/AppConstants'; -import BackgroundBridge from '../../BackgroundBridge/BackgroundBridge'; -import { Platform } from 'react-native'; import Logger from '../../../util/Logger'; import Device from '../../../util/device'; import { Minimizer } from '../../NativeModules'; -import BatchRPCManager from '../BatchRPCManager'; import { Connection } from '../Connection'; -import RPCQueueManager from '../RPCQueueManager'; import { METHODS_TO_DELAY } from '../SDKConnect'; import DevLogger from '../utils/DevLogger'; import { wait } from '../utils/wait.util'; @@ -15,38 +12,29 @@ import handleBatchRpcResponse from './handleBatchRpcResponse'; export const handleSendMessage = async ({ msg, - rpcQueueManager, - backgroundBridge, - batchRpcManager, connection, }: { msg: any; - rpcQueueManager: RPCQueueManager; - backgroundBridge?: BackgroundBridge; - batchRpcManager: BatchRPCManager; connection: Connection; }) => { const msgId = msg?.data?.id + ''; const needsRedirect = connection.requestsToRedirect[msgId] !== undefined; - const method = rpcQueueManager.getId(msgId); + const method = connection.rpcQueueManager.getId(msgId); DevLogger.log(`Connection::sendMessage`, msg); // handle multichain rpc call responses separately - const chainRPCs = batchRpcManager.getById(msgId); + const chainRPCs = connection.batchRPCManager.getById(msgId); if (chainRPCs) { await handleBatchRpcResponse({ chainRpcs: chainRPCs, msg, connection, - batchRpcManager, - rpcQueueManager, - backgroundBridge, }); return; } if (msgId && method) { - rpcQueueManager.remove(msgId); + connection.rpcQueueManager.remove(msgId); } connection.remote.sendMessage(msg).catch((err) => { @@ -68,7 +56,7 @@ export const handleSendMessage = async ({ if (connection.origin === AppConstants.DEEPLINKS.ORIGIN_QR_CODE) return; - if (!rpcQueueManager.isEmpty()) { + if (!connection.rpcQueueManager.isEmpty()) { DevLogger.log(`Connection::sendMessage NOT empty --- skip goBack()`); return; } diff --git a/app/core/SDKConnect/handlers/setupBridge.ts b/app/core/SDKConnect/handlers/setupBridge.ts index 668dcf342cc..ce8caf27c60 100644 --- a/app/core/SDKConnect/handlers/setupBridge.ts +++ b/app/core/SDKConnect/handlers/setupBridge.ts @@ -6,10 +6,10 @@ import getRpcMethodMiddleware, { import { OriginatorInfo } from '@metamask/sdk-communication-layer'; import { PROTOCOLS } from '../../../constants/deeplinks'; -import DevLogger from '../utils/DevLogger'; +import Logger from '../../../util/Logger'; import { Connection } from '../Connection'; +import DevLogger from '../utils/DevLogger'; import handleSendMessage from './handleSendMessage'; -import Logger from '../../../util/Logger'; export const setupBridge = ({ originatorInfo, @@ -30,12 +30,10 @@ export const setupBridge = ({ url: PROTOCOLS.METAMASK + '://' + AppConstants.MM_SDK.SDK_REMOTE_ORIGIN, isRemoteConn: true, sendMessage: (msg: any) => { + DevLogger.log(`setupBride::sendMessage`, msg); handleSendMessage({ msg, connection, - batchRpcManager: connection.batchRPCManager, - rpcQueueManager: connection.rpcQueueManager, - backgroundBridge: connection.backgroundBridge, }).catch((err) => { Logger.error(err, 'Connection::sendMessage failed to send'); }); From b550c5b2677558558cca23f61c0fd4590956545e Mon Sep 17 00:00:00 2001 From: abretonc7s Date: Thu, 23 Nov 2023 21:19:21 +0800 Subject: [PATCH 6/7] feat: remove unused comments --- app/core/SDKConnect/SDKConnect.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/app/core/SDKConnect/SDKConnect.ts b/app/core/SDKConnect/SDKConnect.ts index 085a3b8d9e7..1862baf6c07 100644 --- a/app/core/SDKConnect/SDKConnect.ts +++ b/app/core/SDKConnect/SDKConnect.ts @@ -283,21 +283,6 @@ export class SDKConnect extends EventEmitter2 { delete this.sdkLoadingState[channelId]; await this.hideLoadingState(); } - - // const loadingSessions = Object.keys(this.sdkLoadingState).length; - // if (loadingSessions > 0) { - // this.navigation?.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - // screen: Routes.SHEET.SDK_LOADING, - // }); - // } else { - // const currentRoute = this.navigation?.getCurrentRoute()?.name; - // if (currentRoute === Routes.SHEET.SDK_LOADING) { - // DevLogger.log(`updateSDKLoadingState - goBack`); - // this.navigation?.goBack(); - // } else { - // DevLogger.log(`updateSDKLoadingState - currentRoute=${currentRoute}`); - // } - // } } public async hideLoadingState() { From 36a82e2191c566ee78a155443452ce43dd3e0515 Mon Sep 17 00:00:00 2001 From: abretonc7s Date: Thu, 23 Nov 2023 21:20:13 +0800 Subject: [PATCH 7/7] feat: remove unused comments --- app/core/SDKConnect/SDKConnect.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/core/SDKConnect/SDKConnect.ts b/app/core/SDKConnect/SDKConnect.ts index 1862baf6c07..e976bf6a44b 100644 --- a/app/core/SDKConnect/SDKConnect.ts +++ b/app/core/SDKConnect/SDKConnect.ts @@ -270,7 +270,6 @@ export class SDKConnect extends EventEmitter2 { loading: boolean; }) { if (loading === true) { - DevLogger.log(``); const keyringController = ( Engine.context as { KeyringController: KeyringController } ).KeyringController;