@privateav/sdk 3.4.3 → 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 +20 -7
- package/brands/privateav/index.d.ts +2 -3
- package/core/VerificationSDK.d.ts +11 -0
- package/index.js +213 -30
- package/package.json +1 -1
- package/privateav.min.js +2 -2
- package/sdk.min.js +2 -2
- package/types/base.d.ts +20 -6
- package/utils/security.d.ts +1 -1
- package/utils/validation.d.ts +1 -1
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,7 +154,7 @@ 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.
|
|
157
|
+
`https://api.safepassage.live/api/v1/sessions/${sessionId}`,
|
|
145
158
|
{
|
|
146
159
|
headers: {
|
|
147
160
|
'Authorization': `Bearer ${process.env.PRIVATEAV_SECRET_KEY}`
|
|
@@ -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.
|
|
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.
|
|
343
|
-
- [API Reference](https://docs.
|
|
344
|
-
- [Dashboard](https://portal.
|
|
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
|
|
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.
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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.
|
|
641
|
-
|
|
642
|
-
this.
|
|
643
|
-
|
|
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
|
-
|
|
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: (
|
|
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: (
|
|
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
|
|
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
|
-
(
|
|
734
|
+
(_e = (_d = this.config).onComplete) == null ? void 0 : _e.call(_d, result);
|
|
700
735
|
} else {
|
|
701
|
-
(
|
|
702
|
-
|
|
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.
|
|
716
|
-
|
|
717
|
-
|
|
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
|
-
|
|
776
|
-
|
|
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.
|
|
1132
|
+
var VERSION = "3.4.9";
|
|
950
1133
|
PrivateAV.VERSION = VERSION;
|
|
951
1134
|
if (typeof window !== "undefined") {
|
|
952
1135
|
setupPolyfills();
|
package/package.json
CHANGED
package/privateav.min.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
/* PrivateAV SDK v3.4.
|
|
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
|
-
"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
|
-
*
|
|
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
|
-
/**
|
|
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';
|
package/utils/security.d.ts
CHANGED
|
@@ -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',
|
|
27
|
+
export declare function validateReturnUrl(url: string, environment: 'production' | 'staging', _logLabel?: string): {
|
|
28
28
|
isValid: boolean;
|
|
29
29
|
error?: string;
|
|
30
30
|
};
|
package/utils/validation.d.ts
CHANGED
|
@@ -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,
|
|
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>;
|