Skip to content
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
1 change: 0 additions & 1 deletion src/config/wagmi.config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const queryClient = new QueryClient({
refetchOnWindowFocus: true, // Refetch stale data when user returns
refetchOnReconnect: true, // Refetch when connectivity restored
// Allow queries when offline to read from TanStack Query in-memory cache
// Service Worker provides additional HTTP API response caching (user data, history, prices)
networkMode: 'always', // Run queries even when offline (reads from cache)
},
mutations: {
Expand Down
28 changes: 25 additions & 3 deletions src/hooks/useNativePlugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useEffect } from 'react'
import { useRouter } from 'next/navigation'
import { focusManager } from '@tanstack/react-query'
import { captureMessage } from '@sentry/nextjs'
import { isCapacitor, getPlatform } from '@/utils/capacitor'
import { deepLinkToNativePath } from '@/utils/native-routes'
Expand All @@ -23,6 +24,13 @@ export function useNativePlugins() {
if (!isCapacitor()) return

const cleanups: Array<() => void> = []
let disposed = false
// Registrations resolve async; if the effect tore down first, run the
// cleanup now instead of leaking the handle.
const track = (cleanup: () => void) => {
if (disposed) cleanup()
else cleanups.push(cleanup)
}

const openDeepLink = (url?: string | null) => {
if (!url) return
Expand All @@ -36,20 +44,33 @@ export function useNativePlugins() {
const init = async () => {
try {
const { App } = await import('@capacitor/app')

// TanStack Query's refetchOnWindowFocus keys off visibilitychange,
// which Android WebViews don't reliably fire on app resume — a
// resumed app kept rendering its pre-background query data (stale
// home Activity). Drive the focusManager from the native lifecycle.
const stateListener = await App.addListener('appStateChange', ({ isActive }: { isActive: boolean }) =>
focusManager.setFocused(isActive)
)
track(() => {
stateListener.remove()
focusManager.setFocused(undefined)
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const backListener = await App.addListener('backButton', ({ canGoBack }: { canGoBack: boolean }) => {
if (canGoBack) {
router.back()
} else {
App.minimizeApp()
}
})
cleanups.push(() => backListener.remove())
track(() => backListener.remove())

// App Links: cold start (getLaunchUrl) + warm start (appUrlOpen).
const launch = await App.getLaunchUrl()
openDeepLink(launch?.url)
const urlListener = await App.addListener('appUrlOpen', ({ url }: { url: string }) => openDeepLink(url))
cleanups.push(() => urlListener.remove())
track(() => urlListener.remove())
} catch (e) {
console.warn('failed to init app listeners:', e)
// without these listeners push-tap deep links never route, so surface the failure
Expand All @@ -70,7 +91,7 @@ export function useNativePlugins() {
// relative path the API sends; the launch URL is the fallback for
// notifications sent before that field existed.
const adapter = await getOneSignalAdapter()
cleanups.push(
track(
adapter.onNotificationClick(({ deepLink, additionalData }) => {
const target = additionalData.deepLink
const link = typeof target === 'string' ? target : deepLink
Expand Down Expand Up @@ -123,6 +144,7 @@ export function useNativePlugins() {
init()

return () => {
disposed = true
cleanups.forEach((fn) => fn())
}
}, [router])
Expand Down
5 changes: 4 additions & 1 deletion src/hooks/useTransactionHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ export function useTransactionHistory({
// append targetUsername to the query params if filterMutualTxs is true and username is provided
if (filterMutualTxs && username) queryParams.append('targetUsername', username)

// no-store: home Activity must never render a cached copy of history
// (server also sends Cache-Control: no-store; this covers the WebView path)
const response = await serverFetch(`/users/history?${queryParams.toString()}`, {
method: 'GET',
cache: 'no-store',
})

if (!response.ok) {
Expand All @@ -94,7 +97,7 @@ export function useTransactionHistory({
// that bites if a caller ever flips `mode` mid-life.

// Latest transactions (home page).
// Two-tier caching: TQ in-memory (30s) → SW disk cache (1 week) → Network.
// Cached only in TQ memory (30s stale); the HTTP response is no-store end to end.
const latestQuery = useQuery({
queryKey: [TRANSACTIONS, 'latest', { limit, targetUsername: filterMutualTxs ? username : undefined }],
queryFn: () => fetchHistory({ limit }),
Expand Down
Loading