From 23f4f9e5a65c5b5d76135e7969a228fa605ed7d5 Mon Sep 17 00:00:00 2001 From: Jungho Bang Date: Mon, 13 Nov 2023 19:48:44 -0500 Subject: [PATCH] Clean up syntax (#1060) * clean up cipher * update session variable * keyed constructor * setConnected * implements WalletLinkConnectionUpdateListener / `listener: this,` * handleWeb3ResponseMessage * remove deprecated code * tests --- packages/wallet-sdk/src/CoinbaseWalletSDK.ts | 19 +- .../src/connection/EventListener.ts | 23 --- .../connection/WalletLinkConnection.test.ts | 20 ++- .../src/connection/WalletLinkConnection.ts | 68 ++++---- .../WalletLinkConnectionCipher.test.ts | 24 ++- .../connection/WalletLinkConnectionCipher.ts | 150 ++++++++-------- .../wallet-sdk/src/provider/MobileRelayUI.ts | 2 + packages/wallet-sdk/src/provider/WalletUI.ts | 2 + packages/wallet-sdk/src/relay/MobileRelay.ts | 2 +- .../src/relay/WalletLinkRelay.test.ts | 7 +- .../wallet-sdk/src/relay/WalletLinkRelay.ts | 164 ++++++++---------- 11 files changed, 221 insertions(+), 260 deletions(-) delete mode 100644 packages/wallet-sdk/src/connection/EventListener.ts diff --git a/packages/wallet-sdk/src/CoinbaseWalletSDK.ts b/packages/wallet-sdk/src/CoinbaseWalletSDK.ts index 782f94e938..c6a2642736 100644 --- a/packages/wallet-sdk/src/CoinbaseWalletSDK.ts +++ b/packages/wallet-sdk/src/CoinbaseWalletSDK.ts @@ -3,7 +3,6 @@ import { LogoType, walletLogo } from './assets/wallet-logo'; import { DiagnosticLogger } from './connection/DiagnosticLogger'; -import { EventListener } from './connection/EventListener'; import { LINK_API_URL } from './constants'; import { ScopedLocalStorage } from './lib/ScopedLocalStorage'; import { CoinbaseWalletProvider } from './provider/CoinbaseWalletProvider'; @@ -28,9 +27,6 @@ export interface CoinbaseWalletSDKOptions { linkAPIUrl?: string; /** @optional an implementation of WalletUI; for most, leave it unspecified */ uiConstructor?: (options: Readonly) => WalletUI; - /** @optional an implementation of EventListener for debugging; for most, leave it unspecified */ - /** @deprecated in favor of diagnosticLogger */ - eventListener?: EventListener; /** @optional a diagnostic tool for debugging; for most, leave it unspecified */ diagnosticLogger?: DiagnosticLogger; /** @optional whether wallet link provider should override the isMetaMask property. */ @@ -76,20 +72,7 @@ export class CoinbaseWalletSDK { this._overrideIsCoinbaseWallet = options.overrideIsCoinbaseWallet ?? true; this._overrideIsCoinbaseBrowser = options.overrideIsCoinbaseBrowser ?? false; - if (options.diagnosticLogger && options.eventListener) { - throw new Error( - "Can't have both eventListener and diagnosticLogger options, use only diagnosticLogger" - ); - } - - if (options.eventListener) { - this._diagnosticLogger = { - // eslint-disable-next-line @typescript-eslint/unbound-method - log: options.eventListener.onEvent, - }; - } else { - this._diagnosticLogger = options.diagnosticLogger; - } + this._diagnosticLogger = options.diagnosticLogger; this._reloadOnDisconnect = options.reloadOnDisconnect ?? true; diff --git a/packages/wallet-sdk/src/connection/EventListener.ts b/packages/wallet-sdk/src/connection/EventListener.ts deleted file mode 100644 index 203135c63f..0000000000 --- a/packages/wallet-sdk/src/connection/EventListener.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** @deprecated in favor of DiagnosticLogger */ -export interface EventListener { - onEvent(eventType: string, eventProperties?: any): void; -} - -export const EVENTS = { - STARTED_CONNECTING: 'walletlink_sdk.started.connecting', - CONNECTED_STATE_CHANGE: 'walletlink_sdk.connected', - DISCONNECTED: 'walletlink_sdk.disconnected', - METADATA_DESTROYED: 'walletlink_sdk_metadata_destroyed', - LINKED: 'walletlink_sdk.linked', - FAILURE: 'walletlink_sdk.generic_failure', - SESSION_CONFIG_RECEIVED: 'walletlink_sdk.session_config_event_received', - ETH_ACCOUNTS_STATE: 'walletlink_sdk.eth_accounts_state', - SESSION_STATE_CHANGE: 'walletlink_sdk.session_state_change', - UNLINKED_ERROR_STATE: 'walletlink_sdk.unlinked_error_state', - SKIPPED_CLEARING_SESSION: 'walletlink_sdk.skipped_clearing_session', - GENERAL_ERROR: 'walletlink_sdk.general_error', - WEB3_REQUEST: 'walletlink_sdk.web3.request', - WEB3_REQUEST_PUBLISHED: 'walletlink_sdk.web3.request_published', - WEB3_RESPONSE: 'walletlink_sdk.web3.response', - UNKNOWN_ADDRESS_ENCOUNTERED: 'walletlink_sdk.unknown_address_encountered', -}; diff --git a/packages/wallet-sdk/src/connection/WalletLinkConnection.test.ts b/packages/wallet-sdk/src/connection/WalletLinkConnection.test.ts index 48ed5e25c5..23f51d4f9e 100644 --- a/packages/wallet-sdk/src/connection/WalletLinkConnection.test.ts +++ b/packages/wallet-sdk/src/connection/WalletLinkConnection.test.ts @@ -18,14 +18,18 @@ describe('WalletLinkConnection', () => { beforeEach(() => { jest.clearAllMocks(); - connection = new WalletLinkConnection(session, 'http://link-api-url', { - linkedUpdated: jest.fn(), - connectedUpdated: jest.fn(), - handleResponseMessage: jest.fn(), - chainUpdated: jest.fn(), - accountUpdated: jest.fn(), - metadataUpdated: jest.fn(), - resetAndReload: jest.fn(), + connection = new WalletLinkConnection({ + session, + linkAPIUrl: 'http://link-api-url', + listener: { + linkedUpdated: jest.fn(), + connectedUpdated: jest.fn(), + handleWeb3ResponseMessage: jest.fn(), + chainUpdated: jest.fn(), + accountUpdated: jest.fn(), + metadataUpdated: jest.fn(), + resetAndReload: jest.fn(), + }, }); listener = (connection as any).listener; }); diff --git a/packages/wallet-sdk/src/connection/WalletLinkConnection.ts b/packages/wallet-sdk/src/connection/WalletLinkConnection.ts index cd73c15ef5..6679d678f3 100644 --- a/packages/wallet-sdk/src/connection/WalletLinkConnection.ts +++ b/packages/wallet-sdk/src/connection/WalletLinkConnection.ts @@ -38,25 +38,36 @@ const REQUEST_TIMEOUT = 60000; export interface WalletLinkConnectionUpdateListener { linkedUpdated: (linked: boolean) => void; connectedUpdated: (connected: boolean) => void; - handleResponseMessage: (message: Web3ResponseMessage) => void; + handleWeb3ResponseMessage: (message: Web3ResponseMessage) => void; chainUpdated: (chainId: string, jsonRpcUrl: string) => void; accountUpdated: (selectedAddress: string) => void; metadataUpdated: (key: string, metadataValue: string) => void; resetAndReload: () => void; } +interface WalletLinkConnectionParams { + session: Session; + linkAPIUrl: string; + listener: WalletLinkConnectionUpdateListener; + diagnostic?: DiagnosticLogger; + WebSocketClass?: typeof WebSocket; +} + /** * Coinbase Wallet Connection */ export class WalletLinkConnection { - private ws: WalletLinkWebSocket; - private http: WalletLinkHTTP; - private cipher: WalletLinkConnectionCipher; private destroyed = false; private lastHeartbeatResponse = 0; private nextReqId = IntNumber(1); + private readonly session: Session; + private listener?: WalletLinkConnectionUpdateListener; + private diagnostic?: DiagnosticLogger; + private cipher: WalletLinkConnectionCipher; + private ws: WalletLinkWebSocket; + private http: WalletLinkHTTP; /** * Constructor @@ -65,19 +76,16 @@ export class WalletLinkConnection { * @param listener WalletLinkConnectionUpdateListener * @param [WebSocketClass] Custom WebSocket implementation */ - private sessionId: string; - private sessionKey: string; - constructor( - session: Session, - linkAPIUrl: string, - listener: WalletLinkConnectionUpdateListener, - private diagnostic?: DiagnosticLogger, - WebSocketClass: typeof WebSocket = WebSocket - ) { - const sessionId = (this.sessionId = session.id); - const sessionKey = (this.sessionKey = session.key); + constructor({ + session, + linkAPIUrl, + listener, + diagnostic, + WebSocketClass = WebSocket, + }: WalletLinkConnectionParams) { + this.session = session; this.cipher = new WalletLinkConnectionCipher(session.secret); - + this.diagnostic = diagnostic; this.listener = listener; const ws = new WalletLinkWebSocket(`${linkAPIUrl}/rpc`, WebSocketClass); @@ -85,7 +93,7 @@ export class WalletLinkConnection { // attempt to reconnect every 5 seconds when disconnected this.diagnostic?.log(EVENTS.CONNECTED_STATE_CHANGE, { state, - sessionIdHash: Session.hash(sessionId), + sessionIdHash: Session.hash(session.id), }); let connected = false; @@ -156,7 +164,7 @@ export class WalletLinkConnection { case 'Linked': { const msg = m as Omit & ServerMessageLinked; this.diagnostic?.log(EVENTS.LINKED, { - sessionIdHash: Session.hash(sessionId), + sessionIdHash: Session.hash(session.id), linked: msg.linked, type: m.type, onlineGuests: msg.onlineGuests, @@ -172,7 +180,7 @@ export class WalletLinkConnection { const msg = m as Omit & ServerMessageSessionConfigUpdated; this.diagnostic?.log(EVENTS.SESSION_CONFIG_RECEIVED, { - sessionIdHash: Session.hash(sessionId), + sessionIdHash: Session.hash(session.id), metadata_keys: msg && msg.metadata ? Object.keys(msg.metadata) : undefined, }); this.handleSessionMetadataUpdated(msg.metadata); @@ -192,7 +200,7 @@ export class WalletLinkConnection { }); this.ws = ws; - this.http = new WalletLinkHTTP(linkAPIUrl, sessionId, sessionKey); + this.http = new WalletLinkHTTP(linkAPIUrl, session.id, session.key); } /** @@ -203,7 +211,7 @@ export class WalletLinkConnection { throw new Error('instance is destroyed'); } this.diagnostic?.log(EVENTS.STARTED_CONNECTING, { - sessionIdHash: Session.hash(this.sessionId), + sessionIdHash: Session.hash(this.session.id), }); this.ws.connect(); } @@ -217,7 +225,7 @@ export class WalletLinkConnection { this.ws.disconnect(); this.diagnostic?.log(EVENTS.DISCONNECTED, { - sessionIdHash: Session.hash(this.sessionId), + sessionIdHash: Session.hash(this.session.id), }); this.listener = undefined; @@ -300,7 +308,7 @@ export class WalletLinkConnection { if (!isWeb3ResponseMessage(message)) return; - this.listener?.handleResponseMessage(message); + this.listener?.handleWeb3ResponseMessage(message); } catch { this.diagnostic?.log(EVENTS.GENERAL_ERROR, { message: 'Had error decrypting', @@ -341,7 +349,7 @@ export class WalletLinkConnection { public async setSessionMetadata(key: string, value: string | null) { const message = ClientMessageSetSessionConfig({ id: IntNumber(this.nextReqId++), - sessionId: this.sessionId, + sessionId: this.session.id, metadata: { [key]: value }, }); @@ -371,7 +379,7 @@ export class WalletLinkConnection { const message = ClientMessagePublishEvent({ id: IntNumber(this.nextReqId++), - sessionId: this.sessionId, + sessionId: this.session.id, event, data, callWebhook, @@ -436,8 +444,8 @@ export class WalletLinkConnection { private async authenticate() { const msg = ClientMessageHostSession({ id: IntNumber(this.nextReqId++), - sessionId: this.sessionId, - sessionKey: this.sessionKey, + sessionId: this.session.id, + sessionKey: this.session.key, }); const res = await this.makeRequest(msg); if (isServerMessageFail(res)) { @@ -448,7 +456,7 @@ export class WalletLinkConnection { private sendIsLinked(): void { const msg = ClientMessageIsLinked({ id: IntNumber(this.nextReqId++), - sessionId: this.sessionId, + sessionId: this.session.id, }); this.sendData(msg); } @@ -456,7 +464,7 @@ export class WalletLinkConnection { private sendGetSessionConfig(): void { const msg = ClientMessageGetSessionConfig({ id: IntNumber(this.nextReqId++), - sessionId: this.sessionId, + sessionId: this.session.id, }); this.sendData(msg); } @@ -490,7 +498,7 @@ export class WalletLinkConnection { this.listener?.resetAndReload(); this.diagnostic?.log(EVENTS.METADATA_DESTROYED, { alreadyDestroyed: this.isDestroyed, - sessionIdHash: Session.hash(this.sessionId), + sessionIdHash: Session.hash(this.session.id), }); }; diff --git a/packages/wallet-sdk/src/connection/WalletLinkConnectionCipher.test.ts b/packages/wallet-sdk/src/connection/WalletLinkConnectionCipher.test.ts index 456ecd07ba..204c18df71 100644 --- a/packages/wallet-sdk/src/connection/WalletLinkConnectionCipher.test.ts +++ b/packages/wallet-sdk/src/connection/WalletLinkConnectionCipher.test.ts @@ -1,17 +1,19 @@ import { randomBytesHex } from '../util'; -import { decrypt, encrypt } from './WalletLinkConnectionCipher'; +import { WalletLinkConnectionCipher } from './WalletLinkConnectionCipher'; const secret = 'c356fe708ea7bbf7b1cc9ff9813c32772b6e0d16332da4c031ba9ea88be9b5ed'; describe('aes256gcm', () => { test('encrypt/decrypt', async () => { const randSecret = randomBytesHex(32); - const encrypted = await encrypt('plain text string', randSecret); + const cipher = new WalletLinkConnectionCipher(randSecret); + + const encrypted = await cipher.encrypt('plain text string'); expect(encrypted.length).toEqual(90); // decrypted output matches original input - const decrypted = await decrypt(encrypted, randSecret); + const decrypted = await cipher.decrypt(encrypted); expect(decrypted).toBe('plain text string'); }); @@ -28,18 +30,26 @@ describe('aes256gcm', () => { }, }; - const decrypted = await decrypt(cipherText, secret); + const cipher = new WalletLinkConnectionCipher(secret); + + const decrypted = await cipher.decrypt(cipherText); expect(sampleDataResult).toEqual(JSON.parse(decrypted)); }); test('errors', async () => { - await expect(encrypt('plain text string', '123456')).rejects.toThrowError( + const cipher = new WalletLinkConnectionCipher('123456'); + + await expect(cipher.encrypt('plain text string')).rejects.toThrowError( + 'secret must be 256 bits' + ); + + await expect(cipher.decrypt('plain stext string')).rejects.toThrowError( 'secret must be 256 bits' ); - expect(() => decrypt('plain stext string', '123456')).toThrowError('secret must be 256 bits'); + const differentCipher = new WalletLinkConnectionCipher(secret); - await expect(decrypt('text', secret)).rejects.toThrow(); + await expect(differentCipher.decrypt('text')).rejects.toThrow(); }); }); diff --git a/packages/wallet-sdk/src/connection/WalletLinkConnectionCipher.ts b/packages/wallet-sdk/src/connection/WalletLinkConnectionCipher.ts index 55c7736103..a3bcec49f8 100644 --- a/packages/wallet-sdk/src/connection/WalletLinkConnectionCipher.ts +++ b/packages/wallet-sdk/src/connection/WalletLinkConnectionCipher.ts @@ -7,91 +7,83 @@ export class WalletLinkConnectionCipher { // @param secret hex representation of 32-byte secret constructor(private readonly secret: string) {} + /** + * + * @param plainText string to be encrypted + * returns hex string representation of bytes in the order: initialization vector (iv), + * auth tag, encrypted plaintext. IV is 12 bytes. Auth tag is 16 bytes. Remaining bytes are the + * encrypted plainText. + */ async encrypt(plainText: string): Promise { - return encrypt(plainText, this.secret); - } - - async decrypt(cipherText: string): Promise { - return decrypt(cipherText, this.secret); - } -} - -/** - * - * @param plainText string to be encrypted - * @param secret hex representation of 32-byte secret - * returns hex string representation of bytes in the order: initialization vector (iv), - * auth tag, encrypted plaintext. IV is 12 bytes. Auth tag is 16 bytes. Remaining bytes are the - * encrypted plainText. - */ -export async function encrypt(plainText: string, secret: string): Promise { - if (secret.length !== 64) throw Error(`secret must be 256 bits`); - const ivBytes = crypto.getRandomValues(new Uint8Array(12)); - const secretKey: CryptoKey = await crypto.subtle.importKey( - 'raw', - hexStringToUint8Array(secret), - { name: 'aes-gcm' }, - false, - ['encrypt', 'decrypt'] - ); + const secret = this.secret; + if (secret.length !== 64) throw Error(`secret must be 256 bits`); + const ivBytes = crypto.getRandomValues(new Uint8Array(12)); + const secretKey: CryptoKey = await crypto.subtle.importKey( + 'raw', + hexStringToUint8Array(secret), + { name: 'aes-gcm' }, + false, + ['encrypt', 'decrypt'] + ); - const enc = new TextEncoder(); + const enc = new TextEncoder(); - // Will return encrypted plainText with auth tag (ie MAC or checksum) appended at the end - const encryptedResult: ArrayBuffer = await window.crypto.subtle.encrypt( - { - name: 'AES-GCM', - iv: ivBytes, - }, - secretKey, - enc.encode(plainText) - ); + // Will return encrypted plainText with auth tag (ie MAC or checksum) appended at the end + const encryptedResult: ArrayBuffer = await window.crypto.subtle.encrypt( + { + name: 'AES-GCM', + iv: ivBytes, + }, + secretKey, + enc.encode(plainText) + ); - const tagLength = 16; - const authTag: ArrayBuffer = encryptedResult.slice(encryptedResult.byteLength - tagLength); - const encryptedPlaintext = encryptedResult.slice(0, encryptedResult.byteLength - tagLength); + const tagLength = 16; + const authTag: ArrayBuffer = encryptedResult.slice(encryptedResult.byteLength - tagLength); + const encryptedPlaintext = encryptedResult.slice(0, encryptedResult.byteLength - tagLength); - const authTagBytes = new Uint8Array(authTag); - const encryptedPlaintextBytes = new Uint8Array(encryptedPlaintext); - const concatted = new Uint8Array([...ivBytes, ...authTagBytes, ...encryptedPlaintextBytes]); - return uint8ArrayToHex(concatted); -} + const authTagBytes = new Uint8Array(authTag); + const encryptedPlaintextBytes = new Uint8Array(encryptedPlaintext); + const concatted = new Uint8Array([...ivBytes, ...authTagBytes, ...encryptedPlaintextBytes]); + return uint8ArrayToHex(concatted); + } -/** - * - * @param cipherText hex string representation of bytes in the order: initialization vector (iv), - * auth tag, encrypted plaintext. IV is 12 bytes. Auth tag is 16 bytes. - * @param secret hex string representation of 32-byte secret - */ -export function decrypt(cipherText: string, secret: string): Promise { - if (secret.length !== 64) throw Error(`secret must be 256 bits`); - return new Promise((resolve, reject) => { - void (async function () { - const secretKey: CryptoKey = await crypto.subtle.importKey( - 'raw', - hexStringToUint8Array(secret), - { name: 'aes-gcm' }, - false, - ['encrypt', 'decrypt'] - ); + /** + * + * @param cipherText hex string representation of bytes in the order: initialization vector (iv), + * auth tag, encrypted plaintext. IV is 12 bytes. Auth tag is 16 bytes. + */ + async decrypt(cipherText: string): Promise { + const secret = this.secret; + if (secret.length !== 64) throw Error(`secret must be 256 bits`); + return new Promise((resolve, reject) => { + void (async function () { + const secretKey: CryptoKey = await crypto.subtle.importKey( + 'raw', + hexStringToUint8Array(secret), + { name: 'aes-gcm' }, + false, + ['encrypt', 'decrypt'] + ); - const encrypted: Uint8Array = hexStringToUint8Array(cipherText); + const encrypted: Uint8Array = hexStringToUint8Array(cipherText); - const ivBytes = encrypted.slice(0, 12); - const authTagBytes = encrypted.slice(12, 28); - const encryptedPlaintextBytes = encrypted.slice(28); - const concattedBytes = new Uint8Array([...encryptedPlaintextBytes, ...authTagBytes]); - const algo = { - name: 'AES-GCM', - iv: new Uint8Array(ivBytes), - }; - try { - const decrypted = await window.crypto.subtle.decrypt(algo, secretKey, concattedBytes); - const decoder = new TextDecoder(); - resolve(decoder.decode(decrypted)); - } catch (err) { - reject(err); - } - })(); - }); + const ivBytes = encrypted.slice(0, 12); + const authTagBytes = encrypted.slice(12, 28); + const encryptedPlaintextBytes = encrypted.slice(28); + const concattedBytes = new Uint8Array([...encryptedPlaintextBytes, ...authTagBytes]); + const algo = { + name: 'AES-GCM', + iv: new Uint8Array(ivBytes), + }; + try { + const decrypted = await window.crypto.subtle.decrypt(algo, secretKey, concattedBytes); + const decoder = new TextDecoder(); + resolve(decoder.decode(decrypted)); + } catch (err) { + reject(err); + } + })(); + }); + } } diff --git a/packages/wallet-sdk/src/provider/MobileRelayUI.ts b/packages/wallet-sdk/src/provider/MobileRelayUI.ts index 148bb6d479..66bd1d942a 100644 --- a/packages/wallet-sdk/src/provider/MobileRelayUI.ts +++ b/packages/wallet-sdk/src/provider/MobileRelayUI.ts @@ -35,6 +35,8 @@ export class MobileRelayUI implements WalletUI { this.attached = true; } + setConnected(_connected: boolean) {} // no-op + closeOpenedWindow() { this.openedWindow?.close(); this.openedWindow = null; diff --git a/packages/wallet-sdk/src/provider/WalletUI.ts b/packages/wallet-sdk/src/provider/WalletUI.ts index 1210ed03fa..f86c51d184 100644 --- a/packages/wallet-sdk/src/provider/WalletUI.ts +++ b/packages/wallet-sdk/src/provider/WalletUI.ts @@ -24,6 +24,8 @@ export interface WalletUIOptions { export interface WalletUI { attach(): void; + setConnected(connected: boolean): void; + /** * Opens a qr code or auth page to connect with Coinbase Wallet mobile app * @param options onCancel callback diff --git a/packages/wallet-sdk/src/relay/MobileRelay.ts b/packages/wallet-sdk/src/relay/MobileRelay.ts index aa2ab17136..25458bb8e3 100644 --- a/packages/wallet-sdk/src/relay/MobileRelay.ts +++ b/packages/wallet-sdk/src/relay/MobileRelay.ts @@ -75,7 +75,7 @@ export class MobileRelay extends WalletLinkRelay { } // override - protected handleWeb3ResponseMessage(message: Web3ResponseMessage) { + handleWeb3ResponseMessage(message: Web3ResponseMessage) { super.handleWeb3ResponseMessage(message); if (this._enableMobileWalletLink && this.ui instanceof MobileRelayUI) { diff --git a/packages/wallet-sdk/src/relay/WalletLinkRelay.test.ts b/packages/wallet-sdk/src/relay/WalletLinkRelay.test.ts index 641f160417..44c67904d2 100644 --- a/packages/wallet-sdk/src/relay/WalletLinkRelay.test.ts +++ b/packages/wallet-sdk/src/relay/WalletLinkRelay.test.ts @@ -60,10 +60,7 @@ describe('WalletLinkRelay', () => { const relay = new WalletLinkRelay(options); - const handleWeb3ResponseMessageSpy = jest.spyOn( - (relay as any).listener, - 'handleResponseMessage' - ); + const handleWeb3ResponseMessageSpy = jest.spyOn(relay, 'handleWeb3ResponseMessage'); (relay as any).connection.ws.incomingDataListener?.(serverMessageEvent); @@ -97,7 +94,7 @@ describe('WalletLinkRelay', () => { const relay = new WalletLinkRelay(options); - const metadataUpdatedSpy = jest.spyOn((relay as any).listener, 'metadataUpdated'); + const metadataUpdatedSpy = jest.spyOn(relay, 'metadataUpdated'); (relay as any).connection.ws.incomingDataListener?.({ ...sessionConfig, diff --git a/packages/wallet-sdk/src/relay/WalletLinkRelay.ts b/packages/wallet-sdk/src/relay/WalletLinkRelay.ts index 56a5609144..30beb7eac7 100644 --- a/packages/wallet-sdk/src/relay/WalletLinkRelay.ts +++ b/packages/wallet-sdk/src/relay/WalletLinkRelay.ts @@ -2,7 +2,6 @@ // Licensed under the Apache License, version 2.0 import { DiagnosticLogger, EVENTS } from '../connection/DiagnosticLogger'; -import { EventListener } from '../connection/EventListener'; import { WalletLinkConnection, WalletLinkConnectionUpdateListener, @@ -67,12 +66,14 @@ export interface WalletLinkRelayOptions { relayEventManager: WalletSDKRelayEventManager; uiConstructor: (options: Readonly) => WalletUI; diagnosticLogger?: DiagnosticLogger; - eventListener?: EventListener; reloadOnDisconnect?: boolean; enableMobileWalletLink?: boolean; } -export class WalletLinkRelay extends WalletSDKRelayAbstract { +export class WalletLinkRelay + extends WalletSDKRelayAbstract + implements WalletLinkConnectionUpdateListener +{ private static accountRequestCallbackIds = new Set(); private readonly linkAPIUrl: string; @@ -110,20 +111,7 @@ export class WalletLinkRelay extends WalletSDKRelayAbstract { this.relayEventManager = options.relayEventManager; - if (options.diagnosticLogger && options.eventListener) { - throw new Error( - "Can't have both eventListener and diagnosticLogger options, use only diagnosticLogger" - ); - } - - if (options.eventListener) { - this.diagnostic = { - // eslint-disable-next-line @typescript-eslint/unbound-method - log: options.eventListener.onEvent, - }; - } else { - this.diagnostic = options.diagnosticLogger; - } + this.diagnostic = options.diagnosticLogger; this._reloadOnDisconnect = options.reloadOnDisconnect ?? true; @@ -133,17 +121,19 @@ export class WalletLinkRelay extends WalletSDKRelayAbstract { public subscribe() { const session = Session.load(this.storage) || new Session(this.storage).save(); - const connection = new WalletLinkConnection( + const { linkAPIUrl, diagnostic } = this; + const connection = new WalletLinkConnection({ session, - this.linkAPIUrl, - this.listener, - this.diagnostic - ); + linkAPIUrl, + diagnostic, + listener: this, + }); + const { version, darkMode } = this.options; const ui = this.options.uiConstructor({ - linkAPIUrl: this.options.linkAPIUrl, - version: this.options.version, - darkMode: this.options.darkMode, + linkAPIUrl, + version, + darkMode, session, }); @@ -152,77 +142,73 @@ export class WalletLinkRelay extends WalletSDKRelayAbstract { return { session, ui, connection }; } - private listener: WalletLinkConnectionUpdateListener = { - handleResponseMessage: this.handleWeb3ResponseMessage, + linkedUpdated = (linked: boolean) => { + this.isLinked = linked; + const cachedAddresses = this.storage.getItem(LOCAL_STORAGE_ADDRESSES_KEY); + + if (linked) { + // Only set linked session variable one way + this.session.linked = linked; + } - linkedUpdated: (linked: boolean) => { - this.isLinked = linked; - const cachedAddresses = this.storage.getItem(LOCAL_STORAGE_ADDRESSES_KEY); + this.isUnlinkedErrorState = false; - if (linked) { - // Only set linked session variable one way - this.session.linked = linked; + if (cachedAddresses) { + const addresses = cachedAddresses.split(' ') as AddressString[]; + const wasConnectedViaStandalone = this.storage.getItem('IsStandaloneSigning') === 'true'; + if (addresses[0] !== '' && !linked && this.session.linked && !wasConnectedViaStandalone) { + this.isUnlinkedErrorState = true; + const sessionIdHash = this.getSessionIdHash(); + this.diagnostic?.log(EVENTS.UNLINKED_ERROR_STATE, { + sessionIdHash, + }); } + } + }; - this.isUnlinkedErrorState = false; + metadataUpdated = (key: string, value: string) => { + this.storage.setItem(key, value); + }; - if (cachedAddresses) { - const addresses = cachedAddresses.split(' ') as AddressString[]; - const wasConnectedViaStandalone = this.storage.getItem('IsStandaloneSigning') === 'true'; - if (addresses[0] !== '' && !linked && this.session.linked && !wasConnectedViaStandalone) { - this.isUnlinkedErrorState = true; - const sessionIdHash = this.getSessionIdHash(); - this.diagnostic?.log(EVENTS.UNLINKED_ERROR_STATE, { - sessionIdHash, - }); - } - } - }, - metadataUpdated: (key: string, value: string) => { - this.storage.setItem(key, value); - }, - chainUpdated: (chainId: string, jsonRpcUrl: string) => { - if ( - this.chainCallbackParams.chainId === chainId && - this.chainCallbackParams.jsonRpcUrl === jsonRpcUrl - ) { - return; - } - this.chainCallbackParams = { - chainId, - jsonRpcUrl, - }; + chainUpdated = (chainId: string, jsonRpcUrl: string) => { + if ( + this.chainCallbackParams.chainId === chainId && + this.chainCallbackParams.jsonRpcUrl === jsonRpcUrl + ) { + return; + } + this.chainCallbackParams = { + chainId, + jsonRpcUrl, + }; - if (this.chainCallback) { - this.chainCallback(chainId, jsonRpcUrl); - } - }, - accountUpdated: (selectedAddress: string) => { - if (this.accountsCallback) { - this.accountsCallback([selectedAddress]); - } + if (this.chainCallback) { + this.chainCallback(chainId, jsonRpcUrl); + } + }; - if (WalletLinkRelay.accountRequestCallbackIds.size > 0) { - // We get the ethereum address from the metadata. If for whatever - // reason we don't get a response via an explicit web3 message - // we can still fulfill the eip1102 request. - Array.from(WalletLinkRelay.accountRequestCallbackIds.values()).forEach((id) => { - const message = Web3ResponseMessage({ - id, - response: RequestEthereumAccountsResponse([selectedAddress as AddressString]), - }); - this.invokeCallback({ ...message, id }); + accountUpdated = (selectedAddress: string) => { + if (this.accountsCallback) { + this.accountsCallback([selectedAddress]); + } + + if (WalletLinkRelay.accountRequestCallbackIds.size > 0) { + // We get the ethereum address from the metadata. If for whatever + // reason we don't get a response via an explicit web3 message + // we can still fulfill the eip1102 request. + Array.from(WalletLinkRelay.accountRequestCallbackIds.values()).forEach((id) => { + const message = Web3ResponseMessage({ + id, + response: RequestEthereumAccountsResponse([selectedAddress as AddressString]), }); - WalletLinkRelay.accountRequestCallbackIds.clear(); - } - }, - connectedUpdated: (connected: boolean) => { - const { ui } = this; - if (ui instanceof WalletLinkRelayUI) { - ui.setConnected(connected); - } - }, - resetAndReload: this.resetAndReload, + this.invokeCallback({ ...message, id }); + }); + WalletLinkRelay.accountRequestCallbackIds.clear(); + } + }; + + connectedUpdated = (connected: boolean) => { + this.ui.setConnected(connected); }; public attachUI() { @@ -539,7 +525,7 @@ export class WalletLinkRelay extends WalletSDKRelayAbstract { return this.connection.publishEvent(event, message, callWebhook); } - protected handleWeb3ResponseMessage(message: Web3ResponseMessage) { + handleWeb3ResponseMessage(message: Web3ResponseMessage) { const { response } = message; this.diagnostic?.log(EVENTS.WEB3_RESPONSE, { eventId: message.id,