Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
martykan committed Feb 6, 2025
1 parent 2a38d81 commit d9d89dc
Show file tree
Hide file tree
Showing 18 changed files with 287 additions and 235 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { useDispatch, useSelector } from 'src/hooks/suite';
export const ConnectPopupModal = () => {
const dispatch = useDispatch();
const popupCall = useSelector(selectConnectPopupCall);
if (!popupCall) return null;
if (!popupCall || popupCall?.state !== 'request') return null;

const { method, processName, origin } = popupCall;
const { methodTitle, confirmLabel, processName, origin } = popupCall;
const onConfirm = () => dispatch(connectPopupActions.approveCall());
const onCancel = () =>
dispatch(connectPopupActions.rejectCall(ERRORS.TypedError('Method_Cancel')));
Expand All @@ -27,13 +27,13 @@ export const ConnectPopupModal = () => {
<Translation id="TR_CANCEL" />
</NewModal.Button>
<NewModal.Button variant="primary" onClick={onConfirm}>
<Translation id="TR_CONFIRM" />
{confirmLabel || <Translation id="TR_CONFIRM" />}
</NewModal.Button>
</>
}
heading={<Translation id="TR_TREZOR_CONNECT" />}
>
<H2>{method}</H2>
<H2>{methodTitle}</H2>

{processName && (
<Paragraph margin={{ top: spacings.xs }}>
Expand Down
2 changes: 1 addition & 1 deletion suite-common/connect-popup/src/connectPopupMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const prepareConnectPopupMiddleware = createMiddlewareWithExtraDeps(
async (action, { dispatch, next, extra }) => {
await next(action);

if (connectPopupActions.initiateCall.match(action)) {
if (connectPopupActions.initiateCall.match(action) && action.payload.state === 'request') {
dispatch(extra.actions.openModal({ type: 'connect-popup' }));
}
if (
Expand Down
5 changes: 3 additions & 2 deletions suite-common/connect-popup/src/connectPopupReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ export const prepareConnectPopupReducer = createReducerWithExtraDeps(
state.activeCall = payload;
})
.addCase(connectPopupActions.approveCall, state => {
state?.activeCall?.confirmation.resolve();
if (state.activeCall?.state === 'request') state.activeCall.confirmation.resolve();
state.activeCall = undefined;
})
.addCase(connectPopupActions.rejectCall, (state, { payload }) => {
state?.activeCall?.confirmation.reject(payload);
if (state.activeCall?.state === 'request')
state.activeCall.confirmation.reject(payload);
state.activeCall = undefined;
});
},
Expand Down
104 changes: 92 additions & 12 deletions suite-common/connect-popup/src/connectPopupThunks.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import { AsyncThunkAction } from '@reduxjs/toolkit';

import { CustomThunkAPI, createThunk } from '@suite-common/redux-utils';
import { selectSelectedDevice } from '@suite-common/wallet-core';
import { deviceActions, selectSelectedDevice } from '@suite-common/wallet-core';
import TrezorConnect, { CallMethodParams, CallMethodResponse, ERRORS } from '@trezor/connect';
import { serializeError } from '@trezor/connect/src/constants/errors';
import { TypedError, serializeError } from '@trezor/connect/src/constants/errors';
import { DEEPLINK_VERSION } from '@trezor/connect/src/data/version';
import { createDeferred } from '@trezor/utils';

import { connectPopupActions } from './connectPopupActions';

const CONNECT_POPUP_MODULE = '@common/connect-popup';

type ConnectPopupCallThunkResponse<M extends keyof typeof TrezorConnect> = Promise<{
id: number;
success: boolean;
payload: CallMethodResponse<M>;
}>;

type ConnectPopupCallThunkParams<M extends keyof typeof TrezorConnect> = {
id: number;
processName?: string;
origin?: string;
method: M;
Expand All @@ -29,22 +28,37 @@ export const connectPopupCallThunkInner = createThunk<
ConnectPopupCallThunkParams<keyof typeof TrezorConnect>
>(
`${CONNECT_POPUP_MODULE}/callThunk`,
async ({ id, method, payload, processName, origin }, { dispatch, getState, extra }) => {
async ({ method, payload, processName, origin }, { dispatch, getState, extra }) => {
try {
// @ts-expect-error: method is dynamic
const methodInfo = await TrezorConnect[method]({
...payload,
__info: true,
});
if (!methodInfo.success) {
connectPopupActions.initiateCall({
state: 'call-error',
callError: methodInfo.error.message,
});
throw methodInfo;
}
if (
methodInfo.payload.requiredPermissions.includes('management') ||
methodInfo.payload.requiredPermissions.includes('push_tx')
) {
setMethodError('Method requires unsafe permissions');

return;
}

const confirmation = createDeferred();
dispatch(extra.actions.lockDevice(true));
dispatch(
connectPopupActions.initiateCall({
method: methodInfo.payload.info,
state: 'request',
method,
methodTitle: methodInfo.payload.confirmation?.label ?? methodInfo.payload.info,
confirmLabel: methodInfo.payload.confirmation?.customConfirmButton?.label,
processName,
origin,
confirmation,
Expand All @@ -64,20 +78,20 @@ export const connectPopupCallThunkInner = createThunk<
instance: device.instance,
state: device.state,
},
useEmptyPassphrase: device.useEmptyPassphrase,
...payload,
});

return {
...response,
id,
};
// Note: for mobile this needs to be called explicitly, on desktop it's automatically handled by middleware
dispatch(deviceActions.removeButtonRequests({ device }));

return response;
} catch (error) {
console.error('connectPopupCallThunk', error);

return {
success: false,
payload: serializeError(error),
id,
};
} finally {
dispatch(extra.actions.lockDevice(false));
Expand All @@ -95,6 +109,72 @@ export const connectPopupCallThunk = <M extends keyof typeof TrezorConnect>(
CustomThunkAPI
> => connectPopupCallThunkInner(params) as any;

export const connectPopupDeeplinkThunk = createThunk<void, { url: string }>(
`${CONNECT_POPUP_MODULE}/deeplinkThunk`,
async ({ url }, { dispatch }) => {
try {
const parsedUrl = new URL(url);
const path = parsedUrl.pathname;
const queryParams = Object.fromEntries(parsedUrl.searchParams.entries());

const version = path && path.split('/').slice(-2, -1)[0];
if (
!queryParams?.method ||
!queryParams?.params ||
!queryParams?.callback ||
typeof queryParams?.params !== 'string' ||
typeof queryParams?.method !== 'string' ||
typeof queryParams?.callback !== 'string' ||
!Object.prototype.hasOwnProperty.call(TrezorConnect, queryParams?.method)
) {
dispatch(
connectPopupActions.initiateCall({
state: 'call-error',
callError: TypedError('Method_InvalidParameter'),
}),
);

return;
}

if (!version || parseInt(version) > DEEPLINK_VERSION) {
dispatch(
connectPopupActions.initiateCall({
state: 'call-error',
callError: TypedError('Deeplink_VersionMismatch'),
}),
);

return;
}

const payload = JSON.parse(queryParams.params);
const { method, callback } = queryParams;
const callbackUrl = new URL(callback);

const response = await dispatch(
connectPopupCallThunk({
processName: 'deeplink',
origin: `${callbackUrl.protocol}//${callbackUrl.host}`,
// @ts-expect-error: method is dynamic
method,
payload,
}),
).unwrap();

callbackUrl.searchParams.set('response', JSON.stringify(response));
dispatch(
connectPopupActions.initiateCall({
state: 'deeplink-callback',
callbackUrl: callbackUrl.toString(),
}),
);
} catch (error) {
console.warn('Ignoring invalid deeplink URL', { error, url });
}
},
);

// Desktop only - initialize event handler
export const connectPopupDesktopInitThunk = createThunk(
`${CONNECT_POPUP_MODULE}/initPopupThunk`,
Expand All @@ -105,7 +185,7 @@ export const connectPopupDesktopInitThunk = createThunk(
desktopApi.on('connect-popup/call', async params => {
// @ts-expect-error: params in desktopApi are not fully typed
const response = await dispatch(connectPopupCallThunk(params)).unwrap();
desktopApi.connectPopupResponse(response);
desktopApi.connectPopupResponse({ ...response, id: params.id });
});
desktopApi.connectPopupReady();
}
Expand Down
25 changes: 19 additions & 6 deletions suite-common/connect-popup/src/connectPopupTypes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import { TrezorError } from '@trezor/connect/src/constants/errors';
import { Deferred } from '@trezor/utils';

export type ConnectPopupCall = {
method: string;
processName?: string;
origin?: string;
confirmation: Deferred<void>;
};
export type ConnectPopupCall =
| {
state: 'request';
method: string;
methodTitle: string;
confirmLabel: string;
processName?: string;
origin?: string;
confirmation: Deferred<void>;
}
| {
state: 'call-error';
callError: TrezorError;
}
| {
state: 'deeplink-callback';
callbackUrl: string;
};
3 changes: 0 additions & 3 deletions suite-common/walletconnect/src/adapters/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ const ethereumRequestThunk = createThunk<
const account = getAccount(address);
const response = await dispatch(
trezorConnectPopupActions.connectPopupCallThunk({
id: 0,
method: 'ethereumSignMessage',
payload: {
path: account.path,
Expand All @@ -62,7 +61,6 @@ const ethereumRequestThunk = createThunk<
const account = getAccount(address);
const response = await dispatch(
trezorConnectPopupActions.connectPopupCallThunk({
id: 0,
method: 'ethereumSignTypedData',
payload: {
path: account.path,
Expand Down Expand Up @@ -120,7 +118,6 @@ const ethereumRequestThunk = createThunk<
};
const signResponse = await dispatch(
trezorConnectPopupActions.connectPopupCallThunk({
id: 0,
method: 'ethereumSignTransaction',
payload,
processName: 'WalletConnect',
Expand Down
6 changes: 6 additions & 0 deletions suite-native/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"dependencies": {
"@gorhom/bottom-sheet": "5.0.5",
"@mobily/ts-belt": "^3.13.1",
"@react-native-async-storage/async-storage": "^2.1.1",
"@react-native-community/netinfo": "^11.4.1",
"@react-native/metro-config": "^0.76.1",
"@react-navigation/bottom-tabs": "6.6.1",
Expand Down Expand Up @@ -83,6 +84,7 @@
"@trezor/styles": "workspace:*",
"@trezor/theme": "workspace:*",
"@trezor/trezor-user-env-link": "workspace:*",
"@walletconnect/react-native-compat": "^2.18.0",
"@whatwg-node/events": "0.1.2",
"abortcontroller-polyfill": "1.7.6",
"buffer": "^6.0.3",
Expand All @@ -104,13 +106,15 @@
"expo-system-ui": "^4.0.2",
"expo-updates": "0.26.6",
"expo-video": "^2.0.1",
"fast-text-encoding": "^1.0.6",
"lottie-react-native": "^7.1.0",
"node-libs-browser": "^2.2.1",
"react": "18.2.0",
"react-intl": "^6.6.8",
"react-native": "0.76.1",
"react-native-edge-to-edge": "^1.3.1",
"react-native-gesture-handler": "^2.21.0",
"react-native-get-random-values": "^1.11.0",
"react-native-keyboard-aware-scroll-view": "0.9.5",
"react-native-mmkv": "2.12.2",
"react-native-quick-crypto": "^0.7.6",
Expand All @@ -132,8 +136,10 @@
"@react-native/babel-preset": "^0.75.2",
"@suite-common/test-utils": "workspace:^",
"@trezor/connect-mobile": "workspace:^",
"@types/fast-text-encoding": "^1",
"@types/jest": "^29.5.12",
"@types/node": "22.10.1",
"@types/react-native-get-random-values": "^1",
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
"babel-plugin-transform-remove-console": "^6.9.4",
"detox": "^20.25.6",
Expand Down
5 changes: 5 additions & 0 deletions suite-native/app/src/initActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
initStakeDataThunk,
periodicFetchFiatRatesThunk,
} from '@suite-common/wallet-core';
// WalletConnect and its polyfills for RN
import '@walletconnect/react-native-compat';
import { walletConnectInitThunk } from '@suite-common/walletconnect';
import { initAnalyticsThunk } from '@suite-native/analytics';
import { selectFiatCurrencyCode } from '@suite-native/settings';
import { setIsAppReady, setIsConnectInitialized } from '@suite-native/state/src/appSlice';
Expand Down Expand Up @@ -47,6 +50,8 @@ export const applicationInit = createThunk(
}),
);

dispatch(walletConnectInitThunk());

// Create Portfolio Tracker device if it doesn't exist
dispatch(createImportedDeviceThunk());
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions suite-native/module-connect-popup/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"dependencies": {
"@react-navigation/native": "6.1.18",
"@reduxjs/toolkit": "1.9.5",
"@suite-common/suite-types": "workspace:*",
"@suite-common/wallet-core": "workspace:*",
"@suite-native/atoms": "workspace:*",
Expand Down
7 changes: 7 additions & 0 deletions suite-native/module-connect-popup/redux.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AsyncThunkAction } from '@reduxjs/toolkit';

declare module 'redux' {
export interface Dispatch {
<TThunk extends AsyncThunkAction<any, any, any>>(thunk: TThunk): ReturnType<TThunk>;
}
}
Loading

0 comments on commit d9d89dc

Please sign in to comment.