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.js CHANGED
@@ -1,5 +1,61 @@
1
1
  'use strict';
2
2
 
3
+ // src/core/auth.ts
4
+ var StaticApiKeyProvider = class {
5
+ constructor(apiKey) {
6
+ this.apiKey = apiKey;
7
+ }
8
+ async getCredential() {
9
+ return this.apiKey ? { scheme: "Bearer", token: this.apiKey } : null;
10
+ }
11
+ canRefresh() {
12
+ return false;
13
+ }
14
+ };
15
+ var RefreshingTokenProvider = class {
16
+ constructor(opts) {
17
+ this.opts = opts;
18
+ this.cached = null;
19
+ this.inFlight = null;
20
+ this.skewMs = opts.skewMs ?? 3e4;
21
+ this.scheme = opts.scheme ?? "Bearer";
22
+ this.now = opts.now ?? (() => Date.now());
23
+ }
24
+ canRefresh() {
25
+ return true;
26
+ }
27
+ async getCredential(ctx) {
28
+ const forceRefresh = ctx?.forceRefresh ?? false;
29
+ if (!forceRefresh && this.cached && !this.isExpiring(this.cached)) {
30
+ return { scheme: this.scheme, token: this.cached.token };
31
+ }
32
+ const fresh = await this.refresh();
33
+ return { scheme: this.scheme, token: fresh.token };
34
+ }
35
+ isExpiring(token) {
36
+ if (token.expiresAt === void 0) {
37
+ return false;
38
+ }
39
+ return this.now() >= token.expiresAt - this.skewMs;
40
+ }
41
+ refresh() {
42
+ if (this.inFlight) {
43
+ return this.inFlight;
44
+ }
45
+ this.inFlight = (async () => {
46
+ try {
47
+ const fresh = await this.opts.fetchToken();
48
+ this.cached = fresh;
49
+ this.opts.onRotate?.({ expiresAt: fresh.expiresAt });
50
+ return fresh;
51
+ } finally {
52
+ this.inFlight = null;
53
+ }
54
+ })();
55
+ return this.inFlight;
56
+ }
57
+ };
58
+
3
59
  // src/core/errors.ts
4
60
  var VesantError = class _VesantError extends Error {
5
61
  constructor(message, code, statusCode, details) {
@@ -242,7 +298,7 @@ var noopLogger = {
242
298
  };
243
299
 
244
300
  // src/core/version.ts
245
- var SDK_VERSION = "1.7.0";
301
+ var SDK_VERSION = "1.7.1";
246
302
 
247
303
  // src/shared/browser-utils.ts
248
304
  function generateUUID() {
@@ -324,6 +380,12 @@ var BaseClient = class {
324
380
  if (!config.tenantId?.trim()) {
325
381
  throw new ValidationError("tenantId is required and must be a non-empty string", ["tenantId"]);
326
382
  }
383
+ if (typeof fetch === "undefined") {
384
+ throw new VesantError(
385
+ "The Vesant SDK requires a global fetch (Node.js >= 18). Upgrade Node or provide a fetch polyfill.",
386
+ "RUNTIME_UNSUPPORTED"
387
+ );
388
+ }
327
389
  this.interceptors = config.interceptors || [];
328
390
  this.logger = config.logger || createConsoleLogger();
329
391
  let environment = config.environment;
@@ -342,11 +404,12 @@ var BaseClient = class {
342
404
  environment,
343
405
  headers: config.headers || {},
344
406
  timeout: config.timeout || 1e4,
345
- retries: config.retries || 3,
407
+ retries: config.retries ?? 3,
346
408
  debug: config.debug || false,
347
409
  interceptors: this.interceptors,
348
410
  logger: this.logger
349
411
  };
412
+ this.authProvider = config.authProvider ?? (apiKey ? new StaticApiKeyProvider(apiKey) : void 0);
350
413
  if (config.circuitBreaker) {
351
414
  this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
352
415
  }
@@ -354,6 +417,19 @@ var BaseClient = class {
354
417
  this.rateLimitTracker = new RateLimitTracker();
355
418
  }
356
419
  }
420
+ /**
421
+ * Resolve the Idempotency-Key for a request. Mutating methods (POST/PUT/PATCH)
422
+ * get the caller-supplied key or a freshly minted UUID; non-mutating methods get
423
+ * none. requestWithRetry resolves this once and injects it so every retry reuses
424
+ * one key (letting the server dedup replays); a direct request() resolves per call.
425
+ */
426
+ resolveIdempotencyKey(method, requestOptions) {
427
+ const isMutating = ["POST", "PUT", "PATCH"].includes((method || "GET").toUpperCase());
428
+ if (!isMutating) {
429
+ return void 0;
430
+ }
431
+ return requestOptions?.idempotencyKey ?? generateUUID();
432
+ }
357
433
  /**
358
434
  * Make an HTTP request with timeout and error handling
359
435
  */
@@ -379,15 +455,20 @@ var BaseClient = class {
379
455
  ...this.config.headers,
380
456
  ...options.headers || {}
381
457
  };
382
- if (this.config.apiKey) {
383
- headers["Authorization"] = `Bearer ${this.config.apiKey}`;
458
+ const credential = await this.authProvider?.getCredential({
459
+ tenantId: this.config.tenantId,
460
+ requestId
461
+ });
462
+ if (credential) {
463
+ headers["Authorization"] = `${credential.scheme ?? "Bearer"} ${credential.token}`;
384
464
  }
385
465
  if (this.config.environment === "sandbox") {
386
466
  headers["X-Sandbox"] = "true";
387
467
  }
388
468
  const method = (options.method || "GET").toUpperCase();
389
- if (["POST", "PUT", "PATCH"].includes(method)) {
390
- headers["Idempotency-Key"] = requestOptions?.idempotencyKey || generateUUID();
469
+ const idempotencyKey = this.resolveIdempotencyKey(method, requestOptions);
470
+ if (idempotencyKey) {
471
+ headers["Idempotency-Key"] = idempotencyKey;
391
472
  }
392
473
  const controller = new AbortController();
393
474
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
@@ -408,10 +489,28 @@ var BaseClient = class {
408
489
  if (this.config.debug) {
409
490
  this.logger.debug(`${finalOptions.method || "GET"} ${endpoint}`);
410
491
  }
411
- const response = await fetch(url, {
492
+ let response = await fetch(url, {
412
493
  ...finalOptions,
413
494
  signal: controller.signal
414
495
  });
496
+ if (response.status === 401 && this.authProvider?.canRefresh?.()) {
497
+ const refreshed = await this.authProvider.getCredential({
498
+ tenantId: this.config.tenantId,
499
+ requestId,
500
+ forceRefresh: true
501
+ });
502
+ if (refreshed) {
503
+ this.config.onCredentialRotated?.({ reason: "unauthorized" });
504
+ finalOptions = {
505
+ ...finalOptions,
506
+ headers: {
507
+ ...finalOptions.headers,
508
+ Authorization: `${refreshed.scheme ?? "Bearer"} ${refreshed.token}`
509
+ }
510
+ };
511
+ response = await fetch(url, { ...finalOptions, signal: controller.signal });
512
+ }
513
+ }
415
514
  clearTimeout(timeoutId);
416
515
  if (this.rateLimitTracker) {
417
516
  this.rateLimitTracker.updateFromHeaders(response.headers);
@@ -500,9 +599,11 @@ var BaseClient = class {
500
599
  */
501
600
  async requestWithRetry(endpoint, options = {}, serviceURL, retries = this.config.retries, requestOptions) {
502
601
  let lastError;
602
+ const idempotencyKey = this.resolveIdempotencyKey(options.method || "GET", requestOptions);
603
+ const attemptOptions = idempotencyKey ? { ...requestOptions, idempotencyKey } : requestOptions;
503
604
  for (let attempt = 0; attempt <= retries; attempt++) {
504
605
  try {
505
- return await this.request(endpoint, options, serviceURL, requestOptions);
606
+ return await this.request(endpoint, options, serviceURL, attemptOptions);
506
607
  } catch (error) {
507
608
  lastError = error instanceof Error ? error : new Error("Unknown error");
508
609
  if (requestOptions?.signal?.aborted) {
@@ -599,6 +700,9 @@ var BaseClient = class {
599
700
  if (config.interceptors) {
600
701
  this.interceptors = config.interceptors;
601
702
  }
703
+ if (config.authProvider !== void 0 || config.apiKey !== void 0) {
704
+ this.authProvider = this.config.authProvider ?? (this.config.apiKey ? new StaticApiKeyProvider(this.config.apiKey) : void 0);
705
+ }
602
706
  }
603
707
  /**
604
708
  * Get current configuration (readonly)
@@ -626,8 +730,69 @@ var BaseClient = class {
626
730
  }
627
731
  };
628
732
 
733
+ // src/core/pagination.ts
734
+ async function* paginate(fetchPage) {
735
+ let page = 1;
736
+ let totalPages;
737
+ do {
738
+ const response = await fetchPage(page);
739
+ const items = response.data ?? [];
740
+ for (const item of items) {
741
+ yield item;
742
+ }
743
+ totalPages = response.total_pages ?? page;
744
+ if (items.length === 0) {
745
+ break;
746
+ }
747
+ page += 1;
748
+ } while (page <= totalPages);
749
+ }
750
+ async function collectAll(fetchPage) {
751
+ const items = [];
752
+ for await (const item of paginate(fetchPage)) {
753
+ items.push(item);
754
+ }
755
+ return items;
756
+ }
757
+
758
+ // src/core/dedup.ts
759
+ var InMemoryDedupStore = class {
760
+ constructor(opts = {}) {
761
+ this.expiryById = /* @__PURE__ */ new Map();
762
+ this.maxEntries = opts.maxEntries ?? 1e3;
763
+ this.now = opts.now ?? (() => Date.now());
764
+ }
765
+ async seen(id) {
766
+ return this.expiryById.has(id);
767
+ }
768
+ async mark(id, ttlMs) {
769
+ const now = this.now();
770
+ this.expiryById.set(id, now + ttlMs);
771
+ if (this.expiryById.size > this.maxEntries) {
772
+ for (const [key, expiresAt] of this.expiryById) {
773
+ if (now > expiresAt) {
774
+ this.expiryById.delete(key);
775
+ }
776
+ }
777
+ }
778
+ }
779
+ };
780
+
629
781
  // src/core/webhook-utils.ts
630
- async function verifyWebhookSignature(payload, signature, secret) {
782
+ async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
783
+ const signed = parseSignedTimestamp(signature);
784
+ if (signed) {
785
+ const tolerance = opts.tolerance ?? 3e5;
786
+ const now = opts.now?.() ?? Date.now();
787
+ if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
788
+ return false;
789
+ }
790
+ const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
791
+ return constantTimeEqual(signed.v1, expected);
792
+ }
793
+ if (opts.allowLegacy === false) {
794
+ return false;
795
+ }
631
796
  const hexDigest = await computeHmacSha256(payload, secret);
632
797
  const expectedPrefixed = `sha256=${hexDigest}`;
633
798
  if (signature.startsWith("sha256=")) {
@@ -635,6 +800,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
635
800
  }
636
801
  return constantTimeEqual(signature, hexDigest);
637
802
  }
803
+ function parseSignedTimestamp(signature) {
804
+ if (!signature.includes("t=") || !signature.includes("v1=")) {
805
+ return null;
806
+ }
807
+ let t;
808
+ let v1;
809
+ for (const part of signature.split(",")) {
810
+ const eq = part.indexOf("=");
811
+ if (eq === -1) continue;
812
+ const key = part.slice(0, eq).trim();
813
+ const value = part.slice(eq + 1).trim();
814
+ if (key === "t") t = Number(value);
815
+ else if (key === "v1") v1 = value;
816
+ }
817
+ if (t === void 0 || Number.isNaN(t) || !v1) {
818
+ return null;
819
+ }
820
+ return { t, v1 };
821
+ }
638
822
  async function computeHmacSha256(message, secret) {
639
823
  if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
640
824
  const encoder = new TextEncoder();
@@ -669,27 +853,63 @@ function constantTimeEqual(a, b) {
669
853
  return result === 0;
670
854
  }
671
855
 
672
- // src/geolocation/ciphertext.ts
673
- var CIPHER_TEXT_EXPIRY_MINUTES = 5;
674
- async function computeHMAC(key, message) {
675
- if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
676
- const encoder = new TextEncoder();
677
- const keyData = encoder.encode(key);
678
- const msgData = encoder.encode(message);
679
- const cryptoKey = await globalThis.crypto.subtle.importKey(
680
- "raw",
681
- keyData,
682
- { name: "HMAC", hash: "SHA-256" },
683
- false,
684
- ["sign"]
685
- );
686
- const signature = await globalThis.crypto.subtle.sign("HMAC", cryptoKey, msgData);
687
- const bytes = new Uint8Array(signature);
688
- return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
856
+ // src/geolocation/integrity.ts
857
+ var isNil = (v) => v === null || v === void 0;
858
+ function assessGpsIntegrity(input, opts = {}) {
859
+ const flags = [];
860
+ const now = opts.now ?? (() => Date.now());
861
+ const maxAgeMs = opts.maxAgeMs ?? 6e4;
862
+ const minAccuracy = opts.minAccuracyMeters ?? 1;
863
+ const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
864
+ if (isNil(accuracy)) {
865
+ flags.push("accuracy_missing");
866
+ } else if (accuracy === 0) {
867
+ flags.push("accuracy_zero");
868
+ } else if (accuracy < minAccuracy) {
869
+ flags.push("accuracy_implausibly_small");
870
+ }
871
+ const hasMotion = !isNil(heading) || !isNil(speed);
872
+ if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
873
+ flags.push("motion_without_accuracy");
874
+ }
875
+ if (!isNil(altitude) && isNil(altitudeAccuracy)) {
876
+ flags.push("altitude_without_accuracy");
877
+ }
878
+ if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
879
+ flags.push("stale_timestamp");
880
+ }
881
+ if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
882
+ flags.push(`permission_${opts.permissionState}`);
883
+ }
884
+ return { suspicious: flags.length > 0, flags };
885
+ }
886
+ function gpsIntegrityInputFromPosition(position) {
887
+ const c = position.coords;
888
+ return {
889
+ latitude: c.latitude,
890
+ longitude: c.longitude,
891
+ accuracy: c.accuracy,
892
+ altitude: c.altitude,
893
+ altitudeAccuracy: c.altitudeAccuracy,
894
+ heading: c.heading,
895
+ speed: c.speed,
896
+ timestamp: position.timestamp
897
+ };
898
+ }
899
+ async function probeGeolocationPermission() {
900
+ try {
901
+ if (typeof navigator === "undefined" || !navigator.permissions?.query) {
902
+ return "unavailable";
903
+ }
904
+ const status = await navigator.permissions.query({ name: "geolocation" });
905
+ return status.state;
906
+ } catch {
907
+ return "unavailable";
689
908
  }
690
- const { createHmac } = await import('crypto');
691
- return createHmac("sha256", key).update(message).digest("hex");
692
909
  }
910
+
911
+ // src/geolocation/ciphertext.ts
912
+ var CIPHER_TEXT_EXPIRY_MINUTES = 5;
693
913
  function getWebGLInfo() {
694
914
  if (typeof document === "undefined") return null;
695
915
  try {
@@ -721,6 +941,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
721
941
  if (typeof navigator === "undefined" || !navigator.geolocation) {
722
942
  return null;
723
943
  }
944
+ const permissionState = await probeGeolocationPermission();
724
945
  return new Promise((resolve) => {
725
946
  navigator.geolocation.getCurrentPosition(
726
947
  (position) => {
@@ -737,6 +958,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
737
958
  resolve(null);
738
959
  return;
739
960
  }
961
+ const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
740
962
  resolve({
741
963
  latitude,
742
964
  longitude,
@@ -745,7 +967,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
745
967
  altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
746
968
  heading: position.coords.heading ?? void 0,
747
969
  speed: position.coords.speed ?? void 0,
748
- timestamp: position.timestamp
970
+ timestamp: position.timestamp,
971
+ integrity_flags: flags.length ? flags : void 0
749
972
  });
750
973
  },
751
974
  () => {
@@ -812,6 +1035,9 @@ async function generateCipherText(options, config) {
812
1035
  );
813
1036
  if (location) {
814
1037
  locationData = location;
1038
+ if (location.integrity_flags?.length) {
1039
+ warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
1040
+ }
815
1041
  } else if (gpsRequiredByConfig) {
816
1042
  throw new VesantError(
817
1043
  `GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
@@ -841,15 +1067,7 @@ async function generateCipherText(options, config) {
841
1067
  };
842
1068
  const encoded = encodePayload(payload);
843
1069
  const timestamp = now.getTime().toString(36);
844
- let cipherText;
845
- const hmacKey = options.signingKey || options.apiKey;
846
- if (hmacKey) {
847
- const message = `02.${timestamp}.${encoded}`;
848
- const hmac = await computeHMAC(hmacKey, message);
849
- cipherText = `${message}.${hmac}`;
850
- } else {
851
- cipherText = `01.${timestamp}.${encoded}`;
852
- }
1070
+ const cipherText = `01.${timestamp}.${encoded}`;
853
1071
  return {
854
1072
  cipherText,
855
1073
  locationCaptured: !!locationData,
@@ -858,6 +1076,7 @@ async function generateCipherText(options, config) {
858
1076
  expiresAt: expiry.toISOString()
859
1077
  };
860
1078
  }
1079
+ var collectDeviceSignals = generateCipherText;
861
1080
  function decodeCipherText(cipherText) {
862
1081
  try {
863
1082
  const parts = cipherText.split(".");
@@ -931,8 +1150,8 @@ var GeolocationClient = class extends BaseClient {
931
1150
  * ```
932
1151
  */
933
1152
  async verifyIP(request, requestOptions) {
934
- if (!request.ip_address?.trim()) {
935
- throw new ValidationError("ip_address is required and must be a non-empty string", ["ip_address"]);
1153
+ if (request.ip_address !== void 0 && !request.ip_address.trim()) {
1154
+ throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
936
1155
  }
937
1156
  return this.requestWithRetry("/api/v1/geo/verify", {
938
1157
  method: "POST",
@@ -985,37 +1204,7 @@ var GeolocationClient = class extends BaseClient {
985
1204
  * ```
986
1205
  */
987
1206
  async getGPSConfig(requestOptions) {
988
- const config = await this.requestWithRetry("/api/v1/geo/config", void 0, void 0, void 0, requestOptions);
989
- if (config.signing_key) {
990
- this.cachedSigningKey = config.signing_key;
991
- }
992
- return config;
993
- }
994
- /**
995
- * Fetch the signing key from the dedicated authenticated endpoint.
996
- *
997
- * Falls back to getGPSConfig() for backward compatibility with older servers
998
- * that don't expose `/api/v1/geo/signing-key`.
999
- *
1000
- * @returns The signing key string, or undefined if unavailable
1001
- */
1002
- async fetchSigningKey(requestOptions) {
1003
- try {
1004
- const response = await this.requestWithRetry(
1005
- "/api/v1/geo/signing-key",
1006
- void 0,
1007
- void 0,
1008
- void 0,
1009
- requestOptions
1010
- );
1011
- if (response.signing_key) {
1012
- this.cachedSigningKey = response.signing_key;
1013
- return response.signing_key;
1014
- }
1015
- } catch {
1016
- }
1017
- const config = await this.getGPSConfig(requestOptions);
1018
- return config.signing_key;
1207
+ return this.requestWithRetry("/api/v1/geo/config", void 0, void 0, void 0, requestOptions);
1019
1208
  }
1020
1209
  // ============================================================================
1021
1210
  // CipherText Validation
@@ -1282,20 +1471,12 @@ var GeolocationClient = class extends BaseClient {
1282
1471
  *
1283
1472
  * @example
1284
1473
  * ```typescript
1285
- * // Using GPS (preferred)
1474
+ * // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
1286
1475
  * const result = await client.captureLocation(token, {
1287
1476
  * latitude: 37.7749,
1288
1477
  * longitude: -122.4194,
1289
1478
  * accuracy: 10
1290
1479
  * });
1291
- *
1292
- * // Using WiFi positioning (fallback)
1293
- * const result = await client.captureLocation(token, {
1294
- * wifi_networks: [
1295
- * { macAddress: "00:11:22:33:44:55", signalStrength: -50 },
1296
- * { macAddress: "AA:BB:CC:DD:EE:FF", signalStrength: -70 }
1297
- * ]
1298
- * });
1299
1480
  * ```
1300
1481
  */
1301
1482
  async captureLocation(token, capture, requestOptions) {
@@ -1308,34 +1489,22 @@ var GeolocationClient = class extends BaseClient {
1308
1489
  // CipherText Generation
1309
1490
  // ============================================================================
1310
1491
  /**
1311
- * Generate a signed cipherText containing device and location data.
1492
+ * Generate a device-signals envelope containing device and location data,
1493
+ * validated by the server.
1312
1494
  *
1313
- * Automatically passes the client's API key for HMAC signing (v02 format).
1314
- * If no API key is configured, falls back to unsigned v01 format.
1315
- *
1316
- * @param options - Options for cipherText generation
1495
+ * @param options - Options for envelope generation
1317
1496
  * @param gpsConfig - Optional GPS config (from getGPSConfig)
1318
- * @returns CipherText result with the signed string
1497
+ * @returns The device-signals envelope result
1319
1498
  *
1320
1499
  * @example
1321
1500
  * ```typescript
1322
1501
  * const result = await client.generateCipherText({ reason: 'login' });
1323
- * console.log(result.cipherText); // v02 signed cipherText
1502
+ * console.log(result.cipherText); // device-signals envelope
1324
1503
  * ```
1325
1504
  */
1326
1505
  async generateCipherText(options, gpsConfig) {
1327
- let signingKey = this.cachedSigningKey;
1328
- let resolvedGpsConfig = gpsConfig;
1329
- if (!signingKey) {
1330
- signingKey = await this.fetchSigningKey();
1331
- }
1332
- if (!resolvedGpsConfig) {
1333
- resolvedGpsConfig = await this.getGPSConfig();
1334
- }
1335
- return generateCipherText(
1336
- { ...options, signingKey: signingKey || void 0 },
1337
- resolvedGpsConfig
1338
- );
1506
+ const resolvedGpsConfig = gpsConfig ?? await this.getGPSConfig();
1507
+ return generateCipherText(options, resolvedGpsConfig);
1339
1508
  }
1340
1509
  // ============================================================================
1341
1510
  // Utility Methods (inherited from BaseClient: healthCheck, updateConfig, getConfig, buildQueryString)
@@ -2738,12 +2907,12 @@ var KycClient = class extends BaseClient {
2738
2907
  * console.log(`KYC ID: ${result.kyc_id}`);
2739
2908
  * ```
2740
2909
  */
2741
- async requestKycSubmitLink(request) {
2910
+ async requestKycSubmitLink(request, requestOptions) {
2742
2911
  return this.requestWithRetry("/api/v1/kyc/request", {
2743
2912
  method: "POST",
2744
2913
  body: JSON.stringify(request),
2745
2914
  headers: this.getUserHeaders()
2746
- });
2915
+ }, void 0, void 0, requestOptions);
2747
2916
  }
2748
2917
  /**
2749
2918
  * Create a Event-Based Face Verification session.
@@ -2776,12 +2945,12 @@ var KycClient = class extends BaseClient {
2776
2945
  *
2777
2946
  * @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
2778
2947
  */
2779
- async submitEventBasedFaceVerificationSession(request) {
2948
+ async submitEventBasedFaceVerificationSession(request, requestOptions) {
2780
2949
  return this.request("/api/v1/kyc/face/submit", {
2781
2950
  method: "POST",
2782
2951
  body: JSON.stringify(request),
2783
2952
  headers: this.getUserHeaders()
2784
- });
2953
+ }, void 0, requestOptions);
2785
2954
  }
2786
2955
  /**
2787
2956
  * Look up the current state of a Event-Based Face Verification session by its
@@ -2897,12 +3066,12 @@ var KycClient = class extends BaseClient {
2897
3066
  * console.log(`Verification submitted: ${result.reference}`);
2898
3067
  * ```
2899
3068
  */
2900
- async submitVerification(request) {
3069
+ async submitVerification(request, requestOptions) {
2901
3070
  return this.requestWithRetry("/api/v1/kyc/submit", {
2902
3071
  method: "POST",
2903
3072
  body: JSON.stringify(request),
2904
3073
  headers: this.getUserHeaders()
2905
- });
3074
+ }, void 0, void 0, requestOptions);
2906
3075
  }
2907
3076
  /**
2908
3077
  * Get a KYC request by ID
@@ -3342,10 +3511,13 @@ var TaxClient = class extends BaseClient {
3342
3511
  { method: "POST" }
3343
3512
  );
3344
3513
  }
3345
- async checkTINStatus(customerID) {
3514
+ async checkTINStatus(customerID, requestOptions) {
3346
3515
  return this.requestWithRetry(
3347
3516
  `/api/v1/tax/customer-tax-profiles/${customerID}/check-tin`,
3348
- { method: "POST" }
3517
+ { method: "POST" },
3518
+ void 0,
3519
+ void 0,
3520
+ requestOptions
3349
3521
  );
3350
3522
  }
3351
3523
  async reRequestTaxForm(customerID, input) {
@@ -3426,14 +3598,24 @@ var TaxClient = class extends BaseClient {
3426
3598
  };
3427
3599
 
3428
3600
  // src/webhooks/handler.ts
3601
+ function resolveEventId(event) {
3602
+ if (event.id) {
3603
+ return event.id;
3604
+ }
3605
+ if (event.event_type === "notification.created") {
3606
+ return event.notification?.id;
3607
+ }
3608
+ return void 0;
3609
+ }
3429
3610
  var WebhookHandler = class {
3430
3611
  constructor(config) {
3431
3612
  this.handlers = /* @__PURE__ */ new Map();
3432
3613
  this.anyHandlers = [];
3433
- this.seenEventIds = /* @__PURE__ */ new Map();
3434
3614
  this.secret = config.secret;
3435
3615
  this.tolerance = config.tolerance ?? 3e5;
3436
3616
  this.replayProtection = config.replayProtection ?? true;
3617
+ this.allowLegacySignature = config.allowLegacySignature ?? true;
3618
+ this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
3437
3619
  }
3438
3620
  /**
3439
3621
  * Register a handler for a specific event type.
@@ -3455,7 +3637,10 @@ var WebhookHandler = class {
3455
3637
  * Verify signature and parse the webhook body.
3456
3638
  */
3457
3639
  async verifyAndParse(body, signature) {
3458
- const isValid = await verifyWebhookSignature(body, signature, this.secret);
3640
+ const isValid = await verifyWebhookSignature(body, signature, this.secret, {
3641
+ tolerance: this.tolerance,
3642
+ allowLegacy: this.allowLegacySignature
3643
+ });
3459
3644
  if (!isValid) {
3460
3645
  throw new ValidationError("Invalid webhook signature", ["signature"]);
3461
3646
  }
@@ -3471,18 +3656,21 @@ var WebhookHandler = class {
3471
3656
  /**
3472
3657
  * Parse an event without signature verification (for testing).
3473
3658
  */
3474
- parseEvent(body) {
3659
+ async parseEvent(body) {
3475
3660
  return this.parseAndValidate(body);
3476
3661
  }
3477
- parseAndValidate(body) {
3662
+ async parseAndValidate(body) {
3478
3663
  const event = JSON.parse(body);
3479
- if (!event.type || !event.id || !event.timestamp) {
3480
- throw new ValidationError("Invalid webhook event: missing required fields (type, id, timestamp)", ["type", "id", "timestamp"]);
3664
+ const eventId = resolveEventId(event);
3665
+ if (!event.event_type || !eventId || !event.timestamp) {
3666
+ throw new ValidationError(
3667
+ "Invalid webhook event: missing required fields (event_type, id, timestamp)",
3668
+ ["event_type", "id", "timestamp"]
3669
+ );
3481
3670
  }
3482
3671
  if (this.tolerance > 0) {
3483
3672
  const eventTime = new Date(event.timestamp).getTime();
3484
- const now = Date.now();
3485
- if (Math.abs(now - eventTime) > this.tolerance) {
3673
+ if (Math.abs(Date.now() - eventTime) > this.tolerance) {
3486
3674
  throw new ValidationError(
3487
3675
  `Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
3488
3676
  ["timestamp"]
@@ -3490,26 +3678,18 @@ var WebhookHandler = class {
3490
3678
  }
3491
3679
  }
3492
3680
  if (this.replayProtection) {
3493
- if (this.seenEventIds.has(event.id)) {
3681
+ if (await this.dedupStore.seen(eventId)) {
3494
3682
  throw new ValidationError(
3495
- `Duplicate webhook event: ${event.id} has already been processed`,
3683
+ `Duplicate webhook event: ${eventId} has already been processed`,
3496
3684
  ["id"]
3497
3685
  );
3498
3686
  }
3499
- const now = Date.now();
3500
- this.seenEventIds.set(event.id, now);
3501
- if (this.seenEventIds.size > 1e3) {
3502
- for (const [id, seenAt] of this.seenEventIds) {
3503
- if (now - seenAt > this.tolerance) {
3504
- this.seenEventIds.delete(id);
3505
- }
3506
- }
3507
- }
3687
+ await this.dedupStore.mark(eventId, this.tolerance);
3508
3688
  }
3509
3689
  return event;
3510
3690
  }
3511
3691
  async dispatch(event) {
3512
- const typeHandlers = this.handlers.get(event.type) || [];
3692
+ const typeHandlers = this.handlers.get(event.event_type) || [];
3513
3693
  const allHandlers = [...typeHandlers, ...this.anyHandlers];
3514
3694
  for (const handler of allHandlers) {
3515
3695
  await handler(event);
@@ -3600,26 +3780,35 @@ exports.ComplianceClient = ComplianceClient;
3600
3780
  exports.ComplianceError = ComplianceError;
3601
3781
  exports.DEFAULT_CURRENCY_RATES = DEFAULT_CURRENCY_RATES;
3602
3782
  exports.GeolocationClient = GeolocationClient;
3783
+ exports.InMemoryDedupStore = InMemoryDedupStore;
3603
3784
  exports.KYC_DECLINED_DESCRIPTIONS = KYC_DECLINED_DESCRIPTIONS;
3604
3785
  exports.KycClient = KycClient;
3605
3786
  exports.NetworkError = NetworkError;
3606
3787
  exports.RateLimitError = RateLimitError;
3607
3788
  exports.RateLimitTracker = RateLimitTracker;
3789
+ exports.RefreshingTokenProvider = RefreshingTokenProvider;
3608
3790
  exports.RiskProfileClient = RiskProfileClient;
3609
3791
  exports.SDK_VERSION = SDK_VERSION;
3610
3792
  exports.ServiceUnavailableError = ServiceUnavailableError;
3793
+ exports.StaticApiKeyProvider = StaticApiKeyProvider;
3611
3794
  exports.TaxClient = TaxClient;
3612
3795
  exports.TimeoutError = TimeoutError;
3613
3796
  exports.ValidationError = ValidationError;
3614
3797
  exports.VesantError = VesantError;
3615
3798
  exports.WebhookHandler = WebhookHandler;
3799
+ exports.assessGpsIntegrity = assessGpsIntegrity;
3800
+ exports.collectAll = collectAll;
3801
+ exports.collectDeviceSignals = collectDeviceSignals;
3616
3802
  exports.createConsoleLogger = createConsoleLogger;
3617
3803
  exports.createNextWebhookHandler = createNextWebhookHandler;
3618
3804
  exports.createWebhookMiddleware = createWebhookMiddleware;
3619
3805
  exports.decodeCipherText = decodeCipherText;
3620
3806
  exports.generateCipherText = generateCipherText;
3807
+ exports.gpsIntegrityInputFromPosition = gpsIntegrityInputFromPosition;
3621
3808
  exports.isCipherTextExpired = isCipherTextExpired;
3622
3809
  exports.noopLogger = noopLogger;
3810
+ exports.paginate = paginate;
3811
+ exports.probeGeolocationPermission = probeGeolocationPermission;
3623
3812
  exports.sdkReasons = sdkReasons;
3624
3813
  exports.verifyWebhookSignature = verifyWebhookSignature;
3625
3814
  //# sourceMappingURL=index.js.map