@safepassage/sdk 3.5.4 → 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 +18 -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 +132 -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/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)=>()=>(t&&(e=t(t=0)),e);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.verityav-staging-usw1a.safepassageapp.com",verifyUiUrl:"https://av.verityav-staging-usw1a.safepassageapp.com",engineUrl:"https://engine.verityav-staging-usw1a.safepassageapp.com",wsUrl:"wss://engine.verityav-staging-usw1a.safepassageapp.com/api/websocket/stream",trustedOrigins:["https://av.verityav-staging-usw1a.safepassageapp.com","https://portal.verityav-staging-usw1a.safepassageapp.com","https://api.verityav-staging-usw1a.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.4";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; }
|
package/sdk.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)=>()=>(t&&(e=t(t=0)),e);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.verityav-staging-usw1a.safepassageapp.com",verifyUiUrl:"https://av.verityav-staging-usw1a.safepassageapp.com",engineUrl:"https://engine.verityav-staging-usw1a.safepassageapp.com",wsUrl:"wss://engine.verityav-staging-usw1a.safepassageapp.com/api/websocket/stream",trustedOrigins:["https://av.verityav-staging-usw1a.safepassageapp.com","https://portal.verityav-staging-usw1a.safepassageapp.com","https://api.verityav-staging-usw1a.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.4";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; }
|
package/types/base.d.ts
CHANGED
|
@@ -32,18 +32,18 @@ export interface SDKConfig {
|
|
|
32
32
|
*/
|
|
33
33
|
newTabTarget?: 'popup' | 'tab';
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
* @deprecated Browser SDK policy overrides are ignored. Configure verification
|
|
36
|
+
* policy in the dashboard or create the session server-side with a private key.
|
|
37
37
|
*/
|
|
38
38
|
defaultChallengeAge?: number;
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* @deprecated Browser SDK policy overrides are ignored. Configure verification
|
|
41
|
+
* policy in the dashboard or create the session server-side with a private key.
|
|
42
42
|
*/
|
|
43
43
|
defaultVerificationMode?: 'L1' | 'L2';
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
45
|
+
* @deprecated Browser SDK policy overrides are ignored. Configure verification
|
|
46
|
+
* policy server-side.
|
|
47
47
|
*/
|
|
48
48
|
faceMatchEnabled?: boolean;
|
|
49
49
|
/**
|
|
@@ -76,20 +76,18 @@ export interface SDKConfig {
|
|
|
76
76
|
}
|
|
77
77
|
export interface VerificationOptions {
|
|
78
78
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
79
|
+
* @deprecated Browser SDK policy overrides are ignored. Configure verification
|
|
80
|
+
* policy in the dashboard or create the session server-side with a private key.
|
|
81
81
|
*/
|
|
82
82
|
challengeAge?: number;
|
|
83
83
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* L2: Full ID verification required
|
|
87
|
-
* @default Uses merchant dashboard configuration
|
|
84
|
+
* @deprecated Browser SDK policy overrides are ignored. Configure verification
|
|
85
|
+
* policy in the dashboard or create the session server-side with a private key.
|
|
88
86
|
*/
|
|
89
87
|
verificationMode?: 'L1' | 'L2';
|
|
90
88
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
89
|
+
* @deprecated Browser SDK policy overrides are ignored. Configure verification
|
|
90
|
+
* policy server-side.
|
|
93
91
|
*/
|
|
94
92
|
faceMatchEnabled?: boolean;
|
|
95
93
|
/**
|
|
@@ -136,32 +134,6 @@ export interface VerificationResult {
|
|
|
136
134
|
*/
|
|
137
135
|
externalUserId?: string;
|
|
138
136
|
}
|
|
139
|
-
export interface StatePayload {
|
|
140
|
-
merchantId: string;
|
|
141
|
-
sessionId: string;
|
|
142
|
-
returnUrl: string;
|
|
143
|
-
/** Optional cancel redirect for new-tab abandonment */
|
|
144
|
-
cancelUrl?: string;
|
|
145
|
-
challengeAge?: number;
|
|
146
|
-
verificationMode?: 'L1' | 'L2';
|
|
147
|
-
faceMatchEnabled?: boolean;
|
|
148
|
-
hasOverrides?: boolean;
|
|
149
|
-
externalUserId?: string;
|
|
150
|
-
timestamp: number;
|
|
151
|
-
apiUrl?: string;
|
|
152
|
-
engineUrl?: string;
|
|
153
|
-
wsUrl?: string;
|
|
154
|
-
environment?: 'production' | 'staging';
|
|
155
|
-
features?: {
|
|
156
|
-
testMode: boolean;
|
|
157
|
-
warmupPeriodMs: number;
|
|
158
|
-
qualityThreshold: number;
|
|
159
|
-
sandboxMode?: boolean;
|
|
160
|
-
};
|
|
161
|
-
handoffToken?: string;
|
|
162
|
-
sessionToken?: string;
|
|
163
|
-
verifyUrl?: string;
|
|
164
|
-
}
|
|
165
137
|
export interface SessionValidationResponse {
|
|
166
138
|
sessionId: string;
|
|
167
139
|
merchantId: string;
|
package/utils/validation.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Validation utilities for the SDK
|
|
3
3
|
*/
|
|
4
|
-
import type { SDKConfig
|
|
4
|
+
import type { SDKConfig } 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
|
-
export declare const STATE_EXPIRY_MS = 600000;
|
|
10
9
|
export interface ValidationContext {
|
|
11
10
|
brandName: string;
|
|
12
11
|
docsUrl: string;
|
|
@@ -22,5 +21,3 @@ export interface ValidationContext {
|
|
|
22
21
|
export declare function validateConfig(config: SDKConfig, context: ValidationContext): void;
|
|
23
22
|
export declare function validateSessionId(sessionId: string): void;
|
|
24
23
|
export declare function validateChallengeAge(age?: number): void;
|
|
25
|
-
export declare function generateState(payload: StatePayload, environment: 'production' | 'staging', hmacSecret: string, _logLabel?: string): Promise<string>;
|
|
26
|
-
export declare function parseState(state: string, environment: 'production' | 'staging', hmacSecret: string, logLabel?: string): Promise<StatePayload | null>;
|
package/utils/crypto.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
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>;
|