vesant-sdk 1.7.0-dev.c2e85f3 → 1.7.0-dev.c349626

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-DF7hlMEz.d.ts → client-0NOPDnT0.d.ts} +16 -26
  2. package/dist/{client-DrjgZoH_.d.mts → client-CvWNRwwg.d.mts} +16 -26
  3. package/dist/{client-B0qhE2kr.d.mts → client-Dps40EtF.d.mts} +2 -2
  4. package/dist/{client-DtH2RLuy.d.ts → client-SMxqod4j.d.ts} +2 -2
  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 +126 -18
  10. package/dist/compliance/index.js.map +1 -1
  11. package/dist/compliance/index.mjs +126 -18
  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 +61 -6
  16. package/dist/decisions/index.js.map +1 -1
  17. package/dist/decisions/index.mjs +61 -6
  18. package/dist/decisions/index.mjs.map +1 -1
  19. package/dist/geolocation/index.d.mts +59 -4
  20. package/dist/geolocation/index.d.ts +59 -4
  21. package/dist/geolocation/index.js +129 -18
  22. package/dist/geolocation/index.js.map +1 -1
  23. package/dist/geolocation/index.mjs +127 -19
  24. package/dist/geolocation/index.mjs.map +1 -1
  25. package/dist/index-CPOrMRaH.d.ts +264 -0
  26. package/dist/index-CimDmgZb.d.mts +264 -0
  27. package/dist/index.d.mts +7 -7
  28. package/dist/index.d.ts +7 -7
  29. package/dist/index.js +286 -49
  30. package/dist/index.js.map +1 -1
  31. package/dist/index.mjs +281 -50
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/kyc/core.d.mts +2 -2
  34. package/dist/kyc/core.d.ts +2 -2
  35. package/dist/kyc/core.js +83 -14
  36. package/dist/kyc/core.js.map +1 -1
  37. package/dist/kyc/core.mjs +83 -14
  38. package/dist/kyc/core.mjs.map +1 -1
  39. package/dist/kyc/index.d.mts +56 -8
  40. package/dist/kyc/index.d.ts +56 -8
  41. package/dist/kyc/index.js +83 -14
  42. package/dist/kyc/index.js.map +1 -1
  43. package/dist/kyc/index.mjs +83 -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 +68 -4
  48. package/dist/react.js.map +1 -1
  49. package/dist/react.mjs +68 -4
  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 +61 -6
  54. package/dist/risk-profile/index.js.map +1 -1
  55. package/dist/risk-profile/index.mjs +61 -6
  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 +61 -6
  60. package/dist/scores/index.js.map +1 -1
  61. package/dist/scores/index.mjs +61 -6
  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 +66 -8
  66. package/dist/tax/index.js.map +1 -1
  67. package/dist/tax/index.mjs +66 -8
  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 +1 -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) {
@@ -345,6 +401,7 @@ var BaseClient = class {
345
401
  interceptors: this.interceptors,
346
402
  logger: this.logger
347
403
  };
404
+ this.authProvider = config.authProvider ?? (apiKey ? new StaticApiKeyProvider(apiKey) : void 0);
348
405
  if (config.circuitBreaker) {
349
406
  this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
350
407
  }
@@ -352,6 +409,19 @@ var BaseClient = class {
352
409
  this.rateLimitTracker = new RateLimitTracker();
353
410
  }
354
411
  }
412
+ /**
413
+ * Resolve the Idempotency-Key for a request. Mutating methods (POST/PUT/PATCH)
414
+ * get the caller-supplied key or a freshly minted UUID; non-mutating methods get
415
+ * none. requestWithRetry resolves this once and injects it so every retry reuses
416
+ * one key (letting the server dedup replays); a direct request() resolves per call.
417
+ */
418
+ resolveIdempotencyKey(method, requestOptions) {
419
+ const isMutating = ["POST", "PUT", "PATCH"].includes((method || "GET").toUpperCase());
420
+ if (!isMutating) {
421
+ return void 0;
422
+ }
423
+ return requestOptions?.idempotencyKey ?? generateUUID();
424
+ }
355
425
  /**
356
426
  * Make an HTTP request with timeout and error handling
357
427
  */
@@ -377,15 +447,20 @@ var BaseClient = class {
377
447
  ...this.config.headers,
378
448
  ...options.headers || {}
379
449
  };
380
- if (this.config.apiKey) {
381
- headers["Authorization"] = `Bearer ${this.config.apiKey}`;
450
+ const credential = await this.authProvider?.getCredential({
451
+ tenantId: this.config.tenantId,
452
+ requestId
453
+ });
454
+ if (credential) {
455
+ headers["Authorization"] = `${credential.scheme ?? "Bearer"} ${credential.token}`;
382
456
  }
383
457
  if (this.config.environment === "sandbox") {
384
458
  headers["X-Sandbox"] = "true";
385
459
  }
386
460
  const method = (options.method || "GET").toUpperCase();
387
- if (["POST", "PUT", "PATCH"].includes(method)) {
388
- headers["Idempotency-Key"] = requestOptions?.idempotencyKey || generateUUID();
461
+ const idempotencyKey = this.resolveIdempotencyKey(method, requestOptions);
462
+ if (idempotencyKey) {
463
+ headers["Idempotency-Key"] = idempotencyKey;
389
464
  }
390
465
  const controller = new AbortController();
391
466
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
@@ -406,10 +481,28 @@ var BaseClient = class {
406
481
  if (this.config.debug) {
407
482
  this.logger.debug(`${finalOptions.method || "GET"} ${endpoint}`);
408
483
  }
409
- const response = await fetch(url, {
484
+ let response = await fetch(url, {
410
485
  ...finalOptions,
411
486
  signal: controller.signal
412
487
  });
488
+ if (response.status === 401 && this.authProvider?.canRefresh?.()) {
489
+ const refreshed = await this.authProvider.getCredential({
490
+ tenantId: this.config.tenantId,
491
+ requestId,
492
+ forceRefresh: true
493
+ });
494
+ if (refreshed) {
495
+ this.config.onCredentialRotated?.({ reason: "unauthorized" });
496
+ finalOptions = {
497
+ ...finalOptions,
498
+ headers: {
499
+ ...finalOptions.headers,
500
+ Authorization: `${refreshed.scheme ?? "Bearer"} ${refreshed.token}`
501
+ }
502
+ };
503
+ response = await fetch(url, { ...finalOptions, signal: controller.signal });
504
+ }
505
+ }
413
506
  clearTimeout(timeoutId);
414
507
  if (this.rateLimitTracker) {
415
508
  this.rateLimitTracker.updateFromHeaders(response.headers);
@@ -498,9 +591,11 @@ var BaseClient = class {
498
591
  */
499
592
  async requestWithRetry(endpoint, options = {}, serviceURL, retries = this.config.retries, requestOptions) {
500
593
  let lastError;
594
+ const idempotencyKey = this.resolveIdempotencyKey(options.method || "GET", requestOptions);
595
+ const attemptOptions = idempotencyKey ? { ...requestOptions, idempotencyKey } : requestOptions;
501
596
  for (let attempt = 0; attempt <= retries; attempt++) {
502
597
  try {
503
- return await this.request(endpoint, options, serviceURL, requestOptions);
598
+ return await this.request(endpoint, options, serviceURL, attemptOptions);
504
599
  } catch (error) {
505
600
  lastError = error instanceof Error ? error : new Error("Unknown error");
506
601
  if (requestOptions?.signal?.aborted) {
@@ -597,6 +692,9 @@ var BaseClient = class {
597
692
  if (config.interceptors) {
598
693
  this.interceptors = config.interceptors;
599
694
  }
695
+ if (config.authProvider !== void 0 || config.apiKey !== void 0) {
696
+ this.authProvider = this.config.authProvider ?? (this.config.apiKey ? new StaticApiKeyProvider(this.config.apiKey) : void 0);
697
+ }
600
698
  }
601
699
  /**
602
700
  * Get current configuration (readonly)
@@ -625,7 +723,20 @@ var BaseClient = class {
625
723
  };
626
724
 
627
725
  // src/core/webhook-utils.ts
628
- async function verifyWebhookSignature(payload, signature, secret) {
726
+ async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
727
+ const signed = parseSignedTimestamp(signature);
728
+ if (signed) {
729
+ const tolerance = opts.tolerance ?? 3e5;
730
+ const now = opts.now?.() ?? Date.now();
731
+ if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
732
+ return false;
733
+ }
734
+ const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
735
+ return constantTimeEqual(signed.v1, expected);
736
+ }
737
+ if (opts.allowLegacy === false) {
738
+ return false;
739
+ }
629
740
  const hexDigest = await computeHmacSha256(payload, secret);
630
741
  const expectedPrefixed = `sha256=${hexDigest}`;
631
742
  if (signature.startsWith("sha256=")) {
@@ -633,6 +744,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
633
744
  }
634
745
  return constantTimeEqual(signature, hexDigest);
635
746
  }
747
+ function parseSignedTimestamp(signature) {
748
+ if (!signature.includes("t=") || !signature.includes("v1=")) {
749
+ return null;
750
+ }
751
+ let t;
752
+ let v1;
753
+ for (const part of signature.split(",")) {
754
+ const eq = part.indexOf("=");
755
+ if (eq === -1) continue;
756
+ const key = part.slice(0, eq).trim();
757
+ const value = part.slice(eq + 1).trim();
758
+ if (key === "t") t = Number(value);
759
+ else if (key === "v1") v1 = value;
760
+ }
761
+ if (t === void 0 || Number.isNaN(t) || !v1) {
762
+ return null;
763
+ }
764
+ return { t, v1 };
765
+ }
636
766
  async function computeHmacSha256(message, secret) {
637
767
  if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
638
768
  const encoder = new TextEncoder();
@@ -667,6 +797,61 @@ function constantTimeEqual(a, b) {
667
797
  return result === 0;
668
798
  }
669
799
 
800
+ // src/geolocation/integrity.ts
801
+ var isNil = (v) => v === null || v === void 0;
802
+ function assessGpsIntegrity(input, opts = {}) {
803
+ const flags = [];
804
+ const now = opts.now ?? (() => Date.now());
805
+ const maxAgeMs = opts.maxAgeMs ?? 6e4;
806
+ const minAccuracy = opts.minAccuracyMeters ?? 1;
807
+ const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
808
+ if (isNil(accuracy)) {
809
+ flags.push("accuracy_missing");
810
+ } else if (accuracy === 0) {
811
+ flags.push("accuracy_zero");
812
+ } else if (accuracy < minAccuracy) {
813
+ flags.push("accuracy_implausibly_small");
814
+ }
815
+ const hasMotion = !isNil(heading) || !isNil(speed);
816
+ if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
817
+ flags.push("motion_without_accuracy");
818
+ }
819
+ if (!isNil(altitude) && isNil(altitudeAccuracy)) {
820
+ flags.push("altitude_without_accuracy");
821
+ }
822
+ if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
823
+ flags.push("stale_timestamp");
824
+ }
825
+ if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
826
+ flags.push(`permission_${opts.permissionState}`);
827
+ }
828
+ return { suspicious: flags.length > 0, flags };
829
+ }
830
+ function gpsIntegrityInputFromPosition(position) {
831
+ const c = position.coords;
832
+ return {
833
+ latitude: c.latitude,
834
+ longitude: c.longitude,
835
+ accuracy: c.accuracy,
836
+ altitude: c.altitude,
837
+ altitudeAccuracy: c.altitudeAccuracy,
838
+ heading: c.heading,
839
+ speed: c.speed,
840
+ timestamp: position.timestamp
841
+ };
842
+ }
843
+ async function probeGeolocationPermission() {
844
+ try {
845
+ if (typeof navigator === "undefined" || !navigator.permissions?.query) {
846
+ return "unavailable";
847
+ }
848
+ const status = await navigator.permissions.query({ name: "geolocation" });
849
+ return status.state;
850
+ } catch {
851
+ return "unavailable";
852
+ }
853
+ }
854
+
670
855
  // src/geolocation/ciphertext.ts
671
856
  var CIPHER_TEXT_EXPIRY_MINUTES = 5;
672
857
  async function computeHMAC(key, message) {
@@ -719,6 +904,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
719
904
  if (typeof navigator === "undefined" || !navigator.geolocation) {
720
905
  return null;
721
906
  }
907
+ const permissionState = await probeGeolocationPermission();
722
908
  return new Promise((resolve) => {
723
909
  navigator.geolocation.getCurrentPosition(
724
910
  (position) => {
@@ -735,6 +921,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
735
921
  resolve(null);
736
922
  return;
737
923
  }
924
+ const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
738
925
  resolve({
739
926
  latitude,
740
927
  longitude,
@@ -743,7 +930,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
743
930
  altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
744
931
  heading: position.coords.heading ?? void 0,
745
932
  speed: position.coords.speed ?? void 0,
746
- timestamp: position.timestamp
933
+ timestamp: position.timestamp,
934
+ integrity_flags: flags.length ? flags : void 0
747
935
  });
748
936
  },
749
937
  () => {
@@ -810,6 +998,9 @@ async function generateCipherText(options, config) {
810
998
  );
811
999
  if (location) {
812
1000
  locationData = location;
1001
+ if (location.integrity_flags?.length) {
1002
+ warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
1003
+ }
813
1004
  } else if (gpsRequiredByConfig) {
814
1005
  throw new VesantError(
815
1006
  `GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
@@ -929,8 +1120,8 @@ var GeolocationClient = class extends BaseClient {
929
1120
  * ```
930
1121
  */
931
1122
  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"]);
1123
+ if (request.ip_address !== void 0 && !request.ip_address.trim()) {
1124
+ throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
934
1125
  }
935
1126
  return this.requestWithRetry("/api/v1/geo/verify", {
936
1127
  method: "POST",
@@ -1280,20 +1471,12 @@ var GeolocationClient = class extends BaseClient {
1280
1471
  *
1281
1472
  * @example
1282
1473
  * ```typescript
1283
- * // Using GPS (preferred)
1474
+ * // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
1284
1475
  * const result = await client.captureLocation(token, {
1285
1476
  * latitude: 37.7749,
1286
1477
  * longitude: -122.4194,
1287
1478
  * accuracy: 10
1288
1479
  * });
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
1480
  * ```
1298
1481
  */
1299
1482
  async captureLocation(token, capture, requestOptions) {
@@ -2707,7 +2890,13 @@ var KycClient = class extends BaseClient {
2707
2890
  *
2708
2891
  * Generates a link that the user can visit to submit their KYC documents.
2709
2892
  *
2710
- * @param request - Request containing the user ID, optional redirect URL, and optional callback URL (receives POST requests)
2893
+ * Optionally pass the customer's registered identity data as
2894
+ * `customer_data` (same shape as the geolocation `customer_data` block).
2895
+ * It seeds the customer's risk profile so document verification can
2896
+ * cross-check the submitted document against trusted reference data;
2897
+ * fields never overwrite data already on the profile.
2898
+ *
2899
+ * @param request - Request containing the user ID, optional redirect URL, optional callback URL (receives POST requests), and optional customer identity data
2711
2900
  * @returns Response containing the redirect link and KYC ID
2712
2901
  *
2713
2902
  * @example
@@ -2715,19 +2904,27 @@ var KycClient = class extends BaseClient {
2715
2904
  * const result = await client.requestKycSubmitLink({
2716
2905
  * user_id: "user_123",
2717
2906
  * redirect_url: "https://merchant.com/kyc-complete", // optional
2718
- * callback_url: "https://merchant.com/api/kyc-webhook" // optional - receives POST requests on status change
2907
+ * callback_url: "https://merchant.com/api/kyc-webhook", // optional - receives POST requests on status change
2908
+ * customer_data: { // optional - seeds the risk profile for document cross-checks
2909
+ * full_name: "John Doe",
2910
+ * date_of_birth: "1999-06-02", // ISO 8601
2911
+ * email: "john@example.com",
2912
+ * phone: "+94771234567",
2913
+ * address: "12 Main St, Colombo", // used for address verification
2914
+ * country: "LK"
2915
+ * }
2719
2916
  * });
2720
2917
  *
2721
2918
  * console.log(`Redirect user to: ${result.link}`);
2722
2919
  * console.log(`KYC ID: ${result.kyc_id}`);
2723
2920
  * ```
2724
2921
  */
2725
- async requestKycSubmitLink(request) {
2922
+ async requestKycSubmitLink(request, requestOptions) {
2726
2923
  return this.requestWithRetry("/api/v1/kyc/request", {
2727
2924
  method: "POST",
2728
2925
  body: JSON.stringify(request),
2729
2926
  headers: this.getUserHeaders()
2730
- });
2927
+ }, void 0, void 0, requestOptions);
2731
2928
  }
2732
2929
  /**
2733
2930
  * Create a Event-Based Face Verification session.
@@ -2760,12 +2957,12 @@ var KycClient = class extends BaseClient {
2760
2957
  *
2761
2958
  * @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
2762
2959
  */
2763
- async submitEventBasedFaceVerificationSession(request) {
2960
+ async submitEventBasedFaceVerificationSession(request, requestOptions) {
2764
2961
  return this.request("/api/v1/kyc/face/submit", {
2765
2962
  method: "POST",
2766
2963
  body: JSON.stringify(request),
2767
2964
  headers: this.getUserHeaders()
2768
- });
2965
+ }, void 0, requestOptions);
2769
2966
  }
2770
2967
  /**
2771
2968
  * Look up the current state of a Event-Based Face Verification session by its
@@ -2881,12 +3078,12 @@ var KycClient = class extends BaseClient {
2881
3078
  * console.log(`Verification submitted: ${result.reference}`);
2882
3079
  * ```
2883
3080
  */
2884
- async submitVerification(request) {
3081
+ async submitVerification(request, requestOptions) {
2885
3082
  return this.requestWithRetry("/api/v1/kyc/submit", {
2886
3083
  method: "POST",
2887
3084
  body: JSON.stringify(request),
2888
3085
  headers: this.getUserHeaders()
2889
- });
3086
+ }, void 0, void 0, requestOptions);
2890
3087
  }
2891
3088
  /**
2892
3089
  * Get a KYC request by ID
@@ -3326,10 +3523,13 @@ var TaxClient = class extends BaseClient {
3326
3523
  { method: "POST" }
3327
3524
  );
3328
3525
  }
3329
- async checkTINStatus(customerID) {
3526
+ async checkTINStatus(customerID, requestOptions) {
3330
3527
  return this.requestWithRetry(
3331
3528
  `/api/v1/tax/customer-tax-profiles/${customerID}/check-tin`,
3332
- { method: "POST" }
3529
+ { method: "POST" },
3530
+ void 0,
3531
+ void 0,
3532
+ requestOptions
3333
3533
  );
3334
3534
  }
3335
3535
  async reRequestTaxForm(customerID, input) {
@@ -3409,15 +3609,48 @@ var TaxClient = class extends BaseClient {
3409
3609
  }
3410
3610
  };
3411
3611
 
3612
+ // src/webhooks/dedup.ts
3613
+ var InMemoryDedupStore = class {
3614
+ constructor(opts = {}) {
3615
+ this.expiryById = /* @__PURE__ */ new Map();
3616
+ this.maxEntries = opts.maxEntries ?? 1e3;
3617
+ this.now = opts.now ?? (() => Date.now());
3618
+ }
3619
+ async seen(id) {
3620
+ return this.expiryById.has(id);
3621
+ }
3622
+ async mark(id, ttlMs) {
3623
+ const now = this.now();
3624
+ this.expiryById.set(id, now + ttlMs);
3625
+ if (this.expiryById.size > this.maxEntries) {
3626
+ for (const [key, expiresAt] of this.expiryById) {
3627
+ if (now > expiresAt) {
3628
+ this.expiryById.delete(key);
3629
+ }
3630
+ }
3631
+ }
3632
+ }
3633
+ };
3634
+
3412
3635
  // src/webhooks/handler.ts
3636
+ function resolveEventId(event) {
3637
+ if (event.id) {
3638
+ return event.id;
3639
+ }
3640
+ if (event.event_type === "notification.created") {
3641
+ return event.notification?.id;
3642
+ }
3643
+ return void 0;
3644
+ }
3413
3645
  var WebhookHandler = class {
3414
3646
  constructor(config) {
3415
3647
  this.handlers = /* @__PURE__ */ new Map();
3416
3648
  this.anyHandlers = [];
3417
- this.seenEventIds = /* @__PURE__ */ new Map();
3418
3649
  this.secret = config.secret;
3419
3650
  this.tolerance = config.tolerance ?? 3e5;
3420
3651
  this.replayProtection = config.replayProtection ?? true;
3652
+ this.allowLegacySignature = config.allowLegacySignature ?? true;
3653
+ this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
3421
3654
  }
3422
3655
  /**
3423
3656
  * Register a handler for a specific event type.
@@ -3439,7 +3672,10 @@ var WebhookHandler = class {
3439
3672
  * Verify signature and parse the webhook body.
3440
3673
  */
3441
3674
  async verifyAndParse(body, signature) {
3442
- const isValid = await verifyWebhookSignature(body, signature, this.secret);
3675
+ const isValid = await verifyWebhookSignature(body, signature, this.secret, {
3676
+ tolerance: this.tolerance,
3677
+ allowLegacy: this.allowLegacySignature
3678
+ });
3443
3679
  if (!isValid) {
3444
3680
  throw new ValidationError("Invalid webhook signature", ["signature"]);
3445
3681
  }
@@ -3455,18 +3691,21 @@ var WebhookHandler = class {
3455
3691
  /**
3456
3692
  * Parse an event without signature verification (for testing).
3457
3693
  */
3458
- parseEvent(body) {
3694
+ async parseEvent(body) {
3459
3695
  return this.parseAndValidate(body);
3460
3696
  }
3461
- parseAndValidate(body) {
3697
+ async parseAndValidate(body) {
3462
3698
  const event = JSON.parse(body);
3463
- if (!event.type || !event.id || !event.timestamp) {
3464
- throw new ValidationError("Invalid webhook event: missing required fields (type, id, timestamp)", ["type", "id", "timestamp"]);
3699
+ const eventId = resolveEventId(event);
3700
+ if (!event.event_type || !eventId || !event.timestamp) {
3701
+ throw new ValidationError(
3702
+ "Invalid webhook event: missing required fields (event_type, id, timestamp)",
3703
+ ["event_type", "id", "timestamp"]
3704
+ );
3465
3705
  }
3466
3706
  if (this.tolerance > 0) {
3467
3707
  const eventTime = new Date(event.timestamp).getTime();
3468
- const now = Date.now();
3469
- if (Math.abs(now - eventTime) > this.tolerance) {
3708
+ if (Math.abs(Date.now() - eventTime) > this.tolerance) {
3470
3709
  throw new ValidationError(
3471
3710
  `Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
3472
3711
  ["timestamp"]
@@ -3474,26 +3713,18 @@ var WebhookHandler = class {
3474
3713
  }
3475
3714
  }
3476
3715
  if (this.replayProtection) {
3477
- if (this.seenEventIds.has(event.id)) {
3716
+ if (await this.dedupStore.seen(eventId)) {
3478
3717
  throw new ValidationError(
3479
- `Duplicate webhook event: ${event.id} has already been processed`,
3718
+ `Duplicate webhook event: ${eventId} has already been processed`,
3480
3719
  ["id"]
3481
3720
  );
3482
3721
  }
3483
- const now = Date.now();
3484
- this.seenEventIds.set(event.id, now);
3485
- if (this.seenEventIds.size > 1e3) {
3486
- for (const [id, seenAt] of this.seenEventIds) {
3487
- if (now - seenAt > this.tolerance) {
3488
- this.seenEventIds.delete(id);
3489
- }
3490
- }
3491
- }
3722
+ await this.dedupStore.mark(eventId, this.tolerance);
3492
3723
  }
3493
3724
  return event;
3494
3725
  }
3495
3726
  async dispatch(event) {
3496
- const typeHandlers = this.handlers.get(event.type) || [];
3727
+ const typeHandlers = this.handlers.get(event.event_type) || [];
3497
3728
  const allHandlers = [...typeHandlers, ...this.anyHandlers];
3498
3729
  for (const handler of allHandlers) {
3499
3730
  await handler(event);
@@ -3574,6 +3805,6 @@ function buildHandler(options) {
3574
3805
  return handler;
3575
3806
  }
3576
3807
 
3577
- 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 };
3808
+ 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, createConsoleLogger, createNextWebhookHandler, createWebhookMiddleware, decodeCipherText, generateCipherText, gpsIntegrityInputFromPosition, isCipherTextExpired, noopLogger, probeGeolocationPermission, sdkReasons, verifyWebhookSignature };
3578
3809
  //# sourceMappingURL=index.mjs.map
3579
3810
  //# sourceMappingURL=index.mjs.map