@v-tilt/browser 1.0.10 → 1.1.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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/dist/array.js +1 -1
  3. package/dist/array.js.map +1 -1
  4. package/dist/array.no-external.js +1 -1
  5. package/dist/array.no-external.js.map +1 -1
  6. package/dist/constants.d.ts +1 -0
  7. package/dist/main.js +1 -1
  8. package/dist/main.js.map +1 -1
  9. package/dist/module.d.ts +57 -4
  10. package/dist/module.js +1 -1
  11. package/dist/module.js.map +1 -1
  12. package/dist/module.no-external.d.ts +57 -4
  13. package/dist/module.no-external.js +1 -1
  14. package/dist/module.no-external.js.map +1 -1
  15. package/dist/rate-limiter.d.ts +52 -0
  16. package/dist/request-queue.d.ts +78 -0
  17. package/dist/request.d.ts +54 -0
  18. package/dist/retry-queue.d.ts +64 -0
  19. package/dist/types.d.ts +1 -0
  20. package/dist/user-manager.d.ts +21 -0
  21. package/dist/utils/event-utils.d.ts +35 -17
  22. package/dist/utils/index.d.ts +21 -0
  23. package/dist/utils/request-utils.d.ts +17 -0
  24. package/dist/vtilt.d.ts +40 -8
  25. package/lib/constants.d.ts +1 -0
  26. package/lib/constants.js +2 -1
  27. package/lib/rate-limiter.d.ts +52 -0
  28. package/lib/rate-limiter.js +80 -0
  29. package/lib/request-queue.d.ts +78 -0
  30. package/lib/request-queue.js +156 -0
  31. package/lib/request.d.ts +54 -0
  32. package/lib/request.js +265 -0
  33. package/lib/retry-queue.d.ts +64 -0
  34. package/lib/retry-queue.js +182 -0
  35. package/lib/types.d.ts +1 -0
  36. package/lib/user-manager.d.ts +21 -0
  37. package/lib/user-manager.js +66 -0
  38. package/lib/utils/event-utils.d.ts +35 -17
  39. package/lib/utils/event-utils.js +247 -118
  40. package/lib/utils/index.d.ts +21 -0
  41. package/lib/utils/index.js +58 -0
  42. package/lib/utils/request-utils.d.ts +17 -0
  43. package/lib/utils/request-utils.js +80 -0
  44. package/lib/vtilt.d.ts +40 -8
  45. package/lib/vtilt.js +161 -11
  46. package/package.json +61 -61
@@ -1,63 +1,251 @@
1
1
  "use strict";
2
+ /**
3
+ * Event utilities
4
+ * Functions for extracting event properties, campaign parameters, and person info
5
+ */
2
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.COOKIE_CAMPAIGN_PARAMS = exports.MASKED = exports.EVENT_TO_PERSON_PROPERTIES = exports.CAMPAIGN_PARAMS = exports.PERSONAL_DATA_CAMPAIGN_PARAMS = void 0;
8
+ exports.getCampaignParams = getCampaignParams;
9
+ exports.getSearchInfo = getSearchInfo;
3
10
  exports.getBrowserLanguage = getBrowserLanguage;
4
11
  exports.getBrowserLanguagePrefix = getBrowserLanguagePrefix;
5
12
  exports.getReferrer = getReferrer;
6
13
  exports.getReferringDomain = getReferringDomain;
14
+ exports.getReferrerInfo = getReferrerInfo;
15
+ exports.getPersonInfo = getPersonInfo;
16
+ exports.getPersonPropsFromInfo = getPersonPropsFromInfo;
17
+ exports.getInitialPersonPropsFromInfo = getInitialPersonPropsFromInfo;
7
18
  exports.getTimezone = getTimezone;
8
19
  exports.getTimezoneOffset = getTimezoneOffset;
9
20
  exports.getEventProperties = getEventProperties;
21
+ const request_utils_1 = require("./request-utils");
22
+ const index_1 = require("./index");
10
23
  const globals_1 = require("./globals");
11
24
  const user_agent_utils_1 = require("./user-agent-utils");
25
+ const URL_REGEX_PREFIX = "https?://(.*)";
12
26
  // Library version - should match package.json version
13
27
  const LIB_VERSION = "1.0.7"; // TODO: Auto-import from package.json
28
+ // Campaign parameters that could be considered personal data (e.g., GDPR)
29
+ // These can be masked in URLs and properties before being sent
30
+ exports.PERSONAL_DATA_CAMPAIGN_PARAMS = [
31
+ "gclid", // google ads
32
+ "gclsrc", // google ads 360
33
+ "dclid", // google display ads
34
+ "gbraid", // google ads, web to app
35
+ "wbraid", // google ads, app to web
36
+ "fbclid", // facebook
37
+ "msclkid", // microsoft
38
+ "twclid", // twitter
39
+ "li_fat_id", // linkedin
40
+ "igshid", // instagram
41
+ "ttclid", // tiktok
42
+ "rdt_cid", // reddit
43
+ "epik", // pinterest
44
+ "qclid", // quora
45
+ "sccid", // snapchat
46
+ "irclid", // impact
47
+ "_kx", // klaviyo
48
+ ];
49
+ exports.CAMPAIGN_PARAMS = (0, index_1.extendArray)([
50
+ "utm_source",
51
+ "utm_medium",
52
+ "utm_campaign",
53
+ "utm_content",
54
+ "utm_term",
55
+ "gad_source", // google ads source
56
+ "mc_cid", // mailchimp campaign id
57
+ ], exports.PERSONAL_DATA_CAMPAIGN_PARAMS);
58
+ // Properties that should be automatically copied from events to person properties
59
+ exports.EVENT_TO_PERSON_PROPERTIES = [
60
+ // Mobile app properties
61
+ "$app_build",
62
+ "$app_name",
63
+ "$app_namespace",
64
+ "$app_version",
65
+ // Web browser properties
66
+ "$browser",
67
+ "$browser_version",
68
+ "$device_type",
69
+ "$current_url",
70
+ "$pathname",
71
+ "$os",
72
+ "$os_name", // Special case: treated as alias of $os
73
+ "$os_version",
74
+ "$referring_domain",
75
+ "$referrer",
76
+ "$screen_height",
77
+ "$screen_width",
78
+ "$viewport_height",
79
+ "$viewport_width",
80
+ "$raw_user_agent",
81
+ ];
82
+ exports.MASKED = "<masked>";
83
+ // Campaign params that can be read from cookies (currently not implemented)
84
+ exports.COOKIE_CAMPAIGN_PARAMS = [
85
+ "li_fat_id", // linkedin
86
+ ];
14
87
  /**
15
- * Get browser language
16
- * Returns the browser's language setting (e.g., "en-US")
88
+ * Get campaign parameters from URL
89
+ * Extracts UTM and other campaign tracking parameters from current page URL
90
+ * Masks personal data parameters if configured
17
91
  */
92
+ function getCampaignParams(customTrackedParams, maskPersonalDataProperties, customPersonalDataProperties) {
93
+ if (!globals_1.document) {
94
+ return {};
95
+ }
96
+ const paramsToMask = maskPersonalDataProperties
97
+ ? (0, index_1.extendArray)([], exports.PERSONAL_DATA_CAMPAIGN_PARAMS, customPersonalDataProperties || [])
98
+ : [];
99
+ // Initially get campaign params from the URL
100
+ const urlCampaignParams = _getCampaignParamsFromUrl((0, request_utils_1.maskQueryParams)(globals_1.document.URL, paramsToMask, exports.MASKED), customTrackedParams);
101
+ // But we can also get some of them from the cookie store
102
+ // For now, we'll skip cookie-based campaign params (would need cookie store implementation)
103
+ const cookieCampaignParams = {};
104
+ // Prefer the values found in the urlCampaignParams if possible
105
+ // `extend` will override the values if found in the second argument
106
+ return (0, index_1.extend)(cookieCampaignParams, urlCampaignParams);
107
+ }
108
+ function _getCampaignParamsFromUrl(url, customParams) {
109
+ const campaign_keywords = exports.CAMPAIGN_PARAMS.concat(customParams || []);
110
+ const params = {};
111
+ (0, index_1.each)(campaign_keywords, function (kwkey) {
112
+ const kw = (0, request_utils_1.getQueryParam)(url, kwkey);
113
+ params[kwkey] = kw ? kw : null;
114
+ });
115
+ return params;
116
+ }
117
+ function _getSearchEngine(referrer) {
118
+ if (!referrer) {
119
+ return null;
120
+ }
121
+ else {
122
+ if (referrer.search(URL_REGEX_PREFIX + "google.([^/?]*)") === 0) {
123
+ return "google";
124
+ }
125
+ else if (referrer.search(URL_REGEX_PREFIX + "bing.com") === 0) {
126
+ return "bing";
127
+ }
128
+ else if (referrer.search(URL_REGEX_PREFIX + "yahoo.com") === 0) {
129
+ return "yahoo";
130
+ }
131
+ else if (referrer.search(URL_REGEX_PREFIX + "duckduckgo.com") === 0) {
132
+ return "duckduckgo";
133
+ }
134
+ else {
135
+ return null;
136
+ }
137
+ }
138
+ }
139
+ function _getSearchInfoFromReferrer(referrer) {
140
+ const search = _getSearchEngine(referrer);
141
+ const param = search !== "yahoo" ? "q" : "p";
142
+ const ret = {};
143
+ if (!(0, index_1.isNull)(search)) {
144
+ ret["$search_engine"] = search;
145
+ const keyword = globals_1.document ? (0, request_utils_1.getQueryParam)(globals_1.document.referrer, param) : "";
146
+ if (keyword.length) {
147
+ ret["ph_keyword"] = keyword;
148
+ }
149
+ }
150
+ return ret;
151
+ }
152
+ function getSearchInfo() {
153
+ const referrer = globals_1.document === null || globals_1.document === void 0 ? void 0 : globals_1.document.referrer;
154
+ if (!referrer) {
155
+ return {};
156
+ }
157
+ return _getSearchInfoFromReferrer(referrer);
158
+ }
18
159
  function getBrowserLanguage() {
19
- if (typeof globals_1.navigator === "undefined") {
160
+ if (typeof navigator === "undefined") {
20
161
  return undefined;
21
162
  }
22
- return (globals_1.navigator.language || // Any modern browser
23
- globals_1.navigator.userLanguage // IE11
163
+ return (navigator.language || // Any modern browser
164
+ navigator.userLanguage // IE11
24
165
  );
25
166
  }
26
- /**
27
- * Get browser language prefix
28
- * Returns the language code without region (e.g., "en" from "en-US")
29
- */
30
167
  function getBrowserLanguagePrefix() {
31
168
  const lang = getBrowserLanguage();
32
169
  return typeof lang === "string" ? lang.split("-")[0] : undefined;
33
170
  }
34
- /**
35
- * Get referrer
36
- * Returns document.referrer or '$direct' if no referrer
37
- */
38
171
  function getReferrer() {
39
172
  return (globals_1.document === null || globals_1.document === void 0 ? void 0 : globals_1.document.referrer) || "$direct";
40
173
  }
41
- /**
42
- * Get referring domain
43
- * Returns the hostname of the referrer URL or '$direct' if no referrer
44
- */
45
174
  function getReferringDomain() {
175
+ var _a;
46
176
  if (!(globals_1.document === null || globals_1.document === void 0 ? void 0 : globals_1.document.referrer)) {
47
177
  return "$direct";
48
178
  }
49
- try {
50
- const url = new URL(globals_1.document.referrer);
51
- return url.host || "$direct";
179
+ return ((_a = (0, request_utils_1.convertToURL)(globals_1.document.referrer)) === null || _a === void 0 ? void 0 : _a.host) || "$direct";
180
+ }
181
+ /**
182
+ * Get referrer information
183
+ * Returns current referrer and referring domain
184
+ */
185
+ function getReferrerInfo() {
186
+ return {
187
+ $referrer: getReferrer(),
188
+ $referring_domain: getReferringDomain(),
189
+ };
190
+ }
191
+ /**
192
+ * Get person info for initial storage
193
+ * Extracts referrer and URL info, masks personal data if configured
194
+ * Returns compact format (r: referrer, u: url) for storage efficiency
195
+ */
196
+ function getPersonInfo(maskPersonalDataProperties, customPersonalDataProperties) {
197
+ const paramsToMask = maskPersonalDataProperties
198
+ ? (0, index_1.extendArray)([], exports.PERSONAL_DATA_CAMPAIGN_PARAMS, customPersonalDataProperties || [])
199
+ : [];
200
+ const url = globals_1.location === null || globals_1.location === void 0 ? void 0 : globals_1.location.href.substring(0, 1000);
201
+ // Compact format for storage efficiency (stored in localStorage)
202
+ return {
203
+ r: getReferrer().substring(0, 1000),
204
+ u: url ? (0, request_utils_1.maskQueryParams)(url, paramsToMask, exports.MASKED) : undefined,
205
+ };
206
+ }
207
+ /**
208
+ * Convert person info to person properties
209
+ * Extracts referrer, URL, campaign params, and search info from stored person info
210
+ */
211
+ function getPersonPropsFromInfo(info) {
212
+ var _a;
213
+ const { r: referrer, u: url } = info;
214
+ const referring_domain = referrer == null
215
+ ? undefined
216
+ : referrer === "$direct"
217
+ ? "$direct"
218
+ : (_a = (0, request_utils_1.convertToURL)(referrer)) === null || _a === void 0 ? void 0 : _a.host;
219
+ const props = {
220
+ $referrer: referrer,
221
+ $referring_domain: referring_domain,
222
+ };
223
+ if (url) {
224
+ props["$current_url"] = url;
225
+ const location = (0, request_utils_1.convertToURL)(url);
226
+ props["$host"] = location === null || location === void 0 ? void 0 : location.host;
227
+ props["$pathname"] = location === null || location === void 0 ? void 0 : location.pathname;
228
+ const campaignParams = _getCampaignParamsFromUrl(url);
229
+ (0, index_1.extend)(props, campaignParams);
52
230
  }
53
- catch (_a) {
54
- return "$direct";
231
+ if (referrer) {
232
+ const searchInfo = _getSearchInfoFromReferrer(referrer);
233
+ (0, index_1.extend)(props, searchInfo);
55
234
  }
235
+ return props;
56
236
  }
57
237
  /**
58
- * Get timezone
59
- * Returns the timezone (e.g., "America/New_York")
238
+ * Convert person info to initial person properties
239
+ * Generates $initial_* properties from person info (preserves first values)
60
240
  */
241
+ function getInitialPersonPropsFromInfo(info) {
242
+ const personProps = getPersonPropsFromInfo(info);
243
+ const props = {};
244
+ (0, index_1.each)(personProps, function (val, key) {
245
+ props[`$initial_${(0, index_1.stripLeadingDollar)(String(key))}`] = val;
246
+ });
247
+ return props;
248
+ }
61
249
  function getTimezone() {
62
250
  try {
63
251
  return Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -66,10 +254,6 @@ function getTimezone() {
66
254
  return undefined;
67
255
  }
68
256
  }
69
- /**
70
- * Get timezone offset
71
- * Returns the timezone offset in minutes
72
- */
73
257
  function getTimezoneOffset() {
74
258
  try {
75
259
  return new Date().getTimezoneOffset();
@@ -78,100 +262,45 @@ function getTimezoneOffset() {
78
262
  return undefined;
79
263
  }
80
264
  }
81
- /**
82
- * Generate insert ID for deduplication
83
- */
84
- function generateInsertId() {
85
- return (Math.random().toString(36).substring(2, 10) +
86
- Math.random().toString(36).substring(2, 10));
87
- }
88
265
  /**
89
266
  * Get event properties that should be added to all events
267
+ * Returns all event context properties (browser, device, URL, etc.) plus event metadata
268
+ * Note: Only properties in EVENT_TO_PERSON_PROPERTIES are copied to person properties
90
269
  */
91
- function getEventProperties() {
92
- const props = {};
270
+ function getEventProperties(maskPersonalDataProperties, customPersonalDataProperties) {
93
271
  if (!globals_1.userAgent) {
94
- return props;
272
+ return {};
95
273
  }
96
- // Device/OS properties
274
+ const paramsToMask = maskPersonalDataProperties
275
+ ? (0, index_1.extendArray)([], exports.PERSONAL_DATA_CAMPAIGN_PARAMS, customPersonalDataProperties || [])
276
+ : [];
97
277
  const [os_name, os_version] = (0, user_agent_utils_1.detectOS)(globals_1.userAgent);
98
- if (os_name) {
99
- props.$os = os_name;
100
- }
101
- if (os_version) {
102
- props.$os_version = os_version;
103
- }
104
- const browser = (0, user_agent_utils_1.detectBrowser)(globals_1.userAgent, globals_1.navigator === null || globals_1.navigator === void 0 ? void 0 : globals_1.navigator.vendor);
105
- if (browser) {
106
- props.$browser = browser;
107
- }
108
- const browserVersion = (0, user_agent_utils_1.detectBrowserVersion)(globals_1.userAgent, globals_1.navigator === null || globals_1.navigator === void 0 ? void 0 : globals_1.navigator.vendor);
109
- if (browserVersion) {
110
- props.$browser_version = browserVersion;
111
- }
112
- const device = (0, user_agent_utils_1.detectDevice)(globals_1.userAgent);
113
- if (device) {
114
- props.$device = device;
115
- }
116
- const deviceType = (0, user_agent_utils_1.detectDeviceType)(globals_1.userAgent);
117
- if (deviceType) {
118
- props.$device_type = deviceType;
119
- }
120
- // Timezone properties
121
- const timezone = getTimezone();
122
- if (timezone) {
123
- props.$timezone = timezone;
124
- }
125
- const timezoneOffset = getTimezoneOffset();
126
- if (timezoneOffset !== undefined) {
127
- props.$timezone_offset = timezoneOffset;
128
- }
129
- // URL properties (added to all events)
130
- if (globals_1.location) {
131
- props.$current_url = globals_1.location.href;
132
- props.$host = globals_1.location.host;
133
- props.$pathname = globals_1.location.pathname;
134
- }
135
- // User agent
136
- if (globals_1.userAgent) {
137
- props.$raw_user_agent =
138
- globals_1.userAgent.length > 1000 ? globals_1.userAgent.substring(0, 997) + "..." : globals_1.userAgent;
139
- }
140
- // Browser language (added to all events)
141
- const browserLanguage = getBrowserLanguage();
142
- const browserLanguagePrefix = getBrowserLanguagePrefix();
143
- if (browserLanguage) {
144
- props.$browser_language = browserLanguage;
145
- }
146
- if (browserLanguagePrefix) {
147
- props.$browser_language_prefix = browserLanguagePrefix;
148
- }
149
- // Screen/viewport properties
150
- if (globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.screen) {
151
- if (globals_1.window.screen.height) {
152
- props.$screen_height = globals_1.window.screen.height;
153
- }
154
- if (globals_1.window.screen.width) {
155
- props.$screen_width = globals_1.window.screen.width;
156
- }
157
- }
158
- if (globals_1.window) {
159
- if (globals_1.window.innerHeight) {
160
- props.$viewport_height = globals_1.window.innerHeight;
161
- }
162
- if (globals_1.window.innerWidth) {
163
- props.$viewport_width = globals_1.window.innerWidth;
164
- }
165
- }
166
- // Library info
167
- props.$lib = "web";
168
- props.$lib_version = LIB_VERSION;
169
- // Insert ID for deduplication
170
- props.$insert_id = generateInsertId();
171
- // Timestamp (epoch time in seconds)
172
- props.$time = Date.now() / 1000;
173
- // Referrer properties (added to all events)
174
- props.$referrer = getReferrer();
175
- props.$referring_domain = getReferringDomain();
176
- return props;
278
+ return (0, index_1.extend)((0, index_1.stripEmptyProperties)({
279
+ $os: os_name,
280
+ $os_version: os_version,
281
+ $browser: (0, user_agent_utils_1.detectBrowser)(globals_1.userAgent, navigator.vendor),
282
+ $device: (0, user_agent_utils_1.detectDevice)(globals_1.userAgent),
283
+ $device_type: (0, user_agent_utils_1.detectDeviceType)(globals_1.userAgent),
284
+ $timezone: getTimezone(),
285
+ $timezone_offset: getTimezoneOffset(),
286
+ }), {
287
+ $current_url: (0, request_utils_1.maskQueryParams)(globals_1.location === null || globals_1.location === void 0 ? void 0 : globals_1.location.href, paramsToMask, exports.MASKED),
288
+ $host: globals_1.location === null || globals_1.location === void 0 ? void 0 : globals_1.location.host,
289
+ $pathname: globals_1.location === null || globals_1.location === void 0 ? void 0 : globals_1.location.pathname,
290
+ $raw_user_agent: globals_1.userAgent.length > 1000
291
+ ? globals_1.userAgent.substring(0, 997) + "..."
292
+ : globals_1.userAgent,
293
+ $browser_version: (0, user_agent_utils_1.detectBrowserVersion)(globals_1.userAgent, navigator.vendor),
294
+ $browser_language: getBrowserLanguage(),
295
+ $browser_language_prefix: getBrowserLanguagePrefix(),
296
+ $screen_height: globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.screen.height,
297
+ $screen_width: globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.screen.width,
298
+ $viewport_height: globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.innerHeight,
299
+ $viewport_width: globals_1.window === null || globals_1.window === void 0 ? void 0 : globals_1.window.innerWidth,
300
+ $lib: "web",
301
+ $lib_version: LIB_VERSION,
302
+ $insert_id: Math.random().toString(36).substring(2, 10) +
303
+ Math.random().toString(36).substring(2, 10),
304
+ $time: Date.now() / 1000, // epoch time in seconds
305
+ });
177
306
  }
@@ -23,3 +23,24 @@ export declare function each<T>(obj: T[] | Record<string, T> | null | undefined,
23
23
  * This properly implements the default options for passive event listeners
24
24
  */
25
25
  export declare function addEventListener(element: Window | Document | Element | undefined, event: string, callback: EventListener, options?: AddEventListenerOptions): void;
26
+ /**
27
+ * Extend object with properties from another object
28
+ */
29
+ export declare function extend<T extends Record<string, any>>(target: T, source: Record<string, any> | null | undefined): T;
30
+ /**
31
+ * Extend array with additional items
32
+ * Mutates the base array and returns it (matches PostHog's behavior)
33
+ */
34
+ export declare function extendArray<T>(base: T[], ...additional: T[][]): T[];
35
+ /**
36
+ * Strip properties with empty values (null, undefined, empty string)
37
+ */
38
+ export declare function stripEmptyProperties<T extends Record<string, any>>(obj: T): Partial<T>;
39
+ /**
40
+ * Strip leading dollar sign from string
41
+ */
42
+ export declare function stripLeadingDollar(str: string): string;
43
+ /**
44
+ * Check if value is null
45
+ */
46
+ export declare function isNull(value: any): boolean;
@@ -6,6 +6,11 @@ exports.isValidPayload = isValidPayload;
6
6
  exports.isTestEnvironment = isTestEnvironment;
7
7
  exports.each = each;
8
8
  exports.addEventListener = addEventListener;
9
+ exports.extend = extend;
10
+ exports.extendArray = extendArray;
11
+ exports.stripEmptyProperties = stripEmptyProperties;
12
+ exports.stripLeadingDollar = stripLeadingDollar;
13
+ exports.isNull = isNull;
9
14
  /**
10
15
  * Generate uuid to identify the session. Random, not data-derived
11
16
  */
@@ -74,3 +79,56 @@ function addEventListener(element, event, callback, options) {
74
79
  const { capture = false, passive = true } = options !== null && options !== void 0 ? options : {};
75
80
  element === null || element === void 0 ? void 0 : element.addEventListener(event, callback, { capture, passive });
76
81
  }
82
+ /**
83
+ * Extend object with properties from another object
84
+ */
85
+ function extend(target, source) {
86
+ if (!source) {
87
+ return target;
88
+ }
89
+ for (const key in source) {
90
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
91
+ target[key] = source[key];
92
+ }
93
+ }
94
+ return target;
95
+ }
96
+ /**
97
+ * Extend array with additional items
98
+ * Mutates the base array and returns it (matches PostHog's behavior)
99
+ */
100
+ function extendArray(base, ...additional) {
101
+ for (const arr of additional) {
102
+ for (const item of arr) {
103
+ base.push(item);
104
+ }
105
+ }
106
+ return base;
107
+ }
108
+ /**
109
+ * Strip properties with empty values (null, undefined, empty string)
110
+ */
111
+ function stripEmptyProperties(obj) {
112
+ const result = {};
113
+ for (const key in obj) {
114
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
115
+ const value = obj[key];
116
+ if (value !== null && value !== undefined && value !== "") {
117
+ result[key] = value;
118
+ }
119
+ }
120
+ }
121
+ return result;
122
+ }
123
+ /**
124
+ * Strip leading dollar sign from string
125
+ */
126
+ function stripLeadingDollar(str) {
127
+ return str.startsWith("$") ? str.substring(1) : str;
128
+ }
129
+ /**
130
+ * Check if value is null
131
+ */
132
+ function isNull(value) {
133
+ return value === null;
134
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Request utilities
3
+ * Functions for parsing URLs, query parameters, and masking sensitive data
4
+ */
5
+ /**
6
+ * Convert string URL to HTMLAnchorElement for parsing
7
+ * IE11 doesn't support `new URL`, so we use anchor element
8
+ */
9
+ export declare function convertToURL(url: string): HTMLAnchorElement | null;
10
+ /**
11
+ * Get query parameter from URL
12
+ */
13
+ export declare function getQueryParam(url: string, param: string): string;
14
+ /**
15
+ * Mask query parameters in URL
16
+ */
17
+ export declare function maskQueryParams<T extends string | undefined>(url: T, maskedParams: string[] | undefined, mask: string): T extends string ? string : undefined;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ /**
3
+ * Request utilities
4
+ * Functions for parsing URLs, query parameters, and masking sensitive data
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.convertToURL = convertToURL;
8
+ exports.getQueryParam = getQueryParam;
9
+ exports.maskQueryParams = maskQueryParams;
10
+ const globals_1 = require("./globals");
11
+ /**
12
+ * Convert string URL to HTMLAnchorElement for parsing
13
+ * IE11 doesn't support `new URL`, so we use anchor element
14
+ */
15
+ function convertToURL(url) {
16
+ if (!globals_1.document) {
17
+ return null;
18
+ }
19
+ const anchor = globals_1.document.createElement("a");
20
+ anchor.href = url;
21
+ return anchor;
22
+ }
23
+ /**
24
+ * Get query parameter from URL
25
+ */
26
+ function getQueryParam(url, param) {
27
+ const withoutHash = url.split("#")[0] || "";
28
+ const queryParams = withoutHash.split(/\?(.*)/)[1] || "";
29
+ const cleanedQueryParams = queryParams.replace(/^\?+/g, "");
30
+ const queryParts = cleanedQueryParams.split("&");
31
+ for (let i = 0; i < queryParts.length; i++) {
32
+ const parts = queryParts[i].split("=");
33
+ if (parts[0] === param) {
34
+ if (parts.length < 2) {
35
+ return "";
36
+ }
37
+ let result = parts[1];
38
+ try {
39
+ result = decodeURIComponent(result);
40
+ }
41
+ catch (_a) {
42
+ // Skip decoding for malformed query param
43
+ }
44
+ return result.replace(/\+/g, " ");
45
+ }
46
+ }
47
+ return "";
48
+ }
49
+ /**
50
+ * Mask query parameters in URL
51
+ */
52
+ function maskQueryParams(url, maskedParams, mask) {
53
+ if (!url || !maskedParams || !maskedParams.length) {
54
+ return url;
55
+ }
56
+ const splitHash = url.split("#");
57
+ const withoutHash = splitHash[0] || "";
58
+ const hash = splitHash[1];
59
+ const splitQuery = withoutHash.split("?");
60
+ const queryString = splitQuery[1];
61
+ const urlWithoutQueryAndHash = splitQuery[0];
62
+ const queryParts = (queryString || "").split("&");
63
+ const paramStrings = [];
64
+ for (let i = 0; i < queryParts.length; i++) {
65
+ const parts = queryParts[i].split("=");
66
+ const key = parts[0];
67
+ const value = parts.slice(1).join("=");
68
+ if (maskedParams.indexOf(key) !== -1) {
69
+ paramStrings.push(key + "=" + mask);
70
+ }
71
+ else if (key) {
72
+ paramStrings.push(key + (value ? "=" + value : ""));
73
+ }
74
+ }
75
+ const newQueryString = paramStrings.join("&");
76
+ const newUrl = urlWithoutQueryAndHash +
77
+ (newQueryString ? "?" + newQueryString : "") +
78
+ (hash ? "#" + hash : "");
79
+ return newUrl;
80
+ }