vesant-sdk 1.7.0 → 1.7.1-dev.ec505dc

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-BY4Quazn.d.ts} +2 -2
  2. package/dist/{client-DF7hlMEz.d.ts → client-C-u9lE8N.d.ts} +34 -46
  3. package/dist/{client-B0qhE2kr.d.mts → client-CX1jcLNZ.d.mts} +2 -2
  4. package/dist/{client-DrjgZoH_.d.mts → client-Yxat9wAO.d.mts} +34 -46
  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 +143 -98
  10. package/dist/compliance/index.js.map +1 -1
  11. package/dist/compliance/index.mjs +143 -98
  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 +148 -98
  22. package/dist/geolocation/index.js.map +1 -1
  23. package/dist/geolocation/index.mjs +145 -99
  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 +316 -127
  30. package/dist/index.js.map +1 -1
  31. package/dist/index.mjs +308 -128
  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,7 @@ 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;
991
- }
992
- /**
993
- * Fetch the signing key from the dedicated authenticated endpoint.
994
- *
995
- * Falls back to getGPSConfig() for backward compatibility with older servers
996
- * that don't expose `/api/v1/geo/signing-key`.
997
- *
998
- * @returns The signing key string, or undefined if unavailable
999
- */
1000
- async fetchSigningKey(requestOptions) {
1001
- try {
1002
- const response = await this.requestWithRetry(
1003
- "/api/v1/geo/signing-key",
1004
- void 0,
1005
- void 0,
1006
- void 0,
1007
- requestOptions
1008
- );
1009
- if (response.signing_key) {
1010
- this.cachedSigningKey = response.signing_key;
1011
- return response.signing_key;
1012
- }
1013
- } catch {
1014
- }
1015
- const config = await this.getGPSConfig(requestOptions);
1016
- return config.signing_key;
1205
+ return this.requestWithRetry("/api/v1/geo/config", void 0, void 0, void 0, requestOptions);
1017
1206
  }
1018
1207
  // ============================================================================
1019
1208
  // CipherText Validation
@@ -1280,20 +1469,12 @@ var GeolocationClient = class extends BaseClient {
1280
1469
  *
1281
1470
  * @example
1282
1471
  * ```typescript
1283
- * // Using GPS (preferred)
1472
+ * // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
1284
1473
  * const result = await client.captureLocation(token, {
1285
1474
  * latitude: 37.7749,
1286
1475
  * longitude: -122.4194,
1287
1476
  * accuracy: 10
1288
1477
  * });
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
1478
  * ```
1298
1479
  */
1299
1480
  async captureLocation(token, capture, requestOptions) {
@@ -1306,34 +1487,22 @@ var GeolocationClient = class extends BaseClient {
1306
1487
  // CipherText Generation
1307
1488
  // ============================================================================
1308
1489
  /**
1309
- * Generate a signed cipherText containing device and location data.
1490
+ * Generate a device-signals envelope containing device and location data,
1491
+ * validated by the server.
1310
1492
  *
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
1493
+ * @param options - Options for envelope generation
1315
1494
  * @param gpsConfig - Optional GPS config (from getGPSConfig)
1316
- * @returns CipherText result with the signed string
1495
+ * @returns The device-signals envelope result
1317
1496
  *
1318
1497
  * @example
1319
1498
  * ```typescript
1320
1499
  * const result = await client.generateCipherText({ reason: 'login' });
1321
- * console.log(result.cipherText); // v02 signed cipherText
1500
+ * console.log(result.cipherText); // device-signals envelope
1322
1501
  * ```
1323
1502
  */
1324
1503
  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
- );
1504
+ const resolvedGpsConfig = gpsConfig ?? await this.getGPSConfig();
1505
+ return generateCipherText(options, resolvedGpsConfig);
1337
1506
  }
1338
1507
  // ============================================================================
1339
1508
  // Utility Methods (inherited from BaseClient: healthCheck, updateConfig, getConfig, buildQueryString)
@@ -2736,12 +2905,12 @@ var KycClient = class extends BaseClient {
2736
2905
  * console.log(`KYC ID: ${result.kyc_id}`);
2737
2906
  * ```
2738
2907
  */
2739
- async requestKycSubmitLink(request) {
2908
+ async requestKycSubmitLink(request, requestOptions) {
2740
2909
  return this.requestWithRetry("/api/v1/kyc/request", {
2741
2910
  method: "POST",
2742
2911
  body: JSON.stringify(request),
2743
2912
  headers: this.getUserHeaders()
2744
- });
2913
+ }, void 0, void 0, requestOptions);
2745
2914
  }
2746
2915
  /**
2747
2916
  * Create a Event-Based Face Verification session.
@@ -2774,12 +2943,12 @@ var KycClient = class extends BaseClient {
2774
2943
  *
2775
2944
  * @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
2776
2945
  */
2777
- async submitEventBasedFaceVerificationSession(request) {
2946
+ async submitEventBasedFaceVerificationSession(request, requestOptions) {
2778
2947
  return this.request("/api/v1/kyc/face/submit", {
2779
2948
  method: "POST",
2780
2949
  body: JSON.stringify(request),
2781
2950
  headers: this.getUserHeaders()
2782
- });
2951
+ }, void 0, requestOptions);
2783
2952
  }
2784
2953
  /**
2785
2954
  * Look up the current state of a Event-Based Face Verification session by its
@@ -2895,12 +3064,12 @@ var KycClient = class extends BaseClient {
2895
3064
  * console.log(`Verification submitted: ${result.reference}`);
2896
3065
  * ```
2897
3066
  */
2898
- async submitVerification(request) {
3067
+ async submitVerification(request, requestOptions) {
2899
3068
  return this.requestWithRetry("/api/v1/kyc/submit", {
2900
3069
  method: "POST",
2901
3070
  body: JSON.stringify(request),
2902
3071
  headers: this.getUserHeaders()
2903
- });
3072
+ }, void 0, void 0, requestOptions);
2904
3073
  }
2905
3074
  /**
2906
3075
  * Get a KYC request by ID
@@ -3340,10 +3509,13 @@ var TaxClient = class extends BaseClient {
3340
3509
  { method: "POST" }
3341
3510
  );
3342
3511
  }
3343
- async checkTINStatus(customerID) {
3512
+ async checkTINStatus(customerID, requestOptions) {
3344
3513
  return this.requestWithRetry(
3345
3514
  `/api/v1/tax/customer-tax-profiles/${customerID}/check-tin`,
3346
- { method: "POST" }
3515
+ { method: "POST" },
3516
+ void 0,
3517
+ void 0,
3518
+ requestOptions
3347
3519
  );
3348
3520
  }
3349
3521
  async reRequestTaxForm(customerID, input) {
@@ -3424,14 +3596,24 @@ var TaxClient = class extends BaseClient {
3424
3596
  };
3425
3597
 
3426
3598
  // src/webhooks/handler.ts
3599
+ function resolveEventId(event) {
3600
+ if (event.id) {
3601
+ return event.id;
3602
+ }
3603
+ if (event.event_type === "notification.created") {
3604
+ return event.notification?.id;
3605
+ }
3606
+ return void 0;
3607
+ }
3427
3608
  var WebhookHandler = class {
3428
3609
  constructor(config) {
3429
3610
  this.handlers = /* @__PURE__ */ new Map();
3430
3611
  this.anyHandlers = [];
3431
- this.seenEventIds = /* @__PURE__ */ new Map();
3432
3612
  this.secret = config.secret;
3433
3613
  this.tolerance = config.tolerance ?? 3e5;
3434
3614
  this.replayProtection = config.replayProtection ?? true;
3615
+ this.allowLegacySignature = config.allowLegacySignature ?? true;
3616
+ this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
3435
3617
  }
3436
3618
  /**
3437
3619
  * Register a handler for a specific event type.
@@ -3453,7 +3635,10 @@ var WebhookHandler = class {
3453
3635
  * Verify signature and parse the webhook body.
3454
3636
  */
3455
3637
  async verifyAndParse(body, signature) {
3456
- const isValid = await verifyWebhookSignature(body, signature, this.secret);
3638
+ const isValid = await verifyWebhookSignature(body, signature, this.secret, {
3639
+ tolerance: this.tolerance,
3640
+ allowLegacy: this.allowLegacySignature
3641
+ });
3457
3642
  if (!isValid) {
3458
3643
  throw new ValidationError("Invalid webhook signature", ["signature"]);
3459
3644
  }
@@ -3469,18 +3654,21 @@ var WebhookHandler = class {
3469
3654
  /**
3470
3655
  * Parse an event without signature verification (for testing).
3471
3656
  */
3472
- parseEvent(body) {
3657
+ async parseEvent(body) {
3473
3658
  return this.parseAndValidate(body);
3474
3659
  }
3475
- parseAndValidate(body) {
3660
+ async parseAndValidate(body) {
3476
3661
  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"]);
3662
+ const eventId = resolveEventId(event);
3663
+ if (!event.event_type || !eventId || !event.timestamp) {
3664
+ throw new ValidationError(
3665
+ "Invalid webhook event: missing required fields (event_type, id, timestamp)",
3666
+ ["event_type", "id", "timestamp"]
3667
+ );
3479
3668
  }
3480
3669
  if (this.tolerance > 0) {
3481
3670
  const eventTime = new Date(event.timestamp).getTime();
3482
- const now = Date.now();
3483
- if (Math.abs(now - eventTime) > this.tolerance) {
3671
+ if (Math.abs(Date.now() - eventTime) > this.tolerance) {
3484
3672
  throw new ValidationError(
3485
3673
  `Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
3486
3674
  ["timestamp"]
@@ -3488,26 +3676,18 @@ var WebhookHandler = class {
3488
3676
  }
3489
3677
  }
3490
3678
  if (this.replayProtection) {
3491
- if (this.seenEventIds.has(event.id)) {
3679
+ if (await this.dedupStore.seen(eventId)) {
3492
3680
  throw new ValidationError(
3493
- `Duplicate webhook event: ${event.id} has already been processed`,
3681
+ `Duplicate webhook event: ${eventId} has already been processed`,
3494
3682
  ["id"]
3495
3683
  );
3496
3684
  }
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
- }
3685
+ await this.dedupStore.mark(eventId, this.tolerance);
3506
3686
  }
3507
3687
  return event;
3508
3688
  }
3509
3689
  async dispatch(event) {
3510
- const typeHandlers = this.handlers.get(event.type) || [];
3690
+ const typeHandlers = this.handlers.get(event.event_type) || [];
3511
3691
  const allHandlers = [...typeHandlers, ...this.anyHandlers];
3512
3692
  for (const handler of allHandlers) {
3513
3693
  await handler(event);
@@ -3588,6 +3768,6 @@ function buildHandler(options) {
3588
3768
  return handler;
3589
3769
  }
3590
3770
 
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 };
3771
+ 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
3772
  //# sourceMappingURL=index.mjs.map
3593
3773
  //# sourceMappingURL=index.mjs.map