@formo/analytics 1.19.10 → 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.
- package/README.md +3 -1
- package/dist/cjs/src/FormoAnalytics.d.ts +17 -1
- package/dist/cjs/src/FormoAnalytics.js +42 -1
- package/dist/cjs/src/FormoAnalyticsProvider.js +5 -1
- package/dist/cjs/src/constants/base.d.ts +4 -0
- package/dist/cjs/src/constants/base.js +8 -1
- package/dist/cjs/src/lib/consent.d.ts +35 -0
- package/dist/cjs/src/lib/consent.js +102 -0
- package/dist/cjs/src/lib/event/EventManager.js +6 -0
- package/dist/cjs/src/lib/index.d.ts +1 -0
- package/dist/cjs/src/lib/index.js +1 -0
- package/dist/cjs/src/lib/storage/built-in/blueprint.js +2 -1
- package/dist/cjs/src/lib/version.d.ts +1 -1
- package/dist/cjs/src/lib/version.js +1 -1
- package/dist/cjs/src/types/base.d.ts +3 -0
- package/dist/cjs/src/utils/address.d.ts +7 -0
- package/dist/cjs/src/utils/address.js +21 -1
- package/dist/cjs/src/utils/hash.d.ts +10 -0
- package/dist/cjs/src/utils/hash.js +21 -0
- package/dist/cjs/src/utils/index.d.ts +1 -0
- package/dist/cjs/src/utils/index.js +1 -0
- package/dist/esm/src/FormoAnalytics.d.ts +17 -1
- package/dist/esm/src/FormoAnalytics.js +44 -3
- package/dist/esm/src/FormoAnalyticsProvider.js +5 -1
- package/dist/esm/src/constants/base.d.ts +4 -0
- package/dist/esm/src/constants/base.js +7 -0
- package/dist/esm/src/lib/consent.d.ts +35 -0
- package/dist/esm/src/lib/consent.js +97 -0
- package/dist/esm/src/lib/event/EventManager.js +6 -0
- package/dist/esm/src/lib/index.d.ts +1 -0
- package/dist/esm/src/lib/index.js +1 -0
- package/dist/esm/src/lib/storage/built-in/blueprint.js +2 -1
- package/dist/esm/src/lib/version.d.ts +1 -1
- package/dist/esm/src/lib/version.js +1 -1
- package/dist/esm/src/types/base.d.ts +3 -0
- package/dist/esm/src/utils/address.d.ts +7 -0
- package/dist/esm/src/utils/address.js +19 -0
- package/dist/esm/src/utils/hash.d.ts +10 -0
- package/dist/esm/src/utils/hash.js +18 -0
- package/dist/esm/src/utils/index.d.ts +1 -0
- package/dist/esm/src/utils/index.js +1 -0
- package/dist/index.umd.min.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -24,7 +24,9 @@ You can install Formo on:
|
|
|
24
24
|
- [React apps](https://docs.formo.so/install#react)
|
|
25
25
|
- [Next.js apps](https://docs.formo.so/install#next-js-app-router)
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
## Configuration
|
|
28
|
+
|
|
29
|
+
Visit Formo's [Developer Docs](https://docs.formo.so) for detailed guides on local testing, debugging, and consent management.
|
|
28
30
|
|
|
29
31
|
## Methodology
|
|
30
32
|
|
|
@@ -177,6 +177,22 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
177
177
|
* @returns {Promise<void>}
|
|
178
178
|
*/
|
|
179
179
|
track(event: string, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
|
|
180
|
+
/**
|
|
181
|
+
* Opt out of tracking.
|
|
182
|
+
* @returns {void}
|
|
183
|
+
*/
|
|
184
|
+
optOutTracking(): void;
|
|
185
|
+
/**
|
|
186
|
+
* Opt back into tracking after previously opting out. This will re-enable analytics tracking
|
|
187
|
+
* and switch back to persistent storage.
|
|
188
|
+
* @returns {void}
|
|
189
|
+
*/
|
|
190
|
+
optInTracking(): void;
|
|
191
|
+
/**
|
|
192
|
+
* Check if the user has opted out of tracking.
|
|
193
|
+
* @returns {boolean} True if the user has opted out
|
|
194
|
+
*/
|
|
195
|
+
hasOptedOutTracking(): boolean;
|
|
180
196
|
private trackProvider;
|
|
181
197
|
private trackProviders;
|
|
182
198
|
private addProviderListener;
|
|
@@ -204,7 +220,7 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
204
220
|
private trackPageHit;
|
|
205
221
|
private trackEvent;
|
|
206
222
|
/**
|
|
207
|
-
* Determines if tracking should be enabled based on configuration
|
|
223
|
+
* Determines if tracking should be enabled based on configuration and consent
|
|
208
224
|
* @returns {boolean} True if tracking should be enabled
|
|
209
225
|
*/
|
|
210
226
|
private shouldTrack;
|
|
@@ -126,6 +126,10 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
126
126
|
maxQueueSize: options.maxQueueSize,
|
|
127
127
|
flushInterval: options.flushInterval,
|
|
128
128
|
}));
|
|
129
|
+
// Check consent status on initialization
|
|
130
|
+
if (this.hasOptedOutTracking()) {
|
|
131
|
+
lib_1.logger.info("User has previously opted out of tracking");
|
|
132
|
+
}
|
|
129
133
|
// Handle initial provider (injected) as fallback; listeners for EIP-6963 are added later
|
|
130
134
|
var provider = undefined;
|
|
131
135
|
var optProvider = options.provider;
|
|
@@ -567,6 +571,39 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
567
571
|
});
|
|
568
572
|
});
|
|
569
573
|
};
|
|
574
|
+
/*
|
|
575
|
+
Consent management functions
|
|
576
|
+
*/
|
|
577
|
+
/**
|
|
578
|
+
* Opt out of tracking.
|
|
579
|
+
* @returns {void}
|
|
580
|
+
*/
|
|
581
|
+
FormoAnalytics.prototype.optOutTracking = function () {
|
|
582
|
+
lib_1.logger.info("Opting out of tracking");
|
|
583
|
+
// Set opt-out flag in persistent storage using direct cookie access
|
|
584
|
+
// This must be done before switching storage to ensure persistence
|
|
585
|
+
(0, lib_1.setConsentFlag)(this.writeKey, constants_1.CONSENT_OPT_OUT_KEY, "true");
|
|
586
|
+
this.reset();
|
|
587
|
+
lib_1.logger.info("Successfully opted out of tracking");
|
|
588
|
+
};
|
|
589
|
+
/**
|
|
590
|
+
* Opt back into tracking after previously opting out. This will re-enable analytics tracking
|
|
591
|
+
* and switch back to persistent storage.
|
|
592
|
+
* @returns {void}
|
|
593
|
+
*/
|
|
594
|
+
FormoAnalytics.prototype.optInTracking = function () {
|
|
595
|
+
lib_1.logger.info("Opting back into tracking");
|
|
596
|
+
// Remove opt-out flag
|
|
597
|
+
(0, lib_1.removeConsentFlag)(this.writeKey, constants_1.CONSENT_OPT_OUT_KEY);
|
|
598
|
+
lib_1.logger.info("Successfully opted back into tracking");
|
|
599
|
+
};
|
|
600
|
+
/**
|
|
601
|
+
* Check if the user has opted out of tracking.
|
|
602
|
+
* @returns {boolean} True if the user has opted out
|
|
603
|
+
*/
|
|
604
|
+
FormoAnalytics.prototype.hasOptedOutTracking = function () {
|
|
605
|
+
return (0, lib_1.getConsentFlag)(this.writeKey, constants_1.CONSENT_OPT_OUT_KEY) === "true";
|
|
606
|
+
};
|
|
570
607
|
/*
|
|
571
608
|
SDK tracking and event listener functions
|
|
572
609
|
*/
|
|
@@ -1277,10 +1314,14 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1277
1314
|
});
|
|
1278
1315
|
};
|
|
1279
1316
|
/**
|
|
1280
|
-
* Determines if tracking should be enabled based on configuration
|
|
1317
|
+
* Determines if tracking should be enabled based on configuration and consent
|
|
1281
1318
|
* @returns {boolean} True if tracking should be enabled
|
|
1282
1319
|
*/
|
|
1283
1320
|
FormoAnalytics.prototype.shouldTrack = function () {
|
|
1321
|
+
// First check if user has opted out of tracking
|
|
1322
|
+
if (this.hasOptedOutTracking()) {
|
|
1323
|
+
return false;
|
|
1324
|
+
}
|
|
1284
1325
|
// Check if tracking is explicitly provided as a boolean
|
|
1285
1326
|
if (typeof this.options.tracking === 'boolean') {
|
|
1286
1327
|
return this.options.tracking;
|
|
@@ -55,7 +55,7 @@ var lib_1 = require("./lib");
|
|
|
55
55
|
var defaultContext = {
|
|
56
56
|
chain: function () { return Promise.resolve(); },
|
|
57
57
|
page: function () { return Promise.resolve(); },
|
|
58
|
-
reset: function () {
|
|
58
|
+
reset: function () { },
|
|
59
59
|
detect: function () { return Promise.resolve(); },
|
|
60
60
|
connect: function () { return Promise.resolve(); },
|
|
61
61
|
disconnect: function () { return Promise.resolve(); },
|
|
@@ -63,6 +63,10 @@ var defaultContext = {
|
|
|
63
63
|
transaction: function () { return Promise.resolve(); },
|
|
64
64
|
identify: function () { return Promise.resolve(); },
|
|
65
65
|
track: function () { return Promise.resolve(); },
|
|
66
|
+
// Consent management methods
|
|
67
|
+
optOutTracking: function () { },
|
|
68
|
+
optInTracking: function () { },
|
|
69
|
+
hasOptedOutTracking: function () { return false; },
|
|
66
70
|
};
|
|
67
71
|
exports.FormoAnalyticsContext = (0, react_1.createContext)(defaultContext);
|
|
68
72
|
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
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_PROVIDER_ICON = exports.LOCAL_ANONYMOUS_ID_KEY = exports.SESSION_USER_ID_KEY = exports.SESSION_CURRENT_URL_KEY = exports.SESSION_WALLET_IDENTIFIED_KEY = exports.SESSION_WALLET_DETECTED_KEY = exports.SESSION_TRAFFIC_SOURCE_KEY = void 0;
|
|
3
|
+
exports.BLOCKED_ADDRESSES = exports.DEAD_ADDRESS = exports.ZERO_ADDRESS = exports.DEFAULT_PROVIDER_ICON = exports.CONSENT_OPT_OUT_KEY = exports.LOCAL_ANONYMOUS_ID_KEY = exports.SESSION_USER_ID_KEY = exports.SESSION_CURRENT_URL_KEY = exports.SESSION_WALLET_IDENTIFIED_KEY = exports.SESSION_WALLET_DETECTED_KEY = exports.SESSION_TRAFFIC_SOURCE_KEY = void 0;
|
|
4
4
|
exports.SESSION_TRAFFIC_SOURCE_KEY = "traffic-source";
|
|
5
5
|
exports.SESSION_WALLET_DETECTED_KEY = "wallet-detected";
|
|
6
6
|
exports.SESSION_WALLET_IDENTIFIED_KEY = "wallet-identified";
|
|
7
7
|
exports.SESSION_CURRENT_URL_KEY = "analytics-current-url";
|
|
8
8
|
exports.SESSION_USER_ID_KEY = "user-id";
|
|
9
9
|
exports.LOCAL_ANONYMOUS_ID_KEY = "anonymous-id";
|
|
10
|
+
// Consent management keys
|
|
11
|
+
exports.CONSENT_OPT_OUT_KEY = "opt-out-tracking";
|
|
10
12
|
// Default provider icon (empty data URL)
|
|
11
13
|
exports.DEFAULT_PROVIDER_ICON = 'data:image/svg+xml;base64,';
|
|
14
|
+
// Blocked addresses that should not emit events
|
|
15
|
+
exports.ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
16
|
+
exports.DEAD_ADDRESS = "0x000000000000000000000000000000000000dEaD";
|
|
17
|
+
// Array of all blocked addresses for easy checking
|
|
18
|
+
exports.BLOCKED_ADDRESSES = [exports.ZERO_ADDRESS, exports.DEAD_ADDRESS];
|
|
12
19
|
//# 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,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Consent management utilities for handling tracking consent flags in cookies.
|
|
4
|
+
* These functions bypass the consent-aware storage system to ensure consent
|
|
5
|
+
* preferences are always stored persistently for compliance purposes.
|
|
6
|
+
*
|
|
7
|
+
* All consent cookies are project-specific to avoid conflicts between different
|
|
8
|
+
* Formo projects on the same domain.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.setConsentFlag = setConsentFlag;
|
|
12
|
+
exports.getConsentFlag = getConsentFlag;
|
|
13
|
+
exports.removeConsentFlag = removeConsentFlag;
|
|
14
|
+
var hash_1 = require("../utils/hash");
|
|
15
|
+
/**
|
|
16
|
+
* Generate a project-specific cookie key to avoid conflicts between different Formo projects
|
|
17
|
+
* Uses hashed writeKey for privacy and security
|
|
18
|
+
* @param projectId - The project identifier (writeKey)
|
|
19
|
+
* @param key - The base cookie key
|
|
20
|
+
* @returns Project-specific cookie key
|
|
21
|
+
*/
|
|
22
|
+
function getProjectSpecificKey(projectId, key) {
|
|
23
|
+
return "formo_".concat((0, hash_1.secureHash)(projectId), "_").concat(key);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Set a consent flag directly in cookies, bypassing the consent-aware storage system.
|
|
27
|
+
* Uses cookies for consent storage to ensure:
|
|
28
|
+
* - Cross-domain/subdomain compatibility
|
|
29
|
+
* - Server-side accessibility for compliance auditing
|
|
30
|
+
* - Regulatory compliance (GDPR/CCPA requirements)
|
|
31
|
+
* - Explicit expiration handling
|
|
32
|
+
* - Project isolation (no conflicts between different Formo projects)
|
|
33
|
+
* @param projectId - The project identifier (writeKey)
|
|
34
|
+
* @param key - The cookie key
|
|
35
|
+
* @param value - The cookie value
|
|
36
|
+
*/
|
|
37
|
+
function setConsentFlag(projectId, key, value) {
|
|
38
|
+
var _a;
|
|
39
|
+
if (typeof document !== 'undefined') {
|
|
40
|
+
var projectSpecificKey = getProjectSpecificKey(projectId, key);
|
|
41
|
+
var expires = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toUTCString(); // 1 year (GDPR compliant)
|
|
42
|
+
var isSecure = ((_a = window === null || window === void 0 ? void 0 : window.location) === null || _a === void 0 ? void 0 : _a.protocol) === 'https:';
|
|
43
|
+
// Enhanced privacy settings: Secure (HTTPS), SameSite=Strict for consent cookies
|
|
44
|
+
document.cookie = "".concat(projectSpecificKey, "=").concat(encodeURIComponent(value), "; expires=").concat(expires, "; path=/; SameSite=Strict").concat(isSecure ? '; Secure' : '');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get a consent flag directly from cookies, bypassing the consent-aware storage system
|
|
49
|
+
* @param projectId - The project identifier (writeKey)
|
|
50
|
+
* @param key - The cookie key
|
|
51
|
+
* @returns The cookie value or null if not found
|
|
52
|
+
*/
|
|
53
|
+
function getConsentFlag(projectId, key) {
|
|
54
|
+
if (typeof document === 'undefined')
|
|
55
|
+
return null;
|
|
56
|
+
var projectSpecificKey = getProjectSpecificKey(projectId, key);
|
|
57
|
+
var cookies = document.cookie.split(';');
|
|
58
|
+
for (var _i = 0, cookies_1 = cookies; _i < cookies_1.length; _i++) {
|
|
59
|
+
var cookie = cookies_1[_i];
|
|
60
|
+
var trimmed = cookie.trim();
|
|
61
|
+
var eqIdx = trimmed.indexOf('=');
|
|
62
|
+
if (eqIdx === -1)
|
|
63
|
+
continue;
|
|
64
|
+
var cookieKey = trimmed.substring(0, eqIdx);
|
|
65
|
+
var cookieValue = trimmed.substring(eqIdx + 1);
|
|
66
|
+
if (cookieKey === projectSpecificKey) {
|
|
67
|
+
return decodeURIComponent(cookieValue || '');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Remove a consent flag directly from cookies, bypassing the consent-aware storage system
|
|
74
|
+
* @param projectId - The project identifier (writeKey)
|
|
75
|
+
* @param key - The cookie key
|
|
76
|
+
*/
|
|
77
|
+
function removeConsentFlag(projectId, key) {
|
|
78
|
+
var projectSpecificKey = getProjectSpecificKey(projectId, key);
|
|
79
|
+
deleteCookieDirectly(projectSpecificKey);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Delete a cookie directly, handling various domain scenarios
|
|
83
|
+
* @param cookieName - The name of the cookie to delete
|
|
84
|
+
*/
|
|
85
|
+
function deleteCookieDirectly(cookieName) {
|
|
86
|
+
if (typeof document === 'undefined')
|
|
87
|
+
return;
|
|
88
|
+
// Clear from current domain/path
|
|
89
|
+
document.cookie = "".concat(cookieName, "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;");
|
|
90
|
+
// Try to clear from parent domain if it's a proper multi-level domain
|
|
91
|
+
if (typeof window !== 'undefined') {
|
|
92
|
+
var hostname = window.location.hostname;
|
|
93
|
+
var parts = hostname.split('.');
|
|
94
|
+
// Only try parent domain deletion for proper domains with multiple parts
|
|
95
|
+
// Skip localhost and single-level domains
|
|
96
|
+
if (parts.length >= 2 && hostname !== 'localhost') {
|
|
97
|
+
var domain = parts.slice(-2).join('.');
|
|
98
|
+
document.cookie = "".concat(cookieName, "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=.").concat(domain, ";");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=consent.js.map
|
|
@@ -14,6 +14,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
14
|
exports.EventManager = void 0;
|
|
15
15
|
var logger_1 = require("../logger");
|
|
16
16
|
var EventFactory_1 = require("./EventFactory");
|
|
17
|
+
var address_1 = require("../../utils/address");
|
|
17
18
|
/**
|
|
18
19
|
* A service to generate valid event payloads and queue them for processing
|
|
19
20
|
*/
|
|
@@ -33,6 +34,11 @@ var EventManager = /** @class */ (function () {
|
|
|
33
34
|
EventManager.prototype.addEvent = function (event, address, userId) {
|
|
34
35
|
var callback = event.callback, _event = __rest(event, ["callback"]);
|
|
35
36
|
var formoEvent = this.eventFactory.create(_event, address, userId);
|
|
37
|
+
// Check if the final event has a blocked address - don't queue it
|
|
38
|
+
if (formoEvent.address && (0, address_1.isBlockedAddress)(formoEvent.address)) {
|
|
39
|
+
logger_1.logger.warn("Event blocked: Address ".concat(formoEvent.address, " is in the blocked list and cannot emit events"));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
36
42
|
this.eventQueue.enqueue(formoEvent, function (err, _, data) {
|
|
37
43
|
if (err) {
|
|
38
44
|
logger_1.logger.error("Error sending events:", err);
|
|
@@ -22,6 +22,7 @@ __exportStar(require("./event"), exports);
|
|
|
22
22
|
__exportStar(require("./logger"), exports);
|
|
23
23
|
__exportStar(require("./queue"), exports);
|
|
24
24
|
__exportStar(require("./storage"), exports);
|
|
25
|
+
__exportStar(require("./consent"), exports);
|
|
25
26
|
var fetch_1 = require("./fetch");
|
|
26
27
|
Object.defineProperty(exports, "fetch", { enumerable: true, get: function () { return __importDefault(fetch_1).default; } });
|
|
27
28
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
var constant_1 = require("../constant");
|
|
4
|
+
var hash_1 = require("../../../utils/hash");
|
|
4
5
|
var StorageBlueprint = /** @class */ (function () {
|
|
5
6
|
function StorageBlueprint(writeKey) {
|
|
6
7
|
this.writeKey = writeKey;
|
|
7
8
|
}
|
|
8
9
|
StorageBlueprint.prototype.getKey = function (key) {
|
|
9
|
-
return "".concat(constant_1.KEY_PREFIX, "_").concat(this.writeKey, "
|
|
10
|
+
return "".concat(constant_1.KEY_PREFIX, "_").concat((0, hash_1.secureHash)(this.writeKey), "_").concat(key);
|
|
10
11
|
};
|
|
11
12
|
return StorageBlueprint;
|
|
12
13
|
}());
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version = "1.
|
|
1
|
+
export declare const version = "1.20.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.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
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toChecksumAddress = exports.getValidAddress = exports.isValidAddress = void 0;
|
|
3
|
+
exports.toChecksumAddress = exports.isBlockedAddress = exports.getValidAddress = exports.isValidAddress = void 0;
|
|
4
4
|
var keccak_js_1 = require("ethereum-cryptography/keccak.js");
|
|
5
5
|
var utils_js_1 = require("ethereum-cryptography/utils.js");
|
|
6
6
|
var validators_1 = require("../validators");
|
|
7
7
|
var object_1 = require("../validators/object");
|
|
8
|
+
var constants_1 = require("../constants");
|
|
8
9
|
/**
|
|
9
10
|
* Private helper function to validate and trim an address
|
|
10
11
|
* @param address The address to validate and trim
|
|
@@ -41,6 +42,25 @@ var getValidAddress = function (address) {
|
|
|
41
42
|
return _validateAndTrimAddress(address);
|
|
42
43
|
};
|
|
43
44
|
exports.getValidAddress = getValidAddress;
|
|
45
|
+
/**
|
|
46
|
+
* Checks if an address is in the blocked list and should not emit events.
|
|
47
|
+
* Blocked addresses include the zero address and dead address which are not normal user addresses.
|
|
48
|
+
* @param address The address to check
|
|
49
|
+
* @returns true if the address is blocked, false otherwise
|
|
50
|
+
*/
|
|
51
|
+
var isBlockedAddress = function (address) {
|
|
52
|
+
if (!address)
|
|
53
|
+
return false;
|
|
54
|
+
var validAddress = (0, exports.getValidAddress)(address);
|
|
55
|
+
if (!validAddress)
|
|
56
|
+
return false;
|
|
57
|
+
// Normalize to checksum format for comparison
|
|
58
|
+
var checksumAddress = (0, exports.toChecksumAddress)(validAddress);
|
|
59
|
+
return constants_1.BLOCKED_ADDRESSES.some(function (blockedAddr) {
|
|
60
|
+
return (0, exports.toChecksumAddress)(blockedAddr) === checksumAddress;
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
exports.isBlockedAddress = isBlockedAddress;
|
|
44
64
|
var toChecksumAddress = function (address) {
|
|
45
65
|
if (!(0, validators_1.isAddress)(address, false)) {
|
|
46
66
|
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,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Hash utilities using ethereum-cryptography for secure, deterministic hashing
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.secureHash = secureHash;
|
|
7
|
+
var sha256_1 = require("ethereum-cryptography/sha256");
|
|
8
|
+
var utils_1 = require("ethereum-cryptography/utils");
|
|
9
|
+
/**
|
|
10
|
+
* Generate a secure hash of a string using SHA-256 for creating short, consistent identifiers
|
|
11
|
+
* @param str - The string to hash
|
|
12
|
+
* @returns Short hash string (first 8 characters of SHA-256 hex)
|
|
13
|
+
*/
|
|
14
|
+
function secureHash(str) {
|
|
15
|
+
var bytes = (0, utils_1.utf8ToBytes)(str);
|
|
16
|
+
var hashBytes = (0, sha256_1.sha256)(bytes);
|
|
17
|
+
var hashHex = (0, utils_1.bytesToHex)(hashBytes);
|
|
18
|
+
// Return first 8 characters for reasonable cookie name length
|
|
19
|
+
return hashHex.slice(0, 8);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=hash.js.map
|
|
@@ -18,4 +18,5 @@ __exportStar(require("./address"), exports);
|
|
|
18
18
|
__exportStar(require("./base"), exports);
|
|
19
19
|
__exportStar(require("./converter"), exports);
|
|
20
20
|
__exportStar(require("./generate"), exports);
|
|
21
|
+
__exportStar(require("./hash"), exports);
|
|
21
22
|
//# sourceMappingURL=index.js.map
|
|
@@ -177,6 +177,22 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
177
177
|
* @returns {Promise<void>}
|
|
178
178
|
*/
|
|
179
179
|
track(event: string, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
|
|
180
|
+
/**
|
|
181
|
+
* Opt out of tracking.
|
|
182
|
+
* @returns {void}
|
|
183
|
+
*/
|
|
184
|
+
optOutTracking(): void;
|
|
185
|
+
/**
|
|
186
|
+
* Opt back into tracking after previously opting out. This will re-enable analytics tracking
|
|
187
|
+
* and switch back to persistent storage.
|
|
188
|
+
* @returns {void}
|
|
189
|
+
*/
|
|
190
|
+
optInTracking(): void;
|
|
191
|
+
/**
|
|
192
|
+
* Check if the user has opted out of tracking.
|
|
193
|
+
* @returns {boolean} True if the user has opted out
|
|
194
|
+
*/
|
|
195
|
+
hasOptedOutTracking(): boolean;
|
|
180
196
|
private trackProvider;
|
|
181
197
|
private trackProviders;
|
|
182
198
|
private addProviderListener;
|
|
@@ -204,7 +220,7 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
204
220
|
private trackPageHit;
|
|
205
221
|
private trackEvent;
|
|
206
222
|
/**
|
|
207
|
-
* Determines if tracking should be enabled based on configuration
|
|
223
|
+
* Determines if tracking should be enabled based on configuration and consent
|
|
208
224
|
* @returns {boolean} True if tracking should be enabled
|
|
209
225
|
*/
|
|
210
226
|
private shouldTrack;
|
|
@@ -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;
|
|
@@ -564,6 +568,39 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
564
568
|
});
|
|
565
569
|
});
|
|
566
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
|
+
};
|
|
567
604
|
/*
|
|
568
605
|
SDK tracking and event listener functions
|
|
569
606
|
*/
|
|
@@ -1274,10 +1311,14 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1274
1311
|
});
|
|
1275
1312
|
};
|
|
1276
1313
|
/**
|
|
1277
|
-
* Determines if tracking should be enabled based on configuration
|
|
1314
|
+
* Determines if tracking should be enabled based on configuration and consent
|
|
1278
1315
|
* @returns {boolean} True if tracking should be enabled
|
|
1279
1316
|
*/
|
|
1280
1317
|
FormoAnalytics.prototype.shouldTrack = function () {
|
|
1318
|
+
// First check if user has opted out of tracking
|
|
1319
|
+
if (this.hasOptedOutTracking()) {
|
|
1320
|
+
return false;
|
|
1321
|
+
}
|
|
1281
1322
|
// Check if tracking is explicitly provided as a boolean
|
|
1282
1323
|
if (typeof this.options.tracking === 'boolean') {
|
|
1283
1324
|
return this.options.tracking;
|
|
@@ -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 () {
|
|
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
|