@faststats/web 0.0.4 → 0.1.1
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 +12 -0
- package/dist/error-DtTBFJh_.js +3 -0
- package/dist/module.d.ts +15 -35
- 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 +6 -2
- package/src/analytics.ts +62 -76
- package/src/error.ts +12 -17
- package/src/module.ts +8 -0
- package/src/replay.ts +13 -28
- package/src/web-vitals.ts +4 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @faststats/web
|
|
2
2
|
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2326d00: fix: some cleanup
|
|
8
|
+
|
|
9
|
+
## 0.1.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
3
15
|
## 0.0.4
|
|
4
16
|
|
|
5
17
|
### 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;
|
|
@@ -408,11 +404,9 @@ interface ErrorTrackingConfig {
|
|
|
408
404
|
enabled?: boolean;
|
|
409
405
|
}
|
|
410
406
|
interface WebVitalsConfig {
|
|
411
|
-
enabled?: boolean;
|
|
412
407
|
sampling?: SamplingOptions;
|
|
413
408
|
}
|
|
414
409
|
interface SessionReplayConfig {
|
|
415
|
-
enabled?: boolean;
|
|
416
410
|
sampling?: SamplingOptions;
|
|
417
411
|
}
|
|
418
412
|
type ConsentMode = "pending" | "granted" | "denied";
|
|
@@ -442,22 +436,6 @@ interface WebAnalyticsOptions {
|
|
|
442
436
|
sessionReplays?: SessionReplayConfig;
|
|
443
437
|
replayOptions?: Partial<ReplayTrackerOptions>;
|
|
444
438
|
}
|
|
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
439
|
declare class WebAnalytics {
|
|
462
440
|
private readonly options;
|
|
463
441
|
private readonly endpoint;
|
|
@@ -478,7 +456,7 @@ declare class WebAnalytics {
|
|
|
478
456
|
constructor(options: WebAnalyticsOptions);
|
|
479
457
|
private log;
|
|
480
458
|
private init;
|
|
481
|
-
start(): void
|
|
459
|
+
start(): Promise<void>;
|
|
482
460
|
pageview(extra?: Dict): void;
|
|
483
461
|
track(name: string, extra?: Dict): void;
|
|
484
462
|
identify(externalId: string, email: string, options?: IdentifyOptions): void;
|
|
@@ -487,6 +465,8 @@ declare class WebAnalytics {
|
|
|
487
465
|
optIn(): void;
|
|
488
466
|
optOut(): void;
|
|
489
467
|
getConsentMode(): ConsentMode;
|
|
468
|
+
getAnonymousId(): string;
|
|
469
|
+
getSessionId(): string;
|
|
490
470
|
private isCookielessMode;
|
|
491
471
|
private send;
|
|
492
472
|
private enterPage;
|
|
@@ -498,4 +478,4 @@ declare class WebAnalytics {
|
|
|
498
478
|
private links;
|
|
499
479
|
}
|
|
500
480
|
//#endregion
|
|
501
|
-
export { type ConsentMode, type IdentifyOptions, WebAnalytics, type WebAnalyticsOptions, trackEvent };
|
|
481
|
+
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`||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(){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`),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}window.__FA_webAnalyticsInstance=this,`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.trackWebVitals??e.webVitals?.enabled??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;){if(e instanceof HTMLAnchorElement&&e.href)return e;e=e.parentNode}return null}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,setTimeout(()=>void this.start(),0)}}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;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){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.trackReplay){let{default:e}=await import(`./replay-ZmimkIBT.js`);new e({siteKey:r.siteKey,endpoint:this.endpoint,debug:this.debug,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
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faststats/web",
|
|
3
|
-
"repository":
|
|
3
|
+
"repository": {
|
|
4
|
+
"type": "git",
|
|
5
|
+
"url": "https://github.com/faststats-dev/web-analytics.git"
|
|
6
|
+
},
|
|
4
7
|
"main": "dist/module.js",
|
|
5
8
|
"module": "dist/module.js",
|
|
6
9
|
"types": "dist/module.d.ts",
|
|
@@ -12,10 +15,11 @@
|
|
|
12
15
|
}
|
|
13
16
|
},
|
|
14
17
|
"type": "module",
|
|
18
|
+
"sideEffects": true,
|
|
15
19
|
"publishConfig": {
|
|
16
20
|
"access": "public"
|
|
17
21
|
},
|
|
18
|
-
"version": "0.
|
|
22
|
+
"version": "0.1.1",
|
|
19
23
|
"scripts": {
|
|
20
24
|
"build": "tsdown",
|
|
21
25
|
"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
|
-
|
|
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 {
|
|
@@ -79,13 +108,11 @@ export async function sendData(options: SendDataOptions): Promise<boolean> {
|
|
|
79
108
|
}
|
|
80
109
|
|
|
81
110
|
function getLinkEl(el: Node | null): HTMLAnchorElement | null {
|
|
82
|
-
while (
|
|
83
|
-
el &&
|
|
84
|
-
!((el as Element).tagName === "A" && (el as HTMLAnchorElement).href)
|
|
85
|
-
) {
|
|
111
|
+
while (el) {
|
|
112
|
+
if (el instanceof HTMLAnchorElement && el.href) return el;
|
|
86
113
|
el = el.parentNode;
|
|
87
114
|
}
|
|
88
|
-
return
|
|
115
|
+
return null;
|
|
89
116
|
}
|
|
90
117
|
|
|
91
118
|
function getUTM(): Record<string, string> {
|
|
@@ -116,12 +143,10 @@ interface ErrorTrackingConfig {
|
|
|
116
143
|
}
|
|
117
144
|
|
|
118
145
|
interface WebVitalsConfig {
|
|
119
|
-
enabled?: boolean;
|
|
120
146
|
sampling?: SamplingOptions;
|
|
121
147
|
}
|
|
122
148
|
|
|
123
149
|
interface SessionReplayConfig {
|
|
124
|
-
enabled?: boolean;
|
|
125
150
|
sampling?: SamplingOptions;
|
|
126
151
|
}
|
|
127
152
|
|
|
@@ -156,30 +181,6 @@ export interface WebAnalyticsOptions {
|
|
|
156
181
|
replayOptions?: Partial<ReplayTrackerOptions>;
|
|
157
182
|
}
|
|
158
183
|
|
|
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
184
|
export class WebAnalytics {
|
|
184
185
|
private readonly endpoint: string;
|
|
185
186
|
private readonly debug: boolean;
|
|
@@ -216,18 +217,13 @@ export class WebAnalytics {
|
|
|
216
217
|
this.log("disabled");
|
|
217
218
|
return;
|
|
218
219
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
window.requestIdleCallback(() => this.start());
|
|
222
|
-
else setTimeout(() => this.start(), 1);
|
|
220
|
+
_instance = this;
|
|
221
|
+
setTimeout(() => void this.start(), 0);
|
|
223
222
|
}
|
|
224
223
|
|
|
225
|
-
start(): void {
|
|
224
|
+
async start(): Promise<void> {
|
|
226
225
|
if (this.started || typeof window === "undefined") return;
|
|
227
|
-
if (
|
|
228
|
-
window.__FA_webAnalyticsInstance &&
|
|
229
|
-
window.__FA_webAnalyticsInstance !== this
|
|
230
|
-
) {
|
|
226
|
+
if (_instance && _instance !== this) {
|
|
231
227
|
this.log("already started by another instance");
|
|
232
228
|
return;
|
|
233
229
|
}
|
|
@@ -236,30 +232,13 @@ export class WebAnalytics {
|
|
|
236
232
|
return;
|
|
237
233
|
}
|
|
238
234
|
this.started = true;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
window.__FA_getAnonymousId = () => getAnonymousId(this.isCookielessMode());
|
|
242
|
-
window.__FA_getSessionId = getOrCreateSessionId;
|
|
243
|
-
window.__FA_sendData = sendData;
|
|
244
|
-
window.__FA_identify = (externalId, email, options) =>
|
|
245
|
-
this.identify(externalId, email, options);
|
|
246
|
-
window.__FA_logout = (resetAnonymousIdentity) =>
|
|
247
|
-
this.logout(resetAnonymousIdentity);
|
|
248
|
-
window.__FA_setConsentMode = (mode) => this.setConsentMode(mode);
|
|
249
|
-
window.__FA_optIn = () => this.optIn();
|
|
250
|
-
window.__FA_optOut = () => this.optOut();
|
|
251
|
-
window.__FA_trackEvent = (eventName, props) =>
|
|
252
|
-
this.track(eventName, props ?? {});
|
|
253
|
-
window.__FA_isTrackingDisabled = isTrackingDisabled;
|
|
235
|
+
_instance = this;
|
|
254
236
|
|
|
237
|
+
const getAnonymousIdFn = () => getAnonymousId(this.isCookielessMode());
|
|
255
238
|
const opts = this.options;
|
|
256
|
-
const hasWebVitalsSampling =
|
|
257
|
-
opts.webVitals?.sampling?.percentage !== undefined;
|
|
258
|
-
const hasReplaySampling =
|
|
259
|
-
opts.replayOptions?.samplingPercentage !== undefined ||
|
|
260
|
-
opts.sessionReplays?.sampling?.percentage !== undefined;
|
|
261
239
|
|
|
262
240
|
if (opts.errorTracking?.enabled ?? opts.trackErrors) {
|
|
241
|
+
const { default: ErrorTracker } = await import("./error");
|
|
263
242
|
new ErrorTracker({
|
|
264
243
|
siteKey: opts.siteKey,
|
|
265
244
|
endpoint: this.endpoint,
|
|
@@ -269,14 +248,14 @@ export class WebAnalytics {
|
|
|
269
248
|
page: location.pathname,
|
|
270
249
|
referrer: document.referrer || null,
|
|
271
250
|
}),
|
|
251
|
+
getAnonymousId: getAnonymousIdFn,
|
|
252
|
+
getSessionId: getOrCreateSessionId,
|
|
253
|
+
sendData,
|
|
272
254
|
}).start();
|
|
273
255
|
this.log("error loaded");
|
|
274
256
|
}
|
|
275
|
-
if (
|
|
276
|
-
|
|
277
|
-
opts.webVitals?.enabled ??
|
|
278
|
-
hasWebVitalsSampling
|
|
279
|
-
) {
|
|
257
|
+
if (opts.trackWebVitals) {
|
|
258
|
+
const { default: WebVitalsTracker } = await import("./web-vitals");
|
|
280
259
|
new WebVitalsTracker({
|
|
281
260
|
siteKey: opts.siteKey,
|
|
282
261
|
endpoint: this.endpoint,
|
|
@@ -284,20 +263,19 @@ export class WebAnalytics {
|
|
|
284
263
|
samplingPercentage: normalizeSamplingPercentage(
|
|
285
264
|
opts.webVitals?.sampling?.percentage,
|
|
286
265
|
),
|
|
266
|
+
getSessionId: getOrCreateSessionId,
|
|
287
267
|
}).start();
|
|
288
268
|
this.log("web-vitals loaded");
|
|
289
269
|
}
|
|
290
|
-
if (opts.
|
|
291
|
-
const
|
|
270
|
+
if (opts.trackReplay) {
|
|
271
|
+
const { default: ReplayTracker } = await import("./replay");
|
|
292
272
|
new ReplayTracker({
|
|
293
273
|
siteKey: opts.siteKey,
|
|
294
274
|
endpoint: this.endpoint,
|
|
295
275
|
debug: this.debug,
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
opts.sessionReplays?.sampling?.percentage,
|
|
300
|
-
),
|
|
276
|
+
getAnonymousId: getAnonymousIdFn,
|
|
277
|
+
getSessionId: getOrCreateSessionId,
|
|
278
|
+
sendData,
|
|
301
279
|
}).start();
|
|
302
280
|
this.log("replay loaded");
|
|
303
281
|
}
|
|
@@ -390,6 +368,14 @@ export class WebAnalytics {
|
|
|
390
368
|
return this.consentMode;
|
|
391
369
|
}
|
|
392
370
|
|
|
371
|
+
getAnonymousId(): string {
|
|
372
|
+
return getAnonymousId(this.isCookielessMode());
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
getSessionId(): string {
|
|
376
|
+
return getOrCreateSessionId();
|
|
377
|
+
}
|
|
378
|
+
|
|
393
379
|
private isCookielessMode(): boolean {
|
|
394
380
|
if (this.options.cookieless) return true;
|
|
395
381
|
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,6 +59,9 @@ 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 {
|
|
@@ -260,7 +269,7 @@ class ErrorTracker {
|
|
|
260
269
|
}
|
|
261
270
|
|
|
262
271
|
const commonData = this.getCommonData();
|
|
263
|
-
const identifier =
|
|
272
|
+
const identifier = this.getAnonymousId();
|
|
264
273
|
const sourcemapsBuild = (
|
|
265
274
|
globalThis as {
|
|
266
275
|
__SOURCEMAPS_BUILD__?: { buildId?: unknown };
|
|
@@ -275,7 +284,7 @@ class ErrorTracker {
|
|
|
275
284
|
const payload: Record<string, unknown> = {
|
|
276
285
|
token: this.siteKey,
|
|
277
286
|
...(identifier ? { userId: identifier } : {}),
|
|
278
|
-
sessionId:
|
|
287
|
+
sessionId: this.getSessionId(),
|
|
279
288
|
...(buildId ? { buildId } : {}),
|
|
280
289
|
data: {
|
|
281
290
|
url: commonData.url as string,
|
|
@@ -292,7 +301,7 @@ class ErrorTracker {
|
|
|
292
301
|
console.log("[ErrorTracker] Payload:", payloadString);
|
|
293
302
|
}
|
|
294
303
|
|
|
295
|
-
|
|
304
|
+
this.sendData({
|
|
296
305
|
url: this.endpoint,
|
|
297
306
|
data: payloadString,
|
|
298
307
|
debug: this.debug,
|
|
@@ -302,17 +311,3 @@ class ErrorTracker {
|
|
|
302
311
|
}
|
|
303
312
|
|
|
304
313
|
export default ErrorTracker;
|
|
305
|
-
|
|
306
|
-
declare global {
|
|
307
|
-
interface Window {
|
|
308
|
-
__FA_ErrorTracker?: typeof ErrorTracker;
|
|
309
|
-
__FA_getAnonymousId?: () => string;
|
|
310
|
-
__FA_getSessionId?: () => string;
|
|
311
|
-
__FA_sendData?: (options: SendDataOptions) => Promise<boolean>;
|
|
312
|
-
__FA_isTrackingDisabled?: () => boolean;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (typeof window !== "undefined") {
|
|
317
|
-
window.__FA_ErrorTracker = ErrorTracker;
|
|
318
|
-
}
|
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;
|
|
@@ -286,10 +285,10 @@ class ReplayTracker {
|
|
|
286
285
|
const eventsToSend = this.events;
|
|
287
286
|
this.events = [];
|
|
288
287
|
|
|
289
|
-
const anonymousId =
|
|
288
|
+
const anonymousId = this._getAnonymousId();
|
|
290
289
|
const batch = {
|
|
291
290
|
token: this.siteKey,
|
|
292
|
-
sessionId:
|
|
291
|
+
sessionId: this._getSessionId(),
|
|
293
292
|
...(anonymousId ? { identifier: anonymousId } : {}),
|
|
294
293
|
sequence: this.sequenceNumber++,
|
|
295
294
|
timestamp: Date.now(),
|
|
@@ -447,7 +446,7 @@ class ReplayTracker {
|
|
|
447
446
|
const sizeKB = (data.size / 1024).toFixed(1);
|
|
448
447
|
|
|
449
448
|
return (
|
|
450
|
-
|
|
449
|
+
this.sendData({
|
|
451
450
|
url,
|
|
452
451
|
data,
|
|
453
452
|
contentType: isCompressed
|
|
@@ -461,22 +460,8 @@ class ReplayTracker {
|
|
|
461
460
|
}
|
|
462
461
|
|
|
463
462
|
getSessionId(): string | undefined {
|
|
464
|
-
return
|
|
463
|
+
return this._getSessionId();
|
|
465
464
|
}
|
|
466
465
|
}
|
|
467
466
|
|
|
468
467
|
export default ReplayTracker;
|
|
469
|
-
|
|
470
|
-
declare global {
|
|
471
|
-
interface Window {
|
|
472
|
-
__FA_ReplayTracker?: ReplayTrackerConstructor;
|
|
473
|
-
__FA_getAnonymousId?: () => string;
|
|
474
|
-
__FA_getSessionId?: () => string;
|
|
475
|
-
__FA_sendData?: (options: SendDataOptions) => Promise<boolean>;
|
|
476
|
-
__FA_isTrackingDisabled?: () => boolean;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (typeof window !== "undefined") {
|
|
481
|
-
window.__FA_ReplayTracker = ReplayTracker;
|
|
482
|
-
}
|
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 {
|
|
@@ -146,7 +149,7 @@ class WebVitalsTracker {
|
|
|
146
149
|
|
|
147
150
|
return {
|
|
148
151
|
body: JSON.stringify({
|
|
149
|
-
sessionId:
|
|
152
|
+
sessionId: this.getSessionId(),
|
|
150
153
|
vitals,
|
|
151
154
|
metadata: {
|
|
152
155
|
url: window.location.href,
|
|
@@ -186,15 +189,3 @@ class WebVitalsTracker {
|
|
|
186
189
|
}
|
|
187
190
|
|
|
188
191
|
export default WebVitalsTracker;
|
|
189
|
-
|
|
190
|
-
declare global {
|
|
191
|
-
interface Window {
|
|
192
|
-
__FA_WebVitalsTracker?: typeof WebVitalsTracker;
|
|
193
|
-
__FA_getSessionId?: () => string;
|
|
194
|
-
__FA_isTrackingDisabled?: () => boolean;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (typeof window !== "undefined") {
|
|
199
|
-
window.__FA_WebVitalsTracker = WebVitalsTracker;
|
|
200
|
-
}
|