@velion-la/onboarding-sdk-web 1.0.0-beta.17 → 1.0.0-beta.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -24
- package/dist/velion-sdk.es.js +1 -1
- package/dist/velion-sdk.umd.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# @velion-la/onboarding-sdk (Loader)
|
|
1
|
+
# @velion-la/onboarding-sdk-web (Loader)
|
|
2
2
|
|
|
3
|
-
Thin client for the Velion Onboarding flow. This package creates an iframe that loads the **full onboarding
|
|
3
|
+
Thin client for the Velion Onboarding flow. This package creates an iframe that loads the **full onboarding runtime** from the Velion CDN (`https://static.velion.la/sdk/onboarding/v1` by default) and bridges configuration and events via postMessage. The published bundle is ~3.5 kB gzip; the heavy UI (document capture, liveness, fingerprint) runs inside the iframe and updates without changing your integration.
|
|
4
4
|
|
|
5
|
-
[npm](https://www.npmjs.com/package/@velion-la/onboarding-sdk) · [License](./LICENSE)
|
|
5
|
+
[npm](https://www.npmjs.com/package/@velion-la/onboarding-sdk-web) · [License](./LICENSE)
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -13,7 +13,7 @@ Your backend defines which steps are required per session; the SDK (loader + app
|
|
|
13
13
|
**Key points:**
|
|
14
14
|
|
|
15
15
|
- **Zero framework dependency for consumers.** Works with Angular, Vue, React, or plain HTML.
|
|
16
|
-
- **Optional `cdnUrl`.** Default is `https://static.velion.la/sdk`. Override for
|
|
16
|
+
- **Optional `cdnUrl`.** Default is `https://static.velion.la/sdk/onboarding/v1`. Override only for the development CDN or a self-hosted runtime.
|
|
17
17
|
- **Same API.** `VelionOnboarding`, `onSuccess`, `onError`, `onStepChange`, theme, etc. unchanged.
|
|
18
18
|
|
|
19
19
|
---
|
|
@@ -21,9 +21,9 @@ Your backend defines which steps are required per session; the SDK (loader + app
|
|
|
21
21
|
## Installation
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
npm install @velion-la/onboarding-sdk
|
|
25
|
-
# or: yarn add @velion-la/onboarding-sdk
|
|
26
|
-
# or: pnpm add @velion-la/onboarding-sdk
|
|
24
|
+
npm install @velion-la/onboarding-sdk-web
|
|
25
|
+
# or: yarn add @velion-la/onboarding-sdk-web
|
|
26
|
+
# or: pnpm add @velion-la/onboarding-sdk-web
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
---
|
|
@@ -31,7 +31,7 @@ npm install @velion-la/onboarding-sdk
|
|
|
31
31
|
## Quick Start
|
|
32
32
|
|
|
33
33
|
```typescript
|
|
34
|
-
import { VelionOnboarding, SdkErrorCode } from '@velion-la/onboarding-sdk';
|
|
34
|
+
import { VelionOnboarding, SdkErrorCode } from '@velion-la/onboarding-sdk-web';
|
|
35
35
|
|
|
36
36
|
const { session_id, session_token, expires_at } = await fetch('/api/kyc/sessions', {
|
|
37
37
|
method: 'POST',
|
|
@@ -42,7 +42,7 @@ const sdk = new VelionOnboarding({
|
|
|
42
42
|
sessionId: session_id,
|
|
43
43
|
sessionToken: session_token,
|
|
44
44
|
expiresAt: expires_at,
|
|
45
|
-
apiUrl: 'https://<your-domain>.velion.la/gateway/api/v1/onboarding',
|
|
45
|
+
apiUrl: 'https://<your-domain>.velion.la/gateway/api/v1/onboarding/public/sdk',
|
|
46
46
|
locale: 'es',
|
|
47
47
|
theme: { primaryColor: '#0052CC', borderRadius: '10px' },
|
|
48
48
|
onSuccess: (result) => console.log('Done:', result.status),
|
|
@@ -81,7 +81,7 @@ or full viewport height:
|
|
|
81
81
|
}
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
More detail: [
|
|
84
|
+
More detail: [web/README.md](../web/README.md) (integration notes for the hosted runtime).
|
|
85
85
|
|
|
86
86
|
---
|
|
87
87
|
|
|
@@ -92,9 +92,9 @@ More detail: [app/README.md](../app/README.md) (integration notes for the hosted
|
|
|
92
92
|
| `containerId` | `string` | ✓ | — | `id` of the DOM element where the iframe is inserted. |
|
|
93
93
|
| `sessionId` | `string` | ✓ | — | Session identifier from your backend. |
|
|
94
94
|
| `sessionToken` | `string` | ✓ | — | Short-lived Bearer token (e.g. JWT). |
|
|
95
|
-
| `apiUrl` | `string` | ✓ | — | Gateway base URL (no trailing slash). |
|
|
96
|
-
| `cdnUrl` | `string` | | `'https://static.velion.la/sdk'
|
|
97
|
-
| `expiresAt` | `string` | | — | ISO-8601 token expiry. |
|
|
95
|
+
| `apiUrl` | `string` | ✓ | — | Gateway base URL for the SDK chain, ending in `/public/sdk` (no trailing slash, `https://`). |
|
|
96
|
+
| `cdnUrl` | `string` | | `'https://static.velion.la/sdk/onboarding/v1'` | Base URL of the onboarding runtime (iframe source). |
|
|
97
|
+
| `expiresAt` | `string` | | — | ISO-8601 token expiry (`expires_at` from session creation; 30 minutes by default). |
|
|
98
98
|
| `theme` | `VelionTheme` | | — | Primary color, border radius, etc. |
|
|
99
99
|
| `locale` | `string` | | `'es'` | BCP 47 locale for UI. |
|
|
100
100
|
| `onSuccess` | `(result: OnboardingResult) => void` | | — | Called when the flow completes. |
|
|
@@ -113,7 +113,7 @@ All other options from the full SDK (e.g. `livenessDisplayText`) are passed thro
|
|
|
113
113
|
## Instance methods
|
|
114
114
|
|
|
115
115
|
- **`sdk.start(): Promise<void>`** — Starts the flow (loads iframe, sends config).
|
|
116
|
-
- **`sdk.getStatus(): SdkStatus`** — Returns current state (`'IDLE'` \| `'RUNNING'` \| `'COMPLETED'` \| `'ERROR'` \| `'DESTROYED'`).
|
|
116
|
+
- **`sdk.getStatus(): SdkStatus`** — Returns current state (`'IDLE'` \| `'INITIALIZING'` \| `'RUNNING'` \| `'COMPLETED'` \| `'EXITED'` \| `'ERROR'` \| `'DESTROYED'`).
|
|
117
117
|
- **`sdk.destroy(): void`** — Removes the iframe and cleans up. Idempotent.
|
|
118
118
|
- **`VelionOnboarding.getVersion(): string`** — Static; returns SDK version.
|
|
119
119
|
|
|
@@ -123,13 +123,15 @@ All other options from the full SDK (e.g. `livenessDisplayText`) are passed thro
|
|
|
123
123
|
|
|
124
124
|
```html
|
|
125
125
|
<div id="kyc-container"></div>
|
|
126
|
-
|
|
126
|
+
<!-- Serve the UMD build from your own origin, e.g. copied from
|
|
127
|
+
node_modules/@velion-la/onboarding-sdk-web/dist/velion-sdk.umd.js -->
|
|
128
|
+
<script src="/vendor/velion-sdk.umd.js"></script>
|
|
127
129
|
<script>
|
|
128
130
|
const sdk = new VelionSDK.VelionOnboarding({
|
|
129
131
|
containerId: 'kyc-container',
|
|
130
132
|
sessionId: '...',
|
|
131
133
|
sessionToken: '...',
|
|
132
|
-
apiUrl: 'https://<your-domain>.velion.la/gateway/api/v1/onboarding',
|
|
134
|
+
apiUrl: 'https://<your-domain>.velion.la/gateway/api/v1/onboarding/public/sdk',
|
|
133
135
|
onSuccess: (r) => console.log(r.status),
|
|
134
136
|
onError: (e) => console.error(e.code, e.message),
|
|
135
137
|
});
|
|
@@ -143,23 +145,19 @@ The UMD bundle exposes `window.VelionSDK`.
|
|
|
143
145
|
|
|
144
146
|
## Error handling
|
|
145
147
|
|
|
146
|
-
Errors are delivered via `onError` with a `SdkError` object (`code`, `message`, optional `cameraCode`, `httpStatus`, `cause`). Common codes: `SESSION_EXPIRED`, `SESSION_FETCH_FAILED`, `CAMERA_ERROR`, `CONTAINER_NOT_FOUND`, `FINGERPRINT_FAILED`, `LIVENESS_FAILED`, etc.
|
|
148
|
+
Errors are delivered via `onError` with a `SdkError` object (`code`, `message`, optional `cameraCode`, `httpStatus`, `cause`). Common codes: `SESSION_EXPIRED`, `SESSION_FETCH_FAILED`, `CAMERA_ERROR`, `CONTAINER_NOT_FOUND`, `FINGERPRINT_FAILED`, `LIVENESS_FAILED`, etc. Session tokens last 30 minutes by default: provide `onTokenExpired` so the host can mint a new one (`POST /public/api-key/onboarding/sessions/{id}/tokens` from your backend) and the flow resumes where it was.
|
|
147
149
|
|
|
148
150
|
---
|
|
149
151
|
|
|
150
152
|
## Backend and CDN
|
|
151
153
|
|
|
152
|
-
- Your **backend**
|
|
153
|
-
- The **
|
|
154
|
+
- Your **backend** creates the session against the Velion gateway with your public API key (`POST /public/api-key/onboarding/sessions`, scope `write:onboarding`) and hands `session_id`, `session_token` and `expires_at` to the page. The loader passes `apiUrl` and `sessionToken` to the runtime; every SDK request (`GET /sessions/:id`, the SSE stream, each step's `POST`) is made from the iframe with the session JWT. Your backend never proxies those calls.
|
|
155
|
+
- The **runtime** is served from the Velion CDN at `cdnUrl` (default `https://static.velion.la/sdk/onboarding/v1`; development builds live at `https://static.development.us-central1.velion.cloud/sdk/onboarding/v1`). It is deployed by this repository's CI from the `web/` workspace, together with an Ed25519-signed `manifest.json` the loader verifies before mounting the iframe. Hosts never deploy it themselves.
|
|
154
156
|
|
|
155
157
|
---
|
|
156
158
|
|
|
157
159
|
## Publishing (maintainers)
|
|
158
160
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
1. `npm run build`
|
|
162
|
-
2. `npm version patch|minor|major`
|
|
163
|
-
3. `npm publish`
|
|
161
|
+
The loader is published by CI, not by hand: every push to `develop` publishes a pre-release under the `beta` dist-tag, and `main` publishes stable versions under `latest`. The full procedure (release branch, version bump, CDN deploy, manifest signing) is in [docs/RELEASING.md](../docs/RELEASING.md).
|
|
164
162
|
|
|
165
163
|
`files` in `package.json` includes `dist`, `README.md`, and `LICENSE`.
|
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.UNKNOWN="UNKNOWN",e))(t||{});const i="VELION_INIT",n="VELION_READY",s="VELION_SUCCESS",r="VELION_ERROR",o="VELION_STEP_CHANGE",a="VELION_STEP_COMPLETE",E="VELION_TOKEN_EXPIRED",c="VELION_EXIT",l="VELION_LIVENESS_VIDEO_UPLOADED",d="VELION_TOKEN",h="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 I(e,t=h){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 u="https://static.velion.la/sdk/onboarding/v1";function N(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 R=["containerId","sessionId","sessionToken","apiUrl","expiresAt","theme","colorMode","locale","allowEmbedded","livenessShowIntro","livenessShowBackButton","fingerprintEndpoint","debug","googleMapsApiKey","showFinalResultScreen","showProgressBar","finalResultScreenTexts"];class g{constructor(e){this.status="IDLE",this.iframe=null,this.container=null,this.messageHandler=null,this.containerResizeObserver=null,this.iframeHeightWindowResizeHandler=null,this.cdnOrigin="",this.config=Object.freeze({...e})}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??u).replace(/\/$/,"");if(!N(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&&!N(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 f=document.createElement("iframe");f.setAttribute("title","Velion Onboarding"),f.setAttribute("allow","camera"),f.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-modals"),f.setAttribute("referrerpolicy","no-referrer"),f.style.width="100%",f.style.border="none",f.style.display="block";const I=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.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 o:{const e=i.payload;this.config.onStepChange?.(e.step,e.stepIndex);break}case a:{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",I),this.messageHandler=I,this.iframe=f,e.replaceChildren(f),this.attachIframeHeightSync(e,f);const g=await this.resolveManifest(h);if(!g)return;const O=g.entry.startsWith("/")?g.entry:`/${g.entry}`;return new Promise(e=>{f.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 R){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",e()},f.onerror=()=>{this.status="ERROR",this.config.onError?.({code:t.UNKNOWN,message:`Failed to load onboarding app from ${h}. Check cdnUrl and network.`}),e()},f.src=`${h}${O}`})}async resolveManifest(e){let i;try{i=await I(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.
|
|
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.UNKNOWN="UNKNOWN",e))(t||{});const i="VELION_INIT",n="VELION_READY",s="VELION_SUCCESS",r="VELION_ERROR",o="VELION_STEP_CHANGE",a="VELION_STEP_COMPLETE",E="VELION_TOKEN_EXPIRED",c="VELION_EXIT",l="VELION_LIVENESS_VIDEO_UPLOADED",d="VELION_TOKEN",h="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 I(e,t=h){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 u="https://static.velion.la/sdk/onboarding/v1";function N(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 R=["containerId","sessionId","sessionToken","apiUrl","expiresAt","theme","colorMode","locale","allowEmbedded","livenessShowIntro","livenessShowBackButton","fingerprintEndpoint","debug","googleMapsApiKey","showFinalResultScreen","showProgressBar","finalResultScreenTexts"];class g{constructor(e){this.status="IDLE",this.iframe=null,this.container=null,this.messageHandler=null,this.containerResizeObserver=null,this.iframeHeightWindowResizeHandler=null,this.cdnOrigin="",this.config=Object.freeze({...e})}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??u).replace(/\/$/,"");if(!N(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&&!N(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 f=document.createElement("iframe");f.setAttribute("title","Velion Onboarding"),f.setAttribute("allow","camera"),f.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms allow-popups allow-modals"),f.setAttribute("referrerpolicy","no-referrer"),f.style.width="100%",f.style.border="none",f.style.display="block";const I=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.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 o:{const e=i.payload;this.config.onStepChange?.(e.step,e.stepIndex);break}case a:{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",I),this.messageHandler=I,this.iframe=f,e.replaceChildren(f),this.attachIframeHeightSync(e,f);const g=await this.resolveManifest(h);if(!g)return;const O=g.entry.startsWith("/")?g.entry:`/${g.entry}`;return new Promise(e=>{f.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 R){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",e()},f.onerror=()=>{this.status="ERROR",this.config.onError?.({code:t.UNKNOWN,message:`Failed to load onboarding app from ${h}. Check cdnUrl and network.`}),e()},f.src=`${h}${O}`})}async resolveManifest(e){let i;try{i=await I(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.18"}destroy(){"DESTROYED"!==this.status&&(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 O(e){}export{e as CameraErrorCode,u 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,o as VELION_MSG_STEP_CHANGE,a as VELION_MSG_STEP_COMPLETE,s as VELION_MSG_SUCCESS,d as VELION_MSG_TOKEN,E as VELION_MSG_TOKEN_EXPIRED,g as VelionOnboarding,O as setOpenCVBaseUrl};
|
|
2
2
|
//# sourceMappingURL=velion-sdk.es.js.map
|
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||{}),n=(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.UNKNOWN="UNKNOWN",e))(n||{});const i="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),n=new ArrayBuffer(t.length),i=new Uint8Array(n);for(let s=0;s<t.length;s++)i[s]=t.charCodeAt(s);return n}async function N(e,t=I){let n,i,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}`};n=await t.arrayBuffer(),i=(new TextDecoder).decode(n)}catch(E){return{ok:!1,reason:"unavailable",detail:E.message}}try{if(s=JSON.parse(i),"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,n){return crypto.subtle.verify({name:"Ed25519"},e,t,n)}(o,r,n)}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 u="https://static.velion.la/sdk/onboarding/v1";function O(e){try{const n=new URL(e);return"https:"===n.protocol||"http:"===n.protocol&&("localhost"===(t=n.hostname)||"127.0.0.1"===t||"::1"===t||t.endsWith(".local"))}catch{return!1}var t}const R=["containerId","sessionId","sessionToken","apiUrl","expiresAt","theme","colorMode","locale","allowEmbedded","livenessShowIntro","livenessShowBackButton","fingerprintEndpoint","debug","googleMapsApiKey","showFinalResultScreen","showProgressBar","finalResultScreenTexts"];e.CameraErrorCode=t,e.DEFAULT_CDN_URL=u,e.SdkErrorCode=n,e.VELION_MSG_ERROR=o,e.VELION_MSG_EXIT=l,e.VELION_MSG_INIT=i,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.config=Object.freeze({...e})}async start(){if("DESTROYED"===this.status)return void this.config.onError?.({code:n.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:n.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:n.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??u).replace(/\/$/,"");if(!O(t))return void this.config.onError?.({code:n.UNKNOWN,message:`Insecure cdnUrl: "${t}". Must be https:// (http:// is allowed only for localhost).`});if(this.config.apiUrl&&!O(this.config.apiUrl))return void this.config.onError?.({code:n.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:n.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.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:n.SESSION_EXPIRED,message:"Token refresh failed."})}):this.config.onError?.({code:n.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 N=await this.resolveManifest(t);if(!N)return;const _=N.entry.startsWith("/")?N.entry:`/${N.entry}`;return new Promise(e=>{I.onload=()=>{if(!this.iframe?.contentWindow)return this.status="ERROR",this.config.onError?.({code:n.UNKNOWN,message:"Iframe failed to load."}),void e();const t=function(e){const t={containerId:"velion-sdk-root",allowEmbedded:!0};for(const n of R){if("containerId"===n||"allowEmbedded"===n)continue;const i=e[n];void 0!==i&&"function"!=typeof i&&(t[n]=i)}return t}(this.config);let s;try{s=JSON.parse(JSON.stringify(t))}catch{s=t}this.sendToIframe({type:i,payload:s}),this.status="RUNNING",e()},I.onerror=()=>{this.status="ERROR",this.config.onError?.({code:n.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 N(e)}catch(i){return this.status="ERROR",this.config.onError?.({code:n.INTEGRITY_FAILED,message:`Manifest verification crashed unexpectedly: ${i.message}`}),null}if(t.ok)return!t.signed&&I?(this.status="ERROR",this.config.onError?.({code:n.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:n.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:n.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 n=()=>{if(this.iframe!==t||this.container!==e)return;const n=e.clientHeight;n>0?(t.style.height=`${n}px`,t.style.minHeight=""):(t.style.height="min(85vh, 880px)",t.style.minHeight="")};n(),requestAnimationFrame(n),requestAnimationFrame(()=>{requestAnimationFrame(n)}),"undefined"!=typeof ResizeObserver?(this.containerResizeObserver=new ResizeObserver(n),this.containerResizeObserver.observe(e)):(this.iframeHeightWindowResizeHandler=n,window.addEventListener("resize",n))}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.17"}destroy(){"DESTROYED"!==this.status&&(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||{}),n=(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.UNKNOWN="UNKNOWN",e))(n||{});const i="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),n=new ArrayBuffer(t.length),i=new Uint8Array(n);for(let s=0;s<t.length;s++)i[s]=t.charCodeAt(s);return n}async function N(e,t=I){let n,i,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}`};n=await t.arrayBuffer(),i=(new TextDecoder).decode(n)}catch(E){return{ok:!1,reason:"unavailable",detail:E.message}}try{if(s=JSON.parse(i),"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,n){return crypto.subtle.verify({name:"Ed25519"},e,t,n)}(o,r,n)}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 u="https://static.velion.la/sdk/onboarding/v1";function O(e){try{const n=new URL(e);return"https:"===n.protocol||"http:"===n.protocol&&("localhost"===(t=n.hostname)||"127.0.0.1"===t||"::1"===t||t.endsWith(".local"))}catch{return!1}var t}const R=["containerId","sessionId","sessionToken","apiUrl","expiresAt","theme","colorMode","locale","allowEmbedded","livenessShowIntro","livenessShowBackButton","fingerprintEndpoint","debug","googleMapsApiKey","showFinalResultScreen","showProgressBar","finalResultScreenTexts"];e.CameraErrorCode=t,e.DEFAULT_CDN_URL=u,e.SdkErrorCode=n,e.VELION_MSG_ERROR=o,e.VELION_MSG_EXIT=l,e.VELION_MSG_INIT=i,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.config=Object.freeze({...e})}async start(){if("DESTROYED"===this.status)return void this.config.onError?.({code:n.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:n.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:n.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??u).replace(/\/$/,"");if(!O(t))return void this.config.onError?.({code:n.UNKNOWN,message:`Insecure cdnUrl: "${t}". Must be https:// (http:// is allowed only for localhost).`});if(this.config.apiUrl&&!O(this.config.apiUrl))return void this.config.onError?.({code:n.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:n.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.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:n.SESSION_EXPIRED,message:"Token refresh failed."})}):this.config.onError?.({code:n.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 N=await this.resolveManifest(t);if(!N)return;const _=N.entry.startsWith("/")?N.entry:`/${N.entry}`;return new Promise(e=>{I.onload=()=>{if(!this.iframe?.contentWindow)return this.status="ERROR",this.config.onError?.({code:n.UNKNOWN,message:"Iframe failed to load."}),void e();const t=function(e){const t={containerId:"velion-sdk-root",allowEmbedded:!0};for(const n of R){if("containerId"===n||"allowEmbedded"===n)continue;const i=e[n];void 0!==i&&"function"!=typeof i&&(t[n]=i)}return t}(this.config);let s;try{s=JSON.parse(JSON.stringify(t))}catch{s=t}this.sendToIframe({type:i,payload:s}),this.status="RUNNING",e()},I.onerror=()=>{this.status="ERROR",this.config.onError?.({code:n.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 N(e)}catch(i){return this.status="ERROR",this.config.onError?.({code:n.INTEGRITY_FAILED,message:`Manifest verification crashed unexpectedly: ${i.message}`}),null}if(t.ok)return!t.signed&&I?(this.status="ERROR",this.config.onError?.({code:n.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:n.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:n.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 n=()=>{if(this.iframe!==t||this.container!==e)return;const n=e.clientHeight;n>0?(t.style.height=`${n}px`,t.style.minHeight=""):(t.style.height="min(85vh, 880px)",t.style.minHeight="")};n(),requestAnimationFrame(n),requestAnimationFrame(()=>{requestAnimationFrame(n)}),"undefined"!=typeof ResizeObserver?(this.containerResizeObserver=new ResizeObserver(n),this.containerResizeObserver.observe(e)):(this.iframeHeightWindowResizeHandler=n,window.addEventListener("resize",n))}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.18"}destroy(){"DESTROYED"!==this.status&&(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
|
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.18",
|
|
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",
|