Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: account deactivated #843

Merged
merged 5 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export abstract class BeaconClient {
/**
* This method initializes the SDK by setting some values in the storage and generating a keypair.
*/
private async initSDK(): Promise<void> {
protected async initSDK(): Promise<void> {
this.storage.set(StorageKey.BEACON_SDK_VERSION, SDK_VERSION).catch(console.error)

this.loadOrCreateBeaconSecret().catch(console.error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ClientEvents } from './ClientEvents'
*
*/
export abstract class CommunicationClient {
constructor(protected readonly keyPair?: KeyPair) {}
constructor(protected keyPair?: KeyPair) {}

public eventHandlers: Map<ClientEvents, Function> = new Map()

Expand Down
73 changes: 49 additions & 24 deletions packages/beacon-dapp/src/dapp-client/DAppClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ import {
signMessage,
CONTRACT_PREFIX,
prefixPublicKey,
isValidAddress
isValidAddress,
getKeypairFromSeed
} from '@airgap/beacon-utils'
import { messageEvents } from '../beacon-message-events'
import { BlockExplorer } from '../utils/block-explorer'
Expand Down Expand Up @@ -264,7 +265,9 @@ export class DAppClient extends Client {
this.storage.subscribeToStorageChanged(async (event) => {
if (event.eventType === 'storageCleared') {
this.setActiveAccount(undefined)
} else if (event.eventType === 'entryModified') {
return
}
if (event.eventType === 'entryModified') {
if (event.key === this.storage.getPrefixedKey(StorageKey.ACTIVE_ACCOUNT)) {
const accountIdentifier = event.newValue
if (!accountIdentifier || accountIdentifier === 'undefined') {
Expand All @@ -273,8 +276,17 @@ export class DAppClient extends Client {
const account = await this.getAccount(accountIdentifier)
this.setActiveAccount(account)
}
} else if (event.key === this.storage.getPrefixedKey(StorageKey.ENABLE_METRICS)) {
return
}
if (event.key === this.storage.getPrefixedKey(StorageKey.ENABLE_METRICS)) {
this.enableMetrics = !!(await this.storage.get(StorageKey.ENABLE_METRICS))
return
}
if (event.key === this.storage.getPrefixedKey(StorageKey.BEACON_SDK_SECRET_SEED)) {
this._keyPair = new ExposedPromise()
this._beaconId = new ExposedPromise()
await this.initSDK()
return
}
}
})
Expand All @@ -292,8 +304,9 @@ export class DAppClient extends Client {
}
})
.catch(async (storageError) => {
await this.setActiveAccount(undefined)
logger.error(storageError)
await this.resetInvalidState(false)
this.events.emit(BeaconEvent.INVALID_ACCOUNT_DEACTIVATED)
return undefined
})

Expand Down Expand Up @@ -555,7 +568,11 @@ export class DAppClient extends Client {
}

public async initInternalTransports(): Promise<void> {
const keyPair = await this.keyPair
const seed = await this.storage.get(StorageKey.BEACON_SDK_SECRET_SEED)
if (!seed) {
throw new Error('Secret seed not found')
}
const keyPair = await getKeypairFromSeed(seed)

if (this.postMessageTransport || this.p2pTransport || this.walletConnectTransport) {
return
Expand Down Expand Up @@ -785,33 +802,41 @@ export class DAppClient extends Client {
console.error(error)
})

const abortHandler = async () => {
logger.log('init', 'ABORTED')
this.sendMetrics(
'performance-metrics/save',
await this.buildPayload('connect', 'abort')
)
await Promise.all([
postMessageTransport.disconnect(),
// p2pTransport.disconnect(), do not abort connection manually
walletConnectTransport.disconnect()
])
this.postMessageTransport = this.walletConnectTransport = this.p2pTransport = undefined
this._activeAccount.isResolved() && this.clearActiveAccount()
this._initPromise = undefined
}

this.events
.emit(BeaconEvent.PAIR_INIT, {
p2pPeerInfo: () => {
p2pTransport.connect().then().catch(console.error)
p2pTransport
.connect()
.then()
.catch((err) => {
logger.error(err)
if (err.message === 'The account is deactivated.') {
this.hideUI(['alert'])
abortHandler()
}
})
return p2pTransport.getPairingRequestInfo()
},
postmessagePeerInfo: () => postMessageTransport.getPairingRequestInfo(),
walletConnectPeerInfo: () => walletConnectTransport.getPairingRequestInfo(),
networkType: this.network.type,
abortedHandler: async () => {
logger.log('init', 'ABORTED')
this.sendMetrics(
'performance-metrics/save',
await this.buildPayload('connect', 'abort')
)
await Promise.all([
postMessageTransport.disconnect(),
// p2pTransport.disconnect(), do not abort connection manually
walletConnectTransport.disconnect()
])
this.postMessageTransport =
this.walletConnectTransport =
this.p2pTransport =
undefined
this._activeAccount.isResolved() && this.clearActiveAccount()
this._initPromise = undefined
},
abortedHandler: abortHandler.bind(this),
disclaimerText: this.disclaimerText,
analytics: this.analytics,
featuredWallets: this.featuredWallets
Expand Down
20 changes: 18 additions & 2 deletions packages/beacon-dapp/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export enum BeaconEvent {
SHOW_PREPARE = 'SHOW_PREPARE',
HIDE_UI = 'HIDE_UI',
INVALID_ACTIVE_ACCOUNT_STATE = 'INVALID_ACTIVE_ACCOUNT_STATE',
INVALID_ACCOUNT_DEACTIVATED = 'INVALID_ACCOUNT_DEACTIVATED',
PAIR_INIT = 'PAIR_INIT',
PAIR_SUCCESS = 'PAIR_SUCCESS',
CHANNEL_CLOSED = 'CHANNEL_CLOSED',
Expand Down Expand Up @@ -195,7 +196,8 @@ export interface BeaconEventType {
[BeaconEvent.NO_PERMISSIONS]: undefined
[BeaconEvent.ACTIVE_ACCOUNT_SET]: AccountInfo
[BeaconEvent.ACTIVE_TRANSPORT_SET]: Transport
[BeaconEvent.INVALID_ACTIVE_ACCOUNT_STATE]: undefined
[BeaconEvent.INVALID_ACTIVE_ACCOUNT_STATE]: undefined
[BeaconEvent.INVALID_ACCOUNT_DEACTIVATED]: undefined
[BeaconEvent.SHOW_PREPARE]: { walletInfo?: WalletInfo }
[BeaconEvent.HIDE_UI]: ('alert' | 'toast')[] | undefined
[BeaconEvent.PAIR_INIT]: {
Expand Down Expand Up @@ -314,7 +316,7 @@ const showNoPermissionAlert = async (): Promise<void> => {
}

/**
* Show a
* Show an "Invalid state" alert
*/
const showInvalidActiveAccountState = async (): Promise<void> => {
await openAlert({
Expand All @@ -324,6 +326,16 @@ const showInvalidActiveAccountState = async (): Promise<void> => {
})
}

/**
* Show an "account deactivated" error alert
*/
const showInvalidAccountDeactivated = async (): Promise<void> => {
await openAlert({
title: 'Error',
body: `Your session has expired. Please pair with your wallet again.`
})
}

/**
* Show an error toast
*
Expand Down Expand Up @@ -756,6 +768,7 @@ export const defaultEventCallbacks: {
[BeaconEvent.ACTIVE_ACCOUNT_SET]: emptyHandler(),
[BeaconEvent.ACTIVE_TRANSPORT_SET]: emptyHandler(),
[BeaconEvent.INVALID_ACTIVE_ACCOUNT_STATE]: showInvalidActiveAccountState,
[BeaconEvent.INVALID_ACCOUNT_DEACTIVATED]: showInvalidAccountDeactivated,
[BeaconEvent.SHOW_PREPARE]: showPrepare,
[BeaconEvent.HIDE_UI]: hideUI,
[BeaconEvent.PAIR_INIT]: showPairAlert,
Expand Down Expand Up @@ -816,6 +829,9 @@ export class BeaconEventHandler {
[BeaconEvent.INVALID_ACTIVE_ACCOUNT_STATE]: [
defaultEventCallbacks.INVALID_ACTIVE_ACCOUNT_STATE
],
[BeaconEvent.INVALID_ACCOUNT_DEACTIVATED]: [
defaultEventCallbacks.INVALID_ACCOUNT_DEACTIVATED
],
[BeaconEvent.SHOW_PREPARE]: [defaultEventCallbacks.SHOW_PREPARE],
[BeaconEvent.HIDE_UI]: [defaultEventCallbacks.HIDE_UI],
[BeaconEvent.PAIR_INIT]: [defaultEventCallbacks.PAIR_INIT],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
encryptCryptoboxPayload,
decryptCryptoboxPayload,
secretbox_NONCEBYTES,
secretbox_MACBYTES
secretbox_MACBYTES,
getKeypairFromSeed
} from '@airgap/beacon-utils'
import { MatrixClient } from '../matrix-client/MatrixClient'
import {
Expand Down Expand Up @@ -387,8 +388,15 @@ export class P2PCommunicationClient extends CommunicationClient {
password: `ed:${toHex(rawSignature)}:${await this.getPublicKey()}`,
deviceId: toHex(this.keyPair!.publicKey)
})
} catch (error) {
logger.error('start', 'Could not log in, retrying')
} catch (error: any) {
logger.error('start', 'Could not log in, retrying', error)

if (error.errcode === 'M_USER_DEACTIVATED') {
await this.generateNewKeyPair()
await this.reset()
throw new Error('The account is deactivated.')
}

await this.reset() // If we can't log in, let's reset
if (!this.selectedRegion) {
throw new Error('No region selected.')
Expand Down Expand Up @@ -426,6 +434,7 @@ export class P2PCommunicationClient extends CommunicationClient {
await this.storage.delete(StorageKey.MATRIX_PEER_ROOM_IDS).catch((error) => logger.log(error))
await this.storage.delete(StorageKey.MATRIX_PRESERVED_STATE).catch((error) => logger.log(error))
await this.storage.delete(StorageKey.MATRIX_SELECTED_NODE).catch((error) => logger.log(error))

// Instead of resetting everything, maybe we should make sure a new instance is created?
this.relayServer = undefined
this.client = new ExposedPromise()
Expand Down Expand Up @@ -779,6 +788,16 @@ export class P2PCommunicationClient extends CommunicationClient {
)
}

private async generateNewKeyPair() {
const newSeed = await generateGUID()

console.warn(`The current user ID has been deactivated. Generating new ID: ${newSeed}`)

this.storage.set(StorageKey.BEACON_SDK_SECRET_SEED, newSeed)

this.keyPair = await getKeypairFromSeed(newSeed)
}

private async getRelevantRoom(recipient: string): Promise<string> {
const roomIds = await this.storage.get(StorageKey.MATRIX_PEER_ROOM_IDS)
let roomId = roomIds[recipient]
Expand Down
40 changes: 25 additions & 15 deletions packages/beacon-wallet/src/client/WalletClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,24 +235,34 @@ export class WalletClient extends Client {
/**
* The method will attempt to initiate a connection using the active transport.
*/
public async _connect(): Promise<void> {
public async _connect(attempts: number = 3): Promise<void> {
const transport: WalletP2PTransport = (await this.transport) as WalletP2PTransport
if (transport.connectionStatus === TransportStatus.NOT_CONNECTED) {
if (attempts == 0 || transport.connectionStatus !== TransportStatus.NOT_CONNECTED) {
return
}

try {
await transport.connect()
transport
.addListener(async (message: unknown, connectionInfo: ConnectionContext) => {
if (typeof message === 'string') {
const deserializedMessage = (await new Serializer().deserialize(
message
)) as BeaconRequestMessage
this.handleResponse(deserializedMessage, connectionInfo)
}
})
.catch((error) => logger.log('_connect', error))
this._isConnected.resolve(true)
} else {
// NO-OP
} catch (err: any) {
logger.warn('_connect', err.message)
if (err.message === 'The account is deactivated.') {
await transport.disconnect()
await this._connect(--attempts)
return
}
}

transport
.addListener(async (message: unknown, connectionInfo: ConnectionContext) => {
if (typeof message === 'string') {
const deserializedMessage = (await new Serializer().deserialize(
message
)) as BeaconRequestMessage
this.handleResponse(deserializedMessage, connectionInfo)
}
})
.catch((error) => logger.log('_connect', error))
this._isConnected.resolve(true)
}

/**
Expand Down
Loading