@privateav/sdk 3.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js ADDED
@@ -0,0 +1,960 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __spreadValues = (a, b) => {
10
+ for (var prop in b || (b = {}))
11
+ if (__hasOwnProp.call(b, prop))
12
+ __defNormalProp(a, prop, b[prop]);
13
+ if (__getOwnPropSymbols)
14
+ for (var prop of __getOwnPropSymbols(b)) {
15
+ if (__propIsEnum.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ }
18
+ return a;
19
+ };
20
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
+ var __objRest = (source, exclude) => {
22
+ var target = {};
23
+ for (var prop in source)
24
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
25
+ target[prop] = source[prop];
26
+ if (source != null && __getOwnPropSymbols)
27
+ for (var prop of __getOwnPropSymbols(source)) {
28
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
29
+ target[prop] = source[prop];
30
+ }
31
+ return target;
32
+ };
33
+ var __esm = (fn, res) => function __init() {
34
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
35
+ };
36
+ var __export = (target, all) => {
37
+ for (var name in all)
38
+ __defProp(target, name, { get: all[name], enumerable: true });
39
+ };
40
+
41
+ // src-redirect/utils/security.ts
42
+ function isOriginTrusted(origin, trustedOrigins) {
43
+ return trustedOrigins.includes(origin);
44
+ }
45
+ function validatePostMessageOrigin(event, trustedOrigins, allowedCustomOrigins = [], logLabel = "SDK") {
46
+ var _a;
47
+ const { origin } = event;
48
+ if (isOriginTrusted(origin, trustedOrigins)) {
49
+ return true;
50
+ }
51
+ if (allowedCustomOrigins.length > 0) {
52
+ const isCustomOriginAllowed = allowedCustomOrigins.some((allowedOrigin) => {
53
+ if (allowedOrigin.startsWith("*.")) {
54
+ const domain = allowedOrigin.slice(2);
55
+ return origin.endsWith(`.${domain}`) || origin === `https://${domain}` || origin === `http://${domain}`;
56
+ }
57
+ return origin === allowedOrigin;
58
+ });
59
+ if (isCustomOriginAllowed) {
60
+ return true;
61
+ }
62
+ }
63
+ console.warn(
64
+ `${logLabel} Security: Blocked PostMessage from untrusted origin: ${origin}`,
65
+ {
66
+ trustedOrigins,
67
+ allowedCustomOrigins,
68
+ eventType: (_a = event.data) == null ? void 0 : _a.type
69
+ }
70
+ );
71
+ return false;
72
+ }
73
+ function validateVerificationMessage(event, expectedSessionId, expectedMessageType, legacyMessageType) {
74
+ const { data } = event;
75
+ if (!data || typeof data !== "object") {
76
+ return { isValid: false, error: "Invalid message format" };
77
+ }
78
+ const allowedTypes = legacyMessageType ? [expectedMessageType, legacyMessageType] : [expectedMessageType];
79
+ if (!allowedTypes.includes(data.type)) {
80
+ return { isValid: false, error: "Invalid message type" };
81
+ }
82
+ if (!data.sessionId || data.sessionId !== expectedSessionId) {
83
+ return { isValid: false, error: "Session ID mismatch" };
84
+ }
85
+ if (!data.status || !["verified", "failed"].includes(data.status)) {
86
+ return { isValid: false, error: "Invalid status value" };
87
+ }
88
+ return { isValid: true };
89
+ }
90
+ function enforceHTTPS(environment, logLabel = "SDK") {
91
+ if (environment === "production" && window.location.protocol !== "https:") {
92
+ console.warn(
93
+ `${logLabel} Warning: HTTPS recommended for production environment`,
94
+ { current: window.location.href }
95
+ );
96
+ }
97
+ }
98
+ function validateReturnUrl(url, environment, logLabel = "SDK") {
99
+ try {
100
+ const parsed = new URL(url);
101
+ if (parsed.protocol !== "https:") {
102
+ const isLocalhost = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
103
+ if (!isLocalhost) {
104
+ return {
105
+ isValid: false,
106
+ error: `HTTPS required for return URLs in ${environment}`
107
+ };
108
+ }
109
+ }
110
+ const suspiciousPatterns = [
111
+ /data:/i,
112
+ /javascript:/i,
113
+ /vbscript:/i,
114
+ /file:/i,
115
+ /ftp:/i
116
+ ];
117
+ for (const pattern of suspiciousPatterns) {
118
+ if (pattern.test(url)) {
119
+ return { isValid: false, error: "Blocked suspicious URL scheme" };
120
+ }
121
+ }
122
+ return { isValid: true };
123
+ } catch (e) {
124
+ return { isValid: false, error: "Invalid URL format" };
125
+ }
126
+ }
127
+ function logSecurityEvent(event, metadata, logLabel = "SDK") {
128
+ const context = {
129
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
130
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "unknown",
131
+ url: typeof window !== "undefined" ? window.location.href : "unknown"
132
+ };
133
+ console.warn(`${logLabel} Security Event: ${event}`, __spreadValues(__spreadValues({}, context), metadata));
134
+ }
135
+ var VerificationRateLimit, verificationRateLimit;
136
+ var init_security = __esm({
137
+ "src-redirect/utils/security.ts"() {
138
+ "use strict";
139
+ VerificationRateLimit = class {
140
+ constructor() {
141
+ this.attempts = /* @__PURE__ */ new Map();
142
+ this.maxAttempts = 5;
143
+ this.timeWindow = 6e4;
144
+ }
145
+ // 1 minute
146
+ isAllowed(identifier, logLabel = "SDK") {
147
+ const now = Date.now();
148
+ const attempts = this.attempts.get(identifier) || [];
149
+ const recentAttempts = attempts.filter(
150
+ (time) => now - time < this.timeWindow
151
+ );
152
+ if (recentAttempts.length >= this.maxAttempts) {
153
+ console.warn(
154
+ `${logLabel} Security: Rate limit exceeded for ${identifier}`
155
+ );
156
+ return false;
157
+ }
158
+ recentAttempts.push(now);
159
+ this.attempts.set(identifier, recentAttempts);
160
+ return true;
161
+ }
162
+ reset(identifier) {
163
+ this.attempts.delete(identifier);
164
+ }
165
+ };
166
+ verificationRateLimit = new VerificationRateLimit();
167
+ }
168
+ });
169
+
170
+ // src-redirect/utils/crypto.ts
171
+ var crypto_exports = {};
172
+ __export(crypto_exports, {
173
+ createSignedState: () => createSignedState,
174
+ generateHMAC: () => generateHMAC,
175
+ generateSecureToken: () => generateSecureToken,
176
+ parseSignedState: () => parseSignedState,
177
+ verifyHMAC: () => verifyHMAC
178
+ });
179
+ async function generateHMAC(data, secret) {
180
+ const encoder = new TextEncoder();
181
+ const keyData = encoder.encode(secret);
182
+ const dataBuffer = encoder.encode(data);
183
+ const key = await crypto.subtle.importKey(
184
+ "raw",
185
+ keyData,
186
+ { name: "HMAC", hash: "SHA-256" },
187
+ false,
188
+ ["sign"]
189
+ );
190
+ const signature = await crypto.subtle.sign("HMAC", key, dataBuffer);
191
+ return Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
192
+ }
193
+ async function verifyHMAC(data, signature, secret) {
194
+ try {
195
+ const expectedSignature = await generateHMAC(data, secret);
196
+ return constantTimeCompare(signature, expectedSignature);
197
+ } catch (e) {
198
+ return false;
199
+ }
200
+ }
201
+ function constantTimeCompare(a, b) {
202
+ if (a.length !== b.length) {
203
+ return false;
204
+ }
205
+ let result = 0;
206
+ for (let i = 0; i < a.length; i++) {
207
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
208
+ }
209
+ return result === 0;
210
+ }
211
+ function generateSecureToken(length = 32) {
212
+ const array = new Uint8Array(length);
213
+ crypto.getRandomValues(array);
214
+ return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
215
+ }
216
+ async function createSignedState(payload, hmacSecret) {
217
+ const timestampedPayload = __spreadProps(__spreadValues({}, payload), {
218
+ timestamp: Date.now(),
219
+ nonce: generateSecureToken(16)
220
+ });
221
+ const dataString = JSON.stringify(timestampedPayload);
222
+ const signature = await generateHMAC(dataString, hmacSecret);
223
+ const signedPayload = {
224
+ data: timestampedPayload,
225
+ signature
226
+ };
227
+ return btoa(JSON.stringify(signedPayload));
228
+ }
229
+ async function parseSignedState(signedState, hmacSecret, maxAge = STATE_EXPIRY_MS, logLabel = "SDK") {
230
+ try {
231
+ const json = atob(signedState);
232
+ const signedPayload = JSON.parse(json);
233
+ if (!signedPayload.data || !signedPayload.signature) {
234
+ console.warn(`${logLabel}: Invalid signed state format`);
235
+ return null;
236
+ }
237
+ const { data, signature } = signedPayload;
238
+ const dataString = JSON.stringify(data);
239
+ const isValid = await verifyHMAC(dataString, signature, hmacSecret);
240
+ if (!isValid) {
241
+ console.warn(`${logLabel}: State signature verification failed`);
242
+ return null;
243
+ }
244
+ if (data.timestamp) {
245
+ const age = Date.now() - data.timestamp;
246
+ if (age > maxAge) {
247
+ console.warn(`${logLabel}: State parameter expired`, { age, maxAge });
248
+ return null;
249
+ }
250
+ }
251
+ const _a = data, { timestamp, nonce } = _a, payload = __objRest(_a, ["timestamp", "nonce"]);
252
+ return payload;
253
+ } catch (error) {
254
+ console.warn(`${logLabel}: Failed to parse signed state`, error);
255
+ return null;
256
+ }
257
+ }
258
+ var init_crypto = __esm({
259
+ "src-redirect/utils/crypto.ts"() {
260
+ "use strict";
261
+ init_validation();
262
+ }
263
+ });
264
+
265
+ // src-redirect/utils/validation.ts
266
+ function validateConfig(config, context) {
267
+ if (!config.apiKey) {
268
+ throw new Error("apiKey is required");
269
+ }
270
+ if (config.apiKey.length > MAX_API_KEY_LENGTH) {
271
+ throw new Error(
272
+ `apiKey exceeds maximum length of ${MAX_API_KEY_LENGTH} characters`
273
+ );
274
+ }
275
+ if (!API_KEY_PATTERN.test(config.apiKey)) {
276
+ throw new Error(
277
+ "Invalid apiKey format. Expected pk_xxx (public) or sk_xxx (private)"
278
+ );
279
+ }
280
+ if (typeof window !== "undefined" && config.apiKey.startsWith("sk_")) {
281
+ throw new Error(
282
+ `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: ${context.docsUrl}/server-side-sessions`
283
+ );
284
+ }
285
+ if (!config.returnUrl) {
286
+ throw new Error("returnUrl is required");
287
+ }
288
+ if (config.returnUrl.length > MAX_URL_LENGTH) {
289
+ throw new Error(
290
+ `returnUrl exceeds maximum length of ${MAX_URL_LENGTH} characters`
291
+ );
292
+ }
293
+ const environment = detectEnvironment();
294
+ const returnUrlValidation = validateReturnUrl(config.returnUrl, environment, context.brandName);
295
+ if (!returnUrlValidation.isValid) {
296
+ throw new Error(
297
+ `returnUrl validation failed: ${returnUrlValidation.error}`
298
+ );
299
+ }
300
+ if (config.cancelUrl) {
301
+ if (config.cancelUrl.length > MAX_URL_LENGTH) {
302
+ throw new Error(
303
+ `cancelUrl exceeds maximum length of ${MAX_URL_LENGTH} characters`
304
+ );
305
+ }
306
+ const cancelUrlValidation = validateReturnUrl(config.cancelUrl, environment, context.brandName);
307
+ if (!cancelUrlValidation.isValid) {
308
+ throw new Error(
309
+ `cancelUrl validation failed: ${cancelUrlValidation.error}`
310
+ );
311
+ }
312
+ }
313
+ if (config.defaultChallengeAge !== void 0) {
314
+ if (config.defaultChallengeAge < MINIMUM_AGE) {
315
+ throw new Error(`defaultChallengeAge must be at least ${MINIMUM_AGE}`);
316
+ }
317
+ if (config.defaultChallengeAge > MAXIMUM_AGE) {
318
+ throw new Error(`defaultChallengeAge cannot exceed ${MAXIMUM_AGE}`);
319
+ }
320
+ }
321
+ if (config.defaultVerificationMode && !["L1", "L2"].includes(config.defaultVerificationMode)) {
322
+ throw new Error("defaultVerificationMode must be L1 or L2");
323
+ }
324
+ if (config.mode && !["redirect", "new-tab"].includes(config.mode)) {
325
+ throw new Error("mode must be redirect or new-tab");
326
+ }
327
+ }
328
+ function detectEnvironment() {
329
+ if (typeof window === "undefined") {
330
+ return "production";
331
+ }
332
+ const hostname = window.location.hostname;
333
+ if (hostname.includes("staging") || hostname.includes("stage")) {
334
+ return "staging";
335
+ }
336
+ return "production";
337
+ }
338
+ async function generateState(payload, environment, hmacSecret, logLabel = "SDK") {
339
+ const { createSignedState: createSignedState2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
340
+ return createSignedState2(payload, hmacSecret);
341
+ }
342
+ var MINIMUM_AGE, MAXIMUM_AGE, MAX_URL_LENGTH, MAX_API_KEY_LENGTH, STATE_EXPIRY_MS, API_KEY_PATTERN;
343
+ var init_validation = __esm({
344
+ "src-redirect/utils/validation.ts"() {
345
+ "use strict";
346
+ init_security();
347
+ MINIMUM_AGE = 25;
348
+ MAXIMUM_AGE = 150;
349
+ MAX_URL_LENGTH = 2048;
350
+ MAX_API_KEY_LENGTH = 128;
351
+ STATE_EXPIRY_MS = 6e5;
352
+ API_KEY_PATTERN = /^(pk_|sk_)[a-zA-Z0-9_]+$/;
353
+ }
354
+ });
355
+
356
+ // src-redirect/utils/polyfills.ts
357
+ function setupPolyfills() {
358
+ if (!crypto.randomUUID) {
359
+ crypto.randomUUID = function() {
360
+ const array = new Uint8Array(16);
361
+ crypto.getRandomValues(array);
362
+ array[6] = array[6] & 15 | 64;
363
+ array[8] = array[8] & 63 | 128;
364
+ const hex = Array.from(array).map((b) => b.toString(16).padStart(2, "0")).join("");
365
+ return [
366
+ hex.slice(0, 8),
367
+ hex.slice(8, 12),
368
+ hex.slice(12, 16),
369
+ hex.slice(16, 20),
370
+ hex.slice(20, 32)
371
+ ].join("-");
372
+ };
373
+ }
374
+ }
375
+ function checkBrowserCompatibility(logLabel = "SDK") {
376
+ const warnings = [];
377
+ if (!window.crypto || !window.crypto.getRandomValues) {
378
+ throw new Error(`${logLabel} requires Web Crypto API support`);
379
+ }
380
+ if (!window.crypto.subtle) {
381
+ throw new Error(
382
+ `${logLabel} requires Web Crypto subtle API for HMAC operations`
383
+ );
384
+ }
385
+ if (!crypto.randomUUID) {
386
+ warnings.push("crypto.randomUUID not supported, using polyfill");
387
+ }
388
+ if (!window.URLSearchParams) {
389
+ warnings.push(
390
+ "URLSearchParams not supported, consider adding a polyfill for IE 11 support"
391
+ );
392
+ }
393
+ if (warnings.length > 0) {
394
+ console.warn(`${logLabel} Browser Compatibility:`, warnings.join("; "));
395
+ }
396
+ }
397
+
398
+ // src-redirect/core/VerificationSDK.ts
399
+ init_validation();
400
+
401
+ // src-redirect/utils/environment.ts
402
+ function getEnvironmentUrl(environment, urls) {
403
+ const url = urls.verifyUiUrl;
404
+ if (!url || !url.startsWith("https://")) {
405
+ throw new Error(`HTTPS required for ${environment} environment`);
406
+ }
407
+ return url;
408
+ }
409
+ function getApiUrl(environment, urls) {
410
+ const url = urls.apiUrl;
411
+ if (!url || !url.startsWith("https://")) {
412
+ throw new Error(
413
+ `HTTPS required for API URLs in ${environment} environment`
414
+ );
415
+ }
416
+ return url;
417
+ }
418
+ function validateEnvironmentSecurity(environment, urls, logLabel = "SDK") {
419
+ const isSecure = window.location.protocol === "https:";
420
+ switch (environment) {
421
+ case "production":
422
+ if (!isSecure) {
423
+ console.warn(`${logLabel} Warning: HTTPS recommended for production environment`);
424
+ }
425
+ break;
426
+ case "staging":
427
+ if (!isSecure) {
428
+ console.warn(
429
+ `${logLabel} Warning: HTTPS strongly recommended in staging environment`
430
+ );
431
+ }
432
+ break;
433
+ }
434
+ try {
435
+ getEnvironmentUrl(environment, urls);
436
+ getApiUrl(environment, urls);
437
+ } catch (error) {
438
+ const errorMessage = error instanceof Error ? error.message : String(error);
439
+ throw new Error(`Environment configuration validation failed: ${errorMessage}`);
440
+ }
441
+ }
442
+
443
+ // src-redirect/core/VerificationSDK.ts
444
+ init_security();
445
+ var VerificationSDK = class {
446
+ /**
447
+ * Initialize SDK
448
+ *
449
+ * Validates configuration, sets up security measures, and prepares the SDK
450
+ * for verification operations. Performs comprehensive environment validation
451
+ * and security initialization.
452
+ */
453
+ constructor(config, brandUrls, brandConstants) {
454
+ this.popupWindow = null;
455
+ this.messageListener = null;
456
+ this.popupMonitorInterval = null;
457
+ this.unloadListener = null;
458
+ this.isVerificationInProgress = false;
459
+ this.currentSessionId = null;
460
+ // Server-provided verify URL (includes sessionToken)
461
+ this.lastVerifyUrl = null;
462
+ // Server-provided session token (WS auth)
463
+ this.lastSessionToken = null;
464
+ // Temporary storage for QR handoff token to include in state
465
+ this.temporaryHandoffToken = null;
466
+ this.brandUrls = brandUrls;
467
+ this.brandConstants = brandConstants;
468
+ validateConfig(config, {
469
+ brandName: this.brandConstants.name,
470
+ docsUrl: this.brandConstants.docsUrl
471
+ });
472
+ let normalizedEnvironment = config.environment || this.detectEnvironment();
473
+ if (normalizedEnvironment !== "staging" && normalizedEnvironment !== "production") {
474
+ console.warn(
475
+ `${this.brandConstants.name} SDK: Unknown environment '${normalizedEnvironment}', defaulting to 'production'`
476
+ );
477
+ normalizedEnvironment = "production";
478
+ }
479
+ this.config = __spreadProps(__spreadValues({}, config), {
480
+ environment: normalizedEnvironment,
481
+ mode: config.mode || "redirect"
482
+ });
483
+ validateEnvironmentSecurity(this.config.environment, this.getUrlConfig(), this.brandConstants.name);
484
+ enforceHTTPS(this.config.environment, this.brandConstants.name);
485
+ logSecurityEvent("SDK_INITIALIZED", {
486
+ environment: this.config.environment,
487
+ mode: this.config.mode,
488
+ origin: window.location.origin,
489
+ protocol: window.location.protocol,
490
+ hostname: window.location.hostname
491
+ }, this.brandConstants.name);
492
+ this.setupAutoCleanup();
493
+ }
494
+ /**
495
+ * Initiate verification with race condition protection
496
+ */
497
+ async verify(options = {}) {
498
+ var _a, _b, _c, _d, _e, _f;
499
+ const isPublicKey = this.isPublicKey();
500
+ let sessionId;
501
+ if (isPublicKey) {
502
+ sessionId = await this.createInternalSession(options);
503
+ } else {
504
+ throw new Error(
505
+ "Private API keys (sk_) should use the direct API, not the SDK. The SDK is designed for browser-based public key usage only."
506
+ );
507
+ }
508
+ if (!sessionId) {
509
+ throw new Error("Failed to obtain sessionId from server");
510
+ }
511
+ if (this.isVerificationInProgress) {
512
+ const error = new Error(
513
+ `Verification already in progress for session ${(_a = this.currentSessionId) == null ? void 0 : _a.substring(0, 8)}...`
514
+ );
515
+ logSecurityEvent("RACE_CONDITION_PREVENTED", {
516
+ currentSession: ((_b = this.currentSessionId) == null ? void 0 : _b.substring(0, 8)) + "...",
517
+ attemptedSession: "new-session-attempt",
518
+ origin: window.location.origin
519
+ }, this.brandConstants.name);
520
+ (_d = (_c = this.config).onError) == null ? void 0 : _d.call(_c, error);
521
+ throw error;
522
+ }
523
+ this.isVerificationInProgress = true;
524
+ this.currentSessionId = sessionId;
525
+ try {
526
+ const rateLimitKey = `${this.config.apiKey}:${window.location.origin}`;
527
+ if (!verificationRateLimit.isAllowed(rateLimitKey, this.brandConstants.name)) {
528
+ const error = new Error(
529
+ "Too many verification attempts. Please wait before trying again."
530
+ );
531
+ logSecurityEvent("RATE_LIMIT_EXCEEDED", {
532
+ apiKey: this.config.apiKey.substring(0, 8) + "...",
533
+ origin: window.location.origin,
534
+ sessionId: sessionId ? sessionId.substring(0, 8) + "..." : "undefined"
535
+ }, this.brandConstants.name);
536
+ (_f = (_e = this.config).onError) == null ? void 0 : _f.call(_e, error);
537
+ throw error;
538
+ }
539
+ const verificationUrl = await this.buildVerificationUrl(
540
+ options,
541
+ sessionId
542
+ );
543
+ logSecurityEvent("VERIFICATION_INITIATED", {
544
+ environment: this.config.environment,
545
+ mode: this.config.mode,
546
+ sessionId: sessionId ? sessionId.substring(0, 8) + "..." : "undefined",
547
+ origin: window.location.origin
548
+ }, this.brandConstants.name);
549
+ if (this.config.mode === "new-tab") {
550
+ this.openNewTab(verificationUrl, sessionId);
551
+ } else {
552
+ this.unlockVerification();
553
+ this.redirect(verificationUrl);
554
+ }
555
+ } catch (error) {
556
+ this.unlockVerification();
557
+ throw error;
558
+ }
559
+ }
560
+ /**
561
+ * Build verification URL with HMAC-signed state
562
+ */
563
+ async buildVerificationUrl(options, sessionId) {
564
+ const baseUrl = this.config.verifyUrl || getEnvironmentUrl(this.config.environment, this.getUrlConfig());
565
+ const hasExplicitChallengeAge = options.challengeAge !== void 0;
566
+ const hasExplicitVerificationMode = options.verificationMode !== void 0;
567
+ const hasOverrides = hasExplicitChallengeAge || hasExplicitVerificationMode;
568
+ const state = await generateState(
569
+ {
570
+ merchantId: this.config.apiKey,
571
+ sessionId,
572
+ returnUrl: this.config.returnUrl,
573
+ cancelUrl: this.config.cancelUrl,
574
+ challengeAge: options.challengeAge || this.config.defaultChallengeAge,
575
+ verificationMode: options.verificationMode || this.config.defaultVerificationMode,
576
+ hasOverrides,
577
+ // Flag to indicate explicit overrides
578
+ externalUserId: options.externalUserId,
579
+ timestamp: Date.now(),
580
+ // Additional config for self-contained verify-ui
581
+ apiUrl: this.getPortalApiUrl(),
582
+ engineUrl: this.getEngineUrl(),
583
+ wsUrl: this.getWebSocketUrl(),
584
+ environment: this.config.environment,
585
+ features: {
586
+ testMode: false,
587
+ warmupPeriodMs: 500,
588
+ qualityThreshold: 0.6
589
+ },
590
+ // Include handoffToken if available (for QR code desktop flow)
591
+ handoffToken: this.temporaryHandoffToken || void 0,
592
+ // Include sessionToken and verifyUrl to make UI auth deterministic
593
+ sessionToken: this.lastSessionToken || void 0,
594
+ verifyUrl: this.lastVerifyUrl || void 0
595
+ },
596
+ this.config.environment,
597
+ this.getHmacSecret(),
598
+ this.brandConstants.name
599
+ );
600
+ if (this.lastVerifyUrl) {
601
+ try {
602
+ const url = new URL(this.lastVerifyUrl);
603
+ url.searchParams.set("state", state);
604
+ url.searchParams.set("mode", this.config.mode);
605
+ if (options.skipIntro) {
606
+ url.searchParams.set("skip_intro", "true");
607
+ }
608
+ if (options.autoReturn) {
609
+ url.searchParams.set("auto_return", "true");
610
+ }
611
+ return url.toString();
612
+ } catch (e) {
613
+ }
614
+ }
615
+ const params = new URLSearchParams({ state, sessionId, mode: this.config.mode });
616
+ if (options.skipIntro) {
617
+ params.set("skip_intro", "true");
618
+ }
619
+ if (options.autoReturn) {
620
+ params.set("auto_return", "true");
621
+ }
622
+ return `${baseUrl}/?${params.toString()}`;
623
+ }
624
+ /**
625
+ * Redirect in same tab
626
+ */
627
+ redirect(url) {
628
+ window.location.href = url;
629
+ }
630
+ /**
631
+ * Open in new tab with PostMessage communication and proper cleanup
632
+ */
633
+ openNewTab(url, sessionId) {
634
+ var _a, _b;
635
+ this.cleanup();
636
+ if (this.popupMonitorInterval) {
637
+ clearInterval(this.popupMonitorInterval);
638
+ this.popupMonitorInterval = null;
639
+ }
640
+ this.popupWindow = window.open(
641
+ url,
642
+ this.brandConstants.popupName,
643
+ "width=600,height=700"
644
+ );
645
+ if (!this.popupWindow) {
646
+ (_b = (_a = this.config).onError) == null ? void 0 : _b.call(
647
+ _a,
648
+ new Error(
649
+ "Failed to open verification window. Please check popup blocker settings."
650
+ )
651
+ );
652
+ return;
653
+ }
654
+ const trustedOrigins = this.getTrustedOrigins();
655
+ const expectedMessageType = this.brandConstants.messageType;
656
+ const legacyMessageType = this.brandConstants.legacyMessageType;
657
+ this.messageListener = (event) => {
658
+ var _a2, _b2, _c, _d, _e, _f;
659
+ if (!validatePostMessageOrigin(event, trustedOrigins, [], this.brandConstants.name)) {
660
+ logSecurityEvent("POSTMESSAGE_ORIGIN_BLOCKED", {
661
+ origin: event.origin,
662
+ environment: this.config.environment,
663
+ expectedOrigins: `${this.brandConstants.name} trusted origins for ${this.config.environment}`,
664
+ messageType: (_a2 = event.data) == null ? void 0 : _a2.type
665
+ }, this.brandConstants.name);
666
+ return;
667
+ }
668
+ const messageValidation = validateVerificationMessage(
669
+ event,
670
+ sessionId,
671
+ expectedMessageType,
672
+ legacyMessageType
673
+ );
674
+ if (!messageValidation.isValid) {
675
+ logSecurityEvent("POSTMESSAGE_VALIDATION_FAILED", {
676
+ error: messageValidation.error,
677
+ origin: event.origin,
678
+ sessionId: sessionId.substring(0, 8) + "...",
679
+ messageType: (_b2 = event.data) == null ? void 0 : _b2.type
680
+ }, this.brandConstants.name);
681
+ return;
682
+ }
683
+ const result = {
684
+ sessionId: event.data.sessionId,
685
+ status: event.data.status
686
+ };
687
+ logSecurityEvent("VERIFICATION_COMPLETED", {
688
+ status: result.status,
689
+ sessionId: sessionId.substring(0, 8) + "...",
690
+ origin: event.origin
691
+ }, this.brandConstants.name);
692
+ this.cleanup();
693
+ this.unlockVerification();
694
+ if (this.popupMonitorInterval) {
695
+ clearInterval(this.popupMonitorInterval);
696
+ this.popupMonitorInterval = null;
697
+ }
698
+ if (result.status === "verified") {
699
+ (_d = (_c = this.config).onComplete) == null ? void 0 : _d.call(_c, result);
700
+ } else {
701
+ (_f = (_e = this.config).onError) == null ? void 0 : _f.call(
702
+ _e,
703
+ new Error(`Verification failed: ${result.status}`)
704
+ );
705
+ }
706
+ };
707
+ window.addEventListener("message", this.messageListener);
708
+ this.popupMonitorInterval = setInterval(() => {
709
+ var _a2, _b2;
710
+ if (this.popupWindow && this.popupWindow.closed) {
711
+ logSecurityEvent("POPUP_CLOSED_BY_USER", {
712
+ sessionId: sessionId.substring(0, 8) + "...",
713
+ environment: this.config.environment
714
+ }, this.brandConstants.name);
715
+ this.cleanup();
716
+ this.unlockVerification();
717
+ (_b2 = (_a2 = this.config).onCancel) == null ? void 0 : _b2.call(_a2);
718
+ }
719
+ }, 500);
720
+ }
721
+ /**
722
+ * Set up automatic cleanup on page unload to prevent memory leaks
723
+ */
724
+ setupAutoCleanup() {
725
+ this.unloadListener = () => {
726
+ logSecurityEvent("SDK_AUTO_CLEANUP", {
727
+ environment: this.config.environment,
728
+ trigger: "page_unload"
729
+ }, this.brandConstants.name);
730
+ this.cleanup();
731
+ this.unlockVerification();
732
+ };
733
+ window.addEventListener("beforeunload", this.unloadListener);
734
+ window.addEventListener("pagehide", this.unloadListener);
735
+ if (window.history && window.history.pushState) {
736
+ const originalPushState = window.history.pushState;
737
+ window.history.pushState = (...args) => {
738
+ this.cleanup();
739
+ this.unlockVerification();
740
+ return originalPushState.apply(window.history, args);
741
+ };
742
+ }
743
+ }
744
+ /**
745
+ * Auto-detect environment based on current URL
746
+ */
747
+ detectEnvironment() {
748
+ const hostname = window.location.hostname;
749
+ if (hostname.includes("staging") || hostname.includes("stage")) {
750
+ return "staging";
751
+ }
752
+ return "production";
753
+ }
754
+ /**
755
+ * Get the current environment
756
+ */
757
+ getEnvironment() {
758
+ return this.config.environment;
759
+ }
760
+ /**
761
+ * Unlock verification process to allow new verifications
762
+ */
763
+ unlockVerification() {
764
+ this.isVerificationInProgress = false;
765
+ this.currentSessionId = null;
766
+ logSecurityEvent("VERIFICATION_UNLOCKED", {
767
+ environment: this.config.environment,
768
+ origin: window.location.origin
769
+ }, this.brandConstants.name);
770
+ }
771
+ /**
772
+ * Internal cleanup method to prevent memory leaks
773
+ */
774
+ cleanup() {
775
+ if (this.popupWindow && !this.popupWindow.closed) {
776
+ this.popupWindow.close();
777
+ }
778
+ this.popupWindow = null;
779
+ if (this.messageListener) {
780
+ window.removeEventListener("message", this.messageListener);
781
+ this.messageListener = null;
782
+ }
783
+ if (this.popupMonitorInterval) {
784
+ clearInterval(this.popupMonitorInterval);
785
+ this.popupMonitorInterval = null;
786
+ }
787
+ }
788
+ /**
789
+ * Remove auto-cleanup listeners
790
+ */
791
+ removeAutoCleanupListeners() {
792
+ if (this.unloadListener) {
793
+ window.removeEventListener("beforeunload", this.unloadListener);
794
+ window.removeEventListener("pagehide", this.unloadListener);
795
+ this.unloadListener = null;
796
+ }
797
+ }
798
+ /**
799
+ * Public cleanup method for manual resource management
800
+ */
801
+ destroy() {
802
+ logSecurityEvent("SDK_DESTROYED", {
803
+ environment: this.config.environment,
804
+ origin: window.location.origin
805
+ }, this.brandConstants.name);
806
+ this.cleanup();
807
+ this.unlockVerification();
808
+ this.removeAutoCleanupListeners();
809
+ }
810
+ /**
811
+ * Get Portal API URL based on environment and brand
812
+ */
813
+ getPortalApiUrl() {
814
+ if (this.config.apiUrl) {
815
+ return this.config.apiUrl;
816
+ }
817
+ return this.getUrlConfig().apiUrl;
818
+ }
819
+ /**
820
+ * Get Engine URL based on environment and brand
821
+ */
822
+ getEngineUrl() {
823
+ return this.getUrlConfig().engineUrl;
824
+ }
825
+ /**
826
+ * Get WebSocket URL based on environment and brand
827
+ */
828
+ getWebSocketUrl() {
829
+ return this.getUrlConfig().wsUrl;
830
+ }
831
+ /**
832
+ * Detect if this is a public key (pk_ prefix) vs private key (sk_ prefix)
833
+ */
834
+ isPublicKey() {
835
+ return this.config.apiKey.startsWith("pk_");
836
+ }
837
+ /**
838
+ * Create session internally for public keys
839
+ */
840
+ async createInternalSession(options) {
841
+ var _a, _b;
842
+ try {
843
+ const portalApiUrl = this.getPortalApiUrl();
844
+ const response = await fetch(`${portalApiUrl}/api/v1/sessions/create`, {
845
+ method: "POST",
846
+ headers: {
847
+ "Content-Type": "application/json",
848
+ Authorization: `Bearer ${this.config.apiKey}`
849
+ },
850
+ body: JSON.stringify({
851
+ merchantId: this.config.apiKey,
852
+ returnUrl: this.config.returnUrl,
853
+ cancelUrl: this.config.cancelUrl,
854
+ challengeAge: options.challengeAge,
855
+ verificationMode: options.verificationMode,
856
+ merchantName: document.title || window.location.hostname,
857
+ externalUserId: options.externalUserId
858
+ })
859
+ });
860
+ if (!response.ok) {
861
+ const errorData = await response.json().catch(() => ({}));
862
+ throw new Error(
863
+ `Failed to create session: ${response.status} ${response.statusText}. ${errorData.message || ""}`
864
+ );
865
+ }
866
+ const sessionData = await response.json();
867
+ const sessionId = sessionData.sessionId;
868
+ if (!sessionId) {
869
+ throw new Error("Server did not return a sessionId");
870
+ }
871
+ if (sessionData.verifyUrl) {
872
+ this.lastVerifyUrl = sessionData.verifyUrl;
873
+ }
874
+ if (sessionData.sessionToken) {
875
+ this.lastSessionToken = sessionData.sessionToken;
876
+ }
877
+ if (sessionData.handoffToken) {
878
+ this.temporaryHandoffToken = sessionData.handoffToken;
879
+ }
880
+ logSecurityEvent("INTERNAL_SESSION_CREATED", {
881
+ sessionId: sessionId.substring(0, 8) + "...",
882
+ environment: this.config.environment,
883
+ apiKeyType: "public"
884
+ }, this.brandConstants.name);
885
+ return sessionId;
886
+ } catch (error) {
887
+ const errorMessage = error instanceof Error ? error.message : String(error);
888
+ logSecurityEvent("INTERNAL_SESSION_FAILED", {
889
+ error: errorMessage,
890
+ environment: this.config.environment,
891
+ apiKeyType: "public"
892
+ }, this.brandConstants.name);
893
+ (_b = (_a = this.config).onError) == null ? void 0 : _b.call(_a, error);
894
+ throw new Error(`Failed to create verification session: ${errorMessage}`);
895
+ }
896
+ }
897
+ getUrlConfig() {
898
+ return this.brandUrls[this.config.environment] || this.brandUrls.production;
899
+ }
900
+ getTrustedOrigins() {
901
+ return this.getUrlConfig().trustedOrigins;
902
+ }
903
+ getHmacSecret() {
904
+ return this.config.environment === "staging" ? this.brandConstants.hmacSecretStaging : this.brandConstants.hmacSecretProd;
905
+ }
906
+ };
907
+
908
+ // src-redirect/brands/privateav/urls.ts
909
+ var BRAND_URLS = {
910
+ production: {
911
+ apiUrl: "https://api.privateav.com",
912
+ verifyUiUrl: "https://verify.privateav.com",
913
+ engineUrl: "https://engine.privateav.com",
914
+ wsUrl: "wss://engine.privateav.com/api/websocket/stream",
915
+ trustedOrigins: [
916
+ "https://verify.privateav.com",
917
+ "https://portal.privateav.com",
918
+ "https://api.privateav.com"
919
+ ]
920
+ },
921
+ staging: {
922
+ apiUrl: "https://api.staging.privateav.com",
923
+ verifyUiUrl: "https://verify.staging.privateav.com",
924
+ engineUrl: "https://engine.staging.privateav.com",
925
+ wsUrl: "wss://engine.staging.privateav.com/api/websocket/stream",
926
+ trustedOrigins: [
927
+ "https://verify.staging.privateav.com",
928
+ "https://portal.staging.privateav.com",
929
+ "https://api.staging.privateav.com"
930
+ ]
931
+ }
932
+ };
933
+ var BRAND_CONSTANTS = {
934
+ name: "PrivateAV",
935
+ hmacSecretProd: "privateav-prod-hmac-2025",
936
+ hmacSecretStaging: "privateav-stage-hmac-2025",
937
+ messageType: "privateav:verification:complete",
938
+ legacyMessageType: "privateav-verification",
939
+ popupName: "privateav-verify",
940
+ docsUrl: "https://docs.privateav.com"
941
+ };
942
+
943
+ // src-redirect/brands/privateav/index.ts
944
+ var PrivateAV = class extends VerificationSDK {
945
+ constructor(config) {
946
+ super(config, BRAND_URLS, BRAND_CONSTANTS);
947
+ }
948
+ };
949
+ var VERSION = "3.4.2";
950
+ PrivateAV.VERSION = VERSION;
951
+ if (typeof window !== "undefined") {
952
+ setupPolyfills();
953
+ checkBrowserCompatibility(`${BRAND_CONSTANTS.name} SDK`);
954
+ }
955
+ var index_default = PrivateAV;
956
+ export {
957
+ PrivateAV,
958
+ VERSION,
959
+ index_default as default
960
+ };