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.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) {
@@ -347,6 +403,7 @@ var BaseClient = class {
347
403
  interceptors: this.interceptors,
348
404
  logger: this.logger
349
405
  };
406
+ this.authProvider = config.authProvider ?? (apiKey ? new StaticApiKeyProvider(apiKey) : void 0);
350
407
  if (config.circuitBreaker) {
351
408
  this.circuitBreaker = new CircuitBreaker(config.circuitBreaker);
352
409
  }
@@ -354,6 +411,19 @@ var BaseClient = class {
354
411
  this.rateLimitTracker = new RateLimitTracker();
355
412
  }
356
413
  }
414
+ /**
415
+ * Resolve the Idempotency-Key for a request. Mutating methods (POST/PUT/PATCH)
416
+ * get the caller-supplied key or a freshly minted UUID; non-mutating methods get
417
+ * none. requestWithRetry resolves this once and injects it so every retry reuses
418
+ * one key (letting the server dedup replays); a direct request() resolves per call.
419
+ */
420
+ resolveIdempotencyKey(method, requestOptions) {
421
+ const isMutating = ["POST", "PUT", "PATCH"].includes((method || "GET").toUpperCase());
422
+ if (!isMutating) {
423
+ return void 0;
424
+ }
425
+ return requestOptions?.idempotencyKey ?? generateUUID();
426
+ }
357
427
  /**
358
428
  * Make an HTTP request with timeout and error handling
359
429
  */
@@ -379,15 +449,20 @@ var BaseClient = class {
379
449
  ...this.config.headers,
380
450
  ...options.headers || {}
381
451
  };
382
- if (this.config.apiKey) {
383
- headers["Authorization"] = `Bearer ${this.config.apiKey}`;
452
+ const credential = await this.authProvider?.getCredential({
453
+ tenantId: this.config.tenantId,
454
+ requestId
455
+ });
456
+ if (credential) {
457
+ headers["Authorization"] = `${credential.scheme ?? "Bearer"} ${credential.token}`;
384
458
  }
385
459
  if (this.config.environment === "sandbox") {
386
460
  headers["X-Sandbox"] = "true";
387
461
  }
388
462
  const method = (options.method || "GET").toUpperCase();
389
- if (["POST", "PUT", "PATCH"].includes(method)) {
390
- headers["Idempotency-Key"] = requestOptions?.idempotencyKey || generateUUID();
463
+ const idempotencyKey = this.resolveIdempotencyKey(method, requestOptions);
464
+ if (idempotencyKey) {
465
+ headers["Idempotency-Key"] = idempotencyKey;
391
466
  }
392
467
  const controller = new AbortController();
393
468
  const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
@@ -408,10 +483,28 @@ var BaseClient = class {
408
483
  if (this.config.debug) {
409
484
  this.logger.debug(`${finalOptions.method || "GET"} ${endpoint}`);
410
485
  }
411
- const response = await fetch(url, {
486
+ let response = await fetch(url, {
412
487
  ...finalOptions,
413
488
  signal: controller.signal
414
489
  });
490
+ if (response.status === 401 && this.authProvider?.canRefresh?.()) {
491
+ const refreshed = await this.authProvider.getCredential({
492
+ tenantId: this.config.tenantId,
493
+ requestId,
494
+ forceRefresh: true
495
+ });
496
+ if (refreshed) {
497
+ this.config.onCredentialRotated?.({ reason: "unauthorized" });
498
+ finalOptions = {
499
+ ...finalOptions,
500
+ headers: {
501
+ ...finalOptions.headers,
502
+ Authorization: `${refreshed.scheme ?? "Bearer"} ${refreshed.token}`
503
+ }
504
+ };
505
+ response = await fetch(url, { ...finalOptions, signal: controller.signal });
506
+ }
507
+ }
415
508
  clearTimeout(timeoutId);
416
509
  if (this.rateLimitTracker) {
417
510
  this.rateLimitTracker.updateFromHeaders(response.headers);
@@ -500,9 +593,11 @@ var BaseClient = class {
500
593
  */
501
594
  async requestWithRetry(endpoint, options = {}, serviceURL, retries = this.config.retries, requestOptions) {
502
595
  let lastError;
596
+ const idempotencyKey = this.resolveIdempotencyKey(options.method || "GET", requestOptions);
597
+ const attemptOptions = idempotencyKey ? { ...requestOptions, idempotencyKey } : requestOptions;
503
598
  for (let attempt = 0; attempt <= retries; attempt++) {
504
599
  try {
505
- return await this.request(endpoint, options, serviceURL, requestOptions);
600
+ return await this.request(endpoint, options, serviceURL, attemptOptions);
506
601
  } catch (error) {
507
602
  lastError = error instanceof Error ? error : new Error("Unknown error");
508
603
  if (requestOptions?.signal?.aborted) {
@@ -599,6 +694,9 @@ var BaseClient = class {
599
694
  if (config.interceptors) {
600
695
  this.interceptors = config.interceptors;
601
696
  }
697
+ if (config.authProvider !== void 0 || config.apiKey !== void 0) {
698
+ this.authProvider = this.config.authProvider ?? (this.config.apiKey ? new StaticApiKeyProvider(this.config.apiKey) : void 0);
699
+ }
602
700
  }
603
701
  /**
604
702
  * Get current configuration (readonly)
@@ -627,7 +725,20 @@ var BaseClient = class {
627
725
  };
628
726
 
629
727
  // src/core/webhook-utils.ts
630
- async function verifyWebhookSignature(payload, signature, secret) {
728
+ async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
729
+ const signed = parseSignedTimestamp(signature);
730
+ if (signed) {
731
+ const tolerance = opts.tolerance ?? 3e5;
732
+ const now = opts.now?.() ?? Date.now();
733
+ if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
734
+ return false;
735
+ }
736
+ const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
737
+ return constantTimeEqual(signed.v1, expected);
738
+ }
739
+ if (opts.allowLegacy === false) {
740
+ return false;
741
+ }
631
742
  const hexDigest = await computeHmacSha256(payload, secret);
632
743
  const expectedPrefixed = `sha256=${hexDigest}`;
633
744
  if (signature.startsWith("sha256=")) {
@@ -635,6 +746,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
635
746
  }
636
747
  return constantTimeEqual(signature, hexDigest);
637
748
  }
749
+ function parseSignedTimestamp(signature) {
750
+ if (!signature.includes("t=") || !signature.includes("v1=")) {
751
+ return null;
752
+ }
753
+ let t;
754
+ let v1;
755
+ for (const part of signature.split(",")) {
756
+ const eq = part.indexOf("=");
757
+ if (eq === -1) continue;
758
+ const key = part.slice(0, eq).trim();
759
+ const value = part.slice(eq + 1).trim();
760
+ if (key === "t") t = Number(value);
761
+ else if (key === "v1") v1 = value;
762
+ }
763
+ if (t === void 0 || Number.isNaN(t) || !v1) {
764
+ return null;
765
+ }
766
+ return { t, v1 };
767
+ }
638
768
  async function computeHmacSha256(message, secret) {
639
769
  if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
640
770
  const encoder = new TextEncoder();
@@ -669,6 +799,61 @@ function constantTimeEqual(a, b) {
669
799
  return result === 0;
670
800
  }
671
801
 
802
+ // src/geolocation/integrity.ts
803
+ var isNil = (v) => v === null || v === void 0;
804
+ function assessGpsIntegrity(input, opts = {}) {
805
+ const flags = [];
806
+ const now = opts.now ?? (() => Date.now());
807
+ const maxAgeMs = opts.maxAgeMs ?? 6e4;
808
+ const minAccuracy = opts.minAccuracyMeters ?? 1;
809
+ const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
810
+ if (isNil(accuracy)) {
811
+ flags.push("accuracy_missing");
812
+ } else if (accuracy === 0) {
813
+ flags.push("accuracy_zero");
814
+ } else if (accuracy < minAccuracy) {
815
+ flags.push("accuracy_implausibly_small");
816
+ }
817
+ const hasMotion = !isNil(heading) || !isNil(speed);
818
+ if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
819
+ flags.push("motion_without_accuracy");
820
+ }
821
+ if (!isNil(altitude) && isNil(altitudeAccuracy)) {
822
+ flags.push("altitude_without_accuracy");
823
+ }
824
+ if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
825
+ flags.push("stale_timestamp");
826
+ }
827
+ if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
828
+ flags.push(`permission_${opts.permissionState}`);
829
+ }
830
+ return { suspicious: flags.length > 0, flags };
831
+ }
832
+ function gpsIntegrityInputFromPosition(position) {
833
+ const c = position.coords;
834
+ return {
835
+ latitude: c.latitude,
836
+ longitude: c.longitude,
837
+ accuracy: c.accuracy,
838
+ altitude: c.altitude,
839
+ altitudeAccuracy: c.altitudeAccuracy,
840
+ heading: c.heading,
841
+ speed: c.speed,
842
+ timestamp: position.timestamp
843
+ };
844
+ }
845
+ async function probeGeolocationPermission() {
846
+ try {
847
+ if (typeof navigator === "undefined" || !navigator.permissions?.query) {
848
+ return "unavailable";
849
+ }
850
+ const status = await navigator.permissions.query({ name: "geolocation" });
851
+ return status.state;
852
+ } catch {
853
+ return "unavailable";
854
+ }
855
+ }
856
+
672
857
  // src/geolocation/ciphertext.ts
673
858
  var CIPHER_TEXT_EXPIRY_MINUTES = 5;
674
859
  async function computeHMAC(key, message) {
@@ -721,6 +906,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
721
906
  if (typeof navigator === "undefined" || !navigator.geolocation) {
722
907
  return null;
723
908
  }
909
+ const permissionState = await probeGeolocationPermission();
724
910
  return new Promise((resolve) => {
725
911
  navigator.geolocation.getCurrentPosition(
726
912
  (position) => {
@@ -737,6 +923,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
737
923
  resolve(null);
738
924
  return;
739
925
  }
926
+ const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
740
927
  resolve({
741
928
  latitude,
742
929
  longitude,
@@ -745,7 +932,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
745
932
  altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
746
933
  heading: position.coords.heading ?? void 0,
747
934
  speed: position.coords.speed ?? void 0,
748
- timestamp: position.timestamp
935
+ timestamp: position.timestamp,
936
+ integrity_flags: flags.length ? flags : void 0
749
937
  });
750
938
  },
751
939
  () => {
@@ -812,6 +1000,9 @@ async function generateCipherText(options, config) {
812
1000
  );
813
1001
  if (location) {
814
1002
  locationData = location;
1003
+ if (location.integrity_flags?.length) {
1004
+ warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
1005
+ }
815
1006
  } else if (gpsRequiredByConfig) {
816
1007
  throw new VesantError(
817
1008
  `GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
@@ -931,8 +1122,8 @@ var GeolocationClient = class extends BaseClient {
931
1122
  * ```
932
1123
  */
933
1124
  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"]);
1125
+ if (request.ip_address !== void 0 && !request.ip_address.trim()) {
1126
+ throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
936
1127
  }
937
1128
  return this.requestWithRetry("/api/v1/geo/verify", {
938
1129
  method: "POST",
@@ -1282,20 +1473,12 @@ var GeolocationClient = class extends BaseClient {
1282
1473
  *
1283
1474
  * @example
1284
1475
  * ```typescript
1285
- * // Using GPS (preferred)
1476
+ * // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
1286
1477
  * const result = await client.captureLocation(token, {
1287
1478
  * latitude: 37.7749,
1288
1479
  * longitude: -122.4194,
1289
1480
  * accuracy: 10
1290
1481
  * });
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
1482
  * ```
1300
1483
  */
1301
1484
  async captureLocation(token, capture, requestOptions) {
@@ -2709,7 +2892,13 @@ var KycClient = class extends BaseClient {
2709
2892
  *
2710
2893
  * Generates a link that the user can visit to submit their KYC documents.
2711
2894
  *
2712
- * @param request - Request containing the user ID, optional redirect URL, and optional callback URL (receives POST requests)
2895
+ * Optionally pass the customer's registered identity data as
2896
+ * `customer_data` (same shape as the geolocation `customer_data` block).
2897
+ * It seeds the customer's risk profile so document verification can
2898
+ * cross-check the submitted document against trusted reference data;
2899
+ * fields never overwrite data already on the profile.
2900
+ *
2901
+ * @param request - Request containing the user ID, optional redirect URL, optional callback URL (receives POST requests), and optional customer identity data
2713
2902
  * @returns Response containing the redirect link and KYC ID
2714
2903
  *
2715
2904
  * @example
@@ -2717,19 +2906,27 @@ var KycClient = class extends BaseClient {
2717
2906
  * const result = await client.requestKycSubmitLink({
2718
2907
  * user_id: "user_123",
2719
2908
  * redirect_url: "https://merchant.com/kyc-complete", // optional
2720
- * callback_url: "https://merchant.com/api/kyc-webhook" // optional - receives POST requests on status change
2909
+ * callback_url: "https://merchant.com/api/kyc-webhook", // optional - receives POST requests on status change
2910
+ * customer_data: { // optional - seeds the risk profile for document cross-checks
2911
+ * full_name: "John Doe",
2912
+ * date_of_birth: "1999-06-02", // ISO 8601
2913
+ * email: "john@example.com",
2914
+ * phone: "+94771234567",
2915
+ * address: "12 Main St, Colombo", // used for address verification
2916
+ * country: "LK"
2917
+ * }
2721
2918
  * });
2722
2919
  *
2723
2920
  * console.log(`Redirect user to: ${result.link}`);
2724
2921
  * console.log(`KYC ID: ${result.kyc_id}`);
2725
2922
  * ```
2726
2923
  */
2727
- async requestKycSubmitLink(request) {
2924
+ async requestKycSubmitLink(request, requestOptions) {
2728
2925
  return this.requestWithRetry("/api/v1/kyc/request", {
2729
2926
  method: "POST",
2730
2927
  body: JSON.stringify(request),
2731
2928
  headers: this.getUserHeaders()
2732
- });
2929
+ }, void 0, void 0, requestOptions);
2733
2930
  }
2734
2931
  /**
2735
2932
  * Create a Event-Based Face Verification session.
@@ -2762,12 +2959,12 @@ var KycClient = class extends BaseClient {
2762
2959
  *
2763
2960
  * @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
2764
2961
  */
2765
- async submitEventBasedFaceVerificationSession(request) {
2962
+ async submitEventBasedFaceVerificationSession(request, requestOptions) {
2766
2963
  return this.request("/api/v1/kyc/face/submit", {
2767
2964
  method: "POST",
2768
2965
  body: JSON.stringify(request),
2769
2966
  headers: this.getUserHeaders()
2770
- });
2967
+ }, void 0, requestOptions);
2771
2968
  }
2772
2969
  /**
2773
2970
  * Look up the current state of a Event-Based Face Verification session by its
@@ -2883,12 +3080,12 @@ var KycClient = class extends BaseClient {
2883
3080
  * console.log(`Verification submitted: ${result.reference}`);
2884
3081
  * ```
2885
3082
  */
2886
- async submitVerification(request) {
3083
+ async submitVerification(request, requestOptions) {
2887
3084
  return this.requestWithRetry("/api/v1/kyc/submit", {
2888
3085
  method: "POST",
2889
3086
  body: JSON.stringify(request),
2890
3087
  headers: this.getUserHeaders()
2891
- });
3088
+ }, void 0, void 0, requestOptions);
2892
3089
  }
2893
3090
  /**
2894
3091
  * Get a KYC request by ID
@@ -3328,10 +3525,13 @@ var TaxClient = class extends BaseClient {
3328
3525
  { method: "POST" }
3329
3526
  );
3330
3527
  }
3331
- async checkTINStatus(customerID) {
3528
+ async checkTINStatus(customerID, requestOptions) {
3332
3529
  return this.requestWithRetry(
3333
3530
  `/api/v1/tax/customer-tax-profiles/${customerID}/check-tin`,
3334
- { method: "POST" }
3531
+ { method: "POST" },
3532
+ void 0,
3533
+ void 0,
3534
+ requestOptions
3335
3535
  );
3336
3536
  }
3337
3537
  async reRequestTaxForm(customerID, input) {
@@ -3411,15 +3611,48 @@ var TaxClient = class extends BaseClient {
3411
3611
  }
3412
3612
  };
3413
3613
 
3614
+ // src/webhooks/dedup.ts
3615
+ var InMemoryDedupStore = class {
3616
+ constructor(opts = {}) {
3617
+ this.expiryById = /* @__PURE__ */ new Map();
3618
+ this.maxEntries = opts.maxEntries ?? 1e3;
3619
+ this.now = opts.now ?? (() => Date.now());
3620
+ }
3621
+ async seen(id) {
3622
+ return this.expiryById.has(id);
3623
+ }
3624
+ async mark(id, ttlMs) {
3625
+ const now = this.now();
3626
+ this.expiryById.set(id, now + ttlMs);
3627
+ if (this.expiryById.size > this.maxEntries) {
3628
+ for (const [key, expiresAt] of this.expiryById) {
3629
+ if (now > expiresAt) {
3630
+ this.expiryById.delete(key);
3631
+ }
3632
+ }
3633
+ }
3634
+ }
3635
+ };
3636
+
3414
3637
  // src/webhooks/handler.ts
3638
+ function resolveEventId(event) {
3639
+ if (event.id) {
3640
+ return event.id;
3641
+ }
3642
+ if (event.event_type === "notification.created") {
3643
+ return event.notification?.id;
3644
+ }
3645
+ return void 0;
3646
+ }
3415
3647
  var WebhookHandler = class {
3416
3648
  constructor(config) {
3417
3649
  this.handlers = /* @__PURE__ */ new Map();
3418
3650
  this.anyHandlers = [];
3419
- this.seenEventIds = /* @__PURE__ */ new Map();
3420
3651
  this.secret = config.secret;
3421
3652
  this.tolerance = config.tolerance ?? 3e5;
3422
3653
  this.replayProtection = config.replayProtection ?? true;
3654
+ this.allowLegacySignature = config.allowLegacySignature ?? true;
3655
+ this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
3423
3656
  }
3424
3657
  /**
3425
3658
  * Register a handler for a specific event type.
@@ -3441,7 +3674,10 @@ var WebhookHandler = class {
3441
3674
  * Verify signature and parse the webhook body.
3442
3675
  */
3443
3676
  async verifyAndParse(body, signature) {
3444
- const isValid = await verifyWebhookSignature(body, signature, this.secret);
3677
+ const isValid = await verifyWebhookSignature(body, signature, this.secret, {
3678
+ tolerance: this.tolerance,
3679
+ allowLegacy: this.allowLegacySignature
3680
+ });
3445
3681
  if (!isValid) {
3446
3682
  throw new ValidationError("Invalid webhook signature", ["signature"]);
3447
3683
  }
@@ -3457,18 +3693,21 @@ var WebhookHandler = class {
3457
3693
  /**
3458
3694
  * Parse an event without signature verification (for testing).
3459
3695
  */
3460
- parseEvent(body) {
3696
+ async parseEvent(body) {
3461
3697
  return this.parseAndValidate(body);
3462
3698
  }
3463
- parseAndValidate(body) {
3699
+ async parseAndValidate(body) {
3464
3700
  const event = JSON.parse(body);
3465
- if (!event.type || !event.id || !event.timestamp) {
3466
- throw new ValidationError("Invalid webhook event: missing required fields (type, id, timestamp)", ["type", "id", "timestamp"]);
3701
+ const eventId = resolveEventId(event);
3702
+ if (!event.event_type || !eventId || !event.timestamp) {
3703
+ throw new ValidationError(
3704
+ "Invalid webhook event: missing required fields (event_type, id, timestamp)",
3705
+ ["event_type", "id", "timestamp"]
3706
+ );
3467
3707
  }
3468
3708
  if (this.tolerance > 0) {
3469
3709
  const eventTime = new Date(event.timestamp).getTime();
3470
- const now = Date.now();
3471
- if (Math.abs(now - eventTime) > this.tolerance) {
3710
+ if (Math.abs(Date.now() - eventTime) > this.tolerance) {
3472
3711
  throw new ValidationError(
3473
3712
  `Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
3474
3713
  ["timestamp"]
@@ -3476,26 +3715,18 @@ var WebhookHandler = class {
3476
3715
  }
3477
3716
  }
3478
3717
  if (this.replayProtection) {
3479
- if (this.seenEventIds.has(event.id)) {
3718
+ if (await this.dedupStore.seen(eventId)) {
3480
3719
  throw new ValidationError(
3481
- `Duplicate webhook event: ${event.id} has already been processed`,
3720
+ `Duplicate webhook event: ${eventId} has already been processed`,
3482
3721
  ["id"]
3483
3722
  );
3484
3723
  }
3485
- const now = Date.now();
3486
- this.seenEventIds.set(event.id, now);
3487
- if (this.seenEventIds.size > 1e3) {
3488
- for (const [id, seenAt] of this.seenEventIds) {
3489
- if (now - seenAt > this.tolerance) {
3490
- this.seenEventIds.delete(id);
3491
- }
3492
- }
3493
- }
3724
+ await this.dedupStore.mark(eventId, this.tolerance);
3494
3725
  }
3495
3726
  return event;
3496
3727
  }
3497
3728
  async dispatch(event) {
3498
- const typeHandlers = this.handlers.get(event.type) || [];
3729
+ const typeHandlers = this.handlers.get(event.event_type) || [];
3499
3730
  const allHandlers = [...typeHandlers, ...this.anyHandlers];
3500
3731
  for (const handler of allHandlers) {
3501
3732
  await handler(event);
@@ -3586,26 +3817,32 @@ exports.ComplianceClient = ComplianceClient;
3586
3817
  exports.ComplianceError = ComplianceError;
3587
3818
  exports.DEFAULT_CURRENCY_RATES = DEFAULT_CURRENCY_RATES;
3588
3819
  exports.GeolocationClient = GeolocationClient;
3820
+ exports.InMemoryDedupStore = InMemoryDedupStore;
3589
3821
  exports.KYC_DECLINED_DESCRIPTIONS = KYC_DECLINED_DESCRIPTIONS;
3590
3822
  exports.KycClient = KycClient;
3591
3823
  exports.NetworkError = NetworkError;
3592
3824
  exports.RateLimitError = RateLimitError;
3593
3825
  exports.RateLimitTracker = RateLimitTracker;
3826
+ exports.RefreshingTokenProvider = RefreshingTokenProvider;
3594
3827
  exports.RiskProfileClient = RiskProfileClient;
3595
3828
  exports.SDK_VERSION = SDK_VERSION;
3596
3829
  exports.ServiceUnavailableError = ServiceUnavailableError;
3830
+ exports.StaticApiKeyProvider = StaticApiKeyProvider;
3597
3831
  exports.TaxClient = TaxClient;
3598
3832
  exports.TimeoutError = TimeoutError;
3599
3833
  exports.ValidationError = ValidationError;
3600
3834
  exports.VesantError = VesantError;
3601
3835
  exports.WebhookHandler = WebhookHandler;
3836
+ exports.assessGpsIntegrity = assessGpsIntegrity;
3602
3837
  exports.createConsoleLogger = createConsoleLogger;
3603
3838
  exports.createNextWebhookHandler = createNextWebhookHandler;
3604
3839
  exports.createWebhookMiddleware = createWebhookMiddleware;
3605
3840
  exports.decodeCipherText = decodeCipherText;
3606
3841
  exports.generateCipherText = generateCipherText;
3842
+ exports.gpsIntegrityInputFromPosition = gpsIntegrityInputFromPosition;
3607
3843
  exports.isCipherTextExpired = isCipherTextExpired;
3608
3844
  exports.noopLogger = noopLogger;
3845
+ exports.probeGeolocationPermission = probeGeolocationPermission;
3609
3846
  exports.sdkReasons = sdkReasons;
3610
3847
  exports.verifyWebhookSignature = verifyWebhookSignature;
3611
3848
  //# sourceMappingURL=index.js.map