Skip to content

Commit a3bed7d

Browse files
committed
feat: more doc support
1 parent 4ba8dbd commit a3bed7d

File tree

10 files changed

+4991
-385
lines changed

10 files changed

+4991
-385
lines changed

infrastructure/eid-wallet/src-tauri/gen/android/.idea/kotlinc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
import { getContext, onMount } from "svelte";
1515
import { Shadow } from "svelte-loading-spinners";
1616
import { v4 as uuidv4 } from "uuid";
17+
import DocumentType from "./steps/document-type.svelte";
1718
import Passport from "./steps/passport.svelte";
1819
import Selfie from "./steps/selfie.svelte";
1920
import {
2021
DocFront,
22+
DocBack,
2123
Selfie as SelfiePic,
2224
reason,
2325
status,
@@ -100,7 +102,7 @@
100102
let document: Document;
101103
let loading = $state(false);
102104
let keyManager: KeyManager | null = $state(null);
103-
let websocketData: any = $state(null); // Store websocket data for duplicate case
105+
let websocketData: { w3id?: string } | null = $state(null); // Store websocket data for duplicate case
104106
let hardwareKeySupported = $state(false);
105107
let hardwareKeyCheckComplete = $state(false);
106108
@@ -135,9 +137,10 @@
135137
websocketData = data; // Store the full websocket data
136138
if (data.status === "resubmission_requested") {
137139
DocFront.set(null);
140+
DocBack.set(null);
138141
SelfiePic.set(null);
139142
}
140-
verifStep.set(2);
143+
verifStep.set(3);
141144
};
142145
}
143146
@@ -182,8 +185,12 @@
182185
await initializeKeyManager();
183186
}
184187
188+
if (!keyManager) {
189+
throw new Error("Key manager not initialized");
190+
}
191+
185192
try {
186-
const res = await keyManager!.generate("default");
193+
const res = await keyManager.generate("default");
187194
console.log("Key generation result:", res);
188195
return res;
189196
} catch (e) {
@@ -197,8 +204,12 @@
197204
await initializeKeyManager();
198205
}
199206
207+
if (!keyManager) {
208+
throw new Error("Key manager not initialized");
209+
}
210+
200211
try {
201-
const res = await keyManager!.getPublicKey("default");
212+
const res = await keyManager.getPublicKey("default");
202213
console.log("Public key retrieved:", res);
203214
return res;
204215
} catch (e) {
@@ -218,9 +229,11 @@
218229
219230
// Initialize key manager and check if default key pair exists
220231
await initializeKeyManager();
221-
const keyExists = await keyManager!.exists("default");
222-
if (!keyExists) {
223-
await generateApplicationKeyPair();
232+
if (keyManager) {
233+
const keyExists = await keyManager.exists("default");
234+
if (!keyExists) {
235+
await generateApplicationKeyPair();
236+
}
224237
}
225238
226239
handleContinue = async () => {
@@ -342,8 +355,10 @@
342355
<Drawer bind:isPaneOpen={showVeriffModal}>
343356
<div class="overflow-y-scroll">
344357
{#if $verifStep === 0}
345-
<Passport></Passport>
358+
<DocumentType></DocumentType>
346359
{:else if $verifStep === 1}
360+
<Passport></Passport>
361+
{:else if $verifStep === 2}
347362
<Selfie></Selfie>
348363
{:else if loading}
349364
<div class="my-20">
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
<script lang="ts">
2+
import { goto } from "$app/navigation";
3+
import { page } from "$app/stores";
4+
import { Button } from "$lib";
5+
import { embedClient } from "$lib/axios/axios";
6+
import {
7+
DocBack,
8+
DocFront,
9+
permissionGranted,
10+
verifStep,
11+
} from "$lib/store/store";
12+
import { writable } from "svelte/store";
13+
14+
let video: HTMLVideoElement;
15+
let canvas1: HTMLCanvasElement;
16+
let canvas2: HTMLCanvasElement;
17+
let image = 1;
18+
let image1Captured = writable(false);
19+
let image2Captured = writable(false);
20+
let documentType: "passport" | "id" | "permit" | "dl" | null = null;
21+
let loading = false;
22+
async function requestCameraPermission() {
23+
try {
24+
const stream = await navigator.mediaDevices.getUserMedia({
25+
video: { facingMode: "environment" },
26+
});
27+
video.srcObject = stream;
28+
video.play();
29+
permissionGranted.set(true);
30+
} catch (err) {
31+
permissionGranted.set(false);
32+
console.error("Camera permission denied", err);
33+
}
34+
}
35+
function watchForDocument(doc: typeof documentType) {
36+
if (doc !== null) {
37+
requestCameraPermission();
38+
}
39+
}
40+
41+
$: watchForDocument(documentType);
42+
43+
async function captureImage() {
44+
const id = $page.params.verificationId;
45+
if (image === 1) {
46+
console.log("huh?");
47+
const context1 = canvas1.getContext("2d");
48+
if (context1) {
49+
context1.drawImage(video, 0, 0, 1920, 1080);
50+
canvas1.width = video.videoWidth;
51+
canvas1.height = video.videoHeight;
52+
context1.drawImage(video, 0, 0, canvas1.width, canvas1.height);
53+
const dataUrl = canvas1.toDataURL("image/png");
54+
DocFront.set(dataUrl);
55+
loading = true;
56+
await embedClient.post(`/verification/${id}/media`, {
57+
img: dataUrl,
58+
type: "document-front",
59+
});
60+
loading = false;
61+
image1Captured.set(true);
62+
image = 2;
63+
}
64+
} else if (
65+
(documentType === "passport" && $image1Captured) ||
66+
(image === 3 && $image1Captured && $image2Captured)
67+
) {
68+
verifStep.update((n) => n + 1);
69+
} else if (image === 2) {
70+
console.log("hmmm?");
71+
const context2 = canvas2.getContext("2d");
72+
if (context2) {
73+
context2.drawImage(video, 0, 0, 1920, 1080);
74+
canvas2.width = video.videoWidth;
75+
canvas2.height = video.videoHeight;
76+
context2.drawImage(video, 0, 0, canvas2.width, canvas2.height);
77+
const dataUrl = canvas2.toDataURL("image/png");
78+
DocBack.set(dataUrl);
79+
image2Captured.set(true);
80+
image = 3;
81+
loading = true;
82+
await embedClient.post(`/verification/${id}/media`, {
83+
img: dataUrl,
84+
type: "document-back",
85+
});
86+
loading = false;
87+
}
88+
}
89+
}
90+
91+
function retakeImages() {
92+
image1Captured.set(false);
93+
image2Captured.set(false);
94+
image = 1;
95+
const context1 = canvas1.getContext("2d");
96+
context1.clearRect(0, 0, canvas1.width, canvas1.height);
97+
const context2 = canvas2.getContext("2d");
98+
context2.clearRect(0, 0, canvas2.width, canvas2.height);
99+
DocFront.set(null);
100+
DocBack.set(null);
101+
}
102+
</script>
103+
104+
<div class="flex flex-col items-center justify-center gap-5">
105+
<h1 class="mb-2 text-center text-xl font-extrabold text-white md:text-3xl">
106+
{#if documentType}
107+
Take a photo of your document’s {$DocFront ? "back" : "front"} page
108+
{:else}
109+
Choose from one of the following document types{/if}
110+
</h1>
111+
112+
{#if documentType}
113+
<div class="flex flex-col items-center gap-1">
114+
<div class="relative flex flex-col items-center justify-center">
115+
<!-- svelte-ignore a11y-media-has-caption -->
116+
<video
117+
bind:this={video}
118+
autoplay
119+
playsinline
120+
class=" aspect-[4/3] w-full rounded-lg object-cover"
121+
></video>
122+
<img
123+
src="/images/CameraFrame.svg"
124+
class="absolute left-[50%] top-[50%] w-[90%] translate-x-[-50%] translate-y-[-50%]"
125+
alt=""
126+
/>
127+
</div>
128+
<br />
129+
<canvas bind:this={canvas1} class="hidden"></canvas>
130+
<canvas bind:this={canvas2} class="hidden"></canvas>
131+
<div class="flex w-full justify-center gap-2">
132+
{#if $DocFront}
133+
<img
134+
class="h-[100px] w-full max-w-[177px] object-cover"
135+
src={$DocFront}
136+
alt=""
137+
/>
138+
{:else}
139+
<div
140+
class="div flex h-[100px] w-full max-w-[177px] items-center justify-center rounded-md border-2 border-gray-600 bg-gray-700"
141+
>
142+
<h2>Document Front</h2>
143+
</div>
144+
{/if}
145+
146+
{#if documentType !== "passport"}
147+
{#if $DocBack}
148+
<img
149+
class="h-[100px] w-full max-w-[177px] object-cover"
150+
src={$DocBack}
151+
alt=""
152+
/>
153+
{:else}
154+
<div
155+
class="div flex h-[100px] w-full max-w-[177px] items-center justify-center rounded-md border-2 border-gray-600 bg-gray-700"
156+
>
157+
<h2>Document Back</h2>
158+
</div>
159+
{/if}
160+
{/if}
161+
</div>
162+
</div>
163+
<div class="text-center text-xs text-white">
164+
Accepted documents: Driver’s License, Residence Permit, Passport, ID
165+
Card.
166+
</div>
167+
{#if (documentType !== "passport" && $image1Captured && $image2Captured) || (documentType === "passport" && $image1Captured)}
168+
<!-- svelte-ignore a11y-click-events-have-key-events -->
169+
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
170+
<h2
171+
class="cursor-pointer rounded border-2 border-brand-green px-2 text-center font-medium text-brand-green_dark hover:border-green-200"
172+
on:click={() => {
173+
retakeImages();
174+
}}
175+
>
176+
Retake
177+
</h2>
178+
{/if}
179+
<div
180+
class={`flex w-full flex-col items-center gap-4 border-t-2 border-t-gray-700 pt-4`}
181+
>
182+
<Button
183+
buttonClass="w-full"
184+
color={loading ? "alternative" : "primary"}
185+
disabled={loading}
186+
on:click={captureImage}
187+
>{loading
188+
? "Processing..."
189+
: (documentType === "passport" && image === 2) ||
190+
image === 3
191+
? "Done"
192+
: "Take Photo"}</Button
193+
>
194+
<Button
195+
buttonClass="w-full"
196+
color="alternative"
197+
on:click={() => {
198+
goto("/");
199+
verifStep.set(0);
200+
}}>Cancel</Button
201+
>
202+
</div>
203+
{:else}
204+
<button
205+
class="w-full rounded-md border-2 px-4 py-2 dark:border-gray-600 dark:bg-gray-700"
206+
on:click={() => (documentType = "passport")}>Passport</button
207+
>
208+
<button
209+
class="w-full rounded-md border-2 px-4 py-2 dark:border-gray-600 dark:bg-gray-700"
210+
on:click={() => (documentType = "id")}>ID Card</button
211+
>
212+
<button
213+
class="w-full rounded-md border-2 px-4 py-2 dark:border-gray-600 dark:bg-gray-700"
214+
on:click={() => (documentType = "dl")}>Driving License</button
215+
>
216+
<button
217+
class="w-full rounded-md border-2 px-4 py-2 dark:border-gray-600 dark:bg-gray-700"
218+
on:click={() => (documentType = "permit")}>Residence Permit</button
219+
>
220+
{/if}
221+
</div>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<script lang="ts">
2+
import { ButtonAction } from "$lib/ui";
3+
import { documentType, verifStep } from "../store";
4+
5+
function selectDocumentType(type: "passport" | "id" | "permit" | "dl") {
6+
documentType.set(type);
7+
verifStep.set(1); // Move to document capture step
8+
}
9+
</script>
10+
11+
<div class="flex flex-col gap-5">
12+
<div>
13+
<h3>Choose Document Type</h3>
14+
<p>Select the type of identity document you will be presenting</p>
15+
</div>
16+
17+
<div class="flex flex-col gap-3">
18+
<ButtonAction
19+
class="w-full"
20+
callback={() => selectDocumentType("passport")}
21+
>
22+
Passport
23+
</ButtonAction>
24+
25+
<ButtonAction
26+
class="w-full"
27+
callback={() => selectDocumentType("id")}
28+
>
29+
ID Card
30+
</ButtonAction>
31+
32+
<ButtonAction
33+
class="w-full"
34+
callback={() => selectDocumentType("dl")}
35+
>
36+
Driving License
37+
</ButtonAction>
38+
39+
<ButtonAction
40+
class="w-full"
41+
callback={() => selectDocumentType("permit")}
42+
>
43+
Residence Permit
44+
</ButtonAction>
45+
</div>
46+
47+
<div class="text-center text-xs text-white">
48+
Accepted documents: Driver's License, Residence Permit, Passport, ID Card.
49+
</div>
50+
</div>
51+

0 commit comments

Comments
 (0)