From 0fd943e0dde0a62637e4c6d1715bb0f40eb78e47 Mon Sep 17 00:00:00 2001 From: SoSweetHam Date: Wed, 21 Jan 2026 10:55:47 +0530 Subject: [PATCH] fix: auth refresh --- .../eid-wallet/src-tauri/tauri.conf.json | 2 +- .../src/controllers/AuthController.ts | 11 ++ .../src/components/login/login-main.tsx | 51 ++++++-- .../src/controllers/AuthController.ts | 11 ++ .../src/routes/(auth)/auth/+page.svelte | 122 +++++++++++------- 5 files changed, 140 insertions(+), 57 deletions(-) diff --git a/infrastructure/eid-wallet/src-tauri/tauri.conf.json b/infrastructure/eid-wallet/src-tauri/tauri.conf.json index 49757042..8431a83e 100644 --- a/infrastructure/eid-wallet/src-tauri/tauri.conf.json +++ b/infrastructure/eid-wallet/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "eID for W3DS", "version": "0.5.0", - "identifier": "com.kodski.eid-wallet", + "identifier": "foundation.metastate.eid-wallet", "build": { "beforeDevCommand": "pnpm dev", "devUrl": "http://localhost:1420", diff --git a/platforms/blabsy-w3ds-auth-api/src/controllers/AuthController.ts b/platforms/blabsy-w3ds-auth-api/src/controllers/AuthController.ts index f72a1097..1b3efa89 100644 --- a/platforms/blabsy-w3ds-auth-api/src/controllers/AuthController.ts +++ b/platforms/blabsy-w3ds-auth-api/src/controllers/AuthController.ts @@ -31,14 +31,25 @@ export class AuthController { this.eventEmitter.on(id, handler); + // Send heartbeat every 30 seconds to keep connection alive + const heartbeatInterval = setInterval(() => { + try { + res.write(`: heartbeat\n\n`); + } catch (error) { + clearInterval(heartbeatInterval); + } + }, 30000); + // Handle client disconnect req.on("close", () => { + clearInterval(heartbeatInterval); this.eventEmitter.off(id, handler); res.end(); }); req.on("error", (error) => { console.error("SSE Error:", error); + clearInterval(heartbeatInterval); this.eventEmitter.off(id, handler); res.end(); }); diff --git a/platforms/blabsy/src/components/login/login-main.tsx b/platforms/blabsy/src/components/login/login-main.tsx index 69b95e83..06e03d40 100644 --- a/platforms/blabsy/src/components/login/login-main.tsx +++ b/platforms/blabsy/src/components/login/login-main.tsx @@ -1,6 +1,6 @@ import QRCode from 'react-qr-code'; import axios from 'axios'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef, useCallback } from 'react'; import { useAuth } from '@lib/context/auth-context'; import { NextImage } from '@components/ui/next-image'; import Image from 'next/image'; @@ -10,8 +10,10 @@ export function LoginMain(): JSX.Element { const { signInWithCustomToken } = useAuth(); const [qr, setQr] = useState(); const [errorMessage, setErrorMessage] = useState(null); + const eventSourceRef = useRef(null); + const refreshTimerRef = useRef(null); - function watchEventStream(id: string): void { + const watchEventStream = useCallback((id: string): EventSource => { const sseUrl = new URL( `/api/auth/sessions/${id}`, process.env.NEXT_PUBLIC_BASE_URL @@ -49,9 +51,20 @@ export function LoginMain(): JSX.Element { console.error('SSE connection error'); eventSource.close(); }; - } - const getOfferData = async (): Promise => { + return eventSource; + }, [signInWithCustomToken]); + + const getOfferData = useCallback(async (): Promise => { + // Clean up existing SSE connection + if (eventSourceRef.current) { + eventSourceRef.current.close(); + } + // Clean up existing refresh timer + if (refreshTimerRef.current) { + clearTimeout(refreshTimerRef.current); + } + const { data } = await axios.get<{ uri: string }>( new URL( '/api/auth/offer', @@ -59,10 +72,18 @@ export function LoginMain(): JSX.Element { ).toString() ); setQr(data.uri); - watchEventStream( + eventSourceRef.current = watchEventStream( new URL(data.uri).searchParams.get('session') as string ); - }; + + // Set up auto-refresh after 60 seconds + refreshTimerRef.current = setTimeout(() => { + console.log('Refreshing QR code after 60 seconds'); + getOfferData().catch((error) => + console.error('Error refreshing QR code:', error) + ); + }, 60000); + }, [watchEventStream]); const getAppStoreLink = () => { if (typeof window === 'undefined' || typeof navigator === 'undefined') { @@ -106,7 +127,17 @@ export function LoginMain(): JSX.Element { getOfferData().catch((error) => console.error('Error fetching QR code data:', error) ); - }, []); + + // Cleanup on unmount + return () => { + if (eventSourceRef.current) { + eventSourceRef.current.close(); + } + if (refreshTimerRef.current) { + clearTimeout(refreshTimerRef.current); + } + }; + }, [getOfferData]); const handleAutoLogin = async ( ename: string, @@ -209,8 +240,7 @@ export function LoginMain(): JSX.Element { The button is valid for 60 seconds - Please refresh the page if it - expires + It will refresh automatically

@@ -237,8 +267,7 @@ export function LoginMain(): JSX.Element { The code is valid for 60 seconds - Please refresh the page if it - expires + It will refresh automatically

diff --git a/platforms/pictique-api/src/controllers/AuthController.ts b/platforms/pictique-api/src/controllers/AuthController.ts index 5677d589..8ba8712a 100644 --- a/platforms/pictique-api/src/controllers/AuthController.ts +++ b/platforms/pictique-api/src/controllers/AuthController.ts @@ -34,14 +34,25 @@ export class AuthController { this.eventEmitter.on(id, handler); + // Send heartbeat every 30 seconds to keep connection alive + const heartbeatInterval = setInterval(() => { + try { + res.write(`: heartbeat\n\n`); + } catch (error) { + clearInterval(heartbeatInterval); + } + }, 30000); + // Handle client disconnect req.on("close", () => { + clearInterval(heartbeatInterval); this.eventEmitter.off(id, handler); res.end(); }); req.on("error", (error) => { console.error("SSE Error:", error); + clearInterval(heartbeatInterval); this.eventEmitter.off(id, handler); res.end(); }); diff --git a/platforms/pictique/src/routes/(auth)/auth/+page.svelte b/platforms/pictique/src/routes/(auth)/auth/+page.svelte index 0b247b8b..382c91be 100644 --- a/platforms/pictique/src/routes/(auth)/auth/+page.svelte +++ b/platforms/pictique/src/routes/(auth)/auth/+page.svelte @@ -16,6 +16,8 @@ let qrData = $state(''); let isMobile = $state(false); let errorMessage = $state(null); + let eventSource: EventSource | null = null; + let refreshTimer: ReturnType | null = null; function checkMobile() { isMobile = window.innerWidth <= 640; // Tailwind's `sm` breakpoint @@ -69,6 +71,71 @@ } } + function watchEventStream(id: string) { + const sseUrl = new URL(`/api/auth/sessions/${id}`, PUBLIC_PICTIQUE_BASE_URL).toString(); + const newEventSource = new EventSource(sseUrl); + + newEventSource.onopen = () => { + console.log('Successfully connected.'); + errorMessage = null; + }; + + newEventSource.onmessage = (e) => { + const data = JSON.parse(e.data as string); + + // Check for error messages (version mismatch) + if (data.error && data.type === 'version_mismatch') { + errorMessage = + data.message || + 'Your eID Wallet app version is outdated. Please update to continue.'; + newEventSource.close(); + return; + } + + // Handle successful authentication + if (data.user && data.token) { + const { user } = data; + setAuthId(user.id); + const { token } = data; + setAuthToken(token); + goto('/home'); + } + }; + + newEventSource.onerror = () => { + console.error('SSE connection error'); + newEventSource.close(); + }; + + return newEventSource; + } + + async function fetchOfferAndSetupSSE() { + // Clean up existing SSE connection + if (eventSource) { + eventSource.close(); + eventSource = null; + } + // Clean up existing refresh timer + if (refreshTimer) { + clearTimeout(refreshTimer); + refreshTimer = null; + } + + const { data } = await apiClient.get('/api/auth/offer'); + qrData = data.uri; + + eventSource = watchEventStream(new URL(qrData).searchParams.get('session') as string); + + // Set up auto-refresh after 60 seconds + refreshTimer = setTimeout(() => { + console.log('Refreshing QR code after 60 seconds'); + fetchOfferAndSetupSSE().catch((error) => + console.error('Error refreshing QR code:', error) + ); + }, 60000); + } + onMount(async () => { checkMobile(); window.addEventListener('resize', checkMobile); @@ -90,51 +157,17 @@ } // If no query params, proceed with normal flow - const { data } = await apiClient.get('/api/auth/offer'); - qrData = data.uri; - - function watchEventStream(id: string) { - const sseUrl = new URL(`/api/auth/sessions/${id}`, PUBLIC_PICTIQUE_BASE_URL).toString(); - const eventSource = new EventSource(sseUrl); - - eventSource.onopen = () => { - console.log('Successfully connected.'); - errorMessage = null; - }; - - eventSource.onmessage = (e) => { - const data = JSON.parse(e.data as string); - - // Check for error messages (version mismatch) - if (data.error && data.type === 'version_mismatch') { - errorMessage = - data.message || - 'Your eID Wallet app version is outdated. Please update to continue.'; - eventSource.close(); - return; - } - - // Handle successful authentication - if (data.user && data.token) { - const { user } = data; - setAuthId(user.id); - const { token } = data; - setAuthToken(token); - goto('/home'); - } - }; + await fetchOfferAndSetupSSE(); + }); - eventSource.onerror = () => { - console.error('SSE connection error'); - eventSource.close(); - }; + onDestroy(() => { + window.removeEventListener('resize', checkMobile); + if (eventSource) { + eventSource.close(); + } + if (refreshTimer) { + clearTimeout(refreshTimer); } - - watchEventStream(new URL(qrData).searchParams.get('session') as string); - - onDestroy(() => { - window.removeEventListener('resize', checkMobile); - }); }); @@ -211,8 +244,7 @@ The {isMobileDevice() ? 'button' : 'code'} is valid for 60 seconds - Please refresh the page if it expires + It will refresh automatically