vesant-sdk 1.7.0-dev.ac305d8 → 1.7.0-dev.b4284ca
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{client-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 +325 -67
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +318 -68
- 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 +94 -19
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +94 -19
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +110 -13
- package/dist/kyc/index.d.ts +110 -13
- package/dist/kyc/index.js +94 -19
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +94 -19
- 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 +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 +3 -26
- package/dist/tax/index.d.ts +3 -26
- package/dist/tax/index.js +74 -19
- package/dist/tax/index.js.map +1 -1
- package/dist/tax/index.mjs +74 -19
- package/dist/tax/index.mjs.map +1 -1
- package/dist/webhooks/index.d.mts +2 -189
- package/dist/webhooks/index.d.ts +2 -189
- package/dist/webhooks/index.js +85 -21
- package/dist/webhooks/index.js.map +1 -1
- package/dist/webhooks/index.mjs +85 -22
- package/dist/webhooks/index.mjs.map +1 -1
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// src/core/auth.ts
|
|
4
|
+
var StaticApiKeyProvider = class {
|
|
5
|
+
constructor(apiKey) {
|
|
6
|
+
this.apiKey = apiKey;
|
|
7
|
+
}
|
|
8
|
+
async getCredential() {
|
|
9
|
+
return this.apiKey ? { scheme: "Bearer", token: this.apiKey } : null;
|
|
10
|
+
}
|
|
11
|
+
canRefresh() {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var RefreshingTokenProvider = class {
|
|
16
|
+
constructor(opts) {
|
|
17
|
+
this.opts = opts;
|
|
18
|
+
this.cached = null;
|
|
19
|
+
this.inFlight = null;
|
|
20
|
+
this.skewMs = opts.skewMs ?? 3e4;
|
|
21
|
+
this.scheme = opts.scheme ?? "Bearer";
|
|
22
|
+
this.now = opts.now ?? (() => Date.now());
|
|
23
|
+
}
|
|
24
|
+
canRefresh() {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
async getCredential(ctx) {
|
|
28
|
+
const forceRefresh = ctx?.forceRefresh ?? false;
|
|
29
|
+
if (!forceRefresh && this.cached && !this.isExpiring(this.cached)) {
|
|
30
|
+
return { scheme: this.scheme, token: this.cached.token };
|
|
31
|
+
}
|
|
32
|
+
const fresh = await this.refresh();
|
|
33
|
+
return { scheme: this.scheme, token: fresh.token };
|
|
34
|
+
}
|
|
35
|
+
isExpiring(token) {
|
|
36
|
+
if (token.expiresAt === void 0) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return this.now() >= token.expiresAt - this.skewMs;
|
|
40
|
+
}
|
|
41
|
+
refresh() {
|
|
42
|
+
if (this.inFlight) {
|
|
43
|
+
return this.inFlight;
|
|
44
|
+
}
|
|
45
|
+
this.inFlight = (async () => {
|
|
46
|
+
try {
|
|
47
|
+
const fresh = await this.opts.fetchToken();
|
|
48
|
+
this.cached = fresh;
|
|
49
|
+
this.opts.onRotate?.({ expiresAt: fresh.expiresAt });
|
|
50
|
+
return fresh;
|
|
51
|
+
} finally {
|
|
52
|
+
this.inFlight = null;
|
|
53
|
+
}
|
|
54
|
+
})();
|
|
55
|
+
return this.inFlight;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
3
59
|
// src/core/errors.ts
|
|
4
60
|
var VesantError = class _VesantError extends Error {
|
|
5
61
|
constructor(message, code, statusCode, details) {
|
|
@@ -324,6 +380,12 @@ var BaseClient = class {
|
|
|
324
380
|
if (!config.tenantId?.trim()) {
|
|
325
381
|
throw new ValidationError("tenantId is required and must be a non-empty string", ["tenantId"]);
|
|
326
382
|
}
|
|
383
|
+
if (typeof fetch === "undefined") {
|
|
384
|
+
throw new VesantError(
|
|
385
|
+
"The Vesant SDK requires a global fetch (Node.js >= 18). Upgrade Node or provide a fetch polyfill.",
|
|
386
|
+
"RUNTIME_UNSUPPORTED"
|
|
387
|
+
);
|
|
388
|
+
}
|
|
327
389
|
this.interceptors = config.interceptors || [];
|
|
328
390
|
this.logger = config.logger || createConsoleLogger();
|
|
329
391
|
let environment = config.environment;
|
|
@@ -342,11 +404,12 @@ var BaseClient = class {
|
|
|
342
404
|
environment,
|
|
343
405
|
headers: config.headers || {},
|
|
344
406
|
timeout: config.timeout || 1e4,
|
|
345
|
-
retries: config.retries
|
|
407
|
+
retries: config.retries ?? 3,
|
|
346
408
|
debug: config.debug || false,
|
|
347
409
|
interceptors: this.interceptors,
|
|
348
410
|
logger: this.logger
|
|
349
411
|
};
|
|
412
|
+
this.authProvider = config.authProvider ?? (apiKey ? new StaticApiKeyProvider(apiKey) : void 0);
|
|
350
413
|
if (config.circuitBreaker) {
|
|
351
414
|
this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
|
|
352
415
|
}
|
|
@@ -354,6 +417,19 @@ var BaseClient = class {
|
|
|
354
417
|
this.rateLimitTracker = new RateLimitTracker();
|
|
355
418
|
}
|
|
356
419
|
}
|
|
420
|
+
/**
|
|
421
|
+
* Resolve the Idempotency-Key for a request. Mutating methods (POST/PUT/PATCH)
|
|
422
|
+
* get the caller-supplied key or a freshly minted UUID; non-mutating methods get
|
|
423
|
+
* none. requestWithRetry resolves this once and injects it so every retry reuses
|
|
424
|
+
* one key (letting the server dedup replays); a direct request() resolves per call.
|
|
425
|
+
*/
|
|
426
|
+
resolveIdempotencyKey(method, requestOptions) {
|
|
427
|
+
const isMutating = ["POST", "PUT", "PATCH"].includes((method || "GET").toUpperCase());
|
|
428
|
+
if (!isMutating) {
|
|
429
|
+
return void 0;
|
|
430
|
+
}
|
|
431
|
+
return requestOptions?.idempotencyKey ?? generateUUID();
|
|
432
|
+
}
|
|
357
433
|
/**
|
|
358
434
|
* Make an HTTP request with timeout and error handling
|
|
359
435
|
*/
|
|
@@ -379,15 +455,20 @@ var BaseClient = class {
|
|
|
379
455
|
...this.config.headers,
|
|
380
456
|
...options.headers || {}
|
|
381
457
|
};
|
|
382
|
-
|
|
383
|
-
|
|
458
|
+
const credential = await this.authProvider?.getCredential({
|
|
459
|
+
tenantId: this.config.tenantId,
|
|
460
|
+
requestId
|
|
461
|
+
});
|
|
462
|
+
if (credential) {
|
|
463
|
+
headers["Authorization"] = `${credential.scheme ?? "Bearer"} ${credential.token}`;
|
|
384
464
|
}
|
|
385
465
|
if (this.config.environment === "sandbox") {
|
|
386
466
|
headers["X-Sandbox"] = "true";
|
|
387
467
|
}
|
|
388
468
|
const method = (options.method || "GET").toUpperCase();
|
|
389
|
-
|
|
390
|
-
|
|
469
|
+
const idempotencyKey = this.resolveIdempotencyKey(method, requestOptions);
|
|
470
|
+
if (idempotencyKey) {
|
|
471
|
+
headers["Idempotency-Key"] = idempotencyKey;
|
|
391
472
|
}
|
|
392
473
|
const controller = new AbortController();
|
|
393
474
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
@@ -408,10 +489,28 @@ var BaseClient = class {
|
|
|
408
489
|
if (this.config.debug) {
|
|
409
490
|
this.logger.debug(`${finalOptions.method || "GET"} ${endpoint}`);
|
|
410
491
|
}
|
|
411
|
-
|
|
492
|
+
let response = await fetch(url, {
|
|
412
493
|
...finalOptions,
|
|
413
494
|
signal: controller.signal
|
|
414
495
|
});
|
|
496
|
+
if (response.status === 401 && this.authProvider?.canRefresh?.()) {
|
|
497
|
+
const refreshed = await this.authProvider.getCredential({
|
|
498
|
+
tenantId: this.config.tenantId,
|
|
499
|
+
requestId,
|
|
500
|
+
forceRefresh: true
|
|
501
|
+
});
|
|
502
|
+
if (refreshed) {
|
|
503
|
+
this.config.onCredentialRotated?.({ reason: "unauthorized" });
|
|
504
|
+
finalOptions = {
|
|
505
|
+
...finalOptions,
|
|
506
|
+
headers: {
|
|
507
|
+
...finalOptions.headers,
|
|
508
|
+
Authorization: `${refreshed.scheme ?? "Bearer"} ${refreshed.token}`
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
response = await fetch(url, { ...finalOptions, signal: controller.signal });
|
|
512
|
+
}
|
|
513
|
+
}
|
|
415
514
|
clearTimeout(timeoutId);
|
|
416
515
|
if (this.rateLimitTracker) {
|
|
417
516
|
this.rateLimitTracker.updateFromHeaders(response.headers);
|
|
@@ -500,9 +599,11 @@ var BaseClient = class {
|
|
|
500
599
|
*/
|
|
501
600
|
async requestWithRetry(endpoint, options = {}, serviceURL, retries = this.config.retries, requestOptions) {
|
|
502
601
|
let lastError;
|
|
602
|
+
const idempotencyKey = this.resolveIdempotencyKey(options.method || "GET", requestOptions);
|
|
603
|
+
const attemptOptions = idempotencyKey ? { ...requestOptions, idempotencyKey } : requestOptions;
|
|
503
604
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
504
605
|
try {
|
|
505
|
-
return await this.request(endpoint, options, serviceURL,
|
|
606
|
+
return await this.request(endpoint, options, serviceURL, attemptOptions);
|
|
506
607
|
} catch (error) {
|
|
507
608
|
lastError = error instanceof Error ? error : new Error("Unknown error");
|
|
508
609
|
if (requestOptions?.signal?.aborted) {
|
|
@@ -599,6 +700,9 @@ var BaseClient = class {
|
|
|
599
700
|
if (config.interceptors) {
|
|
600
701
|
this.interceptors = config.interceptors;
|
|
601
702
|
}
|
|
703
|
+
if (config.authProvider !== void 0 || config.apiKey !== void 0) {
|
|
704
|
+
this.authProvider = this.config.authProvider ?? (this.config.apiKey ? new StaticApiKeyProvider(this.config.apiKey) : void 0);
|
|
705
|
+
}
|
|
602
706
|
}
|
|
603
707
|
/**
|
|
604
708
|
* Get current configuration (readonly)
|
|
@@ -626,8 +730,46 @@ var BaseClient = class {
|
|
|
626
730
|
}
|
|
627
731
|
};
|
|
628
732
|
|
|
733
|
+
// src/core/pagination.ts
|
|
734
|
+
async function* paginate(fetchPage) {
|
|
735
|
+
let page = 1;
|
|
736
|
+
let totalPages;
|
|
737
|
+
do {
|
|
738
|
+
const response = await fetchPage(page);
|
|
739
|
+
const items = response.data ?? [];
|
|
740
|
+
for (const item of items) {
|
|
741
|
+
yield item;
|
|
742
|
+
}
|
|
743
|
+
totalPages = response.total_pages ?? page;
|
|
744
|
+
if (items.length === 0) {
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
page += 1;
|
|
748
|
+
} while (page <= totalPages);
|
|
749
|
+
}
|
|
750
|
+
async function collectAll(fetchPage) {
|
|
751
|
+
const items = [];
|
|
752
|
+
for await (const item of paginate(fetchPage)) {
|
|
753
|
+
items.push(item);
|
|
754
|
+
}
|
|
755
|
+
return items;
|
|
756
|
+
}
|
|
757
|
+
|
|
629
758
|
// src/core/webhook-utils.ts
|
|
630
|
-
async function verifyWebhookSignature(payload, signature, secret) {
|
|
759
|
+
async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
|
|
760
|
+
const signed = parseSignedTimestamp(signature);
|
|
761
|
+
if (signed) {
|
|
762
|
+
const tolerance = opts.tolerance ?? 3e5;
|
|
763
|
+
const now = opts.now?.() ?? Date.now();
|
|
764
|
+
if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
|
|
768
|
+
return constantTimeEqual(signed.v1, expected);
|
|
769
|
+
}
|
|
770
|
+
if (opts.allowLegacy === false) {
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
631
773
|
const hexDigest = await computeHmacSha256(payload, secret);
|
|
632
774
|
const expectedPrefixed = `sha256=${hexDigest}`;
|
|
633
775
|
if (signature.startsWith("sha256=")) {
|
|
@@ -635,6 +777,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
|
|
|
635
777
|
}
|
|
636
778
|
return constantTimeEqual(signature, hexDigest);
|
|
637
779
|
}
|
|
780
|
+
function parseSignedTimestamp(signature) {
|
|
781
|
+
if (!signature.includes("t=") || !signature.includes("v1=")) {
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
let t;
|
|
785
|
+
let v1;
|
|
786
|
+
for (const part of signature.split(",")) {
|
|
787
|
+
const eq = part.indexOf("=");
|
|
788
|
+
if (eq === -1) continue;
|
|
789
|
+
const key = part.slice(0, eq).trim();
|
|
790
|
+
const value = part.slice(eq + 1).trim();
|
|
791
|
+
if (key === "t") t = Number(value);
|
|
792
|
+
else if (key === "v1") v1 = value;
|
|
793
|
+
}
|
|
794
|
+
if (t === void 0 || Number.isNaN(t) || !v1) {
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
return { t, v1 };
|
|
798
|
+
}
|
|
638
799
|
async function computeHmacSha256(message, secret) {
|
|
639
800
|
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
640
801
|
const encoder = new TextEncoder();
|
|
@@ -669,6 +830,61 @@ function constantTimeEqual(a, b) {
|
|
|
669
830
|
return result === 0;
|
|
670
831
|
}
|
|
671
832
|
|
|
833
|
+
// src/geolocation/integrity.ts
|
|
834
|
+
var isNil = (v) => v === null || v === void 0;
|
|
835
|
+
function assessGpsIntegrity(input, opts = {}) {
|
|
836
|
+
const flags = [];
|
|
837
|
+
const now = opts.now ?? (() => Date.now());
|
|
838
|
+
const maxAgeMs = opts.maxAgeMs ?? 6e4;
|
|
839
|
+
const minAccuracy = opts.minAccuracyMeters ?? 1;
|
|
840
|
+
const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
|
|
841
|
+
if (isNil(accuracy)) {
|
|
842
|
+
flags.push("accuracy_missing");
|
|
843
|
+
} else if (accuracy === 0) {
|
|
844
|
+
flags.push("accuracy_zero");
|
|
845
|
+
} else if (accuracy < minAccuracy) {
|
|
846
|
+
flags.push("accuracy_implausibly_small");
|
|
847
|
+
}
|
|
848
|
+
const hasMotion = !isNil(heading) || !isNil(speed);
|
|
849
|
+
if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
|
|
850
|
+
flags.push("motion_without_accuracy");
|
|
851
|
+
}
|
|
852
|
+
if (!isNil(altitude) && isNil(altitudeAccuracy)) {
|
|
853
|
+
flags.push("altitude_without_accuracy");
|
|
854
|
+
}
|
|
855
|
+
if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
|
|
856
|
+
flags.push("stale_timestamp");
|
|
857
|
+
}
|
|
858
|
+
if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
|
|
859
|
+
flags.push(`permission_${opts.permissionState}`);
|
|
860
|
+
}
|
|
861
|
+
return { suspicious: flags.length > 0, flags };
|
|
862
|
+
}
|
|
863
|
+
function gpsIntegrityInputFromPosition(position) {
|
|
864
|
+
const c = position.coords;
|
|
865
|
+
return {
|
|
866
|
+
latitude: c.latitude,
|
|
867
|
+
longitude: c.longitude,
|
|
868
|
+
accuracy: c.accuracy,
|
|
869
|
+
altitude: c.altitude,
|
|
870
|
+
altitudeAccuracy: c.altitudeAccuracy,
|
|
871
|
+
heading: c.heading,
|
|
872
|
+
speed: c.speed,
|
|
873
|
+
timestamp: position.timestamp
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
async function probeGeolocationPermission() {
|
|
877
|
+
try {
|
|
878
|
+
if (typeof navigator === "undefined" || !navigator.permissions?.query) {
|
|
879
|
+
return "unavailable";
|
|
880
|
+
}
|
|
881
|
+
const status = await navigator.permissions.query({ name: "geolocation" });
|
|
882
|
+
return status.state;
|
|
883
|
+
} catch {
|
|
884
|
+
return "unavailable";
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
672
888
|
// src/geolocation/ciphertext.ts
|
|
673
889
|
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
674
890
|
async function computeHMAC(key, message) {
|
|
@@ -721,6 +937,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
721
937
|
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
722
938
|
return null;
|
|
723
939
|
}
|
|
940
|
+
const permissionState = await probeGeolocationPermission();
|
|
724
941
|
return new Promise((resolve) => {
|
|
725
942
|
navigator.geolocation.getCurrentPosition(
|
|
726
943
|
(position) => {
|
|
@@ -737,6 +954,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
737
954
|
resolve(null);
|
|
738
955
|
return;
|
|
739
956
|
}
|
|
957
|
+
const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
|
|
740
958
|
resolve({
|
|
741
959
|
latitude,
|
|
742
960
|
longitude,
|
|
@@ -745,7 +963,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
745
963
|
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
746
964
|
heading: position.coords.heading ?? void 0,
|
|
747
965
|
speed: position.coords.speed ?? void 0,
|
|
748
|
-
timestamp: position.timestamp
|
|
966
|
+
timestamp: position.timestamp,
|
|
967
|
+
integrity_flags: flags.length ? flags : void 0
|
|
749
968
|
});
|
|
750
969
|
},
|
|
751
970
|
() => {
|
|
@@ -812,6 +1031,9 @@ async function generateCipherText(options, config) {
|
|
|
812
1031
|
);
|
|
813
1032
|
if (location) {
|
|
814
1033
|
locationData = location;
|
|
1034
|
+
if (location.integrity_flags?.length) {
|
|
1035
|
+
warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
|
|
1036
|
+
}
|
|
815
1037
|
} else if (gpsRequiredByConfig) {
|
|
816
1038
|
throw new VesantError(
|
|
817
1039
|
`GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
|
|
@@ -931,8 +1153,8 @@ var GeolocationClient = class extends BaseClient {
|
|
|
931
1153
|
* ```
|
|
932
1154
|
*/
|
|
933
1155
|
async verifyIP(request, requestOptions) {
|
|
934
|
-
if (!request.ip_address
|
|
935
|
-
throw new ValidationError("ip_address
|
|
1156
|
+
if (request.ip_address !== void 0 && !request.ip_address.trim()) {
|
|
1157
|
+
throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
|
|
936
1158
|
}
|
|
937
1159
|
return this.requestWithRetry("/api/v1/geo/verify", {
|
|
938
1160
|
method: "POST",
|
|
@@ -1282,20 +1504,12 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1282
1504
|
*
|
|
1283
1505
|
* @example
|
|
1284
1506
|
* ```typescript
|
|
1285
|
-
* //
|
|
1507
|
+
* // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
|
|
1286
1508
|
* const result = await client.captureLocation(token, {
|
|
1287
1509
|
* latitude: 37.7749,
|
|
1288
1510
|
* longitude: -122.4194,
|
|
1289
1511
|
* accuracy: 10
|
|
1290
1512
|
* });
|
|
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
1513
|
* ```
|
|
1300
1514
|
*/
|
|
1301
1515
|
async captureLocation(token, capture, requestOptions) {
|
|
@@ -1800,9 +2014,6 @@ var ComplianceClient = class {
|
|
|
1800
2014
|
if (cipherTextResult.risk?.is_blocked) {
|
|
1801
2015
|
blockReasons.push(...cipherTextResult.risk.block_reasons_structured ?? []);
|
|
1802
2016
|
}
|
|
1803
|
-
if (cipherTextResult.risk?.location_mismatch) {
|
|
1804
|
-
blockReasons.push(sdkReasons.gpsIPMismatch());
|
|
1805
|
-
}
|
|
1806
2017
|
}
|
|
1807
2018
|
if (geoVerification.gps_required && !cipherTextResult) {
|
|
1808
2019
|
blockReasons.push(sdkReasons.gpsRequired());
|
|
@@ -2712,7 +2923,13 @@ var KycClient = class extends BaseClient {
|
|
|
2712
2923
|
*
|
|
2713
2924
|
* Generates a link that the user can visit to submit their KYC documents.
|
|
2714
2925
|
*
|
|
2715
|
-
*
|
|
2926
|
+
* Optionally pass the customer's registered identity data as
|
|
2927
|
+
* `customer_data` (same shape as the geolocation `customer_data` block).
|
|
2928
|
+
* It seeds the customer's risk profile so document verification can
|
|
2929
|
+
* cross-check the submitted document against trusted reference data;
|
|
2930
|
+
* fields never overwrite data already on the profile.
|
|
2931
|
+
*
|
|
2932
|
+
* @param request - Request containing the user ID, optional redirect URL, optional callback URL (receives POST requests), and optional customer identity data
|
|
2716
2933
|
* @returns Response containing the redirect link and KYC ID
|
|
2717
2934
|
*
|
|
2718
2935
|
* @example
|
|
@@ -2720,19 +2937,27 @@ var KycClient = class extends BaseClient {
|
|
|
2720
2937
|
* const result = await client.requestKycSubmitLink({
|
|
2721
2938
|
* user_id: "user_123",
|
|
2722
2939
|
* redirect_url: "https://merchant.com/kyc-complete", // optional
|
|
2723
|
-
* callback_url: "https://merchant.com/api/kyc-webhook" // optional - receives POST requests on status change
|
|
2940
|
+
* callback_url: "https://merchant.com/api/kyc-webhook", // optional - receives POST requests on status change
|
|
2941
|
+
* customer_data: { // optional - seeds the risk profile for document cross-checks
|
|
2942
|
+
* full_name: "John Doe",
|
|
2943
|
+
* date_of_birth: "1999-06-02", // ISO 8601
|
|
2944
|
+
* email: "john@example.com",
|
|
2945
|
+
* phone: "+94771234567",
|
|
2946
|
+
* address: "12 Main St, Colombo", // used for address verification
|
|
2947
|
+
* country: "LK"
|
|
2948
|
+
* }
|
|
2724
2949
|
* });
|
|
2725
2950
|
*
|
|
2726
2951
|
* console.log(`Redirect user to: ${result.link}`);
|
|
2727
2952
|
* console.log(`KYC ID: ${result.kyc_id}`);
|
|
2728
2953
|
* ```
|
|
2729
2954
|
*/
|
|
2730
|
-
async requestKycSubmitLink(request) {
|
|
2955
|
+
async requestKycSubmitLink(request, requestOptions) {
|
|
2731
2956
|
return this.requestWithRetry("/api/v1/kyc/request", {
|
|
2732
2957
|
method: "POST",
|
|
2733
2958
|
body: JSON.stringify(request),
|
|
2734
2959
|
headers: this.getUserHeaders()
|
|
2735
|
-
});
|
|
2960
|
+
}, void 0, void 0, requestOptions);
|
|
2736
2961
|
}
|
|
2737
2962
|
/**
|
|
2738
2963
|
* Create a Event-Based Face Verification session.
|
|
@@ -2765,12 +2990,12 @@ var KycClient = class extends BaseClient {
|
|
|
2765
2990
|
*
|
|
2766
2991
|
* @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
|
|
2767
2992
|
*/
|
|
2768
|
-
async submitEventBasedFaceVerificationSession(request) {
|
|
2993
|
+
async submitEventBasedFaceVerificationSession(request, requestOptions) {
|
|
2769
2994
|
return this.request("/api/v1/kyc/face/submit", {
|
|
2770
2995
|
method: "POST",
|
|
2771
2996
|
body: JSON.stringify(request),
|
|
2772
2997
|
headers: this.getUserHeaders()
|
|
2773
|
-
});
|
|
2998
|
+
}, void 0, requestOptions);
|
|
2774
2999
|
}
|
|
2775
3000
|
/**
|
|
2776
3001
|
* Look up the current state of a Event-Based Face Verification session by its
|
|
@@ -2869,7 +3094,7 @@ var KycClient = class extends BaseClient {
|
|
|
2869
3094
|
* @example
|
|
2870
3095
|
* ```typescript
|
|
2871
3096
|
* const result = await client.submitVerification({
|
|
2872
|
-
* reference: "
|
|
3097
|
+
* reference: "4d2aff34-ab86-422d-992e-50b1234a5b67",
|
|
2873
3098
|
* email: "customer@example.com",
|
|
2874
3099
|
* country: "US",
|
|
2875
3100
|
* document: {
|
|
@@ -2886,12 +3111,12 @@ var KycClient = class extends BaseClient {
|
|
|
2886
3111
|
* console.log(`Verification submitted: ${result.reference}`);
|
|
2887
3112
|
* ```
|
|
2888
3113
|
*/
|
|
2889
|
-
async submitVerification(request) {
|
|
3114
|
+
async submitVerification(request, requestOptions) {
|
|
2890
3115
|
return this.requestWithRetry("/api/v1/kyc/submit", {
|
|
2891
3116
|
method: "POST",
|
|
2892
3117
|
body: JSON.stringify(request),
|
|
2893
3118
|
headers: this.getUserHeaders()
|
|
2894
|
-
});
|
|
3119
|
+
}, void 0, void 0, requestOptions);
|
|
2895
3120
|
}
|
|
2896
3121
|
/**
|
|
2897
3122
|
* Get a KYC request by ID
|
|
@@ -2901,7 +3126,7 @@ var KycClient = class extends BaseClient {
|
|
|
2901
3126
|
*
|
|
2902
3127
|
* @example
|
|
2903
3128
|
* ```typescript
|
|
2904
|
-
* const kyc = await client.getKycRequest("
|
|
3129
|
+
* const kyc = await client.getKycRequest("550e8400-e29b-41d4-a716-446655440000");
|
|
2905
3130
|
* console.log(`Status: ${kyc.status}, ID Verified: ${kyc.id_verified}`);
|
|
2906
3131
|
* ```
|
|
2907
3132
|
*/
|
|
@@ -2976,7 +3201,7 @@ var KycClient = class extends BaseClient {
|
|
|
2976
3201
|
* @example
|
|
2977
3202
|
* ```typescript
|
|
2978
3203
|
* await client.requestAdditionalDocuments({
|
|
2979
|
-
* id: "
|
|
3204
|
+
* id: "550e8400-e29b-41d4-a716-446655440000",
|
|
2980
3205
|
* document_types: ["address", "document_two"],
|
|
2981
3206
|
* message: "Please provide proof of address and secondary ID"
|
|
2982
3207
|
* });
|
|
@@ -2997,7 +3222,7 @@ var KycClient = class extends BaseClient {
|
|
|
2997
3222
|
*
|
|
2998
3223
|
* @example
|
|
2999
3224
|
* ```typescript
|
|
3000
|
-
* const proofs = await client.getProofDownloadURLs("
|
|
3225
|
+
* const proofs = await client.getProofDownloadURLs("550e8400-e29b-41d4-a716-446655440000");
|
|
3001
3226
|
* proofs.forEach(proof => {
|
|
3002
3227
|
* console.log(`${proof.type}: ${proof.url} (expires: ${proof.expires_at})`);
|
|
3003
3228
|
* });
|
|
@@ -3331,19 +3556,13 @@ var TaxClient = class extends BaseClient {
|
|
|
3331
3556
|
{ method: "POST" }
|
|
3332
3557
|
);
|
|
3333
3558
|
}
|
|
3334
|
-
async
|
|
3335
|
-
return this.request(
|
|
3336
|
-
"/api/v1/tax/customer-tax-profiles/request-form-with-profile",
|
|
3337
|
-
{
|
|
3338
|
-
method: "POST",
|
|
3339
|
-
body: JSON.stringify(input)
|
|
3340
|
-
}
|
|
3341
|
-
);
|
|
3342
|
-
}
|
|
3343
|
-
async checkTINStatus(customerID) {
|
|
3559
|
+
async checkTINStatus(customerID, requestOptions) {
|
|
3344
3560
|
return this.requestWithRetry(
|
|
3345
3561
|
`/api/v1/tax/customer-tax-profiles/${customerID}/check-tin`,
|
|
3346
|
-
{ method: "POST" }
|
|
3562
|
+
{ method: "POST" },
|
|
3563
|
+
void 0,
|
|
3564
|
+
void 0,
|
|
3565
|
+
requestOptions
|
|
3347
3566
|
);
|
|
3348
3567
|
}
|
|
3349
3568
|
async reRequestTaxForm(customerID, input) {
|
|
@@ -3366,7 +3585,7 @@ var TaxClient = class extends BaseClient {
|
|
|
3366
3585
|
}
|
|
3367
3586
|
async getCustomerDocuments(customerID) {
|
|
3368
3587
|
return this.request(
|
|
3369
|
-
`/api/v1/
|
|
3588
|
+
`/api/v1/tm/customer-tax-profiles/${customerID}/documents`
|
|
3370
3589
|
);
|
|
3371
3590
|
}
|
|
3372
3591
|
async downloadTaxForm(customerID, requestOptions) {
|
|
@@ -3423,15 +3642,48 @@ var TaxClient = class extends BaseClient {
|
|
|
3423
3642
|
}
|
|
3424
3643
|
};
|
|
3425
3644
|
|
|
3645
|
+
// src/webhooks/dedup.ts
|
|
3646
|
+
var InMemoryDedupStore = class {
|
|
3647
|
+
constructor(opts = {}) {
|
|
3648
|
+
this.expiryById = /* @__PURE__ */ new Map();
|
|
3649
|
+
this.maxEntries = opts.maxEntries ?? 1e3;
|
|
3650
|
+
this.now = opts.now ?? (() => Date.now());
|
|
3651
|
+
}
|
|
3652
|
+
async seen(id) {
|
|
3653
|
+
return this.expiryById.has(id);
|
|
3654
|
+
}
|
|
3655
|
+
async mark(id, ttlMs) {
|
|
3656
|
+
const now = this.now();
|
|
3657
|
+
this.expiryById.set(id, now + ttlMs);
|
|
3658
|
+
if (this.expiryById.size > this.maxEntries) {
|
|
3659
|
+
for (const [key, expiresAt] of this.expiryById) {
|
|
3660
|
+
if (now > expiresAt) {
|
|
3661
|
+
this.expiryById.delete(key);
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
};
|
|
3667
|
+
|
|
3426
3668
|
// src/webhooks/handler.ts
|
|
3669
|
+
function resolveEventId(event) {
|
|
3670
|
+
if (event.id) {
|
|
3671
|
+
return event.id;
|
|
3672
|
+
}
|
|
3673
|
+
if (event.event_type === "notification.created") {
|
|
3674
|
+
return event.notification?.id;
|
|
3675
|
+
}
|
|
3676
|
+
return void 0;
|
|
3677
|
+
}
|
|
3427
3678
|
var WebhookHandler = class {
|
|
3428
3679
|
constructor(config) {
|
|
3429
3680
|
this.handlers = /* @__PURE__ */ new Map();
|
|
3430
3681
|
this.anyHandlers = [];
|
|
3431
|
-
this.seenEventIds = /* @__PURE__ */ new Map();
|
|
3432
3682
|
this.secret = config.secret;
|
|
3433
3683
|
this.tolerance = config.tolerance ?? 3e5;
|
|
3434
3684
|
this.replayProtection = config.replayProtection ?? true;
|
|
3685
|
+
this.allowLegacySignature = config.allowLegacySignature ?? true;
|
|
3686
|
+
this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
|
|
3435
3687
|
}
|
|
3436
3688
|
/**
|
|
3437
3689
|
* Register a handler for a specific event type.
|
|
@@ -3453,7 +3705,10 @@ var WebhookHandler = class {
|
|
|
3453
3705
|
* Verify signature and parse the webhook body.
|
|
3454
3706
|
*/
|
|
3455
3707
|
async verifyAndParse(body, signature) {
|
|
3456
|
-
const isValid = await verifyWebhookSignature(body, signature, this.secret
|
|
3708
|
+
const isValid = await verifyWebhookSignature(body, signature, this.secret, {
|
|
3709
|
+
tolerance: this.tolerance,
|
|
3710
|
+
allowLegacy: this.allowLegacySignature
|
|
3711
|
+
});
|
|
3457
3712
|
if (!isValid) {
|
|
3458
3713
|
throw new ValidationError("Invalid webhook signature", ["signature"]);
|
|
3459
3714
|
}
|
|
@@ -3469,18 +3724,21 @@ var WebhookHandler = class {
|
|
|
3469
3724
|
/**
|
|
3470
3725
|
* Parse an event without signature verification (for testing).
|
|
3471
3726
|
*/
|
|
3472
|
-
parseEvent(body) {
|
|
3727
|
+
async parseEvent(body) {
|
|
3473
3728
|
return this.parseAndValidate(body);
|
|
3474
3729
|
}
|
|
3475
|
-
parseAndValidate(body) {
|
|
3730
|
+
async parseAndValidate(body) {
|
|
3476
3731
|
const event = JSON.parse(body);
|
|
3477
|
-
|
|
3478
|
-
|
|
3732
|
+
const eventId = resolveEventId(event);
|
|
3733
|
+
if (!event.event_type || !eventId || !event.timestamp) {
|
|
3734
|
+
throw new ValidationError(
|
|
3735
|
+
"Invalid webhook event: missing required fields (event_type, id, timestamp)",
|
|
3736
|
+
["event_type", "id", "timestamp"]
|
|
3737
|
+
);
|
|
3479
3738
|
}
|
|
3480
3739
|
if (this.tolerance > 0) {
|
|
3481
3740
|
const eventTime = new Date(event.timestamp).getTime();
|
|
3482
|
-
|
|
3483
|
-
if (Math.abs(now - eventTime) > this.tolerance) {
|
|
3741
|
+
if (Math.abs(Date.now() - eventTime) > this.tolerance) {
|
|
3484
3742
|
throw new ValidationError(
|
|
3485
3743
|
`Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
|
|
3486
3744
|
["timestamp"]
|
|
@@ -3488,26 +3746,18 @@ var WebhookHandler = class {
|
|
|
3488
3746
|
}
|
|
3489
3747
|
}
|
|
3490
3748
|
if (this.replayProtection) {
|
|
3491
|
-
if (this.
|
|
3749
|
+
if (await this.dedupStore.seen(eventId)) {
|
|
3492
3750
|
throw new ValidationError(
|
|
3493
|
-
`Duplicate webhook event: ${
|
|
3751
|
+
`Duplicate webhook event: ${eventId} has already been processed`,
|
|
3494
3752
|
["id"]
|
|
3495
3753
|
);
|
|
3496
3754
|
}
|
|
3497
|
-
|
|
3498
|
-
this.seenEventIds.set(event.id, now);
|
|
3499
|
-
if (this.seenEventIds.size > 1e3) {
|
|
3500
|
-
for (const [id, seenAt] of this.seenEventIds) {
|
|
3501
|
-
if (now - seenAt > this.tolerance) {
|
|
3502
|
-
this.seenEventIds.delete(id);
|
|
3503
|
-
}
|
|
3504
|
-
}
|
|
3505
|
-
}
|
|
3755
|
+
await this.dedupStore.mark(eventId, this.tolerance);
|
|
3506
3756
|
}
|
|
3507
3757
|
return event;
|
|
3508
3758
|
}
|
|
3509
3759
|
async dispatch(event) {
|
|
3510
|
-
const typeHandlers = this.handlers.get(event.
|
|
3760
|
+
const typeHandlers = this.handlers.get(event.event_type) || [];
|
|
3511
3761
|
const allHandlers = [...typeHandlers, ...this.anyHandlers];
|
|
3512
3762
|
for (const handler of allHandlers) {
|
|
3513
3763
|
await handler(event);
|
|
@@ -3598,26 +3848,34 @@ exports.ComplianceClient = ComplianceClient;
|
|
|
3598
3848
|
exports.ComplianceError = ComplianceError;
|
|
3599
3849
|
exports.DEFAULT_CURRENCY_RATES = DEFAULT_CURRENCY_RATES;
|
|
3600
3850
|
exports.GeolocationClient = GeolocationClient;
|
|
3851
|
+
exports.InMemoryDedupStore = InMemoryDedupStore;
|
|
3601
3852
|
exports.KYC_DECLINED_DESCRIPTIONS = KYC_DECLINED_DESCRIPTIONS;
|
|
3602
3853
|
exports.KycClient = KycClient;
|
|
3603
3854
|
exports.NetworkError = NetworkError;
|
|
3604
3855
|
exports.RateLimitError = RateLimitError;
|
|
3605
3856
|
exports.RateLimitTracker = RateLimitTracker;
|
|
3857
|
+
exports.RefreshingTokenProvider = RefreshingTokenProvider;
|
|
3606
3858
|
exports.RiskProfileClient = RiskProfileClient;
|
|
3607
3859
|
exports.SDK_VERSION = SDK_VERSION;
|
|
3608
3860
|
exports.ServiceUnavailableError = ServiceUnavailableError;
|
|
3861
|
+
exports.StaticApiKeyProvider = StaticApiKeyProvider;
|
|
3609
3862
|
exports.TaxClient = TaxClient;
|
|
3610
3863
|
exports.TimeoutError = TimeoutError;
|
|
3611
3864
|
exports.ValidationError = ValidationError;
|
|
3612
3865
|
exports.VesantError = VesantError;
|
|
3613
3866
|
exports.WebhookHandler = WebhookHandler;
|
|
3867
|
+
exports.assessGpsIntegrity = assessGpsIntegrity;
|
|
3868
|
+
exports.collectAll = collectAll;
|
|
3614
3869
|
exports.createConsoleLogger = createConsoleLogger;
|
|
3615
3870
|
exports.createNextWebhookHandler = createNextWebhookHandler;
|
|
3616
3871
|
exports.createWebhookMiddleware = createWebhookMiddleware;
|
|
3617
3872
|
exports.decodeCipherText = decodeCipherText;
|
|
3618
3873
|
exports.generateCipherText = generateCipherText;
|
|
3874
|
+
exports.gpsIntegrityInputFromPosition = gpsIntegrityInputFromPosition;
|
|
3619
3875
|
exports.isCipherTextExpired = isCipherTextExpired;
|
|
3620
3876
|
exports.noopLogger = noopLogger;
|
|
3877
|
+
exports.paginate = paginate;
|
|
3878
|
+
exports.probeGeolocationPermission = probeGeolocationPermission;
|
|
3621
3879
|
exports.sdkReasons = sdkReasons;
|
|
3622
3880
|
exports.verifyWebhookSignature = verifyWebhookSignature;
|
|
3623
3881
|
//# sourceMappingURL=index.js.map
|