@arkyc/widget 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/_virtual/_virtual_arkyc-theme-css.mjs +1 -1
- package/dist/arkyc-widget.iife.global.js +57 -3
- package/dist/arkyc-widget.iife.global.js.map +1 -1
- package/dist/client.d.mts +27 -0
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +24 -0
- package/dist/client.mjs.map +1 -1
- package/dist/controller.d.mts +63 -6
- package/dist/controller.d.mts.map +1 -1
- package/dist/controller.mjs +107 -8
- package/dist/controller.mjs.map +1 -1
- package/dist/flow.d.mts.map +1 -1
- package/dist/flow.mjs +1 -0
- package/dist/flow.mjs.map +1 -1
- package/dist/h.mjs +64 -0
- package/dist/h.mjs.map +1 -0
- package/dist/index.d.mts +3 -3
- package/dist/types.d.mts +26 -2
- package/dist/types.d.mts.map +1 -1
- package/dist/ui.d.mts +19 -0
- package/dist/ui.d.mts.map +1 -0
- package/dist/ui.mjs +257 -183
- package/dist/ui.mjs.map +1 -1
- package/package.json +2 -2
package/dist/ui.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { Camera } from "./capture.mjs";
|
|
|
3
3
|
import { DEFAULT_TUNING, createDefaultFaceAnalyzer, isSelfieReady, makeChallengeDetector } from "./face.mjs";
|
|
4
4
|
import { DEFAULT_DOCUMENT_TUNING, createDefaultDocumentAnalyzer, documentGuidance } from "./document.mjs";
|
|
5
5
|
import { countries } from "./countries.mjs";
|
|
6
|
+
import { createDom } from "./h.mjs";
|
|
6
7
|
//#region src/ui.ts
|
|
7
8
|
const DOCUMENT_LABELS = {
|
|
8
9
|
passport: "Passport",
|
|
@@ -99,36 +100,36 @@ var WidgetView = class {
|
|
|
99
100
|
timers = [];
|
|
100
101
|
/** Extra teardown run on each re-render (cancels in-flight detection loops). */
|
|
101
102
|
cleanups = [];
|
|
103
|
+
/** DOM hyperscript helpers (`h`/`append`/`field`) bound to this view's document (see {@link createDom}). */
|
|
104
|
+
dom;
|
|
102
105
|
constructor(doc, theme, handlers, nav = globalThis.navigator, analyzer = createDefaultFaceAnalyzer(), tuning = DEFAULT_TUNING, docAnalyzer = createDefaultDocumentAnalyzer(), docTuning = DEFAULT_DOCUMENT_TUNING) {
|
|
103
106
|
this.doc = doc;
|
|
104
107
|
this.theme = theme;
|
|
105
108
|
this.handlers = handlers;
|
|
109
|
+
this.dom = createDom(doc);
|
|
106
110
|
this.camera = new Camera(doc, nav);
|
|
107
111
|
this.analyzer = analyzer;
|
|
108
112
|
this.tuning = tuning;
|
|
109
113
|
this.docAnalyzer = docAnalyzer;
|
|
110
114
|
this.docTuning = docTuning;
|
|
111
|
-
this.root = this.
|
|
112
|
-
this.styleEl = this.
|
|
115
|
+
this.root = this.dom.h("div", { class: "arkyc-root" });
|
|
116
|
+
this.styleEl = this.dom.h("style", { text: theme.stylesheet() });
|
|
113
117
|
this.root.appendChild(this.styleEl);
|
|
114
|
-
|
|
115
|
-
const header = this.el("div", { class: "arkyc-header" });
|
|
116
|
-
this.brandEl = this.el("div", { class: "arkyc-brand" });
|
|
118
|
+
this.brandEl = this.dom.h("div", { class: "arkyc-brand" });
|
|
117
119
|
this.fillBrand();
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
this.body = this.dom.h("div", { class: "arkyc-body" });
|
|
121
|
+
this.footer = this.dom.h("div", { class: "arkyc-footer" });
|
|
122
|
+
const header = this.dom.h("div", { class: "arkyc-header" }, [this.brandEl, this.dom.h("button", {
|
|
120
123
|
class: "arkyc-close",
|
|
121
124
|
html: "×",
|
|
122
|
-
"aria-label": "Close"
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
card.appendChild(this.footer);
|
|
131
|
-
this.root.appendChild(card);
|
|
125
|
+
"aria-label": "Close",
|
|
126
|
+
on: { click: () => this.handlers.onClose() }
|
|
127
|
+
})]);
|
|
128
|
+
this.root.appendChild(this.dom.h("div", { class: "arkyc-card" }, [
|
|
129
|
+
header,
|
|
130
|
+
this.body,
|
|
131
|
+
this.footer
|
|
132
|
+
]));
|
|
132
133
|
}
|
|
133
134
|
/**
|
|
134
135
|
* Populate the header brand group from the current theme: the project logo
|
|
@@ -137,16 +138,14 @@ var WidgetView = class {
|
|
|
137
138
|
fillBrand() {
|
|
138
139
|
this.clear(this.brandEl);
|
|
139
140
|
const lastTenMins = Date.now() - Math.floor(Math.random() * (600 * 1e3));
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}));
|
|
149
|
-
} else this.brandEl.appendChild(this.el("p", {
|
|
141
|
+
const branded = this.theme.showBranding && (this.theme.logoUrl || this.theme.name);
|
|
142
|
+
this.dom.append(this.brandEl, branded ? [this.theme.logoUrl && this.dom.h("img", {
|
|
143
|
+
class: "arkyc-logo",
|
|
144
|
+
src: `${this.theme.logoUrl}?stamp=${lastTenMins}`
|
|
145
|
+
}), this.theme.name && this.dom.h("span", {
|
|
146
|
+
class: "arkyc-brand-name",
|
|
147
|
+
text: this.theme.name
|
|
148
|
+
})] : this.dom.h("p", {
|
|
150
149
|
class: "arkyc-title",
|
|
151
150
|
text: "Verify your identity"
|
|
152
151
|
}));
|
|
@@ -202,6 +201,8 @@ var WidgetView = class {
|
|
|
202
201
|
case "selfie_capture": return this.renderCapture("Take a selfie", "user", state.allowSkip, true, state.strictCapture);
|
|
203
202
|
case "active_liveness": return this.renderActiveLiveness(state.livenessChallenges ?? [], state.allowSkip, state.requireLiveCamera);
|
|
204
203
|
case "ocr_processing": return this.renderProcessing("Reading your document…");
|
|
204
|
+
case "address_entry": return this.renderAddressForm(state.addressMethods ?? []);
|
|
205
|
+
case "address_processing": return this.renderProcessing("Verifying your address…");
|
|
205
206
|
case "passive_liveness": return this.renderProcessing("Checking liveness…");
|
|
206
207
|
case "face_match": return this.renderProcessing("Matching your face…");
|
|
207
208
|
case "processing": return this.renderProcessing(state.statusLabel ?? "Finalising verification…");
|
|
@@ -214,23 +215,21 @@ var WidgetView = class {
|
|
|
214
215
|
/** Prepend a decorative illustration for the given screen, when one exists. */
|
|
215
216
|
appendArt(key) {
|
|
216
217
|
const art = STEP_ART[key];
|
|
217
|
-
if (art) this.body.appendChild(this.
|
|
218
|
+
if (art) this.body.appendChild(this.dom.h("div", {
|
|
218
219
|
class: "arkyc-illus",
|
|
219
220
|
html: art
|
|
220
221
|
}));
|
|
221
222
|
}
|
|
222
223
|
renderWelcome(handoffAvailable) {
|
|
223
224
|
this.appendArt("welcome");
|
|
224
|
-
this.
|
|
225
|
+
this.dom.append(this.body, [this.dom.h("h2", {
|
|
225
226
|
class: "arkyc-h",
|
|
226
227
|
text: "Verify your identity"
|
|
227
|
-
})
|
|
228
|
-
this.body.appendChild(this.el("p", {
|
|
228
|
+
}), this.dom.h("p", {
|
|
229
229
|
class: "arkyc-p",
|
|
230
230
|
text: "You will need a government-issued ID and a moment to take a selfie. Your data is processed securely."
|
|
231
|
-
}));
|
|
232
|
-
this.
|
|
233
|
-
if (handoffAvailable) this.footer.appendChild(this.button("Continue on your phone", () => this.handlers.onUsePhone(), "arkyc-btn-ghost"));
|
|
231
|
+
})]);
|
|
232
|
+
this.dom.append(this.footer, [this.button("Get started", () => this.handlers.onStart()), handoffAvailable && this.button("Continue on your phone", () => this.handlers.onUsePhone(), "arkyc-btn-ghost")]);
|
|
234
233
|
}
|
|
235
234
|
/** A neutral connecting screen shown while the widget bootstraps the session. */
|
|
236
235
|
renderLoading(label = "Loading…") {
|
|
@@ -251,14 +250,13 @@ var WidgetView = class {
|
|
|
251
250
|
this.clear(this.footer);
|
|
252
251
|
this.root.classList.remove("arkyc-handoff");
|
|
253
252
|
this.appendArt(step);
|
|
254
|
-
this.
|
|
253
|
+
this.dom.append(this.body, [this.dom.h("h2", {
|
|
255
254
|
class: "arkyc-h",
|
|
256
255
|
text: title
|
|
257
|
-
})
|
|
258
|
-
this.body.appendChild(this.el("p", {
|
|
256
|
+
}), this.dom.h("p", {
|
|
259
257
|
class: "arkyc-p",
|
|
260
258
|
text: body
|
|
261
|
-
}));
|
|
259
|
+
})]);
|
|
262
260
|
this.footer.appendChild(this.button(cta, onContinue));
|
|
263
261
|
}
|
|
264
262
|
/**
|
|
@@ -271,85 +269,76 @@ var WidgetView = class {
|
|
|
271
269
|
this.clear(this.body);
|
|
272
270
|
this.clear(this.footer);
|
|
273
271
|
this.root.classList.add("arkyc-handoff");
|
|
274
|
-
this.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
this.body.appendChild(waiting);
|
|
272
|
+
this.dom.append(this.body, [
|
|
273
|
+
this.dom.h("h2", {
|
|
274
|
+
class: "arkyc-h",
|
|
275
|
+
text: "Continue on your phone"
|
|
276
|
+
}),
|
|
277
|
+
this.dom.h("p", {
|
|
278
|
+
class: "arkyc-p",
|
|
279
|
+
text: "Scan this code with your phone camera to finish verifying there."
|
|
280
|
+
}),
|
|
281
|
+
this.dom.h("div", {
|
|
282
|
+
class: "arkyc-qr",
|
|
283
|
+
html: qrSvg
|
|
284
|
+
}),
|
|
285
|
+
this.dom.h("div", { class: "arkyc-handoff-wait" }, [this.dom.h("div", { class: "arkyc-spinner sm" }), this.dom.h("span", { text: "Waiting for your phone…" })])
|
|
286
|
+
]);
|
|
290
287
|
if (allowContinueHere) this.footer.appendChild(this.button("Continue on this device", () => this.handlers.onContinueHere(), "arkyc-btn-ghost"));
|
|
291
288
|
}
|
|
292
289
|
renderDocumentSelection() {
|
|
293
|
-
this.body.appendChild(this.
|
|
290
|
+
this.body.appendChild(this.dom.h("h2", {
|
|
294
291
|
class: "arkyc-h",
|
|
295
292
|
text: "Select your document"
|
|
296
293
|
}));
|
|
297
|
-
const country = this.
|
|
294
|
+
const country = this.dom.field("Country", {
|
|
298
295
|
class: "arkyc-btn arkyc-btn-ghost arkyc-text-center",
|
|
299
296
|
name: "country",
|
|
300
297
|
autocomplete: "country",
|
|
301
|
-
|
|
298
|
+
choices: countries.map(({ name, iso2, flag }) => ({
|
|
299
|
+
value: iso2,
|
|
300
|
+
label: `${flag} ${name}`
|
|
301
|
+
}))
|
|
302
302
|
});
|
|
303
|
-
const
|
|
304
|
-
value: "",
|
|
305
|
-
selected: "selected"
|
|
306
|
-
});
|
|
307
|
-
placeholder.textContent = "Country";
|
|
308
|
-
country.appendChild(placeholder);
|
|
309
|
-
countries.forEach(({ name, iso2, flag }) => {
|
|
310
|
-
const option = this.el("option", { value: iso2 });
|
|
311
|
-
option.textContent = `${flag} ${name}`;
|
|
312
|
-
country.appendChild(option);
|
|
313
|
-
});
|
|
314
|
-
const choices = this.el("div", { class: "arkyc-choices" });
|
|
315
|
-
choices.appendChild(country);
|
|
316
|
-
Object.keys(DOCUMENT_LABELS).forEach((type) => {
|
|
303
|
+
const buttons = Object.keys(DOCUMENT_LABELS).map((type) => {
|
|
317
304
|
const btn = this.button(DOCUMENT_LABELS[type], () => this.handlers.onDocumentSelected(type, (country.value || "").trim().toUpperCase()));
|
|
318
305
|
btn.classList.add("arkyc-btn-ghost");
|
|
319
|
-
|
|
306
|
+
return btn;
|
|
320
307
|
});
|
|
321
|
-
this.body.appendChild(choices);
|
|
308
|
+
this.body.appendChild(this.dom.h("div", { class: "arkyc-choices" }, [country, ...buttons]));
|
|
322
309
|
}
|
|
323
310
|
renderCapture(title, facing, allowSkip, selfie = false, strict = false) {
|
|
324
311
|
const strictDoc = strict && !selfie;
|
|
325
|
-
this.
|
|
326
|
-
class: "arkyc-h",
|
|
327
|
-
text: title
|
|
328
|
-
}));
|
|
329
|
-
this.body.appendChild(this.el("p", {
|
|
330
|
-
class: "arkyc-p",
|
|
331
|
-
text: "Position it clearly in frame, then capture."
|
|
332
|
-
}));
|
|
333
|
-
const fileInput = this.el("input", {
|
|
312
|
+
const fileInput = this.dom.h("input", {
|
|
334
313
|
type: "file",
|
|
335
314
|
accept: "image/*",
|
|
336
|
-
class: "arkyc-hidden"
|
|
315
|
+
class: "arkyc-hidden",
|
|
316
|
+
on: { change: () => this.handlers.onImage(Camera.fileFromInput(fileInput)) }
|
|
337
317
|
});
|
|
338
|
-
|
|
339
|
-
|
|
318
|
+
this.dom.append(this.body, [
|
|
319
|
+
this.dom.h("h2", {
|
|
320
|
+
class: "arkyc-h",
|
|
321
|
+
text: title
|
|
322
|
+
}),
|
|
323
|
+
this.dom.h("p", {
|
|
324
|
+
class: "arkyc-p",
|
|
325
|
+
text: "Position it clearly in frame, then capture."
|
|
326
|
+
}),
|
|
327
|
+
fileInput
|
|
328
|
+
]);
|
|
340
329
|
if (this.camera.supported) {
|
|
341
|
-
const video = this.
|
|
330
|
+
const video = this.dom.h("video", { class: `arkyc-preview${selfie ? " selfie" : ""}` });
|
|
342
331
|
const face = selfie ? this.buildFaceStage(video) : null;
|
|
343
332
|
const doc = selfie ? null : this.buildDocStage(video);
|
|
344
333
|
const mount = face?.stage ?? doc.stage;
|
|
345
334
|
this.body.appendChild(mount);
|
|
346
|
-
const hint = this.
|
|
335
|
+
const hint = this.dom.h("p", { class: "arkyc-p arkyc-hint" });
|
|
347
336
|
this.body.appendChild(hint);
|
|
348
337
|
const confirmCapture = (blob) => {
|
|
349
338
|
this.destroy();
|
|
350
339
|
if (blob) {
|
|
351
340
|
const url = URL.createObjectURL(blob);
|
|
352
|
-
const still = this.
|
|
341
|
+
const still = this.dom.h("img", {
|
|
353
342
|
class: `arkyc-preview${selfie ? " selfie" : ""}`,
|
|
354
343
|
src: url
|
|
355
344
|
});
|
|
@@ -382,16 +371,14 @@ var WidgetView = class {
|
|
|
382
371
|
this.runDocumentAutoCapture(video, hint, doc, strictDoc, onCapture);
|
|
383
372
|
if (!strictDoc) this.footer.appendChild(this.button("Capture", onCapture));
|
|
384
373
|
}
|
|
385
|
-
} else if (strictDoc) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}));
|
|
394
|
-
} else {
|
|
374
|
+
} else if (strictDoc) this.dom.append(this.body, [this.dom.h("div", {
|
|
375
|
+
class: "arkyc-badge err",
|
|
376
|
+
html: "!"
|
|
377
|
+
}), this.dom.h("p", {
|
|
378
|
+
class: "arkyc-p",
|
|
379
|
+
text: "This step needs a working camera to scan your document. Please retry on a device with a camera."
|
|
380
|
+
})]);
|
|
381
|
+
else {
|
|
395
382
|
const upload = this.button("Upload photo", () => fileInput.click());
|
|
396
383
|
this.footer.appendChild(upload);
|
|
397
384
|
}
|
|
@@ -585,18 +572,17 @@ var WidgetView = class {
|
|
|
585
572
|
* @returns
|
|
586
573
|
*/
|
|
587
574
|
renderActiveLiveness(challenges, allowSkip, requireLiveCamera) {
|
|
588
|
-
this.
|
|
589
|
-
class: "arkyc-h",
|
|
590
|
-
text: "Liveness check"
|
|
591
|
-
}));
|
|
592
|
-
const prompt = this.el("p", {
|
|
575
|
+
const prompt = this.dom.h("p", {
|
|
593
576
|
class: "arkyc-p",
|
|
594
577
|
text: "Follow the on-screen prompts. Keep your face centred in the circle."
|
|
595
578
|
});
|
|
596
|
-
this.body.
|
|
579
|
+
this.dom.append(this.body, [this.dom.h("h2", {
|
|
580
|
+
class: "arkyc-h",
|
|
581
|
+
text: "Liveness check"
|
|
582
|
+
}), prompt]);
|
|
597
583
|
if (!this.camera.supported || !this.camera.canRecord) {
|
|
598
584
|
if (requireLiveCamera) {
|
|
599
|
-
this.body.appendChild(this.
|
|
585
|
+
this.body.appendChild(this.dom.h("div", {
|
|
600
586
|
class: "arkyc-badge err",
|
|
601
587
|
html: "!"
|
|
602
588
|
}));
|
|
@@ -617,11 +603,10 @@ var WidgetView = class {
|
|
|
617
603
|
}
|
|
618
604
|
return;
|
|
619
605
|
}
|
|
620
|
-
const video = this.
|
|
606
|
+
const video = this.dom.h("video", { class: "arkyc-preview selfie" });
|
|
621
607
|
const face = this.buildFaceStage(video);
|
|
622
608
|
const dots = this.buildDots(challenges.length);
|
|
623
|
-
|
|
624
|
-
this.body.appendChild(face.stage);
|
|
609
|
+
this.dom.append(this.body, [challenges.length > 1 && dots.el, face.stage]);
|
|
625
610
|
let recording = null;
|
|
626
611
|
let index = 0;
|
|
627
612
|
const performed = [];
|
|
@@ -732,20 +717,23 @@ var WidgetView = class {
|
|
|
732
717
|
* @returns
|
|
733
718
|
*/
|
|
734
719
|
buildFaceStage(video) {
|
|
735
|
-
const
|
|
736
|
-
stage.setAttribute("data-state", "wait");
|
|
737
|
-
stage.appendChild(video);
|
|
738
|
-
const ring = this.el("div", {
|
|
720
|
+
const ring = this.dom.h("div", {
|
|
739
721
|
class: "arkyc-ring",
|
|
740
722
|
html: "<svg viewBox=\"0 0 100 100\"><circle class=\"arkyc-ring-track\" cx=\"50\" cy=\"50\" r=\"46\"/><circle class=\"arkyc-ring-arc\" cx=\"50\" cy=\"50\" r=\"46\"/></svg>"
|
|
741
723
|
});
|
|
742
|
-
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
724
|
+
const cue = this.dom.h("div", { class: "arkyc-cue" });
|
|
725
|
+
const stage = this.dom.h("div", {
|
|
726
|
+
class: "arkyc-stage",
|
|
727
|
+
"data-state": "wait"
|
|
728
|
+
}, [
|
|
729
|
+
video,
|
|
730
|
+
ring,
|
|
731
|
+
cue,
|
|
732
|
+
this.dom.h("div", {
|
|
733
|
+
class: "arkyc-check",
|
|
734
|
+
html: CHECK_ICON
|
|
735
|
+
})
|
|
736
|
+
]);
|
|
749
737
|
const arc = ring.querySelector(".arkyc-ring-arc");
|
|
750
738
|
const circumference = 2 * Math.PI * 46;
|
|
751
739
|
if (arc) {
|
|
@@ -772,15 +760,9 @@ var WidgetView = class {
|
|
|
772
760
|
* @param doc
|
|
773
761
|
*/
|
|
774
762
|
buildDots(count) {
|
|
775
|
-
const
|
|
776
|
-
const dots = [];
|
|
777
|
-
for (let i = 0; i < count; i += 1) {
|
|
778
|
-
const dot = this.el("div", { class: "arkyc-dot" });
|
|
779
|
-
dots.push(dot);
|
|
780
|
-
el.appendChild(dot);
|
|
781
|
-
}
|
|
763
|
+
const dots = Array.from({ length: count }, () => this.dom.h("div", { class: "arkyc-dot" }));
|
|
782
764
|
return {
|
|
783
|
-
el,
|
|
765
|
+
el: this.dom.h("div", { class: "arkyc-dots" }, dots),
|
|
784
766
|
setActive: (index) => dots.forEach((d, i) => d.classList.toggle("active", i === index && !d.classList.contains("done"))),
|
|
785
767
|
markDone: (index) => {
|
|
786
768
|
const d = dots[index];
|
|
@@ -801,18 +783,16 @@ var WidgetView = class {
|
|
|
801
783
|
* @returns
|
|
802
784
|
*/
|
|
803
785
|
buildDocStage(video) {
|
|
804
|
-
const
|
|
805
|
-
stage.setAttribute("data-q", "wait");
|
|
806
|
-
stage.appendChild(video);
|
|
807
|
-
const frame = this.el("div", { class: "arkyc-doc-frame" });
|
|
808
|
-
for (const corner of [
|
|
786
|
+
const frame = this.dom.h("div", { class: "arkyc-doc-frame" }, [...[
|
|
809
787
|
"tl",
|
|
810
788
|
"tr",
|
|
811
789
|
"bl",
|
|
812
790
|
"br"
|
|
813
|
-
])
|
|
814
|
-
|
|
815
|
-
|
|
791
|
+
].map((corner) => this.dom.h("span", { class: `arkyc-corner ${corner}` })), this.dom.h("div", { class: "arkyc-scan" })]);
|
|
792
|
+
const stage = this.dom.h("div", {
|
|
793
|
+
class: "arkyc-doc",
|
|
794
|
+
"data-q": "wait"
|
|
795
|
+
}, [video, frame]);
|
|
816
796
|
return {
|
|
817
797
|
stage,
|
|
818
798
|
setQuality: (quality) => stage.setAttribute("data-q", quality),
|
|
@@ -820,40 +800,42 @@ var WidgetView = class {
|
|
|
820
800
|
};
|
|
821
801
|
}
|
|
822
802
|
renderProcessing(label) {
|
|
823
|
-
this.
|
|
824
|
-
this.body.appendChild(this.el("p", {
|
|
803
|
+
this.dom.append(this.body, [this.dom.h("div", { class: "arkyc-spinner" }), this.dom.h("p", {
|
|
825
804
|
class: "arkyc-p",
|
|
826
805
|
text: label
|
|
827
|
-
}));
|
|
806
|
+
})]);
|
|
828
807
|
}
|
|
829
808
|
renderResult(decision, errorMessage, opts = {}) {
|
|
830
|
-
if (errorMessage)
|
|
831
|
-
this.
|
|
809
|
+
if (errorMessage) this.dom.append(this.body, [
|
|
810
|
+
this.dom.h("div", {
|
|
832
811
|
class: "arkyc-badge err",
|
|
833
812
|
html: "!"
|
|
834
|
-
})
|
|
835
|
-
this.
|
|
813
|
+
}),
|
|
814
|
+
this.dom.h("h2", {
|
|
836
815
|
class: "arkyc-h",
|
|
837
816
|
text: "Something went wrong"
|
|
838
|
-
})
|
|
839
|
-
this.
|
|
817
|
+
}),
|
|
818
|
+
this.dom.h("p", {
|
|
840
819
|
class: "arkyc-p",
|
|
841
820
|
text: errorMessage
|
|
842
|
-
})
|
|
843
|
-
|
|
821
|
+
})
|
|
822
|
+
]);
|
|
823
|
+
else if (opts.terminal) {
|
|
844
824
|
const n = TERMINAL_NOTICE[opts.status ?? "expired"] ?? EXPIRED_NOTICE;
|
|
845
|
-
this.
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
825
|
+
this.dom.append(this.body, [
|
|
826
|
+
this.dom.h("div", {
|
|
827
|
+
class: `arkyc-badge ${n.cls}`,
|
|
828
|
+
text: n.icon
|
|
829
|
+
}),
|
|
830
|
+
this.dom.h("h2", {
|
|
831
|
+
class: "arkyc-h",
|
|
832
|
+
text: n.title
|
|
833
|
+
}),
|
|
834
|
+
this.dom.h("p", {
|
|
835
|
+
class: "arkyc-p",
|
|
836
|
+
text: n.copy
|
|
837
|
+
})
|
|
838
|
+
]);
|
|
857
839
|
this.footer.appendChild(this.button("Close", () => this.handlers.onClose()));
|
|
858
840
|
return;
|
|
859
841
|
} else {
|
|
@@ -878,24 +860,128 @@ var WidgetView = class {
|
|
|
878
860
|
}
|
|
879
861
|
};
|
|
880
862
|
const r = map[decision ?? "requires_review"] ?? map.requires_review;
|
|
881
|
-
this.
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
863
|
+
this.dom.append(this.body, [
|
|
864
|
+
this.dom.h("div", {
|
|
865
|
+
class: `arkyc-badge ${r.cls}`,
|
|
866
|
+
text: r.icon
|
|
867
|
+
}),
|
|
868
|
+
this.dom.h("h2", {
|
|
869
|
+
class: "arkyc-h",
|
|
870
|
+
text: r.title
|
|
871
|
+
}),
|
|
872
|
+
this.dom.h("p", {
|
|
873
|
+
class: "arkyc-p",
|
|
874
|
+
text: r.copy
|
|
875
|
+
})
|
|
876
|
+
]);
|
|
893
877
|
}
|
|
894
878
|
this.footer.appendChild(this.button("Done", () => this.handlers.onAcknowledge()));
|
|
895
879
|
}
|
|
880
|
+
/**
|
|
881
|
+
* The address-entry form (opt-in address stage). Inputs shown depend on methods.
|
|
882
|
+
*
|
|
883
|
+
* @param methods
|
|
884
|
+
*/
|
|
885
|
+
renderAddressForm(methods) {
|
|
886
|
+
this.body.appendChild(this.dom.h("h2", {
|
|
887
|
+
class: "arkyc-h",
|
|
888
|
+
text: "Your address"
|
|
889
|
+
}));
|
|
890
|
+
this.body.appendChild(this.dom.h("p", {
|
|
891
|
+
class: "arkyc-p",
|
|
892
|
+
text: "Enter your residential address so we can verify it."
|
|
893
|
+
}));
|
|
894
|
+
const line1 = this.dom.field("Address line 1", {
|
|
895
|
+
autocomplete: "address-line1",
|
|
896
|
+
name: "street"
|
|
897
|
+
});
|
|
898
|
+
const line2 = this.dom.field("Address line 2 (optional)", {
|
|
899
|
+
autocomplete: "address-line2",
|
|
900
|
+
name: "town"
|
|
901
|
+
});
|
|
902
|
+
const city = this.dom.field("City", {
|
|
903
|
+
autocomplete: "address-level2",
|
|
904
|
+
name: "city"
|
|
905
|
+
});
|
|
906
|
+
const region = this.dom.field("State / region", {
|
|
907
|
+
autocomplete: "address-level1",
|
|
908
|
+
name: "state"
|
|
909
|
+
});
|
|
910
|
+
const postal = this.dom.field("ZIP/Postal code", {
|
|
911
|
+
autocomplete: "postal-code",
|
|
912
|
+
name: "zip"
|
|
913
|
+
});
|
|
914
|
+
const country = this.dom.field("Country", {
|
|
915
|
+
name: "country_iso2",
|
|
916
|
+
choices: countries.map((e) => ({
|
|
917
|
+
value: e.iso2,
|
|
918
|
+
label: `${e.flag} ${e.name}`
|
|
919
|
+
}))
|
|
920
|
+
});
|
|
921
|
+
this.body.appendChild(this.dom.h("div", { class: "arkyc-addr-form" }, [
|
|
922
|
+
line1,
|
|
923
|
+
line2,
|
|
924
|
+
city,
|
|
925
|
+
region,
|
|
926
|
+
postal,
|
|
927
|
+
country
|
|
928
|
+
]));
|
|
929
|
+
let poa = null;
|
|
930
|
+
if (methods.includes("poa_document")) {
|
|
931
|
+
const file = this.dom.h("input", {
|
|
932
|
+
class: "arkyc-addr-input",
|
|
933
|
+
type: "file",
|
|
934
|
+
accept: "image/*",
|
|
935
|
+
on: { change: () => poa = file.files?.[0] ?? null }
|
|
936
|
+
});
|
|
937
|
+
this.dom.append(this.body, [this.dom.h("p", {
|
|
938
|
+
class: "arkyc-p",
|
|
939
|
+
text: "Upload a proof of address (utility bill or bank statement)."
|
|
940
|
+
}), file]);
|
|
941
|
+
}
|
|
942
|
+
let useLocation = false;
|
|
943
|
+
const requiresLocation = methods.includes("device_location");
|
|
944
|
+
const submit = this.button("Continue", () => this.handlers.onAddress({
|
|
945
|
+
line1: line1.value.trim() || null,
|
|
946
|
+
line2: line2.value.trim() || null,
|
|
947
|
+
city: city.value.trim() || null,
|
|
948
|
+
region: region.value.trim() || null,
|
|
949
|
+
postalCode: postal.value.trim() || null,
|
|
950
|
+
country: country.value.trim().toUpperCase() || null,
|
|
951
|
+
poa,
|
|
952
|
+
useLocation
|
|
953
|
+
}));
|
|
954
|
+
if (requiresLocation) {
|
|
955
|
+
const status = this.dom.h("p", { class: "arkyc-p" });
|
|
956
|
+
const cb = this.dom.h("input", {
|
|
957
|
+
type: "checkbox",
|
|
958
|
+
on: { change: () => {
|
|
959
|
+
if (!cb.checked) {
|
|
960
|
+
useLocation = false;
|
|
961
|
+
status.textContent = "";
|
|
962
|
+
submit.setAttribute("disabled", "true");
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
status.textContent = "Requesting your location…";
|
|
966
|
+
submit.setAttribute("disabled", "true");
|
|
967
|
+
Promise.resolve(this.handlers.onShareLocation()).then((ok) => {
|
|
968
|
+
useLocation = ok;
|
|
969
|
+
cb.checked = ok;
|
|
970
|
+
if (ok) submit.removeAttribute("disabled");
|
|
971
|
+
else submit.setAttribute("disabled", "true");
|
|
972
|
+
status.textContent = ok ? "Location shared ✓" : "Couldn’t access your location, allow location access to continue.";
|
|
973
|
+
});
|
|
974
|
+
} }
|
|
975
|
+
});
|
|
976
|
+
submit.setAttribute("disabled", "true");
|
|
977
|
+
this.body.appendChild(this.dom.h("label", { class: "arkyc-addr-opt" }, [cb, this.dom.h("span", { text: "Allow access to my current location to confirm my address" })]));
|
|
978
|
+
this.body.appendChild(status);
|
|
979
|
+
}
|
|
980
|
+
this.footer.appendChild(submit);
|
|
981
|
+
}
|
|
896
982
|
button(label, onClick, extraClass) {
|
|
897
983
|
const cls = extraClass ? `arkyc-btn ${extraClass}` : "arkyc-btn";
|
|
898
|
-
const btn = this.
|
|
984
|
+
const btn = this.dom.h("button", {
|
|
899
985
|
class: cls,
|
|
900
986
|
text: label
|
|
901
987
|
});
|
|
@@ -912,18 +998,6 @@ var WidgetView = class {
|
|
|
912
998
|
clear(node) {
|
|
913
999
|
while (node.firstChild) node.removeChild(node.firstChild);
|
|
914
1000
|
}
|
|
915
|
-
el(tag, props = {}) {
|
|
916
|
-
const node = this.doc.createElement(tag);
|
|
917
|
-
for (const [key, value] of Object.entries(props)) {
|
|
918
|
-
if (value == null) continue;
|
|
919
|
-
if (key === "class") node.className = value;
|
|
920
|
-
else if (key === "text") node.textContent = value;
|
|
921
|
-
else if (key === "html") node.innerHTML = value;
|
|
922
|
-
else if (key === "value" || key === "src" || key === "type" || key === "accept" || key === "placeholder") node[key] = value;
|
|
923
|
-
else node.setAttribute(key, value);
|
|
924
|
-
}
|
|
925
|
-
return node;
|
|
926
|
-
}
|
|
927
1001
|
};
|
|
928
1002
|
//#endregion
|
|
929
1003
|
export { WidgetView };
|