vesant-sdk 1.7.0-dev.6f300f7 → 1.7.0-dev.6f4bf05

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 (61) hide show
  1. package/dist/{client-D9JZgN3X.d.ts → client-0NOPDnT0.d.ts} +15 -25
  2. package/dist/{client-BG8KtVMc.d.mts → client-CvWNRwwg.d.mts} +15 -25
  3. package/dist/{client-CRZea-eJ.d.mts → client-Dps40EtF.d.mts} +1 -1
  4. package/dist/{client-749WqhDT.d.ts → client-SMxqod4j.d.ts} +1 -1
  5. package/dist/compliance/index.d.mts +2 -2
  6. package/dist/compliance/index.d.ts +2 -2
  7. package/dist/compliance/index.js +71 -12
  8. package/dist/compliance/index.js.map +1 -1
  9. package/dist/compliance/index.mjs +71 -12
  10. package/dist/compliance/index.mjs.map +1 -1
  11. package/dist/decisions/index.js +6 -0
  12. package/dist/decisions/index.js.map +1 -1
  13. package/dist/decisions/index.mjs +6 -0
  14. package/dist/decisions/index.mjs.map +1 -1
  15. package/dist/geolocation/index.d.mts +58 -3
  16. package/dist/geolocation/index.d.ts +58 -3
  17. package/dist/geolocation/index.js +74 -12
  18. package/dist/geolocation/index.js.map +1 -1
  19. package/dist/geolocation/index.mjs +72 -13
  20. package/dist/geolocation/index.mjs.map +1 -1
  21. package/dist/index-CPOrMRaH.d.ts +264 -0
  22. package/dist/index-CimDmgZb.d.mts +264 -0
  23. package/dist/index.d.mts +23 -6
  24. package/dist/index.d.ts +23 -6
  25. package/dist/index.js +186 -33
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +181 -34
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/kyc/core.js +6 -0
  30. package/dist/kyc/core.js.map +1 -1
  31. package/dist/kyc/core.mjs +6 -0
  32. package/dist/kyc/core.mjs.map +1 -1
  33. package/dist/kyc/index.js +6 -0
  34. package/dist/kyc/index.js.map +1 -1
  35. package/dist/kyc/index.mjs +6 -0
  36. package/dist/kyc/index.mjs.map +1 -1
  37. package/dist/react.d.mts +6 -2
  38. package/dist/react.d.ts +6 -2
  39. package/dist/react.js +68 -4
  40. package/dist/react.js.map +1 -1
  41. package/dist/react.mjs +68 -4
  42. package/dist/react.mjs.map +1 -1
  43. package/dist/risk-profile/index.js +6 -0
  44. package/dist/risk-profile/index.js.map +1 -1
  45. package/dist/risk-profile/index.mjs +6 -0
  46. package/dist/risk-profile/index.mjs.map +1 -1
  47. package/dist/scores/index.js +6 -0
  48. package/dist/scores/index.js.map +1 -1
  49. package/dist/scores/index.mjs +6 -0
  50. package/dist/scores/index.mjs.map +1 -1
  51. package/dist/tax/index.js +6 -0
  52. package/dist/tax/index.js.map +1 -1
  53. package/dist/tax/index.mjs +6 -0
  54. package/dist/tax/index.mjs.map +1 -1
  55. package/dist/webhooks/index.d.mts +2 -189
  56. package/dist/webhooks/index.d.ts +2 -189
  57. package/dist/webhooks/index.js +85 -21
  58. package/dist/webhooks/index.js.map +1 -1
  59. package/dist/webhooks/index.mjs +85 -22
  60. package/dist/webhooks/index.mjs.map +1 -1
  61. package/package.json +4 -1
package/dist/index.js CHANGED
@@ -380,6 +380,12 @@ var BaseClient = class {
380
380
  if (!config.tenantId?.trim()) {
381
381
  throw new ValidationError("tenantId is required and must be a non-empty string", ["tenantId"]);
382
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
+ }
383
389
  this.interceptors = config.interceptors || [];
384
390
  this.logger = config.logger || createConsoleLogger();
385
391
  let environment = config.environment;
@@ -724,8 +730,46 @@ var BaseClient = class {
724
730
  }
725
731
  };
726
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
+
727
758
  // src/core/webhook-utils.ts
728
- async function verifyWebhookSignature(payload, signature, secret) {
759
+ async function verifyWebhookSignature(payload, signature, secret, opts = {}) {
760
+ const signed = parseSignedTimestamp(signature);
761
+ if (signed) {
762
+ const tolerance = opts.tolerance ?? 3e5;
763
+ const now = opts.now?.() ?? Date.now();
764
+ if (tolerance > 0 && Math.abs(now - signed.t * 1e3) > tolerance) {
765
+ return false;
766
+ }
767
+ const expected = await computeHmacSha256(`${signed.t}.${payload}`, secret);
768
+ return constantTimeEqual(signed.v1, expected);
769
+ }
770
+ if (opts.allowLegacy === false) {
771
+ return false;
772
+ }
729
773
  const hexDigest = await computeHmacSha256(payload, secret);
730
774
  const expectedPrefixed = `sha256=${hexDigest}`;
731
775
  if (signature.startsWith("sha256=")) {
@@ -733,6 +777,25 @@ async function verifyWebhookSignature(payload, signature, secret) {
733
777
  }
734
778
  return constantTimeEqual(signature, hexDigest);
735
779
  }
780
+ function parseSignedTimestamp(signature) {
781
+ if (!signature.includes("t=") || !signature.includes("v1=")) {
782
+ return null;
783
+ }
784
+ let t;
785
+ let v1;
786
+ for (const part of signature.split(",")) {
787
+ const eq = part.indexOf("=");
788
+ if (eq === -1) continue;
789
+ const key = part.slice(0, eq).trim();
790
+ const value = part.slice(eq + 1).trim();
791
+ if (key === "t") t = Number(value);
792
+ else if (key === "v1") v1 = value;
793
+ }
794
+ if (t === void 0 || Number.isNaN(t) || !v1) {
795
+ return null;
796
+ }
797
+ return { t, v1 };
798
+ }
736
799
  async function computeHmacSha256(message, secret) {
737
800
  if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
738
801
  const encoder = new TextEncoder();
@@ -767,6 +830,61 @@ function constantTimeEqual(a, b) {
767
830
  return result === 0;
768
831
  }
769
832
 
833
+ // src/geolocation/integrity.ts
834
+ var isNil = (v) => v === null || v === void 0;
835
+ function assessGpsIntegrity(input, opts = {}) {
836
+ const flags = [];
837
+ const now = opts.now ?? (() => Date.now());
838
+ const maxAgeMs = opts.maxAgeMs ?? 6e4;
839
+ const minAccuracy = opts.minAccuracyMeters ?? 1;
840
+ const { accuracy, altitude, altitudeAccuracy, heading, speed, timestamp } = input;
841
+ if (isNil(accuracy)) {
842
+ flags.push("accuracy_missing");
843
+ } else if (accuracy === 0) {
844
+ flags.push("accuracy_zero");
845
+ } else if (accuracy < minAccuracy) {
846
+ flags.push("accuracy_implausibly_small");
847
+ }
848
+ const hasMotion = !isNil(heading) || !isNil(speed);
849
+ if (hasMotion && (isNil(accuracy) || accuracy === 0)) {
850
+ flags.push("motion_without_accuracy");
851
+ }
852
+ if (!isNil(altitude) && isNil(altitudeAccuracy)) {
853
+ flags.push("altitude_without_accuracy");
854
+ }
855
+ if (timestamp !== void 0 && now() - timestamp > maxAgeMs) {
856
+ flags.push("stale_timestamp");
857
+ }
858
+ if (opts.permissionState === "denied" || opts.permissionState === "prompt") {
859
+ flags.push(`permission_${opts.permissionState}`);
860
+ }
861
+ return { suspicious: flags.length > 0, flags };
862
+ }
863
+ function gpsIntegrityInputFromPosition(position) {
864
+ const c = position.coords;
865
+ return {
866
+ latitude: c.latitude,
867
+ longitude: c.longitude,
868
+ accuracy: c.accuracy,
869
+ altitude: c.altitude,
870
+ altitudeAccuracy: c.altitudeAccuracy,
871
+ heading: c.heading,
872
+ speed: c.speed,
873
+ timestamp: position.timestamp
874
+ };
875
+ }
876
+ async function probeGeolocationPermission() {
877
+ try {
878
+ if (typeof navigator === "undefined" || !navigator.permissions?.query) {
879
+ return "unavailable";
880
+ }
881
+ const status = await navigator.permissions.query({ name: "geolocation" });
882
+ return status.state;
883
+ } catch {
884
+ return "unavailable";
885
+ }
886
+ }
887
+
770
888
  // src/geolocation/ciphertext.ts
771
889
  var CIPHER_TEXT_EXPIRY_MINUTES = 5;
772
890
  async function computeHMAC(key, message) {
@@ -819,6 +937,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
819
937
  if (typeof navigator === "undefined" || !navigator.geolocation) {
820
938
  return null;
821
939
  }
940
+ const permissionState = await probeGeolocationPermission();
822
941
  return new Promise((resolve) => {
823
942
  navigator.geolocation.getCurrentPosition(
824
943
  (position) => {
@@ -835,6 +954,7 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
835
954
  resolve(null);
836
955
  return;
837
956
  }
957
+ const { flags } = assessGpsIntegrity(gpsIntegrityInputFromPosition(position), { permissionState });
838
958
  resolve({
839
959
  latitude,
840
960
  longitude,
@@ -843,7 +963,8 @@ async function requestGPSLocation(timeout = 1e4, highAccuracy = true) {
843
963
  altitude_accuracy: position.coords.altitudeAccuracy ?? void 0,
844
964
  heading: position.coords.heading ?? void 0,
845
965
  speed: position.coords.speed ?? void 0,
846
- timestamp: position.timestamp
966
+ timestamp: position.timestamp,
967
+ integrity_flags: flags.length ? flags : void 0
847
968
  });
848
969
  },
849
970
  () => {
@@ -910,6 +1031,9 @@ async function generateCipherText(options, config) {
910
1031
  );
911
1032
  if (location) {
912
1033
  locationData = location;
1034
+ if (location.integrity_flags?.length) {
1035
+ warnings.push(`GPS integrity flags (advisory): ${location.integrity_flags.join(", ")}`);
1036
+ }
913
1037
  } else if (gpsRequiredByConfig) {
914
1038
  throw new VesantError(
915
1039
  `GPS location is required for ${options.reason} by tenant configuration, but GPS was not available or permission was denied`,
@@ -1029,8 +1153,8 @@ var GeolocationClient = class extends BaseClient {
1029
1153
  * ```
1030
1154
  */
1031
1155
  async verifyIP(request, requestOptions) {
1032
- if (!request.ip_address?.trim()) {
1033
- throw new ValidationError("ip_address is required and must be a non-empty string", ["ip_address"]);
1156
+ if (request.ip_address !== void 0 && !request.ip_address.trim()) {
1157
+ throw new ValidationError("ip_address, when provided, must be a non-empty string", ["ip_address"]);
1034
1158
  }
1035
1159
  return this.requestWithRetry("/api/v1/geo/verify", {
1036
1160
  method: "POST",
@@ -1380,20 +1504,12 @@ var GeolocationClient = class extends BaseClient {
1380
1504
  *
1381
1505
  * @example
1382
1506
  * ```typescript
1383
- * // Using GPS (preferred)
1507
+ * // GPS (the web SDK is GPS + IP only; the server handles the IP fallback)
1384
1508
  * const result = await client.captureLocation(token, {
1385
1509
  * latitude: 37.7749,
1386
1510
  * longitude: -122.4194,
1387
1511
  * accuracy: 10
1388
1512
  * });
1389
- *
1390
- * // Using WiFi positioning (fallback)
1391
- * const result = await client.captureLocation(token, {
1392
- * wifi_networks: [
1393
- * { macAddress: "00:11:22:33:44:55", signalStrength: -50 },
1394
- * { macAddress: "AA:BB:CC:DD:EE:FF", signalStrength: -70 }
1395
- * ]
1396
- * });
1397
1513
  * ```
1398
1514
  */
1399
1515
  async captureLocation(token, capture, requestOptions) {
@@ -3526,15 +3642,48 @@ var TaxClient = class extends BaseClient {
3526
3642
  }
3527
3643
  };
3528
3644
 
3645
+ // src/webhooks/dedup.ts
3646
+ var InMemoryDedupStore = class {
3647
+ constructor(opts = {}) {
3648
+ this.expiryById = /* @__PURE__ */ new Map();
3649
+ this.maxEntries = opts.maxEntries ?? 1e3;
3650
+ this.now = opts.now ?? (() => Date.now());
3651
+ }
3652
+ async seen(id) {
3653
+ return this.expiryById.has(id);
3654
+ }
3655
+ async mark(id, ttlMs) {
3656
+ const now = this.now();
3657
+ this.expiryById.set(id, now + ttlMs);
3658
+ if (this.expiryById.size > this.maxEntries) {
3659
+ for (const [key, expiresAt] of this.expiryById) {
3660
+ if (now > expiresAt) {
3661
+ this.expiryById.delete(key);
3662
+ }
3663
+ }
3664
+ }
3665
+ }
3666
+ };
3667
+
3529
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
+ }
3530
3678
  var WebhookHandler = class {
3531
3679
  constructor(config) {
3532
3680
  this.handlers = /* @__PURE__ */ new Map();
3533
3681
  this.anyHandlers = [];
3534
- this.seenEventIds = /* @__PURE__ */ new Map();
3535
3682
  this.secret = config.secret;
3536
3683
  this.tolerance = config.tolerance ?? 3e5;
3537
3684
  this.replayProtection = config.replayProtection ?? true;
3685
+ this.allowLegacySignature = config.allowLegacySignature ?? true;
3686
+ this.dedupStore = config.dedupStore ?? new InMemoryDedupStore();
3538
3687
  }
3539
3688
  /**
3540
3689
  * Register a handler for a specific event type.
@@ -3556,7 +3705,10 @@ var WebhookHandler = class {
3556
3705
  * Verify signature and parse the webhook body.
3557
3706
  */
3558
3707
  async verifyAndParse(body, signature) {
3559
- 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
+ });
3560
3712
  if (!isValid) {
3561
3713
  throw new ValidationError("Invalid webhook signature", ["signature"]);
3562
3714
  }
@@ -3572,18 +3724,21 @@ var WebhookHandler = class {
3572
3724
  /**
3573
3725
  * Parse an event without signature verification (for testing).
3574
3726
  */
3575
- parseEvent(body) {
3727
+ async parseEvent(body) {
3576
3728
  return this.parseAndValidate(body);
3577
3729
  }
3578
- parseAndValidate(body) {
3730
+ async parseAndValidate(body) {
3579
3731
  const event = JSON.parse(body);
3580
- if (!event.type || !event.id || !event.timestamp) {
3581
- 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
+ );
3582
3738
  }
3583
3739
  if (this.tolerance > 0) {
3584
3740
  const eventTime = new Date(event.timestamp).getTime();
3585
- const now = Date.now();
3586
- if (Math.abs(now - eventTime) > this.tolerance) {
3741
+ if (Math.abs(Date.now() - eventTime) > this.tolerance) {
3587
3742
  throw new ValidationError(
3588
3743
  `Webhook event timestamp is outside tolerance window (${this.tolerance}ms)`,
3589
3744
  ["timestamp"]
@@ -3591,26 +3746,18 @@ var WebhookHandler = class {
3591
3746
  }
3592
3747
  }
3593
3748
  if (this.replayProtection) {
3594
- if (this.seenEventIds.has(event.id)) {
3749
+ if (await this.dedupStore.seen(eventId)) {
3595
3750
  throw new ValidationError(
3596
- `Duplicate webhook event: ${event.id} has already been processed`,
3751
+ `Duplicate webhook event: ${eventId} has already been processed`,
3597
3752
  ["id"]
3598
3753
  );
3599
3754
  }
3600
- const now = Date.now();
3601
- this.seenEventIds.set(event.id, now);
3602
- if (this.seenEventIds.size > 1e3) {
3603
- for (const [id, seenAt] of this.seenEventIds) {
3604
- if (now - seenAt > this.tolerance) {
3605
- this.seenEventIds.delete(id);
3606
- }
3607
- }
3608
- }
3755
+ await this.dedupStore.mark(eventId, this.tolerance);
3609
3756
  }
3610
3757
  return event;
3611
3758
  }
3612
3759
  async dispatch(event) {
3613
- const typeHandlers = this.handlers.get(event.type) || [];
3760
+ const typeHandlers = this.handlers.get(event.event_type) || [];
3614
3761
  const allHandlers = [...typeHandlers, ...this.anyHandlers];
3615
3762
  for (const handler of allHandlers) {
3616
3763
  await handler(event);
@@ -3701,6 +3848,7 @@ exports.ComplianceClient = ComplianceClient;
3701
3848
  exports.ComplianceError = ComplianceError;
3702
3849
  exports.DEFAULT_CURRENCY_RATES = DEFAULT_CURRENCY_RATES;
3703
3850
  exports.GeolocationClient = GeolocationClient;
3851
+ exports.InMemoryDedupStore = InMemoryDedupStore;
3704
3852
  exports.KYC_DECLINED_DESCRIPTIONS = KYC_DECLINED_DESCRIPTIONS;
3705
3853
  exports.KycClient = KycClient;
3706
3854
  exports.NetworkError = NetworkError;
@@ -3716,13 +3864,18 @@ exports.TimeoutError = TimeoutError;
3716
3864
  exports.ValidationError = ValidationError;
3717
3865
  exports.VesantError = VesantError;
3718
3866
  exports.WebhookHandler = WebhookHandler;
3867
+ exports.assessGpsIntegrity = assessGpsIntegrity;
3868
+ exports.collectAll = collectAll;
3719
3869
  exports.createConsoleLogger = createConsoleLogger;
3720
3870
  exports.createNextWebhookHandler = createNextWebhookHandler;
3721
3871
  exports.createWebhookMiddleware = createWebhookMiddleware;
3722
3872
  exports.decodeCipherText = decodeCipherText;
3723
3873
  exports.generateCipherText = generateCipherText;
3874
+ exports.gpsIntegrityInputFromPosition = gpsIntegrityInputFromPosition;
3724
3875
  exports.isCipherTextExpired = isCipherTextExpired;
3725
3876
  exports.noopLogger = noopLogger;
3877
+ exports.paginate = paginate;
3878
+ exports.probeGeolocationPermission = probeGeolocationPermission;
3726
3879
  exports.sdkReasons = sdkReasons;
3727
3880
  exports.verifyWebhookSignature = verifyWebhookSignature;
3728
3881
  //# sourceMappingURL=index.js.map