@safepassage/sdk 3.4.9 → 3.4.11
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 +21 -14
- package/brands/safepassage/index.d.ts +12 -0
- package/brands/safepassage/urls.d.ts +3 -0
- package/core/VerificationSDK.d.ts +135 -0
- package/index.d.ts +2 -0
- package/index.js +1143 -0
- package/package.json +14 -47
- package/safepassage.min.js +3 -0
- package/sdk.min.js +3 -0
- package/{dist/types/index.d.ts → types/base.d.ts} +28 -10
- package/utils/crypto.d.ts +31 -0
- package/utils/environment.d.ts +8 -0
- package/{dist/utils → utils}/polyfills.d.ts +1 -2
- package/{dist/utils → utils}/security.d.ts +11 -11
- package/{dist/utils → utils}/validation.d.ts +9 -12
- package/dist/core/SafePassageSDK.d.ts +0 -231
- package/dist/core/SafePassageSDK.js +0 -656
- package/dist/index.d.ts +0 -21
- package/dist/index.js +0 -1100
- package/dist/safepassage.min.js +0 -3
- package/dist/types/index.js +0 -4
- package/dist/utils/__mocks__/polyfills.d.ts +0 -3
- package/dist/utils/__mocks__/polyfills.js +0 -10
- package/dist/utils/crypto.d.ts +0 -105
- package/dist/utils/crypto.js +0 -216
- package/dist/utils/environment.d.ts +0 -13
- package/dist/utils/environment.js +0 -75
- package/dist/utils/polyfills.js +0 -58
- package/dist/utils/security.js +0 -190
- package/dist/utils/validation.js +0 -158
package/package.json
CHANGED
|
@@ -1,35 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@safepassage/sdk",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.11",
|
|
4
4
|
"description": "SafePassage SDK - Lightweight redirect-based age verification",
|
|
5
|
-
"main": "
|
|
6
|
-
"types": "
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
7
|
"files": [
|
|
8
|
-
"
|
|
8
|
+
"index.js",
|
|
9
|
+
"index.d.ts",
|
|
10
|
+
"brands",
|
|
11
|
+
"core",
|
|
12
|
+
"types",
|
|
13
|
+
"utils",
|
|
14
|
+
"sdk.min.js",
|
|
15
|
+
"safepassage.min.js",
|
|
9
16
|
"README.md",
|
|
10
17
|
"LICENSE"
|
|
11
18
|
],
|
|
12
|
-
"scripts": {
|
|
13
|
-
"build": "npm run clean && tsc && node build-sdk.js",
|
|
14
|
-
"clean": "rm -rf dist",
|
|
15
|
-
"dev": "tsc --watch",
|
|
16
|
-
"demo": "python3 -m http.server 8080",
|
|
17
|
-
"serve-demo": "npx serve . -p 8080",
|
|
18
|
-
"test": "jest",
|
|
19
|
-
"test:browser": "jest test/browser-compat",
|
|
20
|
-
"test:mobile": "jest test/mobile",
|
|
21
|
-
"test:performance": "jest test/performance",
|
|
22
|
-
"test:security": "jest test/security",
|
|
23
|
-
"test:accessibility": "jest test/accessibility",
|
|
24
|
-
"test:edge": "jest test/edge-cases",
|
|
25
|
-
"test:integration": "jest test/integration",
|
|
26
|
-
"test:all": "jest",
|
|
27
|
-
"test:coverage": "jest --coverage",
|
|
28
|
-
"benchmark": "node test/performance/benchmark.js",
|
|
29
|
-
"prepublishOnly": "npm run build",
|
|
30
|
-
"version": "npm run build && git add -A dist",
|
|
31
|
-
"postversion": "git push && git push --tags"
|
|
32
|
-
},
|
|
33
19
|
"keywords": [
|
|
34
20
|
"safepassage",
|
|
35
21
|
"age-verification",
|
|
@@ -40,33 +26,14 @@
|
|
|
40
26
|
],
|
|
41
27
|
"author": "SafePassage",
|
|
42
28
|
"license": "MIT",
|
|
43
|
-
"devDependencies": {
|
|
44
|
-
"@babel/core": "^7.27.4",
|
|
45
|
-
"@babel/preset-env": "^7.27.2",
|
|
46
|
-
"@testing-library/dom": "^10.4.0",
|
|
47
|
-
"@testing-library/jest-dom": "^6.6.3",
|
|
48
|
-
"@testing-library/user-event": "^14.6.1",
|
|
49
|
-
"@types/jest": "^29.5.14",
|
|
50
|
-
"@types/node": "^24.0.1",
|
|
51
|
-
"babel-jest": "^30.0.0",
|
|
52
|
-
"esbuild": "^0.25.5",
|
|
53
|
-
"jest": "^29.7.0",
|
|
54
|
-
"jest-environment-jsdom": "^29.7.0",
|
|
55
|
-
"jsdom": "^26.1.0",
|
|
56
|
-
"puppeteer": "^21.11.0",
|
|
57
|
-
"terser": "^5.42.0",
|
|
58
|
-
"ts-jest": "^29.4.0",
|
|
59
|
-
"typescript": "~5.8.3"
|
|
60
|
-
},
|
|
61
29
|
"repository": {
|
|
62
30
|
"type": "git",
|
|
63
|
-
"url": "https://github.com/
|
|
64
|
-
"directory": "services/verify-ui/sdk"
|
|
31
|
+
"url": "git+https://github.com/safepassageapp/sdk.git"
|
|
65
32
|
},
|
|
66
33
|
"bugs": {
|
|
67
|
-
"url": "https://github.com/
|
|
34
|
+
"url": "https://github.com/safepassageapp/sdk/issues"
|
|
68
35
|
},
|
|
69
|
-
"homepage": "https://
|
|
36
|
+
"homepage": "https://docs.safepassage.live/sdk",
|
|
70
37
|
"publishConfig": {
|
|
71
38
|
"access": "public",
|
|
72
39
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/* SafePassage SDK v3.4.11 */
|
|
2
|
+
"use strict";var SafePassageSDK=(()=>{var y=Object.defineProperty,de=Object.defineProperties,ge=Object.getOwnPropertyDescriptor,pe=Object.getOwnPropertyDescriptors,ue=Object.getOwnPropertyNames,v=Object.getOwnPropertySymbols;var T=Object.prototype.hasOwnProperty,W=Object.prototype.propertyIsEnumerable;var B=(n,e,t)=>e in n?y(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t,u=(n,e)=>{for(var t in e||(e={}))T.call(e,t)&&B(n,t,e[t]);if(v)for(var t of v(e))W.call(e,t)&&B(n,t,e[t]);return n},S=(n,e)=>de(n,pe(e));var H=(n,e)=>{var t={};for(var r in n)T.call(n,r)&&e.indexOf(r)<0&&(t[r]=n[r]);if(n!=null&&v)for(var r of v(n))e.indexOf(r)<0&&W.call(n,r)&&(t[r]=n[r]);return t};var A=(n,e)=>()=>(n&&(e=n(n=0)),e);var F=(n,e)=>{for(var t in e)y(n,t,{get:e[t],enumerable:!0})},fe=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of ue(e))!T.call(n,i)&&i!==t&&y(n,i,{get:()=>e[i],enumerable:!(r=ge(e,i))||r.enumerable});return n};var he=n=>fe(y({},"__esModule",{value:!0}),n);function me(n,e){return e.includes(n)}function G(n,e,t=[],r="SDK"){var s;let{origin:i}=n;return me(i,e)||t.length>0&&t.some(a=>{if(a.startsWith("*.")){let l=a.slice(2);return i.endsWith(`.${l}`)||i===`https://${l}`||i===`http://${l}`}return i===a})?!0:(console.warn(`${r} Security: Blocked PostMessage from untrusted origin: ${i}`,{trustedOrigins:e,allowedCustomOrigins:t,eventType:(s=n.data)==null?void 0:s.type}),!1)}function J(n,e,t,r){let{data:i}=n;return!i||typeof i!="object"?{isValid:!1,error:"Invalid message format"}:(r?[t,r]:[t]).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(n,e="SDK"){n==="production"&&window.location.protocol!=="https:"&&console.warn(`${e} Warning: HTTPS recommended for production environment`,{current:window.location.href})}function x(n,e,t="SDK"){try{let r=new URL(n);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(n))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch(r){return{isValid:!1,error:"Invalid URL format"}}}function g(n,e,t="SDK"){let r={timestamp:new Date().toISOString(),userAgent:typeof navigator!="undefined"?navigator.userAgent:"unknown",url:typeof window!="undefined"?window.location.href:"unknown"};console.warn(`${t} Security Event: ${n}`,u(u({},r),e))}var P,Y,R=A(()=>{"use strict";P=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(e,t="SDK"){let r=Date.now(),s=(this.attempts.get(e)||[]).filter(o=>r-o<this.timeWindow);return s.length>=this.maxAttempts?(console.warn(`${t} 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:()=>ve,generateHMAC:()=>V,generateSecureToken:()=>Z,parseSignedState:()=>ye,verifyHMAC:()=>z});async function V(n,e){let t=new TextEncoder,r=t.encode(e),i=t.encode(n),s=await crypto.subtle.importKey("raw",r,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),o=await crypto.subtle.sign("HMAC",s,i);return Array.from(new Uint8Array(o)).map(a=>a.toString(16).padStart(2,"0")).join("")}async function z(n,e,t){try{let r=await V(n,t);return we(e,r)}catch(r){return!1}}function we(n,e){if(n.length!==e.length)return!1;let t=0;for(let r=0;r<n.length;r++)t|=n.charCodeAt(r)^e.charCodeAt(r);return t===0}function Z(n=32){let e=new Uint8Array(n);return crypto.getRandomValues(e),Array.from(e,t=>t.toString(16).padStart(2,"0")).join("")}async function ve(n,e){let t=S(u({},n),{timestamp:Date.now(),nonce:Z(16)}),r=JSON.stringify(t),i=await V(r,e);return btoa(JSON.stringify({data:t,signature:i}))}async function ye(n,e,t=te,r="SDK"){try{let s=atob(n),o=JSON.parse(s);if(!o.data||!o.signature)return console.warn(`${r}: Invalid signed state format`),null;let{data:a,signature:l}=o,d=JSON.stringify(a);if(!await z(d,l,e))return console.warn(`${r}: State signature verification failed`),null;if(a.timestamp){let h=Date.now()-a.timestamp;if(h>t)return console.warn(`${r}: State parameter expired`,{age:h,maxAge:t}),null}let i=a,{timestamp:p,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(n,e){if(!n.apiKey)throw new Error("apiKey is required");if(n.apiKey.length>ie)throw new Error(`apiKey exceeds maximum length of ${ie} characters`);if(!Se.test(n.apiKey))throw new Error("Invalid apiKey format. Expected pk_xxx (public) or sk_xxx (private)");if(typeof window!="undefined"&&n.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(!n.returnUrl)throw new Error("returnUrl is required");if(n.returnUrl.length>U)throw new Error(`returnUrl exceeds maximum length of ${U} characters`);let t=Ue(),r=x(n.returnUrl,t,e.brandName);if(!r.isValid)throw new Error(`returnUrl validation failed: ${r.error}`);if(n.cancelUrl){if(n.cancelUrl.length>U)throw new Error(`cancelUrl exceeds maximum length of ${U} characters`);let i=x(n.cancelUrl,t,e.brandName);if(!i.isValid)throw new Error(`cancelUrl validation failed: ${i.error}`)}if(n.defaultChallengeAge!==void 0){if(n.defaultChallengeAge<ne)throw new Error(`defaultChallengeAge must be at least ${ne}`);if(n.defaultChallengeAge>re)throw new Error(`defaultChallengeAge cannot exceed ${re}`)}if(n.defaultVerificationMode&&!["L1","L2"].includes(n.defaultVerificationMode))throw new Error("defaultVerificationMode must be L1 or L2");if(n.mode&&!["redirect","new-tab"].includes(n.mode))throw new Error("mode must be redirect or new-tab");if(n.newTabTarget&&!["popup","tab"].includes(n.newTabTarget))throw new Error("newTabTarget must be popup or tab")}function Ue(){if(typeof window=="undefined")return"production";let n=window.location.hostname;return n.includes("staging")||n.includes("stage")?"staging":"production"}async function oe(n,e,t,r="SDK"){let{createSignedState:i}=await Promise.resolve().then(()=>(ee(),Q));return i(n,t)}var ne,re,U,ie,te,Se,L=A(()=>{"use strict";R();ne=25,re=150,U=2048,ie=128,te=6e5,Se=/^(pk_|sk_)[a-zA-Z0-9_]+$/});var Ce={};F(Ce,{SafePassage:()=>m,VERSION:()=>ce,default:()=>be});function q(){crypto.randomUUID||(crypto.randomUUID=function(){let n=new Uint8Array(16);crypto.getRandomValues(n),n[6]=n[6]&15|64,n[8]=n[8]&63|128;let e=Array.from(n).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 j(n="SDK"){let e=[];if(!window.crypto||!window.crypto.getRandomValues)throw new Error(`${n} requires Web Crypto API support`);if(!window.crypto.subtle)throw new Error(`${n} 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(`${n} Browser Compatibility:`,e.join("; "))}L();function E(n,e){let t=e.verifyUiUrl;if(!t||!t.startsWith("https://"))throw new Error(`HTTPS required for ${n} environment`);return t}function Ee(n,e){let t=e.apiUrl;if(!t||!t.startsWith("https://"))throw new Error(`HTTPS required for API URLs in ${n} environment`);return t}function ae(n,e,t="SDK"){let r=window.location.protocol==="https:";switch(n){case"production":r||console.warn(`${t} Warning: HTTPS recommended for production environment`);break;case"staging":r||console.warn(`${t} Warning: HTTPS strongly recommended in staging environment`);break}try{E(n,e),Ee(n,e)}catch(i){let s=i instanceof Error?i.message:String(i);throw new Error(`Environment configuration validation failed: ${s}`)}}R();var C=class C{constructor(e,t,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=t,this.brandConstants=r,se(e,{brandName:this.brandConstants.name,docsUrl:this.brandConstants.docsUrl});let i=e.environment||this.detectEnvironment();i!=="staging"&&i!=="production"&&(console.warn(`${this.brandConstants.name} SDK: Unknown environment '${i}', defaulting to 'production'`),i="production"),this.config=S(u({},e),{environment:i,mode:e.mode||"redirect",newTabTarget:e.newTabTarget||"popup"}),ae(this.config.environment,this.getUrlConfig(),this.brandConstants.name),X(this.config.environment,this.brandConstants.name),g("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,o,a,l,d;let t=this.isPublicKey(),r;if(t)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 c=new Error(`Verification already in progress for session ${(i=this.currentSessionId)==null?void 0:i.substring(0,8)}...`);throw g("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=(o=this.config).onError)==null||a.call(o,c),c}this.isVerificationInProgress=!0,this.currentSessionId=r,this.lastExternalUserId=e.externalUserId||null;try{let c=`${this.config.apiKey}:${window.location.origin}`;if(!Y.isAllowed(c,this.brandConstants.name)){let f=new Error("Too many verification attempts. Please wait before trying again.");throw g("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=(l=this.config).onError)==null||d.call(l,f),f}let p=await this.buildVerificationUrl(e,r);g("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(p,r):(this.unlockVerification(),this.redirect(p))}catch(c){throw this.unlockVerification(),c}}async buildVerificationUrl(e,t){var d;let r=this.config.verifyUrl||E(this.config.environment,this.getUrlConfig()),i=e.challengeAge!==void 0,s=e.verificationMode!==void 0,o=i||s,a=await oe({merchantId:this.config.apiKey,sessionId:t,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:e.challengeAge||this.config.defaultChallengeAge,verificationMode:e.verificationMode||this.config.defaultVerificationMode,hasOverrides:o,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:(d=this.lastSandboxMode)!=null?d:!1},handoffToken:this.temporaryHandoffToken||void 0,sessionToken:this.lastSessionToken||void 0,verifyUrl:this.lastVerifyUrl||void 0},this.config.environment,this.getHmacSecret(),this.brandConstants.name);if(this.lastVerifyUrl)try{let c=this.applyLocalVerifyOverride(this.lastVerifyUrl),p=new URL(c);return p.searchParams.set("state",a),p.searchParams.set("mode",this.config.mode),e.skipIntro&&p.searchParams.set("skip_intro","true"),e.autoReturn&&p.searchParams.set("auto_return","true"),p.toString()}catch(c){}let l=new URLSearchParams({state:a,sessionId:t,mode:this.config.mode});return e.skipIntro&&l.set("skip_intro","true"),e.autoReturn&&l.set("auto_return","true"),`${r}/?${l.toString()}`}redirect(e){window.location.href=e}openNewTab(e,t){var l,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=(l=this.config).onError)==null||d.call(l,new Error("Failed to open verification window. Please check popup blocker settings."));return}let i=this.getTrustedOrigins(),s=this.getAllowedCustomOrigins(e),o=this.brandConstants.messageType,a=this.brandConstants.legacyMessageType;this.messageListener=c=>{var D,M,_,O,$,N,K;let p=(D=c.data)==null?void 0:D.type;if(!p||typeof p!="string"||!(a?[o,a]:[o]).includes(p))return;if(!G(c,i,s,this.brandConstants.name)){g("POSTMESSAGE_ORIGIN_BLOCKED",{origin:c.origin,environment:this.config.environment,expectedOrigins:`${this.brandConstants.name} trusted origins for ${this.config.environment}`,messageType:(M=c.data)==null?void 0:M.type},this.brandConstants.name);return}let I=J(c,t,o,a);if(!I.isValid){g("POSTMESSAGE_VALIDATION_FAILED",{error:I.error,origin:c.origin,sessionId:t.substring(0,8)+"...",messageType:(_=c.data)==null?void 0:_.type},this.brandConstants.name);return}let h=c.data.status;if(h==="cancelled"){this.handleCancellation(t,"postmessage");return}let w={sessionId:c.data.sessionId,status:h,timestamp:c.data.timestamp,externalUserId:c.data.externalUserId};this.hasReceivedResult=!0,g("VERIFICATION_COMPLETED",{status:w.status,sessionId:t.substring(0,8)+"...",origin:c.origin},this.brandConstants.name),this.cleanup({closePopup:!1}),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),w.status==="verified"?($=(O=this.config).onComplete)==null||$.call(O,w):(K=(N=this.config).onError)==null||K.call(N,new Error(`Verification failed: ${w.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{this.popupWindow&&this.popupWindow.closed&&(g("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=()=>{g("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))}}detectEnvironment(){let e=window.location.hostname;return e.includes("staging")||e.includes("stage")?"staging":"production"}getEnvironment(){return this.config.environment}unlockVerification(){this.isVerificationInProgress=!1,this.currentSessionId=null,g("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,g("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 r=!0;if(this.config.onCancel)try{this.config.onCancel()===!1&&(r=!1)}catch(i){g("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 t=decodeURIComponent(this.config.cancelUrl),r=new URL(t);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(t){g("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(){g("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 t,r;try{let i=this.getPortalApiUrl(),s=await fetch(`${i}/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,merchantName:document.title||window.location.hostname,externalUserId:e.externalUserId})});if(!s.ok){let l=await s.json().catch(()=>({})),d=l==null?void 0:l.code;throw this.isBillingBlockError(d)&&this.openBillingBlockPage(d,l==null?void 0:l.portalUrl),new Error(`Failed to create session: ${s.status} ${s.statusText}. ${l.message||""}`)}let o=await s.json(),a=o.sessionId;if(!a)throw new Error("Server did not return a sessionId");return o.verifyUrl&&(this.lastVerifyUrl=o.verifyUrl),o.sessionToken&&(this.lastSessionToken=o.sessionToken),o.handoffToken&&(this.temporaryHandoffToken=o.handoffToken),typeof o.sandboxMode=="boolean"?this.lastSandboxMode=o.sandboxMode:this.lastSandboxMode=null,g("INTERNAL_SESSION_CREATED",{sessionId:a.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),a}catch(i){let s=i instanceof Error?i.message:String(i);throw g("INTERNAL_SESSION_FAILED",{error:s,environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),(r=(t=this.config).onError)==null||r.call(t,i),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 r,i,s,o;try{let a=this.config.verifyUrl||E(this.config.environment,this.getUrlConfig()),l=this.applyLocalVerifyOverride(a),d=new URL(l);if(d.searchParams.set("blocked",e),t&&d.searchParams.set("portalUrl",t),this.config.mode==="new-tab"){((this.config.newTabTarget||"popup")==="tab"?window.open(d.toString(),"_blank"):window.open(d.toString(),this.brandConstants.popupName,"width=600,height=700"))||(i=(r=this.config).onError)==null||i.call(r,new Error("Failed to open billing notice window. Please check popup blocker settings."));return}this.redirect(d.toString())}catch(a){let l=a instanceof Error?a.message:String(a);(o=(s=this.config).onError)==null||o.call(s,new Error(`Failed to open billing notice: ${l}`))}}getUrlConfig(){return this.brandUrls[this.config.environment]||this.brandUrls.production}getTrustedOrigins(){return this.getUrlConfig().trustedOrigins}getAllowedCustomOrigins(e){let t=new Set,r=this.getLocalOrigin(this.config.verifyUrl||null),i=this.getLocalOrigin(e||null);return r&&t.add(r),i&&t.add(i),Array.from(t)}getLocalOrigin(e){if(!e)return null;try{let t=new URL(e);if(C.LOCAL_HOSTNAMES.has(t.hostname))return t.origin}catch(t){return null}return null}applyLocalVerifyOverride(e){let t=this.getLocalOrigin(this.config.verifyUrl||null);if(!t)return e;try{let r=new URL(t),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}};C.LOCAL_HOSTNAMES=new Set(["localhost","127.0.0.1","::1"]);var b=C;var le={production:{apiUrl:"https://api.safepassage.live",verifyUiUrl:"https://av.safepassage.live",engineUrl:"https://engine.safepassage.live",wsUrl:"wss://engine.safepassage.live/api/websocket/stream",trustedOrigins:["https://av.safepassage.live","https://portal.safepassage.live","https://api.safepassage.live"]},staging:{apiUrl:"https://api.staging.safepassage.live",verifyUiUrl:"https://av.staging.safepassage.live",engineUrl:"https://engine.staging.safepassage.live",wsUrl:"wss://engine.staging.safepassage.live/api/websocket/stream",trustedOrigins:["https://av.staging.safepassage.live","https://portal.staging.safepassage.live","https://api.staging.safepassage.live"]}},k={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.safepassage.live"};var m=class extends b{constructor(e){super(e,le,k)}},ce="3.4.9";m.VERSION=ce;typeof window!="undefined"&&(q(),j(`${k.name} SDK`));var be=m;return he(Ce);})();
|
|
3
|
+
if(typeof SafePassageSDK !== "undefined" && SafePassageSDK.SafePassage) { window.SafePassage = SafePassageSDK.SafePassage; window.SafePassage.VERSION = SafePassageSDK.VERSION; }
|
package/sdk.min.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/* SafePassage SDK v3.4.11 */
|
|
2
|
+
"use strict";var SafePassageSDK=(()=>{var y=Object.defineProperty,de=Object.defineProperties,ge=Object.getOwnPropertyDescriptor,pe=Object.getOwnPropertyDescriptors,ue=Object.getOwnPropertyNames,v=Object.getOwnPropertySymbols;var T=Object.prototype.hasOwnProperty,W=Object.prototype.propertyIsEnumerable;var B=(n,e,t)=>e in n?y(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t,u=(n,e)=>{for(var t in e||(e={}))T.call(e,t)&&B(n,t,e[t]);if(v)for(var t of v(e))W.call(e,t)&&B(n,t,e[t]);return n},S=(n,e)=>de(n,pe(e));var H=(n,e)=>{var t={};for(var r in n)T.call(n,r)&&e.indexOf(r)<0&&(t[r]=n[r]);if(n!=null&&v)for(var r of v(n))e.indexOf(r)<0&&W.call(n,r)&&(t[r]=n[r]);return t};var A=(n,e)=>()=>(n&&(e=n(n=0)),e);var F=(n,e)=>{for(var t in e)y(n,t,{get:e[t],enumerable:!0})},fe=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of ue(e))!T.call(n,i)&&i!==t&&y(n,i,{get:()=>e[i],enumerable:!(r=ge(e,i))||r.enumerable});return n};var he=n=>fe(y({},"__esModule",{value:!0}),n);function me(n,e){return e.includes(n)}function G(n,e,t=[],r="SDK"){var s;let{origin:i}=n;return me(i,e)||t.length>0&&t.some(a=>{if(a.startsWith("*.")){let l=a.slice(2);return i.endsWith(`.${l}`)||i===`https://${l}`||i===`http://${l}`}return i===a})?!0:(console.warn(`${r} Security: Blocked PostMessage from untrusted origin: ${i}`,{trustedOrigins:e,allowedCustomOrigins:t,eventType:(s=n.data)==null?void 0:s.type}),!1)}function J(n,e,t,r){let{data:i}=n;return!i||typeof i!="object"?{isValid:!1,error:"Invalid message format"}:(r?[t,r]:[t]).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(n,e="SDK"){n==="production"&&window.location.protocol!=="https:"&&console.warn(`${e} Warning: HTTPS recommended for production environment`,{current:window.location.href})}function x(n,e,t="SDK"){try{let r=new URL(n);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(n))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch(r){return{isValid:!1,error:"Invalid URL format"}}}function g(n,e,t="SDK"){let r={timestamp:new Date().toISOString(),userAgent:typeof navigator!="undefined"?navigator.userAgent:"unknown",url:typeof window!="undefined"?window.location.href:"unknown"};console.warn(`${t} Security Event: ${n}`,u(u({},r),e))}var P,Y,R=A(()=>{"use strict";P=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(e,t="SDK"){let r=Date.now(),s=(this.attempts.get(e)||[]).filter(o=>r-o<this.timeWindow);return s.length>=this.maxAttempts?(console.warn(`${t} 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:()=>ve,generateHMAC:()=>V,generateSecureToken:()=>Z,parseSignedState:()=>ye,verifyHMAC:()=>z});async function V(n,e){let t=new TextEncoder,r=t.encode(e),i=t.encode(n),s=await crypto.subtle.importKey("raw",r,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),o=await crypto.subtle.sign("HMAC",s,i);return Array.from(new Uint8Array(o)).map(a=>a.toString(16).padStart(2,"0")).join("")}async function z(n,e,t){try{let r=await V(n,t);return we(e,r)}catch(r){return!1}}function we(n,e){if(n.length!==e.length)return!1;let t=0;for(let r=0;r<n.length;r++)t|=n.charCodeAt(r)^e.charCodeAt(r);return t===0}function Z(n=32){let e=new Uint8Array(n);return crypto.getRandomValues(e),Array.from(e,t=>t.toString(16).padStart(2,"0")).join("")}async function ve(n,e){let t=S(u({},n),{timestamp:Date.now(),nonce:Z(16)}),r=JSON.stringify(t),i=await V(r,e);return btoa(JSON.stringify({data:t,signature:i}))}async function ye(n,e,t=te,r="SDK"){try{let s=atob(n),o=JSON.parse(s);if(!o.data||!o.signature)return console.warn(`${r}: Invalid signed state format`),null;let{data:a,signature:l}=o,d=JSON.stringify(a);if(!await z(d,l,e))return console.warn(`${r}: State signature verification failed`),null;if(a.timestamp){let h=Date.now()-a.timestamp;if(h>t)return console.warn(`${r}: State parameter expired`,{age:h,maxAge:t}),null}let i=a,{timestamp:p,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(n,e){if(!n.apiKey)throw new Error("apiKey is required");if(n.apiKey.length>ie)throw new Error(`apiKey exceeds maximum length of ${ie} characters`);if(!Se.test(n.apiKey))throw new Error("Invalid apiKey format. Expected pk_xxx (public) or sk_xxx (private)");if(typeof window!="undefined"&&n.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(!n.returnUrl)throw new Error("returnUrl is required");if(n.returnUrl.length>U)throw new Error(`returnUrl exceeds maximum length of ${U} characters`);let t=Ue(),r=x(n.returnUrl,t,e.brandName);if(!r.isValid)throw new Error(`returnUrl validation failed: ${r.error}`);if(n.cancelUrl){if(n.cancelUrl.length>U)throw new Error(`cancelUrl exceeds maximum length of ${U} characters`);let i=x(n.cancelUrl,t,e.brandName);if(!i.isValid)throw new Error(`cancelUrl validation failed: ${i.error}`)}if(n.defaultChallengeAge!==void 0){if(n.defaultChallengeAge<ne)throw new Error(`defaultChallengeAge must be at least ${ne}`);if(n.defaultChallengeAge>re)throw new Error(`defaultChallengeAge cannot exceed ${re}`)}if(n.defaultVerificationMode&&!["L1","L2"].includes(n.defaultVerificationMode))throw new Error("defaultVerificationMode must be L1 or L2");if(n.mode&&!["redirect","new-tab"].includes(n.mode))throw new Error("mode must be redirect or new-tab");if(n.newTabTarget&&!["popup","tab"].includes(n.newTabTarget))throw new Error("newTabTarget must be popup or tab")}function Ue(){if(typeof window=="undefined")return"production";let n=window.location.hostname;return n.includes("staging")||n.includes("stage")?"staging":"production"}async function oe(n,e,t,r="SDK"){let{createSignedState:i}=await Promise.resolve().then(()=>(ee(),Q));return i(n,t)}var ne,re,U,ie,te,Se,L=A(()=>{"use strict";R();ne=25,re=150,U=2048,ie=128,te=6e5,Se=/^(pk_|sk_)[a-zA-Z0-9_]+$/});var Ce={};F(Ce,{SafePassage:()=>m,VERSION:()=>ce,default:()=>be});function q(){crypto.randomUUID||(crypto.randomUUID=function(){let n=new Uint8Array(16);crypto.getRandomValues(n),n[6]=n[6]&15|64,n[8]=n[8]&63|128;let e=Array.from(n).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 j(n="SDK"){let e=[];if(!window.crypto||!window.crypto.getRandomValues)throw new Error(`${n} requires Web Crypto API support`);if(!window.crypto.subtle)throw new Error(`${n} 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(`${n} Browser Compatibility:`,e.join("; "))}L();function E(n,e){let t=e.verifyUiUrl;if(!t||!t.startsWith("https://"))throw new Error(`HTTPS required for ${n} environment`);return t}function Ee(n,e){let t=e.apiUrl;if(!t||!t.startsWith("https://"))throw new Error(`HTTPS required for API URLs in ${n} environment`);return t}function ae(n,e,t="SDK"){let r=window.location.protocol==="https:";switch(n){case"production":r||console.warn(`${t} Warning: HTTPS recommended for production environment`);break;case"staging":r||console.warn(`${t} Warning: HTTPS strongly recommended in staging environment`);break}try{E(n,e),Ee(n,e)}catch(i){let s=i instanceof Error?i.message:String(i);throw new Error(`Environment configuration validation failed: ${s}`)}}R();var C=class C{constructor(e,t,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=t,this.brandConstants=r,se(e,{brandName:this.brandConstants.name,docsUrl:this.brandConstants.docsUrl});let i=e.environment||this.detectEnvironment();i!=="staging"&&i!=="production"&&(console.warn(`${this.brandConstants.name} SDK: Unknown environment '${i}', defaulting to 'production'`),i="production"),this.config=S(u({},e),{environment:i,mode:e.mode||"redirect",newTabTarget:e.newTabTarget||"popup"}),ae(this.config.environment,this.getUrlConfig(),this.brandConstants.name),X(this.config.environment,this.brandConstants.name),g("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,o,a,l,d;let t=this.isPublicKey(),r;if(t)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 c=new Error(`Verification already in progress for session ${(i=this.currentSessionId)==null?void 0:i.substring(0,8)}...`);throw g("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=(o=this.config).onError)==null||a.call(o,c),c}this.isVerificationInProgress=!0,this.currentSessionId=r,this.lastExternalUserId=e.externalUserId||null;try{let c=`${this.config.apiKey}:${window.location.origin}`;if(!Y.isAllowed(c,this.brandConstants.name)){let f=new Error("Too many verification attempts. Please wait before trying again.");throw g("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=(l=this.config).onError)==null||d.call(l,f),f}let p=await this.buildVerificationUrl(e,r);g("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(p,r):(this.unlockVerification(),this.redirect(p))}catch(c){throw this.unlockVerification(),c}}async buildVerificationUrl(e,t){var d;let r=this.config.verifyUrl||E(this.config.environment,this.getUrlConfig()),i=e.challengeAge!==void 0,s=e.verificationMode!==void 0,o=i||s,a=await oe({merchantId:this.config.apiKey,sessionId:t,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:e.challengeAge||this.config.defaultChallengeAge,verificationMode:e.verificationMode||this.config.defaultVerificationMode,hasOverrides:o,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:(d=this.lastSandboxMode)!=null?d:!1},handoffToken:this.temporaryHandoffToken||void 0,sessionToken:this.lastSessionToken||void 0,verifyUrl:this.lastVerifyUrl||void 0},this.config.environment,this.getHmacSecret(),this.brandConstants.name);if(this.lastVerifyUrl)try{let c=this.applyLocalVerifyOverride(this.lastVerifyUrl),p=new URL(c);return p.searchParams.set("state",a),p.searchParams.set("mode",this.config.mode),e.skipIntro&&p.searchParams.set("skip_intro","true"),e.autoReturn&&p.searchParams.set("auto_return","true"),p.toString()}catch(c){}let l=new URLSearchParams({state:a,sessionId:t,mode:this.config.mode});return e.skipIntro&&l.set("skip_intro","true"),e.autoReturn&&l.set("auto_return","true"),`${r}/?${l.toString()}`}redirect(e){window.location.href=e}openNewTab(e,t){var l,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=(l=this.config).onError)==null||d.call(l,new Error("Failed to open verification window. Please check popup blocker settings."));return}let i=this.getTrustedOrigins(),s=this.getAllowedCustomOrigins(e),o=this.brandConstants.messageType,a=this.brandConstants.legacyMessageType;this.messageListener=c=>{var D,M,_,O,$,N,K;let p=(D=c.data)==null?void 0:D.type;if(!p||typeof p!="string"||!(a?[o,a]:[o]).includes(p))return;if(!G(c,i,s,this.brandConstants.name)){g("POSTMESSAGE_ORIGIN_BLOCKED",{origin:c.origin,environment:this.config.environment,expectedOrigins:`${this.brandConstants.name} trusted origins for ${this.config.environment}`,messageType:(M=c.data)==null?void 0:M.type},this.brandConstants.name);return}let I=J(c,t,o,a);if(!I.isValid){g("POSTMESSAGE_VALIDATION_FAILED",{error:I.error,origin:c.origin,sessionId:t.substring(0,8)+"...",messageType:(_=c.data)==null?void 0:_.type},this.brandConstants.name);return}let h=c.data.status;if(h==="cancelled"){this.handleCancellation(t,"postmessage");return}let w={sessionId:c.data.sessionId,status:h,timestamp:c.data.timestamp,externalUserId:c.data.externalUserId};this.hasReceivedResult=!0,g("VERIFICATION_COMPLETED",{status:w.status,sessionId:t.substring(0,8)+"...",origin:c.origin},this.brandConstants.name),this.cleanup({closePopup:!1}),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),w.status==="verified"?($=(O=this.config).onComplete)==null||$.call(O,w):(K=(N=this.config).onError)==null||K.call(N,new Error(`Verification failed: ${w.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{this.popupWindow&&this.popupWindow.closed&&(g("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=()=>{g("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))}}detectEnvironment(){let e=window.location.hostname;return e.includes("staging")||e.includes("stage")?"staging":"production"}getEnvironment(){return this.config.environment}unlockVerification(){this.isVerificationInProgress=!1,this.currentSessionId=null,g("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,g("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 r=!0;if(this.config.onCancel)try{this.config.onCancel()===!1&&(r=!1)}catch(i){g("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 t=decodeURIComponent(this.config.cancelUrl),r=new URL(t);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(t){g("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(){g("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 t,r;try{let i=this.getPortalApiUrl(),s=await fetch(`${i}/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,merchantName:document.title||window.location.hostname,externalUserId:e.externalUserId})});if(!s.ok){let l=await s.json().catch(()=>({})),d=l==null?void 0:l.code;throw this.isBillingBlockError(d)&&this.openBillingBlockPage(d,l==null?void 0:l.portalUrl),new Error(`Failed to create session: ${s.status} ${s.statusText}. ${l.message||""}`)}let o=await s.json(),a=o.sessionId;if(!a)throw new Error("Server did not return a sessionId");return o.verifyUrl&&(this.lastVerifyUrl=o.verifyUrl),o.sessionToken&&(this.lastSessionToken=o.sessionToken),o.handoffToken&&(this.temporaryHandoffToken=o.handoffToken),typeof o.sandboxMode=="boolean"?this.lastSandboxMode=o.sandboxMode:this.lastSandboxMode=null,g("INTERNAL_SESSION_CREATED",{sessionId:a.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),a}catch(i){let s=i instanceof Error?i.message:String(i);throw g("INTERNAL_SESSION_FAILED",{error:s,environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),(r=(t=this.config).onError)==null||r.call(t,i),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 r,i,s,o;try{let a=this.config.verifyUrl||E(this.config.environment,this.getUrlConfig()),l=this.applyLocalVerifyOverride(a),d=new URL(l);if(d.searchParams.set("blocked",e),t&&d.searchParams.set("portalUrl",t),this.config.mode==="new-tab"){((this.config.newTabTarget||"popup")==="tab"?window.open(d.toString(),"_blank"):window.open(d.toString(),this.brandConstants.popupName,"width=600,height=700"))||(i=(r=this.config).onError)==null||i.call(r,new Error("Failed to open billing notice window. Please check popup blocker settings."));return}this.redirect(d.toString())}catch(a){let l=a instanceof Error?a.message:String(a);(o=(s=this.config).onError)==null||o.call(s,new Error(`Failed to open billing notice: ${l}`))}}getUrlConfig(){return this.brandUrls[this.config.environment]||this.brandUrls.production}getTrustedOrigins(){return this.getUrlConfig().trustedOrigins}getAllowedCustomOrigins(e){let t=new Set,r=this.getLocalOrigin(this.config.verifyUrl||null),i=this.getLocalOrigin(e||null);return r&&t.add(r),i&&t.add(i),Array.from(t)}getLocalOrigin(e){if(!e)return null;try{let t=new URL(e);if(C.LOCAL_HOSTNAMES.has(t.hostname))return t.origin}catch(t){return null}return null}applyLocalVerifyOverride(e){let t=this.getLocalOrigin(this.config.verifyUrl||null);if(!t)return e;try{let r=new URL(t),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}};C.LOCAL_HOSTNAMES=new Set(["localhost","127.0.0.1","::1"]);var b=C;var le={production:{apiUrl:"https://api.safepassage.live",verifyUiUrl:"https://av.safepassage.live",engineUrl:"https://engine.safepassage.live",wsUrl:"wss://engine.safepassage.live/api/websocket/stream",trustedOrigins:["https://av.safepassage.live","https://portal.safepassage.live","https://api.safepassage.live"]},staging:{apiUrl:"https://api.staging.safepassage.live",verifyUiUrl:"https://av.staging.safepassage.live",engineUrl:"https://engine.staging.safepassage.live",wsUrl:"wss://engine.staging.safepassage.live/api/websocket/stream",trustedOrigins:["https://av.staging.safepassage.live","https://portal.staging.safepassage.live","https://api.staging.safepassage.live"]}},k={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.safepassage.live"};var m=class extends b{constructor(e){super(e,le,k)}},ce="3.4.9";m.VERSION=ce;typeof window!="undefined"&&(q(),j(`${k.name} SDK`));var be=m;return he(Ce);})();
|
|
3
|
+
if(typeof SafePassageSDK !== "undefined" && SafePassageSDK.SafePassage) { window.SafePassage = SafePassageSDK.SafePassage; window.SafePassage.VERSION = SafePassageSDK.VERSION; }
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* SDK type definitions (brand-neutral).
|
|
3
3
|
*/
|
|
4
|
-
export interface
|
|
4
|
+
export interface SDKConfig {
|
|
5
5
|
/**
|
|
6
6
|
* Public API key (starts with pk_)
|
|
7
7
|
*/
|
|
8
8
|
apiKey: string;
|
|
9
9
|
/**
|
|
10
10
|
* URL to redirect to after successful verification
|
|
11
|
-
* Must be pre-registered in dashboard
|
|
11
|
+
* Must be pre-registered in the dashboard
|
|
12
12
|
*/
|
|
13
13
|
returnUrl: string;
|
|
14
14
|
/**
|
|
15
|
-
* URL to redirect to if user cancels verification
|
|
16
|
-
*
|
|
17
|
-
* Kept for backwards compatibility but will be ignored.
|
|
15
|
+
* URL to redirect to if user cancels verification in new-tab mode
|
|
16
|
+
* Optional; cancellation redirects append `status=cancelled` and `sessionId`.
|
|
18
17
|
*/
|
|
19
18
|
cancelUrl?: string;
|
|
20
19
|
/**
|
|
@@ -27,6 +26,11 @@ export interface SafePassageConfig {
|
|
|
27
26
|
* @default 'redirect'
|
|
28
27
|
*/
|
|
29
28
|
mode?: 'redirect' | 'new-tab';
|
|
29
|
+
/**
|
|
30
|
+
* New-tab target behavior (new-tab mode only)
|
|
31
|
+
* @default 'popup'
|
|
32
|
+
*/
|
|
33
|
+
newTabTarget?: 'popup' | 'tab';
|
|
30
34
|
/**
|
|
31
35
|
* Default challenge age (minimum 25)
|
|
32
36
|
* Can be overridden per verification
|
|
@@ -37,14 +41,23 @@ export interface SafePassageConfig {
|
|
|
37
41
|
* Can be overridden per verification
|
|
38
42
|
*/
|
|
39
43
|
defaultVerificationMode?: 'L1' | 'L2';
|
|
44
|
+
/**
|
|
45
|
+
* Override API URL
|
|
46
|
+
*/
|
|
47
|
+
apiUrl?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Override Verify UI URL
|
|
50
|
+
*/
|
|
51
|
+
verifyUrl?: string;
|
|
40
52
|
/**
|
|
41
53
|
* Callback when verification completes (new-tab mode only)
|
|
42
54
|
*/
|
|
43
55
|
onComplete?: (result: VerificationResult) => void;
|
|
44
56
|
/**
|
|
45
57
|
* Callback when user cancels (new-tab mode only)
|
|
58
|
+
* Return false to suppress automatic cancelUrl redirect.
|
|
46
59
|
*/
|
|
47
|
-
onCancel?: () => void;
|
|
60
|
+
onCancel?: () => boolean | void;
|
|
48
61
|
/**
|
|
49
62
|
* Callback for errors
|
|
50
63
|
*/
|
|
@@ -66,7 +79,7 @@ export interface VerificationOptions {
|
|
|
66
79
|
/**
|
|
67
80
|
* External user identifier from merchant system
|
|
68
81
|
* Optional parameter that will be returned with verification results
|
|
69
|
-
* Useful for correlating
|
|
82
|
+
* Useful for correlating sessions with merchant user records
|
|
70
83
|
*/
|
|
71
84
|
externalUserId?: string;
|
|
72
85
|
/**
|
|
@@ -105,7 +118,7 @@ export interface StatePayload {
|
|
|
105
118
|
merchantId: string;
|
|
106
119
|
sessionId: string;
|
|
107
120
|
returnUrl: string;
|
|
108
|
-
/**
|
|
121
|
+
/** Optional cancel redirect for new-tab abandonment */
|
|
109
122
|
cancelUrl?: string;
|
|
110
123
|
challengeAge?: number;
|
|
111
124
|
verificationMode?: 'L1' | 'L2';
|
|
@@ -120,6 +133,7 @@ export interface StatePayload {
|
|
|
120
133
|
testMode: boolean;
|
|
121
134
|
warmupPeriodMs: number;
|
|
122
135
|
qualityThreshold: number;
|
|
136
|
+
sandboxMode?: boolean;
|
|
123
137
|
};
|
|
124
138
|
handoffToken?: string;
|
|
125
139
|
sessionToken?: string;
|
|
@@ -128,8 +142,12 @@ export interface StatePayload {
|
|
|
128
142
|
export interface SessionValidationResponse {
|
|
129
143
|
sessionId: string;
|
|
130
144
|
merchantId: string;
|
|
131
|
-
status: 'verified' | 'failed';
|
|
145
|
+
status: 'verified' | 'failed' | 'cancelled';
|
|
132
146
|
verified: boolean;
|
|
147
|
+
accessGranted?: boolean;
|
|
148
|
+
/**
|
|
149
|
+
* @deprecated This field is no longer provided and will always be undefined.
|
|
150
|
+
*/
|
|
133
151
|
estimatedAge?: number;
|
|
134
152
|
challengeAge: number;
|
|
135
153
|
verificationMode: 'L1' | 'L2';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crypto utilities for browser-compatible SDKs.
|
|
3
|
+
*
|
|
4
|
+
* These utilities handle HMAC signing, state parameter protection, and secure
|
|
5
|
+
* token generation for client-side tamper resistance.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT SECURITY NOTE:
|
|
8
|
+
* Client-side cryptography provides defense-in-depth but cannot be considered
|
|
9
|
+
* secure against determined attackers. Server-side validation is required for
|
|
10
|
+
* real security.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Generate HMAC-SHA256 signature using Web Crypto API
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateHMAC(data: string, secret: string): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Verify HMAC-SHA256 signature using Web Crypto API
|
|
18
|
+
*/
|
|
19
|
+
export declare function verifyHMAC(data: string, signature: string, secret: string): Promise<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Generate a secure random token for client-side use
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateSecureToken(length?: number): string;
|
|
24
|
+
/**
|
|
25
|
+
* Create signed state parameter with timestamp and integrity protection
|
|
26
|
+
*/
|
|
27
|
+
export declare function createSignedState(payload: unknown, hmacSecret: string): Promise<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Verify and parse signed state parameter
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseSignedState(signedState: string, hmacSecret: string, maxAge?: number, logLabel?: string): Promise<unknown | null>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment utilities with security enforcement.
|
|
3
|
+
*/
|
|
4
|
+
import type { UrlConfig } from '../core/VerificationSDK';
|
|
5
|
+
export declare function getEnvironmentUrl(environment: 'production' | 'staging', urls: UrlConfig): string;
|
|
6
|
+
export declare function getApiUrl(environment: 'production' | 'staging', urls: UrlConfig): string;
|
|
7
|
+
export declare function detectEnvironment(): 'production' | 'staging';
|
|
8
|
+
export declare function validateEnvironmentSecurity(environment: 'production' | 'staging', urls: UrlConfig, logLabel?: string): void;
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
5
|
* Polyfill for crypto.randomUUID (not supported in older browsers)
|
|
6
|
-
* Generates a UUID v4 using crypto.getRandomValues
|
|
7
6
|
*/
|
|
8
7
|
export declare function setupPolyfills(): void;
|
|
9
8
|
/**
|
|
10
9
|
* Check browser compatibility and warn about unsupported features
|
|
11
10
|
*/
|
|
12
|
-
export declare function checkBrowserCompatibility(): void;
|
|
11
|
+
export declare function checkBrowserCompatibility(logLabel?: string): void;
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Security utilities for
|
|
3
|
-
* Enhanced origin validation and security enforcement
|
|
2
|
+
* Security utilities for the SDK.
|
|
3
|
+
* Enhanced origin validation and security enforcement.
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* Validate if an origin is trusted for the given environment
|
|
7
7
|
*/
|
|
8
|
-
export declare function isOriginTrusted(origin: string,
|
|
8
|
+
export declare function isOriginTrusted(origin: string, trustedOrigins: string[]): boolean;
|
|
9
9
|
/**
|
|
10
10
|
* Enhanced origin validation with logging and strict allowlist
|
|
11
11
|
*/
|
|
12
|
-
export declare function validatePostMessageOrigin(event: MessageEvent,
|
|
12
|
+
export declare function validatePostMessageOrigin(event: MessageEvent, trustedOrigins: string[], allowedCustomOrigins?: string[], logLabel?: string): boolean;
|
|
13
13
|
/**
|
|
14
|
-
* Validate
|
|
14
|
+
* Validate verification message format and content
|
|
15
15
|
*/
|
|
16
|
-
export declare function
|
|
16
|
+
export declare function validateVerificationMessage(event: MessageEvent, expectedSessionId: string, expectedMessageType: string, legacyMessageType?: string): {
|
|
17
17
|
isValid: boolean;
|
|
18
18
|
error?: string;
|
|
19
19
|
};
|
|
20
20
|
/**
|
|
21
21
|
* Enforce HTTPS in production environment
|
|
22
22
|
*/
|
|
23
|
-
export declare function enforceHTTPS(environment: 'production' | 'staging'): void;
|
|
23
|
+
export declare function enforceHTTPS(environment: 'production' | 'staging', logLabel?: string): void;
|
|
24
24
|
/**
|
|
25
25
|
* Validate URL security for return/cancel URLs
|
|
26
26
|
*/
|
|
27
|
-
export declare function validateReturnUrl(url: string, environment: 'production' | 'staging'): {
|
|
27
|
+
export declare function validateReturnUrl(url: string, environment: 'production' | 'staging', _logLabel?: string): {
|
|
28
28
|
isValid: boolean;
|
|
29
29
|
error?: string;
|
|
30
30
|
};
|
|
@@ -39,12 +39,12 @@ declare class VerificationRateLimit {
|
|
|
39
39
|
private attempts;
|
|
40
40
|
private readonly maxAttempts;
|
|
41
41
|
private readonly timeWindow;
|
|
42
|
-
isAllowed(identifier: string): boolean;
|
|
42
|
+
isAllowed(identifier: string, logLabel?: string): boolean;
|
|
43
43
|
reset(identifier: string): void;
|
|
44
44
|
}
|
|
45
45
|
export declare const verificationRateLimit: VerificationRateLimit;
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
47
|
+
* Log security events with consistent formatting
|
|
48
48
|
*/
|
|
49
|
-
export declare function logSecurityEvent(event: string,
|
|
49
|
+
export declare function logSecurityEvent(event: string, metadata: Record<string, unknown>, logLabel?: string): void;
|
|
50
50
|
export {};
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Validation utilities for
|
|
2
|
+
* Validation utilities for the SDK
|
|
3
3
|
*/
|
|
4
|
-
import type {
|
|
4
|
+
import type { SDKConfig, StatePayload } from '../types/base';
|
|
5
5
|
export declare const MINIMUM_AGE = 25;
|
|
6
6
|
export declare const MAXIMUM_AGE = 150;
|
|
7
7
|
export declare const MAX_URL_LENGTH = 2048;
|
|
8
8
|
export declare const MAX_API_KEY_LENGTH = 128;
|
|
9
9
|
export declare const STATE_EXPIRY_MS = 600000;
|
|
10
|
-
export
|
|
10
|
+
export interface ValidationContext {
|
|
11
|
+
brandName: string;
|
|
12
|
+
docsUrl: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function validateConfig(config: SDKConfig, context: ValidationContext): void;
|
|
11
15
|
export declare function validateSessionId(sessionId: string): void;
|
|
12
16
|
export declare function validateChallengeAge(age?: number): void;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* Uses client-side HMAC for tamper resistance and server-side verification
|
|
16
|
-
*/
|
|
17
|
-
export declare function generateState(payload: StatePayload, environment: 'production' | 'staging'): Promise<string>;
|
|
18
|
-
/**
|
|
19
|
-
* Parse and validate signed state parameter
|
|
20
|
-
*/
|
|
21
|
-
export declare function parseState(state: string, environment: 'production' | 'staging'): Promise<StatePayload | null>;
|
|
17
|
+
export declare function generateState(payload: StatePayload, environment: 'production' | 'staging', hmacSecret: string, _logLabel?: string): Promise<string>;
|
|
18
|
+
export declare function parseState(state: string, environment: 'production' | 'staging', hmacSecret: string, logLabel?: string): Promise<StatePayload | null>;
|