diff --git a/apps/wallet/browser/bridge/request.ts b/apps/wallet/browser/bridge/request.ts index 4fd19ed5f..71f1787a8 100644 --- a/apps/wallet/browser/bridge/request.ts +++ b/apps/wallet/browser/bridge/request.ts @@ -1,4 +1,7 @@ -import { PopupType, RequestType, ResponseCode, Timeout } from '@walless/core'; +import { ChromeChannel } from '@metacraft/crab/chrome'; +import type { RawRequest, Response } from '@metacraft/crab/core'; +import type { UnknownObject } from '@walless/core'; +import { PopupType, RequestType, Timeout } from '@walless/core'; import type { PureMessagePayload, ResponsePayload } from '@walless/messaging'; import { Channels } from '@walless/messaging'; import * as bs58 from 'bs58'; @@ -18,19 +21,25 @@ export const sendRequest = async ( }; export const handleRequestConnect = async ( - requestId: string, + resolveId: string, isApproved: boolean, ) => { - const payload: PopupPayload = { + const payload: RawRequest = { from: PopupType.REQUEST_CONNECT_POPUP, - type: RequestType.REQUEST_CONNECT, - sourceRequestId: requestId, + type: RequestType.REQUEST_CONNECT as never, + resolveId: resolveId, isApproved, }; try { - const res = await encryptedMessenger.request(Channels.kernel, payload); - if (res.responseCode === ResponseCode.SUCCESS) { + const chromeChannel = new ChromeChannel(Channels.popup); + const res = await chromeChannel.request>( + payload, + Timeout.sixtySeconds, + ); + if (res.error) { + throw Error(res.error); + } else { window.close(); } } catch (error) { @@ -56,36 +65,40 @@ export const handleRequestInstallLayout = async ( } }; -export const handleRequestSignature = async ( - options: PayloadOptions, - type: RequestType, -) => { - const payload: PopupPayload = { +export const handleRequestSignature = async (options: PayloadOptions) => { + const { sourceRequestId } = options; + const payload: RawRequest = { from: PopupType.SIGNATURE_POPUP, - type, + type: RequestType.RESOLVE_REQUEST_SIGNATURE as never, + resolveId: sourceRequestId, ...options, }; try { - return await encryptedMessenger.request(Channels.kernel, payload, 10000); + const chromeChannel = new ChromeChannel(Channels.popup); + const res = await chromeChannel.request>( + payload, + Timeout.sixtySeconds, + ); + return res; } catch (error) { throw Error('Unable to handle sign message request'); } }; export const getDataFromSourceRequest = async ( - requestId: string, + resolveId: string, from: string, ) => { - const payload: PopupPayload = { + const payload: RawRequest = { from, - type: RequestType.REQUEST_PAYLOAD, - sourceRequestId: requestId, + type: RequestType.REQUEST_PAYLOAD as never, + resolveId: resolveId, }; try { - const res = await encryptedMessenger.request( - Channels.kernel, + const chromeChannel = new ChromeChannel(Channels.popup); + const res = await chromeChannel.request>( payload, Timeout.sixtySeconds, ); diff --git a/apps/wallet/browser/bridge/utils.ts b/apps/wallet/browser/bridge/utils.ts index 661ddc727..a88b5c941 100644 --- a/apps/wallet/browser/bridge/utils.ts +++ b/apps/wallet/browser/bridge/utils.ts @@ -1,3 +1,4 @@ +import type { Networks } from '@walless/core'; import { logger } from '@walless/core'; import type { PureMessagePayload } from '@walless/messaging'; import { createEncryptionKeyVault, createMessenger } from '@walless/messaging'; @@ -7,6 +8,7 @@ export interface PayloadOptions { sourceRequestId: string; isApproved?: boolean; passcode?: string; + network?: Networks; } export type PopupPayload = PureMessagePayload & PayloadOptions; diff --git a/apps/wallet/browser/content/utils.ts b/apps/wallet/browser/content/utils.ts index aa65851b5..c469c8e4e 100644 --- a/apps/wallet/browser/content/utils.ts +++ b/apps/wallet/browser/content/utils.ts @@ -1,7 +1,9 @@ -import { createMessenger } from '@walless/messaging'; - -const messenger = createMessenger(); +import { ChromeChannel } from '@metacraft/crab/chrome'; +import type { Response } from '@metacraft/crab/core'; +import { Timeout, type UnknownObject } from '@walless/core'; +import { Channels } from '@walless/messaging'; +const chromeChannel = new ChromeChannel(Channels.kernel); export const initializeMessaging = async () => { window.postMessage({ from: 'walless-content-script-loaded' }); window.addEventListener( @@ -15,9 +17,20 @@ export const initializeMessaging = async () => { await chrome.runtime.sendMessage(data); } else { // TODO: use timeout from sdk that include in data - const response = await messenger.request('kernel', data); + const newResponse = await chromeChannel.request< + Response + >( + { + ...data, + id: data.requestId, + }, + data.timeout || Timeout.sixtySeconds, + ); - window.postMessage(response); + window.postMessage({ + ...newResponse, + from: 'walless@kernel', + }); } } }, diff --git a/apps/wallet/browser/kernel/handlers/common.ts b/apps/wallet/browser/kernel/handlers/common.ts index 4a41fdc0d..9c21aea66 100644 --- a/apps/wallet/browser/kernel/handlers/common.ts +++ b/apps/wallet/browser/kernel/handlers/common.ts @@ -1,8 +1,10 @@ +import type { ChromeKernel } from '@metacraft/crab/chrome'; +import type { Middleware } from '@metacraft/crab/core'; import { PublicKey } from '@solana/web3.js'; -import type { SuiPublicKey } from '@walless/core'; +import type { RequestType, SuiPublicKey } from '@walless/core'; import { Networks } from '@walless/core'; import { ResponseCode } from '@walless/core'; -import type { ConnectOptions } from '@walless/sdk'; +import type { Channels } from '@walless/messaging'; import type { PublicKeyDocument, TrustedDomainDocument } from '@walless/store'; import { selectors } from '@walless/store'; import { encode } from 'bs58'; @@ -13,12 +15,10 @@ import { checkInstalledExtensionById, } from '../utils/helper'; import { openPopup } from '../utils/popup'; -import { getRequestRecord, respond } from '../utils/requestPool'; +import { respond } from '../utils/requestPool'; import type { HandleMethod } from '../utils/types'; -export const connect: HandleMethod<{ options?: ConnectOptions }> = async ({ - payload, -}) => { +export const connect: Middleware = async (payload, respond) => { if (!payload.options) throw new Error('No connection options provided'); const connectOptions = payload.options; @@ -63,12 +63,14 @@ export const connect: HandleMethod<{ options?: ConnectOptions }> = async ({ }) .filter((k) => !!k); - respond(payload.requestId, ResponseCode.SUCCESS, { publicKeys }); + respond({ + publicKeys, + requestId: payload.id, + responseCode: ResponseCode.SUCCESS, + }); }; -export const disconnect: HandleMethod<{ - options?: ConnectOptions; -}> = async ({ payload }) => { +export const disconnect: Middleware = async (payload, respond) => { if (!payload.options) throw Error('No disconnection options provided'); const connectOptions = payload.options; @@ -82,24 +84,33 @@ export const disconnect: HandleMethod<{ ); } - respond(payload.requestId, ResponseCode.SUCCESS); + respond({ + ...payload, + responseCode: ResponseCode.SUCCESS, + }); }; -export const requestPayload: HandleMethod<{ - sourceRequestId?: string; -}> = ({ payload }) => { - if (!payload.sourceRequestId) { - throw Error('Not sourceRequestId provided'); - } - - const { sourceRequestId, requestId } = payload; - const { payload: sourcePayload, channel: sourceChannel } = - getRequestRecord(sourceRequestId); - - respond(requestId, ResponseCode.SUCCESS, { - ...sourceChannel, - ...sourcePayload, - }); +export const requestPayload = ( + kernel: ChromeKernel, +): Middleware => { + return async (payload, respond) => { + if (!payload.resolveId) { + throw Error('Not sourceRequestId provided'); + } + + const { resolveId, id: requestId } = payload; + const response = kernel.getRequestByResolveId(resolveId); + + if ('error' in response) { + respond({ error: response.error }); + } else { + respond({ + requestId, + responseCode: ResponseCode.SUCCESS, + ...response.request, + }); + } + }; }; export type LayoutPayload = { diff --git a/apps/wallet/browser/kernel/handlers/kernel.ts b/apps/wallet/browser/kernel/handlers/kernel.ts index 20269d518..2bffaf5d3 100644 --- a/apps/wallet/browser/kernel/handlers/kernel.ts +++ b/apps/wallet/browser/kernel/handlers/kernel.ts @@ -1,127 +1,133 @@ -import { Networks, PopupType, RequestType, ResponseCode } from '@walless/core'; -import type { MessengerCallback } from '@walless/messaging'; +// import { Networks, RequestType, ResponseCode } from '@walless/core'; +// import type { MessengerCallback } from '@walless/messaging'; -import { handle } from '../utils/handle'; -import { - checkApproval, - checkConnection, - filterSDKSignatureRequest, - forwardToSourceRequest, - getPrivateKey, -} from '../utils/middleware'; -import { addRequestRecord } from '../utils/requestPool'; +// import { handle } from '../utils/handle'; +// import { +// filterSDKSignatureRequest, +// forwardToSourceRequest, +// getPrivateKey, +// } from '../utils/middleware'; +// import { addRequestRecord } from '../utils/requestPool'; -import * as aptos from './aptos'; -import * as common from './common'; -import * as solana from './solana'; -import * as sui from './sui'; -import * as tezos from './tezos'; +// import * as aptos from './aptos'; +// import * as common from './common'; +// import * as solana from './solana'; +// import * as sui from './sui'; +// import * as tezos from './tezos'; -export const onKernelMessage: MessengerCallback = async (payload, channel) => { - addRequestRecord(payload.requestId, channel, payload); - const { type, requestId } = payload; - - if (!requestId) { - return channel.postMessage({ - from: 'walless@kernel', - requestId: requestId, - responseCode: ResponseCode.ERROR, - error: 'No request id provided!', - }); - } - - if (type === RequestType.REQUEST_CONNECT && payload.from === 'walless@sdk') { - handle(payload).execute([checkConnection, common.connect]); - } else if ( - type === RequestType.REQUEST_CONNECT && - payload.from === PopupType.REQUEST_CONNECT_POPUP - ) { - handle(payload).execute([checkApproval, common.connect]); - } else if (type === RequestType.REQUEST_DISCONNECT) { - handle(payload).execute([common.disconnect]); - } else if (type === RequestType.REQUEST_PAYLOAD) { - handle(payload).execute([common.requestPayload]); - } else if (type === RequestType.INSTALL_LAYOUT) { - handle(payload).execute([common.installLayout]); - } else if (type === RequestType.CHECK_INSTALLED_LAYOUT) { - handle(payload).execute([common.checkInstalledLayout]); - } else if (type === RequestType.OPEN_LAYOUT_POPUP) { - handle(payload).execute([common.openLayoutPopup]); - } else if (type === RequestType.SIGN_MESSAGE_ON_SOLANA) { - handle(payload).execute([ - filterSDKSignatureRequest, - getPrivateKey(Networks.solana), - forwardToSourceRequest, - solana.signMessage, - ]); - } else if (type === RequestType.SIGN_TRANSACTION_ON_SOLANA) { - handle(payload).execute([ - filterSDKSignatureRequest, - getPrivateKey(Networks.solana), - forwardToSourceRequest, - solana.signTransaction, - ]); - } else if (type === RequestType.SIGN_SEND_TRANSACTION_ON_SOLANA) { - handle(payload).execute([ - filterSDKSignatureRequest, - getPrivateKey(Networks.solana), - forwardToSourceRequest, - solana.signAndSendTransaction, - ]); - } else if (type === RequestType.SIGN_GASILON_TRANSACTION_ON_SOLANA) { - handle(payload).execute([ - getPrivateKey(Networks.solana), - solana.signAndSendGasilonTransaction, - ]); - } else if (type === RequestType.SIGN_MESSAGE_ON_SUI) { - handle(payload).execute([ - filterSDKSignatureRequest, - getPrivateKey(Networks.sui), - forwardToSourceRequest, - sui.signMessage, - ]); - } else if (type === RequestType.SIGN_TRANSACTION_ON_SUI) { - handle(payload).execute([ - filterSDKSignatureRequest, - getPrivateKey(Networks.sui), - forwardToSourceRequest, - sui.signTransaction, - ]); - } else if (type === RequestType.SIGN_EXECUTE_TRANSACTION_ON_SUI) { - handle(payload).execute([ - filterSDKSignatureRequest, - getPrivateKey(Networks.sui), - forwardToSourceRequest, - sui.signAndExecuteTransaction, - ]); - } else if (payload.type === RequestType.TRANSFER_TEZOS_TOKEN) { - handle(payload).execute([ - getPrivateKey(Networks.tezos), - tezos.transferToken, - ]); - } else if (type === RequestType.TRANSFER_COIN_ON_APTOS) { - handle(payload).execute([ - getPrivateKey(Networks.aptos), - aptos.transferCoin, - ]); - } else if (type === RequestType.TRANSFER_TOKEN_ON_APTOS) { - handle(payload).execute([ - getPrivateKey(Networks.aptos), - aptos.transferToken, - ]); - } else if (type === RequestType.CLAIM_TOKEN_ON_APTOS) { - handle(payload).execute([getPrivateKey(Networks.aptos), aptos.claimToken]); - } else if (type === RequestType.UPDATE_DIRECT_TRANSFER_ON_APTOS) { - handle(payload).execute([ - getPrivateKey(Networks.aptos), - aptos.updateDirectTransfer, - ]); - } else { - return channel.postMessage({ - from: 'walless@kernel', - requestId: payload.requestId, - responseCode: ResponseCode.ERROR, - error: 'Invalid request type!', - }); - } -}; +// export const onKernelMessage: MessengerCallback = async (payload, channel) => { +// addRequestRecord(payload.requestId, channel, payload); +// const { type, requestId } = payload; +// if (!requestId) { +// return channel.postMessage({ +// from: 'walless@kernel', +// requestId: requestId, +// responseCode: ResponseCode.ERROR, +// error: 'No request id provided!', +// }); +// } +// if (requestId) { +// switch (type) { +// case RequestType.REQUEST_CONNECT: +// case RequestType.REQUEST_DISCONNECT: +// case RequestType.REQUEST_PAYLOAD: +// case RequestType.SIGN_MESSAGE_ON_SOLANA: +// return; +// } +// } +// if (type === RequestType.REQUEST_CONNECT && payload.from === 'walless@sdk') { +// handle(payload).execute([checkConnection, common.connect]); +// } else if ( +// type === RequestType.REQUEST_CONNECT && +// payload.from === PopupType.REQUEST_CONNECT_POPUP +// ) { +// handle(payload).execute([checkApproval, common.connect]); +// } else if (type === RequestType.REQUEST_DISCONNECT) { +// handle(payload).execute([common.disconnect]); +// } else if (type === RequestType.REQUEST_PAYLOAD) { +// handle(payload).execute([common.requestPayload]); +// } else if (type === RequestType.INSTALL_LAYOUT) { +// handle(payload).execute([common.installLayout]); +// } else if (type === RequestType.CHECK_INSTALLED_LAYOUT) { +// handle(payload).execute([common.checkInstalledLayout]); +// } else if (type === RequestType.OPEN_LAYOUT_POPUP) { +// handle(payload).execute([common.openLayoutPopup]); +// } +// else if (type === RequestType.SIGN_MESSAGE_ON_SOLANA) { +// handle(payload).execute([ +// filterSDKSignatureRequest, +// getPrivateKey(Networks.solana), +// forwardToSourceRequest, +// solana.signMessage, +// ]); +// } else if (type === RequestType.SIGN_TRANSACTION_ON_SOLANA) { +// handle(payload).execute([ +// filterSDKSignatureRequest, +// getPrivateKey(Networks.solana), +// forwardToSourceRequest, +// solana.signTransaction, +// ]); +// } else if (type === RequestType.SIGN_SEND_TRANSACTION_ON_SOLANA) { +// handle(payload).execute([ +// filterSDKSignatureRequest, +// getPrivateKey(Networks.solana), +// forwardToSourceRequest, +// solana.signAndSendTransaction, +// ]); +// } else if (type === RequestType.SIGN_GASILON_TRANSACTION_ON_SOLANA) { +// handle(payload).execute([ +// getPrivateKey(Networks.solana), +// solana.signAndSendGasilonTransaction, +// ]); +// } else if (type === RequestType.SIGN_MESSAGE_ON_SUI) { +// handle(payload).execute([ +// filterSDKSignatureRequest, +// getPrivateKey(Networks.sui), +// forwardToSourceRequest, +// sui.signMessage, +// ]); +// } else if (type === RequestType.SIGN_TRANSACTION_ON_SUI) { +// handle(payload).execute([ +// filterSDKSignatureRequest, +// getPrivateKey(Networks.sui), +// forwardToSourceRequest, +// sui.signTransaction, +// ]); +// } else if (type === RequestType.SIGN_EXECUTE_TRANSACTION_ON_SUI) { +// handle(payload).execute([ +// filterSDKSignatureRequest, +// getPrivateKey(Networks.sui), +// forwardToSourceRequest, +// sui.signAndExecuteTransaction, +// ]); +// } else if (payload.type === RequestType.TRANSFER_TEZOS_TOKEN) { +// handle(payload).execute([ +// getPrivateKey(Networks.tezos), +// tezos.transferToken, +// ]); +// } else if (type === RequestType.TRANSFER_COIN_ON_APTOS) { +// handle(payload).execute([ +// getPrivateKey(Networks.aptos), +// aptos.transferCoin, +// ]); +// } else if (type === RequestType.TRANSFER_TOKEN_ON_APTOS) { +// handle(payload).execute([ +// getPrivateKey(Networks.aptos), +// aptos.transferToken, +// ]); +// } else if (type === RequestType.CLAIM_TOKEN_ON_APTOS) { +// handle(payload).execute([getPrivateKey(Networks.aptos), aptos.claimToken]); +// } else if (type === RequestType.UPDATE_DIRECT_TRANSFER_ON_APTOS) { +// handle(payload).execute([ +// getPrivateKey(Networks.aptos), +// aptos.updateDirectTransfer, +// ]); +// } else { +// return channel.postMessage({ +// from: 'walless@kernel', +// requestId: payload.requestId, +// responseCode: ResponseCode.ERROR, +// error: 'Invalid request type!', +// }); +// } +// }; diff --git a/apps/wallet/browser/kernel/handlers/solana.ts b/apps/wallet/browser/kernel/handlers/solana.ts index 90062d58f..20fe874c3 100644 --- a/apps/wallet/browser/kernel/handlers/solana.ts +++ b/apps/wallet/browser/kernel/handlers/solana.ts @@ -1,3 +1,4 @@ +import type { Middleware } from '@metacraft/crab/core'; import { clusterApiUrl, Connection } from '@solana/web3.js'; import { ResponseCode } from '@walless/core'; import { solana } from '@walless/network'; @@ -12,10 +13,7 @@ const connection = new Connection( : clusterApiUrl('devnet'), ); -export const signMessage: HandleMethod<{ - privateKey?: Uint8Array; - message?: string; -}> = async ({ payload }) => { +export const signMessage: Middleware = async (payload, respond) => { if (!payload.privateKey || !payload.message) { throw Error('Missing privateKey or message'); } @@ -25,7 +23,11 @@ export const signMessage: HandleMethod<{ payload.privateKey, ); - respond(payload.requestId, ResponseCode.SUCCESS, { signature }); + respond({ + signature, + message: 'Successfully sign message', + responseCode: ResponseCode.SUCCESS, + }); }; export const signTransaction: HandleMethod<{ diff --git a/apps/wallet/browser/kernel/messaging/index.ext.ts b/apps/wallet/browser/kernel/messaging/index.ext.ts index 3ff7f3a0c..22a4f8d84 100644 --- a/apps/wallet/browser/kernel/messaging/index.ext.ts +++ b/apps/wallet/browser/kernel/messaging/index.ext.ts @@ -1,84 +1,31 @@ -import { - PopupType, - ResponseCode, - ResponseMessage, - runtime, -} from '@walless/core'; -import type { - EncryptedMessage, - MessagePayload, - MessengerCallback, -} from '@walless/messaging'; -import { decryptMessage } from '@walless/messaging'; +import { ChromeKernel } from '@metacraft/crab/chrome'; +import { RequestType } from '@walless/core'; +import { Channels } from '@walless/messaging'; -import { onKernelMessage } from '../handlers/kernel'; -import { respond } from '../utils/requestPool'; +import * as common from '../handlers/common'; +import * as solana from '../handlers/solana'; +import { checkConnection, requestUserSignature } from '../utils/middleware'; -import { encryptionKeyVault, initializeVaultKeys } from './shared'; - -/* Manually forward/coordinate communication using chrome.runtime.port messaging, - * included encryption support */ export const initializeMessaging = async (): Promise => { - await initializeVaultKeys(); - - const callbackRegistry: Record = {}; - - runtime.onConnect.addListener((port: chrome.runtime.Port) => { - const handleInComingMessage = async ( - message: EncryptedMessage | MessagePayload, - ) => { - const registeredCallback = callbackRegistry[port.name]; - const isEncrypted = !!message?.iv; - - if (registeredCallback) { - if (isEncrypted) { - const key = await encryptionKeyVault.get(port.name); - const decrypted = await decryptMessage( - message as EncryptedMessage, - key, - ); - registeredCallback?.(decrypted, port); - } else { - registeredCallback?.(message as never, port); - } - } - }; - - const handleDisconnect = (port: chrome.runtime.Port) => { - if (port.name.includes('/')) { - const [id, requestId] = port.name.split('/'); - if (id === PopupType.REQUEST_CONNECT_POPUP) { - try { - respond(requestId, ResponseCode.ERROR, { - error: ResponseMessage.REJECT_REQUEST_CONNECT, - }); - } catch (error) { - return; - } - } else if (id === PopupType.SIGNATURE_POPUP) { - try { - respond(requestId, ResponseCode.ERROR, { - error: ResponseMessage.REJECT_COMMON_REQUEST, - }); - } catch (error) { - return; - } - } else if (id === PopupType.REQUEST_INSTALL_LAYOUT_POPUP) { - try { - respond(requestId, ResponseCode.ERROR); - } catch (error) { - return; - } - } - } - - port.onMessage.removeListener(handleInComingMessage); - port.onDisconnect.removeListener(handleDisconnect); - }; - - port.onMessage.addListener(handleInComingMessage); - port.onDisconnect.addListener(handleDisconnect); - }); - - callbackRegistry.kernel = onKernelMessage; + const kernel = new ChromeKernel(); + kernel + .channel(Channels.kernel) + .handle(RequestType.REQUEST_CONNECT) + .use(checkConnection(kernel)) + .use(common.connect) + .handle(RequestType.REQUEST_DISCONNECT) + .use(common.disconnect) + .handle(RequestType.SIGN_MESSAGE_ON_SOLANA) + .use(requestUserSignature(kernel)) + .use(solana.signMessage) + + .channel(Channels.popup) + .handle(RequestType.REQUEST_PAYLOAD) + .use(common.requestPayload(kernel)) + .handle(RequestType.REQUEST_CONNECT) + .use(kernel.handleCrossResolvingMiddleware) + .handle(RequestType.RESOLVE_REQUEST_SIGNATURE) + .use(kernel.handleCrossResolvingMiddleware) + + .run(); }; diff --git a/apps/wallet/browser/kernel/utils/middleware.ts b/apps/wallet/browser/kernel/utils/middleware.ts index eee54e22b..5ec470356 100644 --- a/apps/wallet/browser/kernel/utils/middleware.ts +++ b/apps/wallet/browser/kernel/utils/middleware.ts @@ -1,89 +1,148 @@ +import type { ChromeKernel } from '@metacraft/crab/chrome'; +import type { Middleware, RawRequest } from '@metacraft/crab/core'; +import type { RequestType } from '@walless/core'; import type { Networks } from '@walless/core'; import { PopupType, - RequestType, ResponseCode, ResponseMessage, + Timeout, } from '@walless/core'; +import type { Channels } from '@walless/messaging'; import { utils } from '@walless/network'; -import type { ConnectOptions } from '@walless/sdk'; import type { TrustedDomainDocument } from '@walless/store'; import { selectors } from '@walless/store'; import { storage } from 'utils/storage/db'; -import { closePopup, openPopup } from './popup'; -import { getRequestRecord, requestPool, respond } from './requestPool'; +import { openPopup } from './popup'; +import { getRequestRecord, respond } from './requestPool'; import type { HandleMethod } from './types'; -export const getPrivateKey = ( - network: Networks, -): HandleMethod<{ passcode?: string }> => { - return async ({ payload, next }) => { - const passcode = payload.passcode; - if (!passcode) { - return respond(payload.requestId, ResponseCode.REQUIRE_PASSCODE); - } +const handleUserAction = async ({ + kernel, + payload, + popupType, + resolveCallback, +}: { + kernel: ChromeKernel; + // eslint-disable-next-line + payload: any; + popupType: PopupType; + resolveCallback?: Middleware; +}) => { + const { resolveId, resolve } = kernel.createCrossResolvingRequest( + payload.requestId, + payload.timeout || Timeout.sixtySeconds, + resolveCallback, + ); - try { - const privateKey = await utils.getPrivateKey(storage, network, passcode); - next?.({ ...payload, privateKey }); - } catch { - respond(payload.requestId, ResponseCode.WRONG_PASSCODE); - } - }; + openPopup(popupType, resolveId); + + return await resolve>(); }; -export const checkConnection: HandleMethod<{ - options?: ConnectOptions; -}> = async ({ payload, next }) => { - if (!payload.options) throw Error('No connection options provided'); - const { onlyIfTrusted, domain } = payload.options; +const handlePopupResponse: Middleware = async (request, respond, resolve) => { + const { isApproved, passcode, network } = request; + if (!network) throw new Error('Required network'); + if (!isApproved) { + respond({ + message: 'User has rejected the signature request', + responseCode: ResponseCode.ERROR, + }); + } else { + const privateKey = await getPrivateKey(network, passcode); + if (privateKey) { + respond({ + message: 'Successfully sign request', + responseCode: ResponseCode.SUCCESS, + }); + resolve?.({ isApproved, privateKey }); + } else { + respond({ + message: 'Failed to decode with passcode', + responseCode: ResponseCode.WRONG_PASSCODE, + }); + } + } +}; - if (!onlyIfTrusted) { - next?.(payload); +export const getPrivateKey = async (network: Networks, passcode: string) => { + try { + return await utils.getPrivateKey(storage, network, passcode); + } catch { return; } +}; - const domainResponse = await storage.find(selectors.trustedDomains); - const trustedDomains = domainResponse.docs as TrustedDomainDocument[]; - const savedDomain = trustedDomains.find(({ _id }) => _id == domain); - if (!savedDomain || !savedDomain.connect) { - Object.values(requestPool).forEach((ele) => { - if ( - ele.payload.requestId !== payload.requestId && - ele.payload.type === RequestType.REQUEST_CONNECT && - ele.payload.options.domain === domain - ) { - respond(ele.payload.requestId, ResponseCode.ERROR); - closePopup(ele.payload.popupId); - } - }); +export const checkConnection = ( + kernel: ChromeKernel, +): Middleware => { + return async (payload, respond, next) => { + if (!payload.options) throw Error('No connection options provided <<<'); + const { onlyIfTrusted, domain } = payload.options; - await openPopup(PopupType.REQUEST_CONNECT_POPUP, payload.requestId); - } else if (!savedDomain.trusted) { - return respond(payload.requestId, ResponseCode.ERROR, { - error: ResponseMessage.REJECT_REQUEST_CONNECT, - }); - } else { - next?.(payload); - } -}; + if (!onlyIfTrusted) { + next?.(payload); + return; + } -export const checkApproval: HandleMethod<{ - isApproved?: boolean; -}> = async ({ payload, next }) => { - const { requestId, sourceRequestId, isApproved } = payload; + const domainResponse = await storage.find(selectors.trustedDomains); + const trustedDomains = domainResponse.docs as TrustedDomainDocument[]; + const savedDomain = trustedDomains.find(({ _id }) => _id == domain); + if (!savedDomain || !savedDomain.connect) { + const { isApproved } = await handleUserAction<{ isApproved: boolean }>({ + kernel, + payload, + popupType: PopupType.REQUEST_CONNECT_POPUP, + }); - if (!isApproved) { - respond(sourceRequestId, ResponseCode.ERROR, { - message: ResponseMessage.REJECT_REQUEST_CONNECT, + if (isApproved) { + next?.(payload); + } else { + respond({ + requestId: payload.id, + error: ResponseMessage.REJECT_REQUEST_CONNECT, + responseCode: ResponseCode.ERROR, + }); + } + } else if (!savedDomain.trusted) { + respond({ + requestId: payload.id, + error: ResponseMessage.REJECT_REQUEST_CONNECT, + responseCode: ResponseCode.ERROR, + }); + } else { + next?.(payload); + } + }; +}; + +export const requestUserSignature = ( + kernel: ChromeKernel, +): Middleware => { + return async (payload, respond, next) => { + const { isApproved, privateKey } = await handleUserAction<{ + passcode: string; + isApproved: boolean; + privateKey: Uint8Array; + }>({ + kernel, + payload, + popupType: PopupType.SIGNATURE_POPUP, + resolveCallback: handlePopupResponse, }); - } else { - payload = getRequestRecord(sourceRequestId).payload; - await next?.(payload); - } - respond(requestId, ResponseCode.SUCCESS); + console.log('handle user request sign >>>', isApproved, privateKey); + if (isApproved) { + next?.({ ...payload, privateKey }); + } else { + respond({ + requestId: payload.id, + error: ResponseMessage.REJECT_COMMON_REQUEST, + responseCode: ResponseCode.ERROR, + }); + } + }; }; export const filterSDKSignatureRequest: HandleMethod<{ diff --git a/apps/wallet/browser/kernel/utils/popup.ts b/apps/wallet/browser/kernel/utils/popup.ts index 8c484c591..589db002d 100644 --- a/apps/wallet/browser/kernel/utils/popup.ts +++ b/apps/wallet/browser/kernel/utils/popup.ts @@ -1,4 +1,4 @@ -export const openPopup = async (id: string, requestId: string) => { +export const openPopup = async (id: string, resolveId: string) => { const lastFocusedWindow = await chrome.windows.getLastFocused(); const { top, left = 0, width = 0 } = lastFocusedWindow; const leftPos = left + width - 420; @@ -9,7 +9,7 @@ export const openPopup = async (id: string, requestId: string) => { type: 'popup', width: 420, height: 600, - url: `popup.html#/requests/${id}/${requestId}`, + url: `popup.html#/requests/${id}/${resolveId}`, focused: true, }); }; diff --git a/apps/wallet/src/screens/Requests/Connect.tsx b/apps/wallet/src/screens/Requests/Connect.tsx index e37afe505..04581ce53 100644 --- a/apps/wallet/src/screens/Requests/Connect.tsx +++ b/apps/wallet/src/screens/Requests/Connect.tsx @@ -17,23 +17,23 @@ import { logoSize, unknownLogo } from './shared'; type Props = StackScreenProps; const RequestConnect: FC = ({ route }) => { - const { requestId } = route.params; + const { resolveId } = route.params; const { sender, options } = useRequestData( - requestId as string, + resolveId as string, PopupType.REQUEST_CONNECT_POPUP, ); const publicKey = usePublicKeys(options.network || Networks.solana)[0]; const onApprovePress = () => { - handleRequestConnect(requestId as string, true); + handleRequestConnect(resolveId as string, true); }; const onRejectPress = () => { - handleRequestConnect(requestId as string, false); + handleRequestConnect(resolveId as string, false); }; useEffect(() => { - initializeKernelConnect(PopupType.REQUEST_CONNECT_POPUP + '/' + requestId); + initializeKernelConnect(PopupType.REQUEST_CONNECT_POPUP + '/' + resolveId); }, []); const title = sender.tab?.title || 'Unknown'; diff --git a/apps/wallet/src/screens/Requests/Signature/index.tsx b/apps/wallet/src/screens/Requests/Signature/index.tsx index 132b4fa55..0e51dbe4c 100644 --- a/apps/wallet/src/screens/Requests/Signature/index.tsx +++ b/apps/wallet/src/screens/Requests/Signature/index.tsx @@ -2,11 +2,9 @@ import type { FC } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react'; import { StyleSheet } from 'react-native'; import type { StackScreenProps } from '@react-navigation/stack'; -import type { RequestType } from '@walless/core'; import { Networks, PopupType } from '@walless/core'; import type { SlideOption } from '@walless/gui'; import { Slider } from '@walless/gui'; -import type { ResponsePayload } from '@walless/messaging'; import { handleRequestSignature } from 'bridge'; import { initializeKernelConnect } from 'bridge/helpers'; import type { PayloadOptions } from 'bridge/utils'; @@ -19,21 +17,22 @@ import RequestSignaturePasscode from './Passcode'; type Props = StackScreenProps; export const RequestSignature: FC = ({ route }) => { - const { requestId } = route.params; - const { sender, message, transaction, type } = useRequestData( - requestId as string, + const { resolveId } = route.params; + const { sender, message, transaction, type, network } = useRequestData( + resolveId as string, PopupType.SIGNATURE_POPUP, ); const [activeIndex, setActiveIndex] = useState(0); const options = useRef({ - sourceRequestId: requestId as string, + sourceRequestId: resolveId as string, isApproved: false, passcode: '', + network, }); const publicKeys = usePublicKeys(Networks.solana)[0]; const handleDenyRequest = useCallback(async () => { - await handleRequestSignature(options.current, type as RequestType); + await handleRequestSignature(options.current); window.close(); }, [type]); @@ -43,9 +42,11 @@ export const RequestSignature: FC = ({ route }) => { }; const handleResolveRequest = useCallback( - async (passcode: string): Promise => { + async (passcode: string) => { options.current.passcode = passcode; - return await handleRequestSignature(options.current, type as RequestType); + options.current.network = network; + const res = await handleRequestSignature(options.current); + return res; }, [type], ); @@ -68,14 +69,14 @@ export const RequestSignature: FC = ({ route }) => { component: () => ( ), }, ]; useEffect(() => { - initializeKernelConnect(`${PopupType.SIGNATURE_POPUP}/${requestId}`); + initializeKernelConnect(PopupType.SIGNATURE_POPUP + '/' + resolveId); }, []); return ( diff --git a/apps/wallet/src/utils/hooks/request.ts b/apps/wallet/src/utils/hooks/request.ts index 881f4fa14..94865e1c2 100644 --- a/apps/wallet/src/utils/hooks/request.ts +++ b/apps/wallet/src/utils/hooks/request.ts @@ -1,21 +1,22 @@ import { useEffect, useState } from 'react'; -import type { UnknownObject } from '@walless/core'; +import type { Networks, UnknownObject } from '@walless/core'; import { runtime } from '@walless/core'; import type { MessagePayload } from '@walless/messaging'; import type { ConnectOptions } from '@walless/sdk'; import { getDataFromSourceRequest } from 'bridge'; -export const useRequestData = (requestId: string, from: string) => { +export const useRequestData = (resolveId: string, from: string) => { const [sender, setSender] = useState({}); const [options, setOptions] = useState({}); const [message, setMessage] = useState(''); const [transaction, setTransaction] = useState(''); const [payload, setPayload] = useState(); + const [network, setNetwork] = useState(); useEffect(() => { const configureSender = async () => { - const result = await getDataFromSourceRequest(requestId, from); - const { sender, message, transaction, options } = result ?? {}; + const result = await getDataFromSourceRequest(resolveId, from); + const { sender, message, transaction, options, network } = result ?? {}; setPayload(result as MessagePayload); @@ -34,12 +35,16 @@ export const useRequestData = (requestId: string, from: string) => { if (options) { setOptions(options); } + + if (network) { + setNetwork(network); + } }; if (runtime.isExtension) { configureSender(); } - }, [requestId]); + }, [resolveId]); return { ...payload, @@ -47,5 +52,6 @@ export const useRequestData = (requestId: string, from: string) => { message, transaction, options, + network, }; }; diff --git a/apps/wallet/src/utils/navigation/index.ts b/apps/wallet/src/utils/navigation/index.ts index fe3e399c2..adbaa3d37 100644 --- a/apps/wallet/src/utils/navigation/index.ts +++ b/apps/wallet/src/utils/navigation/index.ts @@ -93,13 +93,13 @@ export const linking: LinkingOptions = { path: '/requests', screens: { RequestConnect: { - path: '/connect/:requestId', + path: '/connect/:resolveId', }, RequestSignature: { - path: '/signature/:requestId', + path: '/signature/:resolveId', }, RequestInstallLayout: { - path: '/install-layout/:requestId', + path: '/install-layout/:resolveId', }, }, }, diff --git a/apps/wallet/src/utils/navigation/types.ts b/apps/wallet/src/utils/navigation/types.ts index 09a8dcd2d..3e0fb2897 100644 --- a/apps/wallet/src/utils/navigation/types.ts +++ b/apps/wallet/src/utils/navigation/types.ts @@ -60,9 +60,9 @@ export type DashboardParamList = { }; export type RequestsParamList = { - RequestConnect: { requestId: string }; - RequestSignature: { requestId: string }; - RequestInstallLayout: { requestId: string }; + RequestConnect: { resolveId: string }; + RequestSignature: { resolveId: string }; + RequestInstallLayout: { resolveId: string }; }; export type RootParamList = { diff --git a/package.json b/package.json index 285040f6e..4b59abc8b 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "reset": "find . -type dir -name node_modules | xargs rm -rf && rm -rf yarn.lock" }, "dependencies": { + "@metacraft/crab": "^0.0.5", "@tkey/chrome-storage": "9.0.0" }, "devDependencies": { diff --git a/packages/sdk/providers/solana.ts b/packages/sdk/providers/solana.ts index 56a40555f..9fd96e0cd 100644 --- a/packages/sdk/providers/solana.ts +++ b/packages/sdk/providers/solana.ts @@ -1,4 +1,4 @@ -import { RequestType, Timeout } from '@walless/core'; +import { Networks, RequestType, Timeout } from '@walless/core'; import { sendRequest } from '../utils/messaging'; @@ -23,6 +23,7 @@ export const requestSignMessage = async (message: string) => { from: 'walless@sdk', type: RequestType.SIGN_MESSAGE_ON_SOLANA, message, + network: Networks.solana, }, Timeout.sixtySeconds, ); @@ -34,6 +35,7 @@ export const requestSignTransaction = async (transaction: string) => { from: 'walless@sdk', type: RequestType.SIGN_TRANSACTION_ON_SOLANA, transaction, + network: Networks.solana, }, Timeout.sixtySeconds, ); diff --git a/yarn.lock b/yarn.lock index 5f3196187..58a7c75bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6137,6 +6137,13 @@ __metadata: languageName: node linkType: hard +"@metacraft/crab@npm:^0.0.5": + version: 0.0.5 + resolution: "@metacraft/crab@npm:0.0.5" + checksum: 10/0903289b66026ae5bf48b3dbcbbfc1aa1f90cc7c982f3eb8f26f1414754f9e98bee8c98eca4b39c35e3fdfde27bd09b57334a27dc0dace007f771234b2184c17 + languageName: node + linkType: hard + "@metaplex-foundation/beet-solana@npm:0.4.0": version: 0.4.0 resolution: "@metaplex-foundation/beet-solana@npm:0.4.0" @@ -27587,6 +27594,7 @@ __metadata: "@graphql-codegen/typescript": "npm:^4.0.0" "@graphql-codegen/typescript-resolvers": "npm:^4.0.0" "@jest/globals": "npm:^29.4.3" + "@metacraft/crab": "npm:^0.0.5" "@next/eslint-plugin-next": "npm:^13.5.4" "@react-native-clipboard/clipboard": "npm:^1.13.0" "@solana/spl-account-compression": "npm:^0.2.0"