@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
|
@@ -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);
|
|
@@ -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, "
|
|
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.
|
|
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
|
|
@@ -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
|