vesant-sdk 1.1.1 → 1.3.0
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-i5QtnFIa.d.ts → client-B6fUFAUM.d.mts} +26 -1
- package/dist/{client-BfeDYmrZ.d.mts → client-DoczGA6L.d.ts} +26 -1
- package/dist/client-DzElM7u-.d.mts +238 -0
- package/dist/client-DzElM7u-.d.ts +238 -0
- package/dist/compliance/index.d.mts +5 -4
- package/dist/compliance/index.d.ts +5 -4
- package/dist/compliance/index.js +522 -30
- package/dist/compliance/index.js.map +1 -1
- package/dist/compliance/index.mjs +522 -30
- package/dist/compliance/index.mjs.map +1 -1
- package/dist/decisions/index.d.mts +100 -0
- package/dist/decisions/index.d.ts +100 -0
- package/dist/decisions/index.js +607 -0
- package/dist/decisions/index.js.map +1 -0
- package/dist/decisions/index.mjs +605 -0
- package/dist/decisions/index.mjs.map +1 -0
- package/dist/geolocation/index.d.mts +4 -3
- package/dist/geolocation/index.d.ts +4 -3
- package/dist/geolocation/index.js +546 -278
- package/dist/geolocation/index.js.map +1 -1
- package/dist/geolocation/index.mjs +546 -278
- package/dist/geolocation/index.mjs.map +1 -1
- package/dist/index.d.mts +14 -6
- package/dist/index.d.ts +14 -6
- package/dist/index.js +946 -335
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +937 -336
- package/dist/index.mjs.map +1 -1
- package/dist/kyc/core.d.mts +3 -2
- package/dist/kyc/core.d.ts +3 -2
- package/dist/kyc/core.js +249 -29
- package/dist/kyc/core.js.map +1 -1
- package/dist/kyc/core.mjs +249 -29
- package/dist/kyc/core.mjs.map +1 -1
- package/dist/kyc/index.d.mts +3 -2
- package/dist/kyc/index.d.ts +3 -2
- package/dist/kyc/index.js +249 -29
- package/dist/kyc/index.js.map +1 -1
- package/dist/kyc/index.mjs +249 -29
- package/dist/kyc/index.mjs.map +1 -1
- package/dist/react.d.mts +4 -3
- package/dist/react.d.ts +4 -3
- package/dist/react.js +29 -3
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +29 -3
- package/dist/react.mjs.map +1 -1
- package/dist/risk-profile/index.d.mts +4 -4
- package/dist/risk-profile/index.d.ts +4 -4
- package/dist/risk-profile/index.js +249 -29
- package/dist/risk-profile/index.js.map +1 -1
- package/dist/risk-profile/index.mjs +249 -29
- package/dist/risk-profile/index.mjs.map +1 -1
- package/dist/scores/index.d.mts +96 -0
- package/dist/scores/index.d.ts +96 -0
- package/dist/scores/index.js +594 -0
- package/dist/scores/index.js.map +1 -0
- package/dist/scores/index.mjs +591 -0
- package/dist/scores/index.mjs.map +1 -0
- package/dist/{types-DfHLp_tz.d.ts → types-DLC7Sfy5.d.ts} +1 -1
- package/dist/types-DZHongaK.d.mts +61 -0
- package/dist/types-DZHongaK.d.ts +61 -0
- package/dist/{types-DKCQN4C5.d.mts → types-jaLuzruy.d.mts} +1 -1
- package/dist/webhooks/index.d.mts +176 -0
- package/dist/webhooks/index.d.ts +176 -0
- package/dist/webhooks/index.js +193 -0
- package/dist/webhooks/index.js.map +1 -0
- package/dist/webhooks/index.mjs +188 -0
- package/dist/webhooks/index.mjs.map +1 -0
- package/package.json +16 -1
- package/dist/types-BpKxSXGF.d.mts +0 -177
- package/dist/types-BpKxSXGF.d.ts +0 -177
|
@@ -56,6 +56,146 @@ var TimeoutError = class _TimeoutError extends CGSError {
|
|
|
56
56
|
Object.setPrototypeOf(this, _TimeoutError.prototype);
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
|
+
var CircuitBreakerOpenError = class _CircuitBreakerOpenError extends CGSError {
|
|
60
|
+
constructor() {
|
|
61
|
+
super("Circuit breaker is open \u2014 requests are temporarily blocked", "CIRCUIT_BREAKER_OPEN", 503);
|
|
62
|
+
this.name = "CircuitBreakerOpenError";
|
|
63
|
+
Object.setPrototypeOf(this, _CircuitBreakerOpenError.prototype);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// src/core/circuit-breaker.ts
|
|
68
|
+
var CircuitBreaker = class {
|
|
69
|
+
constructor(config = {}) {
|
|
70
|
+
this.state = "closed";
|
|
71
|
+
this.failures = 0;
|
|
72
|
+
this.successes = 0;
|
|
73
|
+
this.lastFailureTime = null;
|
|
74
|
+
this.failureThreshold = config.failureThreshold ?? 5;
|
|
75
|
+
this.resetTimeout = config.resetTimeout ?? 3e4;
|
|
76
|
+
this.successThreshold = config.successThreshold ?? 1;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if a request can proceed through the circuit breaker.
|
|
80
|
+
*/
|
|
81
|
+
canExecute() {
|
|
82
|
+
if (this.state === "closed") {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (this.state === "open") {
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
if (this.lastFailureTime && now - this.lastFailureTime >= this.resetTimeout) {
|
|
88
|
+
this.state = "half-open";
|
|
89
|
+
this.successes = 0;
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Record a successful request.
|
|
98
|
+
*/
|
|
99
|
+
onSuccess() {
|
|
100
|
+
if (this.state === "half-open") {
|
|
101
|
+
this.successes++;
|
|
102
|
+
if (this.successes >= this.successThreshold) {
|
|
103
|
+
this.state = "closed";
|
|
104
|
+
this.failures = 0;
|
|
105
|
+
this.successes = 0;
|
|
106
|
+
this.lastFailureTime = null;
|
|
107
|
+
}
|
|
108
|
+
} else if (this.state === "closed") {
|
|
109
|
+
this.failures = 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Record a failed request.
|
|
114
|
+
*/
|
|
115
|
+
onFailure() {
|
|
116
|
+
this.failures++;
|
|
117
|
+
this.lastFailureTime = Date.now();
|
|
118
|
+
if (this.state === "half-open") {
|
|
119
|
+
this.state = "open";
|
|
120
|
+
this.successes = 0;
|
|
121
|
+
} else if (this.state === "closed" && this.failures >= this.failureThreshold) {
|
|
122
|
+
this.state = "open";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get current circuit breaker status.
|
|
127
|
+
*/
|
|
128
|
+
getStatus() {
|
|
129
|
+
return {
|
|
130
|
+
state: this.state,
|
|
131
|
+
failures: this.failures,
|
|
132
|
+
successes: this.successes,
|
|
133
|
+
lastFailureTime: this.lastFailureTime,
|
|
134
|
+
nextRetryTime: this.state === "open" && this.lastFailureTime ? this.lastFailureTime + this.resetTimeout : null
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Reset the circuit breaker to its initial closed state.
|
|
139
|
+
*/
|
|
140
|
+
reset() {
|
|
141
|
+
this.state = "closed";
|
|
142
|
+
this.failures = 0;
|
|
143
|
+
this.successes = 0;
|
|
144
|
+
this.lastFailureTime = null;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// src/core/rate-limiter.ts
|
|
149
|
+
var RateLimitTracker = class {
|
|
150
|
+
constructor() {
|
|
151
|
+
this.limit = null;
|
|
152
|
+
this.remaining = null;
|
|
153
|
+
this.reset = null;
|
|
154
|
+
this.retryAfter = null;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Extract rate limit information from response headers.
|
|
158
|
+
*/
|
|
159
|
+
updateFromHeaders(headers) {
|
|
160
|
+
const limit = headers.get("x-ratelimit-limit");
|
|
161
|
+
const remaining = headers.get("x-ratelimit-remaining");
|
|
162
|
+
const reset = headers.get("x-ratelimit-reset");
|
|
163
|
+
const retryAfter = headers.get("retry-after");
|
|
164
|
+
if (limit !== null) this.limit = parseInt(limit, 10);
|
|
165
|
+
if (remaining !== null) this.remaining = parseInt(remaining, 10);
|
|
166
|
+
if (reset !== null) this.reset = parseInt(reset, 10);
|
|
167
|
+
if (retryAfter !== null) this.retryAfter = parseInt(retryAfter, 10);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Check if the rate limit has been exceeded based on tracked headers.
|
|
171
|
+
*/
|
|
172
|
+
isLimitExceeded() {
|
|
173
|
+
if (this.remaining !== null && this.remaining <= 0) {
|
|
174
|
+
if (this.reset !== null) {
|
|
175
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
176
|
+
if (now >= this.reset) {
|
|
177
|
+
this.remaining = null;
|
|
178
|
+
this.reset = null;
|
|
179
|
+
this.retryAfter = null;
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get current rate limit status.
|
|
189
|
+
*/
|
|
190
|
+
getStatus() {
|
|
191
|
+
return {
|
|
192
|
+
limit: this.limit,
|
|
193
|
+
remaining: this.remaining,
|
|
194
|
+
reset: this.reset,
|
|
195
|
+
retryAfter: this.retryAfter
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
};
|
|
59
199
|
|
|
60
200
|
// src/core/logger.ts
|
|
61
201
|
function createConsoleLogger() {
|
|
@@ -76,11 +216,82 @@ function createConsoleLogger() {
|
|
|
76
216
|
}
|
|
77
217
|
|
|
78
218
|
// src/core/version.ts
|
|
79
|
-
var SDK_VERSION = "1.
|
|
219
|
+
var SDK_VERSION = "1.3.0";
|
|
220
|
+
|
|
221
|
+
// src/shared/browser-utils.ts
|
|
222
|
+
function generateUUID() {
|
|
223
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
224
|
+
return crypto.randomUUID();
|
|
225
|
+
}
|
|
226
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
227
|
+
const r = Math.random() * 16 | 0;
|
|
228
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
229
|
+
return v.toString(16);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function generateDeviceId() {
|
|
233
|
+
if (typeof window === "undefined" || typeof localStorage === "undefined") {
|
|
234
|
+
return generateUUID();
|
|
235
|
+
}
|
|
236
|
+
const storageKey = "cgs_device_id";
|
|
237
|
+
let deviceId = localStorage.getItem(storageKey);
|
|
238
|
+
if (!deviceId) {
|
|
239
|
+
deviceId = generateUUID();
|
|
240
|
+
localStorage.setItem(storageKey, deviceId);
|
|
241
|
+
}
|
|
242
|
+
return deviceId;
|
|
243
|
+
}
|
|
244
|
+
function getBrowserInfo() {
|
|
245
|
+
if (typeof navigator === "undefined") {
|
|
246
|
+
return { browser: "unknown", browser_version: "", os: "unknown", os_version: "" };
|
|
247
|
+
}
|
|
248
|
+
const ua = navigator.userAgent;
|
|
249
|
+
let browser = "unknown";
|
|
250
|
+
let browserVersion = "";
|
|
251
|
+
let os = "unknown";
|
|
252
|
+
let osVersion = "";
|
|
253
|
+
if (ua.includes("Firefox/")) {
|
|
254
|
+
browser = "Firefox";
|
|
255
|
+
browserVersion = ua.match(/Firefox\/([\d.]+)/)?.[1] || "";
|
|
256
|
+
} else if (ua.includes("Edg/")) {
|
|
257
|
+
browser = "Edge";
|
|
258
|
+
browserVersion = ua.match(/Edg\/([\d.]+)/)?.[1] || "";
|
|
259
|
+
} else if (ua.includes("Chrome/")) {
|
|
260
|
+
browser = "Chrome";
|
|
261
|
+
browserVersion = ua.match(/Chrome\/([\d.]+)/)?.[1] || "";
|
|
262
|
+
} else if (ua.includes("Safari/") && !ua.includes("Chrome")) {
|
|
263
|
+
browser = "Safari";
|
|
264
|
+
browserVersion = ua.match(/Version\/([\d.]+)/)?.[1] || "";
|
|
265
|
+
} else if (ua.includes("Opera") || ua.includes("OPR/")) {
|
|
266
|
+
browser = "Opera";
|
|
267
|
+
browserVersion = ua.match(/(?:Opera|OPR)\/([\d.]+)/)?.[1] || "";
|
|
268
|
+
}
|
|
269
|
+
if (ua.includes("Windows")) {
|
|
270
|
+
os = "Windows";
|
|
271
|
+
if (ua.includes("Windows NT 10.0")) osVersion = "10";
|
|
272
|
+
else if (ua.includes("Windows NT 6.3")) osVersion = "8.1";
|
|
273
|
+
else if (ua.includes("Windows NT 6.2")) osVersion = "8";
|
|
274
|
+
else if (ua.includes("Windows NT 6.1")) osVersion = "7";
|
|
275
|
+
} else if (ua.includes("Mac OS X")) {
|
|
276
|
+
os = "macOS";
|
|
277
|
+
osVersion = ua.match(/Mac OS X ([\d_]+)/)?.[1]?.replace(/_/g, ".") || "";
|
|
278
|
+
} else if (ua.includes("Linux")) {
|
|
279
|
+
os = "Linux";
|
|
280
|
+
} else if (ua.includes("Android")) {
|
|
281
|
+
os = "Android";
|
|
282
|
+
osVersion = ua.match(/Android ([\d.]+)/)?.[1] || "";
|
|
283
|
+
} else if (ua.includes("iOS") || ua.includes("iPhone") || ua.includes("iPad")) {
|
|
284
|
+
os = "iOS";
|
|
285
|
+
osVersion = ua.match(/OS ([\d_]+)/)?.[1]?.replace(/_/g, ".") || "";
|
|
286
|
+
}
|
|
287
|
+
return { browser, browser_version: browserVersion, os, os_version: osVersion };
|
|
288
|
+
}
|
|
80
289
|
|
|
81
290
|
// src/core/client.ts
|
|
82
291
|
var BaseClient = class {
|
|
83
292
|
constructor(config) {
|
|
293
|
+
this.circuitBreaker = null;
|
|
294
|
+
this.rateLimitTracker = null;
|
|
84
295
|
if (!config.baseURL?.trim()) {
|
|
85
296
|
throw new ValidationError("baseURL is required and must be a non-empty string", ["baseURL"]);
|
|
86
297
|
}
|
|
@@ -90,8 +301,7 @@ var BaseClient = class {
|
|
|
90
301
|
this.interceptors = config.interceptors || [];
|
|
91
302
|
this.logger = config.logger || createConsoleLogger();
|
|
92
303
|
this.config = {
|
|
93
|
-
|
|
94
|
-
tenantId: config.tenantId,
|
|
304
|
+
...config,
|
|
95
305
|
apiKey: config.apiKey || "",
|
|
96
306
|
headers: config.headers || {},
|
|
97
307
|
timeout: config.timeout || 1e4,
|
|
@@ -100,22 +310,49 @@ var BaseClient = class {
|
|
|
100
310
|
interceptors: this.interceptors,
|
|
101
311
|
logger: this.logger
|
|
102
312
|
};
|
|
313
|
+
if (config.circuitBreaker) {
|
|
314
|
+
this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
|
|
315
|
+
}
|
|
316
|
+
if (config.enableRateLimitTracking) {
|
|
317
|
+
this.rateLimitTracker = new RateLimitTracker();
|
|
318
|
+
}
|
|
103
319
|
}
|
|
104
320
|
/**
|
|
105
321
|
* Make an HTTP request with timeout and error handling
|
|
106
322
|
*/
|
|
107
323
|
async request(endpoint, options = {}, serviceURL, requestOptions) {
|
|
108
|
-
const
|
|
324
|
+
const requestId = generateUUID();
|
|
325
|
+
if (this.circuitBreaker && !this.circuitBreaker.canExecute()) {
|
|
326
|
+
const error = new CircuitBreakerOpenError();
|
|
327
|
+
error.requestId = requestId;
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
if (this.rateLimitTracker && this.rateLimitTracker.isLimitExceeded()) {
|
|
331
|
+
const status = this.rateLimitTracker.getStatus();
|
|
332
|
+
const error = new RateLimitError(status.retryAfter ?? void 0);
|
|
333
|
+
error.requestId = requestId;
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
const baseURL = this.config.environment === "sandbox" && this.config.sandboxBaseURL ? this.config.sandboxBaseURL : serviceURL || this.config.baseURL;
|
|
337
|
+
const url = `${serviceURL || baseURL}${endpoint}`;
|
|
109
338
|
const headers = {
|
|
110
339
|
"Content-Type": "application/json",
|
|
111
340
|
"X-Tenant-ID": this.config.tenantId,
|
|
112
341
|
"X-SDK-Version": `vesant-sdk-ts/${SDK_VERSION}`,
|
|
342
|
+
"X-Request-ID": requestId,
|
|
113
343
|
...this.config.headers,
|
|
114
344
|
...options.headers || {}
|
|
115
345
|
};
|
|
116
346
|
if (this.config.apiKey) {
|
|
117
347
|
headers["Authorization"] = `Bearer ${this.config.apiKey}`;
|
|
118
348
|
}
|
|
349
|
+
if (this.config.environment === "sandbox") {
|
|
350
|
+
headers["X-Sandbox"] = "true";
|
|
351
|
+
}
|
|
352
|
+
const method = (options.method || "GET").toUpperCase();
|
|
353
|
+
if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
354
|
+
headers["Idempotency-Key"] = requestOptions?.idempotencyKey || generateUUID();
|
|
355
|
+
}
|
|
119
356
|
const controller = new AbortController();
|
|
120
357
|
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
121
358
|
if (requestOptions?.signal) {
|
|
@@ -143,6 +380,9 @@ var BaseClient = class {
|
|
|
143
380
|
signal: controller.signal
|
|
144
381
|
});
|
|
145
382
|
clearTimeout(timeoutId);
|
|
383
|
+
if (this.rateLimitTracker) {
|
|
384
|
+
this.rateLimitTracker.updateFromHeaders(response.headers);
|
|
385
|
+
}
|
|
146
386
|
let data;
|
|
147
387
|
try {
|
|
148
388
|
data = await response.json();
|
|
@@ -151,13 +391,15 @@ var BaseClient = class {
|
|
|
151
391
|
this.handleErrorResponse(response.status, {
|
|
152
392
|
error: `HTTP ${response.status}`,
|
|
153
393
|
message: response.statusText
|
|
154
|
-
});
|
|
394
|
+
}, requestId);
|
|
155
395
|
}
|
|
396
|
+
this.circuitBreaker?.onSuccess();
|
|
156
397
|
return void 0;
|
|
157
398
|
}
|
|
158
399
|
if (!response.ok) {
|
|
159
|
-
this.handleErrorResponse(response.status, data || {});
|
|
400
|
+
this.handleErrorResponse(response.status, data || {}, requestId);
|
|
160
401
|
}
|
|
402
|
+
this.circuitBreaker?.onSuccess();
|
|
161
403
|
let result = data;
|
|
162
404
|
for (const interceptor of this.interceptors) {
|
|
163
405
|
if (interceptor.onResponse) {
|
|
@@ -170,6 +412,14 @@ var BaseClient = class {
|
|
|
170
412
|
return result;
|
|
171
413
|
} catch (error) {
|
|
172
414
|
clearTimeout(timeoutId);
|
|
415
|
+
if (error instanceof CGSError && error.statusCode && error.statusCode >= 500) {
|
|
416
|
+
this.circuitBreaker?.onFailure();
|
|
417
|
+
} else if (error instanceof NetworkError || error instanceof TimeoutError) {
|
|
418
|
+
this.circuitBreaker?.onFailure();
|
|
419
|
+
}
|
|
420
|
+
if (error instanceof CGSError && !error.requestId) {
|
|
421
|
+
error.requestId = requestId;
|
|
422
|
+
}
|
|
173
423
|
if (error instanceof Error) {
|
|
174
424
|
for (const interceptor of this.interceptors) {
|
|
175
425
|
if (interceptor.onError) {
|
|
@@ -180,15 +430,23 @@ var BaseClient = class {
|
|
|
180
430
|
if (error instanceof Error) {
|
|
181
431
|
if (error.name === "AbortError") {
|
|
182
432
|
if (requestOptions?.signal?.aborted) {
|
|
183
|
-
|
|
433
|
+
const abortError = new CGSError("Request aborted", "REQUEST_ABORTED");
|
|
434
|
+
abortError.requestId = requestId;
|
|
435
|
+
throw abortError;
|
|
184
436
|
}
|
|
185
|
-
|
|
437
|
+
this.circuitBreaker?.onFailure();
|
|
438
|
+
const timeoutError = new TimeoutError(this.config.timeout);
|
|
439
|
+
timeoutError.requestId = requestId;
|
|
440
|
+
throw timeoutError;
|
|
186
441
|
}
|
|
187
442
|
if (error instanceof CGSError) {
|
|
188
443
|
throw error;
|
|
189
444
|
}
|
|
190
445
|
}
|
|
191
|
-
|
|
446
|
+
const networkError = new NetworkError("Network request failed", error);
|
|
447
|
+
networkError.requestId = requestId;
|
|
448
|
+
this.circuitBreaker?.onFailure();
|
|
449
|
+
throw networkError;
|
|
192
450
|
}
|
|
193
451
|
}
|
|
194
452
|
/**
|
|
@@ -227,29 +485,36 @@ var BaseClient = class {
|
|
|
227
485
|
/**
|
|
228
486
|
* Handle error responses from API
|
|
229
487
|
*/
|
|
230
|
-
handleErrorResponse(status, data) {
|
|
488
|
+
handleErrorResponse(status, data, requestId) {
|
|
231
489
|
const message = data.error || data.message || `HTTP ${status}`;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
490
|
+
const createError = () => {
|
|
491
|
+
switch (status) {
|
|
492
|
+
case 400:
|
|
493
|
+
return new CGSError(message, "BAD_REQUEST", 400, data);
|
|
494
|
+
case 401:
|
|
495
|
+
return new AuthenticationError(message);
|
|
496
|
+
case 403:
|
|
497
|
+
return new CGSError(message, "FORBIDDEN", 403, data);
|
|
498
|
+
case 404:
|
|
499
|
+
return new CGSError(message, "NOT_FOUND", 404, data);
|
|
500
|
+
case 429: {
|
|
501
|
+
const retryAfter = data.retry_after || data.retryAfter;
|
|
502
|
+
return new RateLimitError(retryAfter);
|
|
503
|
+
}
|
|
504
|
+
case 500:
|
|
505
|
+
case 502:
|
|
506
|
+
case 503:
|
|
507
|
+
case 504:
|
|
508
|
+
return new ServiceUnavailableError(message);
|
|
509
|
+
default:
|
|
510
|
+
return new CGSError(message, "UNKNOWN_ERROR", status, data);
|
|
244
511
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
throw new ServiceUnavailableError(message);
|
|
250
|
-
default:
|
|
251
|
-
throw new CGSError(message, "UNKNOWN_ERROR", status, data);
|
|
512
|
+
};
|
|
513
|
+
const error = createError();
|
|
514
|
+
if (requestId) {
|
|
515
|
+
error.requestId = requestId;
|
|
252
516
|
}
|
|
517
|
+
throw error;
|
|
253
518
|
}
|
|
254
519
|
/**
|
|
255
520
|
* Build query string from parameters
|
|
@@ -293,6 +558,18 @@ var BaseClient = class {
|
|
|
293
558
|
getConfig() {
|
|
294
559
|
return { ...this.config };
|
|
295
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* Get rate limit status from tracked response headers.
|
|
563
|
+
*/
|
|
564
|
+
getRateLimitStatus() {
|
|
565
|
+
return this.rateLimitTracker?.getStatus() ?? null;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Get circuit breaker status.
|
|
569
|
+
*/
|
|
570
|
+
getCircuitBreakerStatus() {
|
|
571
|
+
return this.circuitBreaker?.getStatus() ?? null;
|
|
572
|
+
}
|
|
296
573
|
/**
|
|
297
574
|
* Health check endpoint
|
|
298
575
|
*/
|
|
@@ -301,6 +578,212 @@ var BaseClient = class {
|
|
|
301
578
|
}
|
|
302
579
|
};
|
|
303
580
|
|
|
581
|
+
// src/geolocation/ciphertext.ts
|
|
582
|
+
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
583
|
+
async function computeHMAC(key, message) {
|
|
584
|
+
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
585
|
+
const encoder = new TextEncoder();
|
|
586
|
+
const keyData = encoder.encode(key);
|
|
587
|
+
const msgData = encoder.encode(message);
|
|
588
|
+
const cryptoKey = await globalThis.crypto.subtle.importKey(
|
|
589
|
+
"raw",
|
|
590
|
+
keyData,
|
|
591
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
592
|
+
false,
|
|
593
|
+
["sign"]
|
|
594
|
+
);
|
|
595
|
+
const signature = await globalThis.crypto.subtle.sign("HMAC", cryptoKey, msgData);
|
|
596
|
+
const bytes = new Uint8Array(signature);
|
|
597
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
598
|
+
}
|
|
599
|
+
const { createHmac } = await import('crypto');
|
|
600
|
+
return createHmac("sha256", key).update(message).digest("hex");
|
|
601
|
+
}
|
|
602
|
+
function getWebGLInfo() {
|
|
603
|
+
if (typeof document === "undefined") return null;
|
|
604
|
+
try {
|
|
605
|
+
const canvas = document.createElement("canvas");
|
|
606
|
+
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
607
|
+
if (!gl) return null;
|
|
608
|
+
const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
|
|
609
|
+
if (!debugInfo) return null;
|
|
610
|
+
return {
|
|
611
|
+
vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) || "",
|
|
612
|
+
renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || ""
|
|
613
|
+
};
|
|
614
|
+
} catch {
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function getNetworkInfo() {
|
|
619
|
+
if (typeof navigator === "undefined") return void 0;
|
|
620
|
+
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
621
|
+
if (!connection) return void 0;
|
|
622
|
+
return {
|
|
623
|
+
effective_type: connection.effectiveType,
|
|
624
|
+
downlink: connection.downlink,
|
|
625
|
+
rtt: connection.rtt,
|
|
626
|
+
save_data: connection.saveData
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
630
|
+
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
return new Promise((resolve) => {
|
|
634
|
+
navigator.geolocation.getCurrentPosition(
|
|
635
|
+
(position) => {
|
|
636
|
+
const { latitude, longitude, accuracy } = position.coords;
|
|
637
|
+
if (latitude < -90 || latitude > 90) {
|
|
638
|
+
resolve(null);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
if (longitude < -180 || longitude > 180) {
|
|
642
|
+
resolve(null);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
if (accuracy !== null && accuracy !== void 0 && accuracy <= 0) {
|
|
646
|
+
resolve(null);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
resolve({
|
|
650
|
+
latitude,
|
|
651
|
+
longitude,
|
|
652
|
+
accuracy,
|
|
653
|
+
altitude: position.coords.altitude ?? void 0,
|
|
654
|
+
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
655
|
+
heading: position.coords.heading ?? void 0,
|
|
656
|
+
speed: position.coords.speed ?? void 0,
|
|
657
|
+
timestamp: position.timestamp
|
|
658
|
+
});
|
|
659
|
+
},
|
|
660
|
+
() => {
|
|
661
|
+
resolve(null);
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
enableHighAccuracy: highAccuracy,
|
|
665
|
+
timeout,
|
|
666
|
+
maximumAge: 0
|
|
667
|
+
}
|
|
668
|
+
);
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
function collectDeviceData(includeWebGL) {
|
|
672
|
+
const browserInfo = getBrowserInfo();
|
|
673
|
+
const webglInfo = includeWebGL ? getWebGLInfo() : null;
|
|
674
|
+
return {
|
|
675
|
+
device_id: generateDeviceId(),
|
|
676
|
+
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : "",
|
|
677
|
+
platform: typeof navigator !== "undefined" ? navigator.platform : "",
|
|
678
|
+
browser: browserInfo.browser,
|
|
679
|
+
browser_version: browserInfo.browser_version,
|
|
680
|
+
os: browserInfo.os,
|
|
681
|
+
os_version: browserInfo.os_version,
|
|
682
|
+
screen_resolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0,
|
|
683
|
+
language: typeof navigator !== "undefined" ? navigator.language : void 0,
|
|
684
|
+
timezone: Intl?.DateTimeFormat?.()?.resolvedOptions?.()?.timeZone,
|
|
685
|
+
color_depth: typeof screen !== "undefined" ? screen.colorDepth : void 0,
|
|
686
|
+
hardware_concurrency: typeof navigator !== "undefined" ? navigator.hardwareConcurrency : void 0,
|
|
687
|
+
device_memory: typeof navigator !== "undefined" ? navigator.deviceMemory : void 0,
|
|
688
|
+
touch_support: typeof navigator !== "undefined" ? navigator.maxTouchPoints > 0 : void 0,
|
|
689
|
+
webgl_vendor: webglInfo?.vendor,
|
|
690
|
+
webgl_renderer: webglInfo?.renderer
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
function encodePayload(payload) {
|
|
694
|
+
const json = JSON.stringify(payload);
|
|
695
|
+
if (typeof btoa !== "undefined") {
|
|
696
|
+
return btoa(unescape(encodeURIComponent(json)));
|
|
697
|
+
} else if (typeof Buffer !== "undefined") {
|
|
698
|
+
return Buffer.from(json, "utf-8").toString("base64");
|
|
699
|
+
}
|
|
700
|
+
throw new Error("No base64 encoding method available");
|
|
701
|
+
}
|
|
702
|
+
async function generateCipherText(options, config) {
|
|
703
|
+
const warnings = [];
|
|
704
|
+
let requestLocation = options.requestLocation ?? false;
|
|
705
|
+
if (config?.require_gps) {
|
|
706
|
+
const reason = options.reason;
|
|
707
|
+
if (reason === "login" && config.require_gps.login || reason === "registration" && config.require_gps.registration || reason === "transaction" && config.require_gps.transaction) {
|
|
708
|
+
requestLocation = true;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const deviceData = collectDeviceData(options.includeWebGL !== false);
|
|
712
|
+
let locationData;
|
|
713
|
+
if (requestLocation) {
|
|
714
|
+
const location = await requestGPSLocation(
|
|
715
|
+
options.locationTimeout || 1e4,
|
|
716
|
+
options.highAccuracy !== false
|
|
717
|
+
);
|
|
718
|
+
if (location) {
|
|
719
|
+
locationData = location;
|
|
720
|
+
} else {
|
|
721
|
+
warnings.push("GPS location not available or permission denied");
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
let networkData;
|
|
725
|
+
if (options.includeNetworkInfo !== false) {
|
|
726
|
+
networkData = getNetworkInfo();
|
|
727
|
+
}
|
|
728
|
+
const now = /* @__PURE__ */ new Date();
|
|
729
|
+
const expiry = new Date(now.getTime() + CIPHER_TEXT_EXPIRY_MINUTES * 60 * 1e3);
|
|
730
|
+
const payload = {
|
|
731
|
+
device: deviceData,
|
|
732
|
+
location: locationData,
|
|
733
|
+
network: networkData,
|
|
734
|
+
metadata: {
|
|
735
|
+
collected_at: now.toISOString(),
|
|
736
|
+
sdk_version: SDK_VERSION,
|
|
737
|
+
collection_reason: options.reason,
|
|
738
|
+
page_url: typeof window !== "undefined" ? window.location.href : void 0,
|
|
739
|
+
referrer: typeof document !== "undefined" ? document.referrer || void 0 : void 0
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
const encoded = encodePayload(payload);
|
|
743
|
+
const timestamp = now.getTime().toString(36);
|
|
744
|
+
let cipherText;
|
|
745
|
+
const hmacKey = options.signingKey || options.apiKey;
|
|
746
|
+
if (hmacKey) {
|
|
747
|
+
const message = `02.${timestamp}.${encoded}`;
|
|
748
|
+
const hmac = await computeHMAC(hmacKey, message);
|
|
749
|
+
cipherText = `${message}.${hmac}`;
|
|
750
|
+
} else {
|
|
751
|
+
cipherText = `01.${timestamp}.${encoded}`;
|
|
752
|
+
}
|
|
753
|
+
return {
|
|
754
|
+
cipherText,
|
|
755
|
+
locationCaptured: !!locationData,
|
|
756
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
757
|
+
generatedAt: now.toISOString(),
|
|
758
|
+
expiresAt: expiry.toISOString()
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function decodeCipherText(cipherText) {
|
|
762
|
+
try {
|
|
763
|
+
const parts = cipherText.split(".");
|
|
764
|
+
if (parts.length !== 3 && parts.length !== 4) return null;
|
|
765
|
+
const [version, , encodedB64] = parts;
|
|
766
|
+
if (version !== "01" && version !== "02") return null;
|
|
767
|
+
const json = decodeURIComponent(escape(atob(encodedB64)));
|
|
768
|
+
return JSON.parse(json);
|
|
769
|
+
} catch {
|
|
770
|
+
return null;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
function isCipherTextExpired(cipherText) {
|
|
774
|
+
try {
|
|
775
|
+
const parts = cipherText.split(".");
|
|
776
|
+
if (parts.length !== 3 && parts.length !== 4) return true;
|
|
777
|
+
const timestampB36 = parts[1];
|
|
778
|
+
const timestamp = parseInt(timestampB36, 36);
|
|
779
|
+
const now = Date.now();
|
|
780
|
+
const expiryMs = CIPHER_TEXT_EXPIRY_MINUTES * 60 * 1e3;
|
|
781
|
+
return now - timestamp > expiryMs;
|
|
782
|
+
} catch {
|
|
783
|
+
return true;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
304
787
|
// src/geolocation/client.ts
|
|
305
788
|
var GeolocationClient = class extends BaseClient {
|
|
306
789
|
constructor(config) {
|
|
@@ -401,7 +884,11 @@ var GeolocationClient = class extends BaseClient {
|
|
|
401
884
|
* ```
|
|
402
885
|
*/
|
|
403
886
|
async getGPSConfig(requestOptions) {
|
|
404
|
-
|
|
887
|
+
const config = await this.requestWithRetry("/api/v1/geo/config", void 0, void 0, void 0, requestOptions);
|
|
888
|
+
if (config.signing_key) {
|
|
889
|
+
this.cachedSigningKey = config.signing_key;
|
|
890
|
+
}
|
|
891
|
+
return config;
|
|
405
892
|
}
|
|
406
893
|
// ============================================================================
|
|
407
894
|
// CipherText Validation
|
|
@@ -661,258 +1148,39 @@ var GeolocationClient = class extends BaseClient {
|
|
|
661
1148
|
}, void 0, requestOptions);
|
|
662
1149
|
}
|
|
663
1150
|
// ============================================================================
|
|
664
|
-
//
|
|
1151
|
+
// CipherText Generation
|
|
665
1152
|
// ============================================================================
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
localStorage.setItem(storageKey, deviceId);
|
|
688
|
-
}
|
|
689
|
-
return deviceId;
|
|
690
|
-
}
|
|
691
|
-
function getBrowserInfo() {
|
|
692
|
-
if (typeof navigator === "undefined") {
|
|
693
|
-
return { browser: "unknown", browser_version: "", os: "unknown", os_version: "" };
|
|
694
|
-
}
|
|
695
|
-
const ua = navigator.userAgent;
|
|
696
|
-
let browser = "unknown";
|
|
697
|
-
let browserVersion = "";
|
|
698
|
-
let os = "unknown";
|
|
699
|
-
let osVersion = "";
|
|
700
|
-
if (ua.includes("Firefox/")) {
|
|
701
|
-
browser = "Firefox";
|
|
702
|
-
browserVersion = ua.match(/Firefox\/([\d.]+)/)?.[1] || "";
|
|
703
|
-
} else if (ua.includes("Edg/")) {
|
|
704
|
-
browser = "Edge";
|
|
705
|
-
browserVersion = ua.match(/Edg\/([\d.]+)/)?.[1] || "";
|
|
706
|
-
} else if (ua.includes("Chrome/")) {
|
|
707
|
-
browser = "Chrome";
|
|
708
|
-
browserVersion = ua.match(/Chrome\/([\d.]+)/)?.[1] || "";
|
|
709
|
-
} else if (ua.includes("Safari/") && !ua.includes("Chrome")) {
|
|
710
|
-
browser = "Safari";
|
|
711
|
-
browserVersion = ua.match(/Version\/([\d.]+)/)?.[1] || "";
|
|
712
|
-
} else if (ua.includes("Opera") || ua.includes("OPR/")) {
|
|
713
|
-
browser = "Opera";
|
|
714
|
-
browserVersion = ua.match(/(?:Opera|OPR)\/([\d.]+)/)?.[1] || "";
|
|
715
|
-
}
|
|
716
|
-
if (ua.includes("Windows")) {
|
|
717
|
-
os = "Windows";
|
|
718
|
-
if (ua.includes("Windows NT 10.0")) osVersion = "10";
|
|
719
|
-
else if (ua.includes("Windows NT 6.3")) osVersion = "8.1";
|
|
720
|
-
else if (ua.includes("Windows NT 6.2")) osVersion = "8";
|
|
721
|
-
else if (ua.includes("Windows NT 6.1")) osVersion = "7";
|
|
722
|
-
} else if (ua.includes("Mac OS X")) {
|
|
723
|
-
os = "macOS";
|
|
724
|
-
osVersion = ua.match(/Mac OS X ([\d_]+)/)?.[1]?.replace(/_/g, ".") || "";
|
|
725
|
-
} else if (ua.includes("Linux")) {
|
|
726
|
-
os = "Linux";
|
|
727
|
-
} else if (ua.includes("Android")) {
|
|
728
|
-
os = "Android";
|
|
729
|
-
osVersion = ua.match(/Android ([\d.]+)/)?.[1] || "";
|
|
730
|
-
} else if (ua.includes("iOS") || ua.includes("iPhone") || ua.includes("iPad")) {
|
|
731
|
-
os = "iOS";
|
|
732
|
-
osVersion = ua.match(/OS ([\d_]+)/)?.[1]?.replace(/_/g, ".") || "";
|
|
733
|
-
}
|
|
734
|
-
return { browser, browser_version: browserVersion, os, os_version: osVersion };
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
// src/geolocation/ciphertext.ts
|
|
738
|
-
var CIPHER_TEXT_EXPIRY_MINUTES = 5;
|
|
739
|
-
function getWebGLInfo() {
|
|
740
|
-
if (typeof document === "undefined") return null;
|
|
741
|
-
try {
|
|
742
|
-
const canvas = document.createElement("canvas");
|
|
743
|
-
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
744
|
-
if (!gl) return null;
|
|
745
|
-
const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
|
|
746
|
-
if (!debugInfo) return null;
|
|
747
|
-
return {
|
|
748
|
-
vendor: gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) || "",
|
|
749
|
-
renderer: gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || ""
|
|
750
|
-
};
|
|
751
|
-
} catch {
|
|
752
|
-
return null;
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
function getNetworkInfo() {
|
|
756
|
-
if (typeof navigator === "undefined") return void 0;
|
|
757
|
-
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
758
|
-
if (!connection) return void 0;
|
|
759
|
-
return {
|
|
760
|
-
effective_type: connection.effectiveType,
|
|
761
|
-
downlink: connection.downlink,
|
|
762
|
-
rtt: connection.rtt,
|
|
763
|
-
save_data: connection.saveData
|
|
764
|
-
};
|
|
765
|
-
}
|
|
766
|
-
async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
|
|
767
|
-
if (typeof navigator === "undefined" || !navigator.geolocation) {
|
|
768
|
-
return null;
|
|
769
|
-
}
|
|
770
|
-
return new Promise((resolve) => {
|
|
771
|
-
navigator.geolocation.getCurrentPosition(
|
|
772
|
-
(position) => {
|
|
773
|
-
const { latitude, longitude, accuracy } = position.coords;
|
|
774
|
-
if (latitude < -90 || latitude > 90) {
|
|
775
|
-
resolve(null);
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
if (longitude < -180 || longitude > 180) {
|
|
779
|
-
resolve(null);
|
|
780
|
-
return;
|
|
781
|
-
}
|
|
782
|
-
if (accuracy !== null && accuracy !== void 0 && accuracy <= 0) {
|
|
783
|
-
resolve(null);
|
|
784
|
-
return;
|
|
785
|
-
}
|
|
786
|
-
resolve({
|
|
787
|
-
latitude,
|
|
788
|
-
longitude,
|
|
789
|
-
accuracy,
|
|
790
|
-
altitude: position.coords.altitude ?? void 0,
|
|
791
|
-
altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
|
|
792
|
-
heading: position.coords.heading ?? void 0,
|
|
793
|
-
speed: position.coords.speed ?? void 0,
|
|
794
|
-
timestamp: position.timestamp
|
|
795
|
-
});
|
|
796
|
-
},
|
|
797
|
-
() => {
|
|
798
|
-
resolve(null);
|
|
799
|
-
},
|
|
800
|
-
{
|
|
801
|
-
enableHighAccuracy: highAccuracy,
|
|
802
|
-
timeout,
|
|
803
|
-
maximumAge: 0
|
|
804
|
-
}
|
|
805
|
-
);
|
|
806
|
-
});
|
|
807
|
-
}
|
|
808
|
-
function collectDeviceData(includeWebGL) {
|
|
809
|
-
const browserInfo = getBrowserInfo();
|
|
810
|
-
const webglInfo = includeWebGL ? getWebGLInfo() : null;
|
|
811
|
-
return {
|
|
812
|
-
device_id: generateDeviceId(),
|
|
813
|
-
user_agent: typeof navigator !== "undefined" ? navigator.userAgent : "",
|
|
814
|
-
platform: typeof navigator !== "undefined" ? navigator.platform : "",
|
|
815
|
-
browser: browserInfo.browser,
|
|
816
|
-
browser_version: browserInfo.browser_version,
|
|
817
|
-
os: browserInfo.os,
|
|
818
|
-
os_version: browserInfo.os_version,
|
|
819
|
-
screen_resolution: typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : void 0,
|
|
820
|
-
language: typeof navigator !== "undefined" ? navigator.language : void 0,
|
|
821
|
-
timezone: Intl?.DateTimeFormat?.()?.resolvedOptions?.()?.timeZone,
|
|
822
|
-
color_depth: typeof screen !== "undefined" ? screen.colorDepth : void 0,
|
|
823
|
-
hardware_concurrency: typeof navigator !== "undefined" ? navigator.hardwareConcurrency : void 0,
|
|
824
|
-
device_memory: typeof navigator !== "undefined" ? navigator.deviceMemory : void 0,
|
|
825
|
-
touch_support: typeof navigator !== "undefined" ? navigator.maxTouchPoints > 0 : void 0,
|
|
826
|
-
webgl_vendor: webglInfo?.vendor,
|
|
827
|
-
webgl_renderer: webglInfo?.renderer
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
function encodePayload(payload) {
|
|
831
|
-
const json = JSON.stringify(payload);
|
|
832
|
-
if (typeof btoa !== "undefined") {
|
|
833
|
-
return btoa(unescape(encodeURIComponent(json)));
|
|
834
|
-
} else if (typeof Buffer !== "undefined") {
|
|
835
|
-
return Buffer.from(json, "utf-8").toString("base64");
|
|
836
|
-
}
|
|
837
|
-
throw new Error("No base64 encoding method available");
|
|
838
|
-
}
|
|
839
|
-
async function generateCipherText(options, config) {
|
|
840
|
-
const warnings = [];
|
|
841
|
-
let requestLocation = options.requestLocation ?? false;
|
|
842
|
-
if (config?.require_gps) {
|
|
843
|
-
const reason = options.reason;
|
|
844
|
-
if (reason === "login" && config.require_gps.login || reason === "registration" && config.require_gps.registration || reason === "transaction" && config.require_gps.transaction) {
|
|
845
|
-
requestLocation = true;
|
|
1153
|
+
/**
|
|
1154
|
+
* Generate a signed cipherText containing device and location data.
|
|
1155
|
+
*
|
|
1156
|
+
* Automatically passes the client's API key for HMAC signing (v02 format).
|
|
1157
|
+
* If no API key is configured, falls back to unsigned v01 format.
|
|
1158
|
+
*
|
|
1159
|
+
* @param options - Options for cipherText generation
|
|
1160
|
+
* @param gpsConfig - Optional GPS config (from getGPSConfig)
|
|
1161
|
+
* @returns CipherText result with the signed string
|
|
1162
|
+
*
|
|
1163
|
+
* @example
|
|
1164
|
+
* ```typescript
|
|
1165
|
+
* const result = await client.generateCipherText({ reason: 'login' });
|
|
1166
|
+
* console.log(result.cipherText); // v02 signed cipherText
|
|
1167
|
+
* ```
|
|
1168
|
+
*/
|
|
1169
|
+
async generateCipherText(options, gpsConfig) {
|
|
1170
|
+
let signingKey = this.cachedSigningKey;
|
|
1171
|
+
if (!signingKey) {
|
|
1172
|
+
const config = await this.getGPSConfig();
|
|
1173
|
+
signingKey = config.signing_key;
|
|
846
1174
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
if (requestLocation) {
|
|
851
|
-
const location = await requestGPSLocation(
|
|
852
|
-
options.locationTimeout || 1e4,
|
|
853
|
-
options.highAccuracy !== false
|
|
1175
|
+
return generateCipherText(
|
|
1176
|
+
{ ...options, signingKey: signingKey || void 0 },
|
|
1177
|
+
gpsConfig
|
|
854
1178
|
);
|
|
855
|
-
if (location) {
|
|
856
|
-
locationData = location;
|
|
857
|
-
} else {
|
|
858
|
-
warnings.push("GPS location not available or permission denied");
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
let networkData;
|
|
862
|
-
if (options.includeNetworkInfo !== false) {
|
|
863
|
-
networkData = getNetworkInfo();
|
|
864
|
-
}
|
|
865
|
-
const now = /* @__PURE__ */ new Date();
|
|
866
|
-
const expiry = new Date(now.getTime() + CIPHER_TEXT_EXPIRY_MINUTES * 60 * 1e3);
|
|
867
|
-
const payload = {
|
|
868
|
-
device: deviceData,
|
|
869
|
-
location: locationData,
|
|
870
|
-
network: networkData,
|
|
871
|
-
metadata: {
|
|
872
|
-
collected_at: now.toISOString(),
|
|
873
|
-
sdk_version: SDK_VERSION,
|
|
874
|
-
collection_reason: options.reason,
|
|
875
|
-
page_url: typeof window !== "undefined" ? window.location.href : void 0,
|
|
876
|
-
referrer: typeof document !== "undefined" ? document.referrer || void 0 : void 0
|
|
877
|
-
}
|
|
878
|
-
};
|
|
879
|
-
const encoded = encodePayload(payload);
|
|
880
|
-
const version = "01";
|
|
881
|
-
const timestamp = now.getTime().toString(36);
|
|
882
|
-
const cipherText = `${version}.${timestamp}.${encoded}`;
|
|
883
|
-
return {
|
|
884
|
-
cipherText,
|
|
885
|
-
locationCaptured: !!locationData,
|
|
886
|
-
warnings: warnings.length > 0 ? warnings : void 0,
|
|
887
|
-
generatedAt: now.toISOString(),
|
|
888
|
-
expiresAt: expiry.toISOString()
|
|
889
|
-
};
|
|
890
|
-
}
|
|
891
|
-
function decodeCipherText(cipherText) {
|
|
892
|
-
try {
|
|
893
|
-
const parts = cipherText.split(".");
|
|
894
|
-
if (parts.length !== 3) return null;
|
|
895
|
-
const [version, , encodedB64] = parts;
|
|
896
|
-
if (version !== "01") return null;
|
|
897
|
-
const json = decodeURIComponent(escape(atob(encodedB64)));
|
|
898
|
-
return JSON.parse(json);
|
|
899
|
-
} catch {
|
|
900
|
-
return null;
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
function isCipherTextExpired(cipherText) {
|
|
904
|
-
try {
|
|
905
|
-
const parts = cipherText.split(".");
|
|
906
|
-
if (parts.length !== 3) return true;
|
|
907
|
-
const timestampB36 = parts[1];
|
|
908
|
-
const timestamp = parseInt(timestampB36, 36);
|
|
909
|
-
const now = Date.now();
|
|
910
|
-
const expiryMs = CIPHER_TEXT_EXPIRY_MINUTES * 60 * 1e3;
|
|
911
|
-
return now - timestamp > expiryMs;
|
|
912
|
-
} catch {
|
|
913
|
-
return true;
|
|
914
1179
|
}
|
|
915
|
-
|
|
1180
|
+
// ============================================================================
|
|
1181
|
+
// Utility Methods (inherited from BaseClient: healthCheck, updateConfig, getConfig, buildQueryString)
|
|
1182
|
+
// ============================================================================
|
|
1183
|
+
};
|
|
916
1184
|
|
|
917
1185
|
exports.GeolocationClient = GeolocationClient;
|
|
918
1186
|
exports.decodeCipherText = decodeCipherText;
|