swetrix 3.7.1 → 4.0.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.
@@ -1,98 +1,27 @@
1
1
  'use strict';
2
2
 
3
- /******************************************************************************
4
- Copyright (c) Microsoft Corporation.
5
-
6
- Permission to use, copy, modify, and/or distribute this software for any
7
- purpose with or without fee is hereby granted.
8
-
9
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
- PERFORMANCE OF THIS SOFTWARE.
16
- ***************************************************************************** */
17
- /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
18
-
19
-
20
- var __assign = function() {
21
- __assign = Object.assign || function __assign(t) {
22
- for (var s, i = 1, n = arguments.length; i < n; i++) {
23
- s = arguments[i];
24
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
25
- }
26
- return t;
27
- };
28
- return __assign.apply(this, arguments);
29
- };
30
-
31
- function __awaiter(thisArg, _arguments, P, generator) {
32
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
33
- return new (P || (P = Promise))(function (resolve, reject) {
34
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
35
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
36
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
37
- step((generator = generator.apply(thisArg, _arguments || [])).next());
38
- });
39
- }
40
-
41
- function __generator(thisArg, body) {
42
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
43
- return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
44
- function verb(n) { return function (v) { return step([n, v]); }; }
45
- function step(op) {
46
- if (f) throw new TypeError("Generator is already executing.");
47
- while (g && (g = 0, op[0] && (_ = 0)), _) try {
48
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
49
- if (y = 0, t) op = [op[0] & 2, t.value];
50
- switch (op[0]) {
51
- case 0: case 1: t = op; break;
52
- case 4: _.label++; return { value: op[1], done: false };
53
- case 5: _.label++; y = op[1]; op = [0]; continue;
54
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
55
- default:
56
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
57
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
58
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
59
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
60
- if (t[2]) _.ops.pop();
61
- _.trys.pop(); continue;
62
- }
63
- op = body.call(thisArg, _);
64
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
65
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
66
- }
67
- }
68
-
69
- typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
70
- var e = new Error(message);
71
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
72
- };
73
-
74
- var findInSearch = function (exp) {
75
- var res = location.search.match(exp);
3
+ const findInSearch = (exp) => {
4
+ const res = location.search.match(exp);
76
5
  return (res && res[2]) || undefined;
77
6
  };
78
- var utmSourceRegex = /[?&](ref|source|utm_source)=([^?&]+)/;
79
- var utmCampaignRegex = /[?&](utm_campaign)=([^?&]+)/;
80
- var utmMediumRegex = /[?&](utm_medium)=([^?&]+)/;
81
- var utmTermRegex = /[?&](utm_term)=([^?&]+)/;
82
- var utmContentRegex = /[?&](utm_content)=([^?&]+)/;
83
- var isInBrowser = function () {
7
+ const utmSourceRegex = /[?&](ref|source|utm_source)=([^?&]+)/;
8
+ const utmCampaignRegex = /[?&](utm_campaign)=([^?&]+)/;
9
+ const utmMediumRegex = /[?&](utm_medium)=([^?&]+)/;
10
+ const utmTermRegex = /[?&](utm_term)=([^?&]+)/;
11
+ const utmContentRegex = /[?&](utm_content)=([^?&]+)/;
12
+ const isInBrowser = () => {
84
13
  return typeof window !== 'undefined';
85
14
  };
86
- var isLocalhost = function () {
15
+ const isLocalhost = () => {
87
16
  return (location === null || location === void 0 ? void 0 : location.hostname) === 'localhost' || (location === null || location === void 0 ? void 0 : location.hostname) === '127.0.0.1' || (location === null || location === void 0 ? void 0 : location.hostname) === '';
88
17
  };
89
- var isAutomated = function () {
18
+ const isAutomated = () => {
90
19
  return navigator === null || navigator === void 0 ? void 0 : navigator.webdriver;
91
20
  };
92
- var getLocale = function () {
21
+ const getLocale = () => {
93
22
  return typeof navigator.languages !== 'undefined' ? navigator.languages[0] : navigator.language;
94
23
  };
95
- var getTimezone = function () {
24
+ const getTimezone = () => {
96
25
  try {
97
26
  return Intl.DateTimeFormat().resolvedOptions().timeZone;
98
27
  }
@@ -100,14 +29,14 @@ var getTimezone = function () {
100
29
  return;
101
30
  }
102
31
  };
103
- var getReferrer = function () {
32
+ const getReferrer = () => {
104
33
  return document.referrer || undefined;
105
34
  };
106
- var getUTMSource = function () { return findInSearch(utmSourceRegex); };
107
- var getUTMMedium = function () { return findInSearch(utmMediumRegex); };
108
- var getUTMCampaign = function () { return findInSearch(utmCampaignRegex); };
109
- var getUTMTerm = function () { return findInSearch(utmTermRegex); };
110
- var getUTMContent = function () { return findInSearch(utmContentRegex); };
35
+ const getUTMSource = () => findInSearch(utmSourceRegex);
36
+ const getUTMMedium = () => findInSearch(utmMediumRegex);
37
+ const getUTMCampaign = () => findInSearch(utmCampaignRegex);
38
+ const getUTMTerm = () => findInSearch(utmTermRegex);
39
+ const getUTMContent = () => findInSearch(utmContentRegex);
111
40
  /**
112
41
  * Function used to track the current page (path) of the application.
113
42
  * Will work in cases where the path looks like:
@@ -122,27 +51,30 @@ var getUTMContent = function () { return findInSearch(utmContentRegex); };
122
51
  * @param options.search - Whether to trigger on search change.
123
52
  * @returns The path of the current page.
124
53
  */
125
- var getPath = function (options) {
126
- var result = location.pathname || '';
54
+ const getPath = (options) => {
55
+ let result = location.pathname || '';
127
56
  if (options.hash) {
128
- var hashIndex = location.hash.indexOf('?');
129
- var hashString = hashIndex > -1 ? location.hash.substring(0, hashIndex) : location.hash;
57
+ const hashIndex = location.hash.indexOf('?');
58
+ const hashString = hashIndex > -1 ? location.hash.substring(0, hashIndex) : location.hash;
130
59
  result += hashString;
131
60
  }
132
61
  if (options.search) {
133
- var hashIndex = location.hash.indexOf('?');
134
- var searchString = location.search || (hashIndex > -1 ? location.hash.substring(hashIndex) : '');
62
+ const hashIndex = location.hash.indexOf('?');
63
+ const searchString = location.search || (hashIndex > -1 ? location.hash.substring(hashIndex) : '');
135
64
  result += searchString;
136
65
  }
137
66
  return result;
138
67
  };
139
68
 
140
- var defaultActions = {
141
- stop: function () { },
69
+ const defaultActions = {
70
+ stop() { },
142
71
  };
143
- var DEFAULT_API_HOST = 'https://api.swetrix.com/log';
144
- var Lib = /** @class */ (function () {
145
- function Lib(projectID, options) {
72
+ const DEFAULT_API_HOST = 'https://api.swetrix.com/log';
73
+ const DEFAULT_API_BASE = 'https://api.swetrix.com';
74
+ // Default cache duration: 5 minutes
75
+ const DEFAULT_CACHE_DURATION = 5 * 60 * 1000;
76
+ class Lib {
77
+ constructor(projectID, options) {
146
78
  this.projectID = projectID;
147
79
  this.options = options;
148
80
  this.pageData = null;
@@ -151,13 +83,14 @@ var Lib = /** @class */ (function () {
151
83
  this.perfStatsCollected = false;
152
84
  this.activePage = null;
153
85
  this.errorListenerExists = false;
86
+ this.cachedData = null;
154
87
  this.trackPathChange = this.trackPathChange.bind(this);
155
88
  this.heartbeat = this.heartbeat.bind(this);
156
89
  this.captureError = this.captureError.bind(this);
157
90
  }
158
- Lib.prototype.captureError = function (event) {
91
+ captureError(event) {
159
92
  var _a, _b, _c, _d;
160
- if (typeof ((_a = this.errorsOptions) === null || _a === void 0 ? void 0 : _a.sampleRate) === 'number' && this.errorsOptions.sampleRate > Math.random()) {
93
+ if (typeof ((_a = this.errorsOptions) === null || _a === void 0 ? void 0 : _a.sampleRate) === 'number' && this.errorsOptions.sampleRate >= Math.random()) {
161
94
  return;
162
95
  }
163
96
  this.submitError({
@@ -176,9 +109,8 @@ var Lib = /** @class */ (function () {
176
109
  // Stack trace of the error, if available.
177
110
  stackTrace: (_d = event.error) === null || _d === void 0 ? void 0 : _d.stack,
178
111
  }, true);
179
- };
180
- Lib.prototype.trackErrors = function (options) {
181
- var _this = this;
112
+ }
113
+ trackErrors(options) {
182
114
  if (this.errorListenerExists || !this.canTrack()) {
183
115
  return defaultActions;
184
116
  }
@@ -186,23 +118,29 @@ var Lib = /** @class */ (function () {
186
118
  window.addEventListener('error', this.captureError);
187
119
  this.errorListenerExists = true;
188
120
  return {
189
- stop: function () {
190
- window.removeEventListener('error', _this.captureError);
121
+ stop: () => {
122
+ window.removeEventListener('error', this.captureError);
123
+ this.errorListenerExists = false;
191
124
  },
192
125
  };
193
- };
194
- Lib.prototype.submitError = function (payload, evokeCallback) {
126
+ }
127
+ submitError(payload, evokeCallback) {
195
128
  var _a, _b, _c;
196
- var privateData = {
129
+ const privateData = {
197
130
  pid: this.projectID,
198
131
  };
199
- var errorPayload = __assign({ pg: this.activePage ||
132
+ const errorPayload = {
133
+ pg: this.activePage ||
200
134
  getPath({
201
135
  hash: (_a = this.pageViewsOptions) === null || _a === void 0 ? void 0 : _a.hash,
202
136
  search: (_b = this.pageViewsOptions) === null || _b === void 0 ? void 0 : _b.search,
203
- }), lc: getLocale(), tz: getTimezone() }, payload);
137
+ }),
138
+ lc: getLocale(),
139
+ tz: getTimezone(),
140
+ ...payload,
141
+ };
204
142
  if (evokeCallback && ((_c = this.errorsOptions) === null || _c === void 0 ? void 0 : _c.callback)) {
205
- var callbackResult = this.errorsOptions.callback(errorPayload);
143
+ const callbackResult = this.errorsOptions.callback(errorPayload);
206
144
  if (callbackResult === false) {
207
145
  return;
208
146
  }
@@ -212,26 +150,33 @@ var Lib = /** @class */ (function () {
212
150
  }
213
151
  Object.assign(errorPayload, privateData);
214
152
  this.sendRequest('error', errorPayload);
215
- };
216
- Lib.prototype.track = function (event) {
217
- return __awaiter(this, void 0, void 0, function () {
218
- var data;
219
- return __generator(this, function (_a) {
220
- switch (_a.label) {
221
- case 0:
222
- if (!this.canTrack()) {
223
- return [2 /*return*/];
224
- }
225
- data = __assign(__assign({}, event), { pid: this.projectID, pg: this.activePage, lc: getLocale(), tz: getTimezone(), ref: getReferrer(), so: getUTMSource(), me: getUTMMedium(), ca: getUTMCampaign(), te: getUTMTerm(), co: getUTMContent() });
226
- return [4 /*yield*/, this.sendRequest('custom', data)];
227
- case 1:
228
- _a.sent();
229
- return [2 /*return*/];
230
- }
231
- });
232
- });
233
- };
234
- Lib.prototype.trackPageViews = function (options) {
153
+ }
154
+ async track(event) {
155
+ var _a, _b, _c, _d;
156
+ if (!this.canTrack()) {
157
+ return;
158
+ }
159
+ const data = {
160
+ ...event,
161
+ pid: this.projectID,
162
+ pg: this.activePage ||
163
+ getPath({
164
+ hash: (_a = this.pageViewsOptions) === null || _a === void 0 ? void 0 : _a.hash,
165
+ search: (_b = this.pageViewsOptions) === null || _b === void 0 ? void 0 : _b.search,
166
+ }),
167
+ lc: getLocale(),
168
+ tz: getTimezone(),
169
+ ref: getReferrer(),
170
+ so: getUTMSource(),
171
+ me: getUTMMedium(),
172
+ ca: getUTMCampaign(),
173
+ te: getUTMTerm(),
174
+ co: getUTMContent(),
175
+ profileId: (_c = event.profileId) !== null && _c !== void 0 ? _c : (_d = this.options) === null || _d === void 0 ? void 0 : _d.profileId,
176
+ };
177
+ await this.sendRequest('custom', data);
178
+ }
179
+ trackPageViews(options) {
235
180
  if (!this.canTrack()) {
236
181
  return defaultActions;
237
182
  }
@@ -239,20 +184,20 @@ var Lib = /** @class */ (function () {
239
184
  return this.pageData.actions;
240
185
  }
241
186
  this.pageViewsOptions = options;
242
- var interval;
187
+ let interval;
243
188
  if (!(options === null || options === void 0 ? void 0 : options.unique)) {
244
189
  interval = setInterval(this.trackPathChange, 2000);
245
190
  }
246
191
  setTimeout(this.heartbeat, 3000);
247
- var hbInterval = setInterval(this.heartbeat, 28000);
248
- var path = getPath({
192
+ const hbInterval = setInterval(this.heartbeat, 28000);
193
+ const path = getPath({
249
194
  hash: options === null || options === void 0 ? void 0 : options.hash,
250
195
  search: options === null || options === void 0 ? void 0 : options.search,
251
196
  });
252
197
  this.pageData = {
253
- path: path,
198
+ path,
254
199
  actions: {
255
- stop: function () {
200
+ stop: () => {
256
201
  clearInterval(interval);
257
202
  clearInterval(hbInterval);
258
203
  },
@@ -260,13 +205,13 @@ var Lib = /** @class */ (function () {
260
205
  };
261
206
  this.trackPage(path, options === null || options === void 0 ? void 0 : options.unique);
262
207
  return this.pageData.actions;
263
- };
264
- Lib.prototype.getPerformanceStats = function () {
208
+ }
209
+ getPerformanceStats() {
265
210
  var _a;
266
211
  if (!this.canTrack() || this.perfStatsCollected || !((_a = window.performance) === null || _a === void 0 ? void 0 : _a.getEntriesByType)) {
267
212
  return {};
268
213
  }
269
- var perf = window.performance.getEntriesByType('navigation')[0];
214
+ const perf = window.performance.getEntriesByType('navigation')[0];
270
215
  if (!perf) {
271
216
  return {};
272
217
  }
@@ -286,50 +231,323 @@ var Lib = /** @class */ (function () {
286
231
  // Backend
287
232
  ttfb: perf.responseStart - perf.requestStart,
288
233
  };
289
- };
290
- Lib.prototype.heartbeat = function () {
234
+ }
235
+ /**
236
+ * Fetches all feature flags and experiments for the project.
237
+ * Results are cached for 5 minutes by default.
238
+ *
239
+ * @param options - Options for evaluating feature flags.
240
+ * @param forceRefresh - If true, bypasses the cache and fetches fresh data.
241
+ * @returns A promise that resolves to a record of flag keys to boolean values.
242
+ */
243
+ async getFeatureFlags(options, forceRefresh) {
244
+ var _a, _b, _c, _d;
245
+ if (!isInBrowser()) {
246
+ return {};
247
+ }
248
+ const requestedProfileId = (_a = options === null || options === void 0 ? void 0 : options.profileId) !== null && _a !== void 0 ? _a : (_b = this.options) === null || _b === void 0 ? void 0 : _b.profileId;
249
+ // Check cache first - must match profileId and not be expired
250
+ if (!forceRefresh && this.cachedData) {
251
+ const now = Date.now();
252
+ const isSameProfile = this.cachedData.profileId === requestedProfileId;
253
+ if (isSameProfile && now - this.cachedData.timestamp < DEFAULT_CACHE_DURATION) {
254
+ return this.cachedData.flags;
255
+ }
256
+ }
257
+ try {
258
+ await this.fetchFlagsAndExperiments(options);
259
+ return ((_c = this.cachedData) === null || _c === void 0 ? void 0 : _c.flags) || {};
260
+ }
261
+ catch (error) {
262
+ console.warn('[Swetrix] Error fetching feature flags:', error);
263
+ return ((_d = this.cachedData) === null || _d === void 0 ? void 0 : _d.flags) || {};
264
+ }
265
+ }
266
+ /**
267
+ * Internal method to fetch both feature flags and experiments from the API.
268
+ */
269
+ async fetchFlagsAndExperiments(options) {
270
+ var _a, _b, _c, _d;
271
+ const apiBase = this.getApiBase();
272
+ const body = {
273
+ pid: this.projectID,
274
+ };
275
+ // Use profileId from options, or fall back to global profileId
276
+ const profileId = (_a = options === null || options === void 0 ? void 0 : options.profileId) !== null && _a !== void 0 ? _a : (_b = this.options) === null || _b === void 0 ? void 0 : _b.profileId;
277
+ if (profileId) {
278
+ body.profileId = profileId;
279
+ }
280
+ const response = await fetch(`${apiBase}/feature-flag/evaluate`, {
281
+ method: 'POST',
282
+ headers: {
283
+ 'Content-Type': 'application/json',
284
+ },
285
+ body: JSON.stringify(body),
286
+ });
287
+ if (!response.ok) {
288
+ console.warn('[Swetrix] Failed to fetch feature flags and experiments:', response.status);
289
+ return;
290
+ }
291
+ const data = (await response.json());
292
+ // Use profileId from options, or fall back to global profileId
293
+ const cachedProfileId = (_c = options === null || options === void 0 ? void 0 : options.profileId) !== null && _c !== void 0 ? _c : (_d = this.options) === null || _d === void 0 ? void 0 : _d.profileId;
294
+ // Update cache with both flags and experiments
295
+ this.cachedData = {
296
+ flags: data.flags || {},
297
+ experiments: data.experiments || {},
298
+ timestamp: Date.now(),
299
+ profileId: cachedProfileId,
300
+ };
301
+ }
302
+ /**
303
+ * Gets the value of a single feature flag.
304
+ *
305
+ * @param key - The feature flag key.
306
+ * @param options - Options for evaluating the feature flag.
307
+ * @param defaultValue - Default value to return if the flag is not found. Defaults to false.
308
+ * @returns A promise that resolves to the boolean value of the flag.
309
+ */
310
+ async getFeatureFlag(key, options, defaultValue = false) {
311
+ var _a;
312
+ const flags = await this.getFeatureFlags(options);
313
+ return (_a = flags[key]) !== null && _a !== void 0 ? _a : defaultValue;
314
+ }
315
+ /**
316
+ * Clears the cached feature flags and experiments, forcing a fresh fetch on the next call.
317
+ */
318
+ clearFeatureFlagsCache() {
319
+ this.cachedData = null;
320
+ }
321
+ /**
322
+ * Fetches all A/B test experiments for the project.
323
+ * Results are cached for 5 minutes by default (shared cache with feature flags).
324
+ *
325
+ * @param options - Options for evaluating experiments.
326
+ * @param forceRefresh - If true, bypasses the cache and fetches fresh data.
327
+ * @returns A promise that resolves to a record of experiment IDs to variant keys.
328
+ *
329
+ * @example
330
+ * ```typescript
331
+ * const experiments = await getExperiments()
332
+ * // experiments = { 'exp-123': 'variant-a', 'exp-456': 'control' }
333
+ * ```
334
+ */
335
+ async getExperiments(options, forceRefresh) {
336
+ var _a, _b, _c, _d;
337
+ if (!isInBrowser()) {
338
+ return {};
339
+ }
340
+ const requestedProfileId = (_a = options === null || options === void 0 ? void 0 : options.profileId) !== null && _a !== void 0 ? _a : (_b = this.options) === null || _b === void 0 ? void 0 : _b.profileId;
341
+ // Check cache first - must match profileId and not be expired
342
+ if (!forceRefresh && this.cachedData) {
343
+ const now = Date.now();
344
+ const isSameProfile = this.cachedData.profileId === requestedProfileId;
345
+ if (isSameProfile && now - this.cachedData.timestamp < DEFAULT_CACHE_DURATION) {
346
+ return this.cachedData.experiments;
347
+ }
348
+ }
349
+ try {
350
+ await this.fetchFlagsAndExperiments(options);
351
+ return ((_c = this.cachedData) === null || _c === void 0 ? void 0 : _c.experiments) || {};
352
+ }
353
+ catch (error) {
354
+ console.warn('[Swetrix] Error fetching experiments:', error);
355
+ return ((_d = this.cachedData) === null || _d === void 0 ? void 0 : _d.experiments) || {};
356
+ }
357
+ }
358
+ /**
359
+ * Gets the variant key for a specific A/B test experiment.
360
+ *
361
+ * @param experimentId - The experiment ID.
362
+ * @param options - Options for evaluating the experiment.
363
+ * @param defaultVariant - Default variant key to return if the experiment is not found. Defaults to null.
364
+ * @returns A promise that resolves to the variant key assigned to this user, or defaultVariant if not found.
365
+ *
366
+ * @example
367
+ * ```typescript
368
+ * const variant = await getExperiment('checkout-redesign')
369
+ *
370
+ * if (variant === 'new-checkout') {
371
+ * // Show new checkout flow
372
+ * } else {
373
+ * // Show control (original) checkout
374
+ * }
375
+ * ```
376
+ */
377
+ async getExperiment(experimentId, options, defaultVariant = null) {
378
+ var _a;
379
+ const experiments = await this.getExperiments(options);
380
+ return (_a = experiments[experimentId]) !== null && _a !== void 0 ? _a : defaultVariant;
381
+ }
382
+ /**
383
+ * Clears the cached experiments (alias for clearFeatureFlagsCache since they share the same cache).
384
+ */
385
+ clearExperimentsCache() {
386
+ this.cachedData = null;
387
+ }
388
+ /**
389
+ * Gets the anonymous profile ID for the current visitor.
390
+ * If profileId was set via init options, returns that.
391
+ * Otherwise, requests server to generate one from IP/UA hash.
392
+ *
393
+ * This ID can be used for revenue attribution with payment providers.
394
+ *
395
+ * @returns A promise that resolves to the profile ID string, or null on error.
396
+ *
397
+ * @example
398
+ * ```typescript
399
+ * const profileId = await swetrix.getProfileId()
400
+ *
401
+ * // Pass to Paddle Checkout for revenue attribution
402
+ * Paddle.Checkout.open({
403
+ * items: [{ priceId: 'pri_01234567890', quantity: 1 }],
404
+ * customData: {
405
+ * swetrix_profile_id: profileId,
406
+ * swetrix_session_id: await swetrix.getSessionId()
407
+ * }
408
+ * })
409
+ * ```
410
+ */
411
+ async getProfileId() {
412
+ var _a;
413
+ // If profileId is already set in options, return it
414
+ if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.profileId) {
415
+ return this.options.profileId;
416
+ }
417
+ if (!isInBrowser()) {
418
+ return null;
419
+ }
420
+ try {
421
+ const apiBase = this.getApiBase();
422
+ const response = await fetch(`${apiBase}/log/profile-id`, {
423
+ method: 'POST',
424
+ headers: {
425
+ 'Content-Type': 'application/json',
426
+ },
427
+ body: JSON.stringify({ pid: this.projectID }),
428
+ });
429
+ if (!response.ok) {
430
+ return null;
431
+ }
432
+ const data = (await response.json());
433
+ return data.profileId;
434
+ }
435
+ catch (_b) {
436
+ return null;
437
+ }
438
+ }
439
+ /**
440
+ * Gets the current session ID for the visitor.
441
+ * Session IDs are generated server-side based on IP and user agent.
442
+ *
443
+ * This ID can be used for revenue attribution with payment providers.
444
+ *
445
+ * @returns A promise that resolves to the session ID string, or null on error.
446
+ *
447
+ * @example
448
+ * ```typescript
449
+ * const sessionId = await swetrix.getSessionId()
450
+ *
451
+ * // Pass to Paddle Checkout for revenue attribution
452
+ * Paddle.Checkout.open({
453
+ * items: [{ priceId: 'pri_01234567890', quantity: 1 }],
454
+ * customData: {
455
+ * swetrix_profile_id: await swetrix.getProfileId(),
456
+ * swetrix_session_id: sessionId
457
+ * }
458
+ * })
459
+ * ```
460
+ */
461
+ async getSessionId() {
462
+ if (!isInBrowser()) {
463
+ return null;
464
+ }
465
+ try {
466
+ const apiBase = this.getApiBase();
467
+ const response = await fetch(`${apiBase}/log/session-id`, {
468
+ method: 'POST',
469
+ headers: {
470
+ 'Content-Type': 'application/json',
471
+ },
472
+ body: JSON.stringify({ pid: this.projectID }),
473
+ });
474
+ if (!response.ok) {
475
+ return null;
476
+ }
477
+ const data = (await response.json());
478
+ return data.sessionId;
479
+ }
480
+ catch (_a) {
481
+ return null;
482
+ }
483
+ }
484
+ /**
485
+ * Gets the API base URL (without /log suffix).
486
+ */
487
+ getApiBase() {
291
488
  var _a;
489
+ if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.apiURL) {
490
+ // Remove trailing /log if present
491
+ return this.options.apiURL.replace(/\/log\/?$/, '');
492
+ }
493
+ return DEFAULT_API_BASE;
494
+ }
495
+ heartbeat() {
496
+ var _a, _b;
292
497
  if (!((_a = this.pageViewsOptions) === null || _a === void 0 ? void 0 : _a.heartbeatOnBackground) && document.visibilityState === 'hidden') {
293
498
  return;
294
499
  }
295
- var data = {
500
+ const data = {
296
501
  pid: this.projectID,
297
502
  };
503
+ if ((_b = this.options) === null || _b === void 0 ? void 0 : _b.profileId) {
504
+ data.profileId = this.options.profileId;
505
+ }
298
506
  this.sendRequest('hb', data);
299
- };
507
+ }
300
508
  // Tracking path changes. If path changes -> calling this.trackPage method
301
- Lib.prototype.trackPathChange = function () {
509
+ trackPathChange() {
302
510
  var _a, _b;
303
511
  if (!this.pageData)
304
512
  return;
305
- var newPath = getPath({
513
+ const newPath = getPath({
306
514
  hash: (_a = this.pageViewsOptions) === null || _a === void 0 ? void 0 : _a.hash,
307
515
  search: (_b = this.pageViewsOptions) === null || _b === void 0 ? void 0 : _b.search,
308
516
  });
309
- var path = this.pageData.path;
517
+ const { path } = this.pageData;
310
518
  if (path !== newPath) {
311
519
  this.trackPage(newPath, false);
312
520
  }
313
- };
314
- Lib.prototype.trackPage = function (pg, unique) {
315
- if (unique === void 0) { unique = false; }
521
+ }
522
+ trackPage(pg, unique = false) {
316
523
  if (!this.pageData)
317
524
  return;
318
525
  this.pageData.path = pg;
319
- var perf = this.getPerformanceStats();
526
+ const perf = this.getPerformanceStats();
320
527
  this.activePage = pg;
321
- this.submitPageView({ pg: pg }, unique, perf, true);
322
- };
323
- Lib.prototype.submitPageView = function (payload, unique, perf, evokeCallback) {
324
- var _a;
325
- var privateData = {
528
+ this.submitPageView({ pg }, unique, perf, true);
529
+ }
530
+ submitPageView(payload, unique, perf, evokeCallback) {
531
+ var _a, _b;
532
+ const privateData = {
326
533
  pid: this.projectID,
327
- perf: perf,
328
- unique: unique,
534
+ perf,
535
+ unique,
329
536
  };
330
- var pvPayload = __assign({ lc: getLocale(), tz: getTimezone(), ref: getReferrer(), so: getUTMSource(), me: getUTMMedium(), ca: getUTMCampaign(), te: getUTMTerm(), co: getUTMContent() }, payload);
331
- if (evokeCallback && ((_a = this.pageViewsOptions) === null || _a === void 0 ? void 0 : _a.callback)) {
332
- var callbackResult = this.pageViewsOptions.callback(pvPayload);
537
+ const pvPayload = {
538
+ lc: getLocale(),
539
+ tz: getTimezone(),
540
+ ref: getReferrer(),
541
+ so: getUTMSource(),
542
+ me: getUTMMedium(),
543
+ ca: getUTMCampaign(),
544
+ te: getUTMTerm(),
545
+ co: getUTMContent(),
546
+ profileId: (_a = this.options) === null || _a === void 0 ? void 0 : _a.profileId,
547
+ ...payload,
548
+ };
549
+ if (evokeCallback && ((_b = this.pageViewsOptions) === null || _b === void 0 ? void 0 : _b.callback)) {
550
+ const callbackResult = this.pageViewsOptions.callback(pvPayload);
333
551
  if (callbackResult === false) {
334
552
  return;
335
553
  }
@@ -339,8 +557,8 @@ var Lib = /** @class */ (function () {
339
557
  }
340
558
  Object.assign(pvPayload, privateData);
341
559
  this.sendRequest('', pvPayload);
342
- };
343
- Lib.prototype.canTrack = function () {
560
+ }
561
+ canTrack() {
344
562
  var _a, _b, _c, _d;
345
563
  if (((_a = this.options) === null || _a === void 0 ? void 0 : _a.disabled) ||
346
564
  !isInBrowser() ||
@@ -350,31 +568,19 @@ var Lib = /** @class */ (function () {
350
568
  return false;
351
569
  }
352
570
  return true;
353
- };
354
- Lib.prototype.sendRequest = function (path, body) {
355
- return __awaiter(this, void 0, void 0, function () {
356
- var host;
357
- var _a;
358
- return __generator(this, function (_b) {
359
- switch (_b.label) {
360
- case 0:
361
- host = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.apiURL) || DEFAULT_API_HOST;
362
- return [4 /*yield*/, fetch("".concat(host, "/").concat(path), {
363
- method: 'POST',
364
- headers: {
365
- 'Content-Type': 'application/json',
366
- },
367
- body: JSON.stringify(body),
368
- })];
369
- case 1:
370
- _b.sent();
371
- return [2 /*return*/];
372
- }
373
- });
571
+ }
572
+ async sendRequest(path, body) {
573
+ var _a;
574
+ const host = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.apiURL) || DEFAULT_API_HOST;
575
+ await fetch(`${host}/${path}`, {
576
+ method: 'POST',
577
+ headers: {
578
+ 'Content-Type': 'application/json',
579
+ },
580
+ body: JSON.stringify(body),
374
581
  });
375
- };
376
- return Lib;
377
- }());
582
+ }
583
+ }
378
584
 
379
585
  exports.LIB_INSTANCE = null;
380
586
  /**
@@ -397,20 +603,10 @@ function init(pid, options) {
397
603
  *
398
604
  * @param {TrackEventOptions} event The options related to the custom event.
399
605
  */
400
- function track(event) {
401
- return __awaiter(this, void 0, void 0, function () {
402
- return __generator(this, function (_a) {
403
- switch (_a.label) {
404
- case 0:
405
- if (!exports.LIB_INSTANCE)
406
- return [2 /*return*/];
407
- return [4 /*yield*/, exports.LIB_INSTANCE.track(event)];
408
- case 1:
409
- _a.sent();
410
- return [2 /*return*/];
411
- }
412
- });
413
- });
606
+ async function track(event) {
607
+ if (!exports.LIB_INSTANCE)
608
+ return;
609
+ await exports.LIB_INSTANCE.track(event);
414
610
  }
415
611
  /**
416
612
  * With this function you are able to automatically track pageviews across your application.
@@ -419,7 +615,7 @@ function track(event) {
419
615
  * @returns {PageActions} The actions related to the tracking. Used to stop tracking pages.
420
616
  */
421
617
  function trackViews(options) {
422
- return new Promise(function (resolve) {
618
+ return new Promise((resolve) => {
423
619
  if (!exports.LIB_INSTANCE) {
424
620
  resolve(defaultActions);
425
621
  return;
@@ -429,7 +625,7 @@ function trackViews(options) {
429
625
  resolve(exports.LIB_INSTANCE.trackPageViews(options));
430
626
  }
431
627
  else {
432
- window.addEventListener('load', function () {
628
+ window.addEventListener('load', () => {
433
629
  // @ts-ignore
434
630
  resolve(exports.LIB_INSTANCE.trackPageViews(options));
435
631
  });
@@ -473,14 +669,198 @@ function trackError(payload) {
473
669
  function trackPageview(pg, _prev, unique) {
474
670
  if (!exports.LIB_INSTANCE)
475
671
  return;
476
- exports.LIB_INSTANCE.submitPageView({ pg: pg }, Boolean(unique), {});
672
+ exports.LIB_INSTANCE.submitPageView({ pg }, Boolean(unique), {});
477
673
  }
478
674
  function pageview(options) {
479
675
  if (!exports.LIB_INSTANCE)
480
676
  return;
481
677
  exports.LIB_INSTANCE.submitPageView(options.payload, Boolean(options.unique), {});
482
678
  }
679
+ /**
680
+ * Fetches all feature flags for the project.
681
+ * Results are cached for 5 minutes by default.
682
+ *
683
+ * @param options - Options for evaluating feature flags (visitorId, attributes).
684
+ * @param forceRefresh - If true, bypasses the cache and fetches fresh flags.
685
+ * @returns A promise that resolves to a record of flag keys to boolean values.
686
+ *
687
+ * @example
688
+ * ```typescript
689
+ * const flags = await getFeatureFlags({
690
+ * visitorId: 'user-123',
691
+ * attributes: { cc: 'US', dv: 'desktop' }
692
+ * })
693
+ *
694
+ * if (flags['new-checkout']) {
695
+ * // Show new checkout flow
696
+ * }
697
+ * ```
698
+ */
699
+ async function getFeatureFlags(options, forceRefresh) {
700
+ if (!exports.LIB_INSTANCE)
701
+ return {};
702
+ return exports.LIB_INSTANCE.getFeatureFlags(options, forceRefresh);
703
+ }
704
+ /**
705
+ * Gets the value of a single feature flag.
706
+ *
707
+ * @param key - The feature flag key.
708
+ * @param options - Options for evaluating the feature flag (visitorId, attributes).
709
+ * @param defaultValue - Default value to return if the flag is not found. Defaults to false.
710
+ * @returns A promise that resolves to the boolean value of the flag.
711
+ *
712
+ * @example
713
+ * ```typescript
714
+ * const isEnabled = await getFeatureFlag('dark-mode', { visitorId: 'user-123' })
715
+ *
716
+ * if (isEnabled) {
717
+ * // Enable dark mode
718
+ * }
719
+ * ```
720
+ */
721
+ async function getFeatureFlag(key, options, defaultValue = false) {
722
+ if (!exports.LIB_INSTANCE)
723
+ return defaultValue;
724
+ return exports.LIB_INSTANCE.getFeatureFlag(key, options, defaultValue);
725
+ }
726
+ /**
727
+ * Clears the cached feature flags, forcing a fresh fetch on the next call.
728
+ * Useful when you know the user's context has changed significantly.
729
+ */
730
+ function clearFeatureFlagsCache() {
731
+ if (!exports.LIB_INSTANCE)
732
+ return;
733
+ exports.LIB_INSTANCE.clearFeatureFlagsCache();
734
+ }
735
+ /**
736
+ * Fetches all A/B test experiments for the project.
737
+ * Results are cached for 5 minutes by default (shared cache with feature flags).
738
+ *
739
+ * @param options - Options for evaluating experiments.
740
+ * @param forceRefresh - If true, bypasses the cache and fetches fresh data.
741
+ * @returns A promise that resolves to a record of experiment IDs to variant keys.
742
+ *
743
+ * @example
744
+ * ```typescript
745
+ * const experiments = await getExperiments()
746
+ * // experiments = { 'exp-123': 'variant-a', 'exp-456': 'control' }
747
+ *
748
+ * // Use the assigned variant
749
+ * const checkoutVariant = experiments['checkout-experiment-id']
750
+ * if (checkoutVariant === 'new-checkout') {
751
+ * showNewCheckout()
752
+ * } else {
753
+ * showOriginalCheckout()
754
+ * }
755
+ * ```
756
+ */
757
+ async function getExperiments(options, forceRefresh) {
758
+ if (!exports.LIB_INSTANCE)
759
+ return {};
760
+ return exports.LIB_INSTANCE.getExperiments(options, forceRefresh);
761
+ }
762
+ /**
763
+ * Gets the variant key for a specific A/B test experiment.
764
+ *
765
+ * @param experimentId - The experiment ID.
766
+ * @param options - Options for evaluating the experiment.
767
+ * @param defaultVariant - Default variant key to return if the experiment is not found. Defaults to null.
768
+ * @returns A promise that resolves to the variant key assigned to this user, or defaultVariant if not found.
769
+ *
770
+ * @example
771
+ * ```typescript
772
+ * const variant = await getExperiment('checkout-redesign-experiment-id')
773
+ *
774
+ * if (variant === 'new-checkout') {
775
+ * // Show new checkout flow
776
+ * showNewCheckout()
777
+ * } else if (variant === 'control') {
778
+ * // Show original checkout (control group)
779
+ * showOriginalCheckout()
780
+ * } else {
781
+ * // Experiment not running or user not included
782
+ * showOriginalCheckout()
783
+ * }
784
+ * ```
785
+ */
786
+ async function getExperiment(experimentId, options, defaultVariant = null) {
787
+ if (!exports.LIB_INSTANCE)
788
+ return defaultVariant;
789
+ return exports.LIB_INSTANCE.getExperiment(experimentId, options, defaultVariant);
790
+ }
791
+ /**
792
+ * Clears the cached experiments, forcing a fresh fetch on the next call.
793
+ * This is an alias for clearFeatureFlagsCache since experiments and flags share the same cache.
794
+ */
795
+ function clearExperimentsCache() {
796
+ if (!exports.LIB_INSTANCE)
797
+ return;
798
+ exports.LIB_INSTANCE.clearExperimentsCache();
799
+ }
800
+ /**
801
+ * Gets the anonymous profile ID for the current visitor.
802
+ * If profileId was set via init options, returns that.
803
+ * Otherwise, requests server to generate one from IP/UA hash.
804
+ *
805
+ * This ID can be used for revenue attribution with payment providers like Paddle.
806
+ *
807
+ * @returns A promise that resolves to the profile ID string, or null on error.
808
+ *
809
+ * @example
810
+ * ```typescript
811
+ * const profileId = await getProfileId()
812
+ *
813
+ * // Pass to Paddle Checkout for revenue attribution
814
+ * Paddle.Checkout.open({
815
+ * items: [{ priceId: 'pri_01234567890', quantity: 1 }],
816
+ * customData: {
817
+ * swetrix_profile_id: profileId,
818
+ * swetrix_session_id: await getSessionId()
819
+ * }
820
+ * })
821
+ * ```
822
+ */
823
+ async function getProfileId() {
824
+ if (!exports.LIB_INSTANCE)
825
+ return null;
826
+ return exports.LIB_INSTANCE.getProfileId();
827
+ }
828
+ /**
829
+ * Gets the current session ID for the visitor.
830
+ * Session IDs are generated server-side based on IP and user agent.
831
+ *
832
+ * This ID can be used for revenue attribution with payment providers like Paddle.
833
+ *
834
+ * @returns A promise that resolves to the session ID string, or null on error.
835
+ *
836
+ * @example
837
+ * ```typescript
838
+ * const sessionId = await getSessionId()
839
+ *
840
+ * // Pass to Paddle Checkout for revenue attribution
841
+ * Paddle.Checkout.open({
842
+ * items: [{ priceId: 'pri_01234567890', quantity: 1 }],
843
+ * customData: {
844
+ * swetrix_profile_id: await getProfileId(),
845
+ * swetrix_session_id: sessionId
846
+ * }
847
+ * })
848
+ * ```
849
+ */
850
+ async function getSessionId() {
851
+ if (!exports.LIB_INSTANCE)
852
+ return null;
853
+ return exports.LIB_INSTANCE.getSessionId();
854
+ }
483
855
 
856
+ exports.clearExperimentsCache = clearExperimentsCache;
857
+ exports.clearFeatureFlagsCache = clearFeatureFlagsCache;
858
+ exports.getExperiment = getExperiment;
859
+ exports.getExperiments = getExperiments;
860
+ exports.getFeatureFlag = getFeatureFlag;
861
+ exports.getFeatureFlags = getFeatureFlags;
862
+ exports.getProfileId = getProfileId;
863
+ exports.getSessionId = getSessionId;
484
864
  exports.init = init;
485
865
  exports.pageview = pageview;
486
866
  exports.track = track;