vesant-sdk 1.7.0 → 1.7.1-dev.0520b46
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-B0qhE2kr.d.mts → client-5q4whhGu.d.mts} +2 -2
- package/dist/{client-DF7hlMEz.d.ts → client-BaUekQT8.d.ts} +96 -52
- package/dist/{client-DtH2RLuy.d.ts → client-DUHC-68_.d.ts} +2 -2
- package/dist/{client-DrjgZoH_.d.mts → client-XDEhh9VN.d.mts} +96 -52
- 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 +218 -90
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +218 -90
- 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 +223 -90
- package/dist/geolocation/index.js.map +1 -1
- package/dist/geolocation/index.mjs +220 -91
- 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 +27 -10
- package/dist/index.d.ts +27 -10
- package/dist/index.js +418 -126
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +409 -127
- 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 +102 -21
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +102 -22
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +154 -30
- package/dist/kyc/index.d.ts +154 -30
- package/dist/kyc/index.js +102 -21
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +102 -22
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +14 -3
- package/dist/react.d.ts +14 -3
- package/dist/react.js +317 -68
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +317 -68
- 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
|
@@ -1,5 +1,18 @@
|
|
|
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
|
+
|
|
3
16
|
// src/core/errors.ts
|
|
4
17
|
var VesantError = class _VesantError extends Error {
|
|
5
18
|
constructor(message, code, statusCode, details) {
|
|
@@ -216,7 +229,7 @@ function createConsoleLogger() {
|
|
|
216
229
|
}
|
|
217
230
|
|
|
218
231
|
// src/core/version.ts
|
|
219
|
-
var SDK_VERSION = "1.7.
|
|
232
|
+
var SDK_VERSION = "1.7.1";
|
|
220
233
|
|
|
221
234
|
// src/shared/browser-utils.ts
|
|
222
235
|
function generateUUID() {
|
|
@@ -298,6 +311,12 @@ var BaseClient = class {
|
|
|
298
311
|
if (!config.tenantId?.trim()) {
|
|
299
312
|
throw new ValidationError("tenantId is required and must be a non-empty string", ["tenantId"]);
|
|
300
313
|
}
|
|
314
|
+
if (typeof fetch === "undefined") {
|
|
315
|
+
throw new VesantError(
|
|
316
|
+
"The Vesant SDK requires a global fetch (Node.js >= 18). Upgrade Node or provide a fetch polyfill.",
|
|
317
|
+
"RUNTIME_UNSUPPORTED"
|
|
318
|
+
);
|
|
319
|
+
}
|
|
301
320
|
this.interceptors = config.interceptors || [];
|
|
302
321
|
this.logger = config.logger || createConsoleLogger();
|
|
303
322
|
let environment = config.environment;
|
|
@@ -316,11 +335,12 @@ var BaseClient = class {
|
|
|
316
335
|
environment,
|
|
317
336
|
headers: config.headers || {},
|
|
318
337
|
timeout: config.timeout || 1e4,
|
|
319
|
-
retries: config.retries
|
|
338
|
+
retries: config.retries ?? 3,
|
|
320
339
|
debug: config.debug || false,
|
|
321
340
|
interceptors: this.interceptors,
|
|
322
341
|
logger: this.logger
|
|
323
342
|
};
|
|
343
|
+
this.authProvider = config.authProvider ?? (apiKey ? new StaticApiKeyProvider(apiKey) : void 0);
|
|
324
344
|
if (config.circuitBreaker) {
|
|
325
345
|
this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
|
|
326
346
|
}
|
|
@@ -328,6 +348,19 @@ var BaseClient = class {
|
|
|
328
348
|
this.rateLimitTracker = new RateLimitTracker();
|
|
329
349
|
}
|
|
330
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Resolve the Idempotency-Key for a request. Mutating methods (POST/PUT/PATCH)
|
|
353
|
+
* get the caller-supplied key or a freshly minted UUID; non-mutating methods get
|
|
354
|
+
* none. requestWithRetry resolves this once and injects it so every retry reuses
|
|
355
|
+
* one key (letting the server dedup replays); a direct request() resolves per call.
|
|
356
|
+
*/
|
|
357
|
+
resolveIdempotencyKey(method, requestOptions) {
|
|
358
|
+
const isMutating = ["POST", "PUT", "PATCH"].includes((method || "GET").toUpperCase());
|
|
359
|
+
if (!isMutating) {
|
|
360
|
+
return void 0;
|
|
361
|
+
}
|
|
362
|
+
return requestOptions?.idempotencyKey ?? generateUUID();
|
|
363
|
+
}
|
|
331
364
|
/**
|
|
332
365
|
* Make an HTTP request with timeout and error handling
|
|
333
366
|
*/
|
|
@@ -353,15 +386,20 @@ var BaseClient = class {
|
|
|
353
386
|
...this.config.headers,
|
|
354
387
|
...options.headers || {}
|
|
355
388
|
};
|
|
356
|
-
|
|
357
|
-
|
|
389
|
+
const credential = await this.authProvider?.getCredential({
|
|
390
|
+
tenantId: this.config.tenantId,
|
|
391
|
+
requestId
|
|
392
|
+
});
|
|
393
|
+
if (credential) {
|
|
394
|
+
headers["Authorization"] = `${credential.scheme ?? "Bearer"} ${credential.token}`;
|
|
358
395
|
}
|
|
359
396
|
if (this.config.environment === "sandbox") {
|
|
360
397
|
headers["X-Sandbox"] = "true";
|
|
361
398
|
}
|
|
362
399
|
const method = (options.method || "GET").toUpperCase();
|
|
363
|
-
|
|
364
|
-
|
|
400
|
+
const idempotencyKey = this.resolveIdempotencyKey(method, requestOptions);
|
|
401
|
+
if (idempotencyKey) {
|
|
402
|
+
headers["Idempotency-Key"] = idempotencyKey;
|
|
365
403
|
}
|
|
366
404
|
const controller = new AbortController();
|
|
367
405
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
@@ -382,10 +420,28 @@ var BaseClient = class {
|
|
|
382
420
|
if (this.config.debug) {
|
|
383
421
|
this.logger.debug(`${finalOptions.method || "GET"} ${endpoint}`);
|
|
384
422
|
}
|
|
385
|
-
|
|
423
|
+
let response = await fetch(url, {
|
|
386
424
|
...finalOptions,
|
|
387
425
|
signal: controller.signal
|
|
388
426
|
});
|
|
427
|
+
if (response.status === 401 && this.authProvider?.canRefresh?.()) {
|
|
428
|
+
const refreshed = await this.authProvider.getCredential({
|
|
429
|
+
tenantId: this.config.tenantId,
|
|
430
|
+
requestId,
|
|
431
|
+
forceRefresh: true
|
|
432
|
+
});
|
|
433
|
+
if (refreshed) {
|
|
434
|
+
this.config.onCredentialRotated?.({ reason: "unauthorized" });
|
|
435
|
+
finalOptions = {
|
|
436
|
+
...finalOptions,
|
|
437
|
+
headers: {
|
|
438
|
+
...finalOptions.headers,
|
|
439
|
+
Authorization: `${refreshed.scheme ?? "Bearer"} ${refreshed.token}`
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
response = await fetch(url, { ...finalOptions, signal: controller.signal });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
389
445
|
clearTimeout(timeoutId);
|
|
390
446
|
if (this.rateLimitTracker) {
|
|
391
447
|
this.rateLimitTracker.updateFromHeaders(response.headers);
|
|
@@ -474,9 +530,11 @@ var BaseClient = class {
|
|
|
474
530
|
*/
|
|
475
531
|
async requestWithRetry(endpoint, options = {}, serviceURL, retries = this.config.retries, requestOptions) {
|
|
476
532
|
let lastError;
|
|
533
|
+
const idempotencyKey = this.resolveIdempotencyKey(options.method || "GET", requestOptions);
|
|
534
|
+
const attemptOptions = idempotencyKey ? { ...requestOptions, idempotencyKey } : requestOptions;
|
|
477
535
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
478
536
|
try {
|
|
479
|
-
return await this.request(endpoint, options, serviceURL,
|
|
537
|
+
return await this.request(endpoint, options, serviceURL, attemptOptions);
|
|
480
538
|
} catch (error) {
|
|
481
539
|
lastError = error instanceof Error ? error : new Error("Unknown error");
|
|
482
540
|
if (requestOptions?.signal?.aborted) {
|
|
@@ -573,6 +631,9 @@ var BaseClient = class {
|
|
|
573
631
|
if (config.interceptors) {
|
|
574
632
|
this.interceptors = config.interceptors;
|
|
575
633
|
}
|
|
634
|
+
if (config.authProvider !== void 0 || config.apiKey !== void 0) {
|
|
635
|
+
this.authProvider = this.config.authProvider ?? (this.config.apiKey ? new StaticApiKeyProvider(this.config.apiKey) : void 0);
|
|
636
|
+
}
|
|
576
637
|
}
|
|
577
638
|
/**
|
|
578
639
|
* Get current configuration (readonly)
|
|
@@ -600,27 +661,63 @@ var BaseClient = class {
|
|
|
600
661
|
}
|
|
601
662
|
};
|
|
602
663
|
|
|
603
|
-
// src/geolocation/
|
|
604
|
-
var
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
);
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
664
|
+
// src/geolocation/integrity.ts
|
|
665
|
+
var isNil = (v) => v === null || v === void 0;
|
|
666
|
+
function assessGpsIntegrity(input, opts = {}) {
|
|
667
|
+
const flags = [];
|
|
668
|
+
const now = opts.now ?? (() => Date.now());
|
|
669
|
+
const maxAgeMs = opts.maxAgeMs ?? 6e4;
|
|
670
|
+
const minAccuracy = opts.minAccuracyMeters ?? 1;
|
|
671
|
+
const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
|
|
672
|
+
if (isNil(accuracy)) {
|
|
673
|
+
flags.push("accuracy_missing");
|
|
674
|
+
} else if (accuracy === 0) {
|
|
675
|
+
flags.push("accuracy_zero");
|
|
676
|
+
} else if (accuracy < minAccuracy) {
|
|
677
|
+
flags.push("accuracy_implausibly_small");
|
|
678
|
+
}
|
|
679
|
+
const hasMotion = !isNil(heading) || !isNil(speed);
|
|
680
|
+
if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
|
|
681
|
+
flags.push("motion_without_accuracy");
|
|
682
|
+
}
|
|
683
|
+
if (!isNil(altitude) && isNil(altitudeAccuracy)) {
|
|
684
|
+
flags.push("altitude_without_accuracy");
|
|
685
|
+
}
|
|
686
|
+
if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
|
|
687
|
+
flags.push("stale_timestamp");
|
|
620
688
|
}
|
|
621
|
-
|
|
622
|
-
|
|
689
|
+
if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
|
|
690
|
+
flags.push(`permission_${opts.permissionState}`);
|
|
691
|
+
}
|
|
692
|
+
return { suspicious: flags.length > 0, flags };
|
|
623
693
|
}
|
|
694
|
+
function gpsIntegrityInputFromPosition(position) {
|
|
695
|
+
const c = position.coords;
|
|
696
|
+
return {
|
|
697
|
+
latitude: c.latitude,
|
|
698
|
+
longitude: c.longitude,
|
|
699
|
+
accuracy: c.accuracy,
|
|
700
|
+
altitude: c.altitude,
|
|
701
|
+
altitudeAccuracy: c.altitudeAccuracy,
|
|
702
|
+
heading: c.heading,
|
|
703
|
+
speed: c.speed,
|
|
704
|
+
timestamp: position.timestamp
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
async function probeGeolocationPermission() {
|
|
708
|
+
try {
|
|
709
|
+
if (typeof navigator === "undefined" || !navigator.permissions?.query) {
|
|
710
|
+
return "unavailable";
|
|
711
|
+
}
|
|
712
|
+
const status = await navigator.permissions.query({ name: "geolocation" });
|
|
713
|
+
return status.state;
|
|
714
|
+
} catch {
|
|
715
|
+
return "unavailable";
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// src/geolocation/ciphertext.ts
|
|
720
|
+
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
624
721
|
function getWebGLInfo() {
|
|
625
722
|
if (typeof document === "undefined") return null;
|
|
626
723
|
try {
|
|
@@ -652,6 +749,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
652
749
|
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
653
750
|
return null;
|
|
654
751
|
}
|
|
752
|
+
const permissionState = await probeGeolocationPermission();
|
|
655
753
|
return new Promise((resolve) => {
|
|
656
754
|
navigator.geolocation.getCurrentPosition(
|
|
657
755
|
(position) => {
|
|
@@ -668,6 +766,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
668
766
|
resolve(null);
|
|
669
767
|
return;
|
|
670
768
|
}
|
|
769
|
+
const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
|
|
671
770
|
resolve({
|
|
672
771
|
latitude,
|
|
673
772
|
longitude,
|
|
@@ -676,7 +775,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
676
775
|
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
677
776
|
heading: position.coords.heading ?? void 0,
|
|
678
777
|
speed: position.coords.speed ?? void 0,
|
|
679
|
-
timestamp: position.timestamp
|
|
778
|
+
timestamp: position.timestamp,
|
|
779
|
+
integrity_flags: flags.length ? flags : void 0
|
|
680
780
|
});
|
|
681
781
|
},
|
|
682
782
|
() => {
|
|
@@ -743,6 +843,9 @@ async function generateCipherText(options, config) {
|
|
|
743
843
|
);
|
|
744
844
|
if (location) {
|
|
745
845
|
locationData = location;
|
|
846
|
+
if (location.integrity_flags?.length) {
|
|
847
|
+
warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
|
|
848
|
+
}
|
|
746
849
|
} else if (gpsRequiredByConfig) {
|
|
747
850
|
throw new VesantError(
|
|
748
851
|
`GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
|
|
@@ -772,15 +875,7 @@ async function generateCipherText(options, config) {
|
|
|
772
875
|
};
|
|
773
876
|
const encoded = encodePayload(payload);
|
|
774
877
|
const timestamp = now.getTime().toString(36);
|
|
775
|
-
|
|
776
|
-
const hmacKey = options.signingKey || options.apiKey;
|
|
777
|
-
if (hmacKey) {
|
|
778
|
-
const message = `02.${timestamp}.${encoded}`;
|
|
779
|
-
const hmac = await computeHMAC(hmacKey, message);
|
|
780
|
-
cipherText = `${message}.${hmac}`;
|
|
781
|
-
} else {
|
|
782
|
-
cipherText = `01.${timestamp}.${encoded}`;
|
|
783
|
-
}
|
|
878
|
+
const cipherText = `01.${timestamp}.${encoded}`;
|
|
784
879
|
return {
|
|
785
880
|
cipherText,
|
|
786
881
|
locationCaptured: !!locationData,
|
|
@@ -789,6 +884,7 @@ async function generateCipherText(options, config) {
|
|
|
789
884
|
expiresAt: expiry.toISOString()
|
|
790
885
|
};
|
|
791
886
|
}
|
|
887
|
+
var collectDeviceSignals = generateCipherText;
|
|
792
888
|
function decodeCipherText(cipherText) {
|
|
793
889
|
try {
|
|
794
890
|
const parts = cipherText.split(".");
|
|
@@ -816,6 +912,7 @@ function isCipherTextExpired(cipherText) {
|
|
|
816
912
|
}
|
|
817
913
|
|
|
818
914
|
// src/geolocation/client.ts
|
|
915
|
+
var DEFAULT_GPS_CONFIG_TTL_MS = 5 * 60 * 1e3;
|
|
819
916
|
var GeolocationClient = class extends BaseClient {
|
|
820
917
|
constructor(config) {
|
|
821
918
|
const baseConfig = {
|
|
@@ -831,6 +928,8 @@ var GeolocationClient = class extends BaseClient {
|
|
|
831
928
|
environment: config.environment
|
|
832
929
|
};
|
|
833
930
|
super(baseConfig);
|
|
931
|
+
this.gpsConfigCache = null;
|
|
932
|
+
this.gpsConfigTtlMs = config.gpsConfigTtlMs ?? DEFAULT_GPS_CONFIG_TTL_MS;
|
|
834
933
|
}
|
|
835
934
|
// ============================================================================
|
|
836
935
|
// IP Verification & Compliance
|
|
@@ -862,8 +961,8 @@ var GeolocationClient = class extends BaseClient {
|
|
|
862
961
|
* ```
|
|
863
962
|
*/
|
|
864
963
|
async verifyIP(request, requestOptions) {
|
|
865
|
-
if (!request.ip_address
|
|
866
|
-
throw new ValidationError("ip_address
|
|
964
|
+
if (request.ip_address !== void 0 && !request.ip_address.trim()) {
|
|
965
|
+
throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
|
|
867
966
|
}
|
|
868
967
|
return this.requestWithRetry("/api/v1/geo/verify", {
|
|
869
968
|
method: "POST",
|
|
@@ -916,37 +1015,85 @@ var GeolocationClient = class extends BaseClient {
|
|
|
916
1015
|
* ```
|
|
917
1016
|
*/
|
|
918
1017
|
async getGPSConfig(requestOptions) {
|
|
919
|
-
const
|
|
920
|
-
if (
|
|
921
|
-
|
|
1018
|
+
const cached = this.gpsConfigCache;
|
|
1019
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
1020
|
+
return cached.value;
|
|
922
1021
|
}
|
|
923
|
-
return
|
|
1022
|
+
return this.refreshGPSConfig(requestOptions);
|
|
924
1023
|
}
|
|
925
1024
|
/**
|
|
926
|
-
*
|
|
1025
|
+
* Re-read the tenant's GPS requirement configuration, ignoring any copy held
|
|
1026
|
+
* from an earlier read.
|
|
927
1027
|
*
|
|
928
|
-
*
|
|
929
|
-
*
|
|
1028
|
+
* These requirements are edited in the compliance console while your
|
|
1029
|
+
* application is running. `getGPSConfig` reuses a fetched copy for a short
|
|
1030
|
+
* period so a long-lived session does not call the API on every event; call
|
|
1031
|
+
* this when you need a change to take effect immediately — for example right
|
|
1032
|
+
* after an administrator saves new settings.
|
|
930
1033
|
*
|
|
931
|
-
* @returns The
|
|
1034
|
+
* @returns The current GPS requirement config per event type
|
|
1035
|
+
*
|
|
1036
|
+
* @example
|
|
1037
|
+
* ```typescript
|
|
1038
|
+
* const config = await client.refreshGPSConfig();
|
|
1039
|
+
* if (config.require_gps.login) {
|
|
1040
|
+
* // GPS is now required for login
|
|
1041
|
+
* }
|
|
1042
|
+
* ```
|
|
1043
|
+
*/
|
|
1044
|
+
async refreshGPSConfig(requestOptions) {
|
|
1045
|
+
const value = await this.requestWithRetry(
|
|
1046
|
+
"/api/v1/geo/config",
|
|
1047
|
+
void 0,
|
|
1048
|
+
void 0,
|
|
1049
|
+
void 0,
|
|
1050
|
+
requestOptions
|
|
1051
|
+
);
|
|
1052
|
+
this.gpsConfigCache = { value, expiresAt: Date.now() + this.gpsConfigTtlMs };
|
|
1053
|
+
return value;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Mint a short-lived, single-use device-signals nonce.
|
|
1057
|
+
*
|
|
1058
|
+
* The nonce is bound to the tenant, user, and event type and is consumed
|
|
1059
|
+
* server-side during validation, so a captured device-signals envelope cannot
|
|
1060
|
+
* be replayed. `validateCipherText` mints and attaches one automatically; call
|
|
1061
|
+
* this directly only if you manage the nonce yourself.
|
|
1062
|
+
*
|
|
1063
|
+
* @param userId - User ID the nonce is bound to
|
|
1064
|
+
* @param eventType - Event type the nonce is bound to (login, registration, ...)
|
|
1065
|
+
* @returns The nonce and its expiry
|
|
1066
|
+
*/
|
|
1067
|
+
async fetchSignalsNonce(userId, eventType, requestOptions) {
|
|
1068
|
+
if (!userId?.trim()) {
|
|
1069
|
+
throw new ValidationError("userId is required and must be a non-empty string", ["userId"]);
|
|
1070
|
+
}
|
|
1071
|
+
const request = { user_id: userId, event_type: eventType };
|
|
1072
|
+
return this.request(
|
|
1073
|
+
"/api/v1/geo/signals-token",
|
|
1074
|
+
{
|
|
1075
|
+
method: "POST",
|
|
1076
|
+
body: JSON.stringify(request)
|
|
1077
|
+
},
|
|
1078
|
+
void 0,
|
|
1079
|
+
requestOptions
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Best-effort nonce mint for validation. Returns undefined if minting fails —
|
|
1084
|
+
* the server is the sole authority and blocks when a nonce is required, so this
|
|
1085
|
+
* never downgrades security on the client's say-so.
|
|
932
1086
|
*/
|
|
933
|
-
async
|
|
1087
|
+
async resolveSignalsNonce(userId, eventType, requestOptions) {
|
|
934
1088
|
try {
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
requestOptions
|
|
1089
|
+
const { nonce } = await this.fetchSignalsNonce(userId, eventType, requestOptions);
|
|
1090
|
+
return nonce;
|
|
1091
|
+
} catch (err) {
|
|
1092
|
+
this.logger.warn(
|
|
1093
|
+
`Failed to mint device-signals nonce; proceeding without it: ${err instanceof Error ? err.message : String(err)}`
|
|
941
1094
|
);
|
|
942
|
-
|
|
943
|
-
this.cachedSigningKey = response.signing_key;
|
|
944
|
-
return response.signing_key;
|
|
945
|
-
}
|
|
946
|
-
} catch {
|
|
1095
|
+
return void 0;
|
|
947
1096
|
}
|
|
948
|
-
const config = await this.getGPSConfig(requestOptions);
|
|
949
|
-
return config.signing_key;
|
|
950
1097
|
}
|
|
951
1098
|
// ============================================================================
|
|
952
1099
|
// CipherText Validation
|
|
@@ -999,12 +1146,14 @@ var GeolocationClient = class extends BaseClient {
|
|
|
999
1146
|
if (!userId?.trim()) {
|
|
1000
1147
|
throw new ValidationError("userId is required and must be a non-empty string", ["userId"]);
|
|
1001
1148
|
}
|
|
1149
|
+
const nonce = await this.resolveSignalsNonce(userId, eventType, requestOptions);
|
|
1002
1150
|
const request = {
|
|
1003
1151
|
cipher_text: cipherText,
|
|
1004
1152
|
user_id: userId,
|
|
1005
1153
|
event_type: eventType,
|
|
1006
1154
|
expected_ip: expectedIP,
|
|
1007
|
-
customer_data: customerData
|
|
1155
|
+
customer_data: customerData,
|
|
1156
|
+
nonce
|
|
1008
1157
|
};
|
|
1009
1158
|
return this.requestWithRetry(
|
|
1010
1159
|
"/api/v1/geo/validate-ciphertext",
|
|
@@ -1213,20 +1362,12 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1213
1362
|
*
|
|
1214
1363
|
* @example
|
|
1215
1364
|
* ```typescript
|
|
1216
|
-
* //
|
|
1365
|
+
* // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
|
|
1217
1366
|
* const result = await client.captureLocation(token, {
|
|
1218
1367
|
* latitude: 37.7749,
|
|
1219
1368
|
* longitude: -122.4194,
|
|
1220
1369
|
* accuracy: 10
|
|
1221
1370
|
* });
|
|
1222
|
-
*
|
|
1223
|
-
* // Using WiFi positioning (fallback)
|
|
1224
|
-
* const result = await client.captureLocation(token, {
|
|
1225
|
-
* wifi_networks: [
|
|
1226
|
-
* { macAddress: "00:11:22:33:44:55", signalStrength: -50 },
|
|
1227
|
-
* { macAddress: "AA:BB:CC:DD:EE:FF", signalStrength: -70 }
|
|
1228
|
-
* ]
|
|
1229
|
-
* });
|
|
1230
1371
|
* ```
|
|
1231
1372
|
*/
|
|
1232
1373
|
async captureLocation(token, capture, requestOptions) {
|
|
@@ -1239,34 +1380,22 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1239
1380
|
// CipherText Generation
|
|
1240
1381
|
// ============================================================================
|
|
1241
1382
|
/**
|
|
1242
|
-
* Generate a
|
|
1383
|
+
* Generate a device-signals envelope containing device and location data,
|
|
1384
|
+
* validated by the server.
|
|
1243
1385
|
*
|
|
1244
|
-
*
|
|
1245
|
-
* If no API key is configured, falls back to unsigned v01 format.
|
|
1246
|
-
*
|
|
1247
|
-
* @param options - Options for cipherText generation
|
|
1386
|
+
* @param options - Options for envelope generation
|
|
1248
1387
|
* @param gpsConfig - Optional GPS config (from getGPSConfig)
|
|
1249
|
-
* @returns
|
|
1388
|
+
* @returns The device-signals envelope result
|
|
1250
1389
|
*
|
|
1251
1390
|
* @example
|
|
1252
1391
|
* ```typescript
|
|
1253
1392
|
* const result = await client.generateCipherText({ reason: 'login' });
|
|
1254
|
-
* console.log(result.cipherText); //
|
|
1393
|
+
* console.log(result.cipherText); // device-signals envelope
|
|
1255
1394
|
* ```
|
|
1256
1395
|
*/
|
|
1257
1396
|
async generateCipherText(options, gpsConfig) {
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
if (!signingKey) {
|
|
1261
|
-
signingKey = await this.fetchSigningKey();
|
|
1262
|
-
}
|
|
1263
|
-
if (!resolvedGpsConfig) {
|
|
1264
|
-
resolvedGpsConfig = await this.getGPSConfig();
|
|
1265
|
-
}
|
|
1266
|
-
return generateCipherText(
|
|
1267
|
-
{ ...options, signingKey: signingKey || void 0 },
|
|
1268
|
-
resolvedGpsConfig
|
|
1269
|
-
);
|
|
1397
|
+
const resolvedGpsConfig = gpsConfig ?? await this.getGPSConfig();
|
|
1398
|
+
return generateCipherText(options, resolvedGpsConfig);
|
|
1270
1399
|
}
|
|
1271
1400
|
// ============================================================================
|
|
1272
1401
|
// Utility Methods (inherited from BaseClient: healthCheck, updateConfig, getConfig, buildQueryString)
|
|
@@ -1274,8 +1403,12 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1274
1403
|
};
|
|
1275
1404
|
|
|
1276
1405
|
exports.GeolocationClient = GeolocationClient;
|
|
1406
|
+
exports.assessGpsIntegrity = assessGpsIntegrity;
|
|
1407
|
+
exports.collectDeviceSignals = collectDeviceSignals;
|
|
1277
1408
|
exports.decodeCipherText = decodeCipherText;
|
|
1278
1409
|
exports.generateCipherText = generateCipherText;
|
|
1410
|
+
exports.gpsIntegrityInputFromPosition = gpsIntegrityInputFromPosition;
|
|
1279
1411
|
exports.isCipherTextExpired = isCipherTextExpired;
|
|
1412
|
+
exports.probeGeolocationPermission = probeGeolocationPermission;
|
|
1280
1413
|
//# sourceMappingURL=index.js.map
|
|
1281
1414
|
//# sourceMappingURL=index.js.map
|