vesant-sdk 1.7.0-dev.a85efc5 → 1.7.0-dev.ab635a0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{client-DF7hlMEz.d.ts → client-0NOPDnT0.d.ts} +16 -26
- package/dist/{client-DrjgZoH_.d.mts → client-CvWNRwwg.d.mts} +16 -26
- package/dist/{client-B0qhE2kr.d.mts → client-Dps40EtF.d.mts} +2 -2
- package/dist/{client-DtH2RLuy.d.ts → client-SMxqod4j.d.ts} +2 -2
- package/dist/{client-BolQlL5e.d.mts → client-q9fN8Hhf.d.mts} +103 -1
- package/dist/{client-BolQlL5e.d.ts → client-q9fN8Hhf.d.ts} +103 -1
- package/dist/compliance/index.d.mts +3 -3
- package/dist/compliance/index.d.ts +3 -3
- package/dist/compliance/index.js +133 -19
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +133 -19
- 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-C3zUKydT.d.mts +264 -0
- package/dist/index-DSDgS09k.d.ts +264 -0
- package/dist/index.d.mts +26 -9
- package/dist/index.d.ts +26 -9
- package/dist/index.js +320 -50
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +313 -51
- 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 +90 -15
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +90 -15
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +56 -8
- package/dist/kyc/index.d.ts +56 -8
- package/dist/kyc/index.js +90 -15
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +90 -15
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +7 -3
- package/dist/react.d.ts +7 -3
- package/dist/react.js +68 -4
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +68 -4
- package/dist/react.mjs.map +1 -1
- package/dist/risk-profile/index.d.mts +1 -1
- package/dist/risk-profile/index.d.ts +1 -1
- package/dist/risk-profile/index.js +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 +2 -2
- package/dist/tax/index.d.ts +2 -2
- package/dist/tax/index.js +73 -9
- package/dist/tax/index.js.map +1 -1
- package/dist/tax/index.mjs +73 -9
- 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,69 @@ 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
|
+
|
|
758
|
+
// src/core/dedup.ts
|
|
759
|
+
var InMemoryDedupStore = class {
|
|
760
|
+
constructor(opts = {}) {
|
|
761
|
+
this.expiryById = /* @__PURE__ */ new Map();
|
|
762
|
+
this.maxEntries = opts.maxEntries ?? 1e3;
|
|
763
|
+
this.now = opts.now ?? (() => Date.now());
|
|
764
|
+
}
|
|
765
|
+
async seen(id) {
|
|
766
|
+
return this.expiryById.has(id);
|
|
767
|
+
}
|
|
768
|
+
async mark(id, ttlMs) {
|
|
769
|
+
const now = this.now();
|
|
770
|
+
this.expiryById.set(id, now + ttlMs);
|
|
771
|
+
if (this.expiryById.size > this.maxEntries) {
|
|
772
|
+
for (const [key, expiresAt] of this.expiryById) {
|
|
773
|
+
if (now > expiresAt) {
|
|
774
|
+
this.expiryById.delete(key);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
|
|
629
781
|
// src/core/webhook-utils.ts
|
|
630
|
-
async function verifyWebhookSignature(payload, signature, secret) {
|
|
782
|
+
async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
|
|
783
|
+
const signed = parseSignedTimestamp(signature);
|
|
784
|
+
if (signed) {
|
|
785
|
+
const tolerance = opts.tolerance ?? 3e5;
|
|
786
|
+
const now = opts.now?.() ?? Date.now();
|
|
787
|
+
if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
|
|
791
|
+
return constantTimeEqual(signed.v1, expected);
|
|
792
|
+
}
|
|
793
|
+
if (opts.allowLegacy === false) {
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
631
796
|
const hexDigest = await computeHmacSha256(payload, secret);
|
|
632
797
|
const expectedPrefixed = `sha256=${hexDigest}`;
|
|
633
798
|
if (signature.startsWith("sha256=")) {
|
|
@@ -635,6 +800,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
|
|
|
635
800
|
}
|
|
636
801
|
return constantTimeEqual(signature, hexDigest);
|
|
637
802
|
}
|
|
803
|
+
function parseSignedTimestamp(signature) {
|
|
804
|
+
if (!signature.includes("t=") || !signature.includes("v1=")) {
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
let t;
|
|
808
|
+
let v1;
|
|
809
|
+
for (const part of signature.split(",")) {
|
|
810
|
+
const eq = part.indexOf("=");
|
|
811
|
+
if (eq === -1) continue;
|
|
812
|
+
const key = part.slice(0, eq).trim();
|
|
813
|
+
const value = part.slice(eq + 1).trim();
|
|
814
|
+
if (key === "t") t = Number(value);
|
|
815
|
+
else if (key === "v1") v1 = value;
|
|
816
|
+
}
|
|
817
|
+
if (t === void 0 || Number.isNaN(t) || !v1) {
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
return { t, v1 };
|
|
821
|
+
}
|
|
638
822
|
async function computeHmacSha256(message, secret) {
|
|
639
823
|
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
640
824
|
const encoder = new TextEncoder();
|
|
@@ -669,6 +853,61 @@ function constantTimeEqual(a, b) {
|
|
|
669
853
|
return result === 0;
|
|
670
854
|
}
|
|
671
855
|
|
|
856
|
+
// src/geolocation/integrity.ts
|
|
857
|
+
var isNil = (v) => v === null || v === void 0;
|
|
858
|
+
function assessGpsIntegrity(input, opts = {}) {
|
|
859
|
+
const flags = [];
|
|
860
|
+
const now = opts.now ?? (() => Date.now());
|
|
861
|
+
const maxAgeMs = opts.maxAgeMs ?? 6e4;
|
|
862
|
+
const minAccuracy = opts.minAccuracyMeters ?? 1;
|
|
863
|
+
const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
|
|
864
|
+
if (isNil(accuracy)) {
|
|
865
|
+
flags.push("accuracy_missing");
|
|
866
|
+
} else if (accuracy === 0) {
|
|
867
|
+
flags.push("accuracy_zero");
|
|
868
|
+
} else if (accuracy < minAccuracy) {
|
|
869
|
+
flags.push("accuracy_implausibly_small");
|
|
870
|
+
}
|
|
871
|
+
const hasMotion = !isNil(heading) || !isNil(speed);
|
|
872
|
+
if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
|
|
873
|
+
flags.push("motion_without_accuracy");
|
|
874
|
+
}
|
|
875
|
+
if (!isNil(altitude) && isNil(altitudeAccuracy)) {
|
|
876
|
+
flags.push("altitude_without_accuracy");
|
|
877
|
+
}
|
|
878
|
+
if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
|
|
879
|
+
flags.push("stale_timestamp");
|
|
880
|
+
}
|
|
881
|
+
if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
|
|
882
|
+
flags.push(`permission_${opts.permissionState}`);
|
|
883
|
+
}
|
|
884
|
+
return { suspicious: flags.length > 0, flags };
|
|
885
|
+
}
|
|
886
|
+
function gpsIntegrityInputFromPosition(position) {
|
|
887
|
+
const c = position.coords;
|
|
888
|
+
return {
|
|
889
|
+
latitude: c.latitude,
|
|
890
|
+
longitude: c.longitude,
|
|
891
|
+
accuracy: c.accuracy,
|
|
892
|
+
altitude: c.altitude,
|
|
893
|
+
altitudeAccuracy: c.altitudeAccuracy,
|
|
894
|
+
heading: c.heading,
|
|
895
|
+
speed: c.speed,
|
|
896
|
+
timestamp: position.timestamp
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
async function probeGeolocationPermission() {
|
|
900
|
+
try {
|
|
901
|
+
if (typeof navigator === "undefined" || !navigator.permissions?.query) {
|
|
902
|
+
return "unavailable";
|
|
903
|
+
}
|
|
904
|
+
const status = await navigator.permissions.query({ name: "geolocation" });
|
|
905
|
+
return status.state;
|
|
906
|
+
} catch {
|
|
907
|
+
return "unavailable";
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
672
911
|
// src/geolocation/ciphertext.ts
|
|
673
912
|
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
674
913
|
async function computeHMAC(key, message) {
|
|
@@ -721,6 +960,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
721
960
|
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
722
961
|
return null;
|
|
723
962
|
}
|
|
963
|
+
const permissionState = await probeGeolocationPermission();
|
|
724
964
|
return new Promise((resolve) => {
|
|
725
965
|
navigator.geolocation.getCurrentPosition(
|
|
726
966
|
(position) => {
|
|
@@ -737,6 +977,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
737
977
|
resolve(null);
|
|
738
978
|
return;
|
|
739
979
|
}
|
|
980
|
+
const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
|
|
740
981
|
resolve({
|
|
741
982
|
latitude,
|
|
742
983
|
longitude,
|
|
@@ -745,7 +986,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
745
986
|
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
746
987
|
heading: position.coords.heading ?? void 0,
|
|
747
988
|
speed: position.coords.speed ?? void 0,
|
|
748
|
-
timestamp: position.timestamp
|
|
989
|
+
timestamp: position.timestamp,
|
|
990
|
+
integrity_flags: flags.length ? flags : void 0
|
|
749
991
|
});
|
|
750
992
|
},
|
|
751
993
|
() => {
|
|
@@ -812,6 +1054,9 @@ async function generateCipherText(options, config) {
|
|
|
812
1054
|
);
|
|
813
1055
|
if (location) {
|
|
814
1056
|
locationData = location;
|
|
1057
|
+
if (location.integrity_flags?.length) {
|
|
1058
|
+
warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
|
|
1059
|
+
}
|
|
815
1060
|
} else if (gpsRequiredByConfig) {
|
|
816
1061
|
throw new VesantError(
|
|
817
1062
|
`GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
|
|
@@ -931,8 +1176,8 @@ var GeolocationClient = class extends BaseClient {
|
|
|
931
1176
|
* ```
|
|
932
1177
|
*/
|
|
933
1178
|
async verifyIP(request, requestOptions) {
|
|
934
|
-
if (!request.ip_address
|
|
935
|
-
throw new ValidationError("ip_address
|
|
1179
|
+
if (request.ip_address !== void 0 && !request.ip_address.trim()) {
|
|
1180
|
+
throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
|
|
936
1181
|
}
|
|
937
1182
|
return this.requestWithRetry("/api/v1/geo/verify", {
|
|
938
1183
|
method: "POST",
|
|
@@ -1282,20 +1527,12 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1282
1527
|
*
|
|
1283
1528
|
* @example
|
|
1284
1529
|
* ```typescript
|
|
1285
|
-
* //
|
|
1530
|
+
* // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
|
|
1286
1531
|
* const result = await client.captureLocation(token, {
|
|
1287
1532
|
* latitude: 37.7749,
|
|
1288
1533
|
* longitude: -122.4194,
|
|
1289
1534
|
* accuracy: 10
|
|
1290
1535
|
* });
|
|
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
1536
|
* ```
|
|
1300
1537
|
*/
|
|
1301
1538
|
async captureLocation(token, capture, requestOptions) {
|
|
@@ -2709,7 +2946,13 @@ var KycClient = class extends BaseClient {
|
|
|
2709
2946
|
*
|
|
2710
2947
|
* Generates a link that the user can visit to submit their KYC documents.
|
|
2711
2948
|
*
|
|
2712
|
-
*
|
|
2949
|
+
* Optionally pass the customer's registered identity data as
|
|
2950
|
+
* `customer_data` (same shape as the geolocation `customer_data` block).
|
|
2951
|
+
* It seeds the customer's risk profile so document verification can
|
|
2952
|
+
* cross-check the submitted document against trusted reference data;
|
|
2953
|
+
* fields never overwrite data already on the profile.
|
|
2954
|
+
*
|
|
2955
|
+
* @param request - Request containing the user ID, optional redirect URL, optional callback URL (receives POST requests), and optional customer identity data
|
|
2713
2956
|
* @returns Response containing the redirect link and KYC ID
|
|
2714
2957
|
*
|
|
2715
2958
|
* @example
|
|
@@ -2717,19 +2960,27 @@ var KycClient = class extends BaseClient {
|
|
|
2717
2960
|
* const result = await client.requestKycSubmitLink({
|
|
2718
2961
|
* user_id: "user_123",
|
|
2719
2962
|
* redirect_url: "https://merchant.com/kyc-complete", // optional
|
|
2720
|
-
* callback_url: "https://merchant.com/api/kyc-webhook" // optional - receives POST requests on status change
|
|
2963
|
+
* callback_url: "https://merchant.com/api/kyc-webhook", // optional - receives POST requests on status change
|
|
2964
|
+
* customer_data: { // optional - seeds the risk profile for document cross-checks
|
|
2965
|
+
* full_name: "John Doe",
|
|
2966
|
+
* date_of_birth: "1999-06-02", // ISO 8601
|
|
2967
|
+
* email: "john@example.com",
|
|
2968
|
+
* phone: "+94771234567",
|
|
2969
|
+
* address: "12 Main St, Colombo", // used for address verification
|
|
2970
|
+
* country: "LK"
|
|
2971
|
+
* }
|
|
2721
2972
|
* });
|
|
2722
2973
|
*
|
|
2723
2974
|
* console.log(`Redirect user to: ${result.link}`);
|
|
2724
2975
|
* console.log(`KYC ID: ${result.kyc_id}`);
|
|
2725
2976
|
* ```
|
|
2726
2977
|
*/
|
|
2727
|
-
async requestKycSubmitLink(request) {
|
|
2978
|
+
async requestKycSubmitLink(request, requestOptions) {
|
|
2728
2979
|
return this.requestWithRetry("/api/v1/kyc/request", {
|
|
2729
2980
|
method: "POST",
|
|
2730
2981
|
body: JSON.stringify(request),
|
|
2731
2982
|
headers: this.getUserHeaders()
|
|
2732
|
-
});
|
|
2983
|
+
}, void 0, void 0, requestOptions);
|
|
2733
2984
|
}
|
|
2734
2985
|
/**
|
|
2735
2986
|
* Create a Event-Based Face Verification session.
|
|
@@ -2762,12 +3013,12 @@ var KycClient = class extends BaseClient {
|
|
|
2762
3013
|
*
|
|
2763
3014
|
* @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
|
|
2764
3015
|
*/
|
|
2765
|
-
async submitEventBasedFaceVerificationSession(request) {
|
|
3016
|
+
async submitEventBasedFaceVerificationSession(request, requestOptions) {
|
|
2766
3017
|
return this.request("/api/v1/kyc/face/submit", {
|
|
2767
3018
|
method: "POST",
|
|
2768
3019
|
body: JSON.stringify(request),
|
|
2769
3020
|
headers: this.getUserHeaders()
|
|
2770
|
-
});
|
|
3021
|
+
}, void 0, requestOptions);
|
|
2771
3022
|
}
|
|
2772
3023
|
/**
|
|
2773
3024
|
* Look up the current state of a Event-Based Face Verification session by its
|
|
@@ -2883,12 +3134,12 @@ var KycClient = class extends BaseClient {
|
|
|
2883
3134
|
* console.log(`Verification submitted: ${result.reference}`);
|
|
2884
3135
|
* ```
|
|
2885
3136
|
*/
|
|
2886
|
-
async submitVerification(request) {
|
|
3137
|
+
async submitVerification(request, requestOptions) {
|
|
2887
3138
|
return this.requestWithRetry("/api/v1/kyc/submit", {
|
|
2888
3139
|
method: "POST",
|
|
2889
3140
|
body: JSON.stringify(request),
|
|
2890
3141
|
headers: this.getUserHeaders()
|
|
2891
|
-
});
|
|
3142
|
+
}, void 0, void 0, requestOptions);
|
|
2892
3143
|
}
|
|
2893
3144
|
/**
|
|
2894
3145
|
* Get a KYC request by ID
|
|
@@ -3328,10 +3579,13 @@ var TaxClient = class extends BaseClient {
|
|
|
3328
3579
|
{ method: "POST" }
|
|
3329
3580
|
);
|
|
3330
3581
|
}
|
|
3331
|
-
async checkTINStatus(customerID) {
|
|
3582
|
+
async checkTINStatus(customerID, requestOptions) {
|
|
3332
3583
|
return this.requestWithRetry(
|
|
3333
3584
|
`/api/v1/tax/customer-tax-profiles/${customerID}/check-tin`,
|
|
3334
|
-
{ method: "POST" }
|
|
3585
|
+
{ method: "POST" },
|
|
3586
|
+
void 0,
|
|
3587
|
+
void 0,
|
|
3588
|
+
requestOptions
|
|
3335
3589
|
);
|
|
3336
3590
|
}
|
|
3337
3591
|
async reRequestTaxForm(customerID, input) {
|
|
@@ -3412,14 +3666,24 @@ var TaxClient = class extends BaseClient {
|
|
|
3412
3666
|
};
|
|
3413
3667
|
|
|
3414
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
|
+
}
|
|
3415
3678
|
var WebhookHandler = class {
|
|
3416
3679
|
constructor(config) {
|
|
3417
3680
|
this.handlers = /* @__PURE__ */ new Map();
|
|
3418
3681
|
this.anyHandlers = [];
|
|
3419
|
-
this.seenEventIds = /* @__PURE__ */ new Map();
|
|
3420
3682
|
this.secret = config.secret;
|
|
3421
3683
|
this.tolerance = config.tolerance ?? 3e5;
|
|
3422
3684
|
this.replayProtection = config.replayProtection ?? true;
|
|
3685
|
+
this.allowLegacySignature = config.allowLegacySignature ?? true;
|
|
3686
|
+
this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
|
|
3423
3687
|
}
|
|
3424
3688
|
/**
|
|
3425
3689
|
* Register a handler for a specific event type.
|
|
@@ -3441,7 +3705,10 @@ var WebhookHandler = class {
|
|
|
3441
3705
|
* Verify signature and parse the webhook body.
|
|
3442
3706
|
*/
|
|
3443
3707
|
async verifyAndParse(body, signature) {
|
|
3444
|
-
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
|
+
});
|
|
3445
3712
|
if (!isValid) {
|
|
3446
3713
|
throw new ValidationError("Invalid webhook signature", ["signature"]);
|
|
3447
3714
|
}
|
|
@@ -3457,18 +3724,21 @@ var WebhookHandler = class {
|
|
|
3457
3724
|
/**
|
|
3458
3725
|
* Parse an event without signature verification (for testing).
|
|
3459
3726
|
*/
|
|
3460
|
-
parseEvent(body) {
|
|
3727
|
+
async parseEvent(body) {
|
|
3461
3728
|
return this.parseAndValidate(body);
|
|
3462
3729
|
}
|
|
3463
|
-
parseAndValidate(body) {
|
|
3730
|
+
async parseAndValidate(body) {
|
|
3464
3731
|
const event = JSON.parse(body);
|
|
3465
|
-
|
|
3466
|
-
|
|
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
|
+
);
|
|
3467
3738
|
}
|
|
3468
3739
|
if (this.tolerance > 0) {
|
|
3469
3740
|
const eventTime = new Date(event.timestamp).getTime();
|
|
3470
|
-
|
|
3471
|
-
if (Math.abs(now - eventTime) > this.tolerance) {
|
|
3741
|
+
if (Math.abs(Date.now() - eventTime) > this.tolerance) {
|
|
3472
3742
|
throw new ValidationError(
|
|
3473
3743
|
`Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
|
|
3474
3744
|
["timestamp"]
|
|
@@ -3476,26 +3746,18 @@ var WebhookHandler = class {
|
|
|
3476
3746
|
}
|
|
3477
3747
|
}
|
|
3478
3748
|
if (this.replayProtection) {
|
|
3479
|
-
if (this.
|
|
3749
|
+
if (await this.dedupStore.seen(eventId)) {
|
|
3480
3750
|
throw new ValidationError(
|
|
3481
|
-
`Duplicate webhook event: ${
|
|
3751
|
+
`Duplicate webhook event: ${eventId} has already been processed`,
|
|
3482
3752
|
["id"]
|
|
3483
3753
|
);
|
|
3484
3754
|
}
|
|
3485
|
-
|
|
3486
|
-
this.seenEventIds.set(event.id, now);
|
|
3487
|
-
if (this.seenEventIds.size > 1e3) {
|
|
3488
|
-
for (const [id, seenAt] of this.seenEventIds) {
|
|
3489
|
-
if (now - seenAt > this.tolerance) {
|
|
3490
|
-
this.seenEventIds.delete(id);
|
|
3491
|
-
}
|
|
3492
|
-
}
|
|
3493
|
-
}
|
|
3755
|
+
await this.dedupStore.mark(eventId, this.tolerance);
|
|
3494
3756
|
}
|
|
3495
3757
|
return event;
|
|
3496
3758
|
}
|
|
3497
3759
|
async dispatch(event) {
|
|
3498
|
-
const typeHandlers = this.handlers.get(event.
|
|
3760
|
+
const typeHandlers = this.handlers.get(event.event_type) || [];
|
|
3499
3761
|
const allHandlers = [...typeHandlers, ...this.anyHandlers];
|
|
3500
3762
|
for (const handler of allHandlers) {
|
|
3501
3763
|
await handler(event);
|
|
@@ -3586,26 +3848,34 @@ exports.ComplianceClient = ComplianceClient;
|
|
|
3586
3848
|
exports.ComplianceError = ComplianceError;
|
|
3587
3849
|
exports.DEFAULT_CURRENCY_RATES = DEFAULT_CURRENCY_RATES;
|
|
3588
3850
|
exports.GeolocationClient = GeolocationClient;
|
|
3851
|
+
exports.InMemoryDedupStore = InMemoryDedupStore;
|
|
3589
3852
|
exports.KYC_DECLINED_DESCRIPTIONS = KYC_DECLINED_DESCRIPTIONS;
|
|
3590
3853
|
exports.KycClient = KycClient;
|
|
3591
3854
|
exports.NetworkError = NetworkError;
|
|
3592
3855
|
exports.RateLimitError = RateLimitError;
|
|
3593
3856
|
exports.RateLimitTracker = RateLimitTracker;
|
|
3857
|
+
exports.RefreshingTokenProvider = RefreshingTokenProvider;
|
|
3594
3858
|
exports.RiskProfileClient = RiskProfileClient;
|
|
3595
3859
|
exports.SDK_VERSION = SDK_VERSION;
|
|
3596
3860
|
exports.ServiceUnavailableError = ServiceUnavailableError;
|
|
3861
|
+
exports.StaticApiKeyProvider = StaticApiKeyProvider;
|
|
3597
3862
|
exports.TaxClient = TaxClient;
|
|
3598
3863
|
exports.TimeoutError = TimeoutError;
|
|
3599
3864
|
exports.ValidationError = ValidationError;
|
|
3600
3865
|
exports.VesantError = VesantError;
|
|
3601
3866
|
exports.WebhookHandler = WebhookHandler;
|
|
3867
|
+
exports.assessGpsIntegrity = assessGpsIntegrity;
|
|
3868
|
+
exports.collectAll = collectAll;
|
|
3602
3869
|
exports.createConsoleLogger = createConsoleLogger;
|
|
3603
3870
|
exports.createNextWebhookHandler = createNextWebhookHandler;
|
|
3604
3871
|
exports.createWebhookMiddleware = createWebhookMiddleware;
|
|
3605
3872
|
exports.decodeCipherText = decodeCipherText;
|
|
3606
3873
|
exports.generateCipherText = generateCipherText;
|
|
3874
|
+
exports.gpsIntegrityInputFromPosition = gpsIntegrityInputFromPosition;
|
|
3607
3875
|
exports.isCipherTextExpired = isCipherTextExpired;
|
|
3608
3876
|
exports.noopLogger = noopLogger;
|
|
3877
|
+
exports.paginate = paginate;
|
|
3878
|
+
exports.probeGeolocationPermission = probeGeolocationPermission;
|
|
3609
3879
|
exports.sdkReasons = sdkReasons;
|
|
3610
3880
|
exports.verifyWebhookSignature = verifyWebhookSignature;
|
|
3611
3881
|
//# sourceMappingURL=index.js.map
|