Skip to content

Commit

Permalink
feat: android sdk connectsign + batch request (#7921)
Browse files Browse the repository at this point in the history
## **Description**

- Depends on Connection Refactor PR
#7895
- Adds android sdk `metamask_connectSign` + `metamask_batch`
- Improve loading status

## **Related issues**

Fixes: #

## **Manual testing steps**

1. Go to this page...
2.
3.

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

- [ ] I’ve followed [MetaMask Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've clearly explained what problem this PR is solving and how it
is solved.
- [ ] I've linked related issues
- [ ] I've included manual testing steps
- [ ] I've included screenshots/recordings if applicable
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.
- [ ] I’ve properly set the pull request status:
  - [ ] In case it's not yet "ready for review", I've set it to "draft".
- [ ] In case it's "ready for review", I've changed it from "draft" to
"non-draft".

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
  • Loading branch information
abretonc7s authored Nov 30, 2023
1 parent 78988bc commit 728ed44
Show file tree
Hide file tree
Showing 18 changed files with 1,220 additions and 726 deletions.
1 change: 1 addition & 0 deletions app/components/Nav/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,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

0 comments on commit 728ed44

Please sign in to comment.