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
|
@@ -50,6 +50,80 @@ declare class CircuitBreaker {
|
|
|
50
50
|
reset(): void;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Pluggable authentication for BaseClient.
|
|
55
|
+
*
|
|
56
|
+
* A TokenProvider supplies the credential used for the Authorization header on
|
|
57
|
+
* every request, resolved per-request (async) so callers can source short-lived
|
|
58
|
+
* or rotating credentials (OAuth2 client-credentials, STS, Vault / Secrets
|
|
59
|
+
* Manager) instead of a single static API key. The default StaticApiKeyProvider
|
|
60
|
+
* preserves the original `apiKey` behavior with zero change for existing callers.
|
|
61
|
+
*/
|
|
62
|
+
interface AuthCredential {
|
|
63
|
+
/** Authorization scheme (e.g. 'Bearer'); defaults to 'Bearer' when omitted. */
|
|
64
|
+
scheme?: string;
|
|
65
|
+
/** The credential value (bearer token / API key). */
|
|
66
|
+
token: string;
|
|
67
|
+
}
|
|
68
|
+
interface TokenProviderContext {
|
|
69
|
+
tenantId: string;
|
|
70
|
+
requestId: string;
|
|
71
|
+
/** When true, bypass any cache and obtain a fresh credential (e.g. after a 401). */
|
|
72
|
+
forceRefresh?: boolean;
|
|
73
|
+
}
|
|
74
|
+
interface TokenProvider {
|
|
75
|
+
/** Resolve the credential for a request, or null to send no Authorization header. */
|
|
76
|
+
getCredential(ctx: TokenProviderContext): Promise<AuthCredential | null>;
|
|
77
|
+
/** Whether this provider can obtain a fresh credential on demand (drives 401 retry). */
|
|
78
|
+
canRefresh?(): boolean;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Default provider: wraps a static API key. Returns null when the key is empty
|
|
82
|
+
* so the client sends no Authorization header, matching the pre-provider behavior.
|
|
83
|
+
*/
|
|
84
|
+
declare class StaticApiKeyProvider implements TokenProvider {
|
|
85
|
+
private readonly apiKey;
|
|
86
|
+
constructor(apiKey: string);
|
|
87
|
+
getCredential(): Promise<AuthCredential | null>;
|
|
88
|
+
canRefresh(): boolean;
|
|
89
|
+
}
|
|
90
|
+
interface CachedToken {
|
|
91
|
+
token: string;
|
|
92
|
+
expiresAt?: number;
|
|
93
|
+
}
|
|
94
|
+
interface RefreshingTokenProviderOptions {
|
|
95
|
+
/** Fetches a fresh token. `expiresAt` is epoch milliseconds (optional). */
|
|
96
|
+
fetchToken: () => Promise<CachedToken>;
|
|
97
|
+
/** Refresh this many ms before expiry (default: 30_000). */
|
|
98
|
+
skewMs?: number;
|
|
99
|
+
/** Authorization scheme to emit (default: 'Bearer'). */
|
|
100
|
+
scheme?: string;
|
|
101
|
+
/** Observe each successful (re)fetch — for logging / metrics. */
|
|
102
|
+
onRotate?: (meta: {
|
|
103
|
+
expiresAt?: number;
|
|
104
|
+
}) => void;
|
|
105
|
+
/** Injectable clock (default: Date.now). */
|
|
106
|
+
now?: () => number;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Caches a fetched token and refreshes it when it is within the skew window of
|
|
110
|
+
* expiry or when `forceRefresh` is requested. Concurrent refreshes are de-duped
|
|
111
|
+
* into a single in-flight fetch.
|
|
112
|
+
*/
|
|
113
|
+
declare class RefreshingTokenProvider implements TokenProvider {
|
|
114
|
+
private readonly opts;
|
|
115
|
+
private cached;
|
|
116
|
+
private inFlight;
|
|
117
|
+
private readonly skewMs;
|
|
118
|
+
private readonly scheme;
|
|
119
|
+
private readonly now;
|
|
120
|
+
constructor(opts: RefreshingTokenProviderOptions);
|
|
121
|
+
canRefresh(): boolean;
|
|
122
|
+
getCredential(ctx?: TokenProviderContext): Promise<AuthCredential | null>;
|
|
123
|
+
private isExpiring;
|
|
124
|
+
private refresh;
|
|
125
|
+
}
|
|
126
|
+
|
|
53
127
|
/**
|
|
54
128
|
* Configuration types for Vesant SDK clients
|
|
55
129
|
*/
|
|
@@ -82,6 +156,16 @@ interface BaseClientConfig {
|
|
|
82
156
|
tenantId: string;
|
|
83
157
|
/** Optional API key for authentication */
|
|
84
158
|
apiKey?: string;
|
|
159
|
+
/**
|
|
160
|
+
* Pluggable auth token provider. When set, it supplies the Authorization
|
|
161
|
+
* credential per-request (overriding `apiKey`), enabling OAuth2 / STS / rotating
|
|
162
|
+
* secrets and single-shot 401 re-auth. Defaults to a static-apiKey provider.
|
|
163
|
+
*/
|
|
164
|
+
authProvider?: TokenProvider;
|
|
165
|
+
/** Observe credential rotation triggered by a 401 re-auth (logging / metrics). */
|
|
166
|
+
onCredentialRotated?: (meta: {
|
|
167
|
+
reason: string;
|
|
168
|
+
}) => void;
|
|
85
169
|
/** Custom headers to include in all requests */
|
|
86
170
|
headers?: Record<string, string>;
|
|
87
171
|
/** Request timeout in milliseconds (default: 10000) */
|
|
@@ -108,6 +192,16 @@ interface VesantConfig {
|
|
|
108
192
|
tenantId: string;
|
|
109
193
|
/** Optional API key for authentication */
|
|
110
194
|
apiKey?: string;
|
|
195
|
+
/**
|
|
196
|
+
* Pluggable auth token provider. When set, it supplies the Authorization
|
|
197
|
+
* credential per-request (overriding `apiKey`), enabling OAuth2 / STS / rotating
|
|
198
|
+
* secrets and single-shot 401 re-auth. Defaults to a static-apiKey provider.
|
|
199
|
+
*/
|
|
200
|
+
authProvider?: TokenProvider;
|
|
201
|
+
/** Observe credential rotation triggered by a 401 re-auth (logging / metrics). */
|
|
202
|
+
onCredentialRotated?: (meta: {
|
|
203
|
+
reason: string;
|
|
204
|
+
}) => void;
|
|
111
205
|
/** Custom headers to include in all requests */
|
|
112
206
|
headers?: Record<string, string>;
|
|
113
207
|
/** Request timeout in milliseconds (default: 10000) */
|
|
@@ -195,7 +289,15 @@ declare abstract class BaseClient {
|
|
|
195
289
|
private interceptors;
|
|
196
290
|
private circuitBreaker;
|
|
197
291
|
private rateLimitTracker;
|
|
292
|
+
private authProvider;
|
|
198
293
|
constructor(config: BaseClientConfig);
|
|
294
|
+
/**
|
|
295
|
+
* Resolve the Idempotency-Key for a request. Mutating methods (POST/PUT/PATCH)
|
|
296
|
+
* get the caller-supplied key or a freshly minted UUID; non-mutating methods get
|
|
297
|
+
* none. requestWithRetry resolves this once and injects it so every retry reuses
|
|
298
|
+
* one key (letting the server dedup replays); a direct request() resolves per call.
|
|
299
|
+
*/
|
|
300
|
+
private resolveIdempotencyKey;
|
|
199
301
|
/**
|
|
200
302
|
* Make an HTTP request with timeout and error handling
|
|
201
303
|
*/
|
|
@@ -237,4 +339,4 @@ declare abstract class BaseClient {
|
|
|
237
339
|
}>;
|
|
238
340
|
}
|
|
239
341
|
|
|
240
|
-
export { BaseClient as B, type CGSConfig as C, type Logger as L, type RequestOptions as R, type VesantConfig as V, type BaseClientConfig as a, CircuitBreaker as b, type CircuitBreakerConfig as c, type CircuitBreakerState as d, type CircuitBreakerStatus as e, type RateLimitStatus as f, RateLimitTracker as g, type RequestInterceptor as
|
|
342
|
+
export { type AuthCredential as A, BaseClient as B, type CGSConfig as C, type Logger as L, type RequestOptions as R, StaticApiKeyProvider as S, type TokenProvider as T, type VesantConfig as V, type BaseClientConfig as a, CircuitBreaker as b, type CircuitBreakerConfig as c, type CircuitBreakerState as d, type CircuitBreakerStatus as e, type RateLimitStatus as f, RateLimitTracker as g, RefreshingTokenProvider as h, type RefreshingTokenProviderOptions as i, type RequestInterceptor as j, type RequiredBaseClientConfig as k, type RequiredCGSConfig as l, type RequiredVesantConfig as m, type TokenProviderContext as n };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { B as BlockReason } from '../types-CBQRNL-l.mjs';
|
|
2
|
-
export { C as ComplianceClient, a as ComplianceLocationRequestInput, b as ComplianceLocationRequestResult, c as CurrencyRates, D as DEFAULT_CURRENCY_RATES, E as EventVerificationRequest, d as EventVerificationResponse, L as LoginVerificationRequest, e as LoginVerificationResponse, R as RegistrationVerificationRequest, f as RegistrationVerificationResponse, T as TransactionRiskResult, g as TransactionVerificationRequest, h as TransactionVerificationResponse } from '../client-
|
|
3
|
-
export { p as CreateLocationRequestRequest,
|
|
2
|
+
export { C as ComplianceClient, a as ComplianceLocationRequestInput, b as ComplianceLocationRequestResult, c as CurrencyRates, D as DEFAULT_CURRENCY_RATES, E as EventVerificationRequest, d as EventVerificationResponse, L as LoginVerificationRequest, e as LoginVerificationResponse, R as RegistrationVerificationRequest, f as RegistrationVerificationResponse, T as TransactionRiskResult, g as TransactionVerificationRequest, h as TransactionVerificationResponse } from '../client-D23KhWeh.mjs';
|
|
3
|
+
export { p as CreateLocationRequestRequest, I as LocationCaptureRequest, K as LocationCaptureResponse, a as LocationRequest, M as LocationRequestChannel, b as LocationRequestFilters, c as LocationRequestListResponse, N as LocationRequestResult, O as LocationRequestStatus, P as LocationShareInfo, R as ResendLocationRequestRequest } from '../client-BwXMQA2a.mjs';
|
|
4
4
|
import '../risk-profile/index.mjs';
|
|
5
|
-
import '../client-
|
|
5
|
+
import '../client-q9fN8Hhf.mjs';
|
|
6
6
|
import '../types-BOFaMQxI.mjs';
|
|
7
7
|
|
|
8
8
|
declare const sdkReasons: {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { B as BlockReason } from '../types-CBQRNL-l.js';
|
|
2
|
-
export { C as ComplianceClient, a as ComplianceLocationRequestInput, b as ComplianceLocationRequestResult, c as CurrencyRates, D as DEFAULT_CURRENCY_RATES, E as EventVerificationRequest, d as EventVerificationResponse, L as LoginVerificationRequest, e as LoginVerificationResponse, R as RegistrationVerificationRequest, f as RegistrationVerificationResponse, T as TransactionRiskResult, g as TransactionVerificationRequest, h as TransactionVerificationResponse } from '../client-
|
|
3
|
-
export { p as CreateLocationRequestRequest,
|
|
2
|
+
export { C as ComplianceClient, a as ComplianceLocationRequestInput, b as ComplianceLocationRequestResult, c as CurrencyRates, D as DEFAULT_CURRENCY_RATES, E as EventVerificationRequest, d as EventVerificationResponse, L as LoginVerificationRequest, e as LoginVerificationResponse, R as RegistrationVerificationRequest, f as RegistrationVerificationResponse, T as TransactionRiskResult, g as TransactionVerificationRequest, h as TransactionVerificationResponse } from '../client-B3DCAHlg.js';
|
|
3
|
+
export { p as CreateLocationRequestRequest, I as LocationCaptureRequest, K as LocationCaptureResponse, a as LocationRequest, M as LocationRequestChannel, b as LocationRequestFilters, c as LocationRequestListResponse, N as LocationRequestResult, O as LocationRequestStatus, P as LocationShareInfo, R as ResendLocationRequestRequest } from '../client-fy3trF2K.js';
|
|
4
4
|
import '../risk-profile/index.js';
|
|
5
|
-
import '../client-
|
|
5
|
+
import '../client-q9fN8Hhf.js';
|
|
6
6
|
import '../types-UGyDl1fd.js';
|
|
7
7
|
|
|
8
8
|
declare const sdkReasons: {
|
package/dist/compliance/index.js
CHANGED
|
@@ -75,6 +75,19 @@ var sdkReasons = {
|
|
|
75
75
|
})
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
+
// src/core/auth.ts
|
|
79
|
+
var StaticApiKeyProvider = class {
|
|
80
|
+
constructor(apiKey) {
|
|
81
|
+
this.apiKey = apiKey;
|
|
82
|
+
}
|
|
83
|
+
async getCredential() {
|
|
84
|
+
return this.apiKey ? { scheme: "Bearer", token: this.apiKey } : null;
|
|
85
|
+
}
|
|
86
|
+
canRefresh() {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
78
91
|
// src/core/errors.ts
|
|
79
92
|
var VesantError = class _VesantError extends Error {
|
|
80
93
|
constructor(message, code, statusCode, details) {
|
|
@@ -299,7 +312,7 @@ function createConsoleLogger() {
|
|
|
299
312
|
}
|
|
300
313
|
|
|
301
314
|
// src/core/version.ts
|
|
302
|
-
var SDK_VERSION = "1.7.
|
|
315
|
+
var SDK_VERSION = "1.7.1";
|
|
303
316
|
|
|
304
317
|
// src/shared/browser-utils.ts
|
|
305
318
|
function generateUUID() {
|
|
@@ -381,6 +394,12 @@ var BaseClient = class {
|
|
|
381
394
|
if (!config.tenantId?.trim()) {
|
|
382
395
|
throw new ValidationError("tenantId is required and must be a non-empty string", ["tenantId"]);
|
|
383
396
|
}
|
|
397
|
+
if (typeof fetch === "undefined") {
|
|
398
|
+
throw new VesantError(
|
|
399
|
+
"The Vesant SDK requires a global fetch (Node.js >= 18). Upgrade Node or provide a fetch polyfill.",
|
|
400
|
+
"RUNTIME_UNSUPPORTED"
|
|
401
|
+
);
|
|
402
|
+
}
|
|
384
403
|
this.interceptors = config.interceptors || [];
|
|
385
404
|
this.logger = config.logger || createConsoleLogger();
|
|
386
405
|
let environment = config.environment;
|
|
@@ -399,11 +418,12 @@ var BaseClient = class {
|
|
|
399
418
|
environment,
|
|
400
419
|
headers: config.headers || {},
|
|
401
420
|
timeout: config.timeout || 1e4,
|
|
402
|
-
retries: config.retries
|
|
421
|
+
retries: config.retries ?? 3,
|
|
403
422
|
debug: config.debug || false,
|
|
404
423
|
interceptors: this.interceptors,
|
|
405
424
|
logger: this.logger
|
|
406
425
|
};
|
|
426
|
+
this.authProvider = config.authProvider ?? (apiKey ? new StaticApiKeyProvider(apiKey) : void 0);
|
|
407
427
|
if (config.circuitBreaker) {
|
|
408
428
|
this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
|
|
409
429
|
}
|
|
@@ -411,6 +431,19 @@ var BaseClient = class {
|
|
|
411
431
|
this.rateLimitTracker = new RateLimitTracker();
|
|
412
432
|
}
|
|
413
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* Resolve the Idempotency-Key for a request. Mutating methods (POST/PUT/PATCH)
|
|
436
|
+
* get the caller-supplied key or a freshly minted UUID; non-mutating methods get
|
|
437
|
+
* none. requestWithRetry resolves this once and injects it so every retry reuses
|
|
438
|
+
* one key (letting the server dedup replays); a direct request() resolves per call.
|
|
439
|
+
*/
|
|
440
|
+
resolveIdempotencyKey(method, requestOptions) {
|
|
441
|
+
const isMutating = ["POST", "PUT", "PATCH"].includes((method || "GET").toUpperCase());
|
|
442
|
+
if (!isMutating) {
|
|
443
|
+
return void 0;
|
|
444
|
+
}
|
|
445
|
+
return requestOptions?.idempotencyKey ?? generateUUID();
|
|
446
|
+
}
|
|
414
447
|
/**
|
|
415
448
|
* Make an HTTP request with timeout and error handling
|
|
416
449
|
*/
|
|
@@ -436,15 +469,20 @@ var BaseClient = class {
|
|
|
436
469
|
...this.config.headers,
|
|
437
470
|
...options.headers || {}
|
|
438
471
|
};
|
|
439
|
-
|
|
440
|
-
|
|
472
|
+
const credential = await this.authProvider?.getCredential({
|
|
473
|
+
tenantId: this.config.tenantId,
|
|
474
|
+
requestId
|
|
475
|
+
});
|
|
476
|
+
if (credential) {
|
|
477
|
+
headers["Authorization"] = `${credential.scheme ?? "Bearer"} ${credential.token}`;
|
|
441
478
|
}
|
|
442
479
|
if (this.config.environment === "sandbox") {
|
|
443
480
|
headers["X-Sandbox"] = "true";
|
|
444
481
|
}
|
|
445
482
|
const method = (options.method || "GET").toUpperCase();
|
|
446
|
-
|
|
447
|
-
|
|
483
|
+
const idempotencyKey = this.resolveIdempotencyKey(method, requestOptions);
|
|
484
|
+
if (idempotencyKey) {
|
|
485
|
+
headers["Idempotency-Key"] = idempotencyKey;
|
|
448
486
|
}
|
|
449
487
|
const controller = new AbortController();
|
|
450
488
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
@@ -465,10 +503,28 @@ var BaseClient = class {
|
|
|
465
503
|
if (this.config.debug) {
|
|
466
504
|
this.logger.debug(`${finalOptions.method || "GET"} ${endpoint}`);
|
|
467
505
|
}
|
|
468
|
-
|
|
506
|
+
let response = await fetch(url, {
|
|
469
507
|
...finalOptions,
|
|
470
508
|
signal: controller.signal
|
|
471
509
|
});
|
|
510
|
+
if (response.status === 401 && this.authProvider?.canRefresh?.()) {
|
|
511
|
+
const refreshed = await this.authProvider.getCredential({
|
|
512
|
+
tenantId: this.config.tenantId,
|
|
513
|
+
requestId,
|
|
514
|
+
forceRefresh: true
|
|
515
|
+
});
|
|
516
|
+
if (refreshed) {
|
|
517
|
+
this.config.onCredentialRotated?.({ reason: "unauthorized" });
|
|
518
|
+
finalOptions = {
|
|
519
|
+
...finalOptions,
|
|
520
|
+
headers: {
|
|
521
|
+
...finalOptions.headers,
|
|
522
|
+
Authorization: `${refreshed.scheme ?? "Bearer"} ${refreshed.token}`
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
response = await fetch(url, { ...finalOptions, signal: controller.signal });
|
|
526
|
+
}
|
|
527
|
+
}
|
|
472
528
|
clearTimeout(timeoutId);
|
|
473
529
|
if (this.rateLimitTracker) {
|
|
474
530
|
this.rateLimitTracker.updateFromHeaders(response.headers);
|
|
@@ -557,9 +613,11 @@ var BaseClient = class {
|
|
|
557
613
|
*/
|
|
558
614
|
async requestWithRetry(endpoint, options = {}, serviceURL, retries = this.config.retries, requestOptions) {
|
|
559
615
|
let lastError;
|
|
616
|
+
const idempotencyKey = this.resolveIdempotencyKey(options.method || "GET", requestOptions);
|
|
617
|
+
const attemptOptions = idempotencyKey ? { ...requestOptions, idempotencyKey } : requestOptions;
|
|
560
618
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
561
619
|
try {
|
|
562
|
-
return await this.request(endpoint, options, serviceURL,
|
|
620
|
+
return await this.request(endpoint, options, serviceURL, attemptOptions);
|
|
563
621
|
} catch (error) {
|
|
564
622
|
lastError = error instanceof Error ? error : new Error("Unknown error");
|
|
565
623
|
if (requestOptions?.signal?.aborted) {
|
|
@@ -656,6 +714,9 @@ var BaseClient = class {
|
|
|
656
714
|
if (config.interceptors) {
|
|
657
715
|
this.interceptors = config.interceptors;
|
|
658
716
|
}
|
|
717
|
+
if (config.authProvider !== void 0 || config.apiKey !== void 0) {
|
|
718
|
+
this.authProvider = this.config.authProvider ?? (this.config.apiKey ? new StaticApiKeyProvider(this.config.apiKey) : void 0);
|
|
719
|
+
}
|
|
659
720
|
}
|
|
660
721
|
/**
|
|
661
722
|
* Get current configuration (readonly)
|
|
@@ -683,27 +744,63 @@ var BaseClient = class {
|
|
|
683
744
|
}
|
|
684
745
|
};
|
|
685
746
|
|
|
686
|
-
// src/geolocation/
|
|
687
|
-
var
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
);
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
747
|
+
// src/geolocation/integrity.ts
|
|
748
|
+
var isNil = (v) => v === null || v === void 0;
|
|
749
|
+
function assessGpsIntegrity(input, opts = {}) {
|
|
750
|
+
const flags = [];
|
|
751
|
+
const now = opts.now ?? (() => Date.now());
|
|
752
|
+
const maxAgeMs = opts.maxAgeMs ?? 6e4;
|
|
753
|
+
const minAccuracy = opts.minAccuracyMeters ?? 1;
|
|
754
|
+
const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
|
|
755
|
+
if (isNil(accuracy)) {
|
|
756
|
+
flags.push("accuracy_missing");
|
|
757
|
+
} else if (accuracy === 0) {
|
|
758
|
+
flags.push("accuracy_zero");
|
|
759
|
+
} else if (accuracy < minAccuracy) {
|
|
760
|
+
flags.push("accuracy_implausibly_small");
|
|
761
|
+
}
|
|
762
|
+
const hasMotion = !isNil(heading) || !isNil(speed);
|
|
763
|
+
if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
|
|
764
|
+
flags.push("motion_without_accuracy");
|
|
765
|
+
}
|
|
766
|
+
if (!isNil(altitude) && isNil(altitudeAccuracy)) {
|
|
767
|
+
flags.push("altitude_without_accuracy");
|
|
768
|
+
}
|
|
769
|
+
if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
|
|
770
|
+
flags.push("stale_timestamp");
|
|
771
|
+
}
|
|
772
|
+
if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
|
|
773
|
+
flags.push(`permission_${opts.permissionState}`);
|
|
774
|
+
}
|
|
775
|
+
return { suspicious: flags.length > 0, flags };
|
|
776
|
+
}
|
|
777
|
+
function gpsIntegrityInputFromPosition(position) {
|
|
778
|
+
const c = position.coords;
|
|
779
|
+
return {
|
|
780
|
+
latitude: c.latitude,
|
|
781
|
+
longitude: c.longitude,
|
|
782
|
+
accuracy: c.accuracy,
|
|
783
|
+
altitude: c.altitude,
|
|
784
|
+
altitudeAccuracy: c.altitudeAccuracy,
|
|
785
|
+
heading: c.heading,
|
|
786
|
+
speed: c.speed,
|
|
787
|
+
timestamp: position.timestamp
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
async function probeGeolocationPermission() {
|
|
791
|
+
try {
|
|
792
|
+
if (typeof navigator === "undefined" || !navigator.permissions?.query) {
|
|
793
|
+
return "unavailable";
|
|
794
|
+
}
|
|
795
|
+
const status = await navigator.permissions.query({ name: "geolocation" });
|
|
796
|
+
return status.state;
|
|
797
|
+
} catch {
|
|
798
|
+
return "unavailable";
|
|
703
799
|
}
|
|
704
|
-
const { createHmac } = await import('crypto');
|
|
705
|
-
return createHmac("sha256", key).update(message).digest("hex");
|
|
706
800
|
}
|
|
801
|
+
|
|
802
|
+
// src/geolocation/ciphertext.ts
|
|
803
|
+
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
707
804
|
function getWebGLInfo() {
|
|
708
805
|
if (typeof document === "undefined") return null;
|
|
709
806
|
try {
|
|
@@ -735,6 +832,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
735
832
|
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
736
833
|
return null;
|
|
737
834
|
}
|
|
835
|
+
const permissionState = await probeGeolocationPermission();
|
|
738
836
|
return new Promise((resolve) => {
|
|
739
837
|
navigator.geolocation.getCurrentPosition(
|
|
740
838
|
(position) => {
|
|
@@ -751,6 +849,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
751
849
|
resolve(null);
|
|
752
850
|
return;
|
|
753
851
|
}
|
|
852
|
+
const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
|
|
754
853
|
resolve({
|
|
755
854
|
latitude,
|
|
756
855
|
longitude,
|
|
@@ -759,7 +858,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
|
759
858
|
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
760
859
|
heading: position.coords.heading ?? void 0,
|
|
761
860
|
speed: position.coords.speed ?? void 0,
|
|
762
|
-
timestamp: position.timestamp
|
|
861
|
+
timestamp: position.timestamp,
|
|
862
|
+
integrity_flags: flags.length ? flags : void 0
|
|
763
863
|
});
|
|
764
864
|
},
|
|
765
865
|
() => {
|
|
@@ -826,6 +926,9 @@ async function generateCipherText(options, config) {
|
|
|
826
926
|
);
|
|
827
927
|
if (location) {
|
|
828
928
|
locationData = location;
|
|
929
|
+
if (location.integrity_flags?.length) {
|
|
930
|
+
warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
|
|
931
|
+
}
|
|
829
932
|
} else if (gpsRequiredByConfig) {
|
|
830
933
|
throw new VesantError(
|
|
831
934
|
`GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
|
|
@@ -855,15 +958,7 @@ async function generateCipherText(options, config) {
|
|
|
855
958
|
};
|
|
856
959
|
const encoded = encodePayload(payload);
|
|
857
960
|
const timestamp = now.getTime().toString(36);
|
|
858
|
-
|
|
859
|
-
const hmacKey = options.signingKey || options.apiKey;
|
|
860
|
-
if (hmacKey) {
|
|
861
|
-
const message = `02.${timestamp}.${encoded}`;
|
|
862
|
-
const hmac = await computeHMAC(hmacKey, message);
|
|
863
|
-
cipherText = `${message}.${hmac}`;
|
|
864
|
-
} else {
|
|
865
|
-
cipherText = `01.${timestamp}.${encoded}`;
|
|
866
|
-
}
|
|
961
|
+
const cipherText = `01.${timestamp}.${encoded}`;
|
|
867
962
|
return {
|
|
868
963
|
cipherText,
|
|
869
964
|
locationCaptured: !!locationData,
|
|
@@ -920,8 +1015,8 @@ var GeolocationClient = class extends BaseClient {
|
|
|
920
1015
|
* ```
|
|
921
1016
|
*/
|
|
922
1017
|
async verifyIP(request, requestOptions) {
|
|
923
|
-
if (!request.ip_address
|
|
924
|
-
throw new ValidationError("ip_address
|
|
1018
|
+
if (request.ip_address !== void 0 && !request.ip_address.trim()) {
|
|
1019
|
+
throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
|
|
925
1020
|
}
|
|
926
1021
|
return this.requestWithRetry("/api/v1/geo/verify", {
|
|
927
1022
|
method: "POST",
|
|
@@ -974,37 +1069,50 @@ var GeolocationClient = class extends BaseClient {
|
|
|
974
1069
|
* ```
|
|
975
1070
|
*/
|
|
976
1071
|
async getGPSConfig(requestOptions) {
|
|
977
|
-
|
|
978
|
-
if (config.signing_key) {
|
|
979
|
-
this.cachedSigningKey = config.signing_key;
|
|
980
|
-
}
|
|
981
|
-
return config;
|
|
1072
|
+
return this.requestWithRetry("/api/v1/geo/config", void 0, void 0, void 0, requestOptions);
|
|
982
1073
|
}
|
|
983
1074
|
/**
|
|
984
|
-
*
|
|
1075
|
+
* Mint a short-lived, single-use device-signals nonce.
|
|
985
1076
|
*
|
|
986
|
-
*
|
|
987
|
-
*
|
|
1077
|
+
* The nonce is bound to the tenant, user, and event type and is consumed
|
|
1078
|
+
* server-side during validation, so a captured device-signals envelope cannot
|
|
1079
|
+
* be replayed. `validateCipherText` mints and attaches one automatically; call
|
|
1080
|
+
* this directly only if you manage the nonce yourself.
|
|
988
1081
|
*
|
|
989
|
-
* @
|
|
1082
|
+
* @param userId - User ID the nonce is bound to
|
|
1083
|
+
* @param eventType - Event type the nonce is bound to (login, registration, ...)
|
|
1084
|
+
* @returns The nonce and its expiry
|
|
990
1085
|
*/
|
|
991
|
-
async
|
|
1086
|
+
async fetchSignalsNonce(userId, eventType, requestOptions) {
|
|
1087
|
+
if (!userId?.trim()) {
|
|
1088
|
+
throw new ValidationError("userId is required and must be a non-empty string", ["userId"]);
|
|
1089
|
+
}
|
|
1090
|
+
const request = { user_id: userId, event_type: eventType };
|
|
1091
|
+
return this.request(
|
|
1092
|
+
"/api/v1/geo/signals-token",
|
|
1093
|
+
{
|
|
1094
|
+
method: "POST",
|
|
1095
|
+
body: JSON.stringify(request)
|
|
1096
|
+
},
|
|
1097
|
+
void 0,
|
|
1098
|
+
requestOptions
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Best-effort nonce mint for validation. Returns undefined if minting fails —
|
|
1103
|
+
* the server is the sole authority and blocks when a nonce is required, so this
|
|
1104
|
+
* never downgrades security on the client's say-so.
|
|
1105
|
+
*/
|
|
1106
|
+
async resolveSignalsNonce(userId, eventType, requestOptions) {
|
|
992
1107
|
try {
|
|
993
|
-
const
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
requestOptions
|
|
1108
|
+
const { nonce } = await this.fetchSignalsNonce(userId, eventType, requestOptions);
|
|
1109
|
+
return nonce;
|
|
1110
|
+
} catch (err) {
|
|
1111
|
+
this.logger.warn(
|
|
1112
|
+
`Failed to mint device-signals nonce; proceeding without it: ${err instanceof Error ? err.message : String(err)}`
|
|
999
1113
|
);
|
|
1000
|
-
|
|
1001
|
-
this.cachedSigningKey = response.signing_key;
|
|
1002
|
-
return response.signing_key;
|
|
1003
|
-
}
|
|
1004
|
-
} catch {
|
|
1114
|
+
return void 0;
|
|
1005
1115
|
}
|
|
1006
|
-
const config = await this.getGPSConfig(requestOptions);
|
|
1007
|
-
return config.signing_key;
|
|
1008
1116
|
}
|
|
1009
1117
|
// ============================================================================
|
|
1010
1118
|
// CipherText Validation
|
|
@@ -1057,12 +1165,14 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1057
1165
|
if (!userId?.trim()) {
|
|
1058
1166
|
throw new ValidationError("userId is required and must be a non-empty string", ["userId"]);
|
|
1059
1167
|
}
|
|
1168
|
+
const nonce = await this.resolveSignalsNonce(userId, eventType, requestOptions);
|
|
1060
1169
|
const request = {
|
|
1061
1170
|
cipher_text: cipherText,
|
|
1062
1171
|
user_id: userId,
|
|
1063
1172
|
event_type: eventType,
|
|
1064
1173
|
expected_ip: expectedIP,
|
|
1065
|
-
customer_data: customerData
|
|
1174
|
+
customer_data: customerData,
|
|
1175
|
+
nonce
|
|
1066
1176
|
};
|
|
1067
1177
|
return this.requestWithRetry(
|
|
1068
1178
|
"/api/v1/geo/validate-ciphertext",
|
|
@@ -1271,20 +1381,12 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1271
1381
|
*
|
|
1272
1382
|
* @example
|
|
1273
1383
|
* ```typescript
|
|
1274
|
-
* //
|
|
1384
|
+
* // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
|
|
1275
1385
|
* const result = await client.captureLocation(token, {
|
|
1276
1386
|
* latitude: 37.7749,
|
|
1277
1387
|
* longitude: -122.4194,
|
|
1278
1388
|
* accuracy: 10
|
|
1279
1389
|
* });
|
|
1280
|
-
*
|
|
1281
|
-
* // Using WiFi positioning (fallback)
|
|
1282
|
-
* const result = await client.captureLocation(token, {
|
|
1283
|
-
* wifi_networks: [
|
|
1284
|
-
* { macAddress: "00:11:22:33:44:55", signalStrength: -50 },
|
|
1285
|
-
* { macAddress: "AA:BB:CC:DD:EE:FF", signalStrength: -70 }
|
|
1286
|
-
* ]
|
|
1287
|
-
* });
|
|
1288
1390
|
* ```
|
|
1289
1391
|
*/
|
|
1290
1392
|
async captureLocation(token, capture, requestOptions) {
|
|
@@ -1297,34 +1399,22 @@ var GeolocationClient = class extends BaseClient {
|
|
|
1297
1399
|
// CipherText Generation
|
|
1298
1400
|
// ============================================================================
|
|
1299
1401
|
/**
|
|
1300
|
-
* Generate a
|
|
1402
|
+
* Generate a device-signals envelope containing device and location data,
|
|
1403
|
+
* validated by the server.
|
|
1301
1404
|
*
|
|
1302
|
-
*
|
|
1303
|
-
* If no API key is configured, falls back to unsigned v01 format.
|
|
1304
|
-
*
|
|
1305
|
-
* @param options - Options for cipherText generation
|
|
1405
|
+
* @param options - Options for envelope generation
|
|
1306
1406
|
* @param gpsConfig - Optional GPS config (from getGPSConfig)
|
|
1307
|
-
* @returns
|
|
1407
|
+
* @returns The device-signals envelope result
|
|
1308
1408
|
*
|
|
1309
1409
|
* @example
|
|
1310
1410
|
* ```typescript
|
|
1311
1411
|
* const result = await client.generateCipherText({ reason: 'login' });
|
|
1312
|
-
* console.log(result.cipherText); //
|
|
1412
|
+
* console.log(result.cipherText); // device-signals envelope
|
|
1313
1413
|
* ```
|
|
1314
1414
|
*/
|
|
1315
1415
|
async generateCipherText(options, gpsConfig) {
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
if (!signingKey) {
|
|
1319
|
-
signingKey = await this.fetchSigningKey();
|
|
1320
|
-
}
|
|
1321
|
-
if (!resolvedGpsConfig) {
|
|
1322
|
-
resolvedGpsConfig = await this.getGPSConfig();
|
|
1323
|
-
}
|
|
1324
|
-
return generateCipherText(
|
|
1325
|
-
{ ...options, signingKey: signingKey || void 0 },
|
|
1326
|
-
resolvedGpsConfig
|
|
1327
|
-
);
|
|
1416
|
+
const resolvedGpsConfig = gpsConfig ?? await this.getGPSConfig();
|
|
1417
|
+
return generateCipherText(options, resolvedGpsConfig);
|
|
1328
1418
|
}
|
|
1329
1419
|
// ============================================================================
|
|
1330
1420
|
// Utility Methods (inherited from BaseClient: healthCheck, updateConfig, getConfig, buildQueryString)
|