@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 +14 -0
- package/dist/error-DtTBFJh_.js +3 -0
- package/dist/module.d.ts +15 -33
- package/dist/module.js +1 -3
- package/dist/replay-ZmimkIBT.js +1 -0
- package/dist/types-E07i1PxI.js +1 -0
- package/dist/web-vitals-CUXDm9A3.js +1 -0
- package/package.json +2 -1
- package/src/analytics.ts +59 -51
- package/src/error.ts +12 -23
- package/src/module.ts +8 -0
- package/src/replay.ts +13 -34
- package/src/web-vitals.ts +4 -20
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
|
-
|
|
386
|
-
|
|
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{
|
|
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
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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
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 =
|
|
288
|
+
const anonymousId = this._getAnonymousId();
|
|
296
289
|
const batch = {
|
|
297
290
|
token: this.siteKey,
|
|
298
|
-
sessionId:
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
}
|