Skip to content
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

feat: android sdk connectsign + batch request #7921

Merged
merged 27 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e777e4f
feat: prioritize deeplink connection
abretonc7s Nov 20, 2023
d7255a4
feat: typings
abretonc7s Nov 20, 2023
79cc7a3
feat: overwrite without destroing previous connection
abretonc7s Nov 21, 2023
26e9ef8
feat: refactor sdk Connection class
abretonc7s Nov 23, 2023
b4eb19a
feat: refactor Connection
abretonc7s Nov 23, 2023
29736da
feat: remove unused comments
abretonc7s Nov 23, 2023
d68ae8f
feat: remove unused comments
abretonc7s Nov 23, 2023
736b87b
feat: connect and sign + batch
abretonc7s Nov 23, 2023
c1e3f3b
feat: valid batch calls
abretonc7s Nov 23, 2023
fb69cf3
feat: linting
abretonc7s Nov 23, 2023
bfd1f14
feat: connection flow with progress modal optimization
abretonc7s Nov 24, 2023
0b2196d
fix: android service error
abretonc7s Nov 24, 2023
2b5cb4f
feat: change connection loading modal order
abretonc7s Nov 24, 2023
48adec9
feat: ios recovery
abretonc7s Nov 24, 2023
415b0d6
feat: types for Engine
abretonc7s Nov 24, 2023
3c2ff63
feat: batch flow improvement to display goback after response
abretonc7s Nov 26, 2023
a423d13
feat: improve types to handle unknown exception type
abretonc7s Nov 26, 2023
4782a9b
fix: not returning from batch rpc calls on android
abretonc7s Nov 26, 2023
2a74cd6
feat: connectWith connection flow
abretonc7s Nov 27, 2023
6849754
fix: removed personal_sign overwrite by mistake
abretonc7s Nov 28, 2023
9a1ad35
feat: handle disconnection before bridge is setup
abretonc7s Nov 28, 2023
44d651e
fix: android refresh issue
abretonc7s Nov 29, 2023
1a4257f
feat: check permissions with connectSign
abretonc7s Nov 29, 2023
2c07dc3
feat: batch rpc android
abretonc7s Nov 29, 2023
d269e63
feat: working state
abretonc7s Nov 30, 2023
4fc02d7
fix: sdk initial install issue
abretonc7s Nov 30, 2023
45648df
fix: batch rpc android sdk
abretonc7s Nov 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/components/Nav/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ const App = ({ userLoggedIn }) => {
const triggerSetCurrentRoute = (route) => {
dispatch(setCurrentRoute(route));
if (route === 'Wallet' || route === 'BrowserView') {
setOnboarded(true);
dispatch(setCurrentBottomNavRoute(route));
}
};
Expand Down
2 changes: 1 addition & 1 deletion app/core/DeeplinkManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
152 changes: 140 additions & 12 deletions app/core/SDKConnect/AndroidSDK/AndroidService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { NetworkController } from '@metamask/network-controller';
import { Json } from '@metamask/utils';
import { EventEmitter2 } from 'eventemitter2';
import { NativeModules } from 'react-native';
import Engine from '../../Engine';
import { Minimizer } from '../../NativeModules';
import getRpcMethodMiddleware, {
ApprovalTypes,
} from '../../RPCMethods/RPCMethodMiddleware';
import { RPCQueueManager } from '../RPCQueueManager';

import {
EventType,
Expand All @@ -14,23 +20,25 @@ import AppConstants from '../../AppConstants';
import {
wait,
waitForAndroidServiceBinding,
waitForEmptyRPCQueue,
waitForKeychainUnlocked,
} from '../utils/wait.util';

import BackgroundBridge from '../../BackgroundBridge/BackgroundBridge';
import getRpcMethodMiddleware from '../../RPCMethods/RPCMethodMiddleware';
import {
DEFAULT_SESSION_TIMEOUT_MS,
METHODS_TO_DELAY,
METHODS_TO_REDIRECT,
SDKConnect,
} from '../SDKConnect';

import { KeyringController } from '@metamask/keyring-controller';

import { ApprovalController } from '@metamask/approval-controller';
import { PreferencesController } from '@metamask/preferences-controller';
import { PROTOCOLS } from '../../../constants/deeplinks';
import RPCQueueManager from '../RPCQueueManager';
import BatchRPCManager from '../BatchRPCManager';
import { RPC_METHODS } from '../Connection';
import handleBatchRpcResponse from '../handlers/handleBatchRpcResponse';
import handleCustomRpcCalls from '../handlers/handleCustomRpcCalls';
import DevLogger from '../utils/DevLogger';
import AndroidSDKEventHandler from './AndroidNativeSDKEventHandler';
import { AndroidClient } from './android-sdk-types';
Expand All @@ -41,6 +49,9 @@ export default class AndroidService extends EventEmitter2 {
private rpcQueueManager = new RPCQueueManager();
private bridgeByClientId: { [clientId: string]: BackgroundBridge } = {};
private eventHandler: AndroidSDKEventHandler;
private batchRPCManager: BatchRPCManager = new BatchRPCManager('android');
// To keep track in order to get the associated bridge to handle batch rpc calls
private currentClientId?: string;

constructor() {
super();
Expand Down Expand Up @@ -139,6 +150,13 @@ export default class AndroidService extends EventEmitter2 {

try {
if (!this.connectedClients?.[clientInfo.clientId]) {
DevLogger.log(`AndroidService::clients_connected - new client`);
// Ask for account permissions
await this.checkPermission({
originatorInfo: clientInfo.originatorInfo,
channelId: clientInfo.clientId,
});

this.setupBridge(clientInfo);
// Save session to SDKConnect
await SDKConnect.getInstance().addAndroidConnection({
Expand Down Expand Up @@ -198,6 +216,42 @@ export default class AndroidService extends EventEmitter2 {
});
}

private async checkPermission({
originatorInfo,
channelId,
}: {
originatorInfo: OriginatorInfo;
channelId: string;
}): Promise<unknown> {
const approvalController = (
Engine.context as { ApprovalController: ApprovalController }
).ApprovalController;

const approvalRequest = {
origin: AppConstants.MM_SDK.ANDROID_SDK,
type: ApprovalTypes.CONNECT_ACCOUNTS,
requestData: {
hostname: originatorInfo?.title ?? '',
pageMeta: {
channelId,
reconnect: false,
origin: AppConstants.MM_SDK.ANDROID_SDK,
url: originatorInfo?.url ?? '',
title: originatorInfo?.title ?? '',
icon: originatorInfo?.icon ?? '',
otps: [],
analytics: {
request_source: AppConstants.REQUEST_SOURCES.SDK_REMOTE_CONN,
request_platform:
originatorInfo?.platform ?? AppConstants.MM_SDK.UNKNOWN_PARAM,
},
} as Json,
},
id: channelId,
};
return approvalController.add(approvalRequest);
}

private setupOnMessageReceivedListener() {
this.eventHandler.onMessageReceived((jsonMessage: string) => {
const handleEventAsync = async () => {
Expand Down Expand Up @@ -260,11 +314,40 @@ export default class AndroidService extends EventEmitter2 {
return;
}

const preferencesController = (
Engine.context as {
PreferencesController: PreferencesController;
}
).PreferencesController;
const selectedAddress = preferencesController.state.selectedAddress;

const networkController = (
Engine.context as {
NetworkController: NetworkController;
}
).NetworkController;
const networkId = networkController.state.networkId ?? 1; // default to mainnet;
// transform networkId to 0x value
const hexChainId = `0x${networkId.toString(16)}`;

this.currentClientId = sessionId;
// Handle custom rpc method
const processedRpc = await handleCustomRpcCalls({
batchRPCManager: this.batchRPCManager,
selectedChainId: hexChainId,
selectedAddress,
rpc: { id: data.id, method: data.method, params: data.params },
});

DevLogger.log(
`AndroidService::onMessageReceived processedRpc`,
processedRpc,
);
this.rpcQueueManager.add({
id: data.id,
method: data.method,
id: processedRpc?.id ?? data.id,
method: processedRpc?.method ?? data.method,
});
bridge.onMessage({ name: 'metamask-provider', data });
bridge.onMessage({ name: 'metamask-provider', data: processedRpc });
};
handleEventAsync().catch((err) => {
Logger.log(
Expand Down Expand Up @@ -378,23 +461,68 @@ export default class AndroidService extends EventEmitter2 {
async sendMessage(message: any, forceRedirect?: boolean) {
const id = message?.data?.id;
this.communicationClient.sendMessage(JSON.stringify(message));
const rpcMethod = this.rpcQueueManager.getId(id);
let rpcMethod = this.rpcQueueManager.getId(id);

DevLogger.log(`AndroidService::sendMessage method=${rpcMethod}`, message);
// handle multichain rpc call responses separately
const chainRPCs = this.batchRPCManager.getById(id);
if (chainRPCs) {
const isLastRpcOrError = await handleBatchRpcResponse({
chainRpcs: chainRPCs,
msg: message,
backgroundBridge: this.bridgeByClientId[this.currentClientId ?? ''],
batchRPCManager: this.batchRPCManager,
sendMessage: ({ msg }) => this.sendMessage(msg),
});
DevLogger.log(
`AndroidService::sendMessage isLastRpc=${isLastRpcOrError}`,
chainRPCs,
);

if (!isLastRpcOrError) {
DevLogger.log(
`AndroidService::sendMessage NOT last rpc --- skip goBack()`,
chainRPCs,
);
this.rpcQueueManager.remove(id);
// Only continue processing the message and goback if all rpcs in the batch have been handled
return;
}

// Always set the method to metamask_batch otherwise it may not have been set correctly because of the batch rpc flow.
rpcMethod = RPC_METHODS.METAMASK_BATCH;
DevLogger.log(
`Connection::sendMessage chainRPCs=${chainRPCs} COMPLETED!`,
);
}

this.rpcQueueManager.remove(id);

if (!rpcMethod && forceRedirect !== true) {
DevLogger.log(
`Connection::sendMessage no rpc method --- rpcMethod=${rpcMethod} forceRedirect=${forceRedirect} --- skip goBack()`,
);
return;
}
const needsRedirect = METHODS_TO_REDIRECT[rpcMethod];

this.rpcQueueManager.remove(id);
const needsRedirect = this.rpcQueueManager.canRedirect({
method: rpcMethod,
});

if (needsRedirect || forceRedirect === true) {
try {
if (METHODS_TO_DELAY[rpcMethod]) {
// Add delay to see the feedback modal
await wait(1000);
}
// Make sure we have replied to all messages before redirecting
await waitForEmptyRPCQueue(this.rpcQueueManager);

if (!this.rpcQueueManager.isEmpty()) {
DevLogger.log(
`Connection::sendMessage NOT empty --- skip goBack()`,
this.rpcQueueManager.get(),
);
return;
}

Minimizer.goBack();
} catch (error) {
Expand Down
3 changes: 3 additions & 0 deletions app/core/SDKConnect/BatchRPCManager.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import DevLogger from './utils/DevLogger';

interface RPCMethod {
id: string;
method: string;
Expand Down Expand Up @@ -26,6 +28,7 @@ export class BatchRPCManager {
}

add({ id, rpcs }: { id: string; rpcs: RPCMethod[] }) {
DevLogger.log(`BatchRPCManager::add id=${id} rpcs=`, rpcs);
this.rpcChain[id] = rpcs;
}

Expand Down
Loading
Loading