vesant-sdk 1.7.0-dev.fce9898 → 1.7.0-dev.fe84882

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-DoMSYMMR.d.ts → client-0NOPDnT0.d.ts} +29 -26
  2. package/dist/{client-DMIRx7Tu.d.mts → client-CvWNRwwg.d.mts} +29 -26
  3. package/dist/{client-ZNdnpWe7.d.mts → client-Dps40EtF.d.mts} +2 -2
  4. package/dist/{client-C3DCmGe9.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 +133 -22
  10. package/dist/compliance/index.js.map +1 -1
  11. package/dist/compliance/index.mjs +133 -22
  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 +68 -7
  16. package/dist/decisions/index.js.map +1 -1
  17. package/dist/decisions/index.mjs +68 -7
  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 +136 -19
  22. package/dist/geolocation/index.js.map +1 -1
  23. package/dist/geolocation/index.mjs +134 -20
  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 +324 -57
  30. package/dist/index.js.map +1 -1
  31. package/dist/index.mjs +317 -58
  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 +94 -19
  36. package/dist/kyc/core.js.map +1 -1
  37. package/dist/kyc/core.mjs +94 -19
  38. package/dist/kyc/core.mjs.map +1 -1
  39. package/dist/kyc/index.d.mts +90 -13
  40. package/dist/kyc/index.d.ts +90 -13
  41. package/dist/kyc/index.js +94 -19
  42. package/dist/kyc/index.js.map +1 -1
  43. package/dist/kyc/index.mjs +94 -19
  44. package/dist/kyc/index.mjs.map +1 -1
  45. package/dist/react.d.mts +9 -3
  46. package/dist/react.d.ts +9 -3
  47. package/dist/react.js +180 -77
  48. package/dist/react.js.map +1 -1
  49. package/dist/react.mjs +180 -77
  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 +68 -7
  54. package/dist/risk-profile/index.js.map +1 -1
  55. package/dist/risk-profile/index.mjs +68 -7
  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 +68 -7
  60. package/dist/scores/index.js.map +1 -1
  61. package/dist/scores/index.mjs +68 -7
  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 +73 -9
  66. package/dist/tax/index.js.map +1 -1
  67. package/dist/tax/index.mjs +73 -9
  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) {
@@ -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,6 +853,61 @@ function constantTimeEqual(a, b) {
669
853
  return result === 0;
670
854
  }
671
855
 
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";
908
+ }
909
+ }
910
+
672
911
  // src/geolocation/ciphertext.ts
673
912
  var CIPHER_TEXT_EXPIRY_MINUTES = 5;
674
913
  async function computeHMAC(key, message) {
@@ -721,6 +960,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
721
960
  if (typeof navigator === "undefined" || !navigator.geolocation) {
722
961
  return null;
723
962
  }
963
+ const permissionState = await probeGeolocationPermission();
724
964
  return new Promise((resolve) => {
725
965
  navigator.geolocation.getCurrentPosition(
726
966
  (position) => {
@@ -737,6 +977,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
737
977
  resolve(null);
738
978
  return;
739
979
  }
980
+ const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
740
981
  resolve({
741
982
  latitude,
742
983
  longitude,
@@ -745,7 +986,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
745
986
  altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
746
987
  heading: position.coords.heading ?? void 0,
747
988
  speed: position.coords.speed ?? void 0,
748
- timestamp: position.timestamp
989
+ timestamp: position.timestamp,
990
+ integrity_flags: flags.length ? flags : void 0
749
991
  });
750
992
  },
751
993
  () => {
@@ -812,6 +1054,9 @@ async function generateCipherText(options, config) {
812
1054
  );
813
1055
  if (location) {
814
1056
  locationData = location;
1057
+ if (location.integrity_flags?.length) {
1058
+ warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
1059
+ }
815
1060
  } else if (gpsRequiredByConfig) {
816
1061
  throw new VesantError(
817
1062
  `GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
@@ -931,8 +1176,8 @@ var GeolocationClient = class extends BaseClient {
931
1176
  * ```
932
1177
  */
933
1178
  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"]);
1179
+ if (request.ip_address !== void 0 && !request.ip_address.trim()) {
1180
+ throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
936
1181
  }
937
1182
  return this.requestWithRetry("/api/v1/geo/verify", {
938
1183
  method: "POST",
@@ -1282,20 +1527,12 @@ var GeolocationClient = class extends BaseClient {
1282
1527
  *
1283
1528
  * @example
1284
1529
  * ```typescript
1285
- * // Using GPS (preferred)
1530
+ * // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
1286
1531
  * const result = await client.captureLocation(token, {
1287
1532
  * latitude: 37.7749,
1288
1533
  * longitude: -122.4194,
1289
1534
  * accuracy: 10
1290
1535
  * });
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
1536
  * ```
1300
1537
  */
1301
1538
  async captureLocation(token, capture, requestOptions) {
@@ -1800,9 +2037,6 @@ var ComplianceClient = class {
1800
2037
  if (cipherTextResult.risk?.is_blocked) {
1801
2038
  blockReasons.push(...cipherTextResult.risk.block_reasons_structured ?? []);
1802
2039
  }
1803
- if (cipherTextResult.risk?.location_mismatch) {
1804
- blockReasons.push(sdkReasons.gpsIPMismatch());
1805
- }
1806
2040
  }
1807
2041
  if (geoVerification.gps_required && !cipherTextResult) {
1808
2042
  blockReasons.push(sdkReasons.gpsRequired());
@@ -2712,7 +2946,13 @@ var KycClient = class extends BaseClient {
2712
2946
  *
2713
2947
  * Generates a link that the user can visit to submit their KYC documents.
2714
2948
  *
2715
- * @param request - Request containing the user ID, optional redirect URL, and optional callback URL (receives POST requests)
2949
+ * Optionally pass the customer's registered identity data as
2950
+ * `customer_data` (same shape as the geolocation `customer_data` block).
2951
+ * It seeds the customer's risk profile so document verification can
2952
+ * cross-check the submitted document against trusted reference data;
2953
+ * fields never overwrite data already on the profile.
2954
+ *
2955
+ * @param request - Request containing the user ID, optional redirect URL, optional callback URL (receives POST requests), and optional customer identity data
2716
2956
  * @returns Response containing the redirect link and KYC ID
2717
2957
  *
2718
2958
  * @example
@@ -2720,19 +2960,27 @@ var KycClient = class extends BaseClient {
2720
2960
  * const result = await client.requestKycSubmitLink({
2721
2961
  * user_id: "user_123",
2722
2962
  * redirect_url: "https://merchant.com/kyc-complete", // optional
2723
- * callback_url: "https://merchant.com/api/kyc-webhook" // optional - receives POST requests on status change
2963
+ * callback_url: "https://merchant.com/api/kyc-webhook", // optional - receives POST requests on status change
2964
+ * customer_data: { // optional - seeds the risk profile for document cross-checks
2965
+ * full_name: "John Doe",
2966
+ * date_of_birth: "1999-06-02", // ISO 8601
2967
+ * email: "john@example.com",
2968
+ * phone: "+94771234567",
2969
+ * address: "12 Main St, Colombo", // used for address verification
2970
+ * country: "LK"
2971
+ * }
2724
2972
  * });
2725
2973
  *
2726
2974
  * console.log(`Redirect user to: ${result.link}`);
2727
2975
  * console.log(`KYC ID: ${result.kyc_id}`);
2728
2976
  * ```
2729
2977
  */
2730
- async requestKycSubmitLink(request) {
2978
+ async requestKycSubmitLink(request, requestOptions) {
2731
2979
  return this.requestWithRetry("/api/v1/kyc/request", {
2732
2980
  method: "POST",
2733
2981
  body: JSON.stringify(request),
2734
2982
  headers: this.getUserHeaders()
2735
- });
2983
+ }, void 0, void 0, requestOptions);
2736
2984
  }
2737
2985
  /**
2738
2986
  * Create a Event-Based Face Verification session.
@@ -2765,12 +3013,12 @@ var KycClient = class extends BaseClient {
2765
3013
  *
2766
3014
  * @param request - Token from `createEventBasedFaceVerificationSession`, plus the base64 selfie.
2767
3015
  */
2768
- async submitEventBasedFaceVerificationSession(request) {
3016
+ async submitEventBasedFaceVerificationSession(request, requestOptions) {
2769
3017
  return this.request("/api/v1/kyc/face/submit", {
2770
3018
  method: "POST",
2771
3019
  body: JSON.stringify(request),
2772
3020
  headers: this.getUserHeaders()
2773
- });
3021
+ }, void 0, requestOptions);
2774
3022
  }
2775
3023
  /**
2776
3024
  * Look up the current state of a Event-Based Face Verification session by its
@@ -2869,7 +3117,7 @@ var KycClient = class extends BaseClient {
2869
3117
  * @example
2870
3118
  * ```typescript
2871
3119
  * const result = await client.submitVerification({
2872
- * reference: "customer_123",
3120
+ * reference: "4d2aff34-ab86-422d-992e-50b1234a5b67",
2873
3121
  * email: "customer@example.com",
2874
3122
  * country: "US",
2875
3123
  * document: {
@@ -2886,12 +3134,12 @@ var KycClient = class extends BaseClient {
2886
3134
  * console.log(`Verification submitted: ${result.reference}`);
2887
3135
  * ```
2888
3136
  */
2889
- async submitVerification(request) {
3137
+ async submitVerification(request, requestOptions) {
2890
3138
  return this.requestWithRetry("/api/v1/kyc/submit", {
2891
3139
  method: "POST",
2892
3140
  body: JSON.stringify(request),
2893
3141
  headers: this.getUserHeaders()
2894
- });
3142
+ }, void 0, void 0, requestOptions);
2895
3143
  }
2896
3144
  /**
2897
3145
  * Get a KYC request by ID
@@ -2901,7 +3149,7 @@ var KycClient = class extends BaseClient {
2901
3149
  *
2902
3150
  * @example
2903
3151
  * ```typescript
2904
- * const kyc = await client.getKycRequest("kyc_abc123");
3152
+ * const kyc = await client.getKycRequest("550e8400-e29b-41d4-a716-446655440000");
2905
3153
  * console.log(`Status: ${kyc.status}, ID Verified: ${kyc.id_verified}`);
2906
3154
  * ```
2907
3155
  */
@@ -2976,7 +3224,7 @@ var KycClient = class extends BaseClient {
2976
3224
  * @example
2977
3225
  * ```typescript
2978
3226
  * await client.requestAdditionalDocuments({
2979
- * id: "kyc_abc123",
3227
+ * id: "550e8400-e29b-41d4-a716-446655440000",
2980
3228
  * document_types: ["address", "document_two"],
2981
3229
  * message: "Please provide proof of address and secondary ID"
2982
3230
  * });
@@ -2997,7 +3245,7 @@ var KycClient = class extends BaseClient {
2997
3245
  *
2998
3246
  * @example
2999
3247
  * ```typescript
3000
- * const proofs = await client.getProofDownloadURLs("kyc_abc123");
3248
+ * const proofs = await client.getProofDownloadURLs("550e8400-e29b-41d4-a716-446655440000");
3001
3249
  * proofs.forEach(proof => {
3002
3250
  * console.log(`${proof.type}: ${proof.url} (expires: ${proof.expires_at})`);
3003
3251
  * });
@@ -3331,10 +3579,13 @@ var TaxClient = class extends BaseClient {
3331
3579
  { method: "POST" }
3332
3580
  );
3333
3581
  }
3334
- async checkTINStatus(customerID) {
3582
+ async checkTINStatus(customerID, requestOptions) {
3335
3583
  return this.requestWithRetry(
3336
3584
  `/api/v1/tax/customer-tax-profiles/${customerID}/check-tin`,
3337
- { method: "POST" }
3585
+ { method: "POST" },
3586
+ void 0,
3587
+ void 0,
3588
+ requestOptions
3338
3589
  );
3339
3590
  }
3340
3591
  async reRequestTaxForm(customerID, input) {
@@ -3415,14 +3666,24 @@ var TaxClient = class extends BaseClient {
3415
3666
  };
3416
3667
 
3417
3668
  // src/webhooks/handler.ts
3669
+ function resolveEventId(event) {
3670
+ if (event.id) {
3671
+ return event.id;
3672
+ }
3673
+ if (event.event_type === "notification.created") {
3674
+ return event.notification?.id;
3675
+ }
3676
+ return void 0;
3677
+ }
3418
3678
  var WebhookHandler = class {
3419
3679
  constructor(config) {
3420
3680
  this.handlers = /* @__PURE__ */ new Map();
3421
3681
  this.anyHandlers = [];
3422
- this.seenEventIds = /* @__PURE__ */ new Map();
3423
3682
  this.secret = config.secret;
3424
3683
  this.tolerance = config.tolerance ?? 3e5;
3425
3684
  this.replayProtection = config.replayProtection ?? true;
3685
+ this.allowLegacySignature = config.allowLegacySignature ?? true;
3686
+ this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
3426
3687
  }
3427
3688
  /**
3428
3689
  * Register a handler for a specific event type.
@@ -3444,7 +3705,10 @@ var WebhookHandler = class {
3444
3705
  * Verify signature and parse the webhook body.
3445
3706
  */
3446
3707
  async verifyAndParse(body, signature) {
3447
- const isValid = await verifyWebhookSignature(body, signature, this.secret);
3708
+ const isValid = await verifyWebhookSignature(body, signature, this.secret, {
3709
+ tolerance: this.tolerance,
3710
+ allowLegacy: this.allowLegacySignature
3711
+ });
3448
3712
  if (!isValid) {
3449
3713
  throw new ValidationError("Invalid webhook signature", ["signature"]);
3450
3714
  }
@@ -3460,18 +3724,21 @@ var WebhookHandler = class {
3460
3724
  /**
3461
3725
  * Parse an event without signature verification (for testing).
3462
3726
  */
3463
- parseEvent(body) {
3727
+ async parseEvent(body) {
3464
3728
  return this.parseAndValidate(body);
3465
3729
  }
3466
- parseAndValidate(body) {
3730
+ async parseAndValidate(body) {
3467
3731
  const event = JSON.parse(body);
3468
- if (!event.type || !event.id || !event.timestamp) {
3469
- throw new ValidationError("Invalid webhook event: missing required fields (type, id, timestamp)", ["type", "id", "timestamp"]);
3732
+ const eventId = resolveEventId(event);
3733
+ if (!event.event_type || !eventId || !event.timestamp) {
3734
+ throw new ValidationError(
3735
+ "Invalid webhook event: missing required fields (event_type, id, timestamp)",
3736
+ ["event_type", "id", "timestamp"]
3737
+ );
3470
3738
  }
3471
3739
  if (this.tolerance > 0) {
3472
3740
  const eventTime = new Date(event.timestamp).getTime();
3473
- const now = Date.now();
3474
- if (Math.abs(now - eventTime) > this.tolerance) {
3741
+ if (Math.abs(Date.now() - eventTime) > this.tolerance) {
3475
3742
  throw new ValidationError(
3476
3743
  `Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
3477
3744
  ["timestamp"]
@@ -3479,26 +3746,18 @@ var WebhookHandler = class {
3479
3746
  }
3480
3747
  }
3481
3748
  if (this.replayProtection) {
3482
- if (this.seenEventIds.has(event.id)) {
3749
+ if (await this.dedupStore.seen(eventId)) {
3483
3750
  throw new ValidationError(
3484
- `Duplicate webhook event: ${event.id} has already been processed`,
3751
+ `Duplicate webhook event: ${eventId} has already been processed`,
3485
3752
  ["id"]
3486
3753
  );
3487
3754
  }
3488
- const now = Date.now();
3489
- this.seenEventIds.set(event.id, now);
3490
- if (this.seenEventIds.size > 1e3) {
3491
- for (const [id, seenAt] of this.seenEventIds) {
3492
- if (now - seenAt > this.tolerance) {
3493
- this.seenEventIds.delete(id);
3494
- }
3495
- }
3496
- }
3755
+ await this.dedupStore.mark(eventId, this.tolerance);
3497
3756
  }
3498
3757
  return event;
3499
3758
  }
3500
3759
  async dispatch(event) {
3501
- const typeHandlers = this.handlers.get(event.type) || [];
3760
+ const typeHandlers = this.handlers.get(event.event_type) || [];
3502
3761
  const allHandlers = [...typeHandlers, ...this.anyHandlers];
3503
3762
  for (const handler of allHandlers) {
3504
3763
  await handler(event);
@@ -3589,26 +3848,34 @@ exports.ComplianceClient = ComplianceClient;
3589
3848
  exports.ComplianceError = ComplianceError;
3590
3849
  exports.DEFAULT_CURRENCY_RATES = DEFAULT_CURRENCY_RATES;
3591
3850
  exports.GeolocationClient = GeolocationClient;
3851
+ exports.InMemoryDedupStore = InMemoryDedupStore;
3592
3852
  exports.KYC_DECLINED_DESCRIPTIONS = KYC_DECLINED_DESCRIPTIONS;
3593
3853
  exports.KycClient = KycClient;
3594
3854
  exports.NetworkError = NetworkError;
3595
3855
  exports.RateLimitError = RateLimitError;
3596
3856
  exports.RateLimitTracker = RateLimitTracker;
3857
+ exports.RefreshingTokenProvider = RefreshingTokenProvider;
3597
3858
  exports.RiskProfileClient = RiskProfileClient;
3598
3859
  exports.SDK_VERSION = SDK_VERSION;
3599
3860
  exports.ServiceUnavailableError = ServiceUnavailableError;
3861
+ exports.StaticApiKeyProvider = StaticApiKeyProvider;
3600
3862
  exports.TaxClient = TaxClient;
3601
3863
  exports.TimeoutError = TimeoutError;
3602
3864
  exports.ValidationError = ValidationError;
3603
3865
  exports.VesantError = VesantError;
3604
3866
  exports.WebhookHandler = WebhookHandler;
3867
+ exports.assessGpsIntegrity = assessGpsIntegrity;
3868
+ exports.collectAll = collectAll;
3605
3869
  exports.createConsoleLogger = createConsoleLogger;
3606
3870
  exports.createNextWebhookHandler = createNextWebhookHandler;
3607
3871
  exports.createWebhookMiddleware = createWebhookMiddleware;
3608
3872
  exports.decodeCipherText = decodeCipherText;
3609
3873
  exports.generateCipherText = generateCipherText;
3874
+ exports.gpsIntegrityInputFromPosition = gpsIntegrityInputFromPosition;
3610
3875
  exports.isCipherTextExpired = isCipherTextExpired;
3611
3876
  exports.noopLogger = noopLogger;
3877
+ exports.paginate = paginate;
3878
+ exports.probeGeolocationPermission = probeGeolocationPermission;
3612
3879
  exports.sdkReasons = sdkReasons;
3613
3880
  exports.verifyWebhookSignature = verifyWebhookSignature;
3614
3881
  //# sourceMappingURL=index.js.map