vesant-sdk 1.7.0-dev.362ac6b → 1.7.0-dev.4c91be9
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 +133 -22
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +133 -22
- 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 +68 -7
- package/dist/decisions/index.js.map +1 -1
- package/dist/decisions/index.mjs +68 -7
- 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 +136 -19
- package/dist/geolocation/index.js.map +1 -1
- package/dist/geolocation/index.mjs +134 -20
- 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 +27 -10
- package/dist/index.d.ts +27 -10
- package/dist/index.js +343 -69
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +336 -70
- 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 +109 -30
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +109 -30
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +164 -61
- package/dist/kyc/index.d.ts +164 -61
- package/dist/kyc/index.js +109 -30
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +109 -30
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +21 -15
- package/dist/react.d.ts +21 -15
- package/dist/react.js +199 -87
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +199 -87
- 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 +68 -7
- package/dist/risk-profile/index.js.map +1 -1
- package/dist/risk-profile/index.mjs +68 -7
- 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 +68 -7
- package/dist/scores/index.js.map +1 -1
- package/dist/scores/index.mjs +68 -7
- package/dist/scores/index.mjs.map +1 -1
- package/dist/tax/index.d.mts +20 -3
- package/dist/tax/index.d.ts +20 -3
- package/dist/tax/index.js +77 -10
- package/dist/tax/index.js.map +1 -1
- package/dist/tax/index.mjs +77 -10
- package/dist/tax/index.mjs.map +1 -1
- package/dist/webhooks/index.d.mts +2 -189
- package/dist/webhooks/index.d.ts +2 -189
- package/dist/webhooks/index.js +85 -21
- package/dist/webhooks/index.js.map +1 -1
- package/dist/webhooks/index.mjs +85 -22
- package/dist/webhooks/index.mjs.map +1 -1
- package/package.json +4 -1
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,59 @@
|
|
|
1
|
+
// src/core/auth.ts
|
|
2
|
+
var StaticApiKeyProvider = class {
|
|
3
|
+
constructor(apiKey) {
|
|
4
|
+
this.apiKey = apiKey;
|
|
5
|
+
}
|
|
6
|
+
async getCredential() {
|
|
7
|
+
return this.apiKey ? { scheme: "Bearer", token: this.apiKey } : null;
|
|
8
|
+
}
|
|
9
|
+
canRefresh() {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var RefreshingTokenProvider = class {
|
|
14
|
+
constructor(opts) {
|
|
15
|
+
this.opts = opts;
|
|
16
|
+
this.cached = null;
|
|
17
|
+
this.inFlight = null;
|
|
18
|
+
this.skewMs = opts.skewMs ?? 3e4;
|
|
19
|
+
this.scheme = opts.scheme ?? "Bearer";
|
|
20
|
+
this.now = opts.now ?? (() => Date.now());
|
|
21
|
+
}
|
|
22
|
+
canRefresh() {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
async getCredential(ctx) {
|
|
26
|
+
const forceRefresh = ctx?.forceRefresh ?? false;
|
|
27
|
+
if (!forceRefresh && this.cached && !this.isExpiring(this.cached)) {
|
|
28
|
+
return { scheme: this.scheme, token: this.cached.token };
|
|
29
|
+
}
|
|
30
|
+
const fresh = await this.refresh();
|
|
31
|
+
return { scheme: this.scheme, token: fresh.token };
|
|
32
|
+
}
|
|
33
|
+
isExpiring(token) {
|
|
34
|
+
if (token.expiresAt === void 0) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return this.now() >= token.expiresAt - this.skewMs;
|
|
38
|
+
}
|
|
39
|
+
refresh() {
|
|
40
|
+
if (this.inFlight) {
|
|
41
|
+
return this.inFlight;
|
|
42
|
+
}
|
|
43
|
+
this.inFlight = (async () => {
|
|
44
|
+
try {
|
|
45
|
+
const fresh = await this.opts.fetchToken();
|
|
46
|
+
this.cached = fresh;
|
|
47
|
+
this.opts.onRotate?.({ expiresAt: fresh.expiresAt });
|
|
48
|
+
return fresh;
|
|
49
|
+
} finally {
|
|
50
|
+
this.inFlight = null;
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
return this.inFlight;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
1
57
|
// src/core/errors.ts
|
|
2
58
|
var VesantError = class _VesantError extends Error {
|
|
3
59
|
constructor(message, code, statusCode, details) {
|
|
@@ -322,6 +378,12 @@ var BaseClient = class {
|
|
|
322
378
|
if (!config.tenantId?.trim()) {
|
|
323
379
|
throw new ValidationError("tenantId is required and must be a non-empty string", ["tenantId"]);
|
|
324
380
|
}
|
|
381
|
+
if (typeof fetch === "undefined") {
|
|
382
|
+
throw new VesantError(
|
|
383
|
+
"The Vesant SDK requires a global fetch (Node.js >= 18). Upgrade Node or provide a fetch polyfill.",
|
|
384
|
+
"RUNTIME_UNSUPPORTED"
|
|
385
|
+
);
|
|
386
|
+
}
|
|
325
387
|
this.interceptors = config.interceptors || [];
|
|
326
388
|
this.logger = config.logger || createConsoleLogger();
|
|
327
389
|
let environment = config.environment;
|
|
@@ -340,11 +402,12 @@ var BaseClient = class {
|
|
|
340
402
|
environment,
|
|
341
403
|
headers: config.headers || {},
|
|
342
404
|
timeout: config.timeout || 1e4,
|
|
343
|
-
retries: config.retries
|
|
405
|
+
retries: config.retries ?? 3,
|
|
344
406
|
debug: config.debug || false,
|
|
345
407
|
interceptors: this.interceptors,
|
|
346
408
|
logger: this.logger
|
|
347
409
|
};
|
|
410
|
+
this.authProvider = config.authProvider ?? (apiKey ? new StaticApiKeyProvider(apiKey) : void 0);
|
|
348
411
|
if (config.circuitBreaker) {
|
|
349
412
|
this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
|
|
350
413
|
}
|
|
@@ -352,6 +415,19 @@ var BaseClient = class {
|
|
|
352
415
|
this.rateLimitTracker = new RateLimitTracker();
|
|
353
416
|
}
|
|
354
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* Resolve the Idempotency-Key for a request. Mutating methods (POST/PUT/PATCH)
|
|
420
|
+
* get the caller-supplied key or a freshly minted UUID; non-mutating methods get
|
|
421
|
+
* none. requestWithRetry resolves this once and injects it so every retry reuses
|
|
422
|
+
* one key (letting the server dedup replays); a direct request() resolves per call.
|
|
423
|
+
*/
|
|
424
|
+
resolveIdempotencyKey(method, requestOptions) {
|
|
425
|
+
const isMutating = ["POST", "PUT", "PATCH"].includes((method || "GET").toUpperCase());
|
|
426
|
+
if (!isMutating) {
|
|
427
|
+
return void 0;
|
|
428
|
+
}
|
|
429
|
+
return requestOptions?.idempotencyKey ?? generateUUID();
|
|
430
|
+
}
|
|
355
431
|
/**
|
|
356
432
|
* Make an HTTP request with timeout and error handling
|
|
357
433
|
*/
|
|
@@ -377,15 +453,20 @@ var BaseClient = class {
|
|
|
377
453
|
...this.config.headers,
|
|
378
454
|
...options.headers || {}
|
|
379
455
|
};
|
|
380
|
-
|
|
381
|
-
|
|
456
|
+
const credential = await this.authProvider?.getCredential({
|
|
457
|
+
tenantId: this.config.tenantId,
|
|
458
|
+
requestId
|
|
459
|
+
});
|
|
460
|
+
if (credential) {
|
|
461
|
+
headers["Authorization"] = `${credential.scheme ?? "Bearer"} ${credential.token}`;
|
|
382
462
|
}
|
|
383
463
|
if (this.config.environment === "sandbox") {
|
|
384
464
|
headers["X-Sandbox"] = "true";
|
|
385
465
|
}
|
|
386
466
|
const method = (options.method || "GET").toUpperCase();
|
|
387
|
-
|
|
388
|
-
|
|
467
|
+
const idempotencyKey = this.resolveIdempotencyKey(method, requestOptions);
|
|
468
|
+
if (idempotencyKey) {
|
|
469
|
+
headers["Idempotency-Key"] = idempotencyKey;
|
|
389
470
|
}
|
|
390
471
|
const controller = new AbortController();
|
|
391
472
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
@@ -406,10 +487,28 @@ var BaseClient = class {
|
|
|
406
487
|
if (this.config.debug) {
|
|
407
488
|
this.logger.debug(`${finalOptions.method || "GET"} ${endpoint}`);
|
|
408
489
|
}
|
|
409
|
-
|
|
490
|
+
let response = await fetch(url, {
|
|
410
491
|
...finalOptions,
|
|
411
492
|
signal: controller.signal
|
|
412
493
|
});
|
|
494
|
+
if (response.status === 401 && this.authProvider?.canRefresh?.()) {
|
|
495
|
+
const refreshed = await this.authProvider.getCredential({
|
|
496
|
+
tenantId: this.config.tenantId,
|
|
497
|
+
requestId,
|
|
498
|
+
forceRefresh: true
|
|
499
|
+
});
|
|
500
|
+
if (refreshed) {
|
|
501
|
+
this.config.onCredentialRotated?.({ reason: "unauthorized" });
|
|
502
|
+
finalOptions = {
|
|
503
|
+
...finalOptions,
|
|
504
|
+
headers: {
|
|
505
|
+
...finalOptions.headers,
|
|
506
|
+
Authorization: `${refreshed.scheme ?? "Bearer"} ${refreshed.token}`
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
response = await fetch(url, { ...finalOptions, signal: controller.signal });
|
|
510
|
+
}
|
|
511
|
+
}
|
|
413
512
|
clearTimeout(timeoutId);
|
|
414
513
|
if (this.rateLimitTracker) {
|
|
415
514
|
this.rateLimitTracker.updateFromHeaders(response.headers);
|
|
@@ -498,9 +597,11 @@ var BaseClient = class {
|
|
|
498
597
|
*/
|
|
499
598
|
async requestWithRetry(endpoint, options = {}, serviceURL, retries = this.config.retries, requestOptions) {
|
|
500
599
|
let lastError;
|
|
600
|
+
const idempotencyKey = this.resolveIdempotencyKey(options.method || "GET", requestOptions);
|
|
601
|
+
const attemptOptions = idempotencyKey ? { ...requestOptions, idempotencyKey } : requestOptions;
|
|
501
602
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
502
603
|
try {
|
|
503
|
-
return await this.request(endpoint, options, serviceURL,
|
|
604
|
+
return await this.request(endpoint, options, serviceURL, attemptOptions);
|
|
504
605
|
} catch (error) {
|
|
505
606
|
lastError = error instanceof Error ? error : new Error("Unknown error");
|
|
506
607
|
if (requestOptions?.signal?.aborted) {
|
|
@@ -597,6 +698,9 @@ var BaseClient = class {
|
|
|
597
698
|
if (config.interceptors) {
|
|
598
699
|
this.interceptors = config.interceptors;
|
|
599
700
|
}
|
|
701
|
+
if (config.authProvider !== void 0 || config.apiKey !== void 0) {
|
|
702
|
+
this.authProvider = this.config.authProvider ?? (this.config.apiKey ? new StaticApiKeyProvider(this.config.apiKey) : void 0);
|
|
703
|
+
}
|
|
600
704
|
}
|
|
601
705
|
/**
|
|
602
706
|
* Get current configuration (readonly)
|
|
@@ -624,8 +728,46 @@ var BaseClient = class {
|
|
|
624
728
|
}
|
|
625
729
|
};
|
|
626
730
|
|
|
731
|
+
// src/core/pagination.ts
|
|
732
|
+
async function* paginate(fetchPage) {
|
|
733
|
+
let page = 1;
|
|
734
|
+
let totalPages;
|
|
735
|
+
do {
|
|
736
|
+
const response = await fetchPage(page);
|
|
737
|
+
const items = response.data ?? [];
|
|
738
|
+
for (const item of items) {
|
|
739
|
+
yield item;
|
|
740
|
+
}
|
|
741
|
+
totalPages = response.total_pages ?? page;
|
|
742
|
+
if (items.length === 0) {
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
page += 1;
|
|
746
|
+
} while (page <= totalPages);
|
|
747
|
+
}
|
|
748
|
+
async function collectAll(fetchPage) {
|
|
749
|
+
const items = [];
|
|
750
|
+
for await (const item of paginate(fetchPage)) {
|
|
751
|
+
items.push(item);
|
|
752
|
+
}
|
|
753
|
+
return items;
|
|
754
|
+
}
|
|
755
|
+
|
|
627
756
|
// src/core/webhook-utils.ts
|
|
628
|
-
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
|
+
}
|
|
629
771
|
const hexDigest = await computeHmacSha256(payload, secret);
|
|
630
772
|
const expectedPrefixed = `sha256=${hexDigest}`;
|
|
631
773
|
if (signature.startsWith("sha256=")) {
|
|
@@ -633,6 +775,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
|
|
|
633
775
|
}
|
|
634
776
|
return constantTimeEqual(signature, hexDigest);
|
|
635
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
|
+
}
|
|
636
797
|
async function computeHmacSha256(message, secret) {
|
|
637
798
|
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
638
799
|
const encoder = new TextEncoder();
|
|
@@ -667,6 +828,61 @@ function constantTimeEqual(a, b) {
|
|
|
667
828
|
return result === 0;
|
|
668
829
|
}
|
|
669
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
|
+
|
|
670
886
|
// src/geolocation/ciphertext.ts
|
|
671
887
|
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
672
888
|
async function computeHMAC(key, message) {
|
|
@@ -719,6 +935,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
719
935
|
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
720
936
|
return null;
|
|
721
937
|
}
|
|
938
|
+
const permissionState = await probeGeolocationPermission();
|
|
722
939
|
return new Promise((resolve) => {
|
|
723
940
|
navigator.geolocation.getCurrentPosition(
|
|
724
941
|
(position) => {
|
|
@@ -735,6 +952,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
735
952
|
resolve(null);
|
|
736
953
|
return;
|
|
737
954
|
}
|
|
955
|
+
const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
|
|
738
956
|
resolve({
|
|
739
957
|
latitude,
|
|
740
958
|
longitude,
|
|
@@ -743,7 +961,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
743
961
|
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
744
962
|
heading: position.coords.heading ?? void 0,
|
|
745
963
|
speed: position.coords.speed ?? void 0,
|
|
746
|
-
timestamp: position.timestamp
|
|
964
|
+
timestamp: position.timestamp,
|
|
965
|
+
integrity_flags: flags.length ? flags : void 0
|
|
747
966
|
});
|
|
748
967
|
},
|
|
749
968
|
() => {
|
|
@@ -810,6 +1029,9 @@ async function generateCipherText(options, config) {
|
|
|
810
1029
|
);
|
|
811
1030
|
if (location) {
|
|
812
1031
|
locationData = location;
|
|
1032
|
+
if (location.integrity_flags?.length) {
|
|
1033
|
+
warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
|
|
1034
|
+
}
|
|
813
1035
|
} else if (gpsRequiredByConfig) {
|
|
814
1036
|
throw new VesantError(
|
|
815
1037
|
`GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
|
|
@@ -929,8 +1151,8 @@ var GeolocationClient = class extends BaseClient {
|
|
|
929
1151
|
* ```
|
|
930
1152
|
*/
|
|
931
1153
|
async verifyIP(request, requestOptions) {
|
|
932
|
-
if (!request.ip_address
|
|
933
|
-
throw new ValidationError("ip_address
|
|
1154
|
+
if (request.ip_address !== void 0 && !request.ip_address.trim()) {
|
|
1155
|
+
throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
|
|
934
1156
|
}
|
|
935
1157
|
return this.requestWithRetry("/api/v1/geo/verify", {
|
|
936
1158
|
method: "POST",
|
|
@@ -1280,20 +1502,12 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1280
1502
|
*
|
|
1281
1503
|
* @example
|
|
1282
1504
|
* ```typescript
|
|
1283
|
-
* //
|
|
1505
|
+
* // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
|
|
1284
1506
|
* const result = await client.captureLocation(token, {
|
|
1285
1507
|
* latitude: 37.7749,
|
|
1286
1508
|
* longitude: -122.4194,
|
|
1287
1509
|
* accuracy: 10
|
|
1288
1510
|
* });
|
|
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
1511
|
* ```
|
|
1298
1512
|
*/
|
|
1299
1513
|
async captureLocation(token, capture, requestOptions) {
|
|
@@ -1798,9 +2012,6 @@ var ComplianceClient = class {
|
|
|
1798
2012
|
if (cipherTextResult.risk?.is_blocked) {
|
|
1799
2013
|
blockReasons.push(...cipherTextResult.risk.block_reasons_structured ?? []);
|
|
1800
2014
|
}
|
|
1801
|
-
if (cipherTextResult.risk?.location_mismatch) {
|
|
1802
|
-
blockReasons.push(sdkReasons.gpsIPMismatch());
|
|
1803
|
-
}
|
|
1804
2015
|
}
|
|
1805
2016
|
if (geoVerification.gps_required && !cipherTextResult) {
|
|
1806
2017
|
blockReasons.push(sdkReasons.gpsRequired());
|
|
@@ -2710,7 +2921,13 @@ var KycClient = class extends BaseClient {
|
|
|
2710
2921
|
*
|
|
2711
2922
|
* Generates a link that the user can visit to submit their KYC documents.
|
|
2712
2923
|
*
|
|
2713
|
-
*
|
|
2924
|
+
* Optionally pass the customer's registered identity data as
|
|
2925
|
+
* `customer_data` (same shape as the geolocation `customer_data` block).
|
|
2926
|
+
* It seeds the customer's risk profile so document verification can
|
|
2927
|
+
* cross-check the submitted document against trusted reference data;
|
|
2928
|
+
* fields never overwrite data already on the profile.
|
|
2929
|
+
*
|
|
2930
|
+
* @param request - Request containing the user ID, optional redirect URL, optional callback URL (receives POST requests), and optional customer identity data
|
|
2714
2931
|
* @returns Response containing the redirect link and KYC ID
|
|
2715
2932
|
*
|
|
2716
2933
|
* @example
|
|
@@ -2718,22 +2935,30 @@ var KycClient = class extends BaseClient {
|
|
|
2718
2935
|
* const result = await client.requestKycSubmitLink({
|
|
2719
2936
|
* user_id: "user_123",
|
|
2720
2937
|
* redirect_url: "https://merchant.com/kyc-complete", // optional
|
|
2721
|
-
* callback_url: "https://merchant.com/api/kyc-webhook" // optional - receives POST requests on status change
|
|
2938
|
+
* callback_url: "https://merchant.com/api/kyc-webhook", // optional - receives POST requests on status change
|
|
2939
|
+
* customer_data: { // optional - seeds the risk profile for document cross-checks
|
|
2940
|
+
* full_name: "John Doe",
|
|
2941
|
+
* date_of_birth: "1999-06-02", // ISO 8601
|
|
2942
|
+
* email: "john@example.com",
|
|
2943
|
+
* phone: "+94771234567",
|
|
2944
|
+
* address: "12 Main St, Colombo", // used for address verification
|
|
2945
|
+
* country: "LK"
|
|
2946
|
+
* }
|
|
2722
2947
|
* });
|
|
2723
2948
|
*
|
|
2724
2949
|
* console.log(`Redirect user to: ${result.link}`);
|
|
2725
2950
|
* console.log(`KYC ID: ${result.kyc_id}`);
|
|
2726
2951
|
* ```
|
|
2727
2952
|
*/
|
|
2728
|
-
async requestKycSubmitLink(request) {
|
|
2953
|
+
async requestKycSubmitLink(request, requestOptions) {
|
|
2729
2954
|
return this.requestWithRetry("/api/v1/kyc/request", {
|
|
2730
2955
|
method: "POST",
|
|
2731
2956
|
body: JSON.stringify(request),
|
|
2732
2957
|
headers: this.getUserHeaders()
|
|
2733
|
-
});
|
|
2958
|
+
}, void 0, void 0, requestOptions);
|
|
2734
2959
|
}
|
|
2735
2960
|
/**
|
|
2736
|
-
* Create a
|
|
2961
|
+
* Create a Event-Based Face Verification session.
|
|
2737
2962
|
*
|
|
2738
2963
|
* Inspect the response before showing UI:
|
|
2739
2964
|
* - `is_required === false` → skip face capture; `reason` explains why.
|
|
@@ -2743,7 +2968,7 @@ var KycClient = class extends BaseClient {
|
|
|
2743
2968
|
*
|
|
2744
2969
|
* @param request - Reference, customer_id, event, amount (for threshold events), optional URLs.
|
|
2745
2970
|
*/
|
|
2746
|
-
async
|
|
2971
|
+
async createEventBasedFaceVerificationSession(request) {
|
|
2747
2972
|
return this.request("/api/v1/kyc/face/session", {
|
|
2748
2973
|
method: "POST",
|
|
2749
2974
|
body: JSON.stringify(request),
|
|
@@ -2751,33 +2976,37 @@ var KycClient = class extends BaseClient {
|
|
|
2751
2976
|
});
|
|
2752
2977
|
}
|
|
2753
2978
|
/**
|
|
2754
|
-
* Submit a real-time face capture for an active
|
|
2979
|
+
* Submit a real-time face capture for an active Event-Based Face Verification session.
|
|
2755
2980
|
*
|
|
2756
2981
|
* **Mobile-only.** The server rejects desktop User-Agents with HTTP
|
|
2757
2982
|
* 400 (`face capture must be completed on a mobile device`). Use the
|
|
2758
|
-
* QR handoff from `
|
|
2983
|
+
* QR handoff from `createEventBasedFaceVerificationSession` for desktop callers.
|
|
2759
2984
|
*
|
|
2760
2985
|
* The `data` field on the response carries `retries_remaining`,
|
|
2761
2986
|
* `retry_limit_exceeded`, and the reaction flags (`enforce_logout`,
|
|
2762
2987
|
* `freeze_account`, etc.) so the tenant app can act on a final failure.
|
|
2763
2988
|
*
|
|
2764
|
-
* @param request - Token from `
|
|
2989
|
+
* @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
|
|
2765
2990
|
*/
|
|
2766
|
-
async
|
|
2991
|
+
async submitEventBasedFaceVerificationSession(request, requestOptions) {
|
|
2767
2992
|
return this.request("/api/v1/kyc/face/submit", {
|
|
2768
2993
|
method: "POST",
|
|
2769
2994
|
body: JSON.stringify(request),
|
|
2770
2995
|
headers: this.getUserHeaders()
|
|
2771
|
-
});
|
|
2996
|
+
}, void 0, requestOptions);
|
|
2772
2997
|
}
|
|
2773
2998
|
/**
|
|
2774
|
-
* Look up the current state of a
|
|
2775
|
-
*
|
|
2999
|
+
* Look up the current state of a Event-Based Face Verification session by its
|
|
3000
|
+
* session token. Useful for desktop pollers waiting on the mobile handoff.
|
|
3001
|
+
*
|
|
3002
|
+
* The lookup is scoped by the unguessable, server-issued session token (not
|
|
3003
|
+
* the enumerable forward reference): the endpoint is public/unauthenticated,
|
|
3004
|
+
* and scoping by the token is what prevents cross-tenant status reads.
|
|
2776
3005
|
*
|
|
2777
|
-
* @param
|
|
3006
|
+
* @param token - The session token returned by `createEventBasedFaceVerificationSession`.
|
|
2778
3007
|
*/
|
|
2779
|
-
async
|
|
2780
|
-
return this.requestWithRetry(`/api/v1/kyc/face/verify/${encodeURIComponent(
|
|
3008
|
+
async getEventBasedFaceVerificationSessionStatus(token) {
|
|
3009
|
+
return this.requestWithRetry(`/api/v1/kyc/face/verify/${encodeURIComponent(token)}`, {
|
|
2781
3010
|
method: "GET",
|
|
2782
3011
|
headers: this.getUserHeaders()
|
|
2783
3012
|
});
|
|
@@ -2788,7 +3017,7 @@ var KycClient = class extends BaseClient {
|
|
|
2788
3017
|
* callers poll `mobile_connected` to detect when a mobile device has
|
|
2789
3018
|
* scanned the QR and attached.
|
|
2790
3019
|
*
|
|
2791
|
-
* @param token - The session token returned by `
|
|
3020
|
+
* @param token - The session token returned by `createEventBasedFaceVerificationSession`.
|
|
2792
3021
|
*/
|
|
2793
3022
|
async getHandoffSession(token) {
|
|
2794
3023
|
return this.request(
|
|
@@ -2863,7 +3092,7 @@ var KycClient = class extends BaseClient {
|
|
|
2863
3092
|
* @example
|
|
2864
3093
|
* ```typescript
|
|
2865
3094
|
* const result = await client.submitVerification({
|
|
2866
|
-
* reference: "
|
|
3095
|
+
* reference: "4d2aff34-ab86-422d-992e-50b1234a5b67",
|
|
2867
3096
|
* email: "customer@example.com",
|
|
2868
3097
|
* country: "US",
|
|
2869
3098
|
* document: {
|
|
@@ -2880,12 +3109,12 @@ var KycClient = class extends BaseClient {
|
|
|
2880
3109
|
* console.log(`Verification submitted: ${result.reference}`);
|
|
2881
3110
|
* ```
|
|
2882
3111
|
*/
|
|
2883
|
-
async submitVerification(request) {
|
|
3112
|
+
async submitVerification(request, requestOptions) {
|
|
2884
3113
|
return this.requestWithRetry("/api/v1/kyc/submit", {
|
|
2885
3114
|
method: "POST",
|
|
2886
3115
|
body: JSON.stringify(request),
|
|
2887
3116
|
headers: this.getUserHeaders()
|
|
2888
|
-
});
|
|
3117
|
+
}, void 0, void 0, requestOptions);
|
|
2889
3118
|
}
|
|
2890
3119
|
/**
|
|
2891
3120
|
* Get a KYC request by ID
|
|
@@ -2895,7 +3124,7 @@ var KycClient = class extends BaseClient {
|
|
|
2895
3124
|
*
|
|
2896
3125
|
* @example
|
|
2897
3126
|
* ```typescript
|
|
2898
|
-
* const kyc = await client.getKycRequest("
|
|
3127
|
+
* const kyc = await client.getKycRequest("550e8400-e29b-41d4-a716-446655440000");
|
|
2899
3128
|
* console.log(`Status: ${kyc.status}, ID Verified: ${kyc.id_verified}`);
|
|
2900
3129
|
* ```
|
|
2901
3130
|
*/
|
|
@@ -2970,7 +3199,7 @@ var KycClient = class extends BaseClient {
|
|
|
2970
3199
|
* @example
|
|
2971
3200
|
* ```typescript
|
|
2972
3201
|
* await client.requestAdditionalDocuments({
|
|
2973
|
-
* id: "
|
|
3202
|
+
* id: "550e8400-e29b-41d4-a716-446655440000",
|
|
2974
3203
|
* document_types: ["address", "document_two"],
|
|
2975
3204
|
* message: "Please provide proof of address and secondary ID"
|
|
2976
3205
|
* });
|
|
@@ -2991,7 +3220,7 @@ var KycClient = class extends BaseClient {
|
|
|
2991
3220
|
*
|
|
2992
3221
|
* @example
|
|
2993
3222
|
* ```typescript
|
|
2994
|
-
* const proofs = await client.getProofDownloadURLs("
|
|
3223
|
+
* const proofs = await client.getProofDownloadURLs("550e8400-e29b-41d4-a716-446655440000");
|
|
2995
3224
|
* proofs.forEach(proof => {
|
|
2996
3225
|
* console.log(`${proof.type}: ${proof.url} (expires: ${proof.expires_at})`);
|
|
2997
3226
|
* });
|
|
@@ -3325,10 +3554,13 @@ var TaxClient = class extends BaseClient {
|
|
|
3325
3554
|
{ method: "POST" }
|
|
3326
3555
|
);
|
|
3327
3556
|
}
|
|
3328
|
-
async checkTINStatus(customerID) {
|
|
3557
|
+
async checkTINStatus(customerID, requestOptions) {
|
|
3329
3558
|
return this.requestWithRetry(
|
|
3330
3559
|
`/api/v1/tax/customer-tax-profiles/${customerID}/check-tin`,
|
|
3331
|
-
{ method: "POST" }
|
|
3560
|
+
{ method: "POST" },
|
|
3561
|
+
void 0,
|
|
3562
|
+
void 0,
|
|
3563
|
+
requestOptions
|
|
3332
3564
|
);
|
|
3333
3565
|
}
|
|
3334
3566
|
async reRequestTaxForm(customerID, input) {
|
|
@@ -3351,7 +3583,7 @@ var TaxClient = class extends BaseClient {
|
|
|
3351
3583
|
}
|
|
3352
3584
|
async getCustomerDocuments(customerID) {
|
|
3353
3585
|
return this.request(
|
|
3354
|
-
`/api/v1/
|
|
3586
|
+
`/api/v1/tm/customer-tax-profiles/${customerID}/documents`
|
|
3355
3587
|
);
|
|
3356
3588
|
}
|
|
3357
3589
|
async downloadTaxForm(customerID, requestOptions) {
|
|
@@ -3376,6 +3608,9 @@ var TaxClient = class extends BaseClient {
|
|
|
3376
3608
|
});
|
|
3377
3609
|
return res.version;
|
|
3378
3610
|
}
|
|
3611
|
+
async runReminders() {
|
|
3612
|
+
return this.request("/api/v1/tax/reminders/run", { method: "POST" });
|
|
3613
|
+
}
|
|
3379
3614
|
async getComplianceStats(filters) {
|
|
3380
3615
|
const params = {};
|
|
3381
3616
|
if (filters?.time_range) params.time_range = filters.time_range;
|
|
@@ -3405,15 +3640,48 @@ var TaxClient = class extends BaseClient {
|
|
|
3405
3640
|
}
|
|
3406
3641
|
};
|
|
3407
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
|
+
|
|
3408
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
|
+
}
|
|
3409
3676
|
var WebhookHandler = class {
|
|
3410
3677
|
constructor(config) {
|
|
3411
3678
|
this.handlers = /* @__PURE__ */ new Map();
|
|
3412
3679
|
this.anyHandlers = [];
|
|
3413
|
-
this.seenEventIds = /* @__PURE__ */ new Map();
|
|
3414
3680
|
this.secret = config.secret;
|
|
3415
3681
|
this.tolerance = config.tolerance ?? 3e5;
|
|
3416
3682
|
this.replayProtection = config.replayProtection ?? true;
|
|
3683
|
+
this.allowLegacySignature = config.allowLegacySignature ?? true;
|
|
3684
|
+
this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
|
|
3417
3685
|
}
|
|
3418
3686
|
/**
|
|
3419
3687
|
* Register a handler for a specific event type.
|
|
@@ -3435,7 +3703,10 @@ var WebhookHandler = class {
|
|
|
3435
3703
|
* Verify signature and parse the webhook body.
|
|
3436
3704
|
*/
|
|
3437
3705
|
async verifyAndParse(body, signature) {
|
|
3438
|
-
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
|
+
});
|
|
3439
3710
|
if (!isValid) {
|
|
3440
3711
|
throw new ValidationError("Invalid webhook signature", ["signature"]);
|
|
3441
3712
|
}
|
|
@@ -3451,18 +3722,21 @@ var WebhookHandler = class {
|
|
|
3451
3722
|
/**
|
|
3452
3723
|
* Parse an event without signature verification (for testing).
|
|
3453
3724
|
*/
|
|
3454
|
-
parseEvent(body) {
|
|
3725
|
+
async parseEvent(body) {
|
|
3455
3726
|
return this.parseAndValidate(body);
|
|
3456
3727
|
}
|
|
3457
|
-
parseAndValidate(body) {
|
|
3728
|
+
async parseAndValidate(body) {
|
|
3458
3729
|
const event = JSON.parse(body);
|
|
3459
|
-
|
|
3460
|
-
|
|
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
|
+
);
|
|
3461
3736
|
}
|
|
3462
3737
|
if (this.tolerance > 0) {
|
|
3463
3738
|
const eventTime = new Date(event.timestamp).getTime();
|
|
3464
|
-
|
|
3465
|
-
if (Math.abs(now - eventTime) > this.tolerance) {
|
|
3739
|
+
if (Math.abs(Date.now() - eventTime) > this.tolerance) {
|
|
3466
3740
|
throw new ValidationError(
|
|
3467
3741
|
`Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
|
|
3468
3742
|
["timestamp"]
|
|
@@ -3470,26 +3744,18 @@ var WebhookHandler = class {
|
|
|
3470
3744
|
}
|
|
3471
3745
|
}
|
|
3472
3746
|
if (this.replayProtection) {
|
|
3473
|
-
if (this.
|
|
3747
|
+
if (await this.dedupStore.seen(eventId)) {
|
|
3474
3748
|
throw new ValidationError(
|
|
3475
|
-
`Duplicate webhook event: ${
|
|
3749
|
+
`Duplicate webhook event: ${eventId} has already been processed`,
|
|
3476
3750
|
["id"]
|
|
3477
3751
|
);
|
|
3478
3752
|
}
|
|
3479
|
-
|
|
3480
|
-
this.seenEventIds.set(event.id, now);
|
|
3481
|
-
if (this.seenEventIds.size > 1e3) {
|
|
3482
|
-
for (const [id, seenAt] of this.seenEventIds) {
|
|
3483
|
-
if (now - seenAt > this.tolerance) {
|
|
3484
|
-
this.seenEventIds.delete(id);
|
|
3485
|
-
}
|
|
3486
|
-
}
|
|
3487
|
-
}
|
|
3753
|
+
await this.dedupStore.mark(eventId, this.tolerance);
|
|
3488
3754
|
}
|
|
3489
3755
|
return event;
|
|
3490
3756
|
}
|
|
3491
3757
|
async dispatch(event) {
|
|
3492
|
-
const typeHandlers = this.handlers.get(event.
|
|
3758
|
+
const typeHandlers = this.handlers.get(event.event_type) || [];
|
|
3493
3759
|
const allHandlers = [...typeHandlers, ...this.anyHandlers];
|
|
3494
3760
|
for (const handler of allHandlers) {
|
|
3495
3761
|
await handler(event);
|
|
@@ -3570,6 +3836,6 @@ function buildHandler(options) {
|
|
|
3570
3836
|
return handler;
|
|
3571
3837
|
}
|
|
3572
3838
|
|
|
3573
|
-
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 };
|
|
3574
3840
|
//# sourceMappingURL=index.mjs.map
|
|
3575
3841
|
//# sourceMappingURL=index.mjs.map
|