@safepassage/sdk 3.5.5 → 3.5.6
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 +14 -13
- package/brands/safepassage/index.d.ts +1 -1
- package/core/VerificationSDK.d.ts +3 -17
- package/dist/safepassage.min.js +2 -2
- package/index.js +127 -343
- package/package.json +1 -1
- package/safepassage.min.js +2 -2
- package/sdk.min.js +2 -2
- package/types/base.d.ts +12 -40
- package/utils/validation.d.ts +1 -4
- package/utils/crypto.d.ts +0 -31
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SafePassage SDK v3.5.
|
|
1
|
+
# SafePassage SDK v3.5.6
|
|
2
2
|
|
|
3
3
|
A lightweight SDK for integrating SafePassage age verification into your website or application.
|
|
4
4
|
|
|
@@ -9,11 +9,16 @@ A lightweight SDK for integrating SafePassage age verification into your website
|
|
|
9
9
|
- **Two modes**: Same-tab redirect or new-tab popup
|
|
10
10
|
- **TypeScript support**: Full type definitions included
|
|
11
11
|
- **Auto-environment detection**: Works seamlessly across environments
|
|
12
|
-
- **
|
|
12
|
+
- **Server-issued sessions**: launches from the authenticated URL returned by the API
|
|
13
13
|
- **Compliant**: Enforces minimum age of 25
|
|
14
14
|
|
|
15
15
|
## Changelog
|
|
16
16
|
|
|
17
|
+
### 3.5.6
|
|
18
|
+
- Removed browser HMAC state. Browser callbacks remain convenience notifications, not proof of verification.
|
|
19
|
+
- Launches now require the server-issued `verifyUrl`; API and Verify UI origins are restricted to the selected brand/environment (localhost overrides remain available for development).
|
|
20
|
+
- Browser SDK verification-policy overrides are retired. Configure policy in the dashboard or create sessions server-side with a private key.
|
|
21
|
+
|
|
17
22
|
### 3.5.5
|
|
18
23
|
- Routes explicit staging sessions through the canonical VerityGuard-owned staging hosts for the selected SDK brand.
|
|
19
24
|
- Keeps production routing unchanged.
|
|
@@ -28,7 +33,7 @@ A lightweight SDK for integrating SafePassage age verification into your website
|
|
|
28
33
|
- No public SDK API changes.
|
|
29
34
|
|
|
30
35
|
### 3.5.2
|
|
31
|
-
-
|
|
36
|
+
- Version alignment release across both brand packages at `3.5.2`
|
|
32
37
|
- No SDK API changes from `3.5.0`
|
|
33
38
|
|
|
34
39
|
### 3.5.0
|
|
@@ -44,13 +49,13 @@ A lightweight SDK for integrating SafePassage age verification into your website
|
|
|
44
49
|
## Installation
|
|
45
50
|
|
|
46
51
|
```bash
|
|
47
|
-
npm install @safepassage/sdk
|
|
52
|
+
npm install @safepassage/sdk@3.5.6
|
|
48
53
|
```
|
|
49
54
|
|
|
50
55
|
Or load directly from jsDelivr CDN (no bundler required):
|
|
51
56
|
|
|
52
57
|
```html
|
|
53
|
-
<script src="https://cdn.jsdelivr.net/npm/@safepassage/sdk@3.5.
|
|
58
|
+
<script src="https://cdn.jsdelivr.net/npm/@safepassage/sdk@3.5.6/sdk.min.js"></script>
|
|
54
59
|
```
|
|
55
60
|
|
|
56
61
|
## Quick Start
|
|
@@ -72,7 +77,7 @@ await sp.verify();
|
|
|
72
77
|
### With CDN (no bundler)
|
|
73
78
|
|
|
74
79
|
```html
|
|
75
|
-
<script src="https://cdn.jsdelivr.net/npm/@safepassage/sdk@3.5.
|
|
80
|
+
<script src="https://cdn.jsdelivr.net/npm/@safepassage/sdk@3.5.6/sdk.min.js"></script>
|
|
76
81
|
<script>
|
|
77
82
|
const sp = new SafePassage({
|
|
78
83
|
apiKey: 'pk_...',
|
|
@@ -94,8 +99,6 @@ That's it! The SDK handles session creation automatically.
|
|
|
94
99
|
| cancelUrl | string | No | URL to redirect to if user closes the verification window (new-tab mode) |
|
|
95
100
|
| environment | string | No | `'production'` or `'staging'` (auto-detected) |
|
|
96
101
|
| mode | string | No | `'redirect'` (default) or `'new-tab'` |
|
|
97
|
-
| defaultChallengeAge | number | No | Default minimum age (25 or higher) |
|
|
98
|
-
| defaultVerificationMode | string | No | `'L1'` or `'L2'` |
|
|
99
102
|
| onComplete | function | No | Callback for new-tab mode |
|
|
100
103
|
| onCancel | function | No | Called when user closes popup (new-tab mode). Return `false` to suppress automatic `cancelUrl` redirect. |
|
|
101
104
|
| language | string | No | Default UI language (`'en'`, `'de'`, `'es'`, `'fr'`, `'pt'`, `'it'`). Can be overridden per verification. |
|
|
@@ -107,9 +110,6 @@ Override settings per-verification:
|
|
|
107
110
|
|
|
108
111
|
```javascript
|
|
109
112
|
await sp.verify({
|
|
110
|
-
challengeAge: 30, // Override minimum age for this session
|
|
111
|
-
verificationMode: 'L2', // Force ID verification for this session
|
|
112
|
-
faceMatchEnabled: false, // Disable selfie-vs-ID matching where required
|
|
113
113
|
externalUserId: 'user-123', // Your user ID (returned in webhooks)
|
|
114
114
|
skipIntro: true, // Skip intro screen
|
|
115
115
|
autoReturn: true, // Auto-redirect after success
|
|
@@ -151,7 +151,7 @@ const sp = new SafePassage({
|
|
|
151
151
|
mode: 'new-tab',
|
|
152
152
|
onComplete: (result) => {
|
|
153
153
|
console.log('Verification complete:', result.sessionId, result.status);
|
|
154
|
-
//
|
|
154
|
+
// Convenience notification only: validate on your server before access
|
|
155
155
|
},
|
|
156
156
|
onCancel: () => {
|
|
157
157
|
console.log('User closed the verification window');
|
|
@@ -230,7 +230,7 @@ For reliable verification tracking, configure webhooks in your dashboard:
|
|
|
230
230
|
<html>
|
|
231
231
|
<head>
|
|
232
232
|
<title>Age Verification</title>
|
|
233
|
-
<script src="https://cdn.jsdelivr.net/npm/@safepassage/sdk@3.5.
|
|
233
|
+
<script src="https://cdn.jsdelivr.net/npm/@safepassage/sdk@3.5.6/sdk.min.js"></script>
|
|
234
234
|
</head>
|
|
235
235
|
<body>
|
|
236
236
|
<button id="verify-btn">Verify Your Age</button>
|
|
@@ -291,6 +291,7 @@ const config: SafePassageConfig = {
|
|
|
291
291
|
returnUrl: '/verified',
|
|
292
292
|
mode: 'new-tab',
|
|
293
293
|
onComplete: (result: VerificationResult) => {
|
|
294
|
+
// Convenience notification only; request server-side validation.
|
|
294
295
|
console.log(`Session ${result.sessionId}: ${result.status}`);
|
|
295
296
|
}
|
|
296
297
|
};
|
|
@@ -7,6 +7,6 @@ export type SafePassageConfig = SDKConfig;
|
|
|
7
7
|
export declare class SafePassage extends VerificationSDK {
|
|
8
8
|
constructor(config: SafePassageConfig);
|
|
9
9
|
}
|
|
10
|
-
export declare const VERSION = "3.5.
|
|
10
|
+
export declare const VERSION = "3.5.6";
|
|
11
11
|
export type { SDKConfig, VerificationOptions, VerificationResult, SessionValidationResponse, SessionCreationResponse, CreateSessionRequest, };
|
|
12
12
|
export default SafePassage;
|
|
@@ -19,8 +19,6 @@ export interface BrandUrls {
|
|
|
19
19
|
}
|
|
20
20
|
export interface BrandConstants {
|
|
21
21
|
name: string;
|
|
22
|
-
hmacSecretProd: string;
|
|
23
|
-
hmacSecretStaging: string;
|
|
24
22
|
messageType: string;
|
|
25
23
|
legacyMessageType?: string;
|
|
26
24
|
popupName: string;
|
|
@@ -45,10 +43,7 @@ export declare class VerificationSDK {
|
|
|
45
43
|
private currentSessionId;
|
|
46
44
|
private hasReceivedResult;
|
|
47
45
|
private lastVerifyUrl;
|
|
48
|
-
private lastSessionToken;
|
|
49
46
|
private lastExternalUserId;
|
|
50
|
-
private lastSandboxMode;
|
|
51
|
-
private temporaryHandoffToken;
|
|
52
47
|
private static readonly LOCAL_HOSTNAMES;
|
|
53
48
|
/**
|
|
54
49
|
* Initialize SDK
|
|
@@ -62,9 +57,7 @@ export declare class VerificationSDK {
|
|
|
62
57
|
* Initiate verification with race condition protection
|
|
63
58
|
*/
|
|
64
59
|
verify(options?: VerificationOptions): Promise<void>;
|
|
65
|
-
/**
|
|
66
|
-
* Build verification URL with HMAC-signed state
|
|
67
|
-
*/
|
|
60
|
+
/** Build a launch URL from the Portal-issued authoritative verify URL. */
|
|
68
61
|
private buildVerificationUrl;
|
|
69
62
|
/**
|
|
70
63
|
* Redirect in same tab
|
|
@@ -104,14 +97,7 @@ export declare class VerificationSDK {
|
|
|
104
97
|
* Get Portal API URL based on environment and brand
|
|
105
98
|
*/
|
|
106
99
|
private getPortalApiUrl;
|
|
107
|
-
|
|
108
|
-
* Get Engine URL based on environment and brand
|
|
109
|
-
*/
|
|
110
|
-
private getEngineUrl;
|
|
111
|
-
/**
|
|
112
|
-
* Get WebSocket URL based on environment and brand
|
|
113
|
-
*/
|
|
114
|
-
private getWebSocketUrl;
|
|
100
|
+
private validateServiceOverrides;
|
|
115
101
|
/**
|
|
116
102
|
* Detect if this is a public key (pk_ prefix) vs private key (sk_ prefix)
|
|
117
103
|
*/
|
|
@@ -126,6 +112,6 @@ export declare class VerificationSDK {
|
|
|
126
112
|
private getTrustedOrigins;
|
|
127
113
|
private getAllowedCustomOrigins;
|
|
128
114
|
private getLocalOrigin;
|
|
115
|
+
private getUrlOrigin;
|
|
129
116
|
private applyLocalVerifyOverride;
|
|
130
|
-
private getHmacSecret;
|
|
131
117
|
}
|
package/dist/safepassage.min.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
/* SafePassage SDK v3.5.
|
|
2
|
-
"use strict";var SafePassageSDK=(()=>{var S=Object.defineProperty,pe=Object.defineProperties,ue=Object.getOwnPropertyDescriptor,fe=Object.getOwnPropertyDescriptors,he=Object.getOwnPropertyNames,y=Object.getOwnPropertySymbols;var T=Object.prototype.hasOwnProperty,W=Object.prototype.propertyIsEnumerable;var B=(t,e,n)=>e in t?S(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,w=(t,e)=>{for(var n in e||(e={}))T.call(e,n)&&B(t,n,e[n]);if(y)for(var n of y(e))W.call(e,n)&&B(t,n,e[n]);return t},U=(t,e)=>pe(t,fe(e));var H=(t,e)=>{var n={};for(var r in t)T.call(t,r)&&e.indexOf(r)<0&&(n[r]=t[r]);if(t!=null&&y)for(var r of y(t))e.indexOf(r)<0&&W.call(t,r)&&(n[r]=t[r]);return n};var A=(t,e,n)=>()=>{if(n)throw n[0];try{return t&&(e=t(t=0)),e}catch(r){throw n=[r],r}};var F=(t,e)=>{for(var n in e)S(t,n,{get:e[n],enumerable:!0})},me=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of he(e))!T.call(t,i)&&i!==n&&S(t,i,{get:()=>e[i],enumerable:!(r=ue(e,i))||r.enumerable});return t};var we=t=>me(S({},"__esModule",{value:!0}),t);function ve(t,e){return e.includes(t)}function G(t,e,n=[],r="SDK"){var s;let{origin:i}=t;return ve(i,e)||n.length>0&&n.some(a=>{if(a.startsWith("*.")){let c=a.slice(2);return i.endsWith(`.${c}`)||i===`https://${c}`||i===`http://${c}`}return i===a})?!0:(console.warn(`${r} Security: Blocked PostMessage from untrusted origin: ${i}`,{trustedOrigins:e,allowedCustomOrigins:n,eventType:(s=t.data)==null?void 0:s.type}),!1)}function J(t,e,n,r){let{data:i}=t;return!i||typeof i!="object"?{isValid:!1,error:"Invalid message format"}:(r?[n,r]:[n]).includes(i.type)?!i.sessionId||i.sessionId!==e?{isValid:!1,error:"Session ID mismatch"}:!i.status||!["verified","failed","cancelled"].includes(i.status)?{isValid:!1,error:"Invalid status value"}:{isValid:!0}:{isValid:!1,error:"Invalid message type"}}function X(t,e="SDK"){t==="production"&&window.location.protocol!=="https:"&&console.warn(`${e} Warning: HTTPS recommended for production environment`,{current:window.location.href})}function x(t,e,n="SDK"){try{let r=new URL(t);if(r.protocol==="file:")return{isValid:!1,error:"file:// URLs are not supported. The verification redirect cannot return to local files. Please use a local web server (e.g., npx serve . or python3 -m http.server) instead of opening the HTML file directly."};if(r.protocol!=="https:"&&!(r.hostname==="localhost"||r.hostname==="127.0.0.1"))return{isValid:!1,error:`HTTPS required for return URLs in ${e}`};let i=[/data:/i,/javascript:/i,/vbscript:/i,/file:/i,/ftp:/i];for(let s of i)if(s.test(t))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch(r){return{isValid:!1,error:"Invalid URL format"}}}function p(t,e,n="SDK"){let r={timestamp:new Date().toISOString(),userAgent:typeof navigator!="undefined"?navigator.userAgent:"unknown",url:typeof window!="undefined"?window.location.href:"unknown"};console.warn(`${n} Security Event: ${t}`,w(w({},r),e))}var P,Y,R=A(()=>{"use strict";P=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(e,n="SDK"){let r=Date.now(),s=(this.attempts.get(e)||[]).filter(l=>r-l<this.timeWindow);return s.length>=this.maxAttempts?(console.warn(`${n} Security: Rate limit exceeded for ${e}`),!1):(s.push(r),this.attempts.set(e,s),!0)}reset(e){this.attempts.delete(e)}},Y=new P});var Q={};F(Q,{createSignedState:()=>Se,generateHMAC:()=>M,generateSecureToken:()=>z,parseSignedState:()=>Ue,verifyHMAC:()=>Z});async function M(t,e){let n=new TextEncoder,r=n.encode(e),i=n.encode(t),s=await crypto.subtle.importKey("raw",r,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),l=await crypto.subtle.sign("HMAC",s,i);return Array.from(new Uint8Array(l)).map(a=>a.toString(16).padStart(2,"0")).join("")}async function Z(t,e,n){try{let r=await M(t,n);return ye(e,r)}catch(r){return!1}}function ye(t,e){if(t.length!==e.length)return!1;let n=0;for(let r=0;r<t.length;r++)n|=t.charCodeAt(r)^e.charCodeAt(r);return n===0}function z(t=32){let e=new Uint8Array(t);return crypto.getRandomValues(e),Array.from(e,n=>n.toString(16).padStart(2,"0")).join("")}async function Se(t,e){let n=U(w({},t),{timestamp:Date.now(),nonce:z(16)}),r=JSON.stringify(n),i=await M(r,e);return btoa(JSON.stringify({data:n,signature:i}))}async function Ue(t,e,n=te,r="SDK"){try{let s=atob(t),l=JSON.parse(s);if(!l.data||!l.signature)return console.warn(`${r}: Invalid signed state format`),null;let{data:a,signature:c}=l,d=JSON.stringify(a);if(!await Z(d,c,e))return console.warn(`${r}: State signature verification failed`),null;if(a.timestamp){let h=Date.now()-a.timestamp;if(h>n)return console.warn(`${r}: State parameter expired`,{age:h,maxAge:n}),null}let i=a,{timestamp:g,nonce:f}=i;return H(i,["timestamp","nonce"])}catch(s){return console.warn(`${r}: Failed to parse signed state`,s),null}}var ee=A(()=>{"use strict";L()});function se(t,e){var i;if(!t.apiKey)throw new Error("apiKey is required");if(t.apiKey.length>ie)throw new Error(`apiKey exceeds maximum length of ${ie} characters`);if(!be.test(t.apiKey))throw new Error("Invalid apiKey format. Expected pk_xxx (public) or sk_xxx (private)");if(typeof window!="undefined"&&t.apiKey.startsWith("sk_"))throw new Error(`Secret keys (sk_) should never be used in browser code for security reasons. Secret keys expose your account to unauthorized access if used client-side. Please use your public key (pk_) instead. If you need to use features that require a secret key (like custom challenge age), create the session server-side and pass the sessionId to startVerificationWithSession(). See: ${e.docsUrl}/server-side-sessions`);if(!t.returnUrl)throw new Error("returnUrl is required");if(t.returnUrl.length>b)throw new Error(`returnUrl exceeds maximum length of ${b} characters`);let n=(i=e.environment)!=null?i:"production",r=x(t.returnUrl,n,e.brandName);if(!r.isValid)throw new Error(`returnUrl validation failed: ${r.error}`);if(t.cancelUrl){if(t.cancelUrl.length>b)throw new Error(`cancelUrl exceeds maximum length of ${b} characters`);let s=x(t.cancelUrl,n,e.brandName);if(!s.isValid)throw new Error(`cancelUrl validation failed: ${s.error}`)}if(t.defaultChallengeAge!==void 0){if(t.defaultChallengeAge<ne)throw new Error(`defaultChallengeAge must be at least ${ne}`);if(t.defaultChallengeAge>re)throw new Error(`defaultChallengeAge cannot exceed ${re}`)}if(t.defaultVerificationMode&&!["L1","L2"].includes(t.defaultVerificationMode))throw new Error("defaultVerificationMode must be L1 or L2");if(t.faceMatchEnabled!==void 0&&typeof t.faceMatchEnabled!="boolean")throw new Error("faceMatchEnabled must be a boolean");if(t.mode&&!["redirect","new-tab"].includes(t.mode))throw new Error("mode must be redirect or new-tab");if(t.newTabTarget&&!["popup","tab"].includes(t.newTabTarget))throw new Error("newTabTarget must be popup or tab")}async function oe(t,e,n,r="SDK"){let{createSignedState:i}=await Promise.resolve().then(()=>(ee(),Q));return i(t,n)}var ne,re,b,ie,te,be,L=A(()=>{"use strict";R();ne=25,re=150,b=2048,ie=128,te=6e5,be=/^(pk_|sk_)[a-zA-Z0-9_]+$/});var xe={};F(xe,{SafePassage:()=>v,VERSION:()=>ge,default:()=>Pe});function j(){crypto.randomUUID||(crypto.randomUUID=function(){let t=new Uint8Array(16);crypto.getRandomValues(t),t[6]=t[6]&15|64,t[8]=t[8]&63|128;let e=Array.from(t).map(n=>n.toString(16).padStart(2,"0")).join("");return[e.slice(0,8),e.slice(8,12),e.slice(12,16),e.slice(16,20),e.slice(20,32)].join("-")})}function q(t="SDK"){let e=[];if(!window.crypto||!window.crypto.getRandomValues)throw new Error(`${t} requires Web Crypto API support`);if(!window.crypto.subtle)throw new Error(`${t} requires Web Crypto subtle API for HMAC operations`);crypto.randomUUID||e.push("crypto.randomUUID not supported, using polyfill"),window.URLSearchParams||e.push("URLSearchParams not supported, consider adding a polyfill for IE 11 support"),e.length>0&&console.warn(`${t} Browser Compatibility:`,e.join("; "))}L();function E(t,e){let n=e.verifyUiUrl;if(!n||!n.startsWith("https://"))throw new Error(`HTTPS required for ${t} environment`);return n}function Ee(t,e){let n=e.apiUrl;if(!n||!n.startsWith("https://"))throw new Error(`HTTPS required for API URLs in ${t} environment`);return n}function Ce(t){if(!t)return null;try{return new URL(t).hostname.toLowerCase()}catch(e){return null}}function Ie(t){let e=t.split(".");return e.length<=2?t:e.slice(1).join(".")}function ae(t){let e=new Set;for(let n of t){let r=Ce(n);r&&e.add(Ie(r))}return e}function Te(t){var s,l;if(!t||!t.staging)return[];let e=t.staging,n=ae([e.apiUrl,e.verifyUiUrl,e.engineUrl,e.wsUrl,...(s=e.trustedOrigins)!=null?s:[]]),r=t.production,i=r?ae([r.apiUrl,r.verifyUiUrl,r.engineUrl,r.wsUrl,...(l=r.trustedOrigins)!=null?l:[]]):new Set;for(let a of i)n.delete(a);return Array.from(n)}function Ae(t,e){let n=(t||"").toLowerCase();return n?Te(e).some(r=>n===r||n.endsWith(`.${r}`)):!1}function le(t,e){return t==="staging"||t==="production"?t:t||typeof window=="undefined"||!window.location?"production":Ae(window.location.hostname,e)?"staging":"production"}function ce(t,e,n="SDK"){let r=window.location.protocol==="https:";switch(t){case"production":r||console.warn(`${n} Warning: HTTPS recommended for production environment`);break;case"staging":r||console.warn(`${n} Warning: HTTPS strongly recommended in staging environment`);break}try{E(t,e),Ee(t,e)}catch(i){let s=i instanceof Error?i.message:String(i);throw new Error(`Environment configuration validation failed: ${s}`)}}R();var I=class I{constructor(e,n,r){this.popupWindow=null;this.messageListener=null;this.popupMonitorInterval=null;this.unloadListener=null;this.isVerificationInProgress=!1;this.currentSessionId=null;this.hasReceivedResult=!1;this.lastVerifyUrl=null;this.lastSessionToken=null;this.lastExternalUserId=null;this.lastSandboxMode=null;this.temporaryHandoffToken=null;this.brandUrls=n,this.brandConstants=r;let i=le(e.environment,this.brandUrls);e.environment&&e.environment!=="staging"&&e.environment!=="production"&&console.warn(`${this.brandConstants.name} SDK: Unknown environment '${e.environment}', defaulting to 'production'`),se(e,{brandName:this.brandConstants.name,docsUrl:this.brandConstants.docsUrl,environment:i}),this.config=U(w({},e),{environment:i,mode:e.mode||"redirect",newTabTarget:e.newTabTarget||"popup"}),ce(this.config.environment,this.getUrlConfig(),this.brandConstants.name),X(this.config.environment,this.brandConstants.name),p("SDK_INITIALIZED",{environment:this.config.environment,mode:this.config.mode,origin:window.location.origin,protocol:window.location.protocol,hostname:window.location.hostname},this.brandConstants.name),this.setupAutoCleanup()}async verify(e={}){var i,s,l,a,c,d;let n=this.isPublicKey(),r;if(n)r=await this.createInternalSession(e);else throw new Error("Private API keys (sk_) should use the direct API, not the SDK. The SDK is designed for browser-based public key usage only.");if(!r)throw new Error("Failed to obtain sessionId from server");if(this.isVerificationInProgress){let o=new Error(`Verification already in progress for session ${(i=this.currentSessionId)==null?void 0:i.substring(0,8)}...`);throw p("RACE_CONDITION_PREVENTED",{currentSession:((s=this.currentSessionId)==null?void 0:s.substring(0,8))+"...",attemptedSession:"new-session-attempt",origin:window.location.origin},this.brandConstants.name),(a=(l=this.config).onError)==null||a.call(l,o),o}this.isVerificationInProgress=!0,this.currentSessionId=r,this.lastExternalUserId=e.externalUserId||null;try{let o=`${this.config.apiKey}:${window.location.origin}`;if(!Y.isAllowed(o,this.brandConstants.name)){let f=new Error("Too many verification attempts. Please wait before trying again.");throw p("RATE_LIMIT_EXCEEDED",{apiKey:this.config.apiKey.substring(0,8)+"...",origin:window.location.origin,sessionId:r?r.substring(0,8)+"...":"undefined"},this.brandConstants.name),(d=(c=this.config).onError)==null||d.call(c,f),f}let g=await this.buildVerificationUrl(e,r);p("VERIFICATION_INITIATED",{environment:this.config.environment,mode:this.config.mode,sessionId:r?r.substring(0,8)+"...":"undefined",origin:window.location.origin},this.brandConstants.name),this.config.mode==="new-tab"?this.openNewTab(g,r):(this.unlockVerification(),this.redirect(g))}catch(o){throw this.unlockVerification(),o}}async buildVerificationUrl(e,n){var f,m;let r=this.config.verifyUrl||E(this.config.environment,this.getUrlConfig()),i=e.challengeAge!==void 0,s=e.verificationMode!==void 0,l=e.faceMatchEnabled!==void 0||this.config.faceMatchEnabled!==void 0,a=i||s||l,c=(f=e.faceMatchEnabled)!=null?f:this.config.faceMatchEnabled,d=await oe({merchantId:this.config.apiKey,sessionId:n,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:e.challengeAge||this.config.defaultChallengeAge,verificationMode:e.verificationMode||this.config.defaultVerificationMode,faceMatchEnabled:c,hasOverrides:a,externalUserId:e.externalUserId,timestamp:Date.now(),apiUrl:this.getPortalApiUrl(),engineUrl:this.getEngineUrl(),wsUrl:this.getWebSocketUrl(),environment:this.config.environment,features:{testMode:!1,warmupPeriodMs:500,qualityThreshold:.6,sandboxMode:(m=this.lastSandboxMode)!=null?m:!1},handoffToken:this.temporaryHandoffToken||void 0,sessionToken:this.lastSessionToken||void 0,verifyUrl:this.lastVerifyUrl||void 0},this.config.environment,this.getHmacSecret(),this.brandConstants.name),o=e.language||this.config.language;if(this.lastVerifyUrl)try{let h=this.applyLocalVerifyOverride(this.lastVerifyUrl),u=new URL(h);return u.searchParams.set("state",d),u.searchParams.set("mode",this.config.mode),e.skipIntro&&u.searchParams.set("skip_intro","true"),e.autoReturn&&u.searchParams.set("auto_return","true"),o&&u.searchParams.set("lang",o),u.toString()}catch(h){}let g=new URLSearchParams({state:d,sessionId:n,mode:this.config.mode});return e.skipIntro&&g.set("skip_intro","true"),e.autoReturn&&g.set("auto_return","true"),o&&g.set("lang",o),`${r}/?${g.toString()}`}redirect(e){window.location.href=e}openNewTab(e,n){var c,d;if(this.cleanup(),this.hasReceivedResult=!1,this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),(this.config.newTabTarget||"popup")==="tab"?this.popupWindow=window.open(e,"_blank"):this.popupWindow=window.open(e,this.brandConstants.popupName,"width=600,height=700"),!this.popupWindow){(d=(c=this.config).onError)==null||d.call(c,new Error("Failed to open verification window. Please check popup blocker settings."));return}let i=this.getTrustedOrigins(),s=this.getAllowedCustomOrigins(e),l=this.brandConstants.messageType,a=this.brandConstants.legacyMessageType;this.messageListener=o=>{var k,D,_,O,$,N,K;let g=(k=o.data)==null?void 0:k.type;if(!g||typeof g!="string"||!(a?[l,a]:[l]).includes(g))return;if(!G(o,i,s,this.brandConstants.name)){p("POSTMESSAGE_ORIGIN_BLOCKED",{origin:o.origin,environment:this.config.environment,expectedOrigins:`${this.brandConstants.name} trusted origins for ${this.config.environment}`,messageType:(D=o.data)==null?void 0:D.type},this.brandConstants.name);return}let m=J(o,n,l,a);if(!m.isValid){p("POSTMESSAGE_VALIDATION_FAILED",{error:m.error,origin:o.origin,sessionId:n.substring(0,8)+"...",messageType:(_=o.data)==null?void 0:_.type},this.brandConstants.name);return}let h=o.data.status;if(h==="cancelled"){this.handleCancellation(n,"postmessage");return}let u={sessionId:o.data.sessionId,status:h,timestamp:o.data.timestamp,externalUserId:o.data.externalUserId};this.hasReceivedResult=!0,p("VERIFICATION_COMPLETED",{status:u.status,sessionId:n.substring(0,8)+"...",origin:o.origin},this.brandConstants.name),this.cleanup({closePopup:!1}),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),u.status==="verified"?($=(O=this.config).onComplete)==null||$.call(O,u):(K=(N=this.config).onError)==null||K.call(N,new Error(`Verification failed: ${u.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{this.popupWindow&&this.popupWindow.closed&&(p("POPUP_CLOSED_BY_USER",{sessionId:n.substring(0,8)+"...",environment:this.config.environment},this.brandConstants.name),this.hasReceivedResult||this.handleCancellation(n,"popup-closed"))},500)}setupAutoCleanup(){if(this.unloadListener=()=>{p("SDK_AUTO_CLEANUP",{environment:this.config.environment,trigger:"page_unload"},this.brandConstants.name),this.cleanup(),this.unlockVerification()},window.addEventListener("beforeunload",this.unloadListener),window.addEventListener("pagehide",this.unloadListener),window.history&&window.history.pushState){let e=window.history.pushState;window.history.pushState=(...n)=>(this.cleanup(),this.unlockVerification(),e.apply(window.history,n))}}getEnvironment(){return this.config.environment}unlockVerification(){this.isVerificationInProgress=!1,this.currentSessionId=null,p("VERIFICATION_UNLOCKED",{environment:this.config.environment,origin:window.location.origin},this.brandConstants.name)}cleanup(e={}){let n=e.closePopup!==!1;this.popupWindow&&(n&&!this.popupWindow.closed&&this.popupWindow.close(),(n||this.popupWindow.closed)&&(this.popupWindow=null)),this.messageListener&&(window.removeEventListener("message",this.messageListener),this.messageListener=null),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null)}handleCancellation(e,n){if(this.hasReceivedResult)return;this.hasReceivedResult=!0,p("VERIFICATION_CANCELLED",{source:n,sessionId:e.substring(0,8)+"...",environment:this.config.environment,origin:window.location.origin},this.brandConstants.name),this.cleanup(),this.unlockVerification();let r=!0;if(this.config.onCancel)try{this.config.onCancel()===!1&&(r=!1)}catch(i){p("CANCEL_CALLBACK_FAILED",{error:i instanceof Error?i.message:String(i),sessionId:e.substring(0,8)+"..."},this.brandConstants.name)}r&&this.redirectToCancelUrl(e)}redirectToCancelUrl(e){if(this.config.cancelUrl)try{let n=decodeURIComponent(this.config.cancelUrl),r=new URL(n);r.searchParams.set("sessionId",e),r.searchParams.set("status","cancelled"),r.searchParams.set("timestamp",Date.now().toString()),this.lastExternalUserId&&r.searchParams.set("externalUserId",this.lastExternalUserId),window.location.href=r.toString()}catch(n){p("CANCEL_REDIRECT_FAILED",{error:n instanceof Error?n.message:String(n),sessionId:e.substring(0,8)+"..."},this.brandConstants.name)}}removeAutoCleanupListeners(){this.unloadListener&&(window.removeEventListener("beforeunload",this.unloadListener),window.removeEventListener("pagehide",this.unloadListener),this.unloadListener=null)}destroy(){p("SDK_DESTROYED",{environment:this.config.environment,origin:window.location.origin},this.brandConstants.name),this.cleanup(),this.unlockVerification(),this.removeAutoCleanupListeners()}getPortalApiUrl(){return this.config.apiUrl?this.config.apiUrl:this.getUrlConfig().apiUrl}getEngineUrl(){return this.getUrlConfig().engineUrl}getWebSocketUrl(){return this.getUrlConfig().wsUrl}isPublicKey(){return this.config.apiKey.startsWith("pk_")}async createInternalSession(e){var n,r,i;try{let s=this.getPortalApiUrl(),l=await fetch(`${s}/api/v1/sessions/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({merchantId:this.config.apiKey,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:e.challengeAge,verificationMode:e.verificationMode,faceMatchEnabled:(n=e.faceMatchEnabled)!=null?n:this.config.faceMatchEnabled,merchantName:document.title||window.location.hostname,externalUserId:e.externalUserId})});if(!l.ok){let d=await l.json().catch(()=>({})),o=d==null?void 0:d.code;if(this.isBillingBlockError(o)){let g=e.language||this.config.language;this.openBillingBlockPage(o,d==null?void 0:d.portalUrl,g)}throw new Error(`Failed to create session: ${l.status} ${l.statusText}. ${d.message||""}`)}let a=await l.json(),c=a.sessionId;if(!c)throw new Error("Server did not return a sessionId");return a.verifyUrl&&(this.lastVerifyUrl=a.verifyUrl),a.sessionToken&&(this.lastSessionToken=a.sessionToken),a.handoffToken&&(this.temporaryHandoffToken=a.handoffToken),typeof a.sandboxMode=="boolean"?this.lastSandboxMode=a.sandboxMode:this.lastSandboxMode=null,p("INTERNAL_SESSION_CREATED",{sessionId:c.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),c}catch(s){let l=s instanceof Error?s.message:String(s);throw p("INTERNAL_SESSION_FAILED",{error:l,environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),(i=(r=this.config).onError)==null||i.call(r,s),new Error(`Failed to create verification session: ${l}`)}}isBillingBlockError(e){return e==="SUBSCRIPTION_REQUIRED"||e==="PLAN_LIMIT_REACHED"||e==="SANDBOX_LIMIT_REACHED"}openBillingBlockPage(e,n,r){var i,s,l,a;try{let c=this.config.verifyUrl||E(this.config.environment,this.getUrlConfig()),d=this.applyLocalVerifyOverride(c),o=new URL(d);o.searchParams.set("blocked",e),n&&o.searchParams.set("portalUrl",n);let g=r||this.config.language;if(g&&o.searchParams.set("lang",g),this.config.mode==="new-tab"){((this.config.newTabTarget||"popup")==="tab"?window.open(o.toString(),"_blank"):window.open(o.toString(),this.brandConstants.popupName,"width=600,height=700"))||(s=(i=this.config).onError)==null||s.call(i,new Error("Failed to open billing notice window. Please check popup blocker settings."));return}this.redirect(o.toString())}catch(c){let d=c instanceof Error?c.message:String(c);(a=(l=this.config).onError)==null||a.call(l,new Error(`Failed to open billing notice: ${d}`))}}getUrlConfig(){return this.brandUrls[this.config.environment]||this.brandUrls.production}getTrustedOrigins(){return this.getUrlConfig().trustedOrigins}getAllowedCustomOrigins(e){let n=new Set,r=this.getLocalOrigin(this.config.verifyUrl||null),i=this.getLocalOrigin(e||null);return r&&n.add(r),i&&n.add(i),Array.from(n)}getLocalOrigin(e){if(!e)return null;try{let n=new URL(e);if(I.LOCAL_HOSTNAMES.has(n.hostname))return n.origin}catch(n){return null}return null}applyLocalVerifyOverride(e){let n=this.getLocalOrigin(this.config.verifyUrl||null);if(!n)return e;try{let r=new URL(n),i=new URL(e);return i.protocol=r.protocol,i.host=r.host,i.toString()}catch(r){return e}}getHmacSecret(){return this.config.environment==="staging"?this.brandConstants.hmacSecretStaging:this.brandConstants.hmacSecretProd}};I.LOCAL_HOSTNAMES=new Set(["localhost","127.0.0.1","::1"]);var C=I;var de={production:{apiUrl:"https://api.safepassageapp.com",verifyUiUrl:"https://av.safepassageapp.com",engineUrl:"https://engine.safepassageapp.com",wsUrl:"wss://engine.safepassageapp.com/api/websocket/stream",trustedOrigins:["https://av.safepassageapp.com","https://portal.safepassageapp.com","https://api.safepassageapp.com"]},staging:{apiUrl:"https://api.staging.safepassageapp.com",verifyUiUrl:"https://verify.staging.safepassageapp.com",engineUrl:"https://engine.staging.safepassageapp.com",wsUrl:"wss://engine.staging.safepassageapp.com/api/websocket/stream",trustedOrigins:["https://verify.staging.safepassageapp.com","https://portal.staging.safepassageapp.com","https://api.staging.safepassageapp.com"]}},V={name:"SafePassage",hmacSecretProd:"safepassage-prod-hmac-2025",hmacSecretStaging:"safepassage-stage-hmac-2025",messageType:"safepassage:verification:complete",legacyMessageType:"safepassage-verification",popupName:"safepassage-verify",docsUrl:"https://docs.safepassageapp.com"};var v=class extends C{constructor(e){super(e,de,V)}},ge="3.5.5";v.VERSION=ge;typeof window!="undefined"&&(j(),q(`${V.name} SDK`));var Pe=v;return we(xe);})();
|
|
1
|
+
/* SafePassage SDK v3.5.6 */
|
|
2
|
+
"use strict";var SafePassageSDK=(()=>{var w=Object.defineProperty,Q=Object.defineProperties,ee=Object.getOwnPropertyDescriptor,te=Object.getOwnPropertyDescriptors,re=Object.getOwnPropertyNames,D=Object.getOwnPropertySymbols;var $=Object.prototype.hasOwnProperty,ie=Object.prototype.propertyIsEnumerable;var _=(r,e,t)=>e in r?w(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t,u=(r,e)=>{for(var t in e||(e={}))$.call(e,t)&&_(r,t,e[t]);if(D)for(var t of D(e))ie.call(e,t)&&_(r,t,e[t]);return r},N=(r,e)=>Q(r,te(e));var ne=(r,e)=>{for(var t in e)w(r,t,{get:e[t],enumerable:!0})},se=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of re(e))!$.call(r,n)&&n!==t&&w(r,n,{get:()=>e[n],enumerable:!(i=ee(e,n))||i.enumerable});return r};var oe=r=>se(w({},"__esModule",{value:!0}),r);var he={};ne(he,{SafePassage:()=>f,VERSION:()=>J,default:()=>fe});function k(){crypto.randomUUID||(crypto.randomUUID=function(){let r=new Uint8Array(16);crypto.getRandomValues(r),r[6]=r[6]&15|64,r[8]=r[8]&63|128;let e=Array.from(r).map(t=>t.toString(16).padStart(2,"0")).join("");return[e.slice(0,8),e.slice(8,12),e.slice(12,16),e.slice(16,20),e.slice(20,32)].join("-")})}function M(r="SDK"){let e=[];if(!window.crypto||!window.crypto.getRandomValues)throw new Error(`${r} requires Web Crypto API support`);crypto.randomUUID||e.push("crypto.randomUUID not supported, using polyfill"),window.URLSearchParams||e.push("URLSearchParams not supported, consider adding a polyfill for IE 11 support"),e.length>0&&console.warn(`${r} Browser Compatibility:`,e.join("; "))}function ae(r,e){return e.includes(r)}function K(r,e,t=[],i="SDK"){var s;let{origin:n}=r;return ae(n,e)||t.length>0&&t.some(l=>{if(l.startsWith("*.")){let c=l.slice(2);return n.endsWith(`.${c}`)||n===`https://${c}`||n===`http://${c}`}return n===l})?!0:(console.warn(`${i} Security: Blocked PostMessage from untrusted origin: ${n}`,{trustedOrigins:e,allowedCustomOrigins:t,eventType:(s=r.data)==null?void 0:s.type}),!1)}function B(r,e,t,i){let{data:n}=r;return!n||typeof n!="object"?{isValid:!1,error:"Invalid message format"}:(i?[t,i]:[t]).includes(n.type)?!n.sessionId||n.sessionId!==e?{isValid:!1,error:"Session ID mismatch"}:!n.status||!["verified","failed","cancelled"].includes(n.status)?{isValid:!1,error:"Invalid status value"}:{isValid:!0}:{isValid:!1,error:"Invalid message type"}}function W(r,e="SDK"){r==="production"&&window.location.protocol!=="https:"&&console.warn(`${e} Warning: HTTPS recommended for production environment`,{current:window.location.href})}function S(r,e,t="SDK"){try{let i=new URL(r);if(i.protocol==="file:")return{isValid:!1,error:"file:// URLs are not supported. The verification redirect cannot return to local files. Please use a local web server (e.g., npx serve . or python3 -m http.server) instead of opening the HTML file directly."};if(i.protocol!=="https:"&&!(i.hostname==="localhost"||i.hostname==="127.0.0.1"))return{isValid:!1,error:`HTTPS required for return URLs in ${e}`};let n=[/data:/i,/javascript:/i,/vbscript:/i,/file:/i,/ftp:/i];for(let s of n)if(s.test(r))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch(i){return{isValid:!1,error:"Invalid URL format"}}}var E=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(e,t="SDK"){let i=Date.now(),s=(this.attempts.get(e)||[]).filter(o=>i-o<this.timeWindow);return s.length>=this.maxAttempts?(console.warn(`${t} Security: Rate limit exceeded for ${e}`),!1):(s.push(i),this.attempts.set(e,s),!0)}reset(e){this.attempts.delete(e)}},F=new E;function d(r,e,t="SDK"){let i={timestamp:new Date().toISOString(),userAgent:typeof navigator!="undefined"?navigator.userAgent:"unknown",url:typeof window!="undefined"?window.location.href:"unknown"};console.warn(`${t} Security Event: ${r}`,u(u({},i),e))}var H=25,q=150,v=2048,j=128,le=/^(pk_|sk_)[a-zA-Z0-9_]+$/;function G(r,e){var n;if(!r.apiKey)throw new Error("apiKey is required");if(r.apiKey.length>j)throw new Error(`apiKey exceeds maximum length of ${j} characters`);if(!le.test(r.apiKey))throw new Error("Invalid apiKey format. Expected pk_xxx (public) or sk_xxx (private)");if(typeof window!="undefined"&&r.apiKey.startsWith("sk_"))throw new Error(`Secret keys (sk_) should never be used in browser code for security reasons. Secret keys expose your account to unauthorized access if used client-side. Please use your public key (pk_) instead. If you need to use features that require a secret key (like custom challenge age), create the session server-side and pass the sessionId to startVerificationWithSession(). See: ${e.docsUrl}/server-side-sessions`);if(!r.returnUrl)throw new Error("returnUrl is required");if(r.returnUrl.length>v)throw new Error(`returnUrl exceeds maximum length of ${v} characters`);let t=(n=e.environment)!=null?n:"production",i=S(r.returnUrl,t,e.brandName);if(!i.isValid)throw new Error(`returnUrl validation failed: ${i.error}`);if(r.cancelUrl){if(r.cancelUrl.length>v)throw new Error(`cancelUrl exceeds maximum length of ${v} characters`);let s=S(r.cancelUrl,t,e.brandName);if(!s.isValid)throw new Error(`cancelUrl validation failed: ${s.error}`)}if(r.defaultChallengeAge!==void 0){if(r.defaultChallengeAge<H)throw new Error(`defaultChallengeAge must be at least ${H}`);if(r.defaultChallengeAge>q)throw new Error(`defaultChallengeAge cannot exceed ${q}`)}if(r.defaultVerificationMode&&!["L1","L2"].includes(r.defaultVerificationMode))throw new Error("defaultVerificationMode must be L1 or L2");if(r.faceMatchEnabled!==void 0&&typeof r.faceMatchEnabled!="boolean")throw new Error("faceMatchEnabled must be a boolean");if(r.mode&&!["redirect","new-tab"].includes(r.mode))throw new Error("mode must be redirect or new-tab");if(r.newTabTarget&&!["popup","tab"].includes(r.newTabTarget))throw new Error("newTabTarget must be popup or tab")}function b(r,e){let t=e.verifyUiUrl;if(!t||!t.startsWith("https://"))throw new Error(`HTTPS required for ${r} environment`);return t}function ce(r,e){let t=e.apiUrl;if(!t||!t.startsWith("https://"))throw new Error(`HTTPS required for API URLs in ${r} environment`);return t}function de(r){if(!r)return null;try{return new URL(r).hostname.toLowerCase()}catch(e){return null}}function ge(r){let e=r.split(".");return e.length<=2?r:e.slice(1).join(".")}function Z(r){let e=new Set;for(let t of r){let i=de(t);i&&e.add(ge(i))}return e}function pe(r){var s,o;if(!r||!r.staging)return[];let e=r.staging,t=Z([e.apiUrl,e.verifyUiUrl,e.engineUrl,e.wsUrl,...(s=e.trustedOrigins)!=null?s:[]]),i=r.production,n=i?Z([i.apiUrl,i.verifyUiUrl,i.engineUrl,i.wsUrl,...(o=i.trustedOrigins)!=null?o:[]]):new Set;for(let l of n)t.delete(l);return Array.from(t)}function ue(r,e){let t=(r||"").toLowerCase();return t?pe(e).some(i=>t===i||t.endsWith(`.${i}`)):!1}function X(r,e){return r==="staging"||r==="production"?r:r||typeof window=="undefined"||!window.location?"production":ue(window.location.hostname,e)?"staging":"production"}function z(r,e,t="SDK"){let i=window.location.protocol==="https:";switch(r){case"production":i||console.warn(`${t} Warning: HTTPS recommended for production environment`);break;case"staging":i||console.warn(`${t} Warning: HTTPS strongly recommended in staging environment`);break}try{b(r,e),ce(r,e)}catch(n){let s=n instanceof Error?n.message:String(n);throw new Error(`Environment configuration validation failed: ${s}`)}}var y=class y{constructor(e,t,i){this.popupWindow=null;this.messageListener=null;this.popupMonitorInterval=null;this.unloadListener=null;this.isVerificationInProgress=!1;this.currentSessionId=null;this.hasReceivedResult=!1;this.lastVerifyUrl=null;this.lastExternalUserId=null;this.brandUrls=t,this.brandConstants=i;let n=X(e.environment,this.brandUrls);e.environment&&e.environment!=="staging"&&e.environment!=="production"&&console.warn(`${this.brandConstants.name} SDK: Unknown environment '${e.environment}', defaulting to 'production'`),G(e,{brandName:this.brandConstants.name,docsUrl:this.brandConstants.docsUrl,environment:n}),this.config=N(u({},e),{environment:n,mode:e.mode||"redirect",newTabTarget:e.newTabTarget||"popup"}),this.validateServiceOverrides(),z(this.config.environment,this.getUrlConfig(),this.brandConstants.name),W(this.config.environment,this.brandConstants.name),d("SDK_INITIALIZED",{environment:this.config.environment,mode:this.config.mode,origin:window.location.origin,protocol:window.location.protocol,hostname:window.location.hostname},this.brandConstants.name),this.setupAutoCleanup()}async verify(e={}){var n,s,o,l,c,g;let t=this.isPublicKey(),i;if(t)i=await this.createInternalSession(e);else throw new Error("Private API keys (sk_) should use the direct API, not the SDK. The SDK is designed for browser-based public key usage only.");if(!i)throw new Error("Failed to obtain sessionId from server");if(this.isVerificationInProgress){let a=new Error(`Verification already in progress for session ${(n=this.currentSessionId)==null?void 0:n.substring(0,8)}...`);throw d("RACE_CONDITION_PREVENTED",{currentSession:((s=this.currentSessionId)==null?void 0:s.substring(0,8))+"...",attemptedSession:"new-session-attempt",origin:window.location.origin},this.brandConstants.name),(l=(o=this.config).onError)==null||l.call(o,a),a}this.isVerificationInProgress=!0,this.currentSessionId=i,this.lastExternalUserId=e.externalUserId||null;try{let a=`${this.config.apiKey}:${window.location.origin}`;if(!F.isAllowed(a,this.brandConstants.name)){let h=new Error("Too many verification attempts. Please wait before trying again.");throw d("RATE_LIMIT_EXCEEDED",{apiKey:this.config.apiKey.substring(0,8)+"...",origin:window.location.origin,sessionId:i?i.substring(0,8)+"...":"undefined"},this.brandConstants.name),(g=(c=this.config).onError)==null||g.call(c,h),h}let p=await this.buildVerificationUrl(e,i);d("VERIFICATION_INITIATED",{environment:this.config.environment,mode:this.config.mode,sessionId:i?i.substring(0,8)+"...":"undefined",origin:window.location.origin},this.brandConstants.name),this.config.mode==="new-tab"?this.openNewTab(p,i):(this.unlockVerification(),this.redirect(p))}catch(a){throw this.unlockVerification(),a}}async buildVerificationUrl(e,t){if(!this.lastVerifyUrl)throw new Error("Server did not return an authoritative verification URL");let i=e.language||this.config.language,n=new URL(this.lastVerifyUrl),s=new URL(this.getUrlConfig().verifyUiUrl).origin;if(n.origin!==s)throw new Error("Server returned an untrusted verification origin");if(n.searchParams.get("sessionId")!==t||!n.searchParams.get("sessionToken"))throw new Error("Server verification URL is missing authoritative session context");let o=new URL(this.applyLocalVerifyOverride(n.toString()));return o.searchParams.set("mode",this.config.mode),e.skipIntro&&o.searchParams.set("skip_intro","true"),e.autoReturn&&o.searchParams.set("auto_return","true"),i&&o.searchParams.set("lang",i),o.toString()}redirect(e){window.location.href=e}openNewTab(e,t){var c,g;if(this.cleanup(),this.hasReceivedResult=!1,this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),(this.config.newTabTarget||"popup")==="tab"?this.popupWindow=window.open(e,"_blank"):this.popupWindow=window.open(e,this.brandConstants.popupName,"width=600,height=700"),!this.popupWindow){(g=(c=this.config).onError)==null||g.call(c,new Error("Failed to open verification window. Please check popup blocker settings."));return}let n=this.getTrustedOrigins(),s=this.getAllowedCustomOrigins(e),o=this.brandConstants.messageType,l=this.brandConstants.legacyMessageType;this.messageListener=a=>{var L,A,R,O,P,x,V;let p=(L=a.data)==null?void 0:L.type;if(!p||typeof p!="string"||!(l?[o,l]:[o]).includes(p))return;if(!K(a,n,s,this.brandConstants.name)){d("POSTMESSAGE_ORIGIN_BLOCKED",{origin:a.origin,environment:this.config.environment,expectedOrigins:`${this.brandConstants.name} trusted origins for ${this.config.environment}`,messageType:(A=a.data)==null?void 0:A.type},this.brandConstants.name);return}let I=B(a,t,o,l);if(!I.isValid){d("POSTMESSAGE_VALIDATION_FAILED",{error:I.error,origin:a.origin,sessionId:t.substring(0,8)+"...",messageType:(R=a.data)==null?void 0:R.type},this.brandConstants.name);return}let T=a.data.status;if(T==="cancelled"){this.handleCancellation(t,"postmessage");return}let m={sessionId:a.data.sessionId,status:T,timestamp:a.data.timestamp,externalUserId:a.data.externalUserId};this.hasReceivedResult=!0,d("VERIFICATION_COMPLETED",{status:m.status,sessionId:t.substring(0,8)+"...",origin:a.origin},this.brandConstants.name),this.cleanup({closePopup:!1}),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),m.status==="verified"?(P=(O=this.config).onComplete)==null||P.call(O,m):(V=(x=this.config).onError)==null||V.call(x,new Error(`Verification failed: ${m.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{this.popupWindow&&this.popupWindow.closed&&(d("POPUP_CLOSED_BY_USER",{sessionId:t.substring(0,8)+"...",environment:this.config.environment},this.brandConstants.name),this.hasReceivedResult||this.handleCancellation(t,"popup-closed"))},500)}setupAutoCleanup(){if(this.unloadListener=()=>{d("SDK_AUTO_CLEANUP",{environment:this.config.environment,trigger:"page_unload"},this.brandConstants.name),this.cleanup(),this.unlockVerification()},window.addEventListener("beforeunload",this.unloadListener),window.addEventListener("pagehide",this.unloadListener),window.history&&window.history.pushState){let e=window.history.pushState;window.history.pushState=(...t)=>(this.cleanup(),this.unlockVerification(),e.apply(window.history,t))}}getEnvironment(){return this.config.environment}unlockVerification(){this.isVerificationInProgress=!1,this.currentSessionId=null,d("VERIFICATION_UNLOCKED",{environment:this.config.environment,origin:window.location.origin},this.brandConstants.name)}cleanup(e={}){let t=e.closePopup!==!1;this.popupWindow&&(t&&!this.popupWindow.closed&&this.popupWindow.close(),(t||this.popupWindow.closed)&&(this.popupWindow=null)),this.messageListener&&(window.removeEventListener("message",this.messageListener),this.messageListener=null),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null)}handleCancellation(e,t){if(this.hasReceivedResult)return;this.hasReceivedResult=!0,d("VERIFICATION_CANCELLED",{source:t,sessionId:e.substring(0,8)+"...",environment:this.config.environment,origin:window.location.origin},this.brandConstants.name),this.cleanup(),this.unlockVerification();let i=!0;if(this.config.onCancel)try{this.config.onCancel()===!1&&(i=!1)}catch(n){d("CANCEL_CALLBACK_FAILED",{error:n instanceof Error?n.message:String(n),sessionId:e.substring(0,8)+"..."},this.brandConstants.name)}i&&this.redirectToCancelUrl(e)}redirectToCancelUrl(e){if(this.config.cancelUrl)try{let t=decodeURIComponent(this.config.cancelUrl),i=new URL(t);i.searchParams.set("sessionId",e),i.searchParams.set("status","cancelled"),i.searchParams.set("timestamp",Date.now().toString()),this.lastExternalUserId&&i.searchParams.set("externalUserId",this.lastExternalUserId),window.location.href=i.toString()}catch(t){d("CANCEL_REDIRECT_FAILED",{error:t instanceof Error?t.message:String(t),sessionId:e.substring(0,8)+"..."},this.brandConstants.name)}}removeAutoCleanupListeners(){this.unloadListener&&(window.removeEventListener("beforeunload",this.unloadListener),window.removeEventListener("pagehide",this.unloadListener),this.unloadListener=null)}destroy(){d("SDK_DESTROYED",{environment:this.config.environment,origin:window.location.origin},this.brandConstants.name),this.cleanup(),this.unlockVerification(),this.removeAutoCleanupListeners()}getPortalApiUrl(){let e=this.getUrlConfig().apiUrl;if(!this.config.apiUrl)return e;let t=this.getUrlOrigin(this.config.apiUrl),i=this.getUrlOrigin(e);if(t===i||this.getLocalOrigin(this.config.apiUrl))return this.config.apiUrl;throw new Error("apiUrl must use the selected brand/environment service")}validateServiceOverrides(){let e=this.getUrlConfig();if(this.config.apiUrl){let t=this.getUrlOrigin(this.config.apiUrl),i=this.getUrlOrigin(e.apiUrl);if(t!==i&&!this.getLocalOrigin(this.config.apiUrl))throw new Error("apiUrl must use the selected brand/environment service")}if(this.config.verifyUrl){let t=this.getUrlOrigin(this.config.verifyUrl),i=this.getUrlOrigin(e.verifyUiUrl);if(t!==i&&!this.getLocalOrigin(this.config.verifyUrl))throw new Error("verifyUrl must use the selected brand/environment service")}}isPublicKey(){return this.config.apiKey.startsWith("pk_")}async createInternalSession(e){var t,i;try{let n=this.getPortalApiUrl(),s=await fetch(`${n}/api/v1/sessions/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({merchantId:this.config.apiKey,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,merchantName:document.title||window.location.hostname,externalUserId:e.externalUserId})});if(!s.ok){let c=await s.json().catch(()=>({})),g=c==null?void 0:c.code;if(this.isBillingBlockError(g)){let a=e.language||this.config.language;this.openBillingBlockPage(g,a)}throw new Error(`Failed to create session: ${s.status} ${s.statusText}. ${c.message||""}`)}let o=await s.json(),l=o.sessionId;if(!l)throw new Error("Server did not return a sessionId");return o.verifyUrl&&(this.lastVerifyUrl=o.verifyUrl),d("INTERNAL_SESSION_CREATED",{sessionId:l.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),l}catch(n){let s=n instanceof Error?n.message:String(n);throw d("INTERNAL_SESSION_FAILED",{error:s,environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),(i=(t=this.config).onError)==null||i.call(t,n),new Error(`Failed to create verification session: ${s}`)}}isBillingBlockError(e){return e==="SUBSCRIPTION_REQUIRED"||e==="PLAN_LIMIT_REACHED"||e==="SANDBOX_LIMIT_REACHED"}openBillingBlockPage(e,t){var i,n,s,o;try{let l=b(this.config.environment,this.getUrlConfig()),c=this.applyLocalVerifyOverride(l),g=new URL(c);g.searchParams.set("blocked",e);let a=t||this.config.language;if(a&&g.searchParams.set("lang",a),this.config.mode==="new-tab"){((this.config.newTabTarget||"popup")==="tab"?window.open(g.toString(),"_blank"):window.open(g.toString(),this.brandConstants.popupName,"width=600,height=700"))||(n=(i=this.config).onError)==null||n.call(i,new Error("Failed to open billing notice window. Please check popup blocker settings."));return}this.redirect(g.toString())}catch(l){let c=l instanceof Error?l.message:String(l);(o=(s=this.config).onError)==null||o.call(s,new Error(`Failed to open billing notice: ${c}`))}}getUrlConfig(){return this.brandUrls[this.config.environment]||this.brandUrls.production}getTrustedOrigins(){return this.getUrlConfig().trustedOrigins}getAllowedCustomOrigins(e){let t=new Set,i=this.getLocalOrigin(this.config.verifyUrl||null),n=this.getLocalOrigin(e||null);return i&&t.add(i),n&&t.add(n),Array.from(t)}getLocalOrigin(e){if(!e)return null;try{let t=new URL(e);if(y.LOCAL_HOSTNAMES.has(t.hostname))return t.origin}catch(t){return null}return null}getUrlOrigin(e){try{return new URL(e).origin}catch(t){return null}}applyLocalVerifyOverride(e){let t=this.getLocalOrigin(this.config.verifyUrl||null);if(!t)return e;try{let i=new URL(t),n=new URL(e);return n.protocol=i.protocol,n.host=i.host,n.toString()}catch(i){return e}}};y.LOCAL_HOSTNAMES=new Set(["localhost","127.0.0.1","::1"]);var U=y;var Y={production:{apiUrl:"https://api.safepassageapp.com",verifyUiUrl:"https://av.safepassageapp.com",engineUrl:"https://engine.safepassageapp.com",wsUrl:"wss://engine.safepassageapp.com/api/websocket/stream",trustedOrigins:["https://av.safepassageapp.com"]},staging:{apiUrl:"https://api.staging.safepassageapp.com",verifyUiUrl:"https://verify.staging.safepassageapp.com",engineUrl:"https://engine.staging.safepassageapp.com",wsUrl:"wss://engine.staging.safepassageapp.com/api/websocket/stream",trustedOrigins:["https://verify.staging.safepassageapp.com"]}},C={name:"SafePassage",messageType:"safepassage:verification:complete",legacyMessageType:"safepassage-verification",popupName:"safepassage-verify",docsUrl:"https://docs.safepassageapp.com"};var f=class extends U{constructor(e){super(e,Y,C)}},J="3.5.6";f.VERSION=J;typeof window!="undefined"&&(k(),M(`${C.name} SDK`));var fe=f;return oe(he);})();
|
|
3
3
|
if(typeof SafePassageSDK !== "undefined" && SafePassageSDK.SafePassage) { window.SafePassage = SafePassageSDK.SafePassage; window.SafePassage.VERSION = SafePassageSDK.VERSION; }
|