@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.
Files changed (59) hide show
  1. package/README.md +85 -0
  2. package/dist/ArkycWidget.d.mts +50 -0
  3. package/dist/ArkycWidget.d.mts.map +1 -0
  4. package/dist/ArkycWidget.mjs +80 -0
  5. package/dist/ArkycWidget.mjs.map +1 -0
  6. package/dist/WidgetHandler.d.mts +24 -0
  7. package/dist/WidgetHandler.d.mts.map +1 -0
  8. package/dist/WidgetHandler.mjs +28 -0
  9. package/dist/WidgetHandler.mjs.map +1 -0
  10. package/dist/_virtual/_virtual_arkyc-theme-css.mjs +4 -0
  11. package/dist/arkyc-widget.iife.global.js +670 -0
  12. package/dist/arkyc-widget.iife.global.js.map +1 -0
  13. package/dist/capture.d.mts +73 -0
  14. package/dist/capture.d.mts.map +1 -0
  15. package/dist/capture.mjs +126 -0
  16. package/dist/capture.mjs.map +1 -0
  17. package/dist/client.d.mts +152 -0
  18. package/dist/client.d.mts.map +1 -0
  19. package/dist/client.mjs +120 -0
  20. package/dist/client.mjs.map +1 -0
  21. package/dist/controller.d.mts +126 -0
  22. package/dist/controller.d.mts.map +1 -0
  23. package/dist/controller.mjs +582 -0
  24. package/dist/controller.mjs.map +1 -0
  25. package/dist/countries.mjs +967 -0
  26. package/dist/countries.mjs.map +1 -0
  27. package/dist/device.mjs +17 -0
  28. package/dist/device.mjs.map +1 -0
  29. package/dist/document.d.mts +108 -0
  30. package/dist/document.d.mts.map +1 -0
  31. package/dist/document.mjs +227 -0
  32. package/dist/document.mjs.map +1 -0
  33. package/dist/face.d.mts +82 -0
  34. package/dist/face.d.mts.map +1 -0
  35. package/dist/face.mjs +230 -0
  36. package/dist/face.mjs.map +1 -0
  37. package/dist/flow.d.mts +74 -0
  38. package/dist/flow.d.mts.map +1 -0
  39. package/dist/flow.mjs +132 -0
  40. package/dist/flow.mjs.map +1 -0
  41. package/dist/index.d.mts +19 -0
  42. package/dist/index.d.mts.map +1 -0
  43. package/dist/index.mjs +16 -0
  44. package/dist/index.mjs.map +1 -0
  45. package/dist/qr.mjs +22 -0
  46. package/dist/qr.mjs.map +1 -0
  47. package/dist/realtime.d.mts +29 -0
  48. package/dist/realtime.d.mts.map +1 -0
  49. package/dist/realtime.mjs +107 -0
  50. package/dist/realtime.mjs.map +1 -0
  51. package/dist/theme.d.mts +42 -0
  52. package/dist/theme.d.mts.map +1 -0
  53. package/dist/theme.mjs +77 -0
  54. package/dist/theme.mjs.map +1 -0
  55. package/dist/types.d.mts +153 -0
  56. package/dist/types.d.mts.map +1 -0
  57. package/dist/ui.mjs +931 -0
  58. package/dist/ui.mjs.map +1 -0
  59. package/package.json +43 -0
package/dist/face.mjs ADDED
@@ -0,0 +1,230 @@
1
+ //#region src/face.ts
2
+ const TASKS_VERSION = "0.10.20";
3
+ const npm = (path) => "https://cdn.jsdelivr.net/npm/" + path;
4
+ const MODEL_URL = "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task";
5
+ const NOSE = 1;
6
+ const LEFT_EYE = 33;
7
+ const RIGHT_EYE = 263;
8
+ const LEFT_EDGE = 234;
9
+ const RIGHT_EDGE = 454;
10
+ const CHIN = 152;
11
+ function blend(cats, name) {
12
+ return cats?.find((c) => c.categoryName === name)?.score ?? 0;
13
+ }
14
+ function now() {
15
+ const p = globalThis.performance;
16
+ return p?.now ? p.now() : 0;
17
+ }
18
+ var MediapipeFaceAnalyzer = class {
19
+ landmarker = null;
20
+ state = "idle";
21
+ loading = null;
22
+ async ready() {
23
+ if (this.state === "ready") return true;
24
+ if (this.state === "failed") return false;
25
+ if (this.loading) return this.loading;
26
+ this.loading = this.load();
27
+ return this.loading;
28
+ }
29
+ async load() {
30
+ this.state = "loading";
31
+ try {
32
+ if (typeof document === "undefined") throw new Error("no document");
33
+ if (!document.createElement("canvas").getContext("webgl2")) throw new Error("no webgl2");
34
+ const vision = await import(
35
+ /* @vite-ignore */
36
+ npm(`@mediapipe/tasks-vision@${TASKS_VERSION}/vision_bundle.mjs`)
37
+ );
38
+ const fileset = await vision.FilesetResolver.forVisionTasks(npm(`@mediapipe/tasks-vision@${TASKS_VERSION}/wasm`));
39
+ this.landmarker = await vision.FaceLandmarker.createFromOptions(fileset, {
40
+ baseOptions: {
41
+ modelAssetPath: MODEL_URL,
42
+ delegate: "GPU"
43
+ },
44
+ outputFaceBlendshapes: true,
45
+ runningMode: "VIDEO",
46
+ numFaces: 1
47
+ });
48
+ this.state = "ready";
49
+ return true;
50
+ } catch {
51
+ this.state = "failed";
52
+ return false;
53
+ }
54
+ }
55
+ analyze(video) {
56
+ if (this.state !== "ready" || !this.landmarker || !video.videoWidth) return null;
57
+ let res;
58
+ try {
59
+ res = this.landmarker.detectForVideo(video, now());
60
+ } catch {
61
+ return null;
62
+ }
63
+ const lm = res.faceLandmarks?.[0];
64
+ if (!lm || lm.length === 0) return {
65
+ present: false,
66
+ centerX: .5,
67
+ centerY: .5,
68
+ scale: 0,
69
+ turn: 0,
70
+ pitch: 0,
71
+ blink: 0,
72
+ smile: 0
73
+ };
74
+ let minX = 1;
75
+ let maxX = 0;
76
+ let minY = 1;
77
+ let maxY = 0;
78
+ for (const p of lm) {
79
+ if (p.x < minX) minX = p.x;
80
+ if (p.x > maxX) maxX = p.x;
81
+ if (p.y < minY) minY = p.y;
82
+ if (p.y > maxY) maxY = p.y;
83
+ }
84
+ const height = Math.max(.001, maxY - minY);
85
+ const nose = lm[NOSE];
86
+ const leftEye = lm[LEFT_EYE];
87
+ const rightEye = lm[RIGHT_EYE];
88
+ const leftEdge = lm[LEFT_EDGE];
89
+ const rightEdge = lm[RIGHT_EDGE];
90
+ const chin = lm[CHIN];
91
+ const dLeft = nose.x - leftEdge.x;
92
+ const dRight = rightEdge.x - nose.x;
93
+ const turn = (dRight - dLeft) / Math.max(.001, dLeft + dRight);
94
+ const eyeY = (leftEye.y + rightEye.y) / 2;
95
+ const pitch = (nose.y - eyeY) / Math.max(.001, chin.y - eyeY);
96
+ const cats = res.faceBlendshapes?.[0]?.categories;
97
+ const blink = Math.max(blend(cats, "eyeBlinkLeft"), blend(cats, "eyeBlinkRight"));
98
+ const smile = (blend(cats, "mouthSmileLeft") + blend(cats, "mouthSmileRight")) / 2;
99
+ return {
100
+ present: true,
101
+ centerX: (minX + maxX) / 2,
102
+ centerY: (minY + maxY) / 2,
103
+ scale: height,
104
+ turn,
105
+ pitch,
106
+ blink,
107
+ smile
108
+ };
109
+ }
110
+ close() {
111
+ try {
112
+ this.landmarker?.close();
113
+ } catch {}
114
+ this.landmarker = null;
115
+ this.state = "failed";
116
+ }
117
+ };
118
+ /** The default (real, MediaPipe-backed) analyzer. */
119
+ function createDefaultFaceAnalyzer() {
120
+ return new MediapipeFaceAnalyzer();
121
+ }
122
+ const DEFAULT_TUNING = {
123
+ turn: .18,
124
+ smile: .5,
125
+ blinkClosed: .55,
126
+ blinkOpen: .25,
127
+ hold: 3,
128
+ nodDown: .06,
129
+ nodReturn: .02,
130
+ closerFactor: 1.22,
131
+ selfieCenterTol: .2,
132
+ selfieCenterYTol: .25,
133
+ selfieMinScale: .28,
134
+ selfieMaxScale: .85
135
+ };
136
+ /**
137
+ * Build a stateful detector for a single active-liveness challenge.
138
+ *
139
+ * @param challenge
140
+ * @param t
141
+ * @returns
142
+ */
143
+ function makeChallengeDetector(challenge, t = DEFAULT_TUNING) {
144
+ const sustained = (ok) => {
145
+ let n = 0;
146
+ return {
147
+ feed(s) {
148
+ n = s.present && ok(s) ? n + 1 : 0;
149
+ return n >= t.hold;
150
+ },
151
+ get progress() {
152
+ return Math.min(1, n / Math.max(1, t.hold));
153
+ }
154
+ };
155
+ };
156
+ const twoStage = (pick, extreme, settled) => {
157
+ let base = null;
158
+ let reached = false;
159
+ let done = false;
160
+ return {
161
+ feed(s) {
162
+ if (!s.present) return false;
163
+ const v = pick(s);
164
+ if (base === null) {
165
+ base = v;
166
+ return false;
167
+ }
168
+ if (extreme(v, base)) reached = true;
169
+ else if (reached && settled(v, base)) {
170
+ done = true;
171
+ return true;
172
+ }
173
+ return false;
174
+ },
175
+ get progress() {
176
+ return done ? 1 : reached ? .6 : 0;
177
+ }
178
+ };
179
+ };
180
+ switch (challenge) {
181
+ case "blink": {
182
+ let closed = false;
183
+ let done = false;
184
+ return {
185
+ feed(s) {
186
+ if (!s.present) return false;
187
+ if (s.blink > t.blinkClosed) closed = true;
188
+ else if (closed && s.blink < t.blinkOpen) {
189
+ done = true;
190
+ return true;
191
+ }
192
+ return false;
193
+ },
194
+ get progress() {
195
+ return done ? 1 : closed ? .6 : 0;
196
+ }
197
+ };
198
+ }
199
+ case "smile": return sustained((s) => s.smile > t.smile);
200
+ case "turn_left": return sustained((s) => s.turn < -t.turn);
201
+ case "turn_right": return sustained((s) => s.turn > t.turn);
202
+ case "nod": return twoStage((s) => s.pitch, (v, base) => v > base + t.nodDown, (v, base) => v < base + t.nodReturn);
203
+ case "move_closer": {
204
+ let base = null;
205
+ let n = 0;
206
+ return {
207
+ feed(s) {
208
+ if (!s.present) return false;
209
+ if (base === null) {
210
+ base = s.scale;
211
+ return false;
212
+ }
213
+ n = s.scale > base * t.closerFactor ? n + 1 : 0;
214
+ return n >= t.hold;
215
+ },
216
+ get progress() {
217
+ return Math.min(1, n / Math.max(1, t.hold));
218
+ }
219
+ };
220
+ }
221
+ }
222
+ }
223
+ /** Whether a face is well-framed for a selfie (present, centred, right distance). */
224
+ function isSelfieReady(s, t = DEFAULT_TUNING) {
225
+ return s.present && Math.abs(s.centerX - .5) < t.selfieCenterTol && Math.abs(s.centerY - .5) < t.selfieCenterYTol && s.scale > t.selfieMinScale && s.scale < t.selfieMaxScale;
226
+ }
227
+ //#endregion
228
+ export { DEFAULT_TUNING, createDefaultFaceAnalyzer, isSelfieReady, makeChallengeDetector };
229
+
230
+ //# sourceMappingURL=face.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"face.mjs","names":[],"sources":["../src/face.ts"],"sourcesContent":["/**\n * In-browser face analysis for selfie auto-capture and active-liveness detection.\n *\n * The real implementation loads MediaPipe's FaceLandmarker (landmarks +\n * blendshapes) lazily from a CDN at runtime — it is NOT bundled, and never\n * touched unless a live camera screen actually asks for it. Everything is\n * feature-detected and wrapped so that on any failure (no WebGL/WASM, offline,\n * a non-browser/test host) `ready()` resolves `false` and callers fall back to\n * the manual flow. The widget's pure, fake-DOM tests therefore never load it.\n */\nimport type { LivenessChallenge } from '@arkyc/types'\n\n/** A normalized read of the current frame's face. */\nexport interface FaceSample {\n present: boolean\n /** Face bounding-box centre, normalized 0–1. */\n centerX: number\n centerY: number\n /** Face bounding-box height, normalized 0–1 (proxy for distance). */\n scale: number\n /** Head turn: <0 toward one side, >0 the other (approx, from landmark asymmetry). */\n turn: number\n /** Head pitch proxy: nose-to-eye offset over face height (relative changes matter). */\n pitch: number\n /** Eyes-closed score 0–1 (blendshape). */\n blink: number\n /** Smile score 0–1 (blendshape). */\n smile: number\n}\n\nexport interface FaceAnalyzer {\n /** Load models. Resolves `false` if unavailable — callers then go manual. */\n ready(): Promise<boolean>\n /** Analyze the current video frame, or `null` if it can't be read. */\n analyze(video: HTMLVideoElement): FaceSample | null\n /** Release resources. */\n close(): void\n}\n\n// CDN asset locations. Built at runtime (not string literals) so bundlers leave\n// the dynamic import live instead of trying to resolve/inline it.\nconst TASKS_VERSION = '0.10.20'\nconst npm = (path: string) => 'https://cdn.jsdelivr.net/npm/' + path\nconst MODEL_URL =\n 'https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task'\n\n// MediaPipe FaceMesh canonical landmark indices.\nconst NOSE = 1\nconst LEFT_EYE = 33\nconst RIGHT_EYE = 263\nconst LEFT_EDGE = 234\nconst RIGHT_EDGE = 454\nconst CHIN = 152\n\ninterface Pt {\n x: number\n y: number\n}\ninterface LmResult {\n faceLandmarks: Pt[][]\n faceBlendshapes?: { categories: { categoryName: string; score: number }[] }[]\n}\n\nfunction blend(cats: { categoryName: string; score: number }[] | undefined, name: string): number {\n return cats?.find((c) => c.categoryName === name)?.score ?? 0\n}\n\nfunction now(): number {\n const p = (globalThis as { performance?: { now?: () => number } }).performance\n return p?.now ? p.now() : 0\n}\n\nclass MediapipeFaceAnalyzer implements FaceAnalyzer {\n private landmarker: { detectForVideo(v: HTMLVideoElement, t: number): LmResult; close(): void } | null = null\n private state: 'idle' | 'loading' | 'ready' | 'failed' = 'idle'\n private loading: Promise<boolean> | null = null\n\n async ready(): Promise<boolean> {\n if (this.state === 'ready') return true\n if (this.state === 'failed') return false\n if (this.loading) return this.loading\n this.loading = this.load()\n return this.loading\n }\n\n private async load(): Promise<boolean> {\n this.state = 'loading'\n try {\n if (typeof document === 'undefined') throw new Error('no document')\n // Bail early if WebGL2 isn't available — MediaPipe needs it.\n const probe = document.createElement('canvas')\n if (!probe.getContext('webgl2')) throw new Error('no webgl2')\n\n const visionUrl = npm(`@mediapipe/tasks-vision@${TASKS_VERSION}/vision_bundle.mjs`)\n const vision = (await import(/* @vite-ignore */ visionUrl)) as {\n FilesetResolver: { forVisionTasks(url: string): Promise<unknown> }\n FaceLandmarker: { createFromOptions(fileset: unknown, opts: unknown): Promise<unknown> }\n }\n const fileset = await vision.FilesetResolver.forVisionTasks(npm(`@mediapipe/tasks-vision@${TASKS_VERSION}/wasm`))\n this.landmarker = (await vision.FaceLandmarker.createFromOptions(fileset, {\n baseOptions: { modelAssetPath: MODEL_URL, delegate: 'GPU' },\n outputFaceBlendshapes: true,\n runningMode: 'VIDEO',\n numFaces: 1,\n })) as MediapipeFaceAnalyzer['landmarker']\n this.state = 'ready'\n return true\n } catch {\n this.state = 'failed'\n return false\n }\n }\n\n analyze(video: HTMLVideoElement): FaceSample | null {\n if (this.state !== 'ready' || !this.landmarker || !video.videoWidth) return null\n let res: LmResult\n try {\n res = this.landmarker.detectForVideo(video, now())\n } catch {\n return null\n }\n const lm = res.faceLandmarks?.[0]\n if (!lm || lm.length === 0) {\n return { present: false, centerX: 0.5, centerY: 0.5, scale: 0, turn: 0, pitch: 0, blink: 0, smile: 0 }\n }\n\n let minX = 1\n let maxX = 0\n let minY = 1\n let maxY = 0\n for (const p of lm) {\n if (p.x < minX) minX = p.x\n if (p.x > maxX) maxX = p.x\n if (p.y < minY) minY = p.y\n if (p.y > maxY) maxY = p.y\n }\n const height = Math.max(1e-3, maxY - minY)\n\n const nose = lm[NOSE]!\n const leftEye = lm[LEFT_EYE]!\n const rightEye = lm[RIGHT_EYE]!\n const leftEdge = lm[LEFT_EDGE]!\n const rightEdge = lm[RIGHT_EDGE]!\n const chin = lm[CHIN]!\n\n // Turn: horizontal asymmetry of the nose between the two face edges. Signed\n // (empirically, against a real front camera) so that turning the head right\n // yields turn>0 and left yields turn<0 — matching the turn_left/turn_right\n // prompts. Computed on the raw frame; the preview's CSS mirror is cosmetic.\n const dLeft = nose.x - leftEdge.x\n const dRight = rightEdge.x - nose.x\n const turn = (dRight - dLeft) / Math.max(1e-3, dLeft + dRight)\n\n // Pitch proxy: nose vertical position between the eye line and the chin.\n const eyeY = (leftEye.y + rightEye.y) / 2\n const pitch = (nose.y - eyeY) / Math.max(1e-3, chin.y - eyeY)\n\n const cats = res.faceBlendshapes?.[0]?.categories\n const blink = Math.max(blend(cats, 'eyeBlinkLeft'), blend(cats, 'eyeBlinkRight'))\n const smile = (blend(cats, 'mouthSmileLeft') + blend(cats, 'mouthSmileRight')) / 2\n\n return {\n present: true,\n centerX: (minX + maxX) / 2,\n centerY: (minY + maxY) / 2,\n scale: height,\n turn,\n pitch,\n blink,\n smile,\n }\n }\n\n close(): void {\n try {\n this.landmarker?.close()\n } catch {\n /* ignore */\n }\n this.landmarker = null\n this.state = 'failed'\n }\n}\n\n/** The default (real, MediaPipe-backed) analyzer. */\nexport function createDefaultFaceAnalyzer(): FaceAnalyzer {\n return new MediapipeFaceAnalyzer()\n}\n\n/** A challenge detector: fed frame samples, returns `true` once satisfied. */\nexport interface ChallengeDetector {\n feed(sample: FaceSample): boolean\n /**\n * 0–1 progress toward satisfying the challenge, updated on each `feed` — drives\n * the UI's hold-progress ring. Multi-stage gestures (blink/nod) step through\n * intermediate values; sustained gestures ramp with the hold streak.\n */\n readonly progress: number\n}\n\n/**\n * Detection thresholds. These are the knobs to tune against a real camera —\n * use the calibration harness (`playground/calibration.html`) to read live\n * signal values and adjust. All are overridable per-widget via `faceTuning`.\n */\nexport interface FaceTuning {\n /** |turn| asymmetry needed to count as a head turn. */\n turn: number\n /** Mouth-smile blendshape score needed to count as a smile. */\n smile: number\n /** Eye-blink score above which the eyes count as closed. */\n blinkClosed: number\n /** Eye-blink score below which they count as re-opened. */\n blinkOpen: number\n /** Consecutive frames a condition must hold before it fires. */\n hold: number\n /** Pitch increase (looking down) needed to arm a nod. */\n nodDown: number\n /** Pitch return delta (back up) that completes a nod. */\n nodReturn: number\n /** Face must grow past `baseline * closerFactor` for move-closer. */\n closerFactor: number\n /** Selfie framing tolerances (distance from frame centre / size window). */\n selfieCenterTol: number\n selfieCenterYTol: number\n selfieMinScale: number\n selfieMaxScale: number\n}\n\nexport const DEFAULT_TUNING: FaceTuning = {\n turn: 0.18,\n smile: 0.5,\n blinkClosed: 0.55,\n blinkOpen: 0.25,\n hold: 3,\n nodDown: 0.06,\n nodReturn: 0.02,\n closerFactor: 1.22,\n selfieCenterTol: 0.2,\n selfieCenterYTol: 0.25,\n selfieMinScale: 0.28,\n selfieMaxScale: 0.85,\n}\n\n/**\n * Build a stateful detector for a single active-liveness challenge.\n *\n * @param challenge\n * @param t\n * @returns\n */\nexport function makeChallengeDetector(challenge: LivenessChallenge, t: FaceTuning = DEFAULT_TUNING): ChallengeDetector {\n // A sustained gesture: a condition that must hold for `t.hold` frames. Progress\n // ramps with the streak.\n const sustained = (ok: (s: FaceSample) => boolean): ChallengeDetector => {\n let n = 0\n return {\n feed(s) {\n n = s.present && ok(s) ? n + 1 : 0\n return n >= t.hold\n },\n get progress() {\n return Math.min(1, n / Math.max(1, t.hold))\n },\n }\n }\n\n // A two-stage gesture: reach an extreme, then return. Progress is 0 → ~0.6 (mid)\n // → 1 (done).\n const twoStage = (\n pick: (s: FaceSample) => number,\n extreme: (v: number, base: number) => boolean,\n settled: (v: number, base: number) => boolean,\n ): ChallengeDetector => {\n let base: number | null = null\n let reached = false\n let done = false\n return {\n feed(s) {\n if (!s.present) return false\n const v = pick(s)\n if (base === null) {\n base = v\n return false\n }\n if (extreme(v, base)) reached = true\n else if (reached && settled(v, base)) {\n done = true\n return true\n }\n return false\n },\n get progress() {\n return done ? 1 : reached ? 0.6 : 0\n },\n }\n }\n\n switch (challenge) {\n case 'blink': {\n // No baseline: eyes must close (absolute) then re-open. Processed from the\n // first frame, unlike the baseline-relative two-stage gestures.\n let closed = false\n let done = false\n return {\n feed(s) {\n if (!s.present) return false\n if (s.blink > t.blinkClosed) closed = true\n else if (closed && s.blink < t.blinkOpen) {\n done = true\n return true\n }\n return false\n },\n get progress() {\n return done ? 1 : closed ? 0.6 : 0\n },\n }\n }\n case 'smile':\n return sustained((s) => s.smile > t.smile)\n case 'turn_left':\n return sustained((s) => s.turn < -t.turn)\n case 'turn_right':\n return sustained((s) => s.turn > t.turn)\n case 'nod':\n return twoStage(\n (s) => s.pitch,\n (v, base) => v > base + t.nodDown,\n (v, base) => v < base + t.nodReturn,\n )\n case 'move_closer': {\n let base: number | null = null\n let n = 0\n return {\n feed(s) {\n if (!s.present) return false\n if (base === null) {\n base = s.scale\n return false\n }\n n = s.scale > base * t.closerFactor ? n + 1 : 0\n return n >= t.hold\n },\n get progress() {\n return Math.min(1, n / Math.max(1, t.hold))\n },\n }\n }\n }\n}\n\n/** Whether a face is well-framed for a selfie (present, centred, right distance). */\nexport function isSelfieReady(s: FaceSample, t: FaceTuning = DEFAULT_TUNING): boolean {\n return (\n s.present &&\n Math.abs(s.centerX - 0.5) < t.selfieCenterTol &&\n Math.abs(s.centerY - 0.5) < t.selfieCenterYTol &&\n s.scale > t.selfieMinScale &&\n s.scale < t.selfieMaxScale\n )\n}\n"],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ import { DocumentType, LivenessMode, VerificationDecision, VerificationStatus, WidgetStep, WorkflowConfig } from "@arkyc/types";
2
+
3
+ //#region src/flow.d.ts
4
+ /** Context that influences flow branching. */
5
+ interface FlowContext {
6
+ documentType?: DocumentType | null;
7
+ /** Which liveness flow this session runs (resolved from the capture model). */
8
+ livenessMode?: LivenessMode;
9
+ /** The applied workflow (orders/toggles the coarse stages), or null for the default flow. */
10
+ workflow?: WorkflowConfig | null;
11
+ }
12
+ /**
13
+ * The verification flow's step machine. Stateless — exposed as static members so
14
+ * the single "flow" concern lives in one class rather than as floating helpers.
15
+ */
16
+ declare class Flow {
17
+ /**
18
+ * The flow screens, in the order the widget walks them. The `back_capture`
19
+ * screen is skipped for single-sided documents (passports).
20
+ */
21
+ static readonly STEP_ORDER: WidgetStep[];
22
+ /**
23
+ * Statuses from which a session can no longer progress.
24
+ */
25
+ static readonly TERMINAL_STATUSES: VerificationStatus[];
26
+ /**
27
+ * Whether a document type has a second (back) side to capture.
28
+ *
29
+ * @param type
30
+ * @returns
31
+ */
32
+ static documentHasBack(type: DocumentType | null | undefined): boolean;
33
+ /**
34
+ * The screens to walk for this session, in order. With no workflow this is the
35
+ * default {@link STEP_ORDER}; a workflow reorders the coarse stages and drops
36
+ * disabled ones (their screens are excluded entirely), always bracketed by
37
+ * `welcome` and `processing`/`result`.
38
+ */
39
+ static stepOrder(ctx?: FlowContext): WidgetStep[];
40
+ /**
41
+ * Whether a step runs for the given context. `back_capture` is skipped for
42
+ * single-sided documents; `ocr_processing` is skipped when the workflow skips
43
+ * OCR; the liveness steps branch on the resolved mode — `active_liveness` only
44
+ * in active mode, `selfie_capture`/`passive_liveness` only in passive mode.
45
+ * Stage-level disabling is handled by {@link stepOrder} (disabled stages are
46
+ * absent from the order), so this only covers within-stage branching.
47
+ */
48
+ static isStepEnabled(step: WidgetStep, ctx?: FlowContext): boolean;
49
+ /**
50
+ * The next enabled screen after `current`, honouring the workflow order and the
51
+ * branch rules. Returns `current` when already at the end.
52
+ *
53
+ * @param current
54
+ * @param ctx
55
+ * @returns
56
+ */
57
+ static nextStep(current: WidgetStep, ctx?: FlowContext): WidgetStep;
58
+ /**
59
+ * Whether a session status is terminal (the flow is done).
60
+ *
61
+ * @param status
62
+ * @returns
63
+ */
64
+ static isTerminal(status: VerificationStatus): boolean;
65
+ /**
66
+ * Map a terminal session status to the decision the integrator cares about.
67
+ * Non-decision terminals (`expired`/`cancelled`) and in-flight statuses map to
68
+ * `null`.
69
+ */
70
+ static statusToDecision(status: VerificationStatus): VerificationDecision | null;
71
+ }
72
+ //#endregion
73
+ export { Flow, FlowContext };
74
+ //# sourceMappingURL=flow.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow.d.mts","names":[],"sources":["../src/flow.ts"],"mappings":";;;;UAaiB,WAAA;EACf,YAAA,GAAe,YAAA;EADW;EAG1B,YAAA,GAAe,YAAA;EAFA;EAIf,QAAA,GAAW,cAAA;AAAA;;;;;cAcA,IAAA;EAhBI;;;;EAAA,gBAqBC,UAAA,EAAY,UAAA;EALjB;;;EAAA,gBAsBK,iBAAA,EAAmB,kBAAA;EAAA;;;;;;EAAA,OAc5B,eAAA,CAAgB,IAAA,EAAM,YAAA;EA2Ca;;;;;;EAAA,OAjCnC,SAAA,CAAU,GAAA,GAAK,WAAA,GAAmB,UAAA;EAzCzB;;;;;;;;EAAA,OAwDT,aAAA,CAAc,IAAA,EAAM,UAAA,EAAY,GAAA,GAAK,WAAA;EAf3B;;;;;;;;EAAA,OAiCV,QAAA,CAAS,OAAA,EAAS,UAAA,EAAY,GAAA,GAAK,WAAA,GAAmB,UAAA;EAA7C;;;;;;EAAA,OAiBT,UAAA,CAAW,MAAA,EAAQ,kBAAA;EASnB;;;;;EAAA,OAAA,gBAAA,CAAiB,MAAA,EAAQ,kBAAA,GAAqB,oBAAA;AAAA"}
package/dist/flow.mjs ADDED
@@ -0,0 +1,132 @@
1
+ import { workflowEnabledSteps, workflowRunsOcr } from "@arkyc/types";
2
+ //#region src/flow.ts
3
+ /** The widget screens that make up each coarse verification stage, in stage-local order. */
4
+ const STAGE_STEPS = {
5
+ document: [
6
+ "document_selection",
7
+ "front_capture",
8
+ "back_capture",
9
+ "ocr_processing"
10
+ ],
11
+ liveness: [
12
+ "active_liveness",
13
+ "selfie_capture",
14
+ "passive_liveness"
15
+ ],
16
+ face_match: ["face_match"]
17
+ };
18
+ /**
19
+ * The verification flow's step machine. Stateless — exposed as static members so
20
+ * the single "flow" concern lives in one class rather than as floating helpers.
21
+ */
22
+ var Flow = class Flow {
23
+ /**
24
+ * The flow screens, in the order the widget walks them. The `back_capture`
25
+ * screen is skipped for single-sided documents (passports).
26
+ */
27
+ static STEP_ORDER = [
28
+ "welcome",
29
+ "document_selection",
30
+ "front_capture",
31
+ "back_capture",
32
+ "ocr_processing",
33
+ "active_liveness",
34
+ "selfie_capture",
35
+ "passive_liveness",
36
+ "face_match",
37
+ "processing",
38
+ "result"
39
+ ];
40
+ /**
41
+ * Statuses from which a session can no longer progress.
42
+ */
43
+ static TERMINAL_STATUSES = [
44
+ "approved",
45
+ "rejected",
46
+ "requires_review",
47
+ "expired",
48
+ "cancelled"
49
+ ];
50
+ /**
51
+ * Whether a document type has a second (back) side to capture.
52
+ *
53
+ * @param type
54
+ * @returns
55
+ */
56
+ static documentHasBack(type) {
57
+ return type != null && type !== "passport";
58
+ }
59
+ /**
60
+ * The screens to walk for this session, in order. With no workflow this is the
61
+ * default {@link STEP_ORDER}; a workflow reorders the coarse stages and drops
62
+ * disabled ones (their screens are excluded entirely), always bracketed by
63
+ * `welcome` and `processing`/`result`.
64
+ */
65
+ static stepOrder(ctx = {}) {
66
+ return [
67
+ "welcome",
68
+ ...workflowEnabledSteps(ctx.workflow).flatMap((stage) => STAGE_STEPS[stage]),
69
+ "processing",
70
+ "result"
71
+ ];
72
+ }
73
+ /**
74
+ * Whether a step runs for the given context. `back_capture` is skipped for
75
+ * single-sided documents; `ocr_processing` is skipped when the workflow skips
76
+ * OCR; the liveness steps branch on the resolved mode — `active_liveness` only
77
+ * in active mode, `selfie_capture`/`passive_liveness` only in passive mode.
78
+ * Stage-level disabling is handled by {@link stepOrder} (disabled stages are
79
+ * absent from the order), so this only covers within-stage branching.
80
+ */
81
+ static isStepEnabled(step, ctx = {}) {
82
+ if (step === "back_capture") return Flow.documentHasBack(ctx.documentType);
83
+ if (step === "ocr_processing") return workflowRunsOcr(ctx.workflow);
84
+ if (step === "active_liveness") return ctx.livenessMode === "active";
85
+ if (step === "selfie_capture" || step === "passive_liveness") return ctx.livenessMode !== "active";
86
+ return true;
87
+ }
88
+ /**
89
+ * The next enabled screen after `current`, honouring the workflow order and the
90
+ * branch rules. Returns `current` when already at the end.
91
+ *
92
+ * @param current
93
+ * @param ctx
94
+ * @returns
95
+ */
96
+ static nextStep(current, ctx = {}) {
97
+ const order = Flow.stepOrder(ctx);
98
+ const idx = order.indexOf(current);
99
+ if (idx < 0) return current;
100
+ for (let i = idx + 1; i < order.length; i += 1) {
101
+ const step = order[i];
102
+ if (Flow.isStepEnabled(step, ctx)) return step;
103
+ }
104
+ return current;
105
+ }
106
+ /**
107
+ * Whether a session status is terminal (the flow is done).
108
+ *
109
+ * @param status
110
+ * @returns
111
+ */
112
+ static isTerminal(status) {
113
+ return Flow.TERMINAL_STATUSES.includes(status);
114
+ }
115
+ /**
116
+ * Map a terminal session status to the decision the integrator cares about.
117
+ * Non-decision terminals (`expired`/`cancelled`) and in-flight statuses map to
118
+ * `null`.
119
+ */
120
+ static statusToDecision(status) {
121
+ switch (status) {
122
+ case "approved": return "approved";
123
+ case "rejected": return "rejected";
124
+ case "requires_review": return "requires_review";
125
+ default: return null;
126
+ }
127
+ }
128
+ };
129
+ //#endregion
130
+ export { Flow };
131
+
132
+ //# sourceMappingURL=flow.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow.mjs","names":[],"sources":["../src/flow.ts"],"sourcesContent":["import {\n type DocumentType,\n type LivenessMode,\n type VerificationDecision,\n type VerificationStatus,\n type WidgetStep,\n type WorkflowConfig,\n type WorkflowStepKey,\n workflowEnabledSteps,\n workflowRunsOcr,\n} from '@arkyc/types'\n\n/** Context that influences flow branching. */\nexport interface FlowContext {\n documentType?: DocumentType | null\n /** Which liveness flow this session runs (resolved from the capture model). */\n livenessMode?: LivenessMode\n /** The applied workflow (orders/toggles the coarse stages), or null for the default flow. */\n workflow?: WorkflowConfig | null\n}\n\n/** The widget screens that make up each coarse verification stage, in stage-local order. */\nconst STAGE_STEPS: Record<WorkflowStepKey, WidgetStep[]> = {\n document: ['document_selection', 'front_capture', 'back_capture', 'ocr_processing'],\n liveness: ['active_liveness', 'selfie_capture', 'passive_liveness'],\n face_match: ['face_match'],\n}\n\n/**\n * The verification flow's step machine. Stateless — exposed as static members so\n * the single \"flow\" concern lives in one class rather than as floating helpers.\n */\nexport class Flow {\n /**\n * The flow screens, in the order the widget walks them. The `back_capture`\n * screen is skipped for single-sided documents (passports).\n */\n static readonly STEP_ORDER: WidgetStep[] = [\n 'welcome',\n 'document_selection',\n 'front_capture',\n 'back_capture',\n 'ocr_processing',\n 'active_liveness',\n 'selfie_capture',\n 'passive_liveness',\n 'face_match',\n 'processing',\n 'result',\n ]\n\n /**\n * Statuses from which a session can no longer progress.\n */\n static readonly TERMINAL_STATUSES: VerificationStatus[] = [\n 'approved',\n 'rejected',\n 'requires_review',\n 'expired',\n 'cancelled',\n ]\n\n /**\n * Whether a document type has a second (back) side to capture.\n *\n * @param type\n * @returns\n */\n static documentHasBack(type: DocumentType | null | undefined): boolean {\n return type != null && type !== 'passport'\n }\n\n /**\n * The screens to walk for this session, in order. With no workflow this is the\n * default {@link STEP_ORDER}; a workflow reorders the coarse stages and drops\n * disabled ones (their screens are excluded entirely), always bracketed by\n * `welcome` and `processing`/`result`.\n */\n static stepOrder(ctx: FlowContext = {}): WidgetStep[] {\n const stages = workflowEnabledSteps(ctx.workflow) as WorkflowStepKey[]\n const middle = stages.flatMap((stage) => STAGE_STEPS[stage])\n\n return ['welcome', ...middle, 'processing', 'result']\n }\n\n /**\n * Whether a step runs for the given context. `back_capture` is skipped for\n * single-sided documents; `ocr_processing` is skipped when the workflow skips\n * OCR; the liveness steps branch on the resolved mode — `active_liveness` only\n * in active mode, `selfie_capture`/`passive_liveness` only in passive mode.\n * Stage-level disabling is handled by {@link stepOrder} (disabled stages are\n * absent from the order), so this only covers within-stage branching.\n */\n static isStepEnabled(step: WidgetStep, ctx: FlowContext = {}): boolean {\n if (step === 'back_capture') return Flow.documentHasBack(ctx.documentType)\n if (step === 'ocr_processing') return workflowRunsOcr(ctx.workflow)\n if (step === 'active_liveness') return ctx.livenessMode === 'active'\n if (step === 'selfie_capture' || step === 'passive_liveness') {\n return ctx.livenessMode !== 'active'\n }\n return true\n }\n\n /**\n * The next enabled screen after `current`, honouring the workflow order and the\n * branch rules. Returns `current` when already at the end.\n *\n * @param current\n * @param ctx\n * @returns\n */\n static nextStep(current: WidgetStep, ctx: FlowContext = {}): WidgetStep {\n const order = Flow.stepOrder(ctx)\n const idx = order.indexOf(current)\n if (idx < 0) return current\n for (let i = idx + 1; i < order.length; i += 1) {\n const step = order[i]!\n if (Flow.isStepEnabled(step, ctx)) return step\n }\n return current\n }\n\n /**\n * Whether a session status is terminal (the flow is done).\n *\n * @param status\n * @returns\n */\n static isTerminal(status: VerificationStatus): boolean {\n return Flow.TERMINAL_STATUSES.includes(status)\n }\n\n /**\n * Map a terminal session status to the decision the integrator cares about.\n * Non-decision terminals (`expired`/`cancelled`) and in-flight statuses map to\n * `null`.\n */\n static statusToDecision(status: VerificationStatus): VerificationDecision | null {\n switch (status) {\n case 'approved':\n return 'approved'\n case 'rejected':\n return 'rejected'\n case 'requires_review':\n return 'requires_review'\n default:\n return null\n }\n }\n}\n"],"mappings":";;;AAsBA,MAAM,cAAqD;CACzD,UAAU;EAAC;EAAsB;EAAiB;EAAgB;CAAgB;CAClF,UAAU;EAAC;EAAmB;EAAkB;CAAkB;CAClE,YAAY,CAAC,YAAY;AAC3B;;;;;AAMA,IAAa,OAAb,MAAa,KAAK;;;;;CAKhB,OAAgB,aAA2B;EACzC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;;;;CAKA,OAAgB,oBAA0C;EACxD;EACA;EACA;EACA;EACA;CACF;;;;;;;CAQA,OAAO,gBAAgB,MAAgD;EACrE,OAAO,QAAQ,QAAQ,SAAS;CAClC;;;;;;;CAQA,OAAO,UAAU,MAAmB,CAAC,GAAiB;EAIpD,OAAO;GAAC;GAAW,GAHJ,qBAAqB,IAAI,QACpB,CAAC,CAAC,SAAS,UAAU,YAAY,MAE1B;GAAG;GAAc;EAAQ;CACtD;;;;;;;;;CAUA,OAAO,cAAc,MAAkB,MAAmB,CAAC,GAAY;EACrE,IAAI,SAAS,gBAAgB,OAAO,KAAK,gBAAgB,IAAI,YAAY;EACzE,IAAI,SAAS,kBAAkB,OAAO,gBAAgB,IAAI,QAAQ;EAClE,IAAI,SAAS,mBAAmB,OAAO,IAAI,iBAAiB;EAC5D,IAAI,SAAS,oBAAoB,SAAS,oBACxC,OAAO,IAAI,iBAAiB;EAE9B,OAAO;CACT;;;;;;;;;CAUA,OAAO,SAAS,SAAqB,MAAmB,CAAC,GAAe;EACtE,MAAM,QAAQ,KAAK,UAAU,GAAG;EAChC,MAAM,MAAM,MAAM,QAAQ,OAAO;EACjC,IAAI,MAAM,GAAG,OAAO;EACpB,KAAK,IAAI,IAAI,MAAM,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;GAC9C,MAAM,OAAO,MAAM;GACnB,IAAI,KAAK,cAAc,MAAM,GAAG,GAAG,OAAO;EAC5C;EACA,OAAO;CACT;;;;;;;CAQA,OAAO,WAAW,QAAqC;EACrD,OAAO,KAAK,kBAAkB,SAAS,MAAM;CAC/C;;;;;;CAOA,OAAO,iBAAiB,QAAyD;EAC/E,QAAQ,QAAR;GACE,KAAK,YACH,OAAO;GACT,KAAK,YACH,OAAO;GACT,KAAK,mBACH,OAAO;GACT,SACE,OAAO;EACX;CACF;AACF"}
@@ -0,0 +1,19 @@
1
+ import { DEFAULT_DOCUMENT_TUNING, DocRect, DocumentAnalyzer, DocumentSample, DocumentTuning, analyzeDocumentGray, createDefaultDocumentAnalyzer, documentGuidance, isDocumentReady } from "./document.mjs";
2
+ import { ChallengeDetector, DEFAULT_TUNING, FaceAnalyzer, FaceSample, FaceTuning, createDefaultFaceAnalyzer, isSelfieReady, makeChallengeDetector } from "./face.mjs";
3
+ import { ArkycClient, ArkycClientOptions, ClientRealtime, ClientSession, ProviderSignalHints, WidgetApiError } from "./client.mjs";
4
+ import { WidgetRealtimeClient, WidgetRealtimeFactory, WidgetRealtimeHandler, createWidgetRealtimeClient } from "./realtime.mjs";
5
+ import { BaseWidgetOptions, MountWidgetOptions, WidgetControllerConfig, WidgetEvent, WidgetEventListener, WidgetHandle } from "./types.mjs";
6
+ import { WidgetController } from "./controller.mjs";
7
+ import { Theme } from "./theme.mjs";
8
+ import { Camera, Facing } from "./capture.mjs";
9
+ import { Flow, FlowContext } from "./flow.mjs";
10
+ import { ArkycWidget } from "./ArkycWidget.mjs";
11
+ import { WidgetHandler } from "./WidgetHandler.mjs";
12
+ import { WidgetResult } from "@arkyc/types";
13
+
14
+ //#region src/index.d.ts
15
+ declare const PACKAGE_NAME = "@arkyc/widget";
16
+ declare const VERSION = "0.1.0";
17
+ //#endregion
18
+ export { ArkycClient, type ArkycClientOptions, ArkycWidget, BaseWidgetOptions, Camera, type ChallengeDetector, type ClientRealtime, type ClientSession, DEFAULT_DOCUMENT_TUNING, DEFAULT_TUNING, type DocRect, type DocumentAnalyzer, type DocumentSample, type DocumentTuning, type FaceAnalyzer, type FaceSample, type FaceTuning, type Facing, Flow, type FlowContext, MountWidgetOptions, PACKAGE_NAME, type ProviderSignalHints, Theme, VERSION, WidgetApiError, type WidgetController, WidgetControllerConfig, WidgetEvent, WidgetEventListener, WidgetHandle, WidgetHandler, type WidgetRealtimeClient, type WidgetRealtimeFactory, type WidgetRealtimeHandler, type WidgetResult, analyzeDocumentGray, createDefaultDocumentAnalyzer, createDefaultFaceAnalyzer, createWidgetRealtimeClient, documentGuidance, isDocumentReady, isSelfieReady, makeChallengeDetector };
19
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;;;;cAAa,YAAA;AAAA,cACA,OAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,16 @@
1
+ import { ArkycClient, WidgetApiError } from "./client.mjs";
2
+ import { createWidgetRealtimeClient } from "./realtime.mjs";
3
+ import { Theme } from "./theme.mjs";
4
+ import { Camera } from "./capture.mjs";
5
+ import { Flow } from "./flow.mjs";
6
+ import { DEFAULT_TUNING, createDefaultFaceAnalyzer, isSelfieReady, makeChallengeDetector } from "./face.mjs";
7
+ import { DEFAULT_DOCUMENT_TUNING, analyzeDocumentGray, createDefaultDocumentAnalyzer, documentGuidance, isDocumentReady } from "./document.mjs";
8
+ import { WidgetHandler } from "./WidgetHandler.mjs";
9
+ import { ArkycWidget } from "./ArkycWidget.mjs";
10
+ //#region src/index.ts
11
+ const PACKAGE_NAME = "@arkyc/widget";
12
+ const VERSION = "0.1.0";
13
+ //#endregion
14
+ export { ArkycClient, ArkycWidget, Camera, DEFAULT_DOCUMENT_TUNING, DEFAULT_TUNING, Flow, PACKAGE_NAME, Theme, VERSION, WidgetApiError, WidgetHandler, analyzeDocumentGray, createDefaultDocumentAnalyzer, createDefaultFaceAnalyzer, createWidgetRealtimeClient, documentGuidance, isDocumentReady, isSelfieReady, makeChallengeDetector };
15
+
16
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["export const PACKAGE_NAME = '@arkyc/widget'\nexport const VERSION = '0.1.0'\n\nexport type { WidgetController } from './controller'\nexport { ArkycClient, WidgetApiError } from './client'\nexport type { ClientSession, ClientRealtime, ProviderSignalHints, ArkycClientOptions } from './client'\nexport { createWidgetRealtimeClient } from './realtime'\nexport type { WidgetRealtimeClient, WidgetRealtimeFactory, WidgetRealtimeHandler } from './realtime'\nexport { Theme } from './theme'\nexport { Camera } from './capture'\nexport type { Facing } from './capture'\nexport { Flow } from './flow'\nexport type { FlowContext } from './flow'\nexport { createDefaultFaceAnalyzer, DEFAULT_TUNING, makeChallengeDetector, isSelfieReady } from './face'\nexport type { FaceAnalyzer, FaceSample, FaceTuning, ChallengeDetector } from './face'\nexport {\n createDefaultDocumentAnalyzer,\n DEFAULT_DOCUMENT_TUNING,\n analyzeDocumentGray,\n documentGuidance,\n isDocumentReady,\n} from './document'\nexport type { DocumentAnalyzer, DocumentSample, DocumentTuning, DocRect } from './document'\nexport type { WidgetResult } from '@arkyc/types'\nexport { ArkycWidget } from './ArkycWidget'\nexport { WidgetHandler } from './WidgetHandler'\nexport * from './types'\n"],"mappings":";;;;;;;;;;AAAA,MAAa,eAAe;AAC5B,MAAa,UAAU"}
package/dist/qr.mjs ADDED
@@ -0,0 +1,22 @@
1
+ import qrcode from "qrcode-generator";
2
+ //#region src/qr.ts
3
+ /**
4
+ * Encode `text` as a QR code and return a self-contained, scalable SVG string
5
+ * (black modules on white) suitable for injecting via `innerHTML`. Type number 0
6
+ * auto-sizes to the data; error-correction level "M" tolerates camera noise while
7
+ * keeping the code dense enough to scan from a screen.
8
+ */
9
+ function renderQrSvg(text) {
10
+ const qr = qrcode(0, "M");
11
+ qr.addData(text);
12
+ qr.make();
13
+ return qr.createSvgTag({
14
+ cellSize: 6,
15
+ margin: 4,
16
+ scalable: true
17
+ });
18
+ }
19
+ //#endregion
20
+ export { renderQrSvg };
21
+
22
+ //# sourceMappingURL=qr.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr.mjs","names":[],"sources":["../src/qr.ts"],"sourcesContent":["import qrcode from 'qrcode-generator'\n\n/**\n * Encode `text` as a QR code and return a self-contained, scalable SVG string\n * (black modules on white) suitable for injecting via `innerHTML`. Type number 0\n * auto-sizes to the data; error-correction level \"M\" tolerates camera noise while\n * keeping the code dense enough to scan from a screen.\n */\nexport function renderQrSvg(text: string): string {\n const qr = qrcode(0, 'M')\n qr.addData(text)\n qr.make()\n return qr.createSvgTag({ cellSize: 6, margin: 4, scalable: true })\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,YAAY,MAAsB;CAChD,MAAM,KAAK,OAAO,GAAG,GAAG;CACxB,GAAG,QAAQ,IAAI;CACf,GAAG,KAAK;CACR,OAAO,GAAG,aAAa;EAAE,UAAU;EAAG,QAAQ;EAAG,UAAU;CAAK,CAAC;AACnE"}
@@ -0,0 +1,29 @@
1
+ import { ClientRealtime } from "./client.mjs";
2
+
3
+ //#region src/realtime.d.ts
4
+ /** Fires for every event received on a subscribed channel. */
5
+ type WidgetRealtimeHandler = (event: string, data: unknown) => void;
6
+ /** A connected push transport. `subscribe` returns an unsubscribe function. */
7
+ interface WidgetRealtimeClient {
8
+ subscribe(channel: string, handler: WidgetRealtimeHandler): () => void;
9
+ disconnect(): void;
10
+ }
11
+ /** A factory that builds a push client from the session's realtime config. */
12
+ type WidgetRealtimeFactory = (config: ClientRealtime, options: WidgetRealtimeOptions) => Promise<WidgetRealtimeClient | null>;
13
+ interface WidgetRealtimeOptions {
14
+ /** The client-token channel-auth endpoint (pusher). */
15
+ authEndpoint: string;
16
+ /** The session's client token (sent as `X-Client-Token` to the authorizer). */
17
+ token: string;
18
+ }
19
+ /**
20
+ * Connect to whichever push transport the API reported for this session. Returns
21
+ * null for `polling`/`off`/`memory` (no push — the widget polls instead) and also
22
+ * when the transport SDK can't be loaded (e.g. a plain `<script>` standalone embed
23
+ * with no bundler), so realtime degrades gracefully to polling. The SDK is
24
+ * dynamically imported so only the active transport is ever pulled in.
25
+ */
26
+ declare function createWidgetRealtimeClient(config: ClientRealtime, options: WidgetRealtimeOptions): Promise<WidgetRealtimeClient | null>;
27
+ //#endregion
28
+ export { WidgetRealtimeClient, WidgetRealtimeFactory, WidgetRealtimeHandler, createWidgetRealtimeClient };
29
+ //# sourceMappingURL=realtime.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime.d.mts","names":[],"sources":["../src/realtime.ts"],"mappings":";;;;KAGY,qBAAA,IAAyB,KAAA,UAAe,IAAa;AAAjE;AAAA,UAGiB,oBAAA;EACf,SAAA,CAAU,OAAA,UAAiB,OAAA,EAAS,qBAAqB;EACzD,UAAA;AAAA;AAFF;AAAA,KAMY,qBAAA,IACV,MAAA,EAAQ,cAAA,EACR,OAAA,EAAS,qBAAA,KACN,OAAA,CAAQ,oBAAA;AAAA,UAEI,qBAAA;EAV0C;EAYzD,YAAA;EAZU;EAcV,KAAK;AAAA;;;AAbK;AAIZ;;;;iBAuCsB,0BAAA,CACpB,MAAA,EAAQ,cAAA,EACR,OAAA,EAAS,qBAAA,GACR,OAAA,CAAQ,oBAAA"}