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.
Files changed (75) hide show
  1. package/dist/{client-DtH2RLuy.d.ts → client-B3DCAHlg.d.ts} +2 -2
  2. package/dist/{client-DrjgZoH_.d.mts → client-BwXMQA2a.d.mts} +64 -42
  3. package/dist/{client-B0qhE2kr.d.mts → client-D23KhWeh.d.mts} +2 -2
  4. package/dist/{client-DF7hlMEz.d.ts → client-fy3trF2K.d.ts} +64 -42
  5. package/dist/{client-BolQlL5e.d.mts → client-q9fN8Hhf.d.mts} +103 -1
  6. package/dist/{client-BolQlL5e.d.ts → client-q9fN8Hhf.d.ts} +103 -1
  7. package/dist/compliance/index.d.mts +3 -3
  8. package/dist/compliance/index.d.ts +3 -3
  9. package/dist/compliance/index.js +181 -91
  10. package/dist/compliance/index.js.map +1 -1
  11. package/dist/compliance/index.mjs +181 -91
  12. package/dist/compliance/index.mjs.map +1 -1
  13. package/dist/decisions/index.d.mts +1 -1
  14. package/dist/decisions/index.d.ts +1 -1
  15. package/dist/decisions/index.js +69 -8
  16. package/dist/decisions/index.js.map +1 -1
  17. package/dist/decisions/index.mjs +69 -8
  18. package/dist/decisions/index.mjs.map +1 -1
  19. package/dist/geolocation/index.d.mts +68 -7
  20. package/dist/geolocation/index.d.ts +68 -7
  21. package/dist/geolocation/index.js +186 -91
  22. package/dist/geolocation/index.js.map +1 -1
  23. package/dist/geolocation/index.mjs +183 -92
  24. package/dist/geolocation/index.mjs.map +1 -1
  25. package/dist/index-C3zUKydT.d.mts +264 -0
  26. package/dist/index-DSDgS09k.d.ts +264 -0
  27. package/dist/index.d.mts +26 -9
  28. package/dist/index.d.ts +26 -9
  29. package/dist/index.js +354 -120
  30. package/dist/index.js.map +1 -1
  31. package/dist/index.mjs +346 -121
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/kyc/core.d.mts +1 -1
  34. package/dist/kyc/core.d.ts +1 -1
  35. package/dist/kyc/core.js +75 -14
  36. package/dist/kyc/core.js.map +1 -1
  37. package/dist/kyc/core.mjs +75 -14
  38. package/dist/kyc/core.mjs.map +1 -1
  39. package/dist/kyc/index.d.mts +4 -4
  40. package/dist/kyc/index.d.ts +4 -4
  41. package/dist/kyc/index.js +75 -14
  42. package/dist/kyc/index.js.map +1 -1
  43. package/dist/kyc/index.mjs +75 -14
  44. package/dist/kyc/index.mjs.map +1 -1
  45. package/dist/react.d.mts +7 -3
  46. package/dist/react.d.ts +7 -3
  47. package/dist/react.js +70 -33
  48. package/dist/react.js.map +1 -1
  49. package/dist/react.mjs +70 -33
  50. package/dist/react.mjs.map +1 -1
  51. package/dist/risk-profile/index.d.mts +1 -1
  52. package/dist/risk-profile/index.d.ts +1 -1
  53. package/dist/risk-profile/index.js +69 -8
  54. package/dist/risk-profile/index.js.map +1 -1
  55. package/dist/risk-profile/index.mjs +69 -8
  56. package/dist/risk-profile/index.mjs.map +1 -1
  57. package/dist/scores/index.d.mts +1 -1
  58. package/dist/scores/index.d.ts +1 -1
  59. package/dist/scores/index.js +69 -8
  60. package/dist/scores/index.js.map +1 -1
  61. package/dist/scores/index.mjs +69 -8
  62. package/dist/scores/index.mjs.map +1 -1
  63. package/dist/tax/index.d.mts +2 -2
  64. package/dist/tax/index.d.ts +2 -2
  65. package/dist/tax/index.js +74 -10
  66. package/dist/tax/index.js.map +1 -1
  67. package/dist/tax/index.mjs +74 -10
  68. package/dist/tax/index.mjs.map +1 -1
  69. package/dist/webhooks/index.d.mts +2 -189
  70. package/dist/webhooks/index.d.ts +2 -189
  71. package/dist/webhooks/index.js +85 -21
  72. package/dist/webhooks/index.js.map +1 -1
  73. package/dist/webhooks/index.mjs +85 -22
  74. package/dist/webhooks/index.mjs.map +1 -1
  75. package/package.json +4 -1
package/dist/index.mjs CHANGED
@@ -1,3 +1,59 @@
1
+ // src/core/auth.ts
2
+ var StaticApiKeyProvider = class {
3
+ constructor(apiKey) {
4
+ this.apiKey = apiKey;
5
+ }
6
+ async getCredential() {
7
+ return this.apiKey ? { scheme: "Bearer", token: this.apiKey } : null;
8
+ }
9
+ canRefresh() {
10
+ return false;
11
+ }
12
+ };
13
+ var RefreshingTokenProvider = class {
14
+ constructor(opts) {
15
+ this.opts = opts;
16
+ this.cached = null;
17
+ this.inFlight = null;
18
+ this.skewMs = opts.skewMs ?? 3e4;
19
+ this.scheme = opts.scheme ?? "Bearer";
20
+ this.now = opts.now ?? (() => Date.now());
21
+ }
22
+ canRefresh() {
23
+ return true;
24
+ }
25
+ async getCredential(ctx) {
26
+ const forceRefresh = ctx?.forceRefresh ?? false;
27
+ if (!forceRefresh && this.cached && !this.isExpiring(this.cached)) {
28
+ return { scheme: this.scheme, token: this.cached.token };
29
+ }
30
+ const fresh = await this.refresh();
31
+ return { scheme: this.scheme, token: fresh.token };
32
+ }
33
+ isExpiring(token) {
34
+ if (token.expiresAt === void 0) {
35
+ return false;
36
+ }
37
+ return this.now() >= token.expiresAt - this.skewMs;
38
+ }
39
+ refresh() {
40
+ if (this.inFlight) {
41
+ return this.inFlight;
42
+ }
43
+ this.inFlight = (async () => {
44
+ try {
45
+ const fresh = await this.opts.fetchToken();
46
+ this.cached = fresh;
47
+ this.opts.onRotate?.({ expiresAt: fresh.expiresAt });
48
+ return fresh;
49
+ } finally {
50
+ this.inFlight = null;
51
+ }
52
+ })();
53
+ return this.inFlight;
54
+ }
55
+ };
56
+
1
57
  // src/core/errors.ts
2
58
  var VesantError = class _VesantError extends Error {
3
59
  constructor(message, code, statusCode, details) {
@@ -240,7 +296,7 @@ var noopLogger = {
240
296
  };
241
297
 
242
298
  // src/core/version.ts
243
- var SDK_VERSION = "1.7.0";
299
+ var SDK_VERSION = "1.7.1";
244
300
 
245
301
  // src/shared/browser-utils.ts
246
302
  function generateUUID() {
@@ -322,6 +378,12 @@ var BaseClient = class {
322
378
  if (!config.tenantId?.trim()) {
323
379
  throw new ValidationError("tenantId is required and must be a non-empty string", ["tenantId"]);
324
380
  }
381
+ if (typeof fetch === "undefined") {
382
+ throw new VesantError(
383
+ "The Vesant SDK requires a global fetch (Node.js >= 18). Upgrade Node or provide a fetch polyfill.",
384
+ "RUNTIME_UNSUPPORTED"
385
+ );
386
+ }
325
387
  this.interceptors = config.interceptors || [];
326
388
  this.logger = config.logger || createConsoleLogger();
327
389
  let environment = config.environment;
@@ -340,11 +402,12 @@ var BaseClient = class {
340
402
  environment,
341
403
  headers: config.headers || {},
342
404
  timeout: config.timeout || 1e4,
343
- retries: config.retries || 3,
405
+ retries: config.retries ?? 3,
344
406
  debug: config.debug || false,
345
407
  interceptors: this.interceptors,
346
408
  logger: this.logger
347
409
  };
410
+ this.authProvider = config.authProvider ?? (apiKey ? new StaticApiKeyProvider(apiKey) : void 0);
348
411
  if (config.circuitBreaker) {
349
412
  this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
350
413
  }
@@ -352,6 +415,19 @@ var BaseClient = class {
352
415
  this.rateLimitTracker = new RateLimitTracker();
353
416
  }
354
417
  }
418
+ /**
419
+ * Resolve the Idempotency-Key for a request. Mutating methods (POST/PUT/PATCH)
420
+ * get the caller-supplied key or a freshly minted UUID; non-mutating methods get
421
+ * none. requestWithRetry resolves this once and injects it so every retry reuses
422
+ * one key (letting the server dedup replays); a direct request() resolves per call.
423
+ */
424
+ resolveIdempotencyKey(method, requestOptions) {
425
+ const isMutating = ["POST", "PUT", "PATCH"].includes((method || "GET").toUpperCase());
426
+ if (!isMutating) {
427
+ return void 0;
428
+ }
429
+ return requestOptions?.idempotencyKey ?? generateUUID();
430
+ }
355
431
  /**
356
432
  * Make an HTTP request with timeout and error handling
357
433
  */
@@ -377,15 +453,20 @@ var BaseClient = class {
377
453
  ...this.config.headers,
378
454
  ...options.headers || {}
379
455
  };
380
- if (this.config.apiKey) {
381
- headers["Authorization"] = `Bearer ${this.config.apiKey}`;
456
+ const credential = await this.authProvider?.getCredential({
457
+ tenantId: this.config.tenantId,
458
+ requestId
459
+ });
460
+ if (credential) {
461
+ headers["Authorization"] = `${credential.scheme ?? "Bearer"} ${credential.token}`;
382
462
  }
383
463
  if (this.config.environment === "sandbox") {
384
464
  headers["X-Sandbox"] = "true";
385
465
  }
386
466
  const method = (options.method || "GET").toUpperCase();
387
- if (["POST", "PUT", "PATCH"].includes(method)) {
388
- headers["Idempotency-Key"] = requestOptions?.idempotencyKey || generateUUID();
467
+ const idempotencyKey = this.resolveIdempotencyKey(method, requestOptions);
468
+ if (idempotencyKey) {
469
+ headers["Idempotency-Key"] = idempotencyKey;
389
470
  }
390
471
  const controller = new AbortController();
391
472
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
@@ -406,10 +487,28 @@ var BaseClient = class {
406
487
  if (this.config.debug) {
407
488
  this.logger.debug(`${finalOptions.method || "GET"} ${endpoint}`);
408
489
  }
409
- const response = await fetch(url, {
490
+ let response = await fetch(url, {
410
491
  ...finalOptions,
411
492
  signal: controller.signal
412
493
  });
494
+ if (response.status === 401 && this.authProvider?.canRefresh?.()) {
495
+ const refreshed = await this.authProvider.getCredential({
496
+ tenantId: this.config.tenantId,
497
+ requestId,
498
+ forceRefresh: true
499
+ });
500
+ if (refreshed) {
501
+ this.config.onCredentialRotated?.({ reason: "unauthorized" });
502
+ finalOptions = {
503
+ ...finalOptions,
504
+ headers: {
505
+ ...finalOptions.headers,
506
+ Authorization: `${refreshed.scheme ?? "Bearer"} ${refreshed.token}`
507
+ }
508
+ };
509
+ response = await fetch(url, { ...finalOptions, signal: controller.signal });
510
+ }
511
+ }
413
512
  clearTimeout(timeoutId);
414
513
  if (this.rateLimitTracker) {
415
514
  this.rateLimitTracker.updateFromHeaders(response.headers);
@@ -498,9 +597,11 @@ var BaseClient = class {
498
597
  */
499
598
  async requestWithRetry(endpoint, options = {}, serviceURL, retries = this.config.retries, requestOptions) {
500
599
  let lastError;
600
+ const idempotencyKey = this.resolveIdempotencyKey(options.method || "GET", requestOptions);
601
+ const attemptOptions = idempotencyKey ? { ...requestOptions, idempotencyKey } : requestOptions;
501
602
  for (let attempt = 0; attempt <= retries; attempt++) {
502
603
  try {
503
- return await this.request(endpoint, options, serviceURL, requestOptions);
604
+ return await this.request(endpoint, options, serviceURL, attemptOptions);
504
605
  } catch (error) {
505
606
  lastError = error instanceof Error ? error : new Error("Unknown error");
506
607
  if (requestOptions?.signal?.aborted) {
@@ -597,6 +698,9 @@ var BaseClient = class {
597
698
  if (config.interceptors) {
598
699
  this.interceptors = config.interceptors;
599
700
  }
701
+ if (config.authProvider !== void 0 || config.apiKey !== void 0) {
702
+ this.authProvider = this.config.authProvider ?? (this.config.apiKey ? new StaticApiKeyProvider(this.config.apiKey) : void 0);
703
+ }
600
704
  }
601
705
  /**
602
706
  * Get current configuration (readonly)
@@ -624,8 +728,69 @@ var BaseClient = class {
624
728
  }
625
729
  };
626
730
 
731
+ // src/core/pagination.ts
732
+ async function* paginate(fetchPage) {
733
+ let page = 1;
734
+ let totalPages;
735
+ do {
736
+ const response = await fetchPage(page);
737
+ const items = response.data ?? [];
738
+ for (const item of items) {
739
+ yield item;
740
+ }
741
+ totalPages = response.total_pages ?? page;
742
+ if (items.length === 0) {
743
+ break;
744
+ }
745
+ page += 1;
746
+ } while (page <= totalPages);
747
+ }
748
+ async function collectAll(fetchPage) {
749
+ const items = [];
750
+ for await (const item of paginate(fetchPage)) {
751
+ items.push(item);
752
+ }
753
+ return items;
754
+ }
755
+
756
+ // src/core/dedup.ts
757
+ var InMemoryDedupStore = class {
758
+ constructor(opts = {}) {
759
+ this.expiryById = /* @__PURE__ */ new Map();
760
+ this.maxEntries = opts.maxEntries ?? 1e3;
761
+ this.now = opts.now ?? (() => Date.now());
762
+ }
763
+ async seen(id) {
764
+ return this.expiryById.has(id);
765
+ }
766
+ async mark(id, ttlMs) {
767
+ const now = this.now();
768
+ this.expiryById.set(id, now + ttlMs);
769
+ if (this.expiryById.size > this.maxEntries) {
770
+ for (const [key, expiresAt] of this.expiryById) {
771
+ if (now > expiresAt) {
772
+ this.expiryById.delete(key);
773
+ }
774
+ }
775
+ }
776
+ }
777
+ };
778
+
627
779
  // src/core/webhook-utils.ts
628
- async function verifyWebhookSignature(payload, signature, secret) {
780
+ async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
781
+ const signed = parseSignedTimestamp(signature);
782
+ if (signed) {
783
+ const tolerance = opts.tolerance ?? 3e5;
784
+ const now = opts.now?.() ?? Date.now();
785
+ if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
786
+ return false;
787
+ }
788
+ const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
789
+ return constantTimeEqual(signed.v1, expected);
790
+ }
791
+ if (opts.allowLegacy === false) {
792
+ return false;
793
+ }
629
794
  const hexDigest = await computeHmacSha256(payload, secret);
630
795
  const expectedPrefixed = `sha256=${hexDigest}`;
631
796
  if (signature.startsWith("sha256=")) {
@@ -633,6 +798,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
633
798
  }
634
799
  return constantTimeEqual(signature, hexDigest);
635
800
  }
801
+ function parseSignedTimestamp(signature) {
802
+ if (!signature.includes("t=") || !signature.includes("v1=")) {
803
+ return null;
804
+ }
805
+ let t;
806
+ let v1;
807
+ for (const part of signature.split(",")) {
808
+ const eq = part.indexOf("=");
809
+ if (eq === -1) continue;
810
+ const key = part.slice(0, eq).trim();
811
+ const value = part.slice(eq + 1).trim();
812
+ if (key === "t") t = Number(value);
813
+ else if (key === "v1") v1 = value;
814
+ }
815
+ if (t === void 0 || Number.isNaN(t) || !v1) {
816
+ return null;
817
+ }
818
+ return { t, v1 };
819
+ }
636
820
  async function computeHmacSha256(message, secret) {
637
821
  if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
638
822
  const encoder = new TextEncoder();
@@ -667,27 +851,63 @@ function constantTimeEqual(a, b) {
667
851
  return result === 0;
668
852
  }
669
853
 
670
- // src/geolocation/ciphertext.ts
671
- var CIPHER_TEXT_EXPIRY_MINUTES = 5;
672
- async function computeHMAC(key, message) {
673
- if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
674
- const encoder = new TextEncoder();
675
- const keyData = encoder.encode(key);
676
- const msgData = encoder.encode(message);
677
- const cryptoKey = await globalThis.crypto.subtle.importKey(
678
- "raw",
679
- keyData,
680
- { name: "HMAC", hash: "SHA-256" },
681
- false,
682
- ["sign"]
683
- );
684
- const signature = await globalThis.crypto.subtle.sign("HMAC", cryptoKey, msgData);
685
- const bytes = new Uint8Array(signature);
686
- return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
854
+ // src/geolocation/integrity.ts
855
+ var isNil = (v) => v === null || v === void 0;
856
+ function assessGpsIntegrity(input, opts = {}) {
857
+ const flags = [];
858
+ const now = opts.now ?? (() => Date.now());
859
+ const maxAgeMs = opts.maxAgeMs ?? 6e4;
860
+ const minAccuracy = opts.minAccuracyMeters ?? 1;
861
+ const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
862
+ if (isNil(accuracy)) {
863
+ flags.push("accuracy_missing");
864
+ } else if (accuracy === 0) {
865
+ flags.push("accuracy_zero");
866
+ } else if (accuracy < minAccuracy) {
867
+ flags.push("accuracy_implausibly_small");
868
+ }
869
+ const hasMotion = !isNil(heading) || !isNil(speed);
870
+ if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
871
+ flags.push("motion_without_accuracy");
872
+ }
873
+ if (!isNil(altitude) && isNil(altitudeAccuracy)) {
874
+ flags.push("altitude_without_accuracy");
875
+ }
876
+ if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
877
+ flags.push("stale_timestamp");
878
+ }
879
+ if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
880
+ flags.push(`permission_${opts.permissionState}`);
881
+ }
882
+ return { suspicious: flags.length > 0, flags };
883
+ }
884
+ function gpsIntegrityInputFromPosition(position) {
885
+ const c = position.coords;
886
+ return {
887
+ latitude: c.latitude,
888
+ longitude: c.longitude,
889
+ accuracy: c.accuracy,
890
+ altitude: c.altitude,
891
+ altitudeAccuracy: c.altitudeAccuracy,
892
+ heading: c.heading,
893
+ speed: c.speed,
894
+ timestamp: position.timestamp
895
+ };
896
+ }
897
+ async function probeGeolocationPermission() {
898
+ try {
899
+ if (typeof navigator === "undefined" || !navigator.permissions?.query) {
900
+ return "unavailable";
901
+ }
902
+ const status = await navigator.permissions.query({ name: "geolocation" });
903
+ return status.state;
904
+ } catch {
905
+ return "unavailable";
687
906
  }
688
- const { createHmac } = await import('crypto');
689
- return createHmac("sha256", key).update(message).digest("hex");
690
907
  }
908
+
909
+ // src/geolocation/ciphertext.ts
910
+ var CIPHER_TEXT_EXPIRY_MINUTES = 5;
691
911
  function getWebGLInfo() {
692
912
  if (typeof document === "undefined") return null;
693
913
  try {
@@ -719,6 +939,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
719
939
  if (typeof navigator === "undefined" || !navigator.geolocation) {
720
940
  return null;
721
941
  }
942
+ const permissionState = await probeGeolocationPermission();
722
943
  return new Promise((resolve) => {
723
944
  navigator.geolocation.getCurrentPosition(
724
945
  (position) => {
@@ -735,6 +956,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
735
956
  resolve(null);
736
957
  return;
737
958
  }
959
+ const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
738
960
  resolve({
739
961
  latitude,
740
962
  longitude,
@@ -743,7 +965,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
743
965
  altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
744
966
  heading: position.coords.heading ?? void 0,
745
967
  speed: position.coords.speed ?? void 0,
746
- timestamp: position.timestamp
968
+ timestamp: position.timestamp,
969
+ integrity_flags: flags.length ? flags : void 0
747
970
  });
748
971
  },
749
972
  () => {
@@ -810,6 +1033,9 @@ async function generateCipherText(options, config) {
810
1033
  );
811
1034
  if (location) {
812
1035
  locationData = location;
1036
+ if (location.integrity_flags?.length) {
1037
+ warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
1038
+ }
813
1039
  } else if (gpsRequiredByConfig) {
814
1040
  throw new VesantError(
815
1041
  `GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
@@ -839,15 +1065,7 @@ async function generateCipherText(options, config) {
839
1065
  };
840
1066
  const encoded = encodePayload(payload);
841
1067
  const timestamp = now.getTime().toString(36);
842
- let cipherText;
843
- const hmacKey = options.signingKey || options.apiKey;
844
- if (hmacKey) {
845
- const message = `02.${timestamp}.${encoded}`;
846
- const hmac = await computeHMAC(hmacKey, message);
847
- cipherText = `${message}.${hmac}`;
848
- } else {
849
- cipherText = `01.${timestamp}.${encoded}`;
850
- }
1068
+ const cipherText = `01.${timestamp}.${encoded}`;
851
1069
  return {
852
1070
  cipherText,
853
1071
  locationCaptured: !!locationData,
@@ -856,6 +1074,7 @@ async function generateCipherText(options, config) {
856
1074
  expiresAt: expiry.toISOString()
857
1075
  };
858
1076
  }
1077
+ var collectDeviceSignals = generateCipherText;
859
1078
  function decodeCipherText(cipherText) {
860
1079
  try {
861
1080
  const parts = cipherText.split(".");
@@ -929,8 +1148,8 @@ var GeolocationClient = class extends BaseClient {
929
1148
  * ```
930
1149
  */
931
1150
  async verifyIP(request, requestOptions) {
932
- if (!request.ip_address?.trim()) {
933
- throw new ValidationError("ip_address is required and must be a non-empty string", ["ip_address"]);
1151
+ if (request.ip_address !== void 0 && !request.ip_address.trim()) {
1152
+ throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
934
1153
  }
935
1154
  return this.requestWithRetry("/api/v1/geo/verify", {
936
1155
  method: "POST",
@@ -983,37 +1202,50 @@ var GeolocationClient = class extends BaseClient {
983
1202
  * ```
984
1203
  */
985
1204
  async getGPSConfig(requestOptions) {
986
- const config = await this.requestWithRetry("/api/v1/geo/config", void 0, void 0, void 0, requestOptions);
987
- if (config.signing_key) {
988
- this.cachedSigningKey = config.signing_key;
989
- }
990
- return config;
1205
+ return this.requestWithRetry("/api/v1/geo/config", void 0, void 0, void 0, requestOptions);
991
1206
  }
992
1207
  /**
993
- * Fetch the signing key from the dedicated authenticated endpoint.
1208
+ * Mint a short-lived, single-use device-signals nonce.
994
1209
  *
995
- * Falls back to getGPSConfig() for backward compatibility with older servers
996
- * that don't expose `/api/v1/geo/signing-key`.
1210
+ * The nonce is bound to the tenant, user, and event type and is consumed
1211
+ * server-side during validation, so a captured device-signals envelope cannot
1212
+ * be replayed. `validateCipherText` mints and attaches one automatically; call
1213
+ * this directly only if you manage the nonce yourself.
997
1214
  *
998
- * @returns The signing key string, or undefined if unavailable
1215
+ * @param userId - User ID the nonce is bound to
1216
+ * @param eventType - Event type the nonce is bound to (login, registration, ...)
1217
+ * @returns The nonce and its expiry
1218
+ */
1219
+ async fetchSignalsNonce(userId, eventType, requestOptions) {
1220
+ if (!userId?.trim()) {
1221
+ throw new ValidationError("userId is required and must be a non-empty string", ["userId"]);
1222
+ }
1223
+ const request = { user_id: userId, event_type: eventType };
1224
+ return this.request(
1225
+ "/api/v1/geo/signals-token",
1226
+ {
1227
+ method: "POST",
1228
+ body: JSON.stringify(request)
1229
+ },
1230
+ void 0,
1231
+ requestOptions
1232
+ );
1233
+ }
1234
+ /**
1235
+ * Best-effort nonce mint for validation. Returns undefined if minting fails —
1236
+ * the server is the sole authority and blocks when a nonce is required, so this
1237
+ * never downgrades security on the client's say-so.
999
1238
  */
1000
- async fetchSigningKey(requestOptions) {
1239
+ async resolveSignalsNonce(userId, eventType, requestOptions) {
1001
1240
  try {
1002
- const response = await this.requestWithRetry(
1003
- "/api/v1/geo/signing-key",
1004
- void 0,
1005
- void 0,
1006
- void 0,
1007
- requestOptions
1241
+ const { nonce } = await this.fetchSignalsNonce(userId, eventType, requestOptions);
1242
+ return nonce;
1243
+ } catch (err) {
1244
+ this.logger.warn(
1245
+ `Failed to mint device-signals nonce; proceeding without it: ${err instanceof Error ? err.message : String(err)}`
1008
1246
  );
1009
- if (response.signing_key) {
1010
- this.cachedSigningKey = response.signing_key;
1011
- return response.signing_key;
1012
- }
1013
- } catch {
1247
+ return void 0;
1014
1248
  }
1015
- const config = await this.getGPSConfig(requestOptions);
1016
- return config.signing_key;
1017
1249
  }
1018
1250
  // ============================================================================
1019
1251
  // CipherText Validation
@@ -1066,12 +1298,14 @@ var GeolocationClient = class extends BaseClient {
1066
1298
  if (!userId?.trim()) {
1067
1299
  throw new ValidationError("userId is required and must be a non-empty string", ["userId"]);
1068
1300
  }
1301
+ const nonce = await this.resolveSignalsNonce(userId, eventType, requestOptions);
1069
1302
  const request = {
1070
1303
  cipher_text: cipherText,
1071
1304
  user_id: userId,
1072
1305
  event_type: eventType,
1073
1306
  expected_ip: expectedIP,
1074
- customer_data: customerData
1307
+ customer_data: customerData,
1308
+ nonce
1075
1309
  };
1076
1310
  return this.requestWithRetry(
1077
1311
  "/api/v1/geo/validate-ciphertext",
@@ -1280,20 +1514,12 @@ var GeolocationClient = class extends BaseClient {
1280
1514
  *
1281
1515
  * @example
1282
1516
  * ```typescript
1283
- * // Using GPS (preferred)
1517
+ * // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
1284
1518
  * const result = await client.captureLocation(token, {
1285
1519
  * latitude: 37.7749,
1286
1520
  * longitude: -122.4194,
1287
1521
  * accuracy: 10
1288
1522
  * });
1289
- *
1290
- * // Using WiFi positioning (fallback)
1291
- * const result = await client.captureLocation(token, {
1292
- * wifi_networks: [
1293
- * { macAddress: "00:11:22:33:44:55", signalStrength: -50 },
1294
- * { macAddress: "AA:BB:CC:DD:EE:FF", signalStrength: -70 }
1295
- * ]
1296
- * });
1297
1523
  * ```
1298
1524
  */
1299
1525
  async captureLocation(token, capture, requestOptions) {
@@ -1306,34 +1532,22 @@ var GeolocationClient = class extends BaseClient {
1306
1532
  // CipherText Generation
1307
1533
  // ============================================================================
1308
1534
  /**
1309
- * Generate a signed cipherText containing device and location data.
1535
+ * Generate a device-signals envelope containing device and location data,
1536
+ * validated by the server.
1310
1537
  *
1311
- * Automatically passes the client's API key for HMAC signing (v02 format).
1312
- * If no API key is configured, falls back to unsigned v01 format.
1313
- *
1314
- * @param options - Options for cipherText generation
1538
+ * @param options - Options for envelope generation
1315
1539
  * @param gpsConfig - Optional GPS config (from getGPSConfig)
1316
- * @returns CipherText result with the signed string
1540
+ * @returns The device-signals envelope result
1317
1541
  *
1318
1542
  * @example
1319
1543
  * ```typescript
1320
1544
  * const result = await client.generateCipherText({ reason: 'login' });
1321
- * console.log(result.cipherText); // v02 signed cipherText
1545
+ * console.log(result.cipherText); // device-signals envelope
1322
1546
  * ```
1323
1547
  */
1324
1548
  async generateCipherText(options, gpsConfig) {
1325
- let signingKey = this.cachedSigningKey;
1326
- let resolvedGpsConfig = gpsConfig;
1327
- if (!signingKey) {
1328
- signingKey = await this.fetchSigningKey();
1329
- }
1330
- if (!resolvedGpsConfig) {
1331
- resolvedGpsConfig = await this.getGPSConfig();
1332
- }
1333
- return generateCipherText(
1334
- { ...options, signingKey: signingKey || void 0 },
1335
- resolvedGpsConfig
1336
- );
1549
+ const resolvedGpsConfig = gpsConfig ?? await this.getGPSConfig();
1550
+ return generateCipherText(options, resolvedGpsConfig);
1337
1551
  }
1338
1552
  // ============================================================================
1339
1553
  // Utility Methods (inherited from BaseClient: healthCheck, updateConfig, getConfig, buildQueryString)
@@ -2736,12 +2950,12 @@ var KycClient = class extends BaseClient {
2736
2950
  * console.log(`KYC ID: ${result.kyc_id}`);
2737
2951
  * ```
2738
2952
  */
2739
- async requestKycSubmitLink(request) {
2953
+ async requestKycSubmitLink(request, requestOptions) {
2740
2954
  return this.requestWithRetry("/api/v1/kyc/request", {
2741
2955
  method: "POST",
2742
2956
  body: JSON.stringify(request),
2743
2957
  headers: this.getUserHeaders()
2744
- });
2958
+ }, void 0, void 0, requestOptions);
2745
2959
  }
2746
2960
  /**
2747
2961
  * Create a Event-Based Face Verification session.
@@ -2774,12 +2988,12 @@ var KycClient = class extends BaseClient {
2774
2988
  *
2775
2989
  * @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
2776
2990
  */
2777
- async submitEventBasedFaceVerificationSession(request) {
2991
+ async submitEventBasedFaceVerificationSession(request, requestOptions) {
2778
2992
  return this.request("/api/v1/kyc/face/submit", {
2779
2993
  method: "POST",
2780
2994
  body: JSON.stringify(request),
2781
2995
  headers: this.getUserHeaders()
2782
- });
2996
+ }, void 0, requestOptions);
2783
2997
  }
2784
2998
  /**
2785
2999
  * Look up the current state of a Event-Based Face Verification session by its
@@ -2895,12 +3109,12 @@ var KycClient = class extends BaseClient {
2895
3109
  * console.log(`Verification submitted: ${result.reference}`);
2896
3110
  * ```
2897
3111
  */
2898
- async submitVerification(request) {
3112
+ async submitVerification(request, requestOptions) {
2899
3113
  return this.requestWithRetry("/api/v1/kyc/submit", {
2900
3114
  method: "POST",
2901
3115
  body: JSON.stringify(request),
2902
3116
  headers: this.getUserHeaders()
2903
- });
3117
+ }, void 0, void 0, requestOptions);
2904
3118
  }
2905
3119
  /**
2906
3120
  * Get a KYC request by ID
@@ -3340,10 +3554,13 @@ var TaxClient = class extends BaseClient {
3340
3554
  { method: "POST" }
3341
3555
  );
3342
3556
  }
3343
- async checkTINStatus(customerID) {
3557
+ async checkTINStatus(customerID, requestOptions) {
3344
3558
  return this.requestWithRetry(
3345
3559
  `/api/v1/tax/customer-tax-profiles/${customerID}/check-tin`,
3346
- { method: "POST" }
3560
+ { method: "POST" },
3561
+ void 0,
3562
+ void 0,
3563
+ requestOptions
3347
3564
  );
3348
3565
  }
3349
3566
  async reRequestTaxForm(customerID, input) {
@@ -3424,14 +3641,24 @@ var TaxClient = class extends BaseClient {
3424
3641
  };
3425
3642
 
3426
3643
  // src/webhooks/handler.ts
3644
+ function resolveEventId(event) {
3645
+ if (event.id) {
3646
+ return event.id;
3647
+ }
3648
+ if (event.event_type === "notification.created") {
3649
+ return event.notification?.id;
3650
+ }
3651
+ return void 0;
3652
+ }
3427
3653
  var WebhookHandler = class {
3428
3654
  constructor(config) {
3429
3655
  this.handlers = /* @__PURE__ */ new Map();
3430
3656
  this.anyHandlers = [];
3431
- this.seenEventIds = /* @__PURE__ */ new Map();
3432
3657
  this.secret = config.secret;
3433
3658
  this.tolerance = config.tolerance ?? 3e5;
3434
3659
  this.replayProtection = config.replayProtection ?? true;
3660
+ this.allowLegacySignature = config.allowLegacySignature ?? true;
3661
+ this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
3435
3662
  }
3436
3663
  /**
3437
3664
  * Register a handler for a specific event type.
@@ -3453,7 +3680,10 @@ var WebhookHandler = class {
3453
3680
  * Verify signature and parse the webhook body.
3454
3681
  */
3455
3682
  async verifyAndParse(body, signature) {
3456
- const isValid = await verifyWebhookSignature(body, signature, this.secret);
3683
+ const isValid = await verifyWebhookSignature(body, signature, this.secret, {
3684
+ tolerance: this.tolerance,
3685
+ allowLegacy: this.allowLegacySignature
3686
+ });
3457
3687
  if (!isValid) {
3458
3688
  throw new ValidationError("Invalid webhook signature", ["signature"]);
3459
3689
  }
@@ -3469,18 +3699,21 @@ var WebhookHandler = class {
3469
3699
  /**
3470
3700
  * Parse an event without signature verification (for testing).
3471
3701
  */
3472
- parseEvent(body) {
3702
+ async parseEvent(body) {
3473
3703
  return this.parseAndValidate(body);
3474
3704
  }
3475
- parseAndValidate(body) {
3705
+ async parseAndValidate(body) {
3476
3706
  const event = JSON.parse(body);
3477
- if (!event.type || !event.id || !event.timestamp) {
3478
- throw new ValidationError("Invalid webhook event: missing required fields (type, id, timestamp)", ["type", "id", "timestamp"]);
3707
+ const eventId = resolveEventId(event);
3708
+ if (!event.event_type || !eventId || !event.timestamp) {
3709
+ throw new ValidationError(
3710
+ "Invalid webhook event: missing required fields (event_type, id, timestamp)",
3711
+ ["event_type", "id", "timestamp"]
3712
+ );
3479
3713
  }
3480
3714
  if (this.tolerance > 0) {
3481
3715
  const eventTime = new Date(event.timestamp).getTime();
3482
- const now = Date.now();
3483
- if (Math.abs(now - eventTime) > this.tolerance) {
3716
+ if (Math.abs(Date.now() - eventTime) > this.tolerance) {
3484
3717
  throw new ValidationError(
3485
3718
  `Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
3486
3719
  ["timestamp"]
@@ -3488,26 +3721,18 @@ var WebhookHandler = class {
3488
3721
  }
3489
3722
  }
3490
3723
  if (this.replayProtection) {
3491
- if (this.seenEventIds.has(event.id)) {
3724
+ if (await this.dedupStore.seen(eventId)) {
3492
3725
  throw new ValidationError(
3493
- `Duplicate webhook event: ${event.id} has already been processed`,
3726
+ `Duplicate webhook event: ${eventId} has already been processed`,
3494
3727
  ["id"]
3495
3728
  );
3496
3729
  }
3497
- const now = Date.now();
3498
- this.seenEventIds.set(event.id, now);
3499
- if (this.seenEventIds.size > 1e3) {
3500
- for (const [id, seenAt] of this.seenEventIds) {
3501
- if (now - seenAt > this.tolerance) {
3502
- this.seenEventIds.delete(id);
3503
- }
3504
- }
3505
- }
3730
+ await this.dedupStore.mark(eventId, this.tolerance);
3506
3731
  }
3507
3732
  return event;
3508
3733
  }
3509
3734
  async dispatch(event) {
3510
- const typeHandlers = this.handlers.get(event.type) || [];
3735
+ const typeHandlers = this.handlers.get(event.event_type) || [];
3511
3736
  const allHandlers = [...typeHandlers, ...this.anyHandlers];
3512
3737
  for (const handler of allHandlers) {
3513
3738
  await handler(event);
@@ -3588,6 +3813,6 @@ function buildHandler(options) {
3588
3813
  return handler;
3589
3814
  }
3590
3815
 
3591
- export { AuthenticationError, BaseClient, CGSError, CircuitBreaker, CircuitBreakerOpenError, ComplianceBlockedError, ComplianceClient, ComplianceError, DEFAULT_CURRENCY_RATES, GeolocationClient, KYC_DECLINED_DESCRIPTIONS, KycClient, NetworkError, RateLimitError, RateLimitTracker, RiskProfileClient, SDK_VERSION, ServiceUnavailableError, TaxClient, TimeoutError, ValidationError, VesantError, WebhookHandler, createConsoleLogger, createNextWebhookHandler, createWebhookMiddleware, decodeCipherText, generateCipherText, isCipherTextExpired, noopLogger, sdkReasons, verifyWebhookSignature };
3816
+ export { AuthenticationError, BaseClient, CGSError, CircuitBreaker, CircuitBreakerOpenError, ComplianceBlockedError, ComplianceClient, ComplianceError, DEFAULT_CURRENCY_RATES, GeolocationClient, InMemoryDedupStore, KYC_DECLINED_DESCRIPTIONS, KycClient, NetworkError, RateLimitError, RateLimitTracker, RefreshingTokenProvider, RiskProfileClient, SDK_VERSION, ServiceUnavailableError, StaticApiKeyProvider, TaxClient, TimeoutError, ValidationError, VesantError, WebhookHandler, assessGpsIntegrity, collectAll, collectDeviceSignals, createConsoleLogger, createNextWebhookHandler, createWebhookMiddleware, decodeCipherText, generateCipherText, gpsIntegrityInputFromPosition, isCipherTextExpired, noopLogger, paginate, probeGeolocationPermission, sdkReasons, verifyWebhookSignature };
3592
3817
  //# sourceMappingURL=index.mjs.map
3593
3818
  //# sourceMappingURL=index.mjs.map