@safepassage/sdk 3.2.3 → 3.3.1

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
@@ -31,8 +31,7 @@ npm install @safepassage/sdk
31
31
  // Initialize SDK
32
32
  const safePassage = new SafePassage({
33
33
  apiKey: 'sk_xxxxx', // Your API key
34
- returnUrl: 'https://yoursite.com/verified',
35
- cancelUrl: 'https://yoursite.com/cancelled'
34
+ returnUrl: 'https://yoursite.com/verified'
36
35
  });
37
36
 
38
37
  // Generate a session ID (must be UUID v4)
@@ -52,11 +51,9 @@ safePassage.verify({
52
51
  |--------|------|----------|-------------|
53
52
  | apiKey | string | Yes | Your API key (pk_xxx for client-side, sk_xxx for server-side) |
54
53
  | returnUrl | string | Yes | URL to redirect after successful verification |
55
- | cancelUrl | string | Yes | URL to redirect if user cancels |
56
54
  | environment | string | No | 'production', 'staging', or 'development' (auto-detected) |
57
55
  | mode | string | No | 'redirect' (default) or 'new-tab' |
58
56
  | onComplete | function | No | Callback for new-tab mode completion |
59
- | onCancel | function | No | Callback for new-tab mode cancellation |
60
57
  | onError | function | No | Error handler |
61
58
 
62
59
  ### API Key Types
@@ -127,8 +124,7 @@ User is redirected to SafePassage, then back to your site:
127
124
  ```javascript
128
125
  const safePassage = new SafePassage({
129
126
  apiKey: 'sk_xxxxx',
130
- returnUrl: '/age-verified',
131
- cancelUrl: '/age-gate'
127
+ returnUrl: '/age-verified'
132
128
  });
133
129
 
134
130
  safePassage.verify({ sessionId: crypto.randomUUID() });
@@ -141,20 +137,54 @@ Verification opens in a popup window:
141
137
  const safePassage = new SafePassage({
142
138
  apiKey: 'sk_xxxxx',
143
139
  returnUrl: '/age-verified',
144
- cancelUrl: '/age-gate',
145
140
  mode: 'new-tab',
146
141
  onComplete: (result) => {
147
142
  console.log('Verified:', result.sessionId);
148
143
  // Validate on your server!
149
- },
150
- onCancel: () => {
151
- console.log('User cancelled');
152
144
  }
153
145
  });
154
146
 
155
147
  safePassage.verify({ sessionId: crypto.randomUUID() });
156
148
  ```
157
149
 
150
+ ## URL Query Parameters
151
+
152
+ SafePassage supports optional query parameters for streamlined verification flows:
153
+
154
+ ### skip_intro
155
+ Skip the introductory screen and navigate directly to camera access:
156
+
157
+ ```javascript
158
+ // Via SDK (automatic)
159
+ safePassage.verify({ sessionId: crypto.randomUUID() });
160
+
161
+ // Via server-side session creation
162
+ const url = new URL(verifyUrl);
163
+ url.searchParams.set('skip_intro', 'true');
164
+ window.location.href = url.toString();
165
+ ```
166
+
167
+ ### auto_return
168
+ Automatically redirect to `returnUrl` after successful verification (redirect mode only):
169
+
170
+ ```javascript
171
+ // Via server-side session creation
172
+ const url = new URL(verifyUrl);
173
+ url.searchParams.set('auto_return', 'true');
174
+ window.location.href = url.toString();
175
+ ```
176
+
177
+ ### Combined Example
178
+ ```javascript
179
+ const url = new URL(verifyUrl);
180
+ url.searchParams.set('skip_intro', 'true');
181
+ url.searchParams.set('auto_return', 'true');
182
+ // Result: Streamlined flow with minimal user interaction
183
+ window.location.href = url.toString();
184
+ ```
185
+
186
+ **For comprehensive parameter documentation, usage examples, and integration patterns, see [URL Parameters Reference](../../../docs/technical/api/VERIFY_UI_PARAMETERS.md).**
187
+
158
188
  ## Server-Side Validation (Required!)
159
189
 
160
190
  Always validate the session on your server:
@@ -209,8 +239,7 @@ function generateUUID() {
209
239
  <script>
210
240
  const safePassage = new SafePassage({
211
241
  apiKey: 'sk_xxxxx',
212
- returnUrl: window.location.href + '?verified=true',
213
- cancelUrl: window.location.href
242
+ returnUrl: window.location.href + '?verified=true'
214
243
  });
215
244
 
216
245
  function verifyAge() {
@@ -248,8 +277,7 @@ import { SafePassage, SafePassageConfig } from '@safepassage/sdk';
248
277
 
249
278
  const config: SafePassageConfig = {
250
279
  apiKey: process.env.SAFEPASSAGE_PUBLIC_KEY!,
251
- returnUrl: '/verified',
252
- cancelUrl: '/cancelled'
280
+ returnUrl: '/verified'
253
281
  };
254
282
 
255
283
  const safePassage = new SafePassage(config);
@@ -261,8 +289,7 @@ New streamlined approach (10 lines):
261
289
  ```javascript
262
290
  const safePassage = new SafePassage({
263
291
  apiKey: 'sk_xxxxx',
264
- returnUrl: '/verified',
265
- cancelUrl: '/cancelled'
292
+ returnUrl: '/verified'
266
293
  });
267
294
  safePassage.verify({ sessionId: crypto.randomUUID() });
268
295
  ```
@@ -280,4 +307,4 @@ safePassage.verify({ sessionId: crypto.randomUUID() });
280
307
  1. Always generate session IDs on the merchant side
281
308
  2. Validate sessions server-side before granting access
282
309
  3. Pre-register callback URLs in your dashboard
283
- 4. Never expose your secret key (sk_xxx)
310
+ 4. Never expose your secret key (sk_xxx)
@@ -48,6 +48,7 @@ export declare class SafePassage {
48
48
  private isVerificationInProgress;
49
49
  private currentSessionId;
50
50
  private lastVerifyUrl;
51
+ private lastSessionToken;
51
52
  /**
52
53
  * Initialize SafePassage SDK
53
54
  *
@@ -61,6 +61,8 @@ export class SafePassage {
61
61
  this.currentSessionId = null;
62
62
  // Server-provided verify URL (includes sessionToken)
63
63
  this.lastVerifyUrl = null;
64
+ // Server-provided session token (WS auth)
65
+ this.lastSessionToken = null;
64
66
  validateConfig(config);
65
67
  // Normalize environment: treat 'development' or any unknown value as 'production'
66
68
  let normalizedEnvironment = config.environment || this.detectEnvironment();
@@ -68,11 +70,7 @@ export class SafePassage {
68
70
  console.warn(`SafePassage SDK: Unknown environment '${normalizedEnvironment}', defaulting to 'production'`);
69
71
  normalizedEnvironment = 'production';
70
72
  }
71
- this.config = {
72
- ...config,
73
- environment: normalizedEnvironment,
74
- mode: config.mode || 'redirect',
75
- };
73
+ this.config = Object.assign(Object.assign({}, config), { environment: normalizedEnvironment, mode: config.mode || 'redirect' });
76
74
  // Comprehensive environment security validation
77
75
  validateEnvironmentSecurity(this.config.environment);
78
76
  // Enforce HTTPS in production (additional layer)
@@ -107,6 +105,7 @@ export class SafePassage {
107
105
  * @throws {Error} If verification cannot be started or is already in progress
108
106
  */
109
107
  async verify(options = {}) {
108
+ var _a, _b, _c, _d, _e, _f;
110
109
  const isPublicKey = this.isPublicKey();
111
110
  // For public keys, create session via API (SafePassage generates the sessionId)
112
111
  let sessionId;
@@ -123,13 +122,13 @@ export class SafePassage {
123
122
  }
124
123
  // Race condition check - prevent multiple simultaneous verifications
125
124
  if (this.isVerificationInProgress) {
126
- const error = new Error(`Verification already in progress for session ${this.currentSessionId?.substring(0, 8)}...`);
125
+ const error = new Error(`Verification already in progress for session ${(_a = this.currentSessionId) === null || _a === void 0 ? void 0 : _a.substring(0, 8)}...`);
127
126
  logSecurityEvent('RACE_CONDITION_PREVENTED', {
128
- currentSession: this.currentSessionId?.substring(0, 8) + '...',
127
+ currentSession: ((_b = this.currentSessionId) === null || _b === void 0 ? void 0 : _b.substring(0, 8)) + '...',
129
128
  attemptedSession: 'new-session-attempt',
130
129
  origin: window.location.origin,
131
130
  });
132
- this.config.onError?.(error);
131
+ (_d = (_c = this.config).onError) === null || _d === void 0 ? void 0 : _d.call(_c, error);
133
132
  throw error;
134
133
  }
135
134
  // Lock verification process
@@ -147,7 +146,7 @@ export class SafePassage {
147
146
  ? sessionId.substring(0, 8) + '...'
148
147
  : 'undefined',
149
148
  });
150
- this.config.onError?.(error);
149
+ (_f = (_e = this.config).onError) === null || _f === void 0 ? void 0 : _f.call(_e, error);
151
150
  throw error;
152
151
  }
153
152
  const verificationUrl = await this.buildVerificationUrl(options, sessionId);
@@ -212,6 +211,9 @@ export class SafePassage {
212
211
  },
213
212
  // Include handoffToken if available (for QR code desktop flow)
214
213
  handoffToken: this._temporaryHandoffToken,
214
+ // Include sessionToken and verifyUrl to make UI auth deterministic
215
+ sessionToken: this.lastSessionToken || undefined,
216
+ verifyUrl: this.lastVerifyUrl || undefined,
215
217
  }, this.config.environment);
216
218
  // Prefer server-provided verifyUrl (contains sessionToken) and append state/mode
217
219
  if (this.lastVerifyUrl) {
@@ -219,13 +221,28 @@ export class SafePassage {
219
221
  const url = new URL(this.lastVerifyUrl);
220
222
  url.searchParams.set('state', state);
221
223
  url.searchParams.set('mode', this.config.mode);
224
+ // Append skip parameters if provided
225
+ if (options.skipIntro) {
226
+ url.searchParams.set('skip_intro', 'true');
227
+ }
228
+ if (options.autoReturn) {
229
+ url.searchParams.set('auto_return', 'true');
230
+ }
222
231
  return url.toString();
223
232
  }
224
- catch {
233
+ catch (_a) {
225
234
  // Fall back to client-constructed URL if parsing fails
226
235
  }
227
236
  }
237
+ // Client-constructed URL fallback (for backwards compatibility)
228
238
  const params = new URLSearchParams({ state, sessionId, mode: this.config.mode });
239
+ // Append skip parameters if provided
240
+ if (options.skipIntro) {
241
+ params.set('skip_intro', 'true');
242
+ }
243
+ if (options.autoReturn) {
244
+ params.set('auto_return', 'true');
245
+ }
229
246
  return `${baseUrl}/?${params.toString()}`;
230
247
  }
231
248
  /**
@@ -252,6 +269,7 @@ export class SafePassage {
252
269
  * @private
253
270
  */
254
271
  openNewTab(url, sessionId) {
272
+ var _a, _b;
255
273
  // Clean up any existing resources
256
274
  this.cleanup();
257
275
  // Clear any existing popup monitor interval
@@ -262,18 +280,19 @@ export class SafePassage {
262
280
  // Open new tab
263
281
  this.popupWindow = window.open(url, 'safepassage-verify', 'width=600,height=700');
264
282
  if (!this.popupWindow) {
265
- this.config.onError?.(new Error('Failed to open verification window. Please check popup blocker settings.'));
283
+ (_b = (_a = this.config).onError) === null || _b === void 0 ? void 0 : _b.call(_a, new Error('Failed to open verification window. Please check popup blocker settings.'));
266
284
  return;
267
285
  }
268
286
  // Set up PostMessage listener with enhanced security
269
287
  this.messageListener = (event) => {
288
+ var _a, _b, _c, _d, _e, _f, _g, _h;
270
289
  // Enhanced origin validation with strict allowlist
271
290
  if (!validatePostMessageOrigin(event, this.config.environment)) {
272
291
  logSecurityEvent('POSTMESSAGE_ORIGIN_BLOCKED', {
273
292
  origin: event.origin,
274
293
  environment: this.config.environment,
275
294
  expectedOrigins: `SafePassage trusted origins for ${this.config.environment}`,
276
- messageType: event.data?.type,
295
+ messageType: (_a = event.data) === null || _a === void 0 ? void 0 : _a.type,
277
296
  });
278
297
  return;
279
298
  }
@@ -284,7 +303,7 @@ export class SafePassage {
284
303
  error: messageValidation.error,
285
304
  origin: event.origin,
286
305
  sessionId: sessionId.substring(0, 8) + '...',
287
- messageType: event.data?.type,
306
+ messageType: (_b = event.data) === null || _b === void 0 ? void 0 : _b.type,
288
307
  });
289
308
  return;
290
309
  }
@@ -309,18 +328,19 @@ export class SafePassage {
309
328
  }
310
329
  // Trigger appropriate callback
311
330
  if (result.status === 'verified') {
312
- this.config.onComplete?.(result);
331
+ (_d = (_c = this.config).onComplete) === null || _d === void 0 ? void 0 : _d.call(_c, result);
313
332
  }
314
333
  else if (result.status === 'cancelled') {
315
- this.config.onCancel?.();
334
+ (_f = (_e = this.config).onCancel) === null || _f === void 0 ? void 0 : _f.call(_e);
316
335
  }
317
336
  else {
318
- this.config.onError?.(new Error(`Verification failed: ${result.status}`));
337
+ (_h = (_g = this.config).onError) === null || _h === void 0 ? void 0 : _h.call(_g, new Error(`Verification failed: ${result.status}`));
319
338
  }
320
339
  };
321
340
  window.addEventListener('message', this.messageListener);
322
341
  // Monitor popup window with proper cleanup
323
342
  this.popupMonitorInterval = setInterval(() => {
343
+ var _a, _b;
324
344
  if (this.popupWindow && this.popupWindow.closed) {
325
345
  // Log popup closed event
326
346
  logSecurityEvent('POPUP_CLOSED_BY_USER', {
@@ -331,7 +351,7 @@ export class SafePassage {
331
351
  this.cleanup();
332
352
  // Unlock verification after popup closed
333
353
  this.unlockVerification();
334
- this.config.onCancel?.();
354
+ (_b = (_a = this.config).onCancel) === null || _b === void 0 ? void 0 : _b.call(_a);
335
355
  }
336
356
  }, 500);
337
357
  }
@@ -551,6 +571,7 @@ export class SafePassage {
551
571
  * @private
552
572
  */
553
573
  async createInternalSession(options) {
574
+ var _a, _b;
554
575
  try {
555
576
  const portalApiUrl = this.getPortalApiUrl();
556
577
  const response = await fetch(`${portalApiUrl}/api/v1/sessions/create`, {
@@ -583,6 +604,10 @@ export class SafePassage {
583
604
  if (sessionData.verifyUrl) {
584
605
  this.lastVerifyUrl = sessionData.verifyUrl;
585
606
  }
607
+ // Capture sessionToken for state payload (ensures UI can always auth WS)
608
+ if (sessionData.sessionToken) {
609
+ this.lastSessionToken = sessionData.sessionToken;
610
+ }
586
611
  // Store the handoffToken if it exists for desktop QR flow
587
612
  if (sessionData.handoffToken) {
588
613
  // Store it temporarily so it can be included in the state
@@ -603,7 +628,7 @@ export class SafePassage {
603
628
  environment: this.config.environment,
604
629
  apiKeyType: 'public',
605
630
  });
606
- this.config.onError?.(error);
631
+ (_b = (_a = this.config).onError) === null || _b === void 0 ? void 0 : _b.call(_a, error);
607
632
  throw new Error(`Failed to create verification session: ${errorMessage}`);
608
633
  }
609
634
  }
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.0.15";
21
+ export declare const VERSION = "3.3.0";
package/dist/index.js CHANGED
@@ -24,8 +24,8 @@ if (typeof window !== 'undefined') {
24
24
  checkBrowserCompatibility();
25
25
  }
26
26
  export { SafePassage, SafePassage as default } from './core/SafePassageSDK';
27
- // Version - Updated for handoffToken QR code fix
28
- export const VERSION = '3.0.15';
27
+ // Version - Updated for skip_intro and auto_return support
28
+ export const VERSION = '3.3.0';
29
29
  // For UMD builds
30
30
  if (typeof window !== 'undefined' && window) {
31
31
  // Dynamic import for UMD builds
@@ -1,3 +1,3 @@
1
1
  /* SafePassage SDK v3.0.15 - Redirect Implementation with Enhanced Security */
2
- "use strict";var SafePassageSDK=(()=>{var f=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var z=Object.prototype.hasOwnProperty;var p=(t,e)=>()=>(t&&(e=t(t=0)),e);var h=(t,e)=>{for(var n in e)f(t,n,{get:e[n],enumerable:!0})},Z=(t,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of Y(e))!z.call(t,r)&&r!==n&&f(t,r,{get:()=>e[r],enumerable:!(i=X(e,r))||i.enumerable});return t};var A=t=>Z(f({},"__esModule",{value:!0}),t);function Q(t,e){return x[e].includes(t)}function V(t,e,n=[]){let{origin:i}=t;return Q(i,e)||n.length>0&&n.some(o=>{if(o.startsWith("*.")){let s=o.slice(2);return i.endsWith(`.${s}`)||i===`https://${s}`||i===`http://${s}`}return i===o})?!0:(console.warn(`SafePassage Security: Blocked PostMessage from untrusted origin: ${i}`,{environment:e,trustedOrigins:x[e],allowedCustomOrigins:n,eventType:t.data?.type}),!1)}function M(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 k(t){t==="production"&&window.location.protocol!=="https:"&&console.warn("SafePassage Warning: HTTPS recommended for production environment",{current:window.location.href})}function w(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 a(t,e){console.warn(`SafePassage Security Event: ${t}`,{timestamp:new Date().toISOString(),userAgent:navigator.userAgent,url:window.location.href,...e})}var x,m,L,v=p(()=>{"use strict";x={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"]};m=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(e){let n=Date.now(),r=(this.attempts.get(e)||[]).filter(o=>n-o<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 m});var R={};h(R,{createSignedState:()=>te,generateHMAC:()=>S,generateSecureToken:()=>C,getSigningSecret:()=>y,parseSignedState:()=>ne,verifyHMAC:()=>_});async function S(t,e){let n=new TextEncoder,i=n.encode(e),r=n.encode(t),o=await crypto.subtle.importKey("raw",i,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),s=await crypto.subtle.sign("HMAC",o,r);return Array.from(new Uint8Array(s)).map(l=>l.toString(16).padStart(2,"0")).join("")}async function _(t,e,n){try{let i=await S(t,n);return ee(e,i)}catch{return!1}}function ee(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 y(t){return{production:"safepassage-prod-hmac-2025",staging:"safepassage-stage-hmac-2025"}[t]}async function te(t,e){let n={...t,timestamp:Date.now(),nonce:C(16)},i=JSON.stringify(n),r=y(e),o=await S(i,r);return btoa(JSON.stringify({data:n,signature:o}))}async function ne(t,e,n=$){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:o,signature:s}=r,l=JSON.stringify(o),u=y(e);if(!await _(l,s,u))return console.warn("SafePassage: State signature verification failed"),null;if(o.timestamp){let U=Date.now()-o.timestamp;if(U>n)return console.warn("SafePassage: State parameter expired",{age:U,maxAge:n}),null}let{timestamp:ce,nonce:le,...J}=o;return J}catch(i){return console.warn("SafePassage: Failed to parse signed state",i),null}}var D=p(()=>{"use strict";E()});function W(t){if(!t.apiKey)throw new Error("apiKey is required");if(t.apiKey.length>K)throw new Error(`apiKey exceeds maximum length of ${K} characters`);if(!ie.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>g)throw new Error(`returnUrl exceeds maximum length of ${g} characters`);if(!t.cancelUrl)throw new Error("cancelUrl is required");if(t.cancelUrl.length>g)throw new Error(`cancelUrl exceeds maximum length of ${g} characters`);let e=re(),n=w(t.returnUrl,e);if(!n.isValid)throw new Error(`returnUrl validation failed: ${n.error}`);let i=w(t.cancelUrl,e);if(!i.isValid)throw new Error(`cancelUrl validation failed: ${i.error}`);if(t.defaultChallengeAge!==void 0){if(t.defaultChallengeAge<O)throw new Error(`defaultChallengeAge must be at least ${O}`);if(t.defaultChallengeAge>N)throw new Error(`defaultChallengeAge cannot exceed ${N}`)}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 re(){if(typeof window>"u")return"production";let t=window.location.hostname;return t.includes("staging")||t.includes("stage")?"staging":"production"}async function H(t,e){let{createSignedState:n}=await Promise.resolve().then(()=>(D(),R));return n(t,e)}var O,N,g,K,$,ie,E=p(()=>{"use strict";v();O=25,N=150,g=2048,K=128,$=6e5,ie=/^(pk_|sk_)[a-zA-Z0-9_]+$/});function I(t){let e=q[t]||q.production;if(!e||!e.startsWith("https://"))throw new Error(`HTTPS required for ${t} environment`);return e}function oe(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 j(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{I(t),oe(t)}catch(n){let i=n instanceof Error?n.message:String(n);throw new Error(`Environment configuration validation failed: ${i}`)}}var q,F=p(()=>{"use strict";q={production:"https://av.safepassageapp.com",staging:"https://av.staging.safepassageapp.com"}});var B={};h(B,{SafePassage:()=>c,default:()=>se});var c,se,P=p(()=>{"use strict";E();F();v();c=class{constructor(e){this.popupWindow=null;this.messageListener=null;this.popupMonitorInterval=null;this.unloadListener=null;this.isVerificationInProgress=!1;this.currentSessionId=null;this.lastVerifyUrl=null;W(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={...e,environment:n,mode:e.mode||"redirect"},j(this.config.environment),k(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;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 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:"new-session-attempt",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 s=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?.(s),s}let o=await this.buildVerificationUrl(e,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(o,i):(this.unlockVerification(),this.redirect(o))}catch(r){throw this.unlockVerification(),r}}async buildVerificationUrl(e,n){let i=I(this.config.environment),r=e.challengeAge!==void 0,o=e.verificationMode!==void 0,s=r||o,l=await H({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: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);if(this.lastVerifyUrl)try{let d=new URL(this.lastVerifyUrl);return d.searchParams.set("state",l),d.searchParams.set("mode",this.config.mode),d.toString()}catch{}let u=new URLSearchParams({state:l,sessionId:n,mode:this.config.mode});return`${i}/?${u.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(!V(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=M(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 o={sessionId:i.data.sessionId,status:i.data.status};a("VERIFICATION_COMPLETED",{status:o.status,sessionId:n.substring(0,8)+"...",origin:i.origin}),this.cleanup(),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),o.status==="verified"?this.config.onComplete?.(o):o.status==="cancelled"?this.config.onCancel?.():this.config.onError?.(new Error(`Verification failed: ${o.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){try{let n=this.getPortalApiUrl(),i=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,challengeAge:e.challengeAge,verificationMode:e.verificationMode,merchantName:document.title||window.location.hostname,externalUserId:e.externalUserId})});if(!i.ok){let s=await i.json().catch(()=>({}));throw new Error(`Failed to create session: ${i.status} ${i.statusText}. ${s.message||""}`)}let r=await i.json(),o=r.sessionId;if(!o)throw new Error("Server did not return a sessionId");return r.verifyUrl&&(this.lastVerifyUrl=r.verifyUrl),r.handoffToken&&(this._temporaryHandoffToken=r.handoffToken),a("INTERNAL_SESSION_CREATED",{sessionId:o.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"}),o}catch(n){let i=n instanceof Error?n.message:String(n);throw a("INTERNAL_SESSION_FAILED",{error:i,environment:this.config.environment,apiKeyType:"public"}),this.config.onError?.(n),new Error(`Failed to create verification session: ${i}`)}}},se=c});var ae={};h(ae,{SafePassage:()=>c,VERSION:()=>G,default:()=>c});function b(){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 T(){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("; "))}P();typeof window<"u"&&(b(),T());var G="3.0.15";if(typeof window<"u"&&window){let{SafePassage:t}=(P(),A(B)),e=window;e.SafePassage=t,e.SafePassage&&(e.SafePassage.VERSION=G)}return A(ae);})();
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 r of le(e))!P.call(t,r)&&r!==n&&S(t,r,{get:()=>e[r],enumerable:!(i=ae(e,r))||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 r;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:(r=t.data)==null?void 0:r.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","cancelled"].includes(n.status)?{isValid:!1,error:"Invalid status value"}:{isValid:!0}}function q(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 r of i)if(r.test(t))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch(n){return{isValid:!1,error:"Invalid URL format"}}}function l(t,e){console.warn(`SafePassage Security Event: ${t}`,h({timestamp:new Date().toISOString(),userAgent:navigator.userAgent,url:window.location.href},e))}var K,A,j,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(),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)}},j=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),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(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),r=V(e),s=await x(i,r);return btoa(JSON.stringify({data:n,signature:s}))}async function fe(t,e,n=X){try{let r=atob(t),s=JSON.parse(r);if(!s.data||!s.signature)return console.warn("SafePassage: Invalid signed state format"),null;let{data:o,signature:a}=s,p=JSON.stringify(o),c=V(e);if(!await F(p,a,c))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(r){return console.warn("SafePassage: Failed to parse signed state",r),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`);if(!t.cancelUrl)throw new Error("cancelUrl is required");if(t.cancelUrl.length>E)throw new Error(`cancelUrl 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}`);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,_=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;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),q(this.config.environment),l("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 r,s,o,a,p,c;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 ${(r=this.currentSessionId)==null?void 0:r.substring(0,8)}...`);throw l("RACE_CONDITION_PREVENTED",{currentSession:((s=this.currentSessionId)==null?void 0:s.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(!j.isAllowed(d)){let f=new Error("Too many verification attempts. Please wait before trying again.");throw l("RATE_LIMIT_EXCEEDED",{apiKey:this.config.apiKey.substring(0,8)+"...",origin:window.location.origin,sessionId:i?i.substring(0,8)+"...":"undefined"}),(c=(p=this.config).onError)==null||c.call(p,f),f}let g=await this.buildVerificationUrl(e,i);l("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),r=e.challengeAge!==void 0,s=e.verificationMode!==void 0,o=r||s,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,sessionToken:this.lastSessionToken||void 0,verifyUrl:this.lastVerifyUrl||void 0},this.config.environment);if(this.lastVerifyUrl)try{let c=new URL(this.lastVerifyUrl);return c.searchParams.set("state",a),c.searchParams.set("mode",this.config.mode),e.skipIntro&&c.searchParams.set("skip_intro","true"),e.autoReturn&&c.searchParams.set("auto_return","true"),c.toString()}catch(c){}let p=new URLSearchParams({state:a,sessionId:n,mode:this.config.mode});return e.skipIntro&&p.set("skip_intro","true"),e.autoReturn&&p.set("auto_return","true"),`${i}/?${p.toString()}`}redirect(e){window.location.href=e}openNewTab(e,n){var i,r;if(this.cleanup(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),this.popupWindow=window.open(e,"safepassage-verify","width=600,height=700"),!this.popupWindow){(r=(i=this.config).onError)==null||r.call(i,new Error("Failed to open verification window. Please check popup blocker settings."));return}this.messageListener=s=>{var p,c,d,g,f,I,m,L;if(!W(s,this.config.environment)){l("POSTMESSAGE_ORIGIN_BLOCKED",{origin:s.origin,environment:this.config.environment,expectedOrigins:`SafePassage trusted origins for ${this.config.environment}`,messageType:(p=s.data)==null?void 0:p.type});return}let o=H(s,n);if(!o.isValid){l("POSTMESSAGE_VALIDATION_FAILED",{error:o.error,origin:s.origin,sessionId:n.substring(0,8)+"...",messageType:(c=s.data)==null?void 0:c.type});return}let a={sessionId:s.data.sessionId,status:s.data.status};l("VERIFICATION_COMPLETED",{status:a.status,sessionId:n.substring(0,8)+"...",origin:s.origin}),this.cleanup(),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),a.status==="verified"?(g=(d=this.config).onComplete)==null||g.call(d,a):a.status==="cancelled"?(I=(f=this.config).onCancel)==null||I.call(f):(L=(m=this.config).onError)==null||L.call(m,new Error(`Verification failed: ${a.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{var s,o;this.popupWindow&&this.popupWindow.closed&&(l("POPUP_CLOSED_BY_USER",{sessionId:n.substring(0,8)+"...",environment:this.config.environment}),this.cleanup(),this.unlockVerification(),(o=(s=this.config).onCancel)==null||o.call(s))},500)}setupAutoCleanup(){if(this.unloadListener=()=>{l("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,l("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(){l("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 r=this.getPortalApiUrl(),s=await fetch(`${r}/api/v1/sessions/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({merchantId:this.config.apiKey,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:e.challengeAge,verificationMode:e.verificationMode,merchantName:document.title||window.location.hostname,externalUserId:e.externalUserId})});if(!s.ok){let p=await s.json().catch(()=>({}));throw new Error(`Failed to create session: ${s.status} ${s.statusText}. ${p.message||""}`)}let o=await s.json(),a=o.sessionId;if(!a)throw new Error("Server did not return a sessionId");return o.verifyUrl&&(this.lastVerifyUrl=o.verifyUrl),o.sessionToken&&(this.lastSessionToken=o.sessionToken),o.handoffToken&&(this._temporaryHandoffToken=o.handoffToken),l("INTERNAL_SESSION_CREATED",{sessionId:a.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"}),a}catch(r){let s=r instanceof Error?r.message:String(r);throw l("INTERNAL_SESSION_FAILED",{error:s,environment:this.config.environment,apiKeyType:"public"}),(i=(n=this.config).onError)==null||i.call(n,r),new Error(`Failed to create verification session: ${s}`)}}},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("; "))}_();typeof window!="undefined"&&(O(),N());var se="3.3.0";if(typeof window!="undefined"&&window){let{SafePassage:t}=(_(),$(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; }
@@ -68,6 +68,18 @@ export interface VerificationOptions {
68
68
  * Useful for correlating SafePassage sessions with merchant user records
69
69
  */
70
70
  externalUserId?: string;
71
+ /**
72
+ * Skip the intro screen and go directly to camera access
73
+ * Useful for embedded/streamlined flows where user has already consented
74
+ * @default false
75
+ */
76
+ skipIntro?: boolean;
77
+ /**
78
+ * Automatically redirect to returnUrl immediately after successful verification
79
+ * Skips the success screen for seamless embedded experiences
80
+ * @default false
81
+ */
82
+ autoReturn?: boolean;
71
83
  }
72
84
  export interface VerificationResult {
73
85
  /**
@@ -104,6 +116,8 @@ export interface StatePayload {
104
116
  qualityThreshold: number;
105
117
  };
106
118
  handoffToken?: string;
119
+ sessionToken?: string;
120
+ verifyUrl?: string;
107
121
  }
108
122
  export interface SessionValidationResponse {
109
123
  sessionId: string;
@@ -22,6 +22,17 @@
22
22
  * @author SafePassage Engineering
23
23
  * @version 1.0.0
24
24
  */
25
+ var __rest = (this && this.__rest) || function (s, e) {
26
+ var t = {};
27
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
28
+ t[p] = s[p];
29
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
30
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
31
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
32
+ t[p[i]] = s[p[i]];
33
+ }
34
+ return t;
35
+ };
25
36
  import { STATE_EXPIRY_MS } from './validation';
26
37
  /**
27
38
  * Generate HMAC-SHA256 signature using Web Crypto API
@@ -69,7 +80,7 @@ export async function verifyHMAC(data, signature, secret) {
69
80
  const expectedSignature = await generateHMAC(data, secret);
70
81
  return constantTimeCompare(signature, expectedSignature);
71
82
  }
72
- catch {
83
+ catch (_a) {
73
84
  return false;
74
85
  }
75
86
  }
@@ -145,11 +156,7 @@ export function getSigningSecret(environment) {
145
156
  */
146
157
  export async function createSignedState(payload, environment) {
147
158
  // Add timestamp for freshness
148
- const timestampedPayload = {
149
- ...payload,
150
- timestamp: Date.now(),
151
- nonce: generateSecureToken(16), // Add nonce to prevent replay attacks
152
- };
159
+ const timestampedPayload = Object.assign(Object.assign({}, payload), { timestamp: Date.now(), nonce: generateSecureToken(16) });
153
160
  const dataString = JSON.stringify(timestampedPayload);
154
161
  const secret = getSigningSecret(environment);
155
162
  const signature = await generateHMAC(dataString, secret);
@@ -199,7 +206,7 @@ export async function parseSignedState(signedState, environment, maxAge = STATE_
199
206
  }
200
207
  // Remove internal fields before returning
201
208
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
202
- const { timestamp, nonce, ...payload } = data;
209
+ const { timestamp, nonce } = data, payload = __rest(data, ["timestamp", "nonce"]);
203
210
  return payload;
204
211
  }
205
212
  catch (error) {
@@ -51,17 +51,6 @@ export function checkBrowserCompatibility() {
51
51
  if (!window.URLSearchParams) {
52
52
  warnings.push('URLSearchParams not supported, consider adding a polyfill for IE 11 support');
53
53
  }
54
- // Check for modern JavaScript features
55
- try {
56
- // Test optional chaining
57
- const test = { a: { b: 1 } };
58
- const result = test?.a?.b;
59
- if (result !== 1)
60
- throw new Error();
61
- }
62
- catch {
63
- warnings.push('Optional chaining (?.) not supported, ensure transpilation for older browsers');
64
- }
65
54
  // Log warnings if any
66
55
  if (warnings.length > 0) {
67
56
  console.warn('SafePassage SDK Browser Compatibility:', warnings.join('; '));
@@ -29,6 +29,7 @@ export function isOriginTrusted(origin, environment) {
29
29
  * Enhanced origin validation with logging and strict allowlist
30
30
  */
31
31
  export function validatePostMessageOrigin(event, environment, allowedCustomOrigins = []) {
32
+ var _a;
32
33
  const { origin } = event;
33
34
  // Check against trusted SafePassage origins
34
35
  if (isOriginTrusted(origin, environment)) {
@@ -55,7 +56,7 @@ export function validatePostMessageOrigin(event, environment, allowedCustomOrigi
55
56
  environment,
56
57
  trustedOrigins: TRUSTED_ORIGINS[environment],
57
58
  allowedCustomOrigins,
58
- eventType: event.data?.type,
59
+ eventType: (_a = event.data) === null || _a === void 0 ? void 0 : _a.type,
59
60
  });
60
61
  return false;
61
62
  }
@@ -125,7 +126,7 @@ export function validateReturnUrl(url, environment) {
125
126
  }
126
127
  return { isValid: true };
127
128
  }
128
- catch {
129
+ catch (_a) {
129
130
  return { isValid: false, error: 'Invalid URL format' };
130
131
  }
131
132
  }
@@ -185,11 +186,6 @@ export const verificationRateLimit = new VerificationRateLimit();
185
186
  * Security event logging for monitoring
186
187
  */
187
188
  export function logSecurityEvent(event, details) {
188
- console.warn(`SafePassage Security Event: ${event}`, {
189
- timestamp: new Date().toISOString(),
190
- userAgent: navigator.userAgent,
191
- url: window.location.href,
192
- ...details,
193
- });
189
+ console.warn(`SafePassage Security Event: ${event}`, Object.assign({ timestamp: new Date().toISOString(), userAgent: navigator.userAgent, url: window.location.href }, details));
194
190
  // In production, this could send events to a security monitoring service
195
191
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@safepassage/sdk",
3
- "version": "3.2.3",
3
+ "version": "3.3.1",
4
4
  "description": "SafePassage SDK - Lightweight redirect-based age verification",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",