@safepassage/sdk 3.0.4 → 3.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,57 @@
1
1
  /**
2
2
  * SafePassage SDK - Redirect-based age verification
3
- * Lightweight SDK for integrating SafePassage age verification using redirect flow
3
+ *
4
+ * Lightweight SDK for integrating SafePassage age verification using redirect flow.
5
+ * Provides a secure, easy-to-implement solution for age verification with comprehensive
6
+ * security features and flexible integration options.
7
+ *
8
+ * Key Features:
9
+ * - Redirect and new-tab verification modes
10
+ * - Automatic session management for public API keys
11
+ * - HMAC-signed state parameters for security
12
+ * - Rate limiting and race condition protection
13
+ * - Comprehensive security validation and logging
14
+ * - PostMessage communication for new-tab mode
15
+ * - Automatic cleanup and resource management
16
+ * - Environment-specific configuration
17
+ *
18
+ * Security Features:
19
+ * - Origin validation for PostMessage communication
20
+ * - HTTPS enforcement in production
21
+ * - Rate limiting per API key and origin
22
+ * - State parameter signing with timestamps and nonces
23
+ * - Comprehensive security event logging
24
+ * - Protection against race conditions and replay attacks
25
+ *
26
+ * @author SafePassage Engineering
27
+ * @version 2.0.0
28
+ * @since 1.0.0
4
29
  */
5
30
  import { generateState, validateConfig } from '../utils/validation';
6
- import { getEnvironmentUrl, validateEnvironmentSecurity } from '../utils/environment';
7
- import { validatePostMessageOrigin, validateSafePassageMessage, enforceHTTPS, verificationRateLimit, logSecurityEvent } from '../utils/security';
31
+ import { getEnvironmentUrl, validateEnvironmentSecurity, } from '../utils/environment';
32
+ import { validatePostMessageOrigin, validateSafePassageMessage, enforceHTTPS, verificationRateLimit, logSecurityEvent, } from '../utils/security';
33
+ /**
34
+ * SafePassage SDK Main Class
35
+ *
36
+ * Primary SDK class that manages age verification sessions with comprehensive
37
+ * security and error handling. Supports both redirect and new-tab modes with
38
+ * automatic session management and PostMessage communication.
39
+ *
40
+ * The class implements multiple security layers including origin validation,
41
+ * rate limiting, state signing, and comprehensive event logging to ensure
42
+ * secure verification flows.
43
+ */
8
44
  export class SafePassage {
45
+ /**
46
+ * Initialize SafePassage SDK
47
+ *
48
+ * Validates configuration, sets up security measures, and prepares the SDK
49
+ * for verification operations. Performs comprehensive environment validation
50
+ * and security initialization.
51
+ *
52
+ * @param {SafePassageConfig} config - SDK configuration object
53
+ * @throws {Error} If configuration validation fails
54
+ */
9
55
  constructor(config) {
10
56
  this.popupWindow = null;
11
57
  this.messageListener = null;
@@ -17,7 +63,7 @@ export class SafePassage {
17
63
  this.config = {
18
64
  ...config,
19
65
  environment: config.environment || this.detectEnvironment(),
20
- mode: config.mode || 'redirect'
66
+ mode: config.mode || 'redirect',
21
67
  };
22
68
  // Comprehensive environment security validation
23
69
  validateEnvironmentSecurity(this.config.environment);
@@ -29,13 +75,28 @@ export class SafePassage {
29
75
  mode: this.config.mode,
30
76
  origin: window.location.origin,
31
77
  protocol: window.location.protocol,
32
- hostname: window.location.hostname
78
+ hostname: window.location.hostname,
33
79
  });
34
80
  // Set up automatic cleanup on page unload
35
81
  this.setupAutoCleanup();
36
82
  }
37
83
  /**
38
84
  * Initiate age verification with race condition protection
85
+ *
86
+ * Main verification method that handles session creation, security validation,
87
+ * and verification flow initiation. Includes race condition protection and
88
+ * comprehensive error handling.
89
+ *
90
+ * For public keys (pk_*), automatically creates sessions via the portal API.
91
+ * For private keys (sk_*), requires a pre-created sessionId.
92
+ *
93
+ * @param {VerificationOptions} [options={}] - Verification options
94
+ * @param {string} [options.sessionId] - Session ID (required for private keys)
95
+ * @param {number} [options.challengeAge] - Age challenge override
96
+ * @param {string} [options.verificationMode] - Verification mode override
97
+ * @param {string} [options.externalUserId] - External user identifier
98
+ * @returns {Promise<void>} Promise that resolves when verification is initiated
99
+ * @throws {Error} If verification cannot be started or is already in progress
39
100
  */
40
101
  async verify(options = {}) {
41
102
  const isPublicKey = this.isPublicKey();
@@ -56,8 +117,10 @@ export class SafePassage {
56
117
  const error = new Error(`Verification already in progress for session ${this.currentSessionId?.substring(0, 8)}...`);
57
118
  logSecurityEvent('RACE_CONDITION_PREVENTED', {
58
119
  currentSession: this.currentSessionId?.substring(0, 8) + '...',
59
- attemptedSession: options.sessionId ? options.sessionId.substring(0, 8) + '...' : 'undefined',
60
- origin: window.location.origin
120
+ attemptedSession: options.sessionId
121
+ ? options.sessionId.substring(0, 8) + '...'
122
+ : 'undefined',
123
+ origin: window.location.origin,
61
124
  });
62
125
  this.config.onError?.(error);
63
126
  throw error;
@@ -73,18 +136,23 @@ export class SafePassage {
73
136
  logSecurityEvent('RATE_LIMIT_EXCEEDED', {
74
137
  apiKey: this.config.apiKey.substring(0, 8) + '...',
75
138
  origin: window.location.origin,
76
- sessionId: sessionId ? sessionId.substring(0, 8) + '...' : 'undefined'
139
+ sessionId: sessionId
140
+ ? sessionId.substring(0, 8) + '...'
141
+ : 'undefined',
77
142
  });
78
143
  this.config.onError?.(error);
79
144
  throw error;
80
145
  }
81
- const verificationUrl = await this.buildVerificationUrl({ ...options, sessionId });
146
+ const verificationUrl = await this.buildVerificationUrl({
147
+ ...options,
148
+ sessionId,
149
+ });
82
150
  // Log verification attempt
83
151
  logSecurityEvent('VERIFICATION_INITIATED', {
84
152
  environment: this.config.environment,
85
153
  mode: this.config.mode,
86
154
  sessionId: sessionId ? sessionId.substring(0, 8) + '...' : 'undefined',
87
- origin: window.location.origin
155
+ origin: window.location.origin,
88
156
  });
89
157
  if (this.config.mode === 'new-tab') {
90
158
  this.openNewTab(verificationUrl, sessionId);
@@ -103,6 +171,14 @@ export class SafePassage {
103
171
  }
104
172
  /**
105
173
  * Build verification URL with HMAC-signed state
174
+ *
175
+ * Constructs the verification URL with signed state parameter containing
176
+ * all necessary configuration and security information. The state parameter
177
+ * includes HMAC signature for integrity protection.
178
+ *
179
+ * @param {VerificationOptions} options - Verification options
180
+ * @returns {Promise<string>} Complete verification URL
181
+ * @private
106
182
  */
107
183
  async buildVerificationUrl(options) {
108
184
  const baseUrl = getEnvironmentUrl(this.config.environment);
@@ -119,23 +195,48 @@ export class SafePassage {
119
195
  verificationMode: options.verificationMode || this.config.defaultVerificationMode,
120
196
  hasOverrides: hasOverrides, // Flag to indicate explicit overrides
121
197
  externalUserId: options.externalUserId,
122
- timestamp: Date.now()
198
+ timestamp: Date.now(),
199
+ // Phase 2: Include config for self-contained verify-ui
200
+ apiUrl: this.getPortalApiUrl(),
201
+ engineUrl: this.getEngineUrl(),
202
+ wsUrl: this.getWebSocketUrl(),
203
+ environment: this.config.environment,
204
+ features: {
205
+ captureMode: 'enhanced_verification',
206
+ testMode: false,
207
+ warmupPeriodMs: 2000,
208
+ qualityThreshold: 0.6,
209
+ },
123
210
  }, this.config.environment);
124
211
  const params = new URLSearchParams({
125
212
  state,
126
213
  sessionId: options.sessionId,
127
- mode: this.config.mode
214
+ mode: this.config.mode,
128
215
  });
129
- return `${baseUrl}/verify?${params.toString()}`;
216
+ return `${baseUrl}/?${params.toString()}`;
130
217
  }
131
218
  /**
132
219
  * Redirect in same tab
220
+ *
221
+ * Performs a full page redirect to the verification URL.
222
+ * Used for redirect mode verification.
223
+ *
224
+ * @param {string} url - Verification URL to redirect to
225
+ * @private
133
226
  */
134
227
  redirect(url) {
135
228
  window.location.href = url;
136
229
  }
137
230
  /**
138
231
  * Open in new tab with PostMessage communication and proper cleanup
232
+ *
233
+ * Opens verification URL in a new tab/window and sets up secure PostMessage
234
+ * communication for receiving verification results. Includes comprehensive
235
+ * security validation and automatic cleanup.
236
+ *
237
+ * @param {string} url - Verification URL to open
238
+ * @param {string} sessionId - Session ID for result correlation
239
+ * @private
139
240
  */
140
241
  openNewTab(url, sessionId) {
141
242
  // Clean up any existing resources
@@ -159,7 +260,7 @@ export class SafePassage {
159
260
  origin: event.origin,
160
261
  environment: this.config.environment,
161
262
  expectedOrigins: `SafePassage trusted origins for ${this.config.environment}`,
162
- messageType: event.data?.type
263
+ messageType: event.data?.type,
163
264
  });
164
265
  return;
165
266
  }
@@ -170,19 +271,19 @@ export class SafePassage {
170
271
  error: messageValidation.error,
171
272
  origin: event.origin,
172
273
  sessionId: sessionId.substring(0, 8) + '...',
173
- messageType: event.data?.type
274
+ messageType: event.data?.type,
174
275
  });
175
276
  return;
176
277
  }
177
278
  const result = {
178
279
  sessionId: event.data.sessionId,
179
- status: event.data.status
280
+ status: event.data.status,
180
281
  };
181
282
  // Log successful verification completion
182
283
  logSecurityEvent('VERIFICATION_COMPLETED', {
183
284
  status: result.status,
184
285
  sessionId: sessionId.substring(0, 8) + '...',
185
- origin: event.origin
286
+ origin: event.origin,
186
287
  });
187
288
  // Clean up resources
188
289
  this.cleanup();
@@ -211,7 +312,7 @@ export class SafePassage {
211
312
  // Log popup closed event
212
313
  logSecurityEvent('POPUP_CLOSED_BY_USER', {
213
314
  sessionId: sessionId.substring(0, 8) + '...',
214
- environment: this.config.environment
315
+ environment: this.config.environment,
215
316
  });
216
317
  // Clean up and trigger cancel callback
217
318
  this.cleanup();
@@ -223,13 +324,19 @@ export class SafePassage {
223
324
  }
224
325
  /**
225
326
  * Set up automatic cleanup on page unload to prevent memory leaks
327
+ *
328
+ * Registers event listeners for page unload events to ensure proper
329
+ * cleanup of resources and verification state. Handles both traditional
330
+ * page navigation and single-page application route changes.
331
+ *
332
+ * @private
226
333
  */
227
334
  setupAutoCleanup() {
228
335
  this.unloadListener = () => {
229
336
  // Log automatic cleanup
230
337
  logSecurityEvent('SDK_AUTO_CLEANUP', {
231
338
  environment: this.config.environment,
232
- trigger: 'page_unload'
339
+ trigger: 'page_unload',
233
340
  });
234
341
  // Clean up all resources and unlock verification
235
342
  this.cleanup();
@@ -250,10 +357,18 @@ export class SafePassage {
250
357
  }
251
358
  /**
252
359
  * Auto-detect environment based on current URL
360
+ *
361
+ * Analyzes the current hostname to determine the appropriate environment
362
+ * configuration. Used when environment is not explicitly specified.
363
+ *
364
+ * @returns {'production' | 'staging' | 'development'} Detected environment
365
+ * @private
253
366
  */
254
367
  detectEnvironment() {
255
368
  const hostname = window.location.hostname;
256
- if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname.includes('.local')) {
369
+ if (hostname === 'localhost' ||
370
+ hostname === '127.0.0.1' ||
371
+ hostname.includes('.local')) {
257
372
  return 'development';
258
373
  }
259
374
  if (hostname.includes('staging') || hostname.includes('stage')) {
@@ -263,17 +378,27 @@ export class SafePassage {
263
378
  }
264
379
  /**
265
380
  * Unlock verification process to allow new verifications
381
+ *
382
+ * Resets the verification lock state to allow new verification attempts.
383
+ * Called after successful completion, errors, or cleanup.
384
+ *
385
+ * @private
266
386
  */
267
387
  unlockVerification() {
268
388
  this.isVerificationInProgress = false;
269
389
  this.currentSessionId = null;
270
390
  logSecurityEvent('VERIFICATION_UNLOCKED', {
271
391
  environment: this.config.environment,
272
- origin: window.location.origin
392
+ origin: window.location.origin,
273
393
  });
274
394
  }
275
395
  /**
276
396
  * Internal cleanup method to prevent memory leaks
397
+ *
398
+ * Cleans up popup windows, event listeners, and intervals.
399
+ * Does not unlock verification state - that's handled by specific callers.
400
+ *
401
+ * @private
277
402
  */
278
403
  cleanup() {
279
404
  // Close popup window
@@ -295,6 +420,10 @@ export class SafePassage {
295
420
  }
296
421
  /**
297
422
  * Remove auto-cleanup listeners
423
+ *
424
+ * Removes page unload event listeners that were set up for automatic cleanup.
425
+ *
426
+ * @private
298
427
  */
299
428
  removeAutoCleanupListeners() {
300
429
  if (this.unloadListener) {
@@ -305,12 +434,17 @@ export class SafePassage {
305
434
  }
306
435
  /**
307
436
  * Public cleanup method for manual resource management
437
+ *
438
+ * Completely destroys the SDK instance, cleaning up all resources and
439
+ * removing all event listeners. Should be called when the SDK is no longer needed.
440
+ *
441
+ * @public
308
442
  */
309
443
  destroy() {
310
444
  // Log destruction for security monitoring
311
445
  logSecurityEvent('SDK_DESTROYED', {
312
446
  environment: this.config.environment,
313
- origin: window.location.origin
447
+ origin: window.location.origin,
314
448
  });
315
449
  // Clean up all resources
316
450
  this.cleanup();
@@ -319,14 +453,89 @@ export class SafePassage {
319
453
  // Remove auto-cleanup listeners
320
454
  this.removeAutoCleanupListeners();
321
455
  }
456
+ /**
457
+ * Get Portal API URL based on environment
458
+ *
459
+ * Returns the appropriate portal-api URL for the current environment.
460
+ *
461
+ * @returns {string} Portal API URL
462
+ * @private
463
+ */
464
+ getPortalApiUrl() {
465
+ switch (this.config.environment) {
466
+ case 'development':
467
+ return 'http://localhost:3001';
468
+ case 'staging':
469
+ case 'production':
470
+ return 'https://api.safepassageapp.com';
471
+ default:
472
+ return 'https://api.safepassageapp.com';
473
+ }
474
+ }
475
+ /**
476
+ * Get Engine URL based on environment
477
+ *
478
+ * Returns the appropriate verify-engine URL for the current environment.
479
+ * In production, engine access is proxied through portal-api.
480
+ *
481
+ * @returns {string} Engine URL
482
+ * @private
483
+ */
484
+ getEngineUrl() {
485
+ switch (this.config.environment) {
486
+ case 'development':
487
+ return 'http://localhost:8000';
488
+ case 'staging':
489
+ case 'production':
490
+ // Direct access to verify-engine
491
+ return 'https://engine.safepassageapp.com';
492
+ default:
493
+ return 'https://engine.safepassageapp.com';
494
+ }
495
+ }
496
+ /**
497
+ * Get WebSocket URL based on environment
498
+ *
499
+ * Returns the appropriate WebSocket URL for the current environment.
500
+ * In production, WebSocket connections are proxied through portal-api.
501
+ *
502
+ * @returns {string} WebSocket URL
503
+ * @private
504
+ */
505
+ getWebSocketUrl() {
506
+ switch (this.config.environment) {
507
+ case 'development':
508
+ return 'ws://localhost:8000/safe-passage-llm/api/v1/websocket/stream';
509
+ case 'staging':
510
+ case 'production':
511
+ // Direct WebSocket connection to verify-engine
512
+ return 'wss://engine.safepassageapp.com/safe-passage-llm/api/v1/websocket/stream';
513
+ default:
514
+ return 'wss://engine.safepassageapp.com/safe-passage-llm/api/v1/websocket/stream';
515
+ }
516
+ }
322
517
  /**
323
518
  * Detect if this is a public key (pk_ prefix) vs private key (sk_ prefix)
519
+ *
520
+ * Determines API key type based on prefix to handle different authentication flows.
521
+ *
522
+ * @returns {boolean} True if public key, false if private key
523
+ * @private
324
524
  */
325
525
  isPublicKey() {
326
526
  return this.config.apiKey.startsWith('pk_');
327
527
  }
328
528
  /**
329
529
  * Create session internally for public keys
530
+ *
531
+ * Creates a verification session via the portal API for public key authentication.
532
+ * Generates a UUID session ID and submits session creation request with
533
+ * verification parameters.
534
+ *
535
+ * @param {VerificationOptions} options - Verification options
536
+ * @returns {Promise<string>} Created session ID
537
+ * @throws {Error} If session creation fails
538
+ * @private
330
539
  */
331
540
  async createInternalSession(options) {
332
541
  const sessionId = crypto.randomUUID();
@@ -336,7 +545,7 @@ export class SafePassage {
336
545
  method: 'POST',
337
546
  headers: {
338
547
  'Content-Type': 'application/json',
339
- 'Authorization': `Bearer ${this.config.apiKey}`
548
+ Authorization: `Bearer ${this.config.apiKey}`,
340
549
  },
341
550
  body: JSON.stringify({
342
551
  merchantId: this.config.apiKey, // API expects merchantId even though it uses auth header
@@ -346,19 +555,19 @@ export class SafePassage {
346
555
  challengeAge: options.challengeAge,
347
556
  verificationMode: options.verificationMode,
348
557
  merchantName: document.title || window.location.hostname,
349
- externalUserId: options.externalUserId
350
- })
558
+ externalUserId: options.externalUserId,
559
+ }),
351
560
  });
352
561
  if (!response.ok) {
353
562
  const errorData = await response.json().catch(() => ({}));
354
563
  throw new Error(`Failed to create session: ${response.status} ${response.statusText}. ${errorData.message || ''}`);
355
564
  }
356
- const sessionData = await response.json();
565
+ await response.json();
357
566
  // Log successful session creation
358
567
  logSecurityEvent('INTERNAL_SESSION_CREATED', {
359
568
  sessionId: sessionId.substring(0, 8) + '...',
360
569
  environment: this.config.environment,
361
- apiKeyType: 'public'
570
+ apiKeyType: 'public',
362
571
  });
363
572
  return sessionId;
364
573
  }
@@ -367,26 +576,12 @@ export class SafePassage {
367
576
  logSecurityEvent('INTERNAL_SESSION_FAILED', {
368
577
  error: errorMessage,
369
578
  environment: this.config.environment,
370
- apiKeyType: 'public'
579
+ apiKeyType: 'public',
371
580
  });
372
581
  this.config.onError?.(error);
373
582
  throw new Error(`Failed to create verification session: ${errorMessage}`);
374
583
  }
375
584
  }
376
- /**
377
- * Get portal API URL based on environment
378
- */
379
- getPortalApiUrl() {
380
- switch (this.config.environment) {
381
- case 'production':
382
- return 'https://api.safepassageapp.com';
383
- case 'staging':
384
- return 'https://api-staging.safepassageapp.com';
385
- case 'development':
386
- default:
387
- return 'http://localhost:3001';
388
- }
389
- }
390
585
  }
391
586
  // For backwards compatibility
392
587
  export default SafePassage;
package/dist/index.d.ts CHANGED
@@ -1,117 +1,21 @@
1
1
  /**
2
- * SafePassage SDK Type Definitions
2
+ * SafePassage SDK - Redirect-based age verification
3
+ *
4
+ * @example
5
+ * ```javascript
6
+ * // Initialize SDK
7
+ * const sp = new SafePassage({
8
+ * apiKey: 'pk_live_xxxxx',
9
+ * returnUrl: 'https://merchant.com/verified',
10
+ * cancelUrl: 'https://merchant.com/cancelled'
11
+ * });
12
+ *
13
+ * // Trigger verification
14
+ * sp.verify({
15
+ * sessionId: generateUUID() // Merchant-generated UUID v4
16
+ * });
17
+ * ```
3
18
  */
4
- export interface SafePassageConfig {
5
- /**
6
- * Public API key (starts with pk_live_ or pk_test_)
7
- */
8
- apiKey: string;
9
- /**
10
- * URL to redirect to after successful verification
11
- * Must be pre-registered in dashboard
12
- */
13
- returnUrl: string;
14
- /**
15
- * URL to redirect to if user cancels verification
16
- * Must be pre-registered in dashboard
17
- */
18
- cancelUrl: string;
19
- /**
20
- * Environment to use
21
- * @default Auto-detected based on hostname
22
- */
23
- environment?: 'production' | 'staging' | 'development';
24
- /**
25
- * Verification mode
26
- * @default 'redirect'
27
- */
28
- mode?: 'redirect' | 'new-tab';
29
- /**
30
- * Default challenge age (minimum 25)
31
- * Can be overridden per verification
32
- */
33
- defaultChallengeAge?: number;
34
- /**
35
- * Default verification mode
36
- * Can be overridden per verification
37
- */
38
- defaultVerificationMode?: 'L1' | 'L2';
39
- /**
40
- * Callback when verification completes (new-tab mode only)
41
- */
42
- onComplete?: (result: VerificationResult) => void;
43
- /**
44
- * Callback when user cancels (new-tab mode only)
45
- */
46
- onCancel?: () => void;
47
- /**
48
- * Callback for errors
49
- */
50
- onError?: (error: Error) => void;
51
- }
52
- export interface VerificationOptions {
53
- /**
54
- * Merchant-generated UUID v4 for this verification session
55
- * Required for private keys (sk_), optional for public keys (pk_)
56
- * For public keys: SDK will generate session internally
57
- */
58
- sessionId?: string;
59
- /**
60
- * Minimum age to verify (minimum 25)
61
- * @default Uses merchant dashboard configuration
62
- */
63
- challengeAge?: number;
64
- /**
65
- * Verification mode
66
- * L1: Age estimation allowed if user appears older
67
- * L2: Full ID verification required
68
- * @default Uses merchant dashboard configuration
69
- */
70
- verificationMode?: 'L1' | 'L2';
71
- }
72
- export interface VerificationResult {
73
- /**
74
- * The session ID that was verified
75
- */
76
- sessionId: string;
77
- /**
78
- * Binary result: 'verified' or 'failed'
79
- * Full details available via server-side API
80
- */
81
- status: 'verified' | 'failed' | 'cancelled';
82
- }
83
- export interface StatePayload {
84
- merchantId: string;
85
- sessionId: string;
86
- returnUrl: string;
87
- cancelUrl: string;
88
- challengeAge?: number;
89
- verificationMode?: 'L1' | 'L2';
90
- hasOverrides?: boolean;
91
- timestamp: number;
92
- }
93
- export interface SessionValidationResponse {
94
- sessionId: string;
95
- merchantId: string;
96
- status: 'verified' | 'failed';
97
- verified: boolean;
98
- estimatedAge?: number;
99
- challengeAge: number;
100
- verificationMode: 'L1' | 'L2';
101
- verificationMethod?: 'facial' | 'document' | 'combined';
102
- timestamp: string;
103
- expiresAt: string;
104
- }
105
- export interface SessionCreationResponse {
106
- sessionToken: string;
107
- verifyUrl: string;
108
- expiresAt: string;
109
- }
110
- export interface CreateSessionRequest {
111
- sessionId: string;
112
- returnUrl: string;
113
- cancelUrl?: string;
114
- challengeAge?: number;
115
- verificationMode?: 'L1' | 'L2';
116
- merchantName?: string;
117
- }
19
+ export { SafePassage, SafePassage as default } from './core/SafePassageSDK';
20
+ export type { SafePassageConfig, VerificationOptions, VerificationResult, SessionValidationResponse, } from './types';
21
+ export declare const VERSION = "3.0.4";
package/dist/index.js CHANGED
@@ -28,7 +28,13 @@ export { SafePassage, SafePassage as default } from './core/SafePassageSDK';
28
28
  export const VERSION = '3.0.4';
29
29
  // For UMD builds
30
30
  if (typeof window !== 'undefined' && window) {
31
+ // Dynamic import for UMD builds
32
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
31
33
  const { SafePassage } = require('./core/SafePassageSDK');
32
- window.SafePassage = SafePassage;
33
- window.SafePassage.VERSION = VERSION;
34
+ // Attach to window object for global access
35
+ const globalWindow = window;
36
+ globalWindow.SafePassage = SafePassage;
37
+ if (globalWindow.SafePassage) {
38
+ globalWindow.SafePassage.VERSION = VERSION;
39
+ }
34
40
  }