vesant-sdk 1.7.0 → 1.7.1-dev.301c6df
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-DtH2RLuy.d.ts → client-B3DCAHlg.d.ts} +2 -2
- package/dist/{client-DrjgZoH_.d.mts → client-BwXMQA2a.d.mts} +64 -42
- package/dist/{client-B0qhE2kr.d.mts → client-D23KhWeh.d.mts} +2 -2
- package/dist/{client-DF7hlMEz.d.ts → client-fy3trF2K.d.ts} +64 -42
- 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 +181 -91
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +181 -91
- 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 +69 -8
- package/dist/decisions/index.js.map +1 -1
- package/dist/decisions/index.mjs +69 -8
- package/dist/decisions/index.mjs.map +1 -1
- package/dist/geolocation/index.d.mts +68 -7
- package/dist/geolocation/index.d.ts +68 -7
- package/dist/geolocation/index.js +186 -91
- package/dist/geolocation/index.js.map +1 -1
- package/dist/geolocation/index.mjs +183 -92
- 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 +354 -120
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +346 -121
- package/dist/index.mjs.map +1 -1
- package/dist/kyc/core.d.mts +1 -1
- package/dist/kyc/core.d.ts +1 -1
- package/dist/kyc/core.js +75 -14
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +75 -14
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +4 -4
- package/dist/kyc/index.d.ts +4 -4
- package/dist/kyc/index.js +75 -14
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +75 -14
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +7 -3
- package/dist/react.d.ts +7 -3
- package/dist/react.js +70 -33
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +70 -33
- 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 +69 -8
- package/dist/risk-profile/index.js.map +1 -1
- package/dist/risk-profile/index.mjs +69 -8
- 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 +69 -8
- package/dist/scores/index.js.map +1 -1
- package/dist/scores/index.mjs +69 -8
- 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 +74 -10
- package/dist/tax/index.js.map +1 -1
- package/dist/tax/index.mjs +74 -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) {
|
|
@@ -240,7 +296,7 @@ var noopLogger = {
|
|
|
240
296
|
};
|
|
241
297
|
|
|
242
298
|
// src/core/version.ts
|
|
243
|
-
var SDK_VERSION = "1.7.
|
|
299
|
+
var SDK_VERSION = "1.7.1";
|
|
244
300
|
|
|
245
301
|
// src/shared/browser-utils.ts
|
|
246
302
|
function generateUUID() {
|
|
@@ -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,69 @@ 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
|
+
|
|
756
|
+
// src/core/dedup.ts
|
|
757
|
+
var InMemoryDedupStore = class {
|
|
758
|
+
constructor(opts = {}) {
|
|
759
|
+
this.expiryById = /* @__PURE__ */ new Map();
|
|
760
|
+
this.maxEntries = opts.maxEntries ?? 1e3;
|
|
761
|
+
this.now = opts.now ?? (() => Date.now());
|
|
762
|
+
}
|
|
763
|
+
async seen(id) {
|
|
764
|
+
return this.expiryById.has(id);
|
|
765
|
+
}
|
|
766
|
+
async mark(id, ttlMs) {
|
|
767
|
+
const now = this.now();
|
|
768
|
+
this.expiryById.set(id, now + ttlMs);
|
|
769
|
+
if (this.expiryById.size > this.maxEntries) {
|
|
770
|
+
for (const [key, expiresAt] of this.expiryById) {
|
|
771
|
+
if (now > expiresAt) {
|
|
772
|
+
this.expiryById.delete(key);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
|
|
627
779
|
// src/core/webhook-utils.ts
|
|
628
|
-
async function verifyWebhookSignature(payload, signature, secret) {
|
|
780
|
+
async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
|
|
781
|
+
const signed = parseSignedTimestamp(signature);
|
|
782
|
+
if (signed) {
|
|
783
|
+
const tolerance = opts.tolerance ?? 3e5;
|
|
784
|
+
const now = opts.now?.() ?? Date.now();
|
|
785
|
+
if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
|
|
789
|
+
return constantTimeEqual(signed.v1, expected);
|
|
790
|
+
}
|
|
791
|
+
if (opts.allowLegacy === false) {
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
629
794
|
const hexDigest = await computeHmacSha256(payload, secret);
|
|
630
795
|
const expectedPrefixed = `sha256=${hexDigest}`;
|
|
631
796
|
if (signature.startsWith("sha256=")) {
|
|
@@ -633,6 +798,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
|
|
|
633
798
|
}
|
|
634
799
|
return constantTimeEqual(signature, hexDigest);
|
|
635
800
|
}
|
|
801
|
+
function parseSignedTimestamp(signature) {
|
|
802
|
+
if (!signature.includes("t=") || !signature.includes("v1=")) {
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
let t;
|
|
806
|
+
let v1;
|
|
807
|
+
for (const part of signature.split(",")) {
|
|
808
|
+
const eq = part.indexOf("=");
|
|
809
|
+
if (eq === -1) continue;
|
|
810
|
+
const key = part.slice(0, eq).trim();
|
|
811
|
+
const value = part.slice(eq + 1).trim();
|
|
812
|
+
if (key === "t") t = Number(value);
|
|
813
|
+
else if (key === "v1") v1 = value;
|
|
814
|
+
}
|
|
815
|
+
if (t === void 0 || Number.isNaN(t) || !v1) {
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
return { t, v1 };
|
|
819
|
+
}
|
|
636
820
|
async function computeHmacSha256(message, secret) {
|
|
637
821
|
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
638
822
|
const encoder = new TextEncoder();
|
|
@@ -667,27 +851,63 @@ function constantTimeEqual(a, b) {
|
|
|
667
851
|
return result === 0;
|
|
668
852
|
}
|
|
669
853
|
|
|
670
|
-
// src/geolocation/
|
|
671
|
-
var
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
);
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
854
|
+
// src/geolocation/integrity.ts
|
|
855
|
+
var isNil = (v) => v === null || v === void 0;
|
|
856
|
+
function assessGpsIntegrity(input, opts = {}) {
|
|
857
|
+
const flags = [];
|
|
858
|
+
const now = opts.now ?? (() => Date.now());
|
|
859
|
+
const maxAgeMs = opts.maxAgeMs ?? 6e4;
|
|
860
|
+
const minAccuracy = opts.minAccuracyMeters ?? 1;
|
|
861
|
+
const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
|
|
862
|
+
if (isNil(accuracy)) {
|
|
863
|
+
flags.push("accuracy_missing");
|
|
864
|
+
} else if (accuracy === 0) {
|
|
865
|
+
flags.push("accuracy_zero");
|
|
866
|
+
} else if (accuracy < minAccuracy) {
|
|
867
|
+
flags.push("accuracy_implausibly_small");
|
|
868
|
+
}
|
|
869
|
+
const hasMotion = !isNil(heading) || !isNil(speed);
|
|
870
|
+
if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
|
|
871
|
+
flags.push("motion_without_accuracy");
|
|
872
|
+
}
|
|
873
|
+
if (!isNil(altitude) && isNil(altitudeAccuracy)) {
|
|
874
|
+
flags.push("altitude_without_accuracy");
|
|
875
|
+
}
|
|
876
|
+
if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
|
|
877
|
+
flags.push("stale_timestamp");
|
|
878
|
+
}
|
|
879
|
+
if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
|
|
880
|
+
flags.push(`permission_${opts.permissionState}`);
|
|
881
|
+
}
|
|
882
|
+
return { suspicious: flags.length > 0, flags };
|
|
883
|
+
}
|
|
884
|
+
function gpsIntegrityInputFromPosition(position) {
|
|
885
|
+
const c = position.coords;
|
|
886
|
+
return {
|
|
887
|
+
latitude: c.latitude,
|
|
888
|
+
longitude: c.longitude,
|
|
889
|
+
accuracy: c.accuracy,
|
|
890
|
+
altitude: c.altitude,
|
|
891
|
+
altitudeAccuracy: c.altitudeAccuracy,
|
|
892
|
+
heading: c.heading,
|
|
893
|
+
speed: c.speed,
|
|
894
|
+
timestamp: position.timestamp
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
async function probeGeolocationPermission() {
|
|
898
|
+
try {
|
|
899
|
+
if (typeof navigator === "undefined" || !navigator.permissions?.query) {
|
|
900
|
+
return "unavailable";
|
|
901
|
+
}
|
|
902
|
+
const status = await navigator.permissions.query({ name: "geolocation" });
|
|
903
|
+
return status.state;
|
|
904
|
+
} catch {
|
|
905
|
+
return "unavailable";
|
|
687
906
|
}
|
|
688
|
-
const { createHmac } = await import('crypto');
|
|
689
|
-
return createHmac("sha256", key).update(message).digest("hex");
|
|
690
907
|
}
|
|
908
|
+
|
|
909
|
+
// src/geolocation/ciphertext.ts
|
|
910
|
+
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
691
911
|
function getWebGLInfo() {
|
|
692
912
|
if (typeof document === "undefined") return null;
|
|
693
913
|
try {
|
|
@@ -719,6 +939,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
719
939
|
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
720
940
|
return null;
|
|
721
941
|
}
|
|
942
|
+
const permissionState = await probeGeolocationPermission();
|
|
722
943
|
return new Promise((resolve) => {
|
|
723
944
|
navigator.geolocation.getCurrentPosition(
|
|
724
945
|
(position) => {
|
|
@@ -735,6 +956,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
735
956
|
resolve(null);
|
|
736
957
|
return;
|
|
737
958
|
}
|
|
959
|
+
const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
|
|
738
960
|
resolve({
|
|
739
961
|
latitude,
|
|
740
962
|
longitude,
|
|
@@ -743,7 +965,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
743
965
|
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
744
966
|
heading: position.coords.heading ?? void 0,
|
|
745
967
|
speed: position.coords.speed ?? void 0,
|
|
746
|
-
timestamp: position.timestamp
|
|
968
|
+
timestamp: position.timestamp,
|
|
969
|
+
integrity_flags: flags.length ? flags : void 0
|
|
747
970
|
});
|
|
748
971
|
},
|
|
749
972
|
() => {
|
|
@@ -810,6 +1033,9 @@ async function generateCipherText(options, config) {
|
|
|
810
1033
|
);
|
|
811
1034
|
if (location) {
|
|
812
1035
|
locationData = location;
|
|
1036
|
+
if (location.integrity_flags?.length) {
|
|
1037
|
+
warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
|
|
1038
|
+
}
|
|
813
1039
|
} else if (gpsRequiredByConfig) {
|
|
814
1040
|
throw new VesantError(
|
|
815
1041
|
`GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
|
|
@@ -839,15 +1065,7 @@ async function generateCipherText(options, config) {
|
|
|
839
1065
|
};
|
|
840
1066
|
const encoded = encodePayload(payload);
|
|
841
1067
|
const timestamp = now.getTime().toString(36);
|
|
842
|
-
|
|
843
|
-
const hmacKey = options.signingKey || options.apiKey;
|
|
844
|
-
if (hmacKey) {
|
|
845
|
-
const message = `02.${timestamp}.${encoded}`;
|
|
846
|
-
const hmac = await computeHMAC(hmacKey, message);
|
|
847
|
-
cipherText = `${message}.${hmac}`;
|
|
848
|
-
} else {
|
|
849
|
-
cipherText = `01.${timestamp}.${encoded}`;
|
|
850
|
-
}
|
|
1068
|
+
const cipherText = `01.${timestamp}.${encoded}`;
|
|
851
1069
|
return {
|
|
852
1070
|
cipherText,
|
|
853
1071
|
locationCaptured: !!locationData,
|
|
@@ -856,6 +1074,7 @@ async function generateCipherText(options, config) {
|
|
|
856
1074
|
expiresAt: expiry.toISOString()
|
|
857
1075
|
};
|
|
858
1076
|
}
|
|
1077
|
+
var collectDeviceSignals = generateCipherText;
|
|
859
1078
|
function decodeCipherText(cipherText) {
|
|
860
1079
|
try {
|
|
861
1080
|
const parts = cipherText.split(".");
|
|
@@ -929,8 +1148,8 @@ var GeolocationClient = class extends BaseClient {
|
|
|
929
1148
|
* ```
|
|
930
1149
|
*/
|
|
931
1150
|
async verifyIP(request, requestOptions) {
|
|
932
|
-
if (!request.ip_address
|
|
933
|
-
throw new ValidationError("ip_address
|
|
1151
|
+
if (request.ip_address !== void 0 && !request.ip_address.trim()) {
|
|
1152
|
+
throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
|
|
934
1153
|
}
|
|
935
1154
|
return this.requestWithRetry("/api/v1/geo/verify", {
|
|
936
1155
|
method: "POST",
|
|
@@ -983,37 +1202,50 @@ var GeolocationClient = class extends BaseClient {
|
|
|
983
1202
|
* ```
|
|
984
1203
|
*/
|
|
985
1204
|
async getGPSConfig(requestOptions) {
|
|
986
|
-
|
|
987
|
-
if (config.signing_key) {
|
|
988
|
-
this.cachedSigningKey = config.signing_key;
|
|
989
|
-
}
|
|
990
|
-
return config;
|
|
1205
|
+
return this.requestWithRetry("/api/v1/geo/config", void 0, void 0, void 0, requestOptions);
|
|
991
1206
|
}
|
|
992
1207
|
/**
|
|
993
|
-
*
|
|
1208
|
+
* Mint a short-lived, single-use device-signals nonce.
|
|
994
1209
|
*
|
|
995
|
-
*
|
|
996
|
-
*
|
|
1210
|
+
* The nonce is bound to the tenant, user, and event type and is consumed
|
|
1211
|
+
* server-side during validation, so a captured device-signals envelope cannot
|
|
1212
|
+
* be replayed. `validateCipherText` mints and attaches one automatically; call
|
|
1213
|
+
* this directly only if you manage the nonce yourself.
|
|
997
1214
|
*
|
|
998
|
-
* @
|
|
1215
|
+
* @param userId - User ID the nonce is bound to
|
|
1216
|
+
* @param eventType - Event type the nonce is bound to (login, registration, ...)
|
|
1217
|
+
* @returns The nonce and its expiry
|
|
1218
|
+
*/
|
|
1219
|
+
async fetchSignalsNonce(userId, eventType, requestOptions) {
|
|
1220
|
+
if (!userId?.trim()) {
|
|
1221
|
+
throw new ValidationError("userId is required and must be a non-empty string", ["userId"]);
|
|
1222
|
+
}
|
|
1223
|
+
const request = { user_id: userId, event_type: eventType };
|
|
1224
|
+
return this.request(
|
|
1225
|
+
"/api/v1/geo/signals-token",
|
|
1226
|
+
{
|
|
1227
|
+
method: "POST",
|
|
1228
|
+
body: JSON.stringify(request)
|
|
1229
|
+
},
|
|
1230
|
+
void 0,
|
|
1231
|
+
requestOptions
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Best-effort nonce mint for validation. Returns undefined if minting fails —
|
|
1236
|
+
* the server is the sole authority and blocks when a nonce is required, so this
|
|
1237
|
+
* never downgrades security on the client's say-so.
|
|
999
1238
|
*/
|
|
1000
|
-
async
|
|
1239
|
+
async resolveSignalsNonce(userId, eventType, requestOptions) {
|
|
1001
1240
|
try {
|
|
1002
|
-
const
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
requestOptions
|
|
1241
|
+
const { nonce } = await this.fetchSignalsNonce(userId, eventType, requestOptions);
|
|
1242
|
+
return nonce;
|
|
1243
|
+
} catch (err) {
|
|
1244
|
+
this.logger.warn(
|
|
1245
|
+
`Failed to mint device-signals nonce; proceeding without it: ${err instanceof Error ? err.message : String(err)}`
|
|
1008
1246
|
);
|
|
1009
|
-
|
|
1010
|
-
this.cachedSigningKey = response.signing_key;
|
|
1011
|
-
return response.signing_key;
|
|
1012
|
-
}
|
|
1013
|
-
} catch {
|
|
1247
|
+
return void 0;
|
|
1014
1248
|
}
|
|
1015
|
-
const config = await this.getGPSConfig(requestOptions);
|
|
1016
|
-
return config.signing_key;
|
|
1017
1249
|
}
|
|
1018
1250
|
// ============================================================================
|
|
1019
1251
|
// CipherText Validation
|
|
@@ -1066,12 +1298,14 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1066
1298
|
if (!userId?.trim()) {
|
|
1067
1299
|
throw new ValidationError("userId is required and must be a non-empty string", ["userId"]);
|
|
1068
1300
|
}
|
|
1301
|
+
const nonce = await this.resolveSignalsNonce(userId, eventType, requestOptions);
|
|
1069
1302
|
const request = {
|
|
1070
1303
|
cipher_text: cipherText,
|
|
1071
1304
|
user_id: userId,
|
|
1072
1305
|
event_type: eventType,
|
|
1073
1306
|
expected_ip: expectedIP,
|
|
1074
|
-
customer_data: customerData
|
|
1307
|
+
customer_data: customerData,
|
|
1308
|
+
nonce
|
|
1075
1309
|
};
|
|
1076
1310
|
return this.requestWithRetry(
|
|
1077
1311
|
"/api/v1/geo/validate-ciphertext",
|
|
@@ -1280,20 +1514,12 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1280
1514
|
*
|
|
1281
1515
|
* @example
|
|
1282
1516
|
* ```typescript
|
|
1283
|
-
* //
|
|
1517
|
+
* // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
|
|
1284
1518
|
* const result = await client.captureLocation(token, {
|
|
1285
1519
|
* latitude: 37.7749,
|
|
1286
1520
|
* longitude: -122.4194,
|
|
1287
1521
|
* accuracy: 10
|
|
1288
1522
|
* });
|
|
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
1523
|
* ```
|
|
1298
1524
|
*/
|
|
1299
1525
|
async captureLocation(token, capture, requestOptions) {
|
|
@@ -1306,34 +1532,22 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1306
1532
|
// CipherText Generation
|
|
1307
1533
|
// ============================================================================
|
|
1308
1534
|
/**
|
|
1309
|
-
* Generate a
|
|
1535
|
+
* Generate a device-signals envelope containing device and location data,
|
|
1536
|
+
* validated by the server.
|
|
1310
1537
|
*
|
|
1311
|
-
*
|
|
1312
|
-
* If no API key is configured, falls back to unsigned v01 format.
|
|
1313
|
-
*
|
|
1314
|
-
* @param options - Options for cipherText generation
|
|
1538
|
+
* @param options - Options for envelope generation
|
|
1315
1539
|
* @param gpsConfig - Optional GPS config (from getGPSConfig)
|
|
1316
|
-
* @returns
|
|
1540
|
+
* @returns The device-signals envelope result
|
|
1317
1541
|
*
|
|
1318
1542
|
* @example
|
|
1319
1543
|
* ```typescript
|
|
1320
1544
|
* const result = await client.generateCipherText({ reason: 'login' });
|
|
1321
|
-
* console.log(result.cipherText); //
|
|
1545
|
+
* console.log(result.cipherText); // device-signals envelope
|
|
1322
1546
|
* ```
|
|
1323
1547
|
*/
|
|
1324
1548
|
async generateCipherText(options, gpsConfig) {
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
if (!signingKey) {
|
|
1328
|
-
signingKey = await this.fetchSigningKey();
|
|
1329
|
-
}
|
|
1330
|
-
if (!resolvedGpsConfig) {
|
|
1331
|
-
resolvedGpsConfig = await this.getGPSConfig();
|
|
1332
|
-
}
|
|
1333
|
-
return generateCipherText(
|
|
1334
|
-
{ ...options, signingKey: signingKey || void 0 },
|
|
1335
|
-
resolvedGpsConfig
|
|
1336
|
-
);
|
|
1549
|
+
const resolvedGpsConfig = gpsConfig ?? await this.getGPSConfig();
|
|
1550
|
+
return generateCipherText(options, resolvedGpsConfig);
|
|
1337
1551
|
}
|
|
1338
1552
|
// ============================================================================
|
|
1339
1553
|
// Utility Methods (inherited from BaseClient: healthCheck, updateConfig, getConfig, buildQueryString)
|
|
@@ -2736,12 +2950,12 @@ var KycClient = class extends BaseClient {
|
|
|
2736
2950
|
* console.log(`KYC ID: ${result.kyc_id}`);
|
|
2737
2951
|
* ```
|
|
2738
2952
|
*/
|
|
2739
|
-
async requestKycSubmitLink(request) {
|
|
2953
|
+
async requestKycSubmitLink(request, requestOptions) {
|
|
2740
2954
|
return this.requestWithRetry("/api/v1/kyc/request", {
|
|
2741
2955
|
method: "POST",
|
|
2742
2956
|
body: JSON.stringify(request),
|
|
2743
2957
|
headers: this.getUserHeaders()
|
|
2744
|
-
});
|
|
2958
|
+
}, void 0, void 0, requestOptions);
|
|
2745
2959
|
}
|
|
2746
2960
|
/**
|
|
2747
2961
|
* Create a Event-Based Face Verification session.
|
|
@@ -2774,12 +2988,12 @@ var KycClient = class extends BaseClient {
|
|
|
2774
2988
|
*
|
|
2775
2989
|
* @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
|
|
2776
2990
|
*/
|
|
2777
|
-
async submitEventBasedFaceVerificationSession(request) {
|
|
2991
|
+
async submitEventBasedFaceVerificationSession(request, requestOptions) {
|
|
2778
2992
|
return this.request("/api/v1/kyc/face/submit", {
|
|
2779
2993
|
method: "POST",
|
|
2780
2994
|
body: JSON.stringify(request),
|
|
2781
2995
|
headers: this.getUserHeaders()
|
|
2782
|
-
});
|
|
2996
|
+
}, void 0, requestOptions);
|
|
2783
2997
|
}
|
|
2784
2998
|
/**
|
|
2785
2999
|
* Look up the current state of a Event-Based Face Verification session by its
|
|
@@ -2895,12 +3109,12 @@ var KycClient = class extends BaseClient {
|
|
|
2895
3109
|
* console.log(`Verification submitted: ${result.reference}`);
|
|
2896
3110
|
* ```
|
|
2897
3111
|
*/
|
|
2898
|
-
async submitVerification(request) {
|
|
3112
|
+
async submitVerification(request, requestOptions) {
|
|
2899
3113
|
return this.requestWithRetry("/api/v1/kyc/submit", {
|
|
2900
3114
|
method: "POST",
|
|
2901
3115
|
body: JSON.stringify(request),
|
|
2902
3116
|
headers: this.getUserHeaders()
|
|
2903
|
-
});
|
|
3117
|
+
}, void 0, void 0, requestOptions);
|
|
2904
3118
|
}
|
|
2905
3119
|
/**
|
|
2906
3120
|
* Get a KYC request by ID
|
|
@@ -3340,10 +3554,13 @@ var TaxClient = class extends BaseClient {
|
|
|
3340
3554
|
{ method: "POST" }
|
|
3341
3555
|
);
|
|
3342
3556
|
}
|
|
3343
|
-
async checkTINStatus(customerID) {
|
|
3557
|
+
async checkTINStatus(customerID, requestOptions) {
|
|
3344
3558
|
return this.requestWithRetry(
|
|
3345
3559
|
`/api/v1/tax/customer-tax-profiles/${customerID}/check-tin`,
|
|
3346
|
-
{ method: "POST" }
|
|
3560
|
+
{ method: "POST" },
|
|
3561
|
+
void 0,
|
|
3562
|
+
void 0,
|
|
3563
|
+
requestOptions
|
|
3347
3564
|
);
|
|
3348
3565
|
}
|
|
3349
3566
|
async reRequestTaxForm(customerID, input) {
|
|
@@ -3424,14 +3641,24 @@ var TaxClient = class extends BaseClient {
|
|
|
3424
3641
|
};
|
|
3425
3642
|
|
|
3426
3643
|
// src/webhooks/handler.ts
|
|
3644
|
+
function resolveEventId(event) {
|
|
3645
|
+
if (event.id) {
|
|
3646
|
+
return event.id;
|
|
3647
|
+
}
|
|
3648
|
+
if (event.event_type === "notification.created") {
|
|
3649
|
+
return event.notification?.id;
|
|
3650
|
+
}
|
|
3651
|
+
return void 0;
|
|
3652
|
+
}
|
|
3427
3653
|
var WebhookHandler = class {
|
|
3428
3654
|
constructor(config) {
|
|
3429
3655
|
this.handlers = /* @__PURE__ */ new Map();
|
|
3430
3656
|
this.anyHandlers = [];
|
|
3431
|
-
this.seenEventIds = /* @__PURE__ */ new Map();
|
|
3432
3657
|
this.secret = config.secret;
|
|
3433
3658
|
this.tolerance = config.tolerance ?? 3e5;
|
|
3434
3659
|
this.replayProtection = config.replayProtection ?? true;
|
|
3660
|
+
this.allowLegacySignature = config.allowLegacySignature ?? true;
|
|
3661
|
+
this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
|
|
3435
3662
|
}
|
|
3436
3663
|
/**
|
|
3437
3664
|
* Register a handler for a specific event type.
|
|
@@ -3453,7 +3680,10 @@ var WebhookHandler = class {
|
|
|
3453
3680
|
* Verify signature and parse the webhook body.
|
|
3454
3681
|
*/
|
|
3455
3682
|
async verifyAndParse(body, signature) {
|
|
3456
|
-
const isValid = await verifyWebhookSignature(body, signature, this.secret
|
|
3683
|
+
const isValid = await verifyWebhookSignature(body, signature, this.secret, {
|
|
3684
|
+
tolerance: this.tolerance,
|
|
3685
|
+
allowLegacy: this.allowLegacySignature
|
|
3686
|
+
});
|
|
3457
3687
|
if (!isValid) {
|
|
3458
3688
|
throw new ValidationError("Invalid webhook signature", ["signature"]);
|
|
3459
3689
|
}
|
|
@@ -3469,18 +3699,21 @@ var WebhookHandler = class {
|
|
|
3469
3699
|
/**
|
|
3470
3700
|
* Parse an event without signature verification (for testing).
|
|
3471
3701
|
*/
|
|
3472
|
-
parseEvent(body) {
|
|
3702
|
+
async parseEvent(body) {
|
|
3473
3703
|
return this.parseAndValidate(body);
|
|
3474
3704
|
}
|
|
3475
|
-
parseAndValidate(body) {
|
|
3705
|
+
async parseAndValidate(body) {
|
|
3476
3706
|
const event = JSON.parse(body);
|
|
3477
|
-
|
|
3478
|
-
|
|
3707
|
+
const eventId = resolveEventId(event);
|
|
3708
|
+
if (!event.event_type || !eventId || !event.timestamp) {
|
|
3709
|
+
throw new ValidationError(
|
|
3710
|
+
"Invalid webhook event: missing required fields (event_type, id, timestamp)",
|
|
3711
|
+
["event_type", "id", "timestamp"]
|
|
3712
|
+
);
|
|
3479
3713
|
}
|
|
3480
3714
|
if (this.tolerance > 0) {
|
|
3481
3715
|
const eventTime = new Date(event.timestamp).getTime();
|
|
3482
|
-
|
|
3483
|
-
if (Math.abs(now - eventTime) > this.tolerance) {
|
|
3716
|
+
if (Math.abs(Date.now() - eventTime) > this.tolerance) {
|
|
3484
3717
|
throw new ValidationError(
|
|
3485
3718
|
`Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
|
|
3486
3719
|
["timestamp"]
|
|
@@ -3488,26 +3721,18 @@ var WebhookHandler = class {
|
|
|
3488
3721
|
}
|
|
3489
3722
|
}
|
|
3490
3723
|
if (this.replayProtection) {
|
|
3491
|
-
if (this.
|
|
3724
|
+
if (await this.dedupStore.seen(eventId)) {
|
|
3492
3725
|
throw new ValidationError(
|
|
3493
|
-
`Duplicate webhook event: ${
|
|
3726
|
+
`Duplicate webhook event: ${eventId} has already been processed`,
|
|
3494
3727
|
["id"]
|
|
3495
3728
|
);
|
|
3496
3729
|
}
|
|
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
|
-
}
|
|
3730
|
+
await this.dedupStore.mark(eventId, this.tolerance);
|
|
3506
3731
|
}
|
|
3507
3732
|
return event;
|
|
3508
3733
|
}
|
|
3509
3734
|
async dispatch(event) {
|
|
3510
|
-
const typeHandlers = this.handlers.get(event.
|
|
3735
|
+
const typeHandlers = this.handlers.get(event.event_type) || [];
|
|
3511
3736
|
const allHandlers = [...typeHandlers, ...this.anyHandlers];
|
|
3512
3737
|
for (const handler of allHandlers) {
|
|
3513
3738
|
await handler(event);
|
|
@@ -3588,6 +3813,6 @@ function buildHandler(options) {
|
|
|
3588
3813
|
return handler;
|
|
3589
3814
|
}
|
|
3590
3815
|
|
|
3591
|
-
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 };
|
|
3816
|
+
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, collectDeviceSignals, createConsoleLogger, createNextWebhookHandler, createWebhookMiddleware, decodeCipherText, generateCipherText, gpsIntegrityInputFromPosition, isCipherTextExpired, noopLogger, paginate, probeGeolocationPermission, sdkReasons, verifyWebhookSignature };
|
|
3592
3817
|
//# sourceMappingURL=index.mjs.map
|
|
3593
3818
|
//# sourceMappingURL=index.mjs.map
|