vesant-sdk 1.7.0-dev.b2c9ecf → 1.7.0-dev.b4284ca
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-B32yBt1b.d.ts → client-0NOPDnT0.d.ts} +16 -26
- package/dist/{client-BqPVh5jH.d.mts → client-CvWNRwwg.d.mts} +16 -26
- package/dist/{client-CHPEgasE.d.mts → client-Dps40EtF.d.mts} +2 -2
- package/dist/{client-pKE_gii_.d.ts → client-SMxqod4j.d.ts} +2 -2
- package/dist/{client-6sSaxufk.d.mts → client-q9fN8Hhf.d.mts} +96 -1
- package/dist/{client-6sSaxufk.d.ts → client-q9fN8Hhf.d.ts} +96 -1
- package/dist/compliance/index.d.mts +3 -3
- package/dist/compliance/index.d.ts +3 -3
- package/dist/compliance/index.js +114 -16
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +114 -16
- 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 +49 -4
- package/dist/decisions/index.js.map +1 -1
- package/dist/decisions/index.mjs +49 -4
- package/dist/decisions/index.mjs.map +1 -1
- package/dist/geolocation/index.d.mts +59 -4
- package/dist/geolocation/index.d.ts +59 -4
- package/dist/geolocation/index.js +117 -16
- package/dist/geolocation/index.js.map +1 -1
- package/dist/geolocation/index.mjs +115 -17
- package/dist/geolocation/index.mjs.map +1 -1
- package/dist/index-CPOrMRaH.d.ts +264 -0
- package/dist/index-CimDmgZb.d.mts +264 -0
- package/dist/index.d.mts +25 -8
- package/dist/index.d.ts +25 -8
- package/dist/index.js +274 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +267 -38
- package/dist/index.mjs.map +1 -1
- package/dist/kyc/core.d.mts +1 -1
- package/dist/kyc/core.d.ts +1 -1
- package/dist/kyc/core.js +49 -4
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +49 -4
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +1 -1
- package/dist/kyc/index.d.ts +1 -1
- package/dist/kyc/index.js +49 -4
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +49 -4
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +7 -3
- package/dist/react.d.ts +7 -3
- package/dist/react.js +68 -4
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +68 -4
- 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 +49 -4
- package/dist/risk-profile/index.js.map +1 -1
- package/dist/risk-profile/index.mjs +49 -4
- 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 +49 -4
- package/dist/scores/index.js.map +1 -1
- package/dist/scores/index.mjs +49 -4
- package/dist/scores/index.mjs.map +1 -1
- package/dist/tax/index.d.mts +1 -1
- package/dist/tax/index.d.ts +1 -1
- package/dist/tax/index.js +49 -4
- package/dist/tax/index.js.map +1 -1
- package/dist/tax/index.mjs +49 -4
- 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.js
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// src/core/auth.ts
|
|
4
|
+
var StaticApiKeyProvider = class {
|
|
5
|
+
constructor(apiKey) {
|
|
6
|
+
this.apiKey = apiKey;
|
|
7
|
+
}
|
|
8
|
+
async getCredential() {
|
|
9
|
+
return this.apiKey ? { scheme: "Bearer", token: this.apiKey } : null;
|
|
10
|
+
}
|
|
11
|
+
canRefresh() {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var RefreshingTokenProvider = class {
|
|
16
|
+
constructor(opts) {
|
|
17
|
+
this.opts = opts;
|
|
18
|
+
this.cached = null;
|
|
19
|
+
this.inFlight = null;
|
|
20
|
+
this.skewMs = opts.skewMs ?? 3e4;
|
|
21
|
+
this.scheme = opts.scheme ?? "Bearer";
|
|
22
|
+
this.now = opts.now ?? (() => Date.now());
|
|
23
|
+
}
|
|
24
|
+
canRefresh() {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
async getCredential(ctx) {
|
|
28
|
+
const forceRefresh = ctx?.forceRefresh ?? false;
|
|
29
|
+
if (!forceRefresh && this.cached && !this.isExpiring(this.cached)) {
|
|
30
|
+
return { scheme: this.scheme, token: this.cached.token };
|
|
31
|
+
}
|
|
32
|
+
const fresh = await this.refresh();
|
|
33
|
+
return { scheme: this.scheme, token: fresh.token };
|
|
34
|
+
}
|
|
35
|
+
isExpiring(token) {
|
|
36
|
+
if (token.expiresAt === void 0) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return this.now() >= token.expiresAt - this.skewMs;
|
|
40
|
+
}
|
|
41
|
+
refresh() {
|
|
42
|
+
if (this.inFlight) {
|
|
43
|
+
return this.inFlight;
|
|
44
|
+
}
|
|
45
|
+
this.inFlight = (async () => {
|
|
46
|
+
try {
|
|
47
|
+
const fresh = await this.opts.fetchToken();
|
|
48
|
+
this.cached = fresh;
|
|
49
|
+
this.opts.onRotate?.({ expiresAt: fresh.expiresAt });
|
|
50
|
+
return fresh;
|
|
51
|
+
} finally {
|
|
52
|
+
this.inFlight = null;
|
|
53
|
+
}
|
|
54
|
+
})();
|
|
55
|
+
return this.inFlight;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
3
59
|
// src/core/errors.ts
|
|
4
60
|
var VesantError = class _VesantError extends Error {
|
|
5
61
|
constructor(message, code, statusCode, details) {
|
|
@@ -324,6 +380,12 @@ var BaseClient = class {
|
|
|
324
380
|
if (!config.tenantId?.trim()) {
|
|
325
381
|
throw new ValidationError("tenantId is required and must be a non-empty string", ["tenantId"]);
|
|
326
382
|
}
|
|
383
|
+
if (typeof fetch === "undefined") {
|
|
384
|
+
throw new VesantError(
|
|
385
|
+
"The Vesant SDK requires a global fetch (Node.js >= 18). Upgrade Node or provide a fetch polyfill.",
|
|
386
|
+
"RUNTIME_UNSUPPORTED"
|
|
387
|
+
);
|
|
388
|
+
}
|
|
327
389
|
this.interceptors = config.interceptors || [];
|
|
328
390
|
this.logger = config.logger || createConsoleLogger();
|
|
329
391
|
let environment = config.environment;
|
|
@@ -342,11 +404,12 @@ var BaseClient = class {
|
|
|
342
404
|
environment,
|
|
343
405
|
headers: config.headers || {},
|
|
344
406
|
timeout: config.timeout || 1e4,
|
|
345
|
-
retries: config.retries
|
|
407
|
+
retries: config.retries ?? 3,
|
|
346
408
|
debug: config.debug || false,
|
|
347
409
|
interceptors: this.interceptors,
|
|
348
410
|
logger: this.logger
|
|
349
411
|
};
|
|
412
|
+
this.authProvider = config.authProvider ?? (apiKey ? new StaticApiKeyProvider(apiKey) : void 0);
|
|
350
413
|
if (config.circuitBreaker) {
|
|
351
414
|
this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
|
|
352
415
|
}
|
|
@@ -392,8 +455,12 @@ var BaseClient = class {
|
|
|
392
455
|
...this.config.headers,
|
|
393
456
|
...options.headers || {}
|
|
394
457
|
};
|
|
395
|
-
|
|
396
|
-
|
|
458
|
+
const credential = await this.authProvider?.getCredential({
|
|
459
|
+
tenantId: this.config.tenantId,
|
|
460
|
+
requestId
|
|
461
|
+
});
|
|
462
|
+
if (credential) {
|
|
463
|
+
headers["Authorization"] = `${credential.scheme ?? "Bearer"} ${credential.token}`;
|
|
397
464
|
}
|
|
398
465
|
if (this.config.environment === "sandbox") {
|
|
399
466
|
headers["X-Sandbox"] = "true";
|
|
@@ -422,10 +489,28 @@ var BaseClient = class {
|
|
|
422
489
|
if (this.config.debug) {
|
|
423
490
|
this.logger.debug(`${finalOptions.method || "GET"} ${endpoint}`);
|
|
424
491
|
}
|
|
425
|
-
|
|
492
|
+
let response = await fetch(url, {
|
|
426
493
|
...finalOptions,
|
|
427
494
|
signal: controller.signal
|
|
428
495
|
});
|
|
496
|
+
if (response.status === 401 && this.authProvider?.canRefresh?.()) {
|
|
497
|
+
const refreshed = await this.authProvider.getCredential({
|
|
498
|
+
tenantId: this.config.tenantId,
|
|
499
|
+
requestId,
|
|
500
|
+
forceRefresh: true
|
|
501
|
+
});
|
|
502
|
+
if (refreshed) {
|
|
503
|
+
this.config.onCredentialRotated?.({ reason: "unauthorized" });
|
|
504
|
+
finalOptions = {
|
|
505
|
+
...finalOptions,
|
|
506
|
+
headers: {
|
|
507
|
+
...finalOptions.headers,
|
|
508
|
+
Authorization: `${refreshed.scheme ?? "Bearer"} ${refreshed.token}`
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
response = await fetch(url, { ...finalOptions, signal: controller.signal });
|
|
512
|
+
}
|
|
513
|
+
}
|
|
429
514
|
clearTimeout(timeoutId);
|
|
430
515
|
if (this.rateLimitTracker) {
|
|
431
516
|
this.rateLimitTracker.updateFromHeaders(response.headers);
|
|
@@ -615,6 +700,9 @@ var BaseClient = class {
|
|
|
615
700
|
if (config.interceptors) {
|
|
616
701
|
this.interceptors = config.interceptors;
|
|
617
702
|
}
|
|
703
|
+
if (config.authProvider !== void 0 || config.apiKey !== void 0) {
|
|
704
|
+
this.authProvider = this.config.authProvider ?? (this.config.apiKey ? new StaticApiKeyProvider(this.config.apiKey) : void 0);
|
|
705
|
+
}
|
|
618
706
|
}
|
|
619
707
|
/**
|
|
620
708
|
* Get current configuration (readonly)
|
|
@@ -642,8 +730,46 @@ var BaseClient = class {
|
|
|
642
730
|
}
|
|
643
731
|
};
|
|
644
732
|
|
|
733
|
+
// src/core/pagination.ts
|
|
734
|
+
async function* paginate(fetchPage) {
|
|
735
|
+
let page = 1;
|
|
736
|
+
let totalPages;
|
|
737
|
+
do {
|
|
738
|
+
const response = await fetchPage(page);
|
|
739
|
+
const items = response.data ?? [];
|
|
740
|
+
for (const item of items) {
|
|
741
|
+
yield item;
|
|
742
|
+
}
|
|
743
|
+
totalPages = response.total_pages ?? page;
|
|
744
|
+
if (items.length === 0) {
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
page += 1;
|
|
748
|
+
} while (page <= totalPages);
|
|
749
|
+
}
|
|
750
|
+
async function collectAll(fetchPage) {
|
|
751
|
+
const items = [];
|
|
752
|
+
for await (const item of paginate(fetchPage)) {
|
|
753
|
+
items.push(item);
|
|
754
|
+
}
|
|
755
|
+
return items;
|
|
756
|
+
}
|
|
757
|
+
|
|
645
758
|
// src/core/webhook-utils.ts
|
|
646
|
-
async function verifyWebhookSignature(payload, signature, secret) {
|
|
759
|
+
async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
|
|
760
|
+
const signed = parseSignedTimestamp(signature);
|
|
761
|
+
if (signed) {
|
|
762
|
+
const tolerance = opts.tolerance ?? 3e5;
|
|
763
|
+
const now = opts.now?.() ?? Date.now();
|
|
764
|
+
if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
|
|
768
|
+
return constantTimeEqual(signed.v1, expected);
|
|
769
|
+
}
|
|
770
|
+
if (opts.allowLegacy === false) {
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
647
773
|
const hexDigest = await computeHmacSha256(payload, secret);
|
|
648
774
|
const expectedPrefixed = `sha256=${hexDigest}`;
|
|
649
775
|
if (signature.startsWith("sha256=")) {
|
|
@@ -651,6 +777,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
|
|
|
651
777
|
}
|
|
652
778
|
return constantTimeEqual(signature, hexDigest);
|
|
653
779
|
}
|
|
780
|
+
function parseSignedTimestamp(signature) {
|
|
781
|
+
if (!signature.includes("t=") || !signature.includes("v1=")) {
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
let t;
|
|
785
|
+
let v1;
|
|
786
|
+
for (const part of signature.split(",")) {
|
|
787
|
+
const eq = part.indexOf("=");
|
|
788
|
+
if (eq === -1) continue;
|
|
789
|
+
const key = part.slice(0, eq).trim();
|
|
790
|
+
const value = part.slice(eq + 1).trim();
|
|
791
|
+
if (key === "t") t = Number(value);
|
|
792
|
+
else if (key === "v1") v1 = value;
|
|
793
|
+
}
|
|
794
|
+
if (t === void 0 || Number.isNaN(t) || !v1) {
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
return { t, v1 };
|
|
798
|
+
}
|
|
654
799
|
async function computeHmacSha256(message, secret) {
|
|
655
800
|
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
656
801
|
const encoder = new TextEncoder();
|
|
@@ -685,6 +830,61 @@ function constantTimeEqual(a, b) {
|
|
|
685
830
|
return result === 0;
|
|
686
831
|
}
|
|
687
832
|
|
|
833
|
+
// src/geolocation/integrity.ts
|
|
834
|
+
var isNil = (v) => v === null || v === void 0;
|
|
835
|
+
function assessGpsIntegrity(input, opts = {}) {
|
|
836
|
+
const flags = [];
|
|
837
|
+
const now = opts.now ?? (() => Date.now());
|
|
838
|
+
const maxAgeMs = opts.maxAgeMs ?? 6e4;
|
|
839
|
+
const minAccuracy = opts.minAccuracyMeters ?? 1;
|
|
840
|
+
const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
|
|
841
|
+
if (isNil(accuracy)) {
|
|
842
|
+
flags.push("accuracy_missing");
|
|
843
|
+
} else if (accuracy === 0) {
|
|
844
|
+
flags.push("accuracy_zero");
|
|
845
|
+
} else if (accuracy < minAccuracy) {
|
|
846
|
+
flags.push("accuracy_implausibly_small");
|
|
847
|
+
}
|
|
848
|
+
const hasMotion = !isNil(heading) || !isNil(speed);
|
|
849
|
+
if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
|
|
850
|
+
flags.push("motion_without_accuracy");
|
|
851
|
+
}
|
|
852
|
+
if (!isNil(altitude) && isNil(altitudeAccuracy)) {
|
|
853
|
+
flags.push("altitude_without_accuracy");
|
|
854
|
+
}
|
|
855
|
+
if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
|
|
856
|
+
flags.push("stale_timestamp");
|
|
857
|
+
}
|
|
858
|
+
if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
|
|
859
|
+
flags.push(`permission_${opts.permissionState}`);
|
|
860
|
+
}
|
|
861
|
+
return { suspicious: flags.length > 0, flags };
|
|
862
|
+
}
|
|
863
|
+
function gpsIntegrityInputFromPosition(position) {
|
|
864
|
+
const c = position.coords;
|
|
865
|
+
return {
|
|
866
|
+
latitude: c.latitude,
|
|
867
|
+
longitude: c.longitude,
|
|
868
|
+
accuracy: c.accuracy,
|
|
869
|
+
altitude: c.altitude,
|
|
870
|
+
altitudeAccuracy: c.altitudeAccuracy,
|
|
871
|
+
heading: c.heading,
|
|
872
|
+
speed: c.speed,
|
|
873
|
+
timestamp: position.timestamp
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
async function probeGeolocationPermission() {
|
|
877
|
+
try {
|
|
878
|
+
if (typeof navigator === "undefined" || !navigator.permissions?.query) {
|
|
879
|
+
return "unavailable";
|
|
880
|
+
}
|
|
881
|
+
const status = await navigator.permissions.query({ name: "geolocation" });
|
|
882
|
+
return status.state;
|
|
883
|
+
} catch {
|
|
884
|
+
return "unavailable";
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
688
888
|
// src/geolocation/ciphertext.ts
|
|
689
889
|
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
690
890
|
async function computeHMAC(key, message) {
|
|
@@ -737,6 +937,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
737
937
|
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
738
938
|
return null;
|
|
739
939
|
}
|
|
940
|
+
const permissionState = await probeGeolocationPermission();
|
|
740
941
|
return new Promise((resolve) => {
|
|
741
942
|
navigator.geolocation.getCurrentPosition(
|
|
742
943
|
(position) => {
|
|
@@ -753,6 +954,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
753
954
|
resolve(null);
|
|
754
955
|
return;
|
|
755
956
|
}
|
|
957
|
+
const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
|
|
756
958
|
resolve({
|
|
757
959
|
latitude,
|
|
758
960
|
longitude,
|
|
@@ -761,7 +963,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
761
963
|
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
762
964
|
heading: position.coords.heading ?? void 0,
|
|
763
965
|
speed: position.coords.speed ?? void 0,
|
|
764
|
-
timestamp: position.timestamp
|
|
966
|
+
timestamp: position.timestamp,
|
|
967
|
+
integrity_flags: flags.length ? flags : void 0
|
|
765
968
|
});
|
|
766
969
|
},
|
|
767
970
|
() => {
|
|
@@ -828,6 +1031,9 @@ async function generateCipherText(options, config) {
|
|
|
828
1031
|
);
|
|
829
1032
|
if (location) {
|
|
830
1033
|
locationData = location;
|
|
1034
|
+
if (location.integrity_flags?.length) {
|
|
1035
|
+
warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
|
|
1036
|
+
}
|
|
831
1037
|
} else if (gpsRequiredByConfig) {
|
|
832
1038
|
throw new VesantError(
|
|
833
1039
|
`GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
|
|
@@ -947,8 +1153,8 @@ var GeolocationClient = class extends BaseClient {
|
|
|
947
1153
|
* ```
|
|
948
1154
|
*/
|
|
949
1155
|
async verifyIP(request, requestOptions) {
|
|
950
|
-
if (!request.ip_address
|
|
951
|
-
throw new ValidationError("ip_address
|
|
1156
|
+
if (request.ip_address !== void 0 && !request.ip_address.trim()) {
|
|
1157
|
+
throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
|
|
952
1158
|
}
|
|
953
1159
|
return this.requestWithRetry("/api/v1/geo/verify", {
|
|
954
1160
|
method: "POST",
|
|
@@ -1298,20 +1504,12 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1298
1504
|
*
|
|
1299
1505
|
* @example
|
|
1300
1506
|
* ```typescript
|
|
1301
|
-
* //
|
|
1507
|
+
* // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
|
|
1302
1508
|
* const result = await client.captureLocation(token, {
|
|
1303
1509
|
* latitude: 37.7749,
|
|
1304
1510
|
* longitude: -122.4194,
|
|
1305
1511
|
* accuracy: 10
|
|
1306
1512
|
* });
|
|
1307
|
-
*
|
|
1308
|
-
* // Using WiFi positioning (fallback)
|
|
1309
|
-
* const result = await client.captureLocation(token, {
|
|
1310
|
-
* wifi_networks: [
|
|
1311
|
-
* { macAddress: "00:11:22:33:44:55", signalStrength: -50 },
|
|
1312
|
-
* { macAddress: "AA:BB:CC:DD:EE:FF", signalStrength: -70 }
|
|
1313
|
-
* ]
|
|
1314
|
-
* });
|
|
1315
1513
|
* ```
|
|
1316
1514
|
*/
|
|
1317
1515
|
async captureLocation(token, capture, requestOptions) {
|
|
@@ -3444,15 +3642,48 @@ var TaxClient = class extends BaseClient {
|
|
|
3444
3642
|
}
|
|
3445
3643
|
};
|
|
3446
3644
|
|
|
3645
|
+
// src/webhooks/dedup.ts
|
|
3646
|
+
var InMemoryDedupStore = class {
|
|
3647
|
+
constructor(opts = {}) {
|
|
3648
|
+
this.expiryById = /* @__PURE__ */ new Map();
|
|
3649
|
+
this.maxEntries = opts.maxEntries ?? 1e3;
|
|
3650
|
+
this.now = opts.now ?? (() => Date.now());
|
|
3651
|
+
}
|
|
3652
|
+
async seen(id) {
|
|
3653
|
+
return this.expiryById.has(id);
|
|
3654
|
+
}
|
|
3655
|
+
async mark(id, ttlMs) {
|
|
3656
|
+
const now = this.now();
|
|
3657
|
+
this.expiryById.set(id, now + ttlMs);
|
|
3658
|
+
if (this.expiryById.size > this.maxEntries) {
|
|
3659
|
+
for (const [key, expiresAt] of this.expiryById) {
|
|
3660
|
+
if (now > expiresAt) {
|
|
3661
|
+
this.expiryById.delete(key);
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
};
|
|
3667
|
+
|
|
3447
3668
|
// src/webhooks/handler.ts
|
|
3669
|
+
function resolveEventId(event) {
|
|
3670
|
+
if (event.id) {
|
|
3671
|
+
return event.id;
|
|
3672
|
+
}
|
|
3673
|
+
if (event.event_type === "notification.created") {
|
|
3674
|
+
return event.notification?.id;
|
|
3675
|
+
}
|
|
3676
|
+
return void 0;
|
|
3677
|
+
}
|
|
3448
3678
|
var WebhookHandler = class {
|
|
3449
3679
|
constructor(config) {
|
|
3450
3680
|
this.handlers = /* @__PURE__ */ new Map();
|
|
3451
3681
|
this.anyHandlers = [];
|
|
3452
|
-
this.seenEventIds = /* @__PURE__ */ new Map();
|
|
3453
3682
|
this.secret = config.secret;
|
|
3454
3683
|
this.tolerance = config.tolerance ?? 3e5;
|
|
3455
3684
|
this.replayProtection = config.replayProtection ?? true;
|
|
3685
|
+
this.allowLegacySignature = config.allowLegacySignature ?? true;
|
|
3686
|
+
this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
|
|
3456
3687
|
}
|
|
3457
3688
|
/**
|
|
3458
3689
|
* Register a handler for a specific event type.
|
|
@@ -3474,7 +3705,10 @@ var WebhookHandler = class {
|
|
|
3474
3705
|
* Verify signature and parse the webhook body.
|
|
3475
3706
|
*/
|
|
3476
3707
|
async verifyAndParse(body, signature) {
|
|
3477
|
-
const isValid = await verifyWebhookSignature(body, signature, this.secret
|
|
3708
|
+
const isValid = await verifyWebhookSignature(body, signature, this.secret, {
|
|
3709
|
+
tolerance: this.tolerance,
|
|
3710
|
+
allowLegacy: this.allowLegacySignature
|
|
3711
|
+
});
|
|
3478
3712
|
if (!isValid) {
|
|
3479
3713
|
throw new ValidationError("Invalid webhook signature", ["signature"]);
|
|
3480
3714
|
}
|
|
@@ -3490,18 +3724,21 @@ var WebhookHandler = class {
|
|
|
3490
3724
|
/**
|
|
3491
3725
|
* Parse an event without signature verification (for testing).
|
|
3492
3726
|
*/
|
|
3493
|
-
parseEvent(body) {
|
|
3727
|
+
async parseEvent(body) {
|
|
3494
3728
|
return this.parseAndValidate(body);
|
|
3495
3729
|
}
|
|
3496
|
-
parseAndValidate(body) {
|
|
3730
|
+
async parseAndValidate(body) {
|
|
3497
3731
|
const event = JSON.parse(body);
|
|
3498
|
-
|
|
3499
|
-
|
|
3732
|
+
const eventId = resolveEventId(event);
|
|
3733
|
+
if (!event.event_type || !eventId || !event.timestamp) {
|
|
3734
|
+
throw new ValidationError(
|
|
3735
|
+
"Invalid webhook event: missing required fields (event_type, id, timestamp)",
|
|
3736
|
+
["event_type", "id", "timestamp"]
|
|
3737
|
+
);
|
|
3500
3738
|
}
|
|
3501
3739
|
if (this.tolerance > 0) {
|
|
3502
3740
|
const eventTime = new Date(event.timestamp).getTime();
|
|
3503
|
-
|
|
3504
|
-
if (Math.abs(now - eventTime) > this.tolerance) {
|
|
3741
|
+
if (Math.abs(Date.now() - eventTime) > this.tolerance) {
|
|
3505
3742
|
throw new ValidationError(
|
|
3506
3743
|
`Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
|
|
3507
3744
|
["timestamp"]
|
|
@@ -3509,26 +3746,18 @@ var WebhookHandler = class {
|
|
|
3509
3746
|
}
|
|
3510
3747
|
}
|
|
3511
3748
|
if (this.replayProtection) {
|
|
3512
|
-
if (this.
|
|
3749
|
+
if (await this.dedupStore.seen(eventId)) {
|
|
3513
3750
|
throw new ValidationError(
|
|
3514
|
-
`Duplicate webhook event: ${
|
|
3751
|
+
`Duplicate webhook event: ${eventId} has already been processed`,
|
|
3515
3752
|
["id"]
|
|
3516
3753
|
);
|
|
3517
3754
|
}
|
|
3518
|
-
|
|
3519
|
-
this.seenEventIds.set(event.id, now);
|
|
3520
|
-
if (this.seenEventIds.size > 1e3) {
|
|
3521
|
-
for (const [id, seenAt] of this.seenEventIds) {
|
|
3522
|
-
if (now - seenAt > this.tolerance) {
|
|
3523
|
-
this.seenEventIds.delete(id);
|
|
3524
|
-
}
|
|
3525
|
-
}
|
|
3526
|
-
}
|
|
3755
|
+
await this.dedupStore.mark(eventId, this.tolerance);
|
|
3527
3756
|
}
|
|
3528
3757
|
return event;
|
|
3529
3758
|
}
|
|
3530
3759
|
async dispatch(event) {
|
|
3531
|
-
const typeHandlers = this.handlers.get(event.
|
|
3760
|
+
const typeHandlers = this.handlers.get(event.event_type) || [];
|
|
3532
3761
|
const allHandlers = [...typeHandlers, ...this.anyHandlers];
|
|
3533
3762
|
for (const handler of allHandlers) {
|
|
3534
3763
|
await handler(event);
|
|
@@ -3619,26 +3848,34 @@ exports.ComplianceClient = ComplianceClient;
|
|
|
3619
3848
|
exports.ComplianceError = ComplianceError;
|
|
3620
3849
|
exports.DEFAULT_CURRENCY_RATES = DEFAULT_CURRENCY_RATES;
|
|
3621
3850
|
exports.GeolocationClient = GeolocationClient;
|
|
3851
|
+
exports.InMemoryDedupStore = InMemoryDedupStore;
|
|
3622
3852
|
exports.KYC_DECLINED_DESCRIPTIONS = KYC_DECLINED_DESCRIPTIONS;
|
|
3623
3853
|
exports.KycClient = KycClient;
|
|
3624
3854
|
exports.NetworkError = NetworkError;
|
|
3625
3855
|
exports.RateLimitError = RateLimitError;
|
|
3626
3856
|
exports.RateLimitTracker = RateLimitTracker;
|
|
3857
|
+
exports.RefreshingTokenProvider = RefreshingTokenProvider;
|
|
3627
3858
|
exports.RiskProfileClient = RiskProfileClient;
|
|
3628
3859
|
exports.SDK_VERSION = SDK_VERSION;
|
|
3629
3860
|
exports.ServiceUnavailableError = ServiceUnavailableError;
|
|
3861
|
+
exports.StaticApiKeyProvider = StaticApiKeyProvider;
|
|
3630
3862
|
exports.TaxClient = TaxClient;
|
|
3631
3863
|
exports.TimeoutError = TimeoutError;
|
|
3632
3864
|
exports.ValidationError = ValidationError;
|
|
3633
3865
|
exports.VesantError = VesantError;
|
|
3634
3866
|
exports.WebhookHandler = WebhookHandler;
|
|
3867
|
+
exports.assessGpsIntegrity = assessGpsIntegrity;
|
|
3868
|
+
exports.collectAll = collectAll;
|
|
3635
3869
|
exports.createConsoleLogger = createConsoleLogger;
|
|
3636
3870
|
exports.createNextWebhookHandler = createNextWebhookHandler;
|
|
3637
3871
|
exports.createWebhookMiddleware = createWebhookMiddleware;
|
|
3638
3872
|
exports.decodeCipherText = decodeCipherText;
|
|
3639
3873
|
exports.generateCipherText = generateCipherText;
|
|
3874
|
+
exports.gpsIntegrityInputFromPosition = gpsIntegrityInputFromPosition;
|
|
3640
3875
|
exports.isCipherTextExpired = isCipherTextExpired;
|
|
3641
3876
|
exports.noopLogger = noopLogger;
|
|
3877
|
+
exports.paginate = paginate;
|
|
3878
|
+
exports.probeGeolocationPermission = probeGeolocationPermission;
|
|
3642
3879
|
exports.sdkReasons = sdkReasons;
|
|
3643
3880
|
exports.verifyWebhookSignature = verifyWebhookSignature;
|
|
3644
3881
|
//# sourceMappingURL=index.js.map
|