vesant-sdk 1.7.0 → 1.7.1-dev.ec505dc
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-BY4Quazn.d.ts} +2 -2
- package/dist/{client-DF7hlMEz.d.ts → client-C-u9lE8N.d.ts} +34 -46
- package/dist/{client-B0qhE2kr.d.mts → client-CX1jcLNZ.d.mts} +2 -2
- package/dist/{client-DrjgZoH_.d.mts → client-Yxat9wAO.d.mts} +34 -46
- 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 +143 -98
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +143 -98
- 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 +148 -98
- package/dist/geolocation/index.js.map +1 -1
- package/dist/geolocation/index.mjs +145 -99
- 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 +316 -127
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +308 -128
- 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,7 @@ 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;
|
|
991
|
-
}
|
|
992
|
-
/**
|
|
993
|
-
* Fetch the signing key from the dedicated authenticated endpoint.
|
|
994
|
-
*
|
|
995
|
-
* Falls back to getGPSConfig() for backward compatibility with older servers
|
|
996
|
-
* that don't expose `/api/v1/geo/signing-key`.
|
|
997
|
-
*
|
|
998
|
-
* @returns The signing key string, or undefined if unavailable
|
|
999
|
-
*/
|
|
1000
|
-
async fetchSigningKey(requestOptions) {
|
|
1001
|
-
try {
|
|
1002
|
-
const response = await this.requestWithRetry(
|
|
1003
|
-
"/api/v1/geo/signing-key",
|
|
1004
|
-
void 0,
|
|
1005
|
-
void 0,
|
|
1006
|
-
void 0,
|
|
1007
|
-
requestOptions
|
|
1008
|
-
);
|
|
1009
|
-
if (response.signing_key) {
|
|
1010
|
-
this.cachedSigningKey = response.signing_key;
|
|
1011
|
-
return response.signing_key;
|
|
1012
|
-
}
|
|
1013
|
-
} catch {
|
|
1014
|
-
}
|
|
1015
|
-
const config = await this.getGPSConfig(requestOptions);
|
|
1016
|
-
return config.signing_key;
|
|
1205
|
+
return this.requestWithRetry("/api/v1/geo/config", void 0, void 0, void 0, requestOptions);
|
|
1017
1206
|
}
|
|
1018
1207
|
// ============================================================================
|
|
1019
1208
|
// CipherText Validation
|
|
@@ -1280,20 +1469,12 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1280
1469
|
*
|
|
1281
1470
|
* @example
|
|
1282
1471
|
* ```typescript
|
|
1283
|
-
* //
|
|
1472
|
+
* // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
|
|
1284
1473
|
* const result = await client.captureLocation(token, {
|
|
1285
1474
|
* latitude: 37.7749,
|
|
1286
1475
|
* longitude: -122.4194,
|
|
1287
1476
|
* accuracy: 10
|
|
1288
1477
|
* });
|
|
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
1478
|
* ```
|
|
1298
1479
|
*/
|
|
1299
1480
|
async captureLocation(token, capture, requestOptions) {
|
|
@@ -1306,34 +1487,22 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1306
1487
|
// CipherText Generation
|
|
1307
1488
|
// ============================================================================
|
|
1308
1489
|
/**
|
|
1309
|
-
* Generate a
|
|
1490
|
+
* Generate a device-signals envelope containing device and location data,
|
|
1491
|
+
* validated by the server.
|
|
1310
1492
|
*
|
|
1311
|
-
*
|
|
1312
|
-
* If no API key is configured, falls back to unsigned v01 format.
|
|
1313
|
-
*
|
|
1314
|
-
* @param options - Options for cipherText generation
|
|
1493
|
+
* @param options - Options for envelope generation
|
|
1315
1494
|
* @param gpsConfig - Optional GPS config (from getGPSConfig)
|
|
1316
|
-
* @returns
|
|
1495
|
+
* @returns The device-signals envelope result
|
|
1317
1496
|
*
|
|
1318
1497
|
* @example
|
|
1319
1498
|
* ```typescript
|
|
1320
1499
|
* const result = await client.generateCipherText({ reason: 'login' });
|
|
1321
|
-
* console.log(result.cipherText); //
|
|
1500
|
+
* console.log(result.cipherText); // device-signals envelope
|
|
1322
1501
|
* ```
|
|
1323
1502
|
*/
|
|
1324
1503
|
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
|
-
);
|
|
1504
|
+
const resolvedGpsConfig = gpsConfig ?? await this.getGPSConfig();
|
|
1505
|
+
return generateCipherText(options, resolvedGpsConfig);
|
|
1337
1506
|
}
|
|
1338
1507
|
// ============================================================================
|
|
1339
1508
|
// Utility Methods (inherited from BaseClient: healthCheck, updateConfig, getConfig, buildQueryString)
|
|
@@ -2736,12 +2905,12 @@ var KycClient = class extends BaseClient {
|
|
|
2736
2905
|
* console.log(`KYC ID: ${result.kyc_id}`);
|
|
2737
2906
|
* ```
|
|
2738
2907
|
*/
|
|
2739
|
-
async requestKycSubmitLink(request) {
|
|
2908
|
+
async requestKycSubmitLink(request, requestOptions) {
|
|
2740
2909
|
return this.requestWithRetry("/api/v1/kyc/request", {
|
|
2741
2910
|
method: "POST",
|
|
2742
2911
|
body: JSON.stringify(request),
|
|
2743
2912
|
headers: this.getUserHeaders()
|
|
2744
|
-
});
|
|
2913
|
+
}, void 0, void 0, requestOptions);
|
|
2745
2914
|
}
|
|
2746
2915
|
/**
|
|
2747
2916
|
* Create a Event-Based Face Verification session.
|
|
@@ -2774,12 +2943,12 @@ var KycClient = class extends BaseClient {
|
|
|
2774
2943
|
*
|
|
2775
2944
|
* @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
|
|
2776
2945
|
*/
|
|
2777
|
-
async submitEventBasedFaceVerificationSession(request) {
|
|
2946
|
+
async submitEventBasedFaceVerificationSession(request, requestOptions) {
|
|
2778
2947
|
return this.request("/api/v1/kyc/face/submit", {
|
|
2779
2948
|
method: "POST",
|
|
2780
2949
|
body: JSON.stringify(request),
|
|
2781
2950
|
headers: this.getUserHeaders()
|
|
2782
|
-
});
|
|
2951
|
+
}, void 0, requestOptions);
|
|
2783
2952
|
}
|
|
2784
2953
|
/**
|
|
2785
2954
|
* Look up the current state of a Event-Based Face Verification session by its
|
|
@@ -2895,12 +3064,12 @@ var KycClient = class extends BaseClient {
|
|
|
2895
3064
|
* console.log(`Verification submitted: ${result.reference}`);
|
|
2896
3065
|
* ```
|
|
2897
3066
|
*/
|
|
2898
|
-
async submitVerification(request) {
|
|
3067
|
+
async submitVerification(request, requestOptions) {
|
|
2899
3068
|
return this.requestWithRetry("/api/v1/kyc/submit", {
|
|
2900
3069
|
method: "POST",
|
|
2901
3070
|
body: JSON.stringify(request),
|
|
2902
3071
|
headers: this.getUserHeaders()
|
|
2903
|
-
});
|
|
3072
|
+
}, void 0, void 0, requestOptions);
|
|
2904
3073
|
}
|
|
2905
3074
|
/**
|
|
2906
3075
|
* Get a KYC request by ID
|
|
@@ -3340,10 +3509,13 @@ var TaxClient = class extends BaseClient {
|
|
|
3340
3509
|
{ method: "POST" }
|
|
3341
3510
|
);
|
|
3342
3511
|
}
|
|
3343
|
-
async checkTINStatus(customerID) {
|
|
3512
|
+
async checkTINStatus(customerID, requestOptions) {
|
|
3344
3513
|
return this.requestWithRetry(
|
|
3345
3514
|
`/api/v1/tax/customer-tax-profiles/${customerID}/check-tin`,
|
|
3346
|
-
{ method: "POST" }
|
|
3515
|
+
{ method: "POST" },
|
|
3516
|
+
void 0,
|
|
3517
|
+
void 0,
|
|
3518
|
+
requestOptions
|
|
3347
3519
|
);
|
|
3348
3520
|
}
|
|
3349
3521
|
async reRequestTaxForm(customerID, input) {
|
|
@@ -3424,14 +3596,24 @@ var TaxClient = class extends BaseClient {
|
|
|
3424
3596
|
};
|
|
3425
3597
|
|
|
3426
3598
|
// src/webhooks/handler.ts
|
|
3599
|
+
function resolveEventId(event) {
|
|
3600
|
+
if (event.id) {
|
|
3601
|
+
return event.id;
|
|
3602
|
+
}
|
|
3603
|
+
if (event.event_type === "notification.created") {
|
|
3604
|
+
return event.notification?.id;
|
|
3605
|
+
}
|
|
3606
|
+
return void 0;
|
|
3607
|
+
}
|
|
3427
3608
|
var WebhookHandler = class {
|
|
3428
3609
|
constructor(config) {
|
|
3429
3610
|
this.handlers = /* @__PURE__ */ new Map();
|
|
3430
3611
|
this.anyHandlers = [];
|
|
3431
|
-
this.seenEventIds = /* @__PURE__ */ new Map();
|
|
3432
3612
|
this.secret = config.secret;
|
|
3433
3613
|
this.tolerance = config.tolerance ?? 3e5;
|
|
3434
3614
|
this.replayProtection = config.replayProtection ?? true;
|
|
3615
|
+
this.allowLegacySignature = config.allowLegacySignature ?? true;
|
|
3616
|
+
this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
|
|
3435
3617
|
}
|
|
3436
3618
|
/**
|
|
3437
3619
|
* Register a handler for a specific event type.
|
|
@@ -3453,7 +3635,10 @@ var WebhookHandler = class {
|
|
|
3453
3635
|
* Verify signature and parse the webhook body.
|
|
3454
3636
|
*/
|
|
3455
3637
|
async verifyAndParse(body, signature) {
|
|
3456
|
-
const isValid = await verifyWebhookSignature(body, signature, this.secret
|
|
3638
|
+
const isValid = await verifyWebhookSignature(body, signature, this.secret, {
|
|
3639
|
+
tolerance: this.tolerance,
|
|
3640
|
+
allowLegacy: this.allowLegacySignature
|
|
3641
|
+
});
|
|
3457
3642
|
if (!isValid) {
|
|
3458
3643
|
throw new ValidationError("Invalid webhook signature", ["signature"]);
|
|
3459
3644
|
}
|
|
@@ -3469,18 +3654,21 @@ var WebhookHandler = class {
|
|
|
3469
3654
|
/**
|
|
3470
3655
|
* Parse an event without signature verification (for testing).
|
|
3471
3656
|
*/
|
|
3472
|
-
parseEvent(body) {
|
|
3657
|
+
async parseEvent(body) {
|
|
3473
3658
|
return this.parseAndValidate(body);
|
|
3474
3659
|
}
|
|
3475
|
-
parseAndValidate(body) {
|
|
3660
|
+
async parseAndValidate(body) {
|
|
3476
3661
|
const event = JSON.parse(body);
|
|
3477
|
-
|
|
3478
|
-
|
|
3662
|
+
const eventId = resolveEventId(event);
|
|
3663
|
+
if (!event.event_type || !eventId || !event.timestamp) {
|
|
3664
|
+
throw new ValidationError(
|
|
3665
|
+
"Invalid webhook event: missing required fields (event_type, id, timestamp)",
|
|
3666
|
+
["event_type", "id", "timestamp"]
|
|
3667
|
+
);
|
|
3479
3668
|
}
|
|
3480
3669
|
if (this.tolerance > 0) {
|
|
3481
3670
|
const eventTime = new Date(event.timestamp).getTime();
|
|
3482
|
-
|
|
3483
|
-
if (Math.abs(now - eventTime) > this.tolerance) {
|
|
3671
|
+
if (Math.abs(Date.now() - eventTime) > this.tolerance) {
|
|
3484
3672
|
throw new ValidationError(
|
|
3485
3673
|
`Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
|
|
3486
3674
|
["timestamp"]
|
|
@@ -3488,26 +3676,18 @@ var WebhookHandler = class {
|
|
|
3488
3676
|
}
|
|
3489
3677
|
}
|
|
3490
3678
|
if (this.replayProtection) {
|
|
3491
|
-
if (this.
|
|
3679
|
+
if (await this.dedupStore.seen(eventId)) {
|
|
3492
3680
|
throw new ValidationError(
|
|
3493
|
-
`Duplicate webhook event: ${
|
|
3681
|
+
`Duplicate webhook event: ${eventId} has already been processed`,
|
|
3494
3682
|
["id"]
|
|
3495
3683
|
);
|
|
3496
3684
|
}
|
|
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
|
-
}
|
|
3685
|
+
await this.dedupStore.mark(eventId, this.tolerance);
|
|
3506
3686
|
}
|
|
3507
3687
|
return event;
|
|
3508
3688
|
}
|
|
3509
3689
|
async dispatch(event) {
|
|
3510
|
-
const typeHandlers = this.handlers.get(event.
|
|
3690
|
+
const typeHandlers = this.handlers.get(event.event_type) || [];
|
|
3511
3691
|
const allHandlers = [...typeHandlers, ...this.anyHandlers];
|
|
3512
3692
|
for (const handler of allHandlers) {
|
|
3513
3693
|
await handler(event);
|
|
@@ -3588,6 +3768,6 @@ function buildHandler(options) {
|
|
|
3588
3768
|
return handler;
|
|
3589
3769
|
}
|
|
3590
3770
|
|
|
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 };
|
|
3771
|
+
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
3772
|
//# sourceMappingURL=index.mjs.map
|
|
3593
3773
|
//# sourceMappingURL=index.mjs.map
|