vesant-sdk 1.7.0-dev.5305eb7 → 1.7.0-dev.65f378a
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-DoMSYMMR.d.ts → client-0NOPDnT0.d.ts} +29 -26
- package/dist/{client-DMIRx7Tu.d.mts → client-CvWNRwwg.d.mts} +29 -26
- package/dist/{client-ZNdnpWe7.d.mts → client-Dps40EtF.d.mts} +2 -2
- package/dist/{client-C3DCmGe9.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 -21
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +126 -21
- 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 +290 -56
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +285 -57
- 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 +87 -18
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +87 -18
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +90 -13
- package/dist/kyc/index.d.ts +90 -13
- package/dist/kyc/index.js +87 -18
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +87 -18
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +9 -3
- package/dist/react.d.ts +9 -3
- package/dist/react.js +180 -77
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +180 -77
- 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.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) {
|
|
@@ -347,6 +403,7 @@ var BaseClient = class {
|
|
|
347
403
|
interceptors: this.interceptors,
|
|
348
404
|
logger: this.logger
|
|
349
405
|
};
|
|
406
|
+
this.authProvider = config.authProvider ?? (apiKey ? new StaticApiKeyProvider(apiKey) : void 0);
|
|
350
407
|
if (config.circuitBreaker) {
|
|
351
408
|
this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
|
|
352
409
|
}
|
|
@@ -354,6 +411,19 @@ var BaseClient = class {
|
|
|
354
411
|
this.rateLimitTracker = new RateLimitTracker();
|
|
355
412
|
}
|
|
356
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* Resolve the Idempotency-Key for a request. Mutating methods (POST/PUT/PATCH)
|
|
416
|
+
* get the caller-supplied key or a freshly minted UUID; non-mutating methods get
|
|
417
|
+
* none. requestWithRetry resolves this once and injects it so every retry reuses
|
|
418
|
+
* one key (letting the server dedup replays); a direct request() resolves per call.
|
|
419
|
+
*/
|
|
420
|
+
resolveIdempotencyKey(method, requestOptions) {
|
|
421
|
+
const isMutating = ["POST", "PUT", "PATCH"].includes((method || "GET").toUpperCase());
|
|
422
|
+
if (!isMutating) {
|
|
423
|
+
return void 0;
|
|
424
|
+
}
|
|
425
|
+
return requestOptions?.idempotencyKey ?? generateUUID();
|
|
426
|
+
}
|
|
357
427
|
/**
|
|
358
428
|
* Make an HTTP request with timeout and error handling
|
|
359
429
|
*/
|
|
@@ -379,15 +449,20 @@ var BaseClient = class {
|
|
|
379
449
|
...this.config.headers,
|
|
380
450
|
...options.headers || {}
|
|
381
451
|
};
|
|
382
|
-
|
|
383
|
-
|
|
452
|
+
const credential = await this.authProvider?.getCredential({
|
|
453
|
+
tenantId: this.config.tenantId,
|
|
454
|
+
requestId
|
|
455
|
+
});
|
|
456
|
+
if (credential) {
|
|
457
|
+
headers["Authorization"] = `${credential.scheme ?? "Bearer"} ${credential.token}`;
|
|
384
458
|
}
|
|
385
459
|
if (this.config.environment === "sandbox") {
|
|
386
460
|
headers["X-Sandbox"] = "true";
|
|
387
461
|
}
|
|
388
462
|
const method = (options.method || "GET").toUpperCase();
|
|
389
|
-
|
|
390
|
-
|
|
463
|
+
const idempotencyKey = this.resolveIdempotencyKey(method, requestOptions);
|
|
464
|
+
if (idempotencyKey) {
|
|
465
|
+
headers["Idempotency-Key"] = idempotencyKey;
|
|
391
466
|
}
|
|
392
467
|
const controller = new AbortController();
|
|
393
468
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
@@ -408,10 +483,28 @@ var BaseClient = class {
|
|
|
408
483
|
if (this.config.debug) {
|
|
409
484
|
this.logger.debug(`${finalOptions.method || "GET"} ${endpoint}`);
|
|
410
485
|
}
|
|
411
|
-
|
|
486
|
+
let response = await fetch(url, {
|
|
412
487
|
...finalOptions,
|
|
413
488
|
signal: controller.signal
|
|
414
489
|
});
|
|
490
|
+
if (response.status === 401 && this.authProvider?.canRefresh?.()) {
|
|
491
|
+
const refreshed = await this.authProvider.getCredential({
|
|
492
|
+
tenantId: this.config.tenantId,
|
|
493
|
+
requestId,
|
|
494
|
+
forceRefresh: true
|
|
495
|
+
});
|
|
496
|
+
if (refreshed) {
|
|
497
|
+
this.config.onCredentialRotated?.({ reason: "unauthorized" });
|
|
498
|
+
finalOptions = {
|
|
499
|
+
...finalOptions,
|
|
500
|
+
headers: {
|
|
501
|
+
...finalOptions.headers,
|
|
502
|
+
Authorization: `${refreshed.scheme ?? "Bearer"} ${refreshed.token}`
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
response = await fetch(url, { ...finalOptions, signal: controller.signal });
|
|
506
|
+
}
|
|
507
|
+
}
|
|
415
508
|
clearTimeout(timeoutId);
|
|
416
509
|
if (this.rateLimitTracker) {
|
|
417
510
|
this.rateLimitTracker.updateFromHeaders(response.headers);
|
|
@@ -500,9 +593,11 @@ var BaseClient = class {
|
|
|
500
593
|
*/
|
|
501
594
|
async requestWithRetry(endpoint, options = {}, serviceURL, retries = this.config.retries, requestOptions) {
|
|
502
595
|
let lastError;
|
|
596
|
+
const idempotencyKey = this.resolveIdempotencyKey(options.method || "GET", requestOptions);
|
|
597
|
+
const attemptOptions = idempotencyKey ? { ...requestOptions, idempotencyKey } : requestOptions;
|
|
503
598
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
504
599
|
try {
|
|
505
|
-
return await this.request(endpoint, options, serviceURL,
|
|
600
|
+
return await this.request(endpoint, options, serviceURL, attemptOptions);
|
|
506
601
|
} catch (error) {
|
|
507
602
|
lastError = error instanceof Error ? error : new Error("Unknown error");
|
|
508
603
|
if (requestOptions?.signal?.aborted) {
|
|
@@ -599,6 +694,9 @@ var BaseClient = class {
|
|
|
599
694
|
if (config.interceptors) {
|
|
600
695
|
this.interceptors = config.interceptors;
|
|
601
696
|
}
|
|
697
|
+
if (config.authProvider !== void 0 || config.apiKey !== void 0) {
|
|
698
|
+
this.authProvider = this.config.authProvider ?? (this.config.apiKey ? new StaticApiKeyProvider(this.config.apiKey) : void 0);
|
|
699
|
+
}
|
|
602
700
|
}
|
|
603
701
|
/**
|
|
604
702
|
* Get current configuration (readonly)
|
|
@@ -627,7 +725,20 @@ var BaseClient = class {
|
|
|
627
725
|
};
|
|
628
726
|
|
|
629
727
|
// src/core/webhook-utils.ts
|
|
630
|
-
async function verifyWebhookSignature(payload, signature, secret) {
|
|
728
|
+
async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
|
|
729
|
+
const signed = parseSignedTimestamp(signature);
|
|
730
|
+
if (signed) {
|
|
731
|
+
const tolerance = opts.tolerance ?? 3e5;
|
|
732
|
+
const now = opts.now?.() ?? Date.now();
|
|
733
|
+
if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
|
|
737
|
+
return constantTimeEqual(signed.v1, expected);
|
|
738
|
+
}
|
|
739
|
+
if (opts.allowLegacy === false) {
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
631
742
|
const hexDigest = await computeHmacSha256(payload, secret);
|
|
632
743
|
const expectedPrefixed = `sha256=${hexDigest}`;
|
|
633
744
|
if (signature.startsWith("sha256=")) {
|
|
@@ -635,6 +746,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
|
|
|
635
746
|
}
|
|
636
747
|
return constantTimeEqual(signature, hexDigest);
|
|
637
748
|
}
|
|
749
|
+
function parseSignedTimestamp(signature) {
|
|
750
|
+
if (!signature.includes("t=") || !signature.includes("v1=")) {
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
let t;
|
|
754
|
+
let v1;
|
|
755
|
+
for (const part of signature.split(",")) {
|
|
756
|
+
const eq = part.indexOf("=");
|
|
757
|
+
if (eq === -1) continue;
|
|
758
|
+
const key = part.slice(0, eq).trim();
|
|
759
|
+
const value = part.slice(eq + 1).trim();
|
|
760
|
+
if (key === "t") t = Number(value);
|
|
761
|
+
else if (key === "v1") v1 = value;
|
|
762
|
+
}
|
|
763
|
+
if (t === void 0 || Number.isNaN(t) || !v1) {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
return { t, v1 };
|
|
767
|
+
}
|
|
638
768
|
async function computeHmacSha256(message, secret) {
|
|
639
769
|
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
640
770
|
const encoder = new TextEncoder();
|
|
@@ -669,6 +799,61 @@ function constantTimeEqual(a, b) {
|
|
|
669
799
|
return result === 0;
|
|
670
800
|
}
|
|
671
801
|
|
|
802
|
+
// src/geolocation/integrity.ts
|
|
803
|
+
var isNil = (v) => v === null || v === void 0;
|
|
804
|
+
function assessGpsIntegrity(input, opts = {}) {
|
|
805
|
+
const flags = [];
|
|
806
|
+
const now = opts.now ?? (() => Date.now());
|
|
807
|
+
const maxAgeMs = opts.maxAgeMs ?? 6e4;
|
|
808
|
+
const minAccuracy = opts.minAccuracyMeters ?? 1;
|
|
809
|
+
const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
|
|
810
|
+
if (isNil(accuracy)) {
|
|
811
|
+
flags.push("accuracy_missing");
|
|
812
|
+
} else if (accuracy === 0) {
|
|
813
|
+
flags.push("accuracy_zero");
|
|
814
|
+
} else if (accuracy < minAccuracy) {
|
|
815
|
+
flags.push("accuracy_implausibly_small");
|
|
816
|
+
}
|
|
817
|
+
const hasMotion = !isNil(heading) || !isNil(speed);
|
|
818
|
+
if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
|
|
819
|
+
flags.push("motion_without_accuracy");
|
|
820
|
+
}
|
|
821
|
+
if (!isNil(altitude) && isNil(altitudeAccuracy)) {
|
|
822
|
+
flags.push("altitude_without_accuracy");
|
|
823
|
+
}
|
|
824
|
+
if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
|
|
825
|
+
flags.push("stale_timestamp");
|
|
826
|
+
}
|
|
827
|
+
if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
|
|
828
|
+
flags.push(`permission_${opts.permissionState}`);
|
|
829
|
+
}
|
|
830
|
+
return { suspicious: flags.length > 0, flags };
|
|
831
|
+
}
|
|
832
|
+
function gpsIntegrityInputFromPosition(position) {
|
|
833
|
+
const c = position.coords;
|
|
834
|
+
return {
|
|
835
|
+
latitude: c.latitude,
|
|
836
|
+
longitude: c.longitude,
|
|
837
|
+
accuracy: c.accuracy,
|
|
838
|
+
altitude: c.altitude,
|
|
839
|
+
altitudeAccuracy: c.altitudeAccuracy,
|
|
840
|
+
heading: c.heading,
|
|
841
|
+
speed: c.speed,
|
|
842
|
+
timestamp: position.timestamp
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
async function probeGeolocationPermission() {
|
|
846
|
+
try {
|
|
847
|
+
if (typeof navigator === "undefined" || !navigator.permissions?.query) {
|
|
848
|
+
return "unavailable";
|
|
849
|
+
}
|
|
850
|
+
const status = await navigator.permissions.query({ name: "geolocation" });
|
|
851
|
+
return status.state;
|
|
852
|
+
} catch {
|
|
853
|
+
return "unavailable";
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
672
857
|
// src/geolocation/ciphertext.ts
|
|
673
858
|
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
674
859
|
async function computeHMAC(key, message) {
|
|
@@ -721,6 +906,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
721
906
|
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
722
907
|
return null;
|
|
723
908
|
}
|
|
909
|
+
const permissionState = await probeGeolocationPermission();
|
|
724
910
|
return new Promise((resolve) => {
|
|
725
911
|
navigator.geolocation.getCurrentPosition(
|
|
726
912
|
(position) => {
|
|
@@ -737,6 +923,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
737
923
|
resolve(null);
|
|
738
924
|
return;
|
|
739
925
|
}
|
|
926
|
+
const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
|
|
740
927
|
resolve({
|
|
741
928
|
latitude,
|
|
742
929
|
longitude,
|
|
@@ -745,7 +932,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
745
932
|
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
746
933
|
heading: position.coords.heading ?? void 0,
|
|
747
934
|
speed: position.coords.speed ?? void 0,
|
|
748
|
-
timestamp: position.timestamp
|
|
935
|
+
timestamp: position.timestamp,
|
|
936
|
+
integrity_flags: flags.length ? flags : void 0
|
|
749
937
|
});
|
|
750
938
|
},
|
|
751
939
|
() => {
|
|
@@ -812,6 +1000,9 @@ async function generateCipherText(options, config) {
|
|
|
812
1000
|
);
|
|
813
1001
|
if (location) {
|
|
814
1002
|
locationData = location;
|
|
1003
|
+
if (location.integrity_flags?.length) {
|
|
1004
|
+
warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
|
|
1005
|
+
}
|
|
815
1006
|
} else if (gpsRequiredByConfig) {
|
|
816
1007
|
throw new VesantError(
|
|
817
1008
|
`GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
|
|
@@ -931,8 +1122,8 @@ var GeolocationClient = class extends BaseClient {
|
|
|
931
1122
|
* ```
|
|
932
1123
|
*/
|
|
933
1124
|
async verifyIP(request, requestOptions) {
|
|
934
|
-
if (!request.ip_address
|
|
935
|
-
throw new ValidationError("ip_address
|
|
1125
|
+
if (request.ip_address !== void 0 && !request.ip_address.trim()) {
|
|
1126
|
+
throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
|
|
936
1127
|
}
|
|
937
1128
|
return this.requestWithRetry("/api/v1/geo/verify", {
|
|
938
1129
|
method: "POST",
|
|
@@ -1282,20 +1473,12 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1282
1473
|
*
|
|
1283
1474
|
* @example
|
|
1284
1475
|
* ```typescript
|
|
1285
|
-
* //
|
|
1476
|
+
* // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
|
|
1286
1477
|
* const result = await client.captureLocation(token, {
|
|
1287
1478
|
* latitude: 37.7749,
|
|
1288
1479
|
* longitude: -122.4194,
|
|
1289
1480
|
* accuracy: 10
|
|
1290
1481
|
* });
|
|
1291
|
-
*
|
|
1292
|
-
* // Using WiFi positioning (fallback)
|
|
1293
|
-
* const result = await client.captureLocation(token, {
|
|
1294
|
-
* wifi_networks: [
|
|
1295
|
-
* { macAddress: "00:11:22:33:44:55", signalStrength: -50 },
|
|
1296
|
-
* { macAddress: "AA:BB:CC:DD:EE:FF", signalStrength: -70 }
|
|
1297
|
-
* ]
|
|
1298
|
-
* });
|
|
1299
1482
|
* ```
|
|
1300
1483
|
*/
|
|
1301
1484
|
async captureLocation(token, capture, requestOptions) {
|
|
@@ -1800,9 +1983,6 @@ var ComplianceClient = class {
|
|
|
1800
1983
|
if (cipherTextResult.risk?.is_blocked) {
|
|
1801
1984
|
blockReasons.push(...cipherTextResult.risk.block_reasons_structured ?? []);
|
|
1802
1985
|
}
|
|
1803
|
-
if (cipherTextResult.risk?.location_mismatch) {
|
|
1804
|
-
blockReasons.push(sdkReasons.gpsIPMismatch());
|
|
1805
|
-
}
|
|
1806
1986
|
}
|
|
1807
1987
|
if (geoVerification.gps_required && !cipherTextResult) {
|
|
1808
1988
|
blockReasons.push(sdkReasons.gpsRequired());
|
|
@@ -2712,7 +2892,13 @@ var KycClient = class extends BaseClient {
|
|
|
2712
2892
|
*
|
|
2713
2893
|
* Generates a link that the user can visit to submit their KYC documents.
|
|
2714
2894
|
*
|
|
2715
|
-
*
|
|
2895
|
+
* Optionally pass the customer's registered identity data as
|
|
2896
|
+
* `customer_data` (same shape as the geolocation `customer_data` block).
|
|
2897
|
+
* It seeds the customer's risk profile so document verification can
|
|
2898
|
+
* cross-check the submitted document against trusted reference data;
|
|
2899
|
+
* fields never overwrite data already on the profile.
|
|
2900
|
+
*
|
|
2901
|
+
* @param request - Request containing the user ID, optional redirect URL, optional callback URL (receives POST requests), and optional customer identity data
|
|
2716
2902
|
* @returns Response containing the redirect link and KYC ID
|
|
2717
2903
|
*
|
|
2718
2904
|
* @example
|
|
@@ -2720,19 +2906,27 @@ var KycClient = class extends BaseClient {
|
|
|
2720
2906
|
* const result = await client.requestKycSubmitLink({
|
|
2721
2907
|
* user_id: "user_123",
|
|
2722
2908
|
* redirect_url: "https://merchant.com/kyc-complete", // optional
|
|
2723
|
-
* callback_url: "https://merchant.com/api/kyc-webhook" // optional - receives POST requests on status change
|
|
2909
|
+
* callback_url: "https://merchant.com/api/kyc-webhook", // optional - receives POST requests on status change
|
|
2910
|
+
* customer_data: { // optional - seeds the risk profile for document cross-checks
|
|
2911
|
+
* full_name: "John Doe",
|
|
2912
|
+
* date_of_birth: "1999-06-02", // ISO 8601
|
|
2913
|
+
* email: "john@example.com",
|
|
2914
|
+
* phone: "+94771234567",
|
|
2915
|
+
* address: "12 Main St, Colombo", // used for address verification
|
|
2916
|
+
* country: "LK"
|
|
2917
|
+
* }
|
|
2724
2918
|
* });
|
|
2725
2919
|
*
|
|
2726
2920
|
* console.log(`Redirect user to: ${result.link}`);
|
|
2727
2921
|
* console.log(`KYC ID: ${result.kyc_id}`);
|
|
2728
2922
|
* ```
|
|
2729
2923
|
*/
|
|
2730
|
-
async requestKycSubmitLink(request) {
|
|
2924
|
+
async requestKycSubmitLink(request, requestOptions) {
|
|
2731
2925
|
return this.requestWithRetry("/api/v1/kyc/request", {
|
|
2732
2926
|
method: "POST",
|
|
2733
2927
|
body: JSON.stringify(request),
|
|
2734
2928
|
headers: this.getUserHeaders()
|
|
2735
|
-
});
|
|
2929
|
+
}, void 0, void 0, requestOptions);
|
|
2736
2930
|
}
|
|
2737
2931
|
/**
|
|
2738
2932
|
* Create a Event-Based Face Verification session.
|
|
@@ -2765,12 +2959,12 @@ var KycClient = class extends BaseClient {
|
|
|
2765
2959
|
*
|
|
2766
2960
|
* @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
|
|
2767
2961
|
*/
|
|
2768
|
-
async submitEventBasedFaceVerificationSession(request) {
|
|
2962
|
+
async submitEventBasedFaceVerificationSession(request, requestOptions) {
|
|
2769
2963
|
return this.request("/api/v1/kyc/face/submit", {
|
|
2770
2964
|
method: "POST",
|
|
2771
2965
|
body: JSON.stringify(request),
|
|
2772
2966
|
headers: this.getUserHeaders()
|
|
2773
|
-
});
|
|
2967
|
+
}, void 0, requestOptions);
|
|
2774
2968
|
}
|
|
2775
2969
|
/**
|
|
2776
2970
|
* Look up the current state of a Event-Based Face Verification session by its
|
|
@@ -2869,7 +3063,7 @@ var KycClient = class extends BaseClient {
|
|
|
2869
3063
|
* @example
|
|
2870
3064
|
* ```typescript
|
|
2871
3065
|
* const result = await client.submitVerification({
|
|
2872
|
-
* reference: "
|
|
3066
|
+
* reference: "4d2aff34-ab86-422d-992e-50b1234a5b67",
|
|
2873
3067
|
* email: "customer@example.com",
|
|
2874
3068
|
* country: "US",
|
|
2875
3069
|
* document: {
|
|
@@ -2886,12 +3080,12 @@ var KycClient = class extends BaseClient {
|
|
|
2886
3080
|
* console.log(`Verification submitted: ${result.reference}`);
|
|
2887
3081
|
* ```
|
|
2888
3082
|
*/
|
|
2889
|
-
async submitVerification(request) {
|
|
3083
|
+
async submitVerification(request, requestOptions) {
|
|
2890
3084
|
return this.requestWithRetry("/api/v1/kyc/submit", {
|
|
2891
3085
|
method: "POST",
|
|
2892
3086
|
body: JSON.stringify(request),
|
|
2893
3087
|
headers: this.getUserHeaders()
|
|
2894
|
-
});
|
|
3088
|
+
}, void 0, void 0, requestOptions);
|
|
2895
3089
|
}
|
|
2896
3090
|
/**
|
|
2897
3091
|
* Get a KYC request by ID
|
|
@@ -2901,7 +3095,7 @@ var KycClient = class extends BaseClient {
|
|
|
2901
3095
|
*
|
|
2902
3096
|
* @example
|
|
2903
3097
|
* ```typescript
|
|
2904
|
-
* const kyc = await client.getKycRequest("
|
|
3098
|
+
* const kyc = await client.getKycRequest("550e8400-e29b-41d4-a716-446655440000");
|
|
2905
3099
|
* console.log(`Status: ${kyc.status}, ID Verified: ${kyc.id_verified}`);
|
|
2906
3100
|
* ```
|
|
2907
3101
|
*/
|
|
@@ -2976,7 +3170,7 @@ var KycClient = class extends BaseClient {
|
|
|
2976
3170
|
* @example
|
|
2977
3171
|
* ```typescript
|
|
2978
3172
|
* await client.requestAdditionalDocuments({
|
|
2979
|
-
* id: "
|
|
3173
|
+
* id: "550e8400-e29b-41d4-a716-446655440000",
|
|
2980
3174
|
* document_types: ["address", "document_two"],
|
|
2981
3175
|
* message: "Please provide proof of address and secondary ID"
|
|
2982
3176
|
* });
|
|
@@ -2997,7 +3191,7 @@ var KycClient = class extends BaseClient {
|
|
|
2997
3191
|
*
|
|
2998
3192
|
* @example
|
|
2999
3193
|
* ```typescript
|
|
3000
|
-
* const proofs = await client.getProofDownloadURLs("
|
|
3194
|
+
* const proofs = await client.getProofDownloadURLs("550e8400-e29b-41d4-a716-446655440000");
|
|
3001
3195
|
* proofs.forEach(proof => {
|
|
3002
3196
|
* console.log(`${proof.type}: ${proof.url} (expires: ${proof.expires_at})`);
|
|
3003
3197
|
* });
|
|
@@ -3331,10 +3525,13 @@ var TaxClient = class extends BaseClient {
|
|
|
3331
3525
|
{ method: "POST" }
|
|
3332
3526
|
);
|
|
3333
3527
|
}
|
|
3334
|
-
async checkTINStatus(customerID) {
|
|
3528
|
+
async checkTINStatus(customerID, requestOptions) {
|
|
3335
3529
|
return this.requestWithRetry(
|
|
3336
3530
|
`/api/v1/tax/customer-tax-profiles/${customerID}/check-tin`,
|
|
3337
|
-
{ method: "POST" }
|
|
3531
|
+
{ method: "POST" },
|
|
3532
|
+
void 0,
|
|
3533
|
+
void 0,
|
|
3534
|
+
requestOptions
|
|
3338
3535
|
);
|
|
3339
3536
|
}
|
|
3340
3537
|
async reRequestTaxForm(customerID, input) {
|
|
@@ -3414,15 +3611,48 @@ var TaxClient = class extends BaseClient {
|
|
|
3414
3611
|
}
|
|
3415
3612
|
};
|
|
3416
3613
|
|
|
3614
|
+
// src/webhooks/dedup.ts
|
|
3615
|
+
var InMemoryDedupStore = class {
|
|
3616
|
+
constructor(opts = {}) {
|
|
3617
|
+
this.expiryById = /* @__PURE__ */ new Map();
|
|
3618
|
+
this.maxEntries = opts.maxEntries ?? 1e3;
|
|
3619
|
+
this.now = opts.now ?? (() => Date.now());
|
|
3620
|
+
}
|
|
3621
|
+
async seen(id) {
|
|
3622
|
+
return this.expiryById.has(id);
|
|
3623
|
+
}
|
|
3624
|
+
async mark(id, ttlMs) {
|
|
3625
|
+
const now = this.now();
|
|
3626
|
+
this.expiryById.set(id, now + ttlMs);
|
|
3627
|
+
if (this.expiryById.size > this.maxEntries) {
|
|
3628
|
+
for (const [key, expiresAt] of this.expiryById) {
|
|
3629
|
+
if (now > expiresAt) {
|
|
3630
|
+
this.expiryById.delete(key);
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
};
|
|
3636
|
+
|
|
3417
3637
|
// src/webhooks/handler.ts
|
|
3638
|
+
function resolveEventId(event) {
|
|
3639
|
+
if (event.id) {
|
|
3640
|
+
return event.id;
|
|
3641
|
+
}
|
|
3642
|
+
if (event.event_type === "notification.created") {
|
|
3643
|
+
return event.notification?.id;
|
|
3644
|
+
}
|
|
3645
|
+
return void 0;
|
|
3646
|
+
}
|
|
3418
3647
|
var WebhookHandler = class {
|
|
3419
3648
|
constructor(config) {
|
|
3420
3649
|
this.handlers = /* @__PURE__ */ new Map();
|
|
3421
3650
|
this.anyHandlers = [];
|
|
3422
|
-
this.seenEventIds = /* @__PURE__ */ new Map();
|
|
3423
3651
|
this.secret = config.secret;
|
|
3424
3652
|
this.tolerance = config.tolerance ?? 3e5;
|
|
3425
3653
|
this.replayProtection = config.replayProtection ?? true;
|
|
3654
|
+
this.allowLegacySignature = config.allowLegacySignature ?? true;
|
|
3655
|
+
this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
|
|
3426
3656
|
}
|
|
3427
3657
|
/**
|
|
3428
3658
|
* Register a handler for a specific event type.
|
|
@@ -3444,7 +3674,10 @@ var WebhookHandler = class {
|
|
|
3444
3674
|
* Verify signature and parse the webhook body.
|
|
3445
3675
|
*/
|
|
3446
3676
|
async verifyAndParse(body, signature) {
|
|
3447
|
-
const isValid = await verifyWebhookSignature(body, signature, this.secret
|
|
3677
|
+
const isValid = await verifyWebhookSignature(body, signature, this.secret, {
|
|
3678
|
+
tolerance: this.tolerance,
|
|
3679
|
+
allowLegacy: this.allowLegacySignature
|
|
3680
|
+
});
|
|
3448
3681
|
if (!isValid) {
|
|
3449
3682
|
throw new ValidationError("Invalid webhook signature", ["signature"]);
|
|
3450
3683
|
}
|
|
@@ -3460,18 +3693,21 @@ var WebhookHandler = class {
|
|
|
3460
3693
|
/**
|
|
3461
3694
|
* Parse an event without signature verification (for testing).
|
|
3462
3695
|
*/
|
|
3463
|
-
parseEvent(body) {
|
|
3696
|
+
async parseEvent(body) {
|
|
3464
3697
|
return this.parseAndValidate(body);
|
|
3465
3698
|
}
|
|
3466
|
-
parseAndValidate(body) {
|
|
3699
|
+
async parseAndValidate(body) {
|
|
3467
3700
|
const event = JSON.parse(body);
|
|
3468
|
-
|
|
3469
|
-
|
|
3701
|
+
const eventId = resolveEventId(event);
|
|
3702
|
+
if (!event.event_type || !eventId || !event.timestamp) {
|
|
3703
|
+
throw new ValidationError(
|
|
3704
|
+
"Invalid webhook event: missing required fields (event_type, id, timestamp)",
|
|
3705
|
+
["event_type", "id", "timestamp"]
|
|
3706
|
+
);
|
|
3470
3707
|
}
|
|
3471
3708
|
if (this.tolerance > 0) {
|
|
3472
3709
|
const eventTime = new Date(event.timestamp).getTime();
|
|
3473
|
-
|
|
3474
|
-
if (Math.abs(now - eventTime) > this.tolerance) {
|
|
3710
|
+
if (Math.abs(Date.now() - eventTime) > this.tolerance) {
|
|
3475
3711
|
throw new ValidationError(
|
|
3476
3712
|
`Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
|
|
3477
3713
|
["timestamp"]
|
|
@@ -3479,26 +3715,18 @@ var WebhookHandler = class {
|
|
|
3479
3715
|
}
|
|
3480
3716
|
}
|
|
3481
3717
|
if (this.replayProtection) {
|
|
3482
|
-
if (this.
|
|
3718
|
+
if (await this.dedupStore.seen(eventId)) {
|
|
3483
3719
|
throw new ValidationError(
|
|
3484
|
-
`Duplicate webhook event: ${
|
|
3720
|
+
`Duplicate webhook event: ${eventId} has already been processed`,
|
|
3485
3721
|
["id"]
|
|
3486
3722
|
);
|
|
3487
3723
|
}
|
|
3488
|
-
|
|
3489
|
-
this.seenEventIds.set(event.id, now);
|
|
3490
|
-
if (this.seenEventIds.size > 1e3) {
|
|
3491
|
-
for (const [id, seenAt] of this.seenEventIds) {
|
|
3492
|
-
if (now - seenAt > this.tolerance) {
|
|
3493
|
-
this.seenEventIds.delete(id);
|
|
3494
|
-
}
|
|
3495
|
-
}
|
|
3496
|
-
}
|
|
3724
|
+
await this.dedupStore.mark(eventId, this.tolerance);
|
|
3497
3725
|
}
|
|
3498
3726
|
return event;
|
|
3499
3727
|
}
|
|
3500
3728
|
async dispatch(event) {
|
|
3501
|
-
const typeHandlers = this.handlers.get(event.
|
|
3729
|
+
const typeHandlers = this.handlers.get(event.event_type) || [];
|
|
3502
3730
|
const allHandlers = [...typeHandlers, ...this.anyHandlers];
|
|
3503
3731
|
for (const handler of allHandlers) {
|
|
3504
3732
|
await handler(event);
|
|
@@ -3589,26 +3817,32 @@ exports.ComplianceClient = ComplianceClient;
|
|
|
3589
3817
|
exports.ComplianceError = ComplianceError;
|
|
3590
3818
|
exports.DEFAULT_CURRENCY_RATES = DEFAULT_CURRENCY_RATES;
|
|
3591
3819
|
exports.GeolocationClient = GeolocationClient;
|
|
3820
|
+
exports.InMemoryDedupStore = InMemoryDedupStore;
|
|
3592
3821
|
exports.KYC_DECLINED_DESCRIPTIONS = KYC_DECLINED_DESCRIPTIONS;
|
|
3593
3822
|
exports.KycClient = KycClient;
|
|
3594
3823
|
exports.NetworkError = NetworkError;
|
|
3595
3824
|
exports.RateLimitError = RateLimitError;
|
|
3596
3825
|
exports.RateLimitTracker = RateLimitTracker;
|
|
3826
|
+
exports.RefreshingTokenProvider = RefreshingTokenProvider;
|
|
3597
3827
|
exports.RiskProfileClient = RiskProfileClient;
|
|
3598
3828
|
exports.SDK_VERSION = SDK_VERSION;
|
|
3599
3829
|
exports.ServiceUnavailableError = ServiceUnavailableError;
|
|
3830
|
+
exports.StaticApiKeyProvider = StaticApiKeyProvider;
|
|
3600
3831
|
exports.TaxClient = TaxClient;
|
|
3601
3832
|
exports.TimeoutError = TimeoutError;
|
|
3602
3833
|
exports.ValidationError = ValidationError;
|
|
3603
3834
|
exports.VesantError = VesantError;
|
|
3604
3835
|
exports.WebhookHandler = WebhookHandler;
|
|
3836
|
+
exports.assessGpsIntegrity = assessGpsIntegrity;
|
|
3605
3837
|
exports.createConsoleLogger = createConsoleLogger;
|
|
3606
3838
|
exports.createNextWebhookHandler = createNextWebhookHandler;
|
|
3607
3839
|
exports.createWebhookMiddleware = createWebhookMiddleware;
|
|
3608
3840
|
exports.decodeCipherText = decodeCipherText;
|
|
3609
3841
|
exports.generateCipherText = generateCipherText;
|
|
3842
|
+
exports.gpsIntegrityInputFromPosition = gpsIntegrityInputFromPosition;
|
|
3610
3843
|
exports.isCipherTextExpired = isCipherTextExpired;
|
|
3611
3844
|
exports.noopLogger = noopLogger;
|
|
3845
|
+
exports.probeGeolocationPermission = probeGeolocationPermission;
|
|
3612
3846
|
exports.sdkReasons = sdkReasons;
|
|
3613
3847
|
exports.verifyWebhookSignature = verifyWebhookSignature;
|
|
3614
3848
|
//# sourceMappingURL=index.js.map
|