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.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) {
|
|
@@ -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
|
}
|
|
@@ -390,8 +453,12 @@ var BaseClient = class {
|
|
|
390
453
|
...this.config.headers,
|
|
391
454
|
...options.headers || {}
|
|
392
455
|
};
|
|
393
|
-
|
|
394
|
-
|
|
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}`;
|
|
395
462
|
}
|
|
396
463
|
if (this.config.environment === "sandbox") {
|
|
397
464
|
headers["X-Sandbox"] = "true";
|
|
@@ -420,10 +487,28 @@ var BaseClient = class {
|
|
|
420
487
|
if (this.config.debug) {
|
|
421
488
|
this.logger.debug(`${finalOptions.method || "GET"} ${endpoint}`);
|
|
422
489
|
}
|
|
423
|
-
|
|
490
|
+
let response = await fetch(url, {
|
|
424
491
|
...finalOptions,
|
|
425
492
|
signal: controller.signal
|
|
426
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
|
+
}
|
|
427
512
|
clearTimeout(timeoutId);
|
|
428
513
|
if (this.rateLimitTracker) {
|
|
429
514
|
this.rateLimitTracker.updateFromHeaders(response.headers);
|
|
@@ -613,6 +698,9 @@ var BaseClient = class {
|
|
|
613
698
|
if (config.interceptors) {
|
|
614
699
|
this.interceptors = config.interceptors;
|
|
615
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
|
+
}
|
|
616
704
|
}
|
|
617
705
|
/**
|
|
618
706
|
* Get current configuration (readonly)
|
|
@@ -640,8 +728,46 @@ var BaseClient = class {
|
|
|
640
728
|
}
|
|
641
729
|
};
|
|
642
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
|
+
|
|
643
756
|
// src/core/webhook-utils.ts
|
|
644
|
-
async function verifyWebhookSignature(payload, signature, secret) {
|
|
757
|
+
async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
|
|
758
|
+
const signed = parseSignedTimestamp(signature);
|
|
759
|
+
if (signed) {
|
|
760
|
+
const tolerance = opts.tolerance ?? 3e5;
|
|
761
|
+
const now = opts.now?.() ?? Date.now();
|
|
762
|
+
if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
|
|
766
|
+
return constantTimeEqual(signed.v1, expected);
|
|
767
|
+
}
|
|
768
|
+
if (opts.allowLegacy === false) {
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
645
771
|
const hexDigest = await computeHmacSha256(payload, secret);
|
|
646
772
|
const expectedPrefixed = `sha256=${hexDigest}`;
|
|
647
773
|
if (signature.startsWith("sha256=")) {
|
|
@@ -649,6 +775,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
|
|
|
649
775
|
}
|
|
650
776
|
return constantTimeEqual(signature, hexDigest);
|
|
651
777
|
}
|
|
778
|
+
function parseSignedTimestamp(signature) {
|
|
779
|
+
if (!signature.includes("t=") || !signature.includes("v1=")) {
|
|
780
|
+
return null;
|
|
781
|
+
}
|
|
782
|
+
let t;
|
|
783
|
+
let v1;
|
|
784
|
+
for (const part of signature.split(",")) {
|
|
785
|
+
const eq = part.indexOf("=");
|
|
786
|
+
if (eq === -1) continue;
|
|
787
|
+
const key = part.slice(0, eq).trim();
|
|
788
|
+
const value = part.slice(eq + 1).trim();
|
|
789
|
+
if (key === "t") t = Number(value);
|
|
790
|
+
else if (key === "v1") v1 = value;
|
|
791
|
+
}
|
|
792
|
+
if (t === void 0 || Number.isNaN(t) || !v1) {
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
return { t, v1 };
|
|
796
|
+
}
|
|
652
797
|
async function computeHmacSha256(message, secret) {
|
|
653
798
|
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
654
799
|
const encoder = new TextEncoder();
|
|
@@ -683,6 +828,61 @@ function constantTimeEqual(a, b) {
|
|
|
683
828
|
return result === 0;
|
|
684
829
|
}
|
|
685
830
|
|
|
831
|
+
// src/geolocation/integrity.ts
|
|
832
|
+
var isNil = (v) => v === null || v === void 0;
|
|
833
|
+
function assessGpsIntegrity(input, opts = {}) {
|
|
834
|
+
const flags = [];
|
|
835
|
+
const now = opts.now ?? (() => Date.now());
|
|
836
|
+
const maxAgeMs = opts.maxAgeMs ?? 6e4;
|
|
837
|
+
const minAccuracy = opts.minAccuracyMeters ?? 1;
|
|
838
|
+
const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
|
|
839
|
+
if (isNil(accuracy)) {
|
|
840
|
+
flags.push("accuracy_missing");
|
|
841
|
+
} else if (accuracy === 0) {
|
|
842
|
+
flags.push("accuracy_zero");
|
|
843
|
+
} else if (accuracy < minAccuracy) {
|
|
844
|
+
flags.push("accuracy_implausibly_small");
|
|
845
|
+
}
|
|
846
|
+
const hasMotion = !isNil(heading) || !isNil(speed);
|
|
847
|
+
if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
|
|
848
|
+
flags.push("motion_without_accuracy");
|
|
849
|
+
}
|
|
850
|
+
if (!isNil(altitude) && isNil(altitudeAccuracy)) {
|
|
851
|
+
flags.push("altitude_without_accuracy");
|
|
852
|
+
}
|
|
853
|
+
if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
|
|
854
|
+
flags.push("stale_timestamp");
|
|
855
|
+
}
|
|
856
|
+
if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
|
|
857
|
+
flags.push(`permission_${opts.permissionState}`);
|
|
858
|
+
}
|
|
859
|
+
return { suspicious: flags.length > 0, flags };
|
|
860
|
+
}
|
|
861
|
+
function gpsIntegrityInputFromPosition(position) {
|
|
862
|
+
const c = position.coords;
|
|
863
|
+
return {
|
|
864
|
+
latitude: c.latitude,
|
|
865
|
+
longitude: c.longitude,
|
|
866
|
+
accuracy: c.accuracy,
|
|
867
|
+
altitude: c.altitude,
|
|
868
|
+
altitudeAccuracy: c.altitudeAccuracy,
|
|
869
|
+
heading: c.heading,
|
|
870
|
+
speed: c.speed,
|
|
871
|
+
timestamp: position.timestamp
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
async function probeGeolocationPermission() {
|
|
875
|
+
try {
|
|
876
|
+
if (typeof navigator === "undefined" || !navigator.permissions?.query) {
|
|
877
|
+
return "unavailable";
|
|
878
|
+
}
|
|
879
|
+
const status = await navigator.permissions.query({ name: "geolocation" });
|
|
880
|
+
return status.state;
|
|
881
|
+
} catch {
|
|
882
|
+
return "unavailable";
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
686
886
|
// src/geolocation/ciphertext.ts
|
|
687
887
|
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
688
888
|
async function computeHMAC(key, message) {
|
|
@@ -735,6 +935,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
735
935
|
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
736
936
|
return null;
|
|
737
937
|
}
|
|
938
|
+
const permissionState = await probeGeolocationPermission();
|
|
738
939
|
return new Promise((resolve) => {
|
|
739
940
|
navigator.geolocation.getCurrentPosition(
|
|
740
941
|
(position) => {
|
|
@@ -751,6 +952,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
751
952
|
resolve(null);
|
|
752
953
|
return;
|
|
753
954
|
}
|
|
955
|
+
const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
|
|
754
956
|
resolve({
|
|
755
957
|
latitude,
|
|
756
958
|
longitude,
|
|
@@ -759,7 +961,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
759
961
|
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
760
962
|
heading: position.coords.heading ?? void 0,
|
|
761
963
|
speed: position.coords.speed ?? void 0,
|
|
762
|
-
timestamp: position.timestamp
|
|
964
|
+
timestamp: position.timestamp,
|
|
965
|
+
integrity_flags: flags.length ? flags : void 0
|
|
763
966
|
});
|
|
764
967
|
},
|
|
765
968
|
() => {
|
|
@@ -826,6 +1029,9 @@ async function generateCipherText(options, config) {
|
|
|
826
1029
|
);
|
|
827
1030
|
if (location) {
|
|
828
1031
|
locationData = location;
|
|
1032
|
+
if (location.integrity_flags?.length) {
|
|
1033
|
+
warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
|
|
1034
|
+
}
|
|
829
1035
|
} else if (gpsRequiredByConfig) {
|
|
830
1036
|
throw new VesantError(
|
|
831
1037
|
`GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
|
|
@@ -945,8 +1151,8 @@ var GeolocationClient = class extends BaseClient {
|
|
|
945
1151
|
* ```
|
|
946
1152
|
*/
|
|
947
1153
|
async verifyIP(request, requestOptions) {
|
|
948
|
-
if (!request.ip_address
|
|
949
|
-
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"]);
|
|
950
1156
|
}
|
|
951
1157
|
return this.requestWithRetry("/api/v1/geo/verify", {
|
|
952
1158
|
method: "POST",
|
|
@@ -1296,20 +1502,12 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1296
1502
|
*
|
|
1297
1503
|
* @example
|
|
1298
1504
|
* ```typescript
|
|
1299
|
-
* //
|
|
1505
|
+
* // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
|
|
1300
1506
|
* const result = await client.captureLocation(token, {
|
|
1301
1507
|
* latitude: 37.7749,
|
|
1302
1508
|
* longitude: -122.4194,
|
|
1303
1509
|
* accuracy: 10
|
|
1304
1510
|
* });
|
|
1305
|
-
*
|
|
1306
|
-
* // Using WiFi positioning (fallback)
|
|
1307
|
-
* const result = await client.captureLocation(token, {
|
|
1308
|
-
* wifi_networks: [
|
|
1309
|
-
* { macAddress: "00:11:22:33:44:55", signalStrength: -50 },
|
|
1310
|
-
* { macAddress: "AA:BB:CC:DD:EE:FF", signalStrength: -70 }
|
|
1311
|
-
* ]
|
|
1312
|
-
* });
|
|
1313
1511
|
* ```
|
|
1314
1512
|
*/
|
|
1315
1513
|
async captureLocation(token, capture, requestOptions) {
|
|
@@ -3442,15 +3640,48 @@ var TaxClient = class extends BaseClient {
|
|
|
3442
3640
|
}
|
|
3443
3641
|
};
|
|
3444
3642
|
|
|
3643
|
+
// src/webhooks/dedup.ts
|
|
3644
|
+
var InMemoryDedupStore = class {
|
|
3645
|
+
constructor(opts = {}) {
|
|
3646
|
+
this.expiryById = /* @__PURE__ */ new Map();
|
|
3647
|
+
this.maxEntries = opts.maxEntries ?? 1e3;
|
|
3648
|
+
this.now = opts.now ?? (() => Date.now());
|
|
3649
|
+
}
|
|
3650
|
+
async seen(id) {
|
|
3651
|
+
return this.expiryById.has(id);
|
|
3652
|
+
}
|
|
3653
|
+
async mark(id, ttlMs) {
|
|
3654
|
+
const now = this.now();
|
|
3655
|
+
this.expiryById.set(id, now + ttlMs);
|
|
3656
|
+
if (this.expiryById.size > this.maxEntries) {
|
|
3657
|
+
for (const [key, expiresAt] of this.expiryById) {
|
|
3658
|
+
if (now > expiresAt) {
|
|
3659
|
+
this.expiryById.delete(key);
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
};
|
|
3665
|
+
|
|
3445
3666
|
// src/webhooks/handler.ts
|
|
3667
|
+
function resolveEventId(event) {
|
|
3668
|
+
if (event.id) {
|
|
3669
|
+
return event.id;
|
|
3670
|
+
}
|
|
3671
|
+
if (event.event_type === "notification.created") {
|
|
3672
|
+
return event.notification?.id;
|
|
3673
|
+
}
|
|
3674
|
+
return void 0;
|
|
3675
|
+
}
|
|
3446
3676
|
var WebhookHandler = class {
|
|
3447
3677
|
constructor(config) {
|
|
3448
3678
|
this.handlers = /* @__PURE__ */ new Map();
|
|
3449
3679
|
this.anyHandlers = [];
|
|
3450
|
-
this.seenEventIds = /* @__PURE__ */ new Map();
|
|
3451
3680
|
this.secret = config.secret;
|
|
3452
3681
|
this.tolerance = config.tolerance ?? 3e5;
|
|
3453
3682
|
this.replayProtection = config.replayProtection ?? true;
|
|
3683
|
+
this.allowLegacySignature = config.allowLegacySignature ?? true;
|
|
3684
|
+
this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
|
|
3454
3685
|
}
|
|
3455
3686
|
/**
|
|
3456
3687
|
* Register a handler for a specific event type.
|
|
@@ -3472,7 +3703,10 @@ var WebhookHandler = class {
|
|
|
3472
3703
|
* Verify signature and parse the webhook body.
|
|
3473
3704
|
*/
|
|
3474
3705
|
async verifyAndParse(body, signature) {
|
|
3475
|
-
const isValid = await verifyWebhookSignature(body, signature, this.secret
|
|
3706
|
+
const isValid = await verifyWebhookSignature(body, signature, this.secret, {
|
|
3707
|
+
tolerance: this.tolerance,
|
|
3708
|
+
allowLegacy: this.allowLegacySignature
|
|
3709
|
+
});
|
|
3476
3710
|
if (!isValid) {
|
|
3477
3711
|
throw new ValidationError("Invalid webhook signature", ["signature"]);
|
|
3478
3712
|
}
|
|
@@ -3488,18 +3722,21 @@ var WebhookHandler = class {
|
|
|
3488
3722
|
/**
|
|
3489
3723
|
* Parse an event without signature verification (for testing).
|
|
3490
3724
|
*/
|
|
3491
|
-
parseEvent(body) {
|
|
3725
|
+
async parseEvent(body) {
|
|
3492
3726
|
return this.parseAndValidate(body);
|
|
3493
3727
|
}
|
|
3494
|
-
parseAndValidate(body) {
|
|
3728
|
+
async parseAndValidate(body) {
|
|
3495
3729
|
const event = JSON.parse(body);
|
|
3496
|
-
|
|
3497
|
-
|
|
3730
|
+
const eventId = resolveEventId(event);
|
|
3731
|
+
if (!event.event_type || !eventId || !event.timestamp) {
|
|
3732
|
+
throw new ValidationError(
|
|
3733
|
+
"Invalid webhook event: missing required fields (event_type, id, timestamp)",
|
|
3734
|
+
["event_type", "id", "timestamp"]
|
|
3735
|
+
);
|
|
3498
3736
|
}
|
|
3499
3737
|
if (this.tolerance > 0) {
|
|
3500
3738
|
const eventTime = new Date(event.timestamp).getTime();
|
|
3501
|
-
|
|
3502
|
-
if (Math.abs(now - eventTime) > this.tolerance) {
|
|
3739
|
+
if (Math.abs(Date.now() - eventTime) > this.tolerance) {
|
|
3503
3740
|
throw new ValidationError(
|
|
3504
3741
|
`Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
|
|
3505
3742
|
["timestamp"]
|
|
@@ -3507,26 +3744,18 @@ var WebhookHandler = class {
|
|
|
3507
3744
|
}
|
|
3508
3745
|
}
|
|
3509
3746
|
if (this.replayProtection) {
|
|
3510
|
-
if (this.
|
|
3747
|
+
if (await this.dedupStore.seen(eventId)) {
|
|
3511
3748
|
throw new ValidationError(
|
|
3512
|
-
`Duplicate webhook event: ${
|
|
3749
|
+
`Duplicate webhook event: ${eventId} has already been processed`,
|
|
3513
3750
|
["id"]
|
|
3514
3751
|
);
|
|
3515
3752
|
}
|
|
3516
|
-
|
|
3517
|
-
this.seenEventIds.set(event.id, now);
|
|
3518
|
-
if (this.seenEventIds.size > 1e3) {
|
|
3519
|
-
for (const [id, seenAt] of this.seenEventIds) {
|
|
3520
|
-
if (now - seenAt > this.tolerance) {
|
|
3521
|
-
this.seenEventIds.delete(id);
|
|
3522
|
-
}
|
|
3523
|
-
}
|
|
3524
|
-
}
|
|
3753
|
+
await this.dedupStore.mark(eventId, this.tolerance);
|
|
3525
3754
|
}
|
|
3526
3755
|
return event;
|
|
3527
3756
|
}
|
|
3528
3757
|
async dispatch(event) {
|
|
3529
|
-
const typeHandlers = this.handlers.get(event.
|
|
3758
|
+
const typeHandlers = this.handlers.get(event.event_type) || [];
|
|
3530
3759
|
const allHandlers = [...typeHandlers, ...this.anyHandlers];
|
|
3531
3760
|
for (const handler of allHandlers) {
|
|
3532
3761
|
await handler(event);
|
|
@@ -3607,6 +3836,6 @@ function buildHandler(options) {
|
|
|
3607
3836
|
return handler;
|
|
3608
3837
|
}
|
|
3609
3838
|
|
|
3610
|
-
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 };
|
|
3839
|
+
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, createConsoleLogger, createNextWebhookHandler, createWebhookMiddleware, decodeCipherText, generateCipherText, gpsIntegrityInputFromPosition, isCipherTextExpired, noopLogger, paginate, probeGeolocationPermission, sdkReasons, verifyWebhookSignature };
|
|
3611
3840
|
//# sourceMappingURL=index.mjs.map
|
|
3612
3841
|
//# sourceMappingURL=index.mjs.map
|