diff --git a/packages/client/src/MWPClient.test.ts b/packages/client/src/MWPClient.test.ts index 5d01504..036aa75 100644 --- a/packages/client/src/MWPClient.test.ts +++ b/packages/client/src/MWPClient.test.ts @@ -1,5 +1,4 @@ -import { WebBasedWalletCommunicator } from 'src/components/communicator/webBased/Communicator'; - +import { postRequestToWallet } from './components/communication/postRequestToWallet'; import { KeyManager } from './components/key/KeyManager'; import { MWPClient } from './MWPClient'; import { @@ -25,11 +24,10 @@ jest.mock(':core/util/utils', () => { }; }); +jest.mock('./components/communication/postRequestToWallet'); + jest.mock('expo-web-browser', () => ({ - openBrowserAsync: jest.fn(), - WebBrowserPresentationStyle: { - FORM_SHEET: 'FORM_SHEET', - }, + openAuthSessionAsync: jest.fn(), dismissBrowser: jest.fn(), })); @@ -70,15 +68,12 @@ describe('MWPClient', () => { beforeEach(async () => { mockMetadata = { - appName: 'test', - appChainIds: [1], - appDeeplinkUrl: 'https://example.com', - appCustomScheme: 'myapp://', + name: 'test', + chainIds: [1], + customScheme: 'myapp://', }; - jest - .spyOn(WebBasedWalletCommunicator, 'postRequestAndWaitForResponse') - .mockResolvedValue(mockSuccessResponse); + (postRequestToWallet as jest.Mock).mockResolvedValue(mockSuccessResponse); mockKeyManager = new KeyManager({ wallet: mockWallet, @@ -104,10 +99,9 @@ describe('MWPClient', () => { expect(client['chain']).toEqual({ id: 1 }); expect(client['accounts']).toEqual([]); expect(client['metadata']).toEqual({ - appName: 'test', - appChainIds: [1], - appDeeplinkUrl: `https://example.com/${MWP_RESPONSE_PATH}`, - appCustomScheme: `myapp:///${MWP_RESPONSE_PATH}`, + name: 'test', + chainIds: [1], + customScheme: `myapp:///${MWP_RESPONSE_PATH}`, }); }); @@ -147,9 +141,7 @@ describe('MWPClient', () => { content: { failure: mockError }, timestamp: new Date(), }; - (WebBasedWalletCommunicator.postRequestAndWaitForResponse as jest.Mock).mockResolvedValue( - mockResponse - ); + (postRequestToWallet as jest.Mock).mockResolvedValue(mockResponse); await expect(client.handshake()).rejects.toThrowError(mockError); }); @@ -188,12 +180,13 @@ describe('MWPClient', () => { const result = await client.request(mockRequest); expect(encryptContent).toHaveBeenCalled(); - expect(WebBasedWalletCommunicator.postRequestAndWaitForResponse).toHaveBeenCalledWith( + expect(postRequestToWallet).toHaveBeenCalledWith( expect.objectContaining({ sender: '0xPublicKey', content: { encrypted: encryptedData }, }), - mockWallet.scheme + `${mockMetadata.customScheme}/${MWP_RESPONSE_PATH}`, + mockWallet ); expect(result).toEqual('0xSignature'); }); @@ -227,12 +220,13 @@ describe('MWPClient', () => { await client.request(mockRequest); - expect(WebBasedWalletCommunicator.postRequestAndWaitForResponse).toHaveBeenCalledWith( + expect(postRequestToWallet).toHaveBeenCalledWith( expect.objectContaining({ sender: '0xPublicKey', content: { encrypted: encryptedData }, }), - mockWallet.scheme + `${mockMetadata.customScheme}/${MWP_RESPONSE_PATH}`, + mockWallet ); }); diff --git a/packages/client/src/MWPClient.ts b/packages/client/src/MWPClient.ts index 8b2c5b0..3d8d980 100644 --- a/packages/client/src/MWPClient.ts +++ b/packages/client/src/MWPClient.ts @@ -16,7 +16,7 @@ const ACCOUNTS_KEY = 'accounts'; const ACTIVE_CHAIN_STORAGE_KEY = 'activeChain'; const AVAILABLE_CHAINS_STORAGE_KEY = 'availableChains'; const WALLET_CAPABILITIES_STORAGE_KEY = 'walletCapabilities'; -import * as Communicator from './components/communicator'; +import { postRequestToWallet } from './components/communication/postRequestToWallet'; import { LIB_VERSION } from './version'; import { appendMWPResponsePath, @@ -47,11 +47,8 @@ export class MWPClient { private constructor({ metadata, wallet }: MWPClientOptions) { this.metadata = { ...metadata, - appName: metadata.appName || 'Dapp', - appDeeplinkUrl: appendMWPResponsePath(metadata.appDeeplinkUrl), - appCustomScheme: metadata.appCustomScheme - ? appendMWPResponsePath(metadata.appCustomScheme) - : undefined, + name: metadata.name || 'Dapp', + customScheme: appendMWPResponsePath(metadata.customScheme), }; this.wallet = wallet; @@ -61,7 +58,7 @@ export class MWPClient { // default values this.accounts = []; this.chain = { - id: metadata.appChainIds?.[0] ?? 1, + id: metadata.chainIds?.[0] ?? 1, }; this.handshake = this.handshake.bind(this); @@ -94,13 +91,14 @@ export class MWPClient { handshake: { method: 'eth_requestAccounts', params: { - appName: this.metadata.appName, - appLogoUrl: this.metadata.appLogoUrl, + appName: this.metadata.name, + appLogoUrl: this.metadata.logoUrl, }, }, }); - const response: RPCResponseMessage = await Communicator.postRequestToWallet( + const response: RPCResponseMessage = await postRequestToWallet( handshakeMessage, + this.metadata.customScheme, this.wallet ); @@ -179,7 +177,7 @@ export class MWPClient { await this.keyManager.clear(); this.accounts = []; this.chain = { - id: this.metadata.appChainIds?.[0] ?? 1, + id: this.metadata.chainIds?.[0] ?? 1, }; } @@ -225,7 +223,7 @@ export class MWPClient { ); const message = await this.createRequestMessage({ encrypted }); - return Communicator.postRequestToWallet(message, this.wallet); + return postRequestToWallet(message, this.metadata.customScheme, this.wallet); } private async createRequestMessage( @@ -238,8 +236,7 @@ export class MWPClient { content, sdkVersion: LIB_VERSION, timestamp: new Date(), - callbackUrl: this.metadata.appDeeplinkUrl, - customScheme: this.metadata.appCustomScheme, + callbackUrl: this.metadata.customScheme, }; } diff --git a/packages/client/src/components/communication/postRequestToWallet.test.ts b/packages/client/src/components/communication/postRequestToWallet.test.ts new file mode 100644 index 0000000..8248556 --- /dev/null +++ b/packages/client/src/components/communication/postRequestToWallet.test.ts @@ -0,0 +1,114 @@ +import * as WebBrowser from 'expo-web-browser'; + +import { postRequestToWallet } from './postRequestToWallet'; +import { decodeResponseURLParams, encodeRequestURLParams } from './utils/encoding'; +import { RPCRequestMessage, RPCResponseMessage } from ':core/message'; +import { Wallet } from ':core/wallet'; + +jest.mock('expo-web-browser', () => ({ + openAuthSessionAsync: jest.fn(), + dismissBrowser: jest.fn(), +})); + +jest.mock('./utils/encoding', () => ({ + ...jest.requireActual('./utils/encoding'), + decodeResponseURLParams: jest.fn(), +})); + +const mockAppCustomScheme = 'myapp://'; +const mockWalletScheme = 'https://example.com'; + +describe('postRequestToWallet', () => { + const mockRequest: RPCRequestMessage = { + id: '1-2-3-4-5', + sdkVersion: '1.0.0', + content: { + handshake: { + method: 'eth_requestAccounts', + params: { appName: 'test' }, + }, + }, + callbackUrl: 'https://example.com', + sender: 'Sender', + timestamp: new Date(), + }; + const mockResponse: RPCResponseMessage = { + id: '2-2-3-4-5', + requestId: '1-2-3-4-5', + content: { + encrypted: { + iv: new Uint8Array([1]), + cipherText: new Uint8Array([2]), + }, + }, + sender: 'some-sender', + timestamp: new Date(), + }; + let requestUrl: URL; + + beforeEach(() => { + requestUrl = new URL(mockWalletScheme); + requestUrl.search = encodeRequestURLParams(mockRequest); + jest.clearAllMocks(); + }); + + it('should successfully post request to a web-based wallet', async () => { + const webWallet: Wallet = { type: 'web', scheme: mockWalletScheme } as Wallet; + (WebBrowser.openAuthSessionAsync as jest.Mock).mockResolvedValue({ + type: 'success', + url: 'https://example.com/response', + }); + (decodeResponseURLParams as jest.Mock).mockResolvedValue(mockResponse); + + const result = await postRequestToWallet(mockRequest, mockAppCustomScheme, webWallet); + + expect(WebBrowser.openAuthSessionAsync).toHaveBeenCalledWith( + requestUrl.toString(), + mockAppCustomScheme, + { + preferEphemeralSession: false, + } + ); + expect(result).toEqual(mockResponse); + }); + + it('should throw an error if the user cancels the request', async () => { + const webWallet: Wallet = { type: 'web', scheme: mockWalletScheme } as Wallet; + (WebBrowser.openAuthSessionAsync as jest.Mock).mockResolvedValue({ + type: 'cancel', + }); + + await expect(postRequestToWallet(mockRequest, mockAppCustomScheme, webWallet)).rejects.toThrow( + 'User rejected the request' + ); + }); + + it('should throw an error for native wallet type', async () => { + const nativeWallet: Wallet = { type: 'native', scheme: mockWalletScheme } as Wallet; + + await expect( + postRequestToWallet(mockRequest, mockAppCustomScheme, nativeWallet) + ).rejects.toThrow('Native wallet not supported yet'); + }); + + it('should throw an error for unsupported wallet type', async () => { + const unsupportedWallet: Wallet = { + type: 'unsupported' as any, + scheme: mockWalletScheme, + } as Wallet; + + await expect( + postRequestToWallet(mockRequest, mockAppCustomScheme, unsupportedWallet) + ).rejects.toThrow('Unsupported wallet type'); + }); + + it('should pass through any errors from WebBrowser', async () => { + const webWallet: Wallet = { type: 'web', scheme: mockWalletScheme } as Wallet; + const mockError = new Error('Communication error'); + (WebBrowser.openAuthSessionAsync as jest.Mock).mockRejectedValue(mockError); + + await expect(postRequestToWallet(mockRequest, mockAppCustomScheme, webWallet)).rejects.toThrow( + 'User rejected the request' + ); + }); +}); diff --git a/packages/client/src/components/communication/postRequestToWallet.ts b/packages/client/src/components/communication/postRequestToWallet.ts new file mode 100644 index 0000000..dbcb2f7 --- /dev/null +++ b/packages/client/src/components/communication/postRequestToWallet.ts @@ -0,0 +1,59 @@ +import * as WebBrowser from 'expo-web-browser'; + +import { decodeResponseURLParams } from './utils/encoding'; +import { encodeRequestURLParams } from './utils/encoding'; +import { standardErrors } from ':core/error'; +import { RPCRequestMessage, RPCResponseMessage } from ':core/message'; +import { Wallet } from ':core/wallet'; + +/** + * Posts a request to a wallet and waits for the response. + * + * @param request - The request to send. + * @param wallet - The wallet to send the request to. + * @returns A promise that resolves to the response. + */ +export async function postRequestToWallet( + request: RPCRequestMessage, + appCustomScheme: string, + wallet: Wallet +): Promise { + const { type, scheme } = wallet; + + if (type === 'web') { + return new Promise((resolve, reject) => { + // 1. generate request URL + const requestUrl = new URL(scheme); + requestUrl.search = encodeRequestURLParams(request); + + // 2. send request via Expo WebBrowser + WebBrowser.openAuthSessionAsync(requestUrl.toString(), appCustomScheme, { + preferEphemeralSession: false, + }) + .then((result) => { + if (result.type === 'cancel') { + // iOS only: user cancelled the request + reject(standardErrors.provider.userRejectedRequest()); + WebBrowser.dismissBrowser(); + } + + if (result.type === 'success') { + const { searchParams } = new URL(result.url); + const response = decodeResponseURLParams(searchParams); + + resolve(response); + } + }) + .catch(() => { + reject(standardErrors.provider.userRejectedRequest()); + WebBrowser.dismissBrowser(); + }); + }); + } + + if (type === 'native') { + throw new Error('Native wallet not supported yet'); + } + + throw new Error('Unsupported wallet type'); +} diff --git a/packages/client/src/components/communicator/webBased/encoding.test.ts b/packages/client/src/components/communication/utils/encoding.test.ts similarity index 100% rename from packages/client/src/components/communicator/webBased/encoding.test.ts rename to packages/client/src/components/communication/utils/encoding.test.ts diff --git a/packages/client/src/components/communicator/webBased/encoding.ts b/packages/client/src/components/communication/utils/encoding.ts similarity index 100% rename from packages/client/src/components/communicator/webBased/encoding.ts rename to packages/client/src/components/communication/utils/encoding.ts diff --git a/packages/client/src/components/communicator/webBased/types.ts b/packages/client/src/components/communication/utils/types.ts similarity index 100% rename from packages/client/src/components/communicator/webBased/types.ts rename to packages/client/src/components/communication/utils/types.ts diff --git a/packages/client/src/components/communicator/handleResponse.test.ts b/packages/client/src/components/communicator/handleResponse.test.ts deleted file mode 100644 index 2490395..0000000 --- a/packages/client/src/components/communicator/handleResponse.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { handleResponse } from './handleResponse'; -import { WebBasedWalletCommunicator } from './webBased/Communicator'; -import { MWP_RESPONSE_PATH } from ':core/constants'; - -jest.mock('./webBased/Communicator', () => ({ - WebBasedWalletCommunicator: { - handleResponse: jest.fn(), - }, -})); - -describe('handleResponse', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should return false if the pathname does not include MWP_RESPONSE_PATH', () => { - const responseUrl = 'https://example.com/some-other-path'; - const result = handleResponse(responseUrl); - expect(result).toBe(false); - expect(WebBasedWalletCommunicator.handleResponse).not.toHaveBeenCalled(); - }); - - it('should return true if WebBasedWalletCommunicator handles the response successfully', () => { - const responseUrl = `https://example.com/${MWP_RESPONSE_PATH}/some-params`; - (WebBasedWalletCommunicator.handleResponse as jest.Mock).mockReturnValue(true); - - const result = handleResponse(responseUrl); - - expect(result).toBe(true); - expect(WebBasedWalletCommunicator.handleResponse).toHaveBeenCalledWith(responseUrl); - }); - - it('should return false if WebBasedWalletCommunicator does not handle the response', () => { - const responseUrl = `https://example.com/${MWP_RESPONSE_PATH}/some-params`; - (WebBasedWalletCommunicator.handleResponse as jest.Mock).mockReturnValue(false); - - const result = handleResponse(responseUrl); - - expect(result).toBe(false); - expect(WebBasedWalletCommunicator.handleResponse).toHaveBeenCalledWith(responseUrl); - }); - - it('should handle different URL formats correctly', () => { - const responseUrls = [ - `https://example.com/${MWP_RESPONSE_PATH}`, - `https://example.com/${MWP_RESPONSE_PATH}/`, - `https://example.com/${MWP_RESPONSE_PATH}?param=value`, - `https://example.com/${MWP_RESPONSE_PATH}/?param=value`, - ]; - - responseUrls.forEach((url) => { - (WebBasedWalletCommunicator.handleResponse as jest.Mock).mockReturnValue(true); - expect(handleResponse(url)).toBe(true); - expect(WebBasedWalletCommunicator.handleResponse).toHaveBeenCalledWith(url); - }); - }); - - it('should throw an error for invalid URLs', () => { - const invalidUrl = 'not-a-valid-url'; - expect(() => handleResponse(invalidUrl)).toThrow('Invalid URL'); - }); -}); diff --git a/packages/client/src/components/communicator/handleResponse.ts b/packages/client/src/components/communicator/handleResponse.ts deleted file mode 100644 index f106e4c..0000000 --- a/packages/client/src/components/communicator/handleResponse.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { WebBasedWalletCommunicator } from './webBased/Communicator'; -import { MWP_RESPONSE_PATH } from ':core/constants'; - -/** - * Handles the response from a deeplink. - * - * @param responseUrl - The URL of the response. - * @returns A boolean indicating whether the response was handled successfully. - */ -export function handleResponse(responseUrl: string): boolean { - const pathname = new URL(responseUrl).pathname; - - if (!pathname.includes(MWP_RESPONSE_PATH)) { - return false; - } - - if (WebBasedWalletCommunicator.handleResponse(responseUrl)) { - return true; - } - - // NativeWalletCommunicator.handleResponse - - return false; -} diff --git a/packages/client/src/components/communicator/index.ts b/packages/client/src/components/communicator/index.ts deleted file mode 100644 index c2a5dc1..0000000 --- a/packages/client/src/components/communicator/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './handleResponse'; -export * from './postRequestToWallet'; diff --git a/packages/client/src/components/communicator/postRequestToWallet.test.ts b/packages/client/src/components/communicator/postRequestToWallet.test.ts deleted file mode 100644 index b1e44fb..0000000 --- a/packages/client/src/components/communicator/postRequestToWallet.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { postRequestToWallet } from './postRequestToWallet'; -import { WebBasedWalletCommunicator } from './webBased/Communicator'; -import { RPCRequestMessage, RPCResponseMessage } from ':core/message'; -import { Wallet } from ':core/wallet'; - -jest.mock('./webBased/Communicator', () => ({ - WebBasedWalletCommunicator: { - postRequestAndWaitForResponse: jest.fn(), - }, -})); - -describe('postRequestToWallet', () => { - const mockRequest: RPCRequestMessage = { - id: '1-2-3-4-5', - sdkVersion: '1.0.0', - content: { - handshake: { - method: 'eth_requestAccounts', - params: { appName: 'test' }, - }, - }, - callbackUrl: 'https://example.com', - sender: 'Sender', - timestamp: new Date(), - }; - const mockResponse: RPCResponseMessage = { - id: '2-2-3-4-5', - requestId: '1-2-3-4-5', - content: { - encrypted: { - iv: new Uint8Array([1]), - cipherText: new Uint8Array([2]), - }, - }, - sender: 'some-sender', - timestamp: new Date(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should successfully post request to a web-based wallet', async () => { - const webBasedWallet: Wallet = { type: 'webBased', scheme: 'https' } as Wallet; - (WebBasedWalletCommunicator.postRequestAndWaitForResponse as jest.Mock).mockResolvedValue( - mockResponse - ); - - const result = await postRequestToWallet(mockRequest, webBasedWallet); - - expect(WebBasedWalletCommunicator.postRequestAndWaitForResponse).toHaveBeenCalledWith( - mockRequest, - 'https' - ); - expect(result).toEqual(mockResponse); - }); - - it('should throw an error for native wallet type', async () => { - const nativeWallet: Wallet = { type: 'native', scheme: 'native' } as Wallet; - - await expect(postRequestToWallet(mockRequest, nativeWallet)).rejects.toThrow( - 'Native wallet not supported yet' - ); - }); - - it('should throw an error for unsupported wallet type', async () => { - const unsupportedWallet: Wallet = { type: 'unsupported' as any, scheme: 'unknown' } as Wallet; - - await expect(postRequestToWallet(mockRequest, unsupportedWallet)).rejects.toThrow( - 'Unsupported wallet type' - ); - }); - - it('should pass through any errors from WebBasedWalletCommunicator', async () => { - const webBasedWallet: Wallet = { type: 'webBased', scheme: 'https' } as Wallet; - const mockError = new Error('Communication error'); - (WebBasedWalletCommunicator.postRequestAndWaitForResponse as jest.Mock).mockRejectedValue( - mockError - ); - - await expect(postRequestToWallet(mockRequest, webBasedWallet)).rejects.toThrow( - 'Communication error' - ); - }); -}); diff --git a/packages/client/src/components/communicator/postRequestToWallet.ts b/packages/client/src/components/communicator/postRequestToWallet.ts deleted file mode 100644 index 3a88d39..0000000 --- a/packages/client/src/components/communicator/postRequestToWallet.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { WebBasedWalletCommunicator } from './webBased/Communicator'; -import { RPCRequestMessage, RPCResponseMessage } from ':core/message'; -import { Wallet } from ':core/wallet'; - -/** - * Posts a request to a wallet and waits for the response. - * - * @param request - The request to send. - * @param wallet - The wallet to send the request to. - * @returns A promise that resolves to the response. - */ -export async function postRequestToWallet( - request: RPCRequestMessage, - wallet: Wallet -): Promise { - const { type, scheme } = wallet; - - if (type === 'webBased') { - return WebBasedWalletCommunicator.postRequestAndWaitForResponse(request, scheme); - } - - if (type === 'native') { - throw new Error('Native wallet not supported yet'); - } - - throw new Error('Unsupported wallet type'); -} diff --git a/packages/client/src/components/communicator/webBased/Communicator.test.ts b/packages/client/src/components/communicator/webBased/Communicator.test.ts deleted file mode 100644 index 8cee551..0000000 --- a/packages/client/src/components/communicator/webBased/Communicator.test.ts +++ /dev/null @@ -1,293 +0,0 @@ -import { WebBasedWalletCommunicator } from './Communicator'; -import { standardErrors } from ':core/error'; -import { MessageID } from ':core/message'; - -jest.mock('expo-web-browser', () => ({ - openBrowserAsync: jest.fn(), - WebBrowserPresentationStyle: { - FORM_SHEET: 'FORM_SHEET', - }, - dismissBrowser: jest.fn(), -})); - -import * as WebBrowser from 'expo-web-browser'; - -const mockUrl = 'https://coinbase.com/'; - -describe('Communicator', () => { - const mockID = '123' as MessageID; - - beforeEach(() => { - WebBasedWalletCommunicator['disconnect'](); - jest.clearAllMocks(); - }); - - describe('postRequestAndWaitForResponse', () => { - const mockRequest = { - id: mockID, - sdkVersion: '1.0.0', - callbackUrl: 'https://callback.com', - content: { - encrypted: { - iv: new Uint8Array([70, 85, 197, 66, 13, 97, 238, 140, 77, 183, 134, 63]), - cipherText: new Uint8Array([ - 131, 213, 92, 75, 184, 170, 220, 92, 49, 222, 73, 187, 37, 156, 65, 240, 89, 92, 144, - 92, 225, 54, 98, 253, 16, 248, 66, 132, 148, 245, 196, 226, 175, 50, 184, 200, 130, 240, - 225, 118, 140, 168, 59, 155, 37, 185, 71, 102, 87, 59, 41, 3, 40, 45, 64, 245, 4, 111, - 145, 235, 73, 183, 115, 202, 4, 135, 195, 138, 32, 250, 203, 169, 59, 18, 2, 244, 246, - 94, 141, 104, 163, 37, 224, 46, 163, 242, 194, 109, 147, 86, 231, 93, 16, 138, 112, 178, - 184, 116, 39, 65, 62, 147, 249, 130, 109, 214, 104, 100, 24, 163, 15, 146, 227, 24, 169, - 232, 219, 205, 51, 76, 78, 154, 114, 191, 150, 202, 147, 123, 176, 246, 36, 49, 39, 219, - 124, 248, 90, 65, 68, 160, 87, 173, 124, 216, 253, 94, 245, 231, 86, 184, 131, 3, 93, - 183, 88, - ]), - }, - }, - sender: '123', - timestamp: new Date('2022-02-01T20:30:45.500Z'), - }; - - it('should open browser with correct URL on iOS', async () => { - (WebBrowser.openBrowserAsync as jest.Mock).mockResolvedValue({ - type: 'dismiss', - }); - - WebBasedWalletCommunicator.postRequestAndWaitForResponse(mockRequest, mockUrl); - - expect(WebBrowser.openBrowserAsync).toHaveBeenCalledWith( - 'https://coinbase.com/?id=%22123%22&sender=%22123%22&sdkVersion=%221.0.0%22&callbackUrl=%22https%3A%2F%2Fcallback.com%22×tamp=%222022-02-01T20%3A30%3A45.500Z%22&content=%7B%22encrypted%22%3A%7B%22iv%22%3A%22RlXFQg1h7oxNt4Y%2F%22%2C%22cipherText%22%3A%22g9VcS7iq3Fwx3km7JZxB8FlckFzhNmL9EPhChJT1xOKvMrjIgvDhdoyoO5sluUdmVzspAygtQPUEb5HrSbdzygSHw4og%2BsupOxIC9PZejWijJeAuo%2FLCbZNW510QinCyuHQnQT6T%2BYJt1mhkGKMPkuMYqejbzTNMTppyv5bKk3uw9iQxJ9t8%2BFpBRKBXrXzY%2FV7151a4gwNdt1g%3D%22%7D%7D', - { - presentationStyle: WebBrowser.WebBrowserPresentationStyle.FORM_SHEET, - } - ); - expect(WebBasedWalletCommunicator['responseHandlers'].get(mockID)).toBeDefined(); - }); - - it('should open browser with correct URL on Android', async () => { - (WebBrowser.openBrowserAsync as jest.Mock).mockResolvedValue({ - type: 'opened', - }); - - WebBasedWalletCommunicator.postRequestAndWaitForResponse(mockRequest, mockUrl); - - expect(WebBrowser.openBrowserAsync).toHaveBeenCalledWith( - 'https://coinbase.com/?id=%22123%22&sender=%22123%22&sdkVersion=%221.0.0%22&callbackUrl=%22https%3A%2F%2Fcallback.com%22×tamp=%222022-02-01T20%3A30%3A45.500Z%22&content=%7B%22encrypted%22%3A%7B%22iv%22%3A%22RlXFQg1h7oxNt4Y%2F%22%2C%22cipherText%22%3A%22g9VcS7iq3Fwx3km7JZxB8FlckFzhNmL9EPhChJT1xOKvMrjIgvDhdoyoO5sluUdmVzspAygtQPUEb5HrSbdzygSHw4og%2BsupOxIC9PZejWijJeAuo%2FLCbZNW510QinCyuHQnQT6T%2BYJt1mhkGKMPkuMYqejbzTNMTppyv5bKk3uw9iQxJ9t8%2BFpBRKBXrXzY%2FV7151a4gwNdt1g%3D%22%7D%7D', - { - presentationStyle: WebBrowser.WebBrowserPresentationStyle.FORM_SHEET, - } - ); - expect(WebBasedWalletCommunicator['responseHandlers'].get(mockID)).toBeDefined(); - }); - - it('should reject with user rejected error when browser is cancelled on iOS', async () => { - (WebBrowser.openBrowserAsync as jest.Mock).mockResolvedValue({ - type: 'cancel', - }); - - await expect( - WebBasedWalletCommunicator.postRequestAndWaitForResponse(mockRequest, mockUrl) - ).rejects.toEqual(standardErrors.provider.userRejectedRequest()); - - expect(WebBasedWalletCommunicator['responseHandlers'].get(mockID)).toBeUndefined(); - }); - - it('should reject with user rejected error when browser throws an error', async () => { - (WebBrowser.openBrowserAsync as jest.Mock).mockRejectedValue(new Error('Browser error')); - - await expect( - WebBasedWalletCommunicator.postRequestAndWaitForResponse(mockRequest, mockUrl) - ).rejects.toEqual(standardErrors.provider.userRejectedRequest()); - - expect(WebBasedWalletCommunicator['responseHandlers'].get(mockID)).toBeUndefined(); - }); - }); - - describe('handleResponse', () => { - const mockHandshakeResponse = { - id: '541efc8b-1aa8-4af1-88d1-34c4231f92bf', - sender: - '3059301306072a8648ce3d020106082a8648ce3d030107034200048235f8adc26178a6674f6e684e90eaacec95af520f39b1e14578b5f5648cc3379e48064dbc97331d60b9ed9ab6b4078e06c9c387872a4a4178ffbe9bc56c4a74', - requestId: '300c44da-a3b1-40d8-ad13-35132392e8dd' as MessageID, - content: { - encrypted: { - iv: new Uint8Array([198, 79, 43, 184, 133, 92, 255, 206, 76, 5, 15, 87]), - cipherText: new Uint8Array([ - 87, 77, 213, 130, 164, 31, 135, 79, 218, 132, 25, 93, 200, 28, 204, 226, 252, 216, 27, - 36, 146, 165, 27, 82, 63, 93, 0, 182, 18, 111, 55, 100, 27, 15, 11, 194, 80, 116, 115, - 93, 41, 127, 47, 38, 63, 135, 146, 221, 36, 143, 229, 83, 100, 245, 199, 3, 20, 170, 53, - 72, 255, 220, 205, 186, 31, 128, 166, 226, 19, 217, 168, 215, 255, 83, 232, 18, 65, 240, - 132, 196, 184, 6, 43, 207, 152, 44, 85, 179, 179, 17, 111, 40, 92, 41, 251, 37, 49, 66, - 235, 160, 202, 193, 188, 112, 31, 94, 216, 44, 95, 163, 210, 23, 98, 215, 245, 15, 140, - 174, 145, 165, 12, 44, 45, 124, 94, 161, 107, 70, 138, 169, 156, 135, 213, 96, 236, 250, - 29, 214, 218, 147, 29, 6, 86, 124, 188, 78, 3, 46, 0, 31, 121, 40, 139, 52, 30, 184, - 169, 184, 189, 76, 27, 82, 126, 112, 60, 222, 211, 27, 116, 184, 169, 160, 119, 231, - 190, 183, 190, 34, 40, 7, 56, 194, 246, 61, 210, 113, 170, 152, 135, 82, 254, 111, 63, - 174, 155, 252, 191, 17, 217, 93, 24, 130, 107, 117, 54, 216, 30, 99, 169, 103, 23, 47, - 246, 178, 128, 201, 95, 50, 40, 63, 243, 146, 236, 22, 78, 169, 211, 251, 138, 2, 168, - 16, 253, 218, 119, 151, 122, 66, 87, 172, 33, 162, 57, 213, 214, 223, 52, 72, 175, 58, - 138, 175, 180, 228, 68, 165, 111, 53, 232, 153, 119, 4, 165, 225, 252, 235, 129, 5, 162, - 180, 152, 96, 81, 176, 106, 198, 208, 110, 243, 85, 235, 27, 77, 58, 44, 212, 220, 42, - 72, 89, 240, 244, 166, 24, 83, 193, 238, 195, 182, 248, 129, 232, 72, 164, 81, 57, 170, - 210, 69, 93, 162, 59, 255, 236, 34, 66, 233, 218, 125, 111, 54, 64, 238, 36, 224, 222, - 73, 83, 0, 120, 5, 215, 125, 68, 66, 32, 60, 97, 73, 197, 87, 80, 72, 225, 202, 196, 17, - 200, 169, 127, 95, 132, 87, 175, 127, 26, 80, 108, 223, 80, 29, 151, 44, 179, 50, 161, - 166, 209, 48, 25, 122, 38, 240, 211, 158, 117, 218, 102, 207, 110, 236, 5, 159, 10, 180, - 219, 49, 153, 194, 129, 228, 0, 223, 154, 53, 118, 180, 99, 194, 18, 4, 65, 148, 98, - 232, 34, 97, 160, 150, 123, 243, 53, 57, 60, 129, 38, 180, 229, 69, 166, 141, 59, 45, - 26, 150, 164, 88, 191, 102, 24, 169, 160, 189, 240, 193, 116, 124, 194, 163, 153, 129, - 169, 167, 234, 121, 150, 42, 143, 205, 171, 172, 44, 103, 21, 175, 59, 37, 177, 43, 199, - 180, 158, 34, 205, 208, 179, 25, 64, 0, 134, 205, 7, 248, 114, 83, 12, 38, 218, 157, 92, - 106, 47, 176, 199, 106, 191, 171, 10, 163, 255, 115, 25, 116, 69, 138, 207, 65, 71, 250, - 233, 100, 250, 57, 199, 147, 185, 86, 196, 27, 5, 208, 93, 17, 74, 109, 111, 59, 230, - 235, 9, 221, 184, 219, 254, 158, 48, 240, 154, 44, 108, 176, 68, 188, 131, 33, 134, 87, - 68, 162, 155, 89, 235, 206, 177, 193, 117, 218, 183, 72, 4, 141, 57, 239, 232, 17, 77, - 82, 19, 37, 93, 20, 233, 214, 248, 110, 157, 175, 137, 31, 234, 99, 124, 91, 227, 127, - 11, 248, 54, 164, 103, 30, 75, 73, 23, 249, 186, 241, 139, 166, 146, 59, 25, 160, 103, - 26, 73, 94, 74, 79, 46, 109, 173, 92, 149, 217, 155, 183, 251, 34, 172, 140, 10, 223, - 155, 224, 184, 87, 6, 144, 203, 59, 1, 157, 62, 73, 82, 72, 112, 115, 68, 196, 197, 244, - 114, 186, 119, 0, 55, 170, 253, 21, 103, 215, 8, 248, 18, 155, 233, 217, 115, 74, 71, - 178, 225, 255, 190, 126, 220, 158, 166, 42, 11, 62, 161, 210, 54, 186, 64, 241, 144, 58, - 1, 114, 73, 105, 96, 135, 217, 78, 67, 249, 69, 228, 71, 177, 17, 94, 254, 255, 33, 237, - 151, 53, 201, 160, 190, 122, 158, 162, 48, 219, 177, 110, 252, 63, 44, 217, 220, 189, - 61, 231, 11, 57, 24, 159, 154, 88, 4, 162, 231, 78, 62, 183, 12, 45, 234, 133, 76, 56, - 207, 154, 44, 171, 3, 194, 20, 248, 94, 101, 50, 189, 15, 144, 227, 150, 219, 0, 37, 10, - 136, 162, 17, 20, 5, 32, 39, 73, 20, 125, 157, 124, 54, 159, 138, 151, 83, 0, 136, 180, - 128, 199, 18, 138, 45, 147, 210, 249, 24, 91, 236, 183, 131, 191, 166, 103, 140, 119, - 216, 72, 44, 95, 30, 145, 62, 211, 227, 53, 89, 121, 159, 139, 49, 3, 198, 241, 27, 28, - 242, 5, 112, 107, 159, 246, 109, 87, 77, 37, 68, 225, 164, 89, 7, 238, 89, 227, 113, - 181, 29, 245, 183, 89, 189, 137, 154, 215, 141, 251, 156, 84, 3, 63, 99, 129, 189, 105, - 231, 61, 41, 216, 71, 202, 49, 128, 211, 162, 229, 180, 116, 248, 247, 114, 125, 229, - 183, 74, 148, 102, 54, 69, 27, 105, 114, 236, 249, 48, 79, 133, 215, 137, 228, 14, 188, - 110, 242, 242, 184, 163, 182, 2, 214, 140, 44, 149, 86, 252, 30, 166, 156, 112, 187, 73, - 84, 41, 226, 149, 72, 68, 87, 52, 127, 117, 173, 64, 253, 115, 130, 224, 58, 198, 71, - 199, 193, 39, 31, 88, 51, 228, 243, 32, 156, 79, 51, 30, 246, 169, 35, 210, 238, 186, - 240, 241, 200, 216, 157, 76, 246, 98, 108, 92, 21, 229, 156, 124, 17, 52, 5, 174, 1, - 154, 97, 78, 47, 4, 114, 254, 120, 175, 158, 62, 183, 151, 189, 35, 113, 2, 43, 168, 79, - 94, 10, 246, 55, 163, 129, 83, 209, 169, 204, 73, 70, 122, 149, 22, 171, 39, 216, 223, - 110, 15, 65, 4, 102, 180, 95, 46, 250, 37, 133, 203, 231, 150, 83, 197, 162, 180, 167, - 245, 89, 229, 78, 181, 37, 98, 40, 120, 230, 169, 235, 76, 84, 98, 67, 178, 203, 62, 68, - 19, 54, 218, 9, 117, 160, 206, 57, 218, 8, 40, 3, 134, 255, 12, 94, 41, 233, 89, 4, 213, - 46, 169, 13, 76, 127, 25, 36, 235, 252, 39, 238, 125, 56, 224, 213, 67, 220, 66, 134, - 98, 244, 73, 162, 36, 37, 160, 108, 50, 188, 162, 75, 195, 101, 241, 76, 222, 193, 198, - 81, 113, 46, 233, 91, 161, 13, 145, 37, 178, 68, 32, 233, 184, 60, 90, 13, 208, 5, 179, - 185, 158, 203, 53, 219, 184, 103, 31, 187, 160, 233, 219, 226, 71, 112, 119, 39, 183, - 201, 73, 175, 57, 113, 119, 194, 1, 203, 173, 159, 77, 235, 124, 101, 187, 240, 143, - 205, 224, 163, 166, 179, 25, 178, 233, 221, 47, 4, 247, 208, 88, 249, 63, 81, 225, 246, - 231, 110, 9, 176, 168, 61, 169, 156, 193, 243, 251, 18, 98, 232, 45, 80, 9, 89, 241, - 176, 241, 240, 88, 3, 254, 246, 0, 100, 248, 134, 2, 205, 138, 54, 144, 24, 181, 208, - 154, 109, 41, 147, 65, 251, 41, 231, 51, 214, 221, 66, 77, 1, 253, 3, 19, 253, 174, 236, - 231, 241, 167, 2, 218, 219, 56, 108, 74, 127, 105, 29, 15, 133, 24, 22, 220, 159, 103, - 126, 205, 66, 104, 4, 225, 215, 84, 146, 170, 209, 77, 131, 141, 65, 46, 50, 111, 232, - 146, 208, 141, 203, 203, 212, 31, 225, 171, 93, 200, 255, 26, 139, 132, 108, 56, 148, - 50, 48, 66, 6, 116, 182, 38, 203, 24, 215, 238, 39, 41, 34, 196, 79, 148, 106, 187, 69, - 80, 4, 229, 194, 110, 38, 6, 73, 84, 247, 150, 225, 72, 170, 152, 229, 54, 143, 78, 46, - 138, 0, 137, 120, 123, 95, 174, 74, 165, 76, 236, 198, 75, 205, 45, 174, 48, 247, 3, - 217, 228, 126, 230, 167, 103, 221, 178, 154, 183, 61, 158, 151, 239, 182, 36, 191, 176, - 205, 108, 213, 230, 235, 209, 145, 205, 40, 118, 148, 236, 113, 113, 57, 168, 203, 67, - 120, 149, 96, 94, 118, 78, 12, 235, 91, 84, 11, 59, 83, 78, 21, 94, 181, 105, 114, 100, - 239, 119, 76, 18, 223, 238, 10, 147, 58, 70, 29, 228, 135, 147, 10, 88, 53, 238, 72, 24, - 30, 16, 245, 10, 41, 116, 167, 227, 152, 94, 184, 47, 214, 87, 161, 65, 168, 250, 145, - 100, 23, 198, 30, 1, 108, 103, 46, 160, 142, 28, 79, 48, 108, 95, 249, 85, 150, 187, 55, - 168, 110, 110, 243, 30, 239, 255, 180, 0, 44, 24, 194, 100, 22, 145, 242, 53, 223, 39, - 147, 150, 93, 86, 151, 89, 211, 207, 184, 91, 42, 69, 133, 85, 0, 206, 20, 50, 218, 248, - 185, 130, 35, 98, 77, 45, 130, 181, 210, 41, 9, 101, 36, 45, 215, 217, 227, 20, 246, 89, - 220, 134, 37, 214, 121, 250, 154, 101, 130, 30, 102, 234, 75, 23, 195, 124, 207, 126, - 105, 123, 223, 64, 204, 188, 13, 109, 61, 217, 71, 145, 97, 247, 118, 200, 180, 122, 11, - 184, 240, 57, 214, 232, 38, 130, 119, 170, 54, 155, 116, 126, 119, 56, 31, 153, 231, 96, - 165, 51, 31, 186, 134, 248, 7, 122, 130, 78, 22, 158, 1, 231, 190, 148, 60, 172, 135, - 129, 107, 20, 224, 152, 0, 145, 125, 5, 149, 157, 73, 129, 109, 247, 153, 201, 3, 110, - 72, 152, 231, 151, 31, 99, 73, 86, 208, 23, 239, 157, 249, 184, 213, 185, 10, 170, 247, - 25, 59, 104, 201, 188, 97, 232, 172, 169, 74, 183, 46, 183, 130, 214, 113, 68, 74, 131, - 32, 11, 158, 192, 42, 167, 204, 111, 147, 155, 195, 156, 53, 102, 200, 148, 201, 191, - 173, 5, 30, 19, 171, 17, 202, 144, 190, 219, 122, 243, 72, 222, 135, 46, 75, 230, 249, - 196, 215, 55, 65, 238, 25, 93, 159, 90, 139, 38, 43, 173, 89, 147, 189, 163, 5, 56, 233, - 146, 45, 41, 80, 6, 218, 235, 18, 65, 74, 149, 53, 65, 202, 193, 24, 20, 176, 71, 133, - 230, 12, 140, 177, 61, 108, 129, 50, 224, 161, 78, 203, 60, 226, 126, 92, 67, 83, 200, - 87, 161, 108, 79, 230, 166, 18, 10, 200, 62, 25, 187, 156, 112, 197, 19, 61, 192, 198, - 230, 207, 182, 9, 176, 52, 137, 178, 180, 56, 58, 173, 213, 27, 163, 161, 208, 220, 165, - 102, 65, 89, 67, 82, 226, 10, 88, 35, 161, 226, 88, 22, 197, 252, 241, 239, 163, 129, - 190, 247, 255, 95, 233, 34, 167, 186, 101, 113, 89, 253, 246, 33, 54, 228, 101, 170, 30, - 47, 141, 183, 134, 242, 246, 253, 48, 7, 41, 251, 86, 84, 191, 227, 196, 207, 178, 187, - 172, 16, 240, 208, 21, 141, 106, 120, 45, 182, 39, 129, 139, 142, 223, 82, 174, 174, 82, - 162, 90, 205, 18, 213, 154, 174, 115, 207, 73, 252, 255, 213, 163, 211, 180, 146, 13, - 218, 205, 211, 122, 16, 141, 225, 51, 99, 159, 47, 221, 83, 68, 232, 86, 42, 87, - ]), - }, - }, - timestamp: new Date('2024-08-09T19:10:34.785Z'), - }; - - const responseUrl = `https://callback.example.com/coinbase-wallet-sdk?id=%22541efc8b-1aa8-4af1-88d1-34c4231f92bf%22&sender=%223059301306072a8648ce3d020106082a8648ce3d030107034200048235f8adc26178a6674f6e684e90eaacec95af520f39b1e14578b5f5648cc3379e48064dbc97331d60b9ed9ab6b4078e06c9c387872a4a4178ffbe9bc56c4a74%22&content=%7B%22encrypted%22%3A%7B%22iv%22%3A%22xk8ruIVc%2F85MBQ9X%22%2C%22cipherText%22%3A%22V03VgqQfh0%2FahBldyBzM4vzYGySSpRtSP10AthJvN2QbDwvCUHRzXSl%2FLyY%2Fh5LdJI%2FlU2T1xwMUqjVI%2F9zNuh%2BApuIT2ajX%2F1PoEkHwhMS4BivPmCxVs7MRbyhcKfslMULroMrBvHAfXtgsX6PSF2LX9Q%2BMrpGlDCwtfF6ha0aKqZyH1WDs%2Bh3W2pMdBlZ8vE4DLgAfeSiLNB64qbi9TBtSfnA83tMbdLipoHfnvre%2BIigHOML2PdJxqpiHUv5vP66b%2FL8R2V0Ygmt1NtgeY6lnFy%2F2soDJXzIoP%2FOS7BZOqdP7igKoEP3ad5d6QlesIaI51dbfNEivOoqvtOREpW816Jl3BKXh%2FOuBBaK0mGBRsGrG0G7zVesbTTos1NwqSFnw9KYYU8Huw7b4gehIpFE5qtJFXaI7%2F%2BwiQunafW82QO4k4N5JUwB4Bdd9REIgPGFJxVdQSOHKxBHIqX9fhFevfxpQbN9QHZcsszKhptEwGXom8NOeddpmz27sBZ8KtNsxmcKB5ADfmjV2tGPCEgRBlGLoImGglnvzNTk8gSa05UWmjTstGpakWL9mGKmgvfDBdHzCo5mBqafqeZYqj82rrCxnFa87JbErx7SeIs3QsxlAAIbNB%2FhyUwwm2p1cai%2Bwx2q%2Fqwqj%2F3MZdEWKz0FH%2Bulk%2BjnHk7lWxBsF0F0RSm1vO%2BbrCd242%2F6eMPCaLGywRLyDIYZXRKKbWevOscF12rdIBI057%2BgRTVITJV0U6db4bp2viR%2FqY3xb438L%2BDakZx5LSRf5uvGLppI7GaBnGkleSk8uba1cldmbt%2FsirIwK35vguFcGkMs7AZ0%2BSVJIcHNExMX0crp3ADeq%2FRVn1wj4Epvp2XNKR7Lh%2F75%2B3J6mKgs%2BodI2ukDxkDoBcklpYIfZTkP5ReRHsRFe%2Fv8h7Zc1yaC%2Bep6iMNuxbvw%2FLNncvT3nCzkYn5pYBKLnTj63DC3qhUw4z5osqwPCFPheZTK9D5DjltsAJQqIohEUBSAnSRR9nXw2n4qXUwCItIDHEootk9L5GFvst4O%2FpmeMd9hILF8ekT7T4zVZeZ%2BLMQPG8Rsc8gVwa5%2F2bVdNJUThpFkH7lnjcbUd9bdZvYma1437nFQDP2OBvWnnPSnYR8oxgNOi5bR0%2BPdyfeW3SpRmNkUbaXLs%2BTBPhdeJ5A68bvLyuKO2AtaMLJVW%2FB6mnHC7SVQp4pVIRFc0f3WtQP1zguA6xkfHwScfWDPk8yCcTzMe9qkj0u668PHI2J1M9mJsXBXlnHwRNAWuAZphTi8Ecv54r54%2Bt5e9I3ECK6hPXgr2N6OBU9GpzElGepUWqyfY324PQQRmtF8u%2BiWFy%2BeWU8WitKf1WeVOtSViKHjmqetMVGJDsss%2BRBM22gl1oM452ggoA4b%2FDF4p6VkE1S6pDUx%2FGSTr%2FCfufTjg1UPcQoZi9EmiJCWgbDK8okvDZfFM3sHGUXEu6VuhDZElskQg6bg8Wg3QBbO5nss127hnH7ug6dviR3B3J7fJSa85cXfCAcutn03rfGW78I%2FN4KOmsxmy6d0vBPfQWPk%2FUeH2524JsKg9qZzB8%2FsSYugtUAlZ8bDx8FgD%2FvYAZPiGAs2KNpAYtdCabSmTQfsp5zPW3UJNAf0DE%2F2u7OfxpwLa2zhsSn9pHQ%2BFGBbcn2d%2BzUJoBOHXVJKq0U2DjUEuMm%2FoktCNy8vUH%2BGrXcj%2FGouEbDiUMjBCBnS2JssY1%2B4nKSLET5Rqu0VQBOXCbiYGSVT3luFIqpjlNo9OLooAiXh7X65KpUzsxkvNLa4w9wPZ5H7mp2fdspq3PZ6X77Ykv7DNbNXm69GRzSh2lOxxcTmoy0N4lWBedk4M61tUCztTThVetWlyZO93TBLf7gqTOkYd5IeTClg17kgYHhD1Cil0p%2BOYXrgv1lehQaj6kWQXxh4BbGcuoI4cTzBsX%2FlVlrs3qG5u8x7v%2F7QALBjCZBaR8jXfJ5OWXVaXWdPPuFsqRYVVAM4UMtr4uYIjYk0tgrXSKQllJC3X2eMU9lnchiXWefqaZYIeZupLF8N8z35pe99AzLwNbT3ZR5Fh93bItHoLuPA51ugmgneqNpt0fnc4H5nnYKUzH7qG%2BAd6gk4WngHnvpQ8rIeBaxTgmACRfQWVnUmBbfeZyQNuSJjnlx9jSVbQF%2B%2Bd%2BbjVuQqq9xk7aMm8YeisqUq3LreC1nFESoMgC57AKqfMb5Obw5w1ZsiUyb%2BtBR4TqxHKkL7bevNI3ocuS%2Bb5xNc3Qe4ZXZ9aiyYrrVmTvaMFOOmSLSlQBtrrEkFKlTVBysEYFLBHheYMjLE9bIEy4KFOyzziflxDU8hXoWxP5qYSCsg%2BGbuccMUTPcDG5s%2B2CbA0ibK0ODqt1RujodDcpWZBWUNS4gpYI6HiWBbF%2FPHvo4G%2B9%2F9f6SKnumVxWf32ITbkZaoeL423hvL2%2FTAHKftWVL%2FjxM%2Byu6wQ8NAVjWp4LbYngYuO31KurlKiWs0S1Zquc89J%2FP%2FVo9O0kg3azdN6EI3hM2OfL91TROhWKlc%3D%22%7D%7D&requestId=%22300c44da-a3b1-40d8-ad13-35132392e8dd%22×tamp=%222024-08-09T19%3A10%3A34.785Z%22`; - - it('should parse error response and call the correct handler', () => { - const mockErrorResponse = { - id: 'b6a94830-2448-44df-a6f5-8b1d601d0e61', - sender: '', - requestId: 'de9d8e02-6cd5-4979-b7b9-6c0dc1f4271c' as MessageID, - timestamp: new Date('2024-08-08T17:42:31.771Z'), - content: { - failure: { code: 4001, message: 'User denied connection request' }, - }, - }; - - const mockHandler = jest.fn(); - WebBasedWalletCommunicator['responseHandlers'].set(mockErrorResponse.requestId, mockHandler); - - const responseUrl = - 'https://callback.example.com/coinbase-wallet-sdk?id=%22b6a94830-2448-44df-a6f5-8b1d601d0e61%22&sender=%22%22&content=%7B%22failure%22%3A%7B%22code%22%3A4001%2C%22message%22%3A%22User+denied+connection+request%22%7D%7D&requestId=%22de9d8e02-6cd5-4979-b7b9-6c0dc1f4271c%22×tamp=%222024-08-08T17%3A42%3A31.771Z%22'; - - WebBasedWalletCommunicator.handleResponse(responseUrl); - - expect(mockHandler).toHaveBeenCalledWith(mockErrorResponse); - expect(WebBasedWalletCommunicator['responseHandlers'].size).toBe(0); - expect(WebBrowser.dismissBrowser).toHaveBeenCalled(); - }); - - it('should parse response and call the correct handler', () => { - const mockHandler = jest.fn(); - WebBasedWalletCommunicator['responseHandlers'].set( - mockHandshakeResponse.requestId, - mockHandler - ); - - WebBasedWalletCommunicator.handleResponse(responseUrl); - - expect(mockHandler).toHaveBeenCalledWith(mockHandshakeResponse); - expect(WebBasedWalletCommunicator['responseHandlers'].size).toBe(0); - expect(WebBrowser.dismissBrowser).toHaveBeenCalled(); - }); - - it('should not throw if no handler is found', () => { - expect(() => WebBasedWalletCommunicator.handleResponse(responseUrl)).not.toThrow(); - }); - - it('should return true if the communicator handled the message', () => { - const mockHandler = jest.fn(); - WebBasedWalletCommunicator['responseHandlers'].set( - mockHandshakeResponse.requestId, - mockHandler - ); - - const handled = WebBasedWalletCommunicator.handleResponse(responseUrl); - - expect(handled).toBe(true); - }); - - it('should return false if the communicator did not handle the message', () => { - const handled = WebBasedWalletCommunicator.handleResponse(responseUrl); - - expect(handled).toBe(false); - }); - }); - - describe('disconnect', () => { - it('should clear all response handlers', () => { - WebBasedWalletCommunicator['responseHandlers'].set('123' as MessageID, jest.fn()); - WebBasedWalletCommunicator['responseHandlers'].set('456' as MessageID, jest.fn()); - - WebBasedWalletCommunicator['disconnect'](); - - expect(WebBasedWalletCommunicator['responseHandlers'].size).toBe(0); - }); - }); -}); diff --git a/packages/client/src/components/communicator/webBased/Communicator.ts b/packages/client/src/components/communicator/webBased/Communicator.ts deleted file mode 100644 index d901717..0000000 --- a/packages/client/src/components/communicator/webBased/Communicator.ts +++ /dev/null @@ -1,70 +0,0 @@ -import * as WebBrowser from 'expo-web-browser'; - -import { decodeResponseURLParams, encodeRequestURLParams } from './encoding'; -import { standardErrors } from ':core/error'; -import { MessageID, RPCRequestMessage, RPCResponseMessage } from ':core/message'; - -class WebBasedWalletCommunicatorClass { - private responseHandlers = new Map void>(); - - postRequestAndWaitForResponse = ( - request: RPCRequestMessage, - walletScheme: string - ): Promise => { - return new Promise((resolve, reject) => { - // 1. generate request URL - const requestUrl = new URL(walletScheme); - requestUrl.search = encodeRequestURLParams(request); - - // 2. save response - this.responseHandlers.set(request.id, resolve); - - // 3. send request via native module - WebBrowser.openBrowserAsync(requestUrl.toString(), { - presentationStyle: WebBrowser.WebBrowserPresentationStyle.FORM_SHEET, - }) - .then((result) => { - if (result.type === 'cancel') { - // iOS only: user cancelled the request - reject(standardErrors.provider.userRejectedRequest()); - this.disconnect(); - } - }) - .catch(() => { - reject(standardErrors.provider.userRejectedRequest()); - this.disconnect(); - }); - }); - }; - - handleResponse = (responseUrl: string): boolean => { - const { searchParams } = new URL(responseUrl); - const response = decodeResponseURLParams(searchParams); - - const handler = this.responseHandlers.get(response.requestId); - if (handler) { - // dismissBrowser only returns a promise on iOS for when Expo SDK is >= 52 - const dismissResult = WebBrowser.dismissBrowser() as Promise | void; - if (dismissResult && typeof dismissResult.then === 'function') { - // If dismissBrowser returns a promise, handle it asynchronously - dismissResult.then(() => { - handler(response); - this.responseHandlers.delete(response.requestId); - }); - } else { - // If dismissBrowser is undefined or doesn't return a promise (Android case), handle synchronously - handler(response); - this.responseHandlers.delete(response.requestId); - } - return true; - } - return false; - }; - - private disconnect = () => { - WebBrowser.dismissBrowser(); - this.responseHandlers.clear(); - }; -} - -export const WebBasedWalletCommunicator = new WebBasedWalletCommunicatorClass(); diff --git a/packages/client/src/core/message/RPCMessage.ts b/packages/client/src/core/message/RPCMessage.ts index 904986d..434d007 100644 --- a/packages/client/src/core/message/RPCMessage.ts +++ b/packages/client/src/core/message/RPCMessage.ts @@ -1,6 +1,5 @@ import { Message, MessageID } from './Message'; import { SerializedEthereumRpcError } from ':core/error'; -import { AppMetadata } from ':core/provider/interface'; interface RPCMessage extends Message { id: MessageID; @@ -40,5 +39,8 @@ export interface RPCResponseMessage extends RPCMessage { type RequestAccountsAction = { method: 'eth_requestAccounts'; - params: Pick; + params: { + appName: string; + appLogoUrl?: string; + }; }; diff --git a/packages/client/src/core/provider/interface.ts b/packages/client/src/core/provider/interface.ts index c7cb5be..7d528da 100644 --- a/packages/client/src/core/provider/interface.ts +++ b/packages/client/src/core/provider/interface.ts @@ -35,37 +35,28 @@ export type ProviderEventCallback = ProviderInterface['emit']; export interface AppMetadata { /** - * @param appName + * @param name * @type string * @description Application name */ - appName: string; + name: string; /** - * @param appLogoUrl + * @param logoUrl * @type {string} * @description Application logo image URL */ - appLogoUrl?: string; + logoUrl?: string; /** - * @param appChainIds + * @param chainIds * @type {number[]} * @description Array of chainIds in number your dapp supports */ - appChainIds?: number[]; + chainIds?: number[]; /** - * @param appDeeplinkUrl - * @type string - * @note HTTPS URL is required for production - * @description Universal Link url on iOS or App Link url on Android to establish app's identity - * @example 'https://example.com' - */ - appDeeplinkUrl: string; - /** - * @param appCustomScheme + * @param customScheme * @type {string} - * @note Optional, but will be required in next minor version - * @description Custom URL scheme used for establishing less disruptive communication channel with wallet + * @description Custom URL scheme for returning to this app after wallet interaction * @example 'myapp://' */ - appCustomScheme?: string; + customScheme: string; } diff --git a/packages/client/src/core/wallet/index.ts b/packages/client/src/core/wallet/index.ts index 7311515..a570e3b 100644 --- a/packages/client/src/core/wallet/index.ts +++ b/packages/client/src/core/wallet/index.ts @@ -1,6 +1,6 @@ export type Wallet = | { - type: 'webBased'; + type: 'web'; name: string; scheme: string; iconUrl?: string; @@ -18,19 +18,9 @@ export type Wallet = export const Wallets = { CoinbaseSmartWallet: { - type: 'webBased', + type: 'web', name: 'Coinbase Smart Wallet', scheme: 'https://keys.coinbase.com/connect', iconUrl: 'https://wallet.coinbase.com/assets/images/favicon.ico', }, - CoinbaseWalletApp: { - type: 'native', - name: 'Coinbase Wallet App', - scheme: 'https://keys.coinbase.com/connect', - iconUrl: 'https://wallet.coinbase.com/assets/images/favicon.ico', - storeUrl: { - appStore: 'https://apps.apple.com/app/coinbase-wallet/id1278383455', - googlePlay: 'https://play.google.com/store/apps/details?id=org.toshi', - }, - }, } as const; diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index d1b11d1..c005ba4 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -2,7 +2,6 @@ import { MWPClient } from './MWPClient'; export default MWPClient; -export { handleResponse } from './components/communicator'; export type { AppMetadata, ProviderInterface } from './core/provider/interface'; export type { Wallet } from './core/wallet'; export { Wallets } from './core/wallet'; diff --git a/packages/client/src/interfaces/eip1193/EIP1193Provider.test.ts b/packages/client/src/interfaces/eip1193/EIP1193Provider.test.ts index 01a7242..c13a64b 100644 --- a/packages/client/src/interfaces/eip1193/EIP1193Provider.test.ts +++ b/packages/client/src/interfaces/eip1193/EIP1193Provider.test.ts @@ -30,7 +30,7 @@ describe('EIP1193Provider', () => { (MWPClient.createInstance as jest.Mock).mockResolvedValue(mockClient); provider = new EIP1193Provider({ - metadata: { appName: 'Test App', appDeeplinkUrl: 'test://deeplink' }, + metadata: { name: 'Test App', customScheme: 'test://deeplink' }, wallet: mockWallet, }); console.warn = jest.fn();