@faststats/web 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @faststats/web
2
2
 
3
+ ## 0.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 5d1cc90: fix: enforce proper minimum duration of 3 seconds
8
+
3
9
  ## 0.1.1
4
10
 
5
11
  ### Patch Changes
@@ -0,0 +1 @@
1
+ import{a as e,c as t,i as n,n as r,o as i,r as a,s as o,t as s}from"./types-B5zbhOWK.js";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,t(this.isCookielessMode());let e=this.options;if(e.errorTracking?.enabled??e.trackErrors){let{default:t}=await import(`./error-CMM8PYcW.js`);new t({siteKey:e.siteKey,endpoint:this.endpoint,debug:this.debug}).start(),this.log(`error loaded`)}if(e.trackWebVitals){let{default:t}=await import(`./web-vitals-DU_i1Ejx.js`);new t({siteKey:e.siteKey,endpoint:this.endpoint,debug:this.debug,samplingPercentage:s(e.webVitals?.sampling?.percentage)}).start(),this.log(`web-vitals loaded`)}if(e.trackReplay){let{default:t}=await import(`./replay-DeRZXYG3.js`);new t({siteKey:e.siteKey,endpoint:this.endpoint,debug:this.debug,...e.replayOptions}).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(g()||this.isCookielessMode())return;let i=e.trim(),a=t.trim();!i||!a||_({url:this.endpoint.replace(/\/v1\/web$/,`/v1/identify`),data:JSON.stringify({token:this.options.siteKey,identifier:r(!1),externalId:i,email:a,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){g()||(e&&i(this.isCookielessMode()),o())}setConsentMode(e){this.consentMode=e,t(this.isCookielessMode())}optIn(){this.setConsentMode(`granted`)}optOut(){this.setConsentMode(`denied`)}getConsentMode(){return this.consentMode}getAnonymousId(){return r(this.isCookielessMode())}getSessionId(){return a()}isCookielessMode(){return this.options.cookieless||this.consentMode===`denied`?!0:this.consentMode===`pending`?this.cookielessWhilePending:!1}send(e,t={}){let n=r(this.isCookielessMode()),i=JSON.stringify({token:this.options.siteKey,...n?{userId:n}:{},sessionId:a(),data:{event:e,page:location.pathname,referrer:document.referrer||null,title:document.title||``,url:location.href,...y(),...t}});this.log(e),_({url:this.endpoint,data:i,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-n()})}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}e()},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{f as a,_ as c,g as i,p as l,l as n,m as o,d as r,h as s,b as t,u};
@@ -0,0 +1,3 @@
1
+ import{n as e,r as t}from"./types-B5zbhOWK.js";import{c as n}from"./analytics-p8pLmlA-.js";var r=class{endpoint;siteKey;debug;flushInterval;maxQueueSize;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}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 r=[];for(let{entry:e,hash:t,count:n}of this.errorCounts.values())r.push({...e,hash:t,count:n});this.errorCounts.clear(),this.debug&&console.log(`[ErrorTracker] Flushing errors:`,r);let i=e(),a=typeof document<`u`?{url:location.href,page:location.pathname,referrer:document.referrer||null}:{url:``,page:``,referrer:null},o=globalThis.__SOURCEMAPS_BUILD__,s=typeof o?.buildId==`string`&&o.buildId.trim().length>0?o.buildId:void 0,c={token:this.siteKey,...i?{userId:i}:{},sessionId:t(),...s?{buildId:s}:{},data:{url:a.url,page:a.page,referrer:a.referrer,title:typeof document<`u`?document.title:``},errors:r},l=JSON.stringify(c);this.debug&&console.log(`[ErrorTracker] Payload:`,l),n({url:this.endpoint,data:l,debug:this.debug,debugPrefix:`[ErrorTracker]`})}};export{r as default};
package/dist/module.d.ts CHANGED
@@ -352,16 +352,6 @@ declare global {
352
352
  }
353
353
  }
354
354
  //#endregion
355
- //#region src/utils/types.d.ts
356
- interface SendDataOptions {
357
- url: string;
358
- data: string | Blob;
359
- contentType?: string;
360
- headers?: Record<string, string>;
361
- debug?: boolean;
362
- debugPrefix?: string;
363
- }
364
- //#endregion
365
355
  //#region src/replay.d.ts
366
356
  interface ReplayTrackerOptions {
367
357
  siteKey: string;
@@ -370,6 +360,7 @@ interface ReplayTrackerOptions {
370
360
  samplingPercentage?: number;
371
361
  flushInterval?: number;
372
362
  maxEvents?: number;
363
+ minReplayLengthMs?: number;
373
364
  sampling?: recordOptions<eventWithTime>["sampling"];
374
365
  slimDOMOptions?: SlimDOMOptions;
375
366
  maskAllInputs?: boolean;
@@ -381,9 +372,16 @@ interface ReplayTrackerOptions {
381
372
  checkoutEveryNms?: number;
382
373
  checkoutEveryNth?: number;
383
374
  recordConsole?: boolean;
384
- getAnonymousId?: () => string;
385
- getSessionId?: () => string;
386
- sendData?: (options: SendDataOptions) => Promise<boolean>;
375
+ }
376
+ //#endregion
377
+ //#region src/utils/types.d.ts
378
+ interface SendDataOptions {
379
+ url: string;
380
+ data: string | Blob;
381
+ contentType?: string;
382
+ headers?: Record<string, string>;
383
+ debug?: boolean;
384
+ debugPrefix?: string;
387
385
  }
388
386
  //#endregion
389
387
  //#region src/analytics.d.ts
package/dist/module.js CHANGED
@@ -1 +1 @@
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};
1
+ import"./types-B5zbhOWK.js";import{a as e,c as t,i as n,l as r,n as i,o as a,r as o,s,t as c,u as l}from"./analytics-p8pLmlA-.js";export{c as WebAnalytics,i as getInstance,o as identify,n as isTrackingDisabled,e as logout,a as optIn,s as optOut,t as sendData,r as setConsentMode,l as trackEvent};
@@ -0,0 +1 @@
1
+ import{n as e,r as t,t as n}from"./types-B5zbhOWK.js";import{c as r}from"./analytics-p8pLmlA-.js";import{getRecordConsolePlugin as i}from"@rrweb/rrweb-plugin-console-record";import{record as a}from"rrweb";var o=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;minLengthFlushScheduled=!1;sequenceNumber=0;pendingBatches=[];isFlushing=!1;compressionSupported=!1;sessionSamplingSeed;minReplayLengthMs;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=n(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.minReplayLengthMs=e.minReplayLengthMs??3e3,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 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=[i()]),this.stopRecording=a(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){if(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),!this.hasReachedMinLength()){this.events=[],this.debug&&console.log(`[Replay] Session too short (${Date.now()-this.startTime}ms), discarding events`);return}this.flush()}}handleEvent(e,t){this.events.push(e),t||this.events.length>=this.maxEvents?this.scheduleFlush():!this.minLengthFlushScheduled&&this.hasReachedMinLength()&&(this.minLengthFlushScheduled=!0,this.scheduleFlush())}hasReachedMinLength(){return this.minReplayLengthMs<=0?!0:Date.now()-this.startTime>=this.minReplayLengthMs}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}if(!this.hasReachedMinLength()){this.debug&&console.log(`[Replay] Too short (${Date.now()-this.startTime}ms), skipping`),this.processPendingBatches();return}if(this.isFlushing)return;this.isFlushing=!0;let n=this.events;this.events=[];let r=e(),i={token:this.siteKey,sessionId:t(),...r?{identifier:r}:{},sequence:this.sequenceNumber++,timestamp:Date.now(),url:window.location.href,events:n};this.debug&&console.log(`[Replay] Sending ${n.length} events (seq: ${i.sequence})`);try{let e,t=!1;if(this.compressionSupported)try{e=await this.compress(JSON.stringify(i)),t=!0}catch{this.debug&&console.warn(`[Replay] Compression failed, using uncompressed`),e=new Blob([JSON.stringify(i)],{type:`application/json`})}else e=new Blob([JSON.stringify(i)],{type:`application/json`});await this.send(e,t)||this.pendingBatches.push({batch:i,isCompressed:t,retries:0})}catch(e){this.debug&&console.warn(`[Replay] Flush error:`,e),this.pendingBatches.push({batch:i,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,i=(e.size/1024).toFixed(1);return r({url:n,data:e,contentType:t?`application/octet-stream`:`application/json`,headers:t?{"Content-Encoding":`gzip`}:void 0,debug:this.debug,debugPrefix:`[Replay] ${i}KB`})??Promise.resolve(!1)}getSessionId(){return t()}};export{o as default};
@@ -0,0 +1 @@
1
+ let e=!1;function t(t){e=t}function n(t){return t??e?``:r()}function r(){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 i(e){return e||typeof localStorage>`u`?``:(localStorage.removeItem(`faststats_anon_id`),r())}function a(){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 o(){return typeof sessionStorage>`u`?``:(sessionStorage.removeItem(`session_id`),sessionStorage.removeItem(`session_timestamp`),sessionStorage.removeItem(`session_start`),a())}function s(){typeof sessionStorage>`u`||sessionStorage.getItem(`session_id`)&&sessionStorage.setItem(`session_timestamp`,Date.now().toString())}function c(){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()}function l(e){return typeof e!=`number`||!Number.isFinite(e)?100:Math.max(0,Math.min(100,e))}export{s as a,t as c,c as i,n,i as o,a as r,o as s,l as t};
@@ -0,0 +1 @@
1
+ import{r as e,t}from"./types-B5zbhOWK.js";import{onCLS as n,onFCP as r,onINP as i,onLCP as a,onTTFB as o}from"web-vitals/attribution";var s=class{endpoint;siteKey;debug;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=t(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 t=Array.from(this.metricsMap.entries()).map(([e,t])=>({metric:e,value:t.value,attributes:t.attributes}));return{body:JSON.stringify({sessionId:e(),vitals:t,metadata:{url:window.location.href}}),count:t.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{s as default};
package/package.json CHANGED
@@ -19,7 +19,7 @@
19
19
  "publishConfig": {
20
20
  "access": "public"
21
21
  },
22
- "version": "0.1.1",
22
+ "version": "0.1.2",
23
23
  "scripts": {
24
24
  "build": "tsdown",
25
25
  "dev": "bun run build",
package/src/analytics.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  refreshSessionTimestamp,
7
7
  resetAnonymousId,
8
8
  resetSessionId,
9
+ setCookielessMode,
9
10
  } from "./utils/identifiers";
10
11
  import {
11
12
  normalizeSamplingPercentage,
@@ -234,7 +235,7 @@ export class WebAnalytics {
234
235
  this.started = true;
235
236
  _instance = this;
236
237
 
237
- const getAnonymousIdFn = () => getAnonymousId(this.isCookielessMode());
238
+ setCookielessMode(this.isCookielessMode());
238
239
  const opts = this.options;
239
240
 
240
241
  if (opts.errorTracking?.enabled ?? opts.trackErrors) {
@@ -243,14 +244,6 @@ export class WebAnalytics {
243
244
  siteKey: opts.siteKey,
244
245
  endpoint: this.endpoint,
245
246
  debug: this.debug,
246
- getCommonData: () => ({
247
- url: location.href,
248
- page: location.pathname,
249
- referrer: document.referrer || null,
250
- }),
251
- getAnonymousId: getAnonymousIdFn,
252
- getSessionId: getOrCreateSessionId,
253
- sendData,
254
247
  }).start();
255
248
  this.log("error loaded");
256
249
  }
@@ -263,7 +256,6 @@ export class WebAnalytics {
263
256
  samplingPercentage: normalizeSamplingPercentage(
264
257
  opts.webVitals?.sampling?.percentage,
265
258
  ),
266
- getSessionId: getOrCreateSessionId,
267
259
  }).start();
268
260
  this.log("web-vitals loaded");
269
261
  }
@@ -273,9 +265,7 @@ export class WebAnalytics {
273
265
  siteKey: opts.siteKey,
274
266
  endpoint: this.endpoint,
275
267
  debug: this.debug,
276
- getAnonymousId: getAnonymousIdFn,
277
- getSessionId: getOrCreateSessionId,
278
- sendData,
268
+ ...opts.replayOptions,
279
269
  }).start();
280
270
  this.log("replay loaded");
281
271
  }
@@ -354,6 +344,7 @@ export class WebAnalytics {
354
344
 
355
345
  setConsentMode(mode: ConsentMode): void {
356
346
  this.consentMode = mode;
347
+ setCookielessMode(this.isCookielessMode());
357
348
  }
358
349
 
359
350
  optIn(): void {
package/src/error.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { SendDataOptions } from "./utils/types";
1
+ import { sendData } from "./analytics";
2
+ import { getAnonymousId, getOrCreateSessionId } from "./utils/identifiers";
2
3
 
3
4
  export type ErrorEntry = {
4
5
  error: string;
@@ -27,10 +28,6 @@ export interface ErrorTrackingOptions {
27
28
  debug?: boolean;
28
29
  flushInterval?: number;
29
30
  maxQueueSize?: number;
30
- getCommonData?: () => Record<string, unknown>;
31
- getAnonymousId?: () => string;
32
- getSessionId?: () => string;
33
- sendData?: (options: SendDataOptions) => Promise<boolean>;
34
31
  }
35
32
 
36
33
  class ErrorTracker {
@@ -39,10 +36,6 @@ class ErrorTracker {
39
36
  private readonly debug: boolean;
40
37
  private readonly flushInterval: number;
41
38
  private readonly maxQueueSize: number;
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>;
46
39
 
47
40
  private readonly handledErrors = new WeakSet<Error>();
48
41
  private readonly errorCounts = new Map<
@@ -58,10 +51,6 @@ class ErrorTracker {
58
51
  this.debug = options.debug ?? false;
59
52
  this.flushInterval = options.flushInterval ?? 5000;
60
53
  this.maxQueueSize = options.maxQueueSize ?? 50;
61
- this.getCommonData = options.getCommonData ?? (() => ({}));
62
- this.getAnonymousId = options.getAnonymousId ?? (() => "");
63
- this.getSessionId = options.getSessionId ?? (() => "");
64
- this.sendData = options.sendData ?? (async () => false);
65
54
  }
66
55
 
67
56
  start(): void {
@@ -268,8 +257,15 @@ class ErrorTracker {
268
257
  console.log("[ErrorTracker] Flushing errors:", errors);
269
258
  }
270
259
 
271
- const commonData = this.getCommonData();
272
- const identifier = this.getAnonymousId();
260
+ const identifier = getAnonymousId();
261
+ const commonData =
262
+ typeof document !== "undefined"
263
+ ? {
264
+ url: location.href,
265
+ page: location.pathname,
266
+ referrer: document.referrer || null,
267
+ }
268
+ : { url: "", page: "", referrer: null };
273
269
  const sourcemapsBuild = (
274
270
  globalThis as {
275
271
  __SOURCEMAPS_BUILD__?: { buildId?: unknown };
@@ -284,7 +280,7 @@ class ErrorTracker {
284
280
  const payload: Record<string, unknown> = {
285
281
  token: this.siteKey,
286
282
  ...(identifier ? { userId: identifier } : {}),
287
- sessionId: this.getSessionId(),
283
+ sessionId: getOrCreateSessionId(),
288
284
  ...(buildId ? { buildId } : {}),
289
285
  data: {
290
286
  url: commonData.url as string,
@@ -301,7 +297,7 @@ class ErrorTracker {
301
297
  console.log("[ErrorTracker] Payload:", payloadString);
302
298
  }
303
299
 
304
- this.sendData({
300
+ sendData({
305
301
  url: this.endpoint,
306
302
  data: payloadString,
307
303
  debug: this.debug,
package/src/replay.ts CHANGED
@@ -3,10 +3,9 @@ import type { eventWithTime, listenerHandler } from "@rrweb/types";
3
3
  import { record } from "rrweb";
4
4
  import type { recordOptions } from "rrweb/typings/types";
5
5
  import type { SlimDOMOptions } from "rrweb-snapshot";
6
- import {
7
- normalizeSamplingPercentage,
8
- type SendDataOptions,
9
- } from "./utils/types";
6
+ import { sendData } from "./analytics";
7
+ import { getAnonymousId, getOrCreateSessionId } from "./utils/identifiers";
8
+ import { normalizeSamplingPercentage } from "./utils/types";
10
9
 
11
10
  export interface ReplayTrackerOptions {
12
11
  siteKey: string;
@@ -15,6 +14,7 @@ export interface ReplayTrackerOptions {
15
14
  samplingPercentage?: number;
16
15
  flushInterval?: number;
17
16
  maxEvents?: number;
17
+ minReplayLengthMs?: number;
18
18
  sampling?: recordOptions<eventWithTime>["sampling"];
19
19
  slimDOMOptions?: SlimDOMOptions;
20
20
  maskAllInputs?: boolean;
@@ -26,9 +26,6 @@ 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>;
32
29
  }
33
30
 
34
31
  interface PendingBatch {
@@ -68,15 +65,13 @@ class ReplayTracker {
68
65
  private stopRecording: listenerHandler | undefined = undefined;
69
66
  private started = false;
70
67
  private startTime: number = 0;
68
+ private minLengthFlushScheduled = false;
71
69
  private sequenceNumber = 0;
72
70
  private pendingBatches: PendingBatch[] = [];
73
71
  private isFlushing = false;
74
72
  private compressionSupported = false;
75
73
  private readonly sessionSamplingSeed: number;
76
- private readonly minReplayLengthMs = 3000;
77
- private readonly _getAnonymousId: () => string;
78
- private readonly _getSessionId: () => string;
79
- private readonly sendData: (options: SendDataOptions) => Promise<boolean>;
74
+ private readonly minReplayLengthMs: number;
80
75
 
81
76
  constructor(options: ReplayTrackerOptions) {
82
77
  this.siteKey = options.siteKey;
@@ -120,10 +115,8 @@ class ReplayTracker {
120
115
  this.checkoutEveryNms = options.checkoutEveryNms ?? 60_000;
121
116
  this.checkoutEveryNth = options.checkoutEveryNth;
122
117
  this.recordConsole = options.recordConsole ?? true;
118
+ this.minReplayLengthMs = options.minReplayLengthMs ?? 3000;
123
119
  this.sessionSamplingSeed = Math.random() * 100;
124
- this._getAnonymousId = options.getAnonymousId ?? (() => "");
125
- this._getSessionId = options.getSessionId ?? (() => "");
126
- this.sendData = options.sendData ?? (async () => false);
127
120
 
128
121
  if (typeof window !== "undefined") {
129
122
  this.compressionSupported = "CompressionStream" in window;
@@ -210,12 +203,11 @@ class ReplayTracker {
210
203
  this.handleVisibilityChange,
211
204
  );
212
205
 
213
- const elapsedTime = Date.now() - this.startTime;
214
- if (elapsedTime < this.minReplayLengthMs) {
206
+ if (!this.hasReachedMinLength()) {
215
207
  this.events = [];
216
208
  if (this.debug) {
217
209
  console.log(
218
- `[Replay] Session too short (${elapsedTime}ms), discarding events`,
210
+ `[Replay] Session too short (${Date.now() - this.startTime}ms), discarding events`,
219
211
  );
220
212
  }
221
213
  return;
@@ -231,9 +223,17 @@ class ReplayTracker {
231
223
  this.scheduleFlush();
232
224
  } else if (this.events.length >= this.maxEvents) {
233
225
  this.scheduleFlush();
226
+ } else if (!this.minLengthFlushScheduled && this.hasReachedMinLength()) {
227
+ this.minLengthFlushScheduled = true;
228
+ this.scheduleFlush();
234
229
  }
235
230
  }
236
231
 
232
+ private hasReachedMinLength(): boolean {
233
+ if (this.minReplayLengthMs <= 0) return true;
234
+ return Date.now() - this.startTime >= this.minReplayLengthMs;
235
+ }
236
+
237
237
  private scheduleFlush(): void {
238
238
  if (this.isFlushing || this.events.length === 0) return;
239
239
 
@@ -270,10 +270,11 @@ class ReplayTracker {
270
270
  return;
271
271
  }
272
272
 
273
- const elapsedTime = Date.now() - this.startTime;
274
- if (elapsedTime < this.minReplayLengthMs) {
273
+ if (!this.hasReachedMinLength()) {
275
274
  if (this.debug) {
276
- console.log(`[Replay] Too short (${elapsedTime}ms), skipping`);
275
+ console.log(
276
+ `[Replay] Too short (${Date.now() - this.startTime}ms), skipping`,
277
+ );
277
278
  }
278
279
  this.processPendingBatches();
279
280
  return;
@@ -285,10 +286,10 @@ class ReplayTracker {
285
286
  const eventsToSend = this.events;
286
287
  this.events = [];
287
288
 
288
- const anonymousId = this._getAnonymousId();
289
+ const anonymousId = getAnonymousId();
289
290
  const batch = {
290
291
  token: this.siteKey,
291
- sessionId: this._getSessionId(),
292
+ sessionId: getOrCreateSessionId(),
292
293
  ...(anonymousId ? { identifier: anonymousId } : {}),
293
294
  sequence: this.sequenceNumber++,
294
295
  timestamp: Date.now(),
@@ -446,7 +447,7 @@ class ReplayTracker {
446
447
  const sizeKB = (data.size / 1024).toFixed(1);
447
448
 
448
449
  return (
449
- this.sendData({
450
+ sendData({
450
451
  url,
451
452
  data,
452
453
  contentType: isCompressed
@@ -460,7 +461,7 @@ class ReplayTracker {
460
461
  }
461
462
 
462
463
  getSessionId(): string | undefined {
463
- return this._getSessionId();
464
+ return getOrCreateSessionId();
464
465
  }
465
466
  }
466
467
 
@@ -1,7 +1,14 @@
1
1
  const SESSION_TIMEOUT = 30 * 60 * 1000;
2
2
 
3
+ let cookielessMode = false;
4
+
5
+ export function setCookielessMode(mode: boolean): void {
6
+ cookielessMode = mode;
7
+ }
8
+
3
9
  export function getAnonymousId(cookieless?: boolean): string {
4
- if (cookieless) return "";
10
+ const useCookieless = cookieless ?? cookielessMode;
11
+ if (useCookieless) return "";
5
12
  return getOrCreateAnonymousId();
6
13
  }
7
14
 
package/src/web-vitals.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  onLCP,
7
7
  onTTFB,
8
8
  } from "web-vitals/attribution";
9
+ import { getOrCreateSessionId } from "./utils/identifiers";
9
10
  import { normalizeSamplingPercentage } from "./utils/types";
10
11
 
11
12
  export interface WebVitalsOptions {
@@ -13,7 +14,6 @@ export interface WebVitalsOptions {
13
14
  endpoint?: string;
14
15
  debug?: boolean;
15
16
  samplingPercentage?: number;
16
- getSessionId?: () => string;
17
17
  }
18
18
 
19
19
  type MetricName = "CLS" | "INP" | "LCP" | "FCP" | "TTFB";
@@ -34,7 +34,6 @@ class WebVitalsTracker {
34
34
  private readonly sessionSamplingSeed: number;
35
35
  private readonly metricsMap = new Map<MetricName, MetricState>();
36
36
  private flushed = false;
37
- private readonly getSessionId: () => string;
38
37
 
39
38
  constructor(options: WebVitalsOptions) {
40
39
  this.siteKey = options.siteKey;
@@ -46,7 +45,6 @@ class WebVitalsTracker {
46
45
  options.samplingPercentage,
47
46
  );
48
47
  this.sessionSamplingSeed = Math.random() * 100;
49
- this.getSessionId = options.getSessionId ?? (() => "");
50
48
  }
51
49
 
52
50
  private getVitalsEndpoint(baseEndpoint: string): string {
@@ -149,7 +147,7 @@ class WebVitalsTracker {
149
147
 
150
148
  return {
151
149
  body: JSON.stringify({
152
- sessionId: this.getSessionId(),
150
+ sessionId: getOrCreateSessionId(),
153
151
  vitals,
154
152
  metadata: {
155
153
  url: window.location.href,
@@ -1,3 +0,0 @@
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};
@@ -1 +0,0 @@
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};
@@ -1 +0,0 @@
1
- function e(e){return typeof e!=`number`||!Number.isFinite(e)?100:Math.max(0,Math.min(100,e))}export{e as t};
@@ -1 +0,0 @@
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};