From cf0e946e276afced868f5a88e90f0a60f250ad17 Mon Sep 17 00:00:00 2001 From: 0age <37939117+0age@users.noreply.github.com> Date: Sat, 7 Dec 2024 07:11:54 -0800 Subject: [PATCH] session check on page load --- frontend/src/hooks/useSessionPoller.ts | 57 +++++++++- frontend/src/hooks/useWithdrawalStatus.ts | 125 ++++++++++++++++++++++ 2 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 frontend/src/hooks/useWithdrawalStatus.ts diff --git a/frontend/src/hooks/useSessionPoller.ts b/frontend/src/hooks/useSessionPoller.ts index d1099c7..7d81329 100644 --- a/frontend/src/hooks/useSessionPoller.ts +++ b/frontend/src/hooks/useSessionPoller.ts @@ -32,8 +32,53 @@ export function useSessionPoller( return; } - // Set initial session - onSessionUpdate(sessionId); + // Immediately validate session on mount + const validateSession = async () => { + try { + const response = await fetch('/session', { + headers: { + 'x-session-id': sessionId, + }, + }); + + // Handle 401 Unauthorized explicitly + if (response.status === 401) { + localStorage.removeItem(`session-${address}`); + onSessionUpdate(null); + return; + } + + const data: SessionResponse = await response.json(); + + // Check for invalid session error + if (data.error === 'Invalid session' || !response.ok) { + localStorage.removeItem(`session-${address}`); + onSessionUpdate(null); + return; + } + + // Verify session belongs to current address + if (data.session?.address.toLowerCase() !== address.toLowerCase()) { + throw new Error('Session address mismatch'); + } + + // Check if session has expired + const expiryTime = new Date(data.session.expiresAt).getTime(); + if (expiryTime < Date.now()) { + throw new Error('Session expired'); + } + + // Session is valid, set it + onSessionUpdate(sessionId); + } catch (error) { + // On any error, clear the session + localStorage.removeItem(`session-${address}`); + onSessionUpdate(null); + } + }; + + // Run initial validation + validateSession(); // Start polling const intervalId = setInterval(async () => { @@ -44,11 +89,17 @@ export function useSessionPoller( }, }); + // Handle 401 Unauthorized explicitly + if (response.status === 401) { + localStorage.removeItem(`session-${address}`); + onSessionUpdate(null); + return; + } + const data: SessionResponse = await response.json(); // Check for invalid session error if (data.error === 'Invalid session' || !response.ok) { - // Clear the session and update state localStorage.removeItem(`session-${address}`); onSessionUpdate(null); return; diff --git a/frontend/src/hooks/useWithdrawalStatus.ts b/frontend/src/hooks/useWithdrawalStatus.ts new file mode 100644 index 0000000..382d736 --- /dev/null +++ b/frontend/src/hooks/useWithdrawalStatus.ts @@ -0,0 +1,125 @@ +import { useState, useEffect, useCallback } from 'react'; + +interface Balance { + chainId: string; + lockId: string; + withdrawalStatus: number; + withdrawableAt: string; +} + +interface WithdrawalStatus { + canExecute: boolean; + timeRemaining: string | null; + status: 'active' | 'ready' | 'pending'; +} + +type WithdrawalStatuses = Record; + +// Helper to create a unique key for each balance +function getStatusKey(chainId: string, lockId: string): string { + return `${chainId}-${lockId}`; +} + +// Format time remaining helper +function formatTimeRemaining(expiryTimestamp: number): string { + const now = Math.floor(Date.now() / 1000); + const diff = expiryTimestamp - now; + + if (diff <= 0) return 'Ready'; + + const days = Math.floor(diff / (24 * 60 * 60)); + const hours = Math.floor((diff % (24 * 60 * 60)) / (60 * 60)); + const minutes = Math.floor((diff % (60 * 60)) / 60); + const seconds = diff % 60; + + if (days > 0) return `${days}d ${hours}h ${minutes}m`; + if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`; + if (minutes > 0) return `${minutes}m ${seconds}s`; + return `${seconds}s`; +} + +export function useWithdrawalStatus(balances: Balance[]): WithdrawalStatuses { + const [statuses, setStatuses] = useState({}); + + // Update all statuses + const updateStatuses = useCallback(() => { + const now = Math.floor(Date.now() / 1000); + const newStatuses: WithdrawalStatuses = {}; + + balances.forEach((balance) => { + const statusKey = getStatusKey(balance.chainId, balance.lockId); + + console.log( + `[Chain ${balance.chainId}, Lock ${balance.lockId}] Status Check:`, + { + withdrawalStatus: balance.withdrawalStatus, + withdrawableAt: balance.withdrawableAt, + now, + } + ); + + if (balance.withdrawalStatus === 0) { + console.log(`[${statusKey}] -> ACTIVE (status=0)`); + newStatuses[statusKey] = { + canExecute: false, + timeRemaining: null, + status: 'active', + }; + } else if (balance.withdrawalStatus === 1) { + // If withdrawableAt is undefined or invalid, treat it as pending with default time + const timestamp = balance.withdrawableAt + ? parseInt(balance.withdrawableAt) + : now + 600; // default to 10 minutes if not set + + if (timestamp <= now) { + console.log(`[${statusKey}] -> READY (status=1, time elapsed)`); + newStatuses[statusKey] = { + canExecute: true, + timeRemaining: 'Ready', + status: 'ready', + }; + } else { + const remaining = formatTimeRemaining(timestamp); + console.log( + `[${statusKey}] -> PENDING (status=1, ${remaining} remaining)` + ); + newStatuses[statusKey] = { + canExecute: false, + timeRemaining: remaining, + status: 'pending', + }; + } + } else { + console.log(`[${statusKey}] -> DEFAULT to ACTIVE (unexpected state)`); + newStatuses[statusKey] = { + canExecute: false, + timeRemaining: null, + status: 'active', + }; + } + }); + + console.log('Final Statuses:', newStatuses); + setStatuses(newStatuses); + }, [balances]); + + // Update status every second if there are any pending withdrawals + useEffect(() => { + const hasPendingWithdrawals = balances.some( + (balance) => + balance.withdrawalStatus === 1 && + parseInt(balance.withdrawableAt || '0') > Math.floor(Date.now() / 1000) + ); + + updateStatuses(); + + if (!hasPendingWithdrawals) { + return; + } + + const timer = setInterval(updateStatuses, 1000); + return () => clearInterval(timer); + }, [balances, updateStatuses]); + + return statuses; +}