@velion-la/onboarding-sdk-web 1.0.0-beta.23 → 1.0.0-beta.25
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/velion-sdk.es.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=/* @__PURE__ */(e=>(e.PERMISSION_DENIED="CAMERA_PERMISSION_DENIED",e.DEVICE_NOT_FOUND="CAMERA_DEVICE_NOT_FOUND",e.DEVICE_NOT_READABLE="CAMERA_DEVICE_NOT_READABLE",e.INSECURE_CONTEXT="CAMERA_INSECURE_CONTEXT",e.POLICY_BLOCKED="CAMERA_POLICY_BLOCKED",e.NOT_SUPPORTED="CAMERA_NOT_SUPPORTED",e.WEBVIEW_BLOCKED="CAMERA_WEBVIEW_BLOCKED",e.CONSTRAINT_ERROR="CAMERA_CONSTRAINT_ERROR",e.UNKNOWN="CAMERA_UNKNOWN_ERROR",e))(e||{}),t=/* @__PURE__ */(e=>(e.SESSION_INVALID="SESSION_INVALID",e.SESSION_EXPIRED="SESSION_EXPIRED",e.SESSION_FETCH_FAILED="SESSION_FETCH_FAILED",e.SESSION_DECLINED="SESSION_DECLINED",e.FINGERPRINT_FAILED="FINGERPRINT_FAILED",e.DOCUMENT_UPLOAD_FAILED="DOCUMENT_UPLOAD_FAILED",e.LIVENESS_FAILED="LIVENESS_FAILED",e.LIVENESS_INIT_FAILED="LIVENESS_INIT_FAILED",e.LIVENESS_VIDEO_UPLOAD_FAILED="LIVENESS_VIDEO_UPLOAD_FAILED",e.CAMERA_ERROR="CAMERA_ERROR",e.NETWORK_ERROR="NETWORK_ERROR",e.API_ERROR="API_ERROR",e.CONTAINER_NOT_FOUND="CONTAINER_NOT_FOUND",e.ALREADY_STARTED="ALREADY_STARTED",e.INSTANCE_DESTROYED="INSTANCE_DESTROYED",e.INSECURE_EMBED="INSECURE_EMBED",e.INTEGRITY_FAILED="INTEGRITY_FAILED",e.RUNTIME_LOAD_FAILED="RUNTIME_LOAD_FAILED",e.UNKNOWN="UNKNOWN",e))(t||{});const i="VELION_INIT",n="VELION_READY",s="VELION_SUCCESS",r="VELION_ERROR",a="VELION_STEP_CHANGE",o="VELION_STEP_COMPLETE",c="VELION_TOKEN_EXPIRED",E="VELION_EXIT",l="VELION_LIVENESS_VIDEO_UPLOADED",d="VELION_TOKEN",h="lpqLG4Ta0YyBn/+lQh95tFl4CuTIHWapeeq7+xnPH+U=";function I(e){const t=atob(e),i=new ArrayBuffer(t.length),n=new Uint8Array(i);for(let s=0;s<t.length;s++)n[s]=t.charCodeAt(s);return i}async function u(e,t=h){let i,n,s,r,a,o;try{const t=await fetch(`${e}/manifest.json`,{credentials:"omit",cache:"no-cache",redirect:"error"});if(404===t.status)return{ok:!1,reason:"unavailable"};if(!t.ok)return{ok:!1,reason:"unavailable",detail:`HTTP ${t.status}`};i=await t.arrayBuffer(),n=(new TextDecoder).decode(i)}catch(c){return{ok:!1,reason:"unavailable",detail:c.message}}try{if(s=JSON.parse(n),"string"!=typeof s.version||"string"!=typeof s.entry||"string"!=typeof s.buildTime)return{ok:!1,reason:"malformed"}}catch(c){return{ok:!1,reason:"malformed",detail:c.message}}if(!t)return{ok:!0,manifest:s,signed:!1};try{const t=await fetch(`${e}/manifest.sig`,{credentials:"omit",cache:"no-cache",redirect:"error"});if(!t.ok)return{ok:!1,reason:"invalid_signature",detail:`sig HTTP ${t.status}`};r=I((await t.text()).trim())}catch(c){return{ok:!1,reason:"invalid_signature",detail:c.message}}try{a=await async function(e){const t=I(e);return crypto.subtle.importKey("raw",t,{name:"Ed25519"},!1,["verify"])}(t)}catch(c){return{ok:!1,reason:"invalid_signature",detail:`pubkey import failed: ${c.message}`}}try{o=await async function(e,t,i){return crypto.subtle.verify({name:"Ed25519"},e,t,i)}(a,r,i)}catch(c){return{ok:!1,reason:"invalid_signature",detail:c.message}}return o?{ok:!0,manifest:s,signed:!0}:{ok:!1,reason:"invalid_signature"}}const f="https://static.velion.la/sdk/onboarding/v1";function R(e){try{const i=new URL(e);return"https:"===i.protocol||"http:"===i.protocol&&("localhost"===(t=i.hostname)||"127.0.0.1"===t||"::1"===t||t.endsWith(".local"))}catch{return!1}var t}const N=["containerId","sessionId","sessionToken","apiUrl","expiresAt","theme","colorMode","locale","allowEmbedded","livenessShowIntro","livenessShowBackButton","fingerprintEndpoint","debug","googleMapsApiKey","showFinalResultScreen","showProgressBar","finalResultScreenTexts"];class m{constructor(e){this.status="IDLE",this.iframe=null,this.container=null,this.messageHandler=null,this.containerResizeObserver=null,this.iframeHeightWindowResizeHandler=null,this.cdnOrigin="",this.readyTimer=null,this.config=Object.freeze({...e})}clearReadyTimer(){null!==this.readyTimer&&(clearTimeout(this.readyTimer),this.readyTimer=null)}armReadyTimer(e){this.clearReadyTimer();const i=this.config.runtimeReadyTimeoutMs??3e4;i>0&&(this.readyTimer=setTimeout(()=>{this.readyTimer=null,"RUNNING"===this.status&&(this.status="ERROR",this.config.onError?.({code:t.RUNTIME_LOAD_FAILED,message:`The onboarding runtime at ${e} did not become ready within ${i} ms. Check that the CDN serves the assets referenced by its index.html and that the host page's Content-Security-Policy allows the runtime scripts.`}))},i))}async start(){if("DESTROYED"===this.status)return void this.config.onError?.({code:t.INSTANCE_DESTROYED,message:"This VelionOnboarding instance has been destroyed. Create a new instance to retry."});if("IDLE"!==this.status)return void this.config.onError?.({code:t.ALREADY_STARTED,message:"start() was called on an already-running SDK instance. Call destroy() before re-starting."});const e=document.getElementById(this.config.containerId);if(!e)return void this.config.onError?.({code:t.CONTAINER_NOT_FOUND,message:`No DOM element found with id="${this.config.containerId}". Ensure the element exists before calling start().`});const h=(this.config.cdnUrl??f).replace(/\/$/,"");if(!R(h))return void this.config.onError?.({code:t.UNKNOWN,message:`Insecure cdnUrl: "${h}". Must be https:// (http:// is allowed only for localhost).`});if(this.config.apiUrl&&!R(this.config.apiUrl))return void this.config.onError?.({code:t.UNKNOWN,message:`Insecure apiUrl: "${this.config.apiUrl}". Must be https:// (http:// is allowed only for localhost).`});if(this.cdnOrigin=function(e){try{return new URL(e).origin}catch{return""}}(h),!this.cdnOrigin)return void this.config.onError?.({code:t.UNKNOWN,message:`Invalid cdnUrl: ${h}. Must be a valid absolute URL.`});this.container=e,this.status="INITIALIZING";const I=document.createElement("iframe");I.setAttribute("title","Velion Onboarding"),I.setAttribute("allow","camera"),I.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-modals"),I.setAttribute("referrerpolicy","no-referrer"),I.style.width="100%",I.style.border="none",I.style.display="block";const u=e=>{if(e.origin!==this.cdnOrigin)return;if(e.source!==this.iframe?.contentWindow)return;const i=e.data;if(i&&"string"==typeof i.type)switch(i.type){case n:this.clearReadyTimer(),this.status="RUNNING";break;case s:this.status="COMPLETED",this.config.onSuccess?.(i.payload);break;case r:this.status="ERROR",this.config.onError?.(i.payload);break;case a:{const e=i.payload;this.config.onStepChange?.(e.step,e.stepIndex);break}case o:{const e=i.payload;this.config.onStepComplete?.(e.step);break}case E:this.status="EXITED",this.config.onExit?.(i.payload);break;case l:this.config.onLivenessVideoUploaded?.(i.payload);break;case c:{const e=this.config.onTokenExpired;e?e().then(e=>this.sendToIframe({type:d,payload:e}),()=>{this.config.onError?.({code:t.SESSION_EXPIRED,message:"Token refresh failed."})}):this.config.onError?.({code:t.SESSION_EXPIRED,message:"Session token expired. Provide onTokenExpired in config to refresh."});break}}};window.addEventListener("message",u),this.messageHandler=u,this.iframe=I,e.replaceChildren(I),this.attachIframeHeightSync(e,I);const m=await this.resolveManifest(h);if(!m)return;const g=m.entry.startsWith("/")?m.entry:`/${m.entry}`;return new Promise(e=>{I.onload=()=>{if(!this.iframe?.contentWindow)return this.status="ERROR",this.config.onError?.({code:t.UNKNOWN,message:"Iframe failed to load."}),void e();const n=function(e){const t={containerId:"velion-sdk-root",allowEmbedded:!0};for(const i of N){if("containerId"===i||"allowEmbedded"===i)continue;const n=e[i];void 0!==n&&"function"!=typeof n&&(t[i]=n)}return t}(this.config);let s;try{s=JSON.parse(JSON.stringify(n))}catch{s=n}this.sendToIframe({type:i,payload:s}),this.status="RUNNING",this.armReadyTimer(h),e()},I.onerror=()=>{this.status="ERROR",this.config.onError?.({code:t.UNKNOWN,message:`Failed to load onboarding app from ${h}. Check cdnUrl and network.`}),e()},I.src=`${h}${g}`})}async resolveManifest(e){let i;try{i=await u(e)}catch(n){return this.status="ERROR",this.config.onError?.({code:t.INTEGRITY_FAILED,message:`Manifest verification crashed unexpectedly: ${n.message}`}),null}if(i.ok)return!i.signed&&h?(this.status="ERROR",this.config.onError?.({code:t.INTEGRITY_FAILED,message:"Internal: manifest reported as unsigned despite pubkey being set."}),null):i.manifest;if("unavailable"===i.reason){return!0!==this.config.allowUnverifiedCdn?(this.status="ERROR",this.config.onError?.({code:t.INTEGRITY_FAILED,message:`CDN at "${e}" did not expose /manifest.json. The loader is configured to require a signed manifest. If the CDN is migrating, set \`allowUnverifiedCdn: true\` on the loader config — but only as a temporary measure.`}),null):{version:"legacy",buildTime:/* @__PURE__ */(new Date).toISOString(),entry:"/index.html"}}return this.status="ERROR",this.config.onError?.({code:t.INTEGRITY_FAILED,message:`CDN manifest at "${e}/manifest.json" failed integrity check (${i.reason}). Refusing to load the iframe to protect the user. `+("detail"in i&&i.detail?`Detail: ${i.detail}`:"")}),null}attachIframeHeightSync(e,t){const i=()=>{if(this.iframe!==t||this.container!==e)return;const i=e.clientHeight;i>0?(t.style.height=`${i}px`,t.style.minHeight=""):(t.style.height="min(85vh, 880px)",t.style.minHeight="")};i(),requestAnimationFrame(i),requestAnimationFrame(()=>{requestAnimationFrame(i)}),"undefined"!=typeof ResizeObserver?(this.containerResizeObserver=new ResizeObserver(i),this.containerResizeObserver.observe(e)):(this.iframeHeightWindowResizeHandler=i,window.addEventListener("resize",i))}detachIframeHeightSync(){this.containerResizeObserver&&(this.containerResizeObserver.disconnect(),this.containerResizeObserver=null),this.iframeHeightWindowResizeHandler&&(window.removeEventListener("resize",this.iframeHeightWindowResizeHandler),this.iframeHeightWindowResizeHandler=null)}sendToIframe(e){this.iframe?.contentWindow&&this.iframe.contentWindow.postMessage(e,this.cdnOrigin)}getStatus(){return this.status}static getVersion(){return"1.0.0-beta.23"}destroy(){"DESTROYED"!==this.status&&(this.clearReadyTimer(),this.detachIframeHeightSync(),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.iframe&&this.container&&(this.iframe.remove(),this.container.replaceChildren()),this.iframe=null,this.container=null,this.status="DESTROYED")}}function g(e){}export{e as CameraErrorCode,f as DEFAULT_CDN_URL,t as SdkErrorCode,r as VELION_MSG_ERROR,E as VELION_MSG_EXIT,i as VELION_MSG_INIT,l as VELION_MSG_LIVENESS_VIDEO_UPLOADED,n as VELION_MSG_READY,a as VELION_MSG_STEP_CHANGE,o as VELION_MSG_STEP_COMPLETE,s as VELION_MSG_SUCCESS,d as VELION_MSG_TOKEN,c as VELION_MSG_TOKEN_EXPIRED,m as VelionOnboarding,g as setOpenCVBaseUrl};
|
|
1
|
+
var e=/* @__PURE__ */(e=>(e.PERMISSION_DENIED="CAMERA_PERMISSION_DENIED",e.DEVICE_NOT_FOUND="CAMERA_DEVICE_NOT_FOUND",e.DEVICE_NOT_READABLE="CAMERA_DEVICE_NOT_READABLE",e.INSECURE_CONTEXT="CAMERA_INSECURE_CONTEXT",e.POLICY_BLOCKED="CAMERA_POLICY_BLOCKED",e.NOT_SUPPORTED="CAMERA_NOT_SUPPORTED",e.WEBVIEW_BLOCKED="CAMERA_WEBVIEW_BLOCKED",e.CONSTRAINT_ERROR="CAMERA_CONSTRAINT_ERROR",e.UNKNOWN="CAMERA_UNKNOWN_ERROR",e))(e||{}),t=/* @__PURE__ */(e=>(e.SESSION_INVALID="SESSION_INVALID",e.SESSION_EXPIRED="SESSION_EXPIRED",e.SESSION_FETCH_FAILED="SESSION_FETCH_FAILED",e.SESSION_DECLINED="SESSION_DECLINED",e.ROUTING_TIMEOUT="ROUTING_TIMEOUT",e.FINGERPRINT_FAILED="FINGERPRINT_FAILED",e.DOCUMENT_UPLOAD_FAILED="DOCUMENT_UPLOAD_FAILED",e.LIVENESS_FAILED="LIVENESS_FAILED",e.LIVENESS_INIT_FAILED="LIVENESS_INIT_FAILED",e.LIVENESS_VIDEO_UPLOAD_FAILED="LIVENESS_VIDEO_UPLOAD_FAILED",e.CAMERA_ERROR="CAMERA_ERROR",e.NETWORK_ERROR="NETWORK_ERROR",e.API_ERROR="API_ERROR",e.CONTAINER_NOT_FOUND="CONTAINER_NOT_FOUND",e.ALREADY_STARTED="ALREADY_STARTED",e.INSTANCE_DESTROYED="INSTANCE_DESTROYED",e.INSECURE_EMBED="INSECURE_EMBED",e.INTEGRITY_FAILED="INTEGRITY_FAILED",e.RUNTIME_LOAD_FAILED="RUNTIME_LOAD_FAILED",e.UNKNOWN="UNKNOWN",e))(t||{});const i="VELION_INIT",n="VELION_READY",s="VELION_SUCCESS",r="VELION_ERROR",a="VELION_STEP_CHANGE",o="VELION_STEP_COMPLETE",E="VELION_TOKEN_EXPIRED",c="VELION_EXIT",l="VELION_LIVENESS_VIDEO_UPLOADED",d="VELION_TOKEN",h="lpqLG4Ta0YyBn/+lQh95tFl4CuTIHWapeeq7+xnPH+U=";function I(e){const t=atob(e),i=new ArrayBuffer(t.length),n=new Uint8Array(i);for(let s=0;s<t.length;s++)n[s]=t.charCodeAt(s);return i}async function u(e,t=h){let i,n,s,r,a,o;try{const t=await fetch(`${e}/manifest.json`,{credentials:"omit",cache:"no-cache",redirect:"error"});if(404===t.status)return{ok:!1,reason:"unavailable"};if(!t.ok)return{ok:!1,reason:"unavailable",detail:`HTTP ${t.status}`};i=await t.arrayBuffer(),n=(new TextDecoder).decode(i)}catch(E){return{ok:!1,reason:"unavailable",detail:E.message}}try{if(s=JSON.parse(n),"string"!=typeof s.version||"string"!=typeof s.entry||"string"!=typeof s.buildTime)return{ok:!1,reason:"malformed"}}catch(E){return{ok:!1,reason:"malformed",detail:E.message}}if(!t)return{ok:!0,manifest:s,signed:!1};try{const t=await fetch(`${e}/manifest.sig`,{credentials:"omit",cache:"no-cache",redirect:"error"});if(!t.ok)return{ok:!1,reason:"invalid_signature",detail:`sig HTTP ${t.status}`};r=I((await t.text()).trim())}catch(E){return{ok:!1,reason:"invalid_signature",detail:E.message}}try{a=await async function(e){const t=I(e);return crypto.subtle.importKey("raw",t,{name:"Ed25519"},!1,["verify"])}(t)}catch(E){return{ok:!1,reason:"invalid_signature",detail:`pubkey import failed: ${E.message}`}}try{o=await async function(e,t,i){return crypto.subtle.verify({name:"Ed25519"},e,t,i)}(a,r,i)}catch(E){return{ok:!1,reason:"invalid_signature",detail:E.message}}return o?{ok:!0,manifest:s,signed:!0}:{ok:!1,reason:"invalid_signature"}}const f="https://static.velion.la/sdk/onboarding/v1";function R(e){try{const i=new URL(e);return"https:"===i.protocol||"http:"===i.protocol&&("localhost"===(t=i.hostname)||"127.0.0.1"===t||"::1"===t||t.endsWith(".local"))}catch{return!1}var t}const N=["containerId","sessionId","sessionToken","apiUrl","expiresAt","theme","colorMode","locale","allowEmbedded","livenessShowIntro","livenessShowBackButton","fingerprintEndpoint","debug","googleMapsApiKey","showFinalResultScreen","showProgressBar","finalResultScreenTexts","routingTimeoutMs"];class m{constructor(e){this.status="IDLE",this.iframe=null,this.container=null,this.messageHandler=null,this.containerResizeObserver=null,this.iframeHeightWindowResizeHandler=null,this.cdnOrigin="",this.readyTimer=null,this.config=Object.freeze({...e})}clearReadyTimer(){null!==this.readyTimer&&(clearTimeout(this.readyTimer),this.readyTimer=null)}armReadyTimer(e){this.clearReadyTimer();const i=this.config.runtimeReadyTimeoutMs??3e4;i>0&&(this.readyTimer=setTimeout(()=>{this.readyTimer=null,"RUNNING"===this.status&&(this.status="ERROR",this.config.onError?.({code:t.RUNTIME_LOAD_FAILED,message:`The onboarding runtime at ${e} did not become ready within ${i} ms. Check that the CDN serves the assets referenced by its index.html and that the host page's Content-Security-Policy allows the runtime scripts.`}))},i))}async start(){if("DESTROYED"===this.status)return void this.config.onError?.({code:t.INSTANCE_DESTROYED,message:"This VelionOnboarding instance has been destroyed. Create a new instance to retry."});if("IDLE"!==this.status)return void this.config.onError?.({code:t.ALREADY_STARTED,message:"start() was called on an already-running SDK instance. Call destroy() before re-starting."});const e=document.getElementById(this.config.containerId);if(!e)return void this.config.onError?.({code:t.CONTAINER_NOT_FOUND,message:`No DOM element found with id="${this.config.containerId}". Ensure the element exists before calling start().`});const h=(this.config.cdnUrl??f).replace(/\/$/,"");if(!R(h))return void this.config.onError?.({code:t.UNKNOWN,message:`Insecure cdnUrl: "${h}". Must be https:// (http:// is allowed only for localhost).`});if(this.config.apiUrl&&!R(this.config.apiUrl))return void this.config.onError?.({code:t.UNKNOWN,message:`Insecure apiUrl: "${this.config.apiUrl}". Must be https:// (http:// is allowed only for localhost).`});if(this.cdnOrigin=function(e){try{return new URL(e).origin}catch{return""}}(h),!this.cdnOrigin)return void this.config.onError?.({code:t.UNKNOWN,message:`Invalid cdnUrl: ${h}. Must be a valid absolute URL.`});this.container=e,this.status="INITIALIZING";const I=document.createElement("iframe");I.setAttribute("title","Velion Onboarding"),I.setAttribute("allow","camera"),I.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-modals"),I.setAttribute("referrerpolicy","no-referrer"),I.style.width="100%",I.style.border="none",I.style.display="block";const u=e=>{if(e.origin!==this.cdnOrigin)return;if(e.source!==this.iframe?.contentWindow)return;const i=e.data;if(i&&"string"==typeof i.type)switch(this.clearReadyTimer(),i.type){case n:this.status="RUNNING";break;case s:this.status="COMPLETED",this.config.onSuccess?.(i.payload);break;case r:this.status="ERROR",this.config.onError?.(i.payload);break;case a:{const e=i.payload;this.config.onStepChange?.(e.step,e.stepIndex);break}case o:{const e=i.payload;this.config.onStepComplete?.(e.step);break}case c:this.status="EXITED",this.config.onExit?.(i.payload);break;case l:this.config.onLivenessVideoUploaded?.(i.payload);break;case E:{const e=this.config.onTokenExpired;e?e().then(e=>this.sendToIframe({type:d,payload:e}),()=>{this.config.onError?.({code:t.SESSION_EXPIRED,message:"Token refresh failed."})}):this.config.onError?.({code:t.SESSION_EXPIRED,message:"Session token expired. Provide onTokenExpired in config to refresh."});break}}};window.addEventListener("message",u),this.messageHandler=u,this.iframe=I,e.replaceChildren(I),this.attachIframeHeightSync(e,I);const m=await this.resolveManifest(h);if(!m)return;const g=m.entry.startsWith("/")?m.entry:`/${m.entry}`;return new Promise(e=>{I.onload=()=>{if(!this.iframe?.contentWindow)return this.status="ERROR",this.config.onError?.({code:t.UNKNOWN,message:"Iframe failed to load."}),void e();const n=function(e){const t={containerId:"velion-sdk-root",allowEmbedded:!0};for(const i of N){if("containerId"===i||"allowEmbedded"===i)continue;const n=e[i];void 0!==n&&"function"!=typeof n&&(t[i]=n)}return t}(this.config);let s;try{s=JSON.parse(JSON.stringify(n))}catch{s=n}this.sendToIframe({type:i,payload:s}),this.status="RUNNING",this.armReadyTimer(h),e()},I.onerror=()=>{this.status="ERROR",this.config.onError?.({code:t.UNKNOWN,message:`Failed to load onboarding app from ${h}. Check cdnUrl and network.`}),e()},I.src=`${h}${g}`})}async resolveManifest(e){let i;try{i=await u(e)}catch(n){return this.status="ERROR",this.config.onError?.({code:t.INTEGRITY_FAILED,message:`Manifest verification crashed unexpectedly: ${n.message}`}),null}if(i.ok)return!i.signed&&h?(this.status="ERROR",this.config.onError?.({code:t.INTEGRITY_FAILED,message:"Internal: manifest reported as unsigned despite pubkey being set."}),null):i.manifest;if("unavailable"===i.reason){return!0!==this.config.allowUnverifiedCdn?(this.status="ERROR",this.config.onError?.({code:t.INTEGRITY_FAILED,message:`CDN at "${e}" did not expose /manifest.json. The loader is configured to require a signed manifest. If the CDN is migrating, set \`allowUnverifiedCdn: true\` on the loader config — but only as a temporary measure.`}),null):{version:"legacy",buildTime:/* @__PURE__ */(new Date).toISOString(),entry:"/index.html"}}return this.status="ERROR",this.config.onError?.({code:t.INTEGRITY_FAILED,message:`CDN manifest at "${e}/manifest.json" failed integrity check (${i.reason}). Refusing to load the iframe to protect the user. `+("detail"in i&&i.detail?`Detail: ${i.detail}`:"")}),null}attachIframeHeightSync(e,t){const i=()=>{if(this.iframe!==t||this.container!==e)return;const i=e.clientHeight;i>0?(t.style.height=`${i}px`,t.style.minHeight=""):(t.style.height="min(85vh, 880px)",t.style.minHeight="")};i(),requestAnimationFrame(i),requestAnimationFrame(()=>{requestAnimationFrame(i)}),"undefined"!=typeof ResizeObserver?(this.containerResizeObserver=new ResizeObserver(i),this.containerResizeObserver.observe(e)):(this.iframeHeightWindowResizeHandler=i,window.addEventListener("resize",i))}detachIframeHeightSync(){this.containerResizeObserver&&(this.containerResizeObserver.disconnect(),this.containerResizeObserver=null),this.iframeHeightWindowResizeHandler&&(window.removeEventListener("resize",this.iframeHeightWindowResizeHandler),this.iframeHeightWindowResizeHandler=null)}sendToIframe(e){this.iframe?.contentWindow&&this.iframe.contentWindow.postMessage(e,this.cdnOrigin)}getStatus(){return this.status}static getVersion(){return"1.0.0-beta.25"}destroy(){"DESTROYED"!==this.status&&(this.clearReadyTimer(),this.detachIframeHeightSync(),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.iframe&&this.container&&(this.iframe.remove(),this.container.replaceChildren()),this.iframe=null,this.container=null,this.status="DESTROYED")}}function g(e){}export{e as CameraErrorCode,f as DEFAULT_CDN_URL,t as SdkErrorCode,r as VELION_MSG_ERROR,c as VELION_MSG_EXIT,i as VELION_MSG_INIT,l as VELION_MSG_LIVENESS_VIDEO_UPLOADED,n as VELION_MSG_READY,a as VELION_MSG_STEP_CHANGE,o as VELION_MSG_STEP_COMPLETE,s as VELION_MSG_SUCCESS,d as VELION_MSG_TOKEN,E as VELION_MSG_TOKEN_EXPIRED,m as VelionOnboarding,g as setOpenCVBaseUrl};
|
|
2
2
|
//# sourceMappingURL=velion-sdk.es.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"velion-sdk.es.js","sources":["../../packages/types/src/index.ts","../src/manifest.ts","../src/velion-loader.ts","../src/index.ts"],"sourcesContent":["/**\n * @fileoverview Public TypeScript contracts for the Velion Onboarding SDK.\n *\n * Design principle: All types exported from this file constitute the public API.\n * Internal implementation types should live alongside their respective modules.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// STEP & FLOW TYPES\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The complete set of onboarding steps the backend can require.\n * The SDK dynamically receives the active subset for each session.\n *\n * @remarks\n * - `DEVICE_FINGERPRINT`: Silently collects device entropy via FingerprintJS.\n * - `DOCUMENTS_OCR`: Renders a UI for ID document image capture and upload.\n * - `LIVENESS_MATCH`: Drives the Velion Iris active-liveness flow over WebSocket (HUMAN only).\n * - `EMAIL_OTP` / `PHONE_OTP`: Contact verification via one-time passwords.\n * - `HUMAN_BASIC_INFO`: Natural-person identification data (HUMAN only).\n * - `LEGAL_BASIC_INFO`: Legal-entity identification data (LEGAL only).\n * - `EMAIL_CONTACT` / `PHONE_CONTACT`: contact collection without OTP.\n * - `TAX_INFO`, `HUMAN_EXTENDED_INFO`, `LEGAL_EXTENDED_INFO`, `ADDRESS_INFO`, `SWORN_DECLARATION`: form steps.\n * - `ECONOMIC_ACTIVITY_DECLARATION`: pick exactly one economic activity from a searchable list.\n * - `TERMS_AND_CONDITIONS`: accept configured T&C items.\n * - `FACE_MATCH`: server-side face comparison after liveness (no user input; SDK waits).\n * - `AML_SCREENING`: server-side PEP / sanctions screening (no user input; never routed to\n * the SDK as `NEXT_STEP`, only visible in `steps[]` and `STEP_UPDATED`).\n */\nexport type OnboardingStep =\n | 'DEVICE_FINGERPRINT'\n | 'HUMAN_BASIC_INFO'\n | 'LEGAL_BASIC_INFO'\n | 'EMAIL_OTP'\n | 'EMAIL_CONTACT'\n | 'PHONE_OTP'\n | 'PHONE_CONTACT'\n | 'TAX_INFO'\n | 'HUMAN_EXTENDED_INFO'\n | 'LEGAL_EXTENDED_INFO'\n | 'ADDRESS_INFO'\n | 'SWORN_DECLARATION'\n | 'ECONOMIC_ACTIVITY_DECLARATION'\n | 'DOCUMENTS_OCR'\n | 'LIVENESS_MATCH'\n | 'FACE_MATCH'\n | 'AML_SCREENING'\n | 'TERMS_AND_CONDITIONS';\n\n/**\n * Status of an individual onboarding step (matches backend OnboardingStepStatus).\n */\nexport type OnboardingStepStatus =\n | 'PENDING'\n | 'IN_PROGRESS'\n | 'COMPLETED'\n | 'SKIPPED'\n | 'FAILED';\n\n/**\n * Type of onboarding session (matches backend OnboardingType).\n * Constrains which steps are valid: HUMAN allows biometric steps, LEGAL uses entity data.\n */\nexport type OnboardingType = 'HUMAN' | 'LEGAL';\n\n/** Which face of the ID document is being captured. */\nexport type DocumentSide = 'FRONT' | 'BACK';\n\n/**\n * How the user can provide document images in the DOCUMENTS_OCR step.\n * - `'camera'`: Only \"Take photo\" (opens camera on supported devices).\n * - `'file'`: Only \"Choose file\" (file picker).\n * - `'both'`: Both options; implementer can style via theme.\n *\n * @default 'both'\n */\nexport type DocumentCaptureMode = 'camera' | 'file' | 'both';\n\n/**\n * Orientation of the document capture cutout / crop frame.\n * - `'landscape'`: Horizontal card (default — standard ID-1 cards).\n * - `'portrait'`: Vertical card (e.g. some passports, residence permits).\n *\n * @default 'landscape'\n */\nexport type DocumentOrientation = 'landscape' | 'portrait';\n\n/**\n * Optional overrides for document OCR UI (not part of `VelionConfig`; used internally).\n */\nexport interface DocumentsConfig {\n /**\n * When true (default), the document camera preview is mirrored (like selfie mode).\n * Set to false if the back camera shows the document inverted.\n *\n * @default true\n */\n mirrorDocumentVideo?: boolean;\n\n /**\n * Orientation of the document capture frame and crop.\n * - `'landscape'`: Horizontal card (default — standard ID-1 cards).\n * - `'portrait'`: Vertical card.\n *\n * @default 'landscape'\n */\n documentOrientation?: DocumentOrientation;\n}\n\n/** Generic visible/required field toggle used by workflow step configs. */\nexport interface StepFieldRule {\n visible: boolean;\n required: boolean;\n}\n\n/** Select option used by configurable step fields (e.g. HUMAN_EXTENDED_INFO gender/civil_status). */\nexport interface StepFieldOption {\n id: string;\n display_value?: string;\n}\n\n/** Field rule with optional selectable options. */\nexport interface StepFieldRuleWithOptions extends StepFieldRule {\n options?: StepFieldOption[];\n}\n\n/** STEP config for HUMAN_BASIC_INFO. */\nexport interface HumanBasicInfoStepConfig {\n accepted_country_documents?: Array<{\n country_code: string;\n document_types: Array<{ id: string; display_value?: string }>;\n }>;\n}\n\n/**\n * One selectable option row from TAX_INFO `accepted_tax_information` (backend GET session).\n * Prefer `code` as the value sent in POST tax-info; `id` is supported for older payloads.\n */\nexport interface TaxInfoConfigOptionRow {\n code?: string;\n id?: string;\n display_value?: string;\n tax_code?: string;\n}\n\n/** One country block in TAX_INFO step config (GET session `current_step.config`). */\nexport interface TaxInfoAcceptedCountryRow {\n country_code: string;\n identification_types?: TaxInfoConfigOptionRow[];\n /** Options for the \"Categoría fiscal\" / taxpayer category select. */\n taxpayer_categories?: TaxInfoConfigOptionRow[];\n}\n\n/** STEP config for TAX_INFO. */\nexport interface TaxInfoStepConfig {\n /** Identification types + taxpayer categories per country. */\n accepted_tax_information?: TaxInfoAcceptedCountryRow[];\n}\n\n/** STEP config for HUMAN_EXTENDED_INFO. */\nexport interface HumanExtendedInfoStepConfig {\n /** Optional top-level lists sent by backend for select fields. */\n gender_options?: StepFieldOption[];\n civil_status_options?: StepFieldOption[];\n fields?: Partial<\n Record<\n 'gender' | 'civil_status' | 'birth_date' | 'birth_country' | 'citizenship' | 'residence_country',\n StepFieldRuleWithOptions\n >\n >;\n}\n\n/** STEP config for ADDRESS_INFO. */\nexport interface AddressInfoStepConfig {\n address_type?: { id?: string; display_value?: string };\n fields?: Partial<\n Record<\n 'state' | 'city' | 'district' | 'neighborhood' | 'postal_code' | 'street' | 'number',\n StepFieldRule\n >\n >;\n /**\n * Optional ISO 3166-1 alpha-2 allowlist for the country selector. Only\n * consumed by the manual-entry form (when the SDK is configured without a\n * Google Maps key). When unset, every country is offered.\n */\n country_allow?: string[];\n}\n\n/** STEP config for SWORN_DECLARATION. */\nexport interface SwornDeclarationStepConfig {\n ask_pep?: boolean;\n ask_obligated_subject?: boolean;\n ask_fatca?: boolean;\n ask_ocde?: boolean;\n on_declared_pep?: string;\n on_declared_obligated_subject?: string;\n on_declared_fatca?: string;\n on_declared_ocde?: string;\n}\n\n/**\n * A single selectable economic activity presented in the\n * `ECONOMIC_ACTIVITY_DECLARATION` step. Mirrors the backend `EconomicActivityItem`\n * (`code` + `display_name`); `code` is unique within the list and is what the SDK\n * submits. There is no global catalog — each workflow declares its own list.\n */\nexport interface EconomicActivityItem {\n /** Machine-readable activity code (e.g. an AFIP/CLAE code) — unique within the list. */\n code: string;\n /** Human-readable label shown to the user. */\n display_name: string;\n}\n\n/** STEP config for ECONOMIC_ACTIVITY_DECLARATION (`current_step.config`). */\nexport interface EconomicActivityStepConfig {\n /** Selectable activities; always non-empty when the step is active. */\n items?: EconomicActivityItem[];\n}\n\n/** STEP config for DOCUMENTS_OCR. */\nexport interface DocumentsOcrStepConfig {\n country_allow?: string[];\n capture_methods?: {\n camera_scan?: boolean;\n upload?: boolean;\n };\n /** Crop frame orientation for document camera capture. */\n document_orientation?: DocumentOrientation;\n /** Mirror preview in document camera (default true). */\n mirror_document_video?: boolean;\n}\n\n/** STEP config for TERMS_AND_CONDITIONS. */\nexport interface TermsAndConditionsStepConfig {\n terms?: Array<{ title?: string; url?: string }>;\n}\n\n/** Generic per-step runtime config map carried by session state and SSE. */\nexport type StepConfigMap = Partial<Record<OnboardingStep, Record<string, unknown>>>;\n\n/**\n * Request body for POST /sessions/:sessionId/documents-init.\n * Allowed values are defined by the backend; the SDK currently sends a fixed type until\n * the session exposes selectable document types.\n */\nexport interface DocumentsInitRequest {\n document_type: string;\n}\n\n/**\n * Response from POST /sessions/:sessionId/documents-init.\n * Backend returns signed upload URLs so the SDK can PUT each document side\n * directly to storage (e.g. GCS) without routing bytes through the backend.\n */\n/**\n * Response of `POST /sessions/:id/documents-init`.\n *\n * Each URL is a GCS V4 signed PUT issued by the Velion CDN, valid for about\n * two minutes, and signed over `host`, `content-type` and\n * `x-goog-content-length-range`. The PUT must therefore send\n * `Content-Type: image/jpeg` **and** `x-goog-content-length-range:\n * 0,<max_file_size_bytes>` byte for byte, or GCS answers 400\n * `MalformedSecurityHeader` (OSDKW-5). Request the URLs right before the\n * upload, never ahead of the capture.\n */\nexport interface DocumentsInitResponse {\n /** Pre-signed URL to upload the front side (PUT, see the interface docs for the required headers). */\n front_side_upload_url: string;\n /** Pre-signed URL to upload the back side (PUT, see the interface docs for the required headers). */\n back_side_upload_url: string;\n /**\n * Upper bound the URLs were signed for; the exact number that goes into the\n * `x-goog-content-length-range` header. Also usable to reject oversize\n * captures before the PUT.\n */\n max_file_size_bytes?: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ERROR TYPES\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Granular error codes for camera/hardware failures.\n * Allows consumers to display localized, actionable messages to end-users.\n */\nexport enum CameraErrorCode {\n /** User explicitly denied camera access in the browser permission dialog. */\n PERMISSION_DENIED = 'CAMERA_PERMISSION_DENIED',\n /** No camera device was detected on the system. */\n DEVICE_NOT_FOUND = 'CAMERA_DEVICE_NOT_FOUND',\n /**\n * The camera could not be opened for reading (often in use by another app,\n * or blocked at the OS level). Maps to `NotReadableError` / `TrackStartError`.\n */\n DEVICE_NOT_READABLE = 'CAMERA_DEVICE_NOT_READABLE',\n /**\n * The page is served over HTTP (not HTTPS) in a production environment.\n * `getUserMedia` is blocked by the browser's Secure Context policy.\n */\n INSECURE_CONTEXT = 'CAMERA_INSECURE_CONTEXT',\n /**\n * Browser policy blocked camera (e.g. cross-origin iframe without permission,\n * or `Permissions-Policy` / Feature-Policy denying camera). Maps to `SecurityError`\n * from `getUserMedia` — distinct from {@link INSECURE_CONTEXT} (non-HTTPS).\n */\n POLICY_BLOCKED = 'CAMERA_POLICY_BLOCKED',\n /** The browser or WebView does not support the `getUserMedia` API. */\n NOT_SUPPORTED = 'CAMERA_NOT_SUPPORTED',\n /**\n * Camera access is blocked by a WebView policy (common in in-app browsers\n * on iOS/Android that restrict hardware access).\n */\n WEBVIEW_BLOCKED = 'CAMERA_WEBVIEW_BLOCKED',\n /** The requested video constraints (resolution, facing mode) are not satisfiable. */\n CONSTRAINT_ERROR = 'CAMERA_CONSTRAINT_ERROR',\n /** An unclassified camera error occurred. Check `cause` for details. */\n UNKNOWN = 'CAMERA_UNKNOWN_ERROR',\n}\n\n/**\n * Top-level SDK error codes covering all failure scenarios in the onboarding flow.\n */\nexport enum SdkErrorCode {\n // ── Session ──────────────────────────────────────────────────────────────\n /** The sessionToken was rejected by the backend (401/403). */\n SESSION_INVALID = 'SESSION_INVALID',\n /** The sessionToken has expired (typically a 401 after initial validation). */\n SESSION_EXPIRED = 'SESSION_EXPIRED',\n /** Could not fetch the initial session state from the backend. */\n SESSION_FETCH_FAILED = 'SESSION_FETCH_FAILED',\n /** Backend sent SESSION_DECLINED over SSE; session ended before completion. */\n SESSION_DECLINED = 'SESSION_DECLINED',\n\n // ── Modules ──────────────────────────────────────────────────────────────\n /** FingerprintJS failed to generate the visitor ID. */\n FINGERPRINT_FAILED = 'FINGERPRINT_FAILED',\n /** One or more document images failed to upload successfully. */\n DOCUMENT_UPLOAD_FAILED = 'DOCUMENT_UPLOAD_FAILED',\n /** The liveness check did not pass or returned an unexpected result. */\n LIVENESS_FAILED = 'LIVENESS_FAILED',\n /** The Liveness module failed to initialize (e.g., could not create the Iris session). */\n LIVENESS_INIT_FAILED = 'LIVENESS_INIT_FAILED',\n /**\n * The recorded liveness session video could not be uploaded to storage.\n * Best-effort only: this never fails the liveness step — it is logged so the\n * back-office knows the playback evidence is missing for the session.\n */\n LIVENESS_VIDEO_UPLOAD_FAILED = 'LIVENESS_VIDEO_UPLOAD_FAILED',\n\n // ── Camera ───────────────────────────────────────────────────────────────\n /**\n * A camera-related error occurred. Inspect `cameraCode` for the specific reason.\n * @see CameraErrorCode\n */\n CAMERA_ERROR = 'CAMERA_ERROR',\n\n // ── Network & API ────────────────────────────────────────────────────────\n /** A network-level failure (no internet, CORS, DNS). */\n NETWORK_ERROR = 'NETWORK_ERROR',\n /** The backend returned a non-2xx HTTP status. */\n API_ERROR = 'API_ERROR',\n\n // ── Configuration ────────────────────────────────────────────────────────\n /** The `containerId` provided in the config does not exist in the DOM. */\n CONTAINER_NOT_FOUND = 'CONTAINER_NOT_FOUND',\n /** `start()` was called on an SDK instance that is already running. */\n ALREADY_STARTED = 'ALREADY_STARTED',\n /** `start()` was called after `destroy()`. The instance is no longer usable. */\n INSTANCE_DESTROYED = 'INSTANCE_DESTROYED',\n\n /**\n * The page is running inside an iframe and embedding was not explicitly allowed.\n * Configure Content-Security-Policy: frame-ancestors or X-Frame-Options on the\n * page that hosts the SDK, or set allowEmbedded: true if embedding is intentional.\n */\n INSECURE_EMBED = 'INSECURE_EMBED',\n\n /**\n * The CDN-served manifest could not be downloaded or its Ed25519 signature did\n * not verify against the loader's embedded public key. The loader refuses to\n * mount the iframe to prevent execution of tampered/unsigned bundles.\n */\n INTEGRITY_FAILED = 'INTEGRITY_FAILED',\n /**\n * The iframe loaded but the runtime never reported `VELION_READY` within\n * `runtimeReadyTimeoutMs` (default 30 s). Typical causes: the CDN serves an\n * `index.html` whose hashed assets are missing (deploy under the wrong\n * prefix), a Content-Security-Policy on the host blocking the runtime's\n * scripts, or the runtime crashing before it could boot. Without this the\n * host saw a blank iframe and no callback at all.\n */\n RUNTIME_LOAD_FAILED = 'RUNTIME_LOAD_FAILED',\n\n UNKNOWN = 'UNKNOWN',\n}\n\n/**\n * A structured, machine-readable error object emitted by the SDK.\n *\n * @example\n * ```typescript\n * const sdk = new VelionOnboarding({\n * onError: (err) => {\n * if (err.code === SdkErrorCode.CAMERA_ERROR &&\n * err.cameraCode === CameraErrorCode.PERMISSION_DENIED) {\n * showCameraPermissionDialog();\n * }\n * }\n * });\n * ```\n */\nexport interface SdkError {\n /** Primary SDK-level error classification. */\n readonly code: SdkErrorCode;\n /** Human-readable English description for developer logging. */\n readonly message: string;\n /** Present when `code === SdkErrorCode.SESSION_DECLINED` (SSE `reason`). */\n readonly declineReason?: string;\n /** Present when `code === SdkErrorCode.SESSION_DECLINED` (SSE `step_code`). */\n readonly stepCode?: string;\n /**\n * Localized headline resolved by the SDK for SESSION_DECLINED.\n * Useful when hosts hide the built-in final result screen but still want\n * the same user-facing text in custom UI.\n */\n readonly declineTitle?: string;\n /**\n * Localized body message resolved by the SDK for SESSION_DECLINED.\n * Mirrors the text shown in the built-in final result screen.\n */\n readonly declineMessage?: string;\n /** Present when `code === SdkErrorCode.CAMERA_ERROR`. Provides camera-specific detail. */\n readonly cameraCode?: CameraErrorCode;\n /** HTTP status code when the error originated from an API call. */\n readonly httpStatus?: number;\n /**\n * The original caught value for debugging.\n * Not guaranteed to be an `Error` instance — could be a DOMException or string.\n */\n readonly cause?: unknown;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// THEMING\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * White-labeling theme configuration.\n * Each property maps to a CSS custom property injected on the SDK container,\n * making all SDK UI elements styleable via `--vl-*` variables.\n *\n * @example\n * ```typescript\n * const theme: VelionTheme = {\n * primaryColor: '#0052CC', // → --vl-primary-color\n * borderRadius: '12px', // → --vl-border-radius\n * fontFamily: 'Inter, sans-serif'// → --vl-font-family\n * };\n * ```\n */\nexport interface VelionTheme {\n /** Primary brand color. Used for buttons, progress indicators, and accents. */\n primaryColor?: string;\n /** Secondary color. Used for hover states and secondary actions. */\n secondaryColor?: string;\n /** Background color of the SDK container. */\n backgroundColor?: string;\n /** Main body text color. */\n textColor?: string;\n /** Secondary text color for subtitles, captions, and de-emphasized copy. */\n textSecondaryColor?: string;\n /** Border radius for cards, buttons, and inputs. */\n borderRadius?: string;\n /** Font family string. Must be available in the host page's CSS context. */\n fontFamily?: string;\n /** Color used for error states and destructive actions. */\n errorColor?: string;\n /** Color used for success confirmations. */\n successColor?: string;\n /** Color used for warnings and informational alerts. */\n warningColor?: string;\n /** Border color for input fields and separators. */\n borderColor?: string;\n /** Box shadow definition for elevated elements (cards, modals). */\n cardShadow?: string;\n\n /**\n * When `true`, the round icon that sits above each step's title is hidden.\n * Only the title and subtitle are rendered. Useful when the host UI already\n * provides its own visual identity per step or when a denser layout is\n * preferred.\n *\n * @default false\n */\n hideStepIcons?: boolean;\n\n /**\n * When `true`, the thin progress bar at the top of the SDK container is\n * hidden. Equivalent to setting `VelionConfig.showProgressBar: false` —\n * either flag is enough to disable it (whichever is more convenient given\n * how the host configures the SDK). Disable when your host UI already\n * surfaces its own progress indicator.\n *\n * @default false\n */\n hideProgressBar?: boolean;\n}\n\n/**\n * Optional title and body for one terminal verdict on the final result screen.\n */\nexport interface VelionFinalResultStatusCopy {\n title?: string;\n message?: string;\n}\n\n/**\n * Configurable copy for the built-in final result screen (`showFinalResultScreen`).\n * Omitted fields fall back to SDK defaults for the active `locale`.\n */\nexport interface VelionFinalResultScreenTexts {\n approved?: VelionFinalResultStatusCopy;\n manualReview?: VelionFinalResultStatusCopy;\n rejected?: VelionFinalResultStatusCopy;\n /** Copy when {@link OnboardingResult.status} is `DECLINED`. */\n declined?: VelionFinalResultStatusCopy;\n /** Primary button label (e.g. \"Entendido\" / \"Got it\"). */\n dismissLabel?: string;\n}\n\n/**\n * Localized title/body for one `reason` on the SESSION_DECLINED screen.\n * Keys match backend `reason` (e.g. `TAX_INFO_FAILED`).\n */\nexport interface VelionSessionDeclinedReasonMessages {\n en?: VelionFinalResultStatusCopy;\n es?: VelionFinalResultStatusCopy;\n}\n\n/**\n * Per-reason overrides for the built-in SESSION_DECLINED screen (`showFinalResultScreen` not false).\n * Unspecified reasons fall back to SDK defaults when available, then generic copy.\n */\nexport type VelionSessionDeclinedReasonTexts = Record<\n string,\n VelionSessionDeclinedReasonMessages\n>;\n\n/**\n * Per-step overrides for the retry dialog shown when the backend emits a\n * `ROUTING_COMMAND / RETRY_STEP` (the step failed but the retry quota is not\n * exhausted). Keyed by step code (e.g. `LIVENESS_MATCH`, `DOCUMENTS_OCR`).\n * Unspecified steps fall back to SDK per-step defaults, then generic copy.\n *\n * Note: the SDK intentionally ignores the backend `reason` for this dialog —\n * it is a diagnostic string (e.g. `\"STEP_FAILED_RETRY_ALLOWED (attempt 1/3)\"`),\n * not user-facing localized copy. The copy is derived from the step instead.\n */\nexport type VelionRetryStepTexts = Record<\n string,\n VelionSessionDeclinedReasonMessages\n>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// CALLBACKS\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The complete set of lifecycle callbacks the host application can subscribe to.\n * All callbacks are optional. The SDK is still functional without any of them,\n * but `onError` and `onSuccess` are strongly recommended for production.\n */\nexport interface VelionCallbacks {\n /**\n * Fires when the entire onboarding flow has been completed successfully\n * and the backend has confirmed the session as resolved.\n *\n * @param result - Final onboarding verdict from the backend.\n */\n onSuccess?: (result: OnboardingResult) => void;\n\n /**\n * Fires when an unrecoverable error terminates the onboarding flow.\n * Any terminal failure (camera error, document capture failure, upload failure,\n * liveness failure, API error, etc.) is delivered here so the host can notify the user.\n * After this callback, the SDK instance is in an error state.\n * Call `sdk.destroy()` and re-instantiate to retry.\n *\n * @param error - Structured error with a machine-readable code (e.g. CAMERA_ERROR, DOCUMENT_UPLOAD_FAILED).\n */\n onError?: (error: SdkError) => void;\n\n /**\n * Fires each time the active step changes during the flow.\n * Useful for updating a custom step indicator in the host UI.\n *\n * The required step list is resolved dynamically by the backend, so the SDK\n * does not know the total step count up front. Callers that need a progress\n * indicator should rely on the SDK's built-in progress bar (or the trickling\n * counter in `stepIndex`).\n *\n * @param step - The step that just became active.\n * @param stepIndex - Zero-based index of the current step.\n */\n onStepChange?: (step: OnboardingStep, stepIndex: number) => void;\n\n /**\n * Fires immediately after a step succeeds, before advancing to the next one.\n * Useful for analytics or triggering intermediate UI transitions.\n *\n * @param step - The step that was just completed.\n */\n onStepComplete?: (step: OnboardingStep) => void;\n\n /**\n * Fires for each SSE event from GET /sessions/:sessionId/events\n * (e.g. STEP_UPDATED, SESSION_APPROVED).\n */\n onSessionEvent?: (event: OnboardingSessionEvent) => void;\n\n /**\n * Called when the user taps the dismiss control on the final result screen\n * (only when the final result screen is shown). `onSuccess` has already run.\n */\n onFinalResultScreenDismiss?: () => void;\n\n /**\n * Fires when the LIVENESS_MATCH step successfully uploaded the recorded\n * session video to the onboarding storage (GCS PUT returned 200). Best-effort:\n * if the upload failed, timed out, or the session had no `video_upload` URL,\n * the callback is not invoked — liveness still completes normally.\n *\n * @param ref - URL and metadata of the uploaded video.\n */\n onLivenessVideoUploaded?: (ref: LivenessVideoRef) => void;\n\n /**\n * Fires when the user leaves the onboarding flow without a terminal verdict —\n * either by tapping \"back\" on the first reversible step (`BACK_EXIT`) or\n * \"Cancel\" on the retry dialog shown after a `RETRY_STEP` (`RETRY_CANCELLED`).\n * In both cases the backend told the SDK to leave the flow and hand control\n * back to the host; the SDK has already torn down its UI.\n *\n * Distinct from `onSuccess` (a terminal verdict) and `onError` (a failure):\n * the host should typically dismiss/close the SDK surface when this fires.\n *\n * @param exit - Why the flow exited and the step it exited from.\n */\n onExit?: (exit: OnboardingExit) => void;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// MAIN CONFIGURATION\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Root configuration object for the `VelionOnboarding` class.\n *\n * @example\n * ```typescript\n * const sdk = new VelionOnboarding({\n * containerId: 'velion-root',\n * sessionId: 'ses_abc123',\n * sessionToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',\n * apiUrl: 'https://api.velion.la/v1',\n * theme: { primaryColor: '#0052CC' },\n * onSuccess: (result) => console.log('Done!', result),\n * onError: (err) => console.error('SDK Error:', err),\n * });\n *\n * await sdk.start();\n * ```\n */\nexport interface VelionConfig extends VelionCallbacks {\n /**\n * The `id` of an existing DOM element where the SDK will render its UI.\n * The element **must** be present in the document at the time `start()` is called.\n *\n * @example `'velion-kyc-container'`\n */\n containerId: string;\n\n /**\n * The unique session identifier generated by your backend for this\n * onboarding session. Used to scope all API calls.\n *\n * @example `'ses_9f8d7c6b5a4e3f2d'`\n */\n sessionId: string;\n\n /**\n * A short-lived, opaque authorization token (e.g. HS256 JWT) issued by your\n * backend when creating the session. Sent as `Authorization: Bearer <sessionToken>`\n * on every SDK request. Required for all endpoints behind the gateway.\n *\n * **Security note:** This token is held in memory only (never in localStorage\n * or cookies) and is automatically discarded on `destroy()`.\n */\n sessionToken: string;\n\n /**\n * ISO-8601 expiry timestamp of the sessionToken. If provided, the SDK may\n * check expiration before requests and emit SESSION_EXPIRED without a round-trip.\n * When the backend returns 401/403, the SDK emits SESSION_EXPIRED anyway.\n */\n expiresAt?: string;\n\n /**\n * Base URL of the Velion onboarding edge. Must **not** include a trailing slash.\n * SDK paths are relative to this (e.g. GET `/sessions/{sessionId}`).\n *\n * Defaults to the public production edge `https://sdk.velion.la/onboarding`.\n * Override only for staging or self-hosted environments.\n */\n apiUrl?: string;\n\n /**\n * Optional white-labeling theme. If omitted, SDK renders with Velion defaults.\n */\n theme?: VelionTheme;\n\n /**\n * Global visual mode for SDK overlays and controls.\n * - `'light'`: Light translucent surfaces.\n * - `'dark'`: Dark translucent surfaces.\n *\n * @default 'light'\n */\n colorMode?: 'light' | 'dark';\n\n /**\n * BCP 47 locale tag for SDK UI copy (labels, instructions, error messages).\n *\n * @default 'es'\n */\n locale?: string;\n\n /**\n * When `true`, after the backend returns a terminal verdict the SDK shows a\n * full-screen summary (APPROVED, MANUAL_REVIEW, REJECTED) inside the container,\n * styled with `theme` and `colorMode`. `onSuccess` still fires with the result.\n *\n * @default true\n */\n showFinalResultScreen?: boolean;\n\n /**\n * When `true` (default), the SDK renders a thin progress bar at the top of\n * the container that advances as the user moves through steps. Disable for\n * embedded experiences that already provide their own progress UI.\n *\n * @default true\n */\n showProgressBar?: boolean;\n\n /**\n * Overrides for default titles, messages, and dismiss button on the final result screen.\n * Only used when the final result screen is shown (`showFinalResultScreen` is not `false`).\n */\n finalResultScreenTexts?: VelionFinalResultScreenTexts;\n\n /**\n * Optional copy for SESSION_DECLINED SSE screens, keyed by backend `reason`.\n * Merged with SDK defaults (e.g. `TAX_INFO_FAILED`) for `locale` `en` / `es`.\n */\n sessionDeclinedReasonTexts?: VelionSessionDeclinedReasonTexts;\n\n /**\n * Optional copy for the retry dialog (`ROUTING_COMMAND / RETRY_STEP`), keyed by\n * step code (e.g. `LIVENESS_MATCH`). Merged with SDK per-step defaults for\n * `locale` `en` / `es`; unspecified steps fall back to generic retry copy.\n */\n retryStepTexts?: VelionRetryStepTexts;\n\n /**\n * When `true` (default), shows the Velion preparation screen (face icon, tips,\n * \"Start verification\") before connecting to Iris. When `false`, skips it and\n * starts the camera + Iris session immediately on step entry.\n *\n * @default true\n */\n livenessShowIntro?: boolean;\n\n /**\n * When `false`, hides the \"Atrás\" / \"Back\" button on the liveness preparation\n * screen. The button is only present in the intro; once the camera capture\n * begins the user must complete the Iris challenges to leave the step.\n *\n * @default true\n */\n livenessShowBackButton?: boolean;\n\n /**\n * If true, allows the SDK to run when the page is inside an iframe.\n * Only set to true if you have configured Content-Security-Policy: frame-ancestors\n * or X-Frame-Options on the HTTP response of the page that hosts the SDK.\n * If false (default) and the page is embedded in an iframe, the SDK emits\n * INSECURE_EMBED and does not start.\n *\n * @default false\n */\n allowEmbedded?: boolean;\n\n /**\n * Internal — set by the Velion CDN entry (`cdn-main.ts`) to the `event.origin`\n * captured from the loader's first VELION_INIT message. The SDK validates it\n * against `allowed_parent_origins` returned by the backend and refuses to start\n * if the origin is not in the customer's registered allowlist.\n *\n * Direct integrators (running VelionOnboarding without the loader iframe)\n * should leave this unset.\n *\n * @internal\n */\n iframeParentOrigin?: string;\n\n /**\n * Fingerprint Pro API endpoint (custom subdomain). Used by the DEVICE_FINGERPRINT\n * step to load and identify visitors. Only set this when using a custom Fingerprint\n * subdomain or proxy; otherwise the default Velion endpoint is used.\n *\n * @default 'https://fp.velion.la'\n */\n fingerprintEndpoint?: string;\n\n /**\n * Fingerprint Pro public API key. Used by the DEVICE_FINGERPRINT step to\n * identify visitors. Defaults to the shared Velion public key — set this\n * only when your tenant has its own Fingerprint workspace.\n *\n * @default Velion's shared public key\n */\n fingerprintApiKey?: string;\n\n /**\n * Hard ceiling, in milliseconds, for the DEVICE_FINGERPRINT step (agent\n * load + identification). The Fingerprint agent has its own internal\n * timeout, but with tracker blockers or a blackholed endpoint it can hang\n * well past it; the SDK races against this value so the step fails with\n * `FINGERPRINT_FAILED` instead of leaving the user on a blank screen.\n *\n * @default 15000\n */\n fingerprintTimeoutMs?: number;\n\n /**\n * Google Maps Platform API key (Maps JavaScript API + Places). Required for the\n * {@code ADDRESS_INFO} step to show the map, search, and geocoding. Omit only if\n * your flow never reaches that step.\n */\n googleMapsApiKey?: string;\n\n /**\n * Optional calibration tag forwarded to Iris for the LIVENESS_MATCH step.\n * When the Iris worker is running with `CALIBRATION_BACKEND=fs|gcs`, every\n * frame of this session is mirrored to the dataset bucket with the tag in\n * `metadata.json`, so we can build labeled training sets for the spoof /\n * liveness models. Does NOT affect the score.\n *\n * Suggested values (must match the worker's known labels — see\n * `iris-worker` README): `REAL_GOOD_LIGHT`, `REAL_BAD_LIGHT`, `REAL_BACKLIT`,\n * `REAL_BUSY_BG`, `REAL_NEUTRAL_BG`, `REAL_GLASSES`, `REAL_OUTDOOR`,\n * `FAKE_PHONE_PHOTO`, `FAKE_PHONE_VIDEO`, `FAKE_TABLET_VIDEO`,\n * `FAKE_MONITOR_VIDEO`, `FAKE_PRINT`, `FAKE_MASK`, `UNKNOWN`, `EDGE_CASE`.\n *\n * Leave undefined for normal user-facing sessions.\n */\n livenessCalibrationLabel?: string;\n\n /**\n * When true, the SDK enables verbose `console.info` logging at runtime even in\n * production builds. Useful when reproducing customer issues. Independent of the\n * build-time `__DEBUG__` flag, which already gates dev-server logs.\n *\n * @default false\n */\n debug?: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// BACKEND RESPONSE SHAPES\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Backend step DTO (OnboardingStepDto). GET session returns an array of these.\n */\nexport interface OnboardingStepDto {\n code: string;\n name?: string;\n description?: string;\n order?: number;\n status?: string;\n /**\n * Machine-readable cause of the current `status` when it is `FAILED` or\n * `MANUAL_REVIEW` (backend `StepStatusDetailCode`, VON-54 / VON-56). Examples:\n * `DOC_UNREADABLE`, `DOC_MRZ_NOT_READABLE`, `DOC_EXPIRED`, `DOC_AGE_EXCLUSION`,\n * `LIVENESS_BELOW_THRESHOLD`, `LIVENESS_SPOOF_SUSPECTED`,\n * `FACE_MATCH_BELOW_THRESHOLD`, `FINGERPRINT_BOT_DETECTED`,\n * `TAX_ARCA_VALIDATION_FAILED`, `TERMS_NOT_ACCEPTED`,\n * `ECONOMIC_ACTIVITY_INVALID_SELECTION`, `STEP_UNEXPECTED_ERROR`. Hosts may\n * use it for analytics or support tooling; the SDK derives its user-facing\n * copy from the step, never from this code.\n */\n status_detail?: string;\n required?: boolean;\n /** Step-specific payload (e.g. OCR fields, fingerprint, T&C items). */\n result?: Record<string, unknown>;\n /** Raw workflow config for this step (thresholds, accepted documents, …). */\n config?: Record<string, unknown>;\n /**\n * Whether the user may navigate back INTO this step after completing it\n * (intrinsic per-step flag the backend stamps from the workflow). Used only\n * to derive `can_go_back` for steps reached via SSE (which doesn't carry the\n * flag) and for prefill — NOT to decide whether to render the back button.\n * @see OnboardingDtoResponse.can_go_back\n */\n reversible?: boolean;\n}\n\n/**\n * Backend session/onboarding DTO (OnboardingDto). Returned by GET /sessions/:sessionId.\n * The SDK maps this to SessionState for the step machine.\n */\nexport interface OnboardingDtoResponse {\n id?: string;\n related_id?: string;\n onboarding_type?: string;\n status?: string;\n current_step?: {\n code?: string;\n status?: string;\n config?: Record<string, unknown>;\n /**\n * Previously-captured data for this step, present when a reverse navigation\n * re-opened a completed step. The SDK uses it as editable prefill.\n */\n result?: Record<string, unknown>;\n /** Intrinsic per-step reversibility (prefill / SSE-derivation only). */\n reversible?: boolean;\n };\n steps?: OnboardingStepDto[];\n created_at?: string;\n updated_at?: string;\n completed_at?: string;\n metadata?: Record<string, unknown>;\n /**\n * Whether the SDK should render the \"back\" control on the current step.\n * Computed server-side from the workflow graph (barrier model): `true` when\n * there is a previous reversible step to re-open, OR when the current step is\n * the first reversible step (back exits the flow via `BACK_EXIT`). The SDK\n * uses **this** — not a step's intrinsic `reversible` flag — to decide whether\n * to show the back button.\n */\n can_go_back?: boolean;\n /**\n * Origins (scheme + host + port) authorized by the customer to embed the SDK.\n * The iframe validates `event.origin` of the loader's first VELION_INIT against\n * this list and aborts with INSECURE_EMBED otherwise. Backend must also enforce\n * server-side via the `Origin` header on every API call.\n */\n allowed_parent_origins?: string[];\n}\n\n/**\n * Session state used by the SDK step machine. Derived from GET /sessions/:sessionId\n * (OnboardingDto): steps sorted by order → requiredSteps, first non-completed index → currentStepIndex.\n */\nexport interface SessionState {\n /** Current runnable step reported by backend session state. */\n currentStep?: OnboardingStep;\n /** Step-specific config for `currentStep` from GET /sessions/:id `current_step.config`. */\n currentStepConfig?: Record<string, unknown>;\n /** Cached configs discovered from GET session and SSE `step_config`. */\n stepConfigs?: StepConfigMap;\n /** Session type (HUMAN or LEGAL). Constrains which steps are applicable. */\n onboardingType?: OnboardingType;\n /**\n * Whether the SDK should render the \"back\" control on `currentStep`. Mapped\n * from GET `can_go_back` (workflow-graph computed). When absent (e.g. a step\n * reached via SSE, which doesn't carry the flag) the orchestrator derives it\n * from {@link stepReversibility}.\n */\n canGoBack?: boolean;\n /**\n * Per-step intrinsic reversibility in workflow order, cached from the initial\n * GET so the orchestrator can derive `can_go_back` for steps reached via SSE:\n * a step can go back when any earlier step is reversible, or the step itself\n * is the first reversible one (back exits the flow).\n */\n stepReversibility?: Array<{ code: OnboardingStep; reversible: boolean }>;\n /**\n * Previously-captured data for `currentStep`, present when a reverse\n * navigation re-opened a completed step (GET reflects it on\n * `current_step.result`). Applied by the SDK as editable prefill.\n */\n currentStepResult?: Record<string, unknown>;\n /** Arbitrary session metadata the backend may attach. */\n metadata?: Record<string, unknown>;\n /**\n * Customer-registered allowlist of origins authorized to embed the SDK.\n * When provided, the SDK validates the loader's parent origin against this list.\n */\n allowedParentOrigins?: string[];\n}\n\n/**\n * Why the onboarding flow ended without a terminal verdict, delivered to\n * {@link VelionCallbacks.onExit}.\n *\n * Triggers:\n * - `BACK_EXIT`: the user tapped \"back\" on the first reversible step, where\n * there is no earlier step to return to, so the SDK left the flow and\n * returned control to the host.\n * - `RETRY_CANCELLED`: the user tapped \"Cancel\" on the retry dialog shown after\n * a `ROUTING_COMMAND / RETRY_STEP`. The backend session stays `IN_PROGRESS`\n * (there is no cancellation endpoint); the host may re-open it later via\n * `GET /sessions/:id`.\n */\nexport interface OnboardingExit {\n /** The reason the flow exited. */\n reason: 'BACK_EXIT' | 'RETRY_CANCELLED';\n /** The step the user exited from (when the backend provides `step_code`). */\n step?: OnboardingStep;\n}\n\n/**\n * A single T&C line item (matches backend TermsAndConditionsItemDto / domain record).\n */\nexport interface TermsAndConditionsItem {\n id: string;\n description: string;\n /** Optional URL to the full document. */\n link?: string;\n}\n\n/**\n * Response from session creation (POST from your backend). Use sessionId and\n * sessionToken when instantiating VelionOnboarding; pass expiresAt for optional\n * client-side expiration checks.\n */\nexport interface SessionCreationResponse {\n session_id: string;\n session_token: string;\n expires_at: string;\n status?: string;\n}\n\n/**\n * Shape of the final result the SDK hands to `onSuccess` once the session\n * reaches a terminal status. Built from the terminal SSE event and the\n * `GET /sessions/:sessionId` snapshot (the backend exposes no separate\n * `/result` endpoint).\n */\nexport interface OnboardingResult {\n /** The session ID that was completed. Matches `VelionConfig.sessionId`. */\n sessionId: string;\n /** ISO 8601 timestamp of when the backend finalized the session. */\n completedAt: string;\n /**\n * The final KYC verdict.\n * - `APPROVED`: All checks passed automatically.\n * - `MANUAL_REVIEW`: Flagged for human review (common in AML flows). Backend `IN_REVIEW` maps here.\n * - `REJECTED`: Automatically rejected (fraud signals, liveness failure, etc.).\n * - `DECLINED`: Session ended in a declined state (matches GET session `status: DECLINED`).\n */\n status: 'APPROVED' | 'MANUAL_REVIEW' | 'REJECTED' | 'DECLINED';\n /** An optional tracking reference for support and audit trails. */\n referenceId?: string;\n /**\n * When the terminal outcome came from SSE, optional backend `reason` (e.g.\n * `LIVENESS_SCORE_IN_REVIEW_RANGE` on SESSION_MANUAL_REVIEW) for localized final-screen copy.\n */\n terminalReason?: string;\n}\n\n/**\n * Response from `POST /sessions/:sessionId/liveness-init`.\n * The backend returns the Iris session details the SDK needs to open the WebSocket\n * and stream frames. Credentials are not used: Iris owns the session lifecycle.\n */\nexport interface LivenessSessionResponse {\n /** Iris session identifier. */\n liveness_session_id?: string;\n /** Full WebSocket URL the SDK must connect to (e.g. wss://iris.host/ws/iris/{id}). */\n websocket_url?: string;\n /** ISO-8601 expiry of the Iris session. */\n expires_at?: string;\n /** Effective Iris challenge identifiers in order (TURN_LEFT, SMILE, …). */\n challenges?: string[];\n /** Optional JWT for the WebSocket handshake when Iris auth is enabled. */\n connection_token?: string;\n /**\n * Optional base64 HMAC-SHA256 key. When present, the SDK signs every JPEG\n * frame in a JSON envelope (canonical line: `sessionId\\nframeId\\nts\\nsha256hex(jpeg)`).\n */\n frame_integrity_key_base64?: string;\n /**\n * Signed PUT URL the SDK uses to upload the locally-recorded liveness video\n * directly to the onboarding GCS bucket. `null` (or absent) when video\n * recording is disabled or the signing failed — the SDK skips the upload\n * silently (liveness still works without the video; only the back-office\n * loses the playback evidence).\n */\n video_upload?: VideoUpload | null;\n}\n\n/**\n * Signed-upload payload returned by `POST /liveness-init`. Mirrors the backend\n * `LivenessInitResponseDto.VideoUpload`. The SDK PUTs the recorded video bytes\n * to `upload_url` (Content-Type matching the recorded container) before calling\n * `POST /liveness-verify` with the resulting metadata.\n */\nexport interface VideoUpload {\n /** Absolute https://storage.googleapis.com/... URL with V4 signature params. */\n upload_url: string;\n /** ISO-8601 instant after which GCS rejects the signature. */\n expires_at: string;\n /** HTTP method to use. Always `\"PUT\"` today; future-proofing. */\n method: 'PUT' | string;\n}\n\n/**\n * Optional body of `POST /liveness-verify`. The SDK only includes it when the\n * recorded video uploaded successfully (HTTP 200 from GCS). When omitted, the\n * backend completes liveness based on the Iris score only.\n */\nexport interface LivenessVerifyVideoMeta {\n /** MIME type that was sent in the PUT `Content-Type` header (e.g. `video/mp4`, `video/webm`). */\n content_type: string;\n /** Recorded video duration in milliseconds, or `null` if it could not be determined. */\n duration_ms: number | null;\n}\n\n/**\n * Payload delivered to the optional `onLivenessVideoUploaded` callback after a\n * successful upload of the liveness video. Best-effort: the callback only\n * fires when the GCS PUT returned 200.\n */\nexport interface LivenessVideoRef {\n /** Signed `upload_url` the SDK PUT the video to. */\n upload_url: string;\n /** MIME type used in the PUT. */\n content_type: string;\n /** Recorded video duration in milliseconds, or `null` when unknown. */\n duration_ms: number | null;\n /** Recorded file size in bytes, or `null` when unknown. */\n size_bytes: number | null;\n}\n\n/**\n * Payload for POST /sessions/:sessionId/basic-info/human (HUMAN_BASIC_INFO step).\n * Matches backend SubmitHumanBasicInfoRequestDto (snake_case in JSON).\n */\nexport interface HumanBasicInfoPayload {\n first_name: string;\n middle_name?: string;\n last_name: string;\n second_last_name?: string;\n identification_type: string;\n identification_id: string;\n}\n\n/**\n * Payload for POST /sessions/:sessionId/basic-info/legal (LEGAL_BASIC_INFO step).\n * Matches backend SubmitLegalBasicInfoRequestDto (snake_case in JSON).\n */\nexport interface LegalBasicInfoPayload {\n legal_name: string;\n legal_form: string;\n identification_type: string;\n identification_id: string;\n}\n\n/**\n * Single selectable option for tax category or fiscal identifier type (TAX_INFO step).\n * `id` is submitted to the API; `description` is the user-visible label.\n */\nexport interface TaxInfoOption {\n id: string;\n description: string;\n}\n\n/**\n * Resolved selectable lists for the TAX_INFO step UI (from `accepted_tax_information` only; no SDK defaults).\n */\nexport interface TaxInfoConfig {\n /**\n * Fiscal regime / category options (e.g. Monotributo). The chosen `id` is sent as `taxpayer_type`.\n */\n taxpayerCategories: TaxInfoOption[];\n /**\n * Tax identification scheme options (e.g. CUIT). The chosen `id` is sent as `taxpayer_id_type`.\n */\n fiscalIdentifierTypes: TaxInfoOption[];\n}\n\n/** POST /sessions/:id/tax-info — SubmitTaxInfoRequestDto. */\nexport interface TaxInfoPayload {\n /** Selected fiscal category value (`code` from step config `taxpayer_categories`). */\n taxpayer_type: string;\n /** Selected fiscal identifier kind id (e.g. `CUIT` from step config `identification_types`). */\n taxpayer_id_type: string;\n /** Tax identification number for the chosen type. */\n taxpayer_id: string;\n}\n\n/** POST /sessions/:id/extended-info/human — SubmitHumanExtendedInfoRequestDto. */\nexport interface HumanExtendedInfoPayload {\n gender?: string;\n civil_status?: string;\n birth_date?: string;\n birth_country?: string;\n citizenship?: string;\n residence_country?: string;\n}\n\n/** POST /sessions/:id/extended-info/legal — SubmitLegalExtendedInfoRequestDto. */\nexport interface LegalExtendedInfoPayload {\n incorporation_date: string;\n incorporation_country: string;\n}\n\n/** POST /sessions/:id/address-info — SubmitAddressInfoRequestDto. */\nexport interface AddressInfoPayload {\n type: string;\n country: string;\n state: string;\n city: string;\n district?: string;\n neighborhood?: string;\n street: string;\n number: string;\n floor?: string;\n apartment?: string;\n details?: string;\n zip_code: string;\n latitude?: number;\n longitude?: number;\n}\n\n/** POST /sessions/:id/sworn-declaration — SubmitSwornDeclarationRequestDto. */\nexport interface SwornDeclarationPayload {\n is_pep: boolean;\n is_ssoo: boolean;\n is_fatca: boolean;\n is_ocde: boolean;\n}\n\n/**\n * POST /sessions/:id/economic-activity — EconomicActivitySelectionDto.\n *\n * Carries the `code` of the single economic activity the user selected for the\n * `ECONOMIC_ACTIVITY_DECLARATION` step. The Worker validates it asynchronously\n * against the configured items; an invalid code surfaces over SSE as\n * `RETRY_STEP` (re-mounts the step) or `SESSION_DECLINED`, never as an HTTP error.\n */\nexport interface EconomicActivitySelectionPayload {\n /** `code` of the selected EconomicActivityItem. */\n code: string;\n}\n\n/** POST /sessions/:id/contact/email — SubmitEmailContactRequestDto. */\nexport interface EmailContactPayload {\n email: string;\n}\n\n/** POST /sessions/:id/contact/phone — SubmitPhoneContactRequestDto. */\nexport interface PhoneContactPayload {\n phone: string;\n}\n\n/** POST /sessions/:id/terms-and-conditions — SubmitTermsAndConditionsRequestDto. */\nexport interface TermsAndConditionsAcceptancePayload {\n item_id: string;\n accepted: boolean;\n}\n\nexport interface TermsAndConditionsSubmitPayload {\n acceptances: TermsAndConditionsAcceptancePayload[];\n}\n\n/**\n * SSE event payload (OnboardingSessionEvent). Matches the backend record\n * la.velion.onboarding.api...sdk.OnboardingSessionEventDto (snake_case in JSON).\n *\n * Received on GET /public/sdk/sessions/:sessionId/events. The backend sends\n * ServerSentEvent with id = event_id (epoch millis) for Last-Event-ID replay.\n *\n * Event types (event_type):\n * - STEP_UPDATED — a workflow step changed status (step_code, step_status)\n * - ROUTING_COMMAND — next/retry/terminate instruction (action: NEXT_STEP | RETRY_STEP | TERMINATE)\n * - SESSION_APPROVED — all checks passed; session_status = APPROVED\n * - SESSION_MANUAL_REVIEW — manual review required; session_status = MANUAL_REVIEW\n * - SESSION_DECLINED — session ended with decline (step_code, session_status DECLINED, reason)\n */\nexport interface OnboardingSessionEvent {\n /** Onboarding session identifier (Long in backend). */\n session_id?: number;\n /** Discriminator: STEP_UPDATED | ROUTING_COMMAND | SESSION_APPROVED | SESSION_MANUAL_REVIEW | SESSION_DECLINED */\n event_type?: string;\n /** Present for STEP_UPDATED or ROUTING_COMMAND (next step code). */\n step_code?: string;\n /** Present for STEP_UPDATED; e.g. COMPLETED. */\n step_status?: string;\n /** Present for terminal events; e.g. APPROVED | MANUAL_REVIEW | REJECTED. */\n session_status?: string;\n /**\n * For ROUTING_COMMAND: NEXT_STEP | RETRY_STEP (render step_code), TERMINATE\n * (end flow), PREVIOUS_STEP (reverse navigation — re-open step_code with\n * step_result as prefill), or BACK_EXIT (user backed out of the first\n * reversible step — leave the flow and notify the host via `onExit`).\n */\n action?: string;\n /** For SESSION_DECLINED / SESSION_MANUAL_REVIEW or ROUTING_COMMAND TERMINATE; e.g. UNREADABLE_DOCUMENT. */\n reason?: string;\n /** Step-specific config sent with ROUTING_COMMAND NEXT_STEP / PREVIOUS_STEP. */\n step_config?: Record<string, unknown>;\n /**\n * Previously-captured data for the re-opened step, sent with ROUTING_COMMAND\n * PREVIOUS_STEP. Applied by the SDK as editable prefill.\n */\n step_result?: Record<string, unknown>;\n /**\n * Whether the step the user is landing on can itself go back further. Carried\n * on ROUTING_COMMAND when the backend computes it; when absent the SDK derives\n * it from the cached workflow reversibility map.\n */\n can_go_back?: boolean;\n /** Epoch millis; used as SSE id for Last-Event-ID on reconnect. */\n event_id?: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// BACKEND COMMANDS (Listen & Obey)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Backend command: advance to a specific step. The SDK must render that step's\n * screen without evaluating business rules locally.\n */\nexport interface BackendCommandGoToStep {\n action: 'GO_TO_STEP';\n stepCode: OnboardingStep;\n}\n\n/**\n * Backend command: show an error message to the user. The SDK displays the\n * message and does not advance (backend owns the decision).\n */\nexport interface BackendCommandShowError {\n action: 'SHOW_ERROR';\n message: string;\n}\n\n/**\n * Backend command: flow is complete. SDK should fetch final result and fire onSuccess.\n */\nexport interface BackendCommandFlowComplete {\n action: 'FLOW_COMPLETE';\n}\n\n/**\n * Backend command: retry the current step (e.g. after transient failure).\n */\nexport interface BackendCommandRetry {\n action: 'RETRY';\n}\n\n/**\n * Strict command payload the backend may return in a synchronous HTTP response\n * or send via SSE. The SDK never makes routing decisions; it only obeys these commands.\n */\nexport type BackendCommand =\n | BackendCommandGoToStep\n | BackendCommandShowError\n | BackendCommandFlowComplete\n | BackendCommandRetry;\n\n/**\n * Raw response body that may contain a backend command (e.g. POST submit response).\n */\nexport interface BackendCommandResponse {\n action?: string;\n stepCode?: string;\n message?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// INTERNAL STATE MACHINE TYPES (exported for module authors)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * All possible states of the SDK's internal lifecycle.\n * Transitions are strictly unidirectional (no going back to IDLE once started).\n *\n * @internal\n */\nexport type SdkStatus =\n | 'IDLE' // Instantiated, not yet started\n | 'INITIALIZING' // Fetching session state from backend\n | 'RUNNING' // Orchestrating steps\n | 'STEP_PROCESSING'// A module is actively running\n | 'COMPLETED' // All steps done, onSuccess fired\n | 'EXITED' // User left the flow via back on the first step, onExit fired\n | 'ERROR' // Unrecoverable error, onError fired\n | 'DESTROYED'; // destroy() was called, instance is a no-op\n\n/**\n * Internal event bus event names. Typed via `SdkEventMap`.\n * @internal\n */\nexport type SdkEventName =\n | 'step:change'\n | 'step:complete'\n | 'session:loaded'\n | 'flow:complete'\n | 'flow:exit'\n | 'error'\n | 'status:change';\n\n/**\n * Maps each internal event name to its payload type.\n * Used by the `TypedEventEmitter` for compile-time safety.\n * @internal\n */\nexport interface SdkEventMap extends Record<string, unknown> {\n 'step:change': { step: OnboardingStep; index: number };\n 'step:complete': { step: OnboardingStep };\n 'session:loaded': SessionState;\n 'flow:complete': OnboardingResult;\n 'flow:exit': OnboardingExit;\n 'error': SdkError;\n 'status:change': SdkStatus;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// LOADER ↔ CDN APP POSTMESSAGE PROTOCOL (Sumsub-style)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Message type: parent (loader) sends config to iframe (CDN app). */\nexport const VELION_MSG_INIT = 'VELION_INIT' as const;\n\n/** Message types: iframe (CDN app) sends to parent (loader). */\nexport const VELION_MSG_READY = 'VELION_READY' as const;\nexport const VELION_MSG_SUCCESS = 'VELION_SUCCESS' as const;\nexport const VELION_MSG_ERROR = 'VELION_ERROR' as const;\nexport const VELION_MSG_STEP_CHANGE = 'VELION_STEP_CHANGE' as const;\nexport const VELION_MSG_STEP_COMPLETE = 'VELION_STEP_COMPLETE' as const;\nexport const VELION_MSG_TOKEN_EXPIRED = 'VELION_TOKEN_EXPIRED' as const;\n/**\n * Iframe → parent: the user left the flow via back on the first reversible step\n * (`ROUTING_COMMAND / BACK_EXIT`). The loader forwards this to the host's\n * `onExit` callback so it can dismiss the SDK surface.\n */\nexport const VELION_MSG_EXIT = 'VELION_EXIT' as const;\n/**\n * Iframe → parent: the LIVENESS_MATCH step uploaded the recorded session video\n * (GCS PUT returned 200). The loader forwards the {@link LivenessVideoRef} to\n * the host's `onLivenessVideoUploaded` callback. Best-effort: never sent when\n * recording was disabled or the upload failed.\n */\nexport const VELION_MSG_LIVENESS_VIDEO_UPLOADED = 'VELION_LIVENESS_VIDEO_UPLOADED' as const;\n\n/** Message type: parent sends new token to iframe after VELION_TOKEN_EXPIRED. */\nexport const VELION_MSG_TOKEN = 'VELION_TOKEN' as const;\n\nexport type VelionMessageType =\n | typeof VELION_MSG_INIT\n | typeof VELION_MSG_READY\n | typeof VELION_MSG_SUCCESS\n | typeof VELION_MSG_ERROR\n | typeof VELION_MSG_STEP_CHANGE\n | typeof VELION_MSG_STEP_COMPLETE\n | typeof VELION_MSG_TOKEN_EXPIRED\n | typeof VELION_MSG_EXIT\n | typeof VELION_MSG_LIVENESS_VIDEO_UPLOADED\n | typeof VELION_MSG_TOKEN;\n\n/** Envelope for all loader ↔ iframe messages. Both sides must validate origin. */\nexport interface VelionMessage<T = unknown> {\n type: VelionMessageType;\n payload: T;\n}\n\n/** Payload for VELION_INIT: full config (iframe runs with containerId overwritten to root id). */\nexport type VelionInitPayload = VelionConfig;\n\n/** Payload for VELION_STEP_CHANGE. */\nexport interface VelionStepChangePayload {\n step: OnboardingStep;\n stepIndex: number;\n}\n\n/** Payload for VELION_STEP_COMPLETE. */\nexport interface VelionStepCompletePayload {\n step: OnboardingStep;\n}\n\n/** Payload for VELION_EXIT (mirrors {@link OnboardingExit}). */\nexport type VelionExitPayload = OnboardingExit;\n\n/** Payload for VELION_LIVENESS_VIDEO_UPLOADED (mirrors {@link LivenessVideoRef}). */\nexport type VelionLivenessVideoUploadedPayload = LivenessVideoRef;\n\n/** Payload for VELION_TOKEN (token refresh response from parent to iframe). */\nexport interface VelionTokenPayload {\n sessionToken: string;\n expiresAt?: string;\n}\n","/**\n * @fileoverview Signed-manifest verification for the Velion CDN bundle.\n *\n * Threat model:\n * The loader is published on npm and embedded in customer pages. The full SDK\n * ships from a CDN at runtime. If the CDN is compromised (BGP hijack, bucket\n * takeover, supply-chain on the build pipeline), an attacker could swap the\n * iframe HTML / JS and exfiltrate KYC documents, selfies, and session tokens.\n *\n * Mitigation: every CDN release ships a `manifest.json` that names the\n * entry HTML and asset SHA-384 digests, plus a detached `manifest.sig`\n * (Ed25519 signature over the canonical bytes of `manifest.json`). The\n * loader carries the corresponding Ed25519 *public* key and verifies the\n * signature before letting the iframe load. A compromised CDN cannot\n * forge a manifest without also obtaining the private key — which lives\n * in a KMS keyring outside the CDN's blast radius.\n *\n * Rotation:\n * The public key is embedded in the loader bundle published to npm. Rotation\n * means publishing a new loader version with the new public key. Old loaders\n * still verify against the previous key while customers upgrade.\n */\n\n/**\n * Ed25519 public key used to verify `manifest.sig`, base64-encoded raw bytes.\n *\n * Empty string means \"verification disabled\" — accepted only as a transitional\n * mode while the signing pipeline is being deployed. Production loaders MUST\n * ship with this populated before being published to npm. Replace via the\n * release workflow that fetches the pubkey from KMS metadata.\n */\nexport const VELION_MANIFEST_PUBKEY_B64 = 'lpqLG4Ta0YyBn/+lQh95tFl4CuTIHWapeeq7+xnPH+U=';\n\n/** Schema of the signed `manifest.json` emitted by the CDN build pipeline. */\nexport interface VelionCdnManifest {\n /** Loader-major path token, e.g. `\"v1\"`. Must match the loader's `LOADER_MAJOR_VERSION`. */\n version: string;\n /** ISO-8601 timestamp of the build. */\n buildTime: string;\n /** Path to the iframe entry HTML, relative to `cdnUrl`. */\n entry: string;\n /** Optional human-readable app version (matches the app's `package.json`). */\n appVersion?: string;\n /** Optional asset list with SHA-384 integrity digests for defense-in-depth. */\n assets?: Array<{ url: string; integrity: string }>;\n}\n\n/** Distinguishes \"manifest absent\" (legacy CDN) from \"manifest invalid\" (compromised). */\nexport type ManifestLoadResult =\n | { ok: true; manifest: VelionCdnManifest; signed: boolean }\n | { ok: false; reason: 'unavailable' | 'invalid_signature' | 'malformed'; detail?: string };\n\nfunction base64ToArrayBuffer(b64: string): ArrayBuffer {\n const bin = atob(b64);\n const buf = new ArrayBuffer(bin.length);\n const view = new Uint8Array(buf);\n for (let i = 0; i < bin.length; i++) {\n view[i] = bin.charCodeAt(i);\n }\n return buf;\n}\n\nasync function importPublicKey(rawB64: string): Promise<CryptoKey> {\n const raw = base64ToArrayBuffer(rawB64);\n // Web Crypto Ed25519 is implemented in Chrome 113+, Firefox 130+, Safari 17+.\n // Older browsers throw NotSupportedError — we surface that as `invalid_signature`\n // so the loader refuses to start rather than silently bypassing verification.\n return crypto.subtle.importKey(\n 'raw',\n raw,\n { name: 'Ed25519' } as AlgorithmIdentifier,\n false,\n ['verify'],\n );\n}\n\nasync function verifySignature(\n publicKey: CryptoKey,\n signature: ArrayBuffer,\n data: ArrayBuffer,\n): Promise<boolean> {\n return crypto.subtle.verify(\n { name: 'Ed25519' } as AlgorithmIdentifier,\n publicKey,\n signature,\n data,\n );\n}\n\n/**\n * Fetches `${cdnUrl}/manifest.json` (and `manifest.sig` when verification is enabled).\n *\n * Returns:\n * - `{ ok:true, signed:true }` when the signature verifies against the embedded pubkey.\n * - `{ ok:true, signed:false }` when the manifest exists but verification is disabled\n * (transitional dev/staging mode where `VELION_MANIFEST_PUBKEY_B64` is empty).\n * - `{ ok:false, reason:'unavailable' }` when the CDN has no manifest yet — the\n * loader treats this as legacy mode and may proceed if explicitly allowed.\n * - `{ ok:false, reason:'invalid_signature' | 'malformed' }` when the manifest\n * is present but tampered or syntactically invalid. The loader MUST refuse\n * to mount the iframe in this case.\n */\nexport async function loadAndVerifyManifest(\n cdnUrl: string,\n pubKeyB64: string = VELION_MANIFEST_PUBKEY_B64,\n): Promise<ManifestLoadResult> {\n let manifestBuffer: ArrayBuffer;\n let manifestText: string;\n try {\n const resp = await fetch(`${cdnUrl}/manifest.json`, {\n credentials: 'omit',\n cache: 'no-cache',\n redirect: 'error',\n });\n if (resp.status === 404) {\n return { ok: false, reason: 'unavailable' };\n }\n if (!resp.ok) {\n return { ok: false, reason: 'unavailable', detail: `HTTP ${resp.status}` };\n }\n manifestBuffer = await resp.arrayBuffer();\n manifestText = new TextDecoder().decode(manifestBuffer);\n } catch (err) {\n return { ok: false, reason: 'unavailable', detail: (err as Error).message };\n }\n\n let manifest: VelionCdnManifest;\n try {\n manifest = JSON.parse(manifestText) as VelionCdnManifest;\n if (\n typeof manifest.version !== 'string' ||\n typeof manifest.entry !== 'string' ||\n typeof manifest.buildTime !== 'string'\n ) {\n return { ok: false, reason: 'malformed' };\n }\n } catch (err) {\n return { ok: false, reason: 'malformed', detail: (err as Error).message };\n }\n\n if (!pubKeyB64) {\n // Pubkey not yet provisioned on this loader release. Accept the manifest\n // unsigned but flag it so the caller can decide whether to proceed.\n return { ok: true, manifest, signed: false };\n }\n\n let sigBuffer: ArrayBuffer;\n try {\n const sigResp = await fetch(`${cdnUrl}/manifest.sig`, {\n credentials: 'omit',\n cache: 'no-cache',\n redirect: 'error',\n });\n if (!sigResp.ok) {\n return { ok: false, reason: 'invalid_signature', detail: `sig HTTP ${sigResp.status}` };\n }\n const sigText = (await sigResp.text()).trim();\n sigBuffer = base64ToArrayBuffer(sigText);\n } catch (err) {\n return { ok: false, reason: 'invalid_signature', detail: (err as Error).message };\n }\n\n let pubKey: CryptoKey;\n try {\n pubKey = await importPublicKey(pubKeyB64);\n } catch (err) {\n return { ok: false, reason: 'invalid_signature', detail: `pubkey import failed: ${(err as Error).message}` };\n }\n\n let valid: boolean;\n try {\n valid = await verifySignature(pubKey, sigBuffer, manifestBuffer);\n } catch (err) {\n return { ok: false, reason: 'invalid_signature', detail: (err as Error).message };\n }\n if (!valid) {\n return { ok: false, reason: 'invalid_signature' };\n }\n\n return { ok: true, manifest, signed: true };\n}\n","/**\n * Thin client (loader) for Velion Onboarding.\n * Creates an iframe pointing to the CDN app and bridges config/events via postMessage.\n * Follows the same pattern as Sumsub WebSDK: init → iframe loads → postMessage config → events.\n */\n\nimport type {\n VelionConfig,\n OnboardingResult,\n SdkError,\n SdkStatus,\n OnboardingStep,\n} from './types';\nimport {\n SdkErrorCode,\n VELION_MSG_INIT,\n VELION_MSG_READY,\n VELION_MSG_SUCCESS,\n VELION_MSG_ERROR,\n VELION_MSG_STEP_CHANGE,\n VELION_MSG_STEP_COMPLETE,\n VELION_MSG_TOKEN_EXPIRED,\n VELION_MSG_EXIT,\n VELION_MSG_LIVENESS_VIDEO_UPLOADED,\n VELION_MSG_TOKEN,\n type VelionMessage,\n type OnboardingExit,\n type LivenessVideoRef,\n} from './types';\nimport {\n loadAndVerifyManifest,\n VELION_MANIFEST_PUBKEY_B64,\n type VelionCdnManifest,\n} from './manifest';\n\n/**\n * Product slug under the shared `static.velion.la/sdk/` namespace. This identity\n * SDK lives at `sdk/onboarding/`; future Velion SDKs (e.g. payments, auth) get\n * their own slug so they can be deployed independently from the same CDN.\n */\nexport const LOADER_PRODUCT = 'onboarding' as const;\n\n/**\n * Major version of this loader. Locked into the CDN path so a breaking change\n * to the loader↔app postMessage contract requires publishing loader-`v2`\n * against `/sdk/onboarding/v2/`. Customers can keep older loaders for as long\n * as the matching CDN path is served.\n */\nexport const LOADER_MAJOR_VERSION = 'v1' as const;\n\n/** Default CDN base URL for the onboarding app (no trailing slash). */\nexport const DEFAULT_CDN_URL =\n `https://static.velion.la/sdk/${LOADER_PRODUCT}/${LOADER_MAJOR_VERSION}`;\n\n/**\n * Used only while the host container has no laid-out height yet (e.g. `display: none`\n * or first paint). Replaced by a pixel height as soon as `container.clientHeight > 0`.\n */\nconst IFRAME_FALLBACK_HEIGHT_CSS = 'min(85vh, 880px)';\n\n/** Loader config: VelionConfig plus optional CDN URL and token refresh callback. */\nexport interface VelionLoaderConfig extends VelionConfig {\n /**\n * Base URL of the CDN where the onboarding app is served (e.g. https://static.velion.la/sdk).\n * The loader fetches `${cdnUrl}/manifest.json` (signed with Ed25519), verifies the\n * signature against the loader's embedded public key, and only then loads the\n * iframe entry named in the manifest.\n * @default 'https://static.velion.la/sdk'\n */\n cdnUrl?: string;\n\n /**\n * When `true`, lets the loader proceed against a CDN that does not yet expose a\n * signed `manifest.json` — for staging or migration windows. Has NO effect when\n * the loader carries an embedded pubkey AND the manifest exists but signature\n * verification fails (that case is always fatal).\n *\n * Default `false`: production loaders refuse to mount the iframe without a\n * verified manifest once the pubkey has been provisioned.\n *\n * @default false\n */\n allowUnverifiedCdn?: boolean;\n\n /**\n * Called when the iframe requests a new session token (e.g. after SESSION_EXPIRED).\n * Return the new token and optional expiresAt; the CDN app will continue the flow.\n */\n onTokenExpired?: () => Promise<{ sessionToken: string; expiresAt?: string }>;\n\n /**\n * How long to wait, after the iframe document loaded, for the runtime to\n * report `VELION_READY`. Past it the loader emits `RUNTIME_LOAD_FAILED`\n * instead of leaving the host with a blank iframe and no callback (e.g.\n * when the CDN entry points at assets that 404).\n *\n * @default 30000\n */\n runtimeReadyTimeoutMs?: number;\n}\n\nconst DEFAULT_RUNTIME_READY_TIMEOUT_MS = 30_000;\n\nfunction parseOriginFromUrl(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return '';\n }\n}\n\n/**\n * Allows `https:` and `wss:` everywhere, plus plain `http:`/`ws:` only when the host\n * is a loopback / `.local` address (developer machines and CI). Used to prevent\n * accidental cleartext bearer-token transmission to a misconfigured `apiUrl`/`cdnUrl`.\n */\nfunction isLocalhostHost(host: string): boolean {\n return host === 'localhost' || host === '127.0.0.1' || host === '::1' || host.endsWith('.local');\n}\n\nexport function isSecureHttpUrl(url: string): boolean {\n try {\n const u = new URL(url);\n if (u.protocol === 'https:') return true;\n if (u.protocol === 'http:') return isLocalhostHost(u.hostname);\n return false;\n } catch {\n return false;\n }\n}\n\n/** Keys of VelionConfig that are safe to send via postMessage (no functions). */\nconst SERIALIZABLE_CONFIG_KEYS: (keyof VelionConfig)[] = [\n 'containerId',\n 'sessionId',\n 'sessionToken',\n 'apiUrl',\n 'expiresAt',\n 'theme',\n 'colorMode',\n 'locale',\n 'allowEmbedded',\n 'livenessShowIntro',\n 'livenessShowBackButton',\n 'fingerprintEndpoint',\n 'debug',\n 'googleMapsApiKey',\n 'showFinalResultScreen',\n 'showProgressBar',\n 'finalResultScreenTexts',\n];\n\nfunction buildSerializableConfig(\n config: Readonly<VelionLoaderConfig>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {\n containerId: 'velion-sdk-root',\n allowEmbedded: true,\n };\n for (const key of SERIALIZABLE_CONFIG_KEYS) {\n if (key === 'containerId' || key === 'allowEmbedded') continue;\n const value = config[key as keyof typeof config];\n if (value !== undefined && typeof value !== 'function') {\n out[key] = value;\n }\n }\n return out;\n}\n\nexport class VelionOnboarding {\n private readonly config: Readonly<VelionLoaderConfig>;\n private status: SdkStatus = 'IDLE';\n private iframe: HTMLIFrameElement | null = null;\n private container: HTMLElement | null = null;\n private messageHandler: ((e: MessageEvent) => void) | null = null;\n private containerResizeObserver: ResizeObserver | null = null;\n private iframeHeightWindowResizeHandler: (() => void) | null = null;\n private cdnOrigin = '';\n /** Armed when VELION_INIT is sent; cleared by VELION_READY or destroy(). */\n private readyTimer: ReturnType<typeof setTimeout> | null = null;\n\n private clearReadyTimer(): void {\n if (this.readyTimer !== null) {\n clearTimeout(this.readyTimer);\n this.readyTimer = null;\n }\n }\n\n private armReadyTimer(cdnUrl: string): void {\n this.clearReadyTimer();\n const ms = this.config.runtimeReadyTimeoutMs ?? DEFAULT_RUNTIME_READY_TIMEOUT_MS;\n if (!(ms > 0)) return;\n this.readyTimer = setTimeout(() => {\n this.readyTimer = null;\n if (this.status !== 'RUNNING') return; // already READY-progressed, errored or destroyed\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.RUNTIME_LOAD_FAILED,\n message:\n `The onboarding runtime at ${cdnUrl} did not become ready within ${ms} ms. ` +\n 'Check that the CDN serves the assets referenced by its index.html and that the host ' +\n \"page's Content-Security-Policy allows the runtime scripts.\",\n });\n }, ms);\n }\n\n constructor(config: VelionLoaderConfig) {\n this.config = Object.freeze({ ...config });\n }\n\n async start(): Promise<void> {\n if (this.status === 'DESTROYED') {\n this.config.onError?.({\n code: SdkErrorCode.INSTANCE_DESTROYED,\n message:\n 'This VelionOnboarding instance has been destroyed. Create a new instance to retry.',\n });\n return;\n }\n\n if (this.status !== 'IDLE') {\n this.config.onError?.({\n code: SdkErrorCode.ALREADY_STARTED,\n message:\n 'start() was called on an already-running SDK instance. Call destroy() before re-starting.',\n });\n return;\n }\n\n const container = document.getElementById(this.config.containerId);\n if (!container) {\n this.config.onError?.({\n code: SdkErrorCode.CONTAINER_NOT_FOUND,\n message: `No DOM element found with id=\"${this.config.containerId}\". Ensure the element exists before calling start().`,\n });\n return;\n }\n\n const cdnUrl = (this.config.cdnUrl ?? DEFAULT_CDN_URL).replace(/\\/$/, '');\n if (!isSecureHttpUrl(cdnUrl)) {\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Insecure cdnUrl: \"${cdnUrl}\". Must be https:// (http:// is allowed only for localhost).`,\n });\n return;\n }\n if (this.config.apiUrl && !isSecureHttpUrl(this.config.apiUrl)) {\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Insecure apiUrl: \"${this.config.apiUrl}\". Must be https:// (http:// is allowed only for localhost).`,\n });\n return;\n }\n this.cdnOrigin = parseOriginFromUrl(cdnUrl);\n if (!this.cdnOrigin) {\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Invalid cdnUrl: ${cdnUrl}. Must be a valid absolute URL.`,\n });\n return;\n }\n\n this.container = container;\n this.status = 'INITIALIZING';\n\n const iframe = document.createElement('iframe');\n iframe.setAttribute('title', 'Velion Onboarding');\n // The SDK only needs the camera. Microphone and geolocation are intentionally not granted.\n iframe.setAttribute('allow', 'camera');\n // Sandbox limits blast radius of a CDN compromise. Tokens we keep:\n // allow-scripts — the iframe is a JS app\n // allow-same-origin — needed for fetch to same-origin assets and for postMessage to behave\n // allow-forms — file pickers and form submissions for OCR upload flows\n // allow-popups — Google Maps / Places attribution links open in new tab\n // allow-modals — getUserMedia permission prompts and document picker\n iframe.setAttribute(\n 'sandbox',\n 'allow-scripts allow-same-origin allow-forms allow-popups allow-modals',\n );\n // Avoid leaking host page paths to the CDN logs.\n iframe.setAttribute('referrerpolicy', 'no-referrer');\n iframe.style.width = '100%';\n iframe.style.border = 'none';\n iframe.style.display = 'block';\n /* Height is applied in attachIframeHeightSync (pixel match + internal fallback). */\n\n const messageHandler = (e: MessageEvent): void => {\n // Origin AND source must match: a sibling iframe from the same CDN origin\n // (e.g. opened by the host page) must not be able to spoof events.\n if (e.origin !== this.cdnOrigin) return;\n if (e.source !== this.iframe?.contentWindow) return;\n const msg = e.data as VelionMessage | undefined;\n if (!msg || typeof msg.type !== 'string') return;\n\n switch (msg.type) {\n case VELION_MSG_READY:\n this.clearReadyTimer();\n this.status = 'RUNNING';\n break;\n case VELION_MSG_SUCCESS:\n this.status = 'COMPLETED';\n this.config.onSuccess?.(msg.payload as OnboardingResult);\n break;\n case VELION_MSG_ERROR:\n this.status = 'ERROR';\n this.config.onError?.(msg.payload as SdkError);\n break;\n case VELION_MSG_STEP_CHANGE: {\n const p = msg.payload as { step: OnboardingStep; stepIndex: number };\n this.config.onStepChange?.(p.step, p.stepIndex);\n break;\n }\n case VELION_MSG_STEP_COMPLETE: {\n const p = msg.payload as { step: OnboardingStep };\n this.config.onStepComplete?.(p.step);\n break;\n }\n case VELION_MSG_EXIT: {\n // User backed out of the first reversible step: the flow ended without\n // a verdict. Forward to the host so it can dismiss the SDK surface.\n this.status = 'EXITED';\n this.config.onExit?.(msg.payload as OnboardingExit);\n break;\n }\n case VELION_MSG_LIVENESS_VIDEO_UPLOADED: {\n // Evidence signal from the LIVENESS_MATCH step (OSDKW-11). Informational\n // only: the flow never depends on it, so no status change.\n this.config.onLivenessVideoUploaded?.(msg.payload as LivenessVideoRef);\n break;\n }\n case VELION_MSG_TOKEN_EXPIRED: {\n const refresh = this.config.onTokenExpired;\n if (refresh) {\n refresh().then(\n (t) => this.sendToIframe({ type: VELION_MSG_TOKEN, payload: t }),\n () => {\n this.config.onError?.({\n code: SdkErrorCode.SESSION_EXPIRED,\n message: 'Token refresh failed.',\n });\n }\n );\n } else {\n this.config.onError?.({\n code: SdkErrorCode.SESSION_EXPIRED,\n message: 'Session token expired. Provide onTokenExpired in config to refresh.',\n });\n }\n break;\n }\n default:\n break;\n }\n };\n\n window.addEventListener('message', messageHandler);\n this.messageHandler = messageHandler;\n this.iframe = iframe;\n\n container.replaceChildren(iframe);\n this.attachIframeHeightSync(container, iframe);\n\n // ── Verify the CDN manifest before mounting the iframe ─────────────────\n const manifest = await this.resolveManifest(cdnUrl);\n if (!manifest) {\n // resolveManifest already invoked onError. Stop here so we don't load\n // an unverified bundle.\n return;\n }\n const entryPath = manifest.entry.startsWith('/') ? manifest.entry : `/${manifest.entry}`;\n\n return new Promise<void>((resolve) => {\n iframe.onload = (): void => {\n if (!this.iframe?.contentWindow) {\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: 'Iframe failed to load.',\n });\n resolve();\n return;\n }\n // postMessage cannot clone functions. Sumsub-style: send only JSON-serializable config.\n // Clone via JSON so no function can slip through (e.g. from cached bundle or nested props).\n const initPayload = buildSerializableConfig(this.config);\n let payloadForPostMessage: unknown;\n try {\n payloadForPostMessage = JSON.parse(JSON.stringify(initPayload));\n } catch {\n payloadForPostMessage = initPayload;\n }\n this.sendToIframe({ type: VELION_MSG_INIT, payload: payloadForPostMessage });\n this.status = 'RUNNING';\n // The document loaded, but a 200 index.html whose scripts 404 (or a CSP\n // that blocks them) never answers VELION_INIT. Bound the wait so the\n // host gets an error instead of a silent blank iframe.\n this.armReadyTimer(cdnUrl);\n resolve();\n };\n\n iframe.onerror = (): void => {\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Failed to load onboarding app from ${cdnUrl}. Check cdnUrl and network.`,\n });\n resolve();\n };\n\n iframe.src = `${cdnUrl}${entryPath}`;\n });\n }\n\n /**\n * Fetches and verifies the CDN manifest. Returns `null` and emits onError when\n * the manifest is missing in production mode, or when the signature is invalid.\n */\n private async resolveManifest(cdnUrl: string): Promise<VelionCdnManifest | null> {\n let result;\n try {\n result = await loadAndVerifyManifest(cdnUrl);\n } catch (err) {\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message: `Manifest verification crashed unexpectedly: ${(err as Error).message}`,\n });\n return null;\n }\n if (result.ok) {\n if (!result.signed && VELION_MANIFEST_PUBKEY_B64) {\n // Should not happen — pubkey is set, manifest exists, yet not signed.\n // loadAndVerifyManifest only marks `signed:false` when pubkey is empty.\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message: 'Internal: manifest reported as unsigned despite pubkey being set.',\n });\n return null;\n }\n return result.manifest;\n }\n if (result.reason === 'unavailable') {\n // Manifest endpoint is missing. Permitted only when the loader is configured\n // to tolerate it OR when the loader was published without a pubkey (transitional).\n const tolerateMissing =\n this.config.allowUnverifiedCdn === true || !VELION_MANIFEST_PUBKEY_B64;\n if (tolerateMissing) {\n return {\n version: 'legacy',\n buildTime: new Date().toISOString(),\n entry: '/index.html',\n };\n }\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message:\n `CDN at \"${cdnUrl}\" did not expose /manifest.json. The loader is configured ` +\n 'to require a signed manifest. If the CDN is migrating, set `allowUnverifiedCdn: true` ' +\n 'on the loader config — but only as a temporary measure.',\n });\n return null;\n }\n // 'invalid_signature' or 'malformed' — always fatal, even when allowUnverifiedCdn is true.\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message:\n `CDN manifest at \"${cdnUrl}/manifest.json\" failed integrity check (${result.reason}). ` +\n 'Refusing to load the iframe to protect the user. ' +\n ('detail' in result && result.detail ? `Detail: ${result.detail}` : ''),\n });\n return null;\n }\n\n /**\n * Keep the iframe exactly as tall as the host container so integrators do not need\n * `height: 100%` on the iframe or flex hacks—only a definite block-size on the\n * container (e.g. `height: 700px` or `height: 100vh`), similar to Sumsub WebSDK.\n *\n * Until the container has a non-zero `clientHeight`, apply an internal fallback\n * height so the SDK is usable even before layout or if only `min-height` is set.\n */\n private attachIframeHeightSync(\n container: HTMLElement,\n iframe: HTMLIFrameElement,\n ): void {\n const sync = (): void => {\n if (this.iframe !== iframe || this.container !== container) return;\n const h = container.clientHeight;\n if (h > 0) {\n iframe.style.height = `${h}px`;\n iframe.style.minHeight = '';\n } else {\n iframe.style.height = IFRAME_FALLBACK_HEIGHT_CSS;\n iframe.style.minHeight = '';\n }\n };\n sync();\n requestAnimationFrame(sync);\n requestAnimationFrame(() => {\n requestAnimationFrame(sync);\n });\n if (typeof ResizeObserver !== 'undefined') {\n this.containerResizeObserver = new ResizeObserver(sync);\n this.containerResizeObserver.observe(container);\n } else {\n this.iframeHeightWindowResizeHandler = sync;\n window.addEventListener('resize', sync);\n }\n }\n\n private detachIframeHeightSync(): void {\n if (this.containerResizeObserver) {\n this.containerResizeObserver.disconnect();\n this.containerResizeObserver = null;\n }\n if (this.iframeHeightWindowResizeHandler) {\n window.removeEventListener('resize', this.iframeHeightWindowResizeHandler);\n this.iframeHeightWindowResizeHandler = null;\n }\n }\n\n private sendToIframe(msg: VelionMessage): void {\n if (this.iframe?.contentWindow) {\n this.iframe.contentWindow.postMessage(msg, this.cdnOrigin);\n }\n }\n\n getStatus(): SdkStatus {\n return this.status;\n }\n\n static getVersion(): string {\n return typeof __SDK_VERSION__ !== 'undefined' ? __SDK_VERSION__ : '0.0.0';\n }\n\n destroy(): void {\n if (this.status === 'DESTROYED') return;\n this.clearReadyTimer();\n this.detachIframeHeightSync();\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.iframe && this.container) {\n this.iframe.remove();\n this.container.replaceChildren();\n }\n this.iframe = null;\n this.container = null;\n this.status = 'DESTROYED';\n }\n}\n","/**\n * @fileoverview Public API of the Velion Onboarding SDK (thin client / loader).\n *\n * This package is the loader only: it creates an iframe that loads the full\n * onboarding app from the CDN (e.g. https://static.velion.la/sdk) and bridges\n * config and events via postMessage. No React, OpenCV, or Fingerprint\n * are bundled here — minimal size, same API as before.\n */\n\nexport { VelionOnboarding, DEFAULT_CDN_URL, type VelionLoaderConfig } from './velion-loader';\n\n/**\n * No-op when using the loader: OpenCV is loaded inside the CDN app.\n * Kept for API compatibility; configure OpenCV in the CDN app build if needed.\n */\nexport function setOpenCVBaseUrl(_url: string): void {}\n\nexport {\n SdkErrorCode,\n CameraErrorCode,\n VELION_MSG_INIT,\n VELION_MSG_READY,\n VELION_MSG_SUCCESS,\n VELION_MSG_ERROR,\n VELION_MSG_STEP_CHANGE,\n VELION_MSG_STEP_COMPLETE,\n VELION_MSG_TOKEN_EXPIRED,\n VELION_MSG_EXIT,\n VELION_MSG_LIVENESS_VIDEO_UPLOADED,\n VELION_MSG_TOKEN,\n type VelionConfig,\n type VelionTheme,\n type VelionFinalResultStatusCopy,\n type VelionFinalResultScreenTexts,\n type VelionSessionDeclinedReasonMessages,\n type VelionSessionDeclinedReasonTexts,\n type VelionRetryStepTexts,\n type VelionCallbacks,\n type OnboardingExit,\n type OnboardingStep,\n type OnboardingStepStatus,\n type OnboardingType,\n type DocumentSide,\n type DocumentCaptureMode,\n type DocumentsConfig,\n type TaxInfoConfig,\n type TaxInfoOption,\n type DocumentsInitRequest,\n type DocumentsInitResponse,\n type SessionState,\n type OnboardingResult,\n type OnboardingStepDto,\n type StepFieldRule,\n type HumanBasicInfoStepConfig,\n type HumanExtendedInfoStepConfig,\n type TaxInfoStepConfig,\n type AddressInfoStepConfig,\n type SwornDeclarationStepConfig,\n type DocumentsOcrStepConfig,\n type TermsAndConditionsStepConfig,\n type StepConfigMap,\n type SdkError,\n type SdkStatus,\n type LivenessSessionResponse,\n type VideoUpload,\n type LivenessVerifyVideoMeta,\n type LivenessVideoRef,\n type SessionCreationResponse,\n type HumanBasicInfoPayload,\n type LegalBasicInfoPayload,\n type TermsAndConditionsItem,\n type TaxInfoPayload,\n type HumanExtendedInfoPayload,\n type LegalExtendedInfoPayload,\n type AddressInfoPayload,\n type SwornDeclarationPayload,\n type EconomicActivityItem,\n type EconomicActivityStepConfig,\n type EconomicActivitySelectionPayload,\n type EmailContactPayload,\n type PhoneContactPayload,\n type TermsAndConditionsSubmitPayload,\n type TermsAndConditionsAcceptancePayload,\n type OnboardingSessionEvent,\n type OnboardingDtoResponse,\n type VelionMessage,\n type VelionMessageType,\n type VelionInitPayload,\n type VelionStepChangePayload,\n type VelionStepCompletePayload,\n type VelionExitPayload,\n type VelionLivenessVideoUploadedPayload,\n type VelionTokenPayload,\n} from './types';\n"],"names":["CameraErrorCode","SdkErrorCode","VELION_MSG_INIT","VELION_MSG_READY","VELION_MSG_SUCCESS","VELION_MSG_ERROR","VELION_MSG_STEP_CHANGE","VELION_MSG_STEP_COMPLETE","VELION_MSG_TOKEN_EXPIRED","VELION_MSG_EXIT","VELION_MSG_LIVENESS_VIDEO_UPLOADED","VELION_MSG_TOKEN","VELION_MANIFEST_PUBKEY_B64","base64ToArrayBuffer","b64","bin","atob","buf","ArrayBuffer","length","view","Uint8Array","i","charCodeAt","async","loadAndVerifyManifest","cdnUrl","pubKeyB64","manifestBuffer","manifestText","manifest","sigBuffer","pubKey","valid","resp","fetch","credentials","cache","redirect","status","ok","reason","detail","arrayBuffer","TextDecoder","decode","err","message","JSON","parse","version","entry","buildTime","signed","sigResp","text","trim","rawB64","raw","crypto","subtle","importKey","name","importPublicKey","publicKey","signature","data","verify","verifySignature","DEFAULT_CDN_URL","isSecureHttpUrl","url","u","URL","protocol","host","hostname","endsWith","SERIALIZABLE_CONFIG_KEYS","VelionOnboarding","constructor","config","this","iframe","container","messageHandler","containerResizeObserver","iframeHeightWindowResizeHandler","cdnOrigin","readyTimer","Object","freeze","clearReadyTimer","clearTimeout","armReadyTimer","ms","runtimeReadyTimeoutMs","setTimeout","onError","code","RUNTIME_LOAD_FAILED","start","INSTANCE_DESTROYED","ALREADY_STARTED","document","getElementById","containerId","CONTAINER_NOT_FOUND","replace","UNKNOWN","apiUrl","origin","parseOriginFromUrl","createElement","setAttribute","style","width","border","display","e","source","contentWindow","msg","type","onSuccess","payload","p","onStepChange","step","stepIndex","onStepComplete","onExit","onLivenessVideoUploaded","refresh","onTokenExpired","then","t","sendToIframe","SESSION_EXPIRED","window","addEventListener","replaceChildren","attachIframeHeightSync","resolveManifest","entryPath","startsWith","Promise","resolve","onload","initPayload","out","allowEmbedded","key","value","buildSerializableConfig","payloadForPostMessage","stringify","onerror","src","result","INTEGRITY_FAILED","allowUnverifiedCdn","Date","toISOString","sync","h","clientHeight","height","minHeight","requestAnimationFrame","ResizeObserver","observe","detachIframeHeightSync","disconnect","removeEventListener","postMessage","getStatus","getVersion","destroy","remove","setOpenCVBaseUrl","_url"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"velion-sdk.es.js","sources":["../../packages/types/src/index.ts","../src/manifest.ts","../src/velion-loader.ts","../src/index.ts"],"sourcesContent":["/**\n * @fileoverview Public TypeScript contracts for the Velion Onboarding SDK.\n *\n * Design principle: All types exported from this file constitute the public API.\n * Internal implementation types should live alongside their respective modules.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// STEP & FLOW TYPES\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The complete set of onboarding steps the backend can require.\n * The SDK dynamically receives the active subset for each session.\n *\n * @remarks\n * - `DEVICE_FINGERPRINT`: Silently collects device entropy via FingerprintJS.\n * - `DOCUMENTS_OCR`: Renders a UI for ID document image capture and upload.\n * - `LIVENESS_MATCH`: Drives the Velion Iris active-liveness flow over WebSocket (HUMAN only).\n * - `EMAIL_OTP` / `PHONE_OTP`: Contact verification via one-time passwords.\n * - `HUMAN_BASIC_INFO`: Natural-person identification data (HUMAN only).\n * - `LEGAL_BASIC_INFO`: Legal-entity identification data (LEGAL only).\n * - `EMAIL_CONTACT` / `PHONE_CONTACT`: contact collection without OTP.\n * - `TAX_INFO`, `HUMAN_EXTENDED_INFO`, `LEGAL_EXTENDED_INFO`, `ADDRESS_INFO`, `SWORN_DECLARATION`: form steps.\n * - `ECONOMIC_ACTIVITY_DECLARATION`: pick exactly one economic activity from a searchable list.\n * - `TERMS_AND_CONDITIONS`: accept configured T&C items.\n * - `FACE_MATCH`: server-side face comparison after liveness (no user input; SDK waits).\n * - `AML_SCREENING`: server-side PEP / sanctions screening (no user input; never routed to\n * the SDK as `NEXT_STEP`, only visible in `steps[]` and `STEP_UPDATED`).\n */\nexport type OnboardingStep =\n | 'DEVICE_FINGERPRINT'\n | 'HUMAN_BASIC_INFO'\n | 'LEGAL_BASIC_INFO'\n | 'EMAIL_OTP'\n | 'EMAIL_CONTACT'\n | 'PHONE_OTP'\n | 'PHONE_CONTACT'\n | 'TAX_INFO'\n | 'HUMAN_EXTENDED_INFO'\n | 'LEGAL_EXTENDED_INFO'\n | 'ADDRESS_INFO'\n | 'SWORN_DECLARATION'\n | 'ECONOMIC_ACTIVITY_DECLARATION'\n | 'DOCUMENTS_OCR'\n | 'LIVENESS_MATCH'\n | 'FACE_MATCH'\n | 'AML_SCREENING'\n | 'TERMS_AND_CONDITIONS';\n\n/**\n * Status of an individual onboarding step (matches backend OnboardingStepStatus).\n */\nexport type OnboardingStepStatus =\n | 'PENDING'\n | 'IN_PROGRESS'\n | 'COMPLETED'\n | 'SKIPPED'\n | 'FAILED';\n\n/**\n * Type of onboarding session (matches backend OnboardingType).\n * Constrains which steps are valid: HUMAN allows biometric steps, LEGAL uses entity data.\n */\nexport type OnboardingType = 'HUMAN' | 'LEGAL';\n\n/** Which face of the ID document is being captured. */\nexport type DocumentSide = 'FRONT' | 'BACK';\n\n/**\n * How the user can provide document images in the DOCUMENTS_OCR step.\n * - `'camera'`: Only \"Take photo\" (opens camera on supported devices).\n * - `'file'`: Only \"Choose file\" (file picker).\n * - `'both'`: Both options; implementer can style via theme.\n *\n * @default 'both'\n */\nexport type DocumentCaptureMode = 'camera' | 'file' | 'both';\n\n/**\n * Orientation of the document capture cutout / crop frame.\n * - `'landscape'`: Horizontal card (default — standard ID-1 cards).\n * - `'portrait'`: Vertical card (e.g. some passports, residence permits).\n *\n * @default 'landscape'\n */\nexport type DocumentOrientation = 'landscape' | 'portrait';\n\n/**\n * Optional overrides for document OCR UI (not part of `VelionConfig`; used internally).\n */\nexport interface DocumentsConfig {\n /**\n * When true (default), the document camera preview is mirrored (like selfie mode).\n * Set to false if the back camera shows the document inverted.\n *\n * @default true\n */\n mirrorDocumentVideo?: boolean;\n\n /**\n * Orientation of the document capture frame and crop.\n * - `'landscape'`: Horizontal card (default — standard ID-1 cards).\n * - `'portrait'`: Vertical card.\n *\n * @default 'landscape'\n */\n documentOrientation?: DocumentOrientation;\n}\n\n/** Generic visible/required field toggle used by workflow step configs. */\nexport interface StepFieldRule {\n visible: boolean;\n required: boolean;\n}\n\n/** Select option used by configurable step fields (e.g. HUMAN_EXTENDED_INFO gender/civil_status). */\nexport interface StepFieldOption {\n id: string;\n display_value?: string;\n}\n\n/** Field rule with optional selectable options. */\nexport interface StepFieldRuleWithOptions extends StepFieldRule {\n options?: StepFieldOption[];\n}\n\n/** STEP config for HUMAN_BASIC_INFO. */\nexport interface HumanBasicInfoStepConfig {\n accepted_country_documents?: Array<{\n country_code: string;\n document_types: Array<{ id: string; display_value?: string }>;\n }>;\n}\n\n/**\n * One selectable option row from TAX_INFO `accepted_tax_information` (backend GET session).\n * Prefer `code` as the value sent in POST tax-info; `id` is supported for older payloads.\n */\nexport interface TaxInfoConfigOptionRow {\n code?: string;\n id?: string;\n display_value?: string;\n tax_code?: string;\n}\n\n/** One country block in TAX_INFO step config (GET session `current_step.config`). */\nexport interface TaxInfoAcceptedCountryRow {\n country_code: string;\n identification_types?: TaxInfoConfigOptionRow[];\n /** Options for the \"Categoría fiscal\" / taxpayer category select. */\n taxpayer_categories?: TaxInfoConfigOptionRow[];\n}\n\n/** STEP config for TAX_INFO. */\nexport interface TaxInfoStepConfig {\n /** Identification types + taxpayer categories per country. */\n accepted_tax_information?: TaxInfoAcceptedCountryRow[];\n}\n\n/** STEP config for HUMAN_EXTENDED_INFO. */\nexport interface HumanExtendedInfoStepConfig {\n /** Optional top-level lists sent by backend for select fields. */\n gender_options?: StepFieldOption[];\n civil_status_options?: StepFieldOption[];\n fields?: Partial<\n Record<\n 'gender' | 'civil_status' | 'birth_date' | 'birth_country' | 'citizenship' | 'residence_country',\n StepFieldRuleWithOptions\n >\n >;\n}\n\n/** STEP config for ADDRESS_INFO. */\nexport interface AddressInfoStepConfig {\n address_type?: { id?: string; display_value?: string };\n fields?: Partial<\n Record<\n 'state' | 'city' | 'district' | 'neighborhood' | 'postal_code' | 'street' | 'number',\n StepFieldRule\n >\n >;\n /**\n * Optional ISO 3166-1 alpha-2 allowlist for the country selector. Only\n * consumed by the manual-entry form (when the SDK is configured without a\n * Google Maps key). When unset, every country is offered.\n */\n country_allow?: string[];\n}\n\n/** STEP config for SWORN_DECLARATION. */\nexport interface SwornDeclarationStepConfig {\n ask_pep?: boolean;\n ask_obligated_subject?: boolean;\n ask_fatca?: boolean;\n ask_ocde?: boolean;\n on_declared_pep?: string;\n on_declared_obligated_subject?: string;\n on_declared_fatca?: string;\n on_declared_ocde?: string;\n}\n\n/**\n * A single selectable economic activity presented in the\n * `ECONOMIC_ACTIVITY_DECLARATION` step. Mirrors the backend `EconomicActivityItem`\n * (`code` + `display_name`); `code` is unique within the list and is what the SDK\n * submits. There is no global catalog — each workflow declares its own list.\n */\nexport interface EconomicActivityItem {\n /** Machine-readable activity code (e.g. an AFIP/CLAE code) — unique within the list. */\n code: string;\n /** Human-readable label shown to the user. */\n display_name: string;\n}\n\n/** STEP config for ECONOMIC_ACTIVITY_DECLARATION (`current_step.config`). */\nexport interface EconomicActivityStepConfig {\n /** Selectable activities; always non-empty when the step is active. */\n items?: EconomicActivityItem[];\n}\n\n/** STEP config for DOCUMENTS_OCR. */\nexport interface DocumentsOcrStepConfig {\n country_allow?: string[];\n capture_methods?: {\n camera_scan?: boolean;\n upload?: boolean;\n };\n /** Crop frame orientation for document camera capture. */\n document_orientation?: DocumentOrientation;\n /** Mirror preview in document camera (default true). */\n mirror_document_video?: boolean;\n}\n\n/** STEP config for TERMS_AND_CONDITIONS. */\nexport interface TermsAndConditionsStepConfig {\n terms?: Array<{ title?: string; url?: string }>;\n}\n\n/** Generic per-step runtime config map carried by session state and SSE. */\nexport type StepConfigMap = Partial<Record<OnboardingStep, Record<string, unknown>>>;\n\n/**\n * Request body for POST /sessions/:sessionId/documents-init.\n * Allowed values are defined by the backend; the SDK currently sends a fixed type until\n * the session exposes selectable document types.\n */\nexport interface DocumentsInitRequest {\n document_type: string;\n}\n\n/**\n * Response from POST /sessions/:sessionId/documents-init.\n * Backend returns signed upload URLs so the SDK can PUT each document side\n * directly to storage (e.g. GCS) without routing bytes through the backend.\n */\n/**\n * Response of `POST /sessions/:id/documents-init`.\n *\n * Each URL is a GCS V4 signed PUT issued by the Velion CDN, valid for about\n * two minutes, and signed over `host`, `content-type` and\n * `x-goog-content-length-range`. The PUT must therefore send\n * `Content-Type: image/jpeg` **and** `x-goog-content-length-range:\n * 0,<max_file_size_bytes>` byte for byte, or GCS answers 400\n * `MalformedSecurityHeader` (OSDKW-5). Request the URLs right before the\n * upload, never ahead of the capture.\n */\nexport interface DocumentsInitResponse {\n /** Pre-signed URL to upload the front side (PUT, see the interface docs for the required headers). */\n front_side_upload_url: string;\n /** Pre-signed URL to upload the back side (PUT, see the interface docs for the required headers). */\n back_side_upload_url: string;\n /**\n * Upper bound the URLs were signed for; the exact number that goes into the\n * `x-goog-content-length-range` header. Also usable to reject oversize\n * captures before the PUT.\n */\n max_file_size_bytes?: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ERROR TYPES\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Granular error codes for camera/hardware failures.\n * Allows consumers to display localized, actionable messages to end-users.\n */\nexport enum CameraErrorCode {\n /** User explicitly denied camera access in the browser permission dialog. */\n PERMISSION_DENIED = 'CAMERA_PERMISSION_DENIED',\n /** No camera device was detected on the system. */\n DEVICE_NOT_FOUND = 'CAMERA_DEVICE_NOT_FOUND',\n /**\n * The camera could not be opened for reading (often in use by another app,\n * or blocked at the OS level). Maps to `NotReadableError` / `TrackStartError`.\n */\n DEVICE_NOT_READABLE = 'CAMERA_DEVICE_NOT_READABLE',\n /**\n * The page is served over HTTP (not HTTPS) in a production environment.\n * `getUserMedia` is blocked by the browser's Secure Context policy.\n */\n INSECURE_CONTEXT = 'CAMERA_INSECURE_CONTEXT',\n /**\n * Browser policy blocked camera (e.g. cross-origin iframe without permission,\n * or `Permissions-Policy` / Feature-Policy denying camera). Maps to `SecurityError`\n * from `getUserMedia` — distinct from {@link INSECURE_CONTEXT} (non-HTTPS).\n */\n POLICY_BLOCKED = 'CAMERA_POLICY_BLOCKED',\n /** The browser or WebView does not support the `getUserMedia` API. */\n NOT_SUPPORTED = 'CAMERA_NOT_SUPPORTED',\n /**\n * Camera access is blocked by a WebView policy (common in in-app browsers\n * on iOS/Android that restrict hardware access).\n */\n WEBVIEW_BLOCKED = 'CAMERA_WEBVIEW_BLOCKED',\n /** The requested video constraints (resolution, facing mode) are not satisfiable. */\n CONSTRAINT_ERROR = 'CAMERA_CONSTRAINT_ERROR',\n /** An unclassified camera error occurred. Check `cause` for details. */\n UNKNOWN = 'CAMERA_UNKNOWN_ERROR',\n}\n\n/**\n * Top-level SDK error codes covering all failure scenarios in the onboarding flow.\n */\nexport enum SdkErrorCode {\n // ── Session ──────────────────────────────────────────────────────────────\n /** The sessionToken was rejected by the backend (401/403). */\n SESSION_INVALID = 'SESSION_INVALID',\n /** The sessionToken has expired (typically a 401 after initial validation). */\n SESSION_EXPIRED = 'SESSION_EXPIRED',\n /** Could not fetch the initial session state from the backend. */\n SESSION_FETCH_FAILED = 'SESSION_FETCH_FAILED',\n /** Backend sent SESSION_DECLINED over SSE; session ended before completion. */\n SESSION_DECLINED = 'SESSION_DECLINED',\n /**\n * A step was completed but the backend did not route the session to the\n * next one within `routingTimeoutMs` (default 120 s): no `ROUTING_COMMAND`\n * arrived over SSE and `GET /sessions/:id` kept reporting the completed\n * step. The session is still alive on the backend; `stepCode` names the\n * completed step. Typical cause: a delayed or stalled onboarding worker.\n */\n ROUTING_TIMEOUT = 'ROUTING_TIMEOUT',\n\n // ── Modules ──────────────────────────────────────────────────────────────\n /** FingerprintJS failed to generate the visitor ID. */\n FINGERPRINT_FAILED = 'FINGERPRINT_FAILED',\n /** One or more document images failed to upload successfully. */\n DOCUMENT_UPLOAD_FAILED = 'DOCUMENT_UPLOAD_FAILED',\n /** The liveness check did not pass or returned an unexpected result. */\n LIVENESS_FAILED = 'LIVENESS_FAILED',\n /** The Liveness module failed to initialize (e.g., could not create the Iris session). */\n LIVENESS_INIT_FAILED = 'LIVENESS_INIT_FAILED',\n /**\n * The recorded liveness session video could not be uploaded to storage.\n * Best-effort only: this never fails the liveness step — it is logged so the\n * back-office knows the playback evidence is missing for the session.\n */\n LIVENESS_VIDEO_UPLOAD_FAILED = 'LIVENESS_VIDEO_UPLOAD_FAILED',\n\n // ── Camera ───────────────────────────────────────────────────────────────\n /**\n * A camera-related error occurred. Inspect `cameraCode` for the specific reason.\n * @see CameraErrorCode\n */\n CAMERA_ERROR = 'CAMERA_ERROR',\n\n // ── Network & API ────────────────────────────────────────────────────────\n /** A network-level failure (no internet, CORS, DNS). */\n NETWORK_ERROR = 'NETWORK_ERROR',\n /** The backend returned a non-2xx HTTP status. */\n API_ERROR = 'API_ERROR',\n\n // ── Configuration ────────────────────────────────────────────────────────\n /** The `containerId` provided in the config does not exist in the DOM. */\n CONTAINER_NOT_FOUND = 'CONTAINER_NOT_FOUND',\n /** `start()` was called on an SDK instance that is already running. */\n ALREADY_STARTED = 'ALREADY_STARTED',\n /** `start()` was called after `destroy()`. The instance is no longer usable. */\n INSTANCE_DESTROYED = 'INSTANCE_DESTROYED',\n\n /**\n * The page is running inside an iframe and embedding was not explicitly allowed.\n * Configure Content-Security-Policy: frame-ancestors or X-Frame-Options on the\n * page that hosts the SDK, or set allowEmbedded: true if embedding is intentional.\n */\n INSECURE_EMBED = 'INSECURE_EMBED',\n\n /**\n * The CDN-served manifest could not be downloaded or its Ed25519 signature did\n * not verify against the loader's embedded public key. The loader refuses to\n * mount the iframe to prevent execution of tampered/unsigned bundles.\n */\n INTEGRITY_FAILED = 'INTEGRITY_FAILED',\n /**\n * The iframe loaded but the runtime never reported `VELION_READY` within\n * `runtimeReadyTimeoutMs` (default 30 s). Typical causes: the CDN serves an\n * `index.html` whose hashed assets are missing (deploy under the wrong\n * prefix), a Content-Security-Policy on the host blocking the runtime's\n * scripts, or the runtime crashing before it could boot. Without this the\n * host saw a blank iframe and no callback at all.\n */\n RUNTIME_LOAD_FAILED = 'RUNTIME_LOAD_FAILED',\n\n UNKNOWN = 'UNKNOWN',\n}\n\n/**\n * A structured, machine-readable error object emitted by the SDK.\n *\n * @example\n * ```typescript\n * const sdk = new VelionOnboarding({\n * onError: (err) => {\n * if (err.code === SdkErrorCode.CAMERA_ERROR &&\n * err.cameraCode === CameraErrorCode.PERMISSION_DENIED) {\n * showCameraPermissionDialog();\n * }\n * }\n * });\n * ```\n */\nexport interface SdkError {\n /** Primary SDK-level error classification. */\n readonly code: SdkErrorCode;\n /** Human-readable English description for developer logging. */\n readonly message: string;\n /** Present when `code === SdkErrorCode.SESSION_DECLINED` (SSE `reason`). */\n readonly declineReason?: string;\n /**\n * Step the error refers to. For `SESSION_DECLINED` it is the SSE `step_code`;\n * for `ROUTING_TIMEOUT` it is the step that was completed and never routed.\n */\n readonly stepCode?: string;\n /**\n * Localized headline resolved by the SDK for SESSION_DECLINED.\n * Useful when hosts hide the built-in final result screen but still want\n * the same user-facing text in custom UI.\n */\n readonly declineTitle?: string;\n /**\n * Localized body message resolved by the SDK for SESSION_DECLINED.\n * Mirrors the text shown in the built-in final result screen.\n */\n readonly declineMessage?: string;\n /** Present when `code === SdkErrorCode.CAMERA_ERROR`. Provides camera-specific detail. */\n readonly cameraCode?: CameraErrorCode;\n /** HTTP status code when the error originated from an API call. */\n readonly httpStatus?: number;\n /**\n * The original caught value for debugging.\n * Not guaranteed to be an `Error` instance — could be a DOMException or string.\n */\n readonly cause?: unknown;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// THEMING\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * White-labeling theme configuration.\n * Each property maps to a CSS custom property injected on the SDK container,\n * making all SDK UI elements styleable via `--vl-*` variables.\n *\n * @example\n * ```typescript\n * const theme: VelionTheme = {\n * primaryColor: '#0052CC', // → --vl-primary-color\n * borderRadius: '12px', // → --vl-border-radius\n * fontFamily: 'Inter, sans-serif'// → --vl-font-family\n * };\n * ```\n */\nexport interface VelionTheme {\n /** Primary brand color. Used for buttons, progress indicators, and accents. */\n primaryColor?: string;\n /** Secondary color. Used for hover states and secondary actions. */\n secondaryColor?: string;\n /** Background color of the SDK container. */\n backgroundColor?: string;\n /** Main body text color. */\n textColor?: string;\n /** Secondary text color for subtitles, captions, and de-emphasized copy. */\n textSecondaryColor?: string;\n /** Border radius for cards, buttons, and inputs. */\n borderRadius?: string;\n /** Font family string. Must be available in the host page's CSS context. */\n fontFamily?: string;\n /** Color used for error states and destructive actions. */\n errorColor?: string;\n /** Color used for success confirmations. */\n successColor?: string;\n /** Color used for warnings and informational alerts. */\n warningColor?: string;\n /** Border color for input fields and separators. */\n borderColor?: string;\n /** Box shadow definition for elevated elements (cards, modals). */\n cardShadow?: string;\n\n /**\n * When `true`, the round icon that sits above each step's title is hidden.\n * Only the title and subtitle are rendered. Useful when the host UI already\n * provides its own visual identity per step or when a denser layout is\n * preferred.\n *\n * @default false\n */\n hideStepIcons?: boolean;\n\n /**\n * When `true`, the thin progress bar at the top of the SDK container is\n * hidden. Equivalent to setting `VelionConfig.showProgressBar: false` —\n * either flag is enough to disable it (whichever is more convenient given\n * how the host configures the SDK). Disable when your host UI already\n * surfaces its own progress indicator.\n *\n * @default false\n */\n hideProgressBar?: boolean;\n}\n\n/**\n * Optional title and body for one terminal verdict on the final result screen.\n */\nexport interface VelionFinalResultStatusCopy {\n title?: string;\n message?: string;\n}\n\n/**\n * Configurable copy for the built-in final result screen (`showFinalResultScreen`).\n * Omitted fields fall back to SDK defaults for the active `locale`.\n */\nexport interface VelionFinalResultScreenTexts {\n approved?: VelionFinalResultStatusCopy;\n manualReview?: VelionFinalResultStatusCopy;\n rejected?: VelionFinalResultStatusCopy;\n /** Copy when {@link OnboardingResult.status} is `DECLINED`. */\n declined?: VelionFinalResultStatusCopy;\n /** Primary button label (e.g. \"Entendido\" / \"Got it\"). */\n dismissLabel?: string;\n}\n\n/**\n * Localized title/body for one `reason` on the SESSION_DECLINED screen.\n * Keys match backend `reason` (e.g. `TAX_INFO_FAILED`).\n */\nexport interface VelionSessionDeclinedReasonMessages {\n en?: VelionFinalResultStatusCopy;\n es?: VelionFinalResultStatusCopy;\n}\n\n/**\n * Per-reason overrides for the built-in SESSION_DECLINED screen (`showFinalResultScreen` not false).\n * Unspecified reasons fall back to SDK defaults when available, then generic copy.\n */\nexport type VelionSessionDeclinedReasonTexts = Record<\n string,\n VelionSessionDeclinedReasonMessages\n>;\n\n/**\n * Per-step overrides for the retry dialog shown when the backend emits a\n * `ROUTING_COMMAND / RETRY_STEP` (the step failed but the retry quota is not\n * exhausted). Keyed by step code (e.g. `LIVENESS_MATCH`, `DOCUMENTS_OCR`).\n * Unspecified steps fall back to SDK per-step defaults, then generic copy.\n *\n * Note: the SDK intentionally ignores the backend `reason` for this dialog —\n * it is a diagnostic string (e.g. `\"STEP_FAILED_RETRY_ALLOWED (attempt 1/3)\"`),\n * not user-facing localized copy. The copy is derived from the step instead.\n */\nexport type VelionRetryStepTexts = Record<\n string,\n VelionSessionDeclinedReasonMessages\n>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// CALLBACKS\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The complete set of lifecycle callbacks the host application can subscribe to.\n * All callbacks are optional. The SDK is still functional without any of them,\n * but `onError` and `onSuccess` are strongly recommended for production.\n */\nexport interface VelionCallbacks {\n /**\n * Fires when the entire onboarding flow has been completed successfully\n * and the backend has confirmed the session as resolved.\n *\n * @param result - Final onboarding verdict from the backend.\n */\n onSuccess?: (result: OnboardingResult) => void;\n\n /**\n * Fires when an unrecoverable error terminates the onboarding flow.\n * Any terminal failure (camera error, document capture failure, upload failure,\n * liveness failure, API error, etc.) is delivered here so the host can notify the user.\n * After this callback, the SDK instance is in an error state.\n * Call `sdk.destroy()` and re-instantiate to retry.\n *\n * @param error - Structured error with a machine-readable code (e.g. CAMERA_ERROR, DOCUMENT_UPLOAD_FAILED).\n */\n onError?: (error: SdkError) => void;\n\n /**\n * Fires each time the active step changes during the flow.\n * Useful for updating a custom step indicator in the host UI.\n *\n * The required step list is resolved dynamically by the backend, so the SDK\n * does not know the total step count up front. Callers that need a progress\n * indicator should rely on the SDK's built-in progress bar (or the trickling\n * counter in `stepIndex`).\n *\n * @param step - The step that just became active.\n * @param stepIndex - Zero-based index of the current step.\n */\n onStepChange?: (step: OnboardingStep, stepIndex: number) => void;\n\n /**\n * Fires immediately after a step succeeds, before advancing to the next one.\n * Useful for analytics or triggering intermediate UI transitions.\n *\n * @param step - The step that was just completed.\n */\n onStepComplete?: (step: OnboardingStep) => void;\n\n /**\n * Fires for each SSE event from GET /sessions/:sessionId/events\n * (e.g. STEP_UPDATED, SESSION_APPROVED).\n */\n onSessionEvent?: (event: OnboardingSessionEvent) => void;\n\n /**\n * Called when the user taps the dismiss control on the final result screen\n * (only when the final result screen is shown). `onSuccess` has already run.\n */\n onFinalResultScreenDismiss?: () => void;\n\n /**\n * Fires when the LIVENESS_MATCH step successfully uploaded the recorded\n * session video to the onboarding storage (GCS PUT returned 200). Best-effort:\n * if the upload failed, timed out, or the session had no `video_upload` URL,\n * the callback is not invoked — liveness still completes normally.\n *\n * @param ref - URL and metadata of the uploaded video.\n */\n onLivenessVideoUploaded?: (ref: LivenessVideoRef) => void;\n\n /**\n * Fires when the user leaves the onboarding flow without a terminal verdict —\n * either by tapping \"back\" on the first reversible step (`BACK_EXIT`) or\n * \"Cancel\" on the retry dialog shown after a `RETRY_STEP` (`RETRY_CANCELLED`).\n * In both cases the backend told the SDK to leave the flow and hand control\n * back to the host; the SDK has already torn down its UI.\n *\n * Distinct from `onSuccess` (a terminal verdict) and `onError` (a failure):\n * the host should typically dismiss/close the SDK surface when this fires.\n *\n * @param exit - Why the flow exited and the step it exited from.\n */\n onExit?: (exit: OnboardingExit) => void;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// MAIN CONFIGURATION\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Root configuration object for the `VelionOnboarding` class.\n *\n * @example\n * ```typescript\n * const sdk = new VelionOnboarding({\n * containerId: 'velion-root',\n * sessionId: 'ses_abc123',\n * sessionToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',\n * apiUrl: 'https://api.velion.la/v1',\n * theme: { primaryColor: '#0052CC' },\n * onSuccess: (result) => console.log('Done!', result),\n * onError: (err) => console.error('SDK Error:', err),\n * });\n *\n * await sdk.start();\n * ```\n */\nexport interface VelionConfig extends VelionCallbacks {\n /**\n * The `id` of an existing DOM element where the SDK will render its UI.\n * The element **must** be present in the document at the time `start()` is called.\n *\n * @example `'velion-kyc-container'`\n */\n containerId: string;\n\n /**\n * The unique session identifier generated by your backend for this\n * onboarding session. Used to scope all API calls.\n *\n * @example `'ses_9f8d7c6b5a4e3f2d'`\n */\n sessionId: string;\n\n /**\n * A short-lived, opaque authorization token (e.g. HS256 JWT) issued by your\n * backend when creating the session. Sent as `Authorization: Bearer <sessionToken>`\n * on every SDK request. Required for all endpoints behind the gateway.\n *\n * **Security note:** This token is held in memory only (never in localStorage\n * or cookies) and is automatically discarded on `destroy()`.\n */\n sessionToken: string;\n\n /**\n * ISO-8601 expiry timestamp of the sessionToken. If provided, the SDK may\n * check expiration before requests and emit SESSION_EXPIRED without a round-trip.\n * When the backend returns 401/403, the SDK emits SESSION_EXPIRED anyway.\n */\n expiresAt?: string;\n\n /**\n * Base URL of the Velion onboarding edge. Must **not** include a trailing slash.\n * SDK paths are relative to this (e.g. GET `/sessions/{sessionId}`).\n *\n * Defaults to the public production edge `https://sdk.velion.la/onboarding`.\n * Override only for staging or self-hosted environments.\n */\n apiUrl?: string;\n\n /**\n * Optional white-labeling theme. If omitted, SDK renders with Velion defaults.\n */\n theme?: VelionTheme;\n\n /**\n * Global visual mode for SDK overlays and controls.\n * - `'light'`: Light translucent surfaces.\n * - `'dark'`: Dark translucent surfaces.\n *\n * @default 'light'\n */\n colorMode?: 'light' | 'dark';\n\n /**\n * BCP 47 locale tag for SDK UI copy (labels, instructions, error messages).\n *\n * @default 'es'\n */\n locale?: string;\n\n /**\n * When `true`, after the backend returns a terminal verdict the SDK shows a\n * full-screen summary (APPROVED, MANUAL_REVIEW, REJECTED) inside the container,\n * styled with `theme` and `colorMode`. `onSuccess` still fires with the result.\n *\n * @default true\n */\n showFinalResultScreen?: boolean;\n\n /**\n * When `true` (default), the SDK renders a thin progress bar at the top of\n * the container that advances as the user moves through steps. Disable for\n * embedded experiences that already provide their own progress UI.\n *\n * @default true\n */\n showProgressBar?: boolean;\n\n /**\n * Overrides for default titles, messages, and dismiss button on the final result screen.\n * Only used when the final result screen is shown (`showFinalResultScreen` is not `false`).\n */\n finalResultScreenTexts?: VelionFinalResultScreenTexts;\n\n /**\n * Optional copy for SESSION_DECLINED SSE screens, keyed by backend `reason`.\n * Merged with SDK defaults (e.g. `TAX_INFO_FAILED`) for `locale` `en` / `es`.\n */\n sessionDeclinedReasonTexts?: VelionSessionDeclinedReasonTexts;\n\n /**\n * Optional copy for the retry dialog (`ROUTING_COMMAND / RETRY_STEP`), keyed by\n * step code (e.g. `LIVENESS_MATCH`). Merged with SDK per-step defaults for\n * `locale` `en` / `es`; unspecified steps fall back to generic retry copy.\n */\n retryStepTexts?: VelionRetryStepTexts;\n\n /**\n * When `true` (default), shows the Velion preparation screen (face icon, tips,\n * \"Start verification\") before connecting to Iris. When `false`, skips it and\n * starts the camera + Iris session immediately on step entry.\n *\n * @default true\n */\n livenessShowIntro?: boolean;\n\n /**\n * When `false`, hides the \"Atrás\" / \"Back\" button on the liveness preparation\n * screen. The button is only present in the intro; once the camera capture\n * begins the user must complete the Iris challenges to leave the step.\n *\n * @default true\n */\n livenessShowBackButton?: boolean;\n\n /**\n * If true, allows the SDK to run when the page is inside an iframe.\n * Only set to true if you have configured Content-Security-Policy: frame-ancestors\n * or X-Frame-Options on the HTTP response of the page that hosts the SDK.\n * If false (default) and the page is embedded in an iframe, the SDK emits\n * INSECURE_EMBED and does not start.\n *\n * @default false\n */\n allowEmbedded?: boolean;\n\n /**\n * Internal — set by the Velion CDN entry (`cdn-main.ts`) to the `event.origin`\n * captured from the loader's first VELION_INIT message. The SDK validates it\n * against `allowed_parent_origins` returned by the backend and refuses to start\n * if the origin is not in the customer's registered allowlist.\n *\n * Direct integrators (running VelionOnboarding without the loader iframe)\n * should leave this unset.\n *\n * @internal\n */\n iframeParentOrigin?: string;\n\n /**\n * Fingerprint Pro API endpoint (custom subdomain). Used by the DEVICE_FINGERPRINT\n * step to load and identify visitors. Only set this when using a custom Fingerprint\n * subdomain or proxy; otherwise the default Velion endpoint is used.\n *\n * @default 'https://fp.velion.la'\n */\n fingerprintEndpoint?: string;\n\n /**\n * Fingerprint Pro public API key. Used by the DEVICE_FINGERPRINT step to\n * identify visitors. Defaults to the shared Velion public key — set this\n * only when your tenant has its own Fingerprint workspace.\n *\n * @default Velion's shared public key\n */\n fingerprintApiKey?: string;\n\n /**\n * Hard ceiling, in milliseconds, for the DEVICE_FINGERPRINT step (agent\n * load + identification). The Fingerprint agent has its own internal\n * timeout, but with tracker blockers or a blackholed endpoint it can hang\n * well past it; the SDK races against this value so the step fails with\n * `FINGERPRINT_FAILED` instead of leaving the user on a blank screen.\n *\n * @default 15000\n */\n fingerprintTimeoutMs?: number;\n\n /**\n * Total wait, in milliseconds, for the backend to route the session after a\n * step is completed. The SDK waits for the SSE `ROUTING_COMMAND` and falls\n * back to `GET /sessions/:id` every 30 s; when the backend keeps reporting\n * the completed step (delayed or stalled worker) those cycles used to repeat\n * for ever. Past this bound the flow fails with `ROUTING_TIMEOUT` so the host\n * gets a callback instead of an endless spinner.\n *\n * @default 120000\n */\n routingTimeoutMs?: number;\n\n /**\n * Google Maps Platform API key (Maps JavaScript API + Places). Required for the\n * {@code ADDRESS_INFO} step to show the map, search, and geocoding. Omit only if\n * your flow never reaches that step.\n */\n googleMapsApiKey?: string;\n\n /**\n * Optional calibration tag forwarded to Iris for the LIVENESS_MATCH step.\n * When the Iris worker is running with `CALIBRATION_BACKEND=fs|gcs`, every\n * frame of this session is mirrored to the dataset bucket with the tag in\n * `metadata.json`, so we can build labeled training sets for the spoof /\n * liveness models. Does NOT affect the score.\n *\n * Suggested values (must match the worker's known labels — see\n * `iris-worker` README): `REAL_GOOD_LIGHT`, `REAL_BAD_LIGHT`, `REAL_BACKLIT`,\n * `REAL_BUSY_BG`, `REAL_NEUTRAL_BG`, `REAL_GLASSES`, `REAL_OUTDOOR`,\n * `FAKE_PHONE_PHOTO`, `FAKE_PHONE_VIDEO`, `FAKE_TABLET_VIDEO`,\n * `FAKE_MONITOR_VIDEO`, `FAKE_PRINT`, `FAKE_MASK`, `UNKNOWN`, `EDGE_CASE`.\n *\n * Leave undefined for normal user-facing sessions.\n */\n livenessCalibrationLabel?: string;\n\n /**\n * When true, the SDK enables verbose `console.info` logging at runtime even in\n * production builds. Useful when reproducing customer issues. Independent of the\n * build-time `__DEBUG__` flag, which already gates dev-server logs.\n *\n * @default false\n */\n debug?: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// BACKEND RESPONSE SHAPES\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Backend step DTO (OnboardingStepDto). GET session returns an array of these.\n */\nexport interface OnboardingStepDto {\n code: string;\n name?: string;\n description?: string;\n order?: number;\n status?: string;\n /**\n * Machine-readable cause of the current `status` when it is `FAILED` or\n * `MANUAL_REVIEW` (backend `StepStatusDetailCode`, VON-54 / VON-56). Examples:\n * `DOC_UNREADABLE`, `DOC_MRZ_NOT_READABLE`, `DOC_EXPIRED`, `DOC_AGE_EXCLUSION`,\n * `LIVENESS_BELOW_THRESHOLD`, `LIVENESS_SPOOF_SUSPECTED`,\n * `FACE_MATCH_BELOW_THRESHOLD`, `FINGERPRINT_BOT_DETECTED`,\n * `TAX_ARCA_VALIDATION_FAILED`, `TERMS_NOT_ACCEPTED`,\n * `ECONOMIC_ACTIVITY_INVALID_SELECTION`, `STEP_UNEXPECTED_ERROR`. Hosts may\n * use it for analytics or support tooling; the SDK derives its user-facing\n * copy from the step, never from this code.\n */\n status_detail?: string;\n required?: boolean;\n /** Step-specific payload (e.g. OCR fields, fingerprint, T&C items). */\n result?: Record<string, unknown>;\n /** Raw workflow config for this step (thresholds, accepted documents, …). */\n config?: Record<string, unknown>;\n /**\n * Whether the user may navigate back INTO this step after completing it\n * (intrinsic per-step flag the backend stamps from the workflow). Used only\n * to derive `can_go_back` for steps reached via SSE (which doesn't carry the\n * flag) and for prefill — NOT to decide whether to render the back button.\n * @see OnboardingDtoResponse.can_go_back\n */\n reversible?: boolean;\n}\n\n/**\n * Backend session/onboarding DTO (OnboardingDto). Returned by GET /sessions/:sessionId.\n * The SDK maps this to SessionState for the step machine.\n */\nexport interface OnboardingDtoResponse {\n id?: string;\n related_id?: string;\n onboarding_type?: string;\n status?: string;\n current_step?: {\n code?: string;\n status?: string;\n config?: Record<string, unknown>;\n /**\n * Previously-captured data for this step, present when a reverse navigation\n * re-opened a completed step. The SDK uses it as editable prefill.\n */\n result?: Record<string, unknown>;\n /** Intrinsic per-step reversibility (prefill / SSE-derivation only). */\n reversible?: boolean;\n };\n steps?: OnboardingStepDto[];\n created_at?: string;\n updated_at?: string;\n completed_at?: string;\n metadata?: Record<string, unknown>;\n /**\n * Whether the SDK should render the \"back\" control on the current step.\n * Computed server-side from the workflow graph (barrier model): `true` when\n * there is a previous reversible step to re-open, OR when the current step is\n * the first reversible step (back exits the flow via `BACK_EXIT`). The SDK\n * uses **this** — not a step's intrinsic `reversible` flag — to decide whether\n * to show the back button.\n */\n can_go_back?: boolean;\n /**\n * Origins (scheme + host + port) authorized by the customer to embed the SDK.\n * The iframe validates `event.origin` of the loader's first VELION_INIT against\n * this list and aborts with INSECURE_EMBED otherwise. Backend must also enforce\n * server-side via the `Origin` header on every API call.\n */\n allowed_parent_origins?: string[];\n}\n\n/**\n * Session state used by the SDK step machine. Derived from GET /sessions/:sessionId\n * (OnboardingDto): steps sorted by order → requiredSteps, first non-completed index → currentStepIndex.\n */\nexport interface SessionState {\n /** Current runnable step reported by backend session state. */\n currentStep?: OnboardingStep;\n /** Step-specific config for `currentStep` from GET /sessions/:id `current_step.config`. */\n currentStepConfig?: Record<string, unknown>;\n /** Cached configs discovered from GET session and SSE `step_config`. */\n stepConfigs?: StepConfigMap;\n /** Session type (HUMAN or LEGAL). Constrains which steps are applicable. */\n onboardingType?: OnboardingType;\n /**\n * Whether the SDK should render the \"back\" control on `currentStep`. Mapped\n * from GET `can_go_back` (workflow-graph computed). When absent (e.g. a step\n * reached via SSE, which doesn't carry the flag) the orchestrator derives it\n * from {@link stepReversibility}.\n */\n canGoBack?: boolean;\n /**\n * Per-step intrinsic reversibility in workflow order, cached from the initial\n * GET so the orchestrator can derive `can_go_back` for steps reached via SSE:\n * a step can go back when any earlier step is reversible, or the step itself\n * is the first reversible one (back exits the flow).\n */\n stepReversibility?: Array<{ code: OnboardingStep; reversible: boolean }>;\n /**\n * Previously-captured data for `currentStep`, present when a reverse\n * navigation re-opened a completed step (GET reflects it on\n * `current_step.result`). Applied by the SDK as editable prefill.\n */\n currentStepResult?: Record<string, unknown>;\n /** Arbitrary session metadata the backend may attach. */\n metadata?: Record<string, unknown>;\n /**\n * Customer-registered allowlist of origins authorized to embed the SDK.\n * When provided, the SDK validates the loader's parent origin against this list.\n */\n allowedParentOrigins?: string[];\n}\n\n/**\n * Why the onboarding flow ended without a terminal verdict, delivered to\n * {@link VelionCallbacks.onExit}.\n *\n * Triggers:\n * - `BACK_EXIT`: the user tapped \"back\" on the first reversible step, where\n * there is no earlier step to return to, so the SDK left the flow and\n * returned control to the host.\n * - `RETRY_CANCELLED`: the user tapped \"Cancel\" on the retry dialog shown after\n * a `ROUTING_COMMAND / RETRY_STEP`. The backend session stays `IN_PROGRESS`\n * (there is no cancellation endpoint); the host may re-open it later via\n * `GET /sessions/:id`.\n */\nexport interface OnboardingExit {\n /** The reason the flow exited. */\n reason: 'BACK_EXIT' | 'RETRY_CANCELLED';\n /** The step the user exited from (when the backend provides `step_code`). */\n step?: OnboardingStep;\n}\n\n/**\n * A single T&C line item (matches backend TermsAndConditionsItemDto / domain record).\n */\nexport interface TermsAndConditionsItem {\n id: string;\n description: string;\n /** Optional URL to the full document. */\n link?: string;\n}\n\n/**\n * Response from session creation (POST from your backend). Use sessionId and\n * sessionToken when instantiating VelionOnboarding; pass expiresAt for optional\n * client-side expiration checks.\n */\nexport interface SessionCreationResponse {\n session_id: string;\n session_token: string;\n expires_at: string;\n status?: string;\n}\n\n/**\n * Shape of the final result the SDK hands to `onSuccess` once the session\n * reaches a terminal status. Built from the terminal SSE event and the\n * `GET /sessions/:sessionId` snapshot (the backend exposes no separate\n * `/result` endpoint).\n */\nexport interface OnboardingResult {\n /** The session ID that was completed. Matches `VelionConfig.sessionId`. */\n sessionId: string;\n /** ISO 8601 timestamp of when the backend finalized the session. */\n completedAt: string;\n /**\n * The final KYC verdict.\n * - `APPROVED`: All checks passed automatically.\n * - `MANUAL_REVIEW`: Flagged for human review (common in AML flows). Backend `IN_REVIEW` maps here.\n * - `REJECTED`: Automatically rejected (fraud signals, liveness failure, etc.).\n * - `DECLINED`: Session ended in a declined state (matches GET session `status: DECLINED`).\n */\n status: 'APPROVED' | 'MANUAL_REVIEW' | 'REJECTED' | 'DECLINED';\n /** An optional tracking reference for support and audit trails. */\n referenceId?: string;\n /**\n * When the terminal outcome came from SSE, optional backend `reason` (e.g.\n * `LIVENESS_SCORE_IN_REVIEW_RANGE` on SESSION_MANUAL_REVIEW) for localized final-screen copy.\n */\n terminalReason?: string;\n}\n\n/**\n * Response from `POST /sessions/:sessionId/liveness-init`.\n * The backend returns the Iris session details the SDK needs to open the WebSocket\n * and stream frames. Credentials are not used: Iris owns the session lifecycle.\n */\nexport interface LivenessSessionResponse {\n /** Iris session identifier. */\n liveness_session_id?: string;\n /** Full WebSocket URL the SDK must connect to (e.g. wss://iris.host/ws/iris/{id}). */\n websocket_url?: string;\n /** ISO-8601 expiry of the Iris session. */\n expires_at?: string;\n /** Effective Iris challenge identifiers in order (TURN_LEFT, SMILE, …). */\n challenges?: string[];\n /** Optional JWT for the WebSocket handshake when Iris auth is enabled. */\n connection_token?: string;\n /**\n * Optional base64 HMAC-SHA256 key. When present, the SDK signs every JPEG\n * frame in a JSON envelope (canonical line: `sessionId\\nframeId\\nts\\nsha256hex(jpeg)`).\n */\n frame_integrity_key_base64?: string;\n /**\n * Signed PUT URL the SDK uses to upload the locally-recorded liveness video\n * directly to the onboarding GCS bucket. `null` (or absent) when video\n * recording is disabled or the signing failed — the SDK skips the upload\n * silently (liveness still works without the video; only the back-office\n * loses the playback evidence).\n */\n video_upload?: VideoUpload | null;\n}\n\n/**\n * Signed-upload payload returned by `POST /liveness-init`. Mirrors the backend\n * `LivenessInitResponseDto.VideoUpload`. The SDK PUTs the recorded video bytes\n * to `upload_url` (Content-Type matching the recorded container) before calling\n * `POST /liveness-verify` with the resulting metadata.\n */\nexport interface VideoUpload {\n /** Absolute https://storage.googleapis.com/... URL with V4 signature params. */\n upload_url: string;\n /** ISO-8601 instant after which GCS rejects the signature. */\n expires_at: string;\n /** HTTP method to use. Always `\"PUT\"` today; future-proofing. */\n method: 'PUT' | string;\n}\n\n/**\n * Optional body of `POST /liveness-verify`. The SDK only includes it when the\n * recorded video uploaded successfully (HTTP 200 from GCS). When omitted, the\n * backend completes liveness based on the Iris score only.\n */\nexport interface LivenessVerifyVideoMeta {\n /** MIME type that was sent in the PUT `Content-Type` header (e.g. `video/mp4`, `video/webm`). */\n content_type: string;\n /** Recorded video duration in milliseconds, or `null` if it could not be determined. */\n duration_ms: number | null;\n}\n\n/**\n * Payload delivered to the optional `onLivenessVideoUploaded` callback after a\n * successful upload of the liveness video. Best-effort: the callback only\n * fires when the GCS PUT returned 200.\n */\nexport interface LivenessVideoRef {\n /** Signed `upload_url` the SDK PUT the video to. */\n upload_url: string;\n /** MIME type used in the PUT. */\n content_type: string;\n /** Recorded video duration in milliseconds, or `null` when unknown. */\n duration_ms: number | null;\n /** Recorded file size in bytes, or `null` when unknown. */\n size_bytes: number | null;\n}\n\n/**\n * Payload for POST /sessions/:sessionId/basic-info/human (HUMAN_BASIC_INFO step).\n * Matches backend SubmitHumanBasicInfoRequestDto (snake_case in JSON).\n */\nexport interface HumanBasicInfoPayload {\n first_name: string;\n middle_name?: string;\n last_name: string;\n second_last_name?: string;\n identification_type: string;\n identification_id: string;\n}\n\n/**\n * Payload for POST /sessions/:sessionId/basic-info/legal (LEGAL_BASIC_INFO step).\n * Matches backend SubmitLegalBasicInfoRequestDto (snake_case in JSON).\n */\nexport interface LegalBasicInfoPayload {\n legal_name: string;\n legal_form: string;\n identification_type: string;\n identification_id: string;\n}\n\n/**\n * Single selectable option for tax category or fiscal identifier type (TAX_INFO step).\n * `id` is submitted to the API; `description` is the user-visible label.\n */\nexport interface TaxInfoOption {\n id: string;\n description: string;\n}\n\n/**\n * Resolved selectable lists for the TAX_INFO step UI (from `accepted_tax_information` only; no SDK defaults).\n */\nexport interface TaxInfoConfig {\n /**\n * Fiscal regime / category options (e.g. Monotributo). The chosen `id` is sent as `taxpayer_type`.\n */\n taxpayerCategories: TaxInfoOption[];\n /**\n * Tax identification scheme options (e.g. CUIT). The chosen `id` is sent as `taxpayer_id_type`.\n */\n fiscalIdentifierTypes: TaxInfoOption[];\n}\n\n/** POST /sessions/:id/tax-info — SubmitTaxInfoRequestDto. */\nexport interface TaxInfoPayload {\n /** Selected fiscal category value (`code` from step config `taxpayer_categories`). */\n taxpayer_type: string;\n /** Selected fiscal identifier kind id (e.g. `CUIT` from step config `identification_types`). */\n taxpayer_id_type: string;\n /** Tax identification number for the chosen type. */\n taxpayer_id: string;\n}\n\n/** POST /sessions/:id/extended-info/human — SubmitHumanExtendedInfoRequestDto. */\nexport interface HumanExtendedInfoPayload {\n gender?: string;\n civil_status?: string;\n birth_date?: string;\n birth_country?: string;\n citizenship?: string;\n residence_country?: string;\n}\n\n/** POST /sessions/:id/extended-info/legal — SubmitLegalExtendedInfoRequestDto. */\nexport interface LegalExtendedInfoPayload {\n incorporation_date: string;\n incorporation_country: string;\n}\n\n/** POST /sessions/:id/address-info — SubmitAddressInfoRequestDto. */\nexport interface AddressInfoPayload {\n type: string;\n country: string;\n state: string;\n city: string;\n district?: string;\n neighborhood?: string;\n street: string;\n number: string;\n floor?: string;\n apartment?: string;\n details?: string;\n zip_code: string;\n latitude?: number;\n longitude?: number;\n}\n\n/** POST /sessions/:id/sworn-declaration — SubmitSwornDeclarationRequestDto. */\nexport interface SwornDeclarationPayload {\n is_pep: boolean;\n is_ssoo: boolean;\n is_fatca: boolean;\n is_ocde: boolean;\n}\n\n/**\n * POST /sessions/:id/economic-activity — EconomicActivitySelectionDto.\n *\n * Carries the `code` of the single economic activity the user selected for the\n * `ECONOMIC_ACTIVITY_DECLARATION` step. The Worker validates it asynchronously\n * against the configured items; an invalid code surfaces over SSE as\n * `RETRY_STEP` (re-mounts the step) or `SESSION_DECLINED`, never as an HTTP error.\n */\nexport interface EconomicActivitySelectionPayload {\n /** `code` of the selected EconomicActivityItem. */\n code: string;\n}\n\n/** POST /sessions/:id/contact/email — SubmitEmailContactRequestDto. */\nexport interface EmailContactPayload {\n email: string;\n}\n\n/** POST /sessions/:id/contact/phone — SubmitPhoneContactRequestDto. */\nexport interface PhoneContactPayload {\n phone: string;\n}\n\n/** POST /sessions/:id/terms-and-conditions — SubmitTermsAndConditionsRequestDto. */\nexport interface TermsAndConditionsAcceptancePayload {\n item_id: string;\n accepted: boolean;\n}\n\nexport interface TermsAndConditionsSubmitPayload {\n acceptances: TermsAndConditionsAcceptancePayload[];\n}\n\n/**\n * SSE event payload (OnboardingSessionEvent). Matches the backend record\n * la.velion.onboarding.api...sdk.OnboardingSessionEventDto (snake_case in JSON).\n *\n * Received on GET /public/sdk/sessions/:sessionId/events. The backend sends\n * ServerSentEvent with id = event_id (epoch millis) for Last-Event-ID replay.\n *\n * Event types (event_type):\n * - STEP_UPDATED — a workflow step changed status (step_code, step_status)\n * - ROUTING_COMMAND — next/retry/terminate instruction (action: NEXT_STEP | RETRY_STEP | TERMINATE)\n * - SESSION_APPROVED — all checks passed; session_status = APPROVED\n * - SESSION_MANUAL_REVIEW — manual review required; session_status = MANUAL_REVIEW\n * - SESSION_DECLINED — session ended with decline (step_code, session_status DECLINED, reason)\n */\nexport interface OnboardingSessionEvent {\n /** Onboarding session identifier (Long in backend). */\n session_id?: number;\n /** Discriminator: STEP_UPDATED | ROUTING_COMMAND | SESSION_APPROVED | SESSION_MANUAL_REVIEW | SESSION_DECLINED */\n event_type?: string;\n /** Present for STEP_UPDATED or ROUTING_COMMAND (next step code). */\n step_code?: string;\n /** Present for STEP_UPDATED; e.g. COMPLETED. */\n step_status?: string;\n /** Present for terminal events; e.g. APPROVED | MANUAL_REVIEW | REJECTED. */\n session_status?: string;\n /**\n * For ROUTING_COMMAND: NEXT_STEP | RETRY_STEP (render step_code), TERMINATE\n * (end flow), PREVIOUS_STEP (reverse navigation — re-open step_code with\n * step_result as prefill), or BACK_EXIT (user backed out of the first\n * reversible step — leave the flow and notify the host via `onExit`).\n */\n action?: string;\n /** For SESSION_DECLINED / SESSION_MANUAL_REVIEW or ROUTING_COMMAND TERMINATE; e.g. UNREADABLE_DOCUMENT. */\n reason?: string;\n /** Step-specific config sent with ROUTING_COMMAND NEXT_STEP / PREVIOUS_STEP. */\n step_config?: Record<string, unknown>;\n /**\n * Previously-captured data for the re-opened step, sent with ROUTING_COMMAND\n * PREVIOUS_STEP. Applied by the SDK as editable prefill.\n */\n step_result?: Record<string, unknown>;\n /**\n * Whether the step the user is landing on can itself go back further. Carried\n * on ROUTING_COMMAND when the backend computes it; when absent the SDK derives\n * it from the cached workflow reversibility map.\n */\n can_go_back?: boolean;\n /** Epoch millis; used as SSE id for Last-Event-ID on reconnect. */\n event_id?: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// BACKEND COMMANDS (Listen & Obey)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Backend command: advance to a specific step. The SDK must render that step's\n * screen without evaluating business rules locally.\n */\nexport interface BackendCommandGoToStep {\n action: 'GO_TO_STEP';\n stepCode: OnboardingStep;\n}\n\n/**\n * Backend command: show an error message to the user. The SDK displays the\n * message and does not advance (backend owns the decision).\n */\nexport interface BackendCommandShowError {\n action: 'SHOW_ERROR';\n message: string;\n}\n\n/**\n * Backend command: flow is complete. SDK should fetch final result and fire onSuccess.\n */\nexport interface BackendCommandFlowComplete {\n action: 'FLOW_COMPLETE';\n}\n\n/**\n * Backend command: retry the current step (e.g. after transient failure).\n */\nexport interface BackendCommandRetry {\n action: 'RETRY';\n}\n\n/**\n * Strict command payload the backend may return in a synchronous HTTP response\n * or send via SSE. The SDK never makes routing decisions; it only obeys these commands.\n */\nexport type BackendCommand =\n | BackendCommandGoToStep\n | BackendCommandShowError\n | BackendCommandFlowComplete\n | BackendCommandRetry;\n\n/**\n * Raw response body that may contain a backend command (e.g. POST submit response).\n */\nexport interface BackendCommandResponse {\n action?: string;\n stepCode?: string;\n message?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// INTERNAL STATE MACHINE TYPES (exported for module authors)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * All possible states of the SDK's internal lifecycle.\n * Transitions are strictly unidirectional (no going back to IDLE once started).\n *\n * @internal\n */\nexport type SdkStatus =\n | 'IDLE' // Instantiated, not yet started\n | 'INITIALIZING' // Fetching session state from backend\n | 'RUNNING' // Orchestrating steps\n | 'STEP_PROCESSING'// A module is actively running\n | 'COMPLETED' // All steps done, onSuccess fired\n | 'EXITED' // User left the flow via back on the first step, onExit fired\n | 'ERROR' // Unrecoverable error, onError fired\n | 'DESTROYED'; // destroy() was called, instance is a no-op\n\n/**\n * Internal event bus event names. Typed via `SdkEventMap`.\n * @internal\n */\nexport type SdkEventName =\n | 'step:change'\n | 'step:complete'\n | 'session:loaded'\n | 'flow:complete'\n | 'flow:exit'\n | 'error'\n | 'status:change';\n\n/**\n * Maps each internal event name to its payload type.\n * Used by the `TypedEventEmitter` for compile-time safety.\n * @internal\n */\nexport interface SdkEventMap extends Record<string, unknown> {\n 'step:change': { step: OnboardingStep; index: number };\n 'step:complete': { step: OnboardingStep };\n 'session:loaded': SessionState;\n 'flow:complete': OnboardingResult;\n 'flow:exit': OnboardingExit;\n 'error': SdkError;\n 'status:change': SdkStatus;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// LOADER ↔ CDN APP POSTMESSAGE PROTOCOL (Sumsub-style)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Message type: parent (loader) sends config to iframe (CDN app). */\nexport const VELION_MSG_INIT = 'VELION_INIT' as const;\n\n/** Message types: iframe (CDN app) sends to parent (loader). */\nexport const VELION_MSG_READY = 'VELION_READY' as const;\nexport const VELION_MSG_SUCCESS = 'VELION_SUCCESS' as const;\nexport const VELION_MSG_ERROR = 'VELION_ERROR' as const;\nexport const VELION_MSG_STEP_CHANGE = 'VELION_STEP_CHANGE' as const;\nexport const VELION_MSG_STEP_COMPLETE = 'VELION_STEP_COMPLETE' as const;\nexport const VELION_MSG_TOKEN_EXPIRED = 'VELION_TOKEN_EXPIRED' as const;\n/**\n * Iframe → parent: the user left the flow via back on the first reversible step\n * (`ROUTING_COMMAND / BACK_EXIT`). The loader forwards this to the host's\n * `onExit` callback so it can dismiss the SDK surface.\n */\nexport const VELION_MSG_EXIT = 'VELION_EXIT' as const;\n/**\n * Iframe → parent: the LIVENESS_MATCH step uploaded the recorded session video\n * (GCS PUT returned 200). The loader forwards the {@link LivenessVideoRef} to\n * the host's `onLivenessVideoUploaded` callback. Best-effort: never sent when\n * recording was disabled or the upload failed.\n */\nexport const VELION_MSG_LIVENESS_VIDEO_UPLOADED = 'VELION_LIVENESS_VIDEO_UPLOADED' as const;\n\n/** Message type: parent sends new token to iframe after VELION_TOKEN_EXPIRED. */\nexport const VELION_MSG_TOKEN = 'VELION_TOKEN' as const;\n\nexport type VelionMessageType =\n | typeof VELION_MSG_INIT\n | typeof VELION_MSG_READY\n | typeof VELION_MSG_SUCCESS\n | typeof VELION_MSG_ERROR\n | typeof VELION_MSG_STEP_CHANGE\n | typeof VELION_MSG_STEP_COMPLETE\n | typeof VELION_MSG_TOKEN_EXPIRED\n | typeof VELION_MSG_EXIT\n | typeof VELION_MSG_LIVENESS_VIDEO_UPLOADED\n | typeof VELION_MSG_TOKEN;\n\n/** Envelope for all loader ↔ iframe messages. Both sides must validate origin. */\nexport interface VelionMessage<T = unknown> {\n type: VelionMessageType;\n payload: T;\n}\n\n/** Payload for VELION_INIT: full config (iframe runs with containerId overwritten to root id). */\nexport type VelionInitPayload = VelionConfig;\n\n/** Payload for VELION_STEP_CHANGE. */\nexport interface VelionStepChangePayload {\n step: OnboardingStep;\n stepIndex: number;\n}\n\n/** Payload for VELION_STEP_COMPLETE. */\nexport interface VelionStepCompletePayload {\n step: OnboardingStep;\n}\n\n/** Payload for VELION_EXIT (mirrors {@link OnboardingExit}). */\nexport type VelionExitPayload = OnboardingExit;\n\n/** Payload for VELION_LIVENESS_VIDEO_UPLOADED (mirrors {@link LivenessVideoRef}). */\nexport type VelionLivenessVideoUploadedPayload = LivenessVideoRef;\n\n/** Payload for VELION_TOKEN (token refresh response from parent to iframe). */\nexport interface VelionTokenPayload {\n sessionToken: string;\n expiresAt?: string;\n}\n","/**\n * @fileoverview Signed-manifest verification for the Velion CDN bundle.\n *\n * Threat model:\n * The loader is published on npm and embedded in customer pages. The full SDK\n * ships from a CDN at runtime. If the CDN is compromised (BGP hijack, bucket\n * takeover, supply-chain on the build pipeline), an attacker could swap the\n * iframe HTML / JS and exfiltrate KYC documents, selfies, and session tokens.\n *\n * Mitigation: every CDN release ships a `manifest.json` that names the\n * entry HTML and asset SHA-384 digests, plus a detached `manifest.sig`\n * (Ed25519 signature over the canonical bytes of `manifest.json`). The\n * loader carries the corresponding Ed25519 *public* key and verifies the\n * signature before letting the iframe load. A compromised CDN cannot\n * forge a manifest without also obtaining the private key — which lives\n * in a KMS keyring outside the CDN's blast radius.\n *\n * Rotation:\n * The public key is embedded in the loader bundle published to npm. Rotation\n * means publishing a new loader version with the new public key. Old loaders\n * still verify against the previous key while customers upgrade.\n */\n\n/**\n * Ed25519 public key used to verify `manifest.sig`, base64-encoded raw bytes.\n *\n * Empty string means \"verification disabled\" — accepted only as a transitional\n * mode while the signing pipeline is being deployed. Production loaders MUST\n * ship with this populated before being published to npm. Replace via the\n * release workflow that fetches the pubkey from KMS metadata.\n */\nexport const VELION_MANIFEST_PUBKEY_B64 = 'lpqLG4Ta0YyBn/+lQh95tFl4CuTIHWapeeq7+xnPH+U=';\n\n/** Schema of the signed `manifest.json` emitted by the CDN build pipeline. */\nexport interface VelionCdnManifest {\n /** Loader-major path token, e.g. `\"v1\"`. Must match the loader's `LOADER_MAJOR_VERSION`. */\n version: string;\n /** ISO-8601 timestamp of the build. */\n buildTime: string;\n /** Path to the iframe entry HTML, relative to `cdnUrl`. */\n entry: string;\n /** Optional human-readable app version (matches the app's `package.json`). */\n appVersion?: string;\n /** Optional asset list with SHA-384 integrity digests for defense-in-depth. */\n assets?: Array<{ url: string; integrity: string }>;\n}\n\n/** Distinguishes \"manifest absent\" (legacy CDN) from \"manifest invalid\" (compromised). */\nexport type ManifestLoadResult =\n | { ok: true; manifest: VelionCdnManifest; signed: boolean }\n | { ok: false; reason: 'unavailable' | 'invalid_signature' | 'malformed'; detail?: string };\n\nfunction base64ToArrayBuffer(b64: string): ArrayBuffer {\n const bin = atob(b64);\n const buf = new ArrayBuffer(bin.length);\n const view = new Uint8Array(buf);\n for (let i = 0; i < bin.length; i++) {\n view[i] = bin.charCodeAt(i);\n }\n return buf;\n}\n\nasync function importPublicKey(rawB64: string): Promise<CryptoKey> {\n const raw = base64ToArrayBuffer(rawB64);\n // Web Crypto Ed25519 is implemented in Chrome 113+, Firefox 130+, Safari 17+.\n // Older browsers throw NotSupportedError — we surface that as `invalid_signature`\n // so the loader refuses to start rather than silently bypassing verification.\n return crypto.subtle.importKey(\n 'raw',\n raw,\n { name: 'Ed25519' } as AlgorithmIdentifier,\n false,\n ['verify'],\n );\n}\n\nasync function verifySignature(\n publicKey: CryptoKey,\n signature: ArrayBuffer,\n data: ArrayBuffer,\n): Promise<boolean> {\n return crypto.subtle.verify(\n { name: 'Ed25519' } as AlgorithmIdentifier,\n publicKey,\n signature,\n data,\n );\n}\n\n/**\n * Fetches `${cdnUrl}/manifest.json` (and `manifest.sig` when verification is enabled).\n *\n * Returns:\n * - `{ ok:true, signed:true }` when the signature verifies against the embedded pubkey.\n * - `{ ok:true, signed:false }` when the manifest exists but verification is disabled\n * (transitional dev/staging mode where `VELION_MANIFEST_PUBKEY_B64` is empty).\n * - `{ ok:false, reason:'unavailable' }` when the CDN has no manifest yet — the\n * loader treats this as legacy mode and may proceed if explicitly allowed.\n * - `{ ok:false, reason:'invalid_signature' | 'malformed' }` when the manifest\n * is present but tampered or syntactically invalid. The loader MUST refuse\n * to mount the iframe in this case.\n */\nexport async function loadAndVerifyManifest(\n cdnUrl: string,\n pubKeyB64: string = VELION_MANIFEST_PUBKEY_B64,\n): Promise<ManifestLoadResult> {\n let manifestBuffer: ArrayBuffer;\n let manifestText: string;\n try {\n const resp = await fetch(`${cdnUrl}/manifest.json`, {\n credentials: 'omit',\n cache: 'no-cache',\n redirect: 'error',\n });\n if (resp.status === 404) {\n return { ok: false, reason: 'unavailable' };\n }\n if (!resp.ok) {\n return { ok: false, reason: 'unavailable', detail: `HTTP ${resp.status}` };\n }\n manifestBuffer = await resp.arrayBuffer();\n manifestText = new TextDecoder().decode(manifestBuffer);\n } catch (err) {\n return { ok: false, reason: 'unavailable', detail: (err as Error).message };\n }\n\n let manifest: VelionCdnManifest;\n try {\n manifest = JSON.parse(manifestText) as VelionCdnManifest;\n if (\n typeof manifest.version !== 'string' ||\n typeof manifest.entry !== 'string' ||\n typeof manifest.buildTime !== 'string'\n ) {\n return { ok: false, reason: 'malformed' };\n }\n } catch (err) {\n return { ok: false, reason: 'malformed', detail: (err as Error).message };\n }\n\n if (!pubKeyB64) {\n // Pubkey not yet provisioned on this loader release. Accept the manifest\n // unsigned but flag it so the caller can decide whether to proceed.\n return { ok: true, manifest, signed: false };\n }\n\n let sigBuffer: ArrayBuffer;\n try {\n const sigResp = await fetch(`${cdnUrl}/manifest.sig`, {\n credentials: 'omit',\n cache: 'no-cache',\n redirect: 'error',\n });\n if (!sigResp.ok) {\n return { ok: false, reason: 'invalid_signature', detail: `sig HTTP ${sigResp.status}` };\n }\n const sigText = (await sigResp.text()).trim();\n sigBuffer = base64ToArrayBuffer(sigText);\n } catch (err) {\n return { ok: false, reason: 'invalid_signature', detail: (err as Error).message };\n }\n\n let pubKey: CryptoKey;\n try {\n pubKey = await importPublicKey(pubKeyB64);\n } catch (err) {\n return { ok: false, reason: 'invalid_signature', detail: `pubkey import failed: ${(err as Error).message}` };\n }\n\n let valid: boolean;\n try {\n valid = await verifySignature(pubKey, sigBuffer, manifestBuffer);\n } catch (err) {\n return { ok: false, reason: 'invalid_signature', detail: (err as Error).message };\n }\n if (!valid) {\n return { ok: false, reason: 'invalid_signature' };\n }\n\n return { ok: true, manifest, signed: true };\n}\n","/**\n * Thin client (loader) for Velion Onboarding.\n * Creates an iframe pointing to the CDN app and bridges config/events via postMessage.\n * Follows the same pattern as Sumsub WebSDK: init → iframe loads → postMessage config → events.\n */\n\nimport type {\n VelionConfig,\n OnboardingResult,\n SdkError,\n SdkStatus,\n OnboardingStep,\n} from './types';\nimport {\n SdkErrorCode,\n VELION_MSG_INIT,\n VELION_MSG_READY,\n VELION_MSG_SUCCESS,\n VELION_MSG_ERROR,\n VELION_MSG_STEP_CHANGE,\n VELION_MSG_STEP_COMPLETE,\n VELION_MSG_TOKEN_EXPIRED,\n VELION_MSG_EXIT,\n VELION_MSG_LIVENESS_VIDEO_UPLOADED,\n VELION_MSG_TOKEN,\n type VelionMessage,\n type OnboardingExit,\n type LivenessVideoRef,\n} from './types';\nimport {\n loadAndVerifyManifest,\n VELION_MANIFEST_PUBKEY_B64,\n type VelionCdnManifest,\n} from './manifest';\n\n/**\n * Product slug under the shared `static.velion.la/sdk/` namespace. This identity\n * SDK lives at `sdk/onboarding/`; future Velion SDKs (e.g. payments, auth) get\n * their own slug so they can be deployed independently from the same CDN.\n */\nexport const LOADER_PRODUCT = 'onboarding' as const;\n\n/**\n * Major version of this loader. Locked into the CDN path so a breaking change\n * to the loader↔app postMessage contract requires publishing loader-`v2`\n * against `/sdk/onboarding/v2/`. Customers can keep older loaders for as long\n * as the matching CDN path is served.\n */\nexport const LOADER_MAJOR_VERSION = 'v1' as const;\n\n/** Default CDN base URL for the onboarding app (no trailing slash). */\nexport const DEFAULT_CDN_URL =\n `https://static.velion.la/sdk/${LOADER_PRODUCT}/${LOADER_MAJOR_VERSION}`;\n\n/**\n * Used only while the host container has no laid-out height yet (e.g. `display: none`\n * or first paint). Replaced by a pixel height as soon as `container.clientHeight > 0`.\n */\nconst IFRAME_FALLBACK_HEIGHT_CSS = 'min(85vh, 880px)';\n\n/** Loader config: VelionConfig plus optional CDN URL and token refresh callback. */\nexport interface VelionLoaderConfig extends VelionConfig {\n /**\n * Base URL of the CDN where the onboarding app is served (e.g. https://static.velion.la/sdk).\n * The loader fetches `${cdnUrl}/manifest.json` (signed with Ed25519), verifies the\n * signature against the loader's embedded public key, and only then loads the\n * iframe entry named in the manifest.\n * @default 'https://static.velion.la/sdk'\n */\n cdnUrl?: string;\n\n /**\n * When `true`, lets the loader proceed against a CDN that does not yet expose a\n * signed `manifest.json` — for staging or migration windows. Has NO effect when\n * the loader carries an embedded pubkey AND the manifest exists but signature\n * verification fails (that case is always fatal).\n *\n * Default `false`: production loaders refuse to mount the iframe without a\n * verified manifest once the pubkey has been provisioned.\n *\n * @default false\n */\n allowUnverifiedCdn?: boolean;\n\n /**\n * Called when the iframe requests a new session token (e.g. after SESSION_EXPIRED).\n * Return the new token and optional expiresAt; the CDN app will continue the flow.\n */\n onTokenExpired?: () => Promise<{ sessionToken: string; expiresAt?: string }>;\n\n /**\n * How long to wait, after the iframe document loaded, for the runtime to\n * report `VELION_READY`. Past it the loader emits `RUNTIME_LOAD_FAILED`\n * instead of leaving the host with a blank iframe and no callback (e.g.\n * when the CDN entry points at assets that 404).\n *\n * @default 30000\n */\n runtimeReadyTimeoutMs?: number;\n}\n\nconst DEFAULT_RUNTIME_READY_TIMEOUT_MS = 30_000;\n\nfunction parseOriginFromUrl(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return '';\n }\n}\n\n/**\n * Allows `https:` and `wss:` everywhere, plus plain `http:`/`ws:` only when the host\n * is a loopback / `.local` address (developer machines and CI). Used to prevent\n * accidental cleartext bearer-token transmission to a misconfigured `apiUrl`/`cdnUrl`.\n */\nfunction isLocalhostHost(host: string): boolean {\n return host === 'localhost' || host === '127.0.0.1' || host === '::1' || host.endsWith('.local');\n}\n\nexport function isSecureHttpUrl(url: string): boolean {\n try {\n const u = new URL(url);\n if (u.protocol === 'https:') return true;\n if (u.protocol === 'http:') return isLocalhostHost(u.hostname);\n return false;\n } catch {\n return false;\n }\n}\n\n/** Keys of VelionConfig that are safe to send via postMessage (no functions). */\nconst SERIALIZABLE_CONFIG_KEYS: (keyof VelionConfig)[] = [\n 'containerId',\n 'sessionId',\n 'sessionToken',\n 'apiUrl',\n 'expiresAt',\n 'theme',\n 'colorMode',\n 'locale',\n 'allowEmbedded',\n 'livenessShowIntro',\n 'livenessShowBackButton',\n 'fingerprintEndpoint',\n 'debug',\n 'googleMapsApiKey',\n 'showFinalResultScreen',\n 'showProgressBar',\n 'finalResultScreenTexts',\n 'routingTimeoutMs',\n];\n\nfunction buildSerializableConfig(\n config: Readonly<VelionLoaderConfig>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {\n containerId: 'velion-sdk-root',\n allowEmbedded: true,\n };\n for (const key of SERIALIZABLE_CONFIG_KEYS) {\n if (key === 'containerId' || key === 'allowEmbedded') continue;\n const value = config[key as keyof typeof config];\n if (value !== undefined && typeof value !== 'function') {\n out[key] = value;\n }\n }\n return out;\n}\n\nexport class VelionOnboarding {\n private readonly config: Readonly<VelionLoaderConfig>;\n private status: SdkStatus = 'IDLE';\n private iframe: HTMLIFrameElement | null = null;\n private container: HTMLElement | null = null;\n private messageHandler: ((e: MessageEvent) => void) | null = null;\n private containerResizeObserver: ResizeObserver | null = null;\n private iframeHeightWindowResizeHandler: (() => void) | null = null;\n private cdnOrigin = '';\n /** Armed when VELION_INIT is sent; cleared by VELION_READY or destroy(). */\n private readyTimer: ReturnType<typeof setTimeout> | null = null;\n\n private clearReadyTimer(): void {\n if (this.readyTimer !== null) {\n clearTimeout(this.readyTimer);\n this.readyTimer = null;\n }\n }\n\n private armReadyTimer(cdnUrl: string): void {\n this.clearReadyTimer();\n const ms = this.config.runtimeReadyTimeoutMs ?? DEFAULT_RUNTIME_READY_TIMEOUT_MS;\n if (!(ms > 0)) return;\n this.readyTimer = setTimeout(() => {\n this.readyTimer = null;\n if (this.status !== 'RUNNING') return; // already READY-progressed, errored or destroyed\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.RUNTIME_LOAD_FAILED,\n message:\n `The onboarding runtime at ${cdnUrl} did not become ready within ${ms} ms. ` +\n 'Check that the CDN serves the assets referenced by its index.html and that the host ' +\n \"page's Content-Security-Policy allows the runtime scripts.\",\n });\n }, ms);\n }\n\n constructor(config: VelionLoaderConfig) {\n this.config = Object.freeze({ ...config });\n }\n\n async start(): Promise<void> {\n if (this.status === 'DESTROYED') {\n this.config.onError?.({\n code: SdkErrorCode.INSTANCE_DESTROYED,\n message:\n 'This VelionOnboarding instance has been destroyed. Create a new instance to retry.',\n });\n return;\n }\n\n if (this.status !== 'IDLE') {\n this.config.onError?.({\n code: SdkErrorCode.ALREADY_STARTED,\n message:\n 'start() was called on an already-running SDK instance. Call destroy() before re-starting.',\n });\n return;\n }\n\n const container = document.getElementById(this.config.containerId);\n if (!container) {\n this.config.onError?.({\n code: SdkErrorCode.CONTAINER_NOT_FOUND,\n message: `No DOM element found with id=\"${this.config.containerId}\". Ensure the element exists before calling start().`,\n });\n return;\n }\n\n const cdnUrl = (this.config.cdnUrl ?? DEFAULT_CDN_URL).replace(/\\/$/, '');\n if (!isSecureHttpUrl(cdnUrl)) {\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Insecure cdnUrl: \"${cdnUrl}\". Must be https:// (http:// is allowed only for localhost).`,\n });\n return;\n }\n if (this.config.apiUrl && !isSecureHttpUrl(this.config.apiUrl)) {\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Insecure apiUrl: \"${this.config.apiUrl}\". Must be https:// (http:// is allowed only for localhost).`,\n });\n return;\n }\n this.cdnOrigin = parseOriginFromUrl(cdnUrl);\n if (!this.cdnOrigin) {\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Invalid cdnUrl: ${cdnUrl}. Must be a valid absolute URL.`,\n });\n return;\n }\n\n this.container = container;\n this.status = 'INITIALIZING';\n\n const iframe = document.createElement('iframe');\n iframe.setAttribute('title', 'Velion Onboarding');\n // The SDK only needs the camera. Microphone and geolocation are intentionally not granted.\n iframe.setAttribute('allow', 'camera');\n // Sandbox limits blast radius of a CDN compromise. Tokens we keep:\n // allow-scripts — the iframe is a JS app\n // allow-same-origin — needed for fetch to same-origin assets and for postMessage to behave\n // allow-forms — file pickers and form submissions for OCR upload flows\n // allow-popups — Google Maps / Places attribution links open in new tab\n // allow-modals — getUserMedia permission prompts and document picker\n iframe.setAttribute(\n 'sandbox',\n 'allow-scripts allow-same-origin allow-forms allow-popups allow-modals',\n );\n // Avoid leaking host page paths to the CDN logs.\n iframe.setAttribute('referrerpolicy', 'no-referrer');\n iframe.style.width = '100%';\n iframe.style.border = 'none';\n iframe.style.display = 'block';\n /* Height is applied in attachIframeHeightSync (pixel match + internal fallback). */\n\n const messageHandler = (e: MessageEvent): void => {\n // Origin AND source must match: a sibling iframe from the same CDN origin\n // (e.g. opened by the host page) must not be able to spoof events.\n if (e.origin !== this.cdnOrigin) return;\n if (e.source !== this.iframe?.contentWindow) return;\n const msg = e.data as VelionMessage | undefined;\n if (!msg || typeof msg.type !== 'string') return;\n\n // Any message that passed the origin+source check came from a running\n // runtime, so none of them may be followed by RUNTIME_LOAD_FAILED. READY is\n // the expected first message, but a runtime that reports a step or an\n // error first is alive just the same.\n this.clearReadyTimer();\n\n switch (msg.type) {\n case VELION_MSG_READY:\n this.status = 'RUNNING';\n break;\n case VELION_MSG_SUCCESS:\n this.status = 'COMPLETED';\n this.config.onSuccess?.(msg.payload as OnboardingResult);\n break;\n case VELION_MSG_ERROR:\n this.status = 'ERROR';\n this.config.onError?.(msg.payload as SdkError);\n break;\n case VELION_MSG_STEP_CHANGE: {\n const p = msg.payload as { step: OnboardingStep; stepIndex: number };\n this.config.onStepChange?.(p.step, p.stepIndex);\n break;\n }\n case VELION_MSG_STEP_COMPLETE: {\n const p = msg.payload as { step: OnboardingStep };\n this.config.onStepComplete?.(p.step);\n break;\n }\n case VELION_MSG_EXIT: {\n // User backed out of the first reversible step: the flow ended without\n // a verdict. Forward to the host so it can dismiss the SDK surface.\n this.status = 'EXITED';\n this.config.onExit?.(msg.payload as OnboardingExit);\n break;\n }\n case VELION_MSG_LIVENESS_VIDEO_UPLOADED: {\n // Evidence signal from the LIVENESS_MATCH step (OSDKW-11). Informational\n // only: the flow never depends on it, so no status change.\n this.config.onLivenessVideoUploaded?.(msg.payload as LivenessVideoRef);\n break;\n }\n case VELION_MSG_TOKEN_EXPIRED: {\n const refresh = this.config.onTokenExpired;\n if (refresh) {\n refresh().then(\n (t) => this.sendToIframe({ type: VELION_MSG_TOKEN, payload: t }),\n () => {\n this.config.onError?.({\n code: SdkErrorCode.SESSION_EXPIRED,\n message: 'Token refresh failed.',\n });\n }\n );\n } else {\n this.config.onError?.({\n code: SdkErrorCode.SESSION_EXPIRED,\n message: 'Session token expired. Provide onTokenExpired in config to refresh.',\n });\n }\n break;\n }\n default:\n break;\n }\n };\n\n window.addEventListener('message', messageHandler);\n this.messageHandler = messageHandler;\n this.iframe = iframe;\n\n container.replaceChildren(iframe);\n this.attachIframeHeightSync(container, iframe);\n\n // ── Verify the CDN manifest before mounting the iframe ─────────────────\n const manifest = await this.resolveManifest(cdnUrl);\n if (!manifest) {\n // resolveManifest already invoked onError. Stop here so we don't load\n // an unverified bundle.\n return;\n }\n const entryPath = manifest.entry.startsWith('/') ? manifest.entry : `/${manifest.entry}`;\n\n return new Promise<void>((resolve) => {\n iframe.onload = (): void => {\n if (!this.iframe?.contentWindow) {\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: 'Iframe failed to load.',\n });\n resolve();\n return;\n }\n // postMessage cannot clone functions. Sumsub-style: send only JSON-serializable config.\n // Clone via JSON so no function can slip through (e.g. from cached bundle or nested props).\n const initPayload = buildSerializableConfig(this.config);\n let payloadForPostMessage: unknown;\n try {\n payloadForPostMessage = JSON.parse(JSON.stringify(initPayload));\n } catch {\n payloadForPostMessage = initPayload;\n }\n this.sendToIframe({ type: VELION_MSG_INIT, payload: payloadForPostMessage });\n this.status = 'RUNNING';\n // The document loaded, but a 200 index.html whose scripts 404 (or a CSP\n // that blocks them) never answers VELION_INIT. Bound the wait so the\n // host gets an error instead of a silent blank iframe.\n this.armReadyTimer(cdnUrl);\n resolve();\n };\n\n iframe.onerror = (): void => {\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Failed to load onboarding app from ${cdnUrl}. Check cdnUrl and network.`,\n });\n resolve();\n };\n\n iframe.src = `${cdnUrl}${entryPath}`;\n });\n }\n\n /**\n * Fetches and verifies the CDN manifest. Returns `null` and emits onError when\n * the manifest is missing in production mode, or when the signature is invalid.\n */\n private async resolveManifest(cdnUrl: string): Promise<VelionCdnManifest | null> {\n let result;\n try {\n result = await loadAndVerifyManifest(cdnUrl);\n } catch (err) {\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message: `Manifest verification crashed unexpectedly: ${(err as Error).message}`,\n });\n return null;\n }\n if (result.ok) {\n if (!result.signed && VELION_MANIFEST_PUBKEY_B64) {\n // Should not happen — pubkey is set, manifest exists, yet not signed.\n // loadAndVerifyManifest only marks `signed:false` when pubkey is empty.\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message: 'Internal: manifest reported as unsigned despite pubkey being set.',\n });\n return null;\n }\n return result.manifest;\n }\n if (result.reason === 'unavailable') {\n // Manifest endpoint is missing. Permitted only when the loader is configured\n // to tolerate it OR when the loader was published without a pubkey (transitional).\n const tolerateMissing =\n this.config.allowUnverifiedCdn === true || !VELION_MANIFEST_PUBKEY_B64;\n if (tolerateMissing) {\n return {\n version: 'legacy',\n buildTime: new Date().toISOString(),\n entry: '/index.html',\n };\n }\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message:\n `CDN at \"${cdnUrl}\" did not expose /manifest.json. The loader is configured ` +\n 'to require a signed manifest. If the CDN is migrating, set `allowUnverifiedCdn: true` ' +\n 'on the loader config — but only as a temporary measure.',\n });\n return null;\n }\n // 'invalid_signature' or 'malformed' — always fatal, even when allowUnverifiedCdn is true.\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message:\n `CDN manifest at \"${cdnUrl}/manifest.json\" failed integrity check (${result.reason}). ` +\n 'Refusing to load the iframe to protect the user. ' +\n ('detail' in result && result.detail ? `Detail: ${result.detail}` : ''),\n });\n return null;\n }\n\n /**\n * Keep the iframe exactly as tall as the host container so integrators do not need\n * `height: 100%` on the iframe or flex hacks—only a definite block-size on the\n * container (e.g. `height: 700px` or `height: 100vh`), similar to Sumsub WebSDK.\n *\n * Until the container has a non-zero `clientHeight`, apply an internal fallback\n * height so the SDK is usable even before layout or if only `min-height` is set.\n */\n private attachIframeHeightSync(\n container: HTMLElement,\n iframe: HTMLIFrameElement,\n ): void {\n const sync = (): void => {\n if (this.iframe !== iframe || this.container !== container) return;\n const h = container.clientHeight;\n if (h > 0) {\n iframe.style.height = `${h}px`;\n iframe.style.minHeight = '';\n } else {\n iframe.style.height = IFRAME_FALLBACK_HEIGHT_CSS;\n iframe.style.minHeight = '';\n }\n };\n sync();\n requestAnimationFrame(sync);\n requestAnimationFrame(() => {\n requestAnimationFrame(sync);\n });\n if (typeof ResizeObserver !== 'undefined') {\n this.containerResizeObserver = new ResizeObserver(sync);\n this.containerResizeObserver.observe(container);\n } else {\n this.iframeHeightWindowResizeHandler = sync;\n window.addEventListener('resize', sync);\n }\n }\n\n private detachIframeHeightSync(): void {\n if (this.containerResizeObserver) {\n this.containerResizeObserver.disconnect();\n this.containerResizeObserver = null;\n }\n if (this.iframeHeightWindowResizeHandler) {\n window.removeEventListener('resize', this.iframeHeightWindowResizeHandler);\n this.iframeHeightWindowResizeHandler = null;\n }\n }\n\n private sendToIframe(msg: VelionMessage): void {\n if (this.iframe?.contentWindow) {\n this.iframe.contentWindow.postMessage(msg, this.cdnOrigin);\n }\n }\n\n getStatus(): SdkStatus {\n return this.status;\n }\n\n static getVersion(): string {\n return typeof __SDK_VERSION__ !== 'undefined' ? __SDK_VERSION__ : '0.0.0';\n }\n\n destroy(): void {\n if (this.status === 'DESTROYED') return;\n this.clearReadyTimer();\n this.detachIframeHeightSync();\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.iframe && this.container) {\n this.iframe.remove();\n this.container.replaceChildren();\n }\n this.iframe = null;\n this.container = null;\n this.status = 'DESTROYED';\n }\n}\n","/**\n * @fileoverview Public API of the Velion Onboarding SDK (thin client / loader).\n *\n * This package is the loader only: it creates an iframe that loads the full\n * onboarding app from the CDN (e.g. https://static.velion.la/sdk) and bridges\n * config and events via postMessage. No React, OpenCV, or Fingerprint\n * are bundled here — minimal size, same API as before.\n */\n\nexport { VelionOnboarding, DEFAULT_CDN_URL, type VelionLoaderConfig } from './velion-loader';\n\n/**\n * No-op when using the loader: OpenCV is loaded inside the CDN app.\n * Kept for API compatibility; configure OpenCV in the CDN app build if needed.\n */\nexport function setOpenCVBaseUrl(_url: string): void {}\n\nexport {\n SdkErrorCode,\n CameraErrorCode,\n VELION_MSG_INIT,\n VELION_MSG_READY,\n VELION_MSG_SUCCESS,\n VELION_MSG_ERROR,\n VELION_MSG_STEP_CHANGE,\n VELION_MSG_STEP_COMPLETE,\n VELION_MSG_TOKEN_EXPIRED,\n VELION_MSG_EXIT,\n VELION_MSG_LIVENESS_VIDEO_UPLOADED,\n VELION_MSG_TOKEN,\n type VelionConfig,\n type VelionTheme,\n type VelionFinalResultStatusCopy,\n type VelionFinalResultScreenTexts,\n type VelionSessionDeclinedReasonMessages,\n type VelionSessionDeclinedReasonTexts,\n type VelionRetryStepTexts,\n type VelionCallbacks,\n type OnboardingExit,\n type OnboardingStep,\n type OnboardingStepStatus,\n type OnboardingType,\n type DocumentSide,\n type DocumentCaptureMode,\n type DocumentsConfig,\n type TaxInfoConfig,\n type TaxInfoOption,\n type DocumentsInitRequest,\n type DocumentsInitResponse,\n type SessionState,\n type OnboardingResult,\n type OnboardingStepDto,\n type StepFieldRule,\n type HumanBasicInfoStepConfig,\n type HumanExtendedInfoStepConfig,\n type TaxInfoStepConfig,\n type AddressInfoStepConfig,\n type SwornDeclarationStepConfig,\n type DocumentsOcrStepConfig,\n type TermsAndConditionsStepConfig,\n type StepConfigMap,\n type SdkError,\n type SdkStatus,\n type LivenessSessionResponse,\n type VideoUpload,\n type LivenessVerifyVideoMeta,\n type LivenessVideoRef,\n type SessionCreationResponse,\n type HumanBasicInfoPayload,\n type LegalBasicInfoPayload,\n type TermsAndConditionsItem,\n type TaxInfoPayload,\n type HumanExtendedInfoPayload,\n type LegalExtendedInfoPayload,\n type AddressInfoPayload,\n type SwornDeclarationPayload,\n type EconomicActivityItem,\n type EconomicActivityStepConfig,\n type EconomicActivitySelectionPayload,\n type EmailContactPayload,\n type PhoneContactPayload,\n type TermsAndConditionsSubmitPayload,\n type TermsAndConditionsAcceptancePayload,\n type OnboardingSessionEvent,\n type OnboardingDtoResponse,\n type VelionMessage,\n type VelionMessageType,\n type VelionInitPayload,\n type VelionStepChangePayload,\n type VelionStepCompletePayload,\n type VelionExitPayload,\n type VelionLivenessVideoUploadedPayload,\n type VelionTokenPayload,\n} from './types';\n"],"names":["CameraErrorCode","SdkErrorCode","VELION_MSG_INIT","VELION_MSG_READY","VELION_MSG_SUCCESS","VELION_MSG_ERROR","VELION_MSG_STEP_CHANGE","VELION_MSG_STEP_COMPLETE","VELION_MSG_TOKEN_EXPIRED","VELION_MSG_EXIT","VELION_MSG_LIVENESS_VIDEO_UPLOADED","VELION_MSG_TOKEN","VELION_MANIFEST_PUBKEY_B64","base64ToArrayBuffer","b64","bin","atob","buf","ArrayBuffer","length","view","Uint8Array","i","charCodeAt","async","loadAndVerifyManifest","cdnUrl","pubKeyB64","manifestBuffer","manifestText","manifest","sigBuffer","pubKey","valid","resp","fetch","credentials","cache","redirect","status","ok","reason","detail","arrayBuffer","TextDecoder","decode","err","message","JSON","parse","version","entry","buildTime","signed","sigResp","text","trim","rawB64","raw","crypto","subtle","importKey","name","importPublicKey","publicKey","signature","data","verify","verifySignature","DEFAULT_CDN_URL","isSecureHttpUrl","url","u","URL","protocol","host","hostname","endsWith","SERIALIZABLE_CONFIG_KEYS","VelionOnboarding","constructor","config","this","iframe","container","messageHandler","containerResizeObserver","iframeHeightWindowResizeHandler","cdnOrigin","readyTimer","Object","freeze","clearReadyTimer","clearTimeout","armReadyTimer","ms","runtimeReadyTimeoutMs","setTimeout","onError","code","RUNTIME_LOAD_FAILED","start","INSTANCE_DESTROYED","ALREADY_STARTED","document","getElementById","containerId","CONTAINER_NOT_FOUND","replace","UNKNOWN","apiUrl","origin","parseOriginFromUrl","createElement","setAttribute","style","width","border","display","e","source","contentWindow","msg","type","onSuccess","payload","p","onStepChange","step","stepIndex","onStepComplete","onExit","onLivenessVideoUploaded","refresh","onTokenExpired","then","t","sendToIframe","SESSION_EXPIRED","window","addEventListener","replaceChildren","attachIframeHeightSync","resolveManifest","entryPath","startsWith","Promise","resolve","onload","initPayload","out","allowEmbedded","key","value","buildSerializableConfig","payloadForPostMessage","stringify","onerror","src","result","INTEGRITY_FAILED","allowUnverifiedCdn","Date","toISOString","sync","h","clientHeight","height","minHeight","requestAnimationFrame","ResizeObserver","observe","detachIframeHeightSync","disconnect","removeEventListener","postMessage","getStatus","getVersion","destroy","remove","setOpenCVBaseUrl","_url"],"mappings":"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"}
|
package/dist/velion-sdk.umd.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).VelionSDK={})}(this,function(e){"use strict";var t=(e=>(e.PERMISSION_DENIED="CAMERA_PERMISSION_DENIED",e.DEVICE_NOT_FOUND="CAMERA_DEVICE_NOT_FOUND",e.DEVICE_NOT_READABLE="CAMERA_DEVICE_NOT_READABLE",e.INSECURE_CONTEXT="CAMERA_INSECURE_CONTEXT",e.POLICY_BLOCKED="CAMERA_POLICY_BLOCKED",e.NOT_SUPPORTED="CAMERA_NOT_SUPPORTED",e.WEBVIEW_BLOCKED="CAMERA_WEBVIEW_BLOCKED",e.CONSTRAINT_ERROR="CAMERA_CONSTRAINT_ERROR",e.UNKNOWN="CAMERA_UNKNOWN_ERROR",e))(t||{}),i=(e=>(e.SESSION_INVALID="SESSION_INVALID",e.SESSION_EXPIRED="SESSION_EXPIRED",e.SESSION_FETCH_FAILED="SESSION_FETCH_FAILED",e.SESSION_DECLINED="SESSION_DECLINED",e.FINGERPRINT_FAILED="FINGERPRINT_FAILED",e.DOCUMENT_UPLOAD_FAILED="DOCUMENT_UPLOAD_FAILED",e.LIVENESS_FAILED="LIVENESS_FAILED",e.LIVENESS_INIT_FAILED="LIVENESS_INIT_FAILED",e.LIVENESS_VIDEO_UPLOAD_FAILED="LIVENESS_VIDEO_UPLOAD_FAILED",e.CAMERA_ERROR="CAMERA_ERROR",e.NETWORK_ERROR="NETWORK_ERROR",e.API_ERROR="API_ERROR",e.CONTAINER_NOT_FOUND="CONTAINER_NOT_FOUND",e.ALREADY_STARTED="ALREADY_STARTED",e.INSTANCE_DESTROYED="INSTANCE_DESTROYED",e.INSECURE_EMBED="INSECURE_EMBED",e.INTEGRITY_FAILED="INTEGRITY_FAILED",e.RUNTIME_LOAD_FAILED="RUNTIME_LOAD_FAILED",e.UNKNOWN="UNKNOWN",e))(i||{});const n="VELION_INIT",s="VELION_READY",r="VELION_SUCCESS",o="VELION_ERROR",a="VELION_STEP_CHANGE",E="VELION_STEP_COMPLETE",c="VELION_TOKEN_EXPIRED",l="VELION_EXIT",d="VELION_LIVENESS_VIDEO_UPLOADED",h="VELION_TOKEN",I="lpqLG4Ta0YyBn/+lQh95tFl4CuTIHWapeeq7+xnPH+U=";function f(e){const t=atob(e),i=new ArrayBuffer(t.length),n=new Uint8Array(i);for(let s=0;s<t.length;s++)n[s]=t.charCodeAt(s);return i}async function u(e,t=I){let i,n,s,r,o,a;try{const t=await fetch(`${e}/manifest.json`,{credentials:"omit",cache:"no-cache",redirect:"error"});if(404===t.status)return{ok:!1,reason:"unavailable"};if(!t.ok)return{ok:!1,reason:"unavailable",detail:`HTTP ${t.status}`};i=await t.arrayBuffer(),n=(new TextDecoder).decode(i)}catch(E){return{ok:!1,reason:"unavailable",detail:E.message}}try{if(s=JSON.parse(n),"string"!=typeof s.version||"string"!=typeof s.entry||"string"!=typeof s.buildTime)return{ok:!1,reason:"malformed"}}catch(E){return{ok:!1,reason:"malformed",detail:E.message}}if(!t)return{ok:!0,manifest:s,signed:!1};try{const t=await fetch(`${e}/manifest.sig`,{credentials:"omit",cache:"no-cache",redirect:"error"});if(!t.ok)return{ok:!1,reason:"invalid_signature",detail:`sig HTTP ${t.status}`};r=f((await t.text()).trim())}catch(E){return{ok:!1,reason:"invalid_signature",detail:E.message}}try{o=await async function(e){const t=f(e);return crypto.subtle.importKey("raw",t,{name:"Ed25519"},!1,["verify"])}(t)}catch(E){return{ok:!1,reason:"invalid_signature",detail:`pubkey import failed: ${E.message}`}}try{a=await async function(e,t,i){return crypto.subtle.verify({name:"Ed25519"},e,t,i)}(o,r,i)}catch(E){return{ok:!1,reason:"invalid_signature",detail:E.message}}return a?{ok:!0,manifest:s,signed:!0}:{ok:!1,reason:"invalid_signature"}}const N="https://static.velion.la/sdk/onboarding/v1";function R(e){try{const i=new URL(e);return"https:"===i.protocol||"http:"===i.protocol&&("localhost"===(t=i.hostname)||"127.0.0.1"===t||"::1"===t||t.endsWith(".local"))}catch{return!1}var t}const O=["containerId","sessionId","sessionToken","apiUrl","expiresAt","theme","colorMode","locale","allowEmbedded","livenessShowIntro","livenessShowBackButton","fingerprintEndpoint","debug","googleMapsApiKey","showFinalResultScreen","showProgressBar","finalResultScreenTexts"];e.CameraErrorCode=t,e.DEFAULT_CDN_URL=N,e.SdkErrorCode=i,e.VELION_MSG_ERROR=o,e.VELION_MSG_EXIT=l,e.VELION_MSG_INIT=n,e.VELION_MSG_LIVENESS_VIDEO_UPLOADED=d,e.VELION_MSG_READY=s,e.VELION_MSG_STEP_CHANGE=a,e.VELION_MSG_STEP_COMPLETE=E,e.VELION_MSG_SUCCESS=r,e.VELION_MSG_TOKEN=h,e.VELION_MSG_TOKEN_EXPIRED=c,e.VelionOnboarding=class{constructor(e){this.status="IDLE",this.iframe=null,this.container=null,this.messageHandler=null,this.containerResizeObserver=null,this.iframeHeightWindowResizeHandler=null,this.cdnOrigin="",this.readyTimer=null,this.config=Object.freeze({...e})}clearReadyTimer(){null!==this.readyTimer&&(clearTimeout(this.readyTimer),this.readyTimer=null)}armReadyTimer(e){this.clearReadyTimer();const t=this.config.runtimeReadyTimeoutMs??3e4;t>0&&(this.readyTimer=setTimeout(()=>{this.readyTimer=null,"RUNNING"===this.status&&(this.status="ERROR",this.config.onError?.({code:i.RUNTIME_LOAD_FAILED,message:`The onboarding runtime at ${e} did not become ready within ${t} ms. Check that the CDN serves the assets referenced by its index.html and that the host page's Content-Security-Policy allows the runtime scripts.`}))},t))}async start(){if("DESTROYED"===this.status)return void this.config.onError?.({code:i.INSTANCE_DESTROYED,message:"This VelionOnboarding instance has been destroyed. Create a new instance to retry."});if("IDLE"!==this.status)return void this.config.onError?.({code:i.ALREADY_STARTED,message:"start() was called on an already-running SDK instance. Call destroy() before re-starting."});const e=document.getElementById(this.config.containerId);if(!e)return void this.config.onError?.({code:i.CONTAINER_NOT_FOUND,message:`No DOM element found with id="${this.config.containerId}". Ensure the element exists before calling start().`});const t=(this.config.cdnUrl??N).replace(/\/$/,"");if(!R(t))return void this.config.onError?.({code:i.UNKNOWN,message:`Insecure cdnUrl: "${t}". Must be https:// (http:// is allowed only for localhost).`});if(this.config.apiUrl&&!R(this.config.apiUrl))return void this.config.onError?.({code:i.UNKNOWN,message:`Insecure apiUrl: "${this.config.apiUrl}". Must be https:// (http:// is allowed only for localhost).`});if(this.cdnOrigin=function(e){try{return new URL(e).origin}catch{return""}}(t),!this.cdnOrigin)return void this.config.onError?.({code:i.UNKNOWN,message:`Invalid cdnUrl: ${t}. Must be a valid absolute URL.`});this.container=e,this.status="INITIALIZING";const I=document.createElement("iframe");I.setAttribute("title","Velion Onboarding"),I.setAttribute("allow","camera"),I.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-modals"),I.setAttribute("referrerpolicy","no-referrer"),I.style.width="100%",I.style.border="none",I.style.display="block";const f=e=>{if(e.origin!==this.cdnOrigin)return;if(e.source!==this.iframe?.contentWindow)return;const t=e.data;if(t&&"string"==typeof t.type)switch(t.type){case s:this.clearReadyTimer(),this.status="RUNNING";break;case r:this.status="COMPLETED",this.config.onSuccess?.(t.payload);break;case o:this.status="ERROR",this.config.onError?.(t.payload);break;case a:{const e=t.payload;this.config.onStepChange?.(e.step,e.stepIndex);break}case E:{const e=t.payload;this.config.onStepComplete?.(e.step);break}case l:this.status="EXITED",this.config.onExit?.(t.payload);break;case d:this.config.onLivenessVideoUploaded?.(t.payload);break;case c:{const e=this.config.onTokenExpired;e?e().then(e=>this.sendToIframe({type:h,payload:e}),()=>{this.config.onError?.({code:i.SESSION_EXPIRED,message:"Token refresh failed."})}):this.config.onError?.({code:i.SESSION_EXPIRED,message:"Session token expired. Provide onTokenExpired in config to refresh."});break}}};window.addEventListener("message",f),this.messageHandler=f,this.iframe=I,e.replaceChildren(I),this.attachIframeHeightSync(e,I);const u=await this.resolveManifest(t);if(!u)return;const _=u.entry.startsWith("/")?u.entry:`/${u.entry}`;return new Promise(e=>{I.onload=()=>{if(!this.iframe?.contentWindow)return this.status="ERROR",this.config.onError?.({code:i.UNKNOWN,message:"Iframe failed to load."}),void e();const s=function(e){const t={containerId:"velion-sdk-root",allowEmbedded:!0};for(const i of O){if("containerId"===i||"allowEmbedded"===i)continue;const n=e[i];void 0!==n&&"function"!=typeof n&&(t[i]=n)}return t}(this.config);let r;try{r=JSON.parse(JSON.stringify(s))}catch{r=s}this.sendToIframe({type:n,payload:r}),this.status="RUNNING",this.armReadyTimer(t),e()},I.onerror=()=>{this.status="ERROR",this.config.onError?.({code:i.UNKNOWN,message:`Failed to load onboarding app from ${t}. Check cdnUrl and network.`}),e()},I.src=`${t}${_}`})}async resolveManifest(e){let t;try{t=await u(e)}catch(n){return this.status="ERROR",this.config.onError?.({code:i.INTEGRITY_FAILED,message:`Manifest verification crashed unexpectedly: ${n.message}`}),null}if(t.ok)return!t.signed&&I?(this.status="ERROR",this.config.onError?.({code:i.INTEGRITY_FAILED,message:"Internal: manifest reported as unsigned despite pubkey being set."}),null):t.manifest;if("unavailable"===t.reason){return!0!==this.config.allowUnverifiedCdn?(this.status="ERROR",this.config.onError?.({code:i.INTEGRITY_FAILED,message:`CDN at "${e}" did not expose /manifest.json. The loader is configured to require a signed manifest. If the CDN is migrating, set \`allowUnverifiedCdn: true\` on the loader config — but only as a temporary measure.`}),null):{version:"legacy",buildTime:(new Date).toISOString(),entry:"/index.html"}}return this.status="ERROR",this.config.onError?.({code:i.INTEGRITY_FAILED,message:`CDN manifest at "${e}/manifest.json" failed integrity check (${t.reason}). Refusing to load the iframe to protect the user. `+("detail"in t&&t.detail?`Detail: ${t.detail}`:"")}),null}attachIframeHeightSync(e,t){const i=()=>{if(this.iframe!==t||this.container!==e)return;const i=e.clientHeight;i>0?(t.style.height=`${i}px`,t.style.minHeight=""):(t.style.height="min(85vh, 880px)",t.style.minHeight="")};i(),requestAnimationFrame(i),requestAnimationFrame(()=>{requestAnimationFrame(i)}),"undefined"!=typeof ResizeObserver?(this.containerResizeObserver=new ResizeObserver(i),this.containerResizeObserver.observe(e)):(this.iframeHeightWindowResizeHandler=i,window.addEventListener("resize",i))}detachIframeHeightSync(){this.containerResizeObserver&&(this.containerResizeObserver.disconnect(),this.containerResizeObserver=null),this.iframeHeightWindowResizeHandler&&(window.removeEventListener("resize",this.iframeHeightWindowResizeHandler),this.iframeHeightWindowResizeHandler=null)}sendToIframe(e){this.iframe?.contentWindow&&this.iframe.contentWindow.postMessage(e,this.cdnOrigin)}getStatus(){return this.status}static getVersion(){return"1.0.0-beta.23"}destroy(){"DESTROYED"!==this.status&&(this.clearReadyTimer(),this.detachIframeHeightSync(),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.iframe&&this.container&&(this.iframe.remove(),this.container.replaceChildren()),this.iframe=null,this.container=null,this.status="DESTROYED")}},e.setOpenCVBaseUrl=function(e){},Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).VelionSDK={})}(this,function(e){"use strict";var t=(e=>(e.PERMISSION_DENIED="CAMERA_PERMISSION_DENIED",e.DEVICE_NOT_FOUND="CAMERA_DEVICE_NOT_FOUND",e.DEVICE_NOT_READABLE="CAMERA_DEVICE_NOT_READABLE",e.INSECURE_CONTEXT="CAMERA_INSECURE_CONTEXT",e.POLICY_BLOCKED="CAMERA_POLICY_BLOCKED",e.NOT_SUPPORTED="CAMERA_NOT_SUPPORTED",e.WEBVIEW_BLOCKED="CAMERA_WEBVIEW_BLOCKED",e.CONSTRAINT_ERROR="CAMERA_CONSTRAINT_ERROR",e.UNKNOWN="CAMERA_UNKNOWN_ERROR",e))(t||{}),i=(e=>(e.SESSION_INVALID="SESSION_INVALID",e.SESSION_EXPIRED="SESSION_EXPIRED",e.SESSION_FETCH_FAILED="SESSION_FETCH_FAILED",e.SESSION_DECLINED="SESSION_DECLINED",e.ROUTING_TIMEOUT="ROUTING_TIMEOUT",e.FINGERPRINT_FAILED="FINGERPRINT_FAILED",e.DOCUMENT_UPLOAD_FAILED="DOCUMENT_UPLOAD_FAILED",e.LIVENESS_FAILED="LIVENESS_FAILED",e.LIVENESS_INIT_FAILED="LIVENESS_INIT_FAILED",e.LIVENESS_VIDEO_UPLOAD_FAILED="LIVENESS_VIDEO_UPLOAD_FAILED",e.CAMERA_ERROR="CAMERA_ERROR",e.NETWORK_ERROR="NETWORK_ERROR",e.API_ERROR="API_ERROR",e.CONTAINER_NOT_FOUND="CONTAINER_NOT_FOUND",e.ALREADY_STARTED="ALREADY_STARTED",e.INSTANCE_DESTROYED="INSTANCE_DESTROYED",e.INSECURE_EMBED="INSECURE_EMBED",e.INTEGRITY_FAILED="INTEGRITY_FAILED",e.RUNTIME_LOAD_FAILED="RUNTIME_LOAD_FAILED",e.UNKNOWN="UNKNOWN",e))(i||{});const n="VELION_INIT",s="VELION_READY",r="VELION_SUCCESS",o="VELION_ERROR",a="VELION_STEP_CHANGE",E="VELION_STEP_COMPLETE",c="VELION_TOKEN_EXPIRED",l="VELION_EXIT",d="VELION_LIVENESS_VIDEO_UPLOADED",h="VELION_TOKEN",I="lpqLG4Ta0YyBn/+lQh95tFl4CuTIHWapeeq7+xnPH+U=";function f(e){const t=atob(e),i=new ArrayBuffer(t.length),n=new Uint8Array(i);for(let s=0;s<t.length;s++)n[s]=t.charCodeAt(s);return i}async function u(e,t=I){let i,n,s,r,o,a;try{const t=await fetch(`${e}/manifest.json`,{credentials:"omit",cache:"no-cache",redirect:"error"});if(404===t.status)return{ok:!1,reason:"unavailable"};if(!t.ok)return{ok:!1,reason:"unavailable",detail:`HTTP ${t.status}`};i=await t.arrayBuffer(),n=(new TextDecoder).decode(i)}catch(E){return{ok:!1,reason:"unavailable",detail:E.message}}try{if(s=JSON.parse(n),"string"!=typeof s.version||"string"!=typeof s.entry||"string"!=typeof s.buildTime)return{ok:!1,reason:"malformed"}}catch(E){return{ok:!1,reason:"malformed",detail:E.message}}if(!t)return{ok:!0,manifest:s,signed:!1};try{const t=await fetch(`${e}/manifest.sig`,{credentials:"omit",cache:"no-cache",redirect:"error"});if(!t.ok)return{ok:!1,reason:"invalid_signature",detail:`sig HTTP ${t.status}`};r=f((await t.text()).trim())}catch(E){return{ok:!1,reason:"invalid_signature",detail:E.message}}try{o=await async function(e){const t=f(e);return crypto.subtle.importKey("raw",t,{name:"Ed25519"},!1,["verify"])}(t)}catch(E){return{ok:!1,reason:"invalid_signature",detail:`pubkey import failed: ${E.message}`}}try{a=await async function(e,t,i){return crypto.subtle.verify({name:"Ed25519"},e,t,i)}(o,r,i)}catch(E){return{ok:!1,reason:"invalid_signature",detail:E.message}}return a?{ok:!0,manifest:s,signed:!0}:{ok:!1,reason:"invalid_signature"}}const N="https://static.velion.la/sdk/onboarding/v1";function R(e){try{const i=new URL(e);return"https:"===i.protocol||"http:"===i.protocol&&("localhost"===(t=i.hostname)||"127.0.0.1"===t||"::1"===t||t.endsWith(".local"))}catch{return!1}var t}const O=["containerId","sessionId","sessionToken","apiUrl","expiresAt","theme","colorMode","locale","allowEmbedded","livenessShowIntro","livenessShowBackButton","fingerprintEndpoint","debug","googleMapsApiKey","showFinalResultScreen","showProgressBar","finalResultScreenTexts","routingTimeoutMs"];e.CameraErrorCode=t,e.DEFAULT_CDN_URL=N,e.SdkErrorCode=i,e.VELION_MSG_ERROR=o,e.VELION_MSG_EXIT=l,e.VELION_MSG_INIT=n,e.VELION_MSG_LIVENESS_VIDEO_UPLOADED=d,e.VELION_MSG_READY=s,e.VELION_MSG_STEP_CHANGE=a,e.VELION_MSG_STEP_COMPLETE=E,e.VELION_MSG_SUCCESS=r,e.VELION_MSG_TOKEN=h,e.VELION_MSG_TOKEN_EXPIRED=c,e.VelionOnboarding=class{constructor(e){this.status="IDLE",this.iframe=null,this.container=null,this.messageHandler=null,this.containerResizeObserver=null,this.iframeHeightWindowResizeHandler=null,this.cdnOrigin="",this.readyTimer=null,this.config=Object.freeze({...e})}clearReadyTimer(){null!==this.readyTimer&&(clearTimeout(this.readyTimer),this.readyTimer=null)}armReadyTimer(e){this.clearReadyTimer();const t=this.config.runtimeReadyTimeoutMs??3e4;t>0&&(this.readyTimer=setTimeout(()=>{this.readyTimer=null,"RUNNING"===this.status&&(this.status="ERROR",this.config.onError?.({code:i.RUNTIME_LOAD_FAILED,message:`The onboarding runtime at ${e} did not become ready within ${t} ms. Check that the CDN serves the assets referenced by its index.html and that the host page's Content-Security-Policy allows the runtime scripts.`}))},t))}async start(){if("DESTROYED"===this.status)return void this.config.onError?.({code:i.INSTANCE_DESTROYED,message:"This VelionOnboarding instance has been destroyed. Create a new instance to retry."});if("IDLE"!==this.status)return void this.config.onError?.({code:i.ALREADY_STARTED,message:"start() was called on an already-running SDK instance. Call destroy() before re-starting."});const e=document.getElementById(this.config.containerId);if(!e)return void this.config.onError?.({code:i.CONTAINER_NOT_FOUND,message:`No DOM element found with id="${this.config.containerId}". Ensure the element exists before calling start().`});const t=(this.config.cdnUrl??N).replace(/\/$/,"");if(!R(t))return void this.config.onError?.({code:i.UNKNOWN,message:`Insecure cdnUrl: "${t}". Must be https:// (http:// is allowed only for localhost).`});if(this.config.apiUrl&&!R(this.config.apiUrl))return void this.config.onError?.({code:i.UNKNOWN,message:`Insecure apiUrl: "${this.config.apiUrl}". Must be https:// (http:// is allowed only for localhost).`});if(this.cdnOrigin=function(e){try{return new URL(e).origin}catch{return""}}(t),!this.cdnOrigin)return void this.config.onError?.({code:i.UNKNOWN,message:`Invalid cdnUrl: ${t}. Must be a valid absolute URL.`});this.container=e,this.status="INITIALIZING";const I=document.createElement("iframe");I.setAttribute("title","Velion Onboarding"),I.setAttribute("allow","camera"),I.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-modals"),I.setAttribute("referrerpolicy","no-referrer"),I.style.width="100%",I.style.border="none",I.style.display="block";const f=e=>{if(e.origin!==this.cdnOrigin)return;if(e.source!==this.iframe?.contentWindow)return;const t=e.data;if(t&&"string"==typeof t.type)switch(this.clearReadyTimer(),t.type){case s:this.status="RUNNING";break;case r:this.status="COMPLETED",this.config.onSuccess?.(t.payload);break;case o:this.status="ERROR",this.config.onError?.(t.payload);break;case a:{const e=t.payload;this.config.onStepChange?.(e.step,e.stepIndex);break}case E:{const e=t.payload;this.config.onStepComplete?.(e.step);break}case l:this.status="EXITED",this.config.onExit?.(t.payload);break;case d:this.config.onLivenessVideoUploaded?.(t.payload);break;case c:{const e=this.config.onTokenExpired;e?e().then(e=>this.sendToIframe({type:h,payload:e}),()=>{this.config.onError?.({code:i.SESSION_EXPIRED,message:"Token refresh failed."})}):this.config.onError?.({code:i.SESSION_EXPIRED,message:"Session token expired. Provide onTokenExpired in config to refresh."});break}}};window.addEventListener("message",f),this.messageHandler=f,this.iframe=I,e.replaceChildren(I),this.attachIframeHeightSync(e,I);const u=await this.resolveManifest(t);if(!u)return;const _=u.entry.startsWith("/")?u.entry:`/${u.entry}`;return new Promise(e=>{I.onload=()=>{if(!this.iframe?.contentWindow)return this.status="ERROR",this.config.onError?.({code:i.UNKNOWN,message:"Iframe failed to load."}),void e();const s=function(e){const t={containerId:"velion-sdk-root",allowEmbedded:!0};for(const i of O){if("containerId"===i||"allowEmbedded"===i)continue;const n=e[i];void 0!==n&&"function"!=typeof n&&(t[i]=n)}return t}(this.config);let r;try{r=JSON.parse(JSON.stringify(s))}catch{r=s}this.sendToIframe({type:n,payload:r}),this.status="RUNNING",this.armReadyTimer(t),e()},I.onerror=()=>{this.status="ERROR",this.config.onError?.({code:i.UNKNOWN,message:`Failed to load onboarding app from ${t}. Check cdnUrl and network.`}),e()},I.src=`${t}${_}`})}async resolveManifest(e){let t;try{t=await u(e)}catch(n){return this.status="ERROR",this.config.onError?.({code:i.INTEGRITY_FAILED,message:`Manifest verification crashed unexpectedly: ${n.message}`}),null}if(t.ok)return!t.signed&&I?(this.status="ERROR",this.config.onError?.({code:i.INTEGRITY_FAILED,message:"Internal: manifest reported as unsigned despite pubkey being set."}),null):t.manifest;if("unavailable"===t.reason){return!0!==this.config.allowUnverifiedCdn?(this.status="ERROR",this.config.onError?.({code:i.INTEGRITY_FAILED,message:`CDN at "${e}" did not expose /manifest.json. The loader is configured to require a signed manifest. If the CDN is migrating, set \`allowUnverifiedCdn: true\` on the loader config — but only as a temporary measure.`}),null):{version:"legacy",buildTime:(new Date).toISOString(),entry:"/index.html"}}return this.status="ERROR",this.config.onError?.({code:i.INTEGRITY_FAILED,message:`CDN manifest at "${e}/manifest.json" failed integrity check (${t.reason}). Refusing to load the iframe to protect the user. `+("detail"in t&&t.detail?`Detail: ${t.detail}`:"")}),null}attachIframeHeightSync(e,t){const i=()=>{if(this.iframe!==t||this.container!==e)return;const i=e.clientHeight;i>0?(t.style.height=`${i}px`,t.style.minHeight=""):(t.style.height="min(85vh, 880px)",t.style.minHeight="")};i(),requestAnimationFrame(i),requestAnimationFrame(()=>{requestAnimationFrame(i)}),"undefined"!=typeof ResizeObserver?(this.containerResizeObserver=new ResizeObserver(i),this.containerResizeObserver.observe(e)):(this.iframeHeightWindowResizeHandler=i,window.addEventListener("resize",i))}detachIframeHeightSync(){this.containerResizeObserver&&(this.containerResizeObserver.disconnect(),this.containerResizeObserver=null),this.iframeHeightWindowResizeHandler&&(window.removeEventListener("resize",this.iframeHeightWindowResizeHandler),this.iframeHeightWindowResizeHandler=null)}sendToIframe(e){this.iframe?.contentWindow&&this.iframe.contentWindow.postMessage(e,this.cdnOrigin)}getStatus(){return this.status}static getVersion(){return"1.0.0-beta.25"}destroy(){"DESTROYED"!==this.status&&(this.clearReadyTimer(),this.detachIframeHeightSync(),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.iframe&&this.container&&(this.iframe.remove(),this.container.replaceChildren()),this.iframe=null,this.container=null,this.status="DESTROYED")}},e.setOpenCVBaseUrl=function(e){},Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
|
|
2
2
|
//# sourceMappingURL=velion-sdk.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"velion-sdk.umd.js","sources":["../../packages/types/src/index.ts","../src/manifest.ts","../src/velion-loader.ts","../src/index.ts"],"sourcesContent":["/**\n * @fileoverview Public TypeScript contracts for the Velion Onboarding SDK.\n *\n * Design principle: All types exported from this file constitute the public API.\n * Internal implementation types should live alongside their respective modules.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// STEP & FLOW TYPES\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The complete set of onboarding steps the backend can require.\n * The SDK dynamically receives the active subset for each session.\n *\n * @remarks\n * - `DEVICE_FINGERPRINT`: Silently collects device entropy via FingerprintJS.\n * - `DOCUMENTS_OCR`: Renders a UI for ID document image capture and upload.\n * - `LIVENESS_MATCH`: Drives the Velion Iris active-liveness flow over WebSocket (HUMAN only).\n * - `EMAIL_OTP` / `PHONE_OTP`: Contact verification via one-time passwords.\n * - `HUMAN_BASIC_INFO`: Natural-person identification data (HUMAN only).\n * - `LEGAL_BASIC_INFO`: Legal-entity identification data (LEGAL only).\n * - `EMAIL_CONTACT` / `PHONE_CONTACT`: contact collection without OTP.\n * - `TAX_INFO`, `HUMAN_EXTENDED_INFO`, `LEGAL_EXTENDED_INFO`, `ADDRESS_INFO`, `SWORN_DECLARATION`: form steps.\n * - `ECONOMIC_ACTIVITY_DECLARATION`: pick exactly one economic activity from a searchable list.\n * - `TERMS_AND_CONDITIONS`: accept configured T&C items.\n * - `FACE_MATCH`: server-side face comparison after liveness (no user input; SDK waits).\n * - `AML_SCREENING`: server-side PEP / sanctions screening (no user input; never routed to\n * the SDK as `NEXT_STEP`, only visible in `steps[]` and `STEP_UPDATED`).\n */\nexport type OnboardingStep =\n | 'DEVICE_FINGERPRINT'\n | 'HUMAN_BASIC_INFO'\n | 'LEGAL_BASIC_INFO'\n | 'EMAIL_OTP'\n | 'EMAIL_CONTACT'\n | 'PHONE_OTP'\n | 'PHONE_CONTACT'\n | 'TAX_INFO'\n | 'HUMAN_EXTENDED_INFO'\n | 'LEGAL_EXTENDED_INFO'\n | 'ADDRESS_INFO'\n | 'SWORN_DECLARATION'\n | 'ECONOMIC_ACTIVITY_DECLARATION'\n | 'DOCUMENTS_OCR'\n | 'LIVENESS_MATCH'\n | 'FACE_MATCH'\n | 'AML_SCREENING'\n | 'TERMS_AND_CONDITIONS';\n\n/**\n * Status of an individual onboarding step (matches backend OnboardingStepStatus).\n */\nexport type OnboardingStepStatus =\n | 'PENDING'\n | 'IN_PROGRESS'\n | 'COMPLETED'\n | 'SKIPPED'\n | 'FAILED';\n\n/**\n * Type of onboarding session (matches backend OnboardingType).\n * Constrains which steps are valid: HUMAN allows biometric steps, LEGAL uses entity data.\n */\nexport type OnboardingType = 'HUMAN' | 'LEGAL';\n\n/** Which face of the ID document is being captured. */\nexport type DocumentSide = 'FRONT' | 'BACK';\n\n/**\n * How the user can provide document images in the DOCUMENTS_OCR step.\n * - `'camera'`: Only \"Take photo\" (opens camera on supported devices).\n * - `'file'`: Only \"Choose file\" (file picker).\n * - `'both'`: Both options; implementer can style via theme.\n *\n * @default 'both'\n */\nexport type DocumentCaptureMode = 'camera' | 'file' | 'both';\n\n/**\n * Orientation of the document capture cutout / crop frame.\n * - `'landscape'`: Horizontal card (default — standard ID-1 cards).\n * - `'portrait'`: Vertical card (e.g. some passports, residence permits).\n *\n * @default 'landscape'\n */\nexport type DocumentOrientation = 'landscape' | 'portrait';\n\n/**\n * Optional overrides for document OCR UI (not part of `VelionConfig`; used internally).\n */\nexport interface DocumentsConfig {\n /**\n * When true (default), the document camera preview is mirrored (like selfie mode).\n * Set to false if the back camera shows the document inverted.\n *\n * @default true\n */\n mirrorDocumentVideo?: boolean;\n\n /**\n * Orientation of the document capture frame and crop.\n * - `'landscape'`: Horizontal card (default — standard ID-1 cards).\n * - `'portrait'`: Vertical card.\n *\n * @default 'landscape'\n */\n documentOrientation?: DocumentOrientation;\n}\n\n/** Generic visible/required field toggle used by workflow step configs. */\nexport interface StepFieldRule {\n visible: boolean;\n required: boolean;\n}\n\n/** Select option used by configurable step fields (e.g. HUMAN_EXTENDED_INFO gender/civil_status). */\nexport interface StepFieldOption {\n id: string;\n display_value?: string;\n}\n\n/** Field rule with optional selectable options. */\nexport interface StepFieldRuleWithOptions extends StepFieldRule {\n options?: StepFieldOption[];\n}\n\n/** STEP config for HUMAN_BASIC_INFO. */\nexport interface HumanBasicInfoStepConfig {\n accepted_country_documents?: Array<{\n country_code: string;\n document_types: Array<{ id: string; display_value?: string }>;\n }>;\n}\n\n/**\n * One selectable option row from TAX_INFO `accepted_tax_information` (backend GET session).\n * Prefer `code` as the value sent in POST tax-info; `id` is supported for older payloads.\n */\nexport interface TaxInfoConfigOptionRow {\n code?: string;\n id?: string;\n display_value?: string;\n tax_code?: string;\n}\n\n/** One country block in TAX_INFO step config (GET session `current_step.config`). */\nexport interface TaxInfoAcceptedCountryRow {\n country_code: string;\n identification_types?: TaxInfoConfigOptionRow[];\n /** Options for the \"Categoría fiscal\" / taxpayer category select. */\n taxpayer_categories?: TaxInfoConfigOptionRow[];\n}\n\n/** STEP config for TAX_INFO. */\nexport interface TaxInfoStepConfig {\n /** Identification types + taxpayer categories per country. */\n accepted_tax_information?: TaxInfoAcceptedCountryRow[];\n}\n\n/** STEP config for HUMAN_EXTENDED_INFO. */\nexport interface HumanExtendedInfoStepConfig {\n /** Optional top-level lists sent by backend for select fields. */\n gender_options?: StepFieldOption[];\n civil_status_options?: StepFieldOption[];\n fields?: Partial<\n Record<\n 'gender' | 'civil_status' | 'birth_date' | 'birth_country' | 'citizenship' | 'residence_country',\n StepFieldRuleWithOptions\n >\n >;\n}\n\n/** STEP config for ADDRESS_INFO. */\nexport interface AddressInfoStepConfig {\n address_type?: { id?: string; display_value?: string };\n fields?: Partial<\n Record<\n 'state' | 'city' | 'district' | 'neighborhood' | 'postal_code' | 'street' | 'number',\n StepFieldRule\n >\n >;\n /**\n * Optional ISO 3166-1 alpha-2 allowlist for the country selector. Only\n * consumed by the manual-entry form (when the SDK is configured without a\n * Google Maps key). When unset, every country is offered.\n */\n country_allow?: string[];\n}\n\n/** STEP config for SWORN_DECLARATION. */\nexport interface SwornDeclarationStepConfig {\n ask_pep?: boolean;\n ask_obligated_subject?: boolean;\n ask_fatca?: boolean;\n ask_ocde?: boolean;\n on_declared_pep?: string;\n on_declared_obligated_subject?: string;\n on_declared_fatca?: string;\n on_declared_ocde?: string;\n}\n\n/**\n * A single selectable economic activity presented in the\n * `ECONOMIC_ACTIVITY_DECLARATION` step. Mirrors the backend `EconomicActivityItem`\n * (`code` + `display_name`); `code` is unique within the list and is what the SDK\n * submits. There is no global catalog — each workflow declares its own list.\n */\nexport interface EconomicActivityItem {\n /** Machine-readable activity code (e.g. an AFIP/CLAE code) — unique within the list. */\n code: string;\n /** Human-readable label shown to the user. */\n display_name: string;\n}\n\n/** STEP config for ECONOMIC_ACTIVITY_DECLARATION (`current_step.config`). */\nexport interface EconomicActivityStepConfig {\n /** Selectable activities; always non-empty when the step is active. */\n items?: EconomicActivityItem[];\n}\n\n/** STEP config for DOCUMENTS_OCR. */\nexport interface DocumentsOcrStepConfig {\n country_allow?: string[];\n capture_methods?: {\n camera_scan?: boolean;\n upload?: boolean;\n };\n /** Crop frame orientation for document camera capture. */\n document_orientation?: DocumentOrientation;\n /** Mirror preview in document camera (default true). */\n mirror_document_video?: boolean;\n}\n\n/** STEP config for TERMS_AND_CONDITIONS. */\nexport interface TermsAndConditionsStepConfig {\n terms?: Array<{ title?: string; url?: string }>;\n}\n\n/** Generic per-step runtime config map carried by session state and SSE. */\nexport type StepConfigMap = Partial<Record<OnboardingStep, Record<string, unknown>>>;\n\n/**\n * Request body for POST /sessions/:sessionId/documents-init.\n * Allowed values are defined by the backend; the SDK currently sends a fixed type until\n * the session exposes selectable document types.\n */\nexport interface DocumentsInitRequest {\n document_type: string;\n}\n\n/**\n * Response from POST /sessions/:sessionId/documents-init.\n * Backend returns signed upload URLs so the SDK can PUT each document side\n * directly to storage (e.g. GCS) without routing bytes through the backend.\n */\n/**\n * Response of `POST /sessions/:id/documents-init`.\n *\n * Each URL is a GCS V4 signed PUT issued by the Velion CDN, valid for about\n * two minutes, and signed over `host`, `content-type` and\n * `x-goog-content-length-range`. The PUT must therefore send\n * `Content-Type: image/jpeg` **and** `x-goog-content-length-range:\n * 0,<max_file_size_bytes>` byte for byte, or GCS answers 400\n * `MalformedSecurityHeader` (OSDKW-5). Request the URLs right before the\n * upload, never ahead of the capture.\n */\nexport interface DocumentsInitResponse {\n /** Pre-signed URL to upload the front side (PUT, see the interface docs for the required headers). */\n front_side_upload_url: string;\n /** Pre-signed URL to upload the back side (PUT, see the interface docs for the required headers). */\n back_side_upload_url: string;\n /**\n * Upper bound the URLs were signed for; the exact number that goes into the\n * `x-goog-content-length-range` header. Also usable to reject oversize\n * captures before the PUT.\n */\n max_file_size_bytes?: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ERROR TYPES\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Granular error codes for camera/hardware failures.\n * Allows consumers to display localized, actionable messages to end-users.\n */\nexport enum CameraErrorCode {\n /** User explicitly denied camera access in the browser permission dialog. */\n PERMISSION_DENIED = 'CAMERA_PERMISSION_DENIED',\n /** No camera device was detected on the system. */\n DEVICE_NOT_FOUND = 'CAMERA_DEVICE_NOT_FOUND',\n /**\n * The camera could not be opened for reading (often in use by another app,\n * or blocked at the OS level). Maps to `NotReadableError` / `TrackStartError`.\n */\n DEVICE_NOT_READABLE = 'CAMERA_DEVICE_NOT_READABLE',\n /**\n * The page is served over HTTP (not HTTPS) in a production environment.\n * `getUserMedia` is blocked by the browser's Secure Context policy.\n */\n INSECURE_CONTEXT = 'CAMERA_INSECURE_CONTEXT',\n /**\n * Browser policy blocked camera (e.g. cross-origin iframe without permission,\n * or `Permissions-Policy` / Feature-Policy denying camera). Maps to `SecurityError`\n * from `getUserMedia` — distinct from {@link INSECURE_CONTEXT} (non-HTTPS).\n */\n POLICY_BLOCKED = 'CAMERA_POLICY_BLOCKED',\n /** The browser or WebView does not support the `getUserMedia` API. */\n NOT_SUPPORTED = 'CAMERA_NOT_SUPPORTED',\n /**\n * Camera access is blocked by a WebView policy (common in in-app browsers\n * on iOS/Android that restrict hardware access).\n */\n WEBVIEW_BLOCKED = 'CAMERA_WEBVIEW_BLOCKED',\n /** The requested video constraints (resolution, facing mode) are not satisfiable. */\n CONSTRAINT_ERROR = 'CAMERA_CONSTRAINT_ERROR',\n /** An unclassified camera error occurred. Check `cause` for details. */\n UNKNOWN = 'CAMERA_UNKNOWN_ERROR',\n}\n\n/**\n * Top-level SDK error codes covering all failure scenarios in the onboarding flow.\n */\nexport enum SdkErrorCode {\n // ── Session ──────────────────────────────────────────────────────────────\n /** The sessionToken was rejected by the backend (401/403). */\n SESSION_INVALID = 'SESSION_INVALID',\n /** The sessionToken has expired (typically a 401 after initial validation). */\n SESSION_EXPIRED = 'SESSION_EXPIRED',\n /** Could not fetch the initial session state from the backend. */\n SESSION_FETCH_FAILED = 'SESSION_FETCH_FAILED',\n /** Backend sent SESSION_DECLINED over SSE; session ended before completion. */\n SESSION_DECLINED = 'SESSION_DECLINED',\n\n // ── Modules ──────────────────────────────────────────────────────────────\n /** FingerprintJS failed to generate the visitor ID. */\n FINGERPRINT_FAILED = 'FINGERPRINT_FAILED',\n /** One or more document images failed to upload successfully. */\n DOCUMENT_UPLOAD_FAILED = 'DOCUMENT_UPLOAD_FAILED',\n /** The liveness check did not pass or returned an unexpected result. */\n LIVENESS_FAILED = 'LIVENESS_FAILED',\n /** The Liveness module failed to initialize (e.g., could not create the Iris session). */\n LIVENESS_INIT_FAILED = 'LIVENESS_INIT_FAILED',\n /**\n * The recorded liveness session video could not be uploaded to storage.\n * Best-effort only: this never fails the liveness step — it is logged so the\n * back-office knows the playback evidence is missing for the session.\n */\n LIVENESS_VIDEO_UPLOAD_FAILED = 'LIVENESS_VIDEO_UPLOAD_FAILED',\n\n // ── Camera ───────────────────────────────────────────────────────────────\n /**\n * A camera-related error occurred. Inspect `cameraCode` for the specific reason.\n * @see CameraErrorCode\n */\n CAMERA_ERROR = 'CAMERA_ERROR',\n\n // ── Network & API ────────────────────────────────────────────────────────\n /** A network-level failure (no internet, CORS, DNS). */\n NETWORK_ERROR = 'NETWORK_ERROR',\n /** The backend returned a non-2xx HTTP status. */\n API_ERROR = 'API_ERROR',\n\n // ── Configuration ────────────────────────────────────────────────────────\n /** The `containerId` provided in the config does not exist in the DOM. */\n CONTAINER_NOT_FOUND = 'CONTAINER_NOT_FOUND',\n /** `start()` was called on an SDK instance that is already running. */\n ALREADY_STARTED = 'ALREADY_STARTED',\n /** `start()` was called after `destroy()`. The instance is no longer usable. */\n INSTANCE_DESTROYED = 'INSTANCE_DESTROYED',\n\n /**\n * The page is running inside an iframe and embedding was not explicitly allowed.\n * Configure Content-Security-Policy: frame-ancestors or X-Frame-Options on the\n * page that hosts the SDK, or set allowEmbedded: true if embedding is intentional.\n */\n INSECURE_EMBED = 'INSECURE_EMBED',\n\n /**\n * The CDN-served manifest could not be downloaded or its Ed25519 signature did\n * not verify against the loader's embedded public key. The loader refuses to\n * mount the iframe to prevent execution of tampered/unsigned bundles.\n */\n INTEGRITY_FAILED = 'INTEGRITY_FAILED',\n /**\n * The iframe loaded but the runtime never reported `VELION_READY` within\n * `runtimeReadyTimeoutMs` (default 30 s). Typical causes: the CDN serves an\n * `index.html` whose hashed assets are missing (deploy under the wrong\n * prefix), a Content-Security-Policy on the host blocking the runtime's\n * scripts, or the runtime crashing before it could boot. Without this the\n * host saw a blank iframe and no callback at all.\n */\n RUNTIME_LOAD_FAILED = 'RUNTIME_LOAD_FAILED',\n\n UNKNOWN = 'UNKNOWN',\n}\n\n/**\n * A structured, machine-readable error object emitted by the SDK.\n *\n * @example\n * ```typescript\n * const sdk = new VelionOnboarding({\n * onError: (err) => {\n * if (err.code === SdkErrorCode.CAMERA_ERROR &&\n * err.cameraCode === CameraErrorCode.PERMISSION_DENIED) {\n * showCameraPermissionDialog();\n * }\n * }\n * });\n * ```\n */\nexport interface SdkError {\n /** Primary SDK-level error classification. */\n readonly code: SdkErrorCode;\n /** Human-readable English description for developer logging. */\n readonly message: string;\n /** Present when `code === SdkErrorCode.SESSION_DECLINED` (SSE `reason`). */\n readonly declineReason?: string;\n /** Present when `code === SdkErrorCode.SESSION_DECLINED` (SSE `step_code`). */\n readonly stepCode?: string;\n /**\n * Localized headline resolved by the SDK for SESSION_DECLINED.\n * Useful when hosts hide the built-in final result screen but still want\n * the same user-facing text in custom UI.\n */\n readonly declineTitle?: string;\n /**\n * Localized body message resolved by the SDK for SESSION_DECLINED.\n * Mirrors the text shown in the built-in final result screen.\n */\n readonly declineMessage?: string;\n /** Present when `code === SdkErrorCode.CAMERA_ERROR`. Provides camera-specific detail. */\n readonly cameraCode?: CameraErrorCode;\n /** HTTP status code when the error originated from an API call. */\n readonly httpStatus?: number;\n /**\n * The original caught value for debugging.\n * Not guaranteed to be an `Error` instance — could be a DOMException or string.\n */\n readonly cause?: unknown;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// THEMING\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * White-labeling theme configuration.\n * Each property maps to a CSS custom property injected on the SDK container,\n * making all SDK UI elements styleable via `--vl-*` variables.\n *\n * @example\n * ```typescript\n * const theme: VelionTheme = {\n * primaryColor: '#0052CC', // → --vl-primary-color\n * borderRadius: '12px', // → --vl-border-radius\n * fontFamily: 'Inter, sans-serif'// → --vl-font-family\n * };\n * ```\n */\nexport interface VelionTheme {\n /** Primary brand color. Used for buttons, progress indicators, and accents. */\n primaryColor?: string;\n /** Secondary color. Used for hover states and secondary actions. */\n secondaryColor?: string;\n /** Background color of the SDK container. */\n backgroundColor?: string;\n /** Main body text color. */\n textColor?: string;\n /** Secondary text color for subtitles, captions, and de-emphasized copy. */\n textSecondaryColor?: string;\n /** Border radius for cards, buttons, and inputs. */\n borderRadius?: string;\n /** Font family string. Must be available in the host page's CSS context. */\n fontFamily?: string;\n /** Color used for error states and destructive actions. */\n errorColor?: string;\n /** Color used for success confirmations. */\n successColor?: string;\n /** Color used for warnings and informational alerts. */\n warningColor?: string;\n /** Border color for input fields and separators. */\n borderColor?: string;\n /** Box shadow definition for elevated elements (cards, modals). */\n cardShadow?: string;\n\n /**\n * When `true`, the round icon that sits above each step's title is hidden.\n * Only the title and subtitle are rendered. Useful when the host UI already\n * provides its own visual identity per step or when a denser layout is\n * preferred.\n *\n * @default false\n */\n hideStepIcons?: boolean;\n\n /**\n * When `true`, the thin progress bar at the top of the SDK container is\n * hidden. Equivalent to setting `VelionConfig.showProgressBar: false` —\n * either flag is enough to disable it (whichever is more convenient given\n * how the host configures the SDK). Disable when your host UI already\n * surfaces its own progress indicator.\n *\n * @default false\n */\n hideProgressBar?: boolean;\n}\n\n/**\n * Optional title and body for one terminal verdict on the final result screen.\n */\nexport interface VelionFinalResultStatusCopy {\n title?: string;\n message?: string;\n}\n\n/**\n * Configurable copy for the built-in final result screen (`showFinalResultScreen`).\n * Omitted fields fall back to SDK defaults for the active `locale`.\n */\nexport interface VelionFinalResultScreenTexts {\n approved?: VelionFinalResultStatusCopy;\n manualReview?: VelionFinalResultStatusCopy;\n rejected?: VelionFinalResultStatusCopy;\n /** Copy when {@link OnboardingResult.status} is `DECLINED`. */\n declined?: VelionFinalResultStatusCopy;\n /** Primary button label (e.g. \"Entendido\" / \"Got it\"). */\n dismissLabel?: string;\n}\n\n/**\n * Localized title/body for one `reason` on the SESSION_DECLINED screen.\n * Keys match backend `reason` (e.g. `TAX_INFO_FAILED`).\n */\nexport interface VelionSessionDeclinedReasonMessages {\n en?: VelionFinalResultStatusCopy;\n es?: VelionFinalResultStatusCopy;\n}\n\n/**\n * Per-reason overrides for the built-in SESSION_DECLINED screen (`showFinalResultScreen` not false).\n * Unspecified reasons fall back to SDK defaults when available, then generic copy.\n */\nexport type VelionSessionDeclinedReasonTexts = Record<\n string,\n VelionSessionDeclinedReasonMessages\n>;\n\n/**\n * Per-step overrides for the retry dialog shown when the backend emits a\n * `ROUTING_COMMAND / RETRY_STEP` (the step failed but the retry quota is not\n * exhausted). Keyed by step code (e.g. `LIVENESS_MATCH`, `DOCUMENTS_OCR`).\n * Unspecified steps fall back to SDK per-step defaults, then generic copy.\n *\n * Note: the SDK intentionally ignores the backend `reason` for this dialog —\n * it is a diagnostic string (e.g. `\"STEP_FAILED_RETRY_ALLOWED (attempt 1/3)\"`),\n * not user-facing localized copy. The copy is derived from the step instead.\n */\nexport type VelionRetryStepTexts = Record<\n string,\n VelionSessionDeclinedReasonMessages\n>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// CALLBACKS\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The complete set of lifecycle callbacks the host application can subscribe to.\n * All callbacks are optional. The SDK is still functional without any of them,\n * but `onError` and `onSuccess` are strongly recommended for production.\n */\nexport interface VelionCallbacks {\n /**\n * Fires when the entire onboarding flow has been completed successfully\n * and the backend has confirmed the session as resolved.\n *\n * @param result - Final onboarding verdict from the backend.\n */\n onSuccess?: (result: OnboardingResult) => void;\n\n /**\n * Fires when an unrecoverable error terminates the onboarding flow.\n * Any terminal failure (camera error, document capture failure, upload failure,\n * liveness failure, API error, etc.) is delivered here so the host can notify the user.\n * After this callback, the SDK instance is in an error state.\n * Call `sdk.destroy()` and re-instantiate to retry.\n *\n * @param error - Structured error with a machine-readable code (e.g. CAMERA_ERROR, DOCUMENT_UPLOAD_FAILED).\n */\n onError?: (error: SdkError) => void;\n\n /**\n * Fires each time the active step changes during the flow.\n * Useful for updating a custom step indicator in the host UI.\n *\n * The required step list is resolved dynamically by the backend, so the SDK\n * does not know the total step count up front. Callers that need a progress\n * indicator should rely on the SDK's built-in progress bar (or the trickling\n * counter in `stepIndex`).\n *\n * @param step - The step that just became active.\n * @param stepIndex - Zero-based index of the current step.\n */\n onStepChange?: (step: OnboardingStep, stepIndex: number) => void;\n\n /**\n * Fires immediately after a step succeeds, before advancing to the next one.\n * Useful for analytics or triggering intermediate UI transitions.\n *\n * @param step - The step that was just completed.\n */\n onStepComplete?: (step: OnboardingStep) => void;\n\n /**\n * Fires for each SSE event from GET /sessions/:sessionId/events\n * (e.g. STEP_UPDATED, SESSION_APPROVED).\n */\n onSessionEvent?: (event: OnboardingSessionEvent) => void;\n\n /**\n * Called when the user taps the dismiss control on the final result screen\n * (only when the final result screen is shown). `onSuccess` has already run.\n */\n onFinalResultScreenDismiss?: () => void;\n\n /**\n * Fires when the LIVENESS_MATCH step successfully uploaded the recorded\n * session video to the onboarding storage (GCS PUT returned 200). Best-effort:\n * if the upload failed, timed out, or the session had no `video_upload` URL,\n * the callback is not invoked — liveness still completes normally.\n *\n * @param ref - URL and metadata of the uploaded video.\n */\n onLivenessVideoUploaded?: (ref: LivenessVideoRef) => void;\n\n /**\n * Fires when the user leaves the onboarding flow without a terminal verdict —\n * either by tapping \"back\" on the first reversible step (`BACK_EXIT`) or\n * \"Cancel\" on the retry dialog shown after a `RETRY_STEP` (`RETRY_CANCELLED`).\n * In both cases the backend told the SDK to leave the flow and hand control\n * back to the host; the SDK has already torn down its UI.\n *\n * Distinct from `onSuccess` (a terminal verdict) and `onError` (a failure):\n * the host should typically dismiss/close the SDK surface when this fires.\n *\n * @param exit - Why the flow exited and the step it exited from.\n */\n onExit?: (exit: OnboardingExit) => void;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// MAIN CONFIGURATION\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Root configuration object for the `VelionOnboarding` class.\n *\n * @example\n * ```typescript\n * const sdk = new VelionOnboarding({\n * containerId: 'velion-root',\n * sessionId: 'ses_abc123',\n * sessionToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',\n * apiUrl: 'https://api.velion.la/v1',\n * theme: { primaryColor: '#0052CC' },\n * onSuccess: (result) => console.log('Done!', result),\n * onError: (err) => console.error('SDK Error:', err),\n * });\n *\n * await sdk.start();\n * ```\n */\nexport interface VelionConfig extends VelionCallbacks {\n /**\n * The `id` of an existing DOM element where the SDK will render its UI.\n * The element **must** be present in the document at the time `start()` is called.\n *\n * @example `'velion-kyc-container'`\n */\n containerId: string;\n\n /**\n * The unique session identifier generated by your backend for this\n * onboarding session. Used to scope all API calls.\n *\n * @example `'ses_9f8d7c6b5a4e3f2d'`\n */\n sessionId: string;\n\n /**\n * A short-lived, opaque authorization token (e.g. HS256 JWT) issued by your\n * backend when creating the session. Sent as `Authorization: Bearer <sessionToken>`\n * on every SDK request. Required for all endpoints behind the gateway.\n *\n * **Security note:** This token is held in memory only (never in localStorage\n * or cookies) and is automatically discarded on `destroy()`.\n */\n sessionToken: string;\n\n /**\n * ISO-8601 expiry timestamp of the sessionToken. If provided, the SDK may\n * check expiration before requests and emit SESSION_EXPIRED without a round-trip.\n * When the backend returns 401/403, the SDK emits SESSION_EXPIRED anyway.\n */\n expiresAt?: string;\n\n /**\n * Base URL of the Velion onboarding edge. Must **not** include a trailing slash.\n * SDK paths are relative to this (e.g. GET `/sessions/{sessionId}`).\n *\n * Defaults to the public production edge `https://sdk.velion.la/onboarding`.\n * Override only for staging or self-hosted environments.\n */\n apiUrl?: string;\n\n /**\n * Optional white-labeling theme. If omitted, SDK renders with Velion defaults.\n */\n theme?: VelionTheme;\n\n /**\n * Global visual mode for SDK overlays and controls.\n * - `'light'`: Light translucent surfaces.\n * - `'dark'`: Dark translucent surfaces.\n *\n * @default 'light'\n */\n colorMode?: 'light' | 'dark';\n\n /**\n * BCP 47 locale tag for SDK UI copy (labels, instructions, error messages).\n *\n * @default 'es'\n */\n locale?: string;\n\n /**\n * When `true`, after the backend returns a terminal verdict the SDK shows a\n * full-screen summary (APPROVED, MANUAL_REVIEW, REJECTED) inside the container,\n * styled with `theme` and `colorMode`. `onSuccess` still fires with the result.\n *\n * @default true\n */\n showFinalResultScreen?: boolean;\n\n /**\n * When `true` (default), the SDK renders a thin progress bar at the top of\n * the container that advances as the user moves through steps. Disable for\n * embedded experiences that already provide their own progress UI.\n *\n * @default true\n */\n showProgressBar?: boolean;\n\n /**\n * Overrides for default titles, messages, and dismiss button on the final result screen.\n * Only used when the final result screen is shown (`showFinalResultScreen` is not `false`).\n */\n finalResultScreenTexts?: VelionFinalResultScreenTexts;\n\n /**\n * Optional copy for SESSION_DECLINED SSE screens, keyed by backend `reason`.\n * Merged with SDK defaults (e.g. `TAX_INFO_FAILED`) for `locale` `en` / `es`.\n */\n sessionDeclinedReasonTexts?: VelionSessionDeclinedReasonTexts;\n\n /**\n * Optional copy for the retry dialog (`ROUTING_COMMAND / RETRY_STEP`), keyed by\n * step code (e.g. `LIVENESS_MATCH`). Merged with SDK per-step defaults for\n * `locale` `en` / `es`; unspecified steps fall back to generic retry copy.\n */\n retryStepTexts?: VelionRetryStepTexts;\n\n /**\n * When `true` (default), shows the Velion preparation screen (face icon, tips,\n * \"Start verification\") before connecting to Iris. When `false`, skips it and\n * starts the camera + Iris session immediately on step entry.\n *\n * @default true\n */\n livenessShowIntro?: boolean;\n\n /**\n * When `false`, hides the \"Atrás\" / \"Back\" button on the liveness preparation\n * screen. The button is only present in the intro; once the camera capture\n * begins the user must complete the Iris challenges to leave the step.\n *\n * @default true\n */\n livenessShowBackButton?: boolean;\n\n /**\n * If true, allows the SDK to run when the page is inside an iframe.\n * Only set to true if you have configured Content-Security-Policy: frame-ancestors\n * or X-Frame-Options on the HTTP response of the page that hosts the SDK.\n * If false (default) and the page is embedded in an iframe, the SDK emits\n * INSECURE_EMBED and does not start.\n *\n * @default false\n */\n allowEmbedded?: boolean;\n\n /**\n * Internal — set by the Velion CDN entry (`cdn-main.ts`) to the `event.origin`\n * captured from the loader's first VELION_INIT message. The SDK validates it\n * against `allowed_parent_origins` returned by the backend and refuses to start\n * if the origin is not in the customer's registered allowlist.\n *\n * Direct integrators (running VelionOnboarding without the loader iframe)\n * should leave this unset.\n *\n * @internal\n */\n iframeParentOrigin?: string;\n\n /**\n * Fingerprint Pro API endpoint (custom subdomain). Used by the DEVICE_FINGERPRINT\n * step to load and identify visitors. Only set this when using a custom Fingerprint\n * subdomain or proxy; otherwise the default Velion endpoint is used.\n *\n * @default 'https://fp.velion.la'\n */\n fingerprintEndpoint?: string;\n\n /**\n * Fingerprint Pro public API key. Used by the DEVICE_FINGERPRINT step to\n * identify visitors. Defaults to the shared Velion public key — set this\n * only when your tenant has its own Fingerprint workspace.\n *\n * @default Velion's shared public key\n */\n fingerprintApiKey?: string;\n\n /**\n * Hard ceiling, in milliseconds, for the DEVICE_FINGERPRINT step (agent\n * load + identification). The Fingerprint agent has its own internal\n * timeout, but with tracker blockers or a blackholed endpoint it can hang\n * well past it; the SDK races against this value so the step fails with\n * `FINGERPRINT_FAILED` instead of leaving the user on a blank screen.\n *\n * @default 15000\n */\n fingerprintTimeoutMs?: number;\n\n /**\n * Google Maps Platform API key (Maps JavaScript API + Places). Required for the\n * {@code ADDRESS_INFO} step to show the map, search, and geocoding. Omit only if\n * your flow never reaches that step.\n */\n googleMapsApiKey?: string;\n\n /**\n * Optional calibration tag forwarded to Iris for the LIVENESS_MATCH step.\n * When the Iris worker is running with `CALIBRATION_BACKEND=fs|gcs`, every\n * frame of this session is mirrored to the dataset bucket with the tag in\n * `metadata.json`, so we can build labeled training sets for the spoof /\n * liveness models. Does NOT affect the score.\n *\n * Suggested values (must match the worker's known labels — see\n * `iris-worker` README): `REAL_GOOD_LIGHT`, `REAL_BAD_LIGHT`, `REAL_BACKLIT`,\n * `REAL_BUSY_BG`, `REAL_NEUTRAL_BG`, `REAL_GLASSES`, `REAL_OUTDOOR`,\n * `FAKE_PHONE_PHOTO`, `FAKE_PHONE_VIDEO`, `FAKE_TABLET_VIDEO`,\n * `FAKE_MONITOR_VIDEO`, `FAKE_PRINT`, `FAKE_MASK`, `UNKNOWN`, `EDGE_CASE`.\n *\n * Leave undefined for normal user-facing sessions.\n */\n livenessCalibrationLabel?: string;\n\n /**\n * When true, the SDK enables verbose `console.info` logging at runtime even in\n * production builds. Useful when reproducing customer issues. Independent of the\n * build-time `__DEBUG__` flag, which already gates dev-server logs.\n *\n * @default false\n */\n debug?: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// BACKEND RESPONSE SHAPES\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Backend step DTO (OnboardingStepDto). GET session returns an array of these.\n */\nexport interface OnboardingStepDto {\n code: string;\n name?: string;\n description?: string;\n order?: number;\n status?: string;\n /**\n * Machine-readable cause of the current `status` when it is `FAILED` or\n * `MANUAL_REVIEW` (backend `StepStatusDetailCode`, VON-54 / VON-56). Examples:\n * `DOC_UNREADABLE`, `DOC_MRZ_NOT_READABLE`, `DOC_EXPIRED`, `DOC_AGE_EXCLUSION`,\n * `LIVENESS_BELOW_THRESHOLD`, `LIVENESS_SPOOF_SUSPECTED`,\n * `FACE_MATCH_BELOW_THRESHOLD`, `FINGERPRINT_BOT_DETECTED`,\n * `TAX_ARCA_VALIDATION_FAILED`, `TERMS_NOT_ACCEPTED`,\n * `ECONOMIC_ACTIVITY_INVALID_SELECTION`, `STEP_UNEXPECTED_ERROR`. Hosts may\n * use it for analytics or support tooling; the SDK derives its user-facing\n * copy from the step, never from this code.\n */\n status_detail?: string;\n required?: boolean;\n /** Step-specific payload (e.g. OCR fields, fingerprint, T&C items). */\n result?: Record<string, unknown>;\n /** Raw workflow config for this step (thresholds, accepted documents, …). */\n config?: Record<string, unknown>;\n /**\n * Whether the user may navigate back INTO this step after completing it\n * (intrinsic per-step flag the backend stamps from the workflow). Used only\n * to derive `can_go_back` for steps reached via SSE (which doesn't carry the\n * flag) and for prefill — NOT to decide whether to render the back button.\n * @see OnboardingDtoResponse.can_go_back\n */\n reversible?: boolean;\n}\n\n/**\n * Backend session/onboarding DTO (OnboardingDto). Returned by GET /sessions/:sessionId.\n * The SDK maps this to SessionState for the step machine.\n */\nexport interface OnboardingDtoResponse {\n id?: string;\n related_id?: string;\n onboarding_type?: string;\n status?: string;\n current_step?: {\n code?: string;\n status?: string;\n config?: Record<string, unknown>;\n /**\n * Previously-captured data for this step, present when a reverse navigation\n * re-opened a completed step. The SDK uses it as editable prefill.\n */\n result?: Record<string, unknown>;\n /** Intrinsic per-step reversibility (prefill / SSE-derivation only). */\n reversible?: boolean;\n };\n steps?: OnboardingStepDto[];\n created_at?: string;\n updated_at?: string;\n completed_at?: string;\n metadata?: Record<string, unknown>;\n /**\n * Whether the SDK should render the \"back\" control on the current step.\n * Computed server-side from the workflow graph (barrier model): `true` when\n * there is a previous reversible step to re-open, OR when the current step is\n * the first reversible step (back exits the flow via `BACK_EXIT`). The SDK\n * uses **this** — not a step's intrinsic `reversible` flag — to decide whether\n * to show the back button.\n */\n can_go_back?: boolean;\n /**\n * Origins (scheme + host + port) authorized by the customer to embed the SDK.\n * The iframe validates `event.origin` of the loader's first VELION_INIT against\n * this list and aborts with INSECURE_EMBED otherwise. Backend must also enforce\n * server-side via the `Origin` header on every API call.\n */\n allowed_parent_origins?: string[];\n}\n\n/**\n * Session state used by the SDK step machine. Derived from GET /sessions/:sessionId\n * (OnboardingDto): steps sorted by order → requiredSteps, first non-completed index → currentStepIndex.\n */\nexport interface SessionState {\n /** Current runnable step reported by backend session state. */\n currentStep?: OnboardingStep;\n /** Step-specific config for `currentStep` from GET /sessions/:id `current_step.config`. */\n currentStepConfig?: Record<string, unknown>;\n /** Cached configs discovered from GET session and SSE `step_config`. */\n stepConfigs?: StepConfigMap;\n /** Session type (HUMAN or LEGAL). Constrains which steps are applicable. */\n onboardingType?: OnboardingType;\n /**\n * Whether the SDK should render the \"back\" control on `currentStep`. Mapped\n * from GET `can_go_back` (workflow-graph computed). When absent (e.g. a step\n * reached via SSE, which doesn't carry the flag) the orchestrator derives it\n * from {@link stepReversibility}.\n */\n canGoBack?: boolean;\n /**\n * Per-step intrinsic reversibility in workflow order, cached from the initial\n * GET so the orchestrator can derive `can_go_back` for steps reached via SSE:\n * a step can go back when any earlier step is reversible, or the step itself\n * is the first reversible one (back exits the flow).\n */\n stepReversibility?: Array<{ code: OnboardingStep; reversible: boolean }>;\n /**\n * Previously-captured data for `currentStep`, present when a reverse\n * navigation re-opened a completed step (GET reflects it on\n * `current_step.result`). Applied by the SDK as editable prefill.\n */\n currentStepResult?: Record<string, unknown>;\n /** Arbitrary session metadata the backend may attach. */\n metadata?: Record<string, unknown>;\n /**\n * Customer-registered allowlist of origins authorized to embed the SDK.\n * When provided, the SDK validates the loader's parent origin against this list.\n */\n allowedParentOrigins?: string[];\n}\n\n/**\n * Why the onboarding flow ended without a terminal verdict, delivered to\n * {@link VelionCallbacks.onExit}.\n *\n * Triggers:\n * - `BACK_EXIT`: the user tapped \"back\" on the first reversible step, where\n * there is no earlier step to return to, so the SDK left the flow and\n * returned control to the host.\n * - `RETRY_CANCELLED`: the user tapped \"Cancel\" on the retry dialog shown after\n * a `ROUTING_COMMAND / RETRY_STEP`. The backend session stays `IN_PROGRESS`\n * (there is no cancellation endpoint); the host may re-open it later via\n * `GET /sessions/:id`.\n */\nexport interface OnboardingExit {\n /** The reason the flow exited. */\n reason: 'BACK_EXIT' | 'RETRY_CANCELLED';\n /** The step the user exited from (when the backend provides `step_code`). */\n step?: OnboardingStep;\n}\n\n/**\n * A single T&C line item (matches backend TermsAndConditionsItemDto / domain record).\n */\nexport interface TermsAndConditionsItem {\n id: string;\n description: string;\n /** Optional URL to the full document. */\n link?: string;\n}\n\n/**\n * Response from session creation (POST from your backend). Use sessionId and\n * sessionToken when instantiating VelionOnboarding; pass expiresAt for optional\n * client-side expiration checks.\n */\nexport interface SessionCreationResponse {\n session_id: string;\n session_token: string;\n expires_at: string;\n status?: string;\n}\n\n/**\n * Shape of the final result the SDK hands to `onSuccess` once the session\n * reaches a terminal status. Built from the terminal SSE event and the\n * `GET /sessions/:sessionId` snapshot (the backend exposes no separate\n * `/result` endpoint).\n */\nexport interface OnboardingResult {\n /** The session ID that was completed. Matches `VelionConfig.sessionId`. */\n sessionId: string;\n /** ISO 8601 timestamp of when the backend finalized the session. */\n completedAt: string;\n /**\n * The final KYC verdict.\n * - `APPROVED`: All checks passed automatically.\n * - `MANUAL_REVIEW`: Flagged for human review (common in AML flows). Backend `IN_REVIEW` maps here.\n * - `REJECTED`: Automatically rejected (fraud signals, liveness failure, etc.).\n * - `DECLINED`: Session ended in a declined state (matches GET session `status: DECLINED`).\n */\n status: 'APPROVED' | 'MANUAL_REVIEW' | 'REJECTED' | 'DECLINED';\n /** An optional tracking reference for support and audit trails. */\n referenceId?: string;\n /**\n * When the terminal outcome came from SSE, optional backend `reason` (e.g.\n * `LIVENESS_SCORE_IN_REVIEW_RANGE` on SESSION_MANUAL_REVIEW) for localized final-screen copy.\n */\n terminalReason?: string;\n}\n\n/**\n * Response from `POST /sessions/:sessionId/liveness-init`.\n * The backend returns the Iris session details the SDK needs to open the WebSocket\n * and stream frames. Credentials are not used: Iris owns the session lifecycle.\n */\nexport interface LivenessSessionResponse {\n /** Iris session identifier. */\n liveness_session_id?: string;\n /** Full WebSocket URL the SDK must connect to (e.g. wss://iris.host/ws/iris/{id}). */\n websocket_url?: string;\n /** ISO-8601 expiry of the Iris session. */\n expires_at?: string;\n /** Effective Iris challenge identifiers in order (TURN_LEFT, SMILE, …). */\n challenges?: string[];\n /** Optional JWT for the WebSocket handshake when Iris auth is enabled. */\n connection_token?: string;\n /**\n * Optional base64 HMAC-SHA256 key. When present, the SDK signs every JPEG\n * frame in a JSON envelope (canonical line: `sessionId\\nframeId\\nts\\nsha256hex(jpeg)`).\n */\n frame_integrity_key_base64?: string;\n /**\n * Signed PUT URL the SDK uses to upload the locally-recorded liveness video\n * directly to the onboarding GCS bucket. `null` (or absent) when video\n * recording is disabled or the signing failed — the SDK skips the upload\n * silently (liveness still works without the video; only the back-office\n * loses the playback evidence).\n */\n video_upload?: VideoUpload | null;\n}\n\n/**\n * Signed-upload payload returned by `POST /liveness-init`. Mirrors the backend\n * `LivenessInitResponseDto.VideoUpload`. The SDK PUTs the recorded video bytes\n * to `upload_url` (Content-Type matching the recorded container) before calling\n * `POST /liveness-verify` with the resulting metadata.\n */\nexport interface VideoUpload {\n /** Absolute https://storage.googleapis.com/... URL with V4 signature params. */\n upload_url: string;\n /** ISO-8601 instant after which GCS rejects the signature. */\n expires_at: string;\n /** HTTP method to use. Always `\"PUT\"` today; future-proofing. */\n method: 'PUT' | string;\n}\n\n/**\n * Optional body of `POST /liveness-verify`. The SDK only includes it when the\n * recorded video uploaded successfully (HTTP 200 from GCS). When omitted, the\n * backend completes liveness based on the Iris score only.\n */\nexport interface LivenessVerifyVideoMeta {\n /** MIME type that was sent in the PUT `Content-Type` header (e.g. `video/mp4`, `video/webm`). */\n content_type: string;\n /** Recorded video duration in milliseconds, or `null` if it could not be determined. */\n duration_ms: number | null;\n}\n\n/**\n * Payload delivered to the optional `onLivenessVideoUploaded` callback after a\n * successful upload of the liveness video. Best-effort: the callback only\n * fires when the GCS PUT returned 200.\n */\nexport interface LivenessVideoRef {\n /** Signed `upload_url` the SDK PUT the video to. */\n upload_url: string;\n /** MIME type used in the PUT. */\n content_type: string;\n /** Recorded video duration in milliseconds, or `null` when unknown. */\n duration_ms: number | null;\n /** Recorded file size in bytes, or `null` when unknown. */\n size_bytes: number | null;\n}\n\n/**\n * Payload for POST /sessions/:sessionId/basic-info/human (HUMAN_BASIC_INFO step).\n * Matches backend SubmitHumanBasicInfoRequestDto (snake_case in JSON).\n */\nexport interface HumanBasicInfoPayload {\n first_name: string;\n middle_name?: string;\n last_name: string;\n second_last_name?: string;\n identification_type: string;\n identification_id: string;\n}\n\n/**\n * Payload for POST /sessions/:sessionId/basic-info/legal (LEGAL_BASIC_INFO step).\n * Matches backend SubmitLegalBasicInfoRequestDto (snake_case in JSON).\n */\nexport interface LegalBasicInfoPayload {\n legal_name: string;\n legal_form: string;\n identification_type: string;\n identification_id: string;\n}\n\n/**\n * Single selectable option for tax category or fiscal identifier type (TAX_INFO step).\n * `id` is submitted to the API; `description` is the user-visible label.\n */\nexport interface TaxInfoOption {\n id: string;\n description: string;\n}\n\n/**\n * Resolved selectable lists for the TAX_INFO step UI (from `accepted_tax_information` only; no SDK defaults).\n */\nexport interface TaxInfoConfig {\n /**\n * Fiscal regime / category options (e.g. Monotributo). The chosen `id` is sent as `taxpayer_type`.\n */\n taxpayerCategories: TaxInfoOption[];\n /**\n * Tax identification scheme options (e.g. CUIT). The chosen `id` is sent as `taxpayer_id_type`.\n */\n fiscalIdentifierTypes: TaxInfoOption[];\n}\n\n/** POST /sessions/:id/tax-info — SubmitTaxInfoRequestDto. */\nexport interface TaxInfoPayload {\n /** Selected fiscal category value (`code` from step config `taxpayer_categories`). */\n taxpayer_type: string;\n /** Selected fiscal identifier kind id (e.g. `CUIT` from step config `identification_types`). */\n taxpayer_id_type: string;\n /** Tax identification number for the chosen type. */\n taxpayer_id: string;\n}\n\n/** POST /sessions/:id/extended-info/human — SubmitHumanExtendedInfoRequestDto. */\nexport interface HumanExtendedInfoPayload {\n gender?: string;\n civil_status?: string;\n birth_date?: string;\n birth_country?: string;\n citizenship?: string;\n residence_country?: string;\n}\n\n/** POST /sessions/:id/extended-info/legal — SubmitLegalExtendedInfoRequestDto. */\nexport interface LegalExtendedInfoPayload {\n incorporation_date: string;\n incorporation_country: string;\n}\n\n/** POST /sessions/:id/address-info — SubmitAddressInfoRequestDto. */\nexport interface AddressInfoPayload {\n type: string;\n country: string;\n state: string;\n city: string;\n district?: string;\n neighborhood?: string;\n street: string;\n number: string;\n floor?: string;\n apartment?: string;\n details?: string;\n zip_code: string;\n latitude?: number;\n longitude?: number;\n}\n\n/** POST /sessions/:id/sworn-declaration — SubmitSwornDeclarationRequestDto. */\nexport interface SwornDeclarationPayload {\n is_pep: boolean;\n is_ssoo: boolean;\n is_fatca: boolean;\n is_ocde: boolean;\n}\n\n/**\n * POST /sessions/:id/economic-activity — EconomicActivitySelectionDto.\n *\n * Carries the `code` of the single economic activity the user selected for the\n * `ECONOMIC_ACTIVITY_DECLARATION` step. The Worker validates it asynchronously\n * against the configured items; an invalid code surfaces over SSE as\n * `RETRY_STEP` (re-mounts the step) or `SESSION_DECLINED`, never as an HTTP error.\n */\nexport interface EconomicActivitySelectionPayload {\n /** `code` of the selected EconomicActivityItem. */\n code: string;\n}\n\n/** POST /sessions/:id/contact/email — SubmitEmailContactRequestDto. */\nexport interface EmailContactPayload {\n email: string;\n}\n\n/** POST /sessions/:id/contact/phone — SubmitPhoneContactRequestDto. */\nexport interface PhoneContactPayload {\n phone: string;\n}\n\n/** POST /sessions/:id/terms-and-conditions — SubmitTermsAndConditionsRequestDto. */\nexport interface TermsAndConditionsAcceptancePayload {\n item_id: string;\n accepted: boolean;\n}\n\nexport interface TermsAndConditionsSubmitPayload {\n acceptances: TermsAndConditionsAcceptancePayload[];\n}\n\n/**\n * SSE event payload (OnboardingSessionEvent). Matches the backend record\n * la.velion.onboarding.api...sdk.OnboardingSessionEventDto (snake_case in JSON).\n *\n * Received on GET /public/sdk/sessions/:sessionId/events. The backend sends\n * ServerSentEvent with id = event_id (epoch millis) for Last-Event-ID replay.\n *\n * Event types (event_type):\n * - STEP_UPDATED — a workflow step changed status (step_code, step_status)\n * - ROUTING_COMMAND — next/retry/terminate instruction (action: NEXT_STEP | RETRY_STEP | TERMINATE)\n * - SESSION_APPROVED — all checks passed; session_status = APPROVED\n * - SESSION_MANUAL_REVIEW — manual review required; session_status = MANUAL_REVIEW\n * - SESSION_DECLINED — session ended with decline (step_code, session_status DECLINED, reason)\n */\nexport interface OnboardingSessionEvent {\n /** Onboarding session identifier (Long in backend). */\n session_id?: number;\n /** Discriminator: STEP_UPDATED | ROUTING_COMMAND | SESSION_APPROVED | SESSION_MANUAL_REVIEW | SESSION_DECLINED */\n event_type?: string;\n /** Present for STEP_UPDATED or ROUTING_COMMAND (next step code). */\n step_code?: string;\n /** Present for STEP_UPDATED; e.g. COMPLETED. */\n step_status?: string;\n /** Present for terminal events; e.g. APPROVED | MANUAL_REVIEW | REJECTED. */\n session_status?: string;\n /**\n * For ROUTING_COMMAND: NEXT_STEP | RETRY_STEP (render step_code), TERMINATE\n * (end flow), PREVIOUS_STEP (reverse navigation — re-open step_code with\n * step_result as prefill), or BACK_EXIT (user backed out of the first\n * reversible step — leave the flow and notify the host via `onExit`).\n */\n action?: string;\n /** For SESSION_DECLINED / SESSION_MANUAL_REVIEW or ROUTING_COMMAND TERMINATE; e.g. UNREADABLE_DOCUMENT. */\n reason?: string;\n /** Step-specific config sent with ROUTING_COMMAND NEXT_STEP / PREVIOUS_STEP. */\n step_config?: Record<string, unknown>;\n /**\n * Previously-captured data for the re-opened step, sent with ROUTING_COMMAND\n * PREVIOUS_STEP. Applied by the SDK as editable prefill.\n */\n step_result?: Record<string, unknown>;\n /**\n * Whether the step the user is landing on can itself go back further. Carried\n * on ROUTING_COMMAND when the backend computes it; when absent the SDK derives\n * it from the cached workflow reversibility map.\n */\n can_go_back?: boolean;\n /** Epoch millis; used as SSE id for Last-Event-ID on reconnect. */\n event_id?: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// BACKEND COMMANDS (Listen & Obey)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Backend command: advance to a specific step. The SDK must render that step's\n * screen without evaluating business rules locally.\n */\nexport interface BackendCommandGoToStep {\n action: 'GO_TO_STEP';\n stepCode: OnboardingStep;\n}\n\n/**\n * Backend command: show an error message to the user. The SDK displays the\n * message and does not advance (backend owns the decision).\n */\nexport interface BackendCommandShowError {\n action: 'SHOW_ERROR';\n message: string;\n}\n\n/**\n * Backend command: flow is complete. SDK should fetch final result and fire onSuccess.\n */\nexport interface BackendCommandFlowComplete {\n action: 'FLOW_COMPLETE';\n}\n\n/**\n * Backend command: retry the current step (e.g. after transient failure).\n */\nexport interface BackendCommandRetry {\n action: 'RETRY';\n}\n\n/**\n * Strict command payload the backend may return in a synchronous HTTP response\n * or send via SSE. The SDK never makes routing decisions; it only obeys these commands.\n */\nexport type BackendCommand =\n | BackendCommandGoToStep\n | BackendCommandShowError\n | BackendCommandFlowComplete\n | BackendCommandRetry;\n\n/**\n * Raw response body that may contain a backend command (e.g. POST submit response).\n */\nexport interface BackendCommandResponse {\n action?: string;\n stepCode?: string;\n message?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// INTERNAL STATE MACHINE TYPES (exported for module authors)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * All possible states of the SDK's internal lifecycle.\n * Transitions are strictly unidirectional (no going back to IDLE once started).\n *\n * @internal\n */\nexport type SdkStatus =\n | 'IDLE' // Instantiated, not yet started\n | 'INITIALIZING' // Fetching session state from backend\n | 'RUNNING' // Orchestrating steps\n | 'STEP_PROCESSING'// A module is actively running\n | 'COMPLETED' // All steps done, onSuccess fired\n | 'EXITED' // User left the flow via back on the first step, onExit fired\n | 'ERROR' // Unrecoverable error, onError fired\n | 'DESTROYED'; // destroy() was called, instance is a no-op\n\n/**\n * Internal event bus event names. Typed via `SdkEventMap`.\n * @internal\n */\nexport type SdkEventName =\n | 'step:change'\n | 'step:complete'\n | 'session:loaded'\n | 'flow:complete'\n | 'flow:exit'\n | 'error'\n | 'status:change';\n\n/**\n * Maps each internal event name to its payload type.\n * Used by the `TypedEventEmitter` for compile-time safety.\n * @internal\n */\nexport interface SdkEventMap extends Record<string, unknown> {\n 'step:change': { step: OnboardingStep; index: number };\n 'step:complete': { step: OnboardingStep };\n 'session:loaded': SessionState;\n 'flow:complete': OnboardingResult;\n 'flow:exit': OnboardingExit;\n 'error': SdkError;\n 'status:change': SdkStatus;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// LOADER ↔ CDN APP POSTMESSAGE PROTOCOL (Sumsub-style)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Message type: parent (loader) sends config to iframe (CDN app). */\nexport const VELION_MSG_INIT = 'VELION_INIT' as const;\n\n/** Message types: iframe (CDN app) sends to parent (loader). */\nexport const VELION_MSG_READY = 'VELION_READY' as const;\nexport const VELION_MSG_SUCCESS = 'VELION_SUCCESS' as const;\nexport const VELION_MSG_ERROR = 'VELION_ERROR' as const;\nexport const VELION_MSG_STEP_CHANGE = 'VELION_STEP_CHANGE' as const;\nexport const VELION_MSG_STEP_COMPLETE = 'VELION_STEP_COMPLETE' as const;\nexport const VELION_MSG_TOKEN_EXPIRED = 'VELION_TOKEN_EXPIRED' as const;\n/**\n * Iframe → parent: the user left the flow via back on the first reversible step\n * (`ROUTING_COMMAND / BACK_EXIT`). The loader forwards this to the host's\n * `onExit` callback so it can dismiss the SDK surface.\n */\nexport const VELION_MSG_EXIT = 'VELION_EXIT' as const;\n/**\n * Iframe → parent: the LIVENESS_MATCH step uploaded the recorded session video\n * (GCS PUT returned 200). The loader forwards the {@link LivenessVideoRef} to\n * the host's `onLivenessVideoUploaded` callback. Best-effort: never sent when\n * recording was disabled or the upload failed.\n */\nexport const VELION_MSG_LIVENESS_VIDEO_UPLOADED = 'VELION_LIVENESS_VIDEO_UPLOADED' as const;\n\n/** Message type: parent sends new token to iframe after VELION_TOKEN_EXPIRED. */\nexport const VELION_MSG_TOKEN = 'VELION_TOKEN' as const;\n\nexport type VelionMessageType =\n | typeof VELION_MSG_INIT\n | typeof VELION_MSG_READY\n | typeof VELION_MSG_SUCCESS\n | typeof VELION_MSG_ERROR\n | typeof VELION_MSG_STEP_CHANGE\n | typeof VELION_MSG_STEP_COMPLETE\n | typeof VELION_MSG_TOKEN_EXPIRED\n | typeof VELION_MSG_EXIT\n | typeof VELION_MSG_LIVENESS_VIDEO_UPLOADED\n | typeof VELION_MSG_TOKEN;\n\n/** Envelope for all loader ↔ iframe messages. Both sides must validate origin. */\nexport interface VelionMessage<T = unknown> {\n type: VelionMessageType;\n payload: T;\n}\n\n/** Payload for VELION_INIT: full config (iframe runs with containerId overwritten to root id). */\nexport type VelionInitPayload = VelionConfig;\n\n/** Payload for VELION_STEP_CHANGE. */\nexport interface VelionStepChangePayload {\n step: OnboardingStep;\n stepIndex: number;\n}\n\n/** Payload for VELION_STEP_COMPLETE. */\nexport interface VelionStepCompletePayload {\n step: OnboardingStep;\n}\n\n/** Payload for VELION_EXIT (mirrors {@link OnboardingExit}). */\nexport type VelionExitPayload = OnboardingExit;\n\n/** Payload for VELION_LIVENESS_VIDEO_UPLOADED (mirrors {@link LivenessVideoRef}). */\nexport type VelionLivenessVideoUploadedPayload = LivenessVideoRef;\n\n/** Payload for VELION_TOKEN (token refresh response from parent to iframe). */\nexport interface VelionTokenPayload {\n sessionToken: string;\n expiresAt?: string;\n}\n","/**\n * @fileoverview Signed-manifest verification for the Velion CDN bundle.\n *\n * Threat model:\n * The loader is published on npm and embedded in customer pages. The full SDK\n * ships from a CDN at runtime. If the CDN is compromised (BGP hijack, bucket\n * takeover, supply-chain on the build pipeline), an attacker could swap the\n * iframe HTML / JS and exfiltrate KYC documents, selfies, and session tokens.\n *\n * Mitigation: every CDN release ships a `manifest.json` that names the\n * entry HTML and asset SHA-384 digests, plus a detached `manifest.sig`\n * (Ed25519 signature over the canonical bytes of `manifest.json`). The\n * loader carries the corresponding Ed25519 *public* key and verifies the\n * signature before letting the iframe load. A compromised CDN cannot\n * forge a manifest without also obtaining the private key — which lives\n * in a KMS keyring outside the CDN's blast radius.\n *\n * Rotation:\n * The public key is embedded in the loader bundle published to npm. Rotation\n * means publishing a new loader version with the new public key. Old loaders\n * still verify against the previous key while customers upgrade.\n */\n\n/**\n * Ed25519 public key used to verify `manifest.sig`, base64-encoded raw bytes.\n *\n * Empty string means \"verification disabled\" — accepted only as a transitional\n * mode while the signing pipeline is being deployed. Production loaders MUST\n * ship with this populated before being published to npm. Replace via the\n * release workflow that fetches the pubkey from KMS metadata.\n */\nexport const VELION_MANIFEST_PUBKEY_B64 = 'lpqLG4Ta0YyBn/+lQh95tFl4CuTIHWapeeq7+xnPH+U=';\n\n/** Schema of the signed `manifest.json` emitted by the CDN build pipeline. */\nexport interface VelionCdnManifest {\n /** Loader-major path token, e.g. `\"v1\"`. Must match the loader's `LOADER_MAJOR_VERSION`. */\n version: string;\n /** ISO-8601 timestamp of the build. */\n buildTime: string;\n /** Path to the iframe entry HTML, relative to `cdnUrl`. */\n entry: string;\n /** Optional human-readable app version (matches the app's `package.json`). */\n appVersion?: string;\n /** Optional asset list with SHA-384 integrity digests for defense-in-depth. */\n assets?: Array<{ url: string; integrity: string }>;\n}\n\n/** Distinguishes \"manifest absent\" (legacy CDN) from \"manifest invalid\" (compromised). */\nexport type ManifestLoadResult =\n | { ok: true; manifest: VelionCdnManifest; signed: boolean }\n | { ok: false; reason: 'unavailable' | 'invalid_signature' | 'malformed'; detail?: string };\n\nfunction base64ToArrayBuffer(b64: string): ArrayBuffer {\n const bin = atob(b64);\n const buf = new ArrayBuffer(bin.length);\n const view = new Uint8Array(buf);\n for (let i = 0; i < bin.length; i++) {\n view[i] = bin.charCodeAt(i);\n }\n return buf;\n}\n\nasync function importPublicKey(rawB64: string): Promise<CryptoKey> {\n const raw = base64ToArrayBuffer(rawB64);\n // Web Crypto Ed25519 is implemented in Chrome 113+, Firefox 130+, Safari 17+.\n // Older browsers throw NotSupportedError — we surface that as `invalid_signature`\n // so the loader refuses to start rather than silently bypassing verification.\n return crypto.subtle.importKey(\n 'raw',\n raw,\n { name: 'Ed25519' } as AlgorithmIdentifier,\n false,\n ['verify'],\n );\n}\n\nasync function verifySignature(\n publicKey: CryptoKey,\n signature: ArrayBuffer,\n data: ArrayBuffer,\n): Promise<boolean> {\n return crypto.subtle.verify(\n { name: 'Ed25519' } as AlgorithmIdentifier,\n publicKey,\n signature,\n data,\n );\n}\n\n/**\n * Fetches `${cdnUrl}/manifest.json` (and `manifest.sig` when verification is enabled).\n *\n * Returns:\n * - `{ ok:true, signed:true }` when the signature verifies against the embedded pubkey.\n * - `{ ok:true, signed:false }` when the manifest exists but verification is disabled\n * (transitional dev/staging mode where `VELION_MANIFEST_PUBKEY_B64` is empty).\n * - `{ ok:false, reason:'unavailable' }` when the CDN has no manifest yet — the\n * loader treats this as legacy mode and may proceed if explicitly allowed.\n * - `{ ok:false, reason:'invalid_signature' | 'malformed' }` when the manifest\n * is present but tampered or syntactically invalid. The loader MUST refuse\n * to mount the iframe in this case.\n */\nexport async function loadAndVerifyManifest(\n cdnUrl: string,\n pubKeyB64: string = VELION_MANIFEST_PUBKEY_B64,\n): Promise<ManifestLoadResult> {\n let manifestBuffer: ArrayBuffer;\n let manifestText: string;\n try {\n const resp = await fetch(`${cdnUrl}/manifest.json`, {\n credentials: 'omit',\n cache: 'no-cache',\n redirect: 'error',\n });\n if (resp.status === 404) {\n return { ok: false, reason: 'unavailable' };\n }\n if (!resp.ok) {\n return { ok: false, reason: 'unavailable', detail: `HTTP ${resp.status}` };\n }\n manifestBuffer = await resp.arrayBuffer();\n manifestText = new TextDecoder().decode(manifestBuffer);\n } catch (err) {\n return { ok: false, reason: 'unavailable', detail: (err as Error).message };\n }\n\n let manifest: VelionCdnManifest;\n try {\n manifest = JSON.parse(manifestText) as VelionCdnManifest;\n if (\n typeof manifest.version !== 'string' ||\n typeof manifest.entry !== 'string' ||\n typeof manifest.buildTime !== 'string'\n ) {\n return { ok: false, reason: 'malformed' };\n }\n } catch (err) {\n return { ok: false, reason: 'malformed', detail: (err as Error).message };\n }\n\n if (!pubKeyB64) {\n // Pubkey not yet provisioned on this loader release. Accept the manifest\n // unsigned but flag it so the caller can decide whether to proceed.\n return { ok: true, manifest, signed: false };\n }\n\n let sigBuffer: ArrayBuffer;\n try {\n const sigResp = await fetch(`${cdnUrl}/manifest.sig`, {\n credentials: 'omit',\n cache: 'no-cache',\n redirect: 'error',\n });\n if (!sigResp.ok) {\n return { ok: false, reason: 'invalid_signature', detail: `sig HTTP ${sigResp.status}` };\n }\n const sigText = (await sigResp.text()).trim();\n sigBuffer = base64ToArrayBuffer(sigText);\n } catch (err) {\n return { ok: false, reason: 'invalid_signature', detail: (err as Error).message };\n }\n\n let pubKey: CryptoKey;\n try {\n pubKey = await importPublicKey(pubKeyB64);\n } catch (err) {\n return { ok: false, reason: 'invalid_signature', detail: `pubkey import failed: ${(err as Error).message}` };\n }\n\n let valid: boolean;\n try {\n valid = await verifySignature(pubKey, sigBuffer, manifestBuffer);\n } catch (err) {\n return { ok: false, reason: 'invalid_signature', detail: (err as Error).message };\n }\n if (!valid) {\n return { ok: false, reason: 'invalid_signature' };\n }\n\n return { ok: true, manifest, signed: true };\n}\n","/**\n * Thin client (loader) for Velion Onboarding.\n * Creates an iframe pointing to the CDN app and bridges config/events via postMessage.\n * Follows the same pattern as Sumsub WebSDK: init → iframe loads → postMessage config → events.\n */\n\nimport type {\n VelionConfig,\n OnboardingResult,\n SdkError,\n SdkStatus,\n OnboardingStep,\n} from './types';\nimport {\n SdkErrorCode,\n VELION_MSG_INIT,\n VELION_MSG_READY,\n VELION_MSG_SUCCESS,\n VELION_MSG_ERROR,\n VELION_MSG_STEP_CHANGE,\n VELION_MSG_STEP_COMPLETE,\n VELION_MSG_TOKEN_EXPIRED,\n VELION_MSG_EXIT,\n VELION_MSG_LIVENESS_VIDEO_UPLOADED,\n VELION_MSG_TOKEN,\n type VelionMessage,\n type OnboardingExit,\n type LivenessVideoRef,\n} from './types';\nimport {\n loadAndVerifyManifest,\n VELION_MANIFEST_PUBKEY_B64,\n type VelionCdnManifest,\n} from './manifest';\n\n/**\n * Product slug under the shared `static.velion.la/sdk/` namespace. This identity\n * SDK lives at `sdk/onboarding/`; future Velion SDKs (e.g. payments, auth) get\n * their own slug so they can be deployed independently from the same CDN.\n */\nexport const LOADER_PRODUCT = 'onboarding' as const;\n\n/**\n * Major version of this loader. Locked into the CDN path so a breaking change\n * to the loader↔app postMessage contract requires publishing loader-`v2`\n * against `/sdk/onboarding/v2/`. Customers can keep older loaders for as long\n * as the matching CDN path is served.\n */\nexport const LOADER_MAJOR_VERSION = 'v1' as const;\n\n/** Default CDN base URL for the onboarding app (no trailing slash). */\nexport const DEFAULT_CDN_URL =\n `https://static.velion.la/sdk/${LOADER_PRODUCT}/${LOADER_MAJOR_VERSION}`;\n\n/**\n * Used only while the host container has no laid-out height yet (e.g. `display: none`\n * or first paint). Replaced by a pixel height as soon as `container.clientHeight > 0`.\n */\nconst IFRAME_FALLBACK_HEIGHT_CSS = 'min(85vh, 880px)';\n\n/** Loader config: VelionConfig plus optional CDN URL and token refresh callback. */\nexport interface VelionLoaderConfig extends VelionConfig {\n /**\n * Base URL of the CDN where the onboarding app is served (e.g. https://static.velion.la/sdk).\n * The loader fetches `${cdnUrl}/manifest.json` (signed with Ed25519), verifies the\n * signature against the loader's embedded public key, and only then loads the\n * iframe entry named in the manifest.\n * @default 'https://static.velion.la/sdk'\n */\n cdnUrl?: string;\n\n /**\n * When `true`, lets the loader proceed against a CDN that does not yet expose a\n * signed `manifest.json` — for staging or migration windows. Has NO effect when\n * the loader carries an embedded pubkey AND the manifest exists but signature\n * verification fails (that case is always fatal).\n *\n * Default `false`: production loaders refuse to mount the iframe without a\n * verified manifest once the pubkey has been provisioned.\n *\n * @default false\n */\n allowUnverifiedCdn?: boolean;\n\n /**\n * Called when the iframe requests a new session token (e.g. after SESSION_EXPIRED).\n * Return the new token and optional expiresAt; the CDN app will continue the flow.\n */\n onTokenExpired?: () => Promise<{ sessionToken: string; expiresAt?: string }>;\n\n /**\n * How long to wait, after the iframe document loaded, for the runtime to\n * report `VELION_READY`. Past it the loader emits `RUNTIME_LOAD_FAILED`\n * instead of leaving the host with a blank iframe and no callback (e.g.\n * when the CDN entry points at assets that 404).\n *\n * @default 30000\n */\n runtimeReadyTimeoutMs?: number;\n}\n\nconst DEFAULT_RUNTIME_READY_TIMEOUT_MS = 30_000;\n\nfunction parseOriginFromUrl(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return '';\n }\n}\n\n/**\n * Allows `https:` and `wss:` everywhere, plus plain `http:`/`ws:` only when the host\n * is a loopback / `.local` address (developer machines and CI). Used to prevent\n * accidental cleartext bearer-token transmission to a misconfigured `apiUrl`/`cdnUrl`.\n */\nfunction isLocalhostHost(host: string): boolean {\n return host === 'localhost' || host === '127.0.0.1' || host === '::1' || host.endsWith('.local');\n}\n\nexport function isSecureHttpUrl(url: string): boolean {\n try {\n const u = new URL(url);\n if (u.protocol === 'https:') return true;\n if (u.protocol === 'http:') return isLocalhostHost(u.hostname);\n return false;\n } catch {\n return false;\n }\n}\n\n/** Keys of VelionConfig that are safe to send via postMessage (no functions). */\nconst SERIALIZABLE_CONFIG_KEYS: (keyof VelionConfig)[] = [\n 'containerId',\n 'sessionId',\n 'sessionToken',\n 'apiUrl',\n 'expiresAt',\n 'theme',\n 'colorMode',\n 'locale',\n 'allowEmbedded',\n 'livenessShowIntro',\n 'livenessShowBackButton',\n 'fingerprintEndpoint',\n 'debug',\n 'googleMapsApiKey',\n 'showFinalResultScreen',\n 'showProgressBar',\n 'finalResultScreenTexts',\n];\n\nfunction buildSerializableConfig(\n config: Readonly<VelionLoaderConfig>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {\n containerId: 'velion-sdk-root',\n allowEmbedded: true,\n };\n for (const key of SERIALIZABLE_CONFIG_KEYS) {\n if (key === 'containerId' || key === 'allowEmbedded') continue;\n const value = config[key as keyof typeof config];\n if (value !== undefined && typeof value !== 'function') {\n out[key] = value;\n }\n }\n return out;\n}\n\nexport class VelionOnboarding {\n private readonly config: Readonly<VelionLoaderConfig>;\n private status: SdkStatus = 'IDLE';\n private iframe: HTMLIFrameElement | null = null;\n private container: HTMLElement | null = null;\n private messageHandler: ((e: MessageEvent) => void) | null = null;\n private containerResizeObserver: ResizeObserver | null = null;\n private iframeHeightWindowResizeHandler: (() => void) | null = null;\n private cdnOrigin = '';\n /** Armed when VELION_INIT is sent; cleared by VELION_READY or destroy(). */\n private readyTimer: ReturnType<typeof setTimeout> | null = null;\n\n private clearReadyTimer(): void {\n if (this.readyTimer !== null) {\n clearTimeout(this.readyTimer);\n this.readyTimer = null;\n }\n }\n\n private armReadyTimer(cdnUrl: string): void {\n this.clearReadyTimer();\n const ms = this.config.runtimeReadyTimeoutMs ?? DEFAULT_RUNTIME_READY_TIMEOUT_MS;\n if (!(ms > 0)) return;\n this.readyTimer = setTimeout(() => {\n this.readyTimer = null;\n if (this.status !== 'RUNNING') return; // already READY-progressed, errored or destroyed\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.RUNTIME_LOAD_FAILED,\n message:\n `The onboarding runtime at ${cdnUrl} did not become ready within ${ms} ms. ` +\n 'Check that the CDN serves the assets referenced by its index.html and that the host ' +\n \"page's Content-Security-Policy allows the runtime scripts.\",\n });\n }, ms);\n }\n\n constructor(config: VelionLoaderConfig) {\n this.config = Object.freeze({ ...config });\n }\n\n async start(): Promise<void> {\n if (this.status === 'DESTROYED') {\n this.config.onError?.({\n code: SdkErrorCode.INSTANCE_DESTROYED,\n message:\n 'This VelionOnboarding instance has been destroyed. Create a new instance to retry.',\n });\n return;\n }\n\n if (this.status !== 'IDLE') {\n this.config.onError?.({\n code: SdkErrorCode.ALREADY_STARTED,\n message:\n 'start() was called on an already-running SDK instance. Call destroy() before re-starting.',\n });\n return;\n }\n\n const container = document.getElementById(this.config.containerId);\n if (!container) {\n this.config.onError?.({\n code: SdkErrorCode.CONTAINER_NOT_FOUND,\n message: `No DOM element found with id=\"${this.config.containerId}\". Ensure the element exists before calling start().`,\n });\n return;\n }\n\n const cdnUrl = (this.config.cdnUrl ?? DEFAULT_CDN_URL).replace(/\\/$/, '');\n if (!isSecureHttpUrl(cdnUrl)) {\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Insecure cdnUrl: \"${cdnUrl}\". Must be https:// (http:// is allowed only for localhost).`,\n });\n return;\n }\n if (this.config.apiUrl && !isSecureHttpUrl(this.config.apiUrl)) {\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Insecure apiUrl: \"${this.config.apiUrl}\". Must be https:// (http:// is allowed only for localhost).`,\n });\n return;\n }\n this.cdnOrigin = parseOriginFromUrl(cdnUrl);\n if (!this.cdnOrigin) {\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Invalid cdnUrl: ${cdnUrl}. Must be a valid absolute URL.`,\n });\n return;\n }\n\n this.container = container;\n this.status = 'INITIALIZING';\n\n const iframe = document.createElement('iframe');\n iframe.setAttribute('title', 'Velion Onboarding');\n // The SDK only needs the camera. Microphone and geolocation are intentionally not granted.\n iframe.setAttribute('allow', 'camera');\n // Sandbox limits blast radius of a CDN compromise. Tokens we keep:\n // allow-scripts — the iframe is a JS app\n // allow-same-origin — needed for fetch to same-origin assets and for postMessage to behave\n // allow-forms — file pickers and form submissions for OCR upload flows\n // allow-popups — Google Maps / Places attribution links open in new tab\n // allow-modals — getUserMedia permission prompts and document picker\n iframe.setAttribute(\n 'sandbox',\n 'allow-scripts allow-same-origin allow-forms allow-popups allow-modals',\n );\n // Avoid leaking host page paths to the CDN logs.\n iframe.setAttribute('referrerpolicy', 'no-referrer');\n iframe.style.width = '100%';\n iframe.style.border = 'none';\n iframe.style.display = 'block';\n /* Height is applied in attachIframeHeightSync (pixel match + internal fallback). */\n\n const messageHandler = (e: MessageEvent): void => {\n // Origin AND source must match: a sibling iframe from the same CDN origin\n // (e.g. opened by the host page) must not be able to spoof events.\n if (e.origin !== this.cdnOrigin) return;\n if (e.source !== this.iframe?.contentWindow) return;\n const msg = e.data as VelionMessage | undefined;\n if (!msg || typeof msg.type !== 'string') return;\n\n switch (msg.type) {\n case VELION_MSG_READY:\n this.clearReadyTimer();\n this.status = 'RUNNING';\n break;\n case VELION_MSG_SUCCESS:\n this.status = 'COMPLETED';\n this.config.onSuccess?.(msg.payload as OnboardingResult);\n break;\n case VELION_MSG_ERROR:\n this.status = 'ERROR';\n this.config.onError?.(msg.payload as SdkError);\n break;\n case VELION_MSG_STEP_CHANGE: {\n const p = msg.payload as { step: OnboardingStep; stepIndex: number };\n this.config.onStepChange?.(p.step, p.stepIndex);\n break;\n }\n case VELION_MSG_STEP_COMPLETE: {\n const p = msg.payload as { step: OnboardingStep };\n this.config.onStepComplete?.(p.step);\n break;\n }\n case VELION_MSG_EXIT: {\n // User backed out of the first reversible step: the flow ended without\n // a verdict. Forward to the host so it can dismiss the SDK surface.\n this.status = 'EXITED';\n this.config.onExit?.(msg.payload as OnboardingExit);\n break;\n }\n case VELION_MSG_LIVENESS_VIDEO_UPLOADED: {\n // Evidence signal from the LIVENESS_MATCH step (OSDKW-11). Informational\n // only: the flow never depends on it, so no status change.\n this.config.onLivenessVideoUploaded?.(msg.payload as LivenessVideoRef);\n break;\n }\n case VELION_MSG_TOKEN_EXPIRED: {\n const refresh = this.config.onTokenExpired;\n if (refresh) {\n refresh().then(\n (t) => this.sendToIframe({ type: VELION_MSG_TOKEN, payload: t }),\n () => {\n this.config.onError?.({\n code: SdkErrorCode.SESSION_EXPIRED,\n message: 'Token refresh failed.',\n });\n }\n );\n } else {\n this.config.onError?.({\n code: SdkErrorCode.SESSION_EXPIRED,\n message: 'Session token expired. Provide onTokenExpired in config to refresh.',\n });\n }\n break;\n }\n default:\n break;\n }\n };\n\n window.addEventListener('message', messageHandler);\n this.messageHandler = messageHandler;\n this.iframe = iframe;\n\n container.replaceChildren(iframe);\n this.attachIframeHeightSync(container, iframe);\n\n // ── Verify the CDN manifest before mounting the iframe ─────────────────\n const manifest = await this.resolveManifest(cdnUrl);\n if (!manifest) {\n // resolveManifest already invoked onError. Stop here so we don't load\n // an unverified bundle.\n return;\n }\n const entryPath = manifest.entry.startsWith('/') ? manifest.entry : `/${manifest.entry}`;\n\n return new Promise<void>((resolve) => {\n iframe.onload = (): void => {\n if (!this.iframe?.contentWindow) {\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: 'Iframe failed to load.',\n });\n resolve();\n return;\n }\n // postMessage cannot clone functions. Sumsub-style: send only JSON-serializable config.\n // Clone via JSON so no function can slip through (e.g. from cached bundle or nested props).\n const initPayload = buildSerializableConfig(this.config);\n let payloadForPostMessage: unknown;\n try {\n payloadForPostMessage = JSON.parse(JSON.stringify(initPayload));\n } catch {\n payloadForPostMessage = initPayload;\n }\n this.sendToIframe({ type: VELION_MSG_INIT, payload: payloadForPostMessage });\n this.status = 'RUNNING';\n // The document loaded, but a 200 index.html whose scripts 404 (or a CSP\n // that blocks them) never answers VELION_INIT. Bound the wait so the\n // host gets an error instead of a silent blank iframe.\n this.armReadyTimer(cdnUrl);\n resolve();\n };\n\n iframe.onerror = (): void => {\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Failed to load onboarding app from ${cdnUrl}. Check cdnUrl and network.`,\n });\n resolve();\n };\n\n iframe.src = `${cdnUrl}${entryPath}`;\n });\n }\n\n /**\n * Fetches and verifies the CDN manifest. Returns `null` and emits onError when\n * the manifest is missing in production mode, or when the signature is invalid.\n */\n private async resolveManifest(cdnUrl: string): Promise<VelionCdnManifest | null> {\n let result;\n try {\n result = await loadAndVerifyManifest(cdnUrl);\n } catch (err) {\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message: `Manifest verification crashed unexpectedly: ${(err as Error).message}`,\n });\n return null;\n }\n if (result.ok) {\n if (!result.signed && VELION_MANIFEST_PUBKEY_B64) {\n // Should not happen — pubkey is set, manifest exists, yet not signed.\n // loadAndVerifyManifest only marks `signed:false` when pubkey is empty.\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message: 'Internal: manifest reported as unsigned despite pubkey being set.',\n });\n return null;\n }\n return result.manifest;\n }\n if (result.reason === 'unavailable') {\n // Manifest endpoint is missing. Permitted only when the loader is configured\n // to tolerate it OR when the loader was published without a pubkey (transitional).\n const tolerateMissing =\n this.config.allowUnverifiedCdn === true || !VELION_MANIFEST_PUBKEY_B64;\n if (tolerateMissing) {\n return {\n version: 'legacy',\n buildTime: new Date().toISOString(),\n entry: '/index.html',\n };\n }\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message:\n `CDN at \"${cdnUrl}\" did not expose /manifest.json. The loader is configured ` +\n 'to require a signed manifest. If the CDN is migrating, set `allowUnverifiedCdn: true` ' +\n 'on the loader config — but only as a temporary measure.',\n });\n return null;\n }\n // 'invalid_signature' or 'malformed' — always fatal, even when allowUnverifiedCdn is true.\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message:\n `CDN manifest at \"${cdnUrl}/manifest.json\" failed integrity check (${result.reason}). ` +\n 'Refusing to load the iframe to protect the user. ' +\n ('detail' in result && result.detail ? `Detail: ${result.detail}` : ''),\n });\n return null;\n }\n\n /**\n * Keep the iframe exactly as tall as the host container so integrators do not need\n * `height: 100%` on the iframe or flex hacks—only a definite block-size on the\n * container (e.g. `height: 700px` or `height: 100vh`), similar to Sumsub WebSDK.\n *\n * Until the container has a non-zero `clientHeight`, apply an internal fallback\n * height so the SDK is usable even before layout or if only `min-height` is set.\n */\n private attachIframeHeightSync(\n container: HTMLElement,\n iframe: HTMLIFrameElement,\n ): void {\n const sync = (): void => {\n if (this.iframe !== iframe || this.container !== container) return;\n const h = container.clientHeight;\n if (h > 0) {\n iframe.style.height = `${h}px`;\n iframe.style.minHeight = '';\n } else {\n iframe.style.height = IFRAME_FALLBACK_HEIGHT_CSS;\n iframe.style.minHeight = '';\n }\n };\n sync();\n requestAnimationFrame(sync);\n requestAnimationFrame(() => {\n requestAnimationFrame(sync);\n });\n if (typeof ResizeObserver !== 'undefined') {\n this.containerResizeObserver = new ResizeObserver(sync);\n this.containerResizeObserver.observe(container);\n } else {\n this.iframeHeightWindowResizeHandler = sync;\n window.addEventListener('resize', sync);\n }\n }\n\n private detachIframeHeightSync(): void {\n if (this.containerResizeObserver) {\n this.containerResizeObserver.disconnect();\n this.containerResizeObserver = null;\n }\n if (this.iframeHeightWindowResizeHandler) {\n window.removeEventListener('resize', this.iframeHeightWindowResizeHandler);\n this.iframeHeightWindowResizeHandler = null;\n }\n }\n\n private sendToIframe(msg: VelionMessage): void {\n if (this.iframe?.contentWindow) {\n this.iframe.contentWindow.postMessage(msg, this.cdnOrigin);\n }\n }\n\n getStatus(): SdkStatus {\n return this.status;\n }\n\n static getVersion(): string {\n return typeof __SDK_VERSION__ !== 'undefined' ? __SDK_VERSION__ : '0.0.0';\n }\n\n destroy(): void {\n if (this.status === 'DESTROYED') return;\n this.clearReadyTimer();\n this.detachIframeHeightSync();\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.iframe && this.container) {\n this.iframe.remove();\n this.container.replaceChildren();\n }\n this.iframe = null;\n this.container = null;\n this.status = 'DESTROYED';\n }\n}\n","/**\n * @fileoverview Public API of the Velion Onboarding SDK (thin client / loader).\n *\n * This package is the loader only: it creates an iframe that loads the full\n * onboarding app from the CDN (e.g. https://static.velion.la/sdk) and bridges\n * config and events via postMessage. No React, OpenCV, or Fingerprint\n * are bundled here — minimal size, same API as before.\n */\n\nexport { VelionOnboarding, DEFAULT_CDN_URL, type VelionLoaderConfig } from './velion-loader';\n\n/**\n * No-op when using the loader: OpenCV is loaded inside the CDN app.\n * Kept for API compatibility; configure OpenCV in the CDN app build if needed.\n */\nexport function setOpenCVBaseUrl(_url: string): void {}\n\nexport {\n SdkErrorCode,\n CameraErrorCode,\n VELION_MSG_INIT,\n VELION_MSG_READY,\n VELION_MSG_SUCCESS,\n VELION_MSG_ERROR,\n VELION_MSG_STEP_CHANGE,\n VELION_MSG_STEP_COMPLETE,\n VELION_MSG_TOKEN_EXPIRED,\n VELION_MSG_EXIT,\n VELION_MSG_LIVENESS_VIDEO_UPLOADED,\n VELION_MSG_TOKEN,\n type VelionConfig,\n type VelionTheme,\n type VelionFinalResultStatusCopy,\n type VelionFinalResultScreenTexts,\n type VelionSessionDeclinedReasonMessages,\n type VelionSessionDeclinedReasonTexts,\n type VelionRetryStepTexts,\n type VelionCallbacks,\n type OnboardingExit,\n type OnboardingStep,\n type OnboardingStepStatus,\n type OnboardingType,\n type DocumentSide,\n type DocumentCaptureMode,\n type DocumentsConfig,\n type TaxInfoConfig,\n type TaxInfoOption,\n type DocumentsInitRequest,\n type DocumentsInitResponse,\n type SessionState,\n type OnboardingResult,\n type OnboardingStepDto,\n type StepFieldRule,\n type HumanBasicInfoStepConfig,\n type HumanExtendedInfoStepConfig,\n type TaxInfoStepConfig,\n type AddressInfoStepConfig,\n type SwornDeclarationStepConfig,\n type DocumentsOcrStepConfig,\n type TermsAndConditionsStepConfig,\n type StepConfigMap,\n type SdkError,\n type SdkStatus,\n type LivenessSessionResponse,\n type VideoUpload,\n type LivenessVerifyVideoMeta,\n type LivenessVideoRef,\n type SessionCreationResponse,\n type HumanBasicInfoPayload,\n type LegalBasicInfoPayload,\n type TermsAndConditionsItem,\n type TaxInfoPayload,\n type HumanExtendedInfoPayload,\n type LegalExtendedInfoPayload,\n type AddressInfoPayload,\n type SwornDeclarationPayload,\n type EconomicActivityItem,\n type EconomicActivityStepConfig,\n type EconomicActivitySelectionPayload,\n type EmailContactPayload,\n type PhoneContactPayload,\n type TermsAndConditionsSubmitPayload,\n type TermsAndConditionsAcceptancePayload,\n type OnboardingSessionEvent,\n type OnboardingDtoResponse,\n type VelionMessage,\n type VelionMessageType,\n type VelionInitPayload,\n type VelionStepChangePayload,\n type VelionStepCompletePayload,\n type VelionExitPayload,\n type VelionLivenessVideoUploadedPayload,\n type VelionTokenPayload,\n} from './types';\n"],"names":["CameraErrorCode","SdkErrorCode","VELION_MSG_INIT","VELION_MSG_READY","VELION_MSG_SUCCESS","VELION_MSG_ERROR","VELION_MSG_STEP_CHANGE","VELION_MSG_STEP_COMPLETE","VELION_MSG_TOKEN_EXPIRED","VELION_MSG_EXIT","VELION_MSG_LIVENESS_VIDEO_UPLOADED","VELION_MSG_TOKEN","VELION_MANIFEST_PUBKEY_B64","base64ToArrayBuffer","b64","bin","atob","buf","ArrayBuffer","length","view","Uint8Array","i","charCodeAt","async","loadAndVerifyManifest","cdnUrl","pubKeyB64","manifestBuffer","manifestText","manifest","sigBuffer","pubKey","valid","resp","fetch","credentials","cache","redirect","status","ok","reason","detail","arrayBuffer","TextDecoder","decode","err","message","JSON","parse","version","entry","buildTime","signed","sigResp","text","trim","rawB64","raw","crypto","subtle","importKey","name","importPublicKey","publicKey","signature","data","verify","verifySignature","DEFAULT_CDN_URL","isSecureHttpUrl","url","u","URL","protocol","host","hostname","endsWith","SERIALIZABLE_CONFIG_KEYS","constructor","config","this","iframe","container","messageHandler","containerResizeObserver","iframeHeightWindowResizeHandler","cdnOrigin","readyTimer","Object","freeze","clearReadyTimer","clearTimeout","armReadyTimer","ms","runtimeReadyTimeoutMs","setTimeout","onError","code","RUNTIME_LOAD_FAILED","start","INSTANCE_DESTROYED","ALREADY_STARTED","document","getElementById","containerId","CONTAINER_NOT_FOUND","replace","UNKNOWN","apiUrl","origin","parseOriginFromUrl","createElement","setAttribute","style","width","border","display","e","source","contentWindow","msg","type","onSuccess","payload","p","onStepChange","step","stepIndex","onStepComplete","onExit","onLivenessVideoUploaded","refresh","onTokenExpired","then","t","sendToIframe","SESSION_EXPIRED","window","addEventListener","replaceChildren","attachIframeHeightSync","resolveManifest","entryPath","startsWith","Promise","resolve","onload","initPayload","out","allowEmbedded","key","value","buildSerializableConfig","payloadForPostMessage","stringify","onerror","src","result","INTEGRITY_FAILED","allowUnverifiedCdn","Date","toISOString","sync","h","clientHeight","height","minHeight","requestAnimationFrame","ResizeObserver","observe","detachIframeHeightSync","disconnect","removeEventListener","postMessage","getStatus","getVersion","destroy","remove","_url"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"velion-sdk.umd.js","sources":["../../packages/types/src/index.ts","../src/manifest.ts","../src/velion-loader.ts","../src/index.ts"],"sourcesContent":["/**\n * @fileoverview Public TypeScript contracts for the Velion Onboarding SDK.\n *\n * Design principle: All types exported from this file constitute the public API.\n * Internal implementation types should live alongside their respective modules.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// STEP & FLOW TYPES\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The complete set of onboarding steps the backend can require.\n * The SDK dynamically receives the active subset for each session.\n *\n * @remarks\n * - `DEVICE_FINGERPRINT`: Silently collects device entropy via FingerprintJS.\n * - `DOCUMENTS_OCR`: Renders a UI for ID document image capture and upload.\n * - `LIVENESS_MATCH`: Drives the Velion Iris active-liveness flow over WebSocket (HUMAN only).\n * - `EMAIL_OTP` / `PHONE_OTP`: Contact verification via one-time passwords.\n * - `HUMAN_BASIC_INFO`: Natural-person identification data (HUMAN only).\n * - `LEGAL_BASIC_INFO`: Legal-entity identification data (LEGAL only).\n * - `EMAIL_CONTACT` / `PHONE_CONTACT`: contact collection without OTP.\n * - `TAX_INFO`, `HUMAN_EXTENDED_INFO`, `LEGAL_EXTENDED_INFO`, `ADDRESS_INFO`, `SWORN_DECLARATION`: form steps.\n * - `ECONOMIC_ACTIVITY_DECLARATION`: pick exactly one economic activity from a searchable list.\n * - `TERMS_AND_CONDITIONS`: accept configured T&C items.\n * - `FACE_MATCH`: server-side face comparison after liveness (no user input; SDK waits).\n * - `AML_SCREENING`: server-side PEP / sanctions screening (no user input; never routed to\n * the SDK as `NEXT_STEP`, only visible in `steps[]` and `STEP_UPDATED`).\n */\nexport type OnboardingStep =\n | 'DEVICE_FINGERPRINT'\n | 'HUMAN_BASIC_INFO'\n | 'LEGAL_BASIC_INFO'\n | 'EMAIL_OTP'\n | 'EMAIL_CONTACT'\n | 'PHONE_OTP'\n | 'PHONE_CONTACT'\n | 'TAX_INFO'\n | 'HUMAN_EXTENDED_INFO'\n | 'LEGAL_EXTENDED_INFO'\n | 'ADDRESS_INFO'\n | 'SWORN_DECLARATION'\n | 'ECONOMIC_ACTIVITY_DECLARATION'\n | 'DOCUMENTS_OCR'\n | 'LIVENESS_MATCH'\n | 'FACE_MATCH'\n | 'AML_SCREENING'\n | 'TERMS_AND_CONDITIONS';\n\n/**\n * Status of an individual onboarding step (matches backend OnboardingStepStatus).\n */\nexport type OnboardingStepStatus =\n | 'PENDING'\n | 'IN_PROGRESS'\n | 'COMPLETED'\n | 'SKIPPED'\n | 'FAILED';\n\n/**\n * Type of onboarding session (matches backend OnboardingType).\n * Constrains which steps are valid: HUMAN allows biometric steps, LEGAL uses entity data.\n */\nexport type OnboardingType = 'HUMAN' | 'LEGAL';\n\n/** Which face of the ID document is being captured. */\nexport type DocumentSide = 'FRONT' | 'BACK';\n\n/**\n * How the user can provide document images in the DOCUMENTS_OCR step.\n * - `'camera'`: Only \"Take photo\" (opens camera on supported devices).\n * - `'file'`: Only \"Choose file\" (file picker).\n * - `'both'`: Both options; implementer can style via theme.\n *\n * @default 'both'\n */\nexport type DocumentCaptureMode = 'camera' | 'file' | 'both';\n\n/**\n * Orientation of the document capture cutout / crop frame.\n * - `'landscape'`: Horizontal card (default — standard ID-1 cards).\n * - `'portrait'`: Vertical card (e.g. some passports, residence permits).\n *\n * @default 'landscape'\n */\nexport type DocumentOrientation = 'landscape' | 'portrait';\n\n/**\n * Optional overrides for document OCR UI (not part of `VelionConfig`; used internally).\n */\nexport interface DocumentsConfig {\n /**\n * When true (default), the document camera preview is mirrored (like selfie mode).\n * Set to false if the back camera shows the document inverted.\n *\n * @default true\n */\n mirrorDocumentVideo?: boolean;\n\n /**\n * Orientation of the document capture frame and crop.\n * - `'landscape'`: Horizontal card (default — standard ID-1 cards).\n * - `'portrait'`: Vertical card.\n *\n * @default 'landscape'\n */\n documentOrientation?: DocumentOrientation;\n}\n\n/** Generic visible/required field toggle used by workflow step configs. */\nexport interface StepFieldRule {\n visible: boolean;\n required: boolean;\n}\n\n/** Select option used by configurable step fields (e.g. HUMAN_EXTENDED_INFO gender/civil_status). */\nexport interface StepFieldOption {\n id: string;\n display_value?: string;\n}\n\n/** Field rule with optional selectable options. */\nexport interface StepFieldRuleWithOptions extends StepFieldRule {\n options?: StepFieldOption[];\n}\n\n/** STEP config for HUMAN_BASIC_INFO. */\nexport interface HumanBasicInfoStepConfig {\n accepted_country_documents?: Array<{\n country_code: string;\n document_types: Array<{ id: string; display_value?: string }>;\n }>;\n}\n\n/**\n * One selectable option row from TAX_INFO `accepted_tax_information` (backend GET session).\n * Prefer `code` as the value sent in POST tax-info; `id` is supported for older payloads.\n */\nexport interface TaxInfoConfigOptionRow {\n code?: string;\n id?: string;\n display_value?: string;\n tax_code?: string;\n}\n\n/** One country block in TAX_INFO step config (GET session `current_step.config`). */\nexport interface TaxInfoAcceptedCountryRow {\n country_code: string;\n identification_types?: TaxInfoConfigOptionRow[];\n /** Options for the \"Categoría fiscal\" / taxpayer category select. */\n taxpayer_categories?: TaxInfoConfigOptionRow[];\n}\n\n/** STEP config for TAX_INFO. */\nexport interface TaxInfoStepConfig {\n /** Identification types + taxpayer categories per country. */\n accepted_tax_information?: TaxInfoAcceptedCountryRow[];\n}\n\n/** STEP config for HUMAN_EXTENDED_INFO. */\nexport interface HumanExtendedInfoStepConfig {\n /** Optional top-level lists sent by backend for select fields. */\n gender_options?: StepFieldOption[];\n civil_status_options?: StepFieldOption[];\n fields?: Partial<\n Record<\n 'gender' | 'civil_status' | 'birth_date' | 'birth_country' | 'citizenship' | 'residence_country',\n StepFieldRuleWithOptions\n >\n >;\n}\n\n/** STEP config for ADDRESS_INFO. */\nexport interface AddressInfoStepConfig {\n address_type?: { id?: string; display_value?: string };\n fields?: Partial<\n Record<\n 'state' | 'city' | 'district' | 'neighborhood' | 'postal_code' | 'street' | 'number',\n StepFieldRule\n >\n >;\n /**\n * Optional ISO 3166-1 alpha-2 allowlist for the country selector. Only\n * consumed by the manual-entry form (when the SDK is configured without a\n * Google Maps key). When unset, every country is offered.\n */\n country_allow?: string[];\n}\n\n/** STEP config for SWORN_DECLARATION. */\nexport interface SwornDeclarationStepConfig {\n ask_pep?: boolean;\n ask_obligated_subject?: boolean;\n ask_fatca?: boolean;\n ask_ocde?: boolean;\n on_declared_pep?: string;\n on_declared_obligated_subject?: string;\n on_declared_fatca?: string;\n on_declared_ocde?: string;\n}\n\n/**\n * A single selectable economic activity presented in the\n * `ECONOMIC_ACTIVITY_DECLARATION` step. Mirrors the backend `EconomicActivityItem`\n * (`code` + `display_name`); `code` is unique within the list and is what the SDK\n * submits. There is no global catalog — each workflow declares its own list.\n */\nexport interface EconomicActivityItem {\n /** Machine-readable activity code (e.g. an AFIP/CLAE code) — unique within the list. */\n code: string;\n /** Human-readable label shown to the user. */\n display_name: string;\n}\n\n/** STEP config for ECONOMIC_ACTIVITY_DECLARATION (`current_step.config`). */\nexport interface EconomicActivityStepConfig {\n /** Selectable activities; always non-empty when the step is active. */\n items?: EconomicActivityItem[];\n}\n\n/** STEP config for DOCUMENTS_OCR. */\nexport interface DocumentsOcrStepConfig {\n country_allow?: string[];\n capture_methods?: {\n camera_scan?: boolean;\n upload?: boolean;\n };\n /** Crop frame orientation for document camera capture. */\n document_orientation?: DocumentOrientation;\n /** Mirror preview in document camera (default true). */\n mirror_document_video?: boolean;\n}\n\n/** STEP config for TERMS_AND_CONDITIONS. */\nexport interface TermsAndConditionsStepConfig {\n terms?: Array<{ title?: string; url?: string }>;\n}\n\n/** Generic per-step runtime config map carried by session state and SSE. */\nexport type StepConfigMap = Partial<Record<OnboardingStep, Record<string, unknown>>>;\n\n/**\n * Request body for POST /sessions/:sessionId/documents-init.\n * Allowed values are defined by the backend; the SDK currently sends a fixed type until\n * the session exposes selectable document types.\n */\nexport interface DocumentsInitRequest {\n document_type: string;\n}\n\n/**\n * Response from POST /sessions/:sessionId/documents-init.\n * Backend returns signed upload URLs so the SDK can PUT each document side\n * directly to storage (e.g. GCS) without routing bytes through the backend.\n */\n/**\n * Response of `POST /sessions/:id/documents-init`.\n *\n * Each URL is a GCS V4 signed PUT issued by the Velion CDN, valid for about\n * two minutes, and signed over `host`, `content-type` and\n * `x-goog-content-length-range`. The PUT must therefore send\n * `Content-Type: image/jpeg` **and** `x-goog-content-length-range:\n * 0,<max_file_size_bytes>` byte for byte, or GCS answers 400\n * `MalformedSecurityHeader` (OSDKW-5). Request the URLs right before the\n * upload, never ahead of the capture.\n */\nexport interface DocumentsInitResponse {\n /** Pre-signed URL to upload the front side (PUT, see the interface docs for the required headers). */\n front_side_upload_url: string;\n /** Pre-signed URL to upload the back side (PUT, see the interface docs for the required headers). */\n back_side_upload_url: string;\n /**\n * Upper bound the URLs were signed for; the exact number that goes into the\n * `x-goog-content-length-range` header. Also usable to reject oversize\n * captures before the PUT.\n */\n max_file_size_bytes?: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ERROR TYPES\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Granular error codes for camera/hardware failures.\n * Allows consumers to display localized, actionable messages to end-users.\n */\nexport enum CameraErrorCode {\n /** User explicitly denied camera access in the browser permission dialog. */\n PERMISSION_DENIED = 'CAMERA_PERMISSION_DENIED',\n /** No camera device was detected on the system. */\n DEVICE_NOT_FOUND = 'CAMERA_DEVICE_NOT_FOUND',\n /**\n * The camera could not be opened for reading (often in use by another app,\n * or blocked at the OS level). Maps to `NotReadableError` / `TrackStartError`.\n */\n DEVICE_NOT_READABLE = 'CAMERA_DEVICE_NOT_READABLE',\n /**\n * The page is served over HTTP (not HTTPS) in a production environment.\n * `getUserMedia` is blocked by the browser's Secure Context policy.\n */\n INSECURE_CONTEXT = 'CAMERA_INSECURE_CONTEXT',\n /**\n * Browser policy blocked camera (e.g. cross-origin iframe without permission,\n * or `Permissions-Policy` / Feature-Policy denying camera). Maps to `SecurityError`\n * from `getUserMedia` — distinct from {@link INSECURE_CONTEXT} (non-HTTPS).\n */\n POLICY_BLOCKED = 'CAMERA_POLICY_BLOCKED',\n /** The browser or WebView does not support the `getUserMedia` API. */\n NOT_SUPPORTED = 'CAMERA_NOT_SUPPORTED',\n /**\n * Camera access is blocked by a WebView policy (common in in-app browsers\n * on iOS/Android that restrict hardware access).\n */\n WEBVIEW_BLOCKED = 'CAMERA_WEBVIEW_BLOCKED',\n /** The requested video constraints (resolution, facing mode) are not satisfiable. */\n CONSTRAINT_ERROR = 'CAMERA_CONSTRAINT_ERROR',\n /** An unclassified camera error occurred. Check `cause` for details. */\n UNKNOWN = 'CAMERA_UNKNOWN_ERROR',\n}\n\n/**\n * Top-level SDK error codes covering all failure scenarios in the onboarding flow.\n */\nexport enum SdkErrorCode {\n // ── Session ──────────────────────────────────────────────────────────────\n /** The sessionToken was rejected by the backend (401/403). */\n SESSION_INVALID = 'SESSION_INVALID',\n /** The sessionToken has expired (typically a 401 after initial validation). */\n SESSION_EXPIRED = 'SESSION_EXPIRED',\n /** Could not fetch the initial session state from the backend. */\n SESSION_FETCH_FAILED = 'SESSION_FETCH_FAILED',\n /** Backend sent SESSION_DECLINED over SSE; session ended before completion. */\n SESSION_DECLINED = 'SESSION_DECLINED',\n /**\n * A step was completed but the backend did not route the session to the\n * next one within `routingTimeoutMs` (default 120 s): no `ROUTING_COMMAND`\n * arrived over SSE and `GET /sessions/:id` kept reporting the completed\n * step. The session is still alive on the backend; `stepCode` names the\n * completed step. Typical cause: a delayed or stalled onboarding worker.\n */\n ROUTING_TIMEOUT = 'ROUTING_TIMEOUT',\n\n // ── Modules ──────────────────────────────────────────────────────────────\n /** FingerprintJS failed to generate the visitor ID. */\n FINGERPRINT_FAILED = 'FINGERPRINT_FAILED',\n /** One or more document images failed to upload successfully. */\n DOCUMENT_UPLOAD_FAILED = 'DOCUMENT_UPLOAD_FAILED',\n /** The liveness check did not pass or returned an unexpected result. */\n LIVENESS_FAILED = 'LIVENESS_FAILED',\n /** The Liveness module failed to initialize (e.g., could not create the Iris session). */\n LIVENESS_INIT_FAILED = 'LIVENESS_INIT_FAILED',\n /**\n * The recorded liveness session video could not be uploaded to storage.\n * Best-effort only: this never fails the liveness step — it is logged so the\n * back-office knows the playback evidence is missing for the session.\n */\n LIVENESS_VIDEO_UPLOAD_FAILED = 'LIVENESS_VIDEO_UPLOAD_FAILED',\n\n // ── Camera ───────────────────────────────────────────────────────────────\n /**\n * A camera-related error occurred. Inspect `cameraCode` for the specific reason.\n * @see CameraErrorCode\n */\n CAMERA_ERROR = 'CAMERA_ERROR',\n\n // ── Network & API ────────────────────────────────────────────────────────\n /** A network-level failure (no internet, CORS, DNS). */\n NETWORK_ERROR = 'NETWORK_ERROR',\n /** The backend returned a non-2xx HTTP status. */\n API_ERROR = 'API_ERROR',\n\n // ── Configuration ────────────────────────────────────────────────────────\n /** The `containerId` provided in the config does not exist in the DOM. */\n CONTAINER_NOT_FOUND = 'CONTAINER_NOT_FOUND',\n /** `start()` was called on an SDK instance that is already running. */\n ALREADY_STARTED = 'ALREADY_STARTED',\n /** `start()` was called after `destroy()`. The instance is no longer usable. */\n INSTANCE_DESTROYED = 'INSTANCE_DESTROYED',\n\n /**\n * The page is running inside an iframe and embedding was not explicitly allowed.\n * Configure Content-Security-Policy: frame-ancestors or X-Frame-Options on the\n * page that hosts the SDK, or set allowEmbedded: true if embedding is intentional.\n */\n INSECURE_EMBED = 'INSECURE_EMBED',\n\n /**\n * The CDN-served manifest could not be downloaded or its Ed25519 signature did\n * not verify against the loader's embedded public key. The loader refuses to\n * mount the iframe to prevent execution of tampered/unsigned bundles.\n */\n INTEGRITY_FAILED = 'INTEGRITY_FAILED',\n /**\n * The iframe loaded but the runtime never reported `VELION_READY` within\n * `runtimeReadyTimeoutMs` (default 30 s). Typical causes: the CDN serves an\n * `index.html` whose hashed assets are missing (deploy under the wrong\n * prefix), a Content-Security-Policy on the host blocking the runtime's\n * scripts, or the runtime crashing before it could boot. Without this the\n * host saw a blank iframe and no callback at all.\n */\n RUNTIME_LOAD_FAILED = 'RUNTIME_LOAD_FAILED',\n\n UNKNOWN = 'UNKNOWN',\n}\n\n/**\n * A structured, machine-readable error object emitted by the SDK.\n *\n * @example\n * ```typescript\n * const sdk = new VelionOnboarding({\n * onError: (err) => {\n * if (err.code === SdkErrorCode.CAMERA_ERROR &&\n * err.cameraCode === CameraErrorCode.PERMISSION_DENIED) {\n * showCameraPermissionDialog();\n * }\n * }\n * });\n * ```\n */\nexport interface SdkError {\n /** Primary SDK-level error classification. */\n readonly code: SdkErrorCode;\n /** Human-readable English description for developer logging. */\n readonly message: string;\n /** Present when `code === SdkErrorCode.SESSION_DECLINED` (SSE `reason`). */\n readonly declineReason?: string;\n /**\n * Step the error refers to. For `SESSION_DECLINED` it is the SSE `step_code`;\n * for `ROUTING_TIMEOUT` it is the step that was completed and never routed.\n */\n readonly stepCode?: string;\n /**\n * Localized headline resolved by the SDK for SESSION_DECLINED.\n * Useful when hosts hide the built-in final result screen but still want\n * the same user-facing text in custom UI.\n */\n readonly declineTitle?: string;\n /**\n * Localized body message resolved by the SDK for SESSION_DECLINED.\n * Mirrors the text shown in the built-in final result screen.\n */\n readonly declineMessage?: string;\n /** Present when `code === SdkErrorCode.CAMERA_ERROR`. Provides camera-specific detail. */\n readonly cameraCode?: CameraErrorCode;\n /** HTTP status code when the error originated from an API call. */\n readonly httpStatus?: number;\n /**\n * The original caught value for debugging.\n * Not guaranteed to be an `Error` instance — could be a DOMException or string.\n */\n readonly cause?: unknown;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// THEMING\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * White-labeling theme configuration.\n * Each property maps to a CSS custom property injected on the SDK container,\n * making all SDK UI elements styleable via `--vl-*` variables.\n *\n * @example\n * ```typescript\n * const theme: VelionTheme = {\n * primaryColor: '#0052CC', // → --vl-primary-color\n * borderRadius: '12px', // → --vl-border-radius\n * fontFamily: 'Inter, sans-serif'// → --vl-font-family\n * };\n * ```\n */\nexport interface VelionTheme {\n /** Primary brand color. Used for buttons, progress indicators, and accents. */\n primaryColor?: string;\n /** Secondary color. Used for hover states and secondary actions. */\n secondaryColor?: string;\n /** Background color of the SDK container. */\n backgroundColor?: string;\n /** Main body text color. */\n textColor?: string;\n /** Secondary text color for subtitles, captions, and de-emphasized copy. */\n textSecondaryColor?: string;\n /** Border radius for cards, buttons, and inputs. */\n borderRadius?: string;\n /** Font family string. Must be available in the host page's CSS context. */\n fontFamily?: string;\n /** Color used for error states and destructive actions. */\n errorColor?: string;\n /** Color used for success confirmations. */\n successColor?: string;\n /** Color used for warnings and informational alerts. */\n warningColor?: string;\n /** Border color for input fields and separators. */\n borderColor?: string;\n /** Box shadow definition for elevated elements (cards, modals). */\n cardShadow?: string;\n\n /**\n * When `true`, the round icon that sits above each step's title is hidden.\n * Only the title and subtitle are rendered. Useful when the host UI already\n * provides its own visual identity per step or when a denser layout is\n * preferred.\n *\n * @default false\n */\n hideStepIcons?: boolean;\n\n /**\n * When `true`, the thin progress bar at the top of the SDK container is\n * hidden. Equivalent to setting `VelionConfig.showProgressBar: false` —\n * either flag is enough to disable it (whichever is more convenient given\n * how the host configures the SDK). Disable when your host UI already\n * surfaces its own progress indicator.\n *\n * @default false\n */\n hideProgressBar?: boolean;\n}\n\n/**\n * Optional title and body for one terminal verdict on the final result screen.\n */\nexport interface VelionFinalResultStatusCopy {\n title?: string;\n message?: string;\n}\n\n/**\n * Configurable copy for the built-in final result screen (`showFinalResultScreen`).\n * Omitted fields fall back to SDK defaults for the active `locale`.\n */\nexport interface VelionFinalResultScreenTexts {\n approved?: VelionFinalResultStatusCopy;\n manualReview?: VelionFinalResultStatusCopy;\n rejected?: VelionFinalResultStatusCopy;\n /** Copy when {@link OnboardingResult.status} is `DECLINED`. */\n declined?: VelionFinalResultStatusCopy;\n /** Primary button label (e.g. \"Entendido\" / \"Got it\"). */\n dismissLabel?: string;\n}\n\n/**\n * Localized title/body for one `reason` on the SESSION_DECLINED screen.\n * Keys match backend `reason` (e.g. `TAX_INFO_FAILED`).\n */\nexport interface VelionSessionDeclinedReasonMessages {\n en?: VelionFinalResultStatusCopy;\n es?: VelionFinalResultStatusCopy;\n}\n\n/**\n * Per-reason overrides for the built-in SESSION_DECLINED screen (`showFinalResultScreen` not false).\n * Unspecified reasons fall back to SDK defaults when available, then generic copy.\n */\nexport type VelionSessionDeclinedReasonTexts = Record<\n string,\n VelionSessionDeclinedReasonMessages\n>;\n\n/**\n * Per-step overrides for the retry dialog shown when the backend emits a\n * `ROUTING_COMMAND / RETRY_STEP` (the step failed but the retry quota is not\n * exhausted). Keyed by step code (e.g. `LIVENESS_MATCH`, `DOCUMENTS_OCR`).\n * Unspecified steps fall back to SDK per-step defaults, then generic copy.\n *\n * Note: the SDK intentionally ignores the backend `reason` for this dialog —\n * it is a diagnostic string (e.g. `\"STEP_FAILED_RETRY_ALLOWED (attempt 1/3)\"`),\n * not user-facing localized copy. The copy is derived from the step instead.\n */\nexport type VelionRetryStepTexts = Record<\n string,\n VelionSessionDeclinedReasonMessages\n>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// CALLBACKS\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The complete set of lifecycle callbacks the host application can subscribe to.\n * All callbacks are optional. The SDK is still functional without any of them,\n * but `onError` and `onSuccess` are strongly recommended for production.\n */\nexport interface VelionCallbacks {\n /**\n * Fires when the entire onboarding flow has been completed successfully\n * and the backend has confirmed the session as resolved.\n *\n * @param result - Final onboarding verdict from the backend.\n */\n onSuccess?: (result: OnboardingResult) => void;\n\n /**\n * Fires when an unrecoverable error terminates the onboarding flow.\n * Any terminal failure (camera error, document capture failure, upload failure,\n * liveness failure, API error, etc.) is delivered here so the host can notify the user.\n * After this callback, the SDK instance is in an error state.\n * Call `sdk.destroy()` and re-instantiate to retry.\n *\n * @param error - Structured error with a machine-readable code (e.g. CAMERA_ERROR, DOCUMENT_UPLOAD_FAILED).\n */\n onError?: (error: SdkError) => void;\n\n /**\n * Fires each time the active step changes during the flow.\n * Useful for updating a custom step indicator in the host UI.\n *\n * The required step list is resolved dynamically by the backend, so the SDK\n * does not know the total step count up front. Callers that need a progress\n * indicator should rely on the SDK's built-in progress bar (or the trickling\n * counter in `stepIndex`).\n *\n * @param step - The step that just became active.\n * @param stepIndex - Zero-based index of the current step.\n */\n onStepChange?: (step: OnboardingStep, stepIndex: number) => void;\n\n /**\n * Fires immediately after a step succeeds, before advancing to the next one.\n * Useful for analytics or triggering intermediate UI transitions.\n *\n * @param step - The step that was just completed.\n */\n onStepComplete?: (step: OnboardingStep) => void;\n\n /**\n * Fires for each SSE event from GET /sessions/:sessionId/events\n * (e.g. STEP_UPDATED, SESSION_APPROVED).\n */\n onSessionEvent?: (event: OnboardingSessionEvent) => void;\n\n /**\n * Called when the user taps the dismiss control on the final result screen\n * (only when the final result screen is shown). `onSuccess` has already run.\n */\n onFinalResultScreenDismiss?: () => void;\n\n /**\n * Fires when the LIVENESS_MATCH step successfully uploaded the recorded\n * session video to the onboarding storage (GCS PUT returned 200). Best-effort:\n * if the upload failed, timed out, or the session had no `video_upload` URL,\n * the callback is not invoked — liveness still completes normally.\n *\n * @param ref - URL and metadata of the uploaded video.\n */\n onLivenessVideoUploaded?: (ref: LivenessVideoRef) => void;\n\n /**\n * Fires when the user leaves the onboarding flow without a terminal verdict —\n * either by tapping \"back\" on the first reversible step (`BACK_EXIT`) or\n * \"Cancel\" on the retry dialog shown after a `RETRY_STEP` (`RETRY_CANCELLED`).\n * In both cases the backend told the SDK to leave the flow and hand control\n * back to the host; the SDK has already torn down its UI.\n *\n * Distinct from `onSuccess` (a terminal verdict) and `onError` (a failure):\n * the host should typically dismiss/close the SDK surface when this fires.\n *\n * @param exit - Why the flow exited and the step it exited from.\n */\n onExit?: (exit: OnboardingExit) => void;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// MAIN CONFIGURATION\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Root configuration object for the `VelionOnboarding` class.\n *\n * @example\n * ```typescript\n * const sdk = new VelionOnboarding({\n * containerId: 'velion-root',\n * sessionId: 'ses_abc123',\n * sessionToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',\n * apiUrl: 'https://api.velion.la/v1',\n * theme: { primaryColor: '#0052CC' },\n * onSuccess: (result) => console.log('Done!', result),\n * onError: (err) => console.error('SDK Error:', err),\n * });\n *\n * await sdk.start();\n * ```\n */\nexport interface VelionConfig extends VelionCallbacks {\n /**\n * The `id` of an existing DOM element where the SDK will render its UI.\n * The element **must** be present in the document at the time `start()` is called.\n *\n * @example `'velion-kyc-container'`\n */\n containerId: string;\n\n /**\n * The unique session identifier generated by your backend for this\n * onboarding session. Used to scope all API calls.\n *\n * @example `'ses_9f8d7c6b5a4e3f2d'`\n */\n sessionId: string;\n\n /**\n * A short-lived, opaque authorization token (e.g. HS256 JWT) issued by your\n * backend when creating the session. Sent as `Authorization: Bearer <sessionToken>`\n * on every SDK request. Required for all endpoints behind the gateway.\n *\n * **Security note:** This token is held in memory only (never in localStorage\n * or cookies) and is automatically discarded on `destroy()`.\n */\n sessionToken: string;\n\n /**\n * ISO-8601 expiry timestamp of the sessionToken. If provided, the SDK may\n * check expiration before requests and emit SESSION_EXPIRED without a round-trip.\n * When the backend returns 401/403, the SDK emits SESSION_EXPIRED anyway.\n */\n expiresAt?: string;\n\n /**\n * Base URL of the Velion onboarding edge. Must **not** include a trailing slash.\n * SDK paths are relative to this (e.g. GET `/sessions/{sessionId}`).\n *\n * Defaults to the public production edge `https://sdk.velion.la/onboarding`.\n * Override only for staging or self-hosted environments.\n */\n apiUrl?: string;\n\n /**\n * Optional white-labeling theme. If omitted, SDK renders with Velion defaults.\n */\n theme?: VelionTheme;\n\n /**\n * Global visual mode for SDK overlays and controls.\n * - `'light'`: Light translucent surfaces.\n * - `'dark'`: Dark translucent surfaces.\n *\n * @default 'light'\n */\n colorMode?: 'light' | 'dark';\n\n /**\n * BCP 47 locale tag for SDK UI copy (labels, instructions, error messages).\n *\n * @default 'es'\n */\n locale?: string;\n\n /**\n * When `true`, after the backend returns a terminal verdict the SDK shows a\n * full-screen summary (APPROVED, MANUAL_REVIEW, REJECTED) inside the container,\n * styled with `theme` and `colorMode`. `onSuccess` still fires with the result.\n *\n * @default true\n */\n showFinalResultScreen?: boolean;\n\n /**\n * When `true` (default), the SDK renders a thin progress bar at the top of\n * the container that advances as the user moves through steps. Disable for\n * embedded experiences that already provide their own progress UI.\n *\n * @default true\n */\n showProgressBar?: boolean;\n\n /**\n * Overrides for default titles, messages, and dismiss button on the final result screen.\n * Only used when the final result screen is shown (`showFinalResultScreen` is not `false`).\n */\n finalResultScreenTexts?: VelionFinalResultScreenTexts;\n\n /**\n * Optional copy for SESSION_DECLINED SSE screens, keyed by backend `reason`.\n * Merged with SDK defaults (e.g. `TAX_INFO_FAILED`) for `locale` `en` / `es`.\n */\n sessionDeclinedReasonTexts?: VelionSessionDeclinedReasonTexts;\n\n /**\n * Optional copy for the retry dialog (`ROUTING_COMMAND / RETRY_STEP`), keyed by\n * step code (e.g. `LIVENESS_MATCH`). Merged with SDK per-step defaults for\n * `locale` `en` / `es`; unspecified steps fall back to generic retry copy.\n */\n retryStepTexts?: VelionRetryStepTexts;\n\n /**\n * When `true` (default), shows the Velion preparation screen (face icon, tips,\n * \"Start verification\") before connecting to Iris. When `false`, skips it and\n * starts the camera + Iris session immediately on step entry.\n *\n * @default true\n */\n livenessShowIntro?: boolean;\n\n /**\n * When `false`, hides the \"Atrás\" / \"Back\" button on the liveness preparation\n * screen. The button is only present in the intro; once the camera capture\n * begins the user must complete the Iris challenges to leave the step.\n *\n * @default true\n */\n livenessShowBackButton?: boolean;\n\n /**\n * If true, allows the SDK to run when the page is inside an iframe.\n * Only set to true if you have configured Content-Security-Policy: frame-ancestors\n * or X-Frame-Options on the HTTP response of the page that hosts the SDK.\n * If false (default) and the page is embedded in an iframe, the SDK emits\n * INSECURE_EMBED and does not start.\n *\n * @default false\n */\n allowEmbedded?: boolean;\n\n /**\n * Internal — set by the Velion CDN entry (`cdn-main.ts`) to the `event.origin`\n * captured from the loader's first VELION_INIT message. The SDK validates it\n * against `allowed_parent_origins` returned by the backend and refuses to start\n * if the origin is not in the customer's registered allowlist.\n *\n * Direct integrators (running VelionOnboarding without the loader iframe)\n * should leave this unset.\n *\n * @internal\n */\n iframeParentOrigin?: string;\n\n /**\n * Fingerprint Pro API endpoint (custom subdomain). Used by the DEVICE_FINGERPRINT\n * step to load and identify visitors. Only set this when using a custom Fingerprint\n * subdomain or proxy; otherwise the default Velion endpoint is used.\n *\n * @default 'https://fp.velion.la'\n */\n fingerprintEndpoint?: string;\n\n /**\n * Fingerprint Pro public API key. Used by the DEVICE_FINGERPRINT step to\n * identify visitors. Defaults to the shared Velion public key — set this\n * only when your tenant has its own Fingerprint workspace.\n *\n * @default Velion's shared public key\n */\n fingerprintApiKey?: string;\n\n /**\n * Hard ceiling, in milliseconds, for the DEVICE_FINGERPRINT step (agent\n * load + identification). The Fingerprint agent has its own internal\n * timeout, but with tracker blockers or a blackholed endpoint it can hang\n * well past it; the SDK races against this value so the step fails with\n * `FINGERPRINT_FAILED` instead of leaving the user on a blank screen.\n *\n * @default 15000\n */\n fingerprintTimeoutMs?: number;\n\n /**\n * Total wait, in milliseconds, for the backend to route the session after a\n * step is completed. The SDK waits for the SSE `ROUTING_COMMAND` and falls\n * back to `GET /sessions/:id` every 30 s; when the backend keeps reporting\n * the completed step (delayed or stalled worker) those cycles used to repeat\n * for ever. Past this bound the flow fails with `ROUTING_TIMEOUT` so the host\n * gets a callback instead of an endless spinner.\n *\n * @default 120000\n */\n routingTimeoutMs?: number;\n\n /**\n * Google Maps Platform API key (Maps JavaScript API + Places). Required for the\n * {@code ADDRESS_INFO} step to show the map, search, and geocoding. Omit only if\n * your flow never reaches that step.\n */\n googleMapsApiKey?: string;\n\n /**\n * Optional calibration tag forwarded to Iris for the LIVENESS_MATCH step.\n * When the Iris worker is running with `CALIBRATION_BACKEND=fs|gcs`, every\n * frame of this session is mirrored to the dataset bucket with the tag in\n * `metadata.json`, so we can build labeled training sets for the spoof /\n * liveness models. Does NOT affect the score.\n *\n * Suggested values (must match the worker's known labels — see\n * `iris-worker` README): `REAL_GOOD_LIGHT`, `REAL_BAD_LIGHT`, `REAL_BACKLIT`,\n * `REAL_BUSY_BG`, `REAL_NEUTRAL_BG`, `REAL_GLASSES`, `REAL_OUTDOOR`,\n * `FAKE_PHONE_PHOTO`, `FAKE_PHONE_VIDEO`, `FAKE_TABLET_VIDEO`,\n * `FAKE_MONITOR_VIDEO`, `FAKE_PRINT`, `FAKE_MASK`, `UNKNOWN`, `EDGE_CASE`.\n *\n * Leave undefined for normal user-facing sessions.\n */\n livenessCalibrationLabel?: string;\n\n /**\n * When true, the SDK enables verbose `console.info` logging at runtime even in\n * production builds. Useful when reproducing customer issues. Independent of the\n * build-time `__DEBUG__` flag, which already gates dev-server logs.\n *\n * @default false\n */\n debug?: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// BACKEND RESPONSE SHAPES\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Backend step DTO (OnboardingStepDto). GET session returns an array of these.\n */\nexport interface OnboardingStepDto {\n code: string;\n name?: string;\n description?: string;\n order?: number;\n status?: string;\n /**\n * Machine-readable cause of the current `status` when it is `FAILED` or\n * `MANUAL_REVIEW` (backend `StepStatusDetailCode`, VON-54 / VON-56). Examples:\n * `DOC_UNREADABLE`, `DOC_MRZ_NOT_READABLE`, `DOC_EXPIRED`, `DOC_AGE_EXCLUSION`,\n * `LIVENESS_BELOW_THRESHOLD`, `LIVENESS_SPOOF_SUSPECTED`,\n * `FACE_MATCH_BELOW_THRESHOLD`, `FINGERPRINT_BOT_DETECTED`,\n * `TAX_ARCA_VALIDATION_FAILED`, `TERMS_NOT_ACCEPTED`,\n * `ECONOMIC_ACTIVITY_INVALID_SELECTION`, `STEP_UNEXPECTED_ERROR`. Hosts may\n * use it for analytics or support tooling; the SDK derives its user-facing\n * copy from the step, never from this code.\n */\n status_detail?: string;\n required?: boolean;\n /** Step-specific payload (e.g. OCR fields, fingerprint, T&C items). */\n result?: Record<string, unknown>;\n /** Raw workflow config for this step (thresholds, accepted documents, …). */\n config?: Record<string, unknown>;\n /**\n * Whether the user may navigate back INTO this step after completing it\n * (intrinsic per-step flag the backend stamps from the workflow). Used only\n * to derive `can_go_back` for steps reached via SSE (which doesn't carry the\n * flag) and for prefill — NOT to decide whether to render the back button.\n * @see OnboardingDtoResponse.can_go_back\n */\n reversible?: boolean;\n}\n\n/**\n * Backend session/onboarding DTO (OnboardingDto). Returned by GET /sessions/:sessionId.\n * The SDK maps this to SessionState for the step machine.\n */\nexport interface OnboardingDtoResponse {\n id?: string;\n related_id?: string;\n onboarding_type?: string;\n status?: string;\n current_step?: {\n code?: string;\n status?: string;\n config?: Record<string, unknown>;\n /**\n * Previously-captured data for this step, present when a reverse navigation\n * re-opened a completed step. The SDK uses it as editable prefill.\n */\n result?: Record<string, unknown>;\n /** Intrinsic per-step reversibility (prefill / SSE-derivation only). */\n reversible?: boolean;\n };\n steps?: OnboardingStepDto[];\n created_at?: string;\n updated_at?: string;\n completed_at?: string;\n metadata?: Record<string, unknown>;\n /**\n * Whether the SDK should render the \"back\" control on the current step.\n * Computed server-side from the workflow graph (barrier model): `true` when\n * there is a previous reversible step to re-open, OR when the current step is\n * the first reversible step (back exits the flow via `BACK_EXIT`). The SDK\n * uses **this** — not a step's intrinsic `reversible` flag — to decide whether\n * to show the back button.\n */\n can_go_back?: boolean;\n /**\n * Origins (scheme + host + port) authorized by the customer to embed the SDK.\n * The iframe validates `event.origin` of the loader's first VELION_INIT against\n * this list and aborts with INSECURE_EMBED otherwise. Backend must also enforce\n * server-side via the `Origin` header on every API call.\n */\n allowed_parent_origins?: string[];\n}\n\n/**\n * Session state used by the SDK step machine. Derived from GET /sessions/:sessionId\n * (OnboardingDto): steps sorted by order → requiredSteps, first non-completed index → currentStepIndex.\n */\nexport interface SessionState {\n /** Current runnable step reported by backend session state. */\n currentStep?: OnboardingStep;\n /** Step-specific config for `currentStep` from GET /sessions/:id `current_step.config`. */\n currentStepConfig?: Record<string, unknown>;\n /** Cached configs discovered from GET session and SSE `step_config`. */\n stepConfigs?: StepConfigMap;\n /** Session type (HUMAN or LEGAL). Constrains which steps are applicable. */\n onboardingType?: OnboardingType;\n /**\n * Whether the SDK should render the \"back\" control on `currentStep`. Mapped\n * from GET `can_go_back` (workflow-graph computed). When absent (e.g. a step\n * reached via SSE, which doesn't carry the flag) the orchestrator derives it\n * from {@link stepReversibility}.\n */\n canGoBack?: boolean;\n /**\n * Per-step intrinsic reversibility in workflow order, cached from the initial\n * GET so the orchestrator can derive `can_go_back` for steps reached via SSE:\n * a step can go back when any earlier step is reversible, or the step itself\n * is the first reversible one (back exits the flow).\n */\n stepReversibility?: Array<{ code: OnboardingStep; reversible: boolean }>;\n /**\n * Previously-captured data for `currentStep`, present when a reverse\n * navigation re-opened a completed step (GET reflects it on\n * `current_step.result`). Applied by the SDK as editable prefill.\n */\n currentStepResult?: Record<string, unknown>;\n /** Arbitrary session metadata the backend may attach. */\n metadata?: Record<string, unknown>;\n /**\n * Customer-registered allowlist of origins authorized to embed the SDK.\n * When provided, the SDK validates the loader's parent origin against this list.\n */\n allowedParentOrigins?: string[];\n}\n\n/**\n * Why the onboarding flow ended without a terminal verdict, delivered to\n * {@link VelionCallbacks.onExit}.\n *\n * Triggers:\n * - `BACK_EXIT`: the user tapped \"back\" on the first reversible step, where\n * there is no earlier step to return to, so the SDK left the flow and\n * returned control to the host.\n * - `RETRY_CANCELLED`: the user tapped \"Cancel\" on the retry dialog shown after\n * a `ROUTING_COMMAND / RETRY_STEP`. The backend session stays `IN_PROGRESS`\n * (there is no cancellation endpoint); the host may re-open it later via\n * `GET /sessions/:id`.\n */\nexport interface OnboardingExit {\n /** The reason the flow exited. */\n reason: 'BACK_EXIT' | 'RETRY_CANCELLED';\n /** The step the user exited from (when the backend provides `step_code`). */\n step?: OnboardingStep;\n}\n\n/**\n * A single T&C line item (matches backend TermsAndConditionsItemDto / domain record).\n */\nexport interface TermsAndConditionsItem {\n id: string;\n description: string;\n /** Optional URL to the full document. */\n link?: string;\n}\n\n/**\n * Response from session creation (POST from your backend). Use sessionId and\n * sessionToken when instantiating VelionOnboarding; pass expiresAt for optional\n * client-side expiration checks.\n */\nexport interface SessionCreationResponse {\n session_id: string;\n session_token: string;\n expires_at: string;\n status?: string;\n}\n\n/**\n * Shape of the final result the SDK hands to `onSuccess` once the session\n * reaches a terminal status. Built from the terminal SSE event and the\n * `GET /sessions/:sessionId` snapshot (the backend exposes no separate\n * `/result` endpoint).\n */\nexport interface OnboardingResult {\n /** The session ID that was completed. Matches `VelionConfig.sessionId`. */\n sessionId: string;\n /** ISO 8601 timestamp of when the backend finalized the session. */\n completedAt: string;\n /**\n * The final KYC verdict.\n * - `APPROVED`: All checks passed automatically.\n * - `MANUAL_REVIEW`: Flagged for human review (common in AML flows). Backend `IN_REVIEW` maps here.\n * - `REJECTED`: Automatically rejected (fraud signals, liveness failure, etc.).\n * - `DECLINED`: Session ended in a declined state (matches GET session `status: DECLINED`).\n */\n status: 'APPROVED' | 'MANUAL_REVIEW' | 'REJECTED' | 'DECLINED';\n /** An optional tracking reference for support and audit trails. */\n referenceId?: string;\n /**\n * When the terminal outcome came from SSE, optional backend `reason` (e.g.\n * `LIVENESS_SCORE_IN_REVIEW_RANGE` on SESSION_MANUAL_REVIEW) for localized final-screen copy.\n */\n terminalReason?: string;\n}\n\n/**\n * Response from `POST /sessions/:sessionId/liveness-init`.\n * The backend returns the Iris session details the SDK needs to open the WebSocket\n * and stream frames. Credentials are not used: Iris owns the session lifecycle.\n */\nexport interface LivenessSessionResponse {\n /** Iris session identifier. */\n liveness_session_id?: string;\n /** Full WebSocket URL the SDK must connect to (e.g. wss://iris.host/ws/iris/{id}). */\n websocket_url?: string;\n /** ISO-8601 expiry of the Iris session. */\n expires_at?: string;\n /** Effective Iris challenge identifiers in order (TURN_LEFT, SMILE, …). */\n challenges?: string[];\n /** Optional JWT for the WebSocket handshake when Iris auth is enabled. */\n connection_token?: string;\n /**\n * Optional base64 HMAC-SHA256 key. When present, the SDK signs every JPEG\n * frame in a JSON envelope (canonical line: `sessionId\\nframeId\\nts\\nsha256hex(jpeg)`).\n */\n frame_integrity_key_base64?: string;\n /**\n * Signed PUT URL the SDK uses to upload the locally-recorded liveness video\n * directly to the onboarding GCS bucket. `null` (or absent) when video\n * recording is disabled or the signing failed — the SDK skips the upload\n * silently (liveness still works without the video; only the back-office\n * loses the playback evidence).\n */\n video_upload?: VideoUpload | null;\n}\n\n/**\n * Signed-upload payload returned by `POST /liveness-init`. Mirrors the backend\n * `LivenessInitResponseDto.VideoUpload`. The SDK PUTs the recorded video bytes\n * to `upload_url` (Content-Type matching the recorded container) before calling\n * `POST /liveness-verify` with the resulting metadata.\n */\nexport interface VideoUpload {\n /** Absolute https://storage.googleapis.com/... URL with V4 signature params. */\n upload_url: string;\n /** ISO-8601 instant after which GCS rejects the signature. */\n expires_at: string;\n /** HTTP method to use. Always `\"PUT\"` today; future-proofing. */\n method: 'PUT' | string;\n}\n\n/**\n * Optional body of `POST /liveness-verify`. The SDK only includes it when the\n * recorded video uploaded successfully (HTTP 200 from GCS). When omitted, the\n * backend completes liveness based on the Iris score only.\n */\nexport interface LivenessVerifyVideoMeta {\n /** MIME type that was sent in the PUT `Content-Type` header (e.g. `video/mp4`, `video/webm`). */\n content_type: string;\n /** Recorded video duration in milliseconds, or `null` if it could not be determined. */\n duration_ms: number | null;\n}\n\n/**\n * Payload delivered to the optional `onLivenessVideoUploaded` callback after a\n * successful upload of the liveness video. Best-effort: the callback only\n * fires when the GCS PUT returned 200.\n */\nexport interface LivenessVideoRef {\n /** Signed `upload_url` the SDK PUT the video to. */\n upload_url: string;\n /** MIME type used in the PUT. */\n content_type: string;\n /** Recorded video duration in milliseconds, or `null` when unknown. */\n duration_ms: number | null;\n /** Recorded file size in bytes, or `null` when unknown. */\n size_bytes: number | null;\n}\n\n/**\n * Payload for POST /sessions/:sessionId/basic-info/human (HUMAN_BASIC_INFO step).\n * Matches backend SubmitHumanBasicInfoRequestDto (snake_case in JSON).\n */\nexport interface HumanBasicInfoPayload {\n first_name: string;\n middle_name?: string;\n last_name: string;\n second_last_name?: string;\n identification_type: string;\n identification_id: string;\n}\n\n/**\n * Payload for POST /sessions/:sessionId/basic-info/legal (LEGAL_BASIC_INFO step).\n * Matches backend SubmitLegalBasicInfoRequestDto (snake_case in JSON).\n */\nexport interface LegalBasicInfoPayload {\n legal_name: string;\n legal_form: string;\n identification_type: string;\n identification_id: string;\n}\n\n/**\n * Single selectable option for tax category or fiscal identifier type (TAX_INFO step).\n * `id` is submitted to the API; `description` is the user-visible label.\n */\nexport interface TaxInfoOption {\n id: string;\n description: string;\n}\n\n/**\n * Resolved selectable lists for the TAX_INFO step UI (from `accepted_tax_information` only; no SDK defaults).\n */\nexport interface TaxInfoConfig {\n /**\n * Fiscal regime / category options (e.g. Monotributo). The chosen `id` is sent as `taxpayer_type`.\n */\n taxpayerCategories: TaxInfoOption[];\n /**\n * Tax identification scheme options (e.g. CUIT). The chosen `id` is sent as `taxpayer_id_type`.\n */\n fiscalIdentifierTypes: TaxInfoOption[];\n}\n\n/** POST /sessions/:id/tax-info — SubmitTaxInfoRequestDto. */\nexport interface TaxInfoPayload {\n /** Selected fiscal category value (`code` from step config `taxpayer_categories`). */\n taxpayer_type: string;\n /** Selected fiscal identifier kind id (e.g. `CUIT` from step config `identification_types`). */\n taxpayer_id_type: string;\n /** Tax identification number for the chosen type. */\n taxpayer_id: string;\n}\n\n/** POST /sessions/:id/extended-info/human — SubmitHumanExtendedInfoRequestDto. */\nexport interface HumanExtendedInfoPayload {\n gender?: string;\n civil_status?: string;\n birth_date?: string;\n birth_country?: string;\n citizenship?: string;\n residence_country?: string;\n}\n\n/** POST /sessions/:id/extended-info/legal — SubmitLegalExtendedInfoRequestDto. */\nexport interface LegalExtendedInfoPayload {\n incorporation_date: string;\n incorporation_country: string;\n}\n\n/** POST /sessions/:id/address-info — SubmitAddressInfoRequestDto. */\nexport interface AddressInfoPayload {\n type: string;\n country: string;\n state: string;\n city: string;\n district?: string;\n neighborhood?: string;\n street: string;\n number: string;\n floor?: string;\n apartment?: string;\n details?: string;\n zip_code: string;\n latitude?: number;\n longitude?: number;\n}\n\n/** POST /sessions/:id/sworn-declaration — SubmitSwornDeclarationRequestDto. */\nexport interface SwornDeclarationPayload {\n is_pep: boolean;\n is_ssoo: boolean;\n is_fatca: boolean;\n is_ocde: boolean;\n}\n\n/**\n * POST /sessions/:id/economic-activity — EconomicActivitySelectionDto.\n *\n * Carries the `code` of the single economic activity the user selected for the\n * `ECONOMIC_ACTIVITY_DECLARATION` step. The Worker validates it asynchronously\n * against the configured items; an invalid code surfaces over SSE as\n * `RETRY_STEP` (re-mounts the step) or `SESSION_DECLINED`, never as an HTTP error.\n */\nexport interface EconomicActivitySelectionPayload {\n /** `code` of the selected EconomicActivityItem. */\n code: string;\n}\n\n/** POST /sessions/:id/contact/email — SubmitEmailContactRequestDto. */\nexport interface EmailContactPayload {\n email: string;\n}\n\n/** POST /sessions/:id/contact/phone — SubmitPhoneContactRequestDto. */\nexport interface PhoneContactPayload {\n phone: string;\n}\n\n/** POST /sessions/:id/terms-and-conditions — SubmitTermsAndConditionsRequestDto. */\nexport interface TermsAndConditionsAcceptancePayload {\n item_id: string;\n accepted: boolean;\n}\n\nexport interface TermsAndConditionsSubmitPayload {\n acceptances: TermsAndConditionsAcceptancePayload[];\n}\n\n/**\n * SSE event payload (OnboardingSessionEvent). Matches the backend record\n * la.velion.onboarding.api...sdk.OnboardingSessionEventDto (snake_case in JSON).\n *\n * Received on GET /public/sdk/sessions/:sessionId/events. The backend sends\n * ServerSentEvent with id = event_id (epoch millis) for Last-Event-ID replay.\n *\n * Event types (event_type):\n * - STEP_UPDATED — a workflow step changed status (step_code, step_status)\n * - ROUTING_COMMAND — next/retry/terminate instruction (action: NEXT_STEP | RETRY_STEP | TERMINATE)\n * - SESSION_APPROVED — all checks passed; session_status = APPROVED\n * - SESSION_MANUAL_REVIEW — manual review required; session_status = MANUAL_REVIEW\n * - SESSION_DECLINED — session ended with decline (step_code, session_status DECLINED, reason)\n */\nexport interface OnboardingSessionEvent {\n /** Onboarding session identifier (Long in backend). */\n session_id?: number;\n /** Discriminator: STEP_UPDATED | ROUTING_COMMAND | SESSION_APPROVED | SESSION_MANUAL_REVIEW | SESSION_DECLINED */\n event_type?: string;\n /** Present for STEP_UPDATED or ROUTING_COMMAND (next step code). */\n step_code?: string;\n /** Present for STEP_UPDATED; e.g. COMPLETED. */\n step_status?: string;\n /** Present for terminal events; e.g. APPROVED | MANUAL_REVIEW | REJECTED. */\n session_status?: string;\n /**\n * For ROUTING_COMMAND: NEXT_STEP | RETRY_STEP (render step_code), TERMINATE\n * (end flow), PREVIOUS_STEP (reverse navigation — re-open step_code with\n * step_result as prefill), or BACK_EXIT (user backed out of the first\n * reversible step — leave the flow and notify the host via `onExit`).\n */\n action?: string;\n /** For SESSION_DECLINED / SESSION_MANUAL_REVIEW or ROUTING_COMMAND TERMINATE; e.g. UNREADABLE_DOCUMENT. */\n reason?: string;\n /** Step-specific config sent with ROUTING_COMMAND NEXT_STEP / PREVIOUS_STEP. */\n step_config?: Record<string, unknown>;\n /**\n * Previously-captured data for the re-opened step, sent with ROUTING_COMMAND\n * PREVIOUS_STEP. Applied by the SDK as editable prefill.\n */\n step_result?: Record<string, unknown>;\n /**\n * Whether the step the user is landing on can itself go back further. Carried\n * on ROUTING_COMMAND when the backend computes it; when absent the SDK derives\n * it from the cached workflow reversibility map.\n */\n can_go_back?: boolean;\n /** Epoch millis; used as SSE id for Last-Event-ID on reconnect. */\n event_id?: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// BACKEND COMMANDS (Listen & Obey)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Backend command: advance to a specific step. The SDK must render that step's\n * screen without evaluating business rules locally.\n */\nexport interface BackendCommandGoToStep {\n action: 'GO_TO_STEP';\n stepCode: OnboardingStep;\n}\n\n/**\n * Backend command: show an error message to the user. The SDK displays the\n * message and does not advance (backend owns the decision).\n */\nexport interface BackendCommandShowError {\n action: 'SHOW_ERROR';\n message: string;\n}\n\n/**\n * Backend command: flow is complete. SDK should fetch final result and fire onSuccess.\n */\nexport interface BackendCommandFlowComplete {\n action: 'FLOW_COMPLETE';\n}\n\n/**\n * Backend command: retry the current step (e.g. after transient failure).\n */\nexport interface BackendCommandRetry {\n action: 'RETRY';\n}\n\n/**\n * Strict command payload the backend may return in a synchronous HTTP response\n * or send via SSE. The SDK never makes routing decisions; it only obeys these commands.\n */\nexport type BackendCommand =\n | BackendCommandGoToStep\n | BackendCommandShowError\n | BackendCommandFlowComplete\n | BackendCommandRetry;\n\n/**\n * Raw response body that may contain a backend command (e.g. POST submit response).\n */\nexport interface BackendCommandResponse {\n action?: string;\n stepCode?: string;\n message?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// INTERNAL STATE MACHINE TYPES (exported for module authors)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * All possible states of the SDK's internal lifecycle.\n * Transitions are strictly unidirectional (no going back to IDLE once started).\n *\n * @internal\n */\nexport type SdkStatus =\n | 'IDLE' // Instantiated, not yet started\n | 'INITIALIZING' // Fetching session state from backend\n | 'RUNNING' // Orchestrating steps\n | 'STEP_PROCESSING'// A module is actively running\n | 'COMPLETED' // All steps done, onSuccess fired\n | 'EXITED' // User left the flow via back on the first step, onExit fired\n | 'ERROR' // Unrecoverable error, onError fired\n | 'DESTROYED'; // destroy() was called, instance is a no-op\n\n/**\n * Internal event bus event names. Typed via `SdkEventMap`.\n * @internal\n */\nexport type SdkEventName =\n | 'step:change'\n | 'step:complete'\n | 'session:loaded'\n | 'flow:complete'\n | 'flow:exit'\n | 'error'\n | 'status:change';\n\n/**\n * Maps each internal event name to its payload type.\n * Used by the `TypedEventEmitter` for compile-time safety.\n * @internal\n */\nexport interface SdkEventMap extends Record<string, unknown> {\n 'step:change': { step: OnboardingStep; index: number };\n 'step:complete': { step: OnboardingStep };\n 'session:loaded': SessionState;\n 'flow:complete': OnboardingResult;\n 'flow:exit': OnboardingExit;\n 'error': SdkError;\n 'status:change': SdkStatus;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// LOADER ↔ CDN APP POSTMESSAGE PROTOCOL (Sumsub-style)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Message type: parent (loader) sends config to iframe (CDN app). */\nexport const VELION_MSG_INIT = 'VELION_INIT' as const;\n\n/** Message types: iframe (CDN app) sends to parent (loader). */\nexport const VELION_MSG_READY = 'VELION_READY' as const;\nexport const VELION_MSG_SUCCESS = 'VELION_SUCCESS' as const;\nexport const VELION_MSG_ERROR = 'VELION_ERROR' as const;\nexport const VELION_MSG_STEP_CHANGE = 'VELION_STEP_CHANGE' as const;\nexport const VELION_MSG_STEP_COMPLETE = 'VELION_STEP_COMPLETE' as const;\nexport const VELION_MSG_TOKEN_EXPIRED = 'VELION_TOKEN_EXPIRED' as const;\n/**\n * Iframe → parent: the user left the flow via back on the first reversible step\n * (`ROUTING_COMMAND / BACK_EXIT`). The loader forwards this to the host's\n * `onExit` callback so it can dismiss the SDK surface.\n */\nexport const VELION_MSG_EXIT = 'VELION_EXIT' as const;\n/**\n * Iframe → parent: the LIVENESS_MATCH step uploaded the recorded session video\n * (GCS PUT returned 200). The loader forwards the {@link LivenessVideoRef} to\n * the host's `onLivenessVideoUploaded` callback. Best-effort: never sent when\n * recording was disabled or the upload failed.\n */\nexport const VELION_MSG_LIVENESS_VIDEO_UPLOADED = 'VELION_LIVENESS_VIDEO_UPLOADED' as const;\n\n/** Message type: parent sends new token to iframe after VELION_TOKEN_EXPIRED. */\nexport const VELION_MSG_TOKEN = 'VELION_TOKEN' as const;\n\nexport type VelionMessageType =\n | typeof VELION_MSG_INIT\n | typeof VELION_MSG_READY\n | typeof VELION_MSG_SUCCESS\n | typeof VELION_MSG_ERROR\n | typeof VELION_MSG_STEP_CHANGE\n | typeof VELION_MSG_STEP_COMPLETE\n | typeof VELION_MSG_TOKEN_EXPIRED\n | typeof VELION_MSG_EXIT\n | typeof VELION_MSG_LIVENESS_VIDEO_UPLOADED\n | typeof VELION_MSG_TOKEN;\n\n/** Envelope for all loader ↔ iframe messages. Both sides must validate origin. */\nexport interface VelionMessage<T = unknown> {\n type: VelionMessageType;\n payload: T;\n}\n\n/** Payload for VELION_INIT: full config (iframe runs with containerId overwritten to root id). */\nexport type VelionInitPayload = VelionConfig;\n\n/** Payload for VELION_STEP_CHANGE. */\nexport interface VelionStepChangePayload {\n step: OnboardingStep;\n stepIndex: number;\n}\n\n/** Payload for VELION_STEP_COMPLETE. */\nexport interface VelionStepCompletePayload {\n step: OnboardingStep;\n}\n\n/** Payload for VELION_EXIT (mirrors {@link OnboardingExit}). */\nexport type VelionExitPayload = OnboardingExit;\n\n/** Payload for VELION_LIVENESS_VIDEO_UPLOADED (mirrors {@link LivenessVideoRef}). */\nexport type VelionLivenessVideoUploadedPayload = LivenessVideoRef;\n\n/** Payload for VELION_TOKEN (token refresh response from parent to iframe). */\nexport interface VelionTokenPayload {\n sessionToken: string;\n expiresAt?: string;\n}\n","/**\n * @fileoverview Signed-manifest verification for the Velion CDN bundle.\n *\n * Threat model:\n * The loader is published on npm and embedded in customer pages. The full SDK\n * ships from a CDN at runtime. If the CDN is compromised (BGP hijack, bucket\n * takeover, supply-chain on the build pipeline), an attacker could swap the\n * iframe HTML / JS and exfiltrate KYC documents, selfies, and session tokens.\n *\n * Mitigation: every CDN release ships a `manifest.json` that names the\n * entry HTML and asset SHA-384 digests, plus a detached `manifest.sig`\n * (Ed25519 signature over the canonical bytes of `manifest.json`). The\n * loader carries the corresponding Ed25519 *public* key and verifies the\n * signature before letting the iframe load. A compromised CDN cannot\n * forge a manifest without also obtaining the private key — which lives\n * in a KMS keyring outside the CDN's blast radius.\n *\n * Rotation:\n * The public key is embedded in the loader bundle published to npm. Rotation\n * means publishing a new loader version with the new public key. Old loaders\n * still verify against the previous key while customers upgrade.\n */\n\n/**\n * Ed25519 public key used to verify `manifest.sig`, base64-encoded raw bytes.\n *\n * Empty string means \"verification disabled\" — accepted only as a transitional\n * mode while the signing pipeline is being deployed. Production loaders MUST\n * ship with this populated before being published to npm. Replace via the\n * release workflow that fetches the pubkey from KMS metadata.\n */\nexport const VELION_MANIFEST_PUBKEY_B64 = 'lpqLG4Ta0YyBn/+lQh95tFl4CuTIHWapeeq7+xnPH+U=';\n\n/** Schema of the signed `manifest.json` emitted by the CDN build pipeline. */\nexport interface VelionCdnManifest {\n /** Loader-major path token, e.g. `\"v1\"`. Must match the loader's `LOADER_MAJOR_VERSION`. */\n version: string;\n /** ISO-8601 timestamp of the build. */\n buildTime: string;\n /** Path to the iframe entry HTML, relative to `cdnUrl`. */\n entry: string;\n /** Optional human-readable app version (matches the app's `package.json`). */\n appVersion?: string;\n /** Optional asset list with SHA-384 integrity digests for defense-in-depth. */\n assets?: Array<{ url: string; integrity: string }>;\n}\n\n/** Distinguishes \"manifest absent\" (legacy CDN) from \"manifest invalid\" (compromised). */\nexport type ManifestLoadResult =\n | { ok: true; manifest: VelionCdnManifest; signed: boolean }\n | { ok: false; reason: 'unavailable' | 'invalid_signature' | 'malformed'; detail?: string };\n\nfunction base64ToArrayBuffer(b64: string): ArrayBuffer {\n const bin = atob(b64);\n const buf = new ArrayBuffer(bin.length);\n const view = new Uint8Array(buf);\n for (let i = 0; i < bin.length; i++) {\n view[i] = bin.charCodeAt(i);\n }\n return buf;\n}\n\nasync function importPublicKey(rawB64: string): Promise<CryptoKey> {\n const raw = base64ToArrayBuffer(rawB64);\n // Web Crypto Ed25519 is implemented in Chrome 113+, Firefox 130+, Safari 17+.\n // Older browsers throw NotSupportedError — we surface that as `invalid_signature`\n // so the loader refuses to start rather than silently bypassing verification.\n return crypto.subtle.importKey(\n 'raw',\n raw,\n { name: 'Ed25519' } as AlgorithmIdentifier,\n false,\n ['verify'],\n );\n}\n\nasync function verifySignature(\n publicKey: CryptoKey,\n signature: ArrayBuffer,\n data: ArrayBuffer,\n): Promise<boolean> {\n return crypto.subtle.verify(\n { name: 'Ed25519' } as AlgorithmIdentifier,\n publicKey,\n signature,\n data,\n );\n}\n\n/**\n * Fetches `${cdnUrl}/manifest.json` (and `manifest.sig` when verification is enabled).\n *\n * Returns:\n * - `{ ok:true, signed:true }` when the signature verifies against the embedded pubkey.\n * - `{ ok:true, signed:false }` when the manifest exists but verification is disabled\n * (transitional dev/staging mode where `VELION_MANIFEST_PUBKEY_B64` is empty).\n * - `{ ok:false, reason:'unavailable' }` when the CDN has no manifest yet — the\n * loader treats this as legacy mode and may proceed if explicitly allowed.\n * - `{ ok:false, reason:'invalid_signature' | 'malformed' }` when the manifest\n * is present but tampered or syntactically invalid. The loader MUST refuse\n * to mount the iframe in this case.\n */\nexport async function loadAndVerifyManifest(\n cdnUrl: string,\n pubKeyB64: string = VELION_MANIFEST_PUBKEY_B64,\n): Promise<ManifestLoadResult> {\n let manifestBuffer: ArrayBuffer;\n let manifestText: string;\n try {\n const resp = await fetch(`${cdnUrl}/manifest.json`, {\n credentials: 'omit',\n cache: 'no-cache',\n redirect: 'error',\n });\n if (resp.status === 404) {\n return { ok: false, reason: 'unavailable' };\n }\n if (!resp.ok) {\n return { ok: false, reason: 'unavailable', detail: `HTTP ${resp.status}` };\n }\n manifestBuffer = await resp.arrayBuffer();\n manifestText = new TextDecoder().decode(manifestBuffer);\n } catch (err) {\n return { ok: false, reason: 'unavailable', detail: (err as Error).message };\n }\n\n let manifest: VelionCdnManifest;\n try {\n manifest = JSON.parse(manifestText) as VelionCdnManifest;\n if (\n typeof manifest.version !== 'string' ||\n typeof manifest.entry !== 'string' ||\n typeof manifest.buildTime !== 'string'\n ) {\n return { ok: false, reason: 'malformed' };\n }\n } catch (err) {\n return { ok: false, reason: 'malformed', detail: (err as Error).message };\n }\n\n if (!pubKeyB64) {\n // Pubkey not yet provisioned on this loader release. Accept the manifest\n // unsigned but flag it so the caller can decide whether to proceed.\n return { ok: true, manifest, signed: false };\n }\n\n let sigBuffer: ArrayBuffer;\n try {\n const sigResp = await fetch(`${cdnUrl}/manifest.sig`, {\n credentials: 'omit',\n cache: 'no-cache',\n redirect: 'error',\n });\n if (!sigResp.ok) {\n return { ok: false, reason: 'invalid_signature', detail: `sig HTTP ${sigResp.status}` };\n }\n const sigText = (await sigResp.text()).trim();\n sigBuffer = base64ToArrayBuffer(sigText);\n } catch (err) {\n return { ok: false, reason: 'invalid_signature', detail: (err as Error).message };\n }\n\n let pubKey: CryptoKey;\n try {\n pubKey = await importPublicKey(pubKeyB64);\n } catch (err) {\n return { ok: false, reason: 'invalid_signature', detail: `pubkey import failed: ${(err as Error).message}` };\n }\n\n let valid: boolean;\n try {\n valid = await verifySignature(pubKey, sigBuffer, manifestBuffer);\n } catch (err) {\n return { ok: false, reason: 'invalid_signature', detail: (err as Error).message };\n }\n if (!valid) {\n return { ok: false, reason: 'invalid_signature' };\n }\n\n return { ok: true, manifest, signed: true };\n}\n","/**\n * Thin client (loader) for Velion Onboarding.\n * Creates an iframe pointing to the CDN app and bridges config/events via postMessage.\n * Follows the same pattern as Sumsub WebSDK: init → iframe loads → postMessage config → events.\n */\n\nimport type {\n VelionConfig,\n OnboardingResult,\n SdkError,\n SdkStatus,\n OnboardingStep,\n} from './types';\nimport {\n SdkErrorCode,\n VELION_MSG_INIT,\n VELION_MSG_READY,\n VELION_MSG_SUCCESS,\n VELION_MSG_ERROR,\n VELION_MSG_STEP_CHANGE,\n VELION_MSG_STEP_COMPLETE,\n VELION_MSG_TOKEN_EXPIRED,\n VELION_MSG_EXIT,\n VELION_MSG_LIVENESS_VIDEO_UPLOADED,\n VELION_MSG_TOKEN,\n type VelionMessage,\n type OnboardingExit,\n type LivenessVideoRef,\n} from './types';\nimport {\n loadAndVerifyManifest,\n VELION_MANIFEST_PUBKEY_B64,\n type VelionCdnManifest,\n} from './manifest';\n\n/**\n * Product slug under the shared `static.velion.la/sdk/` namespace. This identity\n * SDK lives at `sdk/onboarding/`; future Velion SDKs (e.g. payments, auth) get\n * their own slug so they can be deployed independently from the same CDN.\n */\nexport const LOADER_PRODUCT = 'onboarding' as const;\n\n/**\n * Major version of this loader. Locked into the CDN path so a breaking change\n * to the loader↔app postMessage contract requires publishing loader-`v2`\n * against `/sdk/onboarding/v2/`. Customers can keep older loaders for as long\n * as the matching CDN path is served.\n */\nexport const LOADER_MAJOR_VERSION = 'v1' as const;\n\n/** Default CDN base URL for the onboarding app (no trailing slash). */\nexport const DEFAULT_CDN_URL =\n `https://static.velion.la/sdk/${LOADER_PRODUCT}/${LOADER_MAJOR_VERSION}`;\n\n/**\n * Used only while the host container has no laid-out height yet (e.g. `display: none`\n * or first paint). Replaced by a pixel height as soon as `container.clientHeight > 0`.\n */\nconst IFRAME_FALLBACK_HEIGHT_CSS = 'min(85vh, 880px)';\n\n/** Loader config: VelionConfig plus optional CDN URL and token refresh callback. */\nexport interface VelionLoaderConfig extends VelionConfig {\n /**\n * Base URL of the CDN where the onboarding app is served (e.g. https://static.velion.la/sdk).\n * The loader fetches `${cdnUrl}/manifest.json` (signed with Ed25519), verifies the\n * signature against the loader's embedded public key, and only then loads the\n * iframe entry named in the manifest.\n * @default 'https://static.velion.la/sdk'\n */\n cdnUrl?: string;\n\n /**\n * When `true`, lets the loader proceed against a CDN that does not yet expose a\n * signed `manifest.json` — for staging or migration windows. Has NO effect when\n * the loader carries an embedded pubkey AND the manifest exists but signature\n * verification fails (that case is always fatal).\n *\n * Default `false`: production loaders refuse to mount the iframe without a\n * verified manifest once the pubkey has been provisioned.\n *\n * @default false\n */\n allowUnverifiedCdn?: boolean;\n\n /**\n * Called when the iframe requests a new session token (e.g. after SESSION_EXPIRED).\n * Return the new token and optional expiresAt; the CDN app will continue the flow.\n */\n onTokenExpired?: () => Promise<{ sessionToken: string; expiresAt?: string }>;\n\n /**\n * How long to wait, after the iframe document loaded, for the runtime to\n * report `VELION_READY`. Past it the loader emits `RUNTIME_LOAD_FAILED`\n * instead of leaving the host with a blank iframe and no callback (e.g.\n * when the CDN entry points at assets that 404).\n *\n * @default 30000\n */\n runtimeReadyTimeoutMs?: number;\n}\n\nconst DEFAULT_RUNTIME_READY_TIMEOUT_MS = 30_000;\n\nfunction parseOriginFromUrl(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return '';\n }\n}\n\n/**\n * Allows `https:` and `wss:` everywhere, plus plain `http:`/`ws:` only when the host\n * is a loopback / `.local` address (developer machines and CI). Used to prevent\n * accidental cleartext bearer-token transmission to a misconfigured `apiUrl`/`cdnUrl`.\n */\nfunction isLocalhostHost(host: string): boolean {\n return host === 'localhost' || host === '127.0.0.1' || host === '::1' || host.endsWith('.local');\n}\n\nexport function isSecureHttpUrl(url: string): boolean {\n try {\n const u = new URL(url);\n if (u.protocol === 'https:') return true;\n if (u.protocol === 'http:') return isLocalhostHost(u.hostname);\n return false;\n } catch {\n return false;\n }\n}\n\n/** Keys of VelionConfig that are safe to send via postMessage (no functions). */\nconst SERIALIZABLE_CONFIG_KEYS: (keyof VelionConfig)[] = [\n 'containerId',\n 'sessionId',\n 'sessionToken',\n 'apiUrl',\n 'expiresAt',\n 'theme',\n 'colorMode',\n 'locale',\n 'allowEmbedded',\n 'livenessShowIntro',\n 'livenessShowBackButton',\n 'fingerprintEndpoint',\n 'debug',\n 'googleMapsApiKey',\n 'showFinalResultScreen',\n 'showProgressBar',\n 'finalResultScreenTexts',\n 'routingTimeoutMs',\n];\n\nfunction buildSerializableConfig(\n config: Readonly<VelionLoaderConfig>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {\n containerId: 'velion-sdk-root',\n allowEmbedded: true,\n };\n for (const key of SERIALIZABLE_CONFIG_KEYS) {\n if (key === 'containerId' || key === 'allowEmbedded') continue;\n const value = config[key as keyof typeof config];\n if (value !== undefined && typeof value !== 'function') {\n out[key] = value;\n }\n }\n return out;\n}\n\nexport class VelionOnboarding {\n private readonly config: Readonly<VelionLoaderConfig>;\n private status: SdkStatus = 'IDLE';\n private iframe: HTMLIFrameElement | null = null;\n private container: HTMLElement | null = null;\n private messageHandler: ((e: MessageEvent) => void) | null = null;\n private containerResizeObserver: ResizeObserver | null = null;\n private iframeHeightWindowResizeHandler: (() => void) | null = null;\n private cdnOrigin = '';\n /** Armed when VELION_INIT is sent; cleared by VELION_READY or destroy(). */\n private readyTimer: ReturnType<typeof setTimeout> | null = null;\n\n private clearReadyTimer(): void {\n if (this.readyTimer !== null) {\n clearTimeout(this.readyTimer);\n this.readyTimer = null;\n }\n }\n\n private armReadyTimer(cdnUrl: string): void {\n this.clearReadyTimer();\n const ms = this.config.runtimeReadyTimeoutMs ?? DEFAULT_RUNTIME_READY_TIMEOUT_MS;\n if (!(ms > 0)) return;\n this.readyTimer = setTimeout(() => {\n this.readyTimer = null;\n if (this.status !== 'RUNNING') return; // already READY-progressed, errored or destroyed\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.RUNTIME_LOAD_FAILED,\n message:\n `The onboarding runtime at ${cdnUrl} did not become ready within ${ms} ms. ` +\n 'Check that the CDN serves the assets referenced by its index.html and that the host ' +\n \"page's Content-Security-Policy allows the runtime scripts.\",\n });\n }, ms);\n }\n\n constructor(config: VelionLoaderConfig) {\n this.config = Object.freeze({ ...config });\n }\n\n async start(): Promise<void> {\n if (this.status === 'DESTROYED') {\n this.config.onError?.({\n code: SdkErrorCode.INSTANCE_DESTROYED,\n message:\n 'This VelionOnboarding instance has been destroyed. Create a new instance to retry.',\n });\n return;\n }\n\n if (this.status !== 'IDLE') {\n this.config.onError?.({\n code: SdkErrorCode.ALREADY_STARTED,\n message:\n 'start() was called on an already-running SDK instance. Call destroy() before re-starting.',\n });\n return;\n }\n\n const container = document.getElementById(this.config.containerId);\n if (!container) {\n this.config.onError?.({\n code: SdkErrorCode.CONTAINER_NOT_FOUND,\n message: `No DOM element found with id=\"${this.config.containerId}\". Ensure the element exists before calling start().`,\n });\n return;\n }\n\n const cdnUrl = (this.config.cdnUrl ?? DEFAULT_CDN_URL).replace(/\\/$/, '');\n if (!isSecureHttpUrl(cdnUrl)) {\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Insecure cdnUrl: \"${cdnUrl}\". Must be https:// (http:// is allowed only for localhost).`,\n });\n return;\n }\n if (this.config.apiUrl && !isSecureHttpUrl(this.config.apiUrl)) {\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Insecure apiUrl: \"${this.config.apiUrl}\". Must be https:// (http:// is allowed only for localhost).`,\n });\n return;\n }\n this.cdnOrigin = parseOriginFromUrl(cdnUrl);\n if (!this.cdnOrigin) {\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Invalid cdnUrl: ${cdnUrl}. Must be a valid absolute URL.`,\n });\n return;\n }\n\n this.container = container;\n this.status = 'INITIALIZING';\n\n const iframe = document.createElement('iframe');\n iframe.setAttribute('title', 'Velion Onboarding');\n // The SDK only needs the camera. Microphone and geolocation are intentionally not granted.\n iframe.setAttribute('allow', 'camera');\n // Sandbox limits blast radius of a CDN compromise. Tokens we keep:\n // allow-scripts — the iframe is a JS app\n // allow-same-origin — needed for fetch to same-origin assets and for postMessage to behave\n // allow-forms — file pickers and form submissions for OCR upload flows\n // allow-popups — Google Maps / Places attribution links open in new tab\n // allow-modals — getUserMedia permission prompts and document picker\n iframe.setAttribute(\n 'sandbox',\n 'allow-scripts allow-same-origin allow-forms allow-popups allow-modals',\n );\n // Avoid leaking host page paths to the CDN logs.\n iframe.setAttribute('referrerpolicy', 'no-referrer');\n iframe.style.width = '100%';\n iframe.style.border = 'none';\n iframe.style.display = 'block';\n /* Height is applied in attachIframeHeightSync (pixel match + internal fallback). */\n\n const messageHandler = (e: MessageEvent): void => {\n // Origin AND source must match: a sibling iframe from the same CDN origin\n // (e.g. opened by the host page) must not be able to spoof events.\n if (e.origin !== this.cdnOrigin) return;\n if (e.source !== this.iframe?.contentWindow) return;\n const msg = e.data as VelionMessage | undefined;\n if (!msg || typeof msg.type !== 'string') return;\n\n // Any message that passed the origin+source check came from a running\n // runtime, so none of them may be followed by RUNTIME_LOAD_FAILED. READY is\n // the expected first message, but a runtime that reports a step or an\n // error first is alive just the same.\n this.clearReadyTimer();\n\n switch (msg.type) {\n case VELION_MSG_READY:\n this.status = 'RUNNING';\n break;\n case VELION_MSG_SUCCESS:\n this.status = 'COMPLETED';\n this.config.onSuccess?.(msg.payload as OnboardingResult);\n break;\n case VELION_MSG_ERROR:\n this.status = 'ERROR';\n this.config.onError?.(msg.payload as SdkError);\n break;\n case VELION_MSG_STEP_CHANGE: {\n const p = msg.payload as { step: OnboardingStep; stepIndex: number };\n this.config.onStepChange?.(p.step, p.stepIndex);\n break;\n }\n case VELION_MSG_STEP_COMPLETE: {\n const p = msg.payload as { step: OnboardingStep };\n this.config.onStepComplete?.(p.step);\n break;\n }\n case VELION_MSG_EXIT: {\n // User backed out of the first reversible step: the flow ended without\n // a verdict. Forward to the host so it can dismiss the SDK surface.\n this.status = 'EXITED';\n this.config.onExit?.(msg.payload as OnboardingExit);\n break;\n }\n case VELION_MSG_LIVENESS_VIDEO_UPLOADED: {\n // Evidence signal from the LIVENESS_MATCH step (OSDKW-11). Informational\n // only: the flow never depends on it, so no status change.\n this.config.onLivenessVideoUploaded?.(msg.payload as LivenessVideoRef);\n break;\n }\n case VELION_MSG_TOKEN_EXPIRED: {\n const refresh = this.config.onTokenExpired;\n if (refresh) {\n refresh().then(\n (t) => this.sendToIframe({ type: VELION_MSG_TOKEN, payload: t }),\n () => {\n this.config.onError?.({\n code: SdkErrorCode.SESSION_EXPIRED,\n message: 'Token refresh failed.',\n });\n }\n );\n } else {\n this.config.onError?.({\n code: SdkErrorCode.SESSION_EXPIRED,\n message: 'Session token expired. Provide onTokenExpired in config to refresh.',\n });\n }\n break;\n }\n default:\n break;\n }\n };\n\n window.addEventListener('message', messageHandler);\n this.messageHandler = messageHandler;\n this.iframe = iframe;\n\n container.replaceChildren(iframe);\n this.attachIframeHeightSync(container, iframe);\n\n // ── Verify the CDN manifest before mounting the iframe ─────────────────\n const manifest = await this.resolveManifest(cdnUrl);\n if (!manifest) {\n // resolveManifest already invoked onError. Stop here so we don't load\n // an unverified bundle.\n return;\n }\n const entryPath = manifest.entry.startsWith('/') ? manifest.entry : `/${manifest.entry}`;\n\n return new Promise<void>((resolve) => {\n iframe.onload = (): void => {\n if (!this.iframe?.contentWindow) {\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: 'Iframe failed to load.',\n });\n resolve();\n return;\n }\n // postMessage cannot clone functions. Sumsub-style: send only JSON-serializable config.\n // Clone via JSON so no function can slip through (e.g. from cached bundle or nested props).\n const initPayload = buildSerializableConfig(this.config);\n let payloadForPostMessage: unknown;\n try {\n payloadForPostMessage = JSON.parse(JSON.stringify(initPayload));\n } catch {\n payloadForPostMessage = initPayload;\n }\n this.sendToIframe({ type: VELION_MSG_INIT, payload: payloadForPostMessage });\n this.status = 'RUNNING';\n // The document loaded, but a 200 index.html whose scripts 404 (or a CSP\n // that blocks them) never answers VELION_INIT. Bound the wait so the\n // host gets an error instead of a silent blank iframe.\n this.armReadyTimer(cdnUrl);\n resolve();\n };\n\n iframe.onerror = (): void => {\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.UNKNOWN,\n message: `Failed to load onboarding app from ${cdnUrl}. Check cdnUrl and network.`,\n });\n resolve();\n };\n\n iframe.src = `${cdnUrl}${entryPath}`;\n });\n }\n\n /**\n * Fetches and verifies the CDN manifest. Returns `null` and emits onError when\n * the manifest is missing in production mode, or when the signature is invalid.\n */\n private async resolveManifest(cdnUrl: string): Promise<VelionCdnManifest | null> {\n let result;\n try {\n result = await loadAndVerifyManifest(cdnUrl);\n } catch (err) {\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message: `Manifest verification crashed unexpectedly: ${(err as Error).message}`,\n });\n return null;\n }\n if (result.ok) {\n if (!result.signed && VELION_MANIFEST_PUBKEY_B64) {\n // Should not happen — pubkey is set, manifest exists, yet not signed.\n // loadAndVerifyManifest only marks `signed:false` when pubkey is empty.\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message: 'Internal: manifest reported as unsigned despite pubkey being set.',\n });\n return null;\n }\n return result.manifest;\n }\n if (result.reason === 'unavailable') {\n // Manifest endpoint is missing. Permitted only when the loader is configured\n // to tolerate it OR when the loader was published without a pubkey (transitional).\n const tolerateMissing =\n this.config.allowUnverifiedCdn === true || !VELION_MANIFEST_PUBKEY_B64;\n if (tolerateMissing) {\n return {\n version: 'legacy',\n buildTime: new Date().toISOString(),\n entry: '/index.html',\n };\n }\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message:\n `CDN at \"${cdnUrl}\" did not expose /manifest.json. The loader is configured ` +\n 'to require a signed manifest. If the CDN is migrating, set `allowUnverifiedCdn: true` ' +\n 'on the loader config — but only as a temporary measure.',\n });\n return null;\n }\n // 'invalid_signature' or 'malformed' — always fatal, even when allowUnverifiedCdn is true.\n this.status = 'ERROR';\n this.config.onError?.({\n code: SdkErrorCode.INTEGRITY_FAILED,\n message:\n `CDN manifest at \"${cdnUrl}/manifest.json\" failed integrity check (${result.reason}). ` +\n 'Refusing to load the iframe to protect the user. ' +\n ('detail' in result && result.detail ? `Detail: ${result.detail}` : ''),\n });\n return null;\n }\n\n /**\n * Keep the iframe exactly as tall as the host container so integrators do not need\n * `height: 100%` on the iframe or flex hacks—only a definite block-size on the\n * container (e.g. `height: 700px` or `height: 100vh`), similar to Sumsub WebSDK.\n *\n * Until the container has a non-zero `clientHeight`, apply an internal fallback\n * height so the SDK is usable even before layout or if only `min-height` is set.\n */\n private attachIframeHeightSync(\n container: HTMLElement,\n iframe: HTMLIFrameElement,\n ): void {\n const sync = (): void => {\n if (this.iframe !== iframe || this.container !== container) return;\n const h = container.clientHeight;\n if (h > 0) {\n iframe.style.height = `${h}px`;\n iframe.style.minHeight = '';\n } else {\n iframe.style.height = IFRAME_FALLBACK_HEIGHT_CSS;\n iframe.style.minHeight = '';\n }\n };\n sync();\n requestAnimationFrame(sync);\n requestAnimationFrame(() => {\n requestAnimationFrame(sync);\n });\n if (typeof ResizeObserver !== 'undefined') {\n this.containerResizeObserver = new ResizeObserver(sync);\n this.containerResizeObserver.observe(container);\n } else {\n this.iframeHeightWindowResizeHandler = sync;\n window.addEventListener('resize', sync);\n }\n }\n\n private detachIframeHeightSync(): void {\n if (this.containerResizeObserver) {\n this.containerResizeObserver.disconnect();\n this.containerResizeObserver = null;\n }\n if (this.iframeHeightWindowResizeHandler) {\n window.removeEventListener('resize', this.iframeHeightWindowResizeHandler);\n this.iframeHeightWindowResizeHandler = null;\n }\n }\n\n private sendToIframe(msg: VelionMessage): void {\n if (this.iframe?.contentWindow) {\n this.iframe.contentWindow.postMessage(msg, this.cdnOrigin);\n }\n }\n\n getStatus(): SdkStatus {\n return this.status;\n }\n\n static getVersion(): string {\n return typeof __SDK_VERSION__ !== 'undefined' ? __SDK_VERSION__ : '0.0.0';\n }\n\n destroy(): void {\n if (this.status === 'DESTROYED') return;\n this.clearReadyTimer();\n this.detachIframeHeightSync();\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.iframe && this.container) {\n this.iframe.remove();\n this.container.replaceChildren();\n }\n this.iframe = null;\n this.container = null;\n this.status = 'DESTROYED';\n }\n}\n","/**\n * @fileoverview Public API of the Velion Onboarding SDK (thin client / loader).\n *\n * This package is the loader only: it creates an iframe that loads the full\n * onboarding app from the CDN (e.g. https://static.velion.la/sdk) and bridges\n * config and events via postMessage. No React, OpenCV, or Fingerprint\n * are bundled here — minimal size, same API as before.\n */\n\nexport { VelionOnboarding, DEFAULT_CDN_URL, type VelionLoaderConfig } from './velion-loader';\n\n/**\n * No-op when using the loader: OpenCV is loaded inside the CDN app.\n * Kept for API compatibility; configure OpenCV in the CDN app build if needed.\n */\nexport function setOpenCVBaseUrl(_url: string): void {}\n\nexport {\n SdkErrorCode,\n CameraErrorCode,\n VELION_MSG_INIT,\n VELION_MSG_READY,\n VELION_MSG_SUCCESS,\n VELION_MSG_ERROR,\n VELION_MSG_STEP_CHANGE,\n VELION_MSG_STEP_COMPLETE,\n VELION_MSG_TOKEN_EXPIRED,\n VELION_MSG_EXIT,\n VELION_MSG_LIVENESS_VIDEO_UPLOADED,\n VELION_MSG_TOKEN,\n type VelionConfig,\n type VelionTheme,\n type VelionFinalResultStatusCopy,\n type VelionFinalResultScreenTexts,\n type VelionSessionDeclinedReasonMessages,\n type VelionSessionDeclinedReasonTexts,\n type VelionRetryStepTexts,\n type VelionCallbacks,\n type OnboardingExit,\n type OnboardingStep,\n type OnboardingStepStatus,\n type OnboardingType,\n type DocumentSide,\n type DocumentCaptureMode,\n type DocumentsConfig,\n type TaxInfoConfig,\n type TaxInfoOption,\n type DocumentsInitRequest,\n type DocumentsInitResponse,\n type SessionState,\n type OnboardingResult,\n type OnboardingStepDto,\n type StepFieldRule,\n type HumanBasicInfoStepConfig,\n type HumanExtendedInfoStepConfig,\n type TaxInfoStepConfig,\n type AddressInfoStepConfig,\n type SwornDeclarationStepConfig,\n type DocumentsOcrStepConfig,\n type TermsAndConditionsStepConfig,\n type StepConfigMap,\n type SdkError,\n type SdkStatus,\n type LivenessSessionResponse,\n type VideoUpload,\n type LivenessVerifyVideoMeta,\n type LivenessVideoRef,\n type SessionCreationResponse,\n type HumanBasicInfoPayload,\n type LegalBasicInfoPayload,\n type TermsAndConditionsItem,\n type TaxInfoPayload,\n type HumanExtendedInfoPayload,\n type LegalExtendedInfoPayload,\n type AddressInfoPayload,\n type SwornDeclarationPayload,\n type EconomicActivityItem,\n type EconomicActivityStepConfig,\n type EconomicActivitySelectionPayload,\n type EmailContactPayload,\n type PhoneContactPayload,\n type TermsAndConditionsSubmitPayload,\n type TermsAndConditionsAcceptancePayload,\n type OnboardingSessionEvent,\n type OnboardingDtoResponse,\n type VelionMessage,\n type VelionMessageType,\n type VelionInitPayload,\n type VelionStepChangePayload,\n type VelionStepCompletePayload,\n type VelionExitPayload,\n type VelionLivenessVideoUploadedPayload,\n type VelionTokenPayload,\n} from './types';\n"],"names":["CameraErrorCode","SdkErrorCode","VELION_MSG_INIT","VELION_MSG_READY","VELION_MSG_SUCCESS","VELION_MSG_ERROR","VELION_MSG_STEP_CHANGE","VELION_MSG_STEP_COMPLETE","VELION_MSG_TOKEN_EXPIRED","VELION_MSG_EXIT","VELION_MSG_LIVENESS_VIDEO_UPLOADED","VELION_MSG_TOKEN","VELION_MANIFEST_PUBKEY_B64","base64ToArrayBuffer","b64","bin","atob","buf","ArrayBuffer","length","view","Uint8Array","i","charCodeAt","async","loadAndVerifyManifest","cdnUrl","pubKeyB64","manifestBuffer","manifestText","manifest","sigBuffer","pubKey","valid","resp","fetch","credentials","cache","redirect","status","ok","reason","detail","arrayBuffer","TextDecoder","decode","err","message","JSON","parse","version","entry","buildTime","signed","sigResp","text","trim","rawB64","raw","crypto","subtle","importKey","name","importPublicKey","publicKey","signature","data","verify","verifySignature","DEFAULT_CDN_URL","isSecureHttpUrl","url","u","URL","protocol","host","hostname","endsWith","SERIALIZABLE_CONFIG_KEYS","constructor","config","this","iframe","container","messageHandler","containerResizeObserver","iframeHeightWindowResizeHandler","cdnOrigin","readyTimer","Object","freeze","clearReadyTimer","clearTimeout","armReadyTimer","ms","runtimeReadyTimeoutMs","setTimeout","onError","code","RUNTIME_LOAD_FAILED","start","INSTANCE_DESTROYED","ALREADY_STARTED","document","getElementById","containerId","CONTAINER_NOT_FOUND","replace","UNKNOWN","apiUrl","origin","parseOriginFromUrl","createElement","setAttribute","style","width","border","display","e","source","contentWindow","msg","type","onSuccess","payload","p","onStepChange","step","stepIndex","onStepComplete","onExit","onLivenessVideoUploaded","refresh","onTokenExpired","then","t","sendToIframe","SESSION_EXPIRED","window","addEventListener","replaceChildren","attachIframeHeightSync","resolveManifest","entryPath","startsWith","Promise","resolve","onload","initPayload","out","allowEmbedded","key","value","buildSerializableConfig","payloadForPostMessage","stringify","onerror","src","result","INTEGRITY_FAILED","allowUnverifiedCdn","Date","toISOString","sync","h","clientHeight","height","minHeight","requestAnimationFrame","ResizeObserver","observe","detachIframeHeightSync","disconnect","removeEventListener","postMessage","getStatus","getVersion","destroy","remove","_url"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velion-la/onboarding-sdk-web",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.25",
|
|
4
4
|
"description": "Web SDK for the Velion Onboarding (KYC) flow. Thin client that boots the full verification experience (ID document OCR, passive + active liveness, device fingerprint, OTP verification, address picker, and personal / legal information forms) inside an embedded iframe served from the Velion CDN.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/velion-sdk.umd.js",
|