From 67972fc0f0a0ed972dd187af8517b4b870d2c28d Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Fri, 30 Jan 2026 19:17:17 +0300 Subject: [PATCH 1/2] fix: adjust bottom button height --- .../src/routes/(app)/main/+page.svelte | 187 +-- .../routes/(app)/settings/pin/+page.svelte | 100 +- .../src/routes/(auth)/login/+page.svelte | 406 ++++--- .../src/routes/(auth)/onboarding/+page.svelte | 496 ++++---- .../src/routes/(auth)/register/+page.svelte | 172 +-- .../src/routes/(auth)/review/+page.svelte | 32 +- .../src/routes/(auth)/verify/+page.svelte | 658 +++++----- .../eid-wallet/src/routes/+layout.svelte | 1078 ++++++++--------- 8 files changed, 1569 insertions(+), 1560 deletions(-) diff --git a/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte b/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte index 98ec0948e..8d89a4fea 100644 --- a/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte @@ -1,101 +1,101 @@ {#if profileCreationStatus === "loading"} @@ -198,12 +198,15 @@ onDestroy(() => { - + alert("Action button clicked!")} - class="mx-auto text-nowrap flex gap-8 fixed bottom-5 left-1/2 -translate-x-1/2 z-10" + class="mx-auto text-nowrap flex gap-8" > -import { goto } from "$app/navigation"; -import type { GlobalState } from "$lib/global"; -import { runtime } from "$lib/global/runtime.svelte"; -import { ButtonAction, Drawer, InputPin } from "$lib/ui"; -import { CircleLock01Icon } from "@hugeicons/core-free-icons"; -import { HugeiconsIcon } from "@hugeicons/svelte"; -import { getContext, onMount } from "svelte"; + import { goto } from "$app/navigation"; + import type { GlobalState } from "$lib/global"; + import { runtime } from "$lib/global/runtime.svelte"; + import { ButtonAction, Drawer, InputPin } from "$lib/ui"; + import { CircleLock01Icon } from "@hugeicons/core-free-icons"; + import { HugeiconsIcon } from "@hugeicons/svelte"; + import { getContext, onMount } from "svelte"; -let globalState: GlobalState | undefined = $state(undefined); -let currentPin = $state(""); -let newPin = $state(""); -let repeatPin = $state(""); -let isError = $state(false); -let showDrawer = $state(false); + let globalState: GlobalState | undefined = $state(undefined); + let currentPin = $state(""); + let newPin = $state(""); + let repeatPin = $state(""); + let isError = $state(false); + let showDrawer = $state(false); -const handleClose = async () => { - // close functionality goes here. - showDrawer = false; - goto("/settings"); -}; + const handleClose = async () => { + // close functionality goes here. + showDrawer = false; + goto("/settings"); + }; -const handleChangePIN = async () => { + const handleChangePIN = async () => { if (newPin.length < 4 || repeatPin.length < 4 || currentPin.length < 4) { - isError = true; - return; - } + isError = true; + return; + } - if (newPin !== repeatPin) { - isError = true; - return; - } + if (newPin !== repeatPin) { + isError = true; + return; + } - try { - await globalState?.securityController.updatePin( - newPin, - repeatPin, - currentPin, - ); - isError = false; - showDrawer = true; - } catch (err) { - console.error("Failed to update PIN:", err); - isError = true; - } -}; + try { + await globalState?.securityController.updatePin( + newPin, + repeatPin, + currentPin, + ); + isError = false; + showDrawer = true; + } catch (err) { + console.error("Failed to update PIN:", err); + isError = true; + } + }; -$effect(() => { - runtime.header.title = "Change PIN"; - if (repeatPin.length === 4 && newPin === repeatPin) isError = false; -}); + $effect(() => { + runtime.header.title = "Change PIN"; + if (repeatPin.length === 4 && newPin === repeatPin) isError = false; + }); -onMount(() => { - globalState = getContext<() => GlobalState>("globalState")(); - if (!globalState) throw new Error("Global state is not defined"); -}); + onMount(() => { + globalState = getContext<() => GlobalState>("globalState")(); + if (!globalState) throw new Error("Global state is not defined"); + });
@@ -83,9 +83,9 @@ onMount(() => {
- + -import { goto } from "$app/navigation"; -import { Hero } from "$lib/fragments"; -import type { GlobalState } from "$lib/global"; -import { Drawer, InputPin } from "$lib/ui"; -import * as Button from "$lib/ui/Button"; -import { - type AuthOptions, - authenticate, - checkStatus, -} from "@tauri-apps/plugin-biometric"; -import { getContext, onMount } from "svelte"; + import { goto } from "$app/navigation"; + import { Hero } from "$lib/fragments"; + import type { GlobalState } from "$lib/global"; + import { Drawer, InputPin } from "$lib/ui"; + import * as Button from "$lib/ui/Button"; + import { + type AuthOptions, + authenticate, + checkStatus, + } from "@tauri-apps/plugin-biometric"; + import { getContext, onMount } from "svelte"; -let pin = $state(""); -let isError = $state(false); -let isPostAuthLoading = $state(false); -let clearPin = $state(async () => {}); -let handlePinInput = $state((pin: string) => {}); -let globalState: GlobalState | undefined = $state(undefined); -let hasPendingDeepLink = $state(false); -let isDeletedVaultModalOpen = $state(false); + let pin = $state(""); + let isError = $state(false); + let isPostAuthLoading = $state(false); + let clearPin = $state(async () => {}); + let handlePinInput = $state((pin: string) => {}); + let globalState: GlobalState | undefined = $state(undefined); + let hasPendingDeepLink = $state(false); + let isDeletedVaultModalOpen = $state(false); -const authOpts: AuthOptions = { - allowDeviceCredential: false, + const authOpts: AuthOptions = { + allowDeviceCredential: false, - cancelTitle: "Cancel", + cancelTitle: "Cancel", - // iOS - fallbackTitle: "Please enter your PIN", + // iOS + fallbackTitle: "Please enter your PIN", - // Android - title: "Login", - subtitle: "Please authenticate to continue", - confirmationRequired: true, -}; - -const getGlobalState = getContext<() => GlobalState>("globalState"); -const setGlobalState = - getContext<(value: GlobalState) => void>("setGlobalState"); + // Android + title: "Login", + subtitle: "Please authenticate to continue", + confirmationRequired: true, + }; -async function nukeWallet() { - if (!globalState) return; - const newGlobalState = await globalState.reset(); - setGlobalState(newGlobalState); - globalState = newGlobalState; - isDeletedVaultModalOpen = false; - await goto("/onboarding"); -} + const getGlobalState = getContext<() => GlobalState>("globalState"); + const setGlobalState = + getContext<(value: GlobalState) => void>("setGlobalState"); -onMount(async () => { - globalState = getContext<() => GlobalState>("globalState")(); - if (!globalState) { - console.error("Global state is not defined"); - await goto("/"); // Redirect to home or error page - return; + async function nukeWallet() { + if (!globalState) return; + const newGlobalState = await globalState.reset(); + setGlobalState(newGlobalState); + globalState = newGlobalState; + isDeletedVaultModalOpen = false; + await goto("/onboarding"); } - // Check if there's a pending deep link - const pendingDeepLink = sessionStorage.getItem("pendingDeepLink"); - hasPendingDeepLink = !!pendingDeepLink; - if (hasPendingDeepLink) { - console.log("Pending deep link detected on login page"); - } + onMount(async () => { + globalState = getContext<() => GlobalState>("globalState")(); + if (!globalState) { + console.error("Global state is not defined"); + await goto("/"); // Redirect to home or error page + return; + } - clearPin = async () => { - pin = ""; - isError = false; - if (isPostAuthLoading) return; - }; + // Check if there's a pending deep link + const pendingDeepLink = sessionStorage.getItem("pendingDeepLink"); + hasPendingDeepLink = !!pendingDeepLink; + if (hasPendingDeepLink) { + console.log("Pending deep link detected on login page"); + } - handlePinInput = async (pin: string) => { - if (isPostAuthLoading) return; - if (pin.length === 4) { + clearPin = async () => { + pin = ""; isError = false; - isPostAuthLoading = true; - const check = globalState - ? await globalState.securityController.verifyPin(pin) - : false; - if (!check) { - isError = true; - isPostAuthLoading = false; - return; - } + if (isPostAuthLoading) return; + }; - // Check eVault health after successful login - try { - const vault = await globalState?.vaultController.vault; - if (vault?.ename && globalState) { - const healthCheck = - await globalState.vaultController.checkHealth( - vault.ename, - ); - if (!healthCheck.healthy) { - console.warn( - "eVault health check failed:", - healthCheck.error, - ); + handlePinInput = async (pin: string) => { + if (isPostAuthLoading) return; + if (pin.length === 4) { + isError = false; + isPostAuthLoading = true; + const check = globalState + ? await globalState.securityController.verifyPin(pin) + : false; + if (!check) { + isError = true; + isPostAuthLoading = false; + return; + } - // If eVault was deleted (404), show modal - if (healthCheck.deleted) { - isDeletedVaultModalOpen = true; - return; // Don't continue to app + // Check eVault health after successful login + try { + const vault = await globalState?.vaultController.vault; + if (vault?.ename && globalState) { + const healthCheck = + await globalState.vaultController.checkHealth( + vault.ename, + ); + if (!healthCheck.healthy) { + console.warn( + "eVault health check failed:", + healthCheck.error, + ); + + // If eVault was deleted (404), show modal + if (healthCheck.deleted) { + isDeletedVaultModalOpen = true; + return; // Don't continue to app + } + // For other errors, continue to app - non-blocking + } + + // Sync public key to eVault core + try { + await globalState.vaultController.syncPublicKey( + vault.ename, + ); + } catch (error) { + console.error("Error syncing public key:", error); + // Continue to app even if sync fails - non-blocking } - // For other errors, continue to app - non-blocking } + } catch (error) { + console.error("Error during eVault health check:", error); + // Continue to app even if health check fails - non-blocking + } - // Sync public key to eVault core + // Check if there's a pending deep link to process + const pendingDeepLink = + sessionStorage.getItem("pendingDeepLink"); + if (pendingDeepLink) { try { - await globalState.vaultController.syncPublicKey( - vault.ename, + const deepLinkData = JSON.parse(pendingDeepLink); + console.log( + "Processing pending deep link after login:", + deepLinkData, ); + + // Store the deep link data for the scan page + sessionStorage.setItem("deepLinkData", pendingDeepLink); + // Clear the pending deep link + sessionStorage.removeItem("pendingDeepLink"); + + // Redirect to scan page to process the deep link + await goto("/scan-qr"); + return; } catch (error) { - console.error("Error syncing public key:", error); - // Continue to app even if sync fails - non-blocking + console.error( + "Error processing pending deep link:", + error, + ); + sessionStorage.removeItem("pendingDeepLink"); } } - } catch (error) { - console.error("Error during eVault health check:", error); - // Continue to app even if health check fails - non-blocking - } - - // Check if there's a pending deep link to process - const pendingDeepLink = sessionStorage.getItem("pendingDeepLink"); - if (pendingDeepLink) { - try { - const deepLinkData = JSON.parse(pendingDeepLink); - console.log( - "Processing pending deep link after login:", - deepLinkData, - ); - // Store the deep link data for the scan page - sessionStorage.setItem("deepLinkData", pendingDeepLink); - // Clear the pending deep link - sessionStorage.removeItem("pendingDeepLink"); - - // Redirect to scan page to process the deep link - await goto("/scan-qr"); - return; - } catch (error) { - console.error("Error processing pending deep link:", error); - sessionStorage.removeItem("pendingDeepLink"); - } + // No pending deep link, go to main page + await goto("/main"); + return; } + isPostAuthLoading = false; + }; - // No pending deep link, go to main page - await goto("/main"); - return; - } - isPostAuthLoading = false; - }; + // for some reason it's important for this to be done before the biometric stuff + // otherwise pin doesn't work + $effect(() => { + handlePinInput(pin); + }); - // for some reason it's important for this to be done before the biometric stuff - // otherwise pin doesn't work - $effect(() => { - handlePinInput(pin); - }); + if ( + (await globalState.securityController.biometricSupport) && + (await checkStatus()).isAvailable + ) { + try { + await authenticate( + "You must authenticate with PIN first", + authOpts, + ); + isPostAuthLoading = true; - if ( - (await globalState.securityController.biometricSupport) && - (await checkStatus()).isAvailable - ) { - try { - await authenticate( - "You must authenticate with PIN first", - authOpts, - ); - isPostAuthLoading = true; + // Check eVault health after successful biometric login + try { + const vault = await globalState.vaultController.vault; + if (vault?.ename) { + const healthCheck = + await globalState.vaultController.checkHealth( + vault.ename, + ); + if (!healthCheck.healthy) { + console.warn( + "eVault health check failed:", + healthCheck.error, + ); - // Check eVault health after successful biometric login - try { - const vault = await globalState.vaultController.vault; - if (vault?.ename) { - const healthCheck = - await globalState.vaultController.checkHealth( - vault.ename, - ); - if (!healthCheck.healthy) { - console.warn( - "eVault health check failed:", - healthCheck.error, - ); + // If eVault was deleted (404), show modal + if (healthCheck.deleted) { + isDeletedVaultModalOpen = true; + return; // Don't continue to app + } + // For other errors, continue to app - non-blocking + } - // If eVault was deleted (404), show modal - if (healthCheck.deleted) { - isDeletedVaultModalOpen = true; - return; // Don't continue to app + // Sync public key to eVault core + try { + await globalState.vaultController.syncPublicKey( + vault.ename, + ); + } catch (error) { + console.error("Error syncing public key:", error); + // Continue to app even if sync fails - non-blocking } - // For other errors, continue to app - non-blocking } + } catch (error) { + console.error("Error during eVault health check:", error); + // Continue to app even if health check fails - non-blocking + } - // Sync public key to eVault core + // Check if there's a pending deep link to process + const pendingDeepLink = + sessionStorage.getItem("pendingDeepLink"); + if (pendingDeepLink) { try { - await globalState.vaultController.syncPublicKey( - vault.ename, + const deepLinkData = JSON.parse(pendingDeepLink); + console.log( + "Processing pending deep link after biometric login:", + deepLinkData, ); + + // Store the deep link data for the scan page + sessionStorage.setItem("deepLinkData", pendingDeepLink); + // Clear the pending deep link + sessionStorage.removeItem("pendingDeepLink"); + + // Redirect to scan page to process the deep link + await goto("/scan-qr"); + return; } catch (error) { - console.error("Error syncing public key:", error); - // Continue to app even if sync fails - non-blocking + console.error( + "Error processing pending deep link:", + error, + ); + sessionStorage.removeItem("pendingDeepLink"); } } - } catch (error) { - console.error("Error during eVault health check:", error); - // Continue to app even if health check fails - non-blocking - } - // Check if there's a pending deep link to process - const pendingDeepLink = sessionStorage.getItem("pendingDeepLink"); - if (pendingDeepLink) { - try { - const deepLinkData = JSON.parse(pendingDeepLink); - console.log( - "Processing pending deep link after biometric login:", - deepLinkData, - ); - - // Store the deep link data for the scan page - sessionStorage.setItem("deepLinkData", pendingDeepLink); - // Clear the pending deep link - sessionStorage.removeItem("pendingDeepLink"); - - // Redirect to scan page to process the deep link - await goto("/scan-qr"); - return; - } catch (error) { - console.error("Error processing pending deep link:", error); - sessionStorage.removeItem("pendingDeepLink"); - } + // No pending deep link, go to main page + await goto("/main"); + } catch (e) { + console.error("Biometric authentication failed", e); + isPostAuthLoading = false; } - - // No pending deep link, go to main page - await goto("/main"); - } catch (e) { - console.error("Biometric authentication failed", e); - isPostAuthLoading = false; } - } -}); + }); -
+
{#snippet subtitle()} diff --git a/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte b/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte index a949e3b93..72692d7ce 100644 --- a/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte @@ -1,282 +1,282 @@
{
-

+

Already have a pre-verification code? { {/if} -

+
{#if !loading && !checkingHardware}
diff --git a/infrastructure/eid-wallet/src/routes/(auth)/register/+page.svelte b/infrastructure/eid-wallet/src/routes/(auth)/register/+page.svelte index c00e6c925..1aa294210 100644 --- a/infrastructure/eid-wallet/src/routes/(auth)/register/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(auth)/register/+page.svelte @@ -1,107 +1,107 @@
{#if currentStep === "CREATE"} diff --git a/infrastructure/eid-wallet/src/routes/(auth)/review/+page.svelte b/infrastructure/eid-wallet/src/routes/(auth)/review/+page.svelte index be64bf8cb..edc1a979c 100644 --- a/infrastructure/eid-wallet/src/routes/(auth)/review/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(auth)/review/+page.svelte @@ -1,26 +1,26 @@
diff --git a/infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte b/infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte index beec2660b..76fe9e807 100644 --- a/infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte @@ -1,374 +1,374 @@
{ {/if}
-
+
{#if !loading}
-import SplashScreen from "$lib/fragments/SplashScreen/SplashScreen.svelte"; -import { getContext, onDestroy, onMount, setContext } from "svelte"; -import "../app.css"; -import { goto, onNavigate } from "$app/navigation"; -import { GlobalState } from "$lib/global/state"; - -import { runtime } from "$lib/global/runtime.svelte"; -import { swipedetect } from "$lib/utils"; -import { type Status, checkStatus } from "@tauri-apps/plugin-biometric"; - -const { children } = $props(); - -let globalState: GlobalState | undefined = $state(undefined); - -let showSplashScreen = $state(false); -let previousRoute = null; -let navigationStack: string[] = []; -let globalDeepLinkHandler: ((event: Event) => void) | undefined; -let mainWrapper: HTMLElement | undefined = $state(undefined); -let isAppReady = $state(false); -let pendingDeepLinks: string[] = $state([]); - -setContext("globalState", () => globalState); -setContext("setGlobalState", (value: GlobalState | undefined) => { - globalState = value; -}); - -// replace with actual data loading logic -async function loadData() { - await new Promise((resolve) => setTimeout(resolve, 1500)); -} - -async function ensureMinimumDelay() { - await new Promise((resolve) => setTimeout(resolve, 500)); -} - -onMount(async () => { - let status: Status | undefined = undefined; - try { - status = await checkStatus(); - } catch (error) { - status = { - biometryType: 0, - isAvailable: false, - }; + import SplashScreen from "$lib/fragments/SplashScreen/SplashScreen.svelte"; + import { getContext, onDestroy, onMount, setContext } from "svelte"; + import "../app.css"; + import { goto, onNavigate } from "$app/navigation"; + import { GlobalState } from "$lib/global/state"; + + import { runtime } from "$lib/global/runtime.svelte"; + import { swipedetect } from "$lib/utils"; + import { type Status, checkStatus } from "@tauri-apps/plugin-biometric"; + + const { children } = $props(); + + let globalState: GlobalState | undefined = $state(undefined); + + let showSplashScreen = $state(false); + let previousRoute = null; + let navigationStack: string[] = []; + let globalDeepLinkHandler: ((event: Event) => void) | undefined; + let mainWrapper: HTMLElement | undefined = $state(undefined); + let isAppReady = $state(false); + let pendingDeepLinks: string[] = $state([]); + + setContext("globalState", () => globalState); + setContext("setGlobalState", (value: GlobalState | undefined) => { + globalState = value; + }); + + // replace with actual data loading logic + async function loadData() { + await new Promise((resolve) => setTimeout(resolve, 1500)); } - runtime.biometry = status.biometryType; - try { - globalState = await GlobalState.create(); - } catch (error) { - console.error("Failed to initialize global state:", error); - // Consider adding fallback behavior or user notification + + async function ensureMinimumDelay() { + await new Promise((resolve) => setTimeout(resolve, 500)); } - // Handle deep links - try { - const { onOpenUrl, getCurrent } = await import( - "@tauri-apps/plugin-deep-link" - ); - - // Check if app was started via deep link - const initialUrls = await getCurrent(); - if (initialUrls && initialUrls.length > 0) { - if (globalState) { - handleDeepLink(initialUrls[0]); - } else { - pendingDeepLinks = [...pendingDeepLinks, initialUrls[0]]; - } + onMount(async () => { + let status: Status | undefined = undefined; + try { + status = await checkStatus(); + } catch (error) { + status = { + biometryType: 0, + isAvailable: false, + }; + } + runtime.biometry = status.biometryType; + try { + globalState = await GlobalState.create(); + } catch (error) { + console.error("Failed to initialize global state:", error); + // Consider adding fallback behavior or user notification } - // Listen for future deep links - await onOpenUrl((urls) => { - if (urls && urls.length > 0) { - try { - if (isAppReady && globalState) { - handleDeepLink(urls[0]); - } else { - // Queue deep link if app isn't ready yet - console.log( - "App not ready, queueing deep link:", - urls[0], - ); - pendingDeepLinks = [...pendingDeepLinks, urls[0]]; - } - } catch (error) { - console.error( - "Error handling deep link from onOpenUrl:", - error, - ); + // Handle deep links + try { + const { onOpenUrl, getCurrent } = await import( + "@tauri-apps/plugin-deep-link" + ); + + // Check if app was started via deep link + const initialUrls = await getCurrent(); + if (initialUrls && initialUrls.length > 0) { + if (globalState) { + handleDeepLink(initialUrls[0]); + } else { + pendingDeepLinks = [...pendingDeepLinks, initialUrls[0]]; } } - }); - // Set up global event listener for deep links that arrive when app is already open - // This ensures deep links work even if the scan-qr page isn't mounted yet - globalDeepLinkHandler = (event: Event) => { - try { - const customEvent = event as CustomEvent; - console.log( - "Global deep link event received:", - customEvent.detail, - ); - - if (!isAppReady || !globalState) { - console.log( - "App not ready, storing deep link data for later", - ); - sessionStorage.setItem( - "deepLinkData", - JSON.stringify(customEvent.detail), - ); - return; + // Listen for future deep links + await onOpenUrl((urls) => { + if (urls && urls.length > 0) { + try { + if (isAppReady && globalState) { + handleDeepLink(urls[0]); + } else { + // Queue deep link if app isn't ready yet + console.log( + "App not ready, queueing deep link:", + urls[0], + ); + pendingDeepLinks = [...pendingDeepLinks, urls[0]]; + } + } catch (error) { + console.error( + "Error handling deep link from onOpenUrl:", + error, + ); + } } + }); - // Check if we're already on the scan page - if (window.location.pathname === "/scan-qr") { - // We're already on the scan page, dispatch the event directly + // Set up global event listener for deep links that arrive when app is already open + // This ensures deep links work even if the scan-qr page isn't mounted yet + globalDeepLinkHandler = (event: Event) => { + try { + const customEvent = event as CustomEvent; console.log( - "Already on scan page, dispatching event directly", + "Global deep link event received:", + customEvent.detail, ); + + if (!isAppReady || !globalState) { + console.log( + "App not ready, storing deep link data for later", + ); + sessionStorage.setItem( + "deepLinkData", + JSON.stringify(customEvent.detail), + ); + return; + } + + // Check if we're already on the scan page + if (window.location.pathname === "/scan-qr") { + // We're already on the scan page, dispatch the event directly + console.log( + "Already on scan page, dispatching event directly", + ); const directEvent = new CustomEvent("deepLinkReceived", { - detail: customEvent.detail, + detail: customEvent.detail, }); - window.dispatchEvent(directEvent); - } else { - // Store the deep link data and navigate to scan page - console.log( - "Not on scan page, storing data and navigating", - ); - sessionStorage.setItem( - "deepLinkData", - JSON.stringify(customEvent.detail), - ); - goto("/scan-qr").catch((error) => { + window.dispatchEvent(directEvent); + } else { + // Store the deep link data and navigate to scan page + console.log( + "Not on scan page, storing data and navigating", + ); + sessionStorage.setItem( + "deepLinkData", + JSON.stringify(customEvent.detail), + ); + goto("/scan-qr").catch((error) => { console.error("Error navigating to scan-qr:", error); - }); + }); + } + } catch (error) { + console.error("Error in globalDeepLinkHandler:", error); } - } catch (error) { - console.error("Error in globalDeepLinkHandler:", error); - } - }; + }; - window.addEventListener("deepLinkReceived", globalDeepLinkHandler); - } catch (error) { - console.error("Failed to initialize deep link listener:", error); - } + window.addEventListener("deepLinkReceived", globalDeepLinkHandler); + } catch (error) { + console.error("Failed to initialize deep link listener:", error); + } - // Helper function to check if user is on an authenticated route - function isAuthenticatedRoute(pathname: string): boolean { - // Authenticated routes are those under (app)/ which are protected by the auth guard - const authenticatedRoutes = ["/main", "/scan-qr", "/settings"]; - return authenticatedRoutes.includes(pathname); - } + // Helper function to check if user is on an authenticated route + function isAuthenticatedRoute(pathname: string): boolean { + // Authenticated routes are those under (app)/ which are protected by the auth guard + const authenticatedRoutes = ["/main", "/scan-qr", "/settings"]; + return authenticatedRoutes.includes(pathname); + } - function handleDeepLink(urlString: string) { - console.log("Deep link received:", urlString); + function handleDeepLink(urlString: string) { + console.log("Deep link received:", urlString); - try { - const url = new URL(urlString); - const path = url.pathname; - const params = url.searchParams; - - console.log("Deep link path:", path); - console.log("Deep link hostname:", url.hostname); - console.log("Deep link protocol:", url.protocol); - console.log("Deep link full URL object:", url); - console.log( - "Deep link params:", - Object.fromEntries(params.entries()), - ); + try { + const url = new URL(urlString); + const path = url.pathname; + const params = url.searchParams; + + console.log("Deep link path:", path); + console.log("Deep link hostname:", url.hostname); + console.log("Deep link protocol:", url.protocol); + console.log("Deep link full URL object:", url); + console.log( + "Deep link params:", + Object.fromEntries(params.entries()), + ); - // Check if we're already on the scan-qr page - const currentPath = window.location.pathname; - const isOnScanPage = currentPath === "/scan-qr"; + // Check if we're already on the scan-qr page + const currentPath = window.location.pathname; + const isOnScanPage = currentPath === "/scan-qr"; const isOnAuthenticatedRoute = isAuthenticatedRoute(currentPath); - console.log( - "Current path:", - currentPath, - "Is on scan page:", - isOnScanPage, - "Is on authenticated route:", - isOnAuthenticatedRoute, - ); - - // For w3ds:// URLs, we need to check the hostname instead of pathname - // w3ds://auth becomes hostname: "auth", pathname: "" - const action = url.hostname || path; - console.log("Deep link action (hostname):", action); - - // Example: w3ds://auth?session=123&platform=example&redirect=https://example.com - if (action === "auth") { - // Handle authentication deep link - const sessionId = params.get("session"); - const platform = params.get("platform"); - const redirect = params.get("redirect"); - console.log( - "Auth deep link - session:", - sessionId, - "platform:", - platform, - "redirect:", - redirect, + "Current path:", + currentPath, + "Is on scan page:", + isOnScanPage, + "Is on authenticated route:", + isOnAuthenticatedRoute, ); - if (sessionId && platform && redirect) { - // Always store the deep link data first - const deepLinkData = { - type: "auth", - session: sessionId, - platform: platform, - redirect: redirect, - }; - - // Check if user is authenticated by checking if they're on an authenticated route - const checkAuth = async () => { - // First check if user is on an authenticated route - // If not, they need to login first regardless of vault existence - if (!isOnAuthenticatedRoute) { - console.log( - "User not on authenticated route, storing deep link and redirecting to login", - ); - sessionStorage.setItem( - "pendingDeepLink", - JSON.stringify(deepLinkData), - ); - goto("/login").catch((error) => { - console.error( - "Error navigating to login:", - error, - ); - }); - return; - } + // For w3ds:// URLs, we need to check the hostname instead of pathname + // w3ds://auth becomes hostname: "auth", pathname: "" + const action = url.hostname || path; + console.log("Deep link action (hostname):", action); + + // Example: w3ds://auth?session=123&platform=example&redirect=https://example.com + if (action === "auth") { + // Handle authentication deep link + const sessionId = params.get("session"); + const platform = params.get("platform"); + const redirect = params.get("redirect"); - try { - // Wait for globalState to be ready if it's not yet - if (!globalState) { + console.log( + "Auth deep link - session:", + sessionId, + "platform:", + platform, + "redirect:", + redirect, + ); + + if (sessionId && platform && redirect) { + // Always store the deep link data first + const deepLinkData = { + type: "auth", + session: sessionId, + platform: platform, + redirect: redirect, + }; + + // Check if user is authenticated by checking if they're on an authenticated route + const checkAuth = async () => { + // First check if user is on an authenticated route + // If not, they need to login first regardless of vault existence + if (!isOnAuthenticatedRoute) { console.log( - "GlobalState not ready, waiting...", + "User not on authenticated route, storing deep link and redirecting to login", ); - // Wait a bit and retry, or just redirect to login - let retries = 0; - const maxRetries = 10; - while (!globalState && retries < maxRetries) { - await new Promise((resolve) => - setTimeout(resolve, 100), + sessionStorage.setItem( + "pendingDeepLink", + JSON.stringify(deepLinkData), + ); + goto("/login").catch((error) => { + console.error( + "Error navigating to login:", + error, ); - retries++; - } + }); + return; + } + try { + // Wait for globalState to be ready if it's not yet if (!globalState) { console.log( - "GlobalState still not ready, storing deep link and redirecting to login", + "GlobalState not ready, waiting...", + ); + // Wait a bit and retry, or just redirect to login + let retries = 0; + const maxRetries = 10; + while (!globalState && retries < maxRetries) { + await new Promise((resolve) => + setTimeout(resolve, 100), + ); + retries++; + } + + if (!globalState) { + console.log( + "GlobalState still not ready, storing deep link and redirecting to login", + ); + sessionStorage.setItem( + "pendingDeepLink", + JSON.stringify(deepLinkData), + ); + goto("/login").catch((error) => { + console.error( + "Error navigating to login:", + error, + ); + }); + return; + } + } + + const vault = + await globalState.vaultController.vault; + if (vault) { + // User is authenticated, dispatch event and navigate to scan page + console.log( + "User authenticated, dispatching deep link event and navigating to scan-qr", + ); + + // Dispatch a custom event that the scan page can listen to + const deepLinkEvent = new CustomEvent( + "deepLinkReceived", + { + detail: deepLinkData, + }, ); + window.dispatchEvent(deepLinkEvent); + + // Also store in sessionStorage as backup sessionStorage.setItem( - "pendingDeepLink", + "deepLinkData", JSON.stringify(deepLinkData), ); - goto("/login").catch((error) => { + + goto("/scan-qr").catch((error) => { console.error( - "Error navigating to login:", + "Error navigating to scan-qr:", error, ); }); return; } - } - - const vault = - await globalState.vaultController.vault; - if (vault) { - // User is authenticated, dispatch event and navigate to scan page + } catch (error) { console.log( - "User authenticated, dispatching deep link event and navigating to scan-qr", - ); - - // Dispatch a custom event that the scan page can listen to - const deepLinkEvent = new CustomEvent( - "deepLinkReceived", - { - detail: deepLinkData, - }, - ); - window.dispatchEvent(deepLinkEvent); - - // Also store in sessionStorage as backup - sessionStorage.setItem( - "deepLinkData", - JSON.stringify(deepLinkData), + "User not authenticated, redirecting to login", + error, ); - - goto("/scan-qr").catch((error) => { - console.error( - "Error navigating to scan-qr:", - error, - ); - }); - return; } - } catch (error) { - console.log( - "User not authenticated, redirecting to login", - error, - ); - } - - // User not authenticated, store deep link data and redirect to login - console.log( - "User not authenticated, storing deep link data and redirecting to login", - ); - sessionStorage.setItem( - "pendingDeepLink", - JSON.stringify(deepLinkData), - ); - goto("/login").catch((error) => { - console.error("Error navigating to login:", error); - }); - }; - - checkAuth(); - } else { - console.log("Missing required auth parameters"); - } - } else if (action === "sign") { - // Handle signing deep link - const sessionId = params.get("session"); - const data = params.get("data"); - const redirectUri = params.get("redirect_uri"); - - console.log( - "Sign deep link - session:", - sessionId, - "data:", - data, - "redirect_uri:", - redirectUri, - ); - if (sessionId && data && redirectUri) { - // Always store the deep link data first - const deepLinkData = { - type: "sign", - session: sessionId, - data: data, - redirect_uri: redirectUri, - }; - - // Check if user is authenticated by checking if they're on an authenticated route - const checkAuth = async () => { - // First check if user is on an authenticated route - // If not, they need to login first regardless of vault existence - if (!isOnAuthenticatedRoute) { + // User not authenticated, store deep link data and redirect to login console.log( - "User not on authenticated route, storing deep link and redirecting to login", + "User not authenticated, storing deep link data and redirecting to login", ); sessionStorage.setItem( "pendingDeepLink", JSON.stringify(deepLinkData), ); goto("/login").catch((error) => { - console.error( - "Error navigating to login:", - error, - ); + console.error("Error navigating to login:", error); }); - return; - } + }; + + checkAuth(); + } else { + console.log("Missing required auth parameters"); + } + } else if (action === "sign") { + // Handle signing deep link + const sessionId = params.get("session"); + const data = params.get("data"); + const redirectUri = params.get("redirect_uri"); + + console.log( + "Sign deep link - session:", + sessionId, + "data:", + data, + "redirect_uri:", + redirectUri, + ); - try { - // Wait for globalState to be ready if it's not yet - if (!globalState) { + if (sessionId && data && redirectUri) { + // Always store the deep link data first + const deepLinkData = { + type: "sign", + session: sessionId, + data: data, + redirect_uri: redirectUri, + }; + + // Check if user is authenticated by checking if they're on an authenticated route + const checkAuth = async () => { + // First check if user is on an authenticated route + // If not, they need to login first regardless of vault existence + if (!isOnAuthenticatedRoute) { console.log( - "GlobalState not ready, waiting...", + "User not on authenticated route, storing deep link and redirecting to login", ); - // Wait a bit and retry, or just redirect to login - let retries = 0; - const maxRetries = 10; - while (!globalState && retries < maxRetries) { - await new Promise((resolve) => - setTimeout(resolve, 100), + sessionStorage.setItem( + "pendingDeepLink", + JSON.stringify(deepLinkData), + ); + goto("/login").catch((error) => { + console.error( + "Error navigating to login:", + error, ); - retries++; - } + }); + return; + } + try { + // Wait for globalState to be ready if it's not yet if (!globalState) { console.log( - "GlobalState still not ready, storing deep link and redirecting to login", + "GlobalState not ready, waiting...", ); + // Wait a bit and retry, or just redirect to login + let retries = 0; + const maxRetries = 10; + while (!globalState && retries < maxRetries) { + await new Promise((resolve) => + setTimeout(resolve, 100), + ); + retries++; + } + + if (!globalState) { + console.log( + "GlobalState still not ready, storing deep link and redirecting to login", + ); + sessionStorage.setItem( + "pendingDeepLink", + JSON.stringify(deepLinkData), + ); + goto("/login").catch((error) => { + console.error( + "Error navigating to login:", + error, + ); + }); + return; + } + } + + const vault = + await globalState.vaultController.vault; + if (vault) { + // User is authenticated, dispatch event and navigate to scan page + console.log( + "User authenticated, dispatching deep link event and navigating to scan-qr", + ); + + // Dispatch a custom event that the scan page can listen to + const deepLinkEvent = new CustomEvent( + "deepLinkReceived", + { + detail: deepLinkData, + }, + ); + window.dispatchEvent(deepLinkEvent); + + // Also store in sessionStorage as backup sessionStorage.setItem( - "pendingDeepLink", + "deepLinkData", JSON.stringify(deepLinkData), ); - goto("/login").catch((error) => { + + goto("/scan-qr").catch((error) => { console.error( - "Error navigating to login:", + "Error navigating to scan-qr:", error, ); }); return; } - } - - const vault = - await globalState.vaultController.vault; - if (vault) { - // User is authenticated, dispatch event and navigate to scan page + } catch (error) { console.log( - "User authenticated, dispatching deep link event and navigating to scan-qr", - ); - - // Dispatch a custom event that the scan page can listen to - const deepLinkEvent = new CustomEvent( - "deepLinkReceived", - { - detail: deepLinkData, - }, - ); - window.dispatchEvent(deepLinkEvent); - - // Also store in sessionStorage as backup - sessionStorage.setItem( - "deepLinkData", - JSON.stringify(deepLinkData), + "User not authenticated, redirecting to login", + error, ); - - goto("/scan-qr").catch((error) => { - console.error( - "Error navigating to scan-qr:", - error, - ); - }); - return; } - } catch (error) { - console.log( - "User not authenticated, redirecting to login", - error, - ); - } - // User not authenticated, store deep link data and redirect to login - console.log( - "User not authenticated, storing deep link data and redirecting to login", - ); - sessionStorage.setItem( - "pendingDeepLink", - JSON.stringify(deepLinkData), - ); - goto("/login").catch((error) => { - console.error("Error navigating to login:", error); - }); - }; - - checkAuth(); - } else { - console.log("Missing required signing parameters"); - } - } else if (action === "reveal") { - // Handle reveal deep link - const pollId = params.get("pollId"); - - console.log("Reveal deep link - pollId:", pollId); - - if (pollId) { - // Always store the deep link data first - const deepLinkData = { - type: "reveal", - pollId: pollId, - }; - - // Check if user is authenticated by checking if they're on an authenticated route - const checkAuth = async () => { - // First check if user is on an authenticated route - // If not, they need to login first regardless of vault existence - if (!isOnAuthenticatedRoute) { + // User not authenticated, store deep link data and redirect to login console.log( - "User not on authenticated route, storing deep link and redirecting to login", + "User not authenticated, storing deep link data and redirecting to login", ); sessionStorage.setItem( "pendingDeepLink", JSON.stringify(deepLinkData), ); goto("/login").catch((error) => { - console.error( - "Error navigating to login:", - error, - ); + console.error("Error navigating to login:", error); }); - return; - } + }; - try { - // Wait for globalState to be ready if it's not yet - if (!globalState) { + checkAuth(); + } else { + console.log("Missing required signing parameters"); + } + } else if (action === "reveal") { + // Handle reveal deep link + const pollId = params.get("pollId"); + + console.log("Reveal deep link - pollId:", pollId); + + if (pollId) { + // Always store the deep link data first + const deepLinkData = { + type: "reveal", + pollId: pollId, + }; + + // Check if user is authenticated by checking if they're on an authenticated route + const checkAuth = async () => { + // First check if user is on an authenticated route + // If not, they need to login first regardless of vault existence + if (!isOnAuthenticatedRoute) { console.log( - "GlobalState not ready, waiting...", + "User not on authenticated route, storing deep link and redirecting to login", ); - // Wait a bit and retry, or just redirect to login - let retries = 0; - const maxRetries = 10; - while (!globalState && retries < maxRetries) { - await new Promise((resolve) => - setTimeout(resolve, 100), + sessionStorage.setItem( + "pendingDeepLink", + JSON.stringify(deepLinkData), + ); + goto("/login").catch((error) => { + console.error( + "Error navigating to login:", + error, ); - retries++; - } + }); + return; + } + try { + // Wait for globalState to be ready if it's not yet if (!globalState) { console.log( - "GlobalState still not ready, storing deep link and redirecting to login", + "GlobalState not ready, waiting...", + ); + // Wait a bit and retry, or just redirect to login + let retries = 0; + const maxRetries = 10; + while (!globalState && retries < maxRetries) { + await new Promise((resolve) => + setTimeout(resolve, 100), + ); + retries++; + } + + if (!globalState) { + console.log( + "GlobalState still not ready, storing deep link and redirecting to login", + ); + sessionStorage.setItem( + "pendingDeepLink", + JSON.stringify(deepLinkData), + ); + goto("/login").catch((error) => { + console.error( + "Error navigating to login:", + error, + ); + }); + return; + } + } + + const vault = + await globalState.vaultController.vault; + if (vault) { + // User is authenticated, dispatch event and navigate to scan page + console.log( + "User authenticated, dispatching deep link event and navigating to scan-qr for reveal", + ); + + // Dispatch a custom event that the scan page can listen to + const deepLinkEvent = new CustomEvent( + "deepLinkReceived", + { + detail: deepLinkData, + }, ); + window.dispatchEvent(deepLinkEvent); + + // Also store in sessionStorage as backup sessionStorage.setItem( - "pendingDeepLink", + "deepLinkData", JSON.stringify(deepLinkData), ); - goto("/login").catch((error) => { + + goto("/scan-qr").catch((error) => { console.error( - "Error navigating to login:", + "Error navigating to scan-qr:", error, ); }); return; } - } - - const vault = - await globalState.vaultController.vault; - if (vault) { - // User is authenticated, dispatch event and navigate to scan page + } catch (error) { console.log( - "User authenticated, dispatching deep link event and navigating to scan-qr for reveal", - ); - - // Dispatch a custom event that the scan page can listen to - const deepLinkEvent = new CustomEvent( - "deepLinkReceived", - { - detail: deepLinkData, - }, - ); - window.dispatchEvent(deepLinkEvent); - - // Also store in sessionStorage as backup - sessionStorage.setItem( - "deepLinkData", - JSON.stringify(deepLinkData), + "User not authenticated, redirecting to login", + error, ); - - goto("/scan-qr").catch((error) => { - console.error( - "Error navigating to scan-qr:", - error, - ); - }); - return; } - } catch (error) { + + // User not authenticated, store deep link data and redirect to login console.log( - "User not authenticated, redirecting to login", - error, + "User not authenticated, storing reveal deep link data and redirecting to login", ); - } - - // User not authenticated, store deep link data and redirect to login - console.log( - "User not authenticated, storing reveal deep link data and redirecting to login", - ); - sessionStorage.setItem( - "pendingDeepLink", - JSON.stringify(deepLinkData), - ); - goto("/login").catch((error) => { + sessionStorage.setItem( + "pendingDeepLink", + JSON.stringify(deepLinkData), + ); + goto("/login").catch((error) => { console.error("Error navigating to login:", error); - }); - }; + }); + }; - checkAuth(); + checkAuth(); + } else { + console.log("Missing required reveal parameters"); + } } else { - console.log("Missing required reveal parameters"); + console.log("Unknown deep link path:", path); } - } else { - console.log("Unknown deep link path:", path); + } catch (error) { + console.error("Failed to parse deep link URL:", error); } - } catch (error) { - console.error("Failed to parse deep link URL:", error); } - } - showSplashScreen = true; // Can't set up the original state to true or animation won't start - navigationStack.push(window.location.pathname); + showSplashScreen = true; // Can't set up the original state to true or animation won't start + navigationStack.push(window.location.pathname); - await Promise.all([loadData(), ensureMinimumDelay()]); + await Promise.all([loadData(), ensureMinimumDelay()]); - showSplashScreen = false; + showSplashScreen = false; - // Mark app as ready and process any pending deep links - isAppReady = true; + // Mark app as ready and process any pending deep links + isAppReady = true; - // Process queued deep links - if (pendingDeepLinks.length > 0 && globalState) { + // Process queued deep links + if (pendingDeepLinks.length > 0 && globalState) { console.log("Processing", pendingDeepLinks.length, "queued deep links"); - for (const deepLink of pendingDeepLinks) { - try { - handleDeepLink(deepLink); - } catch (error) { - console.error("Error processing queued deep link:", error); + for (const deepLink of pendingDeepLinks) { + try { + handleDeepLink(deepLink); + } catch (error) { + console.error("Error processing queued deep link:", error); + } } + pendingDeepLinks = []; } - pendingDeepLinks = []; - } -}); + }); -// Cleanup global event listeners -onDestroy(() => { - if (typeof globalDeepLinkHandler !== "undefined") { + // Cleanup global event listeners + onDestroy(() => { + if (typeof globalDeepLinkHandler !== "undefined") { window.removeEventListener("deepLinkReceived", globalDeepLinkHandler); - } -}); + } + }); -const safeAreaTop = $derived.by( - () => - Number.parseFloat( - getComputedStyle(document.documentElement).getPropertyValue( - "--safe-top", - ), - ) || 0, -); + const safeAreaTop = $derived.by( + () => + Number.parseFloat( + getComputedStyle(document.documentElement).getPropertyValue( + "--safe-top", + ), + ) || 0, + ); -$effect(() => console.log("top", safeAreaTop)); + $effect(() => console.log("top", safeAreaTop)); -onNavigate((navigation) => { - if (!document.startViewTransition) return; + onNavigate((navigation) => { + if (!document.startViewTransition) return; - const from = navigation.from?.url.pathname; - const to = navigation.to?.url.pathname; + const from = navigation.from?.url.pathname; + const to = navigation.to?.url.pathname; - if (!from || !to || from === to) return; + if (!from || !to || from === to) return; - let direction: "left" | "right" = "right"; + let direction: "left" | "right" = "right"; - const fromIndex = navigationStack.lastIndexOf(from); - const toIndex = navigationStack.lastIndexOf(to); + const fromIndex = navigationStack.lastIndexOf(from); + const toIndex = navigationStack.lastIndexOf(to); - if (toIndex !== -1 && toIndex < fromIndex) { - // Backward navigation - direction = "left"; - navigationStack = navigationStack.slice(0, toIndex + 1); - } else { - // Forward navigation (or new path) - direction = "right"; - navigationStack.push(to); - } + if (toIndex !== -1 && toIndex < fromIndex) { + // Backward navigation + direction = "left"; + navigationStack = navigationStack.slice(0, toIndex + 1); + } else { + // Forward navigation (or new path) + direction = "right"; + navigationStack.push(to); + } - document.documentElement.setAttribute("data-transition", direction); - previousRoute = to; + document.documentElement.setAttribute("data-transition", direction); + previousRoute = to; - return new Promise((resolve) => { - document.startViewTransition(async () => { - resolve(); - await navigation.complete; + return new Promise((resolve) => { + document.startViewTransition(async () => { + resolve(); + await navigation.complete; + }); }); }); -}); -$effect(() => { - if (mainWrapper) { - swipedetect(mainWrapper, (dir: string) => { - if (dir === "right") window.history.back(); - }); - } -}); + $effect(() => { + if (mainWrapper) { + swipedetect(mainWrapper, (dir: string) => { + if (dir === "right") window.history.back(); + }); + } + }); {#if showSplashScreen} @@ -681,7 +681,7 @@ $effect(() => { >
{#if children} {@render children()} From 0184932f41813ea107157487f3b03ecfd980507a Mon Sep 17 00:00:00 2001 From: Bekiboo Date: Fri, 30 Jan 2026 19:54:09 +0300 Subject: [PATCH 2/2] format --- .../src/routes/(app)/main/+page.svelte | 178 +-- .../routes/(app)/settings/pin/+page.svelte | 94 +- .../src/routes/(auth)/login/+page.svelte | 402 +++--- .../src/routes/(auth)/onboarding/+page.svelte | 492 ++++---- .../src/routes/(auth)/register/+page.svelte | 170 +-- .../src/routes/(auth)/review/+page.svelte | 30 +- .../src/routes/(auth)/verify/+page.svelte | 656 +++++----- .../eid-wallet/src/routes/+layout.svelte | 1076 ++++++++--------- 8 files changed, 1545 insertions(+), 1553 deletions(-) diff --git a/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte b/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte index 8d89a4fea..3b8387df8 100644 --- a/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte @@ -1,101 +1,101 @@ {#if profileCreationStatus === "loading"} diff --git a/infrastructure/eid-wallet/src/routes/(app)/settings/pin/+page.svelte b/infrastructure/eid-wallet/src/routes/(app)/settings/pin/+page.svelte index 4ec57e4ca..47462eba3 100644 --- a/infrastructure/eid-wallet/src/routes/(app)/settings/pin/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(app)/settings/pin/+page.svelte @@ -1,59 +1,59 @@
- import { goto } from "$app/navigation"; - import { Hero } from "$lib/fragments"; - import type { GlobalState } from "$lib/global"; - import { Drawer, InputPin } from "$lib/ui"; - import * as Button from "$lib/ui/Button"; - import { - type AuthOptions, - authenticate, - checkStatus, - } from "@tauri-apps/plugin-biometric"; - import { getContext, onMount } from "svelte"; +import { goto } from "$app/navigation"; +import { Hero } from "$lib/fragments"; +import type { GlobalState } from "$lib/global"; +import { Drawer, InputPin } from "$lib/ui"; +import * as Button from "$lib/ui/Button"; +import { + type AuthOptions, + authenticate, + checkStatus, +} from "@tauri-apps/plugin-biometric"; +import { getContext, onMount } from "svelte"; - let pin = $state(""); - let isError = $state(false); - let isPostAuthLoading = $state(false); - let clearPin = $state(async () => {}); - let handlePinInput = $state((pin: string) => {}); - let globalState: GlobalState | undefined = $state(undefined); - let hasPendingDeepLink = $state(false); - let isDeletedVaultModalOpen = $state(false); +let pin = $state(""); +let isError = $state(false); +let isPostAuthLoading = $state(false); +let clearPin = $state(async () => {}); +let handlePinInput = $state((pin: string) => {}); +let globalState: GlobalState | undefined = $state(undefined); +let hasPendingDeepLink = $state(false); +let isDeletedVaultModalOpen = $state(false); - const authOpts: AuthOptions = { - allowDeviceCredential: false, +const authOpts: AuthOptions = { + allowDeviceCredential: false, - cancelTitle: "Cancel", + cancelTitle: "Cancel", - // iOS - fallbackTitle: "Please enter your PIN", + // iOS + fallbackTitle: "Please enter your PIN", - // Android - title: "Login", - subtitle: "Please authenticate to continue", - confirmationRequired: true, - }; + // Android + title: "Login", + subtitle: "Please authenticate to continue", + confirmationRequired: true, +}; + +const getGlobalState = getContext<() => GlobalState>("globalState"); +const setGlobalState = + getContext<(value: GlobalState) => void>("setGlobalState"); - const getGlobalState = getContext<() => GlobalState>("globalState"); - const setGlobalState = - getContext<(value: GlobalState) => void>("setGlobalState"); +async function nukeWallet() { + if (!globalState) return; + const newGlobalState = await globalState.reset(); + setGlobalState(newGlobalState); + globalState = newGlobalState; + isDeletedVaultModalOpen = false; + await goto("/onboarding"); +} - async function nukeWallet() { - if (!globalState) return; - const newGlobalState = await globalState.reset(); - setGlobalState(newGlobalState); - globalState = newGlobalState; - isDeletedVaultModalOpen = false; - await goto("/onboarding"); +onMount(async () => { + globalState = getContext<() => GlobalState>("globalState")(); + if (!globalState) { + console.error("Global state is not defined"); + await goto("/"); // Redirect to home or error page + return; } - onMount(async () => { - globalState = getContext<() => GlobalState>("globalState")(); - if (!globalState) { - console.error("Global state is not defined"); - await goto("/"); // Redirect to home or error page - return; - } + // Check if there's a pending deep link + const pendingDeepLink = sessionStorage.getItem("pendingDeepLink"); + hasPendingDeepLink = !!pendingDeepLink; + if (hasPendingDeepLink) { + console.log("Pending deep link detected on login page"); + } - // Check if there's a pending deep link - const pendingDeepLink = sessionStorage.getItem("pendingDeepLink"); - hasPendingDeepLink = !!pendingDeepLink; - if (hasPendingDeepLink) { - console.log("Pending deep link detected on login page"); - } + clearPin = async () => { + pin = ""; + isError = false; + if (isPostAuthLoading) return; + }; - clearPin = async () => { - pin = ""; + handlePinInput = async (pin: string) => { + if (isPostAuthLoading) return; + if (pin.length === 4) { isError = false; - if (isPostAuthLoading) return; - }; - - handlePinInput = async (pin: string) => { - if (isPostAuthLoading) return; - if (pin.length === 4) { - isError = false; - isPostAuthLoading = true; - const check = globalState - ? await globalState.securityController.verifyPin(pin) - : false; - if (!check) { - isError = true; - isPostAuthLoading = false; - return; - } - - // Check eVault health after successful login - try { - const vault = await globalState?.vaultController.vault; - if (vault?.ename && globalState) { - const healthCheck = - await globalState.vaultController.checkHealth( - vault.ename, - ); - if (!healthCheck.healthy) { - console.warn( - "eVault health check failed:", - healthCheck.error, - ); + isPostAuthLoading = true; + const check = globalState + ? await globalState.securityController.verifyPin(pin) + : false; + if (!check) { + isError = true; + isPostAuthLoading = false; + return; + } - // If eVault was deleted (404), show modal - if (healthCheck.deleted) { - isDeletedVaultModalOpen = true; - return; // Don't continue to app - } - // For other errors, continue to app - non-blocking - } + // Check eVault health after successful login + try { + const vault = await globalState?.vaultController.vault; + if (vault?.ename && globalState) { + const healthCheck = + await globalState.vaultController.checkHealth( + vault.ename, + ); + if (!healthCheck.healthy) { + console.warn( + "eVault health check failed:", + healthCheck.error, + ); - // Sync public key to eVault core - try { - await globalState.vaultController.syncPublicKey( - vault.ename, - ); - } catch (error) { - console.error("Error syncing public key:", error); - // Continue to app even if sync fails - non-blocking + // If eVault was deleted (404), show modal + if (healthCheck.deleted) { + isDeletedVaultModalOpen = true; + return; // Don't continue to app } + // For other errors, continue to app - non-blocking } - } catch (error) { - console.error("Error during eVault health check:", error); - // Continue to app even if health check fails - non-blocking - } - // Check if there's a pending deep link to process - const pendingDeepLink = - sessionStorage.getItem("pendingDeepLink"); - if (pendingDeepLink) { + // Sync public key to eVault core try { - const deepLinkData = JSON.parse(pendingDeepLink); - console.log( - "Processing pending deep link after login:", - deepLinkData, + await globalState.vaultController.syncPublicKey( + vault.ename, ); - - // Store the deep link data for the scan page - sessionStorage.setItem("deepLinkData", pendingDeepLink); - // Clear the pending deep link - sessionStorage.removeItem("pendingDeepLink"); - - // Redirect to scan page to process the deep link - await goto("/scan-qr"); - return; } catch (error) { - console.error( - "Error processing pending deep link:", - error, - ); - sessionStorage.removeItem("pendingDeepLink"); + console.error("Error syncing public key:", error); + // Continue to app even if sync fails - non-blocking } } + } catch (error) { + console.error("Error during eVault health check:", error); + // Continue to app even if health check fails - non-blocking + } - // No pending deep link, go to main page - await goto("/main"); - return; + // Check if there's a pending deep link to process + const pendingDeepLink = sessionStorage.getItem("pendingDeepLink"); + if (pendingDeepLink) { + try { + const deepLinkData = JSON.parse(pendingDeepLink); + console.log( + "Processing pending deep link after login:", + deepLinkData, + ); + + // Store the deep link data for the scan page + sessionStorage.setItem("deepLinkData", pendingDeepLink); + // Clear the pending deep link + sessionStorage.removeItem("pendingDeepLink"); + + // Redirect to scan page to process the deep link + await goto("/scan-qr"); + return; + } catch (error) { + console.error("Error processing pending deep link:", error); + sessionStorage.removeItem("pendingDeepLink"); + } } - isPostAuthLoading = false; - }; - // for some reason it's important for this to be done before the biometric stuff - // otherwise pin doesn't work - $effect(() => { - handlePinInput(pin); - }); + // No pending deep link, go to main page + await goto("/main"); + return; + } + isPostAuthLoading = false; + }; - if ( - (await globalState.securityController.biometricSupport) && - (await checkStatus()).isAvailable - ) { - try { - await authenticate( - "You must authenticate with PIN first", - authOpts, - ); - isPostAuthLoading = true; + // for some reason it's important for this to be done before the biometric stuff + // otherwise pin doesn't work + $effect(() => { + handlePinInput(pin); + }); - // Check eVault health after successful biometric login - try { - const vault = await globalState.vaultController.vault; - if (vault?.ename) { - const healthCheck = - await globalState.vaultController.checkHealth( - vault.ename, - ); - if (!healthCheck.healthy) { - console.warn( - "eVault health check failed:", - healthCheck.error, - ); + if ( + (await globalState.securityController.biometricSupport) && + (await checkStatus()).isAvailable + ) { + try { + await authenticate( + "You must authenticate with PIN first", + authOpts, + ); + isPostAuthLoading = true; - // If eVault was deleted (404), show modal - if (healthCheck.deleted) { - isDeletedVaultModalOpen = true; - return; // Don't continue to app - } - // For other errors, continue to app - non-blocking - } + // Check eVault health after successful biometric login + try { + const vault = await globalState.vaultController.vault; + if (vault?.ename) { + const healthCheck = + await globalState.vaultController.checkHealth( + vault.ename, + ); + if (!healthCheck.healthy) { + console.warn( + "eVault health check failed:", + healthCheck.error, + ); - // Sync public key to eVault core - try { - await globalState.vaultController.syncPublicKey( - vault.ename, - ); - } catch (error) { - console.error("Error syncing public key:", error); - // Continue to app even if sync fails - non-blocking + // If eVault was deleted (404), show modal + if (healthCheck.deleted) { + isDeletedVaultModalOpen = true; + return; // Don't continue to app } + // For other errors, continue to app - non-blocking } - } catch (error) { - console.error("Error during eVault health check:", error); - // Continue to app even if health check fails - non-blocking - } - // Check if there's a pending deep link to process - const pendingDeepLink = - sessionStorage.getItem("pendingDeepLink"); - if (pendingDeepLink) { + // Sync public key to eVault core try { - const deepLinkData = JSON.parse(pendingDeepLink); - console.log( - "Processing pending deep link after biometric login:", - deepLinkData, + await globalState.vaultController.syncPublicKey( + vault.ename, ); - - // Store the deep link data for the scan page - sessionStorage.setItem("deepLinkData", pendingDeepLink); - // Clear the pending deep link - sessionStorage.removeItem("pendingDeepLink"); - - // Redirect to scan page to process the deep link - await goto("/scan-qr"); - return; } catch (error) { - console.error( - "Error processing pending deep link:", - error, - ); - sessionStorage.removeItem("pendingDeepLink"); + console.error("Error syncing public key:", error); + // Continue to app even if sync fails - non-blocking } } + } catch (error) { + console.error("Error during eVault health check:", error); + // Continue to app even if health check fails - non-blocking + } - // No pending deep link, go to main page - await goto("/main"); - } catch (e) { - console.error("Biometric authentication failed", e); - isPostAuthLoading = false; + // Check if there's a pending deep link to process + const pendingDeepLink = sessionStorage.getItem("pendingDeepLink"); + if (pendingDeepLink) { + try { + const deepLinkData = JSON.parse(pendingDeepLink); + console.log( + "Processing pending deep link after biometric login:", + deepLinkData, + ); + + // Store the deep link data for the scan page + sessionStorage.setItem("deepLinkData", pendingDeepLink); + // Clear the pending deep link + sessionStorage.removeItem("pendingDeepLink"); + + // Redirect to scan page to process the deep link + await goto("/scan-qr"); + return; + } catch (error) { + console.error("Error processing pending deep link:", error); + sessionStorage.removeItem("pendingDeepLink"); + } } + + // No pending deep link, go to main page + await goto("/main"); + } catch (e) { + console.error("Biometric authentication failed", e); + isPostAuthLoading = false; } - }); + } +});
diff --git a/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte b/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte index 72692d7ce..3d00d7877 100644 --- a/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte +++ b/infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte @@ -1,282 +1,282 @@
- import { goto } from "$app/navigation"; - import { Hero } from "$lib/fragments"; - import type { GlobalState } from "$lib/global"; - import { ButtonAction, InputPin } from "$lib/ui"; - import { CircleLock01Icon, FaceIdIcon } from "@hugeicons/core-free-icons"; - import { HugeiconsIcon } from "@hugeicons/svelte"; - import { checkStatus } from "@tauri-apps/plugin-biometric"; - import { getContext, onMount } from "svelte"; +import { goto } from "$app/navigation"; +import { Hero } from "$lib/fragments"; +import type { GlobalState } from "$lib/global"; +import { ButtonAction, InputPin } from "$lib/ui"; +import { CircleLock01Icon, FaceIdIcon } from "@hugeicons/core-free-icons"; +import { HugeiconsIcon } from "@hugeicons/svelte"; +import { checkStatus } from "@tauri-apps/plugin-biometric"; +import { getContext, onMount } from "svelte"; - type Step = "CREATE" | "REPEAT" | "PIN_DONE" | "BIOMETRICS" | "ALL_SET"; - let currentStep = $state("CREATE"); +type Step = "CREATE" | "REPEAT" | "PIN_DONE" | "BIOMETRICS" | "ALL_SET"; +let currentStep = $state("CREATE"); - let pin = $state(""); - let repeatPin = $state(""); - let isBiometricsAvailable = $state(false); - let isError = $state(false); - let btnVariant = $state<"soft" | "solid">("soft"); - let globalState: GlobalState | undefined = $state(undefined); +let pin = $state(""); +let repeatPin = $state(""); +let isBiometricsAvailable = $state(false); +let isError = $state(false); +let btnVariant = $state<"soft" | "solid">("soft"); +let globalState: GlobalState | undefined = $state(undefined); - const handleBack = () => { - if (currentStep === "REPEAT") { - currentStep = "CREATE"; - repeatPin = ""; - } else if (currentStep === "PIN_DONE") { - currentStep = "REPEAT"; - } else if (currentStep === "BIOMETRICS") { - currentStep = "PIN_DONE"; - } else if (currentStep === "ALL_SET") { - currentStep = "BIOMETRICS"; - } else { - goto("/onboarding"); - } - }; +const handleBack = () => { + if (currentStep === "REPEAT") { + currentStep = "CREATE"; + repeatPin = ""; + } else if (currentStep === "PIN_DONE") { + currentStep = "REPEAT"; + } else if (currentStep === "BIOMETRICS") { + currentStep = "PIN_DONE"; + } else if (currentStep === "ALL_SET") { + currentStep = "BIOMETRICS"; + } else { + goto("/onboarding"); + } +}; - const handleConfirmFirst = () => { - if (pin.length === 4) currentStep = "REPEAT"; - }; +const handleConfirmFirst = () => { + if (pin.length === 4) currentStep = "REPEAT"; +}; - const handleConfirmRepeat = async () => { - if (repeatPin.length !== 4) return; +const handleConfirmRepeat = async () => { + if (repeatPin.length !== 4) return; - if (pin !== repeatPin) { - isError = true; - currentStep = "CREATE"; - pin = ""; - repeatPin = ""; - return; - } + if (pin !== repeatPin) { + isError = true; + currentStep = "CREATE"; + pin = ""; + repeatPin = ""; + return; + } - if (!globalState) { + if (!globalState) { console.error("Global state not available; cannot set onboarding PIN."); - isError = true; - currentStep = "CREATE"; - pin = ""; - repeatPin = ""; - return; - } + isError = true; + currentStep = "CREATE"; + pin = ""; + repeatPin = ""; + return; + } - try { - isError = false; + try { + isError = false; await globalState?.securityController.setOnboardingPin(pin, repeatPin); - currentStep = "PIN_DONE"; - } catch (error) { - console.error("Failed to update PIN:", error); - currentStep = "CREATE"; - pin = ""; - repeatPin = ""; - isError = true; - } - }; + currentStep = "PIN_DONE"; + } catch (error) { + console.error("Failed to update PIN:", error); + currentStep = "CREATE"; + pin = ""; + repeatPin = ""; + isError = true; + } +}; - const handleSetupBiometrics = async () => { - if (isBiometricsAvailable && globalState) { - globalState.securityController.biometricSupport = true; - currentStep = "ALL_SET"; - } - }; +const handleSetupBiometrics = async () => { + if (isBiometricsAvailable && globalState) { + globalState.securityController.biometricSupport = true; + currentStep = "ALL_SET"; + } +}; - const finishOnboarding = () => { - if (!globalState) return goto("/onboarding"); - globalState.isOnboardingComplete = true; - goto("/review"); - }; +const finishOnboarding = () => { + if (!globalState) return goto("/onboarding"); + globalState.isOnboardingComplete = true; + goto("/review"); +}; - $effect(() => { - if (currentStep === "CREATE") - btnVariant = pin.length === 4 ? "solid" : "soft"; - else if (currentStep === "REPEAT") - btnVariant = repeatPin.length === 4 ? "solid" : "soft"; - }); +$effect(() => { + if (currentStep === "CREATE") + btnVariant = pin.length === 4 ? "solid" : "soft"; + else if (currentStep === "REPEAT") + btnVariant = repeatPin.length === 4 ? "solid" : "soft"; +}); - onMount(async () => { - globalState = getContext<() => GlobalState>("globalState")(); - try { - isBiometricsAvailable = (await checkStatus()).isAvailable; - } catch (e) { - isBiometricsAvailable = false; - } - }); +onMount(async () => { + globalState = getContext<() => GlobalState>("globalState")(); + try { + isBiometricsAvailable = (await checkStatus()).isAvailable; + } catch (e) { + isBiometricsAvailable = false; + } +});
- import { goto } from "$app/navigation"; - import { Hero, IdentityCard } from "$lib/fragments"; - import type { GlobalState } from "$lib/global"; - import { ButtonAction } from "$lib/ui"; - import axios from "axios"; - import { getContext, onMount } from "svelte"; +import { goto } from "$app/navigation"; +import { Hero, IdentityCard } from "$lib/fragments"; +import type { GlobalState } from "$lib/global"; +import { ButtonAction } from "$lib/ui"; +import axios from "axios"; +import { getContext, onMount } from "svelte"; - let globalState = getContext<() => GlobalState>("globalState")(); - let ename = $state(); +let globalState = getContext<() => GlobalState>("globalState")(); +let ename = $state(); - const handleNext = async () => { - await goto("/e-passport"); - }; +const handleNext = async () => { + await goto("/e-passport"); +}; - onMount(async () => { - const vault = await globalState.vaultController.vault; - ename = vault?.ename; - }); +onMount(async () => { + const vault = await globalState.vaultController.vault; + ename = vault?.ename; +});
- import { goto } from "$app/navigation"; - import { - PUBLIC_PROVISIONER_URL, - PUBLIC_REGISTRY_URL, - } from "$env/static/public"; - import type { KeyManager } from "$lib/crypto"; - import { Hero } from "$lib/fragments"; - import { GlobalState } from "$lib/global"; - import type { KeyServiceContext } from "$lib/global"; - import { ButtonAction } from "$lib/ui"; - import { capitalize } from "$lib/utils"; - import { ArrowLeft01Icon } from "@hugeicons/core-free-icons"; - import { HugeiconsIcon } from "@hugeicons/svelte"; - import axios from "axios"; - import { getContext, onDestroy, onMount } from "svelte"; - import { Shadow } from "svelte-loading-spinners"; - import { v4 as uuidv4 } from "uuid"; - import DocumentType from "./steps/document-type.svelte"; - import Passport from "./steps/passport.svelte"; - import Selfie from "./steps/selfie.svelte"; - import { - DocBack, - DocFront, - Selfie as SelfiePic, - reason, - status, - verifStep, - verificaitonId, - } from "./store"; +import { goto } from "$app/navigation"; +import { + PUBLIC_PROVISIONER_URL, + PUBLIC_REGISTRY_URL, +} from "$env/static/public"; +import type { KeyManager } from "$lib/crypto"; +import { Hero } from "$lib/fragments"; +import { GlobalState } from "$lib/global"; +import type { KeyServiceContext } from "$lib/global"; +import { ButtonAction } from "$lib/ui"; +import { capitalize } from "$lib/utils"; +import { ArrowLeft01Icon } from "@hugeicons/core-free-icons"; +import { HugeiconsIcon } from "@hugeicons/svelte"; +import axios from "axios"; +import { getContext, onDestroy, onMount } from "svelte"; +import { Shadow } from "svelte-loading-spinners"; +import { v4 as uuidv4 } from "uuid"; +import DocumentType from "./steps/document-type.svelte"; +import Passport from "./steps/passport.svelte"; +import Selfie from "./steps/selfie.svelte"; +import { + DocBack, + DocFront, + Selfie as SelfiePic, + reason, + status, + verifStep, + verificaitonId, +} from "./store"; - type Document = { - country: { value: string }; - firstIssue: Date; - licenseNumber: string; - number: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - placeOfIssue: string; - processNumber: string; - residencePermitType: string; - type: { value: string }; - validFrom: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - validUntil: { - confidenceCategory: string; - value: string; - sources: string[]; - }; +type Document = { + country: { value: string }; + firstIssue: Date; + licenseNumber: string; + number: { + confidenceCategory: string; + value: string; + sources: string[]; }; - - type Person = { - address: { - confidenceCategory: string; - value: string; - components: Record; - sources: string[]; - }; - dateOfBirth: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - employer: string; - extraNames: string; - firstName: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - foreignerStatus: string; - gender: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - idNumber: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - lastName: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - nationality: { - confidenceCategory: string; - value: string; - sources: string[]; - }; - occupation: string; - placeOfBirth: string; + placeOfIssue: string; + processNumber: string; + residencePermitType: string; + type: { value: string }; + validFrom: { + confidenceCategory: string; + value: string; + sources: string[]; + }; + validUntil: { + confidenceCategory: string; + value: string; + sources: string[]; }; +}; - let globalState: GlobalState | undefined = $state(undefined); - let showVeriffModal = $state(false); - let person: Person; - let document: Document; - let loading = $state(false); - let keyManager: KeyManager | null = $state(null); - let websocketData: { w3id?: string } | null = $state(null); // Store websocket data for duplicate case - let hardwareKeySupported = $state(false); - let hardwareKeyCheckComplete = $state(false); - const KEY_ID = "default"; +type Person = { + address: { + confidenceCategory: string; + value: string; + components: Record; + sources: string[]; + }; + dateOfBirth: { + confidenceCategory: string; + value: string; + sources: string[]; + }; + employer: string; + extraNames: string; + firstName: { + confidenceCategory: string; + value: string; + sources: string[]; + }; + foreignerStatus: string; + gender: { + confidenceCategory: string; + value: string; + sources: string[]; + }; + idNumber: { + confidenceCategory: string; + value: string; + sources: string[]; + }; + lastName: { + confidenceCategory: string; + value: string; + sources: string[]; + }; + nationality: { + confidenceCategory: string; + value: string; + sources: string[]; + }; + occupation: string; + placeOfBirth: string; +}; - async function handleVerification() { - try { - // Ensure keys are initialized before starting verification - if (!keyManager) { - try { - await initializeKeyManager(); - await ensureKeyForVerification(); - } catch (keyError) { - console.error("Failed to initialize keys:", keyError); - // If key initialization fails, go back to onboarding - await goto("/onboarding"); - return; - } - } +let globalState: GlobalState | undefined = $state(undefined); +let showVeriffModal = $state(false); +let person: Person; +let document: Document; +let loading = $state(false); +let keyManager: KeyManager | null = $state(null); +let websocketData: { w3id?: string } | null = $state(null); // Store websocket data for duplicate case +let hardwareKeySupported = $state(false); +let hardwareKeyCheckComplete = $state(false); +const KEY_ID = "default"; - const { data } = await axios.post( - new URL("/verification", PUBLIC_PROVISIONER_URL).toString(), - ); - verificaitonId.set(data.id); - showVeriffModal = true; - watchEventStream(data.id); - } catch (error) { - console.error("Failed to start verification:", error); - // If verification fails due to key issues or any initialization error, go back to onboarding - const errorMessage = - error instanceof Error - ? error.message.toLowerCase() - : String(error).toLowerCase(); - if ( - errorMessage.includes("key") || - errorMessage.includes("initialize") || - errorMessage.includes("manager") - ) { +async function handleVerification() { + try { + // Ensure keys are initialized before starting verification + if (!keyManager) { + try { + await initializeKeyManager(); + await ensureKeyForVerification(); + } catch (keyError) { + console.error("Failed to initialize keys:", keyError); + // If key initialization fails, go back to onboarding await goto("/onboarding"); + return; } } - } - let eventSource: EventSource | null = $state(null); - function watchEventStream(id: string) { - const sseUrl = new URL( - `/verification/sessions/${id}`, - PUBLIC_PROVISIONER_URL, - ).toString(); - eventSource = new EventSource(sseUrl); - - eventSource.onopen = () => { - console.log("Successfully connected."); - }; - eventSource.onmessage = (e) => { - const data = JSON.parse(e.data as string); - if (!data.status) console.log(data); - console.log("STATUS", data); - status.set(data.status); - reason.set(data.reason); - person = data.person; - document = data.document; - websocketData = data; // Store the full websocket data - if (data.status === "resubmission_requested") { - DocFront.set(null); - DocBack.set(null); - SelfiePic.set(null); - } - verifStep.set(3); - }; - eventSource.onerror = (error) => { - console.error("EventSource error:", error); - eventSource?.close(); - }; + const { data } = await axios.post( + new URL("/verification", PUBLIC_PROVISIONER_URL).toString(), + ); + verificaitonId.set(data.id); + showVeriffModal = true; + watchEventStream(data.id); + } catch (error) { + console.error("Failed to start verification:", error); + // If verification fails due to key issues or any initialization error, go back to onboarding + const errorMessage = + error instanceof Error + ? error.message.toLowerCase() + : String(error).toLowerCase(); + if ( + errorMessage.includes("key") || + errorMessage.includes("initialize") || + errorMessage.includes("manager") + ) { + await goto("/onboarding"); + } } +} +let eventSource: EventSource | null = $state(null); +function watchEventStream(id: string) { + const sseUrl = new URL( + `/verification/sessions/${id}`, + PUBLIC_PROVISIONER_URL, + ).toString(); + eventSource = new EventSource(sseUrl); - function closeEventStream() { - if (eventSource) { - eventSource.close(); - eventSource = null; + eventSource.onopen = () => { + console.log("Successfully connected."); + }; + + eventSource.onmessage = (e) => { + const data = JSON.parse(e.data as string); + if (!data.status) console.log(data); + console.log("STATUS", data); + status.set(data.status); + reason.set(data.reason); + person = data.person; + document = data.document; + websocketData = data; // Store the full websocket data + if (data.status === "resubmission_requested") { + DocFront.set(null); + DocBack.set(null); + SelfiePic.set(null); } - } + verifStep.set(3); + }; + eventSource.onerror = (error) => { + console.error("EventSource error:", error); + eventSource?.close(); + }; +} - function getKeyContext(): KeyServiceContext { - return "verification"; +function closeEventStream() { + if (eventSource) { + eventSource.close(); + eventSource = null; } +} - // Check if hardware key is supported on this device - async function checkHardwareKeySupport() { - try { - if (!globalState) throw new Error("Global state is not defined"); - hardwareKeySupported = - await globalState.keyService.isHardwareAvailable(); - console.log( - `Hardware key ${hardwareKeySupported ? "is" : "is NOT"} supported on this device`, - ); - } catch (error) { - hardwareKeySupported = false; - console.log("Hardware key is NOT supported on this device:", error); - } finally { - hardwareKeyCheckComplete = true; - } +function getKeyContext(): KeyServiceContext { + return "verification"; +} + +// Check if hardware key is supported on this device +async function checkHardwareKeySupport() { + try { + if (!globalState) throw new Error("Global state is not defined"); + hardwareKeySupported = + await globalState.keyService.isHardwareAvailable(); + console.log( + `Hardware key ${hardwareKeySupported ? "is" : "is NOT"} supported on this device`, + ); + } catch (error) { + hardwareKeySupported = false; + console.log("Hardware key is NOT supported on this device:", error); + } finally { + hardwareKeyCheckComplete = true; } +} - // Initialize key manager for verification context - async function initializeKeyManager() { - try { - if (!globalState) throw new Error("Global state is not defined"); - const context = getKeyContext(); +// Initialize key manager for verification context +async function initializeKeyManager() { + try { + if (!globalState) throw new Error("Global state is not defined"); + const context = getKeyContext(); keyManager = await globalState.keyService.getManager(KEY_ID, context); - console.log(`Key manager initialized: ${keyManager.getType()}`); - return keyManager; - } catch (error) { - console.error("Failed to initialize key manager:", error); - throw error; - } + console.log(`Key manager initialized: ${keyManager.getType()}`); + return keyManager; + } catch (error) { + console.error("Failed to initialize key manager:", error); + throw error; } +} - async function ensureKeyForVerification() { - try { - if (!globalState) throw new Error("Global state is not defined"); - const context = getKeyContext(); - const { manager, created } = await globalState.keyService.ensureKey( - KEY_ID, - context, - ); - keyManager = manager; - console.log( - "Key generation result:", - created ? "key-generated" : "key-exists", - ); - return { manager, created }; - } catch (error) { - console.error("Failed to ensure key:", error); - throw error; - } +async function ensureKeyForVerification() { + try { + if (!globalState) throw new Error("Global state is not defined"); + const context = getKeyContext(); + const { manager, created } = await globalState.keyService.ensureKey( + KEY_ID, + context, + ); + keyManager = manager; + console.log( + "Key generation result:", + created ? "key-generated" : "key-exists", + ); + return { manager, created }; + } catch (error) { + console.error("Failed to ensure key:", error); + throw error; } +} - async function getApplicationPublicKey() { - if (!globalState) throw new Error("Global state is not defined"); - if (!keyManager) { - await initializeKeyManager(); - } +async function getApplicationPublicKey() { + if (!globalState) throw new Error("Global state is not defined"); + if (!keyManager) { + await initializeKeyManager(); + } - try { - const context = getKeyContext(); + try { + const context = getKeyContext(); const res = await globalState.keyService.getPublicKey(KEY_ID, context); - console.log("Public key retrieved:", res); - return res; - } catch (e) { - console.error("Public key retrieval failed:", e); - throw e; - } + console.log("Public key retrieved:", res); + return res; + } catch (e) { + console.error("Public key retrieval failed:", e); + throw e; } +} - let handleContinue: () => Promise = $state(async () => {}); +let handleContinue: () => Promise = $state(async () => {}); - onMount(async () => { - globalState = getContext<() => GlobalState>("globalState")(); - // handle verification logic + sec user data in the store +onMount(async () => { + globalState = getContext<() => GlobalState>("globalState")(); + // handle verification logic + sec user data in the store - // Check hardware key support first - await checkHardwareKeySupport(); + // Check hardware key support first + await checkHardwareKeySupport(); - // If hardware is not available, redirect back to onboarding - if (!hardwareKeySupported) { - console.log("Hardware not available, redirecting to onboarding"); - await goto("/onboarding"); - return; - } + // If hardware is not available, redirect back to onboarding + if (!hardwareKeySupported) { + console.log("Hardware not available, redirecting to onboarding"); + await goto("/onboarding"); + return; + } - // Initialize key manager and check if default key pair exists - try { - await initializeKeyManager(); - await ensureKeyForVerification(); - } catch (error) { - console.error("Failed to initialize keys for verification:", error); - // If key initialization fails, redirect back to onboarding - await goto("/onboarding"); - return; - } + // Initialize key manager and check if default key pair exists + try { + await initializeKeyManager(); + await ensureKeyForVerification(); + } catch (error) { + console.error("Failed to initialize keys for verification:", error); + // If key initialization fails, redirect back to onboarding + await goto("/onboarding"); + return; + } - handleContinue = async () => { - if ($status !== "approved" && $status !== "duplicate") - return verifStep.set(0); - if (!globalState) throw new Error("Global state is not defined"); + handleContinue = async () => { + if ($status !== "approved" && $status !== "duplicate") + return verifStep.set(0); + if (!globalState) throw new Error("Global state is not defined"); - loading = true; - globalState.userController.user = { - name: capitalize( - `${person.firstName.value} ${person.lastName.value ?? ""}`, - ), + loading = true; + globalState.userController.user = { + name: capitalize( + `${person.firstName.value} ${person.lastName.value ?? ""}`, + ), "Date of Birth": new Date(person.dateOfBirth.value).toDateString(), - "ID submitted": - document.type.value === "passport" - ? `Passport - ${document.country.value}` - : document.type.value === "drivers_license" - ? `Driving License - ${document.country.value}` - : `ID Card - ${document.country.value}`, - "Document Number": document.number.value, - }; - globalState.userController.document = { - "Valid From": new Date(document.validFrom.value).toDateString(), + "ID submitted": + document.type.value === "passport" + ? `Passport - ${document.country.value}` + : document.type.value === "drivers_license" + ? `Driving License - ${document.country.value}` + : `ID Card - ${document.country.value}`, + "Document Number": document.number.value, + }; + globalState.userController.document = { + "Valid From": new Date(document.validFrom.value).toDateString(), "Valid Until": new Date(document.validUntil.value).toDateString(), - "Verified On": new Date().toDateString(), - }; - globalState.userController.isFake = false; + "Verified On": new Date().toDateString(), + }; + globalState.userController.isFake = false; - if ($status === "duplicate") { - // For duplicate case, skip provision and resolve the existing eVault URI - // The w3id should be provided in the websocket data - const existingW3id = websocketData?.w3id; // This should come from the websocket data - if (!existingW3id) { - throw new Error("No w3id provided for duplicate eVault"); - } + if ($status === "duplicate") { + // For duplicate case, skip provision and resolve the existing eVault URI + // The w3id should be provided in the websocket data + const existingW3id = websocketData?.w3id; // This should come from the websocket data + if (!existingW3id) { + throw new Error("No w3id provided for duplicate eVault"); + } - // Resolve the eVault URI from the registry - const response = await axios.get( - new URL( - `resolve?w3id=${existingW3id}`, - PUBLIC_REGISTRY_URL, - ).toString(), - ); - // Skip profile creation for duplicates by setting status directly - globalState.vaultController.profileCreationStatus = "success"; - // For duplicates, just set the vault without triggering profile creation - // since the eVault already exists with a profile + // Resolve the eVault URI from the registry + const response = await axios.get( + new URL( + `resolve?w3id=${existingW3id}`, + PUBLIC_REGISTRY_URL, + ).toString(), + ); + // Skip profile creation for duplicates by setting status directly + globalState.vaultController.profileCreationStatus = "success"; + // For duplicates, just set the vault without triggering profile creation + // since the eVault already exists with a profile + globalState.vaultController.vault = { + uri: response.data.uri, + ename: existingW3id, + }; + } else { + // Normal flow for approved status + const { + data: { token: registryEntropy }, + } = await axios.get( + new URL("/entropy", PUBLIC_REGISTRY_URL).toString(), + ); + const { data } = await axios.post( + new URL("/provision", PUBLIC_PROVISIONER_URL).toString(), + { + registryEntropy, + namespace: uuidv4(), + verificationId: $verificaitonId, + publicKey: await getApplicationPublicKey(), + }, + ); + if (data.success === true) { + // Set vault in controller - this will trigger profile creation with retry logic globalState.vaultController.vault = { - uri: response.data.uri, - ename: existingW3id, + uri: data.uri, + ename: data.w3id, }; - } else { - // Normal flow for approved status - const { - data: { token: registryEntropy }, - } = await axios.get( - new URL("/entropy", PUBLIC_REGISTRY_URL).toString(), - ); - const { data } = await axios.post( - new URL("/provision", PUBLIC_PROVISIONER_URL).toString(), - { - registryEntropy, - namespace: uuidv4(), - verificationId: $verificaitonId, - publicKey: await getApplicationPublicKey(), - }, - ); - if (data.success === true) { - // Set vault in controller - this will trigger profile creation with retry logic - globalState.vaultController.vault = { - uri: data.uri, - ename: data.w3id, - }; - } } + } - setTimeout(() => { - goto("/register"); - }, 10_000); - }; - }); + setTimeout(() => { + goto("/register"); + }, 10_000); + }; +}); - onDestroy(() => { - closeEventStream(); - }); +onDestroy(() => { + closeEventStream(); +});
- import SplashScreen from "$lib/fragments/SplashScreen/SplashScreen.svelte"; - import { getContext, onDestroy, onMount, setContext } from "svelte"; - import "../app.css"; - import { goto, onNavigate } from "$app/navigation"; - import { GlobalState } from "$lib/global/state"; - - import { runtime } from "$lib/global/runtime.svelte"; - import { swipedetect } from "$lib/utils"; - import { type Status, checkStatus } from "@tauri-apps/plugin-biometric"; - - const { children } = $props(); - - let globalState: GlobalState | undefined = $state(undefined); - - let showSplashScreen = $state(false); - let previousRoute = null; - let navigationStack: string[] = []; - let globalDeepLinkHandler: ((event: Event) => void) | undefined; - let mainWrapper: HTMLElement | undefined = $state(undefined); - let isAppReady = $state(false); - let pendingDeepLinks: string[] = $state([]); - - setContext("globalState", () => globalState); - setContext("setGlobalState", (value: GlobalState | undefined) => { - globalState = value; - }); - - // replace with actual data loading logic - async function loadData() { - await new Promise((resolve) => setTimeout(resolve, 1500)); +import SplashScreen from "$lib/fragments/SplashScreen/SplashScreen.svelte"; +import { getContext, onDestroy, onMount, setContext } from "svelte"; +import "../app.css"; +import { goto, onNavigate } from "$app/navigation"; +import { GlobalState } from "$lib/global/state"; + +import { runtime } from "$lib/global/runtime.svelte"; +import { swipedetect } from "$lib/utils"; +import { type Status, checkStatus } from "@tauri-apps/plugin-biometric"; + +const { children } = $props(); + +let globalState: GlobalState | undefined = $state(undefined); + +let showSplashScreen = $state(false); +let previousRoute = null; +let navigationStack: string[] = []; +let globalDeepLinkHandler: ((event: Event) => void) | undefined; +let mainWrapper: HTMLElement | undefined = $state(undefined); +let isAppReady = $state(false); +let pendingDeepLinks: string[] = $state([]); + +setContext("globalState", () => globalState); +setContext("setGlobalState", (value: GlobalState | undefined) => { + globalState = value; +}); + +// replace with actual data loading logic +async function loadData() { + await new Promise((resolve) => setTimeout(resolve, 1500)); +} + +async function ensureMinimumDelay() { + await new Promise((resolve) => setTimeout(resolve, 500)); +} + +onMount(async () => { + let status: Status | undefined = undefined; + try { + status = await checkStatus(); + } catch (error) { + status = { + biometryType: 0, + isAvailable: false, + }; } - - async function ensureMinimumDelay() { - await new Promise((resolve) => setTimeout(resolve, 500)); + runtime.biometry = status.biometryType; + try { + globalState = await GlobalState.create(); + } catch (error) { + console.error("Failed to initialize global state:", error); + // Consider adding fallback behavior or user notification } - onMount(async () => { - let status: Status | undefined = undefined; - try { - status = await checkStatus(); - } catch (error) { - status = { - biometryType: 0, - isAvailable: false, - }; - } - runtime.biometry = status.biometryType; - try { - globalState = await GlobalState.create(); - } catch (error) { - console.error("Failed to initialize global state:", error); - // Consider adding fallback behavior or user notification - } - - // Handle deep links - try { - const { onOpenUrl, getCurrent } = await import( - "@tauri-apps/plugin-deep-link" - ); - - // Check if app was started via deep link - const initialUrls = await getCurrent(); - if (initialUrls && initialUrls.length > 0) { - if (globalState) { - handleDeepLink(initialUrls[0]); - } else { - pendingDeepLinks = [...pendingDeepLinks, initialUrls[0]]; - } + // Handle deep links + try { + const { onOpenUrl, getCurrent } = await import( + "@tauri-apps/plugin-deep-link" + ); + + // Check if app was started via deep link + const initialUrls = await getCurrent(); + if (initialUrls && initialUrls.length > 0) { + if (globalState) { + handleDeepLink(initialUrls[0]); + } else { + pendingDeepLinks = [...pendingDeepLinks, initialUrls[0]]; } + } - // Listen for future deep links - await onOpenUrl((urls) => { - if (urls && urls.length > 0) { - try { - if (isAppReady && globalState) { - handleDeepLink(urls[0]); - } else { - // Queue deep link if app isn't ready yet - console.log( - "App not ready, queueing deep link:", - urls[0], - ); - pendingDeepLinks = [...pendingDeepLinks, urls[0]]; - } - } catch (error) { - console.error( - "Error handling deep link from onOpenUrl:", - error, + // Listen for future deep links + await onOpenUrl((urls) => { + if (urls && urls.length > 0) { + try { + if (isAppReady && globalState) { + handleDeepLink(urls[0]); + } else { + // Queue deep link if app isn't ready yet + console.log( + "App not ready, queueing deep link:", + urls[0], ); + pendingDeepLinks = [...pendingDeepLinks, urls[0]]; } + } catch (error) { + console.error( + "Error handling deep link from onOpenUrl:", + error, + ); } - }); + } + }); - // Set up global event listener for deep links that arrive when app is already open - // This ensures deep links work even if the scan-qr page isn't mounted yet - globalDeepLinkHandler = (event: Event) => { - try { - const customEvent = event as CustomEvent; + // Set up global event listener for deep links that arrive when app is already open + // This ensures deep links work even if the scan-qr page isn't mounted yet + globalDeepLinkHandler = (event: Event) => { + try { + const customEvent = event as CustomEvent; + console.log( + "Global deep link event received:", + customEvent.detail, + ); + + if (!isAppReady || !globalState) { console.log( - "Global deep link event received:", - customEvent.detail, + "App not ready, storing deep link data for later", ); + sessionStorage.setItem( + "deepLinkData", + JSON.stringify(customEvent.detail), + ); + return; + } - if (!isAppReady || !globalState) { - console.log( - "App not ready, storing deep link data for later", - ); - sessionStorage.setItem( - "deepLinkData", - JSON.stringify(customEvent.detail), - ); - return; - } - - // Check if we're already on the scan page - if (window.location.pathname === "/scan-qr") { - // We're already on the scan page, dispatch the event directly - console.log( - "Already on scan page, dispatching event directly", - ); + // Check if we're already on the scan page + if (window.location.pathname === "/scan-qr") { + // We're already on the scan page, dispatch the event directly + console.log( + "Already on scan page, dispatching event directly", + ); const directEvent = new CustomEvent("deepLinkReceived", { - detail: customEvent.detail, + detail: customEvent.detail, }); - window.dispatchEvent(directEvent); - } else { - // Store the deep link data and navigate to scan page - console.log( - "Not on scan page, storing data and navigating", - ); - sessionStorage.setItem( - "deepLinkData", - JSON.stringify(customEvent.detail), - ); - goto("/scan-qr").catch((error) => { + window.dispatchEvent(directEvent); + } else { + // Store the deep link data and navigate to scan page + console.log( + "Not on scan page, storing data and navigating", + ); + sessionStorage.setItem( + "deepLinkData", + JSON.stringify(customEvent.detail), + ); + goto("/scan-qr").catch((error) => { console.error("Error navigating to scan-qr:", error); - }); - } - } catch (error) { - console.error("Error in globalDeepLinkHandler:", error); + }); } - }; + } catch (error) { + console.error("Error in globalDeepLinkHandler:", error); + } + }; - window.addEventListener("deepLinkReceived", globalDeepLinkHandler); - } catch (error) { - console.error("Failed to initialize deep link listener:", error); - } + window.addEventListener("deepLinkReceived", globalDeepLinkHandler); + } catch (error) { + console.error("Failed to initialize deep link listener:", error); + } - // Helper function to check if user is on an authenticated route - function isAuthenticatedRoute(pathname: string): boolean { - // Authenticated routes are those under (app)/ which are protected by the auth guard - const authenticatedRoutes = ["/main", "/scan-qr", "/settings"]; - return authenticatedRoutes.includes(pathname); - } + // Helper function to check if user is on an authenticated route + function isAuthenticatedRoute(pathname: string): boolean { + // Authenticated routes are those under (app)/ which are protected by the auth guard + const authenticatedRoutes = ["/main", "/scan-qr", "/settings"]; + return authenticatedRoutes.includes(pathname); + } - function handleDeepLink(urlString: string) { - console.log("Deep link received:", urlString); + function handleDeepLink(urlString: string) { + console.log("Deep link received:", urlString); - try { - const url = new URL(urlString); - const path = url.pathname; - const params = url.searchParams; - - console.log("Deep link path:", path); - console.log("Deep link hostname:", url.hostname); - console.log("Deep link protocol:", url.protocol); - console.log("Deep link full URL object:", url); - console.log( - "Deep link params:", - Object.fromEntries(params.entries()), - ); + try { + const url = new URL(urlString); + const path = url.pathname; + const params = url.searchParams; + + console.log("Deep link path:", path); + console.log("Deep link hostname:", url.hostname); + console.log("Deep link protocol:", url.protocol); + console.log("Deep link full URL object:", url); + console.log( + "Deep link params:", + Object.fromEntries(params.entries()), + ); - // Check if we're already on the scan-qr page - const currentPath = window.location.pathname; - const isOnScanPage = currentPath === "/scan-qr"; + // Check if we're already on the scan-qr page + const currentPath = window.location.pathname; + const isOnScanPage = currentPath === "/scan-qr"; const isOnAuthenticatedRoute = isAuthenticatedRoute(currentPath); - console.log( - "Current path:", - currentPath, - "Is on scan page:", - isOnScanPage, - "Is on authenticated route:", - isOnAuthenticatedRoute, - ); + console.log( + "Current path:", + currentPath, + "Is on scan page:", + isOnScanPage, + "Is on authenticated route:", + isOnAuthenticatedRoute, + ); - // For w3ds:// URLs, we need to check the hostname instead of pathname - // w3ds://auth becomes hostname: "auth", pathname: "" - const action = url.hostname || path; - console.log("Deep link action (hostname):", action); + // For w3ds:// URLs, we need to check the hostname instead of pathname + // w3ds://auth becomes hostname: "auth", pathname: "" + const action = url.hostname || path; + console.log("Deep link action (hostname):", action); - // Example: w3ds://auth?session=123&platform=example&redirect=https://example.com - if (action === "auth") { - // Handle authentication deep link - const sessionId = params.get("session"); - const platform = params.get("platform"); - const redirect = params.get("redirect"); + // Example: w3ds://auth?session=123&platform=example&redirect=https://example.com + if (action === "auth") { + // Handle authentication deep link + const sessionId = params.get("session"); + const platform = params.get("platform"); + const redirect = params.get("redirect"); - console.log( - "Auth deep link - session:", - sessionId, - "platform:", - platform, - "redirect:", - redirect, - ); + console.log( + "Auth deep link - session:", + sessionId, + "platform:", + platform, + "redirect:", + redirect, + ); - if (sessionId && platform && redirect) { - // Always store the deep link data first - const deepLinkData = { - type: "auth", - session: sessionId, - platform: platform, - redirect: redirect, - }; - - // Check if user is authenticated by checking if they're on an authenticated route - const checkAuth = async () => { - // First check if user is on an authenticated route - // If not, they need to login first regardless of vault existence - if (!isOnAuthenticatedRoute) { - console.log( - "User not on authenticated route, storing deep link and redirecting to login", - ); - sessionStorage.setItem( - "pendingDeepLink", - JSON.stringify(deepLinkData), + if (sessionId && platform && redirect) { + // Always store the deep link data first + const deepLinkData = { + type: "auth", + session: sessionId, + platform: platform, + redirect: redirect, + }; + + // Check if user is authenticated by checking if they're on an authenticated route + const checkAuth = async () => { + // First check if user is on an authenticated route + // If not, they need to login first regardless of vault existence + if (!isOnAuthenticatedRoute) { + console.log( + "User not on authenticated route, storing deep link and redirecting to login", + ); + sessionStorage.setItem( + "pendingDeepLink", + JSON.stringify(deepLinkData), + ); + goto("/login").catch((error) => { + console.error( + "Error navigating to login:", + error, ); - goto("/login").catch((error) => { - console.error( - "Error navigating to login:", - error, - ); - }); - return; - } + }); + return; + } - try { - // Wait for globalState to be ready if it's not yet - if (!globalState) { - console.log( - "GlobalState not ready, waiting...", - ); - // Wait a bit and retry, or just redirect to login - let retries = 0; - const maxRetries = 10; + try { + // Wait for globalState to be ready if it's not yet + if (!globalState) { + console.log( + "GlobalState not ready, waiting...", + ); + // Wait a bit and retry, or just redirect to login + let retries = 0; + const maxRetries = 10; while (!globalState && retries < maxRetries) { - await new Promise((resolve) => - setTimeout(resolve, 100), - ); - retries++; - } - - if (!globalState) { - console.log( - "GlobalState still not ready, storing deep link and redirecting to login", - ); - sessionStorage.setItem( - "pendingDeepLink", - JSON.stringify(deepLinkData), - ); - goto("/login").catch((error) => { - console.error( - "Error navigating to login:", - error, - ); - }); - return; - } + await new Promise((resolve) => + setTimeout(resolve, 100), + ); + retries++; } - const vault = - await globalState.vaultController.vault; - if (vault) { - // User is authenticated, dispatch event and navigate to scan page + if (!globalState) { console.log( - "User authenticated, dispatching deep link event and navigating to scan-qr", - ); - - // Dispatch a custom event that the scan page can listen to - const deepLinkEvent = new CustomEvent( - "deepLinkReceived", - { - detail: deepLinkData, - }, + "GlobalState still not ready, storing deep link and redirecting to login", ); - window.dispatchEvent(deepLinkEvent); - - // Also store in sessionStorage as backup sessionStorage.setItem( - "deepLinkData", + "pendingDeepLink", JSON.stringify(deepLinkData), ); - - goto("/scan-qr").catch((error) => { + goto("/login").catch((error) => { console.error( - "Error navigating to scan-qr:", + "Error navigating to login:", error, ); }); return; } - } catch (error) { + } + + const vault = + await globalState.vaultController.vault; + if (vault) { + // User is authenticated, dispatch event and navigate to scan page console.log( - "User not authenticated, redirecting to login", - error, + "User authenticated, dispatching deep link event and navigating to scan-qr", + ); + + // Dispatch a custom event that the scan page can listen to + const deepLinkEvent = new CustomEvent( + "deepLinkReceived", + { + detail: deepLinkData, + }, + ); + window.dispatchEvent(deepLinkEvent); + + // Also store in sessionStorage as backup + sessionStorage.setItem( + "deepLinkData", + JSON.stringify(deepLinkData), ); + + goto("/scan-qr").catch((error) => { + console.error( + "Error navigating to scan-qr:", + error, + ); + }); + return; } + } catch (error) { + console.log( + "User not authenticated, redirecting to login", + error, + ); + } + + // User not authenticated, store deep link data and redirect to login + console.log( + "User not authenticated, storing deep link data and redirecting to login", + ); + sessionStorage.setItem( + "pendingDeepLink", + JSON.stringify(deepLinkData), + ); + goto("/login").catch((error) => { + console.error("Error navigating to login:", error); + }); + }; + + checkAuth(); + } else { + console.log("Missing required auth parameters"); + } + } else if (action === "sign") { + // Handle signing deep link + const sessionId = params.get("session"); + const data = params.get("data"); + const redirectUri = params.get("redirect_uri"); + + console.log( + "Sign deep link - session:", + sessionId, + "data:", + data, + "redirect_uri:", + redirectUri, + ); - // User not authenticated, store deep link data and redirect to login + if (sessionId && data && redirectUri) { + // Always store the deep link data first + const deepLinkData = { + type: "sign", + session: sessionId, + data: data, + redirect_uri: redirectUri, + }; + + // Check if user is authenticated by checking if they're on an authenticated route + const checkAuth = async () => { + // First check if user is on an authenticated route + // If not, they need to login first regardless of vault existence + if (!isOnAuthenticatedRoute) { console.log( - "User not authenticated, storing deep link data and redirecting to login", + "User not on authenticated route, storing deep link and redirecting to login", ); sessionStorage.setItem( "pendingDeepLink", JSON.stringify(deepLinkData), ); goto("/login").catch((error) => { - console.error("Error navigating to login:", error); + console.error( + "Error navigating to login:", + error, + ); }); - }; - - checkAuth(); - } else { - console.log("Missing required auth parameters"); - } - } else if (action === "sign") { - // Handle signing deep link - const sessionId = params.get("session"); - const data = params.get("data"); - const redirectUri = params.get("redirect_uri"); - - console.log( - "Sign deep link - session:", - sessionId, - "data:", - data, - "redirect_uri:", - redirectUri, - ); + return; + } - if (sessionId && data && redirectUri) { - // Always store the deep link data first - const deepLinkData = { - type: "sign", - session: sessionId, - data: data, - redirect_uri: redirectUri, - }; - - // Check if user is authenticated by checking if they're on an authenticated route - const checkAuth = async () => { - // First check if user is on an authenticated route - // If not, they need to login first regardless of vault existence - if (!isOnAuthenticatedRoute) { + try { + // Wait for globalState to be ready if it's not yet + if (!globalState) { console.log( - "User not on authenticated route, storing deep link and redirecting to login", - ); - sessionStorage.setItem( - "pendingDeepLink", - JSON.stringify(deepLinkData), + "GlobalState not ready, waiting...", ); - goto("/login").catch((error) => { - console.error( - "Error navigating to login:", - error, - ); - }); - return; - } - - try { - // Wait for globalState to be ready if it's not yet - if (!globalState) { - console.log( - "GlobalState not ready, waiting...", - ); - // Wait a bit and retry, or just redirect to login - let retries = 0; - const maxRetries = 10; + // Wait a bit and retry, or just redirect to login + let retries = 0; + const maxRetries = 10; while (!globalState && retries < maxRetries) { - await new Promise((resolve) => - setTimeout(resolve, 100), - ); - retries++; - } - - if (!globalState) { - console.log( - "GlobalState still not ready, storing deep link and redirecting to login", - ); - sessionStorage.setItem( - "pendingDeepLink", - JSON.stringify(deepLinkData), - ); - goto("/login").catch((error) => { - console.error( - "Error navigating to login:", - error, - ); - }); - return; - } + await new Promise((resolve) => + setTimeout(resolve, 100), + ); + retries++; } - const vault = - await globalState.vaultController.vault; - if (vault) { - // User is authenticated, dispatch event and navigate to scan page + if (!globalState) { console.log( - "User authenticated, dispatching deep link event and navigating to scan-qr", - ); - - // Dispatch a custom event that the scan page can listen to - const deepLinkEvent = new CustomEvent( - "deepLinkReceived", - { - detail: deepLinkData, - }, + "GlobalState still not ready, storing deep link and redirecting to login", ); - window.dispatchEvent(deepLinkEvent); - - // Also store in sessionStorage as backup sessionStorage.setItem( - "deepLinkData", + "pendingDeepLink", JSON.stringify(deepLinkData), ); - - goto("/scan-qr").catch((error) => { + goto("/login").catch((error) => { console.error( - "Error navigating to scan-qr:", + "Error navigating to login:", error, ); }); return; } - } catch (error) { + } + + const vault = + await globalState.vaultController.vault; + if (vault) { + // User is authenticated, dispatch event and navigate to scan page console.log( - "User not authenticated, redirecting to login", - error, + "User authenticated, dispatching deep link event and navigating to scan-qr", ); + + // Dispatch a custom event that the scan page can listen to + const deepLinkEvent = new CustomEvent( + "deepLinkReceived", + { + detail: deepLinkData, + }, + ); + window.dispatchEvent(deepLinkEvent); + + // Also store in sessionStorage as backup + sessionStorage.setItem( + "deepLinkData", + JSON.stringify(deepLinkData), + ); + + goto("/scan-qr").catch((error) => { + console.error( + "Error navigating to scan-qr:", + error, + ); + }); + return; } + } catch (error) { + console.log( + "User not authenticated, redirecting to login", + error, + ); + } - // User not authenticated, store deep link data and redirect to login + // User not authenticated, store deep link data and redirect to login + console.log( + "User not authenticated, storing deep link data and redirecting to login", + ); + sessionStorage.setItem( + "pendingDeepLink", + JSON.stringify(deepLinkData), + ); + goto("/login").catch((error) => { + console.error("Error navigating to login:", error); + }); + }; + + checkAuth(); + } else { + console.log("Missing required signing parameters"); + } + } else if (action === "reveal") { + // Handle reveal deep link + const pollId = params.get("pollId"); + + console.log("Reveal deep link - pollId:", pollId); + + if (pollId) { + // Always store the deep link data first + const deepLinkData = { + type: "reveal", + pollId: pollId, + }; + + // Check if user is authenticated by checking if they're on an authenticated route + const checkAuth = async () => { + // First check if user is on an authenticated route + // If not, they need to login first regardless of vault existence + if (!isOnAuthenticatedRoute) { console.log( - "User not authenticated, storing deep link data and redirecting to login", + "User not on authenticated route, storing deep link and redirecting to login", ); sessionStorage.setItem( "pendingDeepLink", JSON.stringify(deepLinkData), ); goto("/login").catch((error) => { - console.error("Error navigating to login:", error); + console.error( + "Error navigating to login:", + error, + ); }); - }; + return; + } - checkAuth(); - } else { - console.log("Missing required signing parameters"); - } - } else if (action === "reveal") { - // Handle reveal deep link - const pollId = params.get("pollId"); - - console.log("Reveal deep link - pollId:", pollId); - - if (pollId) { - // Always store the deep link data first - const deepLinkData = { - type: "reveal", - pollId: pollId, - }; - - // Check if user is authenticated by checking if they're on an authenticated route - const checkAuth = async () => { - // First check if user is on an authenticated route - // If not, they need to login first regardless of vault existence - if (!isOnAuthenticatedRoute) { + try { + // Wait for globalState to be ready if it's not yet + if (!globalState) { console.log( - "User not on authenticated route, storing deep link and redirecting to login", - ); - sessionStorage.setItem( - "pendingDeepLink", - JSON.stringify(deepLinkData), + "GlobalState not ready, waiting...", ); - goto("/login").catch((error) => { - console.error( - "Error navigating to login:", - error, - ); - }); - return; - } - - try { - // Wait for globalState to be ready if it's not yet - if (!globalState) { - console.log( - "GlobalState not ready, waiting...", - ); - // Wait a bit and retry, or just redirect to login - let retries = 0; - const maxRetries = 10; + // Wait a bit and retry, or just redirect to login + let retries = 0; + const maxRetries = 10; while (!globalState && retries < maxRetries) { - await new Promise((resolve) => - setTimeout(resolve, 100), - ); - retries++; - } - - if (!globalState) { - console.log( - "GlobalState still not ready, storing deep link and redirecting to login", - ); - sessionStorage.setItem( - "pendingDeepLink", - JSON.stringify(deepLinkData), - ); - goto("/login").catch((error) => { - console.error( - "Error navigating to login:", - error, - ); - }); - return; - } + await new Promise((resolve) => + setTimeout(resolve, 100), + ); + retries++; } - const vault = - await globalState.vaultController.vault; - if (vault) { - // User is authenticated, dispatch event and navigate to scan page + if (!globalState) { console.log( - "User authenticated, dispatching deep link event and navigating to scan-qr for reveal", - ); - - // Dispatch a custom event that the scan page can listen to - const deepLinkEvent = new CustomEvent( - "deepLinkReceived", - { - detail: deepLinkData, - }, + "GlobalState still not ready, storing deep link and redirecting to login", ); - window.dispatchEvent(deepLinkEvent); - - // Also store in sessionStorage as backup sessionStorage.setItem( - "deepLinkData", + "pendingDeepLink", JSON.stringify(deepLinkData), ); - - goto("/scan-qr").catch((error) => { + goto("/login").catch((error) => { console.error( - "Error navigating to scan-qr:", + "Error navigating to login:", error, ); }); return; } - } catch (error) { + } + + const vault = + await globalState.vaultController.vault; + if (vault) { + // User is authenticated, dispatch event and navigate to scan page console.log( - "User not authenticated, redirecting to login", - error, + "User authenticated, dispatching deep link event and navigating to scan-qr for reveal", ); - } - // User not authenticated, store deep link data and redirect to login + // Dispatch a custom event that the scan page can listen to + const deepLinkEvent = new CustomEvent( + "deepLinkReceived", + { + detail: deepLinkData, + }, + ); + window.dispatchEvent(deepLinkEvent); + + // Also store in sessionStorage as backup + sessionStorage.setItem( + "deepLinkData", + JSON.stringify(deepLinkData), + ); + + goto("/scan-qr").catch((error) => { + console.error( + "Error navigating to scan-qr:", + error, + ); + }); + return; + } + } catch (error) { console.log( - "User not authenticated, storing reveal deep link data and redirecting to login", - ); - sessionStorage.setItem( - "pendingDeepLink", - JSON.stringify(deepLinkData), + "User not authenticated, redirecting to login", + error, ); - goto("/login").catch((error) => { + } + + // User not authenticated, store deep link data and redirect to login + console.log( + "User not authenticated, storing reveal deep link data and redirecting to login", + ); + sessionStorage.setItem( + "pendingDeepLink", + JSON.stringify(deepLinkData), + ); + goto("/login").catch((error) => { console.error("Error navigating to login:", error); - }); - }; + }); + }; - checkAuth(); - } else { - console.log("Missing required reveal parameters"); - } + checkAuth(); } else { - console.log("Unknown deep link path:", path); + console.log("Missing required reveal parameters"); } - } catch (error) { - console.error("Failed to parse deep link URL:", error); + } else { + console.log("Unknown deep link path:", path); } + } catch (error) { + console.error("Failed to parse deep link URL:", error); } + } - showSplashScreen = true; // Can't set up the original state to true or animation won't start - navigationStack.push(window.location.pathname); + showSplashScreen = true; // Can't set up the original state to true or animation won't start + navigationStack.push(window.location.pathname); - await Promise.all([loadData(), ensureMinimumDelay()]); + await Promise.all([loadData(), ensureMinimumDelay()]); - showSplashScreen = false; + showSplashScreen = false; - // Mark app as ready and process any pending deep links - isAppReady = true; + // Mark app as ready and process any pending deep links + isAppReady = true; - // Process queued deep links - if (pendingDeepLinks.length > 0 && globalState) { + // Process queued deep links + if (pendingDeepLinks.length > 0 && globalState) { console.log("Processing", pendingDeepLinks.length, "queued deep links"); - for (const deepLink of pendingDeepLinks) { - try { - handleDeepLink(deepLink); - } catch (error) { - console.error("Error processing queued deep link:", error); - } + for (const deepLink of pendingDeepLinks) { + try { + handleDeepLink(deepLink); + } catch (error) { + console.error("Error processing queued deep link:", error); } - pendingDeepLinks = []; } - }); + pendingDeepLinks = []; + } +}); - // Cleanup global event listeners - onDestroy(() => { - if (typeof globalDeepLinkHandler !== "undefined") { +// Cleanup global event listeners +onDestroy(() => { + if (typeof globalDeepLinkHandler !== "undefined") { window.removeEventListener("deepLinkReceived", globalDeepLinkHandler); - } - }); + } +}); - const safeAreaTop = $derived.by( - () => - Number.parseFloat( - getComputedStyle(document.documentElement).getPropertyValue( - "--safe-top", - ), - ) || 0, - ); +const safeAreaTop = $derived.by( + () => + Number.parseFloat( + getComputedStyle(document.documentElement).getPropertyValue( + "--safe-top", + ), + ) || 0, +); - $effect(() => console.log("top", safeAreaTop)); +$effect(() => console.log("top", safeAreaTop)); - onNavigate((navigation) => { - if (!document.startViewTransition) return; +onNavigate((navigation) => { + if (!document.startViewTransition) return; - const from = navigation.from?.url.pathname; - const to = navigation.to?.url.pathname; + const from = navigation.from?.url.pathname; + const to = navigation.to?.url.pathname; - if (!from || !to || from === to) return; + if (!from || !to || from === to) return; - let direction: "left" | "right" = "right"; + let direction: "left" | "right" = "right"; - const fromIndex = navigationStack.lastIndexOf(from); - const toIndex = navigationStack.lastIndexOf(to); + const fromIndex = navigationStack.lastIndexOf(from); + const toIndex = navigationStack.lastIndexOf(to); - if (toIndex !== -1 && toIndex < fromIndex) { - // Backward navigation - direction = "left"; - navigationStack = navigationStack.slice(0, toIndex + 1); - } else { - // Forward navigation (or new path) - direction = "right"; - navigationStack.push(to); - } + if (toIndex !== -1 && toIndex < fromIndex) { + // Backward navigation + direction = "left"; + navigationStack = navigationStack.slice(0, toIndex + 1); + } else { + // Forward navigation (or new path) + direction = "right"; + navigationStack.push(to); + } - document.documentElement.setAttribute("data-transition", direction); - previousRoute = to; + document.documentElement.setAttribute("data-transition", direction); + previousRoute = to; - return new Promise((resolve) => { - document.startViewTransition(async () => { - resolve(); - await navigation.complete; - }); + return new Promise((resolve) => { + document.startViewTransition(async () => { + resolve(); + await navigation.complete; }); }); +}); - $effect(() => { - if (mainWrapper) { - swipedetect(mainWrapper, (dir: string) => { - if (dir === "right") window.history.back(); - }); - } - }); +$effect(() => { + if (mainWrapper) { + swipedetect(mainWrapper, (dir: string) => { + if (dir === "right") window.history.back(); + }); + } +}); {#if showSplashScreen}