vesant-sdk 1.7.0 → 1.7.1-dev.0520b46
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/dist/{client-B0qhE2kr.d.mts → client-5q4whhGu.d.mts} +2 -2
- package/dist/{client-DF7hlMEz.d.ts → client-BaUekQT8.d.ts} +96 -52
- package/dist/{client-DtH2RLuy.d.ts → client-DUHC-68_.d.ts} +2 -2
- package/dist/{client-DrjgZoH_.d.mts → client-XDEhh9VN.d.mts} +96 -52
- package/dist/{client-BolQlL5e.d.mts → client-q9fN8Hhf.d.mts} +103 -1
- package/dist/{client-BolQlL5e.d.ts → client-q9fN8Hhf.d.ts} +103 -1
- package/dist/compliance/index.d.mts +3 -3
- package/dist/compliance/index.d.ts +3 -3
- package/dist/compliance/index.js +218 -90
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +218 -90
- package/dist/compliance/index.mjs.map +1 -1
- package/dist/decisions/index.d.mts +1 -1
- package/dist/decisions/index.d.ts +1 -1
- package/dist/decisions/index.js +69 -8
- package/dist/decisions/index.js.map +1 -1
- package/dist/decisions/index.mjs +69 -8
- package/dist/decisions/index.mjs.map +1 -1
- package/dist/geolocation/index.d.mts +68 -7
- package/dist/geolocation/index.d.ts +68 -7
- package/dist/geolocation/index.js +223 -90
- package/dist/geolocation/index.js.map +1 -1
- package/dist/geolocation/index.mjs +220 -91
- package/dist/geolocation/index.mjs.map +1 -1
- package/dist/index-C3zUKydT.d.mts +264 -0
- package/dist/index-DSDgS09k.d.ts +264 -0
- package/dist/index.d.mts +27 -10
- package/dist/index.d.ts +27 -10
- package/dist/index.js +418 -126
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +409 -127
- package/dist/index.mjs.map +1 -1
- package/dist/kyc/core.d.mts +2 -2
- package/dist/kyc/core.d.ts +2 -2
- package/dist/kyc/core.js +102 -21
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +102 -22
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +154 -30
- package/dist/kyc/index.d.ts +154 -30
- package/dist/kyc/index.js +102 -21
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +102 -22
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +14 -3
- package/dist/react.d.ts +14 -3
- package/dist/react.js +317 -68
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +317 -68
- package/dist/react.mjs.map +1 -1
- package/dist/risk-profile/index.d.mts +1 -1
- package/dist/risk-profile/index.d.ts +1 -1
- package/dist/risk-profile/index.js +69 -8
- package/dist/risk-profile/index.js.map +1 -1
- package/dist/risk-profile/index.mjs +69 -8
- package/dist/risk-profile/index.mjs.map +1 -1
- package/dist/scores/index.d.mts +1 -1
- package/dist/scores/index.d.ts +1 -1
- package/dist/scores/index.js +69 -8
- package/dist/scores/index.js.map +1 -1
- package/dist/scores/index.mjs +69 -8
- package/dist/scores/index.mjs.map +1 -1
- package/dist/tax/index.d.mts +2 -2
- package/dist/tax/index.d.ts +2 -2
- package/dist/tax/index.js +74 -10
- package/dist/tax/index.js.map +1 -1
- package/dist/tax/index.mjs +74 -10
- package/dist/tax/index.mjs.map +1 -1
- package/dist/webhooks/index.d.mts +2 -189
- package/dist/webhooks/index.d.ts +2 -189
- package/dist/webhooks/index.js +85 -21
- package/dist/webhooks/index.js.map +1 -1
- package/dist/webhooks/index.mjs +85 -22
- package/dist/webhooks/index.mjs.map +1 -1
- package/package.json +4 -1
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,59 @@
|
|
|
1
|
+
// src/core/auth.ts
|
|
2
|
+
var StaticApiKeyProvider = class {
|
|
3
|
+
constructor(apiKey) {
|
|
4
|
+
this.apiKey = apiKey;
|
|
5
|
+
}
|
|
6
|
+
async getCredential() {
|
|
7
|
+
return this.apiKey ? { scheme: "Bearer", token: this.apiKey } : null;
|
|
8
|
+
}
|
|
9
|
+
canRefresh() {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var RefreshingTokenProvider = class {
|
|
14
|
+
constructor(opts) {
|
|
15
|
+
this.opts = opts;
|
|
16
|
+
this.cached = null;
|
|
17
|
+
this.inFlight = null;
|
|
18
|
+
this.skewMs = opts.skewMs ?? 3e4;
|
|
19
|
+
this.scheme = opts.scheme ?? "Bearer";
|
|
20
|
+
this.now = opts.now ?? (() => Date.now());
|
|
21
|
+
}
|
|
22
|
+
canRefresh() {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
async getCredential(ctx) {
|
|
26
|
+
const forceRefresh = ctx?.forceRefresh ?? false;
|
|
27
|
+
if (!forceRefresh && this.cached && !this.isExpiring(this.cached)) {
|
|
28
|
+
return { scheme: this.scheme, token: this.cached.token };
|
|
29
|
+
}
|
|
30
|
+
const fresh = await this.refresh();
|
|
31
|
+
return { scheme: this.scheme, token: fresh.token };
|
|
32
|
+
}
|
|
33
|
+
isExpiring(token) {
|
|
34
|
+
if (token.expiresAt === void 0) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return this.now() >= token.expiresAt - this.skewMs;
|
|
38
|
+
}
|
|
39
|
+
refresh() {
|
|
40
|
+
if (this.inFlight) {
|
|
41
|
+
return this.inFlight;
|
|
42
|
+
}
|
|
43
|
+
this.inFlight = (async () => {
|
|
44
|
+
try {
|
|
45
|
+
const fresh = await this.opts.fetchToken();
|
|
46
|
+
this.cached = fresh;
|
|
47
|
+
this.opts.onRotate?.({ expiresAt: fresh.expiresAt });
|
|
48
|
+
return fresh;
|
|
49
|
+
} finally {
|
|
50
|
+
this.inFlight = null;
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
return this.inFlight;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
1
57
|
// src/core/errors.ts
|
|
2
58
|
var VesantError = class _VesantError extends Error {
|
|
3
59
|
constructor(message, code, statusCode, details) {
|
|
@@ -240,7 +296,7 @@ var noopLogger = {
|
|
|
240
296
|
};
|
|
241
297
|
|
|
242
298
|
// src/core/version.ts
|
|
243
|
-
var SDK_VERSION = "1.7.
|
|
299
|
+
var SDK_VERSION = "1.7.1";
|
|
244
300
|
|
|
245
301
|
// src/shared/browser-utils.ts
|
|
246
302
|
function generateUUID() {
|
|
@@ -322,6 +378,12 @@ var BaseClient = class {
|
|
|
322
378
|
if (!config.tenantId?.trim()) {
|
|
323
379
|
throw new ValidationError("tenantId is required and must be a non-empty string", ["tenantId"]);
|
|
324
380
|
}
|
|
381
|
+
if (typeof fetch === "undefined") {
|
|
382
|
+
throw new VesantError(
|
|
383
|
+
"The Vesant SDK requires a global fetch (Node.js >= 18). Upgrade Node or provide a fetch polyfill.",
|
|
384
|
+
"RUNTIME_UNSUPPORTED"
|
|
385
|
+
);
|
|
386
|
+
}
|
|
325
387
|
this.interceptors = config.interceptors || [];
|
|
326
388
|
this.logger = config.logger || createConsoleLogger();
|
|
327
389
|
let environment = config.environment;
|
|
@@ -340,11 +402,12 @@ var BaseClient = class {
|
|
|
340
402
|
environment,
|
|
341
403
|
headers: config.headers || {},
|
|
342
404
|
timeout: config.timeout || 1e4,
|
|
343
|
-
retries: config.retries
|
|
405
|
+
retries: config.retries ?? 3,
|
|
344
406
|
debug: config.debug || false,
|
|
345
407
|
interceptors: this.interceptors,
|
|
346
408
|
logger: this.logger
|
|
347
409
|
};
|
|
410
|
+
this.authProvider = config.authProvider ?? (apiKey ? new StaticApiKeyProvider(apiKey) : void 0);
|
|
348
411
|
if (config.circuitBreaker) {
|
|
349
412
|
this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
|
|
350
413
|
}
|
|
@@ -352,6 +415,19 @@ var BaseClient = class {
|
|
|
352
415
|
this.rateLimitTracker = new RateLimitTracker();
|
|
353
416
|
}
|
|
354
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* Resolve the Idempotency-Key for a request. Mutating methods (POST/PUT/PATCH)
|
|
420
|
+
* get the caller-supplied key or a freshly minted UUID; non-mutating methods get
|
|
421
|
+
* none. requestWithRetry resolves this once and injects it so every retry reuses
|
|
422
|
+
* one key (letting the server dedup replays); a direct request() resolves per call.
|
|
423
|
+
*/
|
|
424
|
+
resolveIdempotencyKey(method, requestOptions) {
|
|
425
|
+
const isMutating = ["POST", "PUT", "PATCH"].includes((method || "GET").toUpperCase());
|
|
426
|
+
if (!isMutating) {
|
|
427
|
+
return void 0;
|
|
428
|
+
}
|
|
429
|
+
return requestOptions?.idempotencyKey ?? generateUUID();
|
|
430
|
+
}
|
|
355
431
|
/**
|
|
356
432
|
* Make an HTTP request with timeout and error handling
|
|
357
433
|
*/
|
|
@@ -377,15 +453,20 @@ var BaseClient = class {
|
|
|
377
453
|
...this.config.headers,
|
|
378
454
|
...options.headers || {}
|
|
379
455
|
};
|
|
380
|
-
|
|
381
|
-
|
|
456
|
+
const credential = await this.authProvider?.getCredential({
|
|
457
|
+
tenantId: this.config.tenantId,
|
|
458
|
+
requestId
|
|
459
|
+
});
|
|
460
|
+
if (credential) {
|
|
461
|
+
headers["Authorization"] = `${credential.scheme ?? "Bearer"} ${credential.token}`;
|
|
382
462
|
}
|
|
383
463
|
if (this.config.environment === "sandbox") {
|
|
384
464
|
headers["X-Sandbox"] = "true";
|
|
385
465
|
}
|
|
386
466
|
const method = (options.method || "GET").toUpperCase();
|
|
387
|
-
|
|
388
|
-
|
|
467
|
+
const idempotencyKey = this.resolveIdempotencyKey(method, requestOptions);
|
|
468
|
+
if (idempotencyKey) {
|
|
469
|
+
headers["Idempotency-Key"] = idempotencyKey;
|
|
389
470
|
}
|
|
390
471
|
const controller = new AbortController();
|
|
391
472
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
@@ -406,10 +487,28 @@ var BaseClient = class {
|
|
|
406
487
|
if (this.config.debug) {
|
|
407
488
|
this.logger.debug(`${finalOptions.method || "GET"} ${endpoint}`);
|
|
408
489
|
}
|
|
409
|
-
|
|
490
|
+
let response = await fetch(url, {
|
|
410
491
|
...finalOptions,
|
|
411
492
|
signal: controller.signal
|
|
412
493
|
});
|
|
494
|
+
if (response.status === 401 && this.authProvider?.canRefresh?.()) {
|
|
495
|
+
const refreshed = await this.authProvider.getCredential({
|
|
496
|
+
tenantId: this.config.tenantId,
|
|
497
|
+
requestId,
|
|
498
|
+
forceRefresh: true
|
|
499
|
+
});
|
|
500
|
+
if (refreshed) {
|
|
501
|
+
this.config.onCredentialRotated?.({ reason: "unauthorized" });
|
|
502
|
+
finalOptions = {
|
|
503
|
+
...finalOptions,
|
|
504
|
+
headers: {
|
|
505
|
+
...finalOptions.headers,
|
|
506
|
+
Authorization: `${refreshed.scheme ?? "Bearer"} ${refreshed.token}`
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
response = await fetch(url, { ...finalOptions, signal: controller.signal });
|
|
510
|
+
}
|
|
511
|
+
}
|
|
413
512
|
clearTimeout(timeoutId);
|
|
414
513
|
if (this.rateLimitTracker) {
|
|
415
514
|
this.rateLimitTracker.updateFromHeaders(response.headers);
|
|
@@ -498,9 +597,11 @@ var BaseClient = class {
|
|
|
498
597
|
*/
|
|
499
598
|
async requestWithRetry(endpoint, options = {}, serviceURL, retries = this.config.retries, requestOptions) {
|
|
500
599
|
let lastError;
|
|
600
|
+
const idempotencyKey = this.resolveIdempotencyKey(options.method || "GET", requestOptions);
|
|
601
|
+
const attemptOptions = idempotencyKey ? { ...requestOptions, idempotencyKey } : requestOptions;
|
|
501
602
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
502
603
|
try {
|
|
503
|
-
return await this.request(endpoint, options, serviceURL,
|
|
604
|
+
return await this.request(endpoint, options, serviceURL, attemptOptions);
|
|
504
605
|
} catch (error) {
|
|
505
606
|
lastError = error instanceof Error ? error : new Error("Unknown error");
|
|
506
607
|
if (requestOptions?.signal?.aborted) {
|
|
@@ -597,6 +698,9 @@ var BaseClient = class {
|
|
|
597
698
|
if (config.interceptors) {
|
|
598
699
|
this.interceptors = config.interceptors;
|
|
599
700
|
}
|
|
701
|
+
if (config.authProvider !== void 0 || config.apiKey !== void 0) {
|
|
702
|
+
this.authProvider = this.config.authProvider ?? (this.config.apiKey ? new StaticApiKeyProvider(this.config.apiKey) : void 0);
|
|
703
|
+
}
|
|
600
704
|
}
|
|
601
705
|
/**
|
|
602
706
|
* Get current configuration (readonly)
|
|
@@ -624,8 +728,69 @@ var BaseClient = class {
|
|
|
624
728
|
}
|
|
625
729
|
};
|
|
626
730
|
|
|
731
|
+
// src/core/pagination.ts
|
|
732
|
+
async function* paginate(fetchPage) {
|
|
733
|
+
let page = 1;
|
|
734
|
+
let totalPages;
|
|
735
|
+
do {
|
|
736
|
+
const response = await fetchPage(page);
|
|
737
|
+
const items = response.data ?? [];
|
|
738
|
+
for (const item of items) {
|
|
739
|
+
yield item;
|
|
740
|
+
}
|
|
741
|
+
totalPages = response.total_pages ?? page;
|
|
742
|
+
if (items.length === 0) {
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
page += 1;
|
|
746
|
+
} while (page <= totalPages);
|
|
747
|
+
}
|
|
748
|
+
async function collectAll(fetchPage) {
|
|
749
|
+
const items = [];
|
|
750
|
+
for await (const item of paginate(fetchPage)) {
|
|
751
|
+
items.push(item);
|
|
752
|
+
}
|
|
753
|
+
return items;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// src/core/dedup.ts
|
|
757
|
+
var InMemoryDedupStore = class {
|
|
758
|
+
constructor(opts = {}) {
|
|
759
|
+
this.expiryById = /* @__PURE__ */ new Map();
|
|
760
|
+
this.maxEntries = opts.maxEntries ?? 1e3;
|
|
761
|
+
this.now = opts.now ?? (() => Date.now());
|
|
762
|
+
}
|
|
763
|
+
async seen(id) {
|
|
764
|
+
return this.expiryById.has(id);
|
|
765
|
+
}
|
|
766
|
+
async mark(id, ttlMs) {
|
|
767
|
+
const now = this.now();
|
|
768
|
+
this.expiryById.set(id, now + ttlMs);
|
|
769
|
+
if (this.expiryById.size > this.maxEntries) {
|
|
770
|
+
for (const [key, expiresAt] of this.expiryById) {
|
|
771
|
+
if (now > expiresAt) {
|
|
772
|
+
this.expiryById.delete(key);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
|
|
627
779
|
// src/core/webhook-utils.ts
|
|
628
|
-
async function verifyWebhookSignature(payload, signature, secret) {
|
|
780
|
+
async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
|
|
781
|
+
const signed = parseSignedTimestamp(signature);
|
|
782
|
+
if (signed) {
|
|
783
|
+
const tolerance = opts.tolerance ?? 3e5;
|
|
784
|
+
const now = opts.now?.() ?? Date.now();
|
|
785
|
+
if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
|
|
789
|
+
return constantTimeEqual(signed.v1, expected);
|
|
790
|
+
}
|
|
791
|
+
if (opts.allowLegacy === false) {
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
629
794
|
const hexDigest = await computeHmacSha256(payload, secret);
|
|
630
795
|
const expectedPrefixed = `sha256=${hexDigest}`;
|
|
631
796
|
if (signature.startsWith("sha256=")) {
|
|
@@ -633,6 +798,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
|
|
|
633
798
|
}
|
|
634
799
|
return constantTimeEqual(signature, hexDigest);
|
|
635
800
|
}
|
|
801
|
+
function parseSignedTimestamp(signature) {
|
|
802
|
+
if (!signature.includes("t=") || !signature.includes("v1=")) {
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
let t;
|
|
806
|
+
let v1;
|
|
807
|
+
for (const part of signature.split(",")) {
|
|
808
|
+
const eq = part.indexOf("=");
|
|
809
|
+
if (eq === -1) continue;
|
|
810
|
+
const key = part.slice(0, eq).trim();
|
|
811
|
+
const value = part.slice(eq + 1).trim();
|
|
812
|
+
if (key === "t") t = Number(value);
|
|
813
|
+
else if (key === "v1") v1 = value;
|
|
814
|
+
}
|
|
815
|
+
if (t === void 0 || Number.isNaN(t) || !v1) {
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
return { t, v1 };
|
|
819
|
+
}
|
|
636
820
|
async function computeHmacSha256(message, secret) {
|
|
637
821
|
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
638
822
|
const encoder = new TextEncoder();
|
|
@@ -667,27 +851,63 @@ function constantTimeEqual(a, b) {
|
|
|
667
851
|
return result === 0;
|
|
668
852
|
}
|
|
669
853
|
|
|
670
|
-
// src/geolocation/
|
|
671
|
-
var
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
);
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
854
|
+
// src/geolocation/integrity.ts
|
|
855
|
+
var isNil = (v) => v === null || v === void 0;
|
|
856
|
+
function assessGpsIntegrity(input, opts = {}) {
|
|
857
|
+
const flags = [];
|
|
858
|
+
const now = opts.now ?? (() => Date.now());
|
|
859
|
+
const maxAgeMs = opts.maxAgeMs ?? 6e4;
|
|
860
|
+
const minAccuracy = opts.minAccuracyMeters ?? 1;
|
|
861
|
+
const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
|
|
862
|
+
if (isNil(accuracy)) {
|
|
863
|
+
flags.push("accuracy_missing");
|
|
864
|
+
} else if (accuracy === 0) {
|
|
865
|
+
flags.push("accuracy_zero");
|
|
866
|
+
} else if (accuracy < minAccuracy) {
|
|
867
|
+
flags.push("accuracy_implausibly_small");
|
|
868
|
+
}
|
|
869
|
+
const hasMotion = !isNil(heading) || !isNil(speed);
|
|
870
|
+
if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
|
|
871
|
+
flags.push("motion_without_accuracy");
|
|
872
|
+
}
|
|
873
|
+
if (!isNil(altitude) && isNil(altitudeAccuracy)) {
|
|
874
|
+
flags.push("altitude_without_accuracy");
|
|
875
|
+
}
|
|
876
|
+
if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
|
|
877
|
+
flags.push("stale_timestamp");
|
|
878
|
+
}
|
|
879
|
+
if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
|
|
880
|
+
flags.push(`permission_${opts.permissionState}`);
|
|
881
|
+
}
|
|
882
|
+
return { suspicious: flags.length > 0, flags };
|
|
883
|
+
}
|
|
884
|
+
function gpsIntegrityInputFromPosition(position) {
|
|
885
|
+
const c = position.coords;
|
|
886
|
+
return {
|
|
887
|
+
latitude: c.latitude,
|
|
888
|
+
longitude: c.longitude,
|
|
889
|
+
accuracy: c.accuracy,
|
|
890
|
+
altitude: c.altitude,
|
|
891
|
+
altitudeAccuracy: c.altitudeAccuracy,
|
|
892
|
+
heading: c.heading,
|
|
893
|
+
speed: c.speed,
|
|
894
|
+
timestamp: position.timestamp
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
async function probeGeolocationPermission() {
|
|
898
|
+
try {
|
|
899
|
+
if (typeof navigator === "undefined" || !navigator.permissions?.query) {
|
|
900
|
+
return "unavailable";
|
|
901
|
+
}
|
|
902
|
+
const status = await navigator.permissions.query({ name: "geolocation" });
|
|
903
|
+
return status.state;
|
|
904
|
+
} catch {
|
|
905
|
+
return "unavailable";
|
|
687
906
|
}
|
|
688
|
-
const { createHmac } = await import('crypto');
|
|
689
|
-
return createHmac("sha256", key).update(message).digest("hex");
|
|
690
907
|
}
|
|
908
|
+
|
|
909
|
+
// src/geolocation/ciphertext.ts
|
|
910
|
+
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
691
911
|
function getWebGLInfo() {
|
|
692
912
|
if (typeof document === "undefined") return null;
|
|
693
913
|
try {
|
|
@@ -719,6 +939,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
719
939
|
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
720
940
|
return null;
|
|
721
941
|
}
|
|
942
|
+
const permissionState = await probeGeolocationPermission();
|
|
722
943
|
return new Promise((resolve) => {
|
|
723
944
|
navigator.geolocation.getCurrentPosition(
|
|
724
945
|
(position) => {
|
|
@@ -735,6 +956,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
735
956
|
resolve(null);
|
|
736
957
|
return;
|
|
737
958
|
}
|
|
959
|
+
const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
|
|
738
960
|
resolve({
|
|
739
961
|
latitude,
|
|
740
962
|
longitude,
|
|
@@ -743,7 +965,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
743
965
|
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
744
966
|
heading: position.coords.heading ?? void 0,
|
|
745
967
|
speed: position.coords.speed ?? void 0,
|
|
746
|
-
timestamp: position.timestamp
|
|
968
|
+
timestamp: position.timestamp,
|
|
969
|
+
integrity_flags: flags.length ? flags : void 0
|
|
747
970
|
});
|
|
748
971
|
},
|
|
749
972
|
() => {
|
|
@@ -810,6 +1033,9 @@ async function generateCipherText(options, config) {
|
|
|
810
1033
|
);
|
|
811
1034
|
if (location) {
|
|
812
1035
|
locationData = location;
|
|
1036
|
+
if (location.integrity_flags?.length) {
|
|
1037
|
+
warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
|
|
1038
|
+
}
|
|
813
1039
|
} else if (gpsRequiredByConfig) {
|
|
814
1040
|
throw new VesantError(
|
|
815
1041
|
`GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
|
|
@@ -839,15 +1065,7 @@ async function generateCipherText(options, config) {
|
|
|
839
1065
|
};
|
|
840
1066
|
const encoded = encodePayload(payload);
|
|
841
1067
|
const timestamp = now.getTime().toString(36);
|
|
842
|
-
|
|
843
|
-
const hmacKey = options.signingKey || options.apiKey;
|
|
844
|
-
if (hmacKey) {
|
|
845
|
-
const message = `02.${timestamp}.${encoded}`;
|
|
846
|
-
const hmac = await computeHMAC(hmacKey, message);
|
|
847
|
-
cipherText = `${message}.${hmac}`;
|
|
848
|
-
} else {
|
|
849
|
-
cipherText = `01.${timestamp}.${encoded}`;
|
|
850
|
-
}
|
|
1068
|
+
const cipherText = `01.${timestamp}.${encoded}`;
|
|
851
1069
|
return {
|
|
852
1070
|
cipherText,
|
|
853
1071
|
locationCaptured: !!locationData,
|
|
@@ -856,6 +1074,7 @@ async function generateCipherText(options, config) {
|
|
|
856
1074
|
expiresAt: expiry.toISOString()
|
|
857
1075
|
};
|
|
858
1076
|
}
|
|
1077
|
+
var collectDeviceSignals = generateCipherText;
|
|
859
1078
|
function decodeCipherText(cipherText) {
|
|
860
1079
|
try {
|
|
861
1080
|
const parts = cipherText.split(".");
|
|
@@ -883,6 +1102,7 @@ function isCipherTextExpired(cipherText) {
|
|
|
883
1102
|
}
|
|
884
1103
|
|
|
885
1104
|
// src/geolocation/client.ts
|
|
1105
|
+
var DEFAULT_GPS_CONFIG_TTL_MS = 5 * 60 * 1e3;
|
|
886
1106
|
var GeolocationClient = class extends BaseClient {
|
|
887
1107
|
constructor(config) {
|
|
888
1108
|
const baseConfig = {
|
|
@@ -898,6 +1118,8 @@ var GeolocationClient = class extends BaseClient {
|
|
|
898
1118
|
environment: config.environment
|
|
899
1119
|
};
|
|
900
1120
|
super(baseConfig);
|
|
1121
|
+
this.gpsConfigCache = null;
|
|
1122
|
+
this.gpsConfigTtlMs = config.gpsConfigTtlMs ?? DEFAULT_GPS_CONFIG_TTL_MS;
|
|
901
1123
|
}
|
|
902
1124
|
// ============================================================================
|
|
903
1125
|
// IP Verification & Compliance
|
|
@@ -929,8 +1151,8 @@ var GeolocationClient = class extends BaseClient {
|
|
|
929
1151
|
* ```
|
|
930
1152
|
*/
|
|
931
1153
|
async verifyIP(request, requestOptions) {
|
|
932
|
-
if (!request.ip_address
|
|
933
|
-
throw new ValidationError("ip_address
|
|
1154
|
+
if (request.ip_address !== void 0 && !request.ip_address.trim()) {
|
|
1155
|
+
throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
|
|
934
1156
|
}
|
|
935
1157
|
return this.requestWithRetry("/api/v1/geo/verify", {
|
|
936
1158
|
method: "POST",
|
|
@@ -983,37 +1205,85 @@ var GeolocationClient = class extends BaseClient {
|
|
|
983
1205
|
* ```
|
|
984
1206
|
*/
|
|
985
1207
|
async getGPSConfig(requestOptions) {
|
|
986
|
-
const
|
|
987
|
-
if (
|
|
988
|
-
|
|
1208
|
+
const cached = this.gpsConfigCache;
|
|
1209
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
1210
|
+
return cached.value;
|
|
989
1211
|
}
|
|
990
|
-
return
|
|
1212
|
+
return this.refreshGPSConfig(requestOptions);
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Re-read the tenant's GPS requirement configuration, ignoring any copy held
|
|
1216
|
+
* from an earlier read.
|
|
1217
|
+
*
|
|
1218
|
+
* These requirements are edited in the compliance console while your
|
|
1219
|
+
* application is running. `getGPSConfig` reuses a fetched copy for a short
|
|
1220
|
+
* period so a long-lived session does not call the API on every event; call
|
|
1221
|
+
* this when you need a change to take effect immediately — for example right
|
|
1222
|
+
* after an administrator saves new settings.
|
|
1223
|
+
*
|
|
1224
|
+
* @returns The current GPS requirement config per event type
|
|
1225
|
+
*
|
|
1226
|
+
* @example
|
|
1227
|
+
* ```typescript
|
|
1228
|
+
* const config = await client.refreshGPSConfig();
|
|
1229
|
+
* if (config.require_gps.login) {
|
|
1230
|
+
* // GPS is now required for login
|
|
1231
|
+
* }
|
|
1232
|
+
* ```
|
|
1233
|
+
*/
|
|
1234
|
+
async refreshGPSConfig(requestOptions) {
|
|
1235
|
+
const value = await this.requestWithRetry(
|
|
1236
|
+
"/api/v1/geo/config",
|
|
1237
|
+
void 0,
|
|
1238
|
+
void 0,
|
|
1239
|
+
void 0,
|
|
1240
|
+
requestOptions
|
|
1241
|
+
);
|
|
1242
|
+
this.gpsConfigCache = { value, expiresAt: Date.now() + this.gpsConfigTtlMs };
|
|
1243
|
+
return value;
|
|
991
1244
|
}
|
|
992
1245
|
/**
|
|
993
|
-
*
|
|
1246
|
+
* Mint a short-lived, single-use device-signals nonce.
|
|
994
1247
|
*
|
|
995
|
-
*
|
|
996
|
-
*
|
|
1248
|
+
* The nonce is bound to the tenant, user, and event type and is consumed
|
|
1249
|
+
* server-side during validation, so a captured device-signals envelope cannot
|
|
1250
|
+
* be replayed. `validateCipherText` mints and attaches one automatically; call
|
|
1251
|
+
* this directly only if you manage the nonce yourself.
|
|
997
1252
|
*
|
|
998
|
-
* @
|
|
1253
|
+
* @param userId - User ID the nonce is bound to
|
|
1254
|
+
* @param eventType - Event type the nonce is bound to (login, registration, ...)
|
|
1255
|
+
* @returns The nonce and its expiry
|
|
1256
|
+
*/
|
|
1257
|
+
async fetchSignalsNonce(userId, eventType, requestOptions) {
|
|
1258
|
+
if (!userId?.trim()) {
|
|
1259
|
+
throw new ValidationError("userId is required and must be a non-empty string", ["userId"]);
|
|
1260
|
+
}
|
|
1261
|
+
const request = { user_id: userId, event_type: eventType };
|
|
1262
|
+
return this.request(
|
|
1263
|
+
"/api/v1/geo/signals-token",
|
|
1264
|
+
{
|
|
1265
|
+
method: "POST",
|
|
1266
|
+
body: JSON.stringify(request)
|
|
1267
|
+
},
|
|
1268
|
+
void 0,
|
|
1269
|
+
requestOptions
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Best-effort nonce mint for validation. Returns undefined if minting fails —
|
|
1274
|
+
* the server is the sole authority and blocks when a nonce is required, so this
|
|
1275
|
+
* never downgrades security on the client's say-so.
|
|
999
1276
|
*/
|
|
1000
|
-
async
|
|
1277
|
+
async resolveSignalsNonce(userId, eventType, requestOptions) {
|
|
1001
1278
|
try {
|
|
1002
|
-
const
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
requestOptions
|
|
1279
|
+
const { nonce } = await this.fetchSignalsNonce(userId, eventType, requestOptions);
|
|
1280
|
+
return nonce;
|
|
1281
|
+
} catch (err) {
|
|
1282
|
+
this.logger.warn(
|
|
1283
|
+
`Failed to mint device-signals nonce; proceeding without it: ${err instanceof Error ? err.message : String(err)}`
|
|
1008
1284
|
);
|
|
1009
|
-
|
|
1010
|
-
this.cachedSigningKey = response.signing_key;
|
|
1011
|
-
return response.signing_key;
|
|
1012
|
-
}
|
|
1013
|
-
} catch {
|
|
1285
|
+
return void 0;
|
|
1014
1286
|
}
|
|
1015
|
-
const config = await this.getGPSConfig(requestOptions);
|
|
1016
|
-
return config.signing_key;
|
|
1017
1287
|
}
|
|
1018
1288
|
// ============================================================================
|
|
1019
1289
|
// CipherText Validation
|
|
@@ -1066,12 +1336,14 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1066
1336
|
if (!userId?.trim()) {
|
|
1067
1337
|
throw new ValidationError("userId is required and must be a non-empty string", ["userId"]);
|
|
1068
1338
|
}
|
|
1339
|
+
const nonce = await this.resolveSignalsNonce(userId, eventType, requestOptions);
|
|
1069
1340
|
const request = {
|
|
1070
1341
|
cipher_text: cipherText,
|
|
1071
1342
|
user_id: userId,
|
|
1072
1343
|
event_type: eventType,
|
|
1073
1344
|
expected_ip: expectedIP,
|
|
1074
|
-
customer_data: customerData
|
|
1345
|
+
customer_data: customerData,
|
|
1346
|
+
nonce
|
|
1075
1347
|
};
|
|
1076
1348
|
return this.requestWithRetry(
|
|
1077
1349
|
"/api/v1/geo/validate-ciphertext",
|
|
@@ -1280,20 +1552,12 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1280
1552
|
*
|
|
1281
1553
|
* @example
|
|
1282
1554
|
* ```typescript
|
|
1283
|
-
* //
|
|
1555
|
+
* // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
|
|
1284
1556
|
* const result = await client.captureLocation(token, {
|
|
1285
1557
|
* latitude: 37.7749,
|
|
1286
1558
|
* longitude: -122.4194,
|
|
1287
1559
|
* accuracy: 10
|
|
1288
1560
|
* });
|
|
1289
|
-
*
|
|
1290
|
-
* // Using WiFi positioning (fallback)
|
|
1291
|
-
* const result = await client.captureLocation(token, {
|
|
1292
|
-
* wifi_networks: [
|
|
1293
|
-
* { macAddress: "00:11:22:33:44:55", signalStrength: -50 },
|
|
1294
|
-
* { macAddress: "AA:BB:CC:DD:EE:FF", signalStrength: -70 }
|
|
1295
|
-
* ]
|
|
1296
|
-
* });
|
|
1297
1561
|
* ```
|
|
1298
1562
|
*/
|
|
1299
1563
|
async captureLocation(token, capture, requestOptions) {
|
|
@@ -1306,34 +1570,22 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1306
1570
|
// CipherText Generation
|
|
1307
1571
|
// ============================================================================
|
|
1308
1572
|
/**
|
|
1309
|
-
* Generate a
|
|
1573
|
+
* Generate a device-signals envelope containing device and location data,
|
|
1574
|
+
* validated by the server.
|
|
1310
1575
|
*
|
|
1311
|
-
*
|
|
1312
|
-
* If no API key is configured, falls back to unsigned v01 format.
|
|
1313
|
-
*
|
|
1314
|
-
* @param options - Options for cipherText generation
|
|
1576
|
+
* @param options - Options for envelope generation
|
|
1315
1577
|
* @param gpsConfig - Optional GPS config (from getGPSConfig)
|
|
1316
|
-
* @returns
|
|
1578
|
+
* @returns The device-signals envelope result
|
|
1317
1579
|
*
|
|
1318
1580
|
* @example
|
|
1319
1581
|
* ```typescript
|
|
1320
1582
|
* const result = await client.generateCipherText({ reason: 'login' });
|
|
1321
|
-
* console.log(result.cipherText); //
|
|
1583
|
+
* console.log(result.cipherText); // device-signals envelope
|
|
1322
1584
|
* ```
|
|
1323
1585
|
*/
|
|
1324
1586
|
async generateCipherText(options, gpsConfig) {
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
if (!signingKey) {
|
|
1328
|
-
signingKey = await this.fetchSigningKey();
|
|
1329
|
-
}
|
|
1330
|
-
if (!resolvedGpsConfig) {
|
|
1331
|
-
resolvedGpsConfig = await this.getGPSConfig();
|
|
1332
|
-
}
|
|
1333
|
-
return generateCipherText(
|
|
1334
|
-
{ ...options, signingKey: signingKey || void 0 },
|
|
1335
|
-
resolvedGpsConfig
|
|
1336
|
-
);
|
|
1587
|
+
const resolvedGpsConfig = gpsConfig ?? await this.getGPSConfig();
|
|
1588
|
+
return generateCipherText(options, resolvedGpsConfig);
|
|
1337
1589
|
}
|
|
1338
1590
|
// ============================================================================
|
|
1339
1591
|
// Utility Methods (inherited from BaseClient: healthCheck, updateConfig, getConfig, buildQueryString)
|
|
@@ -2736,21 +2988,20 @@ var KycClient = class extends BaseClient {
|
|
|
2736
2988
|
* console.log(`KYC ID: ${result.kyc_id}`);
|
|
2737
2989
|
* ```
|
|
2738
2990
|
*/
|
|
2739
|
-
async requestKycSubmitLink(request) {
|
|
2991
|
+
async requestKycSubmitLink(request, requestOptions) {
|
|
2740
2992
|
return this.requestWithRetry("/api/v1/kyc/request", {
|
|
2741
2993
|
method: "POST",
|
|
2742
2994
|
body: JSON.stringify(request),
|
|
2743
2995
|
headers: this.getUserHeaders()
|
|
2744
|
-
});
|
|
2996
|
+
}, void 0, void 0, requestOptions);
|
|
2745
2997
|
}
|
|
2746
2998
|
/**
|
|
2747
2999
|
* Create a Event-Based Face Verification session.
|
|
2748
3000
|
*
|
|
2749
3001
|
* Inspect the response before showing UI:
|
|
2750
|
-
* - `is_required === false` → skip face
|
|
2751
|
-
* - `
|
|
2752
|
-
*
|
|
2753
|
-
* - `device_type === 'mobile'` → open the face capture modal directly.
|
|
3002
|
+
* - `is_required === false` → skip face verification; `reason` explains why.
|
|
3003
|
+
* - `verification_url` present → embed the hosted journey and poll status.
|
|
3004
|
+
* - `verification_url` absent → use the local camera / QR fallback.
|
|
2754
3005
|
*
|
|
2755
3006
|
* @param request - Reference, customer_id, event, amount (for threshold events), optional URLs.
|
|
2756
3007
|
*/
|
|
@@ -2774,12 +3025,12 @@ var KycClient = class extends BaseClient {
|
|
|
2774
3025
|
*
|
|
2775
3026
|
* @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
|
|
2776
3027
|
*/
|
|
2777
|
-
async submitEventBasedFaceVerificationSession(request) {
|
|
3028
|
+
async submitEventBasedFaceVerificationSession(request, requestOptions) {
|
|
2778
3029
|
return this.request("/api/v1/kyc/face/submit", {
|
|
2779
3030
|
method: "POST",
|
|
2780
3031
|
body: JSON.stringify(request),
|
|
2781
3032
|
headers: this.getUserHeaders()
|
|
2782
|
-
});
|
|
3033
|
+
}, void 0, requestOptions);
|
|
2783
3034
|
}
|
|
2784
3035
|
/**
|
|
2785
3036
|
* Look up the current state of a Event-Based Face Verification session by its
|
|
@@ -2797,11 +3048,28 @@ var KycClient = class extends BaseClient {
|
|
|
2797
3048
|
headers: this.getUserHeaders()
|
|
2798
3049
|
});
|
|
2799
3050
|
}
|
|
3051
|
+
/**
|
|
3052
|
+
* Mint a fresh hosted verification journey for the next attempt on an
|
|
3053
|
+
* active session whose previous hosted-journey attempt was declined.
|
|
3054
|
+
* Only valid while the session is active and attempts remain; the
|
|
3055
|
+
* response carries the new `verification_url` to embed. Used internally
|
|
3056
|
+
* by `FaceCaptureModal`'s Try Again flow in hosted-journey mode.
|
|
3057
|
+
*
|
|
3058
|
+
* @param token - The session token returned by `createEventBasedFaceVerificationSession`.
|
|
3059
|
+
*/
|
|
3060
|
+
async retryEventBasedFaceVerificationJourney(token) {
|
|
3061
|
+
return this.request("/api/v1/kyc/face/onsite/journey", {
|
|
3062
|
+
method: "POST",
|
|
3063
|
+
body: JSON.stringify({ token }),
|
|
3064
|
+
headers: this.getUserHeaders()
|
|
3065
|
+
});
|
|
3066
|
+
}
|
|
2800
3067
|
/**
|
|
2801
3068
|
* Fetch the Redis-backed handoff session for a token. Same backing
|
|
2802
|
-
* store as normal KYC (`kyc:session:<token>`, 15-minute TTL).
|
|
2803
|
-
*
|
|
2804
|
-
*
|
|
3069
|
+
* store as normal KYC (`kyc:session:<token>`, 15-minute TTL). The
|
|
3070
|
+
* Event-Based Face Verification database session still expires after
|
|
3071
|
+
* 10 minutes. Desktop callers poll `mobile_connected` to detect when a
|
|
3072
|
+
* mobile device has scanned the QR and attached.
|
|
2805
3073
|
*
|
|
2806
3074
|
* @param token - The session token returned by `createEventBasedFaceVerificationSession`.
|
|
2807
3075
|
*/
|
|
@@ -2895,12 +3163,12 @@ var KycClient = class extends BaseClient {
|
|
|
2895
3163
|
* console.log(`Verification submitted: ${result.reference}`);
|
|
2896
3164
|
* ```
|
|
2897
3165
|
*/
|
|
2898
|
-
async submitVerification(request) {
|
|
3166
|
+
async submitVerification(request, requestOptions) {
|
|
2899
3167
|
return this.requestWithRetry("/api/v1/kyc/submit", {
|
|
2900
3168
|
method: "POST",
|
|
2901
3169
|
body: JSON.stringify(request),
|
|
2902
3170
|
headers: this.getUserHeaders()
|
|
2903
|
-
});
|
|
3171
|
+
}, void 0, void 0, requestOptions);
|
|
2904
3172
|
}
|
|
2905
3173
|
/**
|
|
2906
3174
|
* Get a KYC request by ID
|
|
@@ -3310,6 +3578,9 @@ var KYC_DECLINED_DESCRIPTIONS = {
|
|
|
3310
3578
|
KYC_PROVIDER_REJECTED: "Identity verification was rejected by the verification provider",
|
|
3311
3579
|
KYC_DECLINED: "Identity verification was declined"
|
|
3312
3580
|
};
|
|
3581
|
+
function isKycDocumentExpiredCallbackEvent(value) {
|
|
3582
|
+
return typeof value === "object" && value !== null && value.event === "kyc_document_expired";
|
|
3583
|
+
}
|
|
3313
3584
|
|
|
3314
3585
|
// src/tax/client.ts
|
|
3315
3586
|
var TaxClient = class extends BaseClient {
|
|
@@ -3340,10 +3611,13 @@ var TaxClient = class extends BaseClient {
|
|
|
3340
3611
|
{ method: "POST" }
|
|
3341
3612
|
);
|
|
3342
3613
|
}
|
|
3343
|
-
async checkTINStatus(customerID) {
|
|
3614
|
+
async checkTINStatus(customerID, requestOptions) {
|
|
3344
3615
|
return this.requestWithRetry(
|
|
3345
3616
|
`/api/v1/tax/customer-tax-profiles/${customerID}/check-tin`,
|
|
3346
|
-
{ method: "POST" }
|
|
3617
|
+
{ method: "POST" },
|
|
3618
|
+
void 0,
|
|
3619
|
+
void 0,
|
|
3620
|
+
requestOptions
|
|
3347
3621
|
);
|
|
3348
3622
|
}
|
|
3349
3623
|
async reRequestTaxForm(customerID, input) {
|
|
@@ -3424,14 +3698,24 @@ var TaxClient = class extends BaseClient {
|
|
|
3424
3698
|
};
|
|
3425
3699
|
|
|
3426
3700
|
// src/webhooks/handler.ts
|
|
3701
|
+
function resolveEventId(event) {
|
|
3702
|
+
if (event.id) {
|
|
3703
|
+
return event.id;
|
|
3704
|
+
}
|
|
3705
|
+
if (event.event_type === "notification.created") {
|
|
3706
|
+
return event.notification?.id;
|
|
3707
|
+
}
|
|
3708
|
+
return void 0;
|
|
3709
|
+
}
|
|
3427
3710
|
var WebhookHandler = class {
|
|
3428
3711
|
constructor(config) {
|
|
3429
3712
|
this.handlers = /* @__PURE__ */ new Map();
|
|
3430
3713
|
this.anyHandlers = [];
|
|
3431
|
-
this.seenEventIds = /* @__PURE__ */ new Map();
|
|
3432
3714
|
this.secret = config.secret;
|
|
3433
3715
|
this.tolerance = config.tolerance ?? 3e5;
|
|
3434
3716
|
this.replayProtection = config.replayProtection ?? true;
|
|
3717
|
+
this.allowLegacySignature = config.allowLegacySignature ?? true;
|
|
3718
|
+
this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
|
|
3435
3719
|
}
|
|
3436
3720
|
/**
|
|
3437
3721
|
* Register a handler for a specific event type.
|
|
@@ -3453,7 +3737,10 @@ var WebhookHandler = class {
|
|
|
3453
3737
|
* Verify signature and parse the webhook body.
|
|
3454
3738
|
*/
|
|
3455
3739
|
async verifyAndParse(body, signature) {
|
|
3456
|
-
const isValid = await verifyWebhookSignature(body, signature, this.secret
|
|
3740
|
+
const isValid = await verifyWebhookSignature(body, signature, this.secret, {
|
|
3741
|
+
tolerance: this.tolerance,
|
|
3742
|
+
allowLegacy: this.allowLegacySignature
|
|
3743
|
+
});
|
|
3457
3744
|
if (!isValid) {
|
|
3458
3745
|
throw new ValidationError("Invalid webhook signature", ["signature"]);
|
|
3459
3746
|
}
|
|
@@ -3469,18 +3756,21 @@ var WebhookHandler = class {
|
|
|
3469
3756
|
/**
|
|
3470
3757
|
* Parse an event without signature verification (for testing).
|
|
3471
3758
|
*/
|
|
3472
|
-
parseEvent(body) {
|
|
3759
|
+
async parseEvent(body) {
|
|
3473
3760
|
return this.parseAndValidate(body);
|
|
3474
3761
|
}
|
|
3475
|
-
parseAndValidate(body) {
|
|
3762
|
+
async parseAndValidate(body) {
|
|
3476
3763
|
const event = JSON.parse(body);
|
|
3477
|
-
|
|
3478
|
-
|
|
3764
|
+
const eventId = resolveEventId(event);
|
|
3765
|
+
if (!event.event_type || !eventId || !event.timestamp) {
|
|
3766
|
+
throw new ValidationError(
|
|
3767
|
+
"Invalid webhook event: missing required fields (event_type, id, timestamp)",
|
|
3768
|
+
["event_type", "id", "timestamp"]
|
|
3769
|
+
);
|
|
3479
3770
|
}
|
|
3480
3771
|
if (this.tolerance > 0) {
|
|
3481
3772
|
const eventTime = new Date(event.timestamp).getTime();
|
|
3482
|
-
|
|
3483
|
-
if (Math.abs(now - eventTime) > this.tolerance) {
|
|
3773
|
+
if (Math.abs(Date.now() - eventTime) > this.tolerance) {
|
|
3484
3774
|
throw new ValidationError(
|
|
3485
3775
|
`Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
|
|
3486
3776
|
["timestamp"]
|
|
@@ -3488,26 +3778,18 @@ var WebhookHandler = class {
|
|
|
3488
3778
|
}
|
|
3489
3779
|
}
|
|
3490
3780
|
if (this.replayProtection) {
|
|
3491
|
-
if (this.
|
|
3781
|
+
if (await this.dedupStore.seen(eventId)) {
|
|
3492
3782
|
throw new ValidationError(
|
|
3493
|
-
`Duplicate webhook event: ${
|
|
3783
|
+
`Duplicate webhook event: ${eventId} has already been processed`,
|
|
3494
3784
|
["id"]
|
|
3495
3785
|
);
|
|
3496
3786
|
}
|
|
3497
|
-
|
|
3498
|
-
this.seenEventIds.set(event.id, now);
|
|
3499
|
-
if (this.seenEventIds.size > 1e3) {
|
|
3500
|
-
for (const [id, seenAt] of this.seenEventIds) {
|
|
3501
|
-
if (now - seenAt > this.tolerance) {
|
|
3502
|
-
this.seenEventIds.delete(id);
|
|
3503
|
-
}
|
|
3504
|
-
}
|
|
3505
|
-
}
|
|
3787
|
+
await this.dedupStore.mark(eventId, this.tolerance);
|
|
3506
3788
|
}
|
|
3507
3789
|
return event;
|
|
3508
3790
|
}
|
|
3509
3791
|
async dispatch(event) {
|
|
3510
|
-
const typeHandlers = this.handlers.get(event.
|
|
3792
|
+
const typeHandlers = this.handlers.get(event.event_type) || [];
|
|
3511
3793
|
const allHandlers = [...typeHandlers, ...this.anyHandlers];
|
|
3512
3794
|
for (const handler of allHandlers) {
|
|
3513
3795
|
await handler(event);
|
|
@@ -3588,6 +3870,6 @@ function buildHandler(options) {
|
|
|
3588
3870
|
return handler;
|
|
3589
3871
|
}
|
|
3590
3872
|
|
|
3591
|
-
export { AuthenticationError, BaseClient, CGSError, CircuitBreaker, CircuitBreakerOpenError, ComplianceBlockedError, ComplianceClient, ComplianceError, DEFAULT_CURRENCY_RATES, GeolocationClient, KYC_DECLINED_DESCRIPTIONS, KycClient, NetworkError, RateLimitError, RateLimitTracker, RiskProfileClient, SDK_VERSION, ServiceUnavailableError, TaxClient, TimeoutError, ValidationError, VesantError, WebhookHandler, createConsoleLogger, createNextWebhookHandler, createWebhookMiddleware, decodeCipherText, generateCipherText, isCipherTextExpired, noopLogger, sdkReasons, verifyWebhookSignature };
|
|
3873
|
+
export { AuthenticationError, BaseClient, CGSError, CircuitBreaker, CircuitBreakerOpenError, ComplianceBlockedError, ComplianceClient, ComplianceError, DEFAULT_CURRENCY_RATES, GeolocationClient, InMemoryDedupStore, KYC_DECLINED_DESCRIPTIONS, KycClient, NetworkError, RateLimitError, RateLimitTracker, RefreshingTokenProvider, RiskProfileClient, SDK_VERSION, ServiceUnavailableError, StaticApiKeyProvider, TaxClient, TimeoutError, ValidationError, VesantError, WebhookHandler, assessGpsIntegrity, collectAll, collectDeviceSignals, createConsoleLogger, createNextWebhookHandler, createWebhookMiddleware, decodeCipherText, generateCipherText, gpsIntegrityInputFromPosition, isCipherTextExpired, isKycDocumentExpiredCallbackEvent, noopLogger, paginate, probeGeolocationPermission, sdkReasons, verifyWebhookSignature };
|
|
3592
3874
|
//# sourceMappingURL=index.mjs.map
|
|
3593
3875
|
//# sourceMappingURL=index.mjs.map
|