@safepassage/sdk 3.4.8 → 3.4.9

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 CHANGED
@@ -1,4 +1,4 @@
1
- # SafePassage SDK v3.4.8
1
+ # SafePassage SDK v3.4.9
2
2
 
3
3
  A lightweight SDK for integrating SafePassage age verification into your website or application.
4
4
 
@@ -12,6 +12,11 @@ A lightweight SDK for integrating SafePassage age verification into your website
12
12
  - **Secure**: HMAC-signed state parameters, automatic session management
13
13
  - **Compliant**: Enforces minimum age of 25
14
14
 
15
+ ## Changelog
16
+
17
+ ### 3.4.9
18
+ - Prevents `onCancel` from firing after a successful new-tab verification when the popup closes.
19
+
15
20
  ## Installation
16
21
 
17
22
  ```bash
@@ -47,6 +47,7 @@ export declare class SafePassage {
47
47
  private unloadListener;
48
48
  private isVerificationInProgress;
49
49
  private currentSessionId;
50
+ private hasReceivedResult;
50
51
  private lastVerifyUrl;
51
52
  private lastSessionToken;
52
53
  private temporaryHandoffToken;
@@ -59,6 +59,7 @@ export class SafePassage {
59
59
  this.unloadListener = null;
60
60
  this.isVerificationInProgress = false;
61
61
  this.currentSessionId = null;
62
+ this.hasReceivedResult = false;
62
63
  // Server-provided verify URL (includes sessionToken)
63
64
  this.lastVerifyUrl = null;
64
65
  // Server-provided session token (WS auth)
@@ -274,6 +275,7 @@ export class SafePassage {
274
275
  var _a, _b;
275
276
  // Clean up any existing resources
276
277
  this.cleanup();
278
+ this.hasReceivedResult = false;
277
279
  // Clear any existing popup monitor interval
278
280
  if (this.popupMonitorInterval) {
279
281
  clearInterval(this.popupMonitorInterval);
@@ -322,6 +324,7 @@ export class SafePassage {
322
324
  timestamp: event.data.timestamp,
323
325
  externalUserId: event.data.externalUserId,
324
326
  };
327
+ this.hasReceivedResult = true;
325
328
  // Log successful verification completion
326
329
  logSecurityEvent('VERIFICATION_COMPLETED', {
327
330
  status: result.status,
@@ -360,7 +363,9 @@ export class SafePassage {
360
363
  this.cleanup();
361
364
  // Unlock verification after popup closed
362
365
  this.unlockVerification();
363
- (_b = (_a = this.config).onCancel) === null || _b === void 0 ? void 0 : _b.call(_a);
366
+ if (!this.hasReceivedResult) {
367
+ (_b = (_a = this.config).onCancel) === null || _b === void 0 ? void 0 : _b.call(_a);
368
+ }
364
369
  }
365
370
  }, 500);
366
371
  }
package/dist/index.d.ts CHANGED
@@ -18,4 +18,4 @@
18
18
  */
19
19
  export { SafePassage, SafePassage as default } from './core/SafePassageSDK';
20
20
  export type { SafePassageConfig, VerificationOptions, VerificationResult, SessionValidationResponse, } from './types';
21
- export declare const VERSION = "3.4.8";
21
+ export declare const VERSION = "3.4.9";
package/dist/index.js CHANGED
@@ -475,6 +475,7 @@ var init_SafePassageSDK = __esm({
475
475
  this.unloadListener = null;
476
476
  this.isVerificationInProgress = false;
477
477
  this.currentSessionId = null;
478
+ this.hasReceivedResult = false;
478
479
  // Server-provided verify URL (includes sessionToken)
479
480
  this.lastVerifyUrl = null;
480
481
  // Server-provided session token (WS auth)
@@ -679,6 +680,7 @@ var init_SafePassageSDK = __esm({
679
680
  openNewTab(url, sessionId) {
680
681
  var _a, _b;
681
682
  this.cleanup();
683
+ this.hasReceivedResult = false;
682
684
  if (this.popupMonitorInterval) {
683
685
  clearInterval(this.popupMonitorInterval);
684
686
  this.popupMonitorInterval = null;
@@ -728,6 +730,7 @@ var init_SafePassageSDK = __esm({
728
730
  timestamp: event.data.timestamp,
729
731
  externalUserId: event.data.externalUserId
730
732
  };
733
+ this.hasReceivedResult = true;
731
734
  logSecurityEvent("VERIFICATION_COMPLETED", {
732
735
  status: result.status,
733
736
  sessionId: sessionId.substring(0, 8) + "...",
@@ -758,7 +761,9 @@ var init_SafePassageSDK = __esm({
758
761
  });
759
762
  this.cleanup();
760
763
  this.unlockVerification();
761
- (_b2 = (_a2 = this.config).onCancel) == null ? void 0 : _b2.call(_a2);
764
+ if (!this.hasReceivedResult) {
765
+ (_b2 = (_a2 = this.config).onCancel) == null ? void 0 : _b2.call(_a2);
766
+ }
762
767
  }
763
768
  }, 500);
764
769
  }
@@ -1079,7 +1084,7 @@ if (typeof window !== "undefined") {
1079
1084
  setupPolyfills();
1080
1085
  checkBrowserCompatibility();
1081
1086
  }
1082
- var VERSION = "3.4.8";
1087
+ var VERSION = "3.4.9";
1083
1088
  if (typeof window !== "undefined" && window) {
1084
1089
  const { SafePassage: SafePassage2 } = (init_SafePassageSDK(), __toCommonJS(SafePassageSDK_exports));
1085
1090
  const globalWindow = window;
@@ -1,3 +1,3 @@
1
- /* SafePassage SDK v3.4.8 */
2
- "use strict";var SafePassageSDK=(()=>{var S=Object.defineProperty,oe=Object.defineProperties,ae=Object.getOwnPropertyDescriptor,ce=Object.getOwnPropertyDescriptors,le=Object.getOwnPropertyNames,v=Object.getOwnPropertySymbols;var P=Object.prototype.hasOwnProperty,C=Object.prototype.propertyIsEnumerable;var R=(t,e,n)=>e in t?S(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,h=(t,e)=>{for(var n in e||(e={}))P.call(e,n)&&R(t,n,e[n]);if(v)for(var n of v(e))C.call(e,n)&&R(t,n,e[n]);return t},y=(t,e)=>oe(t,ce(e));var D=(t,e)=>{var n={};for(var i in t)P.call(t,i)&&e.indexOf(i)<0&&(n[i]=t[i]);if(t!=null&&v)for(var i of v(t))e.indexOf(i)<0&&C.call(t,i)&&(n[i]=t[i]);return n};var w=(t,e)=>()=>(t&&(e=t(t=0)),e);var U=(t,e)=>{for(var n in e)S(t,n,{get:e[n],enumerable:!0})},pe=(t,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of le(e))!P.call(t,s)&&s!==n&&S(t,s,{get:()=>e[s],enumerable:!(i=ae(e,s))||i.enumerable});return t};var $=t=>pe(S({},"__esModule",{value:!0}),t);function de(t,e){return K[e].includes(t)}function W(t,e,n=[]){var s;let{origin:i}=t;return de(i,e)||n.length>0&&n.some(o=>{if(o.startsWith("*.")){let a=o.slice(2);return i.endsWith(`.${a}`)||i===`https://${a}`||i===`http://${a}`}return i===o})?!0:(console.warn(`SafePassage Security: Blocked PostMessage from untrusted origin: ${i}`,{environment:e,trustedOrigins:K[e],allowedCustomOrigins:n,eventType:(s=t.data)==null?void 0:s.type}),!1)}function H(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"].includes(n.status)?{isValid:!1,error:"Invalid status value"}:{isValid:!0}}function j(t){t==="production"&&window.location.protocol!=="https:"&&console.warn("SafePassage Warning: HTTPS recommended for production environment",{current:window.location.href})}function T(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 s of i)if(s.test(t))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch(n){return{isValid:!1,error:"Invalid URL format"}}}function p(t,e){console.warn(`SafePassage Security Event: ${t}`,h({timestamp:new Date().toISOString(),userAgent:navigator.userAgent,url:window.location.href},e))}var K,A,q,b=w(()=>{"use strict";K={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"]};A=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(e){let n=Date.now(),s=(this.attempts.get(e)||[]).filter(r=>n-r<this.timeWindow);return s.length>=this.maxAttempts?(console.warn(`SafePassage Security: Rate limit exceeded for ${e}`),!1):(s.push(n),this.attempts.set(e,s),!0)}reset(e){this.attempts.delete(e)}},q=new A});var G={};U(G,{createSignedState:()=>ge,generateHMAC:()=>x,generateSecureToken:()=>B,getSigningSecret:()=>V,parseSignedState:()=>fe,verifyHMAC:()=>F});async function x(t,e){let n=new TextEncoder,i=n.encode(e),s=n.encode(t),r=await crypto.subtle.importKey("raw",i,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),o=await crypto.subtle.sign("HMAC",r,s);return Array.from(new Uint8Array(o)).map(a=>a.toString(16).padStart(2,"0")).join("")}async function F(t,e,n){try{let i=await x(t,n);return ue(e,i)}catch(i){return!1}}function ue(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 B(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 ge(t,e){let n=y(h({},t),{timestamp:Date.now(),nonce:B(16)}),i=JSON.stringify(n),s=V(e),r=await x(i,s);return btoa(JSON.stringify({data:n,signature:r}))}async function fe(t,e,n=X){try{let s=atob(t),r=JSON.parse(s);if(!r.data||!r.signature)return console.warn("SafePassage: Invalid signed state format"),null;let{data:o,signature:a}=r,c=JSON.stringify(o),l=V(e);if(!await F(c,a,l))return console.warn("SafePassage: State signature verification failed"),null;if(o.timestamp){let m=Date.now()-o.timestamp;if(m>n)return console.warn("SafePassage: State parameter expired",{age:m,maxAge:n}),null}let i=o,{timestamp:g,nonce:f}=i;return D(i,["timestamp","nonce"])}catch(s){return console.warn("SafePassage: Failed to parse signed state",s),null}}var J=w(()=>{"use strict";k()});function Q(t){if(!t.apiKey)throw new Error("apiKey is required");if(t.apiKey.length>Z)throw new Error(`apiKey exceeds maximum length of ${Z} characters`);if(!he.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: https://docs.safepassageapp.com/server-side-sessions");if(!t.returnUrl)throw new Error("returnUrl is required");if(t.returnUrl.length>E)throw new Error(`returnUrl exceeds maximum length of ${E} characters`);let e=me(),n=T(t.returnUrl,e);if(!n.isValid)throw new Error(`returnUrl validation failed: ${n.error}`);if(t.cancelUrl){if(t.cancelUrl.length>E)throw new Error(`cancelUrl exceeds maximum length of ${E} characters`);let i=T(t.cancelUrl,e);if(!i.isValid)throw new Error(`cancelUrl validation failed: ${i.error}`)}if(t.defaultChallengeAge!==void 0){if(t.defaultChallengeAge<Y)throw new Error(`defaultChallengeAge must be at least ${Y}`);if(t.defaultChallengeAge>z)throw new Error(`defaultChallengeAge cannot exceed ${z}`)}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 me(){if(typeof window=="undefined")return"production";let t=window.location.hostname;return t.includes("staging")||t.includes("stage")?"staging":"production"}async function ee(t,e){let{createSignedState:n}=await Promise.resolve().then(()=>(J(),G));return n(t,e)}var Y,z,E,Z,X,he,k=w(()=>{"use strict";b();Y=25,z=150,E=2048,Z=128,X=6e5,he=/^(pk_|sk_)[a-zA-Z0-9_]+$/});function M(t){let e=te[t]||te.production;if(!e||!e.startsWith("https://"))throw new Error(`HTTPS required for ${t} environment`);return e}function we(t){let e={production:"https://api.safepassageapp.com",staging:"https://api.staging.safepassageapp.com"},n=e[t]||e.production;if(!n||!n.startsWith("https://"))throw new Error(`HTTPS required for API URLs in ${t} environment`);return n}function ne(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{M(t),we(t)}catch(n){let i=n instanceof Error?n.message:String(n);throw new Error(`Environment configuration validation failed: ${i}`)}}var te,ie=w(()=>{"use strict";te={production:"https://av.safepassageapp.com",staging:"https://av.staging.safepassageapp.com"}});var re={};U(re,{SafePassage:()=>u,default:()=>ve});var u,ve,L=w(()=>{"use strict";k();ie();b();u=class{constructor(e){this.popupWindow=null;this.messageListener=null;this.popupMonitorInterval=null;this.unloadListener=null;this.isVerificationInProgress=!1;this.currentSessionId=null;this.lastVerifyUrl=null;this.lastSessionToken=null;this.temporaryHandoffToken=null;Q(e);let n=e.environment||this.detectEnvironment();n!=="staging"&&n!=="production"&&(console.warn(`SafePassage SDK: Unknown environment '${n}', defaulting to 'production'`),n="production"),this.config=y(h({},e),{environment:n,mode:e.mode||"redirect"}),ne(this.config.environment),j(this.config.environment),p("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={}){var s,r,o,a,c,l;let n=this.isPublicKey(),i;if(n)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 d=new Error(`Verification already in progress for session ${(s=this.currentSessionId)==null?void 0:s.substring(0,8)}...`);throw p("RACE_CONDITION_PREVENTED",{currentSession:((r=this.currentSessionId)==null?void 0:r.substring(0,8))+"...",attemptedSession:"new-session-attempt",origin:window.location.origin}),(a=(o=this.config).onError)==null||a.call(o,d),d}this.isVerificationInProgress=!0,this.currentSessionId=i;try{let d=`${this.config.apiKey}:${window.location.origin}`;if(!q.isAllowed(d)){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:i?i.substring(0,8)+"...":"undefined"}),(l=(c=this.config).onError)==null||l.call(c,f),f}let g=await this.buildVerificationUrl(e,i);p("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(g,i):(this.unlockVerification(),this.redirect(g))}catch(d){throw this.unlockVerification(),d}}async buildVerificationUrl(e,n){let i=M(this.config.environment),s=e.challengeAge!==void 0,r=e.verificationMode!==void 0,o=s||r,a=await ee({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,hasOverrides:o,externalUserId:e.externalUserId,timestamp:Date.now(),apiUrl:this.getPortalApiUrl(),engineUrl:this.getEngineUrl(),wsUrl:this.getWebSocketUrl(),environment:this.config.environment,features:{testMode:!1,warmupPeriodMs:500,qualityThreshold:.6},handoffToken:this.temporaryHandoffToken||void 0,sessionToken:this.lastSessionToken||void 0,verifyUrl:this.lastVerifyUrl||void 0},this.config.environment);if(this.lastVerifyUrl)try{let l=new URL(this.lastVerifyUrl);return l.searchParams.set("state",a),l.searchParams.set("mode",this.config.mode),e.skipIntro&&l.searchParams.set("skip_intro","true"),e.autoReturn&&l.searchParams.set("auto_return","true"),l.toString()}catch(l){}let c=new URLSearchParams({state:a,sessionId:n,mode:this.config.mode});return e.skipIntro&&c.set("skip_intro","true"),e.autoReturn&&c.set("auto_return","true"),`${i}/?${c.toString()}`}redirect(e){window.location.href=e}openNewTab(e,n){var i,s;if(this.cleanup(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),this.popupWindow=window.open(e,"safepassage-verify","width=600,height=700"),!this.popupWindow){(s=(i=this.config).onError)==null||s.call(i,new Error("Failed to open verification window. Please check popup blocker settings."));return}this.messageListener=r=>{var l,d,g,f,I,m,_;let o=(l=r.data)==null?void 0:l.type;if(!o||typeof o!="string"||!o.startsWith("safepassage:"))return;if(!W(r,this.config.environment)){p("POSTMESSAGE_ORIGIN_BLOCKED",{origin:r.origin,environment:this.config.environment,expectedOrigins:`SafePassage trusted origins for ${this.config.environment}`,messageType:(d=r.data)==null?void 0:d.type});return}let a=H(r,n);if(!a.isValid){p("POSTMESSAGE_VALIDATION_FAILED",{error:a.error,origin:r.origin,sessionId:n.substring(0,8)+"...",messageType:(g=r.data)==null?void 0:g.type});return}let c={sessionId:r.data.sessionId,status:r.data.status,timestamp:r.data.timestamp,externalUserId:r.data.externalUserId};p("VERIFICATION_COMPLETED",{status:c.status,sessionId:n.substring(0,8)+"...",origin:r.origin}),this.cleanup({closePopup:!1}),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),c.status==="verified"?(I=(f=this.config).onComplete)==null||I.call(f,c):(_=(m=this.config).onError)==null||_.call(m,new Error(`Verification failed: ${c.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{var r,o;this.popupWindow&&this.popupWindow.closed&&(p("POPUP_CLOSED_BY_USER",{sessionId:n.substring(0,8)+"...",environment:this.config.environment}),this.cleanup(),this.unlockVerification(),(o=(r=this.config).onCancel)==null||o.call(r))},500)}setupAutoCleanup(){if(this.unloadListener=()=>{p("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,p("VERIFICATION_UNLOCKED",{environment:this.config.environment,origin:window.location.origin})}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)}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.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){var n,i;try{let s=this.getPortalApiUrl(),r=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,merchantName:document.title||window.location.hostname,externalUserId:e.externalUserId})});if(!r.ok){let c=await r.json().catch(()=>({}));throw new Error(`Failed to create session: ${r.status} ${r.statusText}. ${c.message||""}`)}let o=await r.json(),a=o.sessionId;if(!a)throw new Error("Server did not return a sessionId");return o.verifyUrl&&(this.lastVerifyUrl=o.verifyUrl),o.sessionToken&&(this.lastSessionToken=o.sessionToken),o.handoffToken&&(this.temporaryHandoffToken=o.handoffToken),p("INTERNAL_SESSION_CREATED",{sessionId:a.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"}),a}catch(s){let r=s instanceof Error?s.message:String(s);throw p("INTERNAL_SESSION_FAILED",{error:r,environment:this.config.environment,apiKeyType:"public"}),(i=(n=this.config).onError)==null||i.call(n,s),new Error(`Failed to create verification session: ${r}`)}}},ve=u});var Se={};U(Se,{SafePassage:()=>u,VERSION:()=>se,default:()=>u});function O(){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 N(){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"),t.length>0&&console.warn("SafePassage SDK Browser Compatibility:",t.join("; "))}L();typeof window!="undefined"&&(O(),N());var se="3.4.8";if(typeof window!="undefined"&&window){let{SafePassage:t}=(L(),$(re)),e=window;e.SafePassage=t,e.SafePassage&&(e.SafePassage.VERSION=se)}return $(Se);})();
1
+ /* SafePassage SDK v3.4.9 */
2
+ "use strict";var SafePassageSDK=(()=>{var S=Object.defineProperty,oe=Object.defineProperties,ae=Object.getOwnPropertyDescriptor,ce=Object.getOwnPropertyDescriptors,le=Object.getOwnPropertyNames,v=Object.getOwnPropertySymbols;var P=Object.prototype.hasOwnProperty,C=Object.prototype.propertyIsEnumerable;var _=(t,e,n)=>e in t?S(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n,h=(t,e)=>{for(var n in e||(e={}))P.call(e,n)&&_(t,n,e[n]);if(v)for(var n of v(e))C.call(e,n)&&_(t,n,e[n]);return t},y=(t,e)=>oe(t,ce(e));var D=(t,e)=>{var n={};for(var i in t)P.call(t,i)&&e.indexOf(i)<0&&(n[i]=t[i]);if(t!=null&&v)for(var i of v(t))e.indexOf(i)<0&&C.call(t,i)&&(n[i]=t[i]);return n};var w=(t,e)=>()=>(t&&(e=t(t=0)),e);var U=(t,e)=>{for(var n in e)S(t,n,{get:e[n],enumerable:!0})},pe=(t,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of le(e))!P.call(t,s)&&s!==n&&S(t,s,{get:()=>e[s],enumerable:!(i=ae(e,s))||i.enumerable});return t};var $=t=>pe(S({},"__esModule",{value:!0}),t);function de(t,e){return K[e].includes(t)}function W(t,e,n=[]){var s;let{origin:i}=t;return de(i,e)||n.length>0&&n.some(o=>{if(o.startsWith("*.")){let a=o.slice(2);return i.endsWith(`.${a}`)||i===`https://${a}`||i===`http://${a}`}return i===o})?!0:(console.warn(`SafePassage Security: Blocked PostMessage from untrusted origin: ${i}`,{environment:e,trustedOrigins:K[e],allowedCustomOrigins:n,eventType:(s=t.data)==null?void 0:s.type}),!1)}function H(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"].includes(n.status)?{isValid:!1,error:"Invalid status value"}:{isValid:!0}}function j(t){t==="production"&&window.location.protocol!=="https:"&&console.warn("SafePassage Warning: HTTPS recommended for production environment",{current:window.location.href})}function T(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 s of i)if(s.test(t))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch(n){return{isValid:!1,error:"Invalid URL format"}}}function p(t,e){console.warn(`SafePassage Security Event: ${t}`,h({timestamp:new Date().toISOString(),userAgent:navigator.userAgent,url:window.location.href},e))}var K,A,q,b=w(()=>{"use strict";K={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"]};A=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(e){let n=Date.now(),s=(this.attempts.get(e)||[]).filter(r=>n-r<this.timeWindow);return s.length>=this.maxAttempts?(console.warn(`SafePassage Security: Rate limit exceeded for ${e}`),!1):(s.push(n),this.attempts.set(e,s),!0)}reset(e){this.attempts.delete(e)}},q=new A});var G={};U(G,{createSignedState:()=>ge,generateHMAC:()=>x,generateSecureToken:()=>B,getSigningSecret:()=>V,parseSignedState:()=>fe,verifyHMAC:()=>F});async function x(t,e){let n=new TextEncoder,i=n.encode(e),s=n.encode(t),r=await crypto.subtle.importKey("raw",i,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),o=await crypto.subtle.sign("HMAC",r,s);return Array.from(new Uint8Array(o)).map(a=>a.toString(16).padStart(2,"0")).join("")}async function F(t,e,n){try{let i=await x(t,n);return ue(e,i)}catch(i){return!1}}function ue(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 B(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 ge(t,e){let n=y(h({},t),{timestamp:Date.now(),nonce:B(16)}),i=JSON.stringify(n),s=V(e),r=await x(i,s);return btoa(JSON.stringify({data:n,signature:r}))}async function fe(t,e,n=X){try{let s=atob(t),r=JSON.parse(s);if(!r.data||!r.signature)return console.warn("SafePassage: Invalid signed state format"),null;let{data:o,signature:a}=r,c=JSON.stringify(o),l=V(e);if(!await F(c,a,l))return console.warn("SafePassage: State signature verification failed"),null;if(o.timestamp){let m=Date.now()-o.timestamp;if(m>n)return console.warn("SafePassage: State parameter expired",{age:m,maxAge:n}),null}let i=o,{timestamp:g,nonce:f}=i;return D(i,["timestamp","nonce"])}catch(s){return console.warn("SafePassage: Failed to parse signed state",s),null}}var J=w(()=>{"use strict";k()});function Q(t){if(!t.apiKey)throw new Error("apiKey is required");if(t.apiKey.length>Z)throw new Error(`apiKey exceeds maximum length of ${Z} characters`);if(!he.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: https://docs.safepassageapp.com/server-side-sessions");if(!t.returnUrl)throw new Error("returnUrl is required");if(t.returnUrl.length>E)throw new Error(`returnUrl exceeds maximum length of ${E} characters`);let e=me(),n=T(t.returnUrl,e);if(!n.isValid)throw new Error(`returnUrl validation failed: ${n.error}`);if(t.cancelUrl){if(t.cancelUrl.length>E)throw new Error(`cancelUrl exceeds maximum length of ${E} characters`);let i=T(t.cancelUrl,e);if(!i.isValid)throw new Error(`cancelUrl validation failed: ${i.error}`)}if(t.defaultChallengeAge!==void 0){if(t.defaultChallengeAge<Y)throw new Error(`defaultChallengeAge must be at least ${Y}`);if(t.defaultChallengeAge>z)throw new Error(`defaultChallengeAge cannot exceed ${z}`)}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 me(){if(typeof window=="undefined")return"production";let t=window.location.hostname;return t.includes("staging")||t.includes("stage")?"staging":"production"}async function ee(t,e){let{createSignedState:n}=await Promise.resolve().then(()=>(J(),G));return n(t,e)}var Y,z,E,Z,X,he,k=w(()=>{"use strict";b();Y=25,z=150,E=2048,Z=128,X=6e5,he=/^(pk_|sk_)[a-zA-Z0-9_]+$/});function R(t){let e=te[t]||te.production;if(!e||!e.startsWith("https://"))throw new Error(`HTTPS required for ${t} environment`);return e}function we(t){let e={production:"https://api.safepassageapp.com",staging:"https://api.staging.safepassageapp.com"},n=e[t]||e.production;if(!n||!n.startsWith("https://"))throw new Error(`HTTPS required for API URLs in ${t} environment`);return n}function ne(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{R(t),we(t)}catch(n){let i=n instanceof Error?n.message:String(n);throw new Error(`Environment configuration validation failed: ${i}`)}}var te,ie=w(()=>{"use strict";te={production:"https://av.safepassageapp.com",staging:"https://av.staging.safepassageapp.com"}});var re={};U(re,{SafePassage:()=>u,default:()=>ve});var u,ve,M=w(()=>{"use strict";k();ie();b();u=class{constructor(e){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.temporaryHandoffToken=null;Q(e);let n=e.environment||this.detectEnvironment();n!=="staging"&&n!=="production"&&(console.warn(`SafePassage SDK: Unknown environment '${n}', defaulting to 'production'`),n="production"),this.config=y(h({},e),{environment:n,mode:e.mode||"redirect"}),ne(this.config.environment),j(this.config.environment),p("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={}){var s,r,o,a,c,l;let n=this.isPublicKey(),i;if(n)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 d=new Error(`Verification already in progress for session ${(s=this.currentSessionId)==null?void 0:s.substring(0,8)}...`);throw p("RACE_CONDITION_PREVENTED",{currentSession:((r=this.currentSessionId)==null?void 0:r.substring(0,8))+"...",attemptedSession:"new-session-attempt",origin:window.location.origin}),(a=(o=this.config).onError)==null||a.call(o,d),d}this.isVerificationInProgress=!0,this.currentSessionId=i;try{let d=`${this.config.apiKey}:${window.location.origin}`;if(!q.isAllowed(d)){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:i?i.substring(0,8)+"...":"undefined"}),(l=(c=this.config).onError)==null||l.call(c,f),f}let g=await this.buildVerificationUrl(e,i);p("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(g,i):(this.unlockVerification(),this.redirect(g))}catch(d){throw this.unlockVerification(),d}}async buildVerificationUrl(e,n){let i=R(this.config.environment),s=e.challengeAge!==void 0,r=e.verificationMode!==void 0,o=s||r,a=await ee({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,hasOverrides:o,externalUserId:e.externalUserId,timestamp:Date.now(),apiUrl:this.getPortalApiUrl(),engineUrl:this.getEngineUrl(),wsUrl:this.getWebSocketUrl(),environment:this.config.environment,features:{testMode:!1,warmupPeriodMs:500,qualityThreshold:.6},handoffToken:this.temporaryHandoffToken||void 0,sessionToken:this.lastSessionToken||void 0,verifyUrl:this.lastVerifyUrl||void 0},this.config.environment);if(this.lastVerifyUrl)try{let l=new URL(this.lastVerifyUrl);return l.searchParams.set("state",a),l.searchParams.set("mode",this.config.mode),e.skipIntro&&l.searchParams.set("skip_intro","true"),e.autoReturn&&l.searchParams.set("auto_return","true"),l.toString()}catch(l){}let c=new URLSearchParams({state:a,sessionId:n,mode:this.config.mode});return e.skipIntro&&c.set("skip_intro","true"),e.autoReturn&&c.set("auto_return","true"),`${i}/?${c.toString()}`}redirect(e){window.location.href=e}openNewTab(e,n){var i,s;if(this.cleanup(),this.hasReceivedResult=!1,this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),this.popupWindow=window.open(e,"safepassage-verify","width=600,height=700"),!this.popupWindow){(s=(i=this.config).onError)==null||s.call(i,new Error("Failed to open verification window. Please check popup blocker settings."));return}this.messageListener=r=>{var l,d,g,f,I,m,L;let o=(l=r.data)==null?void 0:l.type;if(!o||typeof o!="string"||!o.startsWith("safepassage:"))return;if(!W(r,this.config.environment)){p("POSTMESSAGE_ORIGIN_BLOCKED",{origin:r.origin,environment:this.config.environment,expectedOrigins:`SafePassage trusted origins for ${this.config.environment}`,messageType:(d=r.data)==null?void 0:d.type});return}let a=H(r,n);if(!a.isValid){p("POSTMESSAGE_VALIDATION_FAILED",{error:a.error,origin:r.origin,sessionId:n.substring(0,8)+"...",messageType:(g=r.data)==null?void 0:g.type});return}let c={sessionId:r.data.sessionId,status:r.data.status,timestamp:r.data.timestamp,externalUserId:r.data.externalUserId};this.hasReceivedResult=!0,p("VERIFICATION_COMPLETED",{status:c.status,sessionId:n.substring(0,8)+"...",origin:r.origin}),this.cleanup({closePopup:!1}),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),c.status==="verified"?(I=(f=this.config).onComplete)==null||I.call(f,c):(L=(m=this.config).onError)==null||L.call(m,new Error(`Verification failed: ${c.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{var r,o;this.popupWindow&&this.popupWindow.closed&&(p("POPUP_CLOSED_BY_USER",{sessionId:n.substring(0,8)+"...",environment:this.config.environment}),this.cleanup(),this.unlockVerification(),this.hasReceivedResult||(o=(r=this.config).onCancel)==null||o.call(r))},500)}setupAutoCleanup(){if(this.unloadListener=()=>{p("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,p("VERIFICATION_UNLOCKED",{environment:this.config.environment,origin:window.location.origin})}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)}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.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){var n,i;try{let s=this.getPortalApiUrl(),r=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,merchantName:document.title||window.location.hostname,externalUserId:e.externalUserId})});if(!r.ok){let c=await r.json().catch(()=>({}));throw new Error(`Failed to create session: ${r.status} ${r.statusText}. ${c.message||""}`)}let o=await r.json(),a=o.sessionId;if(!a)throw new Error("Server did not return a sessionId");return o.verifyUrl&&(this.lastVerifyUrl=o.verifyUrl),o.sessionToken&&(this.lastSessionToken=o.sessionToken),o.handoffToken&&(this.temporaryHandoffToken=o.handoffToken),p("INTERNAL_SESSION_CREATED",{sessionId:a.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"}),a}catch(s){let r=s instanceof Error?s.message:String(s);throw p("INTERNAL_SESSION_FAILED",{error:r,environment:this.config.environment,apiKeyType:"public"}),(i=(n=this.config).onError)==null||i.call(n,s),new Error(`Failed to create verification session: ${r}`)}}},ve=u});var Se={};U(Se,{SafePassage:()=>u,VERSION:()=>se,default:()=>u});function O(){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 N(){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"),t.length>0&&console.warn("SafePassage SDK Browser Compatibility:",t.join("; "))}M();typeof window!="undefined"&&(O(),N());var se="3.4.9";if(typeof window!="undefined"&&window){let{SafePassage:t}=(M(),$(re)),e=window;e.SafePassage=t,e.SafePassage&&(e.SafePassage.VERSION=se)}return $(Se);})();
3
3
  if(typeof SafePassageSDK !== "undefined" && SafePassageSDK.SafePassage) { window.SafePassage = SafePassageSDK.SafePassage; window.SafePassage.VERSION = SafePassageSDK.VERSION; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@safepassage/sdk",
3
- "version": "3.4.8",
3
+ "version": "3.4.9",
4
4
  "description": "SafePassage SDK - Lightweight redirect-based age verification",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",