@arkyc/widget 1.0.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/README.md +85 -0
- package/dist/ArkycWidget.d.mts +50 -0
- package/dist/ArkycWidget.d.mts.map +1 -0
- package/dist/ArkycWidget.mjs +80 -0
- package/dist/ArkycWidget.mjs.map +1 -0
- package/dist/WidgetHandler.d.mts +24 -0
- package/dist/WidgetHandler.d.mts.map +1 -0
- package/dist/WidgetHandler.mjs +28 -0
- package/dist/WidgetHandler.mjs.map +1 -0
- package/dist/_virtual/_virtual_arkyc-theme-css.mjs +4 -0
- package/dist/arkyc-widget.iife.global.js +670 -0
- package/dist/arkyc-widget.iife.global.js.map +1 -0
- package/dist/capture.d.mts +73 -0
- package/dist/capture.d.mts.map +1 -0
- package/dist/capture.mjs +126 -0
- package/dist/capture.mjs.map +1 -0
- package/dist/client.d.mts +152 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +120 -0
- package/dist/client.mjs.map +1 -0
- package/dist/controller.d.mts +126 -0
- package/dist/controller.d.mts.map +1 -0
- package/dist/controller.mjs +582 -0
- package/dist/controller.mjs.map +1 -0
- package/dist/countries.mjs +967 -0
- package/dist/countries.mjs.map +1 -0
- package/dist/device.mjs +17 -0
- package/dist/device.mjs.map +1 -0
- package/dist/document.d.mts +108 -0
- package/dist/document.d.mts.map +1 -0
- package/dist/document.mjs +227 -0
- package/dist/document.mjs.map +1 -0
- package/dist/face.d.mts +82 -0
- package/dist/face.d.mts.map +1 -0
- package/dist/face.mjs +230 -0
- package/dist/face.mjs.map +1 -0
- package/dist/flow.d.mts +74 -0
- package/dist/flow.d.mts.map +1 -0
- package/dist/flow.mjs +132 -0
- package/dist/flow.mjs.map +1 -0
- package/dist/index.d.mts +19 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +16 -0
- package/dist/index.mjs.map +1 -0
- package/dist/qr.mjs +22 -0
- package/dist/qr.mjs.map +1 -0
- package/dist/realtime.d.mts +29 -0
- package/dist/realtime.d.mts.map +1 -0
- package/dist/realtime.mjs +107 -0
- package/dist/realtime.mjs.map +1 -0
- package/dist/theme.d.mts +42 -0
- package/dist/theme.d.mts.map +1 -0
- package/dist/theme.mjs +77 -0
- package/dist/theme.mjs.map +1 -0
- package/dist/types.d.mts +153 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/ui.mjs +931 -0
- package/dist/ui.mjs.map +1 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# @arkyc/widget
|
|
2
|
+
|
|
3
|
+
The embeddable [Arkyc](../../README.md) identity-verification widget — a polished,
|
|
4
|
+
mobile-friendly, **framework-agnostic** flow that walks a user through document
|
|
5
|
+
capture, liveness, and face match. It talks only to the **Client/Widget API**
|
|
6
|
+
with a short-lived client token (minted by your backend via `@arkyc/sdk`).
|
|
7
|
+
|
|
8
|
+
The flow: **Welcome → Document Selection → Front Capture → Back Capture → OCR
|
|
9
|
+
Processing → Selfie Capture → Passive Liveness → Face Match → Processing →
|
|
10
|
+
Result**. `back_capture` is skipped automatically for single-sided documents
|
|
11
|
+
(passports).
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @arkyc/widget
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Modes
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { ArkycWidget } from '@arkyc/widget'
|
|
23
|
+
|
|
24
|
+
// Overlay modal (full-screen, dismissable):
|
|
25
|
+
ArkycWidget.open({
|
|
26
|
+
token: clientToken,
|
|
27
|
+
branding: { primary_color: '#4f46e5', theme: 'light' },
|
|
28
|
+
onComplete: (r) => console.log(r.status, r.decision),
|
|
29
|
+
onError: (e) => console.error(e),
|
|
30
|
+
onClose: () => {},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// Inline (mounted into a container element):
|
|
34
|
+
ArkycWidget.mount({ token: clientToken, container: '#verify' })
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`token` comes from `arkyc.sessions.create()` on your server. `branding` is
|
|
38
|
+
usually sourced from your project's configuration (colors, logo, border radius,
|
|
39
|
+
light/dark theme).
|
|
40
|
+
|
|
41
|
+
## Hosted / standalone
|
|
42
|
+
|
|
43
|
+
The widget also ships a minified browser global for plain `<script>` embedding —
|
|
44
|
+
this is what the hosted widget page (and `@arkyc/sdk/browser`'s `ArkycWidget.open`
|
|
45
|
+
iframe launcher) loads:
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<script src="https://unpkg.com/@arkyc/widget/dist/arkyc-widget.iife.global.js"></script>
|
|
49
|
+
<script>
|
|
50
|
+
// Reads ?token= (and optional ?baseUrl=) from the URL and posts
|
|
51
|
+
// `arkyc:complete` / `arkyc:error` / `arkyc:close` to the parent window.
|
|
52
|
+
Arkyc.ArkycWidget.hosted()
|
|
53
|
+
</script>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Build the standalone bundle with `pnpm --filter @arkyc/widget build:standalone`
|
|
57
|
+
(the ESM + `.d.ts` build comes from the workspace `tsdown`).
|
|
58
|
+
|
|
59
|
+
## Camera & capture
|
|
60
|
+
|
|
61
|
+
Capture screens use `getUserMedia` for a live preview and grab a JPEG frame via
|
|
62
|
+
an offscreen canvas, falling back to a file input when the camera is unavailable
|
|
63
|
+
or permission is denied. Overlay iframes are launched with
|
|
64
|
+
`allow="camera; microphone"`.
|
|
65
|
+
|
|
66
|
+
## Messaging contract
|
|
67
|
+
|
|
68
|
+
In hosted/iframe mode the widget posts these messages to `window.parent`, which
|
|
69
|
+
`@arkyc/sdk/browser` listens for:
|
|
70
|
+
|
|
71
|
+
| Message | Payload |
|
|
72
|
+
| ---------------- | ---------------------- |
|
|
73
|
+
| `arkyc:complete` | `{ status, decision }` |
|
|
74
|
+
| `arkyc:error` | `{ message, name }` |
|
|
75
|
+
| `arkyc:close` | — |
|
|
76
|
+
|
|
77
|
+
`onComplete` / `onError` fire when the user acknowledges the result screen, so
|
|
78
|
+
the user always sees the outcome before the widget tears down.
|
|
79
|
+
|
|
80
|
+
## Programmatic API
|
|
81
|
+
|
|
82
|
+
Each concern is a class, exported for custom integrations and testing:
|
|
83
|
+
`ArkycClient` (the Client API wrapper), `Theme` (branding → CSS), `Camera`
|
|
84
|
+
(capture), and `Flow` (the step machine: `Flow.nextStep`, `Flow.isTerminal`,
|
|
85
|
+
`Flow.statusToDecision`, `Flow.STEP_ORDER`).
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { BaseWidgetOptions, MountWidgetOptions, WidgetHandle } from "./types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/ArkycWidget.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* @arkyc/widget
|
|
6
|
+
*
|
|
7
|
+
* Framework-agnostic, embeddable identity-verification widget. Drives a single
|
|
8
|
+
* session through the Arkyc Client/Widget API with a short-lived client token.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { ArkycWidget } from '@arkyc/widget'
|
|
12
|
+
*
|
|
13
|
+
* // Overlay modal:
|
|
14
|
+
* ArkycWidget.open({ token, onComplete: (r) => console.log(r.status) })
|
|
15
|
+
*
|
|
16
|
+
* // Inline (mounted into a container):
|
|
17
|
+
* ArkycWidget.mount({ token, container: '#verify' })
|
|
18
|
+
*
|
|
19
|
+
* // Hosted page (reads ?token= and posts results to the parent window):
|
|
20
|
+
* ArkycWidget.hosted()
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare class ArkycWidget {
|
|
24
|
+
/**
|
|
25
|
+
* Open the widget as a full-screen overlay modal. Returns a close handle.
|
|
26
|
+
*
|
|
27
|
+
* @param options
|
|
28
|
+
* @returns
|
|
29
|
+
*/
|
|
30
|
+
static open(options: BaseWidgetOptions): WidgetHandle;
|
|
31
|
+
/**
|
|
32
|
+
* Mount the widget inline into a container element. Returns a close handle.
|
|
33
|
+
*
|
|
34
|
+
* @param options
|
|
35
|
+
* @returns
|
|
36
|
+
*/
|
|
37
|
+
static mount(options: MountWidgetOptions): WidgetHandle;
|
|
38
|
+
/**
|
|
39
|
+
* Bootstrap the widget on its hosted page: read the client `token` (and
|
|
40
|
+
* optional `baseUrl`) from the query string and run in overlay mode, posting
|
|
41
|
+
* `arkyc:*` results to the parent (iframe) window.
|
|
42
|
+
*
|
|
43
|
+
* @param options
|
|
44
|
+
* @returns
|
|
45
|
+
*/
|
|
46
|
+
static hosted(options?: Partial<BaseWidgetOptions>): WidgetHandle;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { ArkycWidget };
|
|
50
|
+
//# sourceMappingURL=ArkycWidget.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ArkycWidget.d.mts","names":[],"sources":["../src/ArkycWidget.ts"],"mappings":";;;;;AAwBA;;;;;;;;;;;;;;;;;cAAa,WAAA;EAuCE;;;;;;EAAA,OAhCN,IAAA,CAAK,OAAA,EAAS,iBAAA,GAAoB,YAAA;EAmD4B;AAAA;;;;;EAAA,OAnB9D,KAAA,CAAM,OAAA,EAAS,kBAAA,GAAqB,YAAA;;;;;;;;;SAmBpC,MAAA,CAAO,OAAA,GAAS,OAAA,CAAQ,iBAAA,IAA0B,YAAA;AAAA"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { buildController, resolveContainer } from "./controller.mjs";
|
|
2
|
+
import { WidgetHandler } from "./WidgetHandler.mjs";
|
|
3
|
+
//#region src/ArkycWidget.ts
|
|
4
|
+
/**
|
|
5
|
+
* @arkyc/widget
|
|
6
|
+
*
|
|
7
|
+
* Framework-agnostic, embeddable identity-verification widget. Drives a single
|
|
8
|
+
* session through the Arkyc Client/Widget API with a short-lived client token.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { ArkycWidget } from '@arkyc/widget'
|
|
12
|
+
*
|
|
13
|
+
* // Overlay modal:
|
|
14
|
+
* ArkycWidget.open({ token, onComplete: (r) => console.log(r.status) })
|
|
15
|
+
*
|
|
16
|
+
* // Inline (mounted into a container):
|
|
17
|
+
* ArkycWidget.mount({ token, container: '#verify' })
|
|
18
|
+
*
|
|
19
|
+
* // Hosted page (reads ?token= and posts results to the parent window):
|
|
20
|
+
* ArkycWidget.hosted()
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
var ArkycWidget = class ArkycWidget {
|
|
24
|
+
/**
|
|
25
|
+
* Open the widget as a full-screen overlay modal. Returns a close handle.
|
|
26
|
+
*
|
|
27
|
+
* @param options
|
|
28
|
+
* @returns
|
|
29
|
+
*/
|
|
30
|
+
static open(options) {
|
|
31
|
+
const doc = options.doc ?? globalThis.document;
|
|
32
|
+
const overlay = doc.createElement("div");
|
|
33
|
+
overlay.setAttribute("data-arkyc-widget", "");
|
|
34
|
+
overlay.style.cssText = options.fullscreen ? "position:fixed;inset:0;z-index:2147483647;background:rgba(0,0,0,0.55);display:flex;align-items:center;justify-content:center;padding:0;" : "position:fixed;inset:0;z-index:2147483647;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;padding:16px;";
|
|
35
|
+
const controller = buildController(options, () => overlay.remove());
|
|
36
|
+
controller.element.classList.add("arkyc-overlay");
|
|
37
|
+
if (options.fullscreen) controller.element.classList.add("arkyc-fullscreen");
|
|
38
|
+
overlay.appendChild(controller.element);
|
|
39
|
+
(doc.body ?? doc.documentElement).appendChild(overlay);
|
|
40
|
+
controller.start();
|
|
41
|
+
return new WidgetHandler(controller);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Mount the widget inline into a container element. Returns a close handle.
|
|
45
|
+
*
|
|
46
|
+
* @param options
|
|
47
|
+
* @returns
|
|
48
|
+
*/
|
|
49
|
+
static mount(options) {
|
|
50
|
+
const doc = options.doc ?? globalThis.document;
|
|
51
|
+
const container = resolveContainer(options.container, doc);
|
|
52
|
+
const controller = buildController(options, () => controller.element.remove());
|
|
53
|
+
container.appendChild(controller.element);
|
|
54
|
+
controller.start();
|
|
55
|
+
return new WidgetHandler(controller);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Bootstrap the widget on its hosted page: read the client `token` (and
|
|
59
|
+
* optional `baseUrl`) from the query string and run in overlay mode, posting
|
|
60
|
+
* `arkyc:*` results to the parent (iframe) window.
|
|
61
|
+
*
|
|
62
|
+
* @param options
|
|
63
|
+
* @returns
|
|
64
|
+
*/
|
|
65
|
+
static hosted(options = {}) {
|
|
66
|
+
const win = options.win ?? globalThis.window;
|
|
67
|
+
const params = new URLSearchParams(win.location.search);
|
|
68
|
+
const token = options.token ?? params.get("token") ?? "";
|
|
69
|
+
const baseUrl = options.baseUrl ?? params.get("baseUrl") ?? void 0;
|
|
70
|
+
return ArkycWidget.open({
|
|
71
|
+
...options,
|
|
72
|
+
token,
|
|
73
|
+
baseUrl
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
//#endregion
|
|
78
|
+
export { ArkycWidget };
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=ArkycWidget.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ArkycWidget.mjs","names":[],"sources":["../src/ArkycWidget.ts"],"sourcesContent":["import type { BaseWidgetOptions, MountWidgetOptions, WidgetHandle } from './types'\nimport { buildController, resolveContainer } from './controller'\n\nimport { WidgetHandler } from './WidgetHandler'\n\n/**\n * @arkyc/widget\n *\n * Framework-agnostic, embeddable identity-verification widget. Drives a single\n * session through the Arkyc Client/Widget API with a short-lived client token.\n *\n * ```ts\n * import { ArkycWidget } from '@arkyc/widget'\n *\n * // Overlay modal:\n * ArkycWidget.open({ token, onComplete: (r) => console.log(r.status) })\n *\n * // Inline (mounted into a container):\n * ArkycWidget.mount({ token, container: '#verify' })\n *\n * // Hosted page (reads ?token= and posts results to the parent window):\n * ArkycWidget.hosted()\n * ```\n */\nexport class ArkycWidget {\n /**\n * Open the widget as a full-screen overlay modal. Returns a close handle.\n *\n * @param options\n * @returns\n */\n static open(options: BaseWidgetOptions): WidgetHandle {\n const doc = options.doc ?? globalThis.document\n const overlay = doc.createElement('div')\n\n overlay.setAttribute('data-arkyc-widget', '')\n // Fullscreen: edge-to-edge with no backdrop gap. Otherwise a centred modal\n // over a dimmed backdrop.\n // Both modes use a dimmed, centred backdrop. Fullscreen fills the viewport on\n // mobile but renders as a large centred dialog on desktop (see the card CSS);\n // the dimmed backdrop is what's visible around that dialog.\n overlay.style.cssText = options.fullscreen\n ? 'position:fixed;inset:0;z-index:2147483647;background:rgba(0,0,0,0.55);display:flex;align-items:center;justify-content:center;padding:0;'\n : 'position:fixed;inset:0;z-index:2147483647;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;padding:16px;'\n\n const controller = buildController(options, () => overlay.remove())\n // Mark overlay mode so a handoff QR can take over the whole viewport here\n // (it escapes the dimmed backdrop) without doing the same when inline-mounted.\n controller.element.classList.add('arkyc-overlay')\n if (options.fullscreen) controller.element.classList.add('arkyc-fullscreen')\n overlay.appendChild(controller.element)\n ;(doc.body ?? doc.documentElement).appendChild(overlay)\n controller.start()\n\n return new WidgetHandler(controller)\n }\n\n /**\n * Mount the widget inline into a container element. Returns a close handle.\n *\n * @param options\n * @returns\n */\n static mount(options: MountWidgetOptions): WidgetHandle {\n const doc = options.doc ?? globalThis.document\n const container = resolveContainer(options.container, doc)\n const controller = buildController(options, () => controller.element.remove())\n\n container.appendChild(controller.element)\n controller.start()\n\n return new WidgetHandler(controller)\n }\n\n /**\n * Bootstrap the widget on its hosted page: read the client `token` (and\n * optional `baseUrl`) from the query string and run in overlay mode, posting\n * `arkyc:*` results to the parent (iframe) window.\n *\n * @param options\n * @returns\n */\n static hosted(options: Partial<BaseWidgetOptions> = {}): WidgetHandle {\n const win = options.win ?? globalThis.window\n const params = new URLSearchParams(win.location.search)\n const token = options.token ?? params.get('token') ?? ''\n const baseUrl = options.baseUrl ?? params.get('baseUrl') ?? undefined\n\n return ArkycWidget.open({ ...options, token, baseUrl })\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAwBA,IAAa,cAAb,MAAa,YAAY;;;;;;;CAOvB,OAAO,KAAK,SAA0C;EACpD,MAAM,MAAM,QAAQ,OAAO,WAAW;EACtC,MAAM,UAAU,IAAI,cAAc,KAAK;EAEvC,QAAQ,aAAa,qBAAqB,EAAE;EAM5C,QAAQ,MAAM,UAAU,QAAQ,aAC5B,4IACA;EAEJ,MAAM,aAAa,gBAAgB,eAAe,QAAQ,OAAO,CAAC;EAGlE,WAAW,QAAQ,UAAU,IAAI,eAAe;EAChD,IAAI,QAAQ,YAAY,WAAW,QAAQ,UAAU,IAAI,kBAAkB;EAC3E,QAAQ,YAAY,WAAW,OAAO;EACrC,CAAC,IAAI,QAAQ,IAAI,gBAAA,CAAiB,YAAY,OAAO;EACtD,WAAW,MAAM;EAEjB,OAAO,IAAI,cAAc,UAAU;CACrC;;;;;;;CAQA,OAAO,MAAM,SAA2C;EACtD,MAAM,MAAM,QAAQ,OAAO,WAAW;EACtC,MAAM,YAAY,iBAAiB,QAAQ,WAAW,GAAG;EACzD,MAAM,aAAa,gBAAgB,eAAe,WAAW,QAAQ,OAAO,CAAC;EAE7E,UAAU,YAAY,WAAW,OAAO;EACxC,WAAW,MAAM;EAEjB,OAAO,IAAI,cAAc,UAAU;CACrC;;;;;;;;;CAUA,OAAO,OAAO,UAAsC,CAAC,GAAiB;EACpE,MAAM,MAAM,QAAQ,OAAO,WAAW;EACtC,MAAM,SAAS,IAAI,gBAAgB,IAAI,SAAS,MAAM;EACtD,MAAM,QAAQ,QAAQ,SAAS,OAAO,IAAI,OAAO,KAAK;EACtD,MAAM,UAAU,QAAQ,WAAW,OAAO,IAAI,SAAS,KAAK,KAAA;EAE5D,OAAO,YAAY,KAAK;GAAE,GAAG;GAAS;GAAO;EAAQ,CAAC;CACxD;AACF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { WidgetEventListener, WidgetHandle } from "./types.mjs";
|
|
2
|
+
import { WidgetController } from "./controller.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/WidgetHandler.d.ts
|
|
5
|
+
declare class WidgetHandler implements WidgetHandle {
|
|
6
|
+
private controller;
|
|
7
|
+
constructor(controller: WidgetController);
|
|
8
|
+
/**
|
|
9
|
+
* Close the widget and release the camera (fires `onClose`).
|
|
10
|
+
*/
|
|
11
|
+
close(): void;
|
|
12
|
+
/**
|
|
13
|
+
* Subscribe to a named widget event (e.g. `session.transition`, `complete`).
|
|
14
|
+
* Returns an unsubscribe function. Registering a listener activates the event
|
|
15
|
+
* stream (events are only delivered while at least one listener is active).
|
|
16
|
+
*
|
|
17
|
+
* @param event
|
|
18
|
+
* @param listener
|
|
19
|
+
*/
|
|
20
|
+
on(event: string, listener: WidgetEventListener): () => void;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { WidgetHandler };
|
|
24
|
+
//# sourceMappingURL=WidgetHandler.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WidgetHandler.d.mts","names":[],"sources":["../src/WidgetHandler.ts"],"mappings":";;;;cAGa,aAAA,YAAyB,YAAA;EAAA,QAChB,UAAA;cAAA,UAAA,EAAY,gBAAA;EADP;;;EAMzB,KAAA;EANoC;;;;;;;;EAiBpC,EAAA,CAAG,KAAA,UAAe,QAAA,EAAU,mBAAA;AAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/WidgetHandler.ts
|
|
2
|
+
var WidgetHandler = class {
|
|
3
|
+
controller;
|
|
4
|
+
constructor(controller) {
|
|
5
|
+
this.controller = controller;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Close the widget and release the camera (fires `onClose`).
|
|
9
|
+
*/
|
|
10
|
+
close() {
|
|
11
|
+
return this.controller.close();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Subscribe to a named widget event (e.g. `session.transition`, `complete`).
|
|
15
|
+
* Returns an unsubscribe function. Registering a listener activates the event
|
|
16
|
+
* stream (events are only delivered while at least one listener is active).
|
|
17
|
+
*
|
|
18
|
+
* @param event
|
|
19
|
+
* @param listener
|
|
20
|
+
*/
|
|
21
|
+
on(event, listener) {
|
|
22
|
+
return this.controller.on(event, listener);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { WidgetHandler };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=WidgetHandler.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WidgetHandler.mjs","names":[],"sources":["../src/WidgetHandler.ts"],"sourcesContent":["import type { WidgetController } from './controller'\nimport type { WidgetEventListener, WidgetHandle } from './types'\n\nexport class WidgetHandler implements WidgetHandle {\n constructor(private controller: WidgetController) {}\n\n /**\n * Close the widget and release the camera (fires `onClose`).\n */\n close(): void {\n return this.controller.close()\n }\n /**\n * Subscribe to a named widget event (e.g. `session.transition`, `complete`).\n * Returns an unsubscribe function. Registering a listener activates the event\n * stream (events are only delivered while at least one listener is active).\n *\n * @param event\n * @param listener\n */\n on(event: string, listener: WidgetEventListener): () => void {\n return this.controller.on(event, listener)\n }\n}\n"],"mappings":";AAGA,IAAa,gBAAb,MAAmD;CAC7B;CAApB,YAAY,YAAsC;EAA9B,KAAA,aAAA;CAA+B;;;;CAKnD,QAAc;EACZ,OAAO,KAAK,WAAW,MAAM;CAC/B;;;;;;;;;CASA,GAAG,OAAe,UAA2C;EAC3D,OAAO,KAAK,WAAW,GAAG,OAAO,QAAQ;CAC3C;AACF"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
//#region \0virtual:arkyc-theme-css
|
|
2
|
+
var _virtual_arkyc_theme_css_default = ".arkyc-root {\n /* {variables} */\n\n color: var(--arkyc-fg);\n font-family:\n system-ui,\n -apple-system,\n Segoe UI,\n Roboto,\n sans-serif;\n box-sizing: border-box;\n}\n\n.arkyc-text-center {\n text-align: center;\n}\n\n.arkyc-root * {\n box-sizing: border-box;\n}\n\n.arkyc-card {\n background: var(--arkyc-bg);\n border-radius: var(--arkyc-radius);\n width: 100vw;\n max-width: 480px;\n height: 100%;\n max-height: 720px;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.arkyc-root.arkyc-fullscreen .arkyc-card {\n max-width: none;\n max-height: none;\n height: 100vh;\n height: 100dvh;\n border-radius: 0;\n}\n\n/* On a wider (desktop) viewport, \"fullscreen\" is a large centred dialog rather than edge-to-edge. */\n@media (min-width: 700px) {\n .arkyc-root.arkyc-fullscreen .arkyc-card {\n max-width: 560px;\n min-width: 400px;\n width: 100%;\n height: auto;\n min-height: min(620px, 92vh);\n max-height: 92vh;\n border-radius: var(--arkyc-radius);\n }\n}\n\n/* Handoff (QR) takes over the whole viewport — but only in overlay (open) mode,\n so an inline mount() stays within its container instead of escaping fullscreen.\n The close affordance is hidden either way: there's nothing to dismiss while\n awaiting the phone. */\n.arkyc-root.arkyc-overlay.arkyc-handoff .arkyc-card {\n position: fixed;\n inset: 0;\n width: 100vw;\n height: 100vh;\n height: 100dvh;\n max-width: none;\n max-height: none;\n min-width: 0;\n min-height: 0;\n border-radius: 0;\n z-index: 2147483646;\n}\n\n.arkyc-root.arkyc-handoff .arkyc-close {\n display: none;\n}\n\n.arkyc-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n /* border-bottom: 1px solid var(--arkyc-border); */\n}\n\n.arkyc-logo {\n height: 24px;\n width: auto;\n}\n\n.arkyc-title {\n font-size: 15px;\n font-weight: 600;\n margin: 0;\n}\n\n.arkyc-brand {\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: 0;\n}\n\n.arkyc-brand-name {\n font-size: 15px;\n font-weight: 600;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.arkyc-close {\n background: none;\n border: 0;\n color: var(--arkyc-muted);\n font-size: 20px;\n line-height: 1;\n cursor: pointer;\n}\n\n.arkyc-body {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n gap: 14px;\n padding: 24px;\n overflow-y: auto;\n}\n\n.arkyc-h {\n font-size: 20px;\n font-weight: 600;\n margin: 0;\n}\n\n.arkyc-p {\n font-size: 14px;\n color: var(--arkyc-muted);\n margin: 0;\n max-width: 340px;\n}\n\n.arkyc-footer {\n padding: 16px 20px;\n /* border-top: 1px solid var(--arkyc-border); */\n}\n\n.arkyc-btn {\n appearance: none;\n border: 0;\n border-radius: var(--arkyc-radius);\n background: var(--arkyc-primary);\n color: #fff;\n font-size: 15px;\n font-weight: 600;\n padding: 12px 18px;\n width: 100%;\n cursor: pointer;\n}\n\n.arkyc-btn[disabled] {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n/* Busy: hide the label and show an inline spinner while the handler runs. */\n.arkyc-btn.arkyc-busy {\n position: relative;\n color: transparent;\n pointer-events: none;\n opacity: 1;\n}\n\n.arkyc-btn.arkyc-busy::after {\n content: '';\n position: absolute;\n inset: 0;\n margin: auto;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n border: 2px solid rgba(255, 255, 255, 0.5);\n border-top-color: #fff;\n animation: arkyc-spin 0.8s linear infinite;\n}\n\n.arkyc-btn-ghost.arkyc-busy::after {\n border-color: color-mix(in srgb, var(--arkyc-primary) 35%, transparent);\n border-top-color: var(--arkyc-primary);\n}\n\n.arkyc-btn-ghost {\n background: transparent;\n color: var(--arkyc-primary);\n border: 1px solid var(--arkyc-border);\n}\n\n.arkyc-choices {\n display: flex;\n flex-direction: column;\n gap: 8px;\n width: 100%;\n max-width: 340px;\n}\n\n.arkyc-preview {\n width: 100%;\n max-width: 360px;\n border-radius: var(--arkyc-radius);\n background: #000;\n aspect-ratio: 3/2;\n object-fit: cover;\n}\n\n.arkyc-preview.selfie {\n aspect-ratio: 1/1;\n max-width: 280px;\n border-radius: 50%;\n transform: scaleX(-1);\n}\n\n.arkyc-spinner {\n width: 42px;\n height: 42px;\n border-radius: 50%;\n border: 4px solid var(--arkyc-border);\n border-top-color: var(--arkyc-primary);\n animation: arkyc-spin 1s linear infinite;\n}\n\n.arkyc-spinner.sm {\n width: 18px;\n height: 18px;\n border-width: 3px;\n}\n\n@keyframes arkyc-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n.arkyc-qr {\n width: 200px;\n height: 200px;\n padding: 12px;\n margin: 4px auto;\n background: #fff;\n border-radius: var(--arkyc-radius);\n border: 1px solid var(--arkyc-border);\n}\n\n.arkyc-qr svg {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n.arkyc-handoff-wait {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n color: var(--arkyc-muted);\n font-size: 14px;\n margin-top: 6px;\n}\n\n/* Decorative line-art illustration on the welcome / instruction screens: a soft\n tinted disc in the primary colour with a halo, holding a stroked icon. */\n.arkyc-illus {\n width: 104px;\n height: 104px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0 auto 2px;\n color: var(--arkyc-primary);\n background: color-mix(in srgb, var(--arkyc-primary) 12%, transparent);\n box-shadow: 0 0 0 8px color-mix(in srgb, var(--arkyc-primary) 5%, transparent);\n}\n\n.arkyc-illus svg {\n width: 58px;\n height: 58px;\n}\n\n.arkyc-badge {\n width: 64px;\n height: 64px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 32px;\n color: #fff;\n}\n\n.arkyc-badge.ok {\n background: #16a34a;\n}\n\n.arkyc-badge.warn {\n background: #d97706;\n}\n\n.arkyc-badge.err {\n background: #dc2626;\n}\n\n/* --- Animated capture overlays --- */\n.arkyc-stage {\n position: relative;\n width: 100%;\n max-width: 280px;\n margin: 0 auto;\n}\n\n.arkyc-stage .arkyc-preview {\n display: block;\n width: 100%;\n max-width: 100%;\n}\n\n.arkyc-ring {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n.arkyc-ring svg {\n width: 100%;\n height: 100%;\n transform: rotate(-90deg);\n overflow: visible;\n}\n\n.arkyc-ring-track {\n fill: none;\n stroke: rgba(125, 130, 150, 0.3);\n stroke-width: 3;\n}\n\n.arkyc-ring-arc {\n fill: none;\n stroke: var(--arkyc-primary);\n stroke-width: 4;\n stroke-linecap: round;\n transition:\n stroke-dashoffset 0.15s linear,\n stroke 0.25s;\n}\n\n.arkyc-stage[data-state='wait'] .arkyc-ring-track {\n animation: arkyc-pulse 1.5s ease-in-out infinite;\n}\n\n.arkyc-stage[data-state='good'] .arkyc-ring-arc,\n.arkyc-stage[data-state='done'] .arkyc-ring-arc,\n.arkyc-stage[data-state='done'] .arkyc-ring-track {\n stroke: #16a34a;\n}\n\n.arkyc-cue {\n position: absolute;\n left: 0;\n right: 0;\n bottom: 7%;\n display: flex;\n justify-content: center;\n align-items: center;\n pointer-events: none;\n opacity: 0;\n transition: opacity 0.25s;\n}\n\n.arkyc-cue.show {\n opacity: 1;\n}\n\n.arkyc-cue svg {\n width: 36px;\n height: 36px;\n color: #fff;\n filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.55));\n}\n\n.arkyc-cue-turn_right svg {\n animation: arkyc-nudge-r 1.1s ease-in-out infinite;\n}\n\n.arkyc-cue-turn_left svg {\n animation: arkyc-nudge-l 1.1s ease-in-out infinite;\n}\n\n.arkyc-cue-nod svg {\n animation: arkyc-nudge-d 1.1s ease-in-out infinite;\n}\n\n.arkyc-cue-move_closer svg {\n animation: arkyc-zoom 1.2s ease-in-out infinite;\n}\n\n.arkyc-cue-blink svg,\n.arkyc-cue-smile svg {\n animation: arkyc-soft 1.3s ease-in-out infinite;\n}\n\n.arkyc-check {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n opacity: 0;\n transform: scale(0.5);\n transition:\n opacity 0.25s,\n transform 0.25s;\n pointer-events: none;\n}\n\n.arkyc-check svg {\n width: 56px;\n height: 56px;\n color: #16a34a;\n background: rgba(255, 255, 255, 0.92);\n border-radius: 50%;\n padding: 10px;\n box-sizing: border-box;\n}\n\n.arkyc-stage[data-state='done'] .arkyc-check {\n opacity: 1;\n transform: scale(1);\n}\n\n.arkyc-stage[data-state='done'] .arkyc-cue {\n opacity: 0;\n}\n\n.arkyc-dots {\n display: flex;\n gap: 8px;\n justify-content: center;\n margin: 0 0 14px;\n}\n\n.arkyc-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--arkyc-border);\n transition:\n background 0.3s,\n transform 0.3s;\n}\n\n.arkyc-dot.active {\n background: var(--arkyc-primary);\n transform: scale(1.4);\n animation: arkyc-pulse 1.2s ease-in-out infinite;\n}\n\n.arkyc-dot.done {\n background: #16a34a;\n}\n\n.arkyc-doc {\n position: relative;\n width: 100%;\n max-width: 360px;\n margin: 0 auto;\n}\n\n.arkyc-doc .arkyc-preview {\n display: block;\n width: 100%;\n max-width: 100%;\n}\n\n.arkyc-doc-frame {\n position: absolute;\n /* Fixed, always-visible alignment target at the ID-1 card aspect ratio\n (85.6 × 54 mm ≈ 1.585:1), centred in the preview. */\n top: 50%;\n left: 50%;\n width: 86%;\n aspect-ratio: 1.585 / 1;\n transform: translate(-50%, -50%);\n pointer-events: none;\n border: 2px solid rgba(255, 255, 255, 0.5);\n border-radius: 10px;\n transition: border-color 0.3s;\n}\n\n.arkyc-corner {\n position: absolute;\n width: 24px;\n height: 24px;\n border: 3px solid rgba(255, 255, 255, 0.9);\n transition: border-color 0.3s;\n}\n\n.arkyc-corner.tl {\n top: 0;\n left: 0;\n border-right: none;\n border-bottom: none;\n border-top-left-radius: 6px;\n}\n\n.arkyc-corner.tr {\n top: 0;\n right: 0;\n border-left: none;\n border-bottom: none;\n border-top-right-radius: 6px;\n}\n\n.arkyc-corner.bl {\n bottom: 0;\n left: 0;\n border-right: none;\n border-top: none;\n border-bottom-left-radius: 6px;\n}\n\n.arkyc-corner.br {\n bottom: 0;\n right: 0;\n border-left: none;\n border-top: none;\n border-bottom-right-radius: 6px;\n}\n\n.arkyc-scan {\n position: absolute;\n /* Animates within the fixed guide (its parent), sweeping the card area. */\n left: 6%;\n right: 6%;\n top: 6%;\n height: 2px;\n background: linear-gradient(90deg, transparent, var(--arkyc-primary), transparent);\n box-shadow: 0 0 8px var(--arkyc-primary);\n animation: arkyc-scan 2.4s ease-in-out infinite;\n}\n\n/* A document is in view: brighten the guide (quality tints still override). */\n.arkyc-doc[data-detected='true'] .arkyc-doc-frame {\n border-color: rgba(255, 255, 255, 0.85);\n}\n\n.arkyc-doc[data-q='good'] .arkyc-corner,\n.arkyc-doc[data-q='good'] .arkyc-doc-frame {\n border-color: #16a34a;\n}\n\n.arkyc-doc[data-q='good'] .arkyc-scan {\n background: linear-gradient(90deg, transparent, #16a34a, transparent);\n box-shadow: 0 0 8px #16a34a;\n}\n\n.arkyc-doc[data-q='bad'] .arkyc-corner,\n.arkyc-doc[data-q='bad'] .arkyc-doc-frame {\n border-color: #f59e0b;\n}\n\n@keyframes arkyc-pulse {\n 0%,\n 100% {\n opacity: 1;\n }\n\n 50% {\n opacity: 0.4;\n }\n}\n\n@keyframes arkyc-nudge-r {\n 0%,\n 100% {\n transform: translateX(-7px);\n }\n\n 50% {\n transform: translateX(7px);\n }\n}\n\n@keyframes arkyc-nudge-l {\n 0%,\n 100% {\n transform: translateX(7px);\n }\n\n 50% {\n transform: translateX(-7px);\n }\n}\n\n@keyframes arkyc-nudge-d {\n 0%,\n 100% {\n transform: translateY(-6px);\n }\n\n 50% {\n transform: translateY(6px);\n }\n}\n\n@keyframes arkyc-zoom {\n 0%,\n 100% {\n transform: scale(0.8);\n }\n\n 50% {\n transform: scale(1.15);\n }\n}\n\n@keyframes arkyc-soft {\n 0%,\n 100% {\n opacity: 0.35;\n }\n\n 50% {\n opacity: 1;\n }\n}\n\n@keyframes arkyc-scan {\n 0%,\n 100% {\n top: 6%;\n }\n\n 50% {\n top: 90%;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .arkyc-ring-track,\n .arkyc-cue svg,\n .arkyc-scan,\n .arkyc-dot,\n .arkyc-spinner {\n animation: none !important;\n }\n\n .arkyc-ring-arc {\n transition: none !important;\n }\n}\n\n.arkyc-hidden {\n display: none;\n}\n";
|
|
3
|
+
//#endregion
|
|
4
|
+
export { _virtual_arkyc_theme_css_default as default };
|