@faststats/web 0.0.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @faststats/web
2
2
 
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c63d783: Remove all window globals; use module-level instance and dependency injection. Add dynamic imports for ErrorTracker, WebVitalsTracker, ReplayTracker (tree-shakable). Export identify, logout, setConsentMode, optIn, optOut, getInstance from @faststats/web. React package now uses getInstance instead of window.
8
+
9
+ ## 0.0.4
10
+
11
+ ### Patch Changes
12
+
13
+ - 93393c0: Remove redundant isTrackingDisabled checks from sub-trackers (ErrorTracker, WebVitalsTracker, ReplayTracker).
14
+ Simplify web vitals config - single source of truth (trackWebVitals ?? webVitals?.enabled ?? hasWebVitalsSampling).
15
+ Fix custom events via trackEvent: set window.\_\_FA_webAnalyticsInstance in init() so it works before async start() completes.
16
+
3
17
  ## 0.0.3
4
18
 
5
19
  ### Patch Changes
@@ -0,0 +1,3 @@
1
+ var e=class{endpoint;siteKey;debug;flushInterval;maxQueueSize;getCommonData;getAnonymousId;getSessionId;sendData;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??(()=>({})),this.getAnonymousId=e.getAnonymousId??(()=>``),this.getSessionId=e.getSessionId??(()=>``),this.sendData=e.sendData??(async()=>!1)}start(){this.started||typeof window>`u`||(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=this.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:this.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),this.sendData({url:this.endpoint,data:o,debug:this.debug,debugPrefix:`[ErrorTracker]`})}};export{e as default};
package/dist/module.d.ts CHANGED
@@ -381,25 +381,21 @@ interface ReplayTrackerOptions {
381
381
  checkoutEveryNms?: number;
382
382
  checkoutEveryNth?: number;
383
383
  recordConsole?: boolean;
384
- }
385
- type ReplayTrackerPublicInstance = {
386
- start(): void;
387
- stop(): void;
388
- getSessionId(): string | undefined;
389
- };
390
- type ReplayTrackerConstructor = new (options: ReplayTrackerOptions) => ReplayTrackerPublicInstance;
391
- declare global {
392
- interface Window {
393
- __FA_ReplayTracker?: ReplayTrackerConstructor;
394
- __FA_getAnonymousId?: () => string;
395
- __FA_getSessionId?: () => string;
396
- __FA_sendData?: (options: SendDataOptions) => Promise<boolean>;
397
- __FA_isTrackingDisabled?: () => boolean;
398
- }
384
+ getAnonymousId?: () => string;
385
+ getSessionId?: () => string;
386
+ sendData?: (options: SendDataOptions) => Promise<boolean>;
399
387
  }
400
388
  //#endregion
401
389
  //#region src/analytics.d.ts
390
+ declare function getInstance(): WebAnalytics | null;
402
391
  declare function trackEvent(eventName: string, properties?: Record<string, unknown>): void;
392
+ declare function identify(externalId: string, email: string, options?: IdentifyOptions): void;
393
+ declare function logout(resetAnonymousIdentity?: boolean): void;
394
+ declare function setConsentMode(mode: ConsentMode): void;
395
+ declare function optIn(): void;
396
+ declare function optOut(): void;
397
+ declare function isTrackingDisabled(): boolean;
398
+ declare function sendData(options: SendDataOptions): Promise<boolean>;
403
399
  type Dict = Record<string, unknown>;
404
400
  interface SamplingOptions {
405
401
  percentage?: number;
@@ -442,22 +438,6 @@ interface WebAnalyticsOptions {
442
438
  sessionReplays?: SessionReplayConfig;
443
439
  replayOptions?: Partial<ReplayTrackerOptions>;
444
440
  }
445
- declare global {
446
- interface Window {
447
- WebAnalytics: typeof WebAnalytics;
448
- __FA_getAnonymousId?: () => string;
449
- __FA_getSessionId?: () => string;
450
- __FA_sendData?: (options: SendDataOptions) => Promise<boolean>;
451
- __FA_identify?: (externalId: string, email: string, options?: IdentifyOptions) => void;
452
- __FA_logout?: (resetAnonymousIdentity?: boolean) => void;
453
- __FA_setConsentMode?: (mode: ConsentMode) => void;
454
- __FA_optIn?: () => void;
455
- __FA_optOut?: () => void;
456
- __FA_isTrackingDisabled?: () => boolean;
457
- __FA_trackEvent?: (eventName: string, properties?: Record<string, unknown>) => void;
458
- __FA_webAnalyticsInstance?: WebAnalytics;
459
- }
460
- }
461
441
  declare class WebAnalytics {
462
442
  private readonly options;
463
443
  private readonly endpoint;
@@ -478,7 +458,7 @@ declare class WebAnalytics {
478
458
  constructor(options: WebAnalyticsOptions);
479
459
  private log;
480
460
  private init;
481
- start(): void;
461
+ start(): Promise<void>;
482
462
  pageview(extra?: Dict): void;
483
463
  track(name: string, extra?: Dict): void;
484
464
  identify(externalId: string, email: string, options?: IdentifyOptions): void;
@@ -487,6 +467,8 @@ declare class WebAnalytics {
487
467
  optIn(): void;
488
468
  optOut(): void;
489
469
  getConsentMode(): ConsentMode;
470
+ getAnonymousId(): string;
471
+ getSessionId(): string;
490
472
  private isCookielessMode;
491
473
  private send;
492
474
  private enterPage;
@@ -498,4 +480,4 @@ declare class WebAnalytics {
498
480
  private links;
499
481
  }
500
482
  //#endregion
501
- export { type ConsentMode, type IdentifyOptions, WebAnalytics, type WebAnalyticsOptions, trackEvent };
483
+ export { type ConsentMode, type IdentifyOptions, WebAnalytics, type WebAnalyticsOptions, getInstance, identify, isTrackingDisabled, logout, optIn, optOut, sendData, setConsentMode, trackEvent };
package/dist/module.js CHANGED
@@ -1,3 +1 @@
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};
1
+ import{t as e}from"./types-E07i1PxI.js";function t(e){return e?``:n()}function n(){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 r(e){return e||typeof localStorage>`u`?``:(localStorage.removeItem(`faststats_anon_id`),n())}function i(){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 a(){return typeof sessionStorage>`u`?``:(sessionStorage.removeItem(`session_id`),sessionStorage.removeItem(`session_timestamp`),sessionStorage.removeItem(`session_start`),i())}function o(){typeof sessionStorage>`u`||sessionStorage.getItem(`session_id`)&&sessionStorage.setItem(`session_timestamp`,Date.now().toString())}function s(){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()}let c=null;function l(){return c}function u(e,t){typeof window>`u`||g()||c?.track(e,t??{})}function d(e,t,n){typeof window>`u`||g()||c?.identify(e,t,n??{})}function f(e=!0){typeof window>`u`||g()||c?.logout(e)}function p(e){c?.setConsentMode(e)}function m(){p(`granted`)}function h(){p(`denied`)}function g(){if(typeof localStorage>`u`)return!1;let e=localStorage.getItem(`disable-faststats`);return e===`true`||e===`1`}async function _(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 v(e){for(;e&&!(e.tagName===`A`&&e.href);)e=e.parentNode;return e}function y(){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 b=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(g()){this.log(`disabled`);return}c=this,`requestIdleCallback`in window?window.requestIdleCallback(()=>void this.start()):setTimeout(()=>void this.start(),1)}}async start(){if(this.started||typeof window>`u`)return;if(c&&c!==this){this.log(`already started by another instance`);return}if(g()){this.log(`disabled`);return}this.started=!0,c=this;let n=()=>t(this.isCookielessMode()),r=this.options,a=r.webVitals?.sampling?.percentage!==void 0,o=r.replayOptions?.samplingPercentage!==void 0||r.sessionReplays?.sampling?.percentage!==void 0;if(r.errorTracking?.enabled??r.trackErrors){let{default:e}=await import(`./error-DtTBFJh_.js`);new e({siteKey:r.siteKey,endpoint:this.endpoint,debug:this.debug,getCommonData:()=>({url:location.href,page:location.pathname,referrer:document.referrer||null}),getAnonymousId:n,getSessionId:i,sendData:_}).start(),this.log(`error loaded`)}if(r.trackWebVitals??r.webVitals?.enabled??a){let{default:t}=await import(`./web-vitals-CUXDm9A3.js`);new t({siteKey:r.siteKey,endpoint:this.endpoint,debug:this.debug,samplingPercentage:e(r.webVitals?.sampling?.percentage),getSessionId:i}).start(),this.log(`web-vitals loaded`)}if(r.sessionReplays?.enabled??r.trackReplay??o){let{default:t}=await import(`./replay-ZmimkIBT.js`),a=r.replayOptions??{};new t({siteKey:r.siteKey,endpoint:this.endpoint,debug:this.debug,...a,samplingPercentage:e(a.samplingPercentage??r.sessionReplays?.sampling?.percentage),getAnonymousId:n,getSessionId:i,sendData:_}).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()),r.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,n,r={}){if(g()||this.isCookielessMode())return;let i=e.trim(),a=n.trim();!i||!a||_({url:this.endpoint.replace(/\/v1\/web$/,`/v1/identify`),data:JSON.stringify({token:this.options.siteKey,identifier:t(!1),externalId:i,email:a,name:r.name?.trim()||void 0,phone:r.phone?.trim()||void 0,avatarUrl:r.avatarUrl?.trim()||void 0,traits:r.traits??{}}),contentType:`text/plain`,debug:this.debug,debugPrefix:`[Analytics] identify`})}logout(e=!0){g()||(e&&r(this.isCookielessMode()),a())}setConsentMode(e){this.consentMode=e}optIn(){this.setConsentMode(`granted`)}optOut(){this.setConsentMode(`denied`)}getConsentMode(){return this.consentMode}getAnonymousId(){return t(this.isCookielessMode())}getSessionId(){return i()}isCookielessMode(){return this.options.cookieless||this.consentMode===`denied`?!0:this.consentMode===`pending`?this.cookielessWhilePending:!1}send(e,n={}){let r=t(this.isCookielessMode()),a=JSON.stringify({token:this.options.siteKey,...r?{userId:r}:{},sessionId:i(),data:{event:e,page:location.pathname,referrer:document.referrer||null,title:document.title||``,url:location.href,...y(),...n}});this.log(e),_({url:this.endpoint,data:a,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-s()})}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}o()},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=v(e.target);t&&t.host!==location.host&&this.track(`outbound_link`,{outbound_link:t.href})};document.addEventListener(`click`,e),document.addEventListener(`auxclick`,e)}};export{b as WebAnalytics,l as getInstance,d as identify,g as isTrackingDisabled,f as logout,m as optIn,h as optOut,_ as sendData,p as setConsentMode,u as trackEvent};
@@ -0,0 +1 @@
1
+ import{t as e}from"./types-E07i1PxI.js";import{getRecordConsolePlugin as t}from"@rrweb/rrweb-plugin-console-record";import{record as n}from"rrweb";var r=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;_getAnonymousId;_getSessionId;sendData;constructor(t){this.siteKey=t.siteKey,this.endpoint=t.endpoint?.replace(/\/v1\/web$/,`/v1/replay`)??`https://metrics.faststats.dev/v1/replay`,this.debug=t.debug??!1,this.samplingPercentage=e(t.samplingPercentage),this.flushInterval=t.flushInterval??1e4,this.maxEvents=t.maxEvents??500,this.sampling=t.sampling??{mousemove:50,mouseInteraction:!0,scroll:150,media:800,input:`last`},this.slimDOMOptions=t.slimDOMOptions??{script:!0,comment:!0,headFavicon:!0,headWhitespace:!0,headMetaDescKeywords:!0,headMetaSocial:!0,headMetaRobots:!0,headMetaHttpEquiv:!0,headMetaAuthorship:!0},this.maskAllInputs=t.maskAllInputs??!0,this.maskInputOptions=t.maskInputOptions??{password:!0,email:!0,tel:!0},this.blockClass=t.blockClass,this.blockSelector=t.blockSelector,this.maskTextClass=t.maskTextClass,this.maskTextSelector=t.maskTextSelector,this.checkoutEveryNms=t.checkoutEveryNms??6e4,this.checkoutEveryNth=t.checkoutEveryNth,this.recordConsole=t.recordConsole??!0,this.sessionSamplingSeed=Math.random()*100,this._getAnonymousId=t.getAnonymousId??(()=>``),this._getSessionId=t.getSessionId??(()=>``),this.sendData=t.sendData??(async()=>!1),typeof window<`u`&&(this.compressionSupported=`CompressionStream`in window)}start(){if(this.started||typeof window>`u`||this.samplingPercentage<100&&this.sessionSamplingSeed>=this.samplingPercentage)return;this.started=!0,this.startTime=Date.now(),this.debug&&console.log(`[Replay] Recording started`);let e={emit:(e,t)=>this.handleEvent(e,t),sampling:this.sampling,slimDOMOptions:this.slimDOMOptions,maskAllInputs:this.maskAllInputs,checkoutEveryNms:this.checkoutEveryNms};this.maskInputOptions&&(e.maskInputOptions=this.maskInputOptions),this.blockClass&&(e.blockClass=this.blockClass),this.blockSelector&&(e.blockSelector=this.blockSelector),this.maskTextClass&&(e.maskTextClass=this.maskTextClass),this.maskTextSelector&&(e.maskTextSelector=this.maskTextSelector),this.checkoutEveryNth&&(e.checkoutEveryNth=this.checkoutEveryNth),this.recordConsole&&(e.plugins=[t()]),this.stopRecording=n(e),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=this._getAnonymousId(),r={token:this.siteKey,sessionId:this._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 this.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 this._getSessionId()}};export{r as default};
@@ -0,0 +1 @@
1
+ function e(e){return typeof e!=`number`||!Number.isFinite(e)?100:Math.max(0,Math.min(100,e))}export{e as t};
@@ -0,0 +1 @@
1
+ import{t as e}from"./types-E07i1PxI.js";import{onCLS as t,onFCP as n,onINP as r,onLCP as i,onTTFB as a}from"web-vitals/attribution";var o=class{endpoint;siteKey;debug;samplingPercentage;started=!1;sessionSamplingSeed;metricsMap=new Map;flushed=!1;getSessionId;constructor(t){this.siteKey=t.siteKey,this.endpoint=this.getVitalsEndpoint(t.endpoint??`https://metrics.faststats.dev/v1/web`),this.debug=t.debug??!1,this.samplingPercentage=e(t.samplingPercentage),this.sessionSamplingSeed=Math.random()*100,this.getSessionId=t.getSessionId??(()=>``)}getVitalsEndpoint(e){let t=new URL(e),n=t.pathname.split(`/`);return n[n.length-1]=`vitals`,t.pathname=n.join(`/`),t.toString()}start(){this.started||typeof window>`u`||(this.started=!0,document.addEventListener(`visibilitychange`,()=>{document.visibilityState===`hidden`&&this.finalizeAndFlush()}),window.addEventListener(`pagehide`,()=>{this.finalizeAndFlush()}),this.debug&&console.log(`[WebVitals] Tracking started`),t(e=>this.captureMetric(e)),r(e=>this.captureMetric(e)),i(e=>this.captureMetric(e)),n(e=>this.captureMetric(e)),a(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:this.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`)})}}};export{o as default};
package/package.json CHANGED
@@ -12,10 +12,11 @@
12
12
  }
13
13
  },
14
14
  "type": "module",
15
+ "sideEffects": false,
15
16
  "publishConfig": {
16
17
  "access": "public"
17
18
  },
18
- "version": "0.0.3",
19
+ "version": "0.1.0",
19
20
  "scripts": {
20
21
  "build": "tsdown",
21
22
  "dev": "bun run build",
package/src/analytics.ts CHANGED
@@ -1,6 +1,4 @@
1
- import ErrorTracker from "./error";
2
1
  import type { ReplayTrackerOptions } from "./replay";
3
- import ReplayTracker from "./replay";
4
2
  import {
5
3
  getAnonymousId,
6
4
  getOrCreateSessionId,
@@ -13,16 +11,47 @@ import {
13
11
  normalizeSamplingPercentage,
14
12
  type SendDataOptions,
15
13
  } from "./utils/types";
16
- import WebVitalsTracker from "./web-vitals";
17
14
 
18
15
  export type { SendDataOptions };
19
16
 
17
+ let _instance: WebAnalytics | null = null;
18
+
19
+ export function getInstance(): WebAnalytics | null {
20
+ return _instance;
21
+ }
22
+
20
23
  export function trackEvent(
21
24
  eventName: string,
22
25
  properties?: Record<string, unknown>,
23
26
  ): void {
24
27
  if (typeof window === "undefined" || isTrackingDisabled()) return;
25
- window.__FA_webAnalyticsInstance?.track(eventName, properties ?? {});
28
+ _instance?.track(eventName, properties ?? {});
29
+ }
30
+
31
+ export function identify(
32
+ externalId: string,
33
+ email: string,
34
+ options?: IdentifyOptions,
35
+ ): void {
36
+ if (typeof window === "undefined" || isTrackingDisabled()) return;
37
+ _instance?.identify(externalId, email, options ?? {});
38
+ }
39
+
40
+ export function logout(resetAnonymousIdentity = true): void {
41
+ if (typeof window === "undefined" || isTrackingDisabled()) return;
42
+ _instance?.logout(resetAnonymousIdentity);
43
+ }
44
+
45
+ export function setConsentMode(mode: ConsentMode): void {
46
+ _instance?.setConsentMode(mode);
47
+ }
48
+
49
+ export function optIn(): void {
50
+ setConsentMode("granted");
51
+ }
52
+
53
+ export function optOut(): void {
54
+ setConsentMode("denied");
26
55
  }
27
56
 
28
57
  export function isTrackingDisabled(): boolean {
@@ -156,30 +185,6 @@ export interface WebAnalyticsOptions {
156
185
  replayOptions?: Partial<ReplayTrackerOptions>;
157
186
  }
158
187
 
159
- declare global {
160
- interface Window {
161
- WebAnalytics: typeof WebAnalytics;
162
- __FA_getAnonymousId?: () => string;
163
- __FA_getSessionId?: () => string;
164
- __FA_sendData?: (options: SendDataOptions) => Promise<boolean>;
165
- __FA_identify?: (
166
- externalId: string,
167
- email: string,
168
- options?: IdentifyOptions,
169
- ) => void;
170
- __FA_logout?: (resetAnonymousIdentity?: boolean) => void;
171
- __FA_setConsentMode?: (mode: ConsentMode) => void;
172
- __FA_optIn?: () => void;
173
- __FA_optOut?: () => void;
174
- __FA_isTrackingDisabled?: () => boolean;
175
- __FA_trackEvent?: (
176
- eventName: string,
177
- properties?: Record<string, unknown>,
178
- ) => void;
179
- __FA_webAnalyticsInstance?: WebAnalytics;
180
- }
181
- }
182
-
183
188
  export class WebAnalytics {
184
189
  private readonly endpoint: string;
185
190
  private readonly debug: boolean;
@@ -216,17 +221,15 @@ export class WebAnalytics {
216
221
  this.log("disabled");
217
222
  return;
218
223
  }
224
+ _instance = this;
219
225
  if ("requestIdleCallback" in window)
220
- window.requestIdleCallback(() => this.start());
221
- else setTimeout(() => this.start(), 1);
226
+ window.requestIdleCallback(() => void this.start());
227
+ else setTimeout(() => void this.start(), 1);
222
228
  }
223
229
 
224
- start(): void {
230
+ async start(): Promise<void> {
225
231
  if (this.started || typeof window === "undefined") return;
226
- if (
227
- window.__FA_webAnalyticsInstance &&
228
- window.__FA_webAnalyticsInstance !== this
229
- ) {
232
+ if (_instance && _instance !== this) {
230
233
  this.log("already started by another instance");
231
234
  return;
232
235
  }
@@ -235,22 +238,9 @@ export class WebAnalytics {
235
238
  return;
236
239
  }
237
240
  this.started = true;
238
- window.__FA_webAnalyticsInstance = this;
239
-
240
- window.__FA_getAnonymousId = () => getAnonymousId(this.isCookielessMode());
241
- window.__FA_getSessionId = getOrCreateSessionId;
242
- window.__FA_sendData = sendData;
243
- window.__FA_identify = (externalId, email, options) =>
244
- this.identify(externalId, email, options);
245
- window.__FA_logout = (resetAnonymousIdentity) =>
246
- this.logout(resetAnonymousIdentity);
247
- window.__FA_setConsentMode = (mode) => this.setConsentMode(mode);
248
- window.__FA_optIn = () => this.optIn();
249
- window.__FA_optOut = () => this.optOut();
250
- window.__FA_trackEvent = (eventName, props) =>
251
- this.track(eventName, props ?? {});
252
- window.__FA_isTrackingDisabled = isTrackingDisabled;
241
+ _instance = this;
253
242
 
243
+ const getAnonymousIdFn = () => getAnonymousId(this.isCookielessMode());
254
244
  const opts = this.options;
255
245
  const hasWebVitalsSampling =
256
246
  opts.webVitals?.sampling?.percentage !== undefined;
@@ -259,6 +249,7 @@ export class WebAnalytics {
259
249
  opts.sessionReplays?.sampling?.percentage !== undefined;
260
250
 
261
251
  if (opts.errorTracking?.enabled ?? opts.trackErrors) {
252
+ const { default: ErrorTracker } = await import("./error");
262
253
  new ErrorTracker({
263
254
  siteKey: opts.siteKey,
264
255
  endpoint: this.endpoint,
@@ -268,14 +259,18 @@ export class WebAnalytics {
268
259
  page: location.pathname,
269
260
  referrer: document.referrer || null,
270
261
  }),
262
+ getAnonymousId: getAnonymousIdFn,
263
+ getSessionId: getOrCreateSessionId,
264
+ sendData,
271
265
  }).start();
272
266
  this.log("error loaded");
273
267
  }
274
268
  if (
275
- opts.webVitals?.enabled ??
276
269
  opts.trackWebVitals ??
270
+ opts.webVitals?.enabled ??
277
271
  hasWebVitalsSampling
278
272
  ) {
273
+ const { default: WebVitalsTracker } = await import("./web-vitals");
279
274
  new WebVitalsTracker({
280
275
  siteKey: opts.siteKey,
281
276
  endpoint: this.endpoint,
@@ -283,10 +278,12 @@ export class WebAnalytics {
283
278
  samplingPercentage: normalizeSamplingPercentage(
284
279
  opts.webVitals?.sampling?.percentage,
285
280
  ),
281
+ getSessionId: getOrCreateSessionId,
286
282
  }).start();
287
283
  this.log("web-vitals loaded");
288
284
  }
289
285
  if (opts.sessionReplays?.enabled ?? opts.trackReplay ?? hasReplaySampling) {
286
+ const { default: ReplayTracker } = await import("./replay");
290
287
  const replayOpts = opts.replayOptions ?? {};
291
288
  new ReplayTracker({
292
289
  siteKey: opts.siteKey,
@@ -297,6 +294,9 @@ export class WebAnalytics {
297
294
  replayOpts.samplingPercentage ??
298
295
  opts.sessionReplays?.sampling?.percentage,
299
296
  ),
297
+ getAnonymousId: getAnonymousIdFn,
298
+ getSessionId: getOrCreateSessionId,
299
+ sendData,
300
300
  }).start();
301
301
  this.log("replay loaded");
302
302
  }
@@ -389,6 +389,14 @@ export class WebAnalytics {
389
389
  return this.consentMode;
390
390
  }
391
391
 
392
+ getAnonymousId(): string {
393
+ return getAnonymousId(this.isCookielessMode());
394
+ }
395
+
396
+ getSessionId(): string {
397
+ return getOrCreateSessionId();
398
+ }
399
+
392
400
  private isCookielessMode(): boolean {
393
401
  if (this.options.cookieless) return true;
394
402
  if (this.consentMode === "denied") return true;
package/src/error.ts CHANGED
@@ -28,6 +28,9 @@ export interface ErrorTrackingOptions {
28
28
  flushInterval?: number;
29
29
  maxQueueSize?: number;
30
30
  getCommonData?: () => Record<string, unknown>;
31
+ getAnonymousId?: () => string;
32
+ getSessionId?: () => string;
33
+ sendData?: (options: SendDataOptions) => Promise<boolean>;
31
34
  }
32
35
 
33
36
  class ErrorTracker {
@@ -37,6 +40,9 @@ class ErrorTracker {
37
40
  private readonly flushInterval: number;
38
41
  private readonly maxQueueSize: number;
39
42
  private readonly getCommonData: () => Record<string, unknown>;
43
+ private readonly getAnonymousId: () => string;
44
+ private readonly getSessionId: () => string;
45
+ private readonly sendData: (options: SendDataOptions) => Promise<boolean>;
40
46
 
41
47
  private readonly handledErrors = new WeakSet<Error>();
42
48
  private readonly errorCounts = new Map<
@@ -53,16 +59,13 @@ class ErrorTracker {
53
59
  this.flushInterval = options.flushInterval ?? 5000;
54
60
  this.maxQueueSize = options.maxQueueSize ?? 50;
55
61
  this.getCommonData = options.getCommonData ?? (() => ({}));
62
+ this.getAnonymousId = options.getAnonymousId ?? (() => "");
63
+ this.getSessionId = options.getSessionId ?? (() => "");
64
+ this.sendData = options.sendData ?? (async () => false);
56
65
  }
57
66
 
58
67
  start(): void {
59
68
  if (this.started || typeof window === "undefined") return;
60
- if (window.__FA_isTrackingDisabled?.()) {
61
- if (this.debug) {
62
- console.log("[ErrorTracker] Tracking disabled via localStorage");
63
- }
64
- return;
65
- }
66
69
  this.started = true;
67
70
 
68
71
  window.addEventListener("error", this.handleErrorEvent);
@@ -266,7 +269,7 @@ class ErrorTracker {
266
269
  }
267
270
 
268
271
  const commonData = this.getCommonData();
269
- const identifier = window.__FA_getAnonymousId?.();
272
+ const identifier = this.getAnonymousId();
270
273
  const sourcemapsBuild = (
271
274
  globalThis as {
272
275
  __SOURCEMAPS_BUILD__?: { buildId?: unknown };
@@ -281,7 +284,7 @@ class ErrorTracker {
281
284
  const payload: Record<string, unknown> = {
282
285
  token: this.siteKey,
283
286
  ...(identifier ? { userId: identifier } : {}),
284
- sessionId: window.__FA_getSessionId?.(),
287
+ sessionId: this.getSessionId(),
285
288
  ...(buildId ? { buildId } : {}),
286
289
  data: {
287
290
  url: commonData.url as string,
@@ -298,7 +301,7 @@ class ErrorTracker {
298
301
  console.log("[ErrorTracker] Payload:", payloadString);
299
302
  }
300
303
 
301
- window.__FA_sendData?.({
304
+ this.sendData({
302
305
  url: this.endpoint,
303
306
  data: payloadString,
304
307
  debug: this.debug,
@@ -308,17 +311,3 @@ class ErrorTracker {
308
311
  }
309
312
 
310
313
  export default ErrorTracker;
311
-
312
- declare global {
313
- interface Window {
314
- __FA_ErrorTracker?: typeof ErrorTracker;
315
- __FA_getAnonymousId?: () => string;
316
- __FA_getSessionId?: () => string;
317
- __FA_sendData?: (options: SendDataOptions) => Promise<boolean>;
318
- __FA_isTrackingDisabled?: () => boolean;
319
- }
320
- }
321
-
322
- if (typeof window !== "undefined") {
323
- window.__FA_ErrorTracker = ErrorTracker;
324
- }
package/src/module.ts CHANGED
@@ -1,6 +1,14 @@
1
1
  export {
2
2
  type ConsentMode,
3
+ getInstance,
3
4
  type IdentifyOptions,
5
+ identify,
6
+ isTrackingDisabled,
7
+ logout,
8
+ optIn,
9
+ optOut,
10
+ sendData,
11
+ setConsentMode,
4
12
  trackEvent,
5
13
  WebAnalytics,
6
14
  type WebAnalyticsOptions,
package/src/replay.ts CHANGED
@@ -26,18 +26,11 @@ export interface ReplayTrackerOptions {
26
26
  checkoutEveryNms?: number;
27
27
  checkoutEveryNth?: number;
28
28
  recordConsole?: boolean;
29
+ getAnonymousId?: () => string;
30
+ getSessionId?: () => string;
31
+ sendData?: (options: SendDataOptions) => Promise<boolean>;
29
32
  }
30
33
 
31
- type ReplayTrackerPublicInstance = {
32
- start(): void;
33
- stop(): void;
34
- getSessionId(): string | undefined;
35
- };
36
-
37
- type ReplayTrackerConstructor = new (
38
- options: ReplayTrackerOptions,
39
- ) => ReplayTrackerPublicInstance;
40
-
41
34
  interface PendingBatch {
42
35
  batch: {
43
36
  token: string;
@@ -81,6 +74,9 @@ class ReplayTracker {
81
74
  private compressionSupported = false;
82
75
  private readonly sessionSamplingSeed: number;
83
76
  private readonly minReplayLengthMs = 3000;
77
+ private readonly _getAnonymousId: () => string;
78
+ private readonly _getSessionId: () => string;
79
+ private readonly sendData: (options: SendDataOptions) => Promise<boolean>;
84
80
 
85
81
  constructor(options: ReplayTrackerOptions) {
86
82
  this.siteKey = options.siteKey;
@@ -125,6 +121,9 @@ class ReplayTracker {
125
121
  this.checkoutEveryNth = options.checkoutEveryNth;
126
122
  this.recordConsole = options.recordConsole ?? true;
127
123
  this.sessionSamplingSeed = Math.random() * 100;
124
+ this._getAnonymousId = options.getAnonymousId ?? (() => "");
125
+ this._getSessionId = options.getSessionId ?? (() => "");
126
+ this.sendData = options.sendData ?? (async () => false);
128
127
 
129
128
  if (typeof window !== "undefined") {
130
129
  this.compressionSupported = "CompressionStream" in window;
@@ -133,12 +132,6 @@ class ReplayTracker {
133
132
 
134
133
  start(): void {
135
134
  if (this.started || typeof window === "undefined") return;
136
- if (window.__FA_isTrackingDisabled?.()) {
137
- if (this.debug) {
138
- console.log("[Replay] Tracking disabled via localStorage");
139
- }
140
- return;
141
- }
142
135
 
143
136
  if (this.samplingPercentage < 100) {
144
137
  if (this.sessionSamplingSeed >= this.samplingPercentage) {
@@ -292,10 +285,10 @@ class ReplayTracker {
292
285
  const eventsToSend = this.events;
293
286
  this.events = [];
294
287
 
295
- const anonymousId = window.__FA_getAnonymousId?.();
288
+ const anonymousId = this._getAnonymousId();
296
289
  const batch = {
297
290
  token: this.siteKey,
298
- sessionId: window.__FA_getSessionId?.(),
291
+ sessionId: this._getSessionId(),
299
292
  ...(anonymousId ? { identifier: anonymousId } : {}),
300
293
  sequence: this.sequenceNumber++,
301
294
  timestamp: Date.now(),
@@ -453,7 +446,7 @@ class ReplayTracker {
453
446
  const sizeKB = (data.size / 1024).toFixed(1);
454
447
 
455
448
  return (
456
- window.__FA_sendData?.({
449
+ this.sendData({
457
450
  url,
458
451
  data,
459
452
  contentType: isCompressed
@@ -467,22 +460,8 @@ class ReplayTracker {
467
460
  }
468
461
 
469
462
  getSessionId(): string | undefined {
470
- return window.__FA_getSessionId?.();
463
+ return this._getSessionId();
471
464
  }
472
465
  }
473
466
 
474
467
  export default ReplayTracker;
475
-
476
- declare global {
477
- interface Window {
478
- __FA_ReplayTracker?: ReplayTrackerConstructor;
479
- __FA_getAnonymousId?: () => string;
480
- __FA_getSessionId?: () => string;
481
- __FA_sendData?: (options: SendDataOptions) => Promise<boolean>;
482
- __FA_isTrackingDisabled?: () => boolean;
483
- }
484
- }
485
-
486
- if (typeof window !== "undefined") {
487
- window.__FA_ReplayTracker = ReplayTracker;
488
- }
package/src/web-vitals.ts CHANGED
@@ -13,6 +13,7 @@ export interface WebVitalsOptions {
13
13
  endpoint?: string;
14
14
  debug?: boolean;
15
15
  samplingPercentage?: number;
16
+ getSessionId?: () => string;
16
17
  }
17
18
 
18
19
  type MetricName = "CLS" | "INP" | "LCP" | "FCP" | "TTFB";
@@ -33,6 +34,7 @@ class WebVitalsTracker {
33
34
  private readonly sessionSamplingSeed: number;
34
35
  private readonly metricsMap = new Map<MetricName, MetricState>();
35
36
  private flushed = false;
37
+ private readonly getSessionId: () => string;
36
38
 
37
39
  constructor(options: WebVitalsOptions) {
38
40
  this.siteKey = options.siteKey;
@@ -44,6 +46,7 @@ class WebVitalsTracker {
44
46
  options.samplingPercentage,
45
47
  );
46
48
  this.sessionSamplingSeed = Math.random() * 100;
49
+ this.getSessionId = options.getSessionId ?? (() => "");
47
50
  }
48
51
 
49
52
  private getVitalsEndpoint(baseEndpoint: string): string {
@@ -57,13 +60,6 @@ class WebVitalsTracker {
57
60
  start(): void {
58
61
  if (this.started || typeof window === "undefined") return;
59
62
 
60
- if (window.__FA_isTrackingDisabled?.()) {
61
- if (this.debug) {
62
- console.log("[WebVitals] Tracking disabled");
63
- }
64
- return;
65
- }
66
-
67
63
  this.started = true;
68
64
 
69
65
  document.addEventListener("visibilitychange", () => {
@@ -153,7 +149,7 @@ class WebVitalsTracker {
153
149
 
154
150
  return {
155
151
  body: JSON.stringify({
156
- sessionId: window.__FA_getSessionId?.(),
152
+ sessionId: this.getSessionId(),
157
153
  vitals,
158
154
  metadata: {
159
155
  url: window.location.href,
@@ -193,15 +189,3 @@ class WebVitalsTracker {
193
189
  }
194
190
 
195
191
  export default WebVitalsTracker;
196
-
197
- declare global {
198
- interface Window {
199
- __FA_WebVitalsTracker?: typeof WebVitalsTracker;
200
- __FA_getSessionId?: () => string;
201
- __FA_isTrackingDisabled?: () => boolean;
202
- }
203
- }
204
-
205
- if (typeof window !== "undefined") {
206
- window.__FA_WebVitalsTracker = WebVitalsTracker;
207
- }