diff --git a/package.json b/package.json index aef6ed3..7d9a5e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mycrypto/wallets", - "version": "1.4.0", + "version": "1.4.1", "description": "Wallet abstractions to be used throughout the MyCrypto product suite.", "repository": "MyCryptoHQ/wallets", "author": "MyCrypto", diff --git a/src/implementations/deterministic/__mocks__/gridplus-sdk.ts b/src/implementations/deterministic/__mocks__/gridplus-sdk.ts index cefbde8..342df00 100644 --- a/src/implementations/deterministic/__mocks__/gridplus-sdk.ts +++ b/src/implementations/deterministic/__mocks__/gridplus-sdk.ts @@ -29,12 +29,24 @@ const convertPathToString = (path: number[]): string => export class Client { isPaired = false; hasActiveWallet = jest.fn().mockReturnValue(true); + pair = jest + .fn() + .mockImplementation( + (_secret: string, callback: (err: Error | null, hasActiveWallet: boolean) => void) => { + // For now we only pair and expect it to fail + callback(new Error('Failed to pair'), false); + } + ); connect = jest .fn() .mockImplementation( - (_deviceID: string, callback: (err: Error | null, isPaired: boolean) => void) => { - this.isPaired = true; - callback(null, true); + (deviceID: string, callback: (err: Error | null, isPaired: boolean) => void) => { + if (deviceID === 'foo') { + this.isPaired = true; + callback(null, true); + } else { + callback(null, false); + } } ); sign = jest diff --git a/src/implementations/deterministic/gridplus.test.ts b/src/implementations/deterministic/gridplus.test.ts index 68de6fe..e7d7234 100644 --- a/src/implementations/deterministic/gridplus.test.ts +++ b/src/implementations/deterministic/gridplus.test.ts @@ -34,6 +34,23 @@ describe('GridPlusWalletInstance', () => { await expect(instance.signTransaction(fTransactionRequest)).resolves.toBe(fSignedTx); }); + it('handles pairing using popup if invalid credentials', async () => { + const postMessage = jest.fn(); + window.open = jest.fn().mockReturnValue({ postMessage }); + window.addEventListener = jest.fn().mockImplementation((_type, callback) => { + callback({ origin, data: JSON.stringify(config) }); + }); + + const wallet = new GridPlusWallet({ ...config, deviceID: 'somethingelse' }); + const instance = await wallet.getWallet(DEFAULT_ETH, 0); + + expect(window.open).toHaveBeenCalled(); + expect(postMessage).toHaveBeenCalled(); + expect(window.addEventListener).toHaveBeenCalled(); + + await expect(instance.signTransaction(fTransactionRequest)).resolves.toBe(fSignedTx); + }); + it('rejects if credentials are not in response', async () => { const postMessage = jest.fn(); window.open = jest.fn().mockReturnValue({ postMessage }); diff --git a/src/implementations/deterministic/gridplus.ts b/src/implementations/deterministic/gridplus.ts index 174abd3..dccd290 100644 --- a/src/implementations/deterministic/gridplus.ts +++ b/src/implementations/deterministic/gridplus.ts @@ -38,8 +38,6 @@ export interface GridPlusCredentials { password?: string; } -// @todo Cleanup / polish - const getPrivateKey = (config: GridPlusConfiguration) => { const buf = Buffer.concat([ Buffer.from(config.password!), @@ -83,30 +81,36 @@ const waitForPairing = (config: GridPlusConfiguration): Promise { +const getClient = async ( + config: GridPlusConfiguration, + client?: Client +): Promise<{ config: GridPlusConfiguration; client: Client }> => { if (client?.isPaired && client?.hasActiveWallet()) { return { client, config }; } const { deviceID, password, ...clientConfig } = config; + + if (client === undefined && deviceID !== undefined && password !== undefined) { + const privKey = getPrivateKey(config); + client = new Client({ ...clientConfig, privKey, crypto }); + } + if (client && deviceID !== undefined && password !== undefined) { const connect = promisify(client.connect).bind(client); - const isPaired = await connect(deviceID); + const isPaired = await connect(deviceID).catch(wrapGridPlusError); if (isPaired) { return { client, config }; + } else { + // Hack to dismiss pairing screen + const pair = promisify(client.pair).bind(client); + await pair('').catch(() => null); } - } else if (deviceID === undefined || password === undefined) { - const result = await waitForPairing(config); - config = { ...config, ...result }; - } - - if (client === undefined) { - const privKey = getPrivateKey(config); - client = new Client({ ...clientConfig, privKey, crypto }); } - return { client, config }; + const result = await waitForPairing(config); + return getClient({ ...clientConfig, ...result }, client); }; export class GridPlusWalletInstance implements Wallet { diff --git a/src/types/vendor/gridplus-sdk/client.d.ts b/src/types/vendor/gridplus-sdk/client.d.ts index 6d3bb62..ab7e000 100644 --- a/src/types/vendor/gridplus-sdk/client.d.ts +++ b/src/types/vendor/gridplus-sdk/client.d.ts @@ -37,6 +37,7 @@ declare module 'gridplus-sdk' { isPaired: boolean; connect(deviceID: string, callback: (err: Error | null, isPaired: boolean) => void): void; sign(opts: SignOpts, callback: (err: Error | null, data: SignResult) => void): void; + pair(secret: string, callback: (err: Error | null, hasActiveWallet: boolean) => void): void; getAddresses(opts: AddressesOpts, callback: (err: Error | null, data: string[]) => void): void; hasActiveWallet(): boolean; }