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.
Files changed (71) hide show
  1. package/dist/{client-i5QtnFIa.d.ts → client-B6fUFAUM.d.mts} +26 -1
  2. package/dist/{client-BfeDYmrZ.d.mts → client-DoczGA6L.d.ts} +26 -1
  3. package/dist/client-DzElM7u-.d.mts +238 -0
  4. package/dist/client-DzElM7u-.d.ts +238 -0
  5. package/dist/compliance/index.d.mts +5 -4
  6. package/dist/compliance/index.d.ts +5 -4
  7. package/dist/compliance/index.js +522 -30
  8. package/dist/compliance/index.js.map +1 -1
  9. package/dist/compliance/index.mjs +522 -30
  10. package/dist/compliance/index.mjs.map +1 -1
  11. package/dist/decisions/index.d.mts +100 -0
  12. package/dist/decisions/index.d.ts +100 -0
  13. package/dist/decisions/index.js +607 -0
  14. package/dist/decisions/index.js.map +1 -0
  15. package/dist/decisions/index.mjs +605 -0
  16. package/dist/decisions/index.mjs.map +1 -0
  17. package/dist/geolocation/index.d.mts +4 -3
  18. package/dist/geolocation/index.d.ts +4 -3
  19. package/dist/geolocation/index.js +546 -278
  20. package/dist/geolocation/index.js.map +1 -1
  21. package/dist/geolocation/index.mjs +546 -278
  22. package/dist/geolocation/index.mjs.map +1 -1
  23. package/dist/index.d.mts +14 -6
  24. package/dist/index.d.ts +14 -6
  25. package/dist/index.js +946 -335
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +937 -336
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/kyc/core.d.mts +3 -2
  30. package/dist/kyc/core.d.ts +3 -2
  31. package/dist/kyc/core.js +249 -29
  32. package/dist/kyc/core.js.map +1 -1
  33. package/dist/kyc/core.mjs +249 -29
  34. package/dist/kyc/core.mjs.map +1 -1
  35. package/dist/kyc/index.d.mts +3 -2
  36. package/dist/kyc/index.d.ts +3 -2
  37. package/dist/kyc/index.js +249 -29
  38. package/dist/kyc/index.js.map +1 -1
  39. package/dist/kyc/index.mjs +249 -29
  40. package/dist/kyc/index.mjs.map +1 -1
  41. package/dist/react.d.mts +4 -3
  42. package/dist/react.d.ts +4 -3
  43. package/dist/react.js +29 -3
  44. package/dist/react.js.map +1 -1
  45. package/dist/react.mjs +29 -3
  46. package/dist/react.mjs.map +1 -1
  47. package/dist/risk-profile/index.d.mts +4 -4
  48. package/dist/risk-profile/index.d.ts +4 -4
  49. package/dist/risk-profile/index.js +249 -29
  50. package/dist/risk-profile/index.js.map +1 -1
  51. package/dist/risk-profile/index.mjs +249 -29
  52. package/dist/risk-profile/index.mjs.map +1 -1
  53. package/dist/scores/index.d.mts +96 -0
  54. package/dist/scores/index.d.ts +96 -0
  55. package/dist/scores/index.js +594 -0
  56. package/dist/scores/index.js.map +1 -0
  57. package/dist/scores/index.mjs +591 -0
  58. package/dist/scores/index.mjs.map +1 -0
  59. package/dist/{types-DfHLp_tz.d.ts → types-DLC7Sfy5.d.ts} +1 -1
  60. package/dist/types-DZHongaK.d.mts +61 -0
  61. package/dist/types-DZHongaK.d.ts +61 -0
  62. package/dist/{types-DKCQN4C5.d.mts → types-jaLuzruy.d.mts} +1 -1
  63. package/dist/webhooks/index.d.mts +176 -0
  64. package/dist/webhooks/index.d.ts +176 -0
  65. package/dist/webhooks/index.js +193 -0
  66. package/dist/webhooks/index.js.map +1 -0
  67. package/dist/webhooks/index.mjs +188 -0
  68. package/dist/webhooks/index.mjs.map +1 -0
  69. package/package.json +16 -1
  70. package/dist/types-BpKxSXGF.d.mts +0 -177
  71. 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.1.0";
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
- baseURL: config.baseURL,
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 url = `${serviceURL || this.config.baseURL}${endpoint}`;
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
- throw new CGSError("Request aborted", "REQUEST_ABORTED");
433
+ const abortError = new CGSError("Request aborted", "REQUEST_ABORTED");
434
+ abortError.requestId = requestId;
435
+ throw abortError;
184
436
  }
185
- throw new TimeoutError(this.config.timeout);
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
- throw new NetworkError("Network request failed", error);
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
- switch (status) {
233
- case 400:
234
- throw new CGSError(message, "BAD_REQUEST", 400, data);
235
- case 401:
236
- throw new AuthenticationError(message);
237
- case 403:
238
- throw new CGSError(message, "FORBIDDEN", 403, data);
239
- case 404:
240
- throw new CGSError(message, "NOT_FOUND", 404, data);
241
- case 429: {
242
- const retryAfter = data.retry_after || data.retryAfter;
243
- throw new RateLimitError(retryAfter);
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
- case 500:
246
- case 502:
247
- case 503:
248
- case 504:
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
- return this.requestWithRetry("/api/v1/geo/config", void 0, void 0, void 0, requestOptions);
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
- // Utility Methods (inherited from BaseClient: healthCheck, updateConfig, getConfig, buildQueryString)
1151
+ // CipherText Generation
665
1152
  // ============================================================================
666
- };
667
-
668
- // src/shared/browser-utils.ts
669
- function generateUUID() {
670
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
671
- return crypto.randomUUID();
672
- }
673
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
674
- const r = Math.random() * 16 | 0;
675
- const v = c === "x" ? r : r & 3 | 8;
676
- return v.toString(16);
677
- });
678
- }
679
- function generateDeviceId() {
680
- if (typeof window === "undefined" || typeof localStorage === "undefined") {
681
- return generateUUID();
682
- }
683
- const storageKey = "cgs_device_id";
684
- let deviceId = localStorage.getItem(storageKey);
685
- if (!deviceId) {
686
- deviceId = generateUUID();
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
- const deviceData = collectDeviceData(options.includeWebGL !== false);
849
- let locationData;
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;