@safepassage/sdk 3.1.0 → 3.1.2

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.
@@ -1,3 +1,3 @@
1
1
  /* SafePassage SDK v3.0.15 - Redirect Implementation with Enhanced Security */
2
- "use strict";var SafePassageSDK=(()=>{var g=Object.defineProperty;var J=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var p=(t,e)=>()=>(t&&(e=t(t=0)),e);var u=(t,e)=>{for(var n in e)g(t,n,{get:e[n],enumerable:!0})},z=(t,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of X(e))!Y.call(t,r)&&r!==n&&g(t,r,{get:()=>e[r],enumerable:!(i=J(e,r))||i.enumerable});return t};var P=t=>z(g({},"__esModule",{value:!0}),t);function Z(t,e){return b[e].includes(t)}function x(t,e,n=[]){let{origin:i}=t;return Z(i,e)||n.length>0&&n.some(s=>{if(s.startsWith("*.")){let o=s.slice(2);return i.endsWith(`.${o}`)||i===`https://${o}`||i===`http://${o}`}return i===s})?!0:(console.warn(`SafePassage Security: Blocked PostMessage from untrusted origin: ${i}`,{environment:e,trustedOrigins:b[e],allowedCustomOrigins:n,eventType:t.data?.type}),!1)}function T(t,e){let{data:n}=t;return!n||typeof n!="object"?{isValid:!1,error:"Invalid message format"}:n.type!=="safepassage:verification:complete"?{isValid:!1,error:"Invalid message 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}}function V(t){t==="production"&&window.location.protocol!=="https:"&&console.warn("SafePassage Warning: HTTPS recommended for production environment",{current:window.location.href})}function h(t,e){try{if(new URL(t).protocol!=="https:")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 r of i)if(r.test(t))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch{return{isValid:!1,error:"Invalid URL format"}}}function M(){if(crypto.randomUUID)return crypto.randomUUID();let t=new Uint8Array(16);crypto.getRandomValues(t);let e=Array.from(t).map(n=>n.toString(16).padStart(2,"0")).join("");return[e.slice(0,8),e.slice(8,12),"4"+e.slice(13,16),(parseInt(e.slice(16,17),16)&3|8).toString(16)+e.slice(17,20),e.slice(20,32)].join("-")}function a(t,e){console.warn(`SafePassage Security Event: ${t}`,{timestamp:new Date().toISOString(),userAgent:navigator.userAgent,url:window.location.href,...e})}var b,f,k,m=p(()=>{"use strict";b={production:["https://av.safepassageapp.com","https://portal.safepassageapp.com","https://api.safepassageapp.com"],staging:["https://av.staging.safepassageapp.com","https://portal.staging.safepassageapp.com","https://api.staging.safepassageapp.com"]};f=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(e){let n=Date.now(),r=(this.attempts.get(e)||[]).filter(s=>n-s<this.timeWindow);return r.length>=this.maxAttempts?(console.warn(`SafePassage Security: Rate limit exceeded for ${e}`),!1):(r.push(n),this.attempts.set(e,r),!0)}reset(e){this.attempts.delete(e)}},k=new f});var _={};u(_,{createSignedState:()=>ee,generateHMAC:()=>w,generateSecureToken:()=>C,getSigningSecret:()=>v,parseSignedState:()=>te,verifyHMAC:()=>L});async function w(t,e){let n=new TextEncoder,i=n.encode(e),r=n.encode(t),s=await crypto.subtle.importKey("raw",i,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),o=await crypto.subtle.sign("HMAC",s,r);return Array.from(new Uint8Array(o)).map(l=>l.toString(16).padStart(2,"0")).join("")}async function L(t,e,n){try{let i=await w(t,n);return Q(e,i)}catch{return!1}}function Q(t,e){if(t.length!==e.length)return!1;let n=0;for(let i=0;i<t.length;i++)n|=t.charCodeAt(i)^e.charCodeAt(i);return n===0}function C(t=32){let e=new Uint8Array(t);return crypto.getRandomValues(e),Array.from(e,n=>n.toString(16).padStart(2,"0")).join("")}function v(t){return{production:"safepassage-prod-hmac-2025",staging:"safepassage-stage-hmac-2025"}[t]}async function ee(t,e){let n={...t,timestamp:Date.now(),nonce:C(16)},i=JSON.stringify(n),r=v(e),s=await w(i,r);return btoa(JSON.stringify({data:n,signature:s}))}async function te(t,e,n=O){try{let i=atob(t),r=JSON.parse(i);if(!r.data||!r.signature)return console.warn("SafePassage: Invalid signed state format"),null;let{data:s,signature:o}=r,l=JSON.stringify(s),B=v(e);if(!await L(l,o,B))return console.warn("SafePassage: State signature verification failed"),null;if(s.timestamp){let E=Date.now()-s.timestamp;if(E>n)return console.warn("SafePassage: State parameter expired",{age:E,maxAge:n}),null}let{timestamp:le,nonce:pe,...G}=s;return G}catch(i){return console.warn("SafePassage: Failed to parse signed state",i),null}}var R=p(()=>{"use strict";S()});function K(t){if(!t.apiKey)throw new Error("apiKey is required");if(t.apiKey.length>N)throw new Error(`apiKey exceeds maximum length of ${N} characters`);if(!ne.test(t.apiKey))throw new Error("Invalid apiKey format. Expected pk_xxx (public) or sk_xxx (private)");if(typeof window<"u"&&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: https://docs.safepassageapp.com/server-side-sessions");if(!t.returnUrl)throw new Error("returnUrl is required");if(t.returnUrl.length>d)throw new Error(`returnUrl exceeds maximum length of ${d} characters`);if(!t.cancelUrl)throw new Error("cancelUrl is required");if(t.cancelUrl.length>d)throw new Error(`cancelUrl exceeds maximum length of ${d} characters`);let e=ie(),n=h(t.returnUrl,e);if(!n.isValid)throw new Error(`returnUrl validation failed: ${n.error}`);let i=h(t.cancelUrl,e);if(!i.isValid)throw new Error(`cancelUrl validation failed: ${i.error}`);if(t.defaultChallengeAge!==void 0){if(t.defaultChallengeAge<$)throw new Error(`defaultChallengeAge must be at least ${$}`);if(t.defaultChallengeAge>D)throw new Error(`defaultChallengeAge cannot exceed ${D}`)}if(t.defaultVerificationMode&&!["L1","L2"].includes(t.defaultVerificationMode))throw new Error("defaultVerificationMode must be L1 or L2");if(t.mode&&!["redirect","new-tab"].includes(t.mode))throw new Error("mode must be redirect or new-tab")}function ie(){if(typeof window>"u")return"production";let t=window.location.hostname;return t.includes("staging")||t.includes("stage")?"staging":"production"}async function W(t,e){let{createSignedState:n}=await Promise.resolve().then(()=>(R(),_));return n(t,e)}var $,D,d,N,O,ne,S=p(()=>{"use strict";m();$=25,D=150,d=2048,N=128,O=6e5,ne=/^(pk_|sk_)[a-zA-Z0-9_]+$/});function y(t){let e=re[t];if(!e.startsWith("https://"))throw new Error(`HTTPS required for ${t} environment`);return e}function se(t){let n={production:"https://api.safepassageapp.com",staging:"https://api.staging.safepassageapp.com"}[t];if(!n.startsWith("https://"))throw new Error(`HTTPS required for API URLs in ${t} environment`);return n}function H(t){let e=window.location.protocol==="https:";switch(t){case"production":e||console.warn("SafePassage Warning: HTTPS recommended for production environment");break;case"staging":e||console.warn("SafePassage Warning: HTTPS strongly recommended in staging environment");break}try{y(t),se(t)}catch(n){throw new Error(`Environment configuration validation failed: ${n}`)}}var re,q=p(()=>{"use strict";re={production:"https://av.safepassageapp.com",staging:"https://av.staging.safepassageapp.com"}});var j={};u(j,{SafePassage:()=>c,default:()=>oe});var c,oe,I=p(()=>{"use strict";S();q();m();c=class{constructor(e){this.popupWindow=null;this.messageListener=null;this.popupMonitorInterval=null;this.unloadListener=null;this.isVerificationInProgress=!1;this.currentSessionId=null;K(e),this.config={...e,environment:e.environment||this.detectEnvironment(),mode:e.mode||"redirect"},H(this.config.environment),V(this.config.environment),a("SDK_INITIALIZED",{environment:this.config.environment,mode:this.config.mode,origin:window.location.origin,protocol:window.location.protocol,hostname:window.location.hostname}),this.setupAutoCleanup()}async verify(e={}){let n=this.isPublicKey(),i=e.sessionId;if(n&&!i&&(i=await this.createInternalSession(e)),!n&&!i)throw new Error("sessionId is required for private API keys - must be a merchant-generated UUID v4");if(!i)throw new Error("Failed to create or obtain sessionId");if(this.isVerificationInProgress){let r=new Error(`Verification already in progress for session ${this.currentSessionId?.substring(0,8)}...`);throw a("RACE_CONDITION_PREVENTED",{currentSession:this.currentSessionId?.substring(0,8)+"...",attemptedSession:e.sessionId?e.sessionId.substring(0,8)+"...":"undefined",origin:window.location.origin}),this.config.onError?.(r),r}this.isVerificationInProgress=!0,this.currentSessionId=i;try{let r=`${this.config.apiKey}:${window.location.origin}`;if(!k.isAllowed(r)){let o=new Error("Too many verification attempts. Please wait before trying again.");throw a("RATE_LIMIT_EXCEEDED",{apiKey:this.config.apiKey.substring(0,8)+"...",origin:window.location.origin,sessionId:i?i.substring(0,8)+"...":"undefined"}),this.config.onError?.(o),o}let s=await this.buildVerificationUrl({...e,sessionId:i});a("VERIFICATION_INITIATED",{environment:this.config.environment,mode:this.config.mode,sessionId:i?i.substring(0,8)+"...":"undefined",origin:window.location.origin}),this.config.mode==="new-tab"?this.openNewTab(s,i):(this.unlockVerification(),this.redirect(s))}catch(r){throw this.unlockVerification(),r}}async buildVerificationUrl(e){let n=y(this.config.environment),i=e.challengeAge!==void 0,r=e.verificationMode!==void 0,s=i||r,o=await W({merchantId:this.config.apiKey,sessionId:e.sessionId,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:e.challengeAge||this.config.defaultChallengeAge,verificationMode:e.verificationMode||this.config.defaultVerificationMode,hasOverrides:s,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},handoffToken:this._temporaryHandoffToken},this.config.environment),l=new URLSearchParams({state:o,sessionId:e.sessionId,mode:this.config.mode});return`${n}/?${l.toString()}`}redirect(e){window.location.href=e}openNewTab(e,n){if(this.cleanup(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),this.popupWindow=window.open(e,"safepassage-verify","width=600,height=700"),!this.popupWindow){this.config.onError?.(new Error("Failed to open verification window. Please check popup blocker settings."));return}this.messageListener=i=>{if(!x(i,this.config.environment)){a("POSTMESSAGE_ORIGIN_BLOCKED",{origin:i.origin,environment:this.config.environment,expectedOrigins:`SafePassage trusted origins for ${this.config.environment}`,messageType:i.data?.type});return}let r=T(i,n);if(!r.isValid){a("POSTMESSAGE_VALIDATION_FAILED",{error:r.error,origin:i.origin,sessionId:n.substring(0,8)+"...",messageType:i.data?.type});return}let s={sessionId:i.data.sessionId,status:i.data.status};a("VERIFICATION_COMPLETED",{status:s.status,sessionId:n.substring(0,8)+"...",origin:i.origin}),this.cleanup(),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),s.status==="verified"?this.config.onComplete?.(s):s.status==="cancelled"?this.config.onCancel?.():this.config.onError?.(new Error(`Verification failed: ${s.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{this.popupWindow&&this.popupWindow.closed&&(a("POPUP_CLOSED_BY_USER",{sessionId:n.substring(0,8)+"...",environment:this.config.environment}),this.cleanup(),this.unlockVerification(),this.config.onCancel?.())},500)}setupAutoCleanup(){if(this.unloadListener=()=>{a("SDK_AUTO_CLEANUP",{environment:this.config.environment,trigger:"page_unload"}),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))}}detectEnvironment(){let e=window.location.hostname;return e.includes("staging")||e.includes("stage")?"staging":"production"}getEnvironment(){return this.config.environment}unlockVerification(){this.isVerificationInProgress=!1,this.currentSessionId=null,a("VERIFICATION_UNLOCKED",{environment:this.config.environment,origin:window.location.origin})}cleanup(){this.popupWindow&&!this.popupWindow.closed&&this.popupWindow.close(),this.popupWindow=null,this.messageListener&&(window.removeEventListener("message",this.messageListener),this.messageListener=null),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null)}removeAutoCleanupListeners(){this.unloadListener&&(window.removeEventListener("beforeunload",this.unloadListener),window.removeEventListener("pagehide",this.unloadListener),this.unloadListener=null)}destroy(){a("SDK_DESTROYED",{environment:this.config.environment,origin:window.location.origin}),this.cleanup(),this.unlockVerification(),this.removeAutoCleanupListeners()}getPortalApiUrl(){switch(this.config.environment){case"staging":return"https://api.staging.safepassageapp.com";case"production":return"https://api.safepassageapp.com";default:return"https://api.safepassageapp.com"}}getEngineUrl(){switch(this.config.environment){case"staging":return"https://engine.staging.safepassageapp.com";case"production":return"https://engine.safepassageapp.com";default:return"https://engine.safepassageapp.com"}}getWebSocketUrl(){switch(this.config.environment){case"staging":return"wss://engine.staging.safepassageapp.com/api/websocket/stream";case"production":return"wss://engine.safepassageapp.com/api/websocket/stream";default:return"wss://engine.safepassageapp.com/api/websocket/stream"}}isPublicKey(){return this.config.apiKey.startsWith("pk_")}async createInternalSession(e){let n=M();try{let i=this.getPortalApiUrl(),r=await fetch(`${i}/api/v1/sessions/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({merchantId:this.config.apiKey,sessionId:n,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:e.challengeAge,verificationMode:e.verificationMode,merchantName:document.title||window.location.hostname,externalUserId:e.externalUserId})});if(!r.ok){let o=await r.json().catch(()=>({}));throw new Error(`Failed to create session: ${r.status} ${r.statusText}. ${o.message||""}`)}let s=await r.json();return s.handoffToken&&(this._temporaryHandoffToken=s.handoffToken),a("INTERNAL_SESSION_CREATED",{sessionId:n.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"}),n}catch(i){let r=i instanceof Error?i.message:String(i);throw a("INTERNAL_SESSION_FAILED",{error:r,environment:this.config.environment,apiKeyType:"public"}),this.config.onError?.(i),new Error(`Failed to create verification session: ${r}`)}}},oe=c});var ae={};u(ae,{SafePassage:()=>c,VERSION:()=>F,default:()=>c});function U(){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 A(){let t=[];if(!window.crypto||!window.crypto.getRandomValues)throw new Error("SafePassage SDK requires Web Crypto API support");if(!window.crypto.subtle)throw new Error("SafePassage SDK requires Web Crypto subtle API for HMAC operations");crypto.randomUUID||t.push("crypto.randomUUID not supported, using polyfill"),window.URLSearchParams||t.push("URLSearchParams not supported, consider adding a polyfill for IE 11 support");try{if({a:{b:1}}?.a?.b!==1)throw new Error}catch{t.push("Optional chaining (?.) not supported, ensure transpilation for older browsers")}t.length>0&&console.warn("SafePassage SDK Browser Compatibility:",t.join("; "))}I();typeof window<"u"&&(U(),A());var F="3.0.15";if(typeof window<"u"&&window){let{SafePassage:t}=(I(),P(j)),e=window;e.SafePassage=t,e.SafePassage&&(e.SafePassage.VERSION=F)}return P(ae);})();
2
+ "use strict";var SafePassageSDK=(()=>{var g=Object.defineProperty;var J=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var p=(t,e)=>()=>(t&&(e=t(t=0)),e);var u=(t,e)=>{for(var n in e)g(t,n,{get:e[n],enumerable:!0})},z=(t,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of X(e))!Y.call(t,r)&&r!==n&&g(t,r,{get:()=>e[r],enumerable:!(i=J(e,r))||i.enumerable});return t};var P=t=>z(g({},"__esModule",{value:!0}),t);function Z(t,e){return b[e].includes(t)}function x(t,e,n=[]){let{origin:i}=t;return Z(i,e)||n.length>0&&n.some(s=>{if(s.startsWith("*.")){let o=s.slice(2);return i.endsWith(`.${o}`)||i===`https://${o}`||i===`http://${o}`}return i===s})?!0:(console.warn(`SafePassage Security: Blocked PostMessage from untrusted origin: ${i}`,{environment:e,trustedOrigins:b[e],allowedCustomOrigins:n,eventType:t.data?.type}),!1)}function T(t,e){let{data:n}=t;return!n||typeof n!="object"?{isValid:!1,error:"Invalid message format"}:n.type!=="safepassage:verification:complete"?{isValid:!1,error:"Invalid message 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}}function V(t){t==="production"&&window.location.protocol!=="https:"&&console.warn("SafePassage Warning: HTTPS recommended for production environment",{current:window.location.href})}function h(t,e){try{let n=new URL(t);if(n.protocol!=="https:"&&!(n.hostname==="localhost"||n.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 r of i)if(r.test(t))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch{return{isValid:!1,error:"Invalid URL format"}}}function M(){if(crypto.randomUUID)return crypto.randomUUID();let t=new Uint8Array(16);crypto.getRandomValues(t);let e=Array.from(t).map(n=>n.toString(16).padStart(2,"0")).join("");return[e.slice(0,8),e.slice(8,12),"4"+e.slice(13,16),(parseInt(e.slice(16,17),16)&3|8).toString(16)+e.slice(17,20),e.slice(20,32)].join("-")}function a(t,e){console.warn(`SafePassage Security Event: ${t}`,{timestamp:new Date().toISOString(),userAgent:navigator.userAgent,url:window.location.href,...e})}var b,f,L,m=p(()=>{"use strict";b={production:["https://av.safepassageapp.com","https://portal.safepassageapp.com","https://api.safepassageapp.com"],staging:["https://av.staging.safepassageapp.com","https://portal.staging.safepassageapp.com","https://api.staging.safepassageapp.com"]};f=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(e){let n=Date.now(),r=(this.attempts.get(e)||[]).filter(s=>n-s<this.timeWindow);return r.length>=this.maxAttempts?(console.warn(`SafePassage Security: Rate limit exceeded for ${e}`),!1):(r.push(n),this.attempts.set(e,r),!0)}reset(e){this.attempts.delete(e)}},L=new f});var _={};u(_,{createSignedState:()=>ee,generateHMAC:()=>w,generateSecureToken:()=>C,getSigningSecret:()=>v,parseSignedState:()=>te,verifyHMAC:()=>k});async function w(t,e){let n=new TextEncoder,i=n.encode(e),r=n.encode(t),s=await crypto.subtle.importKey("raw",i,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),o=await crypto.subtle.sign("HMAC",s,r);return Array.from(new Uint8Array(o)).map(l=>l.toString(16).padStart(2,"0")).join("")}async function k(t,e,n){try{let i=await w(t,n);return Q(e,i)}catch{return!1}}function Q(t,e){if(t.length!==e.length)return!1;let n=0;for(let i=0;i<t.length;i++)n|=t.charCodeAt(i)^e.charCodeAt(i);return n===0}function C(t=32){let e=new Uint8Array(t);return crypto.getRandomValues(e),Array.from(e,n=>n.toString(16).padStart(2,"0")).join("")}function v(t){return{production:"safepassage-prod-hmac-2025",staging:"safepassage-stage-hmac-2025"}[t]}async function ee(t,e){let n={...t,timestamp:Date.now(),nonce:C(16)},i=JSON.stringify(n),r=v(e),s=await w(i,r);return btoa(JSON.stringify({data:n,signature:s}))}async function te(t,e,n=O){try{let i=atob(t),r=JSON.parse(i);if(!r.data||!r.signature)return console.warn("SafePassage: Invalid signed state format"),null;let{data:s,signature:o}=r,l=JSON.stringify(s),B=v(e);if(!await k(l,o,B))return console.warn("SafePassage: State signature verification failed"),null;if(s.timestamp){let E=Date.now()-s.timestamp;if(E>n)return console.warn("SafePassage: State parameter expired",{age:E,maxAge:n}),null}let{timestamp:le,nonce:pe,...G}=s;return G}catch(i){return console.warn("SafePassage: Failed to parse signed state",i),null}}var R=p(()=>{"use strict";S()});function K(t){if(!t.apiKey)throw new Error("apiKey is required");if(t.apiKey.length>N)throw new Error(`apiKey exceeds maximum length of ${N} characters`);if(!ne.test(t.apiKey))throw new Error("Invalid apiKey format. Expected pk_xxx (public) or sk_xxx (private)");if(typeof window<"u"&&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: https://docs.safepassageapp.com/server-side-sessions");if(!t.returnUrl)throw new Error("returnUrl is required");if(t.returnUrl.length>d)throw new Error(`returnUrl exceeds maximum length of ${d} characters`);if(!t.cancelUrl)throw new Error("cancelUrl is required");if(t.cancelUrl.length>d)throw new Error(`cancelUrl exceeds maximum length of ${d} characters`);let e=ie(),n=h(t.returnUrl,e);if(!n.isValid)throw new Error(`returnUrl validation failed: ${n.error}`);let i=h(t.cancelUrl,e);if(!i.isValid)throw new Error(`cancelUrl validation failed: ${i.error}`);if(t.defaultChallengeAge!==void 0){if(t.defaultChallengeAge<$)throw new Error(`defaultChallengeAge must be at least ${$}`);if(t.defaultChallengeAge>D)throw new Error(`defaultChallengeAge cannot exceed ${D}`)}if(t.defaultVerificationMode&&!["L1","L2"].includes(t.defaultVerificationMode))throw new Error("defaultVerificationMode must be L1 or L2");if(t.mode&&!["redirect","new-tab"].includes(t.mode))throw new Error("mode must be redirect or new-tab")}function ie(){if(typeof window>"u")return"production";let t=window.location.hostname;return t.includes("staging")||t.includes("stage")?"staging":"production"}async function W(t,e){let{createSignedState:n}=await Promise.resolve().then(()=>(R(),_));return n(t,e)}var $,D,d,N,O,ne,S=p(()=>{"use strict";m();$=25,D=150,d=2048,N=128,O=6e5,ne=/^(pk_|sk_)[a-zA-Z0-9_]+$/});function y(t){let e=re[t];if(!e.startsWith("https://"))throw new Error(`HTTPS required for ${t} environment`);return e}function se(t){let n={production:"https://api.safepassageapp.com",staging:"https://api.staging.safepassageapp.com"}[t];if(!n.startsWith("https://"))throw new Error(`HTTPS required for API URLs in ${t} environment`);return n}function H(t){let e=window.location.protocol==="https:";switch(t){case"production":e||console.warn("SafePassage Warning: HTTPS recommended for production environment");break;case"staging":e||console.warn("SafePassage Warning: HTTPS strongly recommended in staging environment");break}try{y(t),se(t)}catch(n){throw new Error(`Environment configuration validation failed: ${n}`)}}var re,q=p(()=>{"use strict";re={production:"https://av.safepassageapp.com",staging:"https://av.staging.safepassageapp.com"}});var j={};u(j,{SafePassage:()=>c,default:()=>oe});var c,oe,I=p(()=>{"use strict";S();q();m();c=class{constructor(e){this.popupWindow=null;this.messageListener=null;this.popupMonitorInterval=null;this.unloadListener=null;this.isVerificationInProgress=!1;this.currentSessionId=null;K(e),this.config={...e,environment:e.environment||this.detectEnvironment(),mode:e.mode||"redirect"},H(this.config.environment),V(this.config.environment),a("SDK_INITIALIZED",{environment:this.config.environment,mode:this.config.mode,origin:window.location.origin,protocol:window.location.protocol,hostname:window.location.hostname}),this.setupAutoCleanup()}async verify(e={}){let n=this.isPublicKey(),i=e.sessionId;if(n&&!i&&(i=await this.createInternalSession(e)),!n&&!i)throw new Error("sessionId is required for private API keys - must be a merchant-generated UUID v4");if(!i)throw new Error("Failed to create or obtain sessionId");if(this.isVerificationInProgress){let r=new Error(`Verification already in progress for session ${this.currentSessionId?.substring(0,8)}...`);throw a("RACE_CONDITION_PREVENTED",{currentSession:this.currentSessionId?.substring(0,8)+"...",attemptedSession:e.sessionId?e.sessionId.substring(0,8)+"...":"undefined",origin:window.location.origin}),this.config.onError?.(r),r}this.isVerificationInProgress=!0,this.currentSessionId=i;try{let r=`${this.config.apiKey}:${window.location.origin}`;if(!L.isAllowed(r)){let o=new Error("Too many verification attempts. Please wait before trying again.");throw a("RATE_LIMIT_EXCEEDED",{apiKey:this.config.apiKey.substring(0,8)+"...",origin:window.location.origin,sessionId:i?i.substring(0,8)+"...":"undefined"}),this.config.onError?.(o),o}let s=await this.buildVerificationUrl({...e,sessionId:i});a("VERIFICATION_INITIATED",{environment:this.config.environment,mode:this.config.mode,sessionId:i?i.substring(0,8)+"...":"undefined",origin:window.location.origin}),this.config.mode==="new-tab"?this.openNewTab(s,i):(this.unlockVerification(),this.redirect(s))}catch(r){throw this.unlockVerification(),r}}async buildVerificationUrl(e){let n=y(this.config.environment),i=e.challengeAge!==void 0,r=e.verificationMode!==void 0,s=i||r,o=await W({merchantId:this.config.apiKey,sessionId:e.sessionId,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:e.challengeAge||this.config.defaultChallengeAge,verificationMode:e.verificationMode||this.config.defaultVerificationMode,hasOverrides:s,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},handoffToken:this._temporaryHandoffToken},this.config.environment),l=new URLSearchParams({state:o,sessionId:e.sessionId,mode:this.config.mode});return`${n}/?${l.toString()}`}redirect(e){window.location.href=e}openNewTab(e,n){if(this.cleanup(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),this.popupWindow=window.open(e,"safepassage-verify","width=600,height=700"),!this.popupWindow){this.config.onError?.(new Error("Failed to open verification window. Please check popup blocker settings."));return}this.messageListener=i=>{if(!x(i,this.config.environment)){a("POSTMESSAGE_ORIGIN_BLOCKED",{origin:i.origin,environment:this.config.environment,expectedOrigins:`SafePassage trusted origins for ${this.config.environment}`,messageType:i.data?.type});return}let r=T(i,n);if(!r.isValid){a("POSTMESSAGE_VALIDATION_FAILED",{error:r.error,origin:i.origin,sessionId:n.substring(0,8)+"...",messageType:i.data?.type});return}let s={sessionId:i.data.sessionId,status:i.data.status};a("VERIFICATION_COMPLETED",{status:s.status,sessionId:n.substring(0,8)+"...",origin:i.origin}),this.cleanup(),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),s.status==="verified"?this.config.onComplete?.(s):s.status==="cancelled"?this.config.onCancel?.():this.config.onError?.(new Error(`Verification failed: ${s.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{this.popupWindow&&this.popupWindow.closed&&(a("POPUP_CLOSED_BY_USER",{sessionId:n.substring(0,8)+"...",environment:this.config.environment}),this.cleanup(),this.unlockVerification(),this.config.onCancel?.())},500)}setupAutoCleanup(){if(this.unloadListener=()=>{a("SDK_AUTO_CLEANUP",{environment:this.config.environment,trigger:"page_unload"}),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))}}detectEnvironment(){let e=window.location.hostname;return e.includes("staging")||e.includes("stage")?"staging":"production"}getEnvironment(){return this.config.environment}unlockVerification(){this.isVerificationInProgress=!1,this.currentSessionId=null,a("VERIFICATION_UNLOCKED",{environment:this.config.environment,origin:window.location.origin})}cleanup(){this.popupWindow&&!this.popupWindow.closed&&this.popupWindow.close(),this.popupWindow=null,this.messageListener&&(window.removeEventListener("message",this.messageListener),this.messageListener=null),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null)}removeAutoCleanupListeners(){this.unloadListener&&(window.removeEventListener("beforeunload",this.unloadListener),window.removeEventListener("pagehide",this.unloadListener),this.unloadListener=null)}destroy(){a("SDK_DESTROYED",{environment:this.config.environment,origin:window.location.origin}),this.cleanup(),this.unlockVerification(),this.removeAutoCleanupListeners()}getPortalApiUrl(){switch(this.config.environment){case"staging":return"https://api.staging.safepassageapp.com";case"production":return"https://api.safepassageapp.com";default:return"https://api.safepassageapp.com"}}getEngineUrl(){switch(this.config.environment){case"staging":return"https://engine.staging.safepassageapp.com";case"production":return"https://engine.safepassageapp.com";default:return"https://engine.safepassageapp.com"}}getWebSocketUrl(){switch(this.config.environment){case"staging":return"wss://engine.staging.safepassageapp.com/api/websocket/stream";case"production":return"wss://engine.safepassageapp.com/api/websocket/stream";default:return"wss://engine.safepassageapp.com/api/websocket/stream"}}isPublicKey(){return this.config.apiKey.startsWith("pk_")}async createInternalSession(e){let n=M();try{let i=this.getPortalApiUrl(),r=await fetch(`${i}/api/v1/sessions/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({merchantId:this.config.apiKey,sessionId:n,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:e.challengeAge,verificationMode:e.verificationMode,merchantName:document.title||window.location.hostname,externalUserId:e.externalUserId})});if(!r.ok){let o=await r.json().catch(()=>({}));throw new Error(`Failed to create session: ${r.status} ${r.statusText}. ${o.message||""}`)}let s=await r.json();return s.handoffToken&&(this._temporaryHandoffToken=s.handoffToken),a("INTERNAL_SESSION_CREATED",{sessionId:n.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"}),n}catch(i){let r=i instanceof Error?i.message:String(i);throw a("INTERNAL_SESSION_FAILED",{error:r,environment:this.config.environment,apiKeyType:"public"}),this.config.onError?.(i),new Error(`Failed to create verification session: ${r}`)}}},oe=c});var ae={};u(ae,{SafePassage:()=>c,VERSION:()=>F,default:()=>c});function U(){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 A(){let t=[];if(!window.crypto||!window.crypto.getRandomValues)throw new Error("SafePassage SDK requires Web Crypto API support");if(!window.crypto.subtle)throw new Error("SafePassage SDK requires Web Crypto subtle API for HMAC operations");crypto.randomUUID||t.push("crypto.randomUUID not supported, using polyfill"),window.URLSearchParams||t.push("URLSearchParams not supported, consider adding a polyfill for IE 11 support");try{if({a:{b:1}}?.a?.b!==1)throw new Error}catch{t.push("Optional chaining (?.) not supported, ensure transpilation for older browsers")}t.length>0&&console.warn("SafePassage SDK Browser Compatibility:",t.join("; "))}I();typeof window<"u"&&(U(),A());var F="3.0.15";if(typeof window<"u"&&window){let{SafePassage:t}=(I(),P(j)),e=window;e.SafePassage=t,e.SafePassage&&(e.SafePassage.VERSION=F)}return P(ae);})();
3
3
  if(typeof SafePassageSDK !== "undefined" && SafePassageSDK.SafePassage) { window.SafePassage = SafePassageSDK.SafePassage; window.SafePassage.VERSION = SafePassageSDK.VERSION; }
@@ -99,12 +99,16 @@ export function enforceHTTPS(environment) {
99
99
  export function validateReturnUrl(url, environment) {
100
100
  try {
101
101
  const parsed = new URL(url);
102
- // Production and staging should use HTTPS
102
+ // Production and staging should use HTTPS, except for localhost (for development)
103
103
  if (parsed.protocol !== 'https:') {
104
- return {
105
- isValid: false,
106
- error: `HTTPS required for return URLs in ${environment}`,
107
- };
104
+ // Allow localhost for development use cases
105
+ const isLocalhost = parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';
106
+ if (!isLocalhost) {
107
+ return {
108
+ isValid: false,
109
+ error: `HTTPS required for return URLs in ${environment}`,
110
+ };
111
+ }
108
112
  }
109
113
  // Block suspicious URLs
110
114
  const suspiciousPatterns = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@safepassage/sdk",
3
- "version": "3.1.0",
3
+ "version": "3.1.2",
4
4
  "description": "SafePassage SDK - Lightweight redirect-based age verification",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",