Skip to content

Commit

Permalink
feat(pera): add auto-connect for Pera Discover browser (#323)
Browse files Browse the repository at this point in the history
* feat(pera): add auto-connect for Pera Discover browser

Add automatic connection attempt when Pera wallet is initialized in the Pera
Discover browser environment. The connection attempt is handled silently with
appropriate logging for success and failure cases.

* fix(pera): add window check to prevent SSR errors

Add type check for window and navigator objects to prevent errors during
server-side rendering. This ensures the Pera auto-connect feature only
runs in browser environments.

* refactor(pera): extract auto-connect logic into private method

Extract auto-connect logic from constructor into a private async method for better
error handling and testability. Update tests to use prototype mocking pattern
consistent with other wallet implementations.

- Move auto-connect logic into private `autoConnect` method
- Use async/await with try/catch for better error handling
- Update tests to mock method on prototype before wallet creation
- Align test patterns with other wallet implementations

* refactor(pera): move Pera auto-connect to resumeSession

Move auto-connect logic from constructor to `resumeSession` method in Pera wallet
implementations to fix SSR compatibility issues. Auto-connect now only triggers
when no other wallet is active and after client-side hydration.

* refactor(pera): remove unused autoConnect method

Remove private `autoConnect` method from Pera wallet implementations since the
functionality is now handled directly in resumeSession.
  • Loading branch information
drichar authored Dec 7, 2024
1 parent d1a9696 commit 4d04fb1
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 0 deletions.
73 changes: 73 additions & 0 deletions packages/use-wallet/src/__tests__/wallets/pera.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,79 @@ describe('PeraWallet', () => {
expect(store.state.wallets[WalletId.PERA]).toBeUndefined()
expect(wallet.isConnected).toBe(false)
})

describe('auto-connect in Pera browser', () => {
let mockUserAgent: string

beforeEach(() => {
mockUserAgent = ''
vi.clearAllMocks()

vi.stubGlobal('window', {
navigator: {
get userAgent() {
return mockUserAgent
}
}
})
})

afterEach(() => {
vi.unstubAllGlobals()
})

it('should attempt auto-connect in Pera browser when no session exists and no active wallet', async () => {
mockUserAgent = 'pera/1.0.0'
mockPeraWallet.connect.mockResolvedValueOnce([account1.address])

await wallet.resumeSession()

expect(mockPeraWallet.connect).toHaveBeenCalled()
expect(store.state.wallets[WalletId.PERA]).toBeDefined()
expect(mockLogger.info).toHaveBeenCalledWith('Auto-connect successful')
})

it('should not attempt auto-connect if another wallet is active', async () => {
mockUserAgent = 'pera/1.0.0'

// Set up another active wallet
store.setState((state) => ({
...state,
activeWallet: WalletId.DEFLY,
wallets: {
[WalletId.DEFLY]: {
accounts: [account2],
activeAccount: account2
}
}
}))

await wallet.resumeSession()

expect(mockPeraWallet.connect).not.toHaveBeenCalled()
expect(mockLogger.info).toHaveBeenCalledWith('No session to resume')
})

it('should not attempt auto-connect in other browsers', async () => {
mockUserAgent = 'chrome/1.0.0'

await wallet.resumeSession()

expect(mockPeraWallet.connect).not.toHaveBeenCalled()
expect(mockLogger.info).toHaveBeenCalledWith('No session to resume')
})

it('should handle auto-connect failure gracefully', async () => {
mockUserAgent = 'pera/1.0.0'
mockPeraWallet.connect.mockRejectedValueOnce(new Error('Connect failed'))

await wallet.resumeSession()

expect(mockPeraWallet.connect).toHaveBeenCalled()
expect(mockLogger.warn).toHaveBeenCalledWith('Auto-connect failed:', 'Connect failed')
expect(store.state.wallets[WalletId.PERA]).toBeUndefined()
})
})
})

describe('setActive', () => {
Expand Down
73 changes: 73 additions & 0 deletions packages/use-wallet/src/__tests__/wallets/pera2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,79 @@ describe('PeraWallet', () => {
expect(store.state.wallets[WalletId.PERA2]).toBeUndefined()
expect(wallet.isConnected).toBe(false)
})

describe('auto-connect in Pera browser', () => {
let mockUserAgent: string

beforeEach(() => {
mockUserAgent = ''
vi.clearAllMocks()

vi.stubGlobal('window', {
navigator: {
get userAgent() {
return mockUserAgent
}
}
})
})

afterEach(() => {
vi.unstubAllGlobals()
})

it('should attempt auto-connect in Pera browser when no session exists and no active wallet', async () => {
mockUserAgent = 'pera/1.0.0'
mockPeraWallet.connect.mockResolvedValueOnce([account1.address])

await wallet.resumeSession()

expect(mockPeraWallet.connect).toHaveBeenCalled()
expect(store.state.wallets[WalletId.PERA2]).toBeDefined()
expect(mockLogger.info).toHaveBeenCalledWith('Auto-connect successful')
})

it('should not attempt auto-connect if another wallet is active', async () => {
mockUserAgent = 'pera/1.0.0'

// Set up another active wallet
store.setState((state) => ({
...state,
activeWallet: WalletId.DEFLY,
wallets: {
[WalletId.DEFLY]: {
accounts: [account2],
activeAccount: account2
}
}
}))

await wallet.resumeSession()

expect(mockPeraWallet.connect).not.toHaveBeenCalled()
expect(mockLogger.info).toHaveBeenCalledWith('No session to resume')
})

it('should not attempt auto-connect in other browsers', async () => {
mockUserAgent = 'chrome/1.0.0'

await wallet.resumeSession()

expect(mockPeraWallet.connect).not.toHaveBeenCalled()
expect(mockLogger.info).toHaveBeenCalledWith('No session to resume')
})

it('should handle auto-connect failure gracefully', async () => {
mockUserAgent = 'pera/1.0.0'
mockPeraWallet.connect.mockRejectedValueOnce(new Error('Connect failed'))

await wallet.resumeSession()

expect(mockPeraWallet.connect).toHaveBeenCalled()
expect(mockLogger.warn).toHaveBeenCalledWith('Auto-connect failed:', 'Connect failed')
expect(store.state.wallets[WalletId.PERA2]).toBeUndefined()
})
})
})

describe('signing transactions', () => {
Expand Down
15 changes: 15 additions & 0 deletions packages/use-wallet/src/wallets/pera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,21 @@ export class PeraWallet extends BaseWallet {
const state = this.store.state
const walletState = state.wallets[this.id]

// Check for Pera Discover browser and auto-connect if no other wallet is active
if (typeof window !== 'undefined' && window.navigator) {
const isPeraDiscover = window.navigator.userAgent.includes('pera')
if (isPeraDiscover && !walletState && !state.activeWallet) {
this.logger.info('Pera Discover browser detected, attempting auto-connect...')
try {
await this.connect()
this.logger.info('Auto-connect successful')
return
} catch (error: any) {
this.logger.warn('Auto-connect failed:', error.message)
}
}
}

// No session to resume
if (!walletState) {
this.logger.info('No session to resume')
Expand Down
15 changes: 15 additions & 0 deletions packages/use-wallet/src/wallets/pera2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,21 @@ export class PeraWallet extends BaseWallet {
const state = this.store.state
const walletState = state.wallets[this.id]

// Check for Pera Discover browser and auto-connect if no other wallet is active
if (typeof window !== 'undefined' && window.navigator) {
const isPeraDiscover = window.navigator.userAgent.includes('pera')
if (isPeraDiscover && !walletState && !state.activeWallet) {
this.logger.info('Pera Discover browser detected, attempting auto-connect...')
try {
await this.connect()
this.logger.info('Auto-connect successful')
return
} catch (error: any) {
this.logger.warn('Auto-connect failed:', error.message)
}
}
}

// No session to resume
if (!walletState) {
this.logger.info('No session to resume')
Expand Down

0 comments on commit 4d04fb1

Please sign in to comment.