swetrix 4.1.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,26 +1,257 @@
1
1
  <picture>
2
- <source media="(prefers-color-scheme: dark)" srcset="https://swetrix.com/assets/logo_white.png">
3
- <img alt="" src="https://swetrix.com/assets/logo_blue.png" height="80">
2
+ <source media="(prefers-color-scheme: dark)" srcset="https://swetrix.com/assets/logo_white.png?v=2">
3
+ <img alt="Swetrix" src="https://swetrix.com/assets/logo_blue.png?v=2" height="80">
4
4
  </picture>
5
5
  <br /><br />
6
6
 
7
- [![JSDelivr hits](https://data.jsdelivr.com/v1/package/gh/Swetrix/swetrix-js/badge?style=rounded)](https://data.jsdelivr.com/v1/package/gh/Swetrix/swetrix-js/stats)
8
- [![Package size](https://img.shields.io/bundlephobia/minzip/swetrix)](https://bundlephobia.com/api/size?package=swetrix)
7
+ [![NPM](https://img.shields.io/npm/v/swetrix)](https://www.npmjs.com/package/swetrix)
8
+ [![Package size](https://img.shields.io/bundlephobia/minzip/swetrix)](https://bundlephobia.com/package/swetrix)
9
+ [![JSDelivr hits](https://data.jsdelivr.com/v1/package/npm/swetrix/badge?style=rounded)](https://www.jsdelivr.com/package/npm/swetrix)
9
10
  [![Contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/swetrix/swetrix-js/issues)
10
11
 
11
12
  # Swetrix Tracking Script
12
13
 
13
- This repository contains the analytics script which is used at https://swetrix.com \
14
- You can find the detailed documentation and use cases at our [docs page](https://docs.swetrix.com/).
14
+ Privacy-first, lightweight analytics tracking library for [Swetrix](https://swetrix.com). Tracks page views, custom events, errors, feature flags, and A/B experiments — all without cookies or invading user privacy.
15
15
 
16
- Feel free to contribute to the source code by opening a pull requests. \
17
- For any questions, you can open an issue ticket, refer to our [FAQs](https://swetrix.com/#faq) page or reach us at contact@swetrix.com
16
+ ## Installation
18
17
 
19
- The latest live versions of the script are located at [jsDelivr](https://swetrix.org/swetrix.js) and [NPM](https://www.npmjs.com/package/swetrix).
18
+ ### npm / yarn / pnpm
20
19
 
21
- # Selfhosted API
22
- If you are selfhosting the [Swetrix-API](https://github.com/Swetrix/swetrix-api), be sure to point the `apiUrl` parameter to: `https://yourapiinstance.com/log`
20
+ ```bash
21
+ npm install swetrix
22
+ ```
23
23
 
24
- # Donate
25
- You can support the project by donating us at https://ko-fi.com/andriir \
26
- We can only run our services by once again asking for your financial support!
24
+ ### CDN
25
+
26
+ ```html
27
+ <script src="https://swetrix.org/swetrix.js" defer></script>
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ### ES Modules
33
+
34
+ ```javascript
35
+ import { init, trackViews, trackErrors } from 'swetrix'
36
+
37
+ init('YOUR_PROJECT_ID')
38
+ trackViews()
39
+ trackErrors()
40
+ ```
41
+
42
+ ### CDN / Script Tag
43
+
44
+ ```html
45
+ <script src="https://swetrix.org/swetrix.js" defer></script>
46
+ <script>
47
+ document.addEventListener('DOMContentLoaded', () => {
48
+ swetrix.init('YOUR_PROJECT_ID')
49
+ swetrix.trackViews()
50
+ swetrix.trackErrors()
51
+ })
52
+ </script>
53
+ ```
54
+
55
+ ## API
56
+
57
+ ### `init(projectId, options?)`
58
+
59
+ Initialise the library. Must be called before any other method.
60
+
61
+ ```javascript
62
+ init('YOUR_PROJECT_ID', {
63
+ apiURL: 'https://api.swetrix.com/log',
64
+ devMode: false,
65
+ disabled: false,
66
+ respectDNT: false,
67
+ profileId: 'user-123',
68
+ preloadSessionReplay: false,
69
+ })
70
+ ```
71
+
72
+ | Option | Description | Default |
73
+ |---|---|---|
74
+ | `apiURL` | API endpoint. Change this if you're self-hosting. | `'https://api.swetrix.com/log'` |
75
+ | `devMode` | When `true`, localhost events are sent to the server. | `false` |
76
+ | `disabled` | When `true`, no data is sent. Useful for dev environments. | `false` |
77
+ | `respectDNT` | When `true`, disables tracking for users with Do Not Track enabled. | `false` |
78
+ | `profileId` | Profile ID for long-term user tracking (MAU/DAU). | `undefined` |
79
+ | `preloadSessionReplay` | Preload the session replay recorder after `init()`. Recording only starts after `startSessionReplay()`. | `undefined` |
80
+
81
+ ### `trackViews(options?)`
82
+
83
+ Automatically tracks page views, including navigation changes in SPAs. Returns a `Promise<{ stop() }>`.
84
+
85
+ ```javascript
86
+ const { stop } = await trackViews({
87
+ hash: false,
88
+ search: false,
89
+ unique: false,
90
+ heartbeatOnBackground: false,
91
+ callback: undefined,
92
+ })
93
+
94
+ // Stop tracking when needed
95
+ stop()
96
+ ```
97
+
98
+ | Option | Description | Default |
99
+ |---|---|---|
100
+ | `hash` | Track hash-based routing (e.g. `/#/path`). | `false` |
101
+ | `search` | Track search/query-based routing (e.g. `/path?query`). | `false` |
102
+ | `unique` | Only count unique page views per session. | `false` |
103
+ | `heartbeatOnBackground` | Send heartbeat when the tab is not active. | `false` |
104
+ | `callback` | A function to edit or prevent pageview payloads. Return `false` to block, `true` to send as-is, or return a modified payload object. | `undefined` |
105
+
106
+ ### `track(event)`
107
+
108
+ Track custom events (e.g. button clicks, sign-ups).
109
+
110
+ ```javascript
111
+ track({
112
+ ev: 'signup',
113
+ unique: true,
114
+ meta: { plan: 'pro', source: 'landing' },
115
+ profileId: 'user-123',
116
+ })
117
+ ```
118
+
119
+ | Option | Description | Default |
120
+ |---|---|---|
121
+ | `ev` | Event name (max 256 chars). | **required** |
122
+ | `unique` | Only count once per session. | `false` |
123
+ | `meta` | Key-value metadata (max 20 keys, 1000 chars total). | `{}` |
124
+ | `profileId` | Optional profile ID. Overrides the global `profileId` for this event. | `undefined` |
125
+
126
+ ### `trackErrors(options?)`
127
+
128
+ Automatically captures client-side errors. Returns `{ stop() }`.
129
+
130
+ ```javascript
131
+ const { stop } = trackErrors({
132
+ sampleRate: 1,
133
+ callback: undefined,
134
+ })
135
+ ```
136
+
137
+ | Option | Description | Default |
138
+ |---|---|---|
139
+ | `sampleRate` | Fraction of errors to send (`0` to `1`). | `1` |
140
+ | `callback` | Edit or prevent error payloads. Return `false` to block. | `undefined` |
141
+
142
+ ### `trackError(payload)`
143
+
144
+ Manually report an error.
145
+
146
+ ```javascript
147
+ trackError({
148
+ name: 'PaymentError',
149
+ message: 'Card declined',
150
+ meta: { gateway: 'stripe' },
151
+ })
152
+ ```
153
+
154
+ ### `pageview(options)`
155
+
156
+ Manually track a single page view (useful for custom routing).
157
+
158
+ ```javascript
159
+ pageview({
160
+ payload: { pg: '/checkout', lc: 'en-US' },
161
+ unique: true,
162
+ })
163
+ ```
164
+
165
+ ### Feature Flags
166
+
167
+ ```javascript
168
+ // Get all flags. Results are cached for 5 minutes.
169
+ const flags = await getFeatureFlags({ profileId: 'user-123' })
170
+
171
+ // Force a fresh fetch
172
+ const freshFlags = await getFeatureFlags({ profileId: 'user-123' }, true)
173
+
174
+ // Get a single flag. The third argument is an optional fallback value.
175
+ const enabled = await getFeatureFlag('dark-mode', { profileId: 'user-123' })
176
+ const enabledWithFallback = await getFeatureFlag('dark-mode', { profileId: 'user-123' }, false)
177
+
178
+ // Clear the shared feature flag / experiment cache
179
+ clearFeatureFlagsCache()
180
+ ```
181
+
182
+ ### A/B Experiments
183
+
184
+ ```javascript
185
+ // Get all running experiment assignments. Results are cached for 5 minutes.
186
+ const experiments = await getExperiments({ profileId: 'user-123' })
187
+
188
+ // Force a fresh fetch
189
+ const freshExperiments = await getExperiments({ profileId: 'user-123' }, true)
190
+
191
+ // Get a specific experiment variant. The third argument is an optional fallback variant.
192
+ const variant = await getExperiment('checkout-redesign-experiment-id', { profileId: 'user-123' })
193
+ const variantWithFallback = await getExperiment('checkout-redesign-experiment-id', { profileId: 'user-123' }, 'control')
194
+
195
+ // Clear the shared feature flag / experiment cache
196
+ clearExperimentsCache()
197
+ ```
198
+
199
+ ### `startSessionReplay(options?)`
200
+
201
+ Start recording a session replay. Session replays use `total` privacy by default, which masks text and inputs and blocks media/canvas capture unless you explicitly choose another mode.
202
+
203
+ If you use the npm package, rrweb is dynamically imported from your installed dependencies only when the recorder is preloaded or started. If you use the CDN/script-tag build, the standalone replay recorder is loaded with an async script tag.
204
+
205
+ ```javascript
206
+ const replay = await startSessionReplay({
207
+ privacy: 'total',
208
+ sampleRate: 0.25,
209
+ maxDurationMs: 10 * 60 * 1000,
210
+ idleTimeoutMs: 2 * 60 * 1000,
211
+ })
212
+
213
+ // Stop or flush manually when needed
214
+ await replay.flush()
215
+ await replay.stop()
216
+ ```
217
+
218
+ | Option | Description | Default |
219
+ |---|---|---|
220
+ | `privacy` | Privacy mode: `total`, `normal`, or `none`. | `'total'` |
221
+ | `sampleRate` | Fraction of sessions to record (`0` to `1`). | `1` |
222
+ | `maxDurationMs` | Stop recording after this duration. | `undefined` |
223
+ | `idleTimeoutMs` | Stop recording after this much visitor inactivity. | `undefined` |
224
+ | `flushIntervalMs` | Upload buffered replay events at this interval. | `5000` |
225
+ | `maxEventsPerChunk` | Upload once this many events are buffered. | `100` |
226
+ | `rrweb` | Additional rrweb record options. | `undefined` |
227
+
228
+ ### Session & Profile IDs
229
+
230
+ ```javascript
231
+ const profileId = await getProfileId()
232
+ const sessionId = await getSessionId()
233
+ ```
234
+
235
+ These are useful for revenue attribution with payment providers like Paddle.
236
+
237
+ ## Self-Hosting
238
+
239
+ If you're running a self-hosted [Swetrix API](https://github.com/Swetrix/swetrix-api) instance, point the `apiURL` to your server:
240
+
241
+ ```javascript
242
+ init('YOUR_PROJECT_ID', {
243
+ apiURL: 'https://your-api.example.com/log',
244
+ })
245
+ ```
246
+
247
+ ## Documentation
248
+
249
+ Full reference and guides are available at [docs.swetrix.com](https://docs.swetrix.com).
250
+
251
+ ## Contributing
252
+
253
+ Contributions are welcome — feel free to [open an issue](https://github.com/swetrix/swetrix-js/issues) or submit a pull request.
254
+
255
+ ## License
256
+
257
+ MIT
@@ -1,3 +1,22 @@
1
+ type RrwebEvent = Record<string, unknown>;
2
+ type RrwebEmit = (event: RrwebEvent) => void;
3
+ interface RrwebRecordOptions {
4
+ emit?: RrwebEmit;
5
+ [key: string]: unknown;
6
+ }
7
+ interface RrwebGlobal {
8
+ record?: (options: RrwebRecordOptions) => (() => void) | undefined;
9
+ Replayer?: unknown;
10
+ }
11
+ type SessionReplayPreloadOption = boolean | {
12
+ rrwebUrl?: string;
13
+ };
14
+ declare global {
15
+ interface Window {
16
+ rrweb?: RrwebGlobal;
17
+ __SWETRIX_RRWEB_LOADING__?: Promise<void>;
18
+ }
19
+ }
1
20
  export interface LibOptions {
2
21
  /**
3
22
  * When set to `true`, localhost events will be sent to server.
@@ -19,6 +38,10 @@ export interface LibOptions {
19
38
  * If set, it will be used for all pageviews and events unless overridden per-call.
20
39
  */
21
40
  profileId?: string;
41
+ /**
42
+ * Preload session replay recorder code. Recording only starts after calling startSessionReplay().
43
+ */
44
+ preloadSessionReplay?: SessionReplayPreloadOption;
22
45
  }
23
46
  export interface TrackEventOptions {
24
47
  /** The custom event name. */
@@ -42,6 +65,12 @@ export interface IPageViewPayload {
42
65
  te?: string;
43
66
  co?: string;
44
67
  pg?: string | null;
68
+ /**
69
+ * Raw URL query string of the landing page (without the leading `?`).
70
+ * Used server-side to recover the traffic source from ad/social click
71
+ * IDs (gclid, fbclid, etc.) when the browser stripped the referrer.
72
+ */
73
+ qs?: string;
45
74
  /** Pageview-related metadata object with string values. */
46
75
  meta?: {
47
76
  [key: string]: string | number | boolean | null | undefined;
@@ -111,6 +140,21 @@ export interface ErrorActions {
111
140
  /** Stops the tracking of errors. */
112
141
  stop: () => void;
113
142
  }
143
+ declare const SESSION_REPLAY_PRIVACY_VALUES: readonly ["total", "normal", "none"];
144
+ export type SessionReplayPrivacy = (typeof SESSION_REPLAY_PRIVACY_VALUES)[number];
145
+ export interface SessionReplayOptions {
146
+ privacy?: SessionReplayPrivacy;
147
+ rrweb?: RrwebRecordOptions;
148
+ flushIntervalMs?: number;
149
+ maxEventsPerChunk?: number;
150
+ sampleRate?: number;
151
+ maxDurationMs?: number;
152
+ idleTimeoutMs?: number;
153
+ }
154
+ export interface SessionReplayActions {
155
+ stop: () => Promise<void>;
156
+ flush: () => Promise<void>;
157
+ }
114
158
  export interface PageData {
115
159
  /** Current URL path. */
116
160
  path: string;
@@ -163,6 +207,7 @@ export interface PageViewsOptions {
163
207
  export declare const defaultActions: {
164
208
  stop(): void;
165
209
  };
210
+ export declare const defaultSessionReplayActions: SessionReplayActions;
166
211
  export declare class Lib {
167
212
  private projectID;
168
213
  private options?;
@@ -173,6 +218,9 @@ export declare class Lib {
173
218
  private activePage;
174
219
  private errorListenerExists;
175
220
  private cachedData;
221
+ private rrwebLoader;
222
+ private sessionReplayActions;
223
+ private sessionReplayInitPromise;
176
224
  constructor(projectID: string, options?: LibOptions | undefined);
177
225
  captureError(event: ErrorEvent): void;
178
226
  trackErrors(options?: ErrorOptions): ErrorActions;
@@ -181,10 +229,10 @@ export declare class Lib {
181
229
  trackPageViews(options?: PageViewsOptions): PageActions;
182
230
  getPerformanceStats(): IPerfPayload | {};
183
231
  /**
184
- * Fetches all feature flags and experiments for the project.
185
- * Results are cached for 5 minutes by default.
232
+ * Fetches all feature flags for the project.
233
+ * Results are cached for 5 minutes by default and share a cache with experiments.
186
234
  *
187
- * @param options - Options for evaluating feature flags.
235
+ * @param options - Options for evaluating feature flags (`profileId` only).
188
236
  * @param forceRefresh - If true, bypasses the cache and fetches fresh data.
189
237
  * @returns A promise that resolves to a record of flag keys to boolean values.
190
238
  */
@@ -197,8 +245,8 @@ export declare class Lib {
197
245
  * Gets the value of a single feature flag.
198
246
  *
199
247
  * @param key - The feature flag key.
200
- * @param options - Options for evaluating the feature flag.
201
- * @param defaultValue - Default value to return if the flag is not found. Defaults to false.
248
+ * @param options - Options for evaluating the feature flag (`profileId` only).
249
+ * @param defaultValue - Optional default value to return if the flag is not found. Defaults to false.
202
250
  * @returns A promise that resolves to the boolean value of the flag.
203
251
  */
204
252
  getFeatureFlag(key: string, options?: FeatureFlagsOptions, defaultValue?: boolean): Promise<boolean>;
@@ -207,16 +255,16 @@ export declare class Lib {
207
255
  */
208
256
  clearFeatureFlagsCache(): void;
209
257
  /**
210
- * Fetches all A/B test experiments for the project.
258
+ * Fetches variant assignments for running A/B test experiments returned by feature flag evaluation.
211
259
  * Results are cached for 5 minutes by default (shared cache with feature flags).
212
260
  *
213
- * @param options - Options for evaluating experiments.
261
+ * @param options - Options for evaluating experiments (`profileId` only).
214
262
  * @param forceRefresh - If true, bypasses the cache and fetches fresh data.
215
263
  * @returns A promise that resolves to a record of experiment IDs to variant keys.
216
264
  *
217
265
  * @example
218
266
  * ```typescript
219
- * const experiments = await getExperiments()
267
+ * const experiments = await getExperiments({ profileId: 'user-123' })
220
268
  * // experiments = { 'exp-123': 'variant-a', 'exp-456': 'control' }
221
269
  * ```
222
270
  */
@@ -225,13 +273,16 @@ export declare class Lib {
225
273
  * Gets the variant key for a specific A/B test experiment.
226
274
  *
227
275
  * @param experimentId - The experiment ID.
228
- * @param options - Options for evaluating the experiment.
229
- * @param defaultVariant - Default variant key to return if the experiment is not found. Defaults to null.
276
+ * @param options - Options for evaluating the experiment (`profileId` only).
277
+ * @param defaultVariant - Optional default variant key to return if the experiment is not found. Defaults to null.
230
278
  * @returns A promise that resolves to the variant key assigned to this user, or defaultVariant if not found.
231
279
  *
232
280
  * @example
233
281
  * ```typescript
234
- * const variant = await getExperiment('checkout-redesign')
282
+ * const variant = await getExperiment('checkout-redesign', { profileId: 'user-123' })
283
+ *
284
+ * // Optional fallback variant:
285
+ * const variantWithFallback = await getExperiment('checkout-redesign', undefined, 'control')
235
286
  *
236
287
  * if (variant === 'new-checkout') {
237
288
  * // Show new checkout flow
@@ -292,6 +343,10 @@ export declare class Lib {
292
343
  * ```
293
344
  */
294
345
  getSessionId(): Promise<string | null>;
346
+ startSessionReplay(options?: SessionReplayOptions): Promise<SessionReplayActions>;
347
+ private initialiseSessionReplay;
348
+ private shouldSampleSessionReplay;
349
+ private getSessionReplayPrivacy;
295
350
  /**
296
351
  * Gets the API base URL (without /log suffix).
297
352
  */
@@ -301,6 +356,19 @@ export declare class Lib {
301
356
  private trackPage;
302
357
  submitPageView(payload: Partial<IPageViewPayload>, unique: boolean, perf: IPerfPayload | {}, evokeCallback?: boolean): void;
303
358
  private canTrack;
359
+ private getSessionReplayUrl;
360
+ private getSessionReplayPreloadOption;
361
+ private getDefaultSessionReplayUrl;
362
+ private getTrackerScript;
363
+ private preloadSessionReplay;
364
+ private loadSessionReplayRecorder;
365
+ private loadSessionReplayPackage;
366
+ private loadSessionReplayScript;
367
+ private getSessionReplayRecordOptions;
368
+ private mergeSelectors;
369
+ private createReplayId;
370
+ private sendSessionReplayStart;
371
+ private sendSessionReplayChunk;
304
372
  private sendRequest;
305
373
  }
306
374
  export {};