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