@arkyc/widget 1.2.7 → 1.2.8

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/dist/client.d.mts CHANGED
@@ -142,6 +142,14 @@ declare class ArkycClient {
142
142
  private readonly baseUrl;
143
143
  private readonly fetchImpl;
144
144
  constructor(options: ArkycClientOptions);
145
+ /**
146
+ * The resolved Client API base (always origin-qualified: the absolute/proxy
147
+ * value, or the hosted default when `baseUrl` was omitted). Callers that need
148
+ * a sibling endpoint — e.g. the realtime auth URL — must build it from this,
149
+ * not the raw option, so a zero-config widget doesn't fall back to the page
150
+ * origin.
151
+ */
152
+ get base(): string;
145
153
  /**
146
154
  * Load the current session (marks it `started` on first call).
147
155
  *
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":";;;;UAYiB,aAAA;EACf,EAAA;EACA,MAAA,EAAQ,kBAAA;EACR,UAAA;EADQ;EAGR,aAAA,GAAgB,YAAA;EAEM;EAAtB,mBAAA,GAAsB,iBAAA;EAIX;EAFX,OAAA,GAAU,aAAA;EAMC;EAJX,QAAA,GAAW,cAAA;EAIc;EAFzB,QAAA,GAAW,eAAA;EAXX;EAaA,QAAA,GAAW,cAAA;AAAA;;;;;;;;UAUI,cAAA;EACf,SAAA;EAbW;EAeX,OAAA;EAbW;EAeX,KAAA;EAAA,CACC,KAAA;AAAA;;UAIc,aAAA;EACf,OAAA;EAVA;EAYA,aAAA;EARA;EAUA,GAAA;AAAA;AATc;AAIhB;;;AAJgB,UAgBC,mBAAA;EACf,aAAA;EACA,cAAA;EACA,OAAA;EACA,cAAA;EACA,eAAA;EACA,cAAA;EACA,eAAA;EACA,iBAAA;EACA,aAAA;EACA,cAAA;AAAA;;UAIe,YAAA;EATf;EAWA,KAAA;EACA,KAAA;EACA,IAAA;EACA,MAAA;EACA,UAAA;EAVc;EAYd,OAAA;EARe;EAUf,GAAA,GAAM,IAAA;;EAEN,QAAA;EACA,SAAA;EACA,OAAA,GAAU,mBAAmB;AAAA;;UAId,kBAAA;EACf,KAAA,GAAQ,IAAA;EACR,OAAA;EACA,YAAA,GAAe,YAAA;EACf,OAAA,GAAU,mBAAA;AAAA;;UAIK,iBAAA;EACf,KAAA,GAAQ,IAAA;EACR,OAAA;EACA,YAAA,GAAe,YAAY;AAAA;;UAIZ,aAAA;EACf,MAAA,GAAS,IAAA;EAZC;EAcV,KAAA,GAAQ,IAAA;EAdqB;EAgB7B,IAAA,GAAO,YAAA;EAnBC;EAqBR,UAAA,GAAa,iBAAA;EACb,OAAA,GAAU,mBAAA;AAAA;;UAIK,aAAA;EACf,OAAA,GAAU,mBAAmB;AAAA;AApB/B;;;AAAA,cA0Ba,cAAA,SAAuB,KAAA;EAAA,SACzB,MAAA;EA1BD;;;;EAAA,SA+BC,KAAA,GAAQ,WAAA;cAEL,OAAA,UAAiB,MAAA,UAAgB,KAAA,GAAQ,WAAA;AAAA;;UAStC,kBAAA;EAnCN;EAqCT,KAAA;EAjCO;;;;;;;;;EA2CP,OAAA;EA3CO;EA6CP,KAAA,UAAe,KAAK;AAAA;;;;;;cAkDT,WAAA;EAAA,iBACM,KAAA;EAAA,iBACA,OAAA;EAAA,iBACA,SAAA;cAEL,OAAA,EAAS,kBAAA;;;;;;EAcrB,UAAA,IAAc,OAAA,CAAQ,aAAA;EA5F0C;AASlE;;;;;EA6FE,mBAAA,CAAoB,KAAA,EAAO,kBAAA,GAAqB,OAAA,CAAQ,aAAA;EA/ExD;;;AAAoB;AAkDtB;;EA4CE,kBAAA,CAAmB,KAAA,EAAO,iBAAA,GAAoB,OAAA,CAAQ,aAAA;EAvCjC;;;;;;EAqDrB,cAAA,CAAe,KAAA,EAAO,aAAA,GAAgB,OAAA,CAAQ,aAAA;EAdQ;;;;;;;EA+BtD,aAAA,CAAc,KAAA,EAAO,YAAA,GAAe,OAAA,CAAQ,aAAA;EA0B5B;;;;;;EAAhB,QAAA,CAAS,KAAA,GAAO,aAAA,GAAqB,OAAA,CAAQ,aAAA;EAAA,QAI/B,OAAA;AAAA"}
1
+ {"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":";;;;UAYiB,aAAA;EACf,EAAA;EACA,MAAA,EAAQ,kBAAA;EACR,UAAA;EADQ;EAGR,aAAA,GAAgB,YAAA;EAEM;EAAtB,mBAAA,GAAsB,iBAAA;EAIX;EAFX,OAAA,GAAU,aAAA;EAMC;EAJX,QAAA,GAAW,cAAA;EAIc;EAFzB,QAAA,GAAW,eAAA;EAXX;EAaA,QAAA,GAAW,cAAA;AAAA;;;;;;;;UAUI,cAAA;EACf,SAAA;EAbW;EAeX,OAAA;EAbW;EAeX,KAAA;EAAA,CACC,KAAA;AAAA;;UAIc,aAAA;EACf,OAAA;EAVA;EAYA,aAAA;EARA;EAUA,GAAA;AAAA;AATc;AAIhB;;;AAJgB,UAgBC,mBAAA;EACf,aAAA;EACA,cAAA;EACA,OAAA;EACA,cAAA;EACA,eAAA;EACA,cAAA;EACA,eAAA;EACA,iBAAA;EACA,aAAA;EACA,cAAA;AAAA;;UAIe,YAAA;EATf;EAWA,KAAA;EACA,KAAA;EACA,IAAA;EACA,MAAA;EACA,UAAA;EAVc;EAYd,OAAA;EARe;EAUf,GAAA,GAAM,IAAA;;EAEN,QAAA;EACA,SAAA;EACA,OAAA,GAAU,mBAAmB;AAAA;;UAId,kBAAA;EACf,KAAA,GAAQ,IAAA;EACR,OAAA;EACA,YAAA,GAAe,YAAA;EACf,OAAA,GAAU,mBAAA;AAAA;;UAIK,iBAAA;EACf,KAAA,GAAQ,IAAA;EACR,OAAA;EACA,YAAA,GAAe,YAAY;AAAA;;UAIZ,aAAA;EACf,MAAA,GAAS,IAAA;EAZC;EAcV,KAAA,GAAQ,IAAA;EAdqB;EAgB7B,IAAA,GAAO,YAAA;EAnBC;EAqBR,UAAA,GAAa,iBAAA;EACb,OAAA,GAAU,mBAAA;AAAA;;UAIK,aAAA;EACf,OAAA,GAAU,mBAAmB;AAAA;AApB/B;;;AAAA,cA0Ba,cAAA,SAAuB,KAAA;EAAA,SACzB,MAAA;EA1BD;;;;EAAA,SA+BC,KAAA,GAAQ,WAAA;cAEL,OAAA,UAAiB,MAAA,UAAgB,KAAA,GAAQ,WAAA;AAAA;;UAStC,kBAAA;EAnCN;EAqCT,KAAA;EAjCO;;;;;;;;;EA2CP,OAAA;EA3CO;EA6CP,KAAA,UAAe,KAAK;AAAA;;;;;;cAkDT,WAAA;EAAA,iBACM,KAAA;EAAA,iBACA,OAAA;EAAA,iBACA,SAAA;cAEL,OAAA,EAAS,kBAAA;;;;;;;AA9E2C;MA8F5D,IAAA;EArF6B;;;;;EA8FjC,UAAA,IAAc,OAAA,CAAQ,aAAA;EAhFP;;AAAK;AAkDtB;;;EAwCE,mBAAA,CAAoB,KAAA,EAAO,kBAAA,GAAqB,OAAA,CAAQ,aAAA;EAVlC;;;;;;EAyBtB,kBAAA,CAAmB,KAAA,EAAO,iBAAA,GAAoB,OAAA,CAAQ,aAAA;EAAR;;;;;;EAc9C,cAAA,CAAe,KAAA,EAAO,aAAA,GAAgB,OAAA,CAAQ,aAAA;EA2C9B;;;;;;;EA1BhB,aAAA,CAAc,KAAA,EAAO,YAAA,GAAe,OAAA,CAAQ,aAAA;;;;;;;EA0B5C,QAAA,CAAS,KAAA,GAAO,aAAA,GAAqB,OAAA,CAAQ,aAAA;EAAA,QAI/B,OAAA;AAAA"}
package/dist/client.mjs CHANGED
@@ -57,6 +57,16 @@ var ArkycClient = class {
57
57
  this.fetchImpl = f.bind(globalThis);
58
58
  }
59
59
  /**
60
+ * The resolved Client API base (always origin-qualified: the absolute/proxy
61
+ * value, or the hosted default when `baseUrl` was omitted). Callers that need
62
+ * a sibling endpoint — e.g. the realtime auth URL — must build it from this,
63
+ * not the raw option, so a zero-config widget doesn't fall back to the page
64
+ * origin.
65
+ */
66
+ get base() {
67
+ return this.baseUrl;
68
+ }
69
+ /**
60
70
  * Load the current session (marks it `started` on first call).
61
71
  *
62
72
  * @returns
@@ -1 +1 @@
1
- {"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import type {\n ApiErrorKey,\n CaptureModel,\n DocumentType,\n LivenessChallenge,\n LivenessMode,\n ProjectBranding,\n VerificationStatus,\n WorkflowConfig,\n} from '@arkyc/types'\n\n/** The session view exposed by the Client/Widget API (`GET /v1/client/session`). */\nexport interface ClientSession {\n id: string\n status: VerificationStatus\n expires_at: string\n /** The capture flow this session runs (Phase 17). */\n capture_model?: CaptureModel\n /** The active-liveness challenge sequence to prompt the user through. */\n liveness_challenges?: LivenessChallenge[]\n /** Cross-device handoff config resolved from the project setting. */\n handoff?: ClientHandoff\n /** How to watch this session live (push transport params or `polling`). */\n realtime?: ClientRealtime\n /** Project branding (colours/logo/name) so the widget themes itself at runtime. */\n branding?: ProjectBranding | null\n /** The applied workflow (ordered, toggleable stages + skip_ocr), or null for the default flow. */\n workflow?: WorkflowConfig | null\n}\n\n/**\n * Realtime connection info for this session (Phase 16). `transport` selects how\n * the widget watches the session: `pusher`/`firebase` subscribe to `channel`;\n * `polling` (and `off`/`memory`) mean poll the session endpoint instead. The\n * remaining fields carry the active transport's public connection params; `token`\n * is a per-session Firebase custom token when applicable.\n */\nexport interface ClientRealtime {\n transport: 'pusher' | 'firebase' | 'polling' | 'off' | 'memory'\n /** The private channel this session's events publish to. */\n channel: string\n /** Per-session Firebase custom token (firebase transport only). */\n token?: string | null\n [param: string]: unknown\n}\n\n/** Whether (and where) the widget may offer cross-device handoff. */\nexport interface ClientHandoff {\n enabled: boolean\n /** Whether a desktop user may continue on the desktop device instead of handing off. */\n allow_desktop: boolean\n /** First-party hosted page the QR points to (the phone resumes the session there). */\n url: string\n}\n\n/**\n * Mock-driver signal hints. Real provider drivers ignore these; the `mock`\n * drivers use them to make the demo/playground flow deterministic.\n */\nexport interface ProviderSignalHints {\n quality_score?: number\n ocr_confidence?: number\n expired?: boolean\n liveness_score?: number\n liveness_passed?: boolean\n multiple_faces?: boolean\n face_similarity?: number\n face_match_passed?: boolean\n address_score?: number\n address_passed?: boolean\n}\n\n/** Input for the address-verification submission (opt-in stage). */\nexport interface AddressInput {\n /** The claimed address the user entered. */\n line1?: string | null\n line2?: string | null\n city?: string | null\n region?: string | null\n postalCode?: string | null\n /** ISO 3166-1 alpha-2 country code. */\n country?: string | null\n /** Proof-of-address document image. */\n poa?: Blob | null\n /** Captured device coordinates. */\n latitude?: number | null\n longitude?: number | null\n signals?: ProviderSignalHints\n}\n\n/** Input for the document-front submission. */\nexport interface DocumentFrontInput {\n image?: Blob | null\n country?: string | null\n documentType?: DocumentType | null\n signals?: ProviderSignalHints\n}\n\n/** Input for the document-back submission. */\nexport interface DocumentBackInput {\n image?: Blob | null\n country?: string | null\n documentType?: DocumentType | null\n}\n\n/** Input for the liveness submission (passive selfie or active challenge video). */\nexport interface LivenessInput {\n selfie?: Blob | null\n /** Recorded challenge video (active mode). */\n video?: Blob | null\n /** Passive (selfie) or active (challenge video). Defaults to passive. */\n mode?: LivenessMode\n /** The challenge sequence the user performed, in order (active mode). */\n challenges?: LivenessChallenge[]\n signals?: ProviderSignalHints\n}\n\n/** Input for completing the session (runs face-match + decision). */\nexport interface CompleteInput {\n signals?: ProviderSignalHints\n}\n\n/**\n * Raised when the Client API returns a non-2xx response.\n */\nexport class WidgetApiError extends Error {\n readonly status: number\n /**\n * Stable error key (e.g. `session_expired`) for errors Arkyc\n * deliberately raises. Undefined for unexpected errors.\n */\n readonly error?: ApiErrorKey\n\n constructor(message: string, status: number, error?: ApiErrorKey) {\n super(message)\n this.name = 'WidgetApiError'\n this.status = status\n this.error = error\n }\n}\n\n/** Options for {@link ArkycClient}. */\nexport interface ArkycClientOptions {\n /** Short-lived client token minted for this session. */\n token: string\n /**\n * The Client API **base** the widget appends each endpoint to — `{baseUrl}/session`,\n * `{baseUrl}/document/front`, etc. (no version prefix is added; `baseUrl` owns it).\n *\n * **Optional** — defaults to the hosted Arkyc API ({@link DEFAULT_CLIENT_BASE_URL}),\n * so the hosted product needs no config. Override only to self-host or proxy: a\n * relative path resolves against the current origin (same-origin, no CORS); an\n * absolute URL is used as-is (the API must allow your origin via CORS).\n */\n baseUrl?: string\n /** Injectable for testing; defaults to the global `fetch`. */\n fetch?: typeof fetch\n}\n\n/**\n * The default Client API base, used when no `baseUrl` is passed — so the hosted\n * product is zero-config: the browser calls it cross-origin with the client\n * token (the API allows it via CORS).\n *\n * Baked at **build time** from the `ARKYC_API_URL` env (via the tsdown `define`),\n * falling back to the hosted Arkyc API. Self-hosters set `ARKYC_API_URL` when\n * building the widget; integrators on the published package override per call\n * with `baseUrl`. Referenced through `typeof` so running the source directly\n * (tests) without the define is safe.\n */\ndeclare const __ARKYC_API_BASE__: string | undefined\nexport const DEFAULT_CLIENT_BASE_URL =\n (typeof __ARKYC_API_BASE__ === 'string' ? __ARKYC_API_BASE__ : '') || 'https://api.arkyc.toneflix.net/api/v1/client'\n\n/**\n * Resolve the request base: an explicit `baseUrl` overrides the build-time\n * {@link DEFAULT_CLIENT_BASE_URL}. The chosen value is used as-is when absolute\n * (cross-origin, CORS); a relative path resolves against the current origin\n * (same-origin proxy, no CORS).\n *\n * @param baseUrl\n * @returns\n */\nexport function resolveClientBaseUrl(baseUrl?: string): string {\n const raw = (baseUrl ?? '').trim().replace(/\\/$/, '')\n if (!raw) return DEFAULT_CLIENT_BASE_URL\n if (/^https?:\\/\\//i.test(raw)) return raw\n const origin = (globalThis.location?.origin ?? '').replace(/\\/$/, '')\n return `${origin}${raw.startsWith('/') ? '' : '/'}${raw}`\n}\n\ntype EnvelopeNumber = number | boolean\n\nfunction appendSignals(form: FormData, signals?: ProviderSignalHints): void {\n if (!signals) return\n for (const [key, value] of Object.entries(signals)) {\n if (value == null) continue\n form.append(key, String(value as EnvelopeNumber))\n }\n}\n\n/**\n * A thin, framework-agnostic client for the Arkyc Client/Widget API. Drives a\n * single verification session with a short-lived client token. All responses\n * are unwrapped from the standard `{ status, message, data }` envelope.\n */\nexport class ArkycClient {\n private readonly token: string\n private readonly baseUrl: string\n private readonly fetchImpl: typeof fetch\n\n constructor(options: ArkycClientOptions) {\n if (!options.token) throw new Error('ArkycClient requires a client `token`.')\n this.token = options.token\n this.baseUrl = resolveClientBaseUrl(options.baseUrl)\n const f = options.fetch ?? globalThis.fetch\n if (!f) throw new Error('No `fetch` implementation available.')\n this.fetchImpl = f.bind(globalThis)\n }\n\n /**\n * Load the current session (marks it `started` on first call).\n *\n * @returns\n */\n getSession(): Promise<ClientSession> {\n return this.request('GET', '/session')\n }\n\n /**\n * Submit the document front image (triggers OCR + portrait extraction).\n *\n * @param input\n * @returns\n */\n submitDocumentFront(input: DocumentFrontInput): Promise<ClientSession> {\n const form = new FormData()\n if (input.image) form.append('image', input.image, 'document-front.jpg')\n if (input.country) form.append('country', input.country)\n if (input.documentType) form.append('document_type', input.documentType)\n appendSignals(form, input.signals)\n return this.request('POST', '/document/front', form)\n }\n\n /**\n * Submit the document back image.\n *\n * @param input\n * @returns\n */\n submitDocumentBack(input: DocumentBackInput): Promise<ClientSession> {\n const form = new FormData()\n if (input.image) form.append('image', input.image, 'document-back.jpg')\n if (input.country) form.append('country', input.country)\n if (input.documentType) form.append('document_type', input.documentType)\n return this.request('POST', '/document/back', form)\n }\n\n /**\n * Submit the selfie for the liveness check.\n *\n * @param input\n * @returns\n */\n submitLiveness(input: LivenessInput): Promise<ClientSession> {\n const form = new FormData()\n if (input.selfie) form.append('selfie', input.selfie, 'selfie.jpg')\n if (input.video) form.append('video', input.video, 'liveness.webm')\n if (input.mode) form.append('mode', input.mode)\n if (input.challenges) form.append('challenges', JSON.stringify(input.challenges))\n appendSignals(form, input.signals)\n return this.request('POST', '/liveness', form)\n }\n\n /**\n * Submit address verification (opt-in stage). Sends the claimed address plus\n * whichever evidence was gathered (proof-of-address image, device coordinates).\n *\n * @param input\n * @returns\n */\n submitAddress(input: AddressInput): Promise<ClientSession> {\n const form = new FormData()\n const fields: Record<string, string | null | undefined> = {\n line1: input.line1,\n line2: input.line2,\n city: input.city,\n region: input.region,\n postal_code: input.postalCode,\n country: input.country,\n }\n for (const [key, value] of Object.entries(fields)) {\n if (value) form.append(key, value)\n }\n if (input.poa) form.append('poa', input.poa, 'proof-of-address.jpg')\n if (input.latitude != null) form.append('latitude', String(input.latitude))\n if (input.longitude != null) form.append('longitude', String(input.longitude))\n appendSignals(form, input.signals)\n return this.request('POST', '/address', form)\n }\n\n /**\n * Finalise the session (runs face-match + the decision engine).\n *\n * @param input\n * @returns\n */\n complete(input: CompleteInput = {}): Promise<ClientSession> {\n return this.request('POST', '/complete', input.signals ?? {})\n }\n\n private async request(method: string, path: string, body?: FormData | object): Promise<ClientSession> {\n const headers: Record<string, string> = { 'X-Client-Token': this.token }\n let payload: BodyInit | undefined\n\n if (body instanceof FormData) {\n payload = body\n } else if (body) {\n headers['Content-Type'] = 'application/json'\n payload = JSON.stringify(body)\n }\n\n const res = await this.fetchImpl(`${this.baseUrl}${path}`, { method, headers, body: payload })\n const text = await res.text()\n const json = text ? (JSON.parse(text) as { message?: string; data?: ClientSession; error?: ApiErrorKey }) : {}\n\n if (!res.ok) {\n throw new WidgetApiError(json.message ?? `Request failed (${res.status})`, res.status, json.error)\n }\n return json.data as ClientSession\n }\n}\n"],"mappings":";;;;AA6HA,IAAa,iBAAb,cAAoC,MAAM;CACxC;;;;;CAKA;CAEA,YAAY,SAAiB,QAAgB,OAAqB;EAChE,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,SAAS;EACd,KAAK,QAAQ;CACf;AACF;AAgCA,MAAa,0BAC2D;;;;;;;;;;AAWxE,SAAgB,qBAAqB,SAA0B;CAC7D,MAAM,OAAO,WAAW,GAAA,CAAI,KAAK,CAAC,CAAC,QAAQ,OAAO,EAAE;CACpD,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI,gBAAgB,KAAK,GAAG,GAAG,OAAO;CAEtC,OAAO,IADS,WAAW,UAAU,UAAU,GAAA,CAAI,QAAQ,OAAO,EACnD,IAAI,IAAI,WAAW,GAAG,IAAI,KAAK,MAAM;AACtD;AAIA,SAAS,cAAc,MAAgB,SAAqC;CAC1E,IAAI,CAAC,SAAS;CACd,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAAG;EAClD,IAAI,SAAS,MAAM;EACnB,KAAK,OAAO,KAAK,OAAO,KAAuB,CAAC;CAClD;AACF;;;;;;AAOA,IAAa,cAAb,MAAyB;CACvB;CACA;CACA;CAEA,YAAY,SAA6B;EACvC,IAAI,CAAC,QAAQ,OAAO,MAAM,IAAI,MAAM,wCAAwC;EAC5E,KAAK,QAAQ,QAAQ;EACrB,KAAK,UAAU,qBAAqB,QAAQ,OAAO;EACnD,MAAM,IAAI,QAAQ,SAAS,WAAW;EACtC,IAAI,CAAC,GAAG,MAAM,IAAI,MAAM,sCAAsC;EAC9D,KAAK,YAAY,EAAE,KAAK,UAAU;CACpC;;;;;;CAOA,aAAqC;EACnC,OAAO,KAAK,QAAQ,OAAO,UAAU;CACvC;;;;;;;CAQA,oBAAoB,OAAmD;EACrE,MAAM,OAAO,IAAI,SAAS;EAC1B,IAAI,MAAM,OAAO,KAAK,OAAO,SAAS,MAAM,OAAO,oBAAoB;EACvE,IAAI,MAAM,SAAS,KAAK,OAAO,WAAW,MAAM,OAAO;EACvD,IAAI,MAAM,cAAc,KAAK,OAAO,iBAAiB,MAAM,YAAY;EACvE,cAAc,MAAM,MAAM,OAAO;EACjC,OAAO,KAAK,QAAQ,QAAQ,mBAAmB,IAAI;CACrD;;;;;;;CAQA,mBAAmB,OAAkD;EACnE,MAAM,OAAO,IAAI,SAAS;EAC1B,IAAI,MAAM,OAAO,KAAK,OAAO,SAAS,MAAM,OAAO,mBAAmB;EACtE,IAAI,MAAM,SAAS,KAAK,OAAO,WAAW,MAAM,OAAO;EACvD,IAAI,MAAM,cAAc,KAAK,OAAO,iBAAiB,MAAM,YAAY;EACvE,OAAO,KAAK,QAAQ,QAAQ,kBAAkB,IAAI;CACpD;;;;;;;CAQA,eAAe,OAA8C;EAC3D,MAAM,OAAO,IAAI,SAAS;EAC1B,IAAI,MAAM,QAAQ,KAAK,OAAO,UAAU,MAAM,QAAQ,YAAY;EAClE,IAAI,MAAM,OAAO,KAAK,OAAO,SAAS,MAAM,OAAO,eAAe;EAClE,IAAI,MAAM,MAAM,KAAK,OAAO,QAAQ,MAAM,IAAI;EAC9C,IAAI,MAAM,YAAY,KAAK,OAAO,cAAc,KAAK,UAAU,MAAM,UAAU,CAAC;EAChF,cAAc,MAAM,MAAM,OAAO;EACjC,OAAO,KAAK,QAAQ,QAAQ,aAAa,IAAI;CAC/C;;;;;;;;CASA,cAAc,OAA6C;EACzD,MAAM,OAAO,IAAI,SAAS;EAC1B,MAAM,SAAoD;GACxD,OAAO,MAAM;GACb,OAAO,MAAM;GACb,MAAM,MAAM;GACZ,QAAQ,MAAM;GACd,aAAa,MAAM;GACnB,SAAS,MAAM;EACjB;EACA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAC9C,IAAI,OAAO,KAAK,OAAO,KAAK,KAAK;EAEnC,IAAI,MAAM,KAAK,KAAK,OAAO,OAAO,MAAM,KAAK,sBAAsB;EACnE,IAAI,MAAM,YAAY,MAAM,KAAK,OAAO,YAAY,OAAO,MAAM,QAAQ,CAAC;EAC1E,IAAI,MAAM,aAAa,MAAM,KAAK,OAAO,aAAa,OAAO,MAAM,SAAS,CAAC;EAC7E,cAAc,MAAM,MAAM,OAAO;EACjC,OAAO,KAAK,QAAQ,QAAQ,YAAY,IAAI;CAC9C;;;;;;;CAQA,SAAS,QAAuB,CAAC,GAA2B;EAC1D,OAAO,KAAK,QAAQ,QAAQ,aAAa,MAAM,WAAW,CAAC,CAAC;CAC9D;CAEA,MAAc,QAAQ,QAAgB,MAAc,MAAkD;EACpG,MAAM,UAAkC,EAAE,kBAAkB,KAAK,MAAM;EACvE,IAAI;EAEJ,IAAI,gBAAgB,UAClB,UAAU;OACL,IAAI,MAAM;GACf,QAAQ,kBAAkB;GAC1B,UAAU,KAAK,UAAU,IAAI;EAC/B;EAEA,MAAM,MAAM,MAAM,KAAK,UAAU,GAAG,KAAK,UAAU,QAAQ;GAAE;GAAQ;GAAS,MAAM;EAAQ,CAAC;EAC7F,MAAM,OAAO,MAAM,IAAI,KAAK;EAC5B,MAAM,OAAO,OAAQ,KAAK,MAAM,IAAI,IAAwE,CAAC;EAE7G,IAAI,CAAC,IAAI,IACP,MAAM,IAAI,eAAe,KAAK,WAAW,mBAAmB,IAAI,OAAO,IAAI,IAAI,QAAQ,KAAK,KAAK;EAEnG,OAAO,KAAK;CACd;AACF"}
1
+ {"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import type {\n ApiErrorKey,\n CaptureModel,\n DocumentType,\n LivenessChallenge,\n LivenessMode,\n ProjectBranding,\n VerificationStatus,\n WorkflowConfig,\n} from '@arkyc/types'\n\n/** The session view exposed by the Client/Widget API (`GET /v1/client/session`). */\nexport interface ClientSession {\n id: string\n status: VerificationStatus\n expires_at: string\n /** The capture flow this session runs (Phase 17). */\n capture_model?: CaptureModel\n /** The active-liveness challenge sequence to prompt the user through. */\n liveness_challenges?: LivenessChallenge[]\n /** Cross-device handoff config resolved from the project setting. */\n handoff?: ClientHandoff\n /** How to watch this session live (push transport params or `polling`). */\n realtime?: ClientRealtime\n /** Project branding (colours/logo/name) so the widget themes itself at runtime. */\n branding?: ProjectBranding | null\n /** The applied workflow (ordered, toggleable stages + skip_ocr), or null for the default flow. */\n workflow?: WorkflowConfig | null\n}\n\n/**\n * Realtime connection info for this session (Phase 16). `transport` selects how\n * the widget watches the session: `pusher`/`firebase` subscribe to `channel`;\n * `polling` (and `off`/`memory`) mean poll the session endpoint instead. The\n * remaining fields carry the active transport's public connection params; `token`\n * is a per-session Firebase custom token when applicable.\n */\nexport interface ClientRealtime {\n transport: 'pusher' | 'firebase' | 'polling' | 'off' | 'memory'\n /** The private channel this session's events publish to. */\n channel: string\n /** Per-session Firebase custom token (firebase transport only). */\n token?: string | null\n [param: string]: unknown\n}\n\n/** Whether (and where) the widget may offer cross-device handoff. */\nexport interface ClientHandoff {\n enabled: boolean\n /** Whether a desktop user may continue on the desktop device instead of handing off. */\n allow_desktop: boolean\n /** First-party hosted page the QR points to (the phone resumes the session there). */\n url: string\n}\n\n/**\n * Mock-driver signal hints. Real provider drivers ignore these; the `mock`\n * drivers use them to make the demo/playground flow deterministic.\n */\nexport interface ProviderSignalHints {\n quality_score?: number\n ocr_confidence?: number\n expired?: boolean\n liveness_score?: number\n liveness_passed?: boolean\n multiple_faces?: boolean\n face_similarity?: number\n face_match_passed?: boolean\n address_score?: number\n address_passed?: boolean\n}\n\n/** Input for the address-verification submission (opt-in stage). */\nexport interface AddressInput {\n /** The claimed address the user entered. */\n line1?: string | null\n line2?: string | null\n city?: string | null\n region?: string | null\n postalCode?: string | null\n /** ISO 3166-1 alpha-2 country code. */\n country?: string | null\n /** Proof-of-address document image. */\n poa?: Blob | null\n /** Captured device coordinates. */\n latitude?: number | null\n longitude?: number | null\n signals?: ProviderSignalHints\n}\n\n/** Input for the document-front submission. */\nexport interface DocumentFrontInput {\n image?: Blob | null\n country?: string | null\n documentType?: DocumentType | null\n signals?: ProviderSignalHints\n}\n\n/** Input for the document-back submission. */\nexport interface DocumentBackInput {\n image?: Blob | null\n country?: string | null\n documentType?: DocumentType | null\n}\n\n/** Input for the liveness submission (passive selfie or active challenge video). */\nexport interface LivenessInput {\n selfie?: Blob | null\n /** Recorded challenge video (active mode). */\n video?: Blob | null\n /** Passive (selfie) or active (challenge video). Defaults to passive. */\n mode?: LivenessMode\n /** The challenge sequence the user performed, in order (active mode). */\n challenges?: LivenessChallenge[]\n signals?: ProviderSignalHints\n}\n\n/** Input for completing the session (runs face-match + decision). */\nexport interface CompleteInput {\n signals?: ProviderSignalHints\n}\n\n/**\n * Raised when the Client API returns a non-2xx response.\n */\nexport class WidgetApiError extends Error {\n readonly status: number\n /**\n * Stable error key (e.g. `session_expired`) for errors Arkyc\n * deliberately raises. Undefined for unexpected errors.\n */\n readonly error?: ApiErrorKey\n\n constructor(message: string, status: number, error?: ApiErrorKey) {\n super(message)\n this.name = 'WidgetApiError'\n this.status = status\n this.error = error\n }\n}\n\n/** Options for {@link ArkycClient}. */\nexport interface ArkycClientOptions {\n /** Short-lived client token minted for this session. */\n token: string\n /**\n * The Client API **base** the widget appends each endpoint to — `{baseUrl}/session`,\n * `{baseUrl}/document/front`, etc. (no version prefix is added; `baseUrl` owns it).\n *\n * **Optional** — defaults to the hosted Arkyc API ({@link DEFAULT_CLIENT_BASE_URL}),\n * so the hosted product needs no config. Override only to self-host or proxy: a\n * relative path resolves against the current origin (same-origin, no CORS); an\n * absolute URL is used as-is (the API must allow your origin via CORS).\n */\n baseUrl?: string\n /** Injectable for testing; defaults to the global `fetch`. */\n fetch?: typeof fetch\n}\n\n/**\n * The default Client API base, used when no `baseUrl` is passed — so the hosted\n * product is zero-config: the browser calls it cross-origin with the client\n * token (the API allows it via CORS).\n *\n * Baked at **build time** from the `ARKYC_API_URL` env (via the tsdown `define`),\n * falling back to the hosted Arkyc API. Self-hosters set `ARKYC_API_URL` when\n * building the widget; integrators on the published package override per call\n * with `baseUrl`. Referenced through `typeof` so running the source directly\n * (tests) without the define is safe.\n */\ndeclare const __ARKYC_API_BASE__: string | undefined\nexport const DEFAULT_CLIENT_BASE_URL =\n (typeof __ARKYC_API_BASE__ === 'string' ? __ARKYC_API_BASE__ : '') || 'https://api.arkyc.toneflix.net/api/v1/client'\n\n/**\n * Resolve the request base: an explicit `baseUrl` overrides the build-time\n * {@link DEFAULT_CLIENT_BASE_URL}. The chosen value is used as-is when absolute\n * (cross-origin, CORS); a relative path resolves against the current origin\n * (same-origin proxy, no CORS).\n *\n * @param baseUrl\n * @returns\n */\nexport function resolveClientBaseUrl(baseUrl?: string): string {\n const raw = (baseUrl ?? '').trim().replace(/\\/$/, '')\n if (!raw) return DEFAULT_CLIENT_BASE_URL\n if (/^https?:\\/\\//i.test(raw)) return raw\n const origin = (globalThis.location?.origin ?? '').replace(/\\/$/, '')\n return `${origin}${raw.startsWith('/') ? '' : '/'}${raw}`\n}\n\ntype EnvelopeNumber = number | boolean\n\nfunction appendSignals(form: FormData, signals?: ProviderSignalHints): void {\n if (!signals) return\n for (const [key, value] of Object.entries(signals)) {\n if (value == null) continue\n form.append(key, String(value as EnvelopeNumber))\n }\n}\n\n/**\n * A thin, framework-agnostic client for the Arkyc Client/Widget API. Drives a\n * single verification session with a short-lived client token. All responses\n * are unwrapped from the standard `{ status, message, data }` envelope.\n */\nexport class ArkycClient {\n private readonly token: string\n private readonly baseUrl: string\n private readonly fetchImpl: typeof fetch\n\n constructor(options: ArkycClientOptions) {\n if (!options.token) throw new Error('ArkycClient requires a client `token`.')\n this.token = options.token\n this.baseUrl = resolveClientBaseUrl(options.baseUrl)\n const f = options.fetch ?? globalThis.fetch\n if (!f) throw new Error('No `fetch` implementation available.')\n this.fetchImpl = f.bind(globalThis)\n }\n\n /**\n * The resolved Client API base (always origin-qualified: the absolute/proxy\n * value, or the hosted default when `baseUrl` was omitted). Callers that need\n * a sibling endpoint — e.g. the realtime auth URL — must build it from this,\n * not the raw option, so a zero-config widget doesn't fall back to the page\n * origin.\n */\n get base(): string {\n return this.baseUrl\n }\n\n /**\n * Load the current session (marks it `started` on first call).\n *\n * @returns\n */\n getSession(): Promise<ClientSession> {\n return this.request('GET', '/session')\n }\n\n /**\n * Submit the document front image (triggers OCR + portrait extraction).\n *\n * @param input\n * @returns\n */\n submitDocumentFront(input: DocumentFrontInput): Promise<ClientSession> {\n const form = new FormData()\n if (input.image) form.append('image', input.image, 'document-front.jpg')\n if (input.country) form.append('country', input.country)\n if (input.documentType) form.append('document_type', input.documentType)\n appendSignals(form, input.signals)\n return this.request('POST', '/document/front', form)\n }\n\n /**\n * Submit the document back image.\n *\n * @param input\n * @returns\n */\n submitDocumentBack(input: DocumentBackInput): Promise<ClientSession> {\n const form = new FormData()\n if (input.image) form.append('image', input.image, 'document-back.jpg')\n if (input.country) form.append('country', input.country)\n if (input.documentType) form.append('document_type', input.documentType)\n return this.request('POST', '/document/back', form)\n }\n\n /**\n * Submit the selfie for the liveness check.\n *\n * @param input\n * @returns\n */\n submitLiveness(input: LivenessInput): Promise<ClientSession> {\n const form = new FormData()\n if (input.selfie) form.append('selfie', input.selfie, 'selfie.jpg')\n if (input.video) form.append('video', input.video, 'liveness.webm')\n if (input.mode) form.append('mode', input.mode)\n if (input.challenges) form.append('challenges', JSON.stringify(input.challenges))\n appendSignals(form, input.signals)\n return this.request('POST', '/liveness', form)\n }\n\n /**\n * Submit address verification (opt-in stage). Sends the claimed address plus\n * whichever evidence was gathered (proof-of-address image, device coordinates).\n *\n * @param input\n * @returns\n */\n submitAddress(input: AddressInput): Promise<ClientSession> {\n const form = new FormData()\n const fields: Record<string, string | null | undefined> = {\n line1: input.line1,\n line2: input.line2,\n city: input.city,\n region: input.region,\n postal_code: input.postalCode,\n country: input.country,\n }\n for (const [key, value] of Object.entries(fields)) {\n if (value) form.append(key, value)\n }\n if (input.poa) form.append('poa', input.poa, 'proof-of-address.jpg')\n if (input.latitude != null) form.append('latitude', String(input.latitude))\n if (input.longitude != null) form.append('longitude', String(input.longitude))\n appendSignals(form, input.signals)\n return this.request('POST', '/address', form)\n }\n\n /**\n * Finalise the session (runs face-match + the decision engine).\n *\n * @param input\n * @returns\n */\n complete(input: CompleteInput = {}): Promise<ClientSession> {\n return this.request('POST', '/complete', input.signals ?? {})\n }\n\n private async request(method: string, path: string, body?: FormData | object): Promise<ClientSession> {\n const headers: Record<string, string> = { 'X-Client-Token': this.token }\n let payload: BodyInit | undefined\n\n if (body instanceof FormData) {\n payload = body\n } else if (body) {\n headers['Content-Type'] = 'application/json'\n payload = JSON.stringify(body)\n }\n\n const res = await this.fetchImpl(`${this.baseUrl}${path}`, { method, headers, body: payload })\n const text = await res.text()\n const json = text ? (JSON.parse(text) as { message?: string; data?: ClientSession; error?: ApiErrorKey }) : {}\n\n if (!res.ok) {\n throw new WidgetApiError(json.message ?? `Request failed (${res.status})`, res.status, json.error)\n }\n return json.data as ClientSession\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"controller.d.mts","names":[],"sources":["../src/controller.ts"],"mappings":";;;cAoEa,gBAAA;EAAA,iBAmDkB,MAAA;EAAA,iBAlDZ,MAAA;EAAA,iBACA,IAAA;EAAA,iBACA,GAAA;EAAA,iBACA,GAAA;EAAA,iBACA,YAAA;EAAA,iBACA,SAAA;EAAA,iBACA,WAAA;EAAA,iBACA,MAAA;EAAA,iBACA,QAAA;EAAA,iBACA,eAAA;EARA;EAAA,QAWT,aAAA;EAAA,QACA,YAAA;EAAA,QACA,aAAA;EATS;EAAA,QAYT,SAAA;EAAA,QACA,cAAA;EAAA,QACA,QAAA;EAAA,QACA,UAAA;EARA;EAAA,QAUA,UAAA;EARA;EAAA,QAUA,SAAA;EANA;EAAA,QAQA,SAAA;EAAA,QAEA,IAAA;EAAA,QACA,YAAA;EAAA,QACA,OAAA;EAAA,QACA,MAAA;EAAA,QACA,YAAA;EAHA;EAAA,QAKA,YAAA;EAAA,QACA,kBAAA;EAHA;EAAA,QAKA,WAAA;EAFA;EAAA,QAIA,aAAA;EAAA;EAAA,QAEA,QAAA;EAAA,QACA,MAAA;EAEA;EAAA,QAAA,cAAA;EAGA;EAAA,QADA,gBAAA;EAAA,QACA,YAAA;EAAA,QACA,OAAA;cAEqB,MAAA,EAAQ,sBAAA;EAkCjC;EAAA,IAAA,OAAA,IAAW,WAAA;EAKf;EAAA,KAAA;EAWA;EALA,OAAA;EAaG;EARH,KAAA;EAQkB;;;;EAAlB,EAAA,CAAG,KAAA,UAAe,QAAA,EAAU,mBAAA;EAiEd;EAAA,QAxDN,WAAA;EAoHM;;;;;EAAA,QAxGN,QAAA;EAoQA;EAAA,QA5PA,IAAA;EAqSM;EAAA,QAjRN,OAAA;EA2UA;;;;;EAAA,QA3TM,eAAA;EAAA,QAoBN,QAAA;EA8YA;;;;;EAAA,QAtWM,SAAA;EAyaN;EAAA,QA9YA,aAAA;EAoZI;EAAA,QA/YE,YAAA;;;;;;UAcN,eAAA;;;;;;;UAgBM,WAAA;;;;;;;;;;;;UA8CN,SAAA;;;;;;UAgDA,eAAA;EAAA,QAWM,OAAA;;;;;;;UA8BA,KAAA;;;;;UAwCA,IAAA;EAAA,QAkBN,UAAA;;;;;;UAWA,YAAA;EAAA,QAOA,MAAA;;;;;;;UAwBM,SAAA;;UAYA,aAAA;;;;;;UAWA,aAAA;;;;;;;;UAyBN,SAAA;EAAA,QAaA,IAAA;EAAA,QAQA,KAAA;;UAKM,GAAA;EAAA,QAQN,IAAA;EAAA,QAcA,YAAA;EAAA,QAmBA,WAAA;;UAaA,gBAAA;EAAA,QAMA,IAAA;AAAA"}
1
+ {"version":3,"file":"controller.d.mts","names":[],"sources":["../src/controller.ts"],"mappings":";;;cAoEa,gBAAA;EAAA,iBAmDkB,MAAA;EAAA,iBAlDZ,MAAA;EAAA,iBACA,IAAA;EAAA,iBACA,GAAA;EAAA,iBACA,GAAA;EAAA,iBACA,YAAA;EAAA,iBACA,SAAA;EAAA,iBACA,WAAA;EAAA,iBACA,MAAA;EAAA,iBACA,QAAA;EAAA,iBACA,eAAA;EARA;EAAA,QAWT,aAAA;EAAA,QACA,YAAA;EAAA,QACA,aAAA;EATS;EAAA,QAYT,SAAA;EAAA,QACA,cAAA;EAAA,QACA,QAAA;EAAA,QACA,UAAA;EARA;EAAA,QAUA,UAAA;EARA;EAAA,QAUA,SAAA;EANA;EAAA,QAQA,SAAA;EAAA,QAEA,IAAA;EAAA,QACA,YAAA;EAAA,QACA,OAAA;EAAA,QACA,MAAA;EAAA,QACA,YAAA;EAHA;EAAA,QAKA,YAAA;EAAA,QACA,kBAAA;EAHA;EAAA,QAKA,WAAA;EAFA;EAAA,QAIA,aAAA;EAAA;EAAA,QAEA,QAAA;EAAA,QACA,MAAA;EAEA;EAAA,QAAA,cAAA;EAGA;EAAA,QADA,gBAAA;EAAA,QACA,YAAA;EAAA,QACA,OAAA;cAEqB,MAAA,EAAQ,sBAAA;EAkCjC;EAAA,IAAA,OAAA,IAAW,WAAA;EAKf;EAAA,KAAA;EAWA;EALA,OAAA;EAaG;EARH,KAAA;EAQkB;;;;EAAlB,EAAA,CAAG,KAAA,UAAe,QAAA,EAAU,mBAAA;EAiEd;EAAA,QAxDN,WAAA;EAuHM;;;;;EAAA,QA3GN,QAAA;EAkQA;EAAA,QA1PA,IAAA;EAmSM;EAAA,QA/QN,OAAA;EAyUA;;;;;EAAA,QAzTM,eAAA;EAAA,QAuBN,QAAA;EAyYA;;;;;EAAA,QAjWM,SAAA;EAoaN;EAAA,QAzYA,aAAA;EA+YI;EAAA,QA1YE,YAAA;;;;;;UAcN,eAAA;;;;;;;UAWM,WAAA;;;;;;;;;;;;UA8CN,SAAA;;;;;;UAgDA,eAAA;EAAA,QAWM,OAAA;;;;;;;UA8BA,KAAA;;;;;UAwCA,IAAA;EAAA,QAkBN,UAAA;;;;;;UAWA,YAAA;EAAA,QAOA,MAAA;;;;;;;UAwBM,SAAA;;UAYA,aAAA;;;;;;UAWA,aAAA;;;;;;;;UAyBN,SAAA;EAAA,QAaA,IAAA;EAAA,QAQA,KAAA;;UAKM,GAAA;EAAA,QAQN,IAAA;EAAA,QAcA,YAAA;EAAA,QAmBA,WAAA;;UAaA,gBAAA;EAAA,QAMA,IAAA;AAAA"}
@@ -210,7 +210,7 @@ var WidgetController = class {
210
210
  const cfg = this.realtimeConfig;
211
211
  if (!cfg || cfg.transport === "polling" || cfg.transport === "off" || cfg.transport === "memory") return null;
212
212
  const factory = this.config.realtimeFactory ?? createWidgetRealtimeClient;
213
- const apiBase = (this.config.baseUrl ?? "").replace(/\/$/, "");
213
+ const apiBase = this.client.base;
214
214
  try {
215
215
  this.rtClient = await factory(cfg, {
216
216
  authEndpoint: `${apiBase}/realtime/auth`,
@@ -296,10 +296,8 @@ var WidgetController = class {
296
296
  * @returns
297
297
  */
298
298
  buildHandoffUrl(target) {
299
- let url = `${target}${target.includes("?") ? "&" : "?"}token=${encodeURIComponent(this.config.token)}`;
300
- const apiBase = (this.config.baseUrl ?? "").trim();
301
- if (/^https?:\/\//i.test(apiBase)) url += `&baseUrl=${encodeURIComponent(apiBase)}`;
302
- return url;
299
+ const pageOrigin = this.win.location?.origin ?? globalThis.location?.origin ?? "";
300
+ return composeHandoffUrl(target, this.config.token, this.client.base, pageOrigin);
303
301
  }
304
302
  /**
305
303
  * Watch the session while the user verifies on the other
@@ -646,6 +644,28 @@ function serializeError(error) {
646
644
  name: error.name
647
645
  };
648
646
  }
647
+ /** The origin of an absolute URL, or '' when it can't be parsed. */
648
+ function originOf(url) {
649
+ try {
650
+ return new URL(url).origin;
651
+ } catch {
652
+ return "";
653
+ }
654
+ }
655
+ /**
656
+ * Build the hosted-page URL the handoff QR encodes: always the `target` page
657
+ * plus the session `token`, and the API base **only when the phone can actually
658
+ * reach it** — i.e. a genuine cross-origin base. `apiBase` is the widget's
659
+ * resolved Client API base (always origin-qualified), so a relative proxy
660
+ * resolves to the desktop's own `pageOrigin` and is skipped (the hosted page
661
+ * then falls back to its own default base instead of a URL the phone can't use).
662
+ */
663
+ const composeHandoffUrl = (target, token, apiBase, pageOrigin) => {
664
+ let url = `${target}${target.includes("?") ? "&" : "?"}token=${encodeURIComponent(token)}`;
665
+ const baseOrigin = originOf(apiBase);
666
+ if (baseOrigin && baseOrigin !== pageOrigin) url += `&baseUrl=${encodeURIComponent(apiBase)}`;
667
+ return url;
668
+ };
649
669
  const resolveContainer = (container, doc) => {
650
670
  const el = typeof container === "string" ? doc.querySelector(container) : container;
651
671
  if (!el) throw new Error(`ArkycWidget.mount: container "${String(container)}" not found.`);
@@ -1 +1 @@
1
- {"version":3,"file":"controller.mjs","names":[],"sources":["../src/controller.ts"],"sourcesContent":["import type {\n DocumentType,\n LivenessChallenge,\n LivenessMode,\n SessionTransitionEvent,\n VerificationStatus,\n WidgetResult,\n WidgetStep,\n WorkflowConfig,\n} from '@arkyc/types'\nimport { REALTIME_EVENT, workflowAddressConfig } from '@arkyc/types'\nimport { ArkycClient, type ClientHandoff, type ClientRealtime, type ClientSession, WidgetApiError } from './client'\nimport { Flow } from './flow'\nimport { Theme } from './theme'\nimport { WidgetView, type AddressFormData } from './ui'\nimport { isDesktopDevice } from './device'\nimport { renderQrSvg } from './qr'\nimport { createWidgetRealtimeClient, type WidgetRealtimeClient } from './realtime'\nimport type { BaseWidgetOptions, ViewHandlers, WidgetControllerConfig, WidgetEventListener } from './types'\n\n/** Per-step lead-in copy shown before each capture/liveness step. */\nconst STEP_INSTRUCTIONS: Partial<Record<WidgetStep, { title: string; body: string; cta?: string }>> = {\n front_capture: {\n title: 'Front of your document',\n body: 'Place the front of your ID flat and fully inside the frame. We’ll capture it automatically.',\n },\n back_capture: {\n title: 'Back of your document',\n body: 'Now turn your ID over and show the back, fully inside the frame.',\n },\n selfie_capture: {\n title: 'Take a selfie',\n body: 'Look straight at the camera in good light and hold still — we’ll capture it automatically.',\n },\n active_liveness: {\n title: 'Quick liveness check',\n body: 'Follow the on-screen prompts (such as turn your head, blink or smile). This confirms you’re really here.',\n },\n}\n\n/**\n * Drives the full verification flow: renders each screen via {@link WidgetView},\n * advances through {@link flow}, and calls the Client API at each step. Cosmetic\n * processing screens auto-advance; capture screens wait for an image. On a\n * terminal result it surfaces the outcome and (in hosted mode) posts\n * `arkyc:complete` / `arkyc:error` / `arkyc:close` to the parent window.\n */\n/**\n * Re-base the server-resolved project logo onto the API origin the widget is\n * actually talking to. The stored `logo_url` carries the API's own host, which a\n * handed-off phone cannot reach — it reaches the API through the absolute\n * `baseUrl` passed in the handoff link. Rewriting the origin (keeping the path)\n * makes the logo load on whatever device runs the widget. Only applied when\n * `baseUrl` is absolute; the integrator's own branding is never re-based.\n */\nfunction rebaseBrandingLogo<T extends { logo_url?: string | null }>(branding: T, baseUrl: string | undefined): T {\n const logo = branding.logo_url\n if (!logo || !baseUrl || !/^https?:\\/\\//i.test(baseUrl)) return branding\n try {\n const base = new URL(baseUrl)\n const resolved = new URL(logo, base)\n if (resolved.origin === base.origin) return branding\n return { ...branding, logo_url: `${base.origin}${resolved.pathname}${resolved.search}${resolved.hash}` }\n } catch {\n return branding\n }\n}\n\nexport class WidgetController {\n private readonly client: ArkycClient\n private readonly view: WidgetView\n private readonly win: Window\n private readonly nav: Navigator\n private readonly postToParent: boolean\n private readonly scheduler: (fn: () => void, ms: number) => void\n private readonly transientMs: number\n private readonly pollMs: number\n private readonly maxPolls: number\n private readonly maxHandoffPolls: number\n\n /** Cross-device handoff: project config + whether the offer/QR is showing. */\n private handoffConfig: ClientHandoff = { enabled: false, allow_desktop: true, url: '' }\n private handoffReady = false\n private handoffActive = false\n\n /** This session's id + how to watch it live (push transport vs. polling). */\n private sessionId = ''\n private realtimeConfig: ClientRealtime | null = null\n private rtClient: WidgetRealtimeClient | null = null\n private rtResolved = false\n /** The last status observed, so a transition only emits once per change. */\n private lastStatus: VerificationStatus | null = null\n /** Per-name event listeners registered via {@link on}. */\n private listeners = new Map<string, Set<WidgetEventListener>>()\n /** Cancels the active session watch (e.g. continue-on-this-device). */\n private stopWatch: (() => void) | undefined\n\n private step: WidgetStep = 'welcome'\n private documentType: DocumentType | null = null\n private country: string | null = null\n private selfie: Blob | null = null\n private livenessMode: LivenessMode = 'passive'\n /** The session's capture model; `active` mandates a live camera (no fallback). */\n private captureModel: 'passive' | 'active' | 'both' = 'passive'\n private livenessChallenges: LivenessChallenge[] = []\n /** The address-entry form data, captured before the address submission. */\n private addressData: AddressFormData | null = null\n /** Device coordinates resolved on the address step (when the user opts in). */\n private addressCoords: { latitude: number; longitude: number } | null = null\n /** The applied workflow (orders/toggles stages, skip_ocr), or null for the default flow. */\n private workflow: WorkflowConfig | null = null\n private result: WidgetResult | null = null\n /** The session was already terminal when the widget loaded (show a close-only notice). */\n private terminalOnLoad = false\n /** Whether the current step's instruction interstitial has been acknowledged. */\n private instructionAcked = false\n private pendingError: Error | null = null\n private settled = false\n\n constructor(private readonly config: WidgetControllerConfig) {\n const doc = config.doc ?? globalThis.document\n const win = config.win ?? globalThis.window\n if (!doc || !win) throw new Error('The Arkyc widget must run in a browser environment.')\n this.win = win\n this.nav = config.nav ?? globalThis.navigator\n\n this.client = new ArkycClient({\n token: config.token,\n baseUrl: config.baseUrl,\n fetch: config.fetch,\n })\n const theme = new Theme(config.branding)\n this.view = new WidgetView(\n doc,\n theme,\n this.handlers(),\n config.nav ?? globalThis.navigator,\n config.faceAnalyzer,\n config.faceTuning,\n config.documentAnalyzer,\n config.documentTuning,\n )\n\n this.postToParent = config.postToParent ?? (!!win.parent && win.parent !== win)\n this.scheduler = config.scheduler ?? ((fn, ms) => setTimeout(fn, ms))\n this.transientMs = config.transientMs ?? 700\n this.pollMs = config.pollMs ?? 1500\n this.maxPolls = config.maxPolls ?? 40\n // A handed-off session is bounded by its TTL (~15 min); poll generously.\n this.maxHandoffPolls = config.maxHandoffPolls ?? 600\n }\n\n /** The widget's root element — append it to an overlay or inline container. */\n get element(): HTMLElement {\n return this.view.element\n }\n\n /** Connect to the session, then route to the result, the QR, or the welcome flow. */\n start(): void {\n this.view.renderLoading()\n void this.run(() => this.bootstrap())\n }\n\n /** Tear down the view and release the camera (does not fire callbacks). */\n destroy(): void {\n this.view.destroy()\n }\n\n /** Close the widget as if the user dismissed it (fires `onClose`/`onSettle`). */\n close(): void {\n this.finishClose()\n }\n\n /**\n * Subscribe to a named widget event; returns an unsubscribe function. Having a\n * listener (here or via `onEvent`) is what activates the event stream.\n */\n on(event: string, listener: WidgetEventListener): () => void {\n const set = this.listeners.get(event) ?? new Set<WidgetEventListener>()\n set.add(listener)\n this.listeners.set(event, set)\n\n return () => set.delete(listener)\n }\n\n /** Whether anyone is listening for `name` (the firehose or a named listener). */\n private hasListener(name: string): boolean {\n if (this.config.onEvent) return true\n const set = this.listeners.get(name)\n\n return !!set && set.size > 0\n }\n\n /**\n * Surface an event: to local listeners (firehose + `on`), and — in hosted\n * iframe mode — to the embedding parent as `arkyc:event` so the SDK's\n * `handle.on(...)` works across the frame.\n */\n private dispatch(name: string, data?: unknown): void {\n this.emit(name, data)\n // The parent window is the consumer in iframe mode, so forwarding there isn't\n // gated on a local listener (post() already no-ops when not embedding).\n this.post('arkyc:event', { name, data })\n }\n\n /** Emit an event to the firehose + named listeners — but only if one is active. */\n private emit(name: string, data?: unknown): void {\n if (!this.hasListener(name)) return\n try {\n this.config.onEvent?.({ name, data })\n } catch {\n // A consumer callback threw — never let it break the flow.\n }\n const set = this.listeners.get(name)\n if (set) {\n for (const listener of [...set]) {\n try {\n listener(data)\n } catch {\n // ditto\n }\n }\n }\n }\n\n /** Record an observed status, emitting `session.transition` on a real change. */\n private observe(status: VerificationStatus): void {\n if (status === this.lastStatus) return\n const previous = this.lastStatus\n this.lastStatus = status\n this.dispatch(REALTIME_EVENT.sessionTransition, {\n session_id: this.sessionId,\n status,\n previous_status: previous,\n } satisfies Partial<SessionTransitionEvent>)\n }\n\n /**\n * Lazily connect the push transport for this session (once). Returns null for\n * `polling`/`off`/`memory` or when the transport SDK can't load — the caller\n * then polls instead.\n */\n private async resolveRealtime(): Promise<WidgetRealtimeClient | null> {\n if (this.rtResolved) return this.rtClient\n this.rtResolved = true\n const cfg = this.realtimeConfig\n if (!cfg || cfg.transport === 'polling' || cfg.transport === 'off' || cfg.transport === 'memory') return null\n\n const factory = this.config.realtimeFactory ?? createWidgetRealtimeClient\n const apiBase = (this.config.baseUrl ?? '').replace(/\\/$/, '')\n try {\n this.rtClient = await factory(cfg, {\n authEndpoint: `${apiBase}/realtime/auth`,\n token: this.config.token,\n })\n } catch {\n this.rtClient = null\n }\n\n return this.rtClient\n }\n\n private handlers(): ViewHandlers {\n return {\n onClose: () => this.finishClose(),\n // The session was already fetched + resolved during bootstrap; just advance.\n onStart: () => void this.run(() => this.enter(this.next())),\n onDocumentSelected: (type, country) => {\n this.documentType = type\n this.country = country || null\n void this.run(() => this.enter('front_capture'))\n },\n onImage: (blob) => void this.run(() => this.onImage(blob)),\n onActiveLiveness: (video, performed, selfie) =>\n void this.run(async () => {\n await this.client.submitLiveness({\n selfie,\n video,\n mode: 'active',\n challenges: performed,\n signals: this.config.signals,\n })\n await this.enter(this.next())\n }),\n onAddress: (data) => void this.run(() => this.onAddress(data)),\n onShareLocation: () => this.shareLocation(),\n onAcknowledge: () => this.finishResult(),\n onUsePhone: () => void this.run(() => this.startHandoff()),\n onContinueHere: () => {\n this.handoffActive = false\n this.stopWatch?.()\n this.step = 'welcome'\n this.render()\n },\n }\n }\n\n /**\n * Fetch the session and route: an already-terminal session (e.g. a stale handoff\n * link, or one finished on another device) shows a close-only notice; on a\n * desktop with handoff enabled we lead with the QR; otherwise the welcome flow.\n */\n private async bootstrap(): Promise<void> {\n if (this.settled) return\n const session = await this.client.getSession()\n if (this.settled) return\n this.sessionId = session.id\n this.realtimeConfig = session.realtime ?? null\n this.workflow = session.workflow ?? null\n // Theme from the project's branding (server-resolved) unless the integrator\n // passed branding explicitly — their value wins.\n if (this.config.branding == null && session.branding)\n this.view.applyBranding(rebaseBrandingLogo(session.branding, this.config.baseUrl))\n this.observe(session.status)\n if (Flow.isTerminal(session.status)) return this.showTerminal(session.status)\n this.resolveLiveness(session)\n if (session.handoff) this.handoffConfig = session.handoff\n const canHandoff =\n this.config.handoff !== false && isDesktopDevice(this.nav) && this.handoffConfig.enabled && !!this.handoffTarget()\n if (canHandoff) {\n this.handoffReady = true\n await this.startHandoff()\n } else {\n this.step = 'welcome'\n this.render()\n }\n }\n\n /** The hosted handoff page URL: a consumer override, else the server-provided one. */\n private handoffTarget(): string {\n return (this.config.handoffUrl ?? this.handoffConfig.url ?? '').trim()\n }\n\n /** Render the QR for this session and wait for the other device to finish. */\n private async startHandoff(): Promise<void> {\n const target = this.handoffTarget()\n if (!target) return\n this.handoffActive = true\n // Offer \"continue on this device\" only when the project permits it.\n this.view.renderHandoff(renderQrSvg(this.buildHandoffUrl(target)), this.handoffConfig.allow_desktop)\n await this.pollHandoff()\n }\n\n /** Build the hosted-page URL the QR encodes (carries the session token).\n *\n * @param target\n * @returns\n */\n private buildHandoffUrl(target: string): string {\n const sep = target.includes('?') ? '&' : '?'\n let url = `${target}${sep}token=${encodeURIComponent(this.config.token)}`\n // The hosted page supplies its own API base; only pass one when ours is\n // absolute, so a custom cross-origin host can still reach the right API.\n const apiBase = (this.config.baseUrl ?? '').trim()\n if (/^https?:\\/\\//i.test(apiBase)) url += `&baseUrl=${encodeURIComponent(apiBase)}`\n return url\n }\n\n /**\n * Watch the session while the user verifies on the other\n * device; mirror the result.\n *\n * @returns\n */\n private async pollHandoff(): Promise<void> {\n const rt = await this.resolveRealtime()\n if (rt) {\n const status = await this.pushWatch(rt, this.maxHandoffPolls, () => this.handoffActive && !this.settled)\n if (status) {\n this.handoffActive = false\n return this.showResult(status)\n }\n\n return // cancelled (continue here) or budget elapsed → the QR persists\n }\n\n let errors = 0\n for (let i = 0; i < this.maxHandoffPolls && this.handoffActive && !this.settled; i++) {\n await this.delay(this.pollMs)\n if (!this.handoffActive || this.settled) return\n try {\n const session = await this.client.getSession()\n errors = 0\n this.observe(session.status)\n if (Flow.isTerminal(session.status)) {\n this.handoffActive = false\n return this.showResult(session.status)\n }\n } catch (error) {\n // The session/token can expire mid-wait (401) — stop and reflect that.\n if (error instanceof WidgetApiError && error.status === 401) {\n this.handoffActive = false\n return this.showResult('expired')\n }\n if (++errors >= 5) return\n }\n }\n }\n\n /**\n * Await a terminal status over the push transport. Resolves to the terminal\n * status, or null if `active()` went false or the tick budget elapsed (push can\n * miss events / disconnect; the budget bounds the wait). An initial fetch\n * catches a session that finished before we subscribed.\n *\n * @param rt\n * @param budget\n * @param active\n * @returns\n */\n private pushWatch(\n rt: WidgetRealtimeClient,\n budget: number,\n active: () => boolean,\n ): Promise<VerificationStatus | null> {\n return new Promise((resolve) => {\n let done = false\n const finish = (status: VerificationStatus | null) => {\n if (done) return\n done = true\n this.stopWatch = undefined\n unsubscribe()\n resolve(status)\n }\n const channel = this.realtimeConfig!.channel\n const unsubscribe = rt.subscribe(channel, (event, data) => {\n if (event !== REALTIME_EVENT.sessionTransition) return\n const status = (data as SessionTransitionEvent).status\n this.observe(status)\n if (Flow.isTerminal(status)) finish(status)\n })\n this.stopWatch = () => finish(null)\n // Catch a race where the session already finished before we subscribed.\n void this.client\n .getSession()\n .then((session) => {\n this.observe(session.status)\n if (Flow.isTerminal(session.status)) finish(session.status)\n })\n .catch((error) => {\n if (error instanceof WidgetApiError && error.status === 401) finish('expired')\n })\n // Bound the wait by the same tick budget the poller would use.\n const countdown = async (n: number): Promise<void> => {\n if (done) return\n if (!active() || n >= budget) return finish(null)\n await this.delay(this.pollMs)\n void countdown(n + 1)\n }\n void countdown(0)\n })\n }\n\n /**\n * Resolve which liveness flow to run from the session's capture model.\n *\n * @param session\n */\n private resolveLiveness(session: ClientSession): void {\n this.livenessChallenges = session.liveness_challenges ?? []\n const model = session.capture_model ?? 'passive'\n this.captureModel = model\n const wantsActive = model === 'active' || model === 'both'\n // `active` is honoured even without a live camera — the active-liveness\n // screen then shows the device as unsupported (no fallback). `both` falls\n // back to passive when the camera/recorder isn't available.\n this.livenessMode = wantsActive && (model === 'active' || this.view.cameraSupported) ? 'active' : 'passive'\n }\n\n private async onImage(blob: Blob | null): Promise<void> {\n const signals = this.config.signals\n switch (this.step) {\n case 'front_capture':\n await this.client.submitDocumentFront({\n image: blob,\n country: this.country,\n documentType: this.documentType,\n signals,\n })\n return this.enter(this.next())\n case 'back_capture':\n await this.client.submitDocumentBack({\n image: blob,\n country: this.country,\n documentType: this.documentType,\n })\n return this.enter('ocr_processing')\n case 'selfie_capture':\n this.selfie = blob\n return this.enter('passive_liveness')\n }\n }\n\n /**\n * Enter a step: render it, and drive any automatic (processing) work.\n *\n * @param step\n * @returns\n */\n private async enter(step: WidgetStep): Promise<void> {\n if (this.settled) return\n this.step = step\n\n // Lead each capture/liveness step with an instruction screen so the user\n // knows what's next and starts it deliberately. The Continue button re-enters\n // the same step with the instruction acknowledged.\n const instruction = STEP_INSTRUCTIONS[step]\n if (instruction && !this.instructionAcked) {\n this.view.renderInstruction(step, instruction.title, instruction.body, instruction.cta ?? 'Continue', () => {\n this.instructionAcked = true\n void this.run(() => this.enter(step))\n })\n return\n }\n this.instructionAcked = false\n this.render()\n\n switch (step) {\n case 'ocr_processing':\n await this.delay(this.transientMs)\n return this.enter(this.next())\n case 'address_processing':\n await this.submitAddress()\n return this.enter(this.next())\n case 'passive_liveness':\n await this.client.submitLiveness({ selfie: this.selfie, signals: this.config.signals })\n return this.enter(this.next())\n case 'face_match':\n await this.client.complete({ signals: this.config.signals })\n return this.enter(this.next())\n case 'processing':\n return this.poll()\n }\n }\n\n /** Watch the session until it reaches a terminal status (then show the result).\n *\n * @returns\n */\n private async poll(): Promise<void> {\n const rt = await this.resolveRealtime()\n if (rt) {\n const status = await this.pushWatch(rt, this.maxPolls, () => !this.settled)\n\n return this.showResult(status ?? 'requires_review')\n }\n\n for (let i = 0; i < this.maxPolls && !this.settled; i++) {\n const session = await this.client.getSession()\n this.observe(session.status)\n if (Flow.isTerminal(session.status)) return this.showResult(session.status)\n await this.delay(this.pollMs)\n }\n // Still processing after the poll budget — surface as pending review.\n this.showResult('requires_review')\n }\n\n private showResult(status: VerificationStatus): void {\n this.result = { status, decision: Flow.statusToDecision(status) }\n this.step = 'result'\n this.render()\n }\n\n /**\n * A session that was already terminal on load: a notice with only a Close button.\n *\n * @param status\n */\n private showTerminal(status: VerificationStatus): void {\n this.result = { status, decision: Flow.statusToDecision(status) }\n this.step = 'result'\n this.terminalOnLoad = true\n this.render()\n }\n\n private render(): void {\n this.view.render({\n step: this.step,\n documentType: this.documentType,\n decision: this.result?.decision,\n status: this.result?.status,\n terminalNotice: this.terminalOnLoad,\n allowSkip: !!this.config.signals,\n livenessChallenges: this.livenessChallenges,\n requireLiveCamera: this.captureModel === 'active',\n // The active flow is strict end-to-end: documents must be detected, not\n // manually waved through.\n strictCapture: this.livenessMode === 'active',\n handoffAvailable: this.handoffReady,\n addressMethods: workflowAddressConfig(this.workflow)?.methods ?? [],\n })\n }\n\n /**\n * Store the entered address, resolve device location if opted in, then verify.\n *\n * @param data\n * @returns\n */\n private async onAddress(data: AddressFormData): Promise<void> {\n this.addressData = data\n // Coordinates are normally resolved up front by `shareLocation` when the user\n // ticks \"use my location\" (so the prompt is immediate); geolocate here only as\n // a fallback, and clear them if they opted out.\n if (!data.useLocation) this.addressCoords = null\n else if (!this.addressCoords) this.addressCoords = await this.geolocate()\n\n return this.enter('address_processing')\n }\n\n /** Request the device's location (from the address-entry \"use my location\" opt-in). */\n private async shareLocation(): Promise<boolean> {\n this.addressCoords = await this.geolocate()\n\n return !!this.addressCoords\n }\n\n /**\n * Submit the captured address evidence to the Client API.\n *\n * @returns\n */\n private async submitAddress(): Promise<void> {\n const data = this.addressData\n if (!data) return\n\n await this.client.submitAddress({\n line1: data.line1,\n line2: data.line2,\n city: data.city,\n region: data.region,\n postalCode: data.postalCode,\n country: data.country,\n poa: data.poa,\n latitude: this.addressCoords?.latitude ?? null,\n longitude: this.addressCoords?.longitude ?? null,\n signals: this.config.signals,\n })\n }\n\n /**\n * Resolve the device's coordinates via the Geolocation API. Resolves to `null`\n * on denial/unavailability/timeout — the `device_location` method then simply\n * has no coordinates to check (routing the session to review).\n *\n * @returns\n */\n private geolocate(): Promise<{ latitude: number; longitude: number } | null> {\n const geo = this.nav?.geolocation\n if (!geo) return Promise.resolve(null)\n\n return new Promise((resolve) => {\n geo.getCurrentPosition(\n (pos) => resolve({ latitude: pos.coords.latitude, longitude: pos.coords.longitude }),\n () => resolve(null),\n { enableHighAccuracy: false, timeout: 10_000, maximumAge: 60_000 },\n )\n })\n }\n\n private next(): WidgetStep {\n return Flow.nextStep(this.step, {\n documentType: this.documentType,\n livenessMode: this.livenessMode,\n workflow: this.workflow,\n })\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => this.scheduler(resolve, ms))\n }\n\n /** Run an async step, routing any failure to the error screen. */\n private async run(fn: () => Promise<void> | void): Promise<void> {\n try {\n await fn()\n } catch (error) {\n this.fail(error)\n }\n }\n\n private fail(error: unknown): void {\n if (this.settled) return\n const err = error instanceof Error ? error : new Error(String(error))\n this.step = 'result'\n\n this.view.render({\n step: 'result',\n documentType: this.documentType,\n errorMessage: err.message,\n })\n\n this.pendingError = err\n }\n\n private finishResult(): void {\n if (this.settled) return\n this.settled = true\n this.teardownRealtime()\n\n if (this.pendingError) {\n this.post('arkyc:error', { error: serializeError(this.pendingError) })\n this.dispatch('error', { message: this.pendingError.message })\n this.config.onError?.(this.pendingError)\n } else if (this.result) {\n this.post('arkyc:complete', { payload: this.result })\n this.dispatch('complete', this.result)\n this.config.onComplete?.(this.result)\n }\n\n this.destroy()\n this.config.onSettle?.()\n }\n\n private finishClose(): void {\n if (this.settled) return\n\n this.settled = true\n this.teardownRealtime()\n this.post('arkyc:close', {})\n this.dispatch('close')\n this.config.onClose?.()\n this.destroy()\n this.config.onSettle?.()\n }\n\n /** Stop any active session watch + disconnect the push transport. */\n private teardownRealtime(): void {\n this.stopWatch?.()\n this.rtClient?.disconnect()\n this.rtClient = null\n }\n\n private post(type: string, extra: Record<string, unknown>): void {\n if (!this.postToParent) return\n try {\n this.win.parent?.postMessage({ type, ...extra }, '*')\n } catch {\n // Cross-origin parent without access — nothing more we can do.\n }\n }\n}\n\nfunction serializeError(error: Error): { message: string; name: string } {\n return { message: error.message, name: error.name }\n}\n\nexport const resolveContainer = (container: string | HTMLElement, doc: Document): HTMLElement => {\n const el = typeof container === 'string' ? doc.querySelector<HTMLElement>(container) : container\n if (!el) throw new Error(`ArkycWidget.mount: container \"${String(container)}\" not found.`)\n return el\n}\n\n/**\n * Make an inline mount point a reliable host for the widget, so `mount()` renders\n * predictably regardless of the page layout — a static or zero-height container,\n * or one sharing the viewport with an app root that would otherwise paint over it.\n *\n * The widget gives the host its own stacking context and lifts it above sibling\n * app content, the way `open()` owns its overlay. Each property is only set when\n * the host hasn't already committed to one (checked against computed style where\n * available, else inline style), so an embed that wants normal flow or a specific\n * layer can still override it with its own CSS.\n */\nexport const adoptContainer = (container: HTMLElement, doc: Document): void => {\n const computed = doc.defaultView?.getComputedStyle?.(container)\n const position = computed?.position ?? container.style.position\n if (!position || position === 'static') container.style.position = 'relative'\n\n const zIndex = computed?.zIndex ?? container.style.zIndex\n if (!zIndex || zIndex === 'auto') container.style.zIndex = '1'\n\n // A self-contained stacking context keeps the widget's layers from bleeding\n // into (or being clipped by) the surrounding page.\n if (!container.style.isolation) container.style.isolation = 'isolate'\n}\n\nexport const buildController = (options: BaseWidgetOptions, onSettle: () => void): WidgetController => {\n if (!options.token) throw new Error('ArkycWidget requires a client `token`.')\n return new WidgetController({\n token: options.token,\n baseUrl: options.baseUrl,\n handoff: options.handoff,\n handoffUrl: options.handoffUrl,\n branding: options.branding,\n signals: options.signals,\n onComplete: options.onComplete,\n onError: options.onError,\n onClose: options.onClose,\n onEvent: options.onEvent,\n onSettle,\n fetch: options.fetch,\n doc: options.doc,\n win: options.win,\n nav: options.nav,\n faceAnalyzer: options.faceAnalyzer,\n faceTuning: options.faceTuning,\n documentAnalyzer: options.documentAnalyzer,\n documentTuning: options.documentTuning,\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"controller.mjs","names":[],"sources":["../src/controller.ts"],"sourcesContent":["import type {\n DocumentType,\n LivenessChallenge,\n LivenessMode,\n SessionTransitionEvent,\n VerificationStatus,\n WidgetResult,\n WidgetStep,\n WorkflowConfig,\n} from '@arkyc/types'\nimport { REALTIME_EVENT, workflowAddressConfig } from '@arkyc/types'\nimport { ArkycClient, type ClientHandoff, type ClientRealtime, type ClientSession, WidgetApiError } from './client'\nimport { Flow } from './flow'\nimport { Theme } from './theme'\nimport { WidgetView, type AddressFormData } from './ui'\nimport { isDesktopDevice } from './device'\nimport { renderQrSvg } from './qr'\nimport { createWidgetRealtimeClient, type WidgetRealtimeClient } from './realtime'\nimport type { BaseWidgetOptions, ViewHandlers, WidgetControllerConfig, WidgetEventListener } from './types'\n\n/** Per-step lead-in copy shown before each capture/liveness step. */\nconst STEP_INSTRUCTIONS: Partial<Record<WidgetStep, { title: string; body: string; cta?: string }>> = {\n front_capture: {\n title: 'Front of your document',\n body: 'Place the front of your ID flat and fully inside the frame. We’ll capture it automatically.',\n },\n back_capture: {\n title: 'Back of your document',\n body: 'Now turn your ID over and show the back, fully inside the frame.',\n },\n selfie_capture: {\n title: 'Take a selfie',\n body: 'Look straight at the camera in good light and hold still — we’ll capture it automatically.',\n },\n active_liveness: {\n title: 'Quick liveness check',\n body: 'Follow the on-screen prompts (such as turn your head, blink or smile). This confirms you’re really here.',\n },\n}\n\n/**\n * Drives the full verification flow: renders each screen via {@link WidgetView},\n * advances through {@link flow}, and calls the Client API at each step. Cosmetic\n * processing screens auto-advance; capture screens wait for an image. On a\n * terminal result it surfaces the outcome and (in hosted mode) posts\n * `arkyc:complete` / `arkyc:error` / `arkyc:close` to the parent window.\n */\n/**\n * Re-base the server-resolved project logo onto the API origin the widget is\n * actually talking to. The stored `logo_url` carries the API's own host, which a\n * handed-off phone cannot reach — it reaches the API through the absolute\n * `baseUrl` passed in the handoff link. Rewriting the origin (keeping the path)\n * makes the logo load on whatever device runs the widget. Only applied when\n * `baseUrl` is absolute; the integrator's own branding is never re-based.\n */\nfunction rebaseBrandingLogo<T extends { logo_url?: string | null }>(branding: T, baseUrl: string | undefined): T {\n const logo = branding.logo_url\n if (!logo || !baseUrl || !/^https?:\\/\\//i.test(baseUrl)) return branding\n try {\n const base = new URL(baseUrl)\n const resolved = new URL(logo, base)\n if (resolved.origin === base.origin) return branding\n return { ...branding, logo_url: `${base.origin}${resolved.pathname}${resolved.search}${resolved.hash}` }\n } catch {\n return branding\n }\n}\n\nexport class WidgetController {\n private readonly client: ArkycClient\n private readonly view: WidgetView\n private readonly win: Window\n private readonly nav: Navigator\n private readonly postToParent: boolean\n private readonly scheduler: (fn: () => void, ms: number) => void\n private readonly transientMs: number\n private readonly pollMs: number\n private readonly maxPolls: number\n private readonly maxHandoffPolls: number\n\n /** Cross-device handoff: project config + whether the offer/QR is showing. */\n private handoffConfig: ClientHandoff = { enabled: false, allow_desktop: true, url: '' }\n private handoffReady = false\n private handoffActive = false\n\n /** This session's id + how to watch it live (push transport vs. polling). */\n private sessionId = ''\n private realtimeConfig: ClientRealtime | null = null\n private rtClient: WidgetRealtimeClient | null = null\n private rtResolved = false\n /** The last status observed, so a transition only emits once per change. */\n private lastStatus: VerificationStatus | null = null\n /** Per-name event listeners registered via {@link on}. */\n private listeners = new Map<string, Set<WidgetEventListener>>()\n /** Cancels the active session watch (e.g. continue-on-this-device). */\n private stopWatch: (() => void) | undefined\n\n private step: WidgetStep = 'welcome'\n private documentType: DocumentType | null = null\n private country: string | null = null\n private selfie: Blob | null = null\n private livenessMode: LivenessMode = 'passive'\n /** The session's capture model; `active` mandates a live camera (no fallback). */\n private captureModel: 'passive' | 'active' | 'both' = 'passive'\n private livenessChallenges: LivenessChallenge[] = []\n /** The address-entry form data, captured before the address submission. */\n private addressData: AddressFormData | null = null\n /** Device coordinates resolved on the address step (when the user opts in). */\n private addressCoords: { latitude: number; longitude: number } | null = null\n /** The applied workflow (orders/toggles stages, skip_ocr), or null for the default flow. */\n private workflow: WorkflowConfig | null = null\n private result: WidgetResult | null = null\n /** The session was already terminal when the widget loaded (show a close-only notice). */\n private terminalOnLoad = false\n /** Whether the current step's instruction interstitial has been acknowledged. */\n private instructionAcked = false\n private pendingError: Error | null = null\n private settled = false\n\n constructor(private readonly config: WidgetControllerConfig) {\n const doc = config.doc ?? globalThis.document\n const win = config.win ?? globalThis.window\n if (!doc || !win) throw new Error('The Arkyc widget must run in a browser environment.')\n this.win = win\n this.nav = config.nav ?? globalThis.navigator\n\n this.client = new ArkycClient({\n token: config.token,\n baseUrl: config.baseUrl,\n fetch: config.fetch,\n })\n const theme = new Theme(config.branding)\n this.view = new WidgetView(\n doc,\n theme,\n this.handlers(),\n config.nav ?? globalThis.navigator,\n config.faceAnalyzer,\n config.faceTuning,\n config.documentAnalyzer,\n config.documentTuning,\n )\n\n this.postToParent = config.postToParent ?? (!!win.parent && win.parent !== win)\n this.scheduler = config.scheduler ?? ((fn, ms) => setTimeout(fn, ms))\n this.transientMs = config.transientMs ?? 700\n this.pollMs = config.pollMs ?? 1500\n this.maxPolls = config.maxPolls ?? 40\n // A handed-off session is bounded by its TTL (~15 min); poll generously.\n this.maxHandoffPolls = config.maxHandoffPolls ?? 600\n }\n\n /** The widget's root element — append it to an overlay or inline container. */\n get element(): HTMLElement {\n return this.view.element\n }\n\n /** Connect to the session, then route to the result, the QR, or the welcome flow. */\n start(): void {\n this.view.renderLoading()\n void this.run(() => this.bootstrap())\n }\n\n /** Tear down the view and release the camera (does not fire callbacks). */\n destroy(): void {\n this.view.destroy()\n }\n\n /** Close the widget as if the user dismissed it (fires `onClose`/`onSettle`). */\n close(): void {\n this.finishClose()\n }\n\n /**\n * Subscribe to a named widget event; returns an unsubscribe function. Having a\n * listener (here or via `onEvent`) is what activates the event stream.\n */\n on(event: string, listener: WidgetEventListener): () => void {\n const set = this.listeners.get(event) ?? new Set<WidgetEventListener>()\n set.add(listener)\n this.listeners.set(event, set)\n\n return () => set.delete(listener)\n }\n\n /** Whether anyone is listening for `name` (the firehose or a named listener). */\n private hasListener(name: string): boolean {\n if (this.config.onEvent) return true\n const set = this.listeners.get(name)\n\n return !!set && set.size > 0\n }\n\n /**\n * Surface an event: to local listeners (firehose + `on`), and — in hosted\n * iframe mode — to the embedding parent as `arkyc:event` so the SDK's\n * `handle.on(...)` works across the frame.\n */\n private dispatch(name: string, data?: unknown): void {\n this.emit(name, data)\n // The parent window is the consumer in iframe mode, so forwarding there isn't\n // gated on a local listener (post() already no-ops when not embedding).\n this.post('arkyc:event', { name, data })\n }\n\n /** Emit an event to the firehose + named listeners — but only if one is active. */\n private emit(name: string, data?: unknown): void {\n if (!this.hasListener(name)) return\n try {\n this.config.onEvent?.({ name, data })\n } catch {\n // A consumer callback threw — never let it break the flow.\n }\n const set = this.listeners.get(name)\n if (set) {\n for (const listener of [...set]) {\n try {\n listener(data)\n } catch {\n // ditto\n }\n }\n }\n }\n\n /** Record an observed status, emitting `session.transition` on a real change. */\n private observe(status: VerificationStatus): void {\n if (status === this.lastStatus) return\n const previous = this.lastStatus\n this.lastStatus = status\n this.dispatch(REALTIME_EVENT.sessionTransition, {\n session_id: this.sessionId,\n status,\n previous_status: previous,\n } satisfies Partial<SessionTransitionEvent>)\n }\n\n /**\n * Lazily connect the push transport for this session (once). Returns null for\n * `polling`/`off`/`memory` or when the transport SDK can't load — the caller\n * then polls instead.\n */\n private async resolveRealtime(): Promise<WidgetRealtimeClient | null> {\n if (this.rtResolved) return this.rtClient\n this.rtResolved = true\n const cfg = this.realtimeConfig\n if (!cfg || cfg.transport === 'polling' || cfg.transport === 'off' || cfg.transport === 'memory') return null\n\n const factory = this.config.realtimeFactory ?? createWidgetRealtimeClient\n // Build from the client's *resolved* base (absolute/proxy value, or the\n // hosted default when `baseUrl` was omitted) — never the raw option, which\n // is empty in the zero-config case and would resolve against the page origin.\n const apiBase = this.client.base\n try {\n this.rtClient = await factory(cfg, {\n authEndpoint: `${apiBase}/realtime/auth`,\n token: this.config.token,\n })\n } catch {\n this.rtClient = null\n }\n\n return this.rtClient\n }\n\n private handlers(): ViewHandlers {\n return {\n onClose: () => this.finishClose(),\n // The session was already fetched + resolved during bootstrap; just advance.\n onStart: () => void this.run(() => this.enter(this.next())),\n onDocumentSelected: (type, country) => {\n this.documentType = type\n this.country = country || null\n void this.run(() => this.enter('front_capture'))\n },\n onImage: (blob) => void this.run(() => this.onImage(blob)),\n onActiveLiveness: (video, performed, selfie) =>\n void this.run(async () => {\n await this.client.submitLiveness({\n selfie,\n video,\n mode: 'active',\n challenges: performed,\n signals: this.config.signals,\n })\n await this.enter(this.next())\n }),\n onAddress: (data) => void this.run(() => this.onAddress(data)),\n onShareLocation: () => this.shareLocation(),\n onAcknowledge: () => this.finishResult(),\n onUsePhone: () => void this.run(() => this.startHandoff()),\n onContinueHere: () => {\n this.handoffActive = false\n this.stopWatch?.()\n this.step = 'welcome'\n this.render()\n },\n }\n }\n\n /**\n * Fetch the session and route: an already-terminal session (e.g. a stale handoff\n * link, or one finished on another device) shows a close-only notice; on a\n * desktop with handoff enabled we lead with the QR; otherwise the welcome flow.\n */\n private async bootstrap(): Promise<void> {\n if (this.settled) return\n const session = await this.client.getSession()\n if (this.settled) return\n this.sessionId = session.id\n this.realtimeConfig = session.realtime ?? null\n this.workflow = session.workflow ?? null\n // Theme from the project's branding (server-resolved) unless the integrator\n // passed branding explicitly — their value wins.\n if (this.config.branding == null && session.branding)\n this.view.applyBranding(rebaseBrandingLogo(session.branding, this.config.baseUrl))\n this.observe(session.status)\n if (Flow.isTerminal(session.status)) return this.showTerminal(session.status)\n this.resolveLiveness(session)\n if (session.handoff) this.handoffConfig = session.handoff\n const canHandoff =\n this.config.handoff !== false && isDesktopDevice(this.nav) && this.handoffConfig.enabled && !!this.handoffTarget()\n if (canHandoff) {\n this.handoffReady = true\n await this.startHandoff()\n } else {\n this.step = 'welcome'\n this.render()\n }\n }\n\n /** The hosted handoff page URL: a consumer override, else the server-provided one. */\n private handoffTarget(): string {\n return (this.config.handoffUrl ?? this.handoffConfig.url ?? '').trim()\n }\n\n /** Render the QR for this session and wait for the other device to finish. */\n private async startHandoff(): Promise<void> {\n const target = this.handoffTarget()\n if (!target) return\n this.handoffActive = true\n // Offer \"continue on this device\" only when the project permits it.\n this.view.renderHandoff(renderQrSvg(this.buildHandoffUrl(target)), this.handoffConfig.allow_desktop)\n await this.pollHandoff()\n }\n\n /** Build the hosted-page URL the QR encodes (carries the session token).\n *\n * @param target\n * @returns\n */\n private buildHandoffUrl(target: string): string {\n const pageOrigin = this.win.location?.origin ?? globalThis.location?.origin ?? ''\n return composeHandoffUrl(target, this.config.token, this.client.base, pageOrigin)\n }\n\n /**\n * Watch the session while the user verifies on the other\n * device; mirror the result.\n *\n * @returns\n */\n private async pollHandoff(): Promise<void> {\n const rt = await this.resolveRealtime()\n if (rt) {\n const status = await this.pushWatch(rt, this.maxHandoffPolls, () => this.handoffActive && !this.settled)\n if (status) {\n this.handoffActive = false\n return this.showResult(status)\n }\n\n return // cancelled (continue here) or budget elapsed → the QR persists\n }\n\n let errors = 0\n for (let i = 0; i < this.maxHandoffPolls && this.handoffActive && !this.settled; i++) {\n await this.delay(this.pollMs)\n if (!this.handoffActive || this.settled) return\n try {\n const session = await this.client.getSession()\n errors = 0\n this.observe(session.status)\n if (Flow.isTerminal(session.status)) {\n this.handoffActive = false\n return this.showResult(session.status)\n }\n } catch (error) {\n // The session/token can expire mid-wait (401) — stop and reflect that.\n if (error instanceof WidgetApiError && error.status === 401) {\n this.handoffActive = false\n return this.showResult('expired')\n }\n if (++errors >= 5) return\n }\n }\n }\n\n /**\n * Await a terminal status over the push transport. Resolves to the terminal\n * status, or null if `active()` went false or the tick budget elapsed (push can\n * miss events / disconnect; the budget bounds the wait). An initial fetch\n * catches a session that finished before we subscribed.\n *\n * @param rt\n * @param budget\n * @param active\n * @returns\n */\n private pushWatch(\n rt: WidgetRealtimeClient,\n budget: number,\n active: () => boolean,\n ): Promise<VerificationStatus | null> {\n return new Promise((resolve) => {\n let done = false\n const finish = (status: VerificationStatus | null) => {\n if (done) return\n done = true\n this.stopWatch = undefined\n unsubscribe()\n resolve(status)\n }\n const channel = this.realtimeConfig!.channel\n const unsubscribe = rt.subscribe(channel, (event, data) => {\n if (event !== REALTIME_EVENT.sessionTransition) return\n const status = (data as SessionTransitionEvent).status\n this.observe(status)\n if (Flow.isTerminal(status)) finish(status)\n })\n this.stopWatch = () => finish(null)\n // Catch a race where the session already finished before we subscribed.\n void this.client\n .getSession()\n .then((session) => {\n this.observe(session.status)\n if (Flow.isTerminal(session.status)) finish(session.status)\n })\n .catch((error) => {\n if (error instanceof WidgetApiError && error.status === 401) finish('expired')\n })\n // Bound the wait by the same tick budget the poller would use.\n const countdown = async (n: number): Promise<void> => {\n if (done) return\n if (!active() || n >= budget) return finish(null)\n await this.delay(this.pollMs)\n void countdown(n + 1)\n }\n void countdown(0)\n })\n }\n\n /**\n * Resolve which liveness flow to run from the session's capture model.\n *\n * @param session\n */\n private resolveLiveness(session: ClientSession): void {\n this.livenessChallenges = session.liveness_challenges ?? []\n const model = session.capture_model ?? 'passive'\n this.captureModel = model\n const wantsActive = model === 'active' || model === 'both'\n // `active` is honoured even without a live camera — the active-liveness\n // screen then shows the device as unsupported (no fallback). `both` falls\n // back to passive when the camera/recorder isn't available.\n this.livenessMode = wantsActive && (model === 'active' || this.view.cameraSupported) ? 'active' : 'passive'\n }\n\n private async onImage(blob: Blob | null): Promise<void> {\n const signals = this.config.signals\n switch (this.step) {\n case 'front_capture':\n await this.client.submitDocumentFront({\n image: blob,\n country: this.country,\n documentType: this.documentType,\n signals,\n })\n return this.enter(this.next())\n case 'back_capture':\n await this.client.submitDocumentBack({\n image: blob,\n country: this.country,\n documentType: this.documentType,\n })\n return this.enter('ocr_processing')\n case 'selfie_capture':\n this.selfie = blob\n return this.enter('passive_liveness')\n }\n }\n\n /**\n * Enter a step: render it, and drive any automatic (processing) work.\n *\n * @param step\n * @returns\n */\n private async enter(step: WidgetStep): Promise<void> {\n if (this.settled) return\n this.step = step\n\n // Lead each capture/liveness step with an instruction screen so the user\n // knows what's next and starts it deliberately. The Continue button re-enters\n // the same step with the instruction acknowledged.\n const instruction = STEP_INSTRUCTIONS[step]\n if (instruction && !this.instructionAcked) {\n this.view.renderInstruction(step, instruction.title, instruction.body, instruction.cta ?? 'Continue', () => {\n this.instructionAcked = true\n void this.run(() => this.enter(step))\n })\n return\n }\n this.instructionAcked = false\n this.render()\n\n switch (step) {\n case 'ocr_processing':\n await this.delay(this.transientMs)\n return this.enter(this.next())\n case 'address_processing':\n await this.submitAddress()\n return this.enter(this.next())\n case 'passive_liveness':\n await this.client.submitLiveness({ selfie: this.selfie, signals: this.config.signals })\n return this.enter(this.next())\n case 'face_match':\n await this.client.complete({ signals: this.config.signals })\n return this.enter(this.next())\n case 'processing':\n return this.poll()\n }\n }\n\n /** Watch the session until it reaches a terminal status (then show the result).\n *\n * @returns\n */\n private async poll(): Promise<void> {\n const rt = await this.resolveRealtime()\n if (rt) {\n const status = await this.pushWatch(rt, this.maxPolls, () => !this.settled)\n\n return this.showResult(status ?? 'requires_review')\n }\n\n for (let i = 0; i < this.maxPolls && !this.settled; i++) {\n const session = await this.client.getSession()\n this.observe(session.status)\n if (Flow.isTerminal(session.status)) return this.showResult(session.status)\n await this.delay(this.pollMs)\n }\n // Still processing after the poll budget — surface as pending review.\n this.showResult('requires_review')\n }\n\n private showResult(status: VerificationStatus): void {\n this.result = { status, decision: Flow.statusToDecision(status) }\n this.step = 'result'\n this.render()\n }\n\n /**\n * A session that was already terminal on load: a notice with only a Close button.\n *\n * @param status\n */\n private showTerminal(status: VerificationStatus): void {\n this.result = { status, decision: Flow.statusToDecision(status) }\n this.step = 'result'\n this.terminalOnLoad = true\n this.render()\n }\n\n private render(): void {\n this.view.render({\n step: this.step,\n documentType: this.documentType,\n decision: this.result?.decision,\n status: this.result?.status,\n terminalNotice: this.terminalOnLoad,\n allowSkip: !!this.config.signals,\n livenessChallenges: this.livenessChallenges,\n requireLiveCamera: this.captureModel === 'active',\n // The active flow is strict end-to-end: documents must be detected, not\n // manually waved through.\n strictCapture: this.livenessMode === 'active',\n handoffAvailable: this.handoffReady,\n addressMethods: workflowAddressConfig(this.workflow)?.methods ?? [],\n })\n }\n\n /**\n * Store the entered address, resolve device location if opted in, then verify.\n *\n * @param data\n * @returns\n */\n private async onAddress(data: AddressFormData): Promise<void> {\n this.addressData = data\n // Coordinates are normally resolved up front by `shareLocation` when the user\n // ticks \"use my location\" (so the prompt is immediate); geolocate here only as\n // a fallback, and clear them if they opted out.\n if (!data.useLocation) this.addressCoords = null\n else if (!this.addressCoords) this.addressCoords = await this.geolocate()\n\n return this.enter('address_processing')\n }\n\n /** Request the device's location (from the address-entry \"use my location\" opt-in). */\n private async shareLocation(): Promise<boolean> {\n this.addressCoords = await this.geolocate()\n\n return !!this.addressCoords\n }\n\n /**\n * Submit the captured address evidence to the Client API.\n *\n * @returns\n */\n private async submitAddress(): Promise<void> {\n const data = this.addressData\n if (!data) return\n\n await this.client.submitAddress({\n line1: data.line1,\n line2: data.line2,\n city: data.city,\n region: data.region,\n postalCode: data.postalCode,\n country: data.country,\n poa: data.poa,\n latitude: this.addressCoords?.latitude ?? null,\n longitude: this.addressCoords?.longitude ?? null,\n signals: this.config.signals,\n })\n }\n\n /**\n * Resolve the device's coordinates via the Geolocation API. Resolves to `null`\n * on denial/unavailability/timeout — the `device_location` method then simply\n * has no coordinates to check (routing the session to review).\n *\n * @returns\n */\n private geolocate(): Promise<{ latitude: number; longitude: number } | null> {\n const geo = this.nav?.geolocation\n if (!geo) return Promise.resolve(null)\n\n return new Promise((resolve) => {\n geo.getCurrentPosition(\n (pos) => resolve({ latitude: pos.coords.latitude, longitude: pos.coords.longitude }),\n () => resolve(null),\n { enableHighAccuracy: false, timeout: 10_000, maximumAge: 60_000 },\n )\n })\n }\n\n private next(): WidgetStep {\n return Flow.nextStep(this.step, {\n documentType: this.documentType,\n livenessMode: this.livenessMode,\n workflow: this.workflow,\n })\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => this.scheduler(resolve, ms))\n }\n\n /** Run an async step, routing any failure to the error screen. */\n private async run(fn: () => Promise<void> | void): Promise<void> {\n try {\n await fn()\n } catch (error) {\n this.fail(error)\n }\n }\n\n private fail(error: unknown): void {\n if (this.settled) return\n const err = error instanceof Error ? error : new Error(String(error))\n this.step = 'result'\n\n this.view.render({\n step: 'result',\n documentType: this.documentType,\n errorMessage: err.message,\n })\n\n this.pendingError = err\n }\n\n private finishResult(): void {\n if (this.settled) return\n this.settled = true\n this.teardownRealtime()\n\n if (this.pendingError) {\n this.post('arkyc:error', { error: serializeError(this.pendingError) })\n this.dispatch('error', { message: this.pendingError.message })\n this.config.onError?.(this.pendingError)\n } else if (this.result) {\n this.post('arkyc:complete', { payload: this.result })\n this.dispatch('complete', this.result)\n this.config.onComplete?.(this.result)\n }\n\n this.destroy()\n this.config.onSettle?.()\n }\n\n private finishClose(): void {\n if (this.settled) return\n\n this.settled = true\n this.teardownRealtime()\n this.post('arkyc:close', {})\n this.dispatch('close')\n this.config.onClose?.()\n this.destroy()\n this.config.onSettle?.()\n }\n\n /** Stop any active session watch + disconnect the push transport. */\n private teardownRealtime(): void {\n this.stopWatch?.()\n this.rtClient?.disconnect()\n this.rtClient = null\n }\n\n private post(type: string, extra: Record<string, unknown>): void {\n if (!this.postToParent) return\n try {\n this.win.parent?.postMessage({ type, ...extra }, '*')\n } catch {\n // Cross-origin parent without access — nothing more we can do.\n }\n }\n}\n\nfunction serializeError(error: Error): { message: string; name: string } {\n return { message: error.message, name: error.name }\n}\n\n/** The origin of an absolute URL, or '' when it can't be parsed. */\nfunction originOf(url: string): string {\n try {\n return new URL(url).origin\n } catch {\n return ''\n }\n}\n\n/**\n * Build the hosted-page URL the handoff QR encodes: always the `target` page\n * plus the session `token`, and the API base **only when the phone can actually\n * reach it** — i.e. a genuine cross-origin base. `apiBase` is the widget's\n * resolved Client API base (always origin-qualified), so a relative proxy\n * resolves to the desktop's own `pageOrigin` and is skipped (the hosted page\n * then falls back to its own default base instead of a URL the phone can't use).\n */\nexport const composeHandoffUrl = (target: string, token: string, apiBase: string, pageOrigin: string): string => {\n const sep = target.includes('?') ? '&' : '?'\n let url = `${target}${sep}token=${encodeURIComponent(token)}`\n const baseOrigin = originOf(apiBase)\n if (baseOrigin && baseOrigin !== pageOrigin) url += `&baseUrl=${encodeURIComponent(apiBase)}`\n return url\n}\n\nexport const resolveContainer = (container: string | HTMLElement, doc: Document): HTMLElement => {\n const el = typeof container === 'string' ? doc.querySelector<HTMLElement>(container) : container\n if (!el) throw new Error(`ArkycWidget.mount: container \"${String(container)}\" not found.`)\n return el\n}\n\n/**\n * Make an inline mount point a reliable host for the widget, so `mount()` renders\n * predictably regardless of the page layout — a static or zero-height container,\n * or one sharing the viewport with an app root that would otherwise paint over it.\n *\n * The widget gives the host its own stacking context and lifts it above sibling\n * app content, the way `open()` owns its overlay. Each property is only set when\n * the host hasn't already committed to one (checked against computed style where\n * available, else inline style), so an embed that wants normal flow or a specific\n * layer can still override it with its own CSS.\n */\nexport const adoptContainer = (container: HTMLElement, doc: Document): void => {\n const computed = doc.defaultView?.getComputedStyle?.(container)\n const position = computed?.position ?? container.style.position\n if (!position || position === 'static') container.style.position = 'relative'\n\n const zIndex = computed?.zIndex ?? container.style.zIndex\n if (!zIndex || zIndex === 'auto') container.style.zIndex = '1'\n\n // A self-contained stacking context keeps the widget's layers from bleeding\n // into (or being clipped by) the surrounding page.\n if (!container.style.isolation) container.style.isolation = 'isolate'\n}\n\nexport const buildController = (options: BaseWidgetOptions, onSettle: () => void): WidgetController => {\n if (!options.token) throw new Error('ArkycWidget requires a client `token`.')\n return new WidgetController({\n token: options.token,\n baseUrl: options.baseUrl,\n handoff: options.handoff,\n handoffUrl: options.handoffUrl,\n branding: options.branding,\n signals: options.signals,\n onComplete: options.onComplete,\n onError: options.onError,\n onClose: options.onClose,\n onEvent: options.onEvent,\n onSettle,\n fetch: options.fetch,\n doc: options.doc,\n win: options.win,\n nav: options.nav,\n faceAnalyzer: options.faceAnalyzer,\n faceTuning: options.faceTuning,\n documentAnalyzer: options.documentAnalyzer,\n documentTuning: options.documentTuning,\n })\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkyc/widget",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
4
4
  "description": "Embeddable, framework-agnostic Arkyc identity-verification widget",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -40,7 +40,7 @@
40
40
  ],
41
41
  "dependencies": {
42
42
  "qrcode-generator": "^1.4.4",
43
- "@arkyc/types": "^1.2.7"
43
+ "@arkyc/types": "^1.2.8"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "firebase": "^11.0.0",