diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1f968e0cf..aa9daa9ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,10 +18,10 @@ jobs: key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - - name: Node 16 + - name: Node 18 uses: actions/setup-node@v1 with: - node-version: 16.x + node-version: 18.x - name: Prepare run: npm ci diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index bd6421dff..50f71733c 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -17,10 +17,10 @@ jobs: key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - - name: Node 16 + - name: Node 18 uses: actions/setup-node@v1 with: - node-version: 16.x + node-version: 18.x - name: Prepare run: npm ci diff --git a/.github/workflows/dev-demo-deploy.yml b/.github/workflows/dev-demo-deploy.yml index b6eb8f293..7f036e991 100644 --- a/.github/workflows/dev-demo-deploy.yml +++ b/.github/workflows/dev-demo-deploy.yml @@ -17,10 +17,10 @@ jobs: key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - - name: Node 16 + - name: Node 18 uses: actions/setup-node@v1 with: - node-version: 16.x + node-version: 18.x - name: Prepare run: npm ci diff --git a/package-lock.json b/package-lock.json index eee6f2df5..7782f3832 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25134,8 +25134,7 @@ "@airgap/beacon-transport-matrix": "4.3.0", "@airgap/beacon-transport-postmessage": "4.3.0", "@airgap/beacon-transport-walletconnect": "4.3.0", - "@airgap/beacon-ui": "4.3.0", - "broadcast-channel": "^7.0.0" + "@airgap/beacon-ui": "4.3.0" } }, "packages/beacon-sdk": { diff --git a/packages/beacon-core/package.json b/packages/beacon-core/package.json index 83bf202b5..e355216e9 100644 --- a/packages/beacon-core/package.json +++ b/packages/beacon-core/package.json @@ -40,7 +40,7 @@ "@stablelib/nacl": "^1.0.4", "@stablelib/utf8": "^1.0.1", "@stablelib/x25519-session": "^1.0.4", - "bs58check": "2.1.2", - "broadcast-channel": "^7.0.0" + "broadcast-channel": "^7.0.0", + "bs58check": "2.1.2" } } diff --git a/packages/beacon-core/src/utils/multi-tab-channel.ts b/packages/beacon-core/src/utils/multi-tab-channel.ts index c52e37fa9..4da41ff39 100644 --- a/packages/beacon-core/src/utils/multi-tab-channel.ts +++ b/packages/beacon-core/src/utils/multi-tab-channel.ts @@ -1,15 +1,25 @@ import { Logger } from '@airgap/beacon-core' +import { BeaconMessageType } from '@airgap/beacon-types' +import { ExposedPromise } from '@airgap/beacon-utils' import { createLeaderElection, BroadcastChannel, LeaderElector } from 'broadcast-channel' -type Message = { - type: string - id: string - data: any +type BCMessageType = + | 'LEADER_DEAD' + | 'RESPONSE' + | 'DISCONNECT' + | BeaconMessageType + +type BCMessage = { + type: BCMessageType + sender: string + recipient?: string + data?: any } const logger = new Logger('MultiTabChannel') export class MultiTabChannel { + private id: string = String(Date.now()) private channel: BroadcastChannel private elector: LeaderElector private eventListeners = [ @@ -18,6 +28,7 @@ export class MultiTabChannel { ] private onBCMessageHandler: Function private onElectedLeaderHandler: Function + private _isLeader: ExposedPromise = new ExposedPromise() // Auxiliary variable needed for handling beforeUnload. // Closing a tab causes the elector to be killed immediately private wasLeader: boolean = false @@ -27,17 +38,29 @@ export class MultiTabChannel { this.onElectedLeaderHandler = onElectedLeaderHandler this.channel = new BroadcastChannel(name) this.elector = createLeaderElection(this.channel) - this.init().then(() => logger.debug('MultiTabChannel', 'constructor', 'init', 'done')) + this.init() + .then(() => logger.debug('MultiTabChannel', 'constructor', 'init', 'done')) + .catch((err) => logger.warn(err.message)) } private async init() { + const isMobile = typeof window !== 'undefined' ? /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( + navigator.userAgent + ) : false + + if (isMobile) { + throw new Error('BroadcastChannel is not fully supported on mobile.') + } + const hasLeader = await this.elector.hasLeader() if (!hasLeader) { await this.elector.awaitLeadership() - this.wasLeader = this.isLeader() + this.wasLeader = this.elector.isLeader + this.wasLeader && logger.log('The current tab is the leader.') } + this._isLeader.resolve(this.wasLeader) this.channel.onmessage = this.eventListeners[1] window?.addEventListener('beforeunload', this.eventListeners[0]) } @@ -52,14 +75,21 @@ export class MultiTabChannel { this.channel.removeEventListener('message', this.eventListeners[1]) } - private async onMessageHandler(message: Message) { + private async onMessageHandler(message: BCMessage) { + if (message.recipient && message.recipient !== this.id) { + return + } + if (message.type === 'LEADER_DEAD') { await this.elector.awaitLeadership() - this.wasLeader = this.isLeader() + this.wasLeader = this.elector.isLeader + this._isLeader = new ExposedPromise() + this._isLeader.resolve(this.wasLeader) - if (this.isLeader()) { + if (this.wasLeader) { this.onElectedLeaderHandler() + logger.log('The current tab is the leader.') } return } @@ -67,8 +97,8 @@ export class MultiTabChannel { this.onBCMessageHandler(message) } - isLeader(): boolean { - return this.elector.isLeader + isLeader(): Promise { + return this._isLeader.promise } async getLeadership() { @@ -79,7 +109,7 @@ export class MultiTabChannel { return this.elector.hasLeader() } - postMessage(message: any): void { - this.channel.postMessage(message) + postMessage(message: Omit): void { + this.channel.postMessage({ ...message, sender: this.id }) } } diff --git a/packages/beacon-dapp/package.json b/packages/beacon-dapp/package.json index ab85de39e..37efb5b02 100644 --- a/packages/beacon-dapp/package.json +++ b/packages/beacon-dapp/package.json @@ -39,7 +39,6 @@ "@airgap/beacon-transport-matrix": "4.3.0", "@airgap/beacon-transport-postmessage": "4.3.0", "@airgap/beacon-transport-walletconnect": "4.3.0", - "@airgap/beacon-ui": "4.3.0", - "broadcast-channel": "^7.0.0" + "@airgap/beacon-ui": "4.3.0" } } diff --git a/packages/beacon-dapp/src/dapp-client/DAppClient.ts b/packages/beacon-dapp/src/dapp-client/DAppClient.ts index 3aa3116c8..9071d4494 100644 --- a/packages/beacon-dapp/src/dapp-client/DAppClient.ts +++ b/packages/beacon-dapp/src/dapp-client/DAppClient.ts @@ -317,8 +317,7 @@ export class DAppClient extends Client { data: { message, connectionInfo - }, - id: message.id + } }) if (typedMessage.type !== BeaconMessageType.Acknowledge) { @@ -342,8 +341,8 @@ export class DAppClient extends Client { connectionInfo.origin === Origin.P2P ? this.p2pTransport : connectionInfo.origin === Origin.WALLETCONNECT - ? this.walletConnectTransport - : this.postMessageTransport ?? (await this.transport) + ? this.walletConnectTransport + : this.postMessageTransport ?? (await this.transport) if (relevantTransport) { const peers: ExtendedPeerInfo[] = await relevantTransport.getPeers() @@ -440,8 +439,8 @@ export class DAppClient extends Client { res.status === 426 ? console.error('Metrics are no longer supported for this version, please upgrade.') : console.warn( - 'Network error encountered. Metrics sharing have been automatically disabled.' - ) + 'Network error encountered. Metrics sharing have been automatically disabled.' + ) } this.enableMetrics = res.ok this.storage.set(StorageKey.ENABLE_METRICS, res.ok) @@ -455,18 +454,6 @@ export class DAppClient extends Client { this.initUserID().catch((err) => logger.error(err.message)) } - private async checkIfBCLeaderExists() { - const hasLeader = await this.multiTabChannel.hasLeader() - - if (hasLeader) { - return this.multiTabChannel.isLeader() - } - - await this.multiTabChannel.getLeadership() - - return this.multiTabChannel.isLeader() - } - private async onElectedLeaderhandler() { if (!this._transport.isResolved()) { return @@ -509,8 +496,8 @@ export class DAppClient extends Client { } } - private async prepareRequest(message: any, isV3 = false) { - if (!this.multiTabChannel.isLeader()) { + private async prepareRequest({ data }: any, isV3 = false) { + if (!(await this.multiTabChannel.isLeader())) { return } @@ -518,10 +505,10 @@ export class DAppClient extends Client { const transport = (await this._transport.promise) as DappWalletConnectTransport await transport.waitForResolution() - this.openRequestsOtherTabs.add(message.id) + this.openRequestsOtherTabs.add(data.id) isV3 - ? this.makeRequestV3(message.data, message.id) - : this.makeRequest(message.data, false, message.id) + ? this.makeRequestV3(data.request, data.id) + : this.makeRequest(data.request, false, data.id) } private async createStateSnapshot() { @@ -594,7 +581,7 @@ export class DAppClient extends Client { network: this.network.type, opts: wcOptions }, - this.checkIfBCLeaderExists.bind(this) + () => this.multiTabChannel.isLeader() ) this.initEvents() @@ -607,10 +594,9 @@ export class DAppClient extends Client { return } - this.walletConnectTransport.setEventHandler( - ClientEvents.CLOSE_ALERT, - this.hideUI.bind(this, ['alert', 'toast']) - ) + this.walletConnectTransport.setEventHandler(ClientEvents.CLOSE_ALERT, () => { + this.hideUI(['alert', 'toast']) + }) this.walletConnectTransport.setEventHandler( ClientEvents.RESET_STATE, this.channelClosedHandler.bind(this) @@ -808,7 +794,7 @@ export class DAppClient extends Client { this.postMessageTransport = this.walletConnectTransport = this.p2pTransport = - undefined + undefined this._activeAccount.isResolved() && this.clearActiveAccount() this._initPromise = undefined }, @@ -889,7 +875,7 @@ export class DAppClient extends Client { this.debounceSetActiveAccount = true this._initPromise = undefined this.postMessageTransport = this.p2pTransport = this.walletConnectTransport = undefined - if (this.multiTabChannel.isLeader()) { + if (await this.multiTabChannel.isLeader()) { await transport.disconnect() this.openRequestsOtherTabs.clear() } else { @@ -944,7 +930,7 @@ export class DAppClient extends Client { if (this._transport.isResolved()) { const transport = await this.transport - if (transport.connectionStatus === TransportStatus.NOT_CONNECTED) { + if (transport.connectionStatus !== TransportStatus.CONNECTED) { await transport.connect() } } @@ -1078,9 +1064,9 @@ export class DAppClient extends Client { private async checkMakeRequest() { const isResolved = this._transport.isResolved() const isWCInstance = isResolved && (await this.transport) instanceof WalletConnectTransport - const isLeader = this.multiTabChannel.isLeader() + const isLeader = await this.multiTabChannel.isLeader() - return !isResolved || (isResolved && (!isWCInstance || (isWCInstance && isLeader))) + return !isResolved || !isWCInstance || isLeader || isMobileOS(window) } /** @@ -1373,9 +1359,9 @@ export class DAppClient extends Client { logger.time(true, logId) const res = (await this.checkMakeRequest()) ? this.makeRequestV3< - BlockchainRequestV3, - BeaconMessageWrapper> - >(request) + BlockchainRequestV3, + BeaconMessageWrapper> + >(request) : this.makeRequestBC(request) res.catch(async (requestError: ErrorResponse) => { @@ -1587,13 +1573,13 @@ export class DAppClient extends Client { const res = (await this.checkMakeRequest()) ? this.makeRequest< - SimulatedProofOfEventChallengeRequest, - SimulatedProofOfEventChallengeResponse - >(request) + SimulatedProofOfEventChallengeRequest, + SimulatedProofOfEventChallengeResponse + >(request) : this.makeRequestBC< - SimulatedProofOfEventChallengeRequest, - SimulatedProofOfEventChallengeResponse - >(request) + SimulatedProofOfEventChallengeRequest, + SimulatedProofOfEventChallengeResponse + >(request) res.catch(async (requestError: ErrorResponse) => { requestError.errorType === BeaconErrorType.ABORTED_ERROR @@ -2057,50 +2043,50 @@ export class DAppClient extends Client { request: BeaconRequestInputMessage, response: | { - account: AccountInfo - output: PermissionResponseOutput - blockExplorer: BlockExplorer - connectionContext: ConnectionContext - walletInfo: WalletInfo - } + account: AccountInfo + output: PermissionResponseOutput + blockExplorer: BlockExplorer + connectionContext: ConnectionContext + walletInfo: WalletInfo + } | { - account: AccountInfo - output: ProofOfEventChallengeResponse - blockExplorer: BlockExplorer - connectionContext: ConnectionContext - walletInfo: WalletInfo - } + account: AccountInfo + output: ProofOfEventChallengeResponse + blockExplorer: BlockExplorer + connectionContext: ConnectionContext + walletInfo: WalletInfo + } | { - account: AccountInfo - output: SimulatedProofOfEventChallengeResponse - blockExplorer: BlockExplorer - connectionContext: ConnectionContext - walletInfo: WalletInfo - } + account: AccountInfo + output: SimulatedProofOfEventChallengeResponse + blockExplorer: BlockExplorer + connectionContext: ConnectionContext + walletInfo: WalletInfo + } | { - account: AccountInfo - output: OperationResponseOutput - blockExplorer: BlockExplorer - connectionContext: ConnectionContext - walletInfo: WalletInfo - } + account: AccountInfo + output: OperationResponseOutput + blockExplorer: BlockExplorer + connectionContext: ConnectionContext + walletInfo: WalletInfo + } | { - output: SignPayloadResponseOutput - connectionContext: ConnectionContext - walletInfo: WalletInfo - } + output: SignPayloadResponseOutput + connectionContext: ConnectionContext + walletInfo: WalletInfo + } // | { // output: EncryptPayloadResponseOutput // connectionContext: ConnectionContext // walletInfo: WalletInfo // } | { - network: Network - output: BroadcastResponseOutput - blockExplorer: BlockExplorer - connectionContext: ConnectionContext - walletInfo: WalletInfo - } + network: Network + output: BroadcastResponseOutput + blockExplorer: BlockExplorer + connectionContext: ConnectionContext + walletInfo: WalletInfo + } ): Promise { this.events .emit(messageEvents[request.type].success, response) @@ -2340,8 +2326,9 @@ export class DAppClient extends Client { const walletInfo = await this.getWalletInfo(peer, account) logger.log('makeRequest', 'sending message', request) + try { - ;(await this.transport).send(payload, peer) + ; (await this.transport).send(payload, peer) if ( request.type !== BeaconMessageType.PermissionRequest || (this._activeAccount.isResolved() && (await this._activeAccount.promise)) @@ -2369,7 +2356,8 @@ export class DAppClient extends Client { .emit(messageEvents[requestInput.type].sent, { walletInfo: { ...walletInfo, - name: walletInfo.name ?? 'Wallet' + name: walletInfo.name ?? 'Wallet', + transport: this._transport.isResolved() ? (await this.transport).type : undefined }, extraInfo: { resetCallback: async () => { @@ -2457,7 +2445,7 @@ export class DAppClient extends Client { logger.log('makeRequest', 'sending message', request) try { - ;(await this.transport).send(payload, peer) + ; (await this.transport).send(payload, peer) if ( request.message.type !== BeaconMessageType.PermissionRequest || (this._activeAccount.isResolved() && (await this._activeAccount.promise)) @@ -2504,9 +2492,9 @@ export class DAppClient extends Client { request: Optional ): Promise< | { - message: U - connectionInfo: ConnectionContext - } + message: U + connectionInfo: ConnectionContext + } | undefined > { if (!this._transport.isResolved()) { @@ -2531,8 +2519,7 @@ export class DAppClient extends Client { this.multiTabChannel.postMessage({ type: request.type, - data: request, - id + data: { request, id } }) if ( diff --git a/packages/beacon-dapp/src/events.ts b/packages/beacon-dapp/src/events.ts index 295ced685..606545b6d 100644 --- a/packages/beacon-dapp/src/events.ts +++ b/packages/beacon-dapp/src/events.ts @@ -29,7 +29,8 @@ import { WalletConnectPairingRequest, AnalyticsInterface, ProofOfEventChallengeResponseOutput, - SimulatedProofOfEventChallengeResponseOutput + SimulatedProofOfEventChallengeResponseOutput, + TransportType } from '@airgap/beacon-types' import { UnknownBeaconError, @@ -262,6 +263,10 @@ const showSentToast = async (data: RequestSentInfo): Promise => { walletInfo: data.walletInfo, state: 'loading', actions, + timer: + isMobile(window) && data.walletInfo.transport === TransportType.WALLETCONNECT + ? SUCCESS_TIMER + : undefined, openWalletAction }).catch((toastError) => console.error(toastError)) } diff --git a/packages/beacon-dapp/src/transports/DappWalletConnectTransport.ts b/packages/beacon-dapp/src/transports/DappWalletConnectTransport.ts index 53007f990..f1a3f2e6e 100644 --- a/packages/beacon-dapp/src/transports/DappWalletConnectTransport.ts +++ b/packages/beacon-dapp/src/transports/DappWalletConnectTransport.ts @@ -2,7 +2,6 @@ import { StorageKey, Storage, ExtendedWalletConnectPairingResponse, - TransportStatus, NetworkType } from '@airgap/beacon-types' import { Logger } from '@airgap/beacon-core' @@ -38,13 +37,7 @@ export class DappWalletConnectTransport extends WalletConnectTransport< ) this.client.listenForChannelOpening(async (peer: ExtendedWalletConnectPairingResponse) => { await this.addPeer(peer) - this._isConnected = (await isLeader()) - ? TransportStatus.CONNECTED - : TransportStatus.SECONDARY_TAB_CONNECTED - if (this.newPeerListener) { - this.newPeerListener(peer) - this.newPeerListener = undefined // TODO: Remove this once we use the id - } + this.updatePeerListener(peer) }) } @@ -59,4 +52,11 @@ export class DappWalletConnectTransport extends WalletConnectTransport< logger.log('stopListeningForNewPeers') this.newPeerListener = undefined } + + updatePeerListener(peer: ExtendedWalletConnectPairingResponse) { + if (this.newPeerListener) { + this.newPeerListener(peer) + this.newPeerListener = undefined // TODO: Remove this once we use the id + } + } } diff --git a/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts b/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts index 52613c3fe..54f3964cd 100644 --- a/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts +++ b/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts @@ -35,7 +35,7 @@ export class WalletConnectTransport< storage: Storage, storageKey: K, private wcOptions: { network: NetworkType; opts: SignClientTypes.Options }, - private isLeader: Function + private isLeader: () => Promise ) { super( name, @@ -88,22 +88,6 @@ export class WalletConnectTransport< return !!this.client.disconnectionEvents.size } - closeClient() { - this.client.closeSignClient() - } - - public async hasPairings() { - return (await this.client.storage.hasPairings()) - ? true - : !!this.client.signClient?.pairing.getAll()?.length - } - - public async hasSessions() { - return (await this.client.storage.hasSessions()) - ? true - : !!this.client.signClient?.session.getAll()?.length - } - public async getPeers(): Promise { const client = WalletConnectCommunicationClient.getInstance(this.wcOptions, this.isLeader) const session = client.currentSession() diff --git a/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts b/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts index 08baf0408..56b53e0d6 100644 --- a/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts +++ b/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts @@ -4,7 +4,6 @@ import { Serializer, ClientEvents, Logger, - WCStorage, SDK_VERSION } from '@airgap/beacon-core' import Client from '@walletconnect/sign-client' @@ -99,7 +98,6 @@ export class WalletConnectCommunicationClient extends CommunicationClient { private static instance: WalletConnectCommunicationClient public signClient: Client | undefined - public storage: WCStorage = new WCStorage() private session: SessionTypes.Struct | undefined private activeAccountOrPbk: string | undefined private activeNetwork: string | undefined @@ -115,7 +113,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { constructor( private wcOptions: { network: NetworkType; opts: SignClientTypes.Options }, - private isLeader: Function + private isLeader: () => Promise ) { super() } @@ -125,7 +123,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { network: NetworkType opts: SignClientTypes.Options }, - isLeader: Function + isLeader: () => Promise ): WalletConnectCommunicationClient { if (!this.instance) { this.instance = new WalletConnectCommunicationClient(wcOptions, isLeader) @@ -163,15 +161,6 @@ export class WalletConnectCommunicationClient extends CommunicationClient { this.channelOpeningListeners.set('channelOpening', callbackFunction) } - private clearEvents() { - this.signClient?.removeAllListeners('session_event') - this.signClient?.removeAllListeners('session_update') - this.signClient?.removeAllListeners('session_delete') - this.signClient?.removeAllListeners('session_expire') - this.signClient?.core.pairing.events.removeAllListeners('pairing_delete') - this.signClient?.core.pairing.events.removeAllListeners('pairing_expire') - } - async unsubscribeFromEncryptedMessages(): Promise { this.activeListeners.clear() this.channelOpeningListeners.clear() @@ -181,33 +170,13 @@ export class WalletConnectCommunicationClient extends CommunicationClient { // implementation } - async closeSignClient() { - if (!this.signClient) { - logger.error('No client active') - return - } - - await this.signClient.core.relayer.transportClose() - this.signClient.core.events.removeAllListeners() - this.signClient.core.relayer.events.removeAllListeners() - this.signClient.core.heartbeat.stop() - this.signClient.core.relayer.provider.events.removeAllListeners() - this.signClient.core.relayer.subscriber.events.removeAllListeners() - this.signClient.core.relayer.provider.connection.events.removeAllListeners() - this.clearEvents() - - this.signClient = undefined - } - private async ping() { - const client = await this.getSignClient() - - if (!client || !this.session) { + if (!this.signClient || !this.session) { logger.error('No session available.') return } - client + this.signClient .ping({ topic: this.session.topic }) .then(() => { if (this.messageIds.length) { @@ -224,7 +193,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { } private async checkWalletReadiness(_topic: string) { - if (this.pingInterval) { + if (this.pingInterval || this.isMobileOS()) { return } @@ -260,11 +229,10 @@ export class WalletConnectCommunicationClient extends CommunicationClient { } private async fetchAccounts(topic: string, chainId: string) { - const signClient = await this.getSignClient() - if (!signClient) { + if (!this.signClient) { return } - return signClient.request< + return this.signClient.request< [ { algo: 'ed25519' @@ -326,11 +294,6 @@ export class WalletConnectCommunicationClient extends CommunicationClient { } } - if (this.signClient && !this.isLeader() && this.isMobileOS()) { - await this.closeSignClient() - this.clearState() - } - if (!publicKey) { throw new Error('Public Key in `tezos_getAccounts` is empty!') } @@ -377,9 +340,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { * @error MissingRequiredScope is thrown if permission to sign payload was not granted */ async signPayload(signPayloadRequest: SignPayloadRequest) { - const signClient = await this.getSignClient() - - if (!signClient) { + if (!this.signClient) { return } @@ -393,8 +354,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { this.checkWalletReadiness(this.getTopicFromSession(session)) - // TODO: Type - signClient + this.signClient .request<{ signature: string }>({ topic: session.topic, chainId: `${TEZOS_PLACEHOLDER}:${network}`, @@ -438,9 +398,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { * @error MissingRequiredScope is thrown if permission to send operation was not granted */ async sendOperations(operationRequest: OperationRequest) { - const signClient = await this.getSignClient() - - if (!signClient) { + if (!this.signClient) { return } @@ -454,7 +412,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { this.validateNetworkAndAccount(network, account) this.checkWalletReadiness(this.getTopicFromSession(session)) - signClient + this.signClient .request<{ // The `operationHash` field should be provided to specify the operation hash, // while the `transactionHash` and `hash` fields are supported for backwards compatibility. @@ -536,20 +494,20 @@ export class WalletConnectCommunicationClient extends CommunicationClient { logger.warn('init') this.disconnectionEvents.size && this.disconnectionEvents.clear() + await this.getSignClient() + if (forceNewConnection) { await this.closePairings() } - const signClient = await this.getSignClient() - - if (!signClient) { + if (!this.signClient) { throw new Error('Failed to connect to the relayer.') } - const lastIndex = signClient.session.keys.length - 1 + const lastIndex = this.signClient.session.keys.length - 1 if (lastIndex > -1) { - this.session = signClient.session.get(signClient.session.keys[lastIndex]) + this.session = this.signClient.session.get(this.signClient.session.keys[lastIndex]) this.updateStorageWallet(this.session) this.setDefaultAccountAndNetwork() @@ -585,7 +543,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { } } - const { uri, approval } = await signClient.connect(connectParams).catch((error) => { + const { uri, approval } = await this.signClient.connect(connectParams).catch((error) => { logger.error(`Init error: ${error.message}`) localStorage && localStorage.setItem(StorageKey.WC_INIT_ERROR, error.message) throw new Error(error.message) @@ -600,7 +558,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { let hasResponse = false - signClient.core.pairing + this.signClient.core.pairing .ping({ topic }) .then(async () => { if (!hasResponse) { @@ -667,8 +625,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { const fun = this.eventHandlers.get(ClientEvents.WC_ACK_NOTIFICATION) fun && fun('error') } else { - const _pairingTopic = topic ?? signClient.core.pairing.getPairings()[0]?.topic - logger.debug('New pairing topic?', []) + const _pairingTopic = topic ?? this.signClient?.core.pairing.getPairings()[0]?.topic const errorResponse: ErrorResponseInput = { type: BeaconMessageType.Error, @@ -680,10 +637,8 @@ export class WalletConnectCommunicationClient extends CommunicationClient { } }) .then(async () => { - const isLeader = await this.isLeader() - if (!isLeader && !this.isMobileOS()) { - await this.closeSignClient() - this.clearState() + if (!this.isMobileOS() && !(await this.isLeader())) { + this.signClient = undefined } }) @@ -693,13 +648,16 @@ export class WalletConnectCommunicationClient extends CommunicationClient { } public async close() { - this.storage.backup() await this.closePairings() this.unsubscribeFromEncryptedMessages() this.messageIds = [] } private subscribeToSessionEvents(signClient: Client): void { + if (signClient.events.listenerCount('session_event') > 0) { + return + } + signClient.on('session_event', (event) => { if ( event.params.event.name === PermissionScopeEvents.REQUEST_ACKNOWLEDGED && @@ -798,7 +756,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { 'session_update' ) } - } catch {} + } catch { } } private async disconnect( @@ -838,8 +796,8 @@ export class WalletConnectCommunicationClient extends CommunicationClient { this.session?.pairingTopic === topic ? this.session : signClient.session - .getAll() - .find((session: SessionTypes.Struct) => session.pairingTopic === topic) + .getAll() + .find((session: SessionTypes.Struct) => session.pairingTopic === topic) if (!session) { return undefined @@ -914,23 +872,21 @@ export class WalletConnectCommunicationClient extends CommunicationClient { } await this.closeSessions() - const signClient = (await this.getSignClient())! - const pairings = signClient.pairing.getAll() ?? [] + const pairings = this.signClient.pairing.getAll() ?? [] pairings.length && (await Promise.allSettled( - pairings.map((pairing) => - signClient.disconnect({ - topic: pairing.topic, - reason: { - code: 0, // TODO: Use constants - message: 'Force new connection' - } - }) + pairings.map( + (pairing) => + this.signClient?.disconnect({ + topic: pairing.topic, + reason: { + code: 0, // TODO: Use constants + message: 'Force new connection' + } + }) ) )) - await this.closeSignClient() - await this.storage.resetState() } private async closeSessions() { @@ -938,19 +894,18 @@ export class WalletConnectCommunicationClient extends CommunicationClient { return } - const signClient = (await this.getSignClient())! - - const sessions = signClient.session.getAll() ?? [] + const sessions = this.signClient.session.getAll() ?? [] sessions.length && (await Promise.allSettled( - sessions.map((session) => - signClient.disconnect({ - topic: (session as any).topic, - reason: { - code: 0, // TODO: Use constants - message: 'Force new connection' - } - }) + sessions.map( + (session) => + this.signClient?.disconnect({ + topic: (session as any).topic, + reason: { + code: 0, // TODO: Use constants + message: 'Force new connection' + } + }) ) )) @@ -958,15 +913,14 @@ export class WalletConnectCommunicationClient extends CommunicationClient { } private async openSession(): Promise { - const signClient = (await this.getSignClient())! - const pairingTopic = signClient.core.pairing.getPairings()[0]?.topic - - logger.debug('Starting open session with', [pairingTopic]) - - if (!signClient) { + if (!this.signClient) { throw new Error('Transport error.') } + const pairingTopic = this.signClient.core.pairing.getPairings()[0]?.topic + + logger.debug('Starting open session with', [pairingTopic]) + const permissionScopeParams: PermissionScopeParam = { networks: [this.wcOptions.network], events: [], @@ -1001,7 +955,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { try { logger.debug('connect', [pairingTopic]) - const { approval } = await signClient.connect(connectParams) + const { approval } = await this.signClient.connect(connectParams) logger.debug('before await approal', [pairingTopic]) const session = await approval() logger.debug('after await approal, have session', [pairingTopic]) @@ -1339,7 +1293,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { throw new Error(`Failed to connect to relayer: ${Array.from(errMessages).join(',')}`) } - private async getSignClient(): Promise { + private async getSignClient(): Promise { if (this.signClient === undefined) { try { this.signClient = await this.tryConnectToRelayer() @@ -1347,11 +1301,8 @@ export class WalletConnectCommunicationClient extends CommunicationClient { } catch (error: any) { logger.error(error.message) localStorage && localStorage.setItem(StorageKey.WC_INIT_ERROR, error.message) - return undefined } } - - return this.signClient } private getSession() { diff --git a/packages/beacon-types/src/types/WalletInfo.ts b/packages/beacon-types/src/types/WalletInfo.ts index 5e1c8f65f..cff1836ad 100644 --- a/packages/beacon-types/src/types/WalletInfo.ts +++ b/packages/beacon-types/src/types/WalletInfo.ts @@ -1,6 +1,9 @@ +import { TransportType } from './transport/TransportType' + export interface WalletInfo { name: string type?: 'extension' | 'mobile' | 'web' | 'desktop' icon?: string deeplink?: string + transport?: TransportType }