Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/hooks/useAccountSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export const useAccountSetup = () => {
setError('Error adding account. Please try refreshing the page.')

// clear auth state if account creation fails
clearAuthState(user?.user.userId)
await clearAuthState(user?.user.userId)
return false
} finally {
setIsProcessing(false)
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useZeroDev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ export const useZeroDev = () => {
// Cancel saved no state; everything else clears stale state and reports the error to Sentry.
if (code !== 'LOGIN_CANCELED') {
console.error('Error logging in', err)
clearAuthState(user?.user.userId)
await clearAuthState(user?.user.userId)
captureException(err, { tags: { error_type: 'login_error' } })
} else if (isCapacitor()) {
// the native plugin maps ceremony failures (.failed/.notHandled) to the
Expand Down
10 changes: 10 additions & 0 deletions src/utils/__tests__/demo-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ describe('demoRespond — routing', () => {
})
})

describe('demoRespond — rain card overview', () => {
it('returns a full RainCardOverview shape (useRainCardOverview derefs .status, cardState derefs .cards)', async () => {
const { res, data } = await body('/rain/cards')
expect(res.status).toBe(200)
expect(data.status.hasApplication).toBe(false)
expect(data.balance).toBeNull()
expect(Array.isArray(data.cards)).toBe(true)
})
})

describe('demoRespond — shape-aware fallback (never throws on undefined.map)', () => {
it('returns [] for an unmatched collection-ish path', async () => {
const { res, data } = await body('/something/payments')
Expand Down
5 changes: 5 additions & 0 deletions src/utils/__tests__/webauthn.utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ describe('classifyPasskeyError', () => {
expect(classifyPasskeyError(err).code).toBe('LOGIN_CANCELED')
})

test('maps WebKit "Load failed" fetch errors to NETWORK', () => {
const err = Object.assign(new TypeError('Load failed'), { name: 'TypeError' })
expect(classifyPasskeyError(err).code).toBe('NETWORK')
})

test('falls back to LOGIN_ERROR for unknown errors', () => {
expect(classifyPasskeyError(new Error('mystery')).code).toBe('LOGIN_ERROR')
})
Expand Down
10 changes: 5 additions & 5 deletions src/utils/auth.utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { removeFromCookie, updateUserPreferences } from './general.utils'
import { clearAuthToken } from './auth-token'
import * as Sentry from '@sentry/nextjs'

/**
Expand All @@ -10,7 +11,7 @@ import * as Sentry from '@sentry/nextjs'
*
* @param userId - Optional user ID for clearing user-scoped preferences
*/
export const clearAuthState = (userId?: string) => {
export const clearAuthState = async (userId?: string) => {
try {
// Clear user preferences if userId available
if (userId) {
Expand All @@ -20,10 +21,9 @@ export const clearAuthState = (userId?: string) => {
// Clear cookies (always do this, even if no userId)
removeFromCookie('web-authn-key')

// Clear JWT cookie
if (typeof document !== 'undefined') {
document.cookie = 'jwt-token=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
}
// Clear the JWT everywhere it can live — on native that includes the
// plugin-managed stores, not just the cookie this used to expire by hand.
await clearAuthToken()

console.log('Cleared auth state', { userId: userId || 'none' })
} catch (error) {
Expand Down
8 changes: 8 additions & 0 deletions src/utils/demo-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,14 @@ const ROUTES: Array<{ method: string; pattern: string; handler: Handler }> = [
tokenSymbol: PEANUT_WALLET_TOKEN_SYMBOL,
}),
},
// useRainCardOverview polls this for every logged-in user; the fallback {}
// has no `status`/`cards` and crashes consumers that deref them
// (PEANUT-UI-RM6). hasApplication:false also stops polling in demo.
{
method: 'GET',
pattern: '/rain/cards',
handler: () => ({ status: { hasApplication: false }, balance: null, cards: [] }),
},

// rhino (crypto deposit / cross-chain) — return a believable deposit address
// (the demo wallet) so the "add money → crypto → choose network" screen renders
Expand Down
4 changes: 3 additions & 1 deletion src/utils/webauthn.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ const PASSKEY_LOGIN_MESSAGES: Record<string, string> = {

function isNetworkError(error: Error): boolean {
if (error.name === 'TypeError' && /fetch|network/i.test(error.message)) return true
return /failed to fetch|network ?error|timeout|connection|offline/i.test(error.message)
// "Load failed" is WebKit's message for a failed fetch (common when the
// request fires right as the app foregrounds from the passkey sheet)
return /failed to fetch|load failed|network ?error|timeout|connection|offline/i.test(error.message)
}

/**
Expand Down
Loading