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

feat: active session management for stale / removed sessions #356

Merged
merged 12 commits into from
Dec 3, 2024
36 changes: 33 additions & 3 deletions src/lib/dapp/DAppSigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ import {
Uint8ArrayToBase64String,
Uint8ArrayToString,
} from '../shared'
import { DefaultLogger, ILogger } from '../shared/logger'
import { DefaultLogger, ILogger, LogLevel } from '../shared/logger'
import { SessionNotFoundError } from './SessionNotFoundError'

const clients: Record<string, Client | null> = {}

Expand All @@ -73,7 +74,7 @@ export class DAppSigner implements Signer {
public readonly topic: string,
private readonly ledgerId: LedgerId = LedgerId.MAINNET,
public readonly extensionId?: string,
logLevel: 'error' | 'warn' | 'info' | 'debug' = 'debug',
logLevel: LogLevel = 'debug',
) {
this.logger = new DefaultLogger(logLevel)
}
Expand All @@ -82,7 +83,7 @@ export class DAppSigner implements Signer {
* Sets the logging level for the DAppSigner
* @param level - The logging level to set
*/
public setLogLevel(level: 'error' | 'warn' | 'info' | 'debug'): void {
public setLogLevel(level: LogLevel): void {
if (this.logger instanceof DefaultLogger) {
this.logger.setLogLevel(level)
}
Expand Down Expand Up @@ -116,6 +117,14 @@ export class DAppSigner implements Signer {
}

request<T>(request: { method: string; params: any }): Promise<T> {
// Avoid a wallet call if the session is no longer valid
if (!this?.signClient?.session?.get(this.topic)) {
this.logger.error('Session no longer exists. Please reconnect to the wallet.')
throw new SessionNotFoundError(
'Session no longer exists. Please reconnect to the wallet.',
)
}

if (this.extensionId) extensionOpen(this.extensionId)
return this.signClient.request<T>({
topic: this.topic,
Expand Down Expand Up @@ -247,6 +256,11 @@ export class DAppSigner implements Signer {
error?: any
}> {
try {
// Verify session is still valid before proceeding
if (!this.signClient.session.get(this.topic)) {
throw new Error('Session no longer exists. Please reconnect to the wallet.')
}
kantorcodes marked this conversation as resolved.
Show resolved Hide resolved

const requestToBytes = request.toBytes()
this.logger.debug('Creating transaction from bytes', requestToBytes, request)

Expand All @@ -265,6 +279,20 @@ export class DAppSigner implements Signer {
return { result: TransactionResponse.fromJSON(result) as OutputT }
} catch (error) {
this.logger.error('Error executing transaction request:', error)

if (error instanceof SessionNotFoundError) {
this.logger.error('Session was deleted, removing signer')
// Notify DAppConnector to remove this signer
this.signClient.emit({
topic: this.topic,
event: {
name: 'session_delete',
data: { topic: this.topic },
},
chainId: ledgerIdToCAIPChainId(this.ledgerId),
})
}

return { error }
}
}
Expand Down Expand Up @@ -356,10 +384,12 @@ export class DAppSigner implements Signer {
query: queryToBase64String(query),
},
})

this.logger.debug('Query request completed successfully', result)

return { result: this._parseQueryResponse(query, result.response) as OutputT }
} catch (error) {
this.logger.error('Error executing query request:', error)
return { error }
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/lib/dapp/SessionNotFoundError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class SessionNotFoundError extends Error {
constructor(message: string) {
super(message)
this.name = 'SessionNotFoundError'
}
}
54 changes: 41 additions & 13 deletions src/lib/dapp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import QRCodeModal from '@walletconnect/qrcode-modal'
import { WalletConnectModal } from '@walletconnect/modal'
import SignClient from '@walletconnect/sign-client'
import { getSdkError } from '@walletconnect/utils'
import { DefaultLogger, ILogger } from '../shared/logger'
import { DefaultLogger, ILogger, LogLevel } from '../shared/logger'
import {
HederaJsonRpcMethod,
accountAndLedgerFromSession,
Expand Down Expand Up @@ -91,7 +91,7 @@ export class DAppConnector {
methods?: string[],
events?: string[],
chains?: string[],
logLevel: 'error' | 'warn' | 'info' | 'debug' = 'debug',
logLevel: LogLevel = 'debug',
) {
this.logger = new DefaultLogger(logLevel)
this.dAppMetadata = metadata
Expand Down Expand Up @@ -120,7 +120,7 @@ export class DAppConnector {
* Sets the logging level for the DAppConnector
* @param level - The logging level to set
*/
public setLogLevel(level: 'error' | 'warn' | 'info' | 'debug'): void {
public setLogLevel(level: LogLevel): void {
if (this.logger instanceof DefaultLogger) {
this.logger.setLogLevel(level)
}
Expand Down Expand Up @@ -151,6 +151,11 @@ export class DAppConnector {
this.walletConnectClient.on('session_event', this.handleSessionEvent.bind(this))
this.walletConnectClient.on('session_update', this.handleSessionUpdate.bind(this))
this.walletConnectClient.on('session_delete', this.handleSessionDelete.bind(this))
// Listen for custom session_delete events from DAppSigner
this.walletConnectClient.core.events.on(
'session_delete',
this.handleSessionDelete.bind(this),
)
this.walletConnectClient.core.pairing.events.on(
'pairing_delete',
this.handlePairingDelete.bind(this),
Expand Down Expand Up @@ -269,9 +274,10 @@ export class DAppConnector {
}

/**
* Validates the session by checking if the session exists.
* Validates the session by checking if the session exists and is valid.
* Also ensures the signer exists for the session.
* @param topic - The topic of the session to validate.
* @returns {boolean} - True if the session exists, false otherwise.
* @returns {boolean} - True if the session exists and has a valid signer, false otherwise.
*/
private validateSession(topic: string): boolean {
try {
Expand All @@ -280,12 +286,24 @@ export class DAppConnector {
}

const session = this.walletConnectClient.session.get(topic)

const hasSigner = this.signers.some((signer) => signer.topic === topic)
if (!session) {
// If session doesn't exist but we have a signer for it, clean up
if (hasSigner) {
this.logger.warn(`Signer exists but no session found for topic: ${topic}`)
this.handleSessionDelete({ topic })
}
return false
}

if (!hasSigner) {
this.logger.warn(`Session exists but no signer found for topic: ${topic}`)
return false
}

return true
} catch {
} catch (e) {
this.logger.error('Error validating session:', e)
return false
}
}
Expand Down Expand Up @@ -662,13 +680,23 @@ export class DAppConnector {

private handleSessionDelete(event: { topic: string }) {
this.logger.info('Session deleted:', event)
this.signers = this.signers.filter((signer) => signer.topic !== event.topic)
try {
this.disconnect(event.topic)
} catch (e) {
this.logger.error('Error disconnecting session:', e)
let deletedSigner: boolean = false
this.signers = this.signers.filter((signer) => {
if (signer.topic !== event.topic) {
return true
}
deletedSigner = true
return false
})
// prevent emitting disconnected event if signers is untouched.
if (deletedSigner) {
try {
this.disconnect(event.topic)
} catch (e) {
this.logger.error('Error disconnecting session:', e)
}
this.logger.info('Session deleted and signer removed')
}
this.logger.info('Session deleted by wallet')
}

private handlePairingDelete(event: { topic: string }) {
Expand Down
10 changes: 6 additions & 4 deletions src/lib/shared/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@ export interface ILogger {
debug(message: string, ...args: any[]): void
}

export type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'off'

export class DefaultLogger implements ILogger {
private logLevel: 'error' | 'warn' | 'info' | 'debug' = 'info'
private logLevel: LogLevel = 'info'

constructor(logLevel: 'error' | 'warn' | 'info' | 'debug' = 'info') {
constructor(logLevel: LogLevel = 'info') {
this.logLevel = logLevel
}

setLogLevel(level: 'error' | 'warn' | 'info' | 'debug'): void {
setLogLevel(level: LogLevel): void {
this.logLevel = level
}

getLogLevel(): 'error' | 'warn' | 'info' | 'debug' {
getLogLevel(): LogLevel {
return this.logLevel
}

Expand Down
Loading