vesant-sdk 1.7.0-dev.c2e85f3 → 1.7.0-dev.c349626
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-DF7hlMEz.d.ts → client-0NOPDnT0.d.ts} +16 -26
- package/dist/{client-DrjgZoH_.d.mts → client-CvWNRwwg.d.mts} +16 -26
- package/dist/{client-B0qhE2kr.d.mts → client-Dps40EtF.d.mts} +2 -2
- package/dist/{client-DtH2RLuy.d.ts → client-SMxqod4j.d.ts} +2 -2
- 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 +126 -18
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +126 -18
- 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 +61 -6
- package/dist/decisions/index.js.map +1 -1
- package/dist/decisions/index.mjs +61 -6
- 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 +129 -18
- package/dist/geolocation/index.js.map +1 -1
- package/dist/geolocation/index.mjs +127 -19
- 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 +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +286 -49
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +281 -50
- 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 +83 -14
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +83 -14
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +56 -8
- package/dist/kyc/index.d.ts +56 -8
- package/dist/kyc/index.js +83 -14
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +83 -14
- 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 +61 -6
- package/dist/risk-profile/index.js.map +1 -1
- package/dist/risk-profile/index.mjs +61 -6
- 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 +61 -6
- package/dist/scores/index.js.map +1 -1
- package/dist/scores/index.mjs +61 -6
- 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 +66 -8
- package/dist/tax/index.js.map +1 -1
- package/dist/tax/index.mjs +66 -8
- 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 +1 -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) {
|
|
@@ -345,6 +401,7 @@ var BaseClient = class {
|
|
|
345
401
|
interceptors: this.interceptors,
|
|
346
402
|
logger: this.logger
|
|
347
403
|
};
|
|
404
|
+
this.authProvider = config.authProvider ?? (apiKey ? new StaticApiKeyProvider(apiKey) : void 0);
|
|
348
405
|
if (config.circuitBreaker) {
|
|
349
406
|
this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
|
|
350
407
|
}
|
|
@@ -352,6 +409,19 @@ var BaseClient = class {
|
|
|
352
409
|
this.rateLimitTracker = new RateLimitTracker();
|
|
353
410
|
}
|
|
354
411
|
}
|
|
412
|
+
/**
|
|
413
|
+
* Resolve the Idempotency-Key for a request. Mutating methods (POST/PUT/PATCH)
|
|
414
|
+
* get the caller-supplied key or a freshly minted UUID; non-mutating methods get
|
|
415
|
+
* none. requestWithRetry resolves this once and injects it so every retry reuses
|
|
416
|
+
* one key (letting the server dedup replays); a direct request() resolves per call.
|
|
417
|
+
*/
|
|
418
|
+
resolveIdempotencyKey(method, requestOptions) {
|
|
419
|
+
const isMutating = ["POST", "PUT", "PATCH"].includes((method || "GET").toUpperCase());
|
|
420
|
+
if (!isMutating) {
|
|
421
|
+
return void 0;
|
|
422
|
+
}
|
|
423
|
+
return requestOptions?.idempotencyKey ?? generateUUID();
|
|
424
|
+
}
|
|
355
425
|
/**
|
|
356
426
|
* Make an HTTP request with timeout and error handling
|
|
357
427
|
*/
|
|
@@ -377,15 +447,20 @@ var BaseClient = class {
|
|
|
377
447
|
...this.config.headers,
|
|
378
448
|
...options.headers || {}
|
|
379
449
|
};
|
|
380
|
-
|
|
381
|
-
|
|
450
|
+
const credential = await this.authProvider?.getCredential({
|
|
451
|
+
tenantId: this.config.tenantId,
|
|
452
|
+
requestId
|
|
453
|
+
});
|
|
454
|
+
if (credential) {
|
|
455
|
+
headers["Authorization"] = `${credential.scheme ?? "Bearer"} ${credential.token}`;
|
|
382
456
|
}
|
|
383
457
|
if (this.config.environment === "sandbox") {
|
|
384
458
|
headers["X-Sandbox"] = "true";
|
|
385
459
|
}
|
|
386
460
|
const method = (options.method || "GET").toUpperCase();
|
|
387
|
-
|
|
388
|
-
|
|
461
|
+
const idempotencyKey = this.resolveIdempotencyKey(method, requestOptions);
|
|
462
|
+
if (idempotencyKey) {
|
|
463
|
+
headers["Idempotency-Key"] = idempotencyKey;
|
|
389
464
|
}
|
|
390
465
|
const controller = new AbortController();
|
|
391
466
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
@@ -406,10 +481,28 @@ var BaseClient = class {
|
|
|
406
481
|
if (this.config.debug) {
|
|
407
482
|
this.logger.debug(`${finalOptions.method || "GET"} ${endpoint}`);
|
|
408
483
|
}
|
|
409
|
-
|
|
484
|
+
let response = await fetch(url, {
|
|
410
485
|
...finalOptions,
|
|
411
486
|
signal: controller.signal
|
|
412
487
|
});
|
|
488
|
+
if (response.status === 401 && this.authProvider?.canRefresh?.()) {
|
|
489
|
+
const refreshed = await this.authProvider.getCredential({
|
|
490
|
+
tenantId: this.config.tenantId,
|
|
491
|
+
requestId,
|
|
492
|
+
forceRefresh: true
|
|
493
|
+
});
|
|
494
|
+
if (refreshed) {
|
|
495
|
+
this.config.onCredentialRotated?.({ reason: "unauthorized" });
|
|
496
|
+
finalOptions = {
|
|
497
|
+
...finalOptions,
|
|
498
|
+
headers: {
|
|
499
|
+
...finalOptions.headers,
|
|
500
|
+
Authorization: `${refreshed.scheme ?? "Bearer"} ${refreshed.token}`
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
response = await fetch(url, { ...finalOptions, signal: controller.signal });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
413
506
|
clearTimeout(timeoutId);
|
|
414
507
|
if (this.rateLimitTracker) {
|
|
415
508
|
this.rateLimitTracker.updateFromHeaders(response.headers);
|
|
@@ -498,9 +591,11 @@ var BaseClient = class {
|
|
|
498
591
|
*/
|
|
499
592
|
async requestWithRetry(endpoint, options = {}, serviceURL, retries = this.config.retries, requestOptions) {
|
|
500
593
|
let lastError;
|
|
594
|
+
const idempotencyKey = this.resolveIdempotencyKey(options.method || "GET", requestOptions);
|
|
595
|
+
const attemptOptions = idempotencyKey ? { ...requestOptions, idempotencyKey } : requestOptions;
|
|
501
596
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
502
597
|
try {
|
|
503
|
-
return await this.request(endpoint, options, serviceURL,
|
|
598
|
+
return await this.request(endpoint, options, serviceURL, attemptOptions);
|
|
504
599
|
} catch (error) {
|
|
505
600
|
lastError = error instanceof Error ? error : new Error("Unknown error");
|
|
506
601
|
if (requestOptions?.signal?.aborted) {
|
|
@@ -597,6 +692,9 @@ var BaseClient = class {
|
|
|
597
692
|
if (config.interceptors) {
|
|
598
693
|
this.interceptors = config.interceptors;
|
|
599
694
|
}
|
|
695
|
+
if (config.authProvider !== void 0 || config.apiKey !== void 0) {
|
|
696
|
+
this.authProvider = this.config.authProvider ?? (this.config.apiKey ? new StaticApiKeyProvider(this.config.apiKey) : void 0);
|
|
697
|
+
}
|
|
600
698
|
}
|
|
601
699
|
/**
|
|
602
700
|
* Get current configuration (readonly)
|
|
@@ -625,7 +723,20 @@ var BaseClient = class {
|
|
|
625
723
|
};
|
|
626
724
|
|
|
627
725
|
// src/core/webhook-utils.ts
|
|
628
|
-
async function verifyWebhookSignature(payload, signature, secret) {
|
|
726
|
+
async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
|
|
727
|
+
const signed = parseSignedTimestamp(signature);
|
|
728
|
+
if (signed) {
|
|
729
|
+
const tolerance = opts.tolerance ?? 3e5;
|
|
730
|
+
const now = opts.now?.() ?? Date.now();
|
|
731
|
+
if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
|
|
735
|
+
return constantTimeEqual(signed.v1, expected);
|
|
736
|
+
}
|
|
737
|
+
if (opts.allowLegacy === false) {
|
|
738
|
+
return false;
|
|
739
|
+
}
|
|
629
740
|
const hexDigest = await computeHmacSha256(payload, secret);
|
|
630
741
|
const expectedPrefixed = `sha256=${hexDigest}`;
|
|
631
742
|
if (signature.startsWith("sha256=")) {
|
|
@@ -633,6 +744,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
|
|
|
633
744
|
}
|
|
634
745
|
return constantTimeEqual(signature, hexDigest);
|
|
635
746
|
}
|
|
747
|
+
function parseSignedTimestamp(signature) {
|
|
748
|
+
if (!signature.includes("t=") || !signature.includes("v1=")) {
|
|
749
|
+
return null;
|
|
750
|
+
}
|
|
751
|
+
let t;
|
|
752
|
+
let v1;
|
|
753
|
+
for (const part of signature.split(",")) {
|
|
754
|
+
const eq = part.indexOf("=");
|
|
755
|
+
if (eq === -1) continue;
|
|
756
|
+
const key = part.slice(0, eq).trim();
|
|
757
|
+
const value = part.slice(eq + 1).trim();
|
|
758
|
+
if (key === "t") t = Number(value);
|
|
759
|
+
else if (key === "v1") v1 = value;
|
|
760
|
+
}
|
|
761
|
+
if (t === void 0 || Number.isNaN(t) || !v1) {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
return { t, v1 };
|
|
765
|
+
}
|
|
636
766
|
async function computeHmacSha256(message, secret) {
|
|
637
767
|
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
638
768
|
const encoder = new TextEncoder();
|
|
@@ -667,6 +797,61 @@ function constantTimeEqual(a, b) {
|
|
|
667
797
|
return result === 0;
|
|
668
798
|
}
|
|
669
799
|
|
|
800
|
+
// src/geolocation/integrity.ts
|
|
801
|
+
var isNil = (v) => v === null || v === void 0;
|
|
802
|
+
function assessGpsIntegrity(input, opts = {}) {
|
|
803
|
+
const flags = [];
|
|
804
|
+
const now = opts.now ?? (() => Date.now());
|
|
805
|
+
const maxAgeMs = opts.maxAgeMs ?? 6e4;
|
|
806
|
+
const minAccuracy = opts.minAccuracyMeters ?? 1;
|
|
807
|
+
const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
|
|
808
|
+
if (isNil(accuracy)) {
|
|
809
|
+
flags.push("accuracy_missing");
|
|
810
|
+
} else if (accuracy === 0) {
|
|
811
|
+
flags.push("accuracy_zero");
|
|
812
|
+
} else if (accuracy < minAccuracy) {
|
|
813
|
+
flags.push("accuracy_implausibly_small");
|
|
814
|
+
}
|
|
815
|
+
const hasMotion = !isNil(heading) || !isNil(speed);
|
|
816
|
+
if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
|
|
817
|
+
flags.push("motion_without_accuracy");
|
|
818
|
+
}
|
|
819
|
+
if (!isNil(altitude) && isNil(altitudeAccuracy)) {
|
|
820
|
+
flags.push("altitude_without_accuracy");
|
|
821
|
+
}
|
|
822
|
+
if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
|
|
823
|
+
flags.push("stale_timestamp");
|
|
824
|
+
}
|
|
825
|
+
if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
|
|
826
|
+
flags.push(`permission_${opts.permissionState}`);
|
|
827
|
+
}
|
|
828
|
+
return { suspicious: flags.length > 0, flags };
|
|
829
|
+
}
|
|
830
|
+
function gpsIntegrityInputFromPosition(position) {
|
|
831
|
+
const c = position.coords;
|
|
832
|
+
return {
|
|
833
|
+
latitude: c.latitude,
|
|
834
|
+
longitude: c.longitude,
|
|
835
|
+
accuracy: c.accuracy,
|
|
836
|
+
altitude: c.altitude,
|
|
837
|
+
altitudeAccuracy: c.altitudeAccuracy,
|
|
838
|
+
heading: c.heading,
|
|
839
|
+
speed: c.speed,
|
|
840
|
+
timestamp: position.timestamp
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
async function probeGeolocationPermission() {
|
|
844
|
+
try {
|
|
845
|
+
if (typeof navigator === "undefined" || !navigator.permissions?.query) {
|
|
846
|
+
return "unavailable";
|
|
847
|
+
}
|
|
848
|
+
const status = await navigator.permissions.query({ name: "geolocation" });
|
|
849
|
+
return status.state;
|
|
850
|
+
} catch {
|
|
851
|
+
return "unavailable";
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
670
855
|
// src/geolocation/ciphertext.ts
|
|
671
856
|
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
672
857
|
async function computeHMAC(key, message) {
|
|
@@ -719,6 +904,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
719
904
|
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
720
905
|
return null;
|
|
721
906
|
}
|
|
907
|
+
const permissionState = await probeGeolocationPermission();
|
|
722
908
|
return new Promise((resolve) => {
|
|
723
909
|
navigator.geolocation.getCurrentPosition(
|
|
724
910
|
(position) => {
|
|
@@ -735,6 +921,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
735
921
|
resolve(null);
|
|
736
922
|
return;
|
|
737
923
|
}
|
|
924
|
+
const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
|
|
738
925
|
resolve({
|
|
739
926
|
latitude,
|
|
740
927
|
longitude,
|
|
@@ -743,7 +930,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
743
930
|
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
744
931
|
heading: position.coords.heading ?? void 0,
|
|
745
932
|
speed: position.coords.speed ?? void 0,
|
|
746
|
-
timestamp: position.timestamp
|
|
933
|
+
timestamp: position.timestamp,
|
|
934
|
+
integrity_flags: flags.length ? flags : void 0
|
|
747
935
|
});
|
|
748
936
|
},
|
|
749
937
|
() => {
|
|
@@ -810,6 +998,9 @@ async function generateCipherText(options, config) {
|
|
|
810
998
|
);
|
|
811
999
|
if (location) {
|
|
812
1000
|
locationData = location;
|
|
1001
|
+
if (location.integrity_flags?.length) {
|
|
1002
|
+
warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
|
|
1003
|
+
}
|
|
813
1004
|
} else if (gpsRequiredByConfig) {
|
|
814
1005
|
throw new VesantError(
|
|
815
1006
|
`GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
|
|
@@ -929,8 +1120,8 @@ var GeolocationClient = class extends BaseClient {
|
|
|
929
1120
|
* ```
|
|
930
1121
|
*/
|
|
931
1122
|
async verifyIP(request, requestOptions) {
|
|
932
|
-
if (!request.ip_address
|
|
933
|
-
throw new ValidationError("ip_address
|
|
1123
|
+
if (request.ip_address !== void 0 && !request.ip_address.trim()) {
|
|
1124
|
+
throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
|
|
934
1125
|
}
|
|
935
1126
|
return this.requestWithRetry("/api/v1/geo/verify", {
|
|
936
1127
|
method: "POST",
|
|
@@ -1280,20 +1471,12 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1280
1471
|
*
|
|
1281
1472
|
* @example
|
|
1282
1473
|
* ```typescript
|
|
1283
|
-
* //
|
|
1474
|
+
* // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
|
|
1284
1475
|
* const result = await client.captureLocation(token, {
|
|
1285
1476
|
* latitude: 37.7749,
|
|
1286
1477
|
* longitude: -122.4194,
|
|
1287
1478
|
* accuracy: 10
|
|
1288
1479
|
* });
|
|
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
1480
|
* ```
|
|
1298
1481
|
*/
|
|
1299
1482
|
async captureLocation(token, capture, requestOptions) {
|
|
@@ -2707,7 +2890,13 @@ var KycClient = class extends BaseClient {
|
|
|
2707
2890
|
*
|
|
2708
2891
|
* Generates a link that the user can visit to submit their KYC documents.
|
|
2709
2892
|
*
|
|
2710
|
-
*
|
|
2893
|
+
* Optionally pass the customer's registered identity data as
|
|
2894
|
+
* `customer_data` (same shape as the geolocation `customer_data` block).
|
|
2895
|
+
* It seeds the customer's risk profile so document verification can
|
|
2896
|
+
* cross-check the submitted document against trusted reference data;
|
|
2897
|
+
* fields never overwrite data already on the profile.
|
|
2898
|
+
*
|
|
2899
|
+
* @param request - Request containing the user ID, optional redirect URL, optional callback URL (receives POST requests), and optional customer identity data
|
|
2711
2900
|
* @returns Response containing the redirect link and KYC ID
|
|
2712
2901
|
*
|
|
2713
2902
|
* @example
|
|
@@ -2715,19 +2904,27 @@ var KycClient = class extends BaseClient {
|
|
|
2715
2904
|
* const result = await client.requestKycSubmitLink({
|
|
2716
2905
|
* user_id: "user_123",
|
|
2717
2906
|
* redirect_url: "https://merchant.com/kyc-complete", // optional
|
|
2718
|
-
* callback_url: "https://merchant.com/api/kyc-webhook" // optional - receives POST requests on status change
|
|
2907
|
+
* callback_url: "https://merchant.com/api/kyc-webhook", // optional - receives POST requests on status change
|
|
2908
|
+
* customer_data: { // optional - seeds the risk profile for document cross-checks
|
|
2909
|
+
* full_name: "John Doe",
|
|
2910
|
+
* date_of_birth: "1999-06-02", // ISO 8601
|
|
2911
|
+
* email: "john@example.com",
|
|
2912
|
+
* phone: "+94771234567",
|
|
2913
|
+
* address: "12 Main St, Colombo", // used for address verification
|
|
2914
|
+
* country: "LK"
|
|
2915
|
+
* }
|
|
2719
2916
|
* });
|
|
2720
2917
|
*
|
|
2721
2918
|
* console.log(`Redirect user to: ${result.link}`);
|
|
2722
2919
|
* console.log(`KYC ID: ${result.kyc_id}`);
|
|
2723
2920
|
* ```
|
|
2724
2921
|
*/
|
|
2725
|
-
async requestKycSubmitLink(request) {
|
|
2922
|
+
async requestKycSubmitLink(request, requestOptions) {
|
|
2726
2923
|
return this.requestWithRetry("/api/v1/kyc/request", {
|
|
2727
2924
|
method: "POST",
|
|
2728
2925
|
body: JSON.stringify(request),
|
|
2729
2926
|
headers: this.getUserHeaders()
|
|
2730
|
-
});
|
|
2927
|
+
}, void 0, void 0, requestOptions);
|
|
2731
2928
|
}
|
|
2732
2929
|
/**
|
|
2733
2930
|
* Create a Event-Based Face Verification session.
|
|
@@ -2760,12 +2957,12 @@ var KycClient = class extends BaseClient {
|
|
|
2760
2957
|
*
|
|
2761
2958
|
* @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
|
|
2762
2959
|
*/
|
|
2763
|
-
async submitEventBasedFaceVerificationSession(request) {
|
|
2960
|
+
async submitEventBasedFaceVerificationSession(request, requestOptions) {
|
|
2764
2961
|
return this.request("/api/v1/kyc/face/submit", {
|
|
2765
2962
|
method: "POST",
|
|
2766
2963
|
body: JSON.stringify(request),
|
|
2767
2964
|
headers: this.getUserHeaders()
|
|
2768
|
-
});
|
|
2965
|
+
}, void 0, requestOptions);
|
|
2769
2966
|
}
|
|
2770
2967
|
/**
|
|
2771
2968
|
* Look up the current state of a Event-Based Face Verification session by its
|
|
@@ -2881,12 +3078,12 @@ var KycClient = class extends BaseClient {
|
|
|
2881
3078
|
* console.log(`Verification submitted: ${result.reference}`);
|
|
2882
3079
|
* ```
|
|
2883
3080
|
*/
|
|
2884
|
-
async submitVerification(request) {
|
|
3081
|
+
async submitVerification(request, requestOptions) {
|
|
2885
3082
|
return this.requestWithRetry("/api/v1/kyc/submit", {
|
|
2886
3083
|
method: "POST",
|
|
2887
3084
|
body: JSON.stringify(request),
|
|
2888
3085
|
headers: this.getUserHeaders()
|
|
2889
|
-
});
|
|
3086
|
+
}, void 0, void 0, requestOptions);
|
|
2890
3087
|
}
|
|
2891
3088
|
/**
|
|
2892
3089
|
* Get a KYC request by ID
|
|
@@ -3326,10 +3523,13 @@ var TaxClient = class extends BaseClient {
|
|
|
3326
3523
|
{ method: "POST" }
|
|
3327
3524
|
);
|
|
3328
3525
|
}
|
|
3329
|
-
async checkTINStatus(customerID) {
|
|
3526
|
+
async checkTINStatus(customerID, requestOptions) {
|
|
3330
3527
|
return this.requestWithRetry(
|
|
3331
3528
|
`/api/v1/tax/customer-tax-profiles/${customerID}/check-tin`,
|
|
3332
|
-
{ method: "POST" }
|
|
3529
|
+
{ method: "POST" },
|
|
3530
|
+
void 0,
|
|
3531
|
+
void 0,
|
|
3532
|
+
requestOptions
|
|
3333
3533
|
);
|
|
3334
3534
|
}
|
|
3335
3535
|
async reRequestTaxForm(customerID, input) {
|
|
@@ -3409,15 +3609,48 @@ var TaxClient = class extends BaseClient {
|
|
|
3409
3609
|
}
|
|
3410
3610
|
};
|
|
3411
3611
|
|
|
3612
|
+
// src/webhooks/dedup.ts
|
|
3613
|
+
var InMemoryDedupStore = class {
|
|
3614
|
+
constructor(opts = {}) {
|
|
3615
|
+
this.expiryById = /* @__PURE__ */ new Map();
|
|
3616
|
+
this.maxEntries = opts.maxEntries ?? 1e3;
|
|
3617
|
+
this.now = opts.now ?? (() => Date.now());
|
|
3618
|
+
}
|
|
3619
|
+
async seen(id) {
|
|
3620
|
+
return this.expiryById.has(id);
|
|
3621
|
+
}
|
|
3622
|
+
async mark(id, ttlMs) {
|
|
3623
|
+
const now = this.now();
|
|
3624
|
+
this.expiryById.set(id, now + ttlMs);
|
|
3625
|
+
if (this.expiryById.size > this.maxEntries) {
|
|
3626
|
+
for (const [key, expiresAt] of this.expiryById) {
|
|
3627
|
+
if (now > expiresAt) {
|
|
3628
|
+
this.expiryById.delete(key);
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
};
|
|
3634
|
+
|
|
3412
3635
|
// src/webhooks/handler.ts
|
|
3636
|
+
function resolveEventId(event) {
|
|
3637
|
+
if (event.id) {
|
|
3638
|
+
return event.id;
|
|
3639
|
+
}
|
|
3640
|
+
if (event.event_type === "notification.created") {
|
|
3641
|
+
return event.notification?.id;
|
|
3642
|
+
}
|
|
3643
|
+
return void 0;
|
|
3644
|
+
}
|
|
3413
3645
|
var WebhookHandler = class {
|
|
3414
3646
|
constructor(config) {
|
|
3415
3647
|
this.handlers = /* @__PURE__ */ new Map();
|
|
3416
3648
|
this.anyHandlers = [];
|
|
3417
|
-
this.seenEventIds = /* @__PURE__ */ new Map();
|
|
3418
3649
|
this.secret = config.secret;
|
|
3419
3650
|
this.tolerance = config.tolerance ?? 3e5;
|
|
3420
3651
|
this.replayProtection = config.replayProtection ?? true;
|
|
3652
|
+
this.allowLegacySignature = config.allowLegacySignature ?? true;
|
|
3653
|
+
this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
|
|
3421
3654
|
}
|
|
3422
3655
|
/**
|
|
3423
3656
|
* Register a handler for a specific event type.
|
|
@@ -3439,7 +3672,10 @@ var WebhookHandler = class {
|
|
|
3439
3672
|
* Verify signature and parse the webhook body.
|
|
3440
3673
|
*/
|
|
3441
3674
|
async verifyAndParse(body, signature) {
|
|
3442
|
-
const isValid = await verifyWebhookSignature(body, signature, this.secret
|
|
3675
|
+
const isValid = await verifyWebhookSignature(body, signature, this.secret, {
|
|
3676
|
+
tolerance: this.tolerance,
|
|
3677
|
+
allowLegacy: this.allowLegacySignature
|
|
3678
|
+
});
|
|
3443
3679
|
if (!isValid) {
|
|
3444
3680
|
throw new ValidationError("Invalid webhook signature", ["signature"]);
|
|
3445
3681
|
}
|
|
@@ -3455,18 +3691,21 @@ var WebhookHandler = class {
|
|
|
3455
3691
|
/**
|
|
3456
3692
|
* Parse an event without signature verification (for testing).
|
|
3457
3693
|
*/
|
|
3458
|
-
parseEvent(body) {
|
|
3694
|
+
async parseEvent(body) {
|
|
3459
3695
|
return this.parseAndValidate(body);
|
|
3460
3696
|
}
|
|
3461
|
-
parseAndValidate(body) {
|
|
3697
|
+
async parseAndValidate(body) {
|
|
3462
3698
|
const event = JSON.parse(body);
|
|
3463
|
-
|
|
3464
|
-
|
|
3699
|
+
const eventId = resolveEventId(event);
|
|
3700
|
+
if (!event.event_type || !eventId || !event.timestamp) {
|
|
3701
|
+
throw new ValidationError(
|
|
3702
|
+
"Invalid webhook event: missing required fields (event_type, id, timestamp)",
|
|
3703
|
+
["event_type", "id", "timestamp"]
|
|
3704
|
+
);
|
|
3465
3705
|
}
|
|
3466
3706
|
if (this.tolerance > 0) {
|
|
3467
3707
|
const eventTime = new Date(event.timestamp).getTime();
|
|
3468
|
-
|
|
3469
|
-
if (Math.abs(now - eventTime) > this.tolerance) {
|
|
3708
|
+
if (Math.abs(Date.now() - eventTime) > this.tolerance) {
|
|
3470
3709
|
throw new ValidationError(
|
|
3471
3710
|
`Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
|
|
3472
3711
|
["timestamp"]
|
|
@@ -3474,26 +3713,18 @@ var WebhookHandler = class {
|
|
|
3474
3713
|
}
|
|
3475
3714
|
}
|
|
3476
3715
|
if (this.replayProtection) {
|
|
3477
|
-
if (this.
|
|
3716
|
+
if (await this.dedupStore.seen(eventId)) {
|
|
3478
3717
|
throw new ValidationError(
|
|
3479
|
-
`Duplicate webhook event: ${
|
|
3718
|
+
`Duplicate webhook event: ${eventId} has already been processed`,
|
|
3480
3719
|
["id"]
|
|
3481
3720
|
);
|
|
3482
3721
|
}
|
|
3483
|
-
|
|
3484
|
-
this.seenEventIds.set(event.id, now);
|
|
3485
|
-
if (this.seenEventIds.size > 1e3) {
|
|
3486
|
-
for (const [id, seenAt] of this.seenEventIds) {
|
|
3487
|
-
if (now - seenAt > this.tolerance) {
|
|
3488
|
-
this.seenEventIds.delete(id);
|
|
3489
|
-
}
|
|
3490
|
-
}
|
|
3491
|
-
}
|
|
3722
|
+
await this.dedupStore.mark(eventId, this.tolerance);
|
|
3492
3723
|
}
|
|
3493
3724
|
return event;
|
|
3494
3725
|
}
|
|
3495
3726
|
async dispatch(event) {
|
|
3496
|
-
const typeHandlers = this.handlers.get(event.
|
|
3727
|
+
const typeHandlers = this.handlers.get(event.event_type) || [];
|
|
3497
3728
|
const allHandlers = [...typeHandlers, ...this.anyHandlers];
|
|
3498
3729
|
for (const handler of allHandlers) {
|
|
3499
3730
|
await handler(event);
|
|
@@ -3574,6 +3805,6 @@ function buildHandler(options) {
|
|
|
3574
3805
|
return handler;
|
|
3575
3806
|
}
|
|
3576
3807
|
|
|
3577
|
-
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 };
|
|
3808
|
+
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, createConsoleLogger, createNextWebhookHandler, createWebhookMiddleware, decodeCipherText, generateCipherText, gpsIntegrityInputFromPosition, isCipherTextExpired, noopLogger, probeGeolocationPermission, sdkReasons, verifyWebhookSignature };
|
|
3578
3809
|
//# sourceMappingURL=index.mjs.map
|
|
3579
3810
|
//# sourceMappingURL=index.mjs.map
|