Skip to content

Conversation

@coodos
Copy link
Contributor

@coodos coodos commented Dec 10, 2025

Description of change

Issue Number

Type of change

  • Update (a change which updates existing functionality)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Toast notifications added and exposed for UI use.
    • Copy button on identity card/eName with clipboard feedback.
  • Bug Fixes

    • Deep-link processing queued until app is ready and authenticated.
    • More robust deeplink/login parsing with delayed re-check fallback.
    • Simplified cancel flows in QR scan modals.
    • Improved duplicate verification detection.
  • Refactor

    • Action areas standardized to two-button (Back + primary) layouts; removed legacy clear-pin UI.
    • Login flow adds a post-auth loading state and spinner.
  • Chores

    • App version bumped to 0.5.0.
    • Removed startup environment logging and streamlined logout flow.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 10, 2025

Walkthrough

Bumps eID Wallet to v0.5.0, adds a Toast component and an eName copy action, queues/guards deep-link processing until app readiness, hardens scan‑QR deep‑link parsing/handling (auth/sign/reveal/poll/blind‑vote), and adjusts several auth UI flows and deeplink-login fallbacks.

Changes

Cohort / File(s) Summary
Version & Config
infrastructure/eid-wallet/package.json, infrastructure/eid-wallet/src-tauri/tauri.conf.json, infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
Bumped app/package/tauri version from 0.4.00.5.0 and updated displayed settings version string.
UI: Toast
infrastructure/eid-wallet/src/lib/ui/Toast/Toast.svelte, infrastructure/eid-wallet/src/lib/ui/Toast/index.ts, infrastructure/eid-wallet/src/lib/ui/index.ts
Adds a Toast Svelte component with auto-dismiss/fade, export barrel, and adds Toast to UI exports.
IdentityCard & Main
infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte, infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
Replace share/view UI with copyBtn?: () => void; add copyEName clipboard handler and show Toast on result; remove previous view/share button flows.
Scan‑QR Pages & Logic
infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte, infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts
Simplify decline handlers (always close + goto /main); add duplicate-request guards, try/catch around deep-link handling, robust parsing/validation for auth/sign/reveal, platform URL resolution, poll fetches, blind‑vote handling, and centralized error logging.
Deep-link Queuing / App Readiness
infrastructure/eid-wallet/src/routes/+layout.svelte
Defer deep-link processing until app readiness/globalState available; add isAppReady, pendingDeepLinks (with sessionStorage backup), auth-route gating, and queued processing after initialization.
Auth Flow UI: Back Buttons & Layouts
infrastructure/eid-wallet/src/routes/(auth)/e-passport/+page.svelte, infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte, infrastructure/eid-wallet/src/routes/(auth)/register/+page.svelte, infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte
Replace single full-width primary actions with two-button rows (Back + primary), add Back navigation, tighten verification/demoName validation, and adjust action layouts.
Home Page Cleanup
infrastructure/eid-wallet/src/routes/+page.svelte
Remove Clear PIN UI control and related inline status text (handler kept).
Login / Auth UX Changes
infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte
Add isPostAuthLoading to prevent re-entry, show loading UI during post-auth flows, and guard PIN/biometric flows.
Platform Deeplink Fallbacks (multiple apps)
platforms/*/src/pages/deeplink-login.tsx, platforms/group-charter-manager/src/app/deeplink-login/page.tsx, platforms/pictique/src/routes/(auth)/deeplink-login/+page.svelte
Add 500ms delayed re-parse fallback from window.location (search/hash) when ename/session/signature missing; re-validate before erroring; strengthen server-response validation and adjust post-auth navigation/reload behavior.
Other client & infra tweaks
platforms/eReputation/..., platforms/dreamSync/..., platforms/eCurrency/..., platforms/blabsy/..., infrastructure/evault-core/src/controllers/VerificationController.ts, platforms/pictique/...
Various deeplink robustness and logging improvements; replace remote logout redirect with local clearAuth() + navigate to /auth; remove env startup logging; change VerificationController lookup to findManyAndCount and detect duplicates from matches.

Sequence Diagram(s)

sequenceDiagram
    participant OS as External deep link
    participant App as App Entrypoint (Tauri/Browser)
    participant Layout as +layout.svelte
    participant Storage as sessionStorage
    participant Scan as scan-qr Page
    participant Logic as scanLogic.ts
    participant UI as Drawers / User UI

    OS->>App: open deep link (payload)
    App->>Layout: emit deep-link event
    Layout->>Layout: if !isAppReady -> enqueue pendingDeepLinks
    Layout->>Storage: persist pendingDeepLinks
    alt app not ready
        Layout-->>App: ack queued
    else app ready
        Layout->>Scan: dispatch deep-link event
        Scan->>Logic: handleDeepLinkData(payload)
        Logic->>Logic: guard duplicate in-flight requests
        alt auth flow
            Logic->>Logic: parse host/session/redirect -> set auth drawer/state
        else sign flow
            Logic->>Logic: validate/resolve platform_url -> fetch poll -> set signing state
        else reveal flow
            Logic->>Logic: validate pollId -> set reveal state
        end
        Logic-->>Scan: open drawer / update UI
        Scan-->>UI: render drawer and user interactions
    end
    Note right of Layout: when isAppReady becomes true, pendingDeepLinks are processed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Key focus areas:
    • infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts — complex parsing, async fetches, platform URL resolution, duplicate-request guards, blind-vote and poll handling.
    • infrastructure/eid-wallet/src/routes/+layout.svelte — deep-link queuing, sessionStorage persistence, and auth-route gating.
    • UI integration — IdentityCard copy prop, main page clipboard/Toast wiring, and Toast component behavior.
    • infrastructure/evault-core/src/controllers/VerificationController.ts — ensure duplicate detection logic correct with findManyAndCount.

Possibly related PRs

Suggested labels

evault-refactor

Suggested reviewers

  • sosweetham
  • grv-saini-20

Poem

🐰 I hopped to v0.5 with a tiny cheer,
I copy names and toast when friends draw near.
Deep links wait tidy in a patient queue,
Drawers open once ready — no duplicates in view.
Carrots, copies, queues — a tidy merge, hooray!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely incomplete, containing only template placeholders without substantive information about the changes, testing approach, or issue number. Fill in all required sections: provide the issue number, describe specific changes (Toast component, copy functionality, UI refactors), explain testing methodology, and check the completion checklist items.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title is vague and generic, using non-descriptive terms like 'Fix/UI ux improvements' that do not convey specific information about the changes. Replace with a more specific title that highlights the main change, such as 'Add Toast component and copy-to-clipboard functionality for eID wallet' or 'Refactor IdentityCard to use copyBtn callback and add Toast notifications'.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/ui-ux-improvements-eid-wallet

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
platforms/blabsy/src/pages/deeplink-login.tsx (1)

111-117: Stricter response validation may require coordinated backend changes

Requiring both data.token and data.user is a good safety check, but it’s a behavior change: any auth endpoint that previously returned only a token will now surface Invalid response from server and block login. Please confirm that all relevant backends already include user, or consider temporarily accepting responses with just token (while logging or tracking missing user) to avoid breaking existing deeplinks during rollout.

infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte (1)

438-456: Clean up CTA button markup and stray >

There is an extra > rendered after the Back button and the button markup is a bit cramped, which can lead to visual glitches.

-                <div class="flex w-full items-center gap-3 pt-4">
-                    {#if $status !== "declined"}
-                        <ButtonAction
-                            variant="soft"
-                            class="flex-1"
-                            callback={() => goto("/onboarding")}>Back</ButtonAction
-                        >
-                        <ButtonAction
-                            class="flex-1"
-                            callback={handleContinue}
-                            color="primary"
-                            >{$status === "approved"
-                                ? "Continue"
-                                : $status === "duplicate"
-                                  ? "Claim old eVault"
-                                  : "Retry"}</ButtonAction
-                        >
-                    {/if}
-                </div>
+                <div class="flex w-full items-center gap-3 pt-4">
+                    {#if $status !== "declined"}
+                        <ButtonAction
+                            variant="soft"
+                            class="flex-1"
+                            callback={() => goto("/onboarding")}
+                        >
+                            Back
+                        </ButtonAction>
+                        <ButtonAction
+                            class="flex-1"
+                            callback={handleContinue}
+                            color="primary"
+                        >
+                            {$status === "approved"
+                                ? "Continue"
+                                : $status === "duplicate"
+                                  ? "Claim old eVault"
+                                  : "Retry"}
+                        </ButtonAction>
+                    {/if}
+                </div>
🧹 Nitpick comments (13)
platforms/group-charter-manager/src/app/deeplink-login/page.tsx (2)

47-63: Consider consolidating redundant parsing attempts.

The fallback logic (lines 49-56) re-parses window.location.search and window.location.hash, but lines 13-38 already extract parameters from these same sources. Both attempts parse identical inputs, creating duplication.

While the defensive approach adds resilience, it increases maintenance complexity. Consider refactoring to a single, more robust parsing pass.

Example consolidation:

-        // Try parsing from search string first
-        let params: URLSearchParams;
-        let searchString = window.location.search;
-
-        // If search is empty, try parsing from hash or full URL
-        if (!searchString || searchString === '') {
-          const hash = window.location.hash;
-          if (hash && hash.includes('?')) {
-            searchString = hash.substring(hash.indexOf('?'));
-          } else {
-            try {
-              const fullUrl = new URL(window.location.href);
-              searchString = fullUrl.search;
-            } catch (e) {
-              // Ignore parsing errors
-            }
-          }
-        }
-
-        // Remove leading ? if present
-        if (searchString.startsWith('?')) {
-          searchString = searchString.substring(1);
-        }
-
-        // Parse the search string
-        params = new URLSearchParams(searchString);
-
-        let ename = params.get('ename');
-        let session = params.get('session');
-        let signature = params.get('signature');
-        const appVersion = params.get('appVersion');
-
-        // Only set error after all parsing attempts have failed
-        // Try additional parsing methods if params are missing
-        if (!ename || !session || !signature) {
-          // Try one more time with window.location directly
-          const directParams = new URLSearchParams(window.location.search);
-          const hashParams = window.location.hash.includes('?') 
-            ? new URLSearchParams(window.location.hash.substring(window.location.hash.indexOf('?') + 1))
-            : null;
-          
-          ename = ename || directParams.get('ename') || hashParams?.get('ename') || null;
-          session = session || directParams.get('session') || hashParams?.get('session') || null;
-          signature = signature || directParams.get('signature') || hashParams?.get('signature') || null;
-          
-          if (!ename || !session || !signature) {
-            setError("Missing required authentication parameters");
-            setIsLoading(false);
-            return;
-          }
-        }
+        // Parse from search and hash in a single pass
+        const searchParams = new URLSearchParams(window.location.search);
+        const hashParams = window.location.hash.includes('?')
+          ? new URLSearchParams(window.location.hash.substring(window.location.hash.indexOf('?') + 1))
+          : new URLSearchParams();
+
+        const ename = searchParams.get('ename') || hashParams.get('ename');
+        const session = searchParams.get('session') || hashParams.get('session');
+        const signature = searchParams.get('signature') || hashParams.get('signature');
+        const appVersion = searchParams.get('appVersion') || hashParams.get('appVersion');
+
+        if (!ename || !session || !signature) {
+          setError("Missing required authentication parameters");
+          setIsLoading(false);
+          return;
+        }

54-56: Optional: Trailing || null is redundant.

The || null at the end of each fallback chain is technically unnecessary—if all previous values are falsy, the result will already be null or undefined. However, it does make the intent explicit, which may aid readability.

platforms/eReputation/client/src/pages/deeplink-login.tsx (1)

39-61: Robust deeplink param fallback; consider tiny polish on defaults/duplication

The new multi-step parsing (initial params, then directParams + hashParams) is sound and should make deeplink handling much more resilient. Optional follow‑ups:

  • You construct URLSearchParams from window.location.search twice (initial params via searchString, then directParams). You could slightly simplify by reusing params as one of the fallback sources, but this is purely cosmetic.
  • Given the PR bumps the app version to 0.5.0, you may want to align the default here:
- const requestBody = { ename, session, signature, appVersion: appVersion || '0.4.0' };
+ const requestBody = { ename, session, signature, appVersion: appVersion || '0.5.0' };

If the '0.4.0' default is intentional for backward-compat behavior, keeping it as-is is fine.

infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts (1)

1235-1248: Consider validating platform URL before opening the drawer.

The drawer is opened at lines 1237 before the platform URL validation at lines 1243-1248. If validation fails, users may briefly see the drawer open before an error state. Moving the UI state changes after validation would provide a smoother experience.

-                            isBlindVotingRequest.set(true);
-                            selectedBlindVoteOption.set(null);
-                            signingDrawerOpen.set(true);
-                            blindVoteError.set(null);
-
                             const platformUrlCandidate =
                                 parsedSigningData?.platformUrl?.trim();

                             if (!platformUrlCandidate) {
                                 const errorMessage =
                                     "Missing platform URL in signing data and no PUBLIC_PLATFORM_URL configured.";
                                 blindVoteError.set(errorMessage);
                                 throw new Error(errorMessage);
                             }

                             let platformUrl: string;
                             try {
                                 // Validate the URL and normalize trailing slashes for consistent API calls.
                                 const validatedUrl = new URL(
                                     platformUrlCandidate,
                                 );
                                 platformUrl = validatedUrl
                                     .toString()
                                     .replace(/\/+$/, "");
                             } catch (error) {
                                 const message =
                                     error instanceof Error
                                         ? error.message
                                         : String(error);
                                 const errorMessage = `Invalid platform URL "${platformUrlCandidate}": ${message}`;
                                 blindVoteError.set(
                                     "Invalid platform URL in signing request.",
                                 );
                                 throw new Error(errorMessage);
                             }
+
+                            // Open drawer only after validation succeeds
+                            isBlindVotingRequest.set(true);
+                            selectedBlindVoteOption.set(null);
+                            blindVoteError.set(null);
+                            signingDrawerOpen.set(true);
platforms/blabsy/src/pages/deeplink-login.tsx (1)

41-82: Robust multi-source param parsing; consider consolidating to reduce duplication

The fallback logic makes the deeplink handling much more resilient, but it now parses window.location.search/hash multiple times (once via searchStringparams, then again as directParams/hashParams). Extracting a small helper (e.g. getAuthParam(name) that tries search + hash once) or reusing the existing params where possible would simplify the flow and make future debugging/maintenance easier without changing behavior.

infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte (1)

143-143: Version string format inconsistency.

The version displays as v0.5.0.0 (4-part) while package.json and tauri.conf.json use 0.5.0 (3-part semver). Consider aligning for consistency:

-            Version v0.5.0.0
+            Version v0.5.0
infrastructure/eid-wallet/src/routes/(auth)/register/+page.svelte (1)

6-11: Remove unused imports.

Button namespace and ArrowLeft01Icon are imported but not used in this file.

-import * as Button from "$lib/ui/Button";
-import {
-    ArrowLeft01Icon,
-    CircleLock01Icon,
-    FaceIdIcon,
-} from "@hugeicons/core-free-icons";
+import { CircleLock01Icon, FaceIdIcon } from "@hugeicons/core-free-icons";
infrastructure/eid-wallet/src/routes/+layout.svelte (2)

237-269: Extract globalState readiness wait logic to reduce duplication.

The retry loop pattern (wait for globalState with polling) is duplicated three times for auth, sign, and reveal actions. Consider extracting it:

async function waitForGlobalState(maxRetries = 10, delayMs = 100): Promise<GlobalState | null> {
    let retries = 0;
    while (!globalState && retries < maxRetries) {
        await new Promise(resolve => setTimeout(resolve, delayMs));
        retries++;
    }
    return globalState ?? null;
}

This would simplify each checkAuth function significantly.


192-325: Significant code duplication across deep link action handlers.

The checkAuth logic for auth (lines 217-320), sign (lines 351-454), and reveal (lines 474-577) actions are nearly identical (~100 lines each). They all:

  1. Check isOnAuthenticatedRoute
  2. Wait for globalState
  3. Check vault authentication
  4. Dispatch events and navigate

Consider refactoring into a shared helper function that accepts the deepLinkData and action-specific parameters. This would reduce ~200 lines of duplicate code and make maintenance easier.

infrastructure/eid-wallet/src/lib/ui/index.ts (1)

5-5: Minor: prefer exporting from the Toast barrel instead of the .svelte file

Since you added lib/ui/Toast/index.ts, it’s a bit cleaner to export from the directory barrel rather than the component file directly.

-export { default as Toast } from "./Toast/Toast.svelte";
+export { default as Toast } from "./Toast";
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (1)

5-6: Copy-to-clipboard + Toast flow is good; consider handling missing eName explicitly

The copyEName + Toast wiring is correct and handles clipboard failures well. One minor UX gap: if ename isn’t loaded yet, the handler returns silently and the user gets no feedback.

You could surface a short error toast in that case:

 async function copyEName() {
-    if (!ename) return;
-    try {
+    if (!ename) {
+        toastMessage = "eName not available yet";
+        showToast = true;
+        return;
+    }
+    try {
         await navigator.clipboard.writeText(ename);
         toastMessage = "eName copied to clipboard!";
         showToast = true;
     } catch (error) {
         console.error("Failed to copy eName:", error);
         toastMessage = "Failed to copy eName";
         showToast = true;
     }
 }

The rest of the integration (IdentityCard copyBtn and conditional <Toast> rendering) looks good.

Also applies to: 27-29, 35-50, 155-160, 219-221

infrastructure/eid-wallet/src/lib/ui/Toast/Toast.svelte (1)

11-24: Enable actual fade-out animation by removing the {#if isVisible} wrapper

Right now {#if isVisible} removes the DOM node as soon as isVisible = false, so the opacity-0 transition never plays and the toast just disappears abruptly.

You can keep the fade behavior and still call onClose after 300ms by dropping the {#if} and relying on the opacity class:

- {#if isVisible}
-    <div
-        {...restProps}
-        class="fixed bottom-[30px] left-1/2 -translate-x-1/2 z-50 w-[90%] bg-primary-500 text-white px-6 py-3 rounded-lg shadow-lg transition-opacity duration-300 {isVisible
-            ? 'opacity-100'
-            : 'opacity-0'}"
-        role="alert"
-    >
-        <div class="flex items-center gap-2">
-            <svg
-                class="w-5 h-5"
-                fill="none"
-                stroke="currentColor"
-                viewBox="0 0 24 24"
-            >
-                <path
-                    stroke-linecap="round"
-                    stroke-linejoin="round"
-                    stroke-width="2"
-                    d="M5 13l4 4L19 7"
-                />
-            </svg>
-            <span class="font-medium">{message}</span>
-        </div>
-    </div>
-{/if}
+<div
+    {...restProps}
+    class="fixed bottom-[30px] left-1/2 -translate-x-1/2 z-50 w-[90%] bg-primary-500 text-white px-6 py-3 rounded-lg shadow-lg transition-opacity duration-300 {isVisible
+        ? 'opacity-100'
+        : 'opacity-0'}"
+    role="alert"
+>
+    <div class="flex items-center gap-2">
+        <svg
+            class="w-5 h-5"
+            fill="none"
+            stroke="currentColor"
+            viewBox="0 0 24 24"
+        >
+            <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M5 13l4 4L19 7"
+            />
+        </svg>
+        <span class="font-medium">{message}</span>
+    </div>
+</div>

With this, the toast fades out over 300ms and then onClose can flip the parent’s showToast to unmount it.

Also applies to: 27-52

infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte (1)

191-199: Pre-verification + demo name flow is sound; you can DRY the readiness check

The new requirement that both verificationId and demoName be present before continuing is correct, and resetting both on failure makes the flow clearer.

The readiness condition is currently duplicated:

  • In handleContinue:
    • guards on !verificationId || !demoName || verificationId.length === 0 || demoName.length === 0
  • In the button props:
    • variant={verificationId.length === 0 || demoName.length === 0 ? "soft" : "solid"}
    • disabled={verificationId.length === 0 || demoName.length === 0}

To avoid future drift, you could centralize this into a helper:

const isPreVerificationReady = () =>
    verificationId.length > 0 && demoName.length > 0;

handleContinue = async () => {
    if (!isPreVerificationReady()) {
        return;
    }
    // ...
};
<ButtonAction
    variant={isPreVerificationReady() ? "solid" : "soft"}
    disabled={!isPreVerificationReady()}
    class="w-full"
    callback={handleContinue}
>
    Next
</ButtonAction>

Functionally you’re in a good place; this just keeps the logic in sync.

Also applies to: 236-239, 362-369, 382-388, 390-393

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 49a865f and 55e054b.

⛔ Files ignored due to path filters (1)
  • infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/app-arm64-release.aab is excluded by !**/gen/**
📒 Files selected for processing (19)
  • infrastructure/eid-wallet/package.json (1 hunks)
  • infrastructure/eid-wallet/src-tauri/tauri.conf.json (1 hunks)
  • infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte (4 hunks)
  • infrastructure/eid-wallet/src/lib/ui/Toast/Toast.svelte (1 hunks)
  • infrastructure/eid-wallet/src/lib/ui/Toast/index.ts (1 hunks)
  • infrastructure/eid-wallet/src/lib/ui/index.ts (1 hunks)
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (4 hunks)
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte (2 hunks)
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts (1 hunks)
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte (2 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/e-passport/+page.svelte (2 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte (5 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/register/+page.svelte (5 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte (2 hunks)
  • infrastructure/eid-wallet/src/routes/+layout.svelte (11 hunks)
  • infrastructure/eid-wallet/src/routes/+page.svelte (0 hunks)
  • platforms/blabsy/src/pages/deeplink-login.tsx (2 hunks)
  • platforms/eReputation/client/src/pages/deeplink-login.tsx (1 hunks)
  • platforms/group-charter-manager/src/app/deeplink-login/page.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • infrastructure/eid-wallet/src/routes/+page.svelte
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-14T17:54:47.719Z
Learnt from: coodos
Repo: MetaState-Prototype-Project/prototype PR: 437
File: infrastructure/eid-wallet/src/lib/global/controllers/security.ts:138-157
Timestamp: 2025-11-14T17:54:47.719Z
Learning: The `setOnboardingPin` method in `infrastructure/eid-wallet/src/lib/global/controllers/security.ts` is intentionally designed to allow overwriting existing PINs without checking if a PIN already exists. This is a workaround for Android keeping stale files around during app reinstallation or onboarding, which causes errors on some devices. Do not suggest adding guards to prevent PIN overwrites in this method.

Applied to files:

  • infrastructure/eid-wallet/src/routes/(auth)/register/+page.svelte
📚 Learning: 2025-11-21T15:49:12.904Z
Learnt from: coodos
Repo: MetaState-Prototype-Project/prototype PR: 458
File: platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts:60-60
Timestamp: 2025-11-21T15:49:12.904Z
Learning: In platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts, the VITE_EREPUTATION_BASE_URL environment variable intentionally has no fallback value. The service should fail if this environment variable is not set, following a fail-fast design pattern.

Applied to files:

  • platforms/eReputation/client/src/pages/deeplink-login.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (18)
platforms/group-charter-manager/src/app/deeplink-login/page.tsx (1)

40-43: LGTM—mutable variables enable fallback parsing.

Changing these to let allows the fallback logic to reassign missing values, which is necessary for the resilient parameter extraction approach.

infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts (4)

1154-1160: Good addition of globalState guard.

The defensive check prevents processing deep links before the application state is ready.


1162-1179: Good safeguards against duplicate in-flight requests.

The guards properly check for active drawers before processing. Note there's a theoretical race condition if two identical deep links arrive simultaneously before the drawer state is set, but this is an edge case and the current implementation is a reasonable improvement.


1181-1203: LGTM - Auth deep link handling is well structured.

Good approach: closing other modals first, handling URL parsing errors gracefully, and marking the request origin.


1324-1345: Good reveal deep link handling with proper validation.

The flow correctly validates the pollId and provides user feedback via revealError when missing.

infrastructure/eid-wallet/src-tauri/tauri.conf.json (1)

4-4: Version bump looks good, but verify Android versionCode.

The version is correctly updated to 0.5.0 to match package.json. However, the android.versionCode (line 29) remains at 12. If this release is intended for the Play Store, the versionCode typically needs to be incremented as well.

infrastructure/eid-wallet/package.json (1)

3-3: LGTM!

Version bump to 0.5.0 is consistent with tauri.conf.json.

infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte (1)

48-52: LGTM!

Making cancelDelete async and navigating to /main after closing confirmation dialogs provides a consistent UX flow, ensuring users return to the main screen after cancellation.

infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte (2)

101-105: LGTM!

Simplifying the decline handler to always navigate to /main provides a predictable user experience. The previous conditional logic for restarting scanning vs. navigating is removed in favor of a consistent cancel-to-main flow.


122-126: LGTM!

Consistent with handleAuthDrawerDecline - the signing drawer decline now follows the same pattern of closing and navigating to /main.

infrastructure/eid-wallet/src/routes/(auth)/register/+page.svelte (3)

152-165: LGTM!

The two-button layout (Back + Confirm) improves navigation UX by providing a clear escape path to onboarding. The flex layout with equal-width buttons is consistent with other auth flows in the PR.


221-228: Back button closes drawer but doesn't navigate.

The Back button at line 225 sets showDrawer = false but doesn't navigate anywhere. This leaves the user on the same page with the drawer closed but PIN already set. Consider whether this is intentional—if the user closes this drawer, they might be confused about next steps.


273-282: Same concern for biometrics Back button.

Similar to the previous drawer, this Back button (line 277) closes the drawer without navigation after biometrics are successfully added. The user would be left seeing the underlying page with their state partially complete. Verify this is the intended behavior.

infrastructure/eid-wallet/src/routes/+layout.svelte (1)

21-22: LGTM!

Good addition of app readiness state and deep link queue to prevent processing deep links before the app is fully initialized.

infrastructure/eid-wallet/src/lib/ui/Toast/index.ts (1)

1-1: Toast barrel re-export is fine

The directory-level default re-export is idiomatic and matches how other UI components are structured.

infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte (1)

12-16: New imports are consistent with patterns elsewhere

Adding the Button namespace and Arrow icon imports aligns with other auth routes and doesn’t affect existing behavior here.

infrastructure/eid-wallet/src/routes/(auth)/e-passport/+page.svelte (1)

7-8: Imports align with shared button/icon usage

The added Button namespace and Arrow icon imports match patterns used in other auth screens; no concerns here.

infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte (1)

6-8: IdentityCard copy button integration looks solid

The new copyBtn prop and Copy01Icon wiring for the eName variant are consistent with existing Button.Icon usage and cleanly scoped behind {#if copyBtn} so the UI degrades gracefully when no handler is passed.

Also applies to: 20-21, 31-32, 74-80

Comment on lines +1283 to +1311
try {
const pollResponse = await fetch(
`${platformUrl}/api/polls/${get(signingData)?.pollId}`,
);
if (pollResponse.ok) {
const pollDetails =
await pollResponse.json();
signingData.update((prev) =>
prev
? {
...prev,
pollDetails,
}
: prev,
);
console.log(
"✅ Poll details fetched:",
pollDetails,
);
}
} catch (error) {
console.error(
"Failed to fetch poll details:",
error,
);
blindVoteError.set(
"Failed to load poll details",
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing error handling for non-ok poll response.

If pollResponse.ok is false (e.g., 404, 500), no error is set and signingData.pollDetails remains as "Loading poll details..." indefinitely. The catch block only handles network errors, not HTTP error statuses.

                             try {
                                 const pollResponse = await fetch(
                                     `${platformUrl}/api/polls/${get(signingData)?.pollId}`,
                                 );
                                 if (pollResponse.ok) {
                                     const pollDetails =
                                         await pollResponse.json();
                                     signingData.update((prev) =>
                                         prev
                                             ? {
                                                   ...prev,
                                                   pollDetails,
                                               }
                                             : prev,
                                     );
                                     console.log(
                                         "✅ Poll details fetched:",
                                         pollDetails,
                                     );
+                                } else {
+                                    console.error(
+                                        `Failed to fetch poll details: ${pollResponse.status} ${pollResponse.statusText}`,
+                                    );
+                                    blindVoteError.set(
+                                        "Failed to load poll details. Please try again.",
+                                    );
                                 }
                             } catch (error) {
🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts around lines
1283 to 1311: the current code only handles network exceptions but ignores HTTP
error statuses when pollResponse.ok is false, leaving signingData.pollDetails
stuck as "Loading poll details..."; update the logic to handle non-ok responses
by reading the response body (if available) or response.status/text, log a
descriptive error including status and body, and set blindVoteError to a clear
message (e.g., "Failed to load poll details: <status or message>");
additionally, ensure signingData is updated to a final state (null or an error
marker) instead of leaving the "Loading..." placeholder so upstream UI stops
waiting.

Comment on lines +1347 to 1349
} catch (error) {
console.error("Error in handleDeepLinkData:", error);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Outer catch block swallows errors without user feedback.

If an unexpected error occurs, it's only logged to console. Users receive no indication that something went wrong. Consider setting a generic error state to inform users.

         } catch (error) {
             console.error("Error in handleDeepLinkData:", error);
+            // Set a generic error based on the most recent context
+            // The specific error stores (authError, signingError, blindVoteError, revealError)
+            // may already be set by inner handlers, but if not, provide generic feedback
+            if (data.type === "auth" && !get(authError)) {
+                authError.set("Failed to process authentication request. Please try again.");
+            } else if (data.type === "sign" && !get(signingError) && !get(blindVoteError)) {
+                signingError.set("Failed to process signing request. Please try again.");
+            } else if (data.type === "reveal" && !get(revealError)) {
+                revealError.set("Failed to process reveal request. Please try again.");
+            }
         }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +51 to +58
<div class="flex items-center gap-3">
<ButtonAction
variant="soft"
class="flex-1"
callback={() => goto("/register")}>Back</ButtonAction
>
<ButtonAction class="flex-1" callback={handleFinish}>Finish</ButtonAction>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix extra > and improve CTA button markup

There’s an extra > after the Back button which will render as text, and the inline child text makes the markup harder to read.

-    <div class="flex items-center gap-3">
-        <ButtonAction
-            variant="soft"
-            class="flex-1"
-            callback={() => goto("/register")}>Back</ButtonAction
-        >
-        <ButtonAction class="flex-1" callback={handleFinish}>Finish</ButtonAction>
-    </div>
+    <div class="flex items-center gap-3">
+        <ButtonAction
+            variant="soft"
+            class="flex-1"
+            callback={() => goto("/register")}
+        >
+            Back
+        </ButtonAction>
+        <ButtonAction class="flex-1" callback={handleFinish}>
+            Finish
+        </ButtonAction>
+    </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div class="flex items-center gap-3">
<ButtonAction
variant="soft"
class="flex-1"
callback={() => goto("/register")}>Back</ButtonAction
>
<ButtonAction class="flex-1" callback={handleFinish}>Finish</ButtonAction>
</div>
<div class="flex items-center gap-3">
<ButtonAction
variant="soft"
class="flex-1"
callback={() => goto("/register")}
>
Back
</ButtonAction>
<ButtonAction class="flex-1" callback={handleFinish}>
Finish
</ButtonAction>
</div>
🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/e-passport/+page.svelte around
lines 51 to 58, there is an extra stray ">" after the Back ButtonAction closing
tag and the button label is inline which reduces readability; remove the stray
">" so it no longer renders as text, and move the button label onto its own
child line (or use a label prop/slot consistently) so the ButtonAction markup is
clean and readable; ensure both ButtonAction components follow the same
formatting style and keep the callback attributes as-is.

Comment on lines +149 to +154
// 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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Authenticated routes list may be incomplete.

The isAuthenticatedRoute helper only checks exact matches for /main, /scan-qr, and /settings. Routes like /settings/language, /settings/pin, and /settings/privacy (used in this same PR) won't match. Consider using startsWith for prefix matching:

 function isAuthenticatedRoute(pathname: string): boolean {
-    const authenticatedRoutes = ["/main", "/scan-qr", "/settings"];
-    return authenticatedRoutes.includes(pathname);
+    const authenticatedPrefixes = ["/main", "/scan-qr", "/settings"];
+    return authenticatedPrefixes.some(prefix => pathname.startsWith(prefix));
 }
🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/+layout.svelte around lines 149-154, the
isAuthenticatedRoute helper only checks exact pathname matches so nested routes
like /settings/language, /settings/pin, /settings/privacy (and any future
subroutes) won't be considered authenticated; update the function to use prefix
matching by defining authenticated route prefixes (e.g. "/main", "/scan-qr",
"/settings") and return true if pathname === prefix or
pathname.startsWith(prefix + "/") (or simply pathname.startsWith(prefix)
depending on desired behavior) so all subroutes under /settings are correctly
treated as authenticated.

Comment on lines +601 to +612
// 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);
}
}
pendingDeepLinks = [];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Queued deep links processed without awaiting.

handleDeepLink contains async operations (checkAuth), but the loop processes links synchronously without awaiting. This could cause race conditions if multiple deep links are queued:

 if (pendingDeepLinks.length > 0 && globalState) {
     console.log("Processing", pendingDeepLinks.length, "queued deep links");
     for (const deepLink of pendingDeepLinks) {
         try {
-            handleDeepLink(deepLink);
+            await handleDeepLink(deepLink);
         } catch (error) {
             console.error("Error processing queued deep link:", error);
         }
     }
     pendingDeepLinks = [];
 }

Note: This would also require making handleDeepLink return a Promise or be declared as async.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/+layout.svelte around lines 601 to 612,
the loop processes queued deep links by calling handleDeepLink synchronously
even though it performs async work (e.g., checkAuth); update the code to await
each call and ensure handleDeepLink is async/returns a Promise — replace the for
loop with a for..of that does await handleDeepLink(deepLink) inside the
try/catch (or collect promises and await Promise.all if parallel processing is
desired), and update handleDeepLink signature to async (or return a Promise) so
callers can await it.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
platforms/eReputation/client/src/pages/deeplink-login.tsx (1)

70-70: Update default appVersion to match wallet version.

The default appVersion is set to '0.4.0', but the PR summary indicates the eID wallet is being bumped to v0.5.0. Update the default to maintain version consistency.

Apply this diff:

-        const requestBody = { ename, session, signature, appVersion: appVersion || '0.4.0' };
+        const requestBody = { ename, session, signature, appVersion: appVersion || '0.5.0' };
🧹 Nitpick comments (2)
platforms/group-charter-manager/src/app/deeplink-login/page.tsx (1)

45-62: Redundant fallback parsing logic.

The extended parsing in lines 49-56 re-parses the same sources (window.location.search and window.location.hash) that were already parsed in lines 13-30. If the initial parsing didn't extract the parameters, parsing the same sources again won't yield different results.

If this is defensive coding for a specific edge case (e.g., browser quirks, timing issues), please add a comment explaining when this fallback is needed. Otherwise, consider simplifying:

-        let ename = params.get('ename');
-        let session = params.get('session');
-        let signature = params.get('signature');
+        const ename = params.get('ename');
+        const session = params.get('session');
+        const signature = params.get('signature');
         const appVersion = params.get('appVersion');
 
-        // Only set error after all parsing attempts have failed
-        // Try additional parsing methods if params are missing
         if (!ename || !session || !signature) {
-          // Try one more time with window.location directly
-          const directParams = new URLSearchParams(window.location.search);
-          const hashParams = window.location.hash.includes('?') 
-            ? new URLSearchParams(window.location.hash.substring(window.location.hash.indexOf('?') + 1))
-            : null;
-          
-          ename = ename || directParams.get('ename') || hashParams?.get('ename') || null;
-          session = session || directParams.get('session') || hashParams?.get('session') || null;
-          signature = signature || directParams.get('signature') || hashParams?.get('signature') || null;
-          
-          if (!ename || !session || !signature) {
-            setError("Missing required authentication parameters");
-            setIsLoading(false);
-            return;
-          }
+          setError("Missing required authentication parameters");
+          setIsLoading(false);
+          return;
         }
platforms/blabsy/src/pages/deeplink-login.tsx (1)

78-78: Consider making error message more specific.

The generic error message doesn't indicate which parameters (ename, session, or signature) are missing, which could help with debugging deep link issues.

Example improvement:

const missingParams = [];
if (!ename) missingParams.push('ename');
if (!session) missingParams.push('session');
if (!signature) missingParams.push('signature');

setError(`Missing required parameters: ${missingParams.join(', ')}`);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55e054b and f1dc317.

📒 Files selected for processing (3)
  • platforms/blabsy/src/pages/deeplink-login.tsx (2 hunks)
  • platforms/eReputation/client/src/pages/deeplink-login.tsx (2 hunks)
  • platforms/group-charter-manager/src/app/deeplink-login/page.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-21T15:49:12.904Z
Learnt from: coodos
Repo: MetaState-Prototype-Project/prototype PR: 458
File: platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts:60-60
Timestamp: 2025-11-21T15:49:12.904Z
Learning: In platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts, the VITE_EREPUTATION_BASE_URL environment variable intentionally has no fallback value. The service should fail if this environment variable is not set, following a fail-fast design pattern.

Applied to files:

  • platforms/eReputation/client/src/pages/deeplink-login.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (5)
platforms/group-charter-manager/src/app/deeplink-login/page.tsx (1)

79-89: LGTM - Redirect improvement.

The change from reload to window.location.href = "/" is a good improvement. It navigates to the root path after successful authentication rather than reloading the deeplink page. Using window.location.href instead of Next.js router is acceptable here since a full navigation ensures the auth state is cleanly initialized.

platforms/eReputation/client/src/pages/deeplink-login.tsx (2)

39-41: LGTM! Variable declarations support fallback parsing.

Changing these to let appropriately enables the fallback parameter parsing implemented below.


80-88: LGTM! Robust response validation.

The additional check for both data.token and data.user prevents storing incomplete credentials and aligns with patterns used in other modules. This defensive approach improves authentication reliability.

platforms/blabsy/src/pages/deeplink-login.tsx (2)

41-43: Variable reassignment enabled for fallback parsing.

Changing these from const to let correctly supports the fallback parameter extraction logic that follows.


109-117: Token-based authentication flow looks correct.

The response handling properly checks for the presence of data.token and falls back to an error state if absent. The comment clarifies the expected API response format.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
platforms/eReputation/client/src/pages/deeplink-login.tsx (1)

76-87: Good validation, but remove unnecessary navigation delay.

The addition of validation for data.token and data.user before storing credentials is good defensive programming.

However, the 100ms setTimeout before navigation is unnecessary. localStorage.setItem is a synchronous operation in browsers—the values are immediately persisted before the next line executes. The delay adds artificial latency without providing any real benefit.

Apply this diff to remove the unnecessary delay:

           if (data.token && data.user) {
             localStorage.setItem("ereputation_token", data.token);
             localStorage.setItem("ereputation_user_id", data.user.id);
-            // Use setTimeout to ensure localStorage is written before navigation
-            setTimeout(() => {
-              window.location.href = "/";
-            }, 100);
+            window.location.href = "/";
           } else {

If you're experiencing issues with localStorage not being available after navigation, the problem likely lies elsewhere (e.g., timing of when the target page reads localStorage, or session storage isolation in certain scenarios). If that's the case, please clarify the specific issue you're addressing.

platforms/pictique/src/routes/(auth)/deeplink-login/+page.svelte (1)

12-130: Remove extensive debug logging before production deployment.

The file contains 20+ console.log and console.error statements that log the authentication flow, including truncated sensitive parameters (ename, session, signature, token). While truncation is better than full values, production code should not log authentication credentials in any form.

Recommended approach:

  1. Remove all debug logging statements for production
  2. If diagnostics are needed, use conditional logging based on environment:
const DEBUG = import.meta.env.DEV; // or use a feature flag

if (DEBUG) {
    console.log('[PICTIQUE DEEPLINK] Starting deeplink login');
}
  1. For production error monitoring, log non-sensitive metadata only:
// Good: no credentials
console.error('[PICTIQUE DEEPLINK] Authentication failed', {
    hasEname: !!ename,
    hasSession: !!session,
    hasSignature: !!signature,
    errorCode: response.status
});

// Bad: contains credential fragments
console.log('ename:', ename ? `${ename.substring(0, 10)}...` : 'MISSING');

Alternatively, integrate with a proper observability tool (e.g., Sentry, DataDog) that can sanitize sensitive data automatically.

♻️ Duplicate comments (4)
platforms/eReputation/client/src/pages/deeplink-login.tsx (1)

44-58: Remove arbitrary delay and investigate root cause of missing parameters.

This fallback block introduces several issues:

  1. Arbitrary 500ms delay: Blocks execution when parameters are missing, creating poor UX. The delay suggests an underlying race condition or timing issue that should be fixed at the root cause, not masked with a delay.

  2. Duplicate parsing: Re-parses window.location.search and hash despite lines 12-37 already performing comprehensive URL parsing. A previous review flagged this duplicate logic as addressed in commit ca2429d, but it reappears here.

  3. Unclear necessity: Why would parameters be missing initially but present after 500ms? This suggests the component may be mounting before the URL is fully populated, which indicates an architectural issue upstream.

Recommended approach:

  • If deep-link parameters arrive asynchronously, implement proper event-driven handling or state management rather than polling with delays.
  • If the issue is platform-specific (e.g., iOS/Android deep-link timing), document the behavior and handle it explicitly.
  • Remove the duplicate parsing and rely on the initial parse (lines 12-37).

Apply this diff to remove the delay and duplicate parsing:

-        if (!ename || !session || !signature) {
-          // Add a small delay to allow URL to fully parse before showing error
-          await new Promise(resolve => setTimeout(resolve, 500));
-          // Re-check one more time after delay
-          const finalParams = new URLSearchParams(window.location.search || (window.location.hash.includes('?') ? window.location.hash.substring(window.location.hash.indexOf('?') + 1) : '') || '');
-          ename = finalParams.get('ename') || ename;
-          session = finalParams.get('session') || session;
-          signature = finalParams.get('signature') || signature;
-          
-          if (!ename || !session || !signature) {
-            setError("Missing required authentication parameters");
-            setIsLoading(false);
-            return;
-          }
+        if (!ename || !session || !signature) {
+          setError("Missing required authentication parameters");
+          setIsLoading(false);
+          return;
         }

Then change lines 39-41 back to const:

-        let ename = params.get('ename');
-        let session = params.get('session');
-        let signature = params.get('signature');
+        const ename = params.get('ename');
+        const session = params.get('session');
+        const signature = params.get('signature');
platforms/dreamSync/client/src/pages/deeplink-login.tsx (1)

39-58: Same 500ms delay workaround as eCurrency platform.

This file contains identical parameter retry logic as platforms/eCurrency/client/src/pages/deeplink-login.tsx. See the review comment on that file for detailed analysis and refactoring suggestions.

platforms/blabsy/src/pages/deeplink-login.tsx (1)

43-62: Same 500ms delay workaround pattern (duplicated).

This implements the identical arbitrary delay retry logic as the other platform files. A previous review comment on this file (lines 46-82) suggested simplifying the fallback parsing to eliminate redundancy by parsing both sources upfront—this PR takes the opposite approach by adding delays.

See the review comment on platforms/eCurrency/client/src/pages/deeplink-login.tsx (lines 39-58) for a detailed refactoring proposal using a centralized utility.

platforms/group-charter-manager/src/app/deeplink-login/page.tsx (1)

40-59: Same 500ms delay workaround pattern (duplicated).

Identical to the other platform files.

See the review comment on platforms/eCurrency/client/src/pages/deeplink-login.tsx (lines 39-58) for detailed analysis and refactoring suggestions.

🧹 Nitpick comments (1)
platforms/pictique/src/routes/(auth)/deeplink-login/+page.svelte (1)

64-72: LGTM—improved error messaging.

Building an explicit list of missing parameters (lines 64-68) and including them in the error message (line 69) provides much clearer feedback than the generic "Missing required authentication parameters" used in other files.

Consider adopting this pattern in the other platform deeplink-login files for consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4887822 and 6ad7a1a.

📒 Files selected for processing (6)
  • platforms/blabsy/src/pages/deeplink-login.tsx (4 hunks)
  • platforms/dreamSync/client/src/pages/deeplink-login.tsx (1 hunks)
  • platforms/eCurrency/client/src/pages/deeplink-login.tsx (1 hunks)
  • platforms/eReputation/client/src/pages/deeplink-login.tsx (2 hunks)
  • platforms/group-charter-manager/src/app/deeplink-login/page.tsx (2 hunks)
  • platforms/pictique/src/routes/(auth)/deeplink-login/+page.svelte (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-21T15:49:12.904Z
Learnt from: coodos
Repo: MetaState-Prototype-Project/prototype PR: 458
File: platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts:60-60
Timestamp: 2025-11-21T15:49:12.904Z
Learning: In platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts, the VITE_EREPUTATION_BASE_URL environment variable intentionally has no fallback value. The service should fail if this environment variable is not set, following a fail-fast design pattern.

Applied to files:

  • platforms/eReputation/client/src/pages/deeplink-login.tsx
🧬 Code graph analysis (1)
platforms/blabsy/src/pages/deeplink-login.tsx (4)
platforms/dreamSync/client/src/pages/deeplink-login.tsx (1)
  • DeeplinkLogin (4-132)
platforms/eCurrency/client/src/pages/deeplink-login.tsx (1)
  • DeeplinkLogin (4-132)
platforms/eReputation/client/src/pages/deeplink-login.tsx (1)
  • DeeplinkLogin (4-136)
platforms/eVoting/src/app/deeplink-login/page.tsx (1)
  • DeeplinkLogin (6-129)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

Comment on lines +94 to +100
// Navigation will happen via auth state change, but ensure we navigate
router.push('/home').catch(() => {
// If push fails, try replace
router.replace('/home').catch(() => {
window.location.href = '/home';
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential double-navigation race between effects.

Two separate code paths can trigger navigation to '/home':

  1. Lines 94-100: Immediately after signInWithCustomToken succeeds
  2. Lines 128-136: When the user state becomes truthy (triggered by the auth state change from signInWithCustomToken)

Since signInWithCustomToken at line 93 likely updates the user state in the auth context, both navigations may fire in quick succession, potentially causing UI flashing or unexpected behavior.

Consider one of these approaches:

Option 1: Remove explicit navigation after login (rely on auth state)

                if (data.token) {
                    await signInWithCustomToken(data.token);
-                   // Navigation will happen via auth state change, but ensure we navigate
-                   router.push('/home').catch(() => {
-                       // If push fails, try replace
-                       router.replace('/home').catch(() => {
-                           window.location.href = '/home';
-                       });
-                   });
+                   // Navigation handled by user effect below
                } else {

Option 2: Remove the separate user effect (handle in login flow only)

-   // If user is authenticated, navigate to home
-   useEffect(() => {
-       if (user) {
-           router.push('/home').catch(() => {
-               router.replace('/home').catch(() => {
-                   window.location.href = '/home';
-               });
-           });
-       }
-   }, [user, router]);

Option 1 is preferred if other pages also need the automatic user-based redirect.

Also applies to: 128-136

Comment on lines 39 to 58
let ename = params.get('ename');
let session = params.get('session');
let signature = params.get('signature');
const appVersion = params.get('appVersion');

if (!ename || !session || !signature) {
setError("Missing required authentication parameters");
setIsLoading(false);
return;
// Add a small delay to allow URL to fully parse before showing error
await new Promise(resolve => setTimeout(resolve, 500));
// Re-check one more time after delay
const finalParams = new URLSearchParams(window.location.search || (window.location.hash.includes('?') ? window.location.hash.substring(window.location.hash.indexOf('?') + 1) : '') || '');
ename = finalParams.get('ename') || ename;
session = finalParams.get('session') || session;
signature = finalParams.get('signature') || signature;

if (!ename || !session || !signature) {
setError("Missing required authentication parameters");
setIsLoading(false);
return;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reconsider the 500ms delay workaround—address the root cause instead.

The arbitrary 500ms delay added to retry parameter parsing is a fragile timing assumption that blocks the UI, offers no guarantee of success, and doesn't address why parameters aren't available on first parse. This pattern is duplicated across multiple platform files (eCurrency, dreamSync, blabsy, group-charter-manager).

Consider these alternatives:

  1. Event-driven approach: Listen for URL changes or app-ready events rather than polling with arbitrary delays.
  2. Synchronous URL access: Ensure the app waits for deep-link intent/URL to be fully resolved before rendering this component.
  3. Centralized utility: Extract this logic into a shared parseDeeplinkParams utility that all platforms can use, eliminating the duplication across 5+ files.

If the delay is truly necessary due to platform limitations, document why 500ms was chosen and add a maximum retry count to prevent indefinite blocking.

Example refactor to centralized utility:

// shared/utils/deeplink.ts
export async function parseDeeplinkParams(
  maxRetries = 2,
  retryDelay = 500
): Promise<{ ename: string; session: string; signature: string; appVersion?: string } | null> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const params = extractParams(); // extract from search/hash
    if (params.ename && params.session && params.signature) {
      return params;
    }
    if (attempt < maxRetries) {
      await new Promise(resolve => setTimeout(resolve, retryDelay));
    }
  }
  return null;
}

Then use it consistently:

-let ename = params.get('ename');
-let session = params.get('session');
-let signature = params.get('signature');
-const appVersion = params.get('appVersion');
-
-if (!ename || !session || !signature) {
-  await new Promise(resolve => setTimeout(resolve, 500));
-  const finalParams = new URLSearchParams(window.location.search || (window.location.hash.includes('?') ? window.location.hash.substring(window.location.hash.indexOf('?') + 1) : '') || '');
-  ename = finalParams.get('ename') || ename;
-  session = finalParams.get('session') || session;
-  signature = finalParams.get('signature') || signature;
-  
-  if (!ename || !session || !signature) {
-    setError("Missing required authentication parameters");
-    setIsLoading(false);
-    return;
-  }
-}
+const authParams = await parseDeeplinkParams();
+if (!authParams) {
+  setError("Missing required authentication parameters");
+  setIsLoading(false);
+  return;
+}
+const { ename, session, signature, appVersion } = authParams;

Comment on lines 81 to 84
// Use setTimeout to ensure tokens are written before navigation
setTimeout(() => {
window.location.href = "/";
}, 100);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unnecessary 100ms navigation delay.

The 100ms setTimeout before navigation (to "ensure tokens are written") is likely unnecessary. setAuthToken and setAuthId appear to be synchronous localStorage operations based on the naming and usage pattern. localStorage writes are synchronous and blocking—they complete before the next line executes.

Remove the setTimeout wrapper:

            if (data.token && data.user) {
                setAuthId(data.user.id);
                setAuthToken(data.token);
-               // Use setTimeout to ensure tokens are written before navigation
-               setTimeout(() => {
-                   window.location.href = "/";
-               }, 100);
+               window.location.href = "/";
            } else {

If there's evidence that tokens aren't available after navigation, investigate the actual root cause rather than adding arbitrary delays.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In platforms/group-charter-manager/src/app/deeplink-login/page.tsx around lines
81 to 84, remove the unnecessary 100ms setTimeout wrapper around navigation;
instead set window.location.href = "/" immediately after calling
setAuthToken/setAuthId (or their equivalents) so navigation occurs synchronously
after the token writes; if token availability issues remain, create a separate
follow-up to investigate the root cause rather than reintroducing artificial
delays.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts (1)

1221-1354: Deep-link sign/blind-vote error handling still leaves users without feedback in several failure modes

The new handleDeepLinkData logic is a big improvement (duplicate guards, clearer state transitions, blind-vote support), but a few important UX/error-handling gaps remain:

  1. Missing user feedback when sign deep-link params are invalid

    If signingSessionId, base64Data, or redirectUri are missing, the if (get(signingSessionId) && base64Data && redirectUri) block is skipped and the function exits silently. Similarly, if atob/JSON.parse fails, the inner catch only logs to console and returns.

    That leaves the user with no visible error or UI change, which is the same underlying issue called out in the previous review.

    Consider surfacing a clear signingError and logging details when validation/decoding fails, e.g.:

  •            if (get(signingSessionId) && base64Data && redirectUri) {
    
  •            if (get(signingSessionId) && base64Data && redirectUri) {
                   redirect.set(redirectUri);
                   signingError.set(null); // Clear any previous signing errors
    

@@

  •                } catch (error) {
    
  •                    console.error("Error decoding signing data:", error);
    
  •                    return;
    
  •                }
    
  •            }
    
  •                } catch (error) {
    
  •                    console.error("Error decoding signing data:", error);
    
  •                    signingError.set(
    
  •                        "Failed to decode signing data. The link may be invalid.",
    
  •                    );
    
  •                    return;
    
  •                }
    
  •            } else {
    
  •                console.error("Invalid signing deep link parameters:", {
    
  •                    signingSessionId: get(signingSessionId),
    
  •                    hasData: !!base64Data,
    
  •                    hasRedirectUri: !!redirectUri,
    
  •                });
    
  •                signingError.set(
    
  •                    "Invalid signing request. Missing required parameters.",
    
  •                );
    
  •                return;
    
  •            }
    
    
    
  1. Blind-vote via sign deep link: missing pollId guard and non-ok poll response handling

    In the blind-vote branch, pollId is pulled from parsedSigningData?.pollId but not validated before constructing the poll-details URL. If pollId is missing or empty, you could end up requesting /api/polls/undefined. Also, non-ok HTTP statuses are ignored; only network exceptions set blindVoteError. That matches the previous concern where pollDetails stays as “Loading…” forever without an error.

    You can tighten this up by:

    • Guarding against missing pollId.
    • Handling !pollResponse.ok with a user-facing error.

    For example:

  •                        signingData.set({
    
  •                            pollId: parsedSigningData?.pollId,
    
  •                        const pollId = parsedSigningData?.pollId;
    
  •                        if (!pollId) {
    
  •                            const errorMessage =
    
  •                                "Missing poll ID in blind-vote signing data.";
    
  •                            blindVoteError.set(errorMessage);
    
  •                            throw new Error(errorMessage);
    
  •                        }
    
  •                        signingData.set({
    
  •                            pollId,
                               sessionId: parsedSigningData?.sessionId,
                               platform_url: platformUrl,
                               redirect: redirectUri,
    

@@

  •                        try {
    
  •                            const pollResponse = await fetch(
    
  •                                `${platformUrl}/api/polls/${get(signingData)?.pollId}`,
    
  •                            );
    
  •                            if (pollResponse.ok) {
    
  •                        try {
    
  •                            const pollResponse = await fetch(
    
  •                                `${platformUrl}/api/polls/${pollId}`,
    
  •                            );
    
  •                            if (pollResponse.ok) {
                                   const pollDetails =
                                       await pollResponse.json();
    

@@

  •                                console.log(
    
  •                                    "✅ Poll details fetched:",
    
  •                                    pollDetails,
    
  •                                );
    
  •                            }
    
  •                                console.log(
    
  •                                    "✅ Poll details fetched:",
    
  •                                    pollDetails,
    
  •                                );
    
  •                            } else {
    
  •                                console.error(
    
  •                                    `Failed to fetch poll details: ${pollResponse.status} ${pollResponse.statusText}`,
    
  •                                );
    
  •                                blindVoteError.set(
    
  •                                    "Failed to load poll details. Please try again.",
    
  •                                );
    
  •                            }
                           } catch (error) {
                               console.error(
                                   "Failed to fetch poll details:",
                                   error,
                               );
                               blindVoteError.set(
    
  •                                "Failed to load poll details",
    
  •                                "Failed to load poll details",
                               );
                           }
    
    
    
  1. Outer catch still only logs — no generic user-facing fallback

    The outer catch at the bottom of handleDeepLinkData still just logs the error. If something unexpected goes wrong before a specific error store is set, the user sees nothing. You can preserve the log while providing a generic per-type fallback:

  •    } catch (error) {
    
  •        console.error("Error in handleDeepLinkData:", error);
    
  •    }
    
  •    } catch (error) {
    
  •        console.error("Error in handleDeepLinkData:", error);
    
  •        // Provide generic user feedback if specific errors were not set
    
  •        if (data.type === "auth" && !get(authError)) {
    
  •            authError.set(
    
  •                "Failed to process authentication request. Please try again.",
    
  •            );
    
  •        } else if (
    
  •            data.type === "sign" &&
    
  •            !get(signingError) &&
    
  •            !get(blindVoteError)
    
  •        ) {
    
  •            signingError.set(
    
  •                "Failed to process signing request. Please try again.",
    
  •            );
    
  •        } else if (data.type === "reveal" && !get(revealError)) {
    
  •            revealError.set(
    
  •                "Failed to process reveal request. Please try again.",
    
  •            );
    
  •        }
    
  •    }
    
    
    
  1. Minor copy nit: PUBLIC_PLATFORM_URL mention

    The message:

    const errorMessage =
        "Missing platform URL in signing data and no PUBLIC_PLATFORM_URL configured.";

    still references PUBLIC_PLATFORM_URL, but the code no longer consults any such fallback. Consider rephrasing to something like "Missing platform URL in signing data." to better reflect the current fail-fast behavior. Based on learnings, the fail-fast pattern itself (no implicit base URL fallback) is consistent with how similar services are configured elsewhere in the repo.

🧹 Nitpick comments (3)
platforms/eReputation/client/src/pages/deeplink-login.tsx (2)

39-52: Async fallback re-parse works but consider unmount safety and optional appVersion refresh

The 500 ms delayed re-parse is reasonable for late-arriving deeplink params, but because handleDeeplinkLogin is async and the effect has no cleanup, setError / setIsLoading can still run after the component unmounts (e.g., user navigates away quickly), which can trigger React warnings. A small cancellation flag in the effect cleanup would avoid that.

Also, the fallback only re-reads ename, session, and signature. If appVersion is also injected late into the URL, you’ll still send the original appVersion (likely null) and fall back to the hard-coded default below. If you intend the server to see the real wallet version, you may want to mirror the re-parse for appVersion as well.

Example sketch:

   useEffect(() => {
-    const handleDeeplinkLogin = async () => {
+    let cancelled = false;
+
+    const handleDeeplinkLogin = async () => {
       try {
         …
-        let ename = params.get('ename');
-        let session = params.get('session');
-        let signature = params.get('signature');
-        const appVersion = params.get('appVersion');
+        let ename = params.get('ename');
+        let session = params.get('session');
+        let signature = params.get('signature');
+        let appVersion = params.get('appVersion');
         …
         if (!ename || !session || !signature) {
           await new Promise(resolve => setTimeout(resolve, 500));
-          const finalParams = new URLSearchParams(…);
-          ename = finalParams.get('ename') || ename;
-          session = finalParams.get('session') || session;
-          signature = finalParams.get('signature') || signature;
+          if (cancelled) return;
+          const finalParams = new URLSearchParams(…);
+          ename = finalParams.get('ename') || ename;
+          session = finalParams.get('session') || session;
+          signature = finalParams.get('signature') || signature;
+          appVersion = finalParams.get('appVersion') || appVersion;
         }
         …
       } catch (error) {
-        console.error('Login request failed:', error);
-        setError("Failed to connect to server");
-        setIsLoading(false);
+        if (!cancelled) {
+          console.error('Login request failed:', error);
+          setError("Failed to connect to server");
+          setIsLoading(false);
+        }
       }
     };
 
     handleDeeplinkLogin();
-  }, []);
+    return () => {
+      cancelled = true;
+    };
+  }, []);

76-83: Stricter success contract looks good; optionally guard user.id and re-evaluate need for setTimeout

Requiring both data.token and data.user before treating the login as successful is a solid improvement. Two small follow‑ups you might consider:

  • If data.user is present but doesn’t have an id, you’ll store "undefined" in ereputation_user_id. If that matters downstream, you could fold data.user.id into the success predicate:

    if (data.token && data.user && data.user.id) {
      localStorage.setItem("ereputation_token", data.token);
      localStorage.setItem("ereputation_user_id", data.user.id);
      setTimeout(() => {
        window.location.href = "/";
      }, 100);
    } else {
      
    }
  • localStorage.setItem is synchronous, so the setTimeout is likely unnecessary from a persistence standpoint. If the delay is only to smooth UX or avoid race conditions elsewhere, consider adding a brief comment about that intent (or reducing/removing it) so future readers understand why it’s there.

infrastructure/eid-wallet/src/routes/(auth)/register/+page.svelte (1)

179-190: Second-step Back/Confirm pairing is consistent with new flow

The new div.flex wrapping Back (to /onboarding) and the primary Confirm action, with flex-1 on both buttons, is structurally sound and matches the first-step layout pattern. No functional issues spotted here.

If the intention is to go “one step back” rather than exit the PIN flow, you might consider toggling firstStep = true instead of navigating to /onboarding, but that’s purely UX-dependent.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ad7a1a and be7b7fa.

⛔ Files ignored due to path filters (6)
  • infrastructure/eid-wallet/src-tauri/gen/android/.idea/gradle.xml is excluded by !**/gen/**
  • infrastructure/eid-wallet/src-tauri/gen/android/.idea/kotlinc.xml is excluded by !**/gen/**
  • infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/app-arm64-release.apk is excluded by !**/gen/**
  • infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/baselineProfiles/0/app-arm64-release.dm is excluded by !**/gen/**
  • infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/baselineProfiles/1/app-arm64-release.dm is excluded by !**/gen/**
  • infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/output-metadata.json is excluded by !**/gen/**
📒 Files selected for processing (8)
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte (2 hunks)
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts (2 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/register/+page.svelte (5 hunks)
  • platforms/blabsy/src/pages/deeplink-login.tsx (4 hunks)
  • platforms/dreamSync/client/src/pages/deeplink-login.tsx (1 hunks)
  • platforms/eCurrency/client/src/pages/deeplink-login.tsx (1 hunks)
  • platforms/eReputation/client/src/pages/deeplink-login.tsx (2 hunks)
  • platforms/group-charter-manager/src/app/deeplink-login/page.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • platforms/dreamSync/client/src/pages/deeplink-login.tsx
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte
  • platforms/group-charter-manager/src/app/deeplink-login/page.tsx
  • platforms/eCurrency/client/src/pages/deeplink-login.tsx
  • platforms/blabsy/src/pages/deeplink-login.tsx
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-21T15:49:12.904Z
Learnt from: coodos
Repo: MetaState-Prototype-Project/prototype PR: 458
File: platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts:60-60
Timestamp: 2025-11-21T15:49:12.904Z
Learning: In platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts, the VITE_EREPUTATION_BASE_URL environment variable intentionally has no fallback value. The service should fail if this environment variable is not set, following a fail-fast design pattern.

Applied to files:

  • platforms/eReputation/client/src/pages/deeplink-login.tsx
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts
📚 Learning: 2025-11-14T17:54:47.719Z
Learnt from: coodos
Repo: MetaState-Prototype-Project/prototype PR: 437
File: infrastructure/eid-wallet/src/lib/global/controllers/security.ts:138-157
Timestamp: 2025-11-14T17:54:47.719Z
Learning: The `setOnboardingPin` method in `infrastructure/eid-wallet/src/lib/global/controllers/security.ts` is intentionally designed to allow overwriting existing PINs without checking if a PIN already exists. This is a workaround for Android keeping stale files around during app reinstallation or onboarding, which causes errors on some devices. Do not suggest adding guards to prevent PIN overwrites in this method.

Applied to files:

  • infrastructure/eid-wallet/src/routes/(auth)/register/+page.svelte
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (5)
platforms/eReputation/client/src/pages/deeplink-login.tsx (1)

66-67: Confirm appVersion default ('0.4.0') is still correct and consider making it less brittle

The request body currently hard‑codes a default:

const requestBody = { ename, session, signature, appVersion: appVersion || '0.4.0' };

Given this PR is about eID Wallet UX and (per the PR summary) bumps the wallet to v0.5.0, it’s worth double‑checking whether '0.4.0' is still the right fallback. If the server logic depends on this version, misreporting could affect feature flags or compatibility checks.

If you expect this to change frequently, you might also consider centralising the “current wallet version” in a single constant or deriving it from build metadata/env to avoid future hard‑coded mismatches.

infrastructure/eid-wallet/src/routes/(auth)/register/+page.svelte (3)

6-11: Icon imports and Button namespace look consistent

The additional @hugeicons imports and Button namespace import fit the existing icon usage patterns in this file; no issues from a correctness standpoint.


221-228: Drawer PIN-set Back/Next actions behave coherently

Using a soft Back button to close the drawer (showDrawer = false) and a primary Next button to advance to the biometrics screen via handleNext is consistent with the stated UX goals. Markup and callbacks look correct.


273-282: Drawer biometrics-added Back/Continue actions are wired correctly

In the “You’re all set!” state, the Back button cleanly closes the drawer while Continue calls handleBiometricsAdded to mark onboarding complete and navigate to review. Layout matches the other two-button groups and the logic is sound.

infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts (1)

335-338: Non-blocking navigation before opening external deeplink looks good

Triggering goto("/main") in a fire-and-forget way with an attached .catch avoids blocking the auth flow while still preventing unhandled rejections. This aligns well with the “navigate home before opening external deeplink” requirement without risking a hard failure if navigation itself breaks.

Comment on lines +152 to +165
<div class="flex items-center gap-3">
<ButtonAction
variant="soft"
class="flex-1"
callback={() => goto("/onboarding")}>Back</ButtonAction
>
<ButtonAction
class="w-full"
class="flex-1"
variant={btnVariant}
callback={handleFirstStep}
>
Confirm
</ButtonAction>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix malformed closing tag for the first-step Confirm button

The ButtonAction wrapping the “Confirm” label is missing the terminating > on its closing tag (</ButtonAction), which will cause a Svelte compilation error.

You can align this with the surrounding pattern by splitting the closing tag over two lines:

-        <ButtonAction
-                class="flex-1"
-            variant={btnVariant}
-            callback={handleFirstStep}
-        >
-            Confirm
-        </ButtonAction
-        </div>
+        <ButtonAction
+            class="flex-1"
+            variant={btnVariant}
+            callback={handleFirstStep}
+        >
+            Confirm
+        </ButtonAction
+        >
+        </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div class="flex items-center gap-3">
<ButtonAction
variant="soft"
class="flex-1"
callback={() => goto("/onboarding")}>Back</ButtonAction
>
<ButtonAction
class="w-full"
class="flex-1"
variant={btnVariant}
callback={handleFirstStep}
>
Confirm
</ButtonAction>
</div>
<div class="flex items-center gap-3">
<ButtonAction
variant="soft"
class="flex-1"
callback={() => goto("/onboarding")}>Back</ButtonAction>
>
<ButtonAction
class="flex-1"
variant={btnVariant}
callback={handleFirstStep}
>
Confirm
</ButtonAction>
</div>
🤖 Prompt for AI Agents
infrastructure/eid-wallet/src/routes/(auth)/register/+page.svelte around lines
152 to 165: the closing tag for the Confirm ButtonAction is malformed (missing
the trailing '>') causing a Svelte compile error; fix it by correcting the
closing tag to "</ButtonAction>" (match the surrounding pattern and split across
two lines if desired so the opening tag, inner text, and full closing tag are
properly formed) and ensure indentation and spacing match the adjacent Back
button.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
platforms/eReputation/client/src/pages/references.tsx (1)

59-67: Align unauthorized revoke flow with the new clearAuth + /auth pattern

On unauthorized errors you currently toast and redirect to /api/login without clearing client auth state. Given that explicit logout is now clearAuth() + redirect to /auth, it would be cleaner and less error‑prone to do the same here so stale tokens don’t linger and the user sees a consistent flow.

A possible adjustment:

      if (isUnauthorizedError(error)) {
        toast({
          title: "Unauthorized",
          description: "You are logged out. Logging in again...",
          variant: "destructive",
        });
-        setTimeout(() => {
-          window.location.href = "/api/login";
-        }, 500);
+        setTimeout(() => {
+          clearAuth();
+          window.location.href = "/auth";
+        }, 500);
         return;
       }
platforms/eReputation/client/src/pages/dashboard.tsx (1)

4-4: Dashboard logout matches new clearAuth pattern; consider centralizing it

The updated logout (call clearAuth() then redirect to /auth) is consistent with the new auth flow and looks correct here.

Since the same pattern now appears in multiple pages (e.g., Dashboard and References), you might consider exposing a shared useLogout() hook or an exported logout() helper from your auth utilities, so that future changes to the logout behavior stay consistent across the app.

Also applies to: 75-78

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd0626a and 75848aa.

📒 Files selected for processing (2)
  • platforms/eReputation/client/src/pages/dashboard.tsx (2 hunks)
  • platforms/eReputation/client/src/pages/references.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
platforms/eReputation/client/src/pages/dashboard.tsx (1)
platforms/eReputation/client/src/lib/authUtils.ts (1)
  • clearAuth (31-34)
platforms/eReputation/client/src/pages/references.tsx (1)
platforms/eReputation/client/src/lib/authUtils.ts (1)
  • clearAuth (31-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: lint
🔇 Additional comments (1)
platforms/eReputation/client/src/pages/references.tsx (1)

4-4: Logout pattern looks good – confirmed stateless JWT design

clearAuth() removes the client-side token/user ID from localStorage and the redirect to /auth is the correct logout flow for your stateless JWT architecture. No server-side logout call is needed since the backend validates tokens on each request without maintaining session or refresh-token state.

This pattern is already consistent across the eReputation platform (no /api/logout usage anywhere), so no changes required.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
platforms/eReputation/client/src/pages/deeplink-login.tsx (1)

112-120: Duplicate issue: Silent redirect hides connection failures.

This exhibits the same problem as lines 101-106—silently redirecting users when an existing token is found, even though the connection or authentication attempt failed. When users click a deeplink, they expect feedback about the outcome, not a silent redirect that masks the failure.

Apply the same fix as suggested for lines 101-109:

-        // If token already exists, silently continue to home
-        const existingToken = localStorage.getItem("ereputation_token");
-        if (existingToken) {
-          window.location.href = "/";
-          return;
-        }
         setError("Failed to connect to server");
         setIsLoading(false);
🧹 Nitpick comments (1)
platforms/eReputation/client/src/pages/deeplink-login.tsx (1)

86-89: The setTimeout delay is unnecessary.

localStorage.setItem is synchronous and completes immediately. The 100ms delay before navigation doesn't serve a functional purpose and only adds unnecessary latency to the user experience.

Apply this diff to remove the unnecessary delay:

-            // Use setTimeout to ensure localStorage is written before navigation
-            setTimeout(() => {
-              window.location.href = "/";
-            }, 100);
+            window.location.href = "/";
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 75848aa and ab76935.

📒 Files selected for processing (1)
  • platforms/eReputation/client/src/pages/deeplink-login.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-21T15:49:12.904Z
Learnt from: coodos
Repo: MetaState-Prototype-Project/prototype PR: 458
File: platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts:60-60
Timestamp: 2025-11-21T15:49:12.904Z
Learning: In platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts, the VITE_EREPUTATION_BASE_URL environment variable intentionally has no fallback value. The service should fail if this environment variable is not set, following a fail-fast design pattern.

Applied to files:

  • platforms/eReputation/client/src/pages/deeplink-login.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

Comment on lines 101 to 109
// If token already exists, silently continue to home
const existingToken = localStorage.getItem("ereputation_token");
if (existingToken) {
window.location.href = "/";
return;
}
setError(errorData.error || "Authentication failed");
setIsLoading(false);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Silent redirect hides authentication failures from users.

When deeplink authentication fails but an existing token is found, the code silently redirects to home without indicating that the authentication attempt failed. This creates a confusing user experience—users who clicked a deeplink expecting to authenticate receive no feedback about the failure.

Additionally, the code doesn't validate whether the existing token is still valid or expired. While this pattern may follow similar implementations elsewhere in the codebase, it risks masking legitimate issues and degrading the user experience.

Consider either:

  1. Removing the silent redirect and allowing the error to display normally
  2. Adding user-visible feedback (e.g., a toast notification) indicating "Already authenticated, redirecting..."
  3. Validating the existing token before deciding to redirect
-          // If token already exists, silently continue to home
-          const existingToken = localStorage.getItem("ereputation_token");
-          if (existingToken) {
-            window.location.href = "/";
-            return;
-          }
           setError(errorData.error || "Authentication failed");
           setIsLoading(false);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// If token already exists, silently continue to home
const existingToken = localStorage.getItem("ereputation_token");
if (existingToken) {
window.location.href = "/";
return;
}
setError(errorData.error || "Authentication failed");
setIsLoading(false);
}
setError(errorData.error || "Authentication failed");
setIsLoading(false);
}
🤖 Prompt for AI Agents
In platforms/eReputation/client/src/pages/deeplink-login.tsx around lines
101-109, the current logic silently redirects to "/" when an existing token is
present, hiding authentication failures; change this to validate the existing
token before redirecting: call the existing token validation or user-info
endpoint (or a validateToken helper) and if the token is valid show a brief
user-visible message (toast or banner) like "Already authenticated,
redirecting…" then redirect, otherwise clear the invalid token from
localStorage, allow the error to display (setError / setIsLoading(false)), and
avoid the silent redirect; ensure all branches update isLoading and error state
appropriately.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
platforms/group-charter-manager/src/app/deeplink-login/page.tsx (1)

100-103: Arbitrary delay added despite prior feedback.

A previous review flagged removing unnecessary navigation delays, noting that setAuthToken/setAuthId are synchronous localStorage operations. This change introduces a 300ms delay and switches to window.location.reload().

If AuthProvider has a race condition reading tokens on mount, that should be fixed in the AuthProvider itself (e.g., by reading tokens synchronously during initialization or using a state flag). Arbitrary timeouts are fragile and create unpredictable behavior across different devices/network conditions.

platforms/eReputation/client/src/pages/deeplink-login.tsx (2)

39-64: 500ms retry + silent redirect still degrade UX and hide deeplink errors.

This block re-introduces a delayed re-parse of the URL and then silently redirects home when ename / session / signature are missing. URL state is synchronous and stable, so a 500ms delay is unlikely to fix missing parameters but always slows the flow, and the silent redirect means a user who just tapped a login deeplink gets no feedback that the link was invalid.

Consider removing the delay and fallback re-parsing entirely and surfacing a clear error instead:

-        if (!ename || !session || !signature) {
-          // Add a small delay to allow URL to fully parse before deciding
-          await new Promise(resolve => setTimeout(resolve, 500));
-          // Re-check one more time after delay
-          const finalParams = new URLSearchParams(
-            window.location.search ||
-              (window.location.hash.includes('?')
-                ? window.location.hash.substring(window.location.hash.indexOf('?') + 1)
-                : '') ||
-              ''
-          );
-          ename = finalParams.get('ename') || ename;
-          session = finalParams.get('session') || session;
-          signature = finalParams.get('signature') || signature;
-          
-          if (!ename || !session || !signature) {
-            // If still missing, silently redirect to home/login to avoid flashing error
-            window.location.href = "/";
-            return;
-          }
-        }
+        if (!ename || !session || !signature) {
+          setError("Missing required authentication parameters");
+          setIsLoading(false);
+          return;
+        }

This aligns with the existing full‑screen error UI and avoids hiding deeplink failures behind a redirect.


115-120: Silent redirects on existing tokens hide deeplink-auth failures.

In both the non‑OK response branch and the catch block, finding an ereputation_token causes an immediate redirect to / without surfacing that the deeplink authentication the user just initiated actually failed. This matches earlier feedback and still leads to confusing UX, especially given the rest of the PR’s focus on explicit user feedback (toasts, full‑screen errors).

Instead, let the error UI show, or explicitly communicate “Already authenticated, redirecting…”:

-          // If token already exists, silently continue to home
-          const existingToken = localStorage.getItem("ereputation_token");
-          if (existingToken) {
-            window.location.href = "/";
-            return;
-          }
           setError(errorData.error || "Authentication failed");
           setIsLoading(false);

and

-        // If token already exists, silently continue to home
-        const existingToken = localStorage.getItem("ereputation_token");
-        if (existingToken) {
-          window.location.href = "/";
-          return;
-        }
         setError("Failed to connect to server");
         setIsLoading(false);

If you want to treat “already authenticated” as success, consider validating the token first and then showing an explicit toast/banner before redirecting.

Also applies to: 126-131

🧹 Nitpick comments (3)
platforms/group-charter-manager/src/app/deeplink-login/page.tsx (2)

87-91: Use a utility function or constant for the localStorage key.

Direct access to localStorage.getItem("group_charter_auth_token") is inconsistent with using setAuthToken/setAuthId utilities elsewhere. If the key name changes, these direct accesses will break.

Consider adding a getAuthToken() utility function in @/lib/authUtils and using it here and at lines 115-116 and 125-126.

+import { setAuthToken, setAuthId, getAuthToken } from "@/lib/authUtils";
 ...
-            const existingToken = localStorage.getItem("group_charter_auth_token");
+            const existingToken = getAuthToken();

125-129: Add logging for network failure fallback.

Redirecting home on network failure when a token exists is reasonable for resilience. However, silent redirects make debugging production issues difficult.

        const existingToken = localStorage.getItem("group_charter_auth_token");
        if (existingToken) {
+         console.warn("Deeplink login network failure, falling back to existing token", error);
          window.location.href = "/";
          return;
        }
platforms/eReputation/client/src/pages/deeplink-login.tsx (1)

81-103: JSON-parse fallback using existing token can mask server issues and wrong-identity logins.

On a 200 OK response, if response.json() throws, the code falls back to existingToken and silently redirects home. That can hide broken server responses (schema regressions, HTML error pages, etc.) and also prevent a user from realizing that a new deeplink login (possibly for a different account) failed and they are still on the old token.

A simpler, more predictable behavior would be to always treat a JSON parse failure as an error, regardless of any existing token:

-          let data: any;
-          try {
-            data = await response.json();
-          } catch (parseError) {
-            console.error("Failed to parse auth response JSON:", parseError);
-            // If token already exists, proceed; otherwise surface error
-            const existingToken = localStorage.getItem("ereputation_token");
-            if (existingToken) {
-              window.location.href = "/";
-              return;
-            }
-            setError("Invalid response from server");
-            setIsLoading(false);
-            return;
-          }
+          let data: any;
+          try {
+            data = await response.json();
+          } catch (parseError) {
+            console.error("Failed to parse auth response JSON:", parseError);
+            setError("Invalid response from server");
+            setIsLoading(false);
+            return;
+          }

If you still want to special‑case “already authenticated”, consider explicitly validating the existing token elsewhere and showing a brief “Already authenticated, redirecting…” message before navigating, rather than treating a parse failure as success.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab76935 and cb494b5.

📒 Files selected for processing (3)
  • platforms/eReputation/client/src/lib/apiClient.ts (0 hunks)
  • platforms/eReputation/client/src/pages/deeplink-login.tsx (3 hunks)
  • platforms/group-charter-manager/src/app/deeplink-login/page.tsx (3 hunks)
💤 Files with no reviewable changes (1)
  • platforms/eReputation/client/src/lib/apiClient.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-21T15:49:12.904Z
Learnt from: coodos
Repo: MetaState-Prototype-Project/prototype PR: 458
File: platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts:60-60
Timestamp: 2025-11-21T15:49:12.904Z
Learning: In platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts, the VITE_EREPUTATION_BASE_URL environment variable intentionally has no fallback value. The service should fail if this environment variable is not set, following a fail-fast design pattern.

Applied to files:

  • platforms/eReputation/client/src/pages/deeplink-login.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (1)
platforms/group-charter-manager/src/app/deeplink-login/page.tsx (1)

40-42: LGTM!

The change from const to let is appropriate to support the re-parsing fallback logic below.

Comment on lines +46 to +64
// Add a small delay to allow URL to fully parse before deciding
await new Promise(resolve => setTimeout(resolve, 500));
// Re-check one more time after delay
const finalParams = new URLSearchParams(
window.location.search ||
(window.location.hash.includes('?')
? window.location.hash.substring(window.location.hash.indexOf('?') + 1)
: '') ||
'',
);
ename = finalParams.get('ename') || ename;
session = finalParams.get('session') || session;
signature = finalParams.get('signature') || signature;

if (!ename || !session || !signature) {
// If still missing, silently redirect to home/login to avoid flashing error
window.location.href = "/";
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Investigate root cause rather than adding arbitrary delays.

URLs are synchronously available in JavaScript—they don't require time to "parse." This 500ms delay appears to be a workaround for an undiagnosed timing issue, possibly related to how the mobile app delivers deep link parameters.

Concerns:

  1. The silent redirect to "/" without any logging makes debugging difficult when parameters genuinely fail to arrive.
  2. The re-parsing logic duplicates the earlier parsing block (lines 14-38).

Consider adding a console.warn before the silent redirect to aid debugging, and investigate the actual root cause (e.g., is the eID Wallet updating the URL after navigation?).

          if (!ename || !session || !signature) {
            // If still missing, silently redirect to home/login to avoid flashing error
+           console.warn("Deeplink login: Required params missing after retry, redirecting home");
            window.location.href = "/";
            return;
          }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In platforms/group-charter-manager/src/app/deeplink-login/page.tsx around lines
46 to 64, remove the arbitrary 500ms timeout and the duplicated parsing logic;
instead extract the URL parsing into a single helper used both places and call
it again deterministically (or subscribe to hashchange/popstate if deep-links
may arrive after navigation), and before performing the silent redirect keep the
redirect but add a console.warn (or process logger) that includes
window.location.href, window.location.search, window.location.hash and the
parsed ename/session/signature so missing-param cases are recorded for
debugging.

Comment on lines +115 to +119
const existingToken = localStorage.getItem("group_charter_auth_token");
if (existingToken) {
window.location.href = "/";
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Token-based redirect may mask server-side validation failures.

If the server returns a non-OK response (e.g., 401/403 indicating invalid credentials or revoked session), silently redirecting based on an existing local token could leave the user in an inconsistent state—they have a locally stored token that the server may no longer accept.

Consider at minimum logging this scenario, or validating whether the existing token is still valid before redirecting:

          const existingToken = localStorage.getItem("group_charter_auth_token");
          if (existingToken) {
+           console.warn("Deeplink login failed but existing token found, redirecting home", { status: response.status });
            window.location.href = "/";
            return;
          }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const existingToken = localStorage.getItem("group_charter_auth_token");
if (existingToken) {
window.location.href = "/";
return;
}
const existingToken = localStorage.getItem("group_charter_auth_token");
if (existingToken) {
console.warn("Deeplink login failed but existing token found, redirecting home", { status: response.status });
window.location.href = "/";
return;
}
🤖 Prompt for AI Agents
In platforms/group-charter-manager/src/app/deeplink-login/page.tsx around lines
115-119, the code unconditionally redirects when a token exists in localStorage
which can mask server-side invalidation; change this to validate the token
before redirecting by calling the relevant auth/validate or user/me endpoint
with the token, only navigate to "/" if the response is OK, and if the response
is not OK remove the token (or log the failure) and let the page proceed to show
the login flow; ensure network errors are handled (treat as invalid or retry)
and surface/log the validation failure for debugging.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
infrastructure/evault-core/src/controllers/VerificationController.ts (1)

231-239: Address non-deterministic selection when multiple duplicate verifications exist.

The find() at line 231 returns the first verification with a linkedEName, but if multiple verifications exist for the same documentId with different linkedEName values (since findManyAndCount returns multiple matches), the selection is non-deterministic depending on the order returned by the database query.

Also, the property name linkedEName is semantically misleading—it stores either w3id values (during provisioning, see line 238) or eName values (during device registration in NotificationService). Consider renaming this property to better reflect its dual purpose (e.g., linkedIdentifier or linkedW3ID), or separating these concerns into distinct properties.

infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte (1)

1-335: Run the formatter locally to fix formatting issues

The GitHub Actions "Check Code" job is failing because formatting checks detected issues in this file. Run the project's formatting pipeline locally (pnpm run format in the infrastructure/eid-wallet directory, or at the root) and commit the resulting changes to fix the CI failure.

♻️ Duplicate comments (6)
platforms/group-charter-manager/src/app/deeplink-login/page.tsx (4)

45-65: Arbitrary delay and missing logging (duplicate concern).

This 500ms delay and retry logic was previously flagged. URLs are synchronously available—the delay suggests an undiagnosed timing issue with how the mobile app delivers parameters. The silent redirect without logging makes debugging impossible when parameters genuinely fail to arrive.

As recommended previously, add logging before the silent redirect:

          if (!ename || !session || !signature) {
            // If still missing, silently redirect to home/login to avoid flashing error
+           console.warn("Deeplink login: Required params missing after retry", {
+             url: window.location.href,
+             search: window.location.search,
+             hash: window.location.hash,
+             ename,
+             session,
+             signature
+           });
            window.location.href = "/";
            return;
          }

98-100: Arbitrary 300ms reload delay remains unnecessary (duplicate concern).

The previous review flagged the 100ms navigation delay as unnecessary. This 300ms delay before reload() likely suffers from the same issue—setAuthToken and setAuthId appear to be synchronous localStorage operations that complete immediately.

Remove the arbitrary delay:

            setAuthId(data.user.id);
            setAuthToken(data.token);
-           setTimeout(() => {
-             window.location.reload();
-           }, 300);
+           window.location.reload();

If tokens aren't available after reload, investigate the actual root cause in the authentication flow.


109-116: Token-based redirect masks authentication failures (duplicate concern).

This was previously flagged: when the server returns non-OK (401/403), silently redirecting based on an existing token can leave the user in an inconsistent state. The local token may no longer be valid on the server.

As recommended previously, add logging and consider token validation:

          const existingToken = localStorage.getItem("group_charter_auth_token");
          if (existingToken) {
+           console.warn("Deeplink login failed but existing token found, redirecting home", { 
+             status: response.status,
+             error: errorData.error 
+           });
            window.location.href = "/";
            return;
          }

121-125: Token-based redirect masks network errors (duplicate concern).

Similar to the previous concerns: when a network error occurs, silently redirecting based on an existing token masks the actual failure. This makes it impossible to distinguish between network issues, server errors, and authentication failures.

Add logging to capture the actual error:

      } catch (error) {
+       console.error("Deeplink login: Network or request error", { error });
        const existingToken = localStorage.getItem("group_charter_auth_token");
        if (existingToken) {
+         console.warn("Falling back to existing token due to error");
          window.location.href = "/";
          return;
        }
platforms/eReputation/client/src/pages/deeplink-login.tsx (2)

40-63: Avoid arbitrary delay + silent redirect when deeplink params are missing.

The 500 ms delay plus second URL parse and silent redirect to / hides real deeplink failures and makes the flow feel flaky. Users who clicked a login link get no indication that anything went wrong.

Consider:

  • Keeping at most a short, well‑documented retry (if you truly see late URL updates), and
  • Always surfacing an explicit error when ename/session/signature are still missing instead of silently redirecting.

A concrete adjustment that preserves the retry but avoids the silent failure:

-        if (!ename || !session || !signature) {
-          // Add a small delay to allow URL to fully parse before deciding
-          await new Promise(resolve => setTimeout(resolve, 500));
-          // Re-check one more time after delay
-          const finalParams = new URLSearchParams(
-            window.location.search ||
-              (window.location.hash.includes('?')
-                ? window.location.hash.substring(window.location.hash.indexOf('?') + 1)
-                : '') ||
-              ''
-          );
-          ename = finalParams.get('ename') || ename;
-          session = finalParams.get('session') || session;
-          signature = finalParams.get('signature') || signature;
-          
-          if (!ename || !session || !signature) {
-            // If still missing, silently redirect to home/login to avoid flashing error
-            window.location.href = "/";
-            return;
-          }
-        }
+        if (!ename || !session || !signature) {
+          // Optional: brief retry window in case URL params are populated asynchronously
+          await new Promise((resolve) => setTimeout(resolve, 500));
+          const finalParams = new URLSearchParams(
+            window.location.search ||
+              (window.location.hash.includes("?")
+                ? window.location.hash.substring(window.location.hash.indexOf("?") + 1)
+                : "") ||
+              ""
+          );
+          ename = finalParams.get("ename") || ename;
+          session = finalParams.get("session") || session;
+          signature = finalParams.get("signature") || signature;
+        }
+
+        if (!ename || !session || !signature) {
+          setError("Missing required authentication parameters");
+          setIsLoading(false);
+          return;
+        }

87-101: Silent redirect on existing token masks deeplink auth failures.

Both the inner and outer catch blocks redirect to / when ereputation_token exists, without validating it or informing the user. That can:

  • Hide actual deeplink authentication problems.
  • Leave users confused when a deeplink “does nothing”.

A minimal change is to remove the silent redirect and always surface the error; you can later enhance this to show a toast like “Already authenticated, redirecting…” if you add token validation:

-        } catch (err) {
-          // If token already exists, silently continue to home
-          const existingToken = localStorage.getItem("ereputation_token");
-          if (existingToken) {
-            window.location.href = "/";
-            return;
-          }
+        } catch (err) {
           const axiosErr = err as any;
           const msg =
             axiosErr?.response?.data?.error ||
             axiosErr?.message ||
             "Failed to connect to server";
           setError(msg);
           setIsLoading(false);
         }
       } catch (error) {
-        // If token already exists, silently continue to home
-        const existingToken = localStorage.getItem("ereputation_token");
-        if (existingToken) {
-          window.location.href = "/";
-          return;
-        }
         setError("Failed to connect to server");
         setIsLoading(false);
       }

If you do want to keep a redirect-on-valid-token flow, consider validating the token first and showing a brief user-visible message before navigating.

Also applies to: 103-108

🧹 Nitpick comments (5)
platforms/group-charter-manager/src/app/deeplink-login/page.tsx (1)

40-42: Consider extracting parameter parsing into a helper function.

The mutation from const to let is necessary for the retry logic but indicates duplicated parsing code. Extract the parameter extraction logic (lines 14-38 and 49-55) into a reusable helper function to improve maintainability and reduce duplication.

Example refactor:

+function parseDeeplinkParams(): { ename: string | null; session: string | null; signature: string | null } {
+  let searchString = window.location.search;
+  if (!searchString || searchString === '') {
+    const hash = window.location.hash;
+    if (hash && hash.includes('?')) {
+      searchString = hash.substring(hash.indexOf('?'));
+    } else {
+      try {
+        const fullUrl = new URL(window.location.href);
+        searchString = fullUrl.search;
+      } catch (e) {
+        // Ignore parsing errors
+      }
+    }
+  }
+  if (searchString.startsWith('?')) {
+    searchString = searchString.substring(1);
+  }
+  const params = new URLSearchParams(searchString);
+  return {
+    ename: params.get('ename'),
+    session: params.get('session'),
+    signature: params.get('signature')
+  };
+}
+
 export default function DeeplinkLogin() {

Then use it consistently:

-        let ename = params.get('ename');
-        let session = params.get('session');
-        let signature = params.get('signature');
+        let { ename, session, signature } = parseDeeplinkParams();
platforms/eReputation/client/src/pages/deeplink-login.tsx (1)

2-2: Reuse apiClient baseURL and make config/version behavior explicit.

You now import apiClient but still read VITE_EREPUTATION_BASE_URL directly to build loginUrl. Given apiClient is already configured with a base URL in platforms/eReputation/client/src/lib/apiClient.ts, this duplicates configuration and increases the chance of drift.

You can simplify and centralize config by relying on the client’s baseURL:

-        // Make POST request to login endpoint using axios (auto JSON parsing)
-        const apiBaseUrl = import.meta.env.VITE_EREPUTATION_BASE_URL;
-        const loginUrl = `${apiBaseUrl}/api/auth`;
-        const requestBody = { ename, session, signature, appVersion: appVersion || '0.4.0' };
+        // Make POST request to login endpoint using shared apiClient
+        const requestBody = { ename, session, signature, appVersion: appVersion || "0.4.0" };
 
         try {
-          const { data } = await apiClient.post(loginUrl, requestBody);
+          const { data } = await apiClient.post("/api/auth", requestBody);

Additionally, two small follow-ups you might want to confirm:

  • If VITE_EREPUTATION_BASE_URL is considered mandatory on the API side (fail-fast pattern), consider mirroring that on the client by surfacing a clear “client misconfigured” error if the baseURL is missing, rather than a generic connection failure. Based on learnings, this keeps misconfigurations obvious.
  • With eID Wallet bumped to v0.5.0, double-check that the fallback appVersion || '0.4.0' is still the right default (or update it to the new minimum supported version).

Also applies to: 70-77, 73-73

infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte (3)

153-157: Avoid potentially calling handlePinInput twice for the same PIN entry

handlePinInput(pin) is invoked both from the $effect reacting to pin changes (Lines 153–157) and via the onchange prop on <InputPin> (Line 290). Depending on how InputPin fires onchange, this can result in duplicated verification and post-auth work (e.g., two health checks and two goto() calls) for a single 4‑digit PIN entry.

Consider centralizing the trigger so that exactly one path calls handlePinInput per user action—for example, either:

  • rely solely on the $effect that watches pin, or
  • rely solely on onchange from InputPin and remove the $effect.

Please verify how InputPin currently uses the onchange prop to avoid unintended duplicate calls.

Also applies to: 290-290


100-103: Reset loading state when eVault is deleted to avoid misleading “Logging you in...” UI

In both PIN and biometric flows, when healthCheck.deleted is true you set isDeletedVaultModalOpen = true and then return (Lines 100–103, 185–188). Because isPostAuthLoading remains true, the background login UI continues to show the loading spinner and “Logging you in...” subtitle (Lines 247–251, 255–260) even though the user is now blocked on the non-dismissible “eVault Has Been Deleted” Drawer.

Functionally this works, but the messaging is slightly misleading. Consider explicitly resetting the loading flag when you show the deleted‑vault modal, e.g.:

-        if (healthCheck.deleted) {
-            isDeletedVaultModalOpen = true;
-            return; // Don't continue to app
-        }
+        if (healthCheck.deleted) {
+            isDeletedVaultModalOpen = true;
+            isPostAuthLoading = false;
+            return; // Don't continue to app
+        }

…and apply the same change in the biometric branch. That keeps the visual state aligned with the fact that the login flow is no longer progressing.

Also applies to: 185-188, 247-251, 255-260


85-147: Consider extracting shared post-auth flow (health check + deep-link + navigation)

The PIN and biometric branches both perform essentially the same steps after successful auth: eVault health check, optional public-key sync, pending deep-link processing, and final navigation to /scan-qr or /main. The duplication is correct but makes it easy for the two paths to drift over time.

If you end up revisiting this area, consider factoring that sequence into a shared helper (e.g., await completePostAuth(globalState)), which can encapsulate isDeletedVaultModalOpen handling, deep-link routing, and final navigation in one place.

Also applies to: 170-232

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb494b5 and ba4ae20.

⛔ Files ignored due to path filters (1)
  • infrastructure/eid-wallet/src-tauri/gen/android/app/arm64/release/app-arm64-release.apk is excluded by !**/gen/**
📒 Files selected for processing (4)
  • infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte (6 hunks)
  • infrastructure/evault-core/src/controllers/VerificationController.ts (1 hunks)
  • platforms/eReputation/client/src/pages/deeplink-login.tsx (2 hunks)
  • platforms/group-charter-manager/src/app/deeplink-login/page.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-11-21T15:49:12.904Z
Learnt from: coodos
Repo: MetaState-Prototype-Project/prototype PR: 458
File: platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts:60-60
Timestamp: 2025-11-21T15:49:12.904Z
Learning: In platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts, the VITE_EREPUTATION_BASE_URL environment variable intentionally has no fallback value. The service should fail if this environment variable is not set, following a fail-fast design pattern.

Applied to files:

  • platforms/eReputation/client/src/pages/deeplink-login.tsx
📚 Learning: 2025-11-14T17:54:47.719Z
Learnt from: coodos
Repo: MetaState-Prototype-Project/prototype PR: 437
File: infrastructure/eid-wallet/src/lib/global/controllers/security.ts:138-157
Timestamp: 2025-11-14T17:54:47.719Z
Learning: The `setOnboardingPin` method in `infrastructure/eid-wallet/src/lib/global/controllers/security.ts` is intentionally designed to allow overwriting existing PINs without checking if a PIN already exists. This is a workaround for Android keeping stale files around during app reinstallation or onboarding, which causes errors on some devices. Do not suggest adding guards to prevent PIN overwrites in this method.

Applied to files:

  • infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte
📚 Learning: 2025-04-24T05:54:12.858Z
Learnt from: sosweetham
Repo: MetaState-Prototype-Project/prototype PR: 97
File: infrastructure/eid-wallet/src/routes/+page.svelte:31-35
Timestamp: 2025-04-24T05:54:12.858Z
Learning: The "Clear PIN" button on the root page of the eID wallet application is intended for debugging purposes only, not for production use, so it doesn't require confirmation dialogs or detailed user guidance.

Applied to files:

  • infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte
🧬 Code graph analysis (2)
platforms/eReputation/client/src/pages/deeplink-login.tsx (2)
platforms/eReputation/client/src/lib/apiClient.ts (1)
  • apiClient (5-10)
platforms/eCurrency/client/src/lib/apiClient.ts (1)
  • apiClient (5-10)
infrastructure/evault-core/src/controllers/VerificationController.ts (1)
platforms/dreamSync/shared/schema.ts (1)
  • matches (109-117)
🪛 GitHub Actions: Check Code
infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte

[error] 1-1: Build/check process failed due to formatter errors detected by svelte-check. Command 'pnpm run check' exited with code 1.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test
  • GitHub Check: build
  • GitHub Check: test-web3-adapter-integration
🔇 Additional comments (1)
infrastructure/eid-wallet/src/routes/(auth)/login/+page.svelte (1)

16-16: isPostAuthLoading reentrancy guard and UX wiring look solid

The new isPostAuthLoading flag is consistently used to prevent re-entry during post-auth work (PIN + biometrics), swap the PIN UI for a clear “Logging you in...” state, and hide the Clear PIN debug action while navigation is in flight. I don’t see logical gaps or race issues in how this flag is set/reset across the flows; this is a good UX and correctness improvement. Keeping Clear PIN one-click but only when not loading also matches the intended debug-only nature of that action. Based on learnings, this aligns with prior guidance on the Clear PIN button behavior.

Also applies to: 65-69, 71-76, 81-82, 148-151, 168-169, 235-236, 247-251, 255-260, 261-295, 296-300

Comment on lines +225 to +233
const [matches] =
await this.verificationService.findManyAndCount({
documentId:
body.data.verification.document.number
.value,
});
console.log("matched", verificationMatch);
const verificationMatch = matches.find(
(v) => !!v.linkedEName,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find the verification service to understand findManyAndCount implementation
fd -e "ts" -e "js" | grep -i "verification.*service" | head -20

Repository: MetaState-Prototype-Project/prototype

Length of output: 292


🏁 Script executed:

# Search for findManyAndCount method definition
rg -n "findManyAndCount" --type ts -A 10

Repository: MetaState-Prototype-Project/prototype

Length of output: 3987


🏁 Script executed:

# Check the controller method context around lines 225-233 to see if id is accessible
sed -n '200,240p' infrastructure/evault-core/src/controllers/VerificationController.ts

Repository: MetaState-Prototype-Project/prototype

Length of output: 1833


🏁 Script executed:

# Verify the exact context around line 220 where id is available
sed -n '215,245p' infrastructure/evault-core/src/controllers/VerificationController.ts

Repository: MetaState-Prototype-Project/prototype

Length of output: 1543


🏁 Script executed:

# Check if there's any other filtering logic that might exclude the current verification
rg -n "documentId" infrastructure/evault-core/src/controllers/VerificationController.ts -B 5 -A 5

Repository: MetaState-Prototype-Project/prototype

Length of output: 1206


🏁 Script executed:

# Double-check if there are any other places where the current verification id might be excluded
rg -n "findManyAndCount" infrastructure/evault-core/src/controllers/VerificationController.ts -B 3 -A 10

Repository: MetaState-Prototype-Project/prototype

Length of output: 875


Exclude the current verification from duplicate detection to prevent self-matching.

The duplicate detection queries for all verifications with matching documentId but does not exclude the current verification. If the current verification already has a linkedEName (from previous processing), it will be included in the matches array and matched against itself, causing a false positive where it marks itself as a duplicate.

Filter the results to exclude the current verification by id:

                        const verificationMatch = matches.find(
-                            (v) => !!v.linkedEName,
+                            (v) => v.id !== id && !!v.linkedEName,
                        );

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
in infrastructure/evault-core/src/controllers/VerificationController.ts around
lines 225-233, the duplicate-detection code queries verifications by documentId
but does not exclude the current verification, so it can self-match; update the
logic to exclude the current verification by id (either add an exclusion
condition to the findManyAndCount query using body.data.verification.id or
immediately filter the returned matches with matches = matches.filter(m => m.id
!== currentVerificationId)) and then perform the .find(v => !!v.linkedEName) on
the filtered list; also add a defensive check that body.data.verification.id
exists before excluding.

Comment on lines +82 to +94
let data: any;
try {
data = await response.json();
} catch {
const existingToken = localStorage.getItem("group_charter_auth_token");
if (existingToken) {
window.location.href = "/";
return;
}
setError("Invalid response from server");
setIsLoading(false);
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Token-based redirect masks JSON parse errors from successful responses.

When the server returns 200 OK but invalid JSON, that indicates a server-side bug. Silently redirecting based on an existing token masks this error, making it impossible to detect and diagnose server issues.

Add logging to capture parse failures:

          } catch {
+           console.error("Deeplink login: JSON parse failed on successful response", {
+             status: response.status,
+             contentType: response.headers.get('content-type')
+           });
            const existingToken = localStorage.getItem("group_charter_auth_token");
            if (existingToken) {
+             console.warn("Falling back to existing token due to parse error");
              window.location.href = "/";
              return;
            }
🤖 Prompt for AI Agents
In platforms/group-charter-manager/src/app/deeplink-login/page.tsx around lines
82 to 94, the JSON parse catch silently redirects to "/" when a token exists,
which masks server-side parse errors; update the catch to capture and log the
parsing error (e.g., console.error or send to telemetry) including the response
status and body when possible, and then decide flow: either setError("Invalid
response from server") and stop loading so the error is visible, or if you must
keep the existing redirect fallback still perform the redirect only after
logging the detailed parse error so server issues can be diagnosed.

@coodos coodos merged commit 91f97eb into main Dec 11, 2025
5 of 6 checks passed
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (5)
infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts (3)

1295-1310: Missing error handling for non-ok poll response.

This issue was flagged in a previous review. If pollResponse.ok is false (e.g., 404, 500), no error is set and signingData.pollDetails remains as "Loading poll details..." indefinitely.


1355-1357: Outer catch block swallows errors without user feedback.

This issue was flagged in a previous review. If an unexpected error occurs, it's only logged to console. Users receive no indication that something went wrong.


1224-1226: Missing error feedback when sign parameters are invalid.

This issue was flagged in a previous review. If the condition at line 1224 evaluates to false (missing session, data, or redirect_uri), the function silently does nothing and the user receives no feedback.

platforms/blabsy/src/pages/deeplink-login.tsx (2)

102-108: Potential double-navigation race.

As flagged in a previous review, this navigation block may race with the user effect (lines 136-144) since signInWithCustomToken updates auth state. Consider removing this explicit navigation and relying solely on the user effect, or vice versa.


135-144: Part of the double-navigation race.

This effect triggers navigation when auth state changes, which occurs immediately after the navigation at lines 102-108. Consider consolidating to a single navigation approach as suggested in the previous review.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba4ae20 and 9691c4c.

📒 Files selected for processing (4)
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte (2 hunks)
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts (2 hunks)
  • platforms/blabsy/src/pages/deeplink-login.tsx (4 hunks)
  • platforms/pictique/src/routes/(auth)/deeplink-login/+page.svelte (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte
  • platforms/pictique/src/routes/(auth)/deeplink-login/+page.svelte
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-21T15:49:12.904Z
Learnt from: coodos
Repo: MetaState-Prototype-Project/prototype PR: 458
File: platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts:60-60
Timestamp: 2025-11-21T15:49:12.904Z
Learning: In platforms/eReputation-api/src/services/ReferenceSigningSessionService.ts, the VITE_EREPUTATION_BASE_URL environment variable intentionally has no fallback value. The service should fail if this environment variable is not set, following a fail-fast design pattern.

Applied to files:

  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts
🧬 Code graph analysis (1)
infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts (1)
platforms/pictique/src/lib/stores/posts.ts (1)
  • error (7-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build
  • GitHub Check: test-web3-adapter-integration
  • GitHub Check: test
🔇 Additional comments (5)
infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts (4)

335-341: LGTM!

Non-blocking navigation to home before triggering external deep links is a reasonable UX pattern. The fire-and-forget approach with error logging is appropriate since the external URL opening is the primary action.


1154-1187: Good defensive additions.

The globalState availability check prevents runtime errors, and the duplicate request guards using drawer state are a sensible approach to prevent overlapping requests from causing UI inconsistencies.


1189-1211: LGTM!

The auth deep link handling properly closes other modals first, has defensive hostname parsing with a fallback, and correctly distinguishes between scan and deep link origins.


1332-1354: LGTM!

The reveal deep link handling properly closes other modals, validates the pollId, and provides appropriate error feedback when the pollId is missing.

platforms/blabsy/src/pages/deeplink-login.tsx (1)

7-11: LGTM on component setup.

The component correctly uses useRouter from next/router for Pages Router and destructures the necessary auth context values.

Comment on lines +1327 to 1330
} catch (error) {
console.error("Error decoding signing data:", error);
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Silent failure on decode error leaves user without feedback.

When base64 decoding or JSON parsing fails at line 1328, the function logs the error and returns without setting signingError. The user sees no indication of why the signing request failed.

                     } catch (error) {
                         console.error("Error decoding signing data:", error);
+                        signingError.set(
+                            "Failed to decode signing data. The deep link may be invalid.",
+                        );
                         return;
                     }
🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts around lines
1327-1330, the catch block currently logs the decode/parse error and returns
silently leaving the UI without feedback; update the catch to set the
component/state variable signingError with a user-friendly message (e.g.,
"Failed to decode signing request — invalid data"), optionally include the
original error for developer logs (processLogger.error or console.error), and
ensure any cleanup/state resets are done before returning so the UI shows the
error to the user.

Comment on lines 48 to 70
if (!ename || !session || !signature) {
setError('Missing required authentication parameters');
setIsLoading(false);
return;
// Add a small delay to allow URL to fully parse before showing error
await new Promise((resolve) => setTimeout(resolve, 500));
// Re-check one more time after delay
const finalParams = new URLSearchParams(
window.location.search ||
(window.location.hash.includes('?')
? window.location.hash.substring(
window.location.hash.indexOf('?') + 1
)
: '') ||
''
);
ename = finalParams.get('ename') || ename;
session = finalParams.get('session') || session;
signature = finalParams.get('signature') || signature;

if (!ename || !session || !signature) {
setError('Missing required authentication parameters');
setIsLoading(false);
return;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Avoid hardcoded delay; use router.isReady instead.

The 500ms delay is an unreliable timing-based workaround. In Next.js Pages Router, the router may not be hydrated on initial render, which is likely the root cause of the missing parameters. Use router.isReady to guard parameter access.

-    useEffect(() => {
+    useEffect(() => {
+        // Wait for router to be ready before accessing URL params
+        if (!router.isReady) return;
+
         const handleDeeplinkLogin = async () => {

Then remove the delay-based fallback logic:

                 if (!ename || !session || !signature) {
-                    // Add a small delay to allow URL to fully parse before showing error
-                    await new Promise((resolve) => setTimeout(resolve, 500));
-                    // Re-check one more time after delay
-                    const finalParams = new URLSearchParams(
-                        window.location.search ||
-                            (window.location.hash.includes('?')
-                                ? window.location.hash.substring(
-                                      window.location.hash.indexOf('?') + 1
-                                  )
-                                : '') ||
-                            ''
-                    );
-                    ename = finalParams.get('ename') || ename;
-                    session = finalParams.get('session') || session;
-                    signature = finalParams.get('signature') || signature;
-
-                    if (!ename || !session || !signature) {
-                        setError('Missing required authentication parameters');
-                        setIsLoading(false);
-                        return;
-                    }
+                    setError('Missing required authentication parameters');
+                    setIsLoading(false);
+                    return;
                 }

And update the dependency array:

-    }, [signInWithCustomToken, router]);
+    }, [signInWithCustomToken, router, router.isReady]);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In platforms/blabsy/src/pages/deeplink-login.tsx around lines 48 to 70, replace
the hardcoded 500ms setTimeout retry logic with a guard that waits for Next.js
router to be ready: remove the delay and the manual URLSearchParams re-check,
and instead only read ename/session/signature after router.isReady is true (or
return early from the effect until router.isReady). Also update the useEffect
dependency array to include router.isReady (and router or router.query as
appropriate) so the effect re-runs when the router becomes ready.

@coodos coodos deleted the fix/ui-ux-improvements-eid-wallet branch December 11, 2025 11:45
@coderabbitai coderabbitai bot mentioned this pull request Jan 16, 2026
13 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants