@privateav/sdk 3.4.2 → 3.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # PrivateAV SDK v3.4
1
+ # PrivateAV SDK v3.4.9
2
2
 
3
3
  A lightweight SDK for integrating PrivateAV age verification into your website or application.
4
4
 
@@ -12,6 +12,11 @@ A lightweight SDK for integrating PrivateAV age verification into your website o
12
12
  - **Secure**: HMAC-signed state parameters, automatic session management
13
13
  - **Compliant**: Enforces minimum age of 25
14
14
 
15
+ ## Changelog
16
+
17
+ ### 3.4.9
18
+ - Prevents `onCancel` from firing after a successful new-tab verification when the popup closes.
19
+
15
20
  ## Installation
16
21
 
17
22
  ```bash
@@ -62,12 +67,13 @@ That's it! The SDK handles session creation automatically.
62
67
  |--------|------|----------|-------------|
63
68
  | apiKey | string | Yes | Your public API key (`pk_...`) |
64
69
  | returnUrl | string | Yes | URL to redirect after verification |
70
+ | cancelUrl | string | No | URL to redirect to if user closes the verification window (new-tab mode) |
65
71
  | environment | string | No | `'production'` or `'staging'` (auto-detected) |
66
72
  | mode | string | No | `'redirect'` (default) or `'new-tab'` |
67
73
  | defaultChallengeAge | number | No | Default minimum age (25 or higher) |
68
74
  | defaultVerificationMode | string | No | `'L1'` or `'L2'` |
69
75
  | onComplete | function | No | Callback for new-tab mode |
70
- | onCancel | function | No | Called when user closes popup (new-tab mode) |
76
+ | onCancel | function | No | Called when user closes popup (new-tab mode). Return `false` to suppress automatic `cancelUrl` redirect. |
71
77
  | onError | function | No | Error handler |
72
78
 
73
79
  ## Verification Options
@@ -114,6 +120,7 @@ Verification opens in a popup window:
114
120
  const sp = new PrivateAV({
115
121
  apiKey: 'pk_...',
116
122
  returnUrl: '/age-verified',
123
+ cancelUrl: '/age-cancelled',
117
124
  mode: 'new-tab',
118
125
  onComplete: (result) => {
119
126
  console.log('Verification complete:', result.sessionId, result.status);
@@ -121,6 +128,8 @@ const sp = new PrivateAV({
121
128
  },
122
129
  onCancel: () => {
123
130
  console.log('User closed the verification window');
131
+ // Return false if you want to handle navigation manually
132
+ // return false;
124
133
  },
125
134
  onError: (error) => {
126
135
  console.error('Verification error:', error.message);
@@ -130,6 +139,10 @@ const sp = new PrivateAV({
130
139
  await sp.verify();
131
140
  ```
132
141
 
142
+ If `cancelUrl` is provided, the SDK will redirect the opener to `cancelUrl` with
143
+ `status=cancelled` and `sessionId` when the user closes the verification window.
144
+ Return `false` from `onCancel` to suppress the automatic redirect.
145
+
133
146
  ## Server-Side Validation (Required!)
134
147
 
135
148
  After verification completes, **always validate the result on your server** before granting access:
@@ -141,10 +154,10 @@ app.get('/age-verified', async (req, res) => {
141
154
 
142
155
  // Validate with your SECRET key (sk_...)
143
156
  const response = await fetch(
144
- `https://api.privateav.com/api/v1/sessions/${sessionId}`,
157
+ `https://api.safepassage.live/api/v1/sessions/${sessionId}`,
145
158
  {
146
159
  headers: {
147
- 'Authorization': `Bearer ${process.env.SAFEPASSAGE_SECRET_KEY}`
160
+ 'Authorization': `Bearer ${process.env.PRIVATEAV_SECRET_KEY}`
148
161
  }
149
162
  }
150
163
  );
@@ -214,7 +227,7 @@ function AgeGate() {
214
227
  const [verifying, setVerifying] = useState(false);
215
228
 
216
229
  const sp = new PrivateAV({
217
- apiKey: process.env.NEXT_PUBLIC_SAFEPASSAGE_KEY,
230
+ apiKey: process.env.NEXT_PUBLIC_PRIVATEAV_KEY,
218
231
  returnUrl: window.location.origin + '/verified'
219
232
  });
220
233
 
@@ -244,7 +257,7 @@ Full TypeScript support included:
244
257
  import { PrivateAV, PrivateAVConfig, VerificationResult } from '@privateav/sdk';
245
258
 
246
259
  const config: PrivateAVConfig = {
247
- apiKey: process.env.NEXT_PUBLIC_SAFEPASSAGE_KEY!,
260
+ apiKey: process.env.NEXT_PUBLIC_PRIVATEAV_KEY!,
248
261
  returnUrl: '/verified',
249
262
  mode: 'new-tab',
250
263
  onComplete: (result: VerificationResult) => {
@@ -297,7 +310,7 @@ PrivateAV uses two types of API keys:
297
310
  | Public Key | `pk_` | Client-side SDK (this package) |
298
311
  | Secret Key | `sk_` | Server-side validation only |
299
312
 
300
- > **Important**: This SDK only works with public keys (`pk_`). For server-side integrations using secret keys, use the [Direct API](https://docs.privateav.com/api) instead.
313
+ > **Important**: This SDK only works with public keys (`pk_`). For server-side integrations using secret keys, use the [Direct API](https://docs.safepassage.live/api) instead.
301
314
 
302
315
  ## Browser Support
303
316
 
@@ -339,6 +352,6 @@ The SDK now creates sessions automatically via the API when using public keys.
339
352
 
340
353
  ## Support
341
354
 
342
- - [Documentation](https://docs.privateav.com)
343
- - [API Reference](https://docs.privateav.com/api)
344
- - [Dashboard](https://portal.privateav.com)
355
+ - [Documentation](https://docs.safepassage.live)
356
+ - [API Reference](https://docs.safepassage.live/api)
357
+ - [Dashboard](https://portal.safepassage.live)
@@ -3,11 +3,10 @@
3
3
  */
4
4
  import { VerificationSDK } from '../../core/VerificationSDK';
5
5
  import type { SDKConfig, VerificationOptions, VerificationResult, SessionValidationResponse, SessionCreationResponse, CreateSessionRequest } from '../../types/base';
6
- export interface PrivateAVConfig extends SDKConfig {
7
- }
6
+ export type PrivateAVConfig = SDKConfig;
8
7
  export declare class PrivateAV extends VerificationSDK {
9
8
  constructor(config: PrivateAVConfig);
10
9
  }
11
- export declare const VERSION = "3.4.2";
10
+ export declare const VERSION = "3.4.9";
12
11
  export type { SDKConfig, VerificationOptions, VerificationResult, SessionValidationResponse, SessionCreationResponse, CreateSessionRequest, };
13
12
  export default PrivateAV;
@@ -43,9 +43,13 @@ export declare class VerificationSDK {
43
43
  private unloadListener;
44
44
  private isVerificationInProgress;
45
45
  private currentSessionId;
46
+ private hasReceivedResult;
46
47
  private lastVerifyUrl;
47
48
  private lastSessionToken;
49
+ private lastExternalUserId;
50
+ private lastSandboxMode;
48
51
  private temporaryHandoffToken;
52
+ private static readonly LOCAL_HOSTNAMES;
49
53
  /**
50
54
  * Initialize SDK
51
55
  *
@@ -90,6 +94,8 @@ export declare class VerificationSDK {
90
94
  * Internal cleanup method to prevent memory leaks
91
95
  */
92
96
  private cleanup;
97
+ private handleCancellation;
98
+ private redirectToCancelUrl;
93
99
  /**
94
100
  * Remove auto-cleanup listeners
95
101
  */
@@ -118,7 +124,12 @@ export declare class VerificationSDK {
118
124
  * Create session internally for public keys
119
125
  */
120
126
  private createInternalSession;
127
+ private isBillingBlockError;
128
+ private openBillingBlockPage;
121
129
  private getUrlConfig;
122
130
  private getTrustedOrigins;
131
+ private getAllowedCustomOrigins;
132
+ private getLocalOrigin;
133
+ private applyLocalVerifyOverride;
123
134
  private getHmacSecret;
124
135
  }
package/index.js CHANGED
@@ -82,7 +82,7 @@ function validateVerificationMessage(event, expectedSessionId, expectedMessageTy
82
82
  if (!data.sessionId || data.sessionId !== expectedSessionId) {
83
83
  return { isValid: false, error: "Session ID mismatch" };
84
84
  }
85
- if (!data.status || !["verified", "failed"].includes(data.status)) {
85
+ if (!data.status || !["verified", "failed", "cancelled"].includes(data.status)) {
86
86
  return { isValid: false, error: "Invalid status value" };
87
87
  }
88
88
  return { isValid: true };
@@ -95,7 +95,7 @@ function enforceHTTPS(environment, logLabel = "SDK") {
95
95
  );
96
96
  }
97
97
  }
98
- function validateReturnUrl(url, environment, logLabel = "SDK") {
98
+ function validateReturnUrl(url, environment, _logLabel = "SDK") {
99
99
  try {
100
100
  const parsed = new URL(url);
101
101
  if (parsed.protocol !== "https:") {
@@ -249,6 +249,8 @@ async function parseSignedState(signedState, hmacSecret, maxAge = STATE_EXPIRY_M
249
249
  }
250
250
  }
251
251
  const _a = data, { timestamp, nonce } = _a, payload = __objRest(_a, ["timestamp", "nonce"]);
252
+ void timestamp;
253
+ void nonce;
252
254
  return payload;
253
255
  } catch (error) {
254
256
  console.warn(`${logLabel}: Failed to parse signed state`, error);
@@ -324,6 +326,9 @@ function validateConfig(config, context) {
324
326
  if (config.mode && !["redirect", "new-tab"].includes(config.mode)) {
325
327
  throw new Error("mode must be redirect or new-tab");
326
328
  }
329
+ if (config.newTabTarget && !["popup", "tab"].includes(config.newTabTarget)) {
330
+ throw new Error("newTabTarget must be popup or tab");
331
+ }
327
332
  }
328
333
  function detectEnvironment() {
329
334
  if (typeof window === "undefined") {
@@ -335,7 +340,7 @@ function detectEnvironment() {
335
340
  }
336
341
  return "production";
337
342
  }
338
- async function generateState(payload, environment, hmacSecret, logLabel = "SDK") {
343
+ async function generateState(payload, environment, hmacSecret, _logLabel = "SDK") {
339
344
  const { createSignedState: createSignedState2 } = await Promise.resolve().then(() => (init_crypto(), crypto_exports));
340
345
  return createSignedState2(payload, hmacSecret);
341
346
  }
@@ -442,7 +447,7 @@ function validateEnvironmentSecurity(environment, urls, logLabel = "SDK") {
442
447
 
443
448
  // src-redirect/core/VerificationSDK.ts
444
449
  init_security();
445
- var VerificationSDK = class {
450
+ var _VerificationSDK = class _VerificationSDK {
446
451
  /**
447
452
  * Initialize SDK
448
453
  *
@@ -457,10 +462,15 @@ var VerificationSDK = class {
457
462
  this.unloadListener = null;
458
463
  this.isVerificationInProgress = false;
459
464
  this.currentSessionId = null;
465
+ this.hasReceivedResult = false;
460
466
  // Server-provided verify URL (includes sessionToken)
461
467
  this.lastVerifyUrl = null;
462
468
  // Server-provided session token (WS auth)
463
469
  this.lastSessionToken = null;
470
+ // External user ID provided during verify() for cancellation redirects
471
+ this.lastExternalUserId = null;
472
+ // Sandbox mode flag from session creation (for UI labeling)
473
+ this.lastSandboxMode = null;
464
474
  // Temporary storage for QR handoff token to include in state
465
475
  this.temporaryHandoffToken = null;
466
476
  this.brandUrls = brandUrls;
@@ -478,7 +488,8 @@ var VerificationSDK = class {
478
488
  }
479
489
  this.config = __spreadProps(__spreadValues({}, config), {
480
490
  environment: normalizedEnvironment,
481
- mode: config.mode || "redirect"
491
+ mode: config.mode || "redirect",
492
+ newTabTarget: config.newTabTarget || "popup"
482
493
  });
483
494
  validateEnvironmentSecurity(this.config.environment, this.getUrlConfig(), this.brandConstants.name);
484
495
  enforceHTTPS(this.config.environment, this.brandConstants.name);
@@ -522,6 +533,7 @@ var VerificationSDK = class {
522
533
  }
523
534
  this.isVerificationInProgress = true;
524
535
  this.currentSessionId = sessionId;
536
+ this.lastExternalUserId = options.externalUserId || null;
525
537
  try {
526
538
  const rateLimitKey = `${this.config.apiKey}:${window.location.origin}`;
527
539
  if (!verificationRateLimit.isAllowed(rateLimitKey, this.brandConstants.name)) {
@@ -561,6 +573,7 @@ var VerificationSDK = class {
561
573
  * Build verification URL with HMAC-signed state
562
574
  */
563
575
  async buildVerificationUrl(options, sessionId) {
576
+ var _a;
564
577
  const baseUrl = this.config.verifyUrl || getEnvironmentUrl(this.config.environment, this.getUrlConfig());
565
578
  const hasExplicitChallengeAge = options.challengeAge !== void 0;
566
579
  const hasExplicitVerificationMode = options.verificationMode !== void 0;
@@ -585,7 +598,8 @@ var VerificationSDK = class {
585
598
  features: {
586
599
  testMode: false,
587
600
  warmupPeriodMs: 500,
588
- qualityThreshold: 0.6
601
+ qualityThreshold: 0.6,
602
+ sandboxMode: (_a = this.lastSandboxMode) != null ? _a : false
589
603
  },
590
604
  // Include handoffToken if available (for QR code desktop flow)
591
605
  handoffToken: this.temporaryHandoffToken || void 0,
@@ -599,7 +613,8 @@ var VerificationSDK = class {
599
613
  );
600
614
  if (this.lastVerifyUrl) {
601
615
  try {
602
- const url = new URL(this.lastVerifyUrl);
616
+ const resolvedVerifyUrl = this.applyLocalVerifyOverride(this.lastVerifyUrl);
617
+ const url = new URL(resolvedVerifyUrl);
603
618
  url.searchParams.set("state", state);
604
619
  url.searchParams.set("mode", this.config.mode);
605
620
  if (options.skipIntro) {
@@ -633,15 +648,21 @@ var VerificationSDK = class {
633
648
  openNewTab(url, sessionId) {
634
649
  var _a, _b;
635
650
  this.cleanup();
651
+ this.hasReceivedResult = false;
636
652
  if (this.popupMonitorInterval) {
637
653
  clearInterval(this.popupMonitorInterval);
638
654
  this.popupMonitorInterval = null;
639
655
  }
640
- this.popupWindow = window.open(
641
- url,
642
- this.brandConstants.popupName,
643
- "width=600,height=700"
644
- );
656
+ const target = this.config.newTabTarget || "popup";
657
+ if (target === "tab") {
658
+ this.popupWindow = window.open(url, "_blank");
659
+ } else {
660
+ this.popupWindow = window.open(
661
+ url,
662
+ this.brandConstants.popupName,
663
+ "width=600,height=700"
664
+ );
665
+ }
645
666
  if (!this.popupWindow) {
646
667
  (_b = (_a = this.config).onError) == null ? void 0 : _b.call(
647
668
  _a,
@@ -652,16 +673,22 @@ var VerificationSDK = class {
652
673
  return;
653
674
  }
654
675
  const trustedOrigins = this.getTrustedOrigins();
676
+ const allowedCustomOrigins = this.getAllowedCustomOrigins(url);
655
677
  const expectedMessageType = this.brandConstants.messageType;
656
678
  const legacyMessageType = this.brandConstants.legacyMessageType;
657
679
  this.messageListener = (event) => {
658
- var _a2, _b2, _c, _d, _e, _f;
659
- if (!validatePostMessageOrigin(event, trustedOrigins, [], this.brandConstants.name)) {
680
+ var _a2, _b2, _c, _d, _e, _f, _g;
681
+ const messageType = (_a2 = event.data) == null ? void 0 : _a2.type;
682
+ const allowedTypes = legacyMessageType ? [expectedMessageType, legacyMessageType] : [expectedMessageType];
683
+ if (!messageType || typeof messageType !== "string" || !allowedTypes.includes(messageType)) {
684
+ return;
685
+ }
686
+ if (!validatePostMessageOrigin(event, trustedOrigins, allowedCustomOrigins, this.brandConstants.name)) {
660
687
  logSecurityEvent("POSTMESSAGE_ORIGIN_BLOCKED", {
661
688
  origin: event.origin,
662
689
  environment: this.config.environment,
663
690
  expectedOrigins: `${this.brandConstants.name} trusted origins for ${this.config.environment}`,
664
- messageType: (_a2 = event.data) == null ? void 0 : _a2.type
691
+ messageType: (_b2 = event.data) == null ? void 0 : _b2.type
665
692
  }, this.brandConstants.name);
666
693
  return;
667
694
  }
@@ -676,45 +703,52 @@ var VerificationSDK = class {
676
703
  error: messageValidation.error,
677
704
  origin: event.origin,
678
705
  sessionId: sessionId.substring(0, 8) + "...",
679
- messageType: (_b2 = event.data) == null ? void 0 : _b2.type
706
+ messageType: (_c = event.data) == null ? void 0 : _c.type
680
707
  }, this.brandConstants.name);
681
708
  return;
682
709
  }
710
+ const status = event.data.status;
711
+ if (status === "cancelled") {
712
+ this.handleCancellation(sessionId, "postmessage");
713
+ return;
714
+ }
683
715
  const result = {
684
716
  sessionId: event.data.sessionId,
685
- status: event.data.status
717
+ status,
718
+ timestamp: event.data.timestamp,
719
+ externalUserId: event.data.externalUserId
686
720
  };
721
+ this.hasReceivedResult = true;
687
722
  logSecurityEvent("VERIFICATION_COMPLETED", {
688
723
  status: result.status,
689
724
  sessionId: sessionId.substring(0, 8) + "...",
690
725
  origin: event.origin
691
726
  }, this.brandConstants.name);
692
- this.cleanup();
727
+ this.cleanup({ closePopup: false });
693
728
  this.unlockVerification();
694
729
  if (this.popupMonitorInterval) {
695
730
  clearInterval(this.popupMonitorInterval);
696
731
  this.popupMonitorInterval = null;
697
732
  }
698
733
  if (result.status === "verified") {
699
- (_d = (_c = this.config).onComplete) == null ? void 0 : _d.call(_c, result);
734
+ (_e = (_d = this.config).onComplete) == null ? void 0 : _e.call(_d, result);
700
735
  } else {
701
- (_f = (_e = this.config).onError) == null ? void 0 : _f.call(
702
- _e,
736
+ (_g = (_f = this.config).onError) == null ? void 0 : _g.call(
737
+ _f,
703
738
  new Error(`Verification failed: ${result.status}`)
704
739
  );
705
740
  }
706
741
  };
707
742
  window.addEventListener("message", this.messageListener);
708
743
  this.popupMonitorInterval = setInterval(() => {
709
- var _a2, _b2;
710
744
  if (this.popupWindow && this.popupWindow.closed) {
711
745
  logSecurityEvent("POPUP_CLOSED_BY_USER", {
712
746
  sessionId: sessionId.substring(0, 8) + "...",
713
747
  environment: this.config.environment
714
748
  }, this.brandConstants.name);
715
- this.cleanup();
716
- this.unlockVerification();
717
- (_b2 = (_a2 = this.config).onCancel) == null ? void 0 : _b2.call(_a2);
749
+ if (!this.hasReceivedResult) {
750
+ this.handleCancellation(sessionId, "popup-closed");
751
+ }
718
752
  }
719
753
  }, 500);
720
754
  }
@@ -771,11 +805,16 @@ var VerificationSDK = class {
771
805
  /**
772
806
  * Internal cleanup method to prevent memory leaks
773
807
  */
774
- cleanup() {
775
- if (this.popupWindow && !this.popupWindow.closed) {
776
- this.popupWindow.close();
808
+ cleanup(options = {}) {
809
+ const shouldClosePopup = options.closePopup !== false;
810
+ if (this.popupWindow) {
811
+ if (shouldClosePopup && !this.popupWindow.closed) {
812
+ this.popupWindow.close();
813
+ }
814
+ if (shouldClosePopup || this.popupWindow.closed) {
815
+ this.popupWindow = null;
816
+ }
777
817
  }
778
- this.popupWindow = null;
779
818
  if (this.messageListener) {
780
819
  window.removeEventListener("message", this.messageListener);
781
820
  this.messageListener = null;
@@ -785,6 +824,58 @@ var VerificationSDK = class {
785
824
  this.popupMonitorInterval = null;
786
825
  }
787
826
  }
827
+ handleCancellation(sessionId, source) {
828
+ if (this.hasReceivedResult) {
829
+ return;
830
+ }
831
+ this.hasReceivedResult = true;
832
+ logSecurityEvent("VERIFICATION_CANCELLED", {
833
+ source,
834
+ sessionId: sessionId.substring(0, 8) + "...",
835
+ environment: this.config.environment,
836
+ origin: window.location.origin
837
+ }, this.brandConstants.name);
838
+ this.cleanup();
839
+ this.unlockVerification();
840
+ let shouldRedirect = true;
841
+ if (this.config.onCancel) {
842
+ try {
843
+ const result = this.config.onCancel();
844
+ if (result === false) {
845
+ shouldRedirect = false;
846
+ }
847
+ } catch (error) {
848
+ logSecurityEvent("CANCEL_CALLBACK_FAILED", {
849
+ error: error instanceof Error ? error.message : String(error),
850
+ sessionId: sessionId.substring(0, 8) + "..."
851
+ }, this.brandConstants.name);
852
+ }
853
+ }
854
+ if (shouldRedirect) {
855
+ this.redirectToCancelUrl(sessionId);
856
+ }
857
+ }
858
+ redirectToCancelUrl(sessionId) {
859
+ if (!this.config.cancelUrl) {
860
+ return;
861
+ }
862
+ try {
863
+ const decodedUrl = decodeURIComponent(this.config.cancelUrl);
864
+ const redirectUrl = new URL(decodedUrl);
865
+ redirectUrl.searchParams.set("sessionId", sessionId);
866
+ redirectUrl.searchParams.set("status", "cancelled");
867
+ redirectUrl.searchParams.set("timestamp", Date.now().toString());
868
+ if (this.lastExternalUserId) {
869
+ redirectUrl.searchParams.set("externalUserId", this.lastExternalUserId);
870
+ }
871
+ window.location.href = redirectUrl.toString();
872
+ } catch (error) {
873
+ logSecurityEvent("CANCEL_REDIRECT_FAILED", {
874
+ error: error instanceof Error ? error.message : String(error),
875
+ sessionId: sessionId.substring(0, 8) + "..."
876
+ }, this.brandConstants.name);
877
+ }
878
+ }
788
879
  /**
789
880
  * Remove auto-cleanup listeners
790
881
  */
@@ -859,6 +950,10 @@ var VerificationSDK = class {
859
950
  });
860
951
  if (!response.ok) {
861
952
  const errorData = await response.json().catch(() => ({}));
953
+ const errorCode = errorData == null ? void 0 : errorData.code;
954
+ if (this.isBillingBlockError(errorCode)) {
955
+ this.openBillingBlockPage(errorCode, errorData == null ? void 0 : errorData.portalUrl);
956
+ }
862
957
  throw new Error(
863
958
  `Failed to create session: ${response.status} ${response.statusText}. ${errorData.message || ""}`
864
959
  );
@@ -877,6 +972,11 @@ var VerificationSDK = class {
877
972
  if (sessionData.handoffToken) {
878
973
  this.temporaryHandoffToken = sessionData.handoffToken;
879
974
  }
975
+ if (typeof sessionData.sandboxMode === "boolean") {
976
+ this.lastSandboxMode = sessionData.sandboxMode;
977
+ } else {
978
+ this.lastSandboxMode = null;
979
+ }
880
980
  logSecurityEvent("INTERNAL_SESSION_CREATED", {
881
981
  sessionId: sessionId.substring(0, 8) + "...",
882
982
  environment: this.config.environment,
@@ -894,16 +994,99 @@ var VerificationSDK = class {
894
994
  throw new Error(`Failed to create verification session: ${errorMessage}`);
895
995
  }
896
996
  }
997
+ isBillingBlockError(code) {
998
+ return code === "SUBSCRIPTION_REQUIRED" || code === "PLAN_LIMIT_REACHED" || code === "SANDBOX_LIMIT_REACHED";
999
+ }
1000
+ openBillingBlockPage(code, portalUrl) {
1001
+ var _a, _b, _c, _d;
1002
+ try {
1003
+ const baseUrl = this.config.verifyUrl || getEnvironmentUrl(this.config.environment, this.getUrlConfig());
1004
+ const resolvedUrl = this.applyLocalVerifyOverride(baseUrl);
1005
+ const url = new URL(resolvedUrl);
1006
+ url.searchParams.set("blocked", code);
1007
+ if (portalUrl) {
1008
+ url.searchParams.set("portalUrl", portalUrl);
1009
+ }
1010
+ if (this.config.mode === "new-tab") {
1011
+ const target = this.config.newTabTarget || "popup";
1012
+ const popup = target === "tab" ? window.open(url.toString(), "_blank") : window.open(
1013
+ url.toString(),
1014
+ this.brandConstants.popupName,
1015
+ "width=600,height=700"
1016
+ );
1017
+ if (!popup) {
1018
+ (_b = (_a = this.config).onError) == null ? void 0 : _b.call(
1019
+ _a,
1020
+ new Error(
1021
+ "Failed to open billing notice window. Please check popup blocker settings."
1022
+ )
1023
+ );
1024
+ }
1025
+ return;
1026
+ }
1027
+ this.redirect(url.toString());
1028
+ } catch (error) {
1029
+ const errorMessage = error instanceof Error ? error.message : String(error);
1030
+ (_d = (_c = this.config).onError) == null ? void 0 : _d.call(
1031
+ _c,
1032
+ new Error(`Failed to open billing notice: ${errorMessage}`)
1033
+ );
1034
+ }
1035
+ }
897
1036
  getUrlConfig() {
898
1037
  return this.brandUrls[this.config.environment] || this.brandUrls.production;
899
1038
  }
900
1039
  getTrustedOrigins() {
901
1040
  return this.getUrlConfig().trustedOrigins;
902
1041
  }
1042
+ getAllowedCustomOrigins(verificationUrl) {
1043
+ const origins = /* @__PURE__ */ new Set();
1044
+ const overrideOrigin = this.getLocalOrigin(this.config.verifyUrl || null);
1045
+ const verificationOrigin = this.getLocalOrigin(verificationUrl || null);
1046
+ if (overrideOrigin) {
1047
+ origins.add(overrideOrigin);
1048
+ }
1049
+ if (verificationOrigin) {
1050
+ origins.add(verificationOrigin);
1051
+ }
1052
+ return Array.from(origins);
1053
+ }
1054
+ getLocalOrigin(urlValue) {
1055
+ if (!urlValue) {
1056
+ return null;
1057
+ }
1058
+ try {
1059
+ const parsed = new URL(urlValue);
1060
+ if (_VerificationSDK.LOCAL_HOSTNAMES.has(parsed.hostname)) {
1061
+ return parsed.origin;
1062
+ }
1063
+ } catch (e) {
1064
+ return null;
1065
+ }
1066
+ return null;
1067
+ }
1068
+ applyLocalVerifyOverride(rawUrl) {
1069
+ const overrideOrigin = this.getLocalOrigin(this.config.verifyUrl || null);
1070
+ if (!overrideOrigin) {
1071
+ return rawUrl;
1072
+ }
1073
+ try {
1074
+ const overrideUrl = new URL(overrideOrigin);
1075
+ const targetUrl = new URL(rawUrl);
1076
+ targetUrl.protocol = overrideUrl.protocol;
1077
+ targetUrl.host = overrideUrl.host;
1078
+ return targetUrl.toString();
1079
+ } catch (e) {
1080
+ return rawUrl;
1081
+ }
1082
+ }
903
1083
  getHmacSecret() {
904
1084
  return this.config.environment === "staging" ? this.brandConstants.hmacSecretStaging : this.brandConstants.hmacSecretProd;
905
1085
  }
906
1086
  };
1087
+ // Local override hostnames allowed for internal testing
1088
+ _VerificationSDK.LOCAL_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
1089
+ var VerificationSDK = _VerificationSDK;
907
1090
 
908
1091
  // src-redirect/brands/privateav/urls.ts
909
1092
  var BRAND_URLS = {
@@ -946,7 +1129,7 @@ var PrivateAV = class extends VerificationSDK {
946
1129
  super(config, BRAND_URLS, BRAND_CONSTANTS);
947
1130
  }
948
1131
  };
949
- var VERSION = "3.4.2";
1132
+ var VERSION = "3.4.9";
950
1133
  PrivateAV.VERSION = VERSION;
951
1134
  if (typeof window !== "undefined") {
952
1135
  setupPolyfills();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@privateav/sdk",
3
- "version": "3.4.2",
3
+ "version": "3.4.9",
4
4
  "description": "PrivateAV SDK - Lightweight redirect-based age verification",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,7 +28,7 @@
28
28
  "license": "MIT",
29
29
  "repository": {
30
30
  "type": "git",
31
- "url": "https://github.com/privateav/sdk"
31
+ "url": "git+https://github.com/privateav/sdk.git"
32
32
  },
33
33
  "bugs": {
34
34
  "url": "https://github.com/privateav/sdk/issues"
package/privateav.min.js CHANGED
@@ -1,3 +1,3 @@
1
- /* PrivateAV SDK v3.4.2 */
2
- "use strict";var PrivateAVSDK=(()=>{var v=Object.defineProperty,rt=Object.defineProperties,it=Object.getOwnPropertyDescriptor,ot=Object.getOwnPropertyDescriptors,st=Object.getOwnPropertyNames,w=Object.getOwnPropertySymbols;var I=Object.prototype.hasOwnProperty,M=Object.prototype.propertyIsEnumerable;var R=(e,t,n)=>t in e?v(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,u=(e,t)=>{for(var n in t||(t={}))I.call(t,n)&&R(e,n,t[n]);if(w)for(var n of w(t))M.call(t,n)&&R(e,n,t[n]);return e},y=(e,t)=>rt(e,ot(t));var _=(e,t)=>{var n={};for(var r in e)I.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&w)for(var r of w(e))t.indexOf(r)<0&&M.call(e,r)&&(n[r]=e[r]);return n};var C=(e,t)=>()=>(e&&(t=e(e=0)),t);var K=(e,t)=>{for(var n in t)v(e,n,{get:t[n],enumerable:!0})},at=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of st(t))!I.call(e,i)&&i!==n&&v(e,i,{get:()=>t[i],enumerable:!(r=it(t,i))||r.enumerable});return e};var ct=e=>at(v({},"__esModule",{value:!0}),e);function lt(e,t){return t.includes(e)}function O(e,t,n=[],r="SDK"){var o;let{origin:i}=e;return lt(i,t)||n.length>0&&n.some(c=>{if(c.startsWith("*.")){let s=c.slice(2);return i.endsWith(`.${s}`)||i===`https://${s}`||i===`http://${s}`}return i===c})?!0:(console.warn(`${r} Security: Blocked PostMessage from untrusted origin: ${i}`,{trustedOrigins:t,allowedCustomOrigins:n,eventType:(o=e.data)==null?void 0:o.type}),!1)}function W(e,t,n,r){let{data:i}=e;return!i||typeof i!="object"?{isValid:!1,error:"Invalid message format"}:(r?[n,r]:[n]).includes(i.type)?!i.sessionId||i.sessionId!==t?{isValid:!1,error:"Session ID mismatch"}:!i.status||!["verified","failed"].includes(i.status)?{isValid:!1,error:"Invalid status value"}:{isValid:!0}:{isValid:!1,error:"Invalid message type"}}function B(e,t="SDK"){e==="production"&&window.location.protocol!=="https:"&&console.warn(`${t} Warning: HTTPS recommended for production environment`,{current:window.location.href})}function A(e,t,n="SDK"){try{let r=new URL(e);if(r.protocol!=="https:"&&!(r.hostname==="localhost"||r.hostname==="127.0.0.1"))return{isValid:!1,error:`HTTPS required for return URLs in ${t}`};let i=[/data:/i,/javascript:/i,/vbscript:/i,/file:/i,/ftp:/i];for(let o of i)if(o.test(e))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch(r){return{isValid:!1,error:"Invalid URL format"}}}function d(e,t,n="SDK"){let r={timestamp:new Date().toISOString(),userAgent:typeof navigator!="undefined"?navigator.userAgent:"unknown",url:typeof window!="undefined"?window.location.href:"unknown"};console.warn(`${n} Security Event: ${e}`,u(u({},r),t))}var b,H,T=C(()=>{"use strict";b=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(t,n="SDK"){let r=Date.now(),o=(this.attempts.get(t)||[]).filter(a=>r-a<this.timeWindow);return o.length>=this.maxAttempts?(console.warn(`${n} Security: Rate limit exceeded for ${t}`),!1):(o.push(r),this.attempts.set(t,o),!0)}reset(t){this.attempts.delete(t)}},H=new b});var F={};K(F,{createSignedState:()=>pt,generateHMAC:()=>P,generateSecureToken:()=>j,parseSignedState:()=>ut,verifyHMAC:()=>q});async function P(e,t){let n=new TextEncoder,r=n.encode(t),i=n.encode(e),o=await crypto.subtle.importKey("raw",r,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),a=await crypto.subtle.sign("HMAC",o,i);return Array.from(new Uint8Array(a)).map(c=>c.toString(16).padStart(2,"0")).join("")}async function q(e,t,n){try{let r=await P(e,n);return dt(t,r)}catch(r){return!1}}function dt(e,t){if(e.length!==t.length)return!1;let n=0;for(let r=0;r<e.length;r++)n|=e.charCodeAt(r)^t.charCodeAt(r);return n===0}function j(e=32){let t=new Uint8Array(e);return crypto.getRandomValues(t),Array.from(t,n=>n.toString(16).padStart(2,"0")).join("")}async function pt(e,t){let n=y(u({},e),{timestamp:Date.now(),nonce:j(16)}),r=JSON.stringify(n),i=await P(r,t);return btoa(JSON.stringify({data:n,signature:i}))}async function ut(e,t,n=J,r="SDK"){try{let o=atob(e),a=JSON.parse(o);if(!a.data||!a.signature)return console.warn(`${r}: Invalid signed state format`),null;let{data:c,signature:s}=a,l=JSON.stringify(c);if(!await q(l,s,t))return console.warn(`${r}: State signature verification failed`),null;if(c.timestamp){let h=Date.now()-c.timestamp;if(h>n)return console.warn(`${r}: State parameter expired`,{age:h,maxAge:n}),null}let i=c,{timestamp:g,nonce:f}=i;return _(i,["timestamp","nonce"])}catch(o){return console.warn(`${r}: Failed to parse signed state`,o),null}}var G=C(()=>{"use strict";x()});function Z(e,t){if(!e.apiKey)throw new Error("apiKey is required");if(e.apiKey.length>z)throw new Error(`apiKey exceeds maximum length of ${z} characters`);if(!gt.test(e.apiKey))throw new Error("Invalid apiKey format. Expected pk_xxx (public) or sk_xxx (private)");if(typeof window!="undefined"&&e.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: ${t.docsUrl}/server-side-sessions`);if(!e.returnUrl)throw new Error("returnUrl is required");if(e.returnUrl.length>S)throw new Error(`returnUrl exceeds maximum length of ${S} characters`);let n=ft(),r=A(e.returnUrl,n,t.brandName);if(!r.isValid)throw new Error(`returnUrl validation failed: ${r.error}`);if(e.cancelUrl){if(e.cancelUrl.length>S)throw new Error(`cancelUrl exceeds maximum length of ${S} characters`);let i=A(e.cancelUrl,n,t.brandName);if(!i.isValid)throw new Error(`cancelUrl validation failed: ${i.error}`)}if(e.defaultChallengeAge!==void 0){if(e.defaultChallengeAge<X)throw new Error(`defaultChallengeAge must be at least ${X}`);if(e.defaultChallengeAge>Y)throw new Error(`defaultChallengeAge cannot exceed ${Y}`)}if(e.defaultVerificationMode&&!["L1","L2"].includes(e.defaultVerificationMode))throw new Error("defaultVerificationMode must be L1 or L2");if(e.mode&&!["redirect","new-tab"].includes(e.mode))throw new Error("mode must be redirect or new-tab")}function ft(){if(typeof window=="undefined")return"production";let e=window.location.hostname;return e.includes("staging")||e.includes("stage")?"staging":"production"}async function Q(e,t,n,r="SDK"){let{createSignedState:i}=await Promise.resolve().then(()=>(G(),F));return i(e,n)}var X,Y,S,z,J,gt,x=C(()=>{"use strict";T();X=25,Y=150,S=2048,z=128,J=6e5,gt=/^(pk_|sk_)[a-zA-Z0-9_]+$/});var wt={};K(wt,{PrivateAV:()=>m,VERSION:()=>nt,default:()=>mt});function N(){crypto.randomUUID||(crypto.randomUUID=function(){let e=new Uint8Array(16);crypto.getRandomValues(e),e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=Array.from(e).map(n=>n.toString(16).padStart(2,"0")).join("");return[t.slice(0,8),t.slice(8,12),t.slice(12,16),t.slice(16,20),t.slice(20,32)].join("-")})}function L(e="SDK"){let t=[];if(!window.crypto||!window.crypto.getRandomValues)throw new Error(`${e} requires Web Crypto API support`);if(!window.crypto.subtle)throw new Error(`${e} 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(`${e} Browser Compatibility:`,t.join("; "))}x();function V(e,t){let n=t.verifyUiUrl;if(!n||!n.startsWith("https://"))throw new Error(`HTTPS required for ${e} environment`);return n}function ht(e,t){let n=t.apiUrl;if(!n||!n.startsWith("https://"))throw new Error(`HTTPS required for API URLs in ${e} environment`);return n}function tt(e,t,n="SDK"){let r=window.location.protocol==="https:";switch(e){case"production":r||console.warn(`${n} Warning: HTTPS recommended for production environment`);break;case"staging":r||console.warn(`${n} Warning: HTTPS strongly recommended in staging environment`);break}try{V(e,t),ht(e,t)}catch(i){let o=i instanceof Error?i.message:String(i);throw new Error(`Environment configuration validation failed: ${o}`)}}T();var U=class{constructor(t,n,r){this.popupWindow=null;this.messageListener=null;this.popupMonitorInterval=null;this.unloadListener=null;this.isVerificationInProgress=!1;this.currentSessionId=null;this.lastVerifyUrl=null;this.lastSessionToken=null;this.temporaryHandoffToken=null;this.brandUrls=n,this.brandConstants=r,Z(t,{brandName:this.brandConstants.name,docsUrl:this.brandConstants.docsUrl});let i=t.environment||this.detectEnvironment();i!=="staging"&&i!=="production"&&(console.warn(`${this.brandConstants.name} SDK: Unknown environment '${i}', defaulting to 'production'`),i="production"),this.config=y(u({},t),{environment:i,mode:t.mode||"redirect"}),tt(this.config.environment,this.getUrlConfig(),this.brandConstants.name),B(this.config.environment,this.brandConstants.name),d("SDK_INITIALIZED",{environment:this.config.environment,mode:this.config.mode,origin:window.location.origin,protocol:window.location.protocol,hostname:window.location.hostname},this.brandConstants.name),this.setupAutoCleanup()}async verify(t={}){var i,o,a,c,s,l;let n=this.isPublicKey(),r;if(n)r=await this.createInternalSession(t);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(!r)throw new Error("Failed to obtain sessionId from server");if(this.isVerificationInProgress){let p=new Error(`Verification already in progress for session ${(i=this.currentSessionId)==null?void 0:i.substring(0,8)}...`);throw d("RACE_CONDITION_PREVENTED",{currentSession:((o=this.currentSessionId)==null?void 0:o.substring(0,8))+"...",attemptedSession:"new-session-attempt",origin:window.location.origin},this.brandConstants.name),(c=(a=this.config).onError)==null||c.call(a,p),p}this.isVerificationInProgress=!0,this.currentSessionId=r;try{let p=`${this.config.apiKey}:${window.location.origin}`;if(!H.isAllowed(p,this.brandConstants.name)){let f=new Error("Too many verification attempts. Please wait before trying again.");throw d("RATE_LIMIT_EXCEEDED",{apiKey:this.config.apiKey.substring(0,8)+"...",origin:window.location.origin,sessionId:r?r.substring(0,8)+"...":"undefined"},this.brandConstants.name),(l=(s=this.config).onError)==null||l.call(s,f),f}let g=await this.buildVerificationUrl(t,r);d("VERIFICATION_INITIATED",{environment:this.config.environment,mode:this.config.mode,sessionId:r?r.substring(0,8)+"...":"undefined",origin:window.location.origin},this.brandConstants.name),this.config.mode==="new-tab"?this.openNewTab(g,r):(this.unlockVerification(),this.redirect(g))}catch(p){throw this.unlockVerification(),p}}async buildVerificationUrl(t,n){let r=this.config.verifyUrl||V(this.config.environment,this.getUrlConfig()),i=t.challengeAge!==void 0,o=t.verificationMode!==void 0,a=i||o,c=await Q({merchantId:this.config.apiKey,sessionId:n,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:t.challengeAge||this.config.defaultChallengeAge,verificationMode:t.verificationMode||this.config.defaultVerificationMode,hasOverrides:a,externalUserId:t.externalUserId,timestamp:Date.now(),apiUrl:this.getPortalApiUrl(),engineUrl:this.getEngineUrl(),wsUrl:this.getWebSocketUrl(),environment:this.config.environment,features:{testMode:!1,warmupPeriodMs:500,qualityThreshold:.6},handoffToken:this.temporaryHandoffToken||void 0,sessionToken:this.lastSessionToken||void 0,verifyUrl:this.lastVerifyUrl||void 0},this.config.environment,this.getHmacSecret(),this.brandConstants.name);if(this.lastVerifyUrl)try{let l=new URL(this.lastVerifyUrl);return l.searchParams.set("state",c),l.searchParams.set("mode",this.config.mode),t.skipIntro&&l.searchParams.set("skip_intro","true"),t.autoReturn&&l.searchParams.set("auto_return","true"),l.toString()}catch(l){}let s=new URLSearchParams({state:c,sessionId:n,mode:this.config.mode});return t.skipIntro&&s.set("skip_intro","true"),t.autoReturn&&s.set("auto_return","true"),`${r}/?${s.toString()}`}redirect(t){window.location.href=t}openNewTab(t,n){var a,c;if(this.cleanup(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),this.popupWindow=window.open(t,this.brandConstants.popupName,"width=600,height=700"),!this.popupWindow){(c=(a=this.config).onError)==null||c.call(a,new Error("Failed to open verification window. Please check popup blocker settings."));return}let r=this.getTrustedOrigins(),i=this.brandConstants.messageType,o=this.brandConstants.legacyMessageType;this.messageListener=s=>{var g,f,E,h,D,$;if(!O(s,r,[],this.brandConstants.name)){d("POSTMESSAGE_ORIGIN_BLOCKED",{origin:s.origin,environment:this.config.environment,expectedOrigins:`${this.brandConstants.name} trusted origins for ${this.config.environment}`,messageType:(g=s.data)==null?void 0:g.type},this.brandConstants.name);return}let l=W(s,n,i,o);if(!l.isValid){d("POSTMESSAGE_VALIDATION_FAILED",{error:l.error,origin:s.origin,sessionId:n.substring(0,8)+"...",messageType:(f=s.data)==null?void 0:f.type},this.brandConstants.name);return}let p={sessionId:s.data.sessionId,status:s.data.status};d("VERIFICATION_COMPLETED",{status:p.status,sessionId:n.substring(0,8)+"...",origin:s.origin},this.brandConstants.name),this.cleanup(),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),p.status==="verified"?(h=(E=this.config).onComplete)==null||h.call(E,p):($=(D=this.config).onError)==null||$.call(D,new Error(`Verification failed: ${p.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{var s,l;this.popupWindow&&this.popupWindow.closed&&(d("POPUP_CLOSED_BY_USER",{sessionId:n.substring(0,8)+"...",environment:this.config.environment},this.brandConstants.name),this.cleanup(),this.unlockVerification(),(l=(s=this.config).onCancel)==null||l.call(s))},500)}setupAutoCleanup(){if(this.unloadListener=()=>{d("SDK_AUTO_CLEANUP",{environment:this.config.environment,trigger:"page_unload"},this.brandConstants.name),this.cleanup(),this.unlockVerification()},window.addEventListener("beforeunload",this.unloadListener),window.addEventListener("pagehide",this.unloadListener),window.history&&window.history.pushState){let t=window.history.pushState;window.history.pushState=(...n)=>(this.cleanup(),this.unlockVerification(),t.apply(window.history,n))}}detectEnvironment(){let t=window.location.hostname;return t.includes("staging")||t.includes("stage")?"staging":"production"}getEnvironment(){return this.config.environment}unlockVerification(){this.isVerificationInProgress=!1,this.currentSessionId=null,d("VERIFICATION_UNLOCKED",{environment:this.config.environment,origin:window.location.origin},this.brandConstants.name)}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(){d("SDK_DESTROYED",{environment:this.config.environment,origin:window.location.origin},this.brandConstants.name),this.cleanup(),this.unlockVerification(),this.removeAutoCleanupListeners()}getPortalApiUrl(){return this.config.apiUrl?this.config.apiUrl:this.getUrlConfig().apiUrl}getEngineUrl(){return this.getUrlConfig().engineUrl}getWebSocketUrl(){return this.getUrlConfig().wsUrl}isPublicKey(){return this.config.apiKey.startsWith("pk_")}async createInternalSession(t){var n,r;try{let i=this.getPortalApiUrl(),o=await fetch(`${i}/api/v1/sessions/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({merchantId:this.config.apiKey,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:t.challengeAge,verificationMode:t.verificationMode,merchantName:document.title||window.location.hostname,externalUserId:t.externalUserId})});if(!o.ok){let s=await o.json().catch(()=>({}));throw new Error(`Failed to create session: ${o.status} ${o.statusText}. ${s.message||""}`)}let a=await o.json(),c=a.sessionId;if(!c)throw new Error("Server did not return a sessionId");return a.verifyUrl&&(this.lastVerifyUrl=a.verifyUrl),a.sessionToken&&(this.lastSessionToken=a.sessionToken),a.handoffToken&&(this.temporaryHandoffToken=a.handoffToken),d("INTERNAL_SESSION_CREATED",{sessionId:c.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),c}catch(i){let o=i instanceof Error?i.message:String(i);throw d("INTERNAL_SESSION_FAILED",{error:o,environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),(r=(n=this.config).onError)==null||r.call(n,i),new Error(`Failed to create verification session: ${o}`)}}getUrlConfig(){return this.brandUrls[this.config.environment]||this.brandUrls.production}getTrustedOrigins(){return this.getUrlConfig().trustedOrigins}getHmacSecret(){return this.config.environment==="staging"?this.brandConstants.hmacSecretStaging:this.brandConstants.hmacSecretProd}};var et={production:{apiUrl:"https://api.privateav.com",verifyUiUrl:"https://verify.privateav.com",engineUrl:"https://engine.privateav.com",wsUrl:"wss://engine.privateav.com/api/websocket/stream",trustedOrigins:["https://verify.privateav.com","https://portal.privateav.com","https://api.privateav.com"]},staging:{apiUrl:"https://api.staging.privateav.com",verifyUiUrl:"https://verify.staging.privateav.com",engineUrl:"https://engine.staging.privateav.com",wsUrl:"wss://engine.staging.privateav.com/api/websocket/stream",trustedOrigins:["https://verify.staging.privateav.com","https://portal.staging.privateav.com","https://api.staging.privateav.com"]}},k={name:"PrivateAV",hmacSecretProd:"privateav-prod-hmac-2025",hmacSecretStaging:"privateav-stage-hmac-2025",messageType:"privateav:verification:complete",legacyMessageType:"privateav-verification",popupName:"privateav-verify",docsUrl:"https://docs.privateav.com"};var m=class extends U{constructor(t){super(t,et,k)}},nt="3.4.2";m.VERSION=nt;typeof window!="undefined"&&(N(),L(`${k.name} SDK`));var mt=m;return ct(wt);})();
1
+ /* PrivateAV SDK v3.4.9 */
2
+ "use strict";var PrivateAVSDK=(()=>{var y=Object.defineProperty,dt=Object.defineProperties,pt=Object.getOwnPropertyDescriptor,gt=Object.getOwnPropertyDescriptors,ut=Object.getOwnPropertyNames,v=Object.getOwnPropertySymbols;var T=Object.prototype.hasOwnProperty,W=Object.prototype.propertyIsEnumerable;var B=(n,t,e)=>t in n?y(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e,u=(n,t)=>{for(var e in t||(t={}))T.call(t,e)&&B(n,e,t[e]);if(v)for(var e of v(t))W.call(t,e)&&B(n,e,t[e]);return n},S=(n,t)=>dt(n,gt(t));var H=(n,t)=>{var e={};for(var r in n)T.call(n,r)&&t.indexOf(r)<0&&(e[r]=n[r]);if(n!=null&&v)for(var r of v(n))t.indexOf(r)<0&&W.call(n,r)&&(e[r]=n[r]);return e};var A=(n,t)=>()=>(n&&(t=n(n=0)),t);var F=(n,t)=>{for(var e in t)y(n,e,{get:t[e],enumerable:!0})},ft=(n,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of ut(t))!T.call(n,i)&&i!==e&&y(n,i,{get:()=>t[i],enumerable:!(r=pt(t,i))||r.enumerable});return n};var ht=n=>ft(y({},"__esModule",{value:!0}),n);function mt(n,t){return t.includes(n)}function G(n,t,e=[],r="SDK"){var o;let{origin:i}=n;return mt(i,t)||e.length>0&&e.some(a=>{if(a.startsWith("*.")){let l=a.slice(2);return i.endsWith(`.${l}`)||i===`https://${l}`||i===`http://${l}`}return i===a})?!0:(console.warn(`${r} Security: Blocked PostMessage from untrusted origin: ${i}`,{trustedOrigins:t,allowedCustomOrigins:e,eventType:(o=n.data)==null?void 0:o.type}),!1)}function J(n,t,e,r){let{data:i}=n;return!i||typeof i!="object"?{isValid:!1,error:"Invalid message format"}:(r?[e,r]:[e]).includes(i.type)?!i.sessionId||i.sessionId!==t?{isValid:!1,error:"Session ID mismatch"}:!i.status||!["verified","failed","cancelled"].includes(i.status)?{isValid:!1,error:"Invalid status value"}:{isValid:!0}:{isValid:!1,error:"Invalid message type"}}function X(n,t="SDK"){n==="production"&&window.location.protocol!=="https:"&&console.warn(`${t} Warning: HTTPS recommended for production environment`,{current:window.location.href})}function x(n,t,e="SDK"){try{let r=new URL(n);if(r.protocol!=="https:"&&!(r.hostname==="localhost"||r.hostname==="127.0.0.1"))return{isValid:!1,error:`HTTPS required for return URLs in ${t}`};let i=[/data:/i,/javascript:/i,/vbscript:/i,/file:/i,/ftp:/i];for(let o of i)if(o.test(n))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch(r){return{isValid:!1,error:"Invalid URL format"}}}function p(n,t,e="SDK"){let r={timestamp:new Date().toISOString(),userAgent:typeof navigator!="undefined"?navigator.userAgent:"unknown",url:typeof window!="undefined"?window.location.href:"unknown"};console.warn(`${e} Security Event: ${n}`,u(u({},r),t))}var P,Y,R=A(()=>{"use strict";P=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(t,e="SDK"){let r=Date.now(),o=(this.attempts.get(t)||[]).filter(s=>r-s<this.timeWindow);return o.length>=this.maxAttempts?(console.warn(`${e} Security: Rate limit exceeded for ${t}`),!1):(o.push(r),this.attempts.set(t,o),!0)}reset(t){this.attempts.delete(t)}},Y=new P});var Q={};F(Q,{createSignedState:()=>vt,generateHMAC:()=>V,generateSecureToken:()=>Z,parseSignedState:()=>yt,verifyHMAC:()=>z});async function V(n,t){let e=new TextEncoder,r=e.encode(t),i=e.encode(n),o=await crypto.subtle.importKey("raw",r,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),s=await crypto.subtle.sign("HMAC",o,i);return Array.from(new Uint8Array(s)).map(a=>a.toString(16).padStart(2,"0")).join("")}async function z(n,t,e){try{let r=await V(n,e);return wt(t,r)}catch(r){return!1}}function wt(n,t){if(n.length!==t.length)return!1;let e=0;for(let r=0;r<n.length;r++)e|=n.charCodeAt(r)^t.charCodeAt(r);return e===0}function Z(n=32){let t=new Uint8Array(n);return crypto.getRandomValues(t),Array.from(t,e=>e.toString(16).padStart(2,"0")).join("")}async function vt(n,t){let e=S(u({},n),{timestamp:Date.now(),nonce:Z(16)}),r=JSON.stringify(e),i=await V(r,t);return btoa(JSON.stringify({data:e,signature:i}))}async function yt(n,t,e=et,r="SDK"){try{let o=atob(n),s=JSON.parse(o);if(!s.data||!s.signature)return console.warn(`${r}: Invalid signed state format`),null;let{data:a,signature:l}=s,d=JSON.stringify(a);if(!await z(d,l,t))return console.warn(`${r}: State signature verification failed`),null;if(a.timestamp){let h=Date.now()-a.timestamp;if(h>e)return console.warn(`${r}: State parameter expired`,{age:h,maxAge:e}),null}let i=a,{timestamp:g,nonce:f}=i;return H(i,["timestamp","nonce"])}catch(o){return console.warn(`${r}: Failed to parse signed state`,o),null}}var tt=A(()=>{"use strict";L()});function ot(n,t){if(!n.apiKey)throw new Error("apiKey is required");if(n.apiKey.length>it)throw new Error(`apiKey exceeds maximum length of ${it} characters`);if(!St.test(n.apiKey))throw new Error("Invalid apiKey format. Expected pk_xxx (public) or sk_xxx (private)");if(typeof window!="undefined"&&n.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: ${t.docsUrl}/server-side-sessions`);if(!n.returnUrl)throw new Error("returnUrl is required");if(n.returnUrl.length>U)throw new Error(`returnUrl exceeds maximum length of ${U} characters`);let e=Ut(),r=x(n.returnUrl,e,t.brandName);if(!r.isValid)throw new Error(`returnUrl validation failed: ${r.error}`);if(n.cancelUrl){if(n.cancelUrl.length>U)throw new Error(`cancelUrl exceeds maximum length of ${U} characters`);let i=x(n.cancelUrl,e,t.brandName);if(!i.isValid)throw new Error(`cancelUrl validation failed: ${i.error}`)}if(n.defaultChallengeAge!==void 0){if(n.defaultChallengeAge<nt)throw new Error(`defaultChallengeAge must be at least ${nt}`);if(n.defaultChallengeAge>rt)throw new Error(`defaultChallengeAge cannot exceed ${rt}`)}if(n.defaultVerificationMode&&!["L1","L2"].includes(n.defaultVerificationMode))throw new Error("defaultVerificationMode must be L1 or L2");if(n.mode&&!["redirect","new-tab"].includes(n.mode))throw new Error("mode must be redirect or new-tab");if(n.newTabTarget&&!["popup","tab"].includes(n.newTabTarget))throw new Error("newTabTarget must be popup or tab")}function Ut(){if(typeof window=="undefined")return"production";let n=window.location.hostname;return n.includes("staging")||n.includes("stage")?"staging":"production"}async function st(n,t,e,r="SDK"){let{createSignedState:i}=await Promise.resolve().then(()=>(tt(),Q));return i(n,e)}var nt,rt,U,it,et,St,L=A(()=>{"use strict";R();nt=25,rt=150,U=2048,it=128,et=6e5,St=/^(pk_|sk_)[a-zA-Z0-9_]+$/});var Ct={};F(Ct,{PrivateAV:()=>m,VERSION:()=>ct,default:()=>bt});function q(){crypto.randomUUID||(crypto.randomUUID=function(){let n=new Uint8Array(16);crypto.getRandomValues(n),n[6]=n[6]&15|64,n[8]=n[8]&63|128;let t=Array.from(n).map(e=>e.toString(16).padStart(2,"0")).join("");return[t.slice(0,8),t.slice(8,12),t.slice(12,16),t.slice(16,20),t.slice(20,32)].join("-")})}function j(n="SDK"){let t=[];if(!window.crypto||!window.crypto.getRandomValues)throw new Error(`${n} requires Web Crypto API support`);if(!window.crypto.subtle)throw new Error(`${n} 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(`${n} Browser Compatibility:`,t.join("; "))}L();function E(n,t){let e=t.verifyUiUrl;if(!e||!e.startsWith("https://"))throw new Error(`HTTPS required for ${n} environment`);return e}function Et(n,t){let e=t.apiUrl;if(!e||!e.startsWith("https://"))throw new Error(`HTTPS required for API URLs in ${n} environment`);return e}function at(n,t,e="SDK"){let r=window.location.protocol==="https:";switch(n){case"production":r||console.warn(`${e} Warning: HTTPS recommended for production environment`);break;case"staging":r||console.warn(`${e} Warning: HTTPS strongly recommended in staging environment`);break}try{E(n,t),Et(n,t)}catch(i){let o=i instanceof Error?i.message:String(i);throw new Error(`Environment configuration validation failed: ${o}`)}}R();var C=class C{constructor(t,e,r){this.popupWindow=null;this.messageListener=null;this.popupMonitorInterval=null;this.unloadListener=null;this.isVerificationInProgress=!1;this.currentSessionId=null;this.hasReceivedResult=!1;this.lastVerifyUrl=null;this.lastSessionToken=null;this.lastExternalUserId=null;this.lastSandboxMode=null;this.temporaryHandoffToken=null;this.brandUrls=e,this.brandConstants=r,ot(t,{brandName:this.brandConstants.name,docsUrl:this.brandConstants.docsUrl});let i=t.environment||this.detectEnvironment();i!=="staging"&&i!=="production"&&(console.warn(`${this.brandConstants.name} SDK: Unknown environment '${i}', defaulting to 'production'`),i="production"),this.config=S(u({},t),{environment:i,mode:t.mode||"redirect",newTabTarget:t.newTabTarget||"popup"}),at(this.config.environment,this.getUrlConfig(),this.brandConstants.name),X(this.config.environment,this.brandConstants.name),p("SDK_INITIALIZED",{environment:this.config.environment,mode:this.config.mode,origin:window.location.origin,protocol:window.location.protocol,hostname:window.location.hostname},this.brandConstants.name),this.setupAutoCleanup()}async verify(t={}){var i,o,s,a,l,d;let e=this.isPublicKey(),r;if(e)r=await this.createInternalSession(t);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(!r)throw new Error("Failed to obtain sessionId from server");if(this.isVerificationInProgress){let c=new Error(`Verification already in progress for session ${(i=this.currentSessionId)==null?void 0:i.substring(0,8)}...`);throw p("RACE_CONDITION_PREVENTED",{currentSession:((o=this.currentSessionId)==null?void 0:o.substring(0,8))+"...",attemptedSession:"new-session-attempt",origin:window.location.origin},this.brandConstants.name),(a=(s=this.config).onError)==null||a.call(s,c),c}this.isVerificationInProgress=!0,this.currentSessionId=r,this.lastExternalUserId=t.externalUserId||null;try{let c=`${this.config.apiKey}:${window.location.origin}`;if(!Y.isAllowed(c,this.brandConstants.name)){let f=new Error("Too many verification attempts. Please wait before trying again.");throw p("RATE_LIMIT_EXCEEDED",{apiKey:this.config.apiKey.substring(0,8)+"...",origin:window.location.origin,sessionId:r?r.substring(0,8)+"...":"undefined"},this.brandConstants.name),(d=(l=this.config).onError)==null||d.call(l,f),f}let g=await this.buildVerificationUrl(t,r);p("VERIFICATION_INITIATED",{environment:this.config.environment,mode:this.config.mode,sessionId:r?r.substring(0,8)+"...":"undefined",origin:window.location.origin},this.brandConstants.name),this.config.mode==="new-tab"?this.openNewTab(g,r):(this.unlockVerification(),this.redirect(g))}catch(c){throw this.unlockVerification(),c}}async buildVerificationUrl(t,e){var d;let r=this.config.verifyUrl||E(this.config.environment,this.getUrlConfig()),i=t.challengeAge!==void 0,o=t.verificationMode!==void 0,s=i||o,a=await st({merchantId:this.config.apiKey,sessionId:e,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:t.challengeAge||this.config.defaultChallengeAge,verificationMode:t.verificationMode||this.config.defaultVerificationMode,hasOverrides:s,externalUserId:t.externalUserId,timestamp:Date.now(),apiUrl:this.getPortalApiUrl(),engineUrl:this.getEngineUrl(),wsUrl:this.getWebSocketUrl(),environment:this.config.environment,features:{testMode:!1,warmupPeriodMs:500,qualityThreshold:.6,sandboxMode:(d=this.lastSandboxMode)!=null?d:!1},handoffToken:this.temporaryHandoffToken||void 0,sessionToken:this.lastSessionToken||void 0,verifyUrl:this.lastVerifyUrl||void 0},this.config.environment,this.getHmacSecret(),this.brandConstants.name);if(this.lastVerifyUrl)try{let c=this.applyLocalVerifyOverride(this.lastVerifyUrl),g=new URL(c);return g.searchParams.set("state",a),g.searchParams.set("mode",this.config.mode),t.skipIntro&&g.searchParams.set("skip_intro","true"),t.autoReturn&&g.searchParams.set("auto_return","true"),g.toString()}catch(c){}let l=new URLSearchParams({state:a,sessionId:e,mode:this.config.mode});return t.skipIntro&&l.set("skip_intro","true"),t.autoReturn&&l.set("auto_return","true"),`${r}/?${l.toString()}`}redirect(t){window.location.href=t}openNewTab(t,e){var l,d;if(this.cleanup(),this.hasReceivedResult=!1,this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),(this.config.newTabTarget||"popup")==="tab"?this.popupWindow=window.open(t,"_blank"):this.popupWindow=window.open(t,this.brandConstants.popupName,"width=600,height=700"),!this.popupWindow){(d=(l=this.config).onError)==null||d.call(l,new Error("Failed to open verification window. Please check popup blocker settings."));return}let i=this.getTrustedOrigins(),o=this.getAllowedCustomOrigins(t),s=this.brandConstants.messageType,a=this.brandConstants.legacyMessageType;this.messageListener=c=>{var D,M,_,O,$,N,K;let g=(D=c.data)==null?void 0:D.type;if(!g||typeof g!="string"||!(a?[s,a]:[s]).includes(g))return;if(!G(c,i,o,this.brandConstants.name)){p("POSTMESSAGE_ORIGIN_BLOCKED",{origin:c.origin,environment:this.config.environment,expectedOrigins:`${this.brandConstants.name} trusted origins for ${this.config.environment}`,messageType:(M=c.data)==null?void 0:M.type},this.brandConstants.name);return}let I=J(c,e,s,a);if(!I.isValid){p("POSTMESSAGE_VALIDATION_FAILED",{error:I.error,origin:c.origin,sessionId:e.substring(0,8)+"...",messageType:(_=c.data)==null?void 0:_.type},this.brandConstants.name);return}let h=c.data.status;if(h==="cancelled"){this.handleCancellation(e,"postmessage");return}let w={sessionId:c.data.sessionId,status:h,timestamp:c.data.timestamp,externalUserId:c.data.externalUserId};this.hasReceivedResult=!0,p("VERIFICATION_COMPLETED",{status:w.status,sessionId:e.substring(0,8)+"...",origin:c.origin},this.brandConstants.name),this.cleanup({closePopup:!1}),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),w.status==="verified"?($=(O=this.config).onComplete)==null||$.call(O,w):(K=(N=this.config).onError)==null||K.call(N,new Error(`Verification failed: ${w.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{this.popupWindow&&this.popupWindow.closed&&(p("POPUP_CLOSED_BY_USER",{sessionId:e.substring(0,8)+"...",environment:this.config.environment},this.brandConstants.name),this.hasReceivedResult||this.handleCancellation(e,"popup-closed"))},500)}setupAutoCleanup(){if(this.unloadListener=()=>{p("SDK_AUTO_CLEANUP",{environment:this.config.environment,trigger:"page_unload"},this.brandConstants.name),this.cleanup(),this.unlockVerification()},window.addEventListener("beforeunload",this.unloadListener),window.addEventListener("pagehide",this.unloadListener),window.history&&window.history.pushState){let t=window.history.pushState;window.history.pushState=(...e)=>(this.cleanup(),this.unlockVerification(),t.apply(window.history,e))}}detectEnvironment(){let t=window.location.hostname;return t.includes("staging")||t.includes("stage")?"staging":"production"}getEnvironment(){return this.config.environment}unlockVerification(){this.isVerificationInProgress=!1,this.currentSessionId=null,p("VERIFICATION_UNLOCKED",{environment:this.config.environment,origin:window.location.origin},this.brandConstants.name)}cleanup(t={}){let e=t.closePopup!==!1;this.popupWindow&&(e&&!this.popupWindow.closed&&this.popupWindow.close(),(e||this.popupWindow.closed)&&(this.popupWindow=null)),this.messageListener&&(window.removeEventListener("message",this.messageListener),this.messageListener=null),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null)}handleCancellation(t,e){if(this.hasReceivedResult)return;this.hasReceivedResult=!0,p("VERIFICATION_CANCELLED",{source:e,sessionId:t.substring(0,8)+"...",environment:this.config.environment,origin:window.location.origin},this.brandConstants.name),this.cleanup(),this.unlockVerification();let r=!0;if(this.config.onCancel)try{this.config.onCancel()===!1&&(r=!1)}catch(i){p("CANCEL_CALLBACK_FAILED",{error:i instanceof Error?i.message:String(i),sessionId:t.substring(0,8)+"..."},this.brandConstants.name)}r&&this.redirectToCancelUrl(t)}redirectToCancelUrl(t){if(this.config.cancelUrl)try{let e=decodeURIComponent(this.config.cancelUrl),r=new URL(e);r.searchParams.set("sessionId",t),r.searchParams.set("status","cancelled"),r.searchParams.set("timestamp",Date.now().toString()),this.lastExternalUserId&&r.searchParams.set("externalUserId",this.lastExternalUserId),window.location.href=r.toString()}catch(e){p("CANCEL_REDIRECT_FAILED",{error:e instanceof Error?e.message:String(e),sessionId:t.substring(0,8)+"..."},this.brandConstants.name)}}removeAutoCleanupListeners(){this.unloadListener&&(window.removeEventListener("beforeunload",this.unloadListener),window.removeEventListener("pagehide",this.unloadListener),this.unloadListener=null)}destroy(){p("SDK_DESTROYED",{environment:this.config.environment,origin:window.location.origin},this.brandConstants.name),this.cleanup(),this.unlockVerification(),this.removeAutoCleanupListeners()}getPortalApiUrl(){return this.config.apiUrl?this.config.apiUrl:this.getUrlConfig().apiUrl}getEngineUrl(){return this.getUrlConfig().engineUrl}getWebSocketUrl(){return this.getUrlConfig().wsUrl}isPublicKey(){return this.config.apiKey.startsWith("pk_")}async createInternalSession(t){var e,r;try{let i=this.getPortalApiUrl(),o=await fetch(`${i}/api/v1/sessions/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({merchantId:this.config.apiKey,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:t.challengeAge,verificationMode:t.verificationMode,merchantName:document.title||window.location.hostname,externalUserId:t.externalUserId})});if(!o.ok){let l=await o.json().catch(()=>({})),d=l==null?void 0:l.code;throw this.isBillingBlockError(d)&&this.openBillingBlockPage(d,l==null?void 0:l.portalUrl),new Error(`Failed to create session: ${o.status} ${o.statusText}. ${l.message||""}`)}let s=await o.json(),a=s.sessionId;if(!a)throw new Error("Server did not return a sessionId");return s.verifyUrl&&(this.lastVerifyUrl=s.verifyUrl),s.sessionToken&&(this.lastSessionToken=s.sessionToken),s.handoffToken&&(this.temporaryHandoffToken=s.handoffToken),typeof s.sandboxMode=="boolean"?this.lastSandboxMode=s.sandboxMode:this.lastSandboxMode=null,p("INTERNAL_SESSION_CREATED",{sessionId:a.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),a}catch(i){let o=i instanceof Error?i.message:String(i);throw p("INTERNAL_SESSION_FAILED",{error:o,environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),(r=(e=this.config).onError)==null||r.call(e,i),new Error(`Failed to create verification session: ${o}`)}}isBillingBlockError(t){return t==="SUBSCRIPTION_REQUIRED"||t==="PLAN_LIMIT_REACHED"||t==="SANDBOX_LIMIT_REACHED"}openBillingBlockPage(t,e){var r,i,o,s;try{let a=this.config.verifyUrl||E(this.config.environment,this.getUrlConfig()),l=this.applyLocalVerifyOverride(a),d=new URL(l);if(d.searchParams.set("blocked",t),e&&d.searchParams.set("portalUrl",e),this.config.mode==="new-tab"){((this.config.newTabTarget||"popup")==="tab"?window.open(d.toString(),"_blank"):window.open(d.toString(),this.brandConstants.popupName,"width=600,height=700"))||(i=(r=this.config).onError)==null||i.call(r,new Error("Failed to open billing notice window. Please check popup blocker settings."));return}this.redirect(d.toString())}catch(a){let l=a instanceof Error?a.message:String(a);(s=(o=this.config).onError)==null||s.call(o,new Error(`Failed to open billing notice: ${l}`))}}getUrlConfig(){return this.brandUrls[this.config.environment]||this.brandUrls.production}getTrustedOrigins(){return this.getUrlConfig().trustedOrigins}getAllowedCustomOrigins(t){let e=new Set,r=this.getLocalOrigin(this.config.verifyUrl||null),i=this.getLocalOrigin(t||null);return r&&e.add(r),i&&e.add(i),Array.from(e)}getLocalOrigin(t){if(!t)return null;try{let e=new URL(t);if(C.LOCAL_HOSTNAMES.has(e.hostname))return e.origin}catch(e){return null}return null}applyLocalVerifyOverride(t){let e=this.getLocalOrigin(this.config.verifyUrl||null);if(!e)return t;try{let r=new URL(e),i=new URL(t);return i.protocol=r.protocol,i.host=r.host,i.toString()}catch(r){return t}}getHmacSecret(){return this.config.environment==="staging"?this.brandConstants.hmacSecretStaging:this.brandConstants.hmacSecretProd}};C.LOCAL_HOSTNAMES=new Set(["localhost","127.0.0.1","::1"]);var b=C;var lt={production:{apiUrl:"https://api.privateav.com",verifyUiUrl:"https://verify.privateav.com",engineUrl:"https://engine.privateav.com",wsUrl:"wss://engine.privateav.com/api/websocket/stream",trustedOrigins:["https://verify.privateav.com","https://portal.privateav.com","https://api.privateav.com"]},staging:{apiUrl:"https://api.staging.privateav.com",verifyUiUrl:"https://verify.staging.privateav.com",engineUrl:"https://engine.staging.privateav.com",wsUrl:"wss://engine.staging.privateav.com/api/websocket/stream",trustedOrigins:["https://verify.staging.privateav.com","https://portal.staging.privateav.com","https://api.staging.privateav.com"]}},k={name:"PrivateAV",hmacSecretProd:"privateav-prod-hmac-2025",hmacSecretStaging:"privateav-stage-hmac-2025",messageType:"privateav:verification:complete",legacyMessageType:"privateav-verification",popupName:"privateav-verify",docsUrl:"https://docs.privateav.com"};var m=class extends b{constructor(t){super(t,lt,k)}},ct="3.4.9";m.VERSION=ct;typeof window!="undefined"&&(q(),j(`${k.name} SDK`));var bt=m;return ht(Ct);})();
3
3
  if(typeof PrivateAVSDK !== "undefined" && PrivateAVSDK.PrivateAV) { window.PrivateAV = PrivateAVSDK.PrivateAV; window.PrivateAV.VERSION = PrivateAVSDK.VERSION; }
package/sdk.min.js CHANGED
@@ -1,3 +1,3 @@
1
- /* PrivateAV SDK v3.4.2 */
2
- "use strict";var PrivateAVSDK=(()=>{var v=Object.defineProperty,rt=Object.defineProperties,it=Object.getOwnPropertyDescriptor,ot=Object.getOwnPropertyDescriptors,st=Object.getOwnPropertyNames,w=Object.getOwnPropertySymbols;var I=Object.prototype.hasOwnProperty,M=Object.prototype.propertyIsEnumerable;var R=(e,t,n)=>t in e?v(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,u=(e,t)=>{for(var n in t||(t={}))I.call(t,n)&&R(e,n,t[n]);if(w)for(var n of w(t))M.call(t,n)&&R(e,n,t[n]);return e},y=(e,t)=>rt(e,ot(t));var _=(e,t)=>{var n={};for(var r in e)I.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&w)for(var r of w(e))t.indexOf(r)<0&&M.call(e,r)&&(n[r]=e[r]);return n};var C=(e,t)=>()=>(e&&(t=e(e=0)),t);var K=(e,t)=>{for(var n in t)v(e,n,{get:t[n],enumerable:!0})},at=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of st(t))!I.call(e,i)&&i!==n&&v(e,i,{get:()=>t[i],enumerable:!(r=it(t,i))||r.enumerable});return e};var ct=e=>at(v({},"__esModule",{value:!0}),e);function lt(e,t){return t.includes(e)}function O(e,t,n=[],r="SDK"){var o;let{origin:i}=e;return lt(i,t)||n.length>0&&n.some(c=>{if(c.startsWith("*.")){let s=c.slice(2);return i.endsWith(`.${s}`)||i===`https://${s}`||i===`http://${s}`}return i===c})?!0:(console.warn(`${r} Security: Blocked PostMessage from untrusted origin: ${i}`,{trustedOrigins:t,allowedCustomOrigins:n,eventType:(o=e.data)==null?void 0:o.type}),!1)}function W(e,t,n,r){let{data:i}=e;return!i||typeof i!="object"?{isValid:!1,error:"Invalid message format"}:(r?[n,r]:[n]).includes(i.type)?!i.sessionId||i.sessionId!==t?{isValid:!1,error:"Session ID mismatch"}:!i.status||!["verified","failed"].includes(i.status)?{isValid:!1,error:"Invalid status value"}:{isValid:!0}:{isValid:!1,error:"Invalid message type"}}function B(e,t="SDK"){e==="production"&&window.location.protocol!=="https:"&&console.warn(`${t} Warning: HTTPS recommended for production environment`,{current:window.location.href})}function A(e,t,n="SDK"){try{let r=new URL(e);if(r.protocol!=="https:"&&!(r.hostname==="localhost"||r.hostname==="127.0.0.1"))return{isValid:!1,error:`HTTPS required for return URLs in ${t}`};let i=[/data:/i,/javascript:/i,/vbscript:/i,/file:/i,/ftp:/i];for(let o of i)if(o.test(e))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch(r){return{isValid:!1,error:"Invalid URL format"}}}function d(e,t,n="SDK"){let r={timestamp:new Date().toISOString(),userAgent:typeof navigator!="undefined"?navigator.userAgent:"unknown",url:typeof window!="undefined"?window.location.href:"unknown"};console.warn(`${n} Security Event: ${e}`,u(u({},r),t))}var b,H,T=C(()=>{"use strict";b=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(t,n="SDK"){let r=Date.now(),o=(this.attempts.get(t)||[]).filter(a=>r-a<this.timeWindow);return o.length>=this.maxAttempts?(console.warn(`${n} Security: Rate limit exceeded for ${t}`),!1):(o.push(r),this.attempts.set(t,o),!0)}reset(t){this.attempts.delete(t)}},H=new b});var F={};K(F,{createSignedState:()=>pt,generateHMAC:()=>P,generateSecureToken:()=>j,parseSignedState:()=>ut,verifyHMAC:()=>q});async function P(e,t){let n=new TextEncoder,r=n.encode(t),i=n.encode(e),o=await crypto.subtle.importKey("raw",r,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),a=await crypto.subtle.sign("HMAC",o,i);return Array.from(new Uint8Array(a)).map(c=>c.toString(16).padStart(2,"0")).join("")}async function q(e,t,n){try{let r=await P(e,n);return dt(t,r)}catch(r){return!1}}function dt(e,t){if(e.length!==t.length)return!1;let n=0;for(let r=0;r<e.length;r++)n|=e.charCodeAt(r)^t.charCodeAt(r);return n===0}function j(e=32){let t=new Uint8Array(e);return crypto.getRandomValues(t),Array.from(t,n=>n.toString(16).padStart(2,"0")).join("")}async function pt(e,t){let n=y(u({},e),{timestamp:Date.now(),nonce:j(16)}),r=JSON.stringify(n),i=await P(r,t);return btoa(JSON.stringify({data:n,signature:i}))}async function ut(e,t,n=J,r="SDK"){try{let o=atob(e),a=JSON.parse(o);if(!a.data||!a.signature)return console.warn(`${r}: Invalid signed state format`),null;let{data:c,signature:s}=a,l=JSON.stringify(c);if(!await q(l,s,t))return console.warn(`${r}: State signature verification failed`),null;if(c.timestamp){let h=Date.now()-c.timestamp;if(h>n)return console.warn(`${r}: State parameter expired`,{age:h,maxAge:n}),null}let i=c,{timestamp:g,nonce:f}=i;return _(i,["timestamp","nonce"])}catch(o){return console.warn(`${r}: Failed to parse signed state`,o),null}}var G=C(()=>{"use strict";x()});function Z(e,t){if(!e.apiKey)throw new Error("apiKey is required");if(e.apiKey.length>z)throw new Error(`apiKey exceeds maximum length of ${z} characters`);if(!gt.test(e.apiKey))throw new Error("Invalid apiKey format. Expected pk_xxx (public) or sk_xxx (private)");if(typeof window!="undefined"&&e.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: ${t.docsUrl}/server-side-sessions`);if(!e.returnUrl)throw new Error("returnUrl is required");if(e.returnUrl.length>S)throw new Error(`returnUrl exceeds maximum length of ${S} characters`);let n=ft(),r=A(e.returnUrl,n,t.brandName);if(!r.isValid)throw new Error(`returnUrl validation failed: ${r.error}`);if(e.cancelUrl){if(e.cancelUrl.length>S)throw new Error(`cancelUrl exceeds maximum length of ${S} characters`);let i=A(e.cancelUrl,n,t.brandName);if(!i.isValid)throw new Error(`cancelUrl validation failed: ${i.error}`)}if(e.defaultChallengeAge!==void 0){if(e.defaultChallengeAge<X)throw new Error(`defaultChallengeAge must be at least ${X}`);if(e.defaultChallengeAge>Y)throw new Error(`defaultChallengeAge cannot exceed ${Y}`)}if(e.defaultVerificationMode&&!["L1","L2"].includes(e.defaultVerificationMode))throw new Error("defaultVerificationMode must be L1 or L2");if(e.mode&&!["redirect","new-tab"].includes(e.mode))throw new Error("mode must be redirect or new-tab")}function ft(){if(typeof window=="undefined")return"production";let e=window.location.hostname;return e.includes("staging")||e.includes("stage")?"staging":"production"}async function Q(e,t,n,r="SDK"){let{createSignedState:i}=await Promise.resolve().then(()=>(G(),F));return i(e,n)}var X,Y,S,z,J,gt,x=C(()=>{"use strict";T();X=25,Y=150,S=2048,z=128,J=6e5,gt=/^(pk_|sk_)[a-zA-Z0-9_]+$/});var wt={};K(wt,{PrivateAV:()=>m,VERSION:()=>nt,default:()=>mt});function N(){crypto.randomUUID||(crypto.randomUUID=function(){let e=new Uint8Array(16);crypto.getRandomValues(e),e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=Array.from(e).map(n=>n.toString(16).padStart(2,"0")).join("");return[t.slice(0,8),t.slice(8,12),t.slice(12,16),t.slice(16,20),t.slice(20,32)].join("-")})}function L(e="SDK"){let t=[];if(!window.crypto||!window.crypto.getRandomValues)throw new Error(`${e} requires Web Crypto API support`);if(!window.crypto.subtle)throw new Error(`${e} 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(`${e} Browser Compatibility:`,t.join("; "))}x();function V(e,t){let n=t.verifyUiUrl;if(!n||!n.startsWith("https://"))throw new Error(`HTTPS required for ${e} environment`);return n}function ht(e,t){let n=t.apiUrl;if(!n||!n.startsWith("https://"))throw new Error(`HTTPS required for API URLs in ${e} environment`);return n}function tt(e,t,n="SDK"){let r=window.location.protocol==="https:";switch(e){case"production":r||console.warn(`${n} Warning: HTTPS recommended for production environment`);break;case"staging":r||console.warn(`${n} Warning: HTTPS strongly recommended in staging environment`);break}try{V(e,t),ht(e,t)}catch(i){let o=i instanceof Error?i.message:String(i);throw new Error(`Environment configuration validation failed: ${o}`)}}T();var U=class{constructor(t,n,r){this.popupWindow=null;this.messageListener=null;this.popupMonitorInterval=null;this.unloadListener=null;this.isVerificationInProgress=!1;this.currentSessionId=null;this.lastVerifyUrl=null;this.lastSessionToken=null;this.temporaryHandoffToken=null;this.brandUrls=n,this.brandConstants=r,Z(t,{brandName:this.brandConstants.name,docsUrl:this.brandConstants.docsUrl});let i=t.environment||this.detectEnvironment();i!=="staging"&&i!=="production"&&(console.warn(`${this.brandConstants.name} SDK: Unknown environment '${i}', defaulting to 'production'`),i="production"),this.config=y(u({},t),{environment:i,mode:t.mode||"redirect"}),tt(this.config.environment,this.getUrlConfig(),this.brandConstants.name),B(this.config.environment,this.brandConstants.name),d("SDK_INITIALIZED",{environment:this.config.environment,mode:this.config.mode,origin:window.location.origin,protocol:window.location.protocol,hostname:window.location.hostname},this.brandConstants.name),this.setupAutoCleanup()}async verify(t={}){var i,o,a,c,s,l;let n=this.isPublicKey(),r;if(n)r=await this.createInternalSession(t);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(!r)throw new Error("Failed to obtain sessionId from server");if(this.isVerificationInProgress){let p=new Error(`Verification already in progress for session ${(i=this.currentSessionId)==null?void 0:i.substring(0,8)}...`);throw d("RACE_CONDITION_PREVENTED",{currentSession:((o=this.currentSessionId)==null?void 0:o.substring(0,8))+"...",attemptedSession:"new-session-attempt",origin:window.location.origin},this.brandConstants.name),(c=(a=this.config).onError)==null||c.call(a,p),p}this.isVerificationInProgress=!0,this.currentSessionId=r;try{let p=`${this.config.apiKey}:${window.location.origin}`;if(!H.isAllowed(p,this.brandConstants.name)){let f=new Error("Too many verification attempts. Please wait before trying again.");throw d("RATE_LIMIT_EXCEEDED",{apiKey:this.config.apiKey.substring(0,8)+"...",origin:window.location.origin,sessionId:r?r.substring(0,8)+"...":"undefined"},this.brandConstants.name),(l=(s=this.config).onError)==null||l.call(s,f),f}let g=await this.buildVerificationUrl(t,r);d("VERIFICATION_INITIATED",{environment:this.config.environment,mode:this.config.mode,sessionId:r?r.substring(0,8)+"...":"undefined",origin:window.location.origin},this.brandConstants.name),this.config.mode==="new-tab"?this.openNewTab(g,r):(this.unlockVerification(),this.redirect(g))}catch(p){throw this.unlockVerification(),p}}async buildVerificationUrl(t,n){let r=this.config.verifyUrl||V(this.config.environment,this.getUrlConfig()),i=t.challengeAge!==void 0,o=t.verificationMode!==void 0,a=i||o,c=await Q({merchantId:this.config.apiKey,sessionId:n,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:t.challengeAge||this.config.defaultChallengeAge,verificationMode:t.verificationMode||this.config.defaultVerificationMode,hasOverrides:a,externalUserId:t.externalUserId,timestamp:Date.now(),apiUrl:this.getPortalApiUrl(),engineUrl:this.getEngineUrl(),wsUrl:this.getWebSocketUrl(),environment:this.config.environment,features:{testMode:!1,warmupPeriodMs:500,qualityThreshold:.6},handoffToken:this.temporaryHandoffToken||void 0,sessionToken:this.lastSessionToken||void 0,verifyUrl:this.lastVerifyUrl||void 0},this.config.environment,this.getHmacSecret(),this.brandConstants.name);if(this.lastVerifyUrl)try{let l=new URL(this.lastVerifyUrl);return l.searchParams.set("state",c),l.searchParams.set("mode",this.config.mode),t.skipIntro&&l.searchParams.set("skip_intro","true"),t.autoReturn&&l.searchParams.set("auto_return","true"),l.toString()}catch(l){}let s=new URLSearchParams({state:c,sessionId:n,mode:this.config.mode});return t.skipIntro&&s.set("skip_intro","true"),t.autoReturn&&s.set("auto_return","true"),`${r}/?${s.toString()}`}redirect(t){window.location.href=t}openNewTab(t,n){var a,c;if(this.cleanup(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),this.popupWindow=window.open(t,this.brandConstants.popupName,"width=600,height=700"),!this.popupWindow){(c=(a=this.config).onError)==null||c.call(a,new Error("Failed to open verification window. Please check popup blocker settings."));return}let r=this.getTrustedOrigins(),i=this.brandConstants.messageType,o=this.brandConstants.legacyMessageType;this.messageListener=s=>{var g,f,E,h,D,$;if(!O(s,r,[],this.brandConstants.name)){d("POSTMESSAGE_ORIGIN_BLOCKED",{origin:s.origin,environment:this.config.environment,expectedOrigins:`${this.brandConstants.name} trusted origins for ${this.config.environment}`,messageType:(g=s.data)==null?void 0:g.type},this.brandConstants.name);return}let l=W(s,n,i,o);if(!l.isValid){d("POSTMESSAGE_VALIDATION_FAILED",{error:l.error,origin:s.origin,sessionId:n.substring(0,8)+"...",messageType:(f=s.data)==null?void 0:f.type},this.brandConstants.name);return}let p={sessionId:s.data.sessionId,status:s.data.status};d("VERIFICATION_COMPLETED",{status:p.status,sessionId:n.substring(0,8)+"...",origin:s.origin},this.brandConstants.name),this.cleanup(),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),p.status==="verified"?(h=(E=this.config).onComplete)==null||h.call(E,p):($=(D=this.config).onError)==null||$.call(D,new Error(`Verification failed: ${p.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{var s,l;this.popupWindow&&this.popupWindow.closed&&(d("POPUP_CLOSED_BY_USER",{sessionId:n.substring(0,8)+"...",environment:this.config.environment},this.brandConstants.name),this.cleanup(),this.unlockVerification(),(l=(s=this.config).onCancel)==null||l.call(s))},500)}setupAutoCleanup(){if(this.unloadListener=()=>{d("SDK_AUTO_CLEANUP",{environment:this.config.environment,trigger:"page_unload"},this.brandConstants.name),this.cleanup(),this.unlockVerification()},window.addEventListener("beforeunload",this.unloadListener),window.addEventListener("pagehide",this.unloadListener),window.history&&window.history.pushState){let t=window.history.pushState;window.history.pushState=(...n)=>(this.cleanup(),this.unlockVerification(),t.apply(window.history,n))}}detectEnvironment(){let t=window.location.hostname;return t.includes("staging")||t.includes("stage")?"staging":"production"}getEnvironment(){return this.config.environment}unlockVerification(){this.isVerificationInProgress=!1,this.currentSessionId=null,d("VERIFICATION_UNLOCKED",{environment:this.config.environment,origin:window.location.origin},this.brandConstants.name)}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(){d("SDK_DESTROYED",{environment:this.config.environment,origin:window.location.origin},this.brandConstants.name),this.cleanup(),this.unlockVerification(),this.removeAutoCleanupListeners()}getPortalApiUrl(){return this.config.apiUrl?this.config.apiUrl:this.getUrlConfig().apiUrl}getEngineUrl(){return this.getUrlConfig().engineUrl}getWebSocketUrl(){return this.getUrlConfig().wsUrl}isPublicKey(){return this.config.apiKey.startsWith("pk_")}async createInternalSession(t){var n,r;try{let i=this.getPortalApiUrl(),o=await fetch(`${i}/api/v1/sessions/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({merchantId:this.config.apiKey,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:t.challengeAge,verificationMode:t.verificationMode,merchantName:document.title||window.location.hostname,externalUserId:t.externalUserId})});if(!o.ok){let s=await o.json().catch(()=>({}));throw new Error(`Failed to create session: ${o.status} ${o.statusText}. ${s.message||""}`)}let a=await o.json(),c=a.sessionId;if(!c)throw new Error("Server did not return a sessionId");return a.verifyUrl&&(this.lastVerifyUrl=a.verifyUrl),a.sessionToken&&(this.lastSessionToken=a.sessionToken),a.handoffToken&&(this.temporaryHandoffToken=a.handoffToken),d("INTERNAL_SESSION_CREATED",{sessionId:c.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),c}catch(i){let o=i instanceof Error?i.message:String(i);throw d("INTERNAL_SESSION_FAILED",{error:o,environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),(r=(n=this.config).onError)==null||r.call(n,i),new Error(`Failed to create verification session: ${o}`)}}getUrlConfig(){return this.brandUrls[this.config.environment]||this.brandUrls.production}getTrustedOrigins(){return this.getUrlConfig().trustedOrigins}getHmacSecret(){return this.config.environment==="staging"?this.brandConstants.hmacSecretStaging:this.brandConstants.hmacSecretProd}};var et={production:{apiUrl:"https://api.privateav.com",verifyUiUrl:"https://verify.privateav.com",engineUrl:"https://engine.privateav.com",wsUrl:"wss://engine.privateav.com/api/websocket/stream",trustedOrigins:["https://verify.privateav.com","https://portal.privateav.com","https://api.privateav.com"]},staging:{apiUrl:"https://api.staging.privateav.com",verifyUiUrl:"https://verify.staging.privateav.com",engineUrl:"https://engine.staging.privateav.com",wsUrl:"wss://engine.staging.privateav.com/api/websocket/stream",trustedOrigins:["https://verify.staging.privateav.com","https://portal.staging.privateav.com","https://api.staging.privateav.com"]}},k={name:"PrivateAV",hmacSecretProd:"privateav-prod-hmac-2025",hmacSecretStaging:"privateav-stage-hmac-2025",messageType:"privateav:verification:complete",legacyMessageType:"privateav-verification",popupName:"privateav-verify",docsUrl:"https://docs.privateav.com"};var m=class extends U{constructor(t){super(t,et,k)}},nt="3.4.2";m.VERSION=nt;typeof window!="undefined"&&(N(),L(`${k.name} SDK`));var mt=m;return ct(wt);})();
1
+ /* PrivateAV SDK v3.4.9 */
2
+ "use strict";var PrivateAVSDK=(()=>{var y=Object.defineProperty,dt=Object.defineProperties,pt=Object.getOwnPropertyDescriptor,gt=Object.getOwnPropertyDescriptors,ut=Object.getOwnPropertyNames,v=Object.getOwnPropertySymbols;var T=Object.prototype.hasOwnProperty,W=Object.prototype.propertyIsEnumerable;var B=(n,t,e)=>t in n?y(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e,u=(n,t)=>{for(var e in t||(t={}))T.call(t,e)&&B(n,e,t[e]);if(v)for(var e of v(t))W.call(t,e)&&B(n,e,t[e]);return n},S=(n,t)=>dt(n,gt(t));var H=(n,t)=>{var e={};for(var r in n)T.call(n,r)&&t.indexOf(r)<0&&(e[r]=n[r]);if(n!=null&&v)for(var r of v(n))t.indexOf(r)<0&&W.call(n,r)&&(e[r]=n[r]);return e};var A=(n,t)=>()=>(n&&(t=n(n=0)),t);var F=(n,t)=>{for(var e in t)y(n,e,{get:t[e],enumerable:!0})},ft=(n,t,e,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of ut(t))!T.call(n,i)&&i!==e&&y(n,i,{get:()=>t[i],enumerable:!(r=pt(t,i))||r.enumerable});return n};var ht=n=>ft(y({},"__esModule",{value:!0}),n);function mt(n,t){return t.includes(n)}function G(n,t,e=[],r="SDK"){var o;let{origin:i}=n;return mt(i,t)||e.length>0&&e.some(a=>{if(a.startsWith("*.")){let l=a.slice(2);return i.endsWith(`.${l}`)||i===`https://${l}`||i===`http://${l}`}return i===a})?!0:(console.warn(`${r} Security: Blocked PostMessage from untrusted origin: ${i}`,{trustedOrigins:t,allowedCustomOrigins:e,eventType:(o=n.data)==null?void 0:o.type}),!1)}function J(n,t,e,r){let{data:i}=n;return!i||typeof i!="object"?{isValid:!1,error:"Invalid message format"}:(r?[e,r]:[e]).includes(i.type)?!i.sessionId||i.sessionId!==t?{isValid:!1,error:"Session ID mismatch"}:!i.status||!["verified","failed","cancelled"].includes(i.status)?{isValid:!1,error:"Invalid status value"}:{isValid:!0}:{isValid:!1,error:"Invalid message type"}}function X(n,t="SDK"){n==="production"&&window.location.protocol!=="https:"&&console.warn(`${t} Warning: HTTPS recommended for production environment`,{current:window.location.href})}function x(n,t,e="SDK"){try{let r=new URL(n);if(r.protocol!=="https:"&&!(r.hostname==="localhost"||r.hostname==="127.0.0.1"))return{isValid:!1,error:`HTTPS required for return URLs in ${t}`};let i=[/data:/i,/javascript:/i,/vbscript:/i,/file:/i,/ftp:/i];for(let o of i)if(o.test(n))return{isValid:!1,error:"Blocked suspicious URL scheme"};return{isValid:!0}}catch(r){return{isValid:!1,error:"Invalid URL format"}}}function p(n,t,e="SDK"){let r={timestamp:new Date().toISOString(),userAgent:typeof navigator!="undefined"?navigator.userAgent:"unknown",url:typeof window!="undefined"?window.location.href:"unknown"};console.warn(`${e} Security Event: ${n}`,u(u({},r),t))}var P,Y,R=A(()=>{"use strict";P=class{constructor(){this.attempts=new Map;this.maxAttempts=5;this.timeWindow=6e4}isAllowed(t,e="SDK"){let r=Date.now(),o=(this.attempts.get(t)||[]).filter(s=>r-s<this.timeWindow);return o.length>=this.maxAttempts?(console.warn(`${e} Security: Rate limit exceeded for ${t}`),!1):(o.push(r),this.attempts.set(t,o),!0)}reset(t){this.attempts.delete(t)}},Y=new P});var Q={};F(Q,{createSignedState:()=>vt,generateHMAC:()=>V,generateSecureToken:()=>Z,parseSignedState:()=>yt,verifyHMAC:()=>z});async function V(n,t){let e=new TextEncoder,r=e.encode(t),i=e.encode(n),o=await crypto.subtle.importKey("raw",r,{name:"HMAC",hash:"SHA-256"},!1,["sign"]),s=await crypto.subtle.sign("HMAC",o,i);return Array.from(new Uint8Array(s)).map(a=>a.toString(16).padStart(2,"0")).join("")}async function z(n,t,e){try{let r=await V(n,e);return wt(t,r)}catch(r){return!1}}function wt(n,t){if(n.length!==t.length)return!1;let e=0;for(let r=0;r<n.length;r++)e|=n.charCodeAt(r)^t.charCodeAt(r);return e===0}function Z(n=32){let t=new Uint8Array(n);return crypto.getRandomValues(t),Array.from(t,e=>e.toString(16).padStart(2,"0")).join("")}async function vt(n,t){let e=S(u({},n),{timestamp:Date.now(),nonce:Z(16)}),r=JSON.stringify(e),i=await V(r,t);return btoa(JSON.stringify({data:e,signature:i}))}async function yt(n,t,e=et,r="SDK"){try{let o=atob(n),s=JSON.parse(o);if(!s.data||!s.signature)return console.warn(`${r}: Invalid signed state format`),null;let{data:a,signature:l}=s,d=JSON.stringify(a);if(!await z(d,l,t))return console.warn(`${r}: State signature verification failed`),null;if(a.timestamp){let h=Date.now()-a.timestamp;if(h>e)return console.warn(`${r}: State parameter expired`,{age:h,maxAge:e}),null}let i=a,{timestamp:g,nonce:f}=i;return H(i,["timestamp","nonce"])}catch(o){return console.warn(`${r}: Failed to parse signed state`,o),null}}var tt=A(()=>{"use strict";L()});function ot(n,t){if(!n.apiKey)throw new Error("apiKey is required");if(n.apiKey.length>it)throw new Error(`apiKey exceeds maximum length of ${it} characters`);if(!St.test(n.apiKey))throw new Error("Invalid apiKey format. Expected pk_xxx (public) or sk_xxx (private)");if(typeof window!="undefined"&&n.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: ${t.docsUrl}/server-side-sessions`);if(!n.returnUrl)throw new Error("returnUrl is required");if(n.returnUrl.length>U)throw new Error(`returnUrl exceeds maximum length of ${U} characters`);let e=Ut(),r=x(n.returnUrl,e,t.brandName);if(!r.isValid)throw new Error(`returnUrl validation failed: ${r.error}`);if(n.cancelUrl){if(n.cancelUrl.length>U)throw new Error(`cancelUrl exceeds maximum length of ${U} characters`);let i=x(n.cancelUrl,e,t.brandName);if(!i.isValid)throw new Error(`cancelUrl validation failed: ${i.error}`)}if(n.defaultChallengeAge!==void 0){if(n.defaultChallengeAge<nt)throw new Error(`defaultChallengeAge must be at least ${nt}`);if(n.defaultChallengeAge>rt)throw new Error(`defaultChallengeAge cannot exceed ${rt}`)}if(n.defaultVerificationMode&&!["L1","L2"].includes(n.defaultVerificationMode))throw new Error("defaultVerificationMode must be L1 or L2");if(n.mode&&!["redirect","new-tab"].includes(n.mode))throw new Error("mode must be redirect or new-tab");if(n.newTabTarget&&!["popup","tab"].includes(n.newTabTarget))throw new Error("newTabTarget must be popup or tab")}function Ut(){if(typeof window=="undefined")return"production";let n=window.location.hostname;return n.includes("staging")||n.includes("stage")?"staging":"production"}async function st(n,t,e,r="SDK"){let{createSignedState:i}=await Promise.resolve().then(()=>(tt(),Q));return i(n,e)}var nt,rt,U,it,et,St,L=A(()=>{"use strict";R();nt=25,rt=150,U=2048,it=128,et=6e5,St=/^(pk_|sk_)[a-zA-Z0-9_]+$/});var Ct={};F(Ct,{PrivateAV:()=>m,VERSION:()=>ct,default:()=>bt});function q(){crypto.randomUUID||(crypto.randomUUID=function(){let n=new Uint8Array(16);crypto.getRandomValues(n),n[6]=n[6]&15|64,n[8]=n[8]&63|128;let t=Array.from(n).map(e=>e.toString(16).padStart(2,"0")).join("");return[t.slice(0,8),t.slice(8,12),t.slice(12,16),t.slice(16,20),t.slice(20,32)].join("-")})}function j(n="SDK"){let t=[];if(!window.crypto||!window.crypto.getRandomValues)throw new Error(`${n} requires Web Crypto API support`);if(!window.crypto.subtle)throw new Error(`${n} 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(`${n} Browser Compatibility:`,t.join("; "))}L();function E(n,t){let e=t.verifyUiUrl;if(!e||!e.startsWith("https://"))throw new Error(`HTTPS required for ${n} environment`);return e}function Et(n,t){let e=t.apiUrl;if(!e||!e.startsWith("https://"))throw new Error(`HTTPS required for API URLs in ${n} environment`);return e}function at(n,t,e="SDK"){let r=window.location.protocol==="https:";switch(n){case"production":r||console.warn(`${e} Warning: HTTPS recommended for production environment`);break;case"staging":r||console.warn(`${e} Warning: HTTPS strongly recommended in staging environment`);break}try{E(n,t),Et(n,t)}catch(i){let o=i instanceof Error?i.message:String(i);throw new Error(`Environment configuration validation failed: ${o}`)}}R();var C=class C{constructor(t,e,r){this.popupWindow=null;this.messageListener=null;this.popupMonitorInterval=null;this.unloadListener=null;this.isVerificationInProgress=!1;this.currentSessionId=null;this.hasReceivedResult=!1;this.lastVerifyUrl=null;this.lastSessionToken=null;this.lastExternalUserId=null;this.lastSandboxMode=null;this.temporaryHandoffToken=null;this.brandUrls=e,this.brandConstants=r,ot(t,{brandName:this.brandConstants.name,docsUrl:this.brandConstants.docsUrl});let i=t.environment||this.detectEnvironment();i!=="staging"&&i!=="production"&&(console.warn(`${this.brandConstants.name} SDK: Unknown environment '${i}', defaulting to 'production'`),i="production"),this.config=S(u({},t),{environment:i,mode:t.mode||"redirect",newTabTarget:t.newTabTarget||"popup"}),at(this.config.environment,this.getUrlConfig(),this.brandConstants.name),X(this.config.environment,this.brandConstants.name),p("SDK_INITIALIZED",{environment:this.config.environment,mode:this.config.mode,origin:window.location.origin,protocol:window.location.protocol,hostname:window.location.hostname},this.brandConstants.name),this.setupAutoCleanup()}async verify(t={}){var i,o,s,a,l,d;let e=this.isPublicKey(),r;if(e)r=await this.createInternalSession(t);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(!r)throw new Error("Failed to obtain sessionId from server");if(this.isVerificationInProgress){let c=new Error(`Verification already in progress for session ${(i=this.currentSessionId)==null?void 0:i.substring(0,8)}...`);throw p("RACE_CONDITION_PREVENTED",{currentSession:((o=this.currentSessionId)==null?void 0:o.substring(0,8))+"...",attemptedSession:"new-session-attempt",origin:window.location.origin},this.brandConstants.name),(a=(s=this.config).onError)==null||a.call(s,c),c}this.isVerificationInProgress=!0,this.currentSessionId=r,this.lastExternalUserId=t.externalUserId||null;try{let c=`${this.config.apiKey}:${window.location.origin}`;if(!Y.isAllowed(c,this.brandConstants.name)){let f=new Error("Too many verification attempts. Please wait before trying again.");throw p("RATE_LIMIT_EXCEEDED",{apiKey:this.config.apiKey.substring(0,8)+"...",origin:window.location.origin,sessionId:r?r.substring(0,8)+"...":"undefined"},this.brandConstants.name),(d=(l=this.config).onError)==null||d.call(l,f),f}let g=await this.buildVerificationUrl(t,r);p("VERIFICATION_INITIATED",{environment:this.config.environment,mode:this.config.mode,sessionId:r?r.substring(0,8)+"...":"undefined",origin:window.location.origin},this.brandConstants.name),this.config.mode==="new-tab"?this.openNewTab(g,r):(this.unlockVerification(),this.redirect(g))}catch(c){throw this.unlockVerification(),c}}async buildVerificationUrl(t,e){var d;let r=this.config.verifyUrl||E(this.config.environment,this.getUrlConfig()),i=t.challengeAge!==void 0,o=t.verificationMode!==void 0,s=i||o,a=await st({merchantId:this.config.apiKey,sessionId:e,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:t.challengeAge||this.config.defaultChallengeAge,verificationMode:t.verificationMode||this.config.defaultVerificationMode,hasOverrides:s,externalUserId:t.externalUserId,timestamp:Date.now(),apiUrl:this.getPortalApiUrl(),engineUrl:this.getEngineUrl(),wsUrl:this.getWebSocketUrl(),environment:this.config.environment,features:{testMode:!1,warmupPeriodMs:500,qualityThreshold:.6,sandboxMode:(d=this.lastSandboxMode)!=null?d:!1},handoffToken:this.temporaryHandoffToken||void 0,sessionToken:this.lastSessionToken||void 0,verifyUrl:this.lastVerifyUrl||void 0},this.config.environment,this.getHmacSecret(),this.brandConstants.name);if(this.lastVerifyUrl)try{let c=this.applyLocalVerifyOverride(this.lastVerifyUrl),g=new URL(c);return g.searchParams.set("state",a),g.searchParams.set("mode",this.config.mode),t.skipIntro&&g.searchParams.set("skip_intro","true"),t.autoReturn&&g.searchParams.set("auto_return","true"),g.toString()}catch(c){}let l=new URLSearchParams({state:a,sessionId:e,mode:this.config.mode});return t.skipIntro&&l.set("skip_intro","true"),t.autoReturn&&l.set("auto_return","true"),`${r}/?${l.toString()}`}redirect(t){window.location.href=t}openNewTab(t,e){var l,d;if(this.cleanup(),this.hasReceivedResult=!1,this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),(this.config.newTabTarget||"popup")==="tab"?this.popupWindow=window.open(t,"_blank"):this.popupWindow=window.open(t,this.brandConstants.popupName,"width=600,height=700"),!this.popupWindow){(d=(l=this.config).onError)==null||d.call(l,new Error("Failed to open verification window. Please check popup blocker settings."));return}let i=this.getTrustedOrigins(),o=this.getAllowedCustomOrigins(t),s=this.brandConstants.messageType,a=this.brandConstants.legacyMessageType;this.messageListener=c=>{var D,M,_,O,$,N,K;let g=(D=c.data)==null?void 0:D.type;if(!g||typeof g!="string"||!(a?[s,a]:[s]).includes(g))return;if(!G(c,i,o,this.brandConstants.name)){p("POSTMESSAGE_ORIGIN_BLOCKED",{origin:c.origin,environment:this.config.environment,expectedOrigins:`${this.brandConstants.name} trusted origins for ${this.config.environment}`,messageType:(M=c.data)==null?void 0:M.type},this.brandConstants.name);return}let I=J(c,e,s,a);if(!I.isValid){p("POSTMESSAGE_VALIDATION_FAILED",{error:I.error,origin:c.origin,sessionId:e.substring(0,8)+"...",messageType:(_=c.data)==null?void 0:_.type},this.brandConstants.name);return}let h=c.data.status;if(h==="cancelled"){this.handleCancellation(e,"postmessage");return}let w={sessionId:c.data.sessionId,status:h,timestamp:c.data.timestamp,externalUserId:c.data.externalUserId};this.hasReceivedResult=!0,p("VERIFICATION_COMPLETED",{status:w.status,sessionId:e.substring(0,8)+"...",origin:c.origin},this.brandConstants.name),this.cleanup({closePopup:!1}),this.unlockVerification(),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null),w.status==="verified"?($=(O=this.config).onComplete)==null||$.call(O,w):(K=(N=this.config).onError)==null||K.call(N,new Error(`Verification failed: ${w.status}`))},window.addEventListener("message",this.messageListener),this.popupMonitorInterval=setInterval(()=>{this.popupWindow&&this.popupWindow.closed&&(p("POPUP_CLOSED_BY_USER",{sessionId:e.substring(0,8)+"...",environment:this.config.environment},this.brandConstants.name),this.hasReceivedResult||this.handleCancellation(e,"popup-closed"))},500)}setupAutoCleanup(){if(this.unloadListener=()=>{p("SDK_AUTO_CLEANUP",{environment:this.config.environment,trigger:"page_unload"},this.brandConstants.name),this.cleanup(),this.unlockVerification()},window.addEventListener("beforeunload",this.unloadListener),window.addEventListener("pagehide",this.unloadListener),window.history&&window.history.pushState){let t=window.history.pushState;window.history.pushState=(...e)=>(this.cleanup(),this.unlockVerification(),t.apply(window.history,e))}}detectEnvironment(){let t=window.location.hostname;return t.includes("staging")||t.includes("stage")?"staging":"production"}getEnvironment(){return this.config.environment}unlockVerification(){this.isVerificationInProgress=!1,this.currentSessionId=null,p("VERIFICATION_UNLOCKED",{environment:this.config.environment,origin:window.location.origin},this.brandConstants.name)}cleanup(t={}){let e=t.closePopup!==!1;this.popupWindow&&(e&&!this.popupWindow.closed&&this.popupWindow.close(),(e||this.popupWindow.closed)&&(this.popupWindow=null)),this.messageListener&&(window.removeEventListener("message",this.messageListener),this.messageListener=null),this.popupMonitorInterval&&(clearInterval(this.popupMonitorInterval),this.popupMonitorInterval=null)}handleCancellation(t,e){if(this.hasReceivedResult)return;this.hasReceivedResult=!0,p("VERIFICATION_CANCELLED",{source:e,sessionId:t.substring(0,8)+"...",environment:this.config.environment,origin:window.location.origin},this.brandConstants.name),this.cleanup(),this.unlockVerification();let r=!0;if(this.config.onCancel)try{this.config.onCancel()===!1&&(r=!1)}catch(i){p("CANCEL_CALLBACK_FAILED",{error:i instanceof Error?i.message:String(i),sessionId:t.substring(0,8)+"..."},this.brandConstants.name)}r&&this.redirectToCancelUrl(t)}redirectToCancelUrl(t){if(this.config.cancelUrl)try{let e=decodeURIComponent(this.config.cancelUrl),r=new URL(e);r.searchParams.set("sessionId",t),r.searchParams.set("status","cancelled"),r.searchParams.set("timestamp",Date.now().toString()),this.lastExternalUserId&&r.searchParams.set("externalUserId",this.lastExternalUserId),window.location.href=r.toString()}catch(e){p("CANCEL_REDIRECT_FAILED",{error:e instanceof Error?e.message:String(e),sessionId:t.substring(0,8)+"..."},this.brandConstants.name)}}removeAutoCleanupListeners(){this.unloadListener&&(window.removeEventListener("beforeunload",this.unloadListener),window.removeEventListener("pagehide",this.unloadListener),this.unloadListener=null)}destroy(){p("SDK_DESTROYED",{environment:this.config.environment,origin:window.location.origin},this.brandConstants.name),this.cleanup(),this.unlockVerification(),this.removeAutoCleanupListeners()}getPortalApiUrl(){return this.config.apiUrl?this.config.apiUrl:this.getUrlConfig().apiUrl}getEngineUrl(){return this.getUrlConfig().engineUrl}getWebSocketUrl(){return this.getUrlConfig().wsUrl}isPublicKey(){return this.config.apiKey.startsWith("pk_")}async createInternalSession(t){var e,r;try{let i=this.getPortalApiUrl(),o=await fetch(`${i}/api/v1/sessions/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`},body:JSON.stringify({merchantId:this.config.apiKey,returnUrl:this.config.returnUrl,cancelUrl:this.config.cancelUrl,challengeAge:t.challengeAge,verificationMode:t.verificationMode,merchantName:document.title||window.location.hostname,externalUserId:t.externalUserId})});if(!o.ok){let l=await o.json().catch(()=>({})),d=l==null?void 0:l.code;throw this.isBillingBlockError(d)&&this.openBillingBlockPage(d,l==null?void 0:l.portalUrl),new Error(`Failed to create session: ${o.status} ${o.statusText}. ${l.message||""}`)}let s=await o.json(),a=s.sessionId;if(!a)throw new Error("Server did not return a sessionId");return s.verifyUrl&&(this.lastVerifyUrl=s.verifyUrl),s.sessionToken&&(this.lastSessionToken=s.sessionToken),s.handoffToken&&(this.temporaryHandoffToken=s.handoffToken),typeof s.sandboxMode=="boolean"?this.lastSandboxMode=s.sandboxMode:this.lastSandboxMode=null,p("INTERNAL_SESSION_CREATED",{sessionId:a.substring(0,8)+"...",environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),a}catch(i){let o=i instanceof Error?i.message:String(i);throw p("INTERNAL_SESSION_FAILED",{error:o,environment:this.config.environment,apiKeyType:"public"},this.brandConstants.name),(r=(e=this.config).onError)==null||r.call(e,i),new Error(`Failed to create verification session: ${o}`)}}isBillingBlockError(t){return t==="SUBSCRIPTION_REQUIRED"||t==="PLAN_LIMIT_REACHED"||t==="SANDBOX_LIMIT_REACHED"}openBillingBlockPage(t,e){var r,i,o,s;try{let a=this.config.verifyUrl||E(this.config.environment,this.getUrlConfig()),l=this.applyLocalVerifyOverride(a),d=new URL(l);if(d.searchParams.set("blocked",t),e&&d.searchParams.set("portalUrl",e),this.config.mode==="new-tab"){((this.config.newTabTarget||"popup")==="tab"?window.open(d.toString(),"_blank"):window.open(d.toString(),this.brandConstants.popupName,"width=600,height=700"))||(i=(r=this.config).onError)==null||i.call(r,new Error("Failed to open billing notice window. Please check popup blocker settings."));return}this.redirect(d.toString())}catch(a){let l=a instanceof Error?a.message:String(a);(s=(o=this.config).onError)==null||s.call(o,new Error(`Failed to open billing notice: ${l}`))}}getUrlConfig(){return this.brandUrls[this.config.environment]||this.brandUrls.production}getTrustedOrigins(){return this.getUrlConfig().trustedOrigins}getAllowedCustomOrigins(t){let e=new Set,r=this.getLocalOrigin(this.config.verifyUrl||null),i=this.getLocalOrigin(t||null);return r&&e.add(r),i&&e.add(i),Array.from(e)}getLocalOrigin(t){if(!t)return null;try{let e=new URL(t);if(C.LOCAL_HOSTNAMES.has(e.hostname))return e.origin}catch(e){return null}return null}applyLocalVerifyOverride(t){let e=this.getLocalOrigin(this.config.verifyUrl||null);if(!e)return t;try{let r=new URL(e),i=new URL(t);return i.protocol=r.protocol,i.host=r.host,i.toString()}catch(r){return t}}getHmacSecret(){return this.config.environment==="staging"?this.brandConstants.hmacSecretStaging:this.brandConstants.hmacSecretProd}};C.LOCAL_HOSTNAMES=new Set(["localhost","127.0.0.1","::1"]);var b=C;var lt={production:{apiUrl:"https://api.privateav.com",verifyUiUrl:"https://verify.privateav.com",engineUrl:"https://engine.privateav.com",wsUrl:"wss://engine.privateav.com/api/websocket/stream",trustedOrigins:["https://verify.privateav.com","https://portal.privateav.com","https://api.privateav.com"]},staging:{apiUrl:"https://api.staging.privateav.com",verifyUiUrl:"https://verify.staging.privateav.com",engineUrl:"https://engine.staging.privateav.com",wsUrl:"wss://engine.staging.privateav.com/api/websocket/stream",trustedOrigins:["https://verify.staging.privateav.com","https://portal.staging.privateav.com","https://api.staging.privateav.com"]}},k={name:"PrivateAV",hmacSecretProd:"privateav-prod-hmac-2025",hmacSecretStaging:"privateav-stage-hmac-2025",messageType:"privateav:verification:complete",legacyMessageType:"privateav-verification",popupName:"privateav-verify",docsUrl:"https://docs.privateav.com"};var m=class extends b{constructor(t){super(t,lt,k)}},ct="3.4.9";m.VERSION=ct;typeof window!="undefined"&&(q(),j(`${k.name} SDK`));var bt=m;return ht(Ct);})();
3
3
  if(typeof PrivateAVSDK !== "undefined" && PrivateAVSDK.PrivateAV) { window.PrivateAV = PrivateAVSDK.PrivateAV; window.PrivateAV.VERSION = PrivateAVSDK.VERSION; }
package/types/base.d.ts CHANGED
@@ -12,9 +12,8 @@ export interface SDKConfig {
12
12
  */
13
13
  returnUrl: string;
14
14
  /**
15
- * URL to redirect to if user cancels verification
16
- * @deprecated This field is no longer used. All redirects go to returnUrl with a status parameter.
17
- * Kept for backwards compatibility but will be ignored.
15
+ * URL to redirect to if user cancels verification in new-tab mode
16
+ * Optional; cancellation redirects append `status=cancelled` and `sessionId`.
18
17
  */
19
18
  cancelUrl?: string;
20
19
  /**
@@ -27,6 +26,11 @@ export interface SDKConfig {
27
26
  * @default 'redirect'
28
27
  */
29
28
  mode?: 'redirect' | 'new-tab';
29
+ /**
30
+ * New-tab target behavior (new-tab mode only)
31
+ * @default 'popup'
32
+ */
33
+ newTabTarget?: 'popup' | 'tab';
30
34
  /**
31
35
  * Default challenge age (minimum 25)
32
36
  * Can be overridden per verification
@@ -51,8 +55,9 @@ export interface SDKConfig {
51
55
  onComplete?: (result: VerificationResult) => void;
52
56
  /**
53
57
  * Callback when user cancels (new-tab mode only)
58
+ * Return false to suppress automatic cancelUrl redirect.
54
59
  */
55
- onCancel?: () => void;
60
+ onCancel?: () => boolean | void;
56
61
  /**
57
62
  * Callback for errors
58
63
  */
@@ -100,6 +105,10 @@ export interface VerificationResult {
100
105
  * Full details available via server-side API
101
106
  */
102
107
  status: 'verified' | 'failed';
108
+ /**
109
+ * Timestamp when verification completed (milliseconds since epoch)
110
+ */
111
+ timestamp?: number;
103
112
  /**
104
113
  * External user identifier if provided during verification
105
114
  */
@@ -109,7 +118,7 @@ export interface StatePayload {
109
118
  merchantId: string;
110
119
  sessionId: string;
111
120
  returnUrl: string;
112
- /** @deprecated No longer used - all redirects use returnUrl with status parameter */
121
+ /** Optional cancel redirect for new-tab abandonment */
113
122
  cancelUrl?: string;
114
123
  challengeAge?: number;
115
124
  verificationMode?: 'L1' | 'L2';
@@ -124,6 +133,7 @@ export interface StatePayload {
124
133
  testMode: boolean;
125
134
  warmupPeriodMs: number;
126
135
  qualityThreshold: number;
136
+ sandboxMode?: boolean;
127
137
  };
128
138
  handoffToken?: string;
129
139
  sessionToken?: string;
@@ -132,8 +142,12 @@ export interface StatePayload {
132
142
  export interface SessionValidationResponse {
133
143
  sessionId: string;
134
144
  merchantId: string;
135
- status: 'verified' | 'failed';
145
+ status: 'verified' | 'failed' | 'cancelled';
136
146
  verified: boolean;
147
+ accessGranted?: boolean;
148
+ /**
149
+ * @deprecated This field is no longer provided and will always be undefined.
150
+ */
137
151
  estimatedAge?: number;
138
152
  challengeAge: number;
139
153
  verificationMode: 'L1' | 'L2';
@@ -24,7 +24,7 @@ export declare function enforceHTTPS(environment: 'production' | 'staging', logL
24
24
  /**
25
25
  * Validate URL security for return/cancel URLs
26
26
  */
27
- export declare function validateReturnUrl(url: string, environment: 'production' | 'staging', logLabel?: string): {
27
+ export declare function validateReturnUrl(url: string, environment: 'production' | 'staging', _logLabel?: string): {
28
28
  isValid: boolean;
29
29
  error?: string;
30
30
  };
@@ -14,5 +14,5 @@ export interface ValidationContext {
14
14
  export declare function validateConfig(config: SDKConfig, context: ValidationContext): void;
15
15
  export declare function validateSessionId(sessionId: string): void;
16
16
  export declare function validateChallengeAge(age?: number): void;
17
- export declare function generateState(payload: StatePayload, environment: 'production' | 'staging', hmacSecret: string, logLabel?: string): Promise<string>;
17
+ export declare function generateState(payload: StatePayload, environment: 'production' | 'staging', hmacSecret: string, _logLabel?: string): Promise<string>;
18
18
  export declare function parseState(state: string, environment: 'production' | 'staging', hmacSecret: string, logLabel?: string): Promise<StatePayload | null>;