From 101e6a232a1ce73a291dd4092f1ac0161f91f6e5 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Fri, 20 Sep 2024 15:25:43 +0200 Subject: [PATCH 01/24] fix: removed closeSignClient --- .../WalletConnectCommunicationClient.ts | 46 ++----------------- 1 file changed, 5 insertions(+), 41 deletions(-) diff --git a/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts b/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts index 5f7424a7a..110fa5b0c 100644 --- a/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts +++ b/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts @@ -105,6 +105,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { private activeNetwork: string | undefined readonly disconnectionEvents: Set = new Set() private pingInterval: NodeJS.Timeout | undefined + private leaderPairingCode: string | undefined /** * this queue stores each active message id @@ -163,15 +164,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,24 +173,6 @@ 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() @@ -326,11 +300,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!') } @@ -679,13 +648,6 @@ export class WalletConnectCommunicationClient extends CommunicationClient { this.notifyListeners(_pairingTopic, errorResponse) } }) - .then(async () => { - const isLeader = await this.isLeader() - if (!isLeader && !this.isMobileOS()) { - await this.closeSignClient() - this.clearState() - } - }) logger.warn('return uri and topic') @@ -700,6 +662,10 @@ export class WalletConnectCommunicationClient extends CommunicationClient { } 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 && @@ -929,8 +895,6 @@ export class WalletConnectCommunicationClient extends CommunicationClient { }) ) )) - await this.closeSignClient() - // await this.storage.resetState() } private async closeSessions() { From c53a0435694fddd3338a4658b30b3936784f3856 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Fri, 20 Sep 2024 17:46:18 +0200 Subject: [PATCH 02/24] fix: init pairing witouth sign client --- .../src/utils/multi-tab-channel.ts | 4 +- .../beacon-dapp/src/dapp-client/DAppClient.ts | 73 ++++++++++++++++++- .../src/WalletConnectTransport.ts | 8 +- .../WalletConnectCommunicationClient.ts | 7 +- 4 files changed, 79 insertions(+), 13 deletions(-) diff --git a/packages/beacon-core/src/utils/multi-tab-channel.ts b/packages/beacon-core/src/utils/multi-tab-channel.ts index e8b81ea7c..ce5b0bfe0 100644 --- a/packages/beacon-core/src/utils/multi-tab-channel.ts +++ b/packages/beacon-core/src/utils/multi-tab-channel.ts @@ -12,6 +12,8 @@ type BCMessageType = | 'IS_CHILD_ALIVE' | 'RESPONSE' | 'DISCONNECT' + | 'REQUEST_PAIRING' + | 'RESPONSE_PAIRING' | BeaconMessageType type BCMessage = { @@ -65,7 +67,7 @@ export class MultiTabChannel { this.isLeader = true logger.log('The current tab is the leader.') }, timeout) - + window?.addEventListener('beforeunload', this.eventListeners[0]) this.channel.onmessage = this.eventListeners[1] } diff --git a/packages/beacon-dapp/src/dapp-client/DAppClient.ts b/packages/beacon-dapp/src/dapp-client/DAppClient.ts index 67bd1d6cc..5bd63dd2c 100644 --- a/packages/beacon-dapp/src/dapp-client/DAppClient.ts +++ b/packages/beacon-dapp/src/dapp-client/DAppClient.ts @@ -232,6 +232,8 @@ export class DAppClient extends Client { this.onElectedLeaderhandler.bind(this) ) + private pairingRequest: ExposedPromise | undefined + constructor(config: DAppClientOptions) { super({ storage: config && config.storage ? config.storage : new LocalStorage(), @@ -492,11 +494,70 @@ export class DAppClient extends Client { case 'DISCONNECT': this._transport.isResolved() && this.disconnect() break + case 'REQUEST_PAIRING': + this.handlePairingRequest(message.sender) + break + case 'RESPONSE_PAIRING': + this.handlePairingResponse(message.data) + break default: logger.error('onBCMessageHandler', 'message type not recognized', message) } } + private async handlePairingRequest(recipient: string) { + const serializer = new Serializer() + + await this.initInternalTransports() + + this.walletConnectTransport + ?.listenForNewPeer(async (peer) => { + logger.log('init', 'walletconnect transport peer connected', peer) + this.analytics.track('event', 'DAppClient', 'WalletConnect Wallet connected', { + peerName: peer.name + }) + this.events + .emit(BeaconEvent.PAIR_SUCCESS, peer) + .catch((emitError) => console.warn(emitError)) + + this.setActivePeer(peer).catch(console.error) + this.setTransport(this.walletConnectTransport).catch(console.error) + + await this.init(this.walletConnectTransport) + + const request: PermissionRequestInput = { + appMetadata: await this.getOwnAppMetadata(), + type: BeaconMessageType.PermissionRequest, + network: this.network, + scopes: [PermissionScope.OPERATION_REQUEST, PermissionScope.SIGN] // todo + } + + const { message, connectionInfo } = await this.makeRequest< + PermissionRequest, + PermissionResponse + >(request).catch((err) => { + throw new Error(err.message) + }) + + await this.hideUI(['toast']) + + const accountInfo = await this.onNewAccount(message, connectionInfo) + await this.accountManager.addAccount(accountInfo) + // todo output + }) + .catch(console.error) + + this.multiTabChannel.postMessage({ + type: 'RESPONSE_PAIRING', + recipient, + data: await serializer.serialize(await this.walletConnectTransport?.getPairingRequestInfo()) + }) + } + + private async handlePairingResponse(data: string) { + this.pairingRequest?.resolve(data) + } + private async prepareRequest({ data }: any, isV3 = false) { if (!this.multiTabChannel.isLeader) { return @@ -656,6 +717,16 @@ export class DAppClient extends Client { await super.destroy() } + private async getPairingRequestInfo(transport: DappWalletConnectTransport) { + if (this.multiTabChannel.isLeader) { + return transport.getPairingRequestInfo() + } + + this.pairingRequest = new ExposedPromise() + this.multiTabChannel.postMessage({ type: 'REQUEST_PAIRING' }) + return await new Serializer().deserialize(await this.pairingRequest.promise) + } + public async init(transport?: Transport): Promise { if (this._initPromise) { return this._initPromise @@ -780,7 +851,7 @@ export class DAppClient extends Client { return p2pTransport.getPairingRequestInfo() }, postmessagePeerInfo: () => postMessageTransport.getPairingRequestInfo(), - walletConnectPeerInfo: () => walletConnectTransport.getPairingRequestInfo(), + walletConnectPeerInfo: () => this.getPairingRequestInfo(walletConnectTransport), networkType: this.network.type, abortedHandler: async () => { logger.log('init', 'ABORTED') diff --git a/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts b/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts index a84c48908..ba11c3d19 100644 --- a/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts +++ b/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts @@ -39,7 +39,7 @@ export class WalletConnectTransport< ) { super( name, - WalletConnectCommunicationClient.getInstance(wcOptions, isLeader), + WalletConnectCommunicationClient.getInstance(wcOptions), new PeerManager(storage, storageKey) ) } @@ -88,10 +88,6 @@ export class WalletConnectTransport< return !!this.client.disconnectionEvents.size } - closeClient() { - this.client.closeSignClient() - } - public async hasPairings() { return (await this.client.storage.hasPairings()) ? true @@ -105,7 +101,7 @@ export class WalletConnectTransport< } public async getPeers(): Promise { - const client = WalletConnectCommunicationClient.getInstance(this.wcOptions, this.isLeader) + const client = WalletConnectCommunicationClient.getInstance(this.wcOptions) const session = client.currentSession() if (!session) { return [] diff --git a/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts b/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts index 110fa5b0c..57e4843aa 100644 --- a/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts +++ b/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts @@ -105,7 +105,6 @@ export class WalletConnectCommunicationClient extends CommunicationClient { private activeNetwork: string | undefined readonly disconnectionEvents: Set = new Set() private pingInterval: NodeJS.Timeout | undefined - private leaderPairingCode: string | undefined /** * this queue stores each active message id @@ -116,7 +115,6 @@ export class WalletConnectCommunicationClient extends CommunicationClient { constructor( private wcOptions: { network: NetworkType; opts: SignClientTypes.Options }, - private isLeader: () => boolean ) { super() } @@ -125,11 +123,10 @@ export class WalletConnectCommunicationClient extends CommunicationClient { wcOptions: { network: NetworkType opts: SignClientTypes.Options - }, - isLeader: () => boolean + } ): WalletConnectCommunicationClient { if (!this.instance) { - this.instance = new WalletConnectCommunicationClient(wcOptions, isLeader) + this.instance = new WalletConnectCommunicationClient(wcOptions) } return WalletConnectCommunicationClient.instance } From c522979abcbe38828b6c6088c8667ea34980057a Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Fri, 20 Sep 2024 19:27:14 +0200 Subject: [PATCH 03/24] fix: hide ui --- .../beacon-core/src/utils/multi-tab-channel.ts | 1 + .../beacon-dapp/src/dapp-client/DAppClient.ts | 16 ++++++++++++---- .../src/WalletConnectTransport.ts | 1 + 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/beacon-core/src/utils/multi-tab-channel.ts b/packages/beacon-core/src/utils/multi-tab-channel.ts index ce5b0bfe0..c77c0b9fc 100644 --- a/packages/beacon-core/src/utils/multi-tab-channel.ts +++ b/packages/beacon-core/src/utils/multi-tab-channel.ts @@ -14,6 +14,7 @@ type BCMessageType = | 'DISCONNECT' | 'REQUEST_PAIRING' | 'RESPONSE_PAIRING' + | 'HIDE_UI' | BeaconMessageType type BCMessage = { diff --git a/packages/beacon-dapp/src/dapp-client/DAppClient.ts b/packages/beacon-dapp/src/dapp-client/DAppClient.ts index 5bd63dd2c..5ef5fdce9 100644 --- a/packages/beacon-dapp/src/dapp-client/DAppClient.ts +++ b/packages/beacon-dapp/src/dapp-client/DAppClient.ts @@ -500,6 +500,9 @@ export class DAppClient extends Client { case 'RESPONSE_PAIRING': this.handlePairingResponse(message.data) break + case 'HIDE_UI': + this.hideUI(message.data) + break default: logger.error('onBCMessageHandler', 'message type not recognized', message) } @@ -535,15 +538,20 @@ export class DAppClient extends Client { const { message, connectionInfo } = await this.makeRequest< PermissionRequest, PermissionResponse - >(request).catch((err) => { - throw new Error(err.message) - }) + >(request) await this.hideUI(['toast']) const accountInfo = await this.onNewAccount(message, connectionInfo) await this.accountManager.addAccount(accountInfo) - // todo output + + this.walletConnectTransport?.connect() + + this.multiTabChannel.postMessage({ + type: 'HIDE_UI', + recipient, + data: ['alert', 'toast'] + }) }) .catch(console.error) diff --git a/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts b/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts index ba11c3d19..20dcc8210 100644 --- a/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts +++ b/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts @@ -57,6 +57,7 @@ export class WalletConnectTransport< public async connect(): Promise { if ([TransportStatus.CONNECTED, TransportStatus.CONNECTING].includes(this._isConnected)) { + this.isReady.isPending() && this.isReady.resolve(true) return } From 578a602bb9a49f09f51f083b2c1d86efdad21366 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Fri, 20 Sep 2024 21:33:11 +0200 Subject: [PATCH 04/24] fix: signClient init --- .../src/WalletConnectTransport.ts | 12 -- .../WalletConnectCommunicationClient.ts | 117 +++++++----------- 2 files changed, 48 insertions(+), 81 deletions(-) diff --git a/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts b/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts index 20dcc8210..e77bca52e 100644 --- a/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts +++ b/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts @@ -89,18 +89,6 @@ export class WalletConnectTransport< return !!this.client.disconnectionEvents.size } - 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) 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 57e4843aa..402f37d5c 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 @@ -113,18 +111,14 @@ export class WalletConnectCommunicationClient extends CommunicationClient { */ private messageIds: string[] = [] - constructor( - private wcOptions: { network: NetworkType; opts: SignClientTypes.Options }, - ) { + constructor(private wcOptions: { network: NetworkType; opts: SignClientTypes.Options }) { super() } - static getInstance( - wcOptions: { - network: NetworkType - opts: SignClientTypes.Options - } - ): WalletConnectCommunicationClient { + static getInstance(wcOptions: { + network: NetworkType + opts: SignClientTypes.Options + }): WalletConnectCommunicationClient { if (!this.instance) { this.instance = new WalletConnectCommunicationClient(wcOptions) } @@ -171,14 +165,12 @@ export class WalletConnectCommunicationClient extends CommunicationClient { } 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) { @@ -231,11 +223,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' @@ -343,9 +334,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 } @@ -359,8 +348,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}`, @@ -404,9 +392,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 } @@ -420,7 +406,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. @@ -502,20 +488,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() @@ -551,7 +537,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) @@ -566,7 +552,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { let hasResponse = false - signClient.core.pairing + this.signClient.core.pairing .ping({ topic }) .then(async () => { if (!hasResponse) { @@ -633,8 +619,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, @@ -652,7 +637,6 @@ export class WalletConnectCommunicationClient extends CommunicationClient { } public async close() { - this.storage.backup() await this.closePairings() this.unsubscribeFromEncryptedMessages() this.messageIds = [] @@ -877,19 +861,19 @@ 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' + } + }) ) )) } @@ -899,19 +883,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' + } + }) ) )) @@ -919,15 +902,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: [], @@ -962,7 +944,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]) @@ -1300,7 +1282,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() @@ -1308,11 +1290,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() { From 08a4d32001c0c64502f2e54907b095d71eff3164 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Fri, 20 Sep 2024 22:49:31 +0200 Subject: [PATCH 05/24] fix: pairing --- .../src/utils/multi-tab-channel.ts | 2 + .../beacon-dapp/src/dapp-client/DAppClient.ts | 79 +++++++++++-------- .../transports/DappWalletConnectTransport.ts | 16 ++-- .../src/WalletConnectTransport.ts | 1 - 4 files changed, 55 insertions(+), 43 deletions(-) diff --git a/packages/beacon-core/src/utils/multi-tab-channel.ts b/packages/beacon-core/src/utils/multi-tab-channel.ts index c77c0b9fc..c46dd5e65 100644 --- a/packages/beacon-core/src/utils/multi-tab-channel.ts +++ b/packages/beacon-core/src/utils/multi-tab-channel.ts @@ -15,6 +15,8 @@ type BCMessageType = | 'REQUEST_PAIRING' | 'RESPONSE_PAIRING' | 'HIDE_UI' + | 'NEW_PEER' + | 'GET_PERMISSIONS' | BeaconMessageType type BCMessage = { diff --git a/packages/beacon-dapp/src/dapp-client/DAppClient.ts b/packages/beacon-dapp/src/dapp-client/DAppClient.ts index 5ef5fdce9..115b8ebce 100644 --- a/packages/beacon-dapp/src/dapp-client/DAppClient.ts +++ b/packages/beacon-dapp/src/dapp-client/DAppClient.ts @@ -233,6 +233,7 @@ export class DAppClient extends Client { ) private pairingRequest: ExposedPromise | undefined + private pairingResponse: { message: BeaconMessage; connectionInfo: ConnectionContext } | undefined constructor(config: DAppClientOptions) { super({ @@ -500,6 +501,9 @@ export class DAppClient extends Client { case 'RESPONSE_PAIRING': this.handlePairingResponse(message.data) break + case 'NEW_PEER': + this.handleNewPeerBC(message.data) + break case 'HIDE_UI': this.hideUI(message.data) break @@ -508,6 +512,11 @@ export class DAppClient extends Client { } } + private async handleNewPeerBC(data: any) { + this.pairingResponse = data.response + this.walletConnectTransport?.updatePeerListener(data.peer) + } + private async handlePairingRequest(recipient: string) { const serializer = new Serializer() @@ -519,9 +528,6 @@ export class DAppClient extends Client { this.analytics.track('event', 'DAppClient', 'WalletConnect Wallet connected', { peerName: peer.name }) - this.events - .emit(BeaconEvent.PAIR_SUCCESS, peer) - .catch((emitError) => console.warn(emitError)) this.setActivePeer(peer).catch(console.error) this.setTransport(this.walletConnectTransport).catch(console.error) @@ -535,22 +541,19 @@ export class DAppClient extends Client { scopes: [PermissionScope.OPERATION_REQUEST, PermissionScope.SIGN] // todo } - const { message, connectionInfo } = await this.makeRequest< - PermissionRequest, - PermissionResponse - >(request) + const response = await this.makeRequest(request) await this.hideUI(['toast']) - const accountInfo = await this.onNewAccount(message, connectionInfo) - await this.accountManager.addAccount(accountInfo) - this.walletConnectTransport?.connect() this.multiTabChannel.postMessage({ - type: 'HIDE_UI', + type: 'NEW_PEER', recipient, - data: ['alert', 'toast'] + data: { + peer, + response + } }) }) .catch(console.error) @@ -2395,7 +2398,11 @@ export class DAppClient extends Client { ErrorResponse >() - this.addOpenRequest(request.id, exposed) + if (this.pairingResponse) { + exposed.resolve(this.pairingResponse) + } else { + this.addOpenRequest(request.id, exposed) + } } const payload = await new Serializer().serialize(request) @@ -2407,28 +2414,32 @@ 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) - if ( - request.type !== BeaconMessageType.PermissionRequest || - (this._activeAccount.isResolved() && (await this._activeAccount.promise)) - ) { - this.tryToAppSwitch() - } - } catch (sendError) { - this.events.emit(BeaconEvent.INTERNAL_ERROR, { - text: 'Unable to send message. If this problem persists, please reset the connection and pair your wallet again.', - buttons: [ - { - text: 'Reset Connection', - actionCallback: async (): Promise => { - await closeToast() - this.disconnect() + if (!this.pairingResponse) { + try { + ;(await this.transport).send(payload, peer) + if ( + request.type !== BeaconMessageType.PermissionRequest || + (this._activeAccount.isResolved() && (await this._activeAccount.promise)) + ) { + this.tryToAppSwitch() + } + } catch (sendError) { + this.events.emit(BeaconEvent.INTERNAL_ERROR, { + text: 'Unable to send message. If this problem persists, please reset the connection and pair your wallet again.', + buttons: [ + { + text: 'Reset Connection', + actionCallback: async (): Promise => { + await closeToast() + this.disconnect() + } } - } - ] - }) - throw sendError + ] + }) + throw sendError + } + } else { + this.pairingResponse = undefined } if (!otherTabMessageId) { diff --git a/packages/beacon-dapp/src/transports/DappWalletConnectTransport.ts b/packages/beacon-dapp/src/transports/DappWalletConnectTransport.ts index 7e5167e25..5a794298d 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 e77bca52e..48061b621 100644 --- a/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts +++ b/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts @@ -57,7 +57,6 @@ export class WalletConnectTransport< public async connect(): Promise { if ([TransportStatus.CONNECTED, TransportStatus.CONNECTING].includes(this._isConnected)) { - this.isReady.isPending() && this.isReady.resolve(true) return } From 95bc756ac98dee3d46297e60858c7d9c6ee6e02e Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Fri, 20 Sep 2024 23:04:23 +0200 Subject: [PATCH 06/24] fix: removed serializer --- packages/beacon-dapp/src/dapp-client/DAppClient.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/beacon-dapp/src/dapp-client/DAppClient.ts b/packages/beacon-dapp/src/dapp-client/DAppClient.ts index 115b8ebce..9400cc666 100644 --- a/packages/beacon-dapp/src/dapp-client/DAppClient.ts +++ b/packages/beacon-dapp/src/dapp-client/DAppClient.ts @@ -518,8 +518,6 @@ export class DAppClient extends Client { } private async handlePairingRequest(recipient: string) { - const serializer = new Serializer() - await this.initInternalTransports() this.walletConnectTransport @@ -561,7 +559,7 @@ export class DAppClient extends Client { this.multiTabChannel.postMessage({ type: 'RESPONSE_PAIRING', recipient, - data: await serializer.serialize(await this.walletConnectTransport?.getPairingRequestInfo()) + data: await this.walletConnectTransport?.getPairingRequestInfo() }) } @@ -735,7 +733,7 @@ export class DAppClient extends Client { this.pairingRequest = new ExposedPromise() this.multiTabChannel.postMessage({ type: 'REQUEST_PAIRING' }) - return await new Serializer().deserialize(await this.pairingRequest.promise) + return await this.pairingRequest.promise } public async init(transport?: Transport): Promise { From 671da69b7eda6041f7a0324e81f08b2c38cf12a4 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Fri, 20 Sep 2024 23:08:15 +0200 Subject: [PATCH 07/24] fix: init on secondary --- packages/beacon-dapp/src/dapp-client/DAppClient.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/beacon-dapp/src/dapp-client/DAppClient.ts b/packages/beacon-dapp/src/dapp-client/DAppClient.ts index 9400cc666..784676d4b 100644 --- a/packages/beacon-dapp/src/dapp-client/DAppClient.ts +++ b/packages/beacon-dapp/src/dapp-client/DAppClient.ts @@ -518,6 +518,10 @@ export class DAppClient extends Client { } private async handlePairingRequest(recipient: string) { + if (!this.multiTabChannel.isLeader) { + return + } + await this.initInternalTransports() this.walletConnectTransport From 9d91c44b000bb56af99b44dc590fd59e523899f9 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Sat, 21 Sep 2024 14:34:33 +0200 Subject: [PATCH 08/24] fix: leader election mechanism --- .../src/utils/multi-tab-channel.ts | 173 ++++++------------ .../beacon-dapp/src/dapp-client/DAppClient.ts | 3 +- .../src/types/storage/StorageKey.ts | 1 + .../types/storage/StorageKeyReturnDefaults.ts | 1 + .../src/types/storage/StorageKeyReturnType.ts | 1 + 5 files changed, 62 insertions(+), 117 deletions(-) diff --git a/packages/beacon-core/src/utils/multi-tab-channel.ts b/packages/beacon-core/src/utils/multi-tab-channel.ts index c46dd5e65..f2542bfc1 100644 --- a/packages/beacon-core/src/utils/multi-tab-channel.ts +++ b/packages/beacon-core/src/utils/multi-tab-channel.ts @@ -1,15 +1,8 @@ -import { BeaconMessageType } from '@airgap/beacon-types' +import { BeaconMessageType, StorageKey } from '@airgap/beacon-types' import { Logger } from './Logger' +import { LocalStorage } from '../storage/LocalStorage' type BCMessageType = - | 'REQUEST_LEADERSHIP' - | 'LEADER_EXISTS' - | 'LEADER_UNLOAD' - | 'LEADER_STILL_ALIVE' - | 'IS_LEADER_ALIVE' - | 'CHILD_UNLOAD' - | 'CHILD_STILL_ALIVE' - | 'IS_CHILD_ALIVE' | 'RESPONSE' | 'DISCONNECT' | 'REQUEST_PAIRING' @@ -31,6 +24,7 @@ const logger = new Logger('MultiTabChannel') export class MultiTabChannel { private id: string = String(Date.now()) + private leaderID: string = '' private neighborhood: Set = new Set() private channel: BroadcastChannel private eventListeners = [ @@ -39,23 +33,14 @@ export class MultiTabChannel { ] private onBCMessageHandler: Function private onElectedLeaderHandler: Function - private leaderElectionTimeout: NodeJS.Timeout | undefined private pendingACKs: Map = new Map() + private storage: LocalStorage = new LocalStorage() isLeader: boolean = false private messageHandlers: { [key in BCMessageType]?: (data: BCMessage) => void - } = { - REQUEST_LEADERSHIP: this.handleRequestLeadership.bind(this), - LEADER_EXISTS: this.handleLeaderExists.bind(this), - CHILD_UNLOAD: this.handleChildUnload.bind(this), - CHILD_STILL_ALIVE: this.handleChildStillAlive.bind(this), - IS_CHILD_ALIVE: this.handleIsChildAlive.bind(this), - LEADER_UNLOAD: this.handleLeaderUnload.bind(this), - LEADER_STILL_ALIVE: this.handleLeaderStillAlive.bind(this), - IS_LEADER_ALIVE: this.handleIsLeaderAlive.bind(this) - } + } = {} constructor(name: string, onBCMessageHandler: Function, onElectedLeaderHandler: Function) { this.onBCMessageHandler = onBCMessageHandler @@ -64,36 +49,69 @@ export class MultiTabChannel { this.init() } - private init() { - this.postMessage({ type: 'REQUEST_LEADERSHIP' }) - this.leaderElectionTimeout = setTimeout(() => { + private async init() { + this.storage.subscribeToStorageChanged(async (event) => { + if ( + event.eventType === 'entryModified' && + event.key === this.storage.getPrefixedKey(StorageKey.BC_NEIGHBORHOOD) + ) { + const newNeighborhood = !event.newValue ? this.neighborhood : JSON.parse(event.newValue) + + if (newNeighborhood[0] !== this.leaderID) { + this.pendingACKs.set( + this.leaderID, + setTimeout(() => { + const neighborhood = Array.from(this.neighborhood) + this.leaderID = neighborhood[0] + if (neighborhood[0] !== this.id) { + return + } + this.isLeader = true + this.onElectedLeaderHandler() + logger.log('The current tab is the leader.') + }, timeout) + ) + } else { + clearTimeout(this.pendingACKs.get(this.leaderID)) + } + + this.neighborhood = newNeighborhood + } + }) + await this.requestLeadership() + } + + private async requestLeadership() { + const neighborhood = await this.storage.get(StorageKey.BC_NEIGHBORHOOD) + + if (!neighborhood.length) { this.isLeader = true logger.log('The current tab is the leader.') - }, timeout) + } + + neighborhood.push(this.id) + this.leaderID = neighborhood[0] + this.neighborhood = new Set(neighborhood) + this.storage.set(StorageKey.BC_NEIGHBORHOOD, neighborhood) window?.addEventListener('beforeunload', this.eventListeners[0]) this.channel.onmessage = this.eventListeners[1] } - private chooseNextLeader() { - return Math.floor(Math.random() * this.neighborhood.size) - } + private async onBeforeUnloadHandler() { + const oldNeighborhood = this.neighborhood + const newNeighborhood = new Set(this.neighborhood) + newNeighborhood.delete(this.id) + + await this.storage.set(StorageKey.BC_NEIGHBORHOOD, Array.from(newNeighborhood)) + this.neighborhood = newNeighborhood - private onBeforeUnloadHandler() { // We can't immediately say that a child or the leader is dead // beacause, on mobile a browser tab gets unloaded every time it no longer has focus - if (this.isLeader) { - this.postMessage({ - type: 'LEADER_UNLOAD', - recipient: Array.from(this.neighborhood)[this.chooseNextLeader()], - data: this.neighborhood - }) - } else { - this.postMessage({ type: 'CHILD_UNLOAD' }) - } - - window?.removeEventListener('beforeunload', this.eventListeners[0]) - this.channel.removeEventListener('message', this.eventListeners[1]) + setTimeout(() => { + this.storage.set(StorageKey.BC_NEIGHBORHOOD, Array.from(oldNeighborhood)) + this.neighborhood = oldNeighborhood + }, timeout / 2) } private onMessageHandler({ data }: { data: BCMessage }) { @@ -105,81 +123,6 @@ export class MultiTabChannel { } } - private handleRequestLeadership(data: BCMessage) { - if (this.isLeader) { - this.postMessage({ type: 'LEADER_EXISTS', recipient: data.sender }) - this.neighborhood.add(data.sender) - } - } - - private handleLeaderExists(data: BCMessage) { - if (data.recipient === this.id) { - clearTimeout(this.leaderElectionTimeout) - } - } - - private handleChildUnload(data: BCMessage) { - if (this.isLeader) { - this.pendingACKs.set( - data.sender, - setTimeout(() => { - this.neighborhood.delete(data.sender) - this.pendingACKs.delete(data.sender) - }, timeout) - ) - - this.postMessage({ type: 'IS_CHILD_ALIVE', recipient: data.sender }) - } - } - - private handleChildStillAlive(data: BCMessage) { - if (this.isLeader) { - this.clearPendingACK(data.sender) - } - } - - private handleIsChildAlive(data: BCMessage) { - if (data.recipient === this.id) { - this.postMessage({ type: 'CHILD_STILL_ALIVE' }) - } - } - - private handleLeaderUnload(data: BCMessage) { - if (data.recipient === this.id) { - this.pendingACKs.set( - data.sender, - setTimeout(() => { - this.isLeader = true - this.neighborhood = data.data - this.neighborhood.delete(this.id) - this.onElectedLeaderHandler() - logger.log('The current tab is the leader.') - }, timeout) - ) - } - this.postMessage({ type: 'IS_LEADER_ALIVE', recipient: data.sender }) - } - - private handleLeaderStillAlive(data: BCMessage) { - if (this.isLeader) { - this.clearPendingACK(data.sender) - } - } - - private handleIsLeaderAlive(data: BCMessage) { - if (data.recipient === this.id) { - this.postMessage({ type: 'LEADER_STILL_ALIVE' }) - } - } - - private clearPendingACK(sender: string) { - const timeout = this.pendingACKs.get(sender) - if (timeout) { - clearTimeout(timeout) - this.pendingACKs.delete(sender) - } - } - postMessage(message: Omit): void { this.channel.postMessage({ ...message, sender: this.id }) } diff --git a/packages/beacon-dapp/src/dapp-client/DAppClient.ts b/packages/beacon-dapp/src/dapp-client/DAppClient.ts index 784676d4b..efca76738 100644 --- a/packages/beacon-dapp/src/dapp-client/DAppClient.ts +++ b/packages/beacon-dapp/src/dapp-client/DAppClient.ts @@ -320,8 +320,7 @@ export class DAppClient extends Client { data: { message, connectionInfo - }, - recipient: message.id + } }) if (typedMessage.type !== BeaconMessageType.Acknowledge) { diff --git a/packages/beacon-types/src/types/storage/StorageKey.ts b/packages/beacon-types/src/types/storage/StorageKey.ts index 5bf47b614..1814ab5fb 100644 --- a/packages/beacon-types/src/types/storage/StorageKey.ts +++ b/packages/beacon-types/src/types/storage/StorageKey.ts @@ -23,6 +23,7 @@ export enum StorageKey { USER_ID = 'beacon:user-id', ENABLE_METRICS = 'beacon:enable_metrics', WC_INIT_ERROR = 'beacon:wc-init-error', + BC_NEIGHBORHOOD = 'beacon:bc-neighborhood', WC_2_CORE_PAIRING = 'wc@2:core:0.3:pairing', WC_2_CLIENT_SESSION = 'wc@2:client:0.3:session', WC_2_CORE_KEYCHAIN = 'wc@2:core:0.3:keychain', diff --git a/packages/beacon-types/src/types/storage/StorageKeyReturnDefaults.ts b/packages/beacon-types/src/types/storage/StorageKeyReturnDefaults.ts index 946e23fb5..c97972e5e 100644 --- a/packages/beacon-types/src/types/storage/StorageKeyReturnDefaults.ts +++ b/packages/beacon-types/src/types/storage/StorageKeyReturnDefaults.ts @@ -30,6 +30,7 @@ export const defaultValues: StorageKeyReturnDefaults = { [StorageKey.WC_2_CLIENT_SESSION]: undefined, [StorageKey.USER_ID]: undefined, [StorageKey.ENABLE_METRICS]: undefined, + [StorageKey.BC_NEIGHBORHOOD]: [], [StorageKey.WC_INIT_ERROR]: undefined, [StorageKey.WC_2_CORE_PAIRING]: undefined, [StorageKey.WC_2_CORE_KEYCHAIN]: undefined, diff --git a/packages/beacon-types/src/types/storage/StorageKeyReturnType.ts b/packages/beacon-types/src/types/storage/StorageKeyReturnType.ts index 45406dd23..825718554 100644 --- a/packages/beacon-types/src/types/storage/StorageKeyReturnType.ts +++ b/packages/beacon-types/src/types/storage/StorageKeyReturnType.ts @@ -50,6 +50,7 @@ export interface StorageKeyReturnType { [StorageKey.USER_ID]: string | undefined [StorageKey.ENABLE_METRICS]: boolean | undefined [StorageKey.WC_INIT_ERROR]: string | undefined + [StorageKey.BC_NEIGHBORHOOD]: string[] [StorageKey.WC_2_CLIENT_SESSION]: string | undefined [StorageKey.WC_2_CORE_PAIRING]: string | undefined [StorageKey.WC_2_CORE_KEYCHAIN]: string | undefined From 9657046a2b3bde3e4305ca84c7465e195a0884ef Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Sat, 21 Sep 2024 15:06:35 +0200 Subject: [PATCH 09/24] feat: added heartbeat for mobile --- .../src/utils/multi-tab-channel.ts | 64 +++++++++++++++---- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/packages/beacon-core/src/utils/multi-tab-channel.ts b/packages/beacon-core/src/utils/multi-tab-channel.ts index f2542bfc1..7072b8723 100644 --- a/packages/beacon-core/src/utils/multi-tab-channel.ts +++ b/packages/beacon-core/src/utils/multi-tab-channel.ts @@ -3,6 +3,8 @@ import { Logger } from './Logger' import { LocalStorage } from '../storage/LocalStorage' type BCMessageType = + | 'HEARTBEAT' + | 'HEARTBEAT_ACK' | 'RESPONSE' | 'DISCONNECT' | 'REQUEST_PAIRING' @@ -40,7 +42,10 @@ export class MultiTabChannel { private messageHandlers: { [key in BCMessageType]?: (data: BCMessage) => void - } = {} + } = { + HEARTBEAT: this.heartbeatHandler.bind(this), + HEARTBEAT_ACK: this.heartbeatACKHandler.bind(this) + } constructor(name: string, onBCMessageHandler: Function, onElectedLeaderHandler: Function) { this.onBCMessageHandler = onBCMessageHandler @@ -58,19 +63,7 @@ export class MultiTabChannel { const newNeighborhood = !event.newValue ? this.neighborhood : JSON.parse(event.newValue) if (newNeighborhood[0] !== this.leaderID) { - this.pendingACKs.set( - this.leaderID, - setTimeout(() => { - const neighborhood = Array.from(this.neighborhood) - this.leaderID = neighborhood[0] - if (neighborhood[0] !== this.id) { - return - } - this.isLeader = true - this.onElectedLeaderHandler() - logger.log('The current tab is the leader.') - }, timeout) - ) + this.leaderElection() } else { clearTimeout(this.pendingACKs.get(this.leaderID)) } @@ -96,6 +89,49 @@ export class MultiTabChannel { window?.addEventListener('beforeunload', this.eventListeners[0]) this.channel.onmessage = this.eventListeners[1] + + this.initHeartbeat() + } + + private initHeartbeat() { + if ( + this.isLeader || + !/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) + ) { + return + } + + setInterval(() => { + this.leaderElection() + this.postMessage({ type: 'HEARTBEAT' }) + }, timeout * 2) + } + + private heartbeatHandler() { + if (!this.isLeader) { + return + } + this.postMessage({ type: 'HEARTBEAT_ACK' }) + } + + private heartbeatACKHandler() { + this.pendingACKs.delete(this.leaderID) + } + + private leaderElection() { + this.pendingACKs.set( + this.leaderID, + setTimeout(() => { + const neighborhood = Array.from(this.neighborhood) + this.leaderID = neighborhood[0] + if (neighborhood[0] !== this.id) { + return + } + this.isLeader = true + this.onElectedLeaderHandler() + logger.log('The current tab is the leader.') + }, timeout) + ) } private async onBeforeUnloadHandler() { From 9932af0e6dea5b7dda530224eac8c3e457313476 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Sat, 21 Sep 2024 15:09:53 +0200 Subject: [PATCH 10/24] fix: add splice leader --- packages/beacon-core/src/utils/multi-tab-channel.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/beacon-core/src/utils/multi-tab-channel.ts b/packages/beacon-core/src/utils/multi-tab-channel.ts index 7072b8723..2f37c2dcb 100644 --- a/packages/beacon-core/src/utils/multi-tab-channel.ts +++ b/packages/beacon-core/src/utils/multi-tab-channel.ts @@ -102,7 +102,7 @@ export class MultiTabChannel { } setInterval(() => { - this.leaderElection() + this.leaderElection(Array.from(this.neighborhood).filter((id) => id !== this.leaderID)) this.postMessage({ type: 'HEARTBEAT' }) }, timeout * 2) } @@ -118,11 +118,10 @@ export class MultiTabChannel { this.pendingACKs.delete(this.leaderID) } - private leaderElection() { + private leaderElection(neighborhood = Array.from(this.neighborhood)) { this.pendingACKs.set( this.leaderID, setTimeout(() => { - const neighborhood = Array.from(this.neighborhood) this.leaderID = neighborhood[0] if (neighborhood[0] !== this.id) { return From c97d833cc1aa4788cddb253f8de52b8c390554f1 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Tue, 24 Sep 2024 14:48:12 +0200 Subject: [PATCH 11/24] fix: broadcast channel only on desktop --- package-lock.json | 51 +++++- packages/beacon-core/package.json | 1 + .../src/utils/multi-tab-channel.ts | 148 ++++++------------ .../beacon-dapp/src/dapp-client/DAppClient.ts | 12 +- .../src/types/storage/StorageKey.ts | 1 - .../types/storage/StorageKeyReturnDefaults.ts | 1 - .../src/types/storage/StorageKeyReturnType.ts | 1 - 7 files changed, 100 insertions(+), 115 deletions(-) diff --git a/package-lock.json b/package-lock.json index 43a25d4f9..7782f3832 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7460,6 +7460,31 @@ "node": ">=8" } }, + "node_modules/broadcast-channel": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-7.0.0.tgz", + "integrity": "sha512-a2tW0Ia1pajcPBOGUF2jXlDnvE9d5/dg6BG9h60OmRUcZVr/veUrU8vEQFwwQIhwG3KVzYwSk3v2nRRGFgQDXQ==", + "dependencies": { + "@babel/runtime": "7.23.4", + "oblivious-set": "1.4.0", + "p-queue": "6.6.2", + "unload": "2.4.1" + }, + "funding": { + "url": "https://github.com/sponsors/pubkey" + } + }, + "node_modules/broadcast-channel/node_modules/@babel/runtime": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.4.tgz", + "integrity": "sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", @@ -10897,8 +10922,7 @@ "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "node_modules/events": { "version": "3.3.0", @@ -17428,6 +17452,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oblivious-set": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.4.0.tgz", + "integrity": "sha512-szyd0ou0T8nsAqHtprRcP3WidfsN1TnAR5yWXf2mFCEr5ek3LEOkT6EZ/92Xfs74HIdyhG5WkGxIssMU0jBaeg==", + "engines": { + "node": ">=16" + } + }, "node_modules/ofetch": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.3.4.tgz", @@ -17644,7 +17676,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true, "engines": { "node": ">=4" } @@ -17719,7 +17750,6 @@ "version": "6.6.2", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", - "dev": true, "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" @@ -17744,7 +17774,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "dev": true, "dependencies": { "p-finally": "^1.0.0" }, @@ -20608,8 +20637,7 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regenerator-transform": { "version": "0.15.2", @@ -23801,6 +23829,14 @@ "node": ">= 10.0.0" } }, + "node_modules/unload": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.4.1.tgz", + "integrity": "sha512-IViSAm8Z3sRBYA+9wc0fLQmU9Nrxb16rcDmIiR6Y9LJSZzI7QY5QsDhqPpKOjAn0O9/kfK1TfNEMMAGPTIraPw==", + "funding": { + "url": "https://github.com/sponsors/pubkey" + } + }, "node_modules/unstorage": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.10.2.tgz", @@ -25085,6 +25121,7 @@ "@stablelib/nacl": "^1.0.4", "@stablelib/utf8": "^1.0.1", "@stablelib/x25519-session": "^1.0.4", + "broadcast-channel": "^7.0.0", "bs58check": "2.1.2" } }, diff --git a/packages/beacon-core/package.json b/packages/beacon-core/package.json index 9f5535a77..e355216e9 100644 --- a/packages/beacon-core/package.json +++ b/packages/beacon-core/package.json @@ -40,6 +40,7 @@ "@stablelib/nacl": "^1.0.4", "@stablelib/utf8": "^1.0.1", "@stablelib/x25519-session": "^1.0.4", + "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 2f37c2dcb..af50eb9b3 100644 --- a/packages/beacon-core/src/utils/multi-tab-channel.ts +++ b/packages/beacon-core/src/utils/multi-tab-channel.ts @@ -1,10 +1,9 @@ -import { BeaconMessageType, StorageKey } from '@airgap/beacon-types' -import { Logger } from './Logger' -import { LocalStorage } from '../storage/LocalStorage' +import { Logger } from '@airgap/beacon-core' +import { BeaconMessageType } from '@airgap/beacon-types' +import { createLeaderElection, BroadcastChannel, LeaderElector } from 'broadcast-channel' type BCMessageType = - | 'HEARTBEAT' - | 'HEARTBEAT_ACK' + | 'LEADER_DEAD' | 'RESPONSE' | 'DISCONNECT' | 'REQUEST_PAIRING' @@ -21,141 +20,92 @@ type BCMessage = { data?: any } -const timeout = 1000 // ms const logger = new Logger('MultiTabChannel') +const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( + navigator.userAgent +) export class MultiTabChannel { private id: string = String(Date.now()) - private leaderID: string = '' - private neighborhood: Set = new Set() private channel: BroadcastChannel + private elector: LeaderElector private eventListeners = [ () => this.onBeforeUnloadHandler(), (message: any) => this.onMessageHandler(message) ] private onBCMessageHandler: Function private onElectedLeaderHandler: Function - private pendingACKs: Map = new Map() - private storage: LocalStorage = new LocalStorage() - - isLeader: boolean = false - - private messageHandlers: { - [key in BCMessageType]?: (data: BCMessage) => void - } = { - HEARTBEAT: this.heartbeatHandler.bind(this), - HEARTBEAT_ACK: this.heartbeatACKHandler.bind(this) - } + // Auxiliary variable needed for handling beforeUnload. + // Closing a tab causes the elector to be killed immediately + private wasLeader: boolean = false constructor(name: string, onBCMessageHandler: Function, onElectedLeaderHandler: Function) { this.onBCMessageHandler = onBCMessageHandler this.onElectedLeaderHandler = onElectedLeaderHandler this.channel = new BroadcastChannel(name) + this.elector = createLeaderElection(this.channel) this.init() + .then(() => logger.debug('MultiTabChannel', 'constructor', 'init', 'done')) + .catch((err) => logger.warn(err.message)) } private async init() { - this.storage.subscribeToStorageChanged(async (event) => { - if ( - event.eventType === 'entryModified' && - event.key === this.storage.getPrefixedKey(StorageKey.BC_NEIGHBORHOOD) - ) { - const newNeighborhood = !event.newValue ? this.neighborhood : JSON.parse(event.newValue) - - if (newNeighborhood[0] !== this.leaderID) { - this.leaderElection() - } else { - clearTimeout(this.pendingACKs.get(this.leaderID)) - } - - this.neighborhood = newNeighborhood - } - }) - await this.requestLeadership() - } + if (isMobile) { + throw new Error('BroadcastChannel is not fully supported on mobile.') + } - private async requestLeadership() { - const neighborhood = await this.storage.get(StorageKey.BC_NEIGHBORHOOD) + const hasLeader = await this.elector.hasLeader() - if (!neighborhood.length) { - this.isLeader = true - logger.log('The current tab is the leader.') + if (!hasLeader) { + await this.elector.awaitLeadership() + this.wasLeader = this.isLeader() + this.wasLeader && logger.log('The current tab is the leader.') } - neighborhood.push(this.id) - this.leaderID = neighborhood[0] - this.neighborhood = new Set(neighborhood) - this.storage.set(StorageKey.BC_NEIGHBORHOOD, neighborhood) - - window?.addEventListener('beforeunload', this.eventListeners[0]) this.channel.onmessage = this.eventListeners[1] - - this.initHeartbeat() + window?.addEventListener('beforeunload', this.eventListeners[0]) } - private initHeartbeat() { - if ( - this.isLeader || - !/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) - ) { - return + private async onBeforeUnloadHandler() { + if (this.wasLeader) { + await this.elector.die() + this.postMessage({ type: 'LEADER_DEAD' }) } - setInterval(() => { - this.leaderElection(Array.from(this.neighborhood).filter((id) => id !== this.leaderID)) - this.postMessage({ type: 'HEARTBEAT' }) - }, timeout * 2) + window?.removeEventListener('beforeunload', this.eventListeners[0]) + this.channel.removeEventListener('message', this.eventListeners[1]) } - private heartbeatHandler() { - if (!this.isLeader) { + private async onMessageHandler(message: BCMessage) { + if (message.recipient && message.recipient !== this.id) { return } - this.postMessage({ type: 'HEARTBEAT_ACK' }) - } - private heartbeatACKHandler() { - this.pendingACKs.delete(this.leaderID) - } + if (message.type === 'LEADER_DEAD') { + await this.elector.awaitLeadership() + + this.wasLeader = this.isLeader() - private leaderElection(neighborhood = Array.from(this.neighborhood)) { - this.pendingACKs.set( - this.leaderID, - setTimeout(() => { - this.leaderID = neighborhood[0] - if (neighborhood[0] !== this.id) { - return - } - this.isLeader = true + if (this.isLeader()) { this.onElectedLeaderHandler() logger.log('The current tab is the leader.') - }, timeout) - ) + } + return + } + + this.onBCMessageHandler(message) } - private async onBeforeUnloadHandler() { - const oldNeighborhood = this.neighborhood - const newNeighborhood = new Set(this.neighborhood) - newNeighborhood.delete(this.id) - - await this.storage.set(StorageKey.BC_NEIGHBORHOOD, Array.from(newNeighborhood)) - this.neighborhood = newNeighborhood - - // We can't immediately say that a child or the leader is dead - // beacause, on mobile a browser tab gets unloaded every time it no longer has focus - setTimeout(() => { - this.storage.set(StorageKey.BC_NEIGHBORHOOD, Array.from(oldNeighborhood)) - this.neighborhood = oldNeighborhood - }, timeout / 2) + isLeader(): boolean { + return this.elector.isLeader } - private onMessageHandler({ data }: { data: BCMessage }) { - const handler = this.messageHandlers[data.type] - if (handler) { - handler(data) - } else { - this.onBCMessageHandler(data) - } + async getLeadership() { + return this.elector.awaitLeadership() + } + + async hasLeader(): Promise { + return this.elector.hasLeader() } postMessage(message: Omit): void { diff --git a/packages/beacon-dapp/src/dapp-client/DAppClient.ts b/packages/beacon-dapp/src/dapp-client/DAppClient.ts index efca76738..fb58bf7c6 100644 --- a/packages/beacon-dapp/src/dapp-client/DAppClient.ts +++ b/packages/beacon-dapp/src/dapp-client/DAppClient.ts @@ -517,7 +517,7 @@ export class DAppClient extends Client { } private async handlePairingRequest(recipient: string) { - if (!this.multiTabChannel.isLeader) { + if (!this.multiTabChannel.isLeader()) { return } @@ -571,7 +571,7 @@ export class DAppClient extends Client { } private async prepareRequest({ data }: any, isV3 = false) { - if (!this.multiTabChannel.isLeader) { + if (!this.multiTabChannel.isLeader()) { return } @@ -655,7 +655,7 @@ export class DAppClient extends Client { network: this.network.type, opts: wcOptions }, - () => this.multiTabChannel.isLeader + () => this.multiTabChannel.isLeader() ) this.initEvents() @@ -730,7 +730,7 @@ export class DAppClient extends Client { } private async getPairingRequestInfo(transport: DappWalletConnectTransport) { - if (this.multiTabChannel.isLeader) { + if (this.multiTabChannel.isLeader()) { return transport.getPairingRequestInfo() } @@ -960,7 +960,7 @@ export class DAppClient extends Client { this.debounceSetActiveAccount = true this._initPromise = undefined this.postMessageTransport = this.p2pTransport = this.walletConnectTransport = undefined - if (this.multiTabChannel.isLeader) { + if (this.multiTabChannel.isLeader()) { await transport.disconnect() this.openRequestsOtherTabs.clear() } else { @@ -1149,7 +1149,7 @@ 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 = this.multiTabChannel.isLeader() return !isResolved || !isWCInstance || isLeader } diff --git a/packages/beacon-types/src/types/storage/StorageKey.ts b/packages/beacon-types/src/types/storage/StorageKey.ts index 1814ab5fb..5bf47b614 100644 --- a/packages/beacon-types/src/types/storage/StorageKey.ts +++ b/packages/beacon-types/src/types/storage/StorageKey.ts @@ -23,7 +23,6 @@ export enum StorageKey { USER_ID = 'beacon:user-id', ENABLE_METRICS = 'beacon:enable_metrics', WC_INIT_ERROR = 'beacon:wc-init-error', - BC_NEIGHBORHOOD = 'beacon:bc-neighborhood', WC_2_CORE_PAIRING = 'wc@2:core:0.3:pairing', WC_2_CLIENT_SESSION = 'wc@2:client:0.3:session', WC_2_CORE_KEYCHAIN = 'wc@2:core:0.3:keychain', diff --git a/packages/beacon-types/src/types/storage/StorageKeyReturnDefaults.ts b/packages/beacon-types/src/types/storage/StorageKeyReturnDefaults.ts index c97972e5e..946e23fb5 100644 --- a/packages/beacon-types/src/types/storage/StorageKeyReturnDefaults.ts +++ b/packages/beacon-types/src/types/storage/StorageKeyReturnDefaults.ts @@ -30,7 +30,6 @@ export const defaultValues: StorageKeyReturnDefaults = { [StorageKey.WC_2_CLIENT_SESSION]: undefined, [StorageKey.USER_ID]: undefined, [StorageKey.ENABLE_METRICS]: undefined, - [StorageKey.BC_NEIGHBORHOOD]: [], [StorageKey.WC_INIT_ERROR]: undefined, [StorageKey.WC_2_CORE_PAIRING]: undefined, [StorageKey.WC_2_CORE_KEYCHAIN]: undefined, diff --git a/packages/beacon-types/src/types/storage/StorageKeyReturnType.ts b/packages/beacon-types/src/types/storage/StorageKeyReturnType.ts index 825718554..45406dd23 100644 --- a/packages/beacon-types/src/types/storage/StorageKeyReturnType.ts +++ b/packages/beacon-types/src/types/storage/StorageKeyReturnType.ts @@ -50,7 +50,6 @@ export interface StorageKeyReturnType { [StorageKey.USER_ID]: string | undefined [StorageKey.ENABLE_METRICS]: boolean | undefined [StorageKey.WC_INIT_ERROR]: string | undefined - [StorageKey.BC_NEIGHBORHOOD]: string[] [StorageKey.WC_2_CLIENT_SESSION]: string | undefined [StorageKey.WC_2_CORE_PAIRING]: string | undefined [StorageKey.WC_2_CORE_KEYCHAIN]: string | undefined From 9fdffaf895958931a27c12662763dc600d3e36e3 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Tue, 24 Sep 2024 16:28:54 +0200 Subject: [PATCH 12/24] fix: add locking mechanism --- .../beacon-core/src/utils/multi-tab-channel.ts | 15 ++++++++++----- .../beacon-dapp/src/dapp-client/DAppClient.ts | 4 ++-- .../src/transports/DappWalletConnectTransport.ts | 2 +- .../src/WalletConnectTransport.ts | 4 ++-- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/beacon-core/src/utils/multi-tab-channel.ts b/packages/beacon-core/src/utils/multi-tab-channel.ts index af50eb9b3..c2fcfd37e 100644 --- a/packages/beacon-core/src/utils/multi-tab-channel.ts +++ b/packages/beacon-core/src/utils/multi-tab-channel.ts @@ -1,5 +1,6 @@ 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 BCMessageType = @@ -35,6 +36,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 @@ -58,8 +60,9 @@ export class MultiTabChannel { 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] @@ -84,9 +87,11 @@ export class MultiTabChannel { 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.') } @@ -96,8 +101,8 @@ export class MultiTabChannel { this.onBCMessageHandler(message) } - isLeader(): boolean { - return this.elector.isLeader + isLeader(): Promise { + return this._isLeader.promise } async getLeadership() { diff --git a/packages/beacon-dapp/src/dapp-client/DAppClient.ts b/packages/beacon-dapp/src/dapp-client/DAppClient.ts index fb58bf7c6..e4734525e 100644 --- a/packages/beacon-dapp/src/dapp-client/DAppClient.ts +++ b/packages/beacon-dapp/src/dapp-client/DAppClient.ts @@ -730,7 +730,7 @@ export class DAppClient extends Client { } private async getPairingRequestInfo(transport: DappWalletConnectTransport) { - if (this.multiTabChannel.isLeader()) { + if (await this.multiTabChannel.isLeader()) { return transport.getPairingRequestInfo() } @@ -960,7 +960,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 { diff --git a/packages/beacon-dapp/src/transports/DappWalletConnectTransport.ts b/packages/beacon-dapp/src/transports/DappWalletConnectTransport.ts index 5a794298d..f1a3f2e6e 100644 --- a/packages/beacon-dapp/src/transports/DappWalletConnectTransport.ts +++ b/packages/beacon-dapp/src/transports/DappWalletConnectTransport.ts @@ -25,7 +25,7 @@ export class DappWalletConnectTransport extends WalletConnectTransport< keyPair: KeyPair, storage: Storage, wcOptions: { network: NetworkType; opts: SignClientTypes.Options }, - isLeader: () => boolean + isLeader: () => Promise ) { super( name, diff --git a/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts b/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts index 48061b621..17f547f1c 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: () => boolean + private isLeader: () => Promise ) { super( name, @@ -62,7 +62,7 @@ export class WalletConnectTransport< this._isConnected = TransportStatus.CONNECTING - const isLeader = this.isLeader() + const isLeader = await this.isLeader() if (isLeader) { await this.client.init() From 18093125b02e1ceb4c1d30d21cf733cdebcdce3b Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Wed, 25 Sep 2024 15:25:41 +0200 Subject: [PATCH 13/24] fix: pairing on second tab --- .../src/utils/multi-tab-channel.ts | 2 +- .../beacon-dapp/src/dapp-client/DAppClient.ts | 147 ++++-------------- .../src/WalletConnectTransport.ts | 4 +- .../WalletConnectCommunicationClient.ts | 23 ++- 4 files changed, 48 insertions(+), 128 deletions(-) diff --git a/packages/beacon-core/src/utils/multi-tab-channel.ts b/packages/beacon-core/src/utils/multi-tab-channel.ts index c2fcfd37e..662602ee3 100644 --- a/packages/beacon-core/src/utils/multi-tab-channel.ts +++ b/packages/beacon-core/src/utils/multi-tab-channel.ts @@ -62,9 +62,9 @@ export class MultiTabChannel { await this.elector.awaitLeadership() this.wasLeader = this.elector.isLeader this.wasLeader && logger.log('The current tab is the leader.') - this._isLeader.resolve(this.wasLeader) } + this._isLeader.resolve(this.wasLeader) this.channel.onmessage = this.eventListeners[1] window?.addEventListener('beforeunload', this.eventListeners[0]) } diff --git a/packages/beacon-dapp/src/dapp-client/DAppClient.ts b/packages/beacon-dapp/src/dapp-client/DAppClient.ts index e4734525e..3e120bd05 100644 --- a/packages/beacon-dapp/src/dapp-client/DAppClient.ts +++ b/packages/beacon-dapp/src/dapp-client/DAppClient.ts @@ -232,9 +232,6 @@ export class DAppClient extends Client { this.onElectedLeaderhandler.bind(this) ) - private pairingRequest: ExposedPromise | undefined - private pairingResponse: { message: BeaconMessage; connectionInfo: ConnectionContext } | undefined - constructor(config: DAppClientOptions) { super({ storage: config && config.storage ? config.storage : new LocalStorage(), @@ -494,82 +491,11 @@ export class DAppClient extends Client { case 'DISCONNECT': this._transport.isResolved() && this.disconnect() break - case 'REQUEST_PAIRING': - this.handlePairingRequest(message.sender) - break - case 'RESPONSE_PAIRING': - this.handlePairingResponse(message.data) - break - case 'NEW_PEER': - this.handleNewPeerBC(message.data) - break - case 'HIDE_UI': - this.hideUI(message.data) - break default: logger.error('onBCMessageHandler', 'message type not recognized', message) } } - private async handleNewPeerBC(data: any) { - this.pairingResponse = data.response - this.walletConnectTransport?.updatePeerListener(data.peer) - } - - private async handlePairingRequest(recipient: string) { - if (!this.multiTabChannel.isLeader()) { - return - } - - await this.initInternalTransports() - - this.walletConnectTransport - ?.listenForNewPeer(async (peer) => { - logger.log('init', 'walletconnect transport peer connected', peer) - this.analytics.track('event', 'DAppClient', 'WalletConnect Wallet connected', { - peerName: peer.name - }) - - this.setActivePeer(peer).catch(console.error) - this.setTransport(this.walletConnectTransport).catch(console.error) - - await this.init(this.walletConnectTransport) - - const request: PermissionRequestInput = { - appMetadata: await this.getOwnAppMetadata(), - type: BeaconMessageType.PermissionRequest, - network: this.network, - scopes: [PermissionScope.OPERATION_REQUEST, PermissionScope.SIGN] // todo - } - - const response = await this.makeRequest(request) - - await this.hideUI(['toast']) - - this.walletConnectTransport?.connect() - - this.multiTabChannel.postMessage({ - type: 'NEW_PEER', - recipient, - data: { - peer, - response - } - }) - }) - .catch(console.error) - - this.multiTabChannel.postMessage({ - type: 'RESPONSE_PAIRING', - recipient, - data: await this.walletConnectTransport?.getPairingRequestInfo() - }) - } - - private async handlePairingResponse(data: string) { - this.pairingRequest?.resolve(data) - } - private async prepareRequest({ data }: any, isV3 = false) { if (!this.multiTabChannel.isLeader()) { return @@ -668,10 +594,10 @@ 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.multiTabChannel.postMessage({ type: 'HIDE_UI', data: ['alert', 'toast'] }) + }) this.walletConnectTransport.setEventHandler( ClientEvents.RESET_STATE, this.channelClosedHandler.bind(this) @@ -729,16 +655,6 @@ export class DAppClient extends Client { await super.destroy() } - private async getPairingRequestInfo(transport: DappWalletConnectTransport) { - if (await this.multiTabChannel.isLeader()) { - return transport.getPairingRequestInfo() - } - - this.pairingRequest = new ExposedPromise() - this.multiTabChannel.postMessage({ type: 'REQUEST_PAIRING' }) - return await this.pairingRequest.promise - } - public async init(transport?: Transport): Promise { if (this._initPromise) { return this._initPromise @@ -863,7 +779,7 @@ export class DAppClient extends Client { return p2pTransport.getPairingRequestInfo() }, postmessagePeerInfo: () => postMessageTransport.getPairingRequestInfo(), - walletConnectPeerInfo: () => this.getPairingRequestInfo(walletConnectTransport), + walletConnectPeerInfo: () => walletConnectTransport.getPairingRequestInfo(), networkType: this.network.type, abortedHandler: async () => { logger.log('init', 'ABORTED') @@ -2399,11 +2315,7 @@ export class DAppClient extends Client { ErrorResponse >() - if (this.pairingResponse) { - exposed.resolve(this.pairingResponse) - } else { - this.addOpenRequest(request.id, exposed) - } + this.addOpenRequest(request.id, exposed) } const payload = await new Serializer().serialize(request) @@ -2415,32 +2327,29 @@ export class DAppClient extends Client { const walletInfo = await this.getWalletInfo(peer, account) logger.log('makeRequest', 'sending message', request) - if (!this.pairingResponse) { - try { - ;(await this.transport).send(payload, peer) - if ( - request.type !== BeaconMessageType.PermissionRequest || - (this._activeAccount.isResolved() && (await this._activeAccount.promise)) - ) { - this.tryToAppSwitch() - } - } catch (sendError) { - this.events.emit(BeaconEvent.INTERNAL_ERROR, { - text: 'Unable to send message. If this problem persists, please reset the connection and pair your wallet again.', - buttons: [ - { - text: 'Reset Connection', - actionCallback: async (): Promise => { - await closeToast() - this.disconnect() - } - } - ] - }) - throw sendError + + try { + ;(await this.transport).send(payload, peer) + if ( + request.type !== BeaconMessageType.PermissionRequest || + (this._activeAccount.isResolved() && (await this._activeAccount.promise)) + ) { + this.tryToAppSwitch() } - } else { - this.pairingResponse = undefined + } catch (sendError) { + this.events.emit(BeaconEvent.INTERNAL_ERROR, { + text: 'Unable to send message. If this problem persists, please reset the connection and pair your wallet again.', + buttons: [ + { + text: 'Reset Connection', + actionCallback: async (): Promise => { + await closeToast() + this.disconnect() + } + } + ] + }) + throw sendError } if (!otherTabMessageId) { diff --git a/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts b/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts index 17f547f1c..54f3964cd 100644 --- a/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts +++ b/packages/beacon-transport-walletconnect/src/WalletConnectTransport.ts @@ -39,7 +39,7 @@ export class WalletConnectTransport< ) { super( name, - WalletConnectCommunicationClient.getInstance(wcOptions), + WalletConnectCommunicationClient.getInstance(wcOptions, isLeader), new PeerManager(storage, storageKey) ) } @@ -89,7 +89,7 @@ export class WalletConnectTransport< } public async getPeers(): Promise { - const client = WalletConnectCommunicationClient.getInstance(this.wcOptions) + const client = WalletConnectCommunicationClient.getInstance(this.wcOptions, this.isLeader) const session = client.currentSession() if (!session) { return [] diff --git a/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts b/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts index 402f37d5c..ba64fef7a 100644 --- a/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts +++ b/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts @@ -111,16 +111,22 @@ export class WalletConnectCommunicationClient extends CommunicationClient { */ private messageIds: string[] = [] - constructor(private wcOptions: { network: NetworkType; opts: SignClientTypes.Options }) { + constructor( + private wcOptions: { network: NetworkType; opts: SignClientTypes.Options }, + private isLeader: () => Promise + ) { super() } - static getInstance(wcOptions: { - network: NetworkType - opts: SignClientTypes.Options - }): WalletConnectCommunicationClient { + static getInstance( + wcOptions: { + network: NetworkType + opts: SignClientTypes.Options + }, + isLeader: () => Promise + ): WalletConnectCommunicationClient { if (!this.instance) { - this.instance = new WalletConnectCommunicationClient(wcOptions) + this.instance = new WalletConnectCommunicationClient(wcOptions, isLeader) } return WalletConnectCommunicationClient.instance } @@ -630,6 +636,11 @@ export class WalletConnectCommunicationClient extends CommunicationClient { this.notifyListeners(_pairingTopic, errorResponse) } }) + .then(() => { + if (!this.isMobileOS() && !this.isLeader()) { + this.clearState() + } + }) logger.warn('return uri and topic') From d76d346fab0148e46796bd18d8e5bbd878be5b3d Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Wed, 25 Sep 2024 15:40:32 +0200 Subject: [PATCH 14/24] fix: mobile make request --- packages/beacon-dapp/src/dapp-client/DAppClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-dapp/src/dapp-client/DAppClient.ts b/packages/beacon-dapp/src/dapp-client/DAppClient.ts index 3e120bd05..c4598f3ec 100644 --- a/packages/beacon-dapp/src/dapp-client/DAppClient.ts +++ b/packages/beacon-dapp/src/dapp-client/DAppClient.ts @@ -1067,7 +1067,7 @@ export class DAppClient extends Client { const isWCInstance = isResolved && (await this.transport) instanceof WalletConnectTransport const isLeader = this.multiTabChannel.isLeader() - return !isResolved || !isWCInstance || isLeader + return !isResolved || !isWCInstance || isLeader || isMobileOS(window) } /** From f128bbc45376a011a82d277ee2f7eba4113b5ff6 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Wed, 25 Sep 2024 15:42:17 +0200 Subject: [PATCH 15/24] fix: removed ping on mobile --- .../communication-client/WalletConnectCommunicationClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts b/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts index ba64fef7a..e68a64b97 100644 --- a/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts +++ b/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts @@ -193,7 +193,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { } private async checkWalletReadiness(_topic: string) { - if (this.pingInterval) { + if (this.pingInterval || this.isMobileOS()) { return } From 0e076d40092708340404f8cafd90b6ddcbecda35 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Wed, 25 Sep 2024 15:58:40 +0200 Subject: [PATCH 16/24] fix: request sent on mobile --- packages/beacon-dapp/src/dapp-client/DAppClient.ts | 3 ++- packages/beacon-dapp/src/events.ts | 7 ++++++- packages/beacon-types/src/types/WalletInfo.ts | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/beacon-dapp/src/dapp-client/DAppClient.ts b/packages/beacon-dapp/src/dapp-client/DAppClient.ts index c4598f3ec..743bcd1cb 100644 --- a/packages/beacon-dapp/src/dapp-client/DAppClient.ts +++ b/packages/beacon-dapp/src/dapp-client/DAppClient.ts @@ -2357,7 +2357,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 () => { 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-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 } From 2872b8f4982e52c5eacfd0145d73c236a27e4bcb Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Mon, 7 Oct 2024 09:47:53 +0200 Subject: [PATCH 17/24] fix: remove unused types --- packages/beacon-core/src/utils/multi-tab-channel.ts | 4 ---- packages/beacon-dapp/src/dapp-client/DAppClient.ts | 1 - 2 files changed, 5 deletions(-) diff --git a/packages/beacon-core/src/utils/multi-tab-channel.ts b/packages/beacon-core/src/utils/multi-tab-channel.ts index 662602ee3..45267f2e1 100644 --- a/packages/beacon-core/src/utils/multi-tab-channel.ts +++ b/packages/beacon-core/src/utils/multi-tab-channel.ts @@ -7,11 +7,7 @@ type BCMessageType = | 'LEADER_DEAD' | 'RESPONSE' | 'DISCONNECT' - | 'REQUEST_PAIRING' - | 'RESPONSE_PAIRING' | 'HIDE_UI' - | 'NEW_PEER' - | 'GET_PERMISSIONS' | BeaconMessageType type BCMessage = { diff --git a/packages/beacon-dapp/src/dapp-client/DAppClient.ts b/packages/beacon-dapp/src/dapp-client/DAppClient.ts index 743bcd1cb..2742fa493 100644 --- a/packages/beacon-dapp/src/dapp-client/DAppClient.ts +++ b/packages/beacon-dapp/src/dapp-client/DAppClient.ts @@ -596,7 +596,6 @@ export class DAppClient extends Client { this.walletConnectTransport.setEventHandler(ClientEvents.CLOSE_ALERT, () => { this.hideUI(['alert', 'toast']) - this.multiTabChannel.postMessage({ type: 'HIDE_UI', data: ['alert', 'toast'] }) }) this.walletConnectTransport.setEventHandler( ClientEvents.RESET_STATE, From 6169a49af1a3e16e74cb5b6cbfbe7b42eddf6304 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Mon, 7 Oct 2024 10:56:46 +0200 Subject: [PATCH 18/24] fix: stuck state --- .../beacon-dapp/src/dapp-client/DAppClient.ts | 110 +++++++++--------- .../WalletConnectCommunicationClient.ts | 12 +- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/packages/beacon-dapp/src/dapp-client/DAppClient.ts b/packages/beacon-dapp/src/dapp-client/DAppClient.ts index 2742fa493..9071d4494 100644 --- a/packages/beacon-dapp/src/dapp-client/DAppClient.ts +++ b/packages/beacon-dapp/src/dapp-client/DAppClient.ts @@ -341,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() @@ -439,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) @@ -497,7 +497,7 @@ export class DAppClient extends Client { } private async prepareRequest({ data }: any, isV3 = false) { - if (!this.multiTabChannel.isLeader()) { + if (!(await this.multiTabChannel.isLeader())) { return } @@ -794,7 +794,7 @@ export class DAppClient extends Client { this.postMessageTransport = this.walletConnectTransport = this.p2pTransport = - undefined + undefined this._activeAccount.isResolved() && this.clearActiveAccount() this._initPromise = undefined }, @@ -1064,7 +1064,7 @@ 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 || !isWCInstance || isLeader || isMobileOS(window) } @@ -1359,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) => { @@ -1573,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 @@ -2043,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) @@ -2328,7 +2328,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.type !== BeaconMessageType.PermissionRequest || (this._activeAccount.isResolved() && (await this._activeAccount.promise)) @@ -2445,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)) @@ -2492,9 +2492,9 @@ export class DAppClient extends Client { request: Optional ): Promise< | { - message: U - connectionInfo: ConnectionContext - } + message: U + connectionInfo: ConnectionContext + } | undefined > { if (!this._transport.isResolved()) { diff --git a/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts b/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts index e68a64b97..56b53e0d6 100644 --- a/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts +++ b/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts @@ -636,9 +636,9 @@ export class WalletConnectCommunicationClient extends CommunicationClient { this.notifyListeners(_pairingTopic, errorResponse) } }) - .then(() => { - if (!this.isMobileOS() && !this.isLeader()) { - this.clearState() + .then(async () => { + if (!this.isMobileOS() && !(await this.isLeader())) { + this.signClient = undefined } }) @@ -756,7 +756,7 @@ export class WalletConnectCommunicationClient extends CommunicationClient { 'session_update' ) } - } catch {} + } catch { } } private async disconnect( @@ -796,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 From 9dd518eba33fe137e79169afccc5d41d03e7d91d Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Tue, 8 Oct 2024 09:56:48 +0200 Subject: [PATCH 19/24] fix: test --- packages/beacon-core/src/utils/multi-tab-channel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/beacon-core/src/utils/multi-tab-channel.ts b/packages/beacon-core/src/utils/multi-tab-channel.ts index 45267f2e1..2bbebf271 100644 --- a/packages/beacon-core/src/utils/multi-tab-channel.ts +++ b/packages/beacon-core/src/utils/multi-tab-channel.ts @@ -18,9 +18,9 @@ type BCMessage = { } const logger = new Logger('MultiTabChannel') -const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( +const isMobile = typeof window === 'undefined' ? /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent -) +) : false export class MultiTabChannel { private id: string = String(Date.now()) From 04f01e289ce3b41b0b3ef36d45fb10aa1cc29db9 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Tue, 8 Oct 2024 10:04:46 +0200 Subject: [PATCH 20/24] fix: check --- packages/beacon-core/src/utils/multi-tab-channel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-core/src/utils/multi-tab-channel.ts b/packages/beacon-core/src/utils/multi-tab-channel.ts index 2bbebf271..f00b6c84b 100644 --- a/packages/beacon-core/src/utils/multi-tab-channel.ts +++ b/packages/beacon-core/src/utils/multi-tab-channel.ts @@ -18,7 +18,7 @@ type BCMessage = { } const logger = new Logger('MultiTabChannel') -const isMobile = typeof window === 'undefined' ? /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( +const isMobile = typeof window !== 'undefined' ? /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent ) : false From 7d86a6cf96c19364115e07f04f850b94a69d6324 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Tue, 8 Oct 2024 10:15:26 +0200 Subject: [PATCH 21/24] fix: test --- packages/beacon-core/src/utils/multi-tab-channel.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/beacon-core/src/utils/multi-tab-channel.ts b/packages/beacon-core/src/utils/multi-tab-channel.ts index f00b6c84b..726947b19 100644 --- a/packages/beacon-core/src/utils/multi-tab-channel.ts +++ b/packages/beacon-core/src/utils/multi-tab-channel.ts @@ -18,9 +18,6 @@ type BCMessage = { } const logger = new Logger('MultiTabChannel') -const isMobile = typeof window !== 'undefined' ? /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( - navigator.userAgent -) : false export class MultiTabChannel { private id: string = String(Date.now()) @@ -48,6 +45,10 @@ export class MultiTabChannel { } 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.') } From e04359ff088edb64736ba0b7d1329cc7147b452d Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Tue, 8 Oct 2024 10:21:57 +0200 Subject: [PATCH 22/24] fix: node-version --- .github/workflows/build.yml | 2 +- .github/workflows/deploy-docs.yml | 2 +- .github/workflows/dev-demo-deploy.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1f968e0cf..6d35f7c14 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: - name: Node 16 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..b38584c42 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -20,7 +20,7 @@ jobs: - name: Node 16 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..1562c4f6e 100644 --- a/.github/workflows/dev-demo-deploy.yml +++ b/.github/workflows/dev-demo-deploy.yml @@ -20,7 +20,7 @@ jobs: - name: Node 16 uses: actions/setup-node@v1 with: - node-version: 16.x + node-version: 18.x - name: Prepare run: npm ci From 53ada0d53bb36337ba04d5fb44a4b5af0b091e05 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Tue, 8 Oct 2024 10:22:56 +0200 Subject: [PATCH 23/24] fix: update name step --- .github/workflows/build.yml | 2 +- .github/workflows/deploy-docs.yml | 2 +- .github/workflows/dev-demo-deploy.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d35f7c14..aa9daa9ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ 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: 18.x diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index b38584c42..50f71733c 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -17,7 +17,7 @@ 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: 18.x diff --git a/.github/workflows/dev-demo-deploy.yml b/.github/workflows/dev-demo-deploy.yml index 1562c4f6e..7f036e991 100644 --- a/.github/workflows/dev-demo-deploy.yml +++ b/.github/workflows/dev-demo-deploy.yml @@ -17,7 +17,7 @@ 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: 18.x From a7a34d34d657787c40f1dc974ffbcb91764572e7 Mon Sep 17 00:00:00 2001 From: IsaccoSordo Date: Tue, 8 Oct 2024 10:28:16 +0200 Subject: [PATCH 24/24] fix: remove message type --- packages/beacon-core/src/utils/multi-tab-channel.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/beacon-core/src/utils/multi-tab-channel.ts b/packages/beacon-core/src/utils/multi-tab-channel.ts index 726947b19..4da41ff39 100644 --- a/packages/beacon-core/src/utils/multi-tab-channel.ts +++ b/packages/beacon-core/src/utils/multi-tab-channel.ts @@ -7,7 +7,6 @@ type BCMessageType = | 'LEADER_DEAD' | 'RESPONSE' | 'DISCONNECT' - | 'HIDE_UI' | BeaconMessageType type BCMessage = {