Skip to content

Commit efec49b

Browse files
committed
feat: health check
1 parent 8216692 commit efec49b

File tree

3 files changed

+139
-4
lines changed

3 files changed

+139
-4
lines changed

infrastructure/eid-wallet/src/lib/global/controllers/evault.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,43 @@ export class VaultController {
127127
}
128128
}
129129

130+
/**
131+
* Simple health check: just checks if registry can resolve the w3id
132+
* Returns the URI if healthy, throws error if not
133+
*/
134+
async checkHealth(w3id: string): Promise<{ healthy: boolean; deleted?: boolean; uri?: string; error?: string }> {
135+
try {
136+
console.log(`🏥 Checking eVault health for ${w3id}...`);
137+
const response = await axios.get(
138+
new URL(
139+
`resolve?w3id=${w3id}`,
140+
PUBLIC_REGISTRY_URL,
141+
).toString(),
142+
{
143+
timeout: 3000 // 3 second timeout
144+
}
145+
);
146+
147+
if (response.data?.uri) {
148+
console.log(`✅ eVault is healthy, URI: ${response.data.uri}`);
149+
return { healthy: true, uri: response.data.uri };
150+
} else {
151+
console.warn(`⚠️ Registry responded but no URI found`);
152+
return { healthy: false, error: "No URI in registry response" };
153+
}
154+
} catch (error) {
155+
// Check if it's a 404 - eVault has been deleted
156+
if (axios.isAxiosError(error) && error.response?.status === 404) {
157+
console.error(`🗑️ eVault not found in registry (404) - it has been deleted`);
158+
return { healthy: false, deleted: true, error: "eVault has been deleted from registry" };
159+
}
160+
161+
const errorMessage = error instanceof Error ? error.message : String(error);
162+
console.error(`❌ eVault health check failed: ${errorMessage}`);
163+
return { healthy: false, error: errorMessage };
164+
}
165+
}
166+
130167
/**
131168
* Resolve eVault endpoint from registry with retry logic
132169
*/
@@ -141,6 +178,9 @@ export class VaultController {
141178
`resolve?w3id=${w3id}`,
142179
PUBLIC_REGISTRY_URL,
143180
).toString(),
181+
{
182+
timeout: 5000 // 5 second timeout for resolve
183+
}
144184
);
145185
return new URL("/graphql", response.data.uri).toString();
146186
} catch (error) {

infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface IDrawerProps extends HTMLAttributes<HTMLDivElement> {
99
isPaneOpen?: boolean;
1010
children?: Snippet;
1111
handleSwipe?: (isOpen: boolean | undefined) => void;
12+
dismissible?: boolean;
1213
}
1314
1415
let drawerElem: HTMLDivElement;
@@ -18,6 +19,7 @@ let {
1819
isPaneOpen = $bindable(),
1920
children = undefined,
2021
handleSwipe,
22+
dismissible = true,
2123
...restProps
2224
}: IDrawerProps = $props();
2325
@@ -33,16 +35,22 @@ $effect(() => {
3335
pane = new CupertinoPane(drawerElem, {
3436
fitHeight: true,
3537
backdrop: true,
36-
backdropOpacity: 0.5,
38+
backdropOpacity: dismissible ? 0.5 : 0.8,
3739
backdropBlur: true,
38-
bottomClose: true,
40+
bottomClose: dismissible,
3941
buttonDestroy: false,
40-
showDraggable: true,
42+
showDraggable: dismissible,
4143
upperThanTop: true,
4244
breaks: {
4345
bottom: { enabled: true, height: 250 },
4446
},
4547
initialBreak: "bottom",
48+
onBackdropTap: () => {
49+
if (!dismissible) {
50+
// Prevent closing on backdrop tap if not dismissible
51+
return false;
52+
}
53+
},
4654
});
4755
4856
return () => pane?.destroy();

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

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { goto } from "$app/navigation";
33
import { Hero } from "$lib/fragments";
44
import type { GlobalState } from "$lib/global";
5-
import { InputPin } from "$lib/ui";
5+
import { InputPin, Drawer } from "$lib/ui";
66
import * as Button from "$lib/ui/Button";
77
import {
88
type AuthOptions,
@@ -17,6 +17,7 @@ let clearPin = $state(async () => {});
1717
let handlePinInput = $state((pin: string) => {});
1818
let globalState: GlobalState | undefined = $state(undefined);
1919
let hasPendingDeepLink = $state(false);
20+
let isDeletedVaultModalOpen = $state(false);
2021
2122
const authOpts: AuthOptions = {
2223
allowDeviceCredential: false,
@@ -32,6 +33,18 @@ const authOpts: AuthOptions = {
3233
confirmationRequired: true,
3334
};
3435
36+
const getGlobalState = getContext<() => GlobalState>("globalState");
37+
const setGlobalState = getContext<(value: GlobalState) => void>("setGlobalState");
38+
39+
async function nukeWallet() {
40+
if (!globalState) return;
41+
const newGlobalState = await globalState.reset();
42+
setGlobalState(newGlobalState);
43+
globalState = newGlobalState;
44+
isDeletedVaultModalOpen = false;
45+
await goto("/onboarding");
46+
}
47+
3548
onMount(async () => {
3649
globalState = getContext<() => GlobalState>("globalState")();
3750
if (!globalState) {
@@ -63,6 +76,27 @@ onMount(async () => {
6376
return;
6477
}
6578
79+
// Check eVault health after successful login
80+
try {
81+
const vault = await globalState?.vaultController.vault;
82+
if (vault?.ename) {
83+
const healthCheck = await globalState.vaultController.checkHealth(vault.ename);
84+
if (!healthCheck.healthy) {
85+
console.warn("eVault health check failed:", healthCheck.error);
86+
87+
// If eVault was deleted (404), show modal
88+
if (healthCheck.deleted) {
89+
isDeletedVaultModalOpen = true;
90+
return; // Don't continue to app
91+
}
92+
// For other errors, continue to app - non-blocking
93+
}
94+
}
95+
} catch (error) {
96+
console.error("Error during eVault health check:", error);
97+
// Continue to app even if health check fails - non-blocking
98+
}
99+
66100
// Check if there's a pending deep link to process
67101
const pendingDeepLink = sessionStorage.getItem("pendingDeepLink");
68102
if (pendingDeepLink) {
@@ -108,6 +142,27 @@ onMount(async () => {
108142
authOpts,
109143
);
110144
145+
// Check eVault health after successful biometric login
146+
try {
147+
const vault = await globalState.vaultController.vault;
148+
if (vault?.ename) {
149+
const healthCheck = await globalState.vaultController.checkHealth(vault.ename);
150+
if (!healthCheck.healthy) {
151+
console.warn("eVault health check failed:", healthCheck.error);
152+
153+
// If eVault was deleted (404), show modal
154+
if (healthCheck.deleted) {
155+
isDeletedVaultModalOpen = true;
156+
return; // Don't continue to app
157+
}
158+
// For other errors, continue to app - non-blocking
159+
}
160+
}
161+
} catch (error) {
162+
console.error("Error during eVault health check:", error);
163+
// Continue to app even if health check fails - non-blocking
164+
}
165+
111166
// Check if there's a pending deep link to process
112167
const pendingDeepLink = sessionStorage.getItem("pendingDeepLink");
113168
if (pendingDeepLink) {
@@ -189,3 +244,35 @@ onMount(async () => {
189244
Clear PIN
190245
</Button.Action>
191246
</main>
247+
248+
<!-- Deleted eVault Modal - Non-dismissible -->
249+
<Drawer bind:isPaneOpen={isDeletedVaultModalOpen} dismissible={false}>
250+
<div class="text-center">
251+
<h4 class="mt-[2.3svh] mb-[0.5svh] text-red-600">
252+
🗑️ eVault Has Been Deleted
253+
</h4>
254+
<p class="text-black-700 mb-4">
255+
Your eVault has been deleted from the registry and is no longer accessible.
256+
</p>
257+
<div class="bg-red-50 border border-red-200 rounded-md p-4 mb-6">
258+
<p class="text-red-800 font-medium">
259+
To continue using the app, you need to delete your local account data and start fresh.
260+
</p>
261+
</div>
262+
<ul class="text-left text-black-700 mb-6 space-y-2">
263+
<li>• All your local data will be deleted</li>
264+
<li>• Your ePassport will be removed</li>
265+
<li>• You will need to onboard again</li>
266+
<li>• This action cannot be undone</li>
267+
</ul>
268+
<p class="text-black-800 mb-4 font-semibold">
269+
You must delete your local data to continue.
270+
</p>
271+
<div class="flex gap-3">
272+
<Button.Action
273+
class="flex-1 bg-red-600 hover:bg-red-700"
274+
callback={nukeWallet}>Delete Local Data</Button.Action
275+
>
276+
</div>
277+
</div>
278+
</Drawer>

0 commit comments

Comments
 (0)