@formo/analytics 1.19.9 → 1.20.0

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 (43) hide show
  1. package/README.md +3 -1
  2. package/dist/cjs/src/FormoAnalytics.d.ts +17 -1
  3. package/dist/cjs/src/FormoAnalytics.js +91 -13
  4. package/dist/cjs/src/FormoAnalyticsProvider.js +5 -1
  5. package/dist/cjs/src/constants/base.d.ts +4 -0
  6. package/dist/cjs/src/constants/base.js +8 -1
  7. package/dist/cjs/src/lib/consent.d.ts +35 -0
  8. package/dist/cjs/src/lib/consent.js +102 -0
  9. package/dist/cjs/src/lib/event/EventManager.js +6 -0
  10. package/dist/cjs/src/lib/index.d.ts +1 -0
  11. package/dist/cjs/src/lib/index.js +1 -0
  12. package/dist/cjs/src/lib/storage/built-in/blueprint.js +2 -1
  13. package/dist/cjs/src/lib/version.d.ts +1 -1
  14. package/dist/cjs/src/lib/version.js +1 -1
  15. package/dist/cjs/src/types/base.d.ts +3 -0
  16. package/dist/cjs/src/utils/address.d.ts +7 -0
  17. package/dist/cjs/src/utils/address.js +21 -1
  18. package/dist/cjs/src/utils/hash.d.ts +10 -0
  19. package/dist/cjs/src/utils/hash.js +21 -0
  20. package/dist/cjs/src/utils/index.d.ts +1 -0
  21. package/dist/cjs/src/utils/index.js +1 -0
  22. package/dist/esm/src/FormoAnalytics.d.ts +17 -1
  23. package/dist/esm/src/FormoAnalytics.js +93 -15
  24. package/dist/esm/src/FormoAnalyticsProvider.js +5 -1
  25. package/dist/esm/src/constants/base.d.ts +4 -0
  26. package/dist/esm/src/constants/base.js +7 -0
  27. package/dist/esm/src/lib/consent.d.ts +35 -0
  28. package/dist/esm/src/lib/consent.js +97 -0
  29. package/dist/esm/src/lib/event/EventManager.js +6 -0
  30. package/dist/esm/src/lib/index.d.ts +1 -0
  31. package/dist/esm/src/lib/index.js +1 -0
  32. package/dist/esm/src/lib/storage/built-in/blueprint.js +2 -1
  33. package/dist/esm/src/lib/version.d.ts +1 -1
  34. package/dist/esm/src/lib/version.js +1 -1
  35. package/dist/esm/src/types/base.d.ts +3 -0
  36. package/dist/esm/src/utils/address.d.ts +7 -0
  37. package/dist/esm/src/utils/address.js +19 -0
  38. package/dist/esm/src/utils/hash.d.ts +10 -0
  39. package/dist/esm/src/utils/hash.js +18 -0
  40. package/dist/esm/src/utils/index.d.ts +1 -0
  41. package/dist/esm/src/utils/index.js +1 -0
  42. package/dist/index.umd.min.js +1 -1
  43. package/package.json +2 -2
@@ -55,8 +55,8 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
55
55
  return to.concat(ar || Array.prototype.slice.call(from));
56
56
  };
57
57
  import { createStore } from "mipd";
58
- import { EVENTS_API_URL, EventType, LOCAL_ANONYMOUS_ID_KEY, SESSION_CURRENT_URL_KEY, SESSION_USER_ID_KEY, SESSION_WALLET_DETECTED_KEY, SESSION_WALLET_IDENTIFIED_KEY, DEFAULT_PROVIDER_ICON, } from "./constants";
59
- import { cookie, EventManager, EventQueue, initStorageManager, logger, Logger, } from "./lib";
58
+ import { EVENTS_API_URL, EventType, LOCAL_ANONYMOUS_ID_KEY, SESSION_CURRENT_URL_KEY, SESSION_USER_ID_KEY, SESSION_WALLET_DETECTED_KEY, SESSION_WALLET_IDENTIFIED_KEY, DEFAULT_PROVIDER_ICON, CONSENT_OPT_OUT_KEY, } from "./constants";
59
+ import { cookie, EventManager, EventQueue, initStorageManager, logger, Logger, setConsentFlag, getConsentFlag, removeConsentFlag, } from "./lib";
60
60
  import { SignatureStatus, TransactionStatus, WRAPPED_REQUEST_SYMBOL, WRAPPED_REQUEST_REF_SYMBOL, } from "./types";
61
61
  import { toChecksumAddress } from "./utils";
62
62
  import { getValidAddress } from "./utils/address";
@@ -123,6 +123,10 @@ var FormoAnalytics = /** @class */ (function () {
123
123
  maxQueueSize: options.maxQueueSize,
124
124
  flushInterval: options.flushInterval,
125
125
  }));
126
+ // Check consent status on initialization
127
+ if (this.hasOptedOutTracking()) {
128
+ logger.info("User has previously opted out of tracking");
129
+ }
126
130
  // Handle initial provider (injected) as fallback; listeners for EIP-6963 are added later
127
131
  var provider = undefined;
128
132
  var optProvider = options.provider;
@@ -404,7 +408,7 @@ var FormoAnalytics = /** @class */ (function () {
404
408
  */
405
409
  FormoAnalytics.prototype.identify = function (params, properties, context, callback) {
406
410
  return __awaiter(this, void 0, void 0, function () {
407
- var _i, _a, providerDetail, provider, address_1, validAddress_1, err_1, userId, address, providerName, rdns, validAddress, e_1;
411
+ var _i, _a, providerDetail, provider, address_1, validAddress_1, err_1, userId, address, providerName, rdns, validAddress, isAlreadyIdentified, e_1;
408
412
  var _b;
409
413
  return __generator(this, function (_c) {
410
414
  switch (_c.label) {
@@ -429,6 +433,12 @@ var FormoAnalytics = /** @class */ (function () {
429
433
  address_1 = _c.sent();
430
434
  if (!address_1) return [3 /*break*/, 6];
431
435
  validAddress_1 = this.validateAndChecksumAddress(address_1);
436
+ logger.info("Auto-identify: Checking deduplication", {
437
+ validAddress: validAddress_1,
438
+ rdns: providerDetail.info.rdns,
439
+ providerName: providerDetail.info.name,
440
+ isAlreadyIdentified: validAddress_1 ? this.session.isWalletIdentified(validAddress_1, providerDetail.info.rdns) : false
441
+ });
432
442
  if (!(validAddress_1 && !this.session.isWalletIdentified(validAddress_1, providerDetail.info.rdns))) return [3 /*break*/, 5];
433
443
  logger.info("Auto-identifying", validAddress_1, providerDetail.info.name, providerDetail.info.rdns);
434
444
  // NOTE: do not set this.currentAddress without explicit connect or identify
@@ -473,14 +483,23 @@ var FormoAnalytics = /** @class */ (function () {
473
483
  this.currentUserId = userId;
474
484
  cookie().set(SESSION_USER_ID_KEY, userId);
475
485
  }
476
- // Check for duplicate identify events in this session
477
- if (validAddress && rdns && this.session.isWalletIdentified(validAddress, rdns)) {
478
- logger.warn("Identify: Wallet ".concat(providerName || 'Unknown', " with address ").concat(validAddress, " already identified in this session"));
486
+ isAlreadyIdentified = validAddress ? this.session.isWalletIdentified(validAddress, rdns || '') : false;
487
+ logger.debug("Identify: Checking deduplication", {
488
+ validAddress: validAddress,
489
+ rdns: rdns,
490
+ providerName: providerName,
491
+ hasValidAddress: !!validAddress,
492
+ hasRdns: !!rdns,
493
+ isAlreadyIdentified: isAlreadyIdentified
494
+ });
495
+ if (isAlreadyIdentified) {
496
+ logger.info("Identify: Wallet ".concat(providerName || 'Unknown', " with address ").concat(validAddress, " already identified in this session (rdns: ").concat(rdns || 'empty', ")"));
479
497
  return [2 /*return*/];
480
498
  }
481
499
  // Mark as identified before emitting the event
482
- if (validAddress && rdns) {
483
- this.session.markWalletIdentified(validAddress, rdns);
500
+ // Mark even if rdns is empty to prevent duplicate empty identifies
501
+ if (validAddress) {
502
+ this.session.markWalletIdentified(validAddress, rdns || '');
484
503
  }
485
504
  return [4 /*yield*/, this.trackEvent(EventType.IDENTIFY, {
486
505
  address: validAddress,
@@ -549,6 +568,39 @@ var FormoAnalytics = /** @class */ (function () {
549
568
  });
550
569
  });
551
570
  };
571
+ /*
572
+ Consent management functions
573
+ */
574
+ /**
575
+ * Opt out of tracking.
576
+ * @returns {void}
577
+ */
578
+ FormoAnalytics.prototype.optOutTracking = function () {
579
+ logger.info("Opting out of tracking");
580
+ // Set opt-out flag in persistent storage using direct cookie access
581
+ // This must be done before switching storage to ensure persistence
582
+ setConsentFlag(this.writeKey, CONSENT_OPT_OUT_KEY, "true");
583
+ this.reset();
584
+ logger.info("Successfully opted out of tracking");
585
+ };
586
+ /**
587
+ * Opt back into tracking after previously opting out. This will re-enable analytics tracking
588
+ * and switch back to persistent storage.
589
+ * @returns {void}
590
+ */
591
+ FormoAnalytics.prototype.optInTracking = function () {
592
+ logger.info("Opting back into tracking");
593
+ // Remove opt-out flag
594
+ removeConsentFlag(this.writeKey, CONSENT_OPT_OUT_KEY);
595
+ logger.info("Successfully opted back into tracking");
596
+ };
597
+ /**
598
+ * Check if the user has opted out of tracking.
599
+ * @returns {boolean} True if the user has opted out
600
+ */
601
+ FormoAnalytics.prototype.hasOptedOutTracking = function () {
602
+ return getConsentFlag(this.writeKey, CONSENT_OPT_OUT_KEY) === "true";
603
+ };
552
604
  /*
553
605
  SDK tracking and event listener functions
554
606
  */
@@ -1259,10 +1311,14 @@ var FormoAnalytics = /** @class */ (function () {
1259
1311
  });
1260
1312
  };
1261
1313
  /**
1262
- * Determines if tracking should be enabled based on configuration
1314
+ * Determines if tracking should be enabled based on configuration and consent
1263
1315
  * @returns {boolean} True if tracking should be enabled
1264
1316
  */
1265
1317
  FormoAnalytics.prototype.shouldTrack = function () {
1318
+ // First check if user has opted out of tracking
1319
+ if (this.hasOptedOutTracking()) {
1320
+ return false;
1321
+ }
1266
1322
  // Check if tracking is explicitly provided as a boolean
1267
1323
  if (typeof this.options.tracking === 'boolean') {
1268
1324
  return this.options.tracking;
@@ -1844,6 +1900,10 @@ export { FormoAnalytics };
1844
1900
  var FormoAnalyticsSession = /** @class */ (function () {
1845
1901
  function FormoAnalyticsSession() {
1846
1902
  }
1903
+ FormoAnalyticsSession.prototype.generateIdentificationKey = function (address, rdns) {
1904
+ // If rdns is missing, use address-only key as fallback for empty identifies
1905
+ return rdns ? "".concat(address, ":").concat(rdns) : address;
1906
+ };
1847
1907
  FormoAnalyticsSession.prototype.isWalletDetected = function (rdns) {
1848
1908
  var _a;
1849
1909
  var rdnses = ((_a = cookie().get(SESSION_WALLET_DETECTED_KEY)) === null || _a === void 0 ? void 0 : _a.split(",")) || [];
@@ -1862,22 +1922,40 @@ var FormoAnalyticsSession = /** @class */ (function () {
1862
1922
  }
1863
1923
  };
1864
1924
  FormoAnalyticsSession.prototype.isWalletIdentified = function (address, rdns) {
1865
- var _a;
1866
- var identifiedKey = "".concat(address, ":").concat(rdns);
1867
- var identifiedWallets = ((_a = cookie().get(SESSION_WALLET_IDENTIFIED_KEY)) === null || _a === void 0 ? void 0 : _a.split(",")) || [];
1868
- return identifiedWallets.includes(identifiedKey);
1925
+ var identifiedKey = this.generateIdentificationKey(address, rdns);
1926
+ var cookieValue = cookie().get(SESSION_WALLET_IDENTIFIED_KEY);
1927
+ var identifiedWallets = (cookieValue === null || cookieValue === void 0 ? void 0 : cookieValue.split(",")) || [];
1928
+ var isIdentified = identifiedWallets.includes(identifiedKey);
1929
+ logger.debug("Session: Checking wallet identification", {
1930
+ identifiedKey: identifiedKey,
1931
+ isIdentified: isIdentified,
1932
+ hasRdns: !!rdns
1933
+ });
1934
+ return isIdentified;
1869
1935
  };
1870
1936
  FormoAnalyticsSession.prototype.markWalletIdentified = function (address, rdns) {
1871
1937
  var _a;
1872
- var identifiedKey = "".concat(address, ":").concat(rdns);
1938
+ var identifiedKey = this.generateIdentificationKey(address, rdns);
1873
1939
  var identifiedWallets = ((_a = cookie().get(SESSION_WALLET_IDENTIFIED_KEY)) === null || _a === void 0 ? void 0 : _a.split(",")) || [];
1874
1940
  if (!identifiedWallets.includes(identifiedKey)) {
1875
1941
  identifiedWallets.push(identifiedKey);
1876
- cookie().set(SESSION_WALLET_IDENTIFIED_KEY, identifiedWallets.join(","), {
1942
+ var newValue = identifiedWallets.join(",");
1943
+ cookie().set(SESSION_WALLET_IDENTIFIED_KEY, newValue, {
1877
1944
  // by the end of the day
1878
1945
  expires: new Date(Date.now() + 86400 * 1000).toUTCString(),
1879
1946
  path: "/",
1880
1947
  });
1948
+ logger.debug("Session: Marked wallet as identified", {
1949
+ identifiedKey: identifiedKey,
1950
+ hasRdns: !!rdns
1951
+ });
1952
+ }
1953
+ else {
1954
+ logger.info("Session: Wallet already marked as identified", {
1955
+ identifiedKey: identifiedKey,
1956
+ existingWallets: identifiedWallets,
1957
+ hasRdns: !!rdns
1958
+ });
1881
1959
  }
1882
1960
  };
1883
1961
  return FormoAnalyticsSession;
@@ -52,7 +52,7 @@ import { initStorageManager, logger } from "./lib";
52
52
  var defaultContext = {
53
53
  chain: function () { return Promise.resolve(); },
54
54
  page: function () { return Promise.resolve(); },
55
- reset: function () { return Promise.resolve(); },
55
+ reset: function () { },
56
56
  detect: function () { return Promise.resolve(); },
57
57
  connect: function () { return Promise.resolve(); },
58
58
  disconnect: function () { return Promise.resolve(); },
@@ -60,6 +60,10 @@ var defaultContext = {
60
60
  transaction: function () { return Promise.resolve(); },
61
61
  identify: function () { return Promise.resolve(); },
62
62
  track: function () { return Promise.resolve(); },
63
+ // Consent management methods
64
+ optOutTracking: function () { },
65
+ optInTracking: function () { },
66
+ hasOptedOutTracking: function () { return false; },
63
67
  };
64
68
  export var FormoAnalyticsContext = createContext(defaultContext);
65
69
  export var FormoAnalyticsProvider = function (props) {
@@ -4,5 +4,9 @@ export declare const SESSION_WALLET_IDENTIFIED_KEY = "wallet-identified";
4
4
  export declare const SESSION_CURRENT_URL_KEY = "analytics-current-url";
5
5
  export declare const SESSION_USER_ID_KEY = "user-id";
6
6
  export declare const LOCAL_ANONYMOUS_ID_KEY = "anonymous-id";
7
+ export declare const CONSENT_OPT_OUT_KEY = "opt-out-tracking";
7
8
  export declare const DEFAULT_PROVIDER_ICON = "data:image/svg+xml;base64,";
9
+ export declare const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
10
+ export declare const DEAD_ADDRESS = "0x000000000000000000000000000000000000dEaD";
11
+ export declare const BLOCKED_ADDRESSES: readonly ["0x0000000000000000000000000000000000000000", "0x000000000000000000000000000000000000dEaD"];
8
12
  //# sourceMappingURL=base.d.ts.map
@@ -4,6 +4,13 @@ export var SESSION_WALLET_IDENTIFIED_KEY = "wallet-identified";
4
4
  export var SESSION_CURRENT_URL_KEY = "analytics-current-url";
5
5
  export var SESSION_USER_ID_KEY = "user-id";
6
6
  export var LOCAL_ANONYMOUS_ID_KEY = "anonymous-id";
7
+ // Consent management keys
8
+ export var CONSENT_OPT_OUT_KEY = "opt-out-tracking";
7
9
  // Default provider icon (empty data URL)
8
10
  export var DEFAULT_PROVIDER_ICON = 'data:image/svg+xml;base64,';
11
+ // Blocked addresses that should not emit events
12
+ export var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
13
+ export var DEAD_ADDRESS = "0x000000000000000000000000000000000000dEaD";
14
+ // Array of all blocked addresses for easy checking
15
+ export var BLOCKED_ADDRESSES = [ZERO_ADDRESS, DEAD_ADDRESS];
9
16
  //# sourceMappingURL=base.js.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Consent management utilities for handling tracking consent flags in cookies.
3
+ * These functions bypass the consent-aware storage system to ensure consent
4
+ * preferences are always stored persistently for compliance purposes.
5
+ *
6
+ * All consent cookies are project-specific to avoid conflicts between different
7
+ * Formo projects on the same domain.
8
+ */
9
+ /**
10
+ * Set a consent flag directly in cookies, bypassing the consent-aware storage system.
11
+ * Uses cookies for consent storage to ensure:
12
+ * - Cross-domain/subdomain compatibility
13
+ * - Server-side accessibility for compliance auditing
14
+ * - Regulatory compliance (GDPR/CCPA requirements)
15
+ * - Explicit expiration handling
16
+ * - Project isolation (no conflicts between different Formo projects)
17
+ * @param projectId - The project identifier (writeKey)
18
+ * @param key - The cookie key
19
+ * @param value - The cookie value
20
+ */
21
+ export declare function setConsentFlag(projectId: string, key: string, value: string): void;
22
+ /**
23
+ * Get a consent flag directly from cookies, bypassing the consent-aware storage system
24
+ * @param projectId - The project identifier (writeKey)
25
+ * @param key - The cookie key
26
+ * @returns The cookie value or null if not found
27
+ */
28
+ export declare function getConsentFlag(projectId: string, key: string): string | null;
29
+ /**
30
+ * Remove a consent flag directly from cookies, bypassing the consent-aware storage system
31
+ * @param projectId - The project identifier (writeKey)
32
+ * @param key - The cookie key
33
+ */
34
+ export declare function removeConsentFlag(projectId: string, key: string): void;
35
+ //# sourceMappingURL=consent.d.ts.map
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Consent management utilities for handling tracking consent flags in cookies.
3
+ * These functions bypass the consent-aware storage system to ensure consent
4
+ * preferences are always stored persistently for compliance purposes.
5
+ *
6
+ * All consent cookies are project-specific to avoid conflicts between different
7
+ * Formo projects on the same domain.
8
+ */
9
+ import { secureHash } from '../utils/hash';
10
+ /**
11
+ * Generate a project-specific cookie key to avoid conflicts between different Formo projects
12
+ * Uses hashed writeKey for privacy and security
13
+ * @param projectId - The project identifier (writeKey)
14
+ * @param key - The base cookie key
15
+ * @returns Project-specific cookie key
16
+ */
17
+ function getProjectSpecificKey(projectId, key) {
18
+ return "formo_".concat(secureHash(projectId), "_").concat(key);
19
+ }
20
+ /**
21
+ * Set a consent flag directly in cookies, bypassing the consent-aware storage system.
22
+ * Uses cookies for consent storage to ensure:
23
+ * - Cross-domain/subdomain compatibility
24
+ * - Server-side accessibility for compliance auditing
25
+ * - Regulatory compliance (GDPR/CCPA requirements)
26
+ * - Explicit expiration handling
27
+ * - Project isolation (no conflicts between different Formo projects)
28
+ * @param projectId - The project identifier (writeKey)
29
+ * @param key - The cookie key
30
+ * @param value - The cookie value
31
+ */
32
+ export function setConsentFlag(projectId, key, value) {
33
+ var _a;
34
+ if (typeof document !== 'undefined') {
35
+ var projectSpecificKey = getProjectSpecificKey(projectId, key);
36
+ var expires = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toUTCString(); // 1 year (GDPR compliant)
37
+ var isSecure = ((_a = window === null || window === void 0 ? void 0 : window.location) === null || _a === void 0 ? void 0 : _a.protocol) === 'https:';
38
+ // Enhanced privacy settings: Secure (HTTPS), SameSite=Strict for consent cookies
39
+ document.cookie = "".concat(projectSpecificKey, "=").concat(encodeURIComponent(value), "; expires=").concat(expires, "; path=/; SameSite=Strict").concat(isSecure ? '; Secure' : '');
40
+ }
41
+ }
42
+ /**
43
+ * Get a consent flag directly from cookies, bypassing the consent-aware storage system
44
+ * @param projectId - The project identifier (writeKey)
45
+ * @param key - The cookie key
46
+ * @returns The cookie value or null if not found
47
+ */
48
+ export function getConsentFlag(projectId, key) {
49
+ if (typeof document === 'undefined')
50
+ return null;
51
+ var projectSpecificKey = getProjectSpecificKey(projectId, key);
52
+ var cookies = document.cookie.split(';');
53
+ for (var _i = 0, cookies_1 = cookies; _i < cookies_1.length; _i++) {
54
+ var cookie = cookies_1[_i];
55
+ var trimmed = cookie.trim();
56
+ var eqIdx = trimmed.indexOf('=');
57
+ if (eqIdx === -1)
58
+ continue;
59
+ var cookieKey = trimmed.substring(0, eqIdx);
60
+ var cookieValue = trimmed.substring(eqIdx + 1);
61
+ if (cookieKey === projectSpecificKey) {
62
+ return decodeURIComponent(cookieValue || '');
63
+ }
64
+ }
65
+ return null;
66
+ }
67
+ /**
68
+ * Remove a consent flag directly from cookies, bypassing the consent-aware storage system
69
+ * @param projectId - The project identifier (writeKey)
70
+ * @param key - The cookie key
71
+ */
72
+ export function removeConsentFlag(projectId, key) {
73
+ var projectSpecificKey = getProjectSpecificKey(projectId, key);
74
+ deleteCookieDirectly(projectSpecificKey);
75
+ }
76
+ /**
77
+ * Delete a cookie directly, handling various domain scenarios
78
+ * @param cookieName - The name of the cookie to delete
79
+ */
80
+ function deleteCookieDirectly(cookieName) {
81
+ if (typeof document === 'undefined')
82
+ return;
83
+ // Clear from current domain/path
84
+ document.cookie = "".concat(cookieName, "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;");
85
+ // Try to clear from parent domain if it's a proper multi-level domain
86
+ if (typeof window !== 'undefined') {
87
+ var hostname = window.location.hostname;
88
+ var parts = hostname.split('.');
89
+ // Only try parent domain deletion for proper domains with multiple parts
90
+ // Skip localhost and single-level domains
91
+ if (parts.length >= 2 && hostname !== 'localhost') {
92
+ var domain = parts.slice(-2).join('.');
93
+ document.cookie = "".concat(cookieName, "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.").concat(domain, ";");
94
+ }
95
+ }
96
+ }
97
+ //# sourceMappingURL=consent.js.map
@@ -11,6 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  };
12
12
  import { logger } from "../logger";
13
13
  import { EventFactory } from "./EventFactory";
14
+ import { isBlockedAddress } from "../../utils/address";
14
15
  /**
15
16
  * A service to generate valid event payloads and queue them for processing
16
17
  */
@@ -30,6 +31,11 @@ var EventManager = /** @class */ (function () {
30
31
  EventManager.prototype.addEvent = function (event, address, userId) {
31
32
  var callback = event.callback, _event = __rest(event, ["callback"]);
32
33
  var formoEvent = this.eventFactory.create(_event, address, userId);
34
+ // Check if the final event has a blocked address - don't queue it
35
+ if (formoEvent.address && isBlockedAddress(formoEvent.address)) {
36
+ logger.warn("Event blocked: Address ".concat(formoEvent.address, " is in the blocked list and cannot emit events"));
37
+ return;
38
+ }
33
39
  this.eventQueue.enqueue(formoEvent, function (err, _, data) {
34
40
  if (err) {
35
41
  logger.error("Error sending events:", err);
@@ -2,5 +2,6 @@ export * from "./event";
2
2
  export * from "./logger";
3
3
  export * from "./queue";
4
4
  export * from "./storage";
5
+ export * from "./consent";
5
6
  export { default as fetch } from "./fetch";
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -2,5 +2,6 @@ export * from "./event";
2
2
  export * from "./logger";
3
3
  export * from "./queue";
4
4
  export * from "./storage";
5
+ export * from "./consent";
5
6
  export { default as fetch } from "./fetch";
6
7
  //# sourceMappingURL=index.js.map
@@ -1,10 +1,11 @@
1
1
  import { KEY_PREFIX } from "../constant";
2
+ import { secureHash } from "../../../utils/hash";
2
3
  var StorageBlueprint = /** @class */ (function () {
3
4
  function StorageBlueprint(writeKey) {
4
5
  this.writeKey = writeKey;
5
6
  }
6
7
  StorageBlueprint.prototype.getKey = function (key) {
7
- return "".concat(KEY_PREFIX, "_").concat(this.writeKey, ".").concat(key);
8
+ return "".concat(KEY_PREFIX, "_").concat(secureHash(this.writeKey), "_").concat(key);
8
9
  };
9
10
  return StorageBlueprint;
10
11
  }());
@@ -1,2 +1,2 @@
1
- export declare const version = "1.19.7";
1
+ export declare const version = "1.20.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Generated by genversion.
2
- export var version = '1.19.7';
2
+ export var version = '1.20.0';
3
3
  //# sourceMappingURL=version.js.map
@@ -48,6 +48,9 @@ export interface IFormoAnalytics {
48
48
  rdns?: string;
49
49
  }, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
50
50
  track(event: string, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
51
+ optOutTracking(): void;
52
+ optInTracking(): void;
53
+ hasOptedOutTracking(): boolean;
51
54
  }
52
55
  export interface Config {
53
56
  writeKey: string;
@@ -18,5 +18,12 @@ export declare const isValidAddress: (address: Address | null | undefined) => ad
18
18
  * This function is the preferred way to get a validated, trimmed address for use in your application.
19
19
  */
20
20
  export declare const getValidAddress: (address: Address | null | undefined) => string | null;
21
+ /**
22
+ * Checks if an address is in the blocked list and should not emit events.
23
+ * Blocked addresses include the zero address and dead address which are not normal user addresses.
24
+ * @param address The address to check
25
+ * @returns true if the address is blocked, false otherwise
26
+ */
27
+ export declare const isBlockedAddress: (address: Address | null | undefined) => boolean;
21
28
  export declare const toChecksumAddress: (address: Address) => string;
22
29
  //# sourceMappingURL=address.d.ts.map
@@ -2,6 +2,7 @@ import { keccak256 } from "ethereum-cryptography/keccak.js";
2
2
  import { utf8ToBytes } from "ethereum-cryptography/utils.js";
3
3
  import { ensureIfUint8Array, isAddress, uint8ArrayToHexString, } from "../validators";
4
4
  import { isNullish } from "../validators/object";
5
+ import { BLOCKED_ADDRESSES } from "../constants";
5
6
  /**
6
7
  * Private helper function to validate and trim an address
7
8
  * @param address The address to validate and trim
@@ -36,6 +37,24 @@ export var isValidAddress = function (address) {
36
37
  export var getValidAddress = function (address) {
37
38
  return _validateAndTrimAddress(address);
38
39
  };
40
+ /**
41
+ * Checks if an address is in the blocked list and should not emit events.
42
+ * Blocked addresses include the zero address and dead address which are not normal user addresses.
43
+ * @param address The address to check
44
+ * @returns true if the address is blocked, false otherwise
45
+ */
46
+ export var isBlockedAddress = function (address) {
47
+ if (!address)
48
+ return false;
49
+ var validAddress = getValidAddress(address);
50
+ if (!validAddress)
51
+ return false;
52
+ // Normalize to checksum format for comparison
53
+ var checksumAddress = toChecksumAddress(validAddress);
54
+ return BLOCKED_ADDRESSES.some(function (blockedAddr) {
55
+ return toChecksumAddress(blockedAddr) === checksumAddress;
56
+ });
57
+ };
39
58
  export var toChecksumAddress = function (address) {
40
59
  if (!isAddress(address, false)) {
41
60
  throw new Error("Invalid address " + address);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Hash utilities using ethereum-cryptography for secure, deterministic hashing
3
+ */
4
+ /**
5
+ * Generate a secure hash of a string using SHA-256 for creating short, consistent identifiers
6
+ * @param str - The string to hash
7
+ * @returns Short hash string (first 8 characters of SHA-256 hex)
8
+ */
9
+ export declare function secureHash(str: string): string;
10
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Hash utilities using ethereum-cryptography for secure, deterministic hashing
3
+ */
4
+ import { sha256 } from 'ethereum-cryptography/sha256';
5
+ import { utf8ToBytes, bytesToHex } from 'ethereum-cryptography/utils';
6
+ /**
7
+ * Generate a secure hash of a string using SHA-256 for creating short, consistent identifiers
8
+ * @param str - The string to hash
9
+ * @returns Short hash string (first 8 characters of SHA-256 hex)
10
+ */
11
+ export function secureHash(str) {
12
+ var bytes = utf8ToBytes(str);
13
+ var hashBytes = sha256(bytes);
14
+ var hashHex = bytesToHex(hashBytes);
15
+ // Return first 8 characters for reasonable cookie name length
16
+ return hashHex.slice(0, 8);
17
+ }
18
+ //# sourceMappingURL=hash.js.map
@@ -2,4 +2,5 @@ export * from "./address";
2
2
  export * from "./base";
3
3
  export * from "./converter";
4
4
  export * from "./generate";
5
+ export * from "./hash";
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -2,4 +2,5 @@ export * from "./address";
2
2
  export * from "./base";
3
3
  export * from "./converter";
4
4
  export * from "./generate";
5
+ export * from "./hash";
5
6
  //# sourceMappingURL=index.js.map