@formo/analytics 1.31.0 → 1.32.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/dist/cjs/src/event/EventFactory.d.ts +21 -0
- package/dist/cjs/src/event/EventFactory.js +108 -16
- package/dist/cjs/src/event/constants.d.ts +13 -1
- package/dist/cjs/src/event/constants.js +18 -1
- package/dist/cjs/src/types/base.d.ts +14 -0
- package/dist/esm/src/event/EventFactory.d.ts +21 -0
- package/dist/esm/src/event/EventFactory.js +109 -17
- package/dist/esm/src/event/constants.d.ts +13 -1
- package/dist/esm/src/event/constants.js +17 -1
- package/dist/esm/src/types/base.d.ts +14 -0
- package/dist/index.umd.min.js +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import { IEventFactory } from "./type";
|
|
|
3
3
|
declare class EventFactory implements IEventFactory {
|
|
4
4
|
private options?;
|
|
5
5
|
private compiledPathPattern?;
|
|
6
|
+
private excludedQueryParams;
|
|
6
7
|
constructor(options?: Options);
|
|
7
8
|
/**
|
|
8
9
|
* Validate an address for both EVM and Solana chains.
|
|
@@ -16,6 +17,18 @@ declare class EventFactory implements IEventFactory {
|
|
|
16
17
|
private getLocation;
|
|
17
18
|
private getLanguage;
|
|
18
19
|
private getLibraryVersion;
|
|
20
|
+
private isExcludedQueryParam;
|
|
21
|
+
/**
|
|
22
|
+
* Strip excluded (sensitive) query parameters from a URL in place. Only the
|
|
23
|
+
* query string is touched; the path and hash/fragment are left as-is.
|
|
24
|
+
*/
|
|
25
|
+
private redactQueryParams;
|
|
26
|
+
/**
|
|
27
|
+
* Return the given absolute URL with excluded query parameters removed. The
|
|
28
|
+
* input is returned unchanged when it is empty or cannot be parsed (e.g. an
|
|
29
|
+
* empty referrer).
|
|
30
|
+
*/
|
|
31
|
+
private redactUrl;
|
|
19
32
|
private extractUTMParameters;
|
|
20
33
|
private extractClickIdParameters;
|
|
21
34
|
private extractReferralParameter;
|
|
@@ -27,6 +40,14 @@ declare class EventFactory implements IEventFactory {
|
|
|
27
40
|
* first-touch referrer after a direct landing.
|
|
28
41
|
*/
|
|
29
42
|
private getExternalReferrer;
|
|
43
|
+
/**
|
|
44
|
+
* Apply the current query-param denylist to a previously-persisted traffic
|
|
45
|
+
* source object. Traffic-source keys (utm_*, click ids, ref) are themselves
|
|
46
|
+
* query-parameter names, so an excluded key's stored value is dropped; the
|
|
47
|
+
* referrer is a URL and is re-redacted. Guards against a stored value
|
|
48
|
+
* outliving the config (or SDK version) under which it was first captured.
|
|
49
|
+
*/
|
|
50
|
+
private redactStoredTrafficSources;
|
|
30
51
|
private getTrafficSources;
|
|
31
52
|
private getScreen;
|
|
32
53
|
private generateContext;
|
|
@@ -46,6 +46,15 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
46
46
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
50
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
51
|
+
if (ar || !(i in from)) {
|
|
52
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
53
|
+
ar[i] = from[i];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
57
|
+
};
|
|
49
58
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
50
59
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
51
60
|
};
|
|
@@ -67,7 +76,7 @@ var ISO_3166_ALPHA_2_REGEX = /^[A-Z]{2}$/;
|
|
|
67
76
|
var EventFactory = /** @class */ (function () {
|
|
68
77
|
function EventFactory(options) {
|
|
69
78
|
var _this = this;
|
|
70
|
-
var _a;
|
|
79
|
+
var _a, _b;
|
|
71
80
|
this.extractUTMParameters = function (url) {
|
|
72
81
|
var result = {
|
|
73
82
|
utm_campaign: "",
|
|
@@ -145,12 +154,16 @@ var EventFactory = /** @class */ (function () {
|
|
|
145
154
|
return "";
|
|
146
155
|
}
|
|
147
156
|
catch (_b) { }
|
|
148
|
-
return ref;
|
|
157
|
+
return _this.redactUrl(ref);
|
|
149
158
|
};
|
|
150
159
|
this.getTrafficSources = function (url) {
|
|
151
160
|
var urlObj = new URL(url);
|
|
152
161
|
var contextTrafficSources = __assign(__assign(__assign({}, _this.extractUTMParameters(url)), _this.extractClickIdParameters(urlObj)), { ref: _this.extractReferralParameter(urlObj), referrer: _this.getExternalReferrer() });
|
|
153
|
-
|
|
162
|
+
// Sticky traffic sources may have been persisted by an older SDK version or
|
|
163
|
+
// a looser config, before the current excludeQueryParams was in effect.
|
|
164
|
+
// Honor the current denylist on the way out so excluded values can never
|
|
165
|
+
// resurface from session storage (or get re-persisted below).
|
|
166
|
+
var storedTrafficSources = _this.redactStoredTrafficSources((0, storage_1.session)().get(constants_1.SESSION_TRAFFIC_SOURCE_KEY) || {});
|
|
154
167
|
var mergedClickIds = {};
|
|
155
168
|
for (var _i = 0, CLICK_ID_PARAMS_2 = constants_2.CLICK_ID_PARAMS; _i < CLICK_ID_PARAMS_2.length; _i++) {
|
|
156
169
|
var p = CLICK_ID_PARAMS_2[_i];
|
|
@@ -191,8 +204,17 @@ var EventFactory = /** @class */ (function () {
|
|
|
191
204
|
this.getPageProperties = function (properties) {
|
|
192
205
|
// Create a copy to avoid mutating the original properties object
|
|
193
206
|
var pageProps = __assign({}, properties);
|
|
207
|
+
// Parse the current URL once and strip any excluded (sensitive) query
|
|
208
|
+
// params up front, so nothing sensitive is forwarded via url, query, or the
|
|
209
|
+
// per-param explosion below. The hash/fragment is intentionally untouched.
|
|
210
|
+
var urlObj = null;
|
|
211
|
+
try {
|
|
212
|
+
urlObj = new URL(globalThis.location.href);
|
|
213
|
+
_this.redactQueryParams(urlObj);
|
|
214
|
+
}
|
|
215
|
+
catch (_a) { }
|
|
194
216
|
if ((0, validators_1.isUndefined)(pageProps.url)) {
|
|
195
|
-
pageProps.url =
|
|
217
|
+
pageProps.url = urlObj ? urlObj.href : globalThis.location.href;
|
|
196
218
|
}
|
|
197
219
|
if ((0, validators_1.isUndefined)(pageProps.path)) {
|
|
198
220
|
pageProps.path = globalThis.location.pathname;
|
|
@@ -202,18 +224,22 @@ var EventFactory = /** @class */ (function () {
|
|
|
202
224
|
}
|
|
203
225
|
// Add query string without the '?' prefix
|
|
204
226
|
if ((0, validators_1.isUndefined)(pageProps.query)) {
|
|
205
|
-
pageProps.query =
|
|
227
|
+
pageProps.query = urlObj
|
|
228
|
+
? urlObj.search.slice(1)
|
|
229
|
+
: globalThis.location.search.slice(1);
|
|
206
230
|
}
|
|
207
231
|
// Parse query parameters and add as individual properties (don't overwrite existing)
|
|
208
|
-
// Skip fields that are already captured in context or are semantic event properties
|
|
232
|
+
// Skip fields that are already captured in context or are semantic event properties.
|
|
233
|
+
// Excluded params were already removed from urlObj above.
|
|
209
234
|
try {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
235
|
+
if (urlObj) {
|
|
236
|
+
urlObj.searchParams.forEach(function (value, key) {
|
|
237
|
+
// Only add if the property doesn't already exist and is not excluded
|
|
238
|
+
if ((0, validators_1.isUndefined)(pageProps[key]) && !constants_2.PAGE_PROPERTIES_EXCLUDED_FIELDS.has(key)) {
|
|
239
|
+
pageProps[key] = value;
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
217
243
|
}
|
|
218
244
|
catch (error) {
|
|
219
245
|
logger_1.logger.error("Error parsing query parameters for page properties:", error);
|
|
@@ -221,8 +247,13 @@ var EventFactory = /** @class */ (function () {
|
|
|
221
247
|
return pageProps;
|
|
222
248
|
};
|
|
223
249
|
this.options = options;
|
|
250
|
+
var tracking = options === null || options === void 0 ? void 0 : options.tracking;
|
|
251
|
+
var configuredExcludes = typeof tracking === "object" ? (_a = tracking.excludeQueryParams) !== null && _a !== void 0 ? _a : [] : [];
|
|
252
|
+
this.excludedQueryParams = new Set(__spreadArray(__spreadArray([], constants_2.DEFAULT_EXCLUDED_QUERY_PARAMS, true), configuredExcludes, true).map(function (key) {
|
|
253
|
+
return key.toLowerCase();
|
|
254
|
+
}));
|
|
224
255
|
// Compile regex pattern once for better performance
|
|
225
|
-
if ((
|
|
256
|
+
if ((_b = options === null || options === void 0 ? void 0 : options.referral) === null || _b === void 0 ? void 0 : _b.pathPattern) {
|
|
226
257
|
try {
|
|
227
258
|
this.compiledPathPattern = new RegExp(options.referral.pathPattern);
|
|
228
259
|
}
|
|
@@ -284,6 +315,66 @@ var EventFactory = /** @class */ (function () {
|
|
|
284
315
|
EventFactory.prototype.getLibraryVersion = function () {
|
|
285
316
|
return version_1.version;
|
|
286
317
|
};
|
|
318
|
+
EventFactory.prototype.isExcludedQueryParam = function (key) {
|
|
319
|
+
return this.excludedQueryParams.has(key.toLowerCase());
|
|
320
|
+
};
|
|
321
|
+
/**
|
|
322
|
+
* Strip excluded (sensitive) query parameters from a URL in place. Only the
|
|
323
|
+
* query string is touched; the path and hash/fragment are left as-is.
|
|
324
|
+
*/
|
|
325
|
+
EventFactory.prototype.redactQueryParams = function (url) {
|
|
326
|
+
var _this = this;
|
|
327
|
+
// Collect first, then delete: mutating searchParams while iterating is
|
|
328
|
+
// unsafe, and deleting a key removes all of its values at once.
|
|
329
|
+
var keysToDelete = new Set();
|
|
330
|
+
url.searchParams.forEach(function (_value, key) {
|
|
331
|
+
if (_this.isExcludedQueryParam(key)) {
|
|
332
|
+
keysToDelete.add(key);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
keysToDelete.forEach(function (key) { return url.searchParams.delete(key); });
|
|
336
|
+
};
|
|
337
|
+
/**
|
|
338
|
+
* Return the given absolute URL with excluded query parameters removed. The
|
|
339
|
+
* input is returned unchanged when it is empty or cannot be parsed (e.g. an
|
|
340
|
+
* empty referrer).
|
|
341
|
+
*/
|
|
342
|
+
EventFactory.prototype.redactUrl = function (href) {
|
|
343
|
+
if (!href)
|
|
344
|
+
return href;
|
|
345
|
+
try {
|
|
346
|
+
var url = new URL(href);
|
|
347
|
+
this.redactQueryParams(url);
|
|
348
|
+
return url.href;
|
|
349
|
+
}
|
|
350
|
+
catch (_a) {
|
|
351
|
+
return href;
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
/**
|
|
355
|
+
* Apply the current query-param denylist to a previously-persisted traffic
|
|
356
|
+
* source object. Traffic-source keys (utm_*, click ids, ref) are themselves
|
|
357
|
+
* query-parameter names, so an excluded key's stored value is dropped; the
|
|
358
|
+
* referrer is a URL and is re-redacted. Guards against a stored value
|
|
359
|
+
* outliving the config (or SDK version) under which it was first captured.
|
|
360
|
+
*/
|
|
361
|
+
EventFactory.prototype.redactStoredTrafficSources = function (stored) {
|
|
362
|
+
var result = {};
|
|
363
|
+
for (var _i = 0, _a = Object.keys(stored); _i < _a.length; _i++) {
|
|
364
|
+
var key = _a[_i];
|
|
365
|
+
var value = stored[key];
|
|
366
|
+
if (key === "referrer") {
|
|
367
|
+
result[key] = this.redactUrl(value || "");
|
|
368
|
+
}
|
|
369
|
+
else if (this.isExcludedQueryParam(key)) {
|
|
370
|
+
result[key] = "";
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
result[key] = value;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return result;
|
|
377
|
+
};
|
|
287
378
|
// Get screen dimensions and pixel density
|
|
288
379
|
// Returns safe defaults if any error occurs to ensure event creation continues
|
|
289
380
|
EventFactory.prototype.getScreen = function () {
|
|
@@ -312,7 +403,7 @@ var EventFactory = /** @class */ (function () {
|
|
|
312
403
|
// Contextual fields that are automatically collected and populated by the Formo SDK
|
|
313
404
|
EventFactory.prototype.generateContext = function (context) {
|
|
314
405
|
return __awaiter(this, void 0, void 0, function () {
|
|
315
|
-
var browserName, language, timezone, location, library_version, defaultContext, mergedContext;
|
|
406
|
+
var browserName, language, timezone, location, library_version, redactedHref, defaultContext, mergedContext;
|
|
316
407
|
return __generator(this, function (_a) {
|
|
317
408
|
switch (_a.label) {
|
|
318
409
|
case 0: return [4 /*yield*/, (0, browsers_1.detectBrowser)()];
|
|
@@ -322,7 +413,8 @@ var EventFactory = /** @class */ (function () {
|
|
|
322
413
|
timezone = this.getTimezone();
|
|
323
414
|
location = this.getLocation();
|
|
324
415
|
library_version = this.getLibraryVersion();
|
|
325
|
-
|
|
416
|
+
redactedHref = this.redactUrl(globalThis.location.href);
|
|
417
|
+
defaultContext = __assign(__assign(__assign({ user_agent: globalThis.navigator.userAgent, locale: language, timezone: timezone, location: location }, this.getTrafficSources(redactedHref)), { page_title: document.title, page_url: redactedHref, library_name: "Formo Web SDK", library_version: library_version, browser: browserName }), this.getScreen());
|
|
326
418
|
mergedContext = (0, mergeDeepRight_1.default)(defaultContext, context || {});
|
|
327
419
|
return [2 /*return*/, mergedContext];
|
|
328
420
|
}
|
|
@@ -6,6 +6,18 @@ declare const VERSION = "0";
|
|
|
6
6
|
* in src/types/events.ts derived from this array.
|
|
7
7
|
*/
|
|
8
8
|
declare const CLICK_ID_PARAMS: readonly ["gclid", "gad_source", "fbclid", "msclkid", "twclid", "li_fat_id", "rdt_cid", "ttclid"];
|
|
9
|
+
/**
|
|
10
|
+
* Query parameters that are ALWAYS stripped from forwarded and stored URLs,
|
|
11
|
+
* regardless of consumer configuration, because they carry high-sensitivity
|
|
12
|
+
* secrets that must never reach Formo:
|
|
13
|
+
* - privy_oauth_code: Privy OAuth authorization code
|
|
14
|
+
* - privy_oauth_state: Privy OAuth CSRF state token
|
|
15
|
+
* - privy_oauth_provider: Privy OAuth provider identifier
|
|
16
|
+
*
|
|
17
|
+
* Consumers can extend the denylist via `tracking.excludeQueryParams` but
|
|
18
|
+
* cannot remove these built-ins. Matched case-insensitively.
|
|
19
|
+
*/
|
|
20
|
+
declare const DEFAULT_EXCLUDED_QUERY_PARAMS: readonly ["privy_oauth_code", "privy_oauth_state", "privy_oauth_provider"];
|
|
9
21
|
/**
|
|
10
22
|
* Fields that should be excluded from page event properties parsing
|
|
11
23
|
* These are either:
|
|
@@ -13,5 +25,5 @@ declare const CLICK_ID_PARAMS: readonly ["gclid", "gad_source", "fbclid", "msclk
|
|
|
13
25
|
* - Semantic event properties that should not be overridden by URL params
|
|
14
26
|
*/
|
|
15
27
|
declare const PAGE_PROPERTIES_EXCLUDED_FIELDS: Set<string>;
|
|
16
|
-
export { CHANNEL, VERSION, CLICK_ID_PARAMS, PAGE_PROPERTIES_EXCLUDED_FIELDS };
|
|
28
|
+
export { CHANNEL, VERSION, CLICK_ID_PARAMS, DEFAULT_EXCLUDED_QUERY_PARAMS, PAGE_PROPERTIES_EXCLUDED_FIELDS, };
|
|
17
29
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -9,7 +9,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
9
9
|
return to.concat(ar || Array.prototype.slice.call(from));
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.PAGE_PROPERTIES_EXCLUDED_FIELDS = exports.CLICK_ID_PARAMS = exports.VERSION = exports.CHANNEL = void 0;
|
|
12
|
+
exports.PAGE_PROPERTIES_EXCLUDED_FIELDS = exports.DEFAULT_EXCLUDED_QUERY_PARAMS = exports.CLICK_ID_PARAMS = exports.VERSION = exports.CHANNEL = void 0;
|
|
13
13
|
var CHANNEL = "web";
|
|
14
14
|
exports.CHANNEL = CHANNEL;
|
|
15
15
|
var VERSION = "0";
|
|
@@ -30,6 +30,23 @@ var CLICK_ID_PARAMS = [
|
|
|
30
30
|
"ttclid", // TikTok Ads
|
|
31
31
|
];
|
|
32
32
|
exports.CLICK_ID_PARAMS = CLICK_ID_PARAMS;
|
|
33
|
+
/**
|
|
34
|
+
* Query parameters that are ALWAYS stripped from forwarded and stored URLs,
|
|
35
|
+
* regardless of consumer configuration, because they carry high-sensitivity
|
|
36
|
+
* secrets that must never reach Formo:
|
|
37
|
+
* - privy_oauth_code: Privy OAuth authorization code
|
|
38
|
+
* - privy_oauth_state: Privy OAuth CSRF state token
|
|
39
|
+
* - privy_oauth_provider: Privy OAuth provider identifier
|
|
40
|
+
*
|
|
41
|
+
* Consumers can extend the denylist via `tracking.excludeQueryParams` but
|
|
42
|
+
* cannot remove these built-ins. Matched case-insensitively.
|
|
43
|
+
*/
|
|
44
|
+
var DEFAULT_EXCLUDED_QUERY_PARAMS = [
|
|
45
|
+
"privy_oauth_code",
|
|
46
|
+
"privy_oauth_state",
|
|
47
|
+
"privy_oauth_provider",
|
|
48
|
+
];
|
|
49
|
+
exports.DEFAULT_EXCLUDED_QUERY_PARAMS = DEFAULT_EXCLUDED_QUERY_PARAMS;
|
|
33
50
|
/**
|
|
34
51
|
* Fields that should be excluded from page event properties parsing
|
|
35
52
|
* These are either:
|
|
@@ -72,6 +72,20 @@ export interface TrackingOptions {
|
|
|
72
72
|
excludeHosts?: string[];
|
|
73
73
|
excludePaths?: string[];
|
|
74
74
|
excludeChains?: ChainID[];
|
|
75
|
+
/**
|
|
76
|
+
* Additional query parameter names to strip from forwarded and stored URLs,
|
|
77
|
+
* on top of a built-in always-on denylist (currently `privy_oauth_code`,
|
|
78
|
+
* `privy_oauth_state`, and `privy_oauth_provider`) that cannot be disabled.
|
|
79
|
+
* Matched case-insensitively. Excluded params are stripped from the captured
|
|
80
|
+
* page URL, query string, per-parameter page properties, and referrer before
|
|
81
|
+
* any event is sent. The URL hash/fragment is intentionally left untouched.
|
|
82
|
+
*
|
|
83
|
+
* Mirrors Mixpanel's `property_blacklist` and PostHog's `property_denylist`,
|
|
84
|
+
* scoped here to URL query parameters.
|
|
85
|
+
*
|
|
86
|
+
* @example ["token", "access_token", "email", "signature"]
|
|
87
|
+
*/
|
|
88
|
+
excludeQueryParams?: string[];
|
|
75
89
|
}
|
|
76
90
|
/**
|
|
77
91
|
* Configuration options for controlling wallet event autocapture
|
|
@@ -3,6 +3,7 @@ import { IEventFactory } from "./type";
|
|
|
3
3
|
declare class EventFactory implements IEventFactory {
|
|
4
4
|
private options?;
|
|
5
5
|
private compiledPathPattern?;
|
|
6
|
+
private excludedQueryParams;
|
|
6
7
|
constructor(options?: Options);
|
|
7
8
|
/**
|
|
8
9
|
* Validate an address for both EVM and Solana chains.
|
|
@@ -16,6 +17,18 @@ declare class EventFactory implements IEventFactory {
|
|
|
16
17
|
private getLocation;
|
|
17
18
|
private getLanguage;
|
|
18
19
|
private getLibraryVersion;
|
|
20
|
+
private isExcludedQueryParam;
|
|
21
|
+
/**
|
|
22
|
+
* Strip excluded (sensitive) query parameters from a URL in place. Only the
|
|
23
|
+
* query string is touched; the path and hash/fragment are left as-is.
|
|
24
|
+
*/
|
|
25
|
+
private redactQueryParams;
|
|
26
|
+
/**
|
|
27
|
+
* Return the given absolute URL with excluded query parameters removed. The
|
|
28
|
+
* input is returned unchanged when it is empty or cannot be parsed (e.g. an
|
|
29
|
+
* empty referrer).
|
|
30
|
+
*/
|
|
31
|
+
private redactUrl;
|
|
19
32
|
private extractUTMParameters;
|
|
20
33
|
private extractClickIdParameters;
|
|
21
34
|
private extractReferralParameter;
|
|
@@ -27,6 +40,14 @@ declare class EventFactory implements IEventFactory {
|
|
|
27
40
|
* first-touch referrer after a direct landing.
|
|
28
41
|
*/
|
|
29
42
|
private getExternalReferrer;
|
|
43
|
+
/**
|
|
44
|
+
* Apply the current query-param denylist to a previously-persisted traffic
|
|
45
|
+
* source object. Traffic-source keys (utm_*, click ids, ref) are themselves
|
|
46
|
+
* query-parameter names, so an excluded key's stored value is dropped; the
|
|
47
|
+
* referrer is a URL and is re-redacted. Guards against a stored value
|
|
48
|
+
* outliving the config (or SDK version) under which it was first captured.
|
|
49
|
+
*/
|
|
50
|
+
private redactStoredTrafficSources;
|
|
30
51
|
private getTrafficSources;
|
|
31
52
|
private getScreen;
|
|
32
53
|
private generateContext;
|
|
@@ -45,6 +45,15 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
45
45
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
49
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
50
|
+
if (ar || !(i in from)) {
|
|
51
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
52
|
+
ar[i] = from[i];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
56
|
+
};
|
|
48
57
|
import { COUNTRY_LIST, LOCAL_ANONYMOUS_ID_KEY, SESSION_TRAFFIC_SOURCE_KEY, } from "../constants";
|
|
49
58
|
import { toSnakeCase } from "../utils";
|
|
50
59
|
import { validateAddress } from "../utils/address";
|
|
@@ -54,14 +63,14 @@ import { logger } from "../logger";
|
|
|
54
63
|
import mergeDeepRight from "../ramda/mergeDeepRight";
|
|
55
64
|
import { session } from "../storage";
|
|
56
65
|
import { version } from "../version";
|
|
57
|
-
import { CHANNEL, CLICK_ID_PARAMS, PAGE_PROPERTIES_EXCLUDED_FIELDS, VERSION, } from "./constants";
|
|
66
|
+
import { CHANNEL, CLICK_ID_PARAMS, DEFAULT_EXCLUDED_QUERY_PARAMS, PAGE_PROPERTIES_EXCLUDED_FIELDS, VERSION, } from "./constants";
|
|
58
67
|
import { generateAnonymousId } from "./utils";
|
|
59
68
|
import { detectBrowser } from "../browser/browsers";
|
|
60
69
|
var ISO_3166_ALPHA_2_REGEX = /^[A-Z]{2}$/;
|
|
61
70
|
var EventFactory = /** @class */ (function () {
|
|
62
71
|
function EventFactory(options) {
|
|
63
72
|
var _this = this;
|
|
64
|
-
var _a;
|
|
73
|
+
var _a, _b;
|
|
65
74
|
this.extractUTMParameters = function (url) {
|
|
66
75
|
var result = {
|
|
67
76
|
utm_campaign: "",
|
|
@@ -139,12 +148,16 @@ var EventFactory = /** @class */ (function () {
|
|
|
139
148
|
return "";
|
|
140
149
|
}
|
|
141
150
|
catch (_b) { }
|
|
142
|
-
return ref;
|
|
151
|
+
return _this.redactUrl(ref);
|
|
143
152
|
};
|
|
144
153
|
this.getTrafficSources = function (url) {
|
|
145
154
|
var urlObj = new URL(url);
|
|
146
155
|
var contextTrafficSources = __assign(__assign(__assign({}, _this.extractUTMParameters(url)), _this.extractClickIdParameters(urlObj)), { ref: _this.extractReferralParameter(urlObj), referrer: _this.getExternalReferrer() });
|
|
147
|
-
|
|
156
|
+
// Sticky traffic sources may have been persisted by an older SDK version or
|
|
157
|
+
// a looser config, before the current excludeQueryParams was in effect.
|
|
158
|
+
// Honor the current denylist on the way out so excluded values can never
|
|
159
|
+
// resurface from session storage (or get re-persisted below).
|
|
160
|
+
var storedTrafficSources = _this.redactStoredTrafficSources(session().get(SESSION_TRAFFIC_SOURCE_KEY) || {});
|
|
148
161
|
var mergedClickIds = {};
|
|
149
162
|
for (var _i = 0, CLICK_ID_PARAMS_2 = CLICK_ID_PARAMS; _i < CLICK_ID_PARAMS_2.length; _i++) {
|
|
150
163
|
var p = CLICK_ID_PARAMS_2[_i];
|
|
@@ -185,8 +198,17 @@ var EventFactory = /** @class */ (function () {
|
|
|
185
198
|
this.getPageProperties = function (properties) {
|
|
186
199
|
// Create a copy to avoid mutating the original properties object
|
|
187
200
|
var pageProps = __assign({}, properties);
|
|
201
|
+
// Parse the current URL once and strip any excluded (sensitive) query
|
|
202
|
+
// params up front, so nothing sensitive is forwarded via url, query, or the
|
|
203
|
+
// per-param explosion below. The hash/fragment is intentionally untouched.
|
|
204
|
+
var urlObj = null;
|
|
205
|
+
try {
|
|
206
|
+
urlObj = new URL(globalThis.location.href);
|
|
207
|
+
_this.redactQueryParams(urlObj);
|
|
208
|
+
}
|
|
209
|
+
catch (_a) { }
|
|
188
210
|
if (isUndefined(pageProps.url)) {
|
|
189
|
-
pageProps.url =
|
|
211
|
+
pageProps.url = urlObj ? urlObj.href : globalThis.location.href;
|
|
190
212
|
}
|
|
191
213
|
if (isUndefined(pageProps.path)) {
|
|
192
214
|
pageProps.path = globalThis.location.pathname;
|
|
@@ -196,18 +218,22 @@ var EventFactory = /** @class */ (function () {
|
|
|
196
218
|
}
|
|
197
219
|
// Add query string without the '?' prefix
|
|
198
220
|
if (isUndefined(pageProps.query)) {
|
|
199
|
-
pageProps.query =
|
|
221
|
+
pageProps.query = urlObj
|
|
222
|
+
? urlObj.search.slice(1)
|
|
223
|
+
: globalThis.location.search.slice(1);
|
|
200
224
|
}
|
|
201
225
|
// Parse query parameters and add as individual properties (don't overwrite existing)
|
|
202
|
-
// Skip fields that are already captured in context or are semantic event properties
|
|
226
|
+
// Skip fields that are already captured in context or are semantic event properties.
|
|
227
|
+
// Excluded params were already removed from urlObj above.
|
|
203
228
|
try {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
229
|
+
if (urlObj) {
|
|
230
|
+
urlObj.searchParams.forEach(function (value, key) {
|
|
231
|
+
// Only add if the property doesn't already exist and is not excluded
|
|
232
|
+
if (isUndefined(pageProps[key]) && !PAGE_PROPERTIES_EXCLUDED_FIELDS.has(key)) {
|
|
233
|
+
pageProps[key] = value;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
211
237
|
}
|
|
212
238
|
catch (error) {
|
|
213
239
|
logger.error("Error parsing query parameters for page properties:", error);
|
|
@@ -215,8 +241,13 @@ var EventFactory = /** @class */ (function () {
|
|
|
215
241
|
return pageProps;
|
|
216
242
|
};
|
|
217
243
|
this.options = options;
|
|
244
|
+
var tracking = options === null || options === void 0 ? void 0 : options.tracking;
|
|
245
|
+
var configuredExcludes = typeof tracking === "object" ? (_a = tracking.excludeQueryParams) !== null && _a !== void 0 ? _a : [] : [];
|
|
246
|
+
this.excludedQueryParams = new Set(__spreadArray(__spreadArray([], DEFAULT_EXCLUDED_QUERY_PARAMS, true), configuredExcludes, true).map(function (key) {
|
|
247
|
+
return key.toLowerCase();
|
|
248
|
+
}));
|
|
218
249
|
// Compile regex pattern once for better performance
|
|
219
|
-
if ((
|
|
250
|
+
if ((_b = options === null || options === void 0 ? void 0 : options.referral) === null || _b === void 0 ? void 0 : _b.pathPattern) {
|
|
220
251
|
try {
|
|
221
252
|
this.compiledPathPattern = new RegExp(options.referral.pathPattern);
|
|
222
253
|
}
|
|
@@ -278,6 +309,66 @@ var EventFactory = /** @class */ (function () {
|
|
|
278
309
|
EventFactory.prototype.getLibraryVersion = function () {
|
|
279
310
|
return version;
|
|
280
311
|
};
|
|
312
|
+
EventFactory.prototype.isExcludedQueryParam = function (key) {
|
|
313
|
+
return this.excludedQueryParams.has(key.toLowerCase());
|
|
314
|
+
};
|
|
315
|
+
/**
|
|
316
|
+
* Strip excluded (sensitive) query parameters from a URL in place. Only the
|
|
317
|
+
* query string is touched; the path and hash/fragment are left as-is.
|
|
318
|
+
*/
|
|
319
|
+
EventFactory.prototype.redactQueryParams = function (url) {
|
|
320
|
+
var _this = this;
|
|
321
|
+
// Collect first, then delete: mutating searchParams while iterating is
|
|
322
|
+
// unsafe, and deleting a key removes all of its values at once.
|
|
323
|
+
var keysToDelete = new Set();
|
|
324
|
+
url.searchParams.forEach(function (_value, key) {
|
|
325
|
+
if (_this.isExcludedQueryParam(key)) {
|
|
326
|
+
keysToDelete.add(key);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
keysToDelete.forEach(function (key) { return url.searchParams.delete(key); });
|
|
330
|
+
};
|
|
331
|
+
/**
|
|
332
|
+
* Return the given absolute URL with excluded query parameters removed. The
|
|
333
|
+
* input is returned unchanged when it is empty or cannot be parsed (e.g. an
|
|
334
|
+
* empty referrer).
|
|
335
|
+
*/
|
|
336
|
+
EventFactory.prototype.redactUrl = function (href) {
|
|
337
|
+
if (!href)
|
|
338
|
+
return href;
|
|
339
|
+
try {
|
|
340
|
+
var url = new URL(href);
|
|
341
|
+
this.redactQueryParams(url);
|
|
342
|
+
return url.href;
|
|
343
|
+
}
|
|
344
|
+
catch (_a) {
|
|
345
|
+
return href;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
/**
|
|
349
|
+
* Apply the current query-param denylist to a previously-persisted traffic
|
|
350
|
+
* source object. Traffic-source keys (utm_*, click ids, ref) are themselves
|
|
351
|
+
* query-parameter names, so an excluded key's stored value is dropped; the
|
|
352
|
+
* referrer is a URL and is re-redacted. Guards against a stored value
|
|
353
|
+
* outliving the config (or SDK version) under which it was first captured.
|
|
354
|
+
*/
|
|
355
|
+
EventFactory.prototype.redactStoredTrafficSources = function (stored) {
|
|
356
|
+
var result = {};
|
|
357
|
+
for (var _i = 0, _a = Object.keys(stored); _i < _a.length; _i++) {
|
|
358
|
+
var key = _a[_i];
|
|
359
|
+
var value = stored[key];
|
|
360
|
+
if (key === "referrer") {
|
|
361
|
+
result[key] = this.redactUrl(value || "");
|
|
362
|
+
}
|
|
363
|
+
else if (this.isExcludedQueryParam(key)) {
|
|
364
|
+
result[key] = "";
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
result[key] = value;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return result;
|
|
371
|
+
};
|
|
281
372
|
// Get screen dimensions and pixel density
|
|
282
373
|
// Returns safe defaults if any error occurs to ensure event creation continues
|
|
283
374
|
EventFactory.prototype.getScreen = function () {
|
|
@@ -306,7 +397,7 @@ var EventFactory = /** @class */ (function () {
|
|
|
306
397
|
// Contextual fields that are automatically collected and populated by the Formo SDK
|
|
307
398
|
EventFactory.prototype.generateContext = function (context) {
|
|
308
399
|
return __awaiter(this, void 0, void 0, function () {
|
|
309
|
-
var browserName, language, timezone, location, library_version, defaultContext, mergedContext;
|
|
400
|
+
var browserName, language, timezone, location, library_version, redactedHref, defaultContext, mergedContext;
|
|
310
401
|
return __generator(this, function (_a) {
|
|
311
402
|
switch (_a.label) {
|
|
312
403
|
case 0: return [4 /*yield*/, detectBrowser()];
|
|
@@ -316,7 +407,8 @@ var EventFactory = /** @class */ (function () {
|
|
|
316
407
|
timezone = this.getTimezone();
|
|
317
408
|
location = this.getLocation();
|
|
318
409
|
library_version = this.getLibraryVersion();
|
|
319
|
-
|
|
410
|
+
redactedHref = this.redactUrl(globalThis.location.href);
|
|
411
|
+
defaultContext = __assign(__assign(__assign({ user_agent: globalThis.navigator.userAgent, locale: language, timezone: timezone, location: location }, this.getTrafficSources(redactedHref)), { page_title: document.title, page_url: redactedHref, library_name: "Formo Web SDK", library_version: library_version, browser: browserName }), this.getScreen());
|
|
320
412
|
mergedContext = mergeDeepRight(defaultContext, context || {});
|
|
321
413
|
return [2 /*return*/, mergedContext];
|
|
322
414
|
}
|
|
@@ -6,6 +6,18 @@ declare const VERSION = "0";
|
|
|
6
6
|
* in src/types/events.ts derived from this array.
|
|
7
7
|
*/
|
|
8
8
|
declare const CLICK_ID_PARAMS: readonly ["gclid", "gad_source", "fbclid", "msclkid", "twclid", "li_fat_id", "rdt_cid", "ttclid"];
|
|
9
|
+
/**
|
|
10
|
+
* Query parameters that are ALWAYS stripped from forwarded and stored URLs,
|
|
11
|
+
* regardless of consumer configuration, because they carry high-sensitivity
|
|
12
|
+
* secrets that must never reach Formo:
|
|
13
|
+
* - privy_oauth_code: Privy OAuth authorization code
|
|
14
|
+
* - privy_oauth_state: Privy OAuth CSRF state token
|
|
15
|
+
* - privy_oauth_provider: Privy OAuth provider identifier
|
|
16
|
+
*
|
|
17
|
+
* Consumers can extend the denylist via `tracking.excludeQueryParams` but
|
|
18
|
+
* cannot remove these built-ins. Matched case-insensitively.
|
|
19
|
+
*/
|
|
20
|
+
declare const DEFAULT_EXCLUDED_QUERY_PARAMS: readonly ["privy_oauth_code", "privy_oauth_state", "privy_oauth_provider"];
|
|
9
21
|
/**
|
|
10
22
|
* Fields that should be excluded from page event properties parsing
|
|
11
23
|
* These are either:
|
|
@@ -13,5 +25,5 @@ declare const CLICK_ID_PARAMS: readonly ["gclid", "gad_source", "fbclid", "msclk
|
|
|
13
25
|
* - Semantic event properties that should not be overridden by URL params
|
|
14
26
|
*/
|
|
15
27
|
declare const PAGE_PROPERTIES_EXCLUDED_FIELDS: Set<string>;
|
|
16
|
-
export { CHANNEL, VERSION, CLICK_ID_PARAMS, PAGE_PROPERTIES_EXCLUDED_FIELDS };
|
|
28
|
+
export { CHANNEL, VERSION, CLICK_ID_PARAMS, DEFAULT_EXCLUDED_QUERY_PARAMS, PAGE_PROPERTIES_EXCLUDED_FIELDS, };
|
|
17
29
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -24,6 +24,22 @@ var CLICK_ID_PARAMS = [
|
|
|
24
24
|
"rdt_cid", // Reddit Ads
|
|
25
25
|
"ttclid", // TikTok Ads
|
|
26
26
|
];
|
|
27
|
+
/**
|
|
28
|
+
* Query parameters that are ALWAYS stripped from forwarded and stored URLs,
|
|
29
|
+
* regardless of consumer configuration, because they carry high-sensitivity
|
|
30
|
+
* secrets that must never reach Formo:
|
|
31
|
+
* - privy_oauth_code: Privy OAuth authorization code
|
|
32
|
+
* - privy_oauth_state: Privy OAuth CSRF state token
|
|
33
|
+
* - privy_oauth_provider: Privy OAuth provider identifier
|
|
34
|
+
*
|
|
35
|
+
* Consumers can extend the denylist via `tracking.excludeQueryParams` but
|
|
36
|
+
* cannot remove these built-ins. Matched case-insensitively.
|
|
37
|
+
*/
|
|
38
|
+
var DEFAULT_EXCLUDED_QUERY_PARAMS = [
|
|
39
|
+
"privy_oauth_code",
|
|
40
|
+
"privy_oauth_state",
|
|
41
|
+
"privy_oauth_provider",
|
|
42
|
+
];
|
|
27
43
|
/**
|
|
28
44
|
* Fields that should be excluded from page event properties parsing
|
|
29
45
|
* These are either:
|
|
@@ -50,5 +66,5 @@ var PAGE_PROPERTIES_EXCLUDED_FIELDS = new Set(__spreadArray(__spreadArray([
|
|
|
50
66
|
'hash',
|
|
51
67
|
'query',
|
|
52
68
|
], false));
|
|
53
|
-
export { CHANNEL, VERSION, CLICK_ID_PARAMS, PAGE_PROPERTIES_EXCLUDED_FIELDS };
|
|
69
|
+
export { CHANNEL, VERSION, CLICK_ID_PARAMS, DEFAULT_EXCLUDED_QUERY_PARAMS, PAGE_PROPERTIES_EXCLUDED_FIELDS, };
|
|
54
70
|
//# sourceMappingURL=constants.js.map
|
|
@@ -72,6 +72,20 @@ export interface TrackingOptions {
|
|
|
72
72
|
excludeHosts?: string[];
|
|
73
73
|
excludePaths?: string[];
|
|
74
74
|
excludeChains?: ChainID[];
|
|
75
|
+
/**
|
|
76
|
+
* Additional query parameter names to strip from forwarded and stored URLs,
|
|
77
|
+
* on top of a built-in always-on denylist (currently `privy_oauth_code`,
|
|
78
|
+
* `privy_oauth_state`, and `privy_oauth_provider`) that cannot be disabled.
|
|
79
|
+
* Matched case-insensitively. Excluded params are stripped from the captured
|
|
80
|
+
* page URL, query string, per-parameter page properties, and referrer before
|
|
81
|
+
* any event is sent. The URL hash/fragment is intentionally left untouched.
|
|
82
|
+
*
|
|
83
|
+
* Mirrors Mixpanel's `property_blacklist` and PostHog's `property_denylist`,
|
|
84
|
+
* scoped here to URL query parameters.
|
|
85
|
+
*
|
|
86
|
+
* @example ["token", "access_token", "email", "signature"]
|
|
87
|
+
*/
|
|
88
|
+
excludeQueryParams?: string[];
|
|
75
89
|
}
|
|
76
90
|
/**
|
|
77
91
|
* Configuration options for controlling wallet event autocapture
|