@faststats/web 0.0.2 → 0.0.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @faststats/web
2
2
 
3
+ ## 0.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - bb3dcd1: ESM only for now
8
+
3
9
  ## 0.0.2
4
10
 
5
11
  ### Patch Changes
package/dist/module.d.ts CHANGED
@@ -429,7 +429,6 @@ interface IdentifyOptions {
429
429
  interface WebAnalyticsOptions {
430
430
  siteKey: string;
431
431
  endpoint?: string;
432
- baseUrl?: string;
433
432
  debug?: boolean;
434
433
  autoTrack?: boolean;
435
434
  trackHash?: boolean;
@@ -463,7 +462,6 @@ declare class WebAnalytics {
463
462
  private readonly options;
464
463
  private readonly endpoint;
465
464
  private readonly debug;
466
- private readonly baseUrl;
467
465
  private started;
468
466
  private pageKey;
469
467
  private navTimer;
@@ -481,7 +479,6 @@ declare class WebAnalytics {
481
479
  private log;
482
480
  private init;
483
481
  start(): void;
484
- private load;
485
482
  pageview(extra?: Dict): void;
486
483
  track(name: string, extra?: Dict): void;
487
484
  identify(externalId: string, email: string, options?: IdentifyOptions): void;
package/dist/module.js CHANGED
@@ -1,408 +1,3 @@
1
- //#region src/utils/identifiers.ts
2
- const SESSION_TIMEOUT = 1800 * 1e3;
3
- function getAnonymousId(cookieless) {
4
- if (cookieless) return "";
5
- return getOrCreateAnonymousId();
6
- }
7
- function getOrCreateAnonymousId() {
8
- if (typeof localStorage === "undefined") return "";
9
- const existingId = localStorage.getItem("faststats_anon_id");
10
- if (existingId) return existingId;
11
- const newId = crypto.randomUUID();
12
- localStorage.setItem("faststats_anon_id", newId);
13
- return newId;
14
- }
15
- function resetAnonymousId(cookieless) {
16
- if (cookieless || typeof localStorage === "undefined") return "";
17
- localStorage.removeItem("faststats_anon_id");
18
- return getOrCreateAnonymousId();
19
- }
20
- function getOrCreateSessionId() {
21
- if (typeof sessionStorage === "undefined") return "";
22
- const existingId = sessionStorage.getItem("session_id");
23
- const sessionTimestamp = sessionStorage.getItem("session_timestamp");
24
- if (existingId && sessionTimestamp) {
25
- if (Date.now() - Number.parseInt(sessionTimestamp, 10) < SESSION_TIMEOUT) {
26
- sessionStorage.setItem("session_timestamp", Date.now().toString());
27
- return existingId;
28
- }
29
- sessionStorage.removeItem("session_id");
30
- sessionStorage.removeItem("session_timestamp");
31
- sessionStorage.removeItem("session_start");
32
- }
33
- const now = Date.now().toString();
34
- const newId = crypto.randomUUID();
35
- sessionStorage.setItem("session_id", newId);
36
- sessionStorage.setItem("session_timestamp", now);
37
- sessionStorage.setItem("session_start", now);
38
- return newId;
39
- }
40
- function resetSessionId() {
41
- if (typeof sessionStorage === "undefined") return "";
42
- sessionStorage.removeItem("session_id");
43
- sessionStorage.removeItem("session_timestamp");
44
- sessionStorage.removeItem("session_start");
45
- return getOrCreateSessionId();
46
- }
47
- function refreshSessionTimestamp() {
48
- if (typeof sessionStorage === "undefined") return;
49
- if (sessionStorage.getItem("session_id")) sessionStorage.setItem("session_timestamp", Date.now().toString());
50
- }
51
- function getSessionStart() {
52
- if (typeof sessionStorage === "undefined") return Date.now();
53
- const start = sessionStorage.getItem("session_start");
54
- if (start) return Number.parseInt(start, 10);
55
- const ts = sessionStorage.getItem("session_timestamp");
56
- return ts ? Number.parseInt(ts, 10) : Date.now();
57
- }
58
-
59
- //#endregion
60
- //#region src/utils/types.ts
61
- function normalizeSamplingPercentage(value) {
62
- if (typeof value !== "number" || !Number.isFinite(value)) return 100;
63
- return Math.max(0, Math.min(100, value));
64
- }
65
-
66
- //#endregion
67
- //#region src/analytics.ts
68
- function trackEvent(eventName, properties) {
69
- if (typeof window === "undefined" || isTrackingDisabled()) return;
70
- window.__FA_webAnalyticsInstance?.track(eventName, properties ?? {});
71
- }
72
- function isTrackingDisabled() {
73
- if (typeof localStorage === "undefined") return false;
74
- const value = localStorage.getItem("disable-faststats");
75
- return value === "true" || value === "1";
76
- }
77
- async function sendData(options) {
78
- const { url, data, contentType = "application/json", headers = {}, debug = false, debugPrefix = "[Analytics]" } = options;
79
- const blob = data instanceof Blob ? data : new Blob([data], { type: contentType });
80
- if (navigator.sendBeacon?.(url, blob)) {
81
- if (debug) console.log(`${debugPrefix} ✓ Sent via beacon`);
82
- return true;
83
- }
84
- try {
85
- const response = await fetch(url, {
86
- method: "POST",
87
- body: data,
88
- headers: {
89
- "Content-Type": contentType,
90
- ...headers
91
- },
92
- keepalive: true
93
- });
94
- const success = response.ok;
95
- if (debug) if (success) console.log(`${debugPrefix} ✓ Sent via fetch`);
96
- else console.warn(`${debugPrefix} ✗ Failed: ${response.status}`);
97
- return success;
98
- } catch {
99
- if (debug) console.warn(`${debugPrefix} ✗ Failed to send`);
100
- return false;
101
- }
102
- }
103
- function getLinkEl(el) {
104
- while (el && !(el.tagName === "A" && el.href)) el = el.parentNode;
105
- return el;
106
- }
107
- function getUTM() {
108
- const params = {};
109
- if (!location.search) return params;
110
- const sp = new URLSearchParams(location.search);
111
- for (const k of [
112
- "utm_source",
113
- "utm_medium",
114
- "utm_campaign",
115
- "utm_term",
116
- "utm_content"
117
- ]) {
118
- const v = sp.get(k);
119
- if (v) params[k] = v;
120
- }
121
- return params;
122
- }
123
- var WebAnalytics = class {
124
- endpoint;
125
- debug;
126
- baseUrl;
127
- started = false;
128
- pageKey = "";
129
- navTimer = null;
130
- heartbeatTimer = null;
131
- scrollDepth = 0;
132
- pageEntryTime = 0;
133
- pagePath = "";
134
- pageUrl = "";
135
- pageHash = "";
136
- hasLeftCurrentPage = false;
137
- scrollHandler = null;
138
- consentMode;
139
- cookielessWhilePending;
140
- constructor(options) {
141
- this.options = options;
142
- this.endpoint = options.endpoint ?? "https://metrics.faststats.dev/v1/web";
143
- this.debug = options.debug ?? false;
144
- this.consentMode = options.consent?.mode ?? "granted";
145
- this.cookielessWhilePending = options.consent?.cookielessWhilePending ?? true;
146
- const script = typeof document !== "undefined" ? document.currentScript : null;
147
- const scriptBase = script?.src ? script.src.substring(0, script.src.lastIndexOf("/")) : "";
148
- this.baseUrl = (options.baseUrl ?? scriptBase) || (typeof window !== "undefined" ? window.location.origin : "");
149
- if (options.autoTrack ?? true) this.init();
150
- }
151
- log(msg) {
152
- if (this.debug) console.log(`[Analytics] ${msg}`);
153
- }
154
- init() {
155
- if (typeof window === "undefined") return;
156
- if (isTrackingDisabled()) {
157
- this.log("disabled");
158
- return;
159
- }
160
- if ("requestIdleCallback" in window) window.requestIdleCallback(() => this.start());
161
- else setTimeout(() => this.start(), 1);
162
- }
163
- start() {
164
- if (this.started || typeof window === "undefined") return;
165
- if (window.__FA_webAnalyticsInstance && window.__FA_webAnalyticsInstance !== this) {
166
- this.log("already started by another instance");
167
- return;
168
- }
169
- if (isTrackingDisabled()) {
170
- this.log("disabled");
171
- return;
172
- }
173
- this.started = true;
174
- window.__FA_webAnalyticsInstance = this;
175
- window.__FA_getAnonymousId = () => getAnonymousId(this.isCookielessMode());
176
- window.__FA_getSessionId = getOrCreateSessionId;
177
- window.__FA_sendData = sendData;
178
- window.__FA_identify = (externalId, email, options) => this.identify(externalId, email, options);
179
- window.__FA_logout = (resetAnonymousIdentity) => this.logout(resetAnonymousIdentity);
180
- window.__FA_setConsentMode = (mode) => this.setConsentMode(mode);
181
- window.__FA_optIn = () => this.optIn();
182
- window.__FA_optOut = () => this.optOut();
183
- window.__FA_trackEvent = (eventName, props) => this.track(eventName, props ?? {});
184
- window.__FA_isTrackingDisabled = isTrackingDisabled;
185
- const opts = this.options;
186
- const hasWebVitalsSampling = opts.webVitals?.sampling?.percentage !== void 0;
187
- const hasReplaySampling = opts.replayOptions?.samplingPercentage !== void 0 || opts.sessionReplays?.sampling?.percentage !== void 0;
188
- if (opts.errorTracking?.enabled ?? opts.trackErrors) this.load("error", "__FA_ErrorTracker", {
189
- siteKey: opts.siteKey,
190
- endpoint: this.endpoint,
191
- debug: this.debug,
192
- getCommonData: () => ({
193
- url: location.href,
194
- page: location.pathname,
195
- referrer: document.referrer || null
196
- })
197
- });
198
- if (opts.webVitals?.enabled ?? opts.trackWebVitals ?? hasWebVitalsSampling) this.load("web-vitals", "__FA_WebVitalsTracker", {
199
- siteKey: opts.siteKey,
200
- endpoint: this.endpoint,
201
- debug: this.debug,
202
- samplingPercentage: normalizeSamplingPercentage(opts.webVitals?.sampling?.percentage)
203
- });
204
- if (opts.sessionReplays?.enabled ?? opts.trackReplay ?? hasReplaySampling) {
205
- const replayOpts = opts.replayOptions ?? {};
206
- this.load("replay", "__FA_ReplayTracker", {
207
- siteKey: opts.siteKey,
208
- endpoint: this.endpoint,
209
- debug: this.debug,
210
- ...replayOpts,
211
- samplingPercentage: normalizeSamplingPercentage(replayOpts.samplingPercentage ?? opts.sessionReplays?.sampling?.percentage)
212
- });
213
- }
214
- this.enterPage();
215
- this.pageview({ trigger: "load" });
216
- this.links();
217
- this.trackScroll();
218
- this.startHeartbeat();
219
- document.addEventListener("visibilitychange", () => {
220
- if (document.visibilityState === "hidden") this.leavePage();
221
- else this.startHeartbeat();
222
- });
223
- window.addEventListener("pagehide", () => this.leavePage());
224
- window.addEventListener("popstate", () => this.navigate());
225
- if (opts.trackHash) window.addEventListener("hashchange", () => this.navigate());
226
- this.patch();
227
- }
228
- load(script, key, trackerOpts) {
229
- const el = document.createElement("script");
230
- el.src = `${this.baseUrl}/${script}.js`;
231
- el.async = true;
232
- el.onload = () => {
233
- const Ctor = window[key];
234
- if (Ctor) {
235
- new Ctor(trackerOpts).start();
236
- this.log(`${script} loaded`);
237
- }
238
- };
239
- el.onerror = () => {
240
- if (this.debug) console.warn(`[Analytics] ${script} failed`);
241
- };
242
- document.head.appendChild(el);
243
- }
244
- pageview(extra = {}) {
245
- const key = `${location.pathname}|${this.options.trackHash ?? false ? location.hash : ""}`;
246
- if (key === this.pageKey) return;
247
- this.pageKey = key;
248
- this.send("pageview", extra);
249
- }
250
- track(name, extra = {}) {
251
- this.send(name, extra);
252
- }
253
- identify(externalId, email, options = {}) {
254
- if (isTrackingDisabled()) return;
255
- if (this.isCookielessMode()) return;
256
- const trimmedExternalId = externalId.trim();
257
- const trimmedEmail = email.trim();
258
- if (!trimmedExternalId || !trimmedEmail) return;
259
- sendData({
260
- url: this.endpoint.replace(/\/v1\/web$/, "/v1/identify"),
261
- data: JSON.stringify({
262
- token: this.options.siteKey,
263
- identifier: getAnonymousId(false),
264
- externalId: trimmedExternalId,
265
- email: trimmedEmail,
266
- name: options.name?.trim() || void 0,
267
- phone: options.phone?.trim() || void 0,
268
- avatarUrl: options.avatarUrl?.trim() || void 0,
269
- traits: options.traits ?? {}
270
- }),
271
- contentType: "text/plain",
272
- debug: this.debug,
273
- debugPrefix: "[Analytics] identify"
274
- });
275
- }
276
- logout(resetAnonymousIdentity = true) {
277
- if (isTrackingDisabled()) return;
278
- if (resetAnonymousIdentity) resetAnonymousId(this.isCookielessMode());
279
- resetSessionId();
280
- }
281
- setConsentMode(mode) {
282
- this.consentMode = mode;
283
- }
284
- optIn() {
285
- this.setConsentMode("granted");
286
- }
287
- optOut() {
288
- this.setConsentMode("denied");
289
- }
290
- getConsentMode() {
291
- return this.consentMode;
292
- }
293
- isCookielessMode() {
294
- if (this.options.cookieless) return true;
295
- if (this.consentMode === "denied") return true;
296
- if (this.consentMode === "pending") return this.cookielessWhilePending;
297
- return false;
298
- }
299
- send(event, extra = {}) {
300
- const identifier = getAnonymousId(this.isCookielessMode());
301
- const payload = JSON.stringify({
302
- token: this.options.siteKey,
303
- ...identifier ? { userId: identifier } : {},
304
- sessionId: getOrCreateSessionId(),
305
- data: {
306
- event,
307
- page: location.pathname,
308
- referrer: document.referrer || null,
309
- title: document.title || "",
310
- url: location.href,
311
- ...getUTM(),
312
- ...extra
313
- }
314
- });
315
- this.log(event);
316
- sendData({
317
- url: this.endpoint,
318
- data: payload,
319
- contentType: "text/plain",
320
- debug: this.debug,
321
- debugPrefix: `[Analytics] ${event}`
322
- });
323
- }
324
- enterPage() {
325
- this.pageEntryTime = Date.now();
326
- this.pagePath = location.pathname;
327
- this.pageUrl = location.href;
328
- this.pageHash = location.hash;
329
- this.scrollDepth = 0;
330
- this.hasLeftCurrentPage = false;
331
- }
332
- leavePage() {
333
- if (this.hasLeftCurrentPage) return;
334
- this.hasLeftCurrentPage = true;
335
- const now = Date.now();
336
- this.send("page_leave", {
337
- page: this.pagePath,
338
- url: this.pageUrl,
339
- time_on_page: now - this.pageEntryTime,
340
- scroll_depth: this.scrollDepth,
341
- session_duration: now - getSessionStart()
342
- });
343
- }
344
- trackScroll() {
345
- if (this.scrollHandler) window.removeEventListener("scroll", this.scrollHandler);
346
- const update = () => {
347
- const doc = document.documentElement;
348
- const body = document.body;
349
- const viewportH = window.innerHeight;
350
- const docH = Math.max(doc.scrollHeight, body.scrollHeight);
351
- if (docH <= viewportH) {
352
- this.scrollDepth = 100;
353
- return;
354
- }
355
- const depth = Math.min(100, Math.round(((window.scrollY || doc.scrollTop) + viewportH) / docH * 100));
356
- if (depth > this.scrollDepth) this.scrollDepth = depth;
357
- };
358
- this.scrollHandler = update;
359
- update();
360
- window.addEventListener("scroll", update, { passive: true });
361
- }
362
- startHeartbeat() {
363
- if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
364
- this.heartbeatTimer = setInterval(() => {
365
- if (document.visibilityState === "hidden") {
366
- clearInterval(this.heartbeatTimer);
367
- this.heartbeatTimer = null;
368
- return;
369
- }
370
- refreshSessionTimestamp();
371
- }, 300 * 1e3);
372
- }
373
- navigate() {
374
- if (this.navTimer) clearTimeout(this.navTimer);
375
- this.navTimer = setTimeout(() => {
376
- this.navTimer = null;
377
- const pathChanged = location.pathname !== this.pagePath;
378
- const hashChanged = (this.options.trackHash ?? false) && location.hash !== this.pageHash;
379
- if (!pathChanged && !hashChanged) return;
380
- this.leavePage();
381
- this.enterPage();
382
- this.trackScroll();
383
- this.pageview({ trigger: "navigation" });
384
- }, 300);
385
- }
386
- patch() {
387
- const fire = () => this.navigate();
388
- for (const method of ["pushState", "replaceState"]) {
389
- const orig = history[method];
390
- history[method] = function(...args) {
391
- const result = orig.apply(this, args);
392
- fire();
393
- return result;
394
- };
395
- }
396
- }
397
- links() {
398
- const handler = (event) => {
399
- const link = getLinkEl(event.target);
400
- if (link && link.host !== location.host) this.track("outbound_link", { outbound_link: link.href });
401
- };
402
- document.addEventListener("click", handler);
403
- document.addEventListener("auxclick", handler);
404
- }
405
- };
406
-
407
- //#endregion
408
- export { WebAnalytics, trackEvent };
1
+ import{getRecordConsolePlugin as e}from"@rrweb/rrweb-plugin-console-record";import{record as t}from"rrweb";import{onCLS as n,onFCP as r,onINP as i,onLCP as a,onTTFB as o}from"web-vitals/attribution";var s=class{endpoint;siteKey;debug;flushInterval;maxQueueSize;getCommonData;handledErrors=new WeakSet;errorCounts=new Map;flushTimer=null;started=!1;constructor(e){this.siteKey=e.siteKey,this.endpoint=e.endpoint??`https://metrics.faststats.dev/v1/web`,this.debug=e.debug??!1,this.flushInterval=e.flushInterval??5e3,this.maxQueueSize=e.maxQueueSize??50,this.getCommonData=e.getCommonData??(()=>({}))}start(){if(!(this.started||typeof window>`u`)){if(window.__FA_isTrackingDisabled?.()){this.debug&&console.log(`[ErrorTracker] Tracking disabled via localStorage`);return}this.started=!0,window.addEventListener(`error`,this.handleErrorEvent),window.addEventListener(`unhandledrejection`,this.handleRejection),this.flushTimer=setInterval(()=>this.flush(),this.flushInterval),document.addEventListener(`visibilitychange`,()=>{document.visibilityState===`hidden`&&this.flush()}),window.addEventListener(`pagehide`,()=>this.flush()),this.debug&&console.log(`[ErrorTracker] Started listening for errors`)}}stop(){!this.started||typeof window>`u`||(this.started=!1,window.removeEventListener(`error`,this.handleErrorEvent),window.removeEventListener(`unhandledrejection`,this.handleRejection),this.flushTimer&&=(clearInterval(this.flushTimer),null),this.flush(),this.debug&&console.log(`[ErrorTracker] Stopped listening for errors`))}handleErrorEvent=e=>{let t=e.error;if(t instanceof Error){if(this.handledErrors.has(t)){this.debug&&console.log(`[ErrorTracker] Skipping duplicate error:`,t.message);return}this.handledErrors.add(t)}this.queueError({message:e.message||(t?.message??`Unknown error`),filename:e.filename||void 0,lineno:e.lineno||void 0,colno:e.colno||void 0,stack:t?.stack||void 0,type:`error`})};handleRejection=e=>{let t=e.reason;if(t instanceof Error){if(this.handledErrors.has(t)){this.debug&&console.log(`[ErrorTracker] Skipping duplicate rejection:`,t.message);return}this.handledErrors.add(t)}this.queueError({message:t instanceof Error?t.message:typeof t==`string`?t:`Unhandled promise rejection`,stack:t instanceof Error?t.stack:void 0,type:`unhandledrejection`})};isExtensionError(e){return!!(e.filename?.startsWith(`chrome-extension://`)||e.stack&&e.stack.split(`
2
+ `).find(e=>e.trim().startsWith(`at `))?.includes(`chrome-extension://`))}parseStack(e){if(e)return e.split(`
3
+ `).map(e=>e.trim()).filter(e=>e.length>0)}async generateErrorHash(e){let t=[e.type,e.message,e.filename??``,e.lineno??``].join(`:`),n=new TextEncoder().encode(t),r=await crypto.subtle.digest(`SHA-256`,n);return`err_${Array.from(new Uint8Array(r)).map(e=>e.toString(16).padStart(2,`0`)).join(``)}`}async queueError(e){if(this.isExtensionError(e))return;this.debug&&console.log(`[ErrorTracker] Captured error:`,e);let t=await this.generateErrorHash(e),n=this.errorCounts.get(t);if(n)n.count++,this.debug&&console.log(`[ErrorTracker] Incremented count for ${t} to ${n.count}`);else{let n={error:e.type===`unhandledrejection`?`UnhandledRejection`:`Error`,message:e.message,stack:this.parseStack(e.stack)};this.errorCounts.set(t,{entry:n,hash:t,count:1}),this.debug&&console.log(`[ErrorTracker] Queued new error: ${t}`)}this.errorCounts.size>=this.maxQueueSize&&this.flush()}captureError(e){if(this.handledErrors.has(e)){this.debug&&console.log(`[ErrorTracker] Skipping duplicate manual capture:`,e.message);return}this.handledErrors.add(e),this.queueError({message:e.message,stack:e.stack,type:`error`})}flush(){if(this.errorCounts.size===0)return;let e=[];for(let{entry:t,hash:n,count:r}of this.errorCounts.values())e.push({...t,hash:n,count:r});this.errorCounts.clear(),this.debug&&console.log(`[ErrorTracker] Flushing errors:`,e);let t=this.getCommonData(),n=window.__FA_getAnonymousId?.(),r=globalThis.__SOURCEMAPS_BUILD__,i=typeof r?.buildId==`string`&&r.buildId.trim().length>0?r.buildId:void 0,a={token:this.siteKey,...n?{userId:n}:{},sessionId:window.__FA_getSessionId?.(),...i?{buildId:i}:{},data:{url:t.url,page:t.page,referrer:t.referrer,title:typeof document<`u`?document.title:``},errors:e},o=JSON.stringify(a);this.debug&&console.log(`[ErrorTracker] Payload:`,o),window.__FA_sendData?.({url:this.endpoint,data:o,debug:this.debug,debugPrefix:`[ErrorTracker]`})}};typeof window<`u`&&(window.__FA_ErrorTracker=s);function c(e){return typeof e!=`number`||!Number.isFinite(e)?100:Math.max(0,Math.min(100,e))}var l=class{endpoint;siteKey;debug;flushInterval;maxEvents;sampling;slimDOMOptions;maskAllInputs;maskInputOptions;blockClass;blockSelector;maskTextClass;maskTextSelector;checkoutEveryNms;checkoutEveryNth;samplingPercentage;recordConsole;events=[];flushTimer=null;stopRecording=void 0;started=!1;startTime=0;sequenceNumber=0;pendingBatches=[];isFlushing=!1;compressionSupported=!1;sessionSamplingSeed;minReplayLengthMs=3e3;constructor(e){this.siteKey=e.siteKey,this.endpoint=e.endpoint?.replace(/\/v1\/web$/,`/v1/replay`)??`https://metrics.faststats.dev/v1/replay`,this.debug=e.debug??!1,this.samplingPercentage=c(e.samplingPercentage),this.flushInterval=e.flushInterval??1e4,this.maxEvents=e.maxEvents??500,this.sampling=e.sampling??{mousemove:50,mouseInteraction:!0,scroll:150,media:800,input:`last`},this.slimDOMOptions=e.slimDOMOptions??{script:!0,comment:!0,headFavicon:!0,headWhitespace:!0,headMetaDescKeywords:!0,headMetaSocial:!0,headMetaRobots:!0,headMetaHttpEquiv:!0,headMetaAuthorship:!0},this.maskAllInputs=e.maskAllInputs??!0,this.maskInputOptions=e.maskInputOptions??{password:!0,email:!0,tel:!0},this.blockClass=e.blockClass,this.blockSelector=e.blockSelector,this.maskTextClass=e.maskTextClass,this.maskTextSelector=e.maskTextSelector,this.checkoutEveryNms=e.checkoutEveryNms??6e4,this.checkoutEveryNth=e.checkoutEveryNth,this.recordConsole=e.recordConsole??!0,this.sessionSamplingSeed=Math.random()*100,typeof window<`u`&&(this.compressionSupported=`CompressionStream`in window)}start(){if(this.started||typeof window>`u`)return;if(window.__FA_isTrackingDisabled?.()){this.debug&&console.log(`[Replay] Tracking disabled via localStorage`);return}if(this.samplingPercentage<100&&this.sessionSamplingSeed>=this.samplingPercentage)return;this.started=!0,this.startTime=Date.now(),this.debug&&console.log(`[Replay] Recording started`);let n={emit:(e,t)=>this.handleEvent(e,t),sampling:this.sampling,slimDOMOptions:this.slimDOMOptions,maskAllInputs:this.maskAllInputs,checkoutEveryNms:this.checkoutEveryNms};this.maskInputOptions&&(n.maskInputOptions=this.maskInputOptions),this.blockClass&&(n.blockClass=this.blockClass),this.blockSelector&&(n.blockSelector=this.blockSelector),this.maskTextClass&&(n.maskTextClass=this.maskTextClass),this.maskTextSelector&&(n.maskTextSelector=this.maskTextSelector),this.checkoutEveryNth&&(n.checkoutEveryNth=this.checkoutEveryNth),this.recordConsole&&(n.plugins=[e()]),this.stopRecording=t(n),this.flushTimer=setInterval(()=>{this.scheduleFlush()},this.flushInterval),window.addEventListener(`beforeunload`,this.handleUnload),window.addEventListener(`pagehide`,this.handleUnload),document.addEventListener(`visibilitychange`,this.handleVisibilityChange)}stop(){if(!this.started)return;this.started=!1,this.debug&&console.log(`[Replay] Recording stopped`),this.stopRecording?.(),this.stopRecording=void 0,this.flushTimer&&=(clearInterval(this.flushTimer),null),window.removeEventListener(`beforeunload`,this.handleUnload),window.removeEventListener(`pagehide`,this.handleUnload),document.removeEventListener(`visibilitychange`,this.handleVisibilityChange);let e=Date.now()-this.startTime;if(e<this.minReplayLengthMs){this.events=[],this.debug&&console.log(`[Replay] Session too short (${e}ms), discarding events`);return}this.flush()}handleEvent(e,t){this.events.push(e),(t||this.events.length>=this.maxEvents)&&this.scheduleFlush()}scheduleFlush(){this.isFlushing||this.events.length===0||(typeof window<`u`&&`requestIdleCallback`in window?window.requestIdleCallback(()=>this.flush(),{timeout:2e3}):setTimeout(()=>this.flush(),0))}handleUnload=()=>{this.flush()};handleVisibilityChange=()=>{document.visibilityState===`hidden`?(this.flushTimer&&=(clearInterval(this.flushTimer),null),this.flush()):document.visibilityState===`visible`&&this.started&&(this.flushTimer||=setInterval(()=>{this.scheduleFlush()},this.flushInterval))};async flush(){if(this.events.length===0){this.processPendingBatches();return}let e=Date.now()-this.startTime;if(e<this.minReplayLengthMs){this.debug&&console.log(`[Replay] Too short (${e}ms), skipping`),this.processPendingBatches();return}if(this.isFlushing)return;this.isFlushing=!0;let t=this.events;this.events=[];let n=window.__FA_getAnonymousId?.(),r={token:this.siteKey,sessionId:window.__FA_getSessionId?.(),...n?{identifier:n}:{},sequence:this.sequenceNumber++,timestamp:Date.now(),url:window.location.href,events:t};this.debug&&console.log(`[Replay] Sending ${t.length} events (seq: ${r.sequence})`);try{let e,t=!1;if(this.compressionSupported)try{e=await this.compress(JSON.stringify(r)),t=!0}catch{this.debug&&console.warn(`[Replay] Compression failed, using uncompressed`),e=new Blob([JSON.stringify(r)],{type:`application/json`})}else e=new Blob([JSON.stringify(r)],{type:`application/json`});await this.send(e,t)||this.pendingBatches.push({batch:r,isCompressed:t,retries:0})}catch(e){this.debug&&console.warn(`[Replay] Flush error:`,e),this.pendingBatches.push({batch:r,isCompressed:!1,retries:0})}finally{this.isFlushing=!1,this.processPendingBatches()}}async processPendingBatches(){if(this.pendingBatches.length===0)return;let e=this.pendingBatches.shift();if(e){if(e.retries>=3){this.debug&&console.warn(`[Replay] Max retries reached, dropping batch ${e.batch.sequence}`),this.processPendingBatches();return}e.retries++;try{let t;if(e.isCompressed&&this.compressionSupported)try{t=await this.compress(JSON.stringify(e.batch))}catch{t=new Blob([JSON.stringify(e.batch)],{type:`application/json`}),e.isCompressed=!1}else t=new Blob([JSON.stringify(e.batch)],{type:`application/json`});await this.send(t,e.isCompressed)?this.processPendingBatches():(this.pendingBatches.push(e),setTimeout(()=>this.processPendingBatches(),1e3*e.retries))}catch{this.debug&&console.warn(`[Replay] Retry ${e.retries} failed`),this.pendingBatches.push(e),setTimeout(()=>this.processPendingBatches(),1e3*e.retries)}}}async compress(e){if(!this.compressionSupported)throw Error(`Compression not supported`);let t=new TextEncoder().encode(e),n=new CompressionStream(`gzip`),r=n.writable.getWriter();r.write(t),r.close();let i=[],a=n.readable.getReader();for(;;){let{done:e,value:t}=await a.read();if(e)break;t&&i.push(t)}let o=i.reduce((e,t)=>e+t.length,0),s=new Uint8Array(o),c=0;for(let e of i)s.set(e,c),c+=e.length;if(this.debug){let e=(s.length/t.length*100).toFixed(1);console.log(`[Replay] Compressed: ${t.length} → ${s.length} bytes (${e}%)`)}return new Blob([s],{type:`application/octet-stream`})}async send(e,t){let n=t?`${this.endpoint}?encoding=gzip`:this.endpoint,r=(e.size/1024).toFixed(1);return window.__FA_sendData?.({url:n,data:e,contentType:t?`application/octet-stream`:`application/json`,headers:t?{"Content-Encoding":`gzip`}:void 0,debug:this.debug,debugPrefix:`[Replay] ${r}KB`})??Promise.resolve(!1)}getSessionId(){return window.__FA_getSessionId?.()}};typeof window<`u`&&(window.__FA_ReplayTracker=l);function u(e){return e?``:d()}function d(){if(typeof localStorage>`u`)return``;let e=localStorage.getItem(`faststats_anon_id`);if(e)return e;let t=crypto.randomUUID();return localStorage.setItem(`faststats_anon_id`,t),t}function f(e){return e||typeof localStorage>`u`?``:(localStorage.removeItem(`faststats_anon_id`),d())}function p(){if(typeof sessionStorage>`u`)return``;let e=sessionStorage.getItem(`session_id`),t=sessionStorage.getItem(`session_timestamp`);if(e&&t){if(Date.now()-Number.parseInt(t,10)<18e5)return sessionStorage.setItem(`session_timestamp`,Date.now().toString()),e;sessionStorage.removeItem(`session_id`),sessionStorage.removeItem(`session_timestamp`),sessionStorage.removeItem(`session_start`)}let n=Date.now().toString(),r=crypto.randomUUID();return sessionStorage.setItem(`session_id`,r),sessionStorage.setItem(`session_timestamp`,n),sessionStorage.setItem(`session_start`,n),r}function m(){return typeof sessionStorage>`u`?``:(sessionStorage.removeItem(`session_id`),sessionStorage.removeItem(`session_timestamp`),sessionStorage.removeItem(`session_start`),p())}function h(){typeof sessionStorage>`u`||sessionStorage.getItem(`session_id`)&&sessionStorage.setItem(`session_timestamp`,Date.now().toString())}function g(){if(typeof sessionStorage>`u`)return Date.now();let e=sessionStorage.getItem(`session_start`);if(e)return Number.parseInt(e,10);let t=sessionStorage.getItem(`session_timestamp`);return t?Number.parseInt(t,10):Date.now()}var _=class{endpoint;siteKey;debug;samplingPercentage;started=!1;sessionSamplingSeed;metricsMap=new Map;flushed=!1;constructor(e){this.siteKey=e.siteKey,this.endpoint=this.getVitalsEndpoint(e.endpoint??`https://metrics.faststats.dev/v1/web`),this.debug=e.debug??!1,this.samplingPercentage=c(e.samplingPercentage),this.sessionSamplingSeed=Math.random()*100}getVitalsEndpoint(e){let t=new URL(e),n=t.pathname.split(`/`);return n[n.length-1]=`vitals`,t.pathname=n.join(`/`),t.toString()}start(){if(!(this.started||typeof window>`u`)){if(window.__FA_isTrackingDisabled?.()){this.debug&&console.log(`[WebVitals] Tracking disabled`);return}this.started=!0,document.addEventListener(`visibilitychange`,()=>{document.visibilityState===`hidden`&&this.finalizeAndFlush()}),window.addEventListener(`pagehide`,()=>{this.finalizeAndFlush()}),this.debug&&console.log(`[WebVitals] Tracking started`),n(e=>this.captureMetric(e)),i(e=>this.captureMetric(e)),a(e=>this.captureMetric(e)),r(e=>this.captureMetric(e)),o(e=>this.captureMetric(e))}}captureMetric(e){if(this.flushed||this.samplingPercentage<100&&this.sessionSamplingSeed>=this.samplingPercentage)return;let t=e.name,n={id:e.id,rating:e.rating,delta:e.delta,navigationType:e.navigationType,...e.attribution??{}},r=t===`FCP`||t===`TTFB`;this.metricsMap.set(t,{value:e.value,attributes:n,final:r}),this.debug&&console.log(`[WebVitals] ${t} captured: ${e.value}`+(r?` (final)`:``))}finalizeAndFlush(){if(!(this.flushed||this.metricsMap.size===0)){for(let[e,t]of this.metricsMap.entries())t.final||(t.final=!0,this.debug&&console.log(`[WebVitals] ${e} finalized: ${t.value}`));this.flushWithBeacon()}}buildPayload(){if(this.metricsMap.size===0)return null;let e=Array.from(this.metricsMap.entries()).map(([e,t])=>({metric:e,value:t.value,attributes:t.attributes}));return{body:JSON.stringify({sessionId:window.__FA_getSessionId?.(),vitals:e,metadata:{url:window.location.href}}),count:e.length}}flushWithBeacon(){let e=this.buildPayload();if(e){if(this.flushed=!0,this.debug){let t=Array.from(this.metricsMap.keys()).join(`, `);console.log(`[WebVitals] Sending final metrics (${e.count}): ${t}`)}fetch(this.endpoint,{method:`POST`,body:e.body,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${this.siteKey}`},keepalive:!0}).catch(()=>{this.debug&&console.warn(`[WebVitals] Failed to send metrics`)})}}};typeof window<`u`&&(window.__FA_WebVitalsTracker=_);function v(e,t){typeof window>`u`||y()||window.__FA_webAnalyticsInstance?.track(e,t??{})}function y(){if(typeof localStorage>`u`)return!1;let e=localStorage.getItem(`disable-faststats`);return e===`true`||e===`1`}async function b(e){let{url:t,data:n,contentType:r=`application/json`,headers:i={},debug:a=!1,debugPrefix:o=`[Analytics]`}=e,s=n instanceof Blob?n:new Blob([n],{type:r});if(navigator.sendBeacon?.(t,s))return a&&console.log(`${o} ✓ Sent via beacon`),!0;try{let e=await fetch(t,{method:`POST`,body:n,headers:{"Content-Type":r,...i},keepalive:!0}),s=e.ok;return a&&(s?console.log(`${o} ✓ Sent via fetch`):console.warn(`${o} ✗ Failed: ${e.status}`)),s}catch{return a&&console.warn(`${o} ✗ Failed to send`),!1}}function x(e){for(;e&&!(e.tagName===`A`&&e.href);)e=e.parentNode;return e}function S(){let e={};if(!location.search)return e;let t=new URLSearchParams(location.search);for(let n of[`utm_source`,`utm_medium`,`utm_campaign`,`utm_term`,`utm_content`]){let r=t.get(n);r&&(e[n]=r)}return e}var C=class{endpoint;debug;started=!1;pageKey=``;navTimer=null;heartbeatTimer=null;scrollDepth=0;pageEntryTime=0;pagePath=``;pageUrl=``;pageHash=``;hasLeftCurrentPage=!1;scrollHandler=null;consentMode;cookielessWhilePending;constructor(e){this.options=e,this.endpoint=e.endpoint??`https://metrics.faststats.dev/v1/web`,this.debug=e.debug??!1,this.consentMode=e.consent?.mode??`granted`,this.cookielessWhilePending=e.consent?.cookielessWhilePending??!0,(e.autoTrack??!0)&&this.init()}log(e){this.debug&&console.log(`[Analytics] ${e}`)}init(){if(!(typeof window>`u`)){if(y()){this.log(`disabled`);return}`requestIdleCallback`in window?window.requestIdleCallback(()=>this.start()):setTimeout(()=>this.start(),1)}}start(){if(this.started||typeof window>`u`)return;if(window.__FA_webAnalyticsInstance&&window.__FA_webAnalyticsInstance!==this){this.log(`already started by another instance`);return}if(y()){this.log(`disabled`);return}this.started=!0,window.__FA_webAnalyticsInstance=this,window.__FA_getAnonymousId=()=>u(this.isCookielessMode()),window.__FA_getSessionId=p,window.__FA_sendData=b,window.__FA_identify=(e,t,n)=>this.identify(e,t,n),window.__FA_logout=e=>this.logout(e),window.__FA_setConsentMode=e=>this.setConsentMode(e),window.__FA_optIn=()=>this.optIn(),window.__FA_optOut=()=>this.optOut(),window.__FA_trackEvent=(e,t)=>this.track(e,t??{}),window.__FA_isTrackingDisabled=y;let e=this.options,t=e.webVitals?.sampling?.percentage!==void 0,n=e.replayOptions?.samplingPercentage!==void 0||e.sessionReplays?.sampling?.percentage!==void 0;if((e.errorTracking?.enabled??e.trackErrors)&&(new s({siteKey:e.siteKey,endpoint:this.endpoint,debug:this.debug,getCommonData:()=>({url:location.href,page:location.pathname,referrer:document.referrer||null})}).start(),this.log(`error loaded`)),(e.webVitals?.enabled??e.trackWebVitals??t)&&(new _({siteKey:e.siteKey,endpoint:this.endpoint,debug:this.debug,samplingPercentage:c(e.webVitals?.sampling?.percentage)}).start(),this.log(`web-vitals loaded`)),e.sessionReplays?.enabled??e.trackReplay??n){let t=e.replayOptions??{};new l({siteKey:e.siteKey,endpoint:this.endpoint,debug:this.debug,...t,samplingPercentage:c(t.samplingPercentage??e.sessionReplays?.sampling?.percentage)}).start(),this.log(`replay loaded`)}this.enterPage(),this.pageview({trigger:`load`}),this.links(),this.trackScroll(),this.startHeartbeat(),document.addEventListener(`visibilitychange`,()=>{document.visibilityState===`hidden`?this.leavePage():this.startHeartbeat()}),window.addEventListener(`pagehide`,()=>this.leavePage()),window.addEventListener(`popstate`,()=>this.navigate()),e.trackHash&&window.addEventListener(`hashchange`,()=>this.navigate()),this.patch()}pageview(e={}){let t=`${location.pathname}|${this.options.trackHash??!1?location.hash:``}`;t!==this.pageKey&&(this.pageKey=t,this.send(`pageview`,e))}track(e,t={}){this.send(e,t)}identify(e,t,n={}){if(y()||this.isCookielessMode())return;let r=e.trim(),i=t.trim();!r||!i||b({url:this.endpoint.replace(/\/v1\/web$/,`/v1/identify`),data:JSON.stringify({token:this.options.siteKey,identifier:u(!1),externalId:r,email:i,name:n.name?.trim()||void 0,phone:n.phone?.trim()||void 0,avatarUrl:n.avatarUrl?.trim()||void 0,traits:n.traits??{}}),contentType:`text/plain`,debug:this.debug,debugPrefix:`[Analytics] identify`})}logout(e=!0){y()||(e&&f(this.isCookielessMode()),m())}setConsentMode(e){this.consentMode=e}optIn(){this.setConsentMode(`granted`)}optOut(){this.setConsentMode(`denied`)}getConsentMode(){return this.consentMode}isCookielessMode(){return this.options.cookieless||this.consentMode===`denied`?!0:this.consentMode===`pending`?this.cookielessWhilePending:!1}send(e,t={}){let n=u(this.isCookielessMode()),r=JSON.stringify({token:this.options.siteKey,...n?{userId:n}:{},sessionId:p(),data:{event:e,page:location.pathname,referrer:document.referrer||null,title:document.title||``,url:location.href,...S(),...t}});this.log(e),b({url:this.endpoint,data:r,contentType:`text/plain`,debug:this.debug,debugPrefix:`[Analytics] ${e}`})}enterPage(){this.pageEntryTime=Date.now(),this.pagePath=location.pathname,this.pageUrl=location.href,this.pageHash=location.hash,this.scrollDepth=0,this.hasLeftCurrentPage=!1}leavePage(){if(this.hasLeftCurrentPage)return;this.hasLeftCurrentPage=!0;let e=Date.now();this.send(`page_leave`,{page:this.pagePath,url:this.pageUrl,time_on_page:e-this.pageEntryTime,scroll_depth:this.scrollDepth,session_duration:e-g()})}trackScroll(){this.scrollHandler&&window.removeEventListener(`scroll`,this.scrollHandler);let e=()=>{let e=document.documentElement,t=document.body,n=window.innerHeight,r=Math.max(e.scrollHeight,t.scrollHeight);if(r<=n){this.scrollDepth=100;return}let i=Math.min(100,Math.round(((window.scrollY||e.scrollTop)+n)/r*100));i>this.scrollDepth&&(this.scrollDepth=i)};this.scrollHandler=e,e(),window.addEventListener(`scroll`,e,{passive:!0})}startHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{if(document.visibilityState===`hidden`){clearInterval(this.heartbeatTimer),this.heartbeatTimer=null;return}h()},300*1e3)}navigate(){this.navTimer&&clearTimeout(this.navTimer),this.navTimer=setTimeout(()=>{this.navTimer=null;let e=location.pathname!==this.pagePath,t=(this.options.trackHash??!1)&&location.hash!==this.pageHash;!e&&!t||(this.leavePage(),this.enterPage(),this.trackScroll(),this.pageview({trigger:`navigation`}))},300)}patch(){let e=()=>this.navigate();for(let t of[`pushState`,`replaceState`]){let n=history[t];history[t]=function(...t){let r=n.apply(this,t);return e(),r}}}links(){let e=e=>{let t=x(e.target);t&&t.host!==location.host&&this.track(`outbound_link`,{outbound_link:t.href})};document.addEventListener(`click`,e),document.addEventListener(`auxclick`,e)}};export{C as WebAnalytics,v as trackEvent};
package/package.json CHANGED
@@ -15,11 +15,10 @@
15
15
  "publishConfig": {
16
16
  "access": "public"
17
17
  },
18
- "version": "0.0.2",
18
+ "version": "0.0.3",
19
19
  "scripts": {
20
- "build": "tsdown && cp -f dist/script.iife.js dist/script.js && cp -f dist/error.iife.js dist/error.js && cp -f dist/web-vitals.iife.js dist/web-vitals.js && cp -f dist/replay.iife.js dist/replay.js",
21
- "sync:example-assets": "mkdir -p ../../examples/spa-test/public && cp -f dist/script.iife.js ../../examples/spa-test/public/script.js && cp -f dist/error.iife.js ../../examples/spa-test/public/error.js && cp -f dist/web-vitals.iife.js ../../examples/spa-test/public/web-vitals.js && cp -f dist/replay.iife.js ../../examples/spa-test/public/replay.js",
22
- "dev": "bun run build && bun run sync:example-assets",
20
+ "build": "tsdown",
21
+ "dev": "bun run build",
23
22
  "deploy:worker": "wrangler deploy"
24
23
  },
25
24
  "devDependencies": {
package/src/analytics.ts CHANGED
@@ -1,4 +1,6 @@
1
+ import ErrorTracker from "./error";
1
2
  import type { ReplayTrackerOptions } from "./replay";
3
+ import ReplayTracker from "./replay";
2
4
  import {
3
5
  getAnonymousId,
4
6
  getOrCreateSessionId,
@@ -11,6 +13,7 @@ import {
11
13
  normalizeSamplingPercentage,
12
14
  type SendDataOptions,
13
15
  } from "./utils/types";
16
+ import WebVitalsTracker from "./web-vitals";
14
17
 
15
18
  export type { SendDataOptions };
16
19
 
@@ -139,7 +142,6 @@ export interface IdentifyOptions {
139
142
  export interface WebAnalyticsOptions {
140
143
  siteKey: string;
141
144
  endpoint?: string;
142
- baseUrl?: string;
143
145
  debug?: boolean;
144
146
  autoTrack?: boolean;
145
147
  trackHash?: boolean;
@@ -170,7 +172,10 @@ declare global {
170
172
  __FA_optIn?: () => void;
171
173
  __FA_optOut?: () => void;
172
174
  __FA_isTrackingDisabled?: () => boolean;
173
- __FA_trackEvent?: (eventName: string, properties?: Record<string, unknown>) => void;
175
+ __FA_trackEvent?: (
176
+ eventName: string,
177
+ properties?: Record<string, unknown>,
178
+ ) => void;
174
179
  __FA_webAnalyticsInstance?: WebAnalytics;
175
180
  }
176
181
  }
@@ -178,7 +183,6 @@ declare global {
178
183
  export class WebAnalytics {
179
184
  private readonly endpoint: string;
180
185
  private readonly debug: boolean;
181
- private readonly baseUrl: string;
182
186
  private started = false;
183
187
  private pageKey = "";
184
188
  private navTimer: ReturnType<typeof setTimeout> | null = null;
@@ -199,16 +203,6 @@ export class WebAnalytics {
199
203
  this.consentMode = options.consent?.mode ?? "granted";
200
204
  this.cookielessWhilePending =
201
205
  options.consent?.cookielessWhilePending ?? true;
202
- const script =
203
- typeof document !== "undefined"
204
- ? (document.currentScript as HTMLScriptElement | null)
205
- : null;
206
- const scriptBase = script?.src
207
- ? script.src.substring(0, script.src.lastIndexOf("/"))
208
- : "";
209
- this.baseUrl =
210
- (options.baseUrl ?? scriptBase) ||
211
- (typeof window !== "undefined" ? window.location.origin : "");
212
206
  if (options.autoTrack ?? true) this.init();
213
207
  }
214
208
 
@@ -265,7 +259,7 @@ export class WebAnalytics {
265
259
  opts.sessionReplays?.sampling?.percentage !== undefined;
266
260
 
267
261
  if (opts.errorTracking?.enabled ?? opts.trackErrors) {
268
- this.load("error", "__FA_ErrorTracker", {
262
+ new ErrorTracker({
269
263
  siteKey: opts.siteKey,
270
264
  endpoint: this.endpoint,
271
265
  debug: this.debug,
@@ -274,25 +268,27 @@ export class WebAnalytics {
274
268
  page: location.pathname,
275
269
  referrer: document.referrer || null,
276
270
  }),
277
- });
271
+ }).start();
272
+ this.log("error loaded");
278
273
  }
279
274
  if (
280
275
  opts.webVitals?.enabled ??
281
276
  opts.trackWebVitals ??
282
277
  hasWebVitalsSampling
283
278
  ) {
284
- this.load("web-vitals", "__FA_WebVitalsTracker", {
279
+ new WebVitalsTracker({
285
280
  siteKey: opts.siteKey,
286
281
  endpoint: this.endpoint,
287
282
  debug: this.debug,
288
283
  samplingPercentage: normalizeSamplingPercentage(
289
284
  opts.webVitals?.sampling?.percentage,
290
285
  ),
291
- });
286
+ }).start();
287
+ this.log("web-vitals loaded");
292
288
  }
293
289
  if (opts.sessionReplays?.enabled ?? opts.trackReplay ?? hasReplaySampling) {
294
290
  const replayOpts = opts.replayOptions ?? {};
295
- this.load("replay", "__FA_ReplayTracker", {
291
+ new ReplayTracker({
296
292
  siteKey: opts.siteKey,
297
293
  endpoint: this.endpoint,
298
294
  debug: this.debug,
@@ -301,7 +297,8 @@ export class WebAnalytics {
301
297
  replayOpts.samplingPercentage ??
302
298
  opts.sessionReplays?.sampling?.percentage,
303
299
  ),
304
- });
300
+ }).start();
301
+ this.log("replay loaded");
305
302
  }
306
303
 
307
304
  this.enterPage();
@@ -321,31 +318,6 @@ export class WebAnalytics {
321
318
  this.patch();
322
319
  }
323
320
 
324
- private load(
325
- script: string,
326
- key: string,
327
- trackerOpts: Record<string, unknown>,
328
- ): void {
329
- const el = document.createElement("script");
330
- el.src = `${this.baseUrl}/${script}.js`;
331
- el.async = true;
332
- el.onload = () => {
333
- const Ctor = (window as unknown as Record<string, unknown>)[key] as
334
- | (new (
335
- o: Record<string, unknown>,
336
- ) => { start(): void })
337
- | undefined;
338
- if (Ctor) {
339
- new Ctor(trackerOpts).start();
340
- this.log(`${script} loaded`);
341
- }
342
- };
343
- el.onerror = () => {
344
- if (this.debug) console.warn(`[Analytics] ${script} failed`);
345
- };
346
- document.head.appendChild(el);
347
- }
348
-
349
321
  pageview(extra: Dict = {}): void {
350
322
  const key = `${location.pathname}|${(this.options.trackHash ?? false) ? location.hash : ""}`;
351
323
  if (key === this.pageKey) return;