@faststats/web 0.2.5 → 0.2.7

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,17 @@
1
1
  # @faststats/web
2
2
 
3
+ ## 0.2.7
4
+
5
+ ### Patch Changes
6
+
7
+ - 08f3719: fix: don't rotate session_id while replay
8
+
9
+ ## 0.2.6
10
+
11
+ ### Patch Changes
12
+
13
+ - 7d57d50: fix: replay flushing issues
14
+
3
15
  ## 0.2.5
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1 @@
1
+ import{a as e,c as t,d as n,f as r,i,l as a,m as o,n as s,o as c,r as l,s as u,t as d,u as f}from"./types-Df70G0eM.js";async function p(e,t){if(t.projectToken&&t.projectId)throw Error(`provide either projectToken or projectId, not both`);if(t.identifier&&t.externalId)throw Error(`provide either serverId or externalId, not both`);let n=t.projectToken,r=t.projectId,i=t.identifier,o=t.externalId;if(!n&&!r)throw Error(`feature flag check requires projectToken or projectId`);if(!i&&!o)throw Error(`feature flag check requires serverId or externalId`);let s={key:e,...r?{projectId:r}:{},...i?{identifier:i}:{externalId:o}};t.attributes&&Object.keys(t.attributes).length>0&&(s.attributes=t.attributes);let c={"Content-Type":`application/json`};n&&(c.Authorization=`Bearer ${n}`);let l=a(t.baseUrl),u=await fetch(l,{method:`POST`,headers:c,body:JSON.stringify(s),credentials:`omit`,signal:t.signal});if(!u.ok){let e=``;try{let t=await u.json();typeof t.error==`string`&&t.error.length>0&&(e=` — ${t.error}`)}catch{}throw Error(`feature flag check failed: HTTP ${u.status}${e}`)}return await u.json()}function m(e){return e.replayOptions?.samplingPercentage!==void 0||e.sessionReplays?.sampling?.percentage!==void 0}function h(e,t,n){if(!(e.sessionReplays?.enabled??e.trackReplay??m(e)))return null;let r=e.replayOptions??{};return{siteKey:e.siteKey,baseUrl:t,debug:n,...r,samplingPercentage:d(r.samplingPercentage??e.sessionReplays?.sampling?.percentage)}}let g=null,_;function v(){return g}function y(e,t){typeof window>`u`||E()||g?.track(e,t??{})}function b(e,t,n){typeof window>`u`||E()||g?.identify(e,t,n??{})}function x(e=!0){typeof window>`u`||E()||g?.logout(e)}function S(e){if(g){g.setConsentMode(e);return}_=e}function C(){S(`granted`)}function w(){S(`denied`)}function T(e){typeof window>`u`||E()||g?.reportError(e)}function E(){if(typeof localStorage>`u`)return!1;let e=localStorage.getItem(`disable-faststats`);return e===`true`||e===`1`}async function D(e){let{url:t,data:n,contentType:r=`application/json`,headers:i={},debug:a=!1,debugPrefix:o=`[Analytics]`,useBeacon:s=!0,keepalive:c=!0}=e;if(s&&typeof navigator<`u`&&typeof navigator.sendBeacon==`function`)try{let e=n instanceof Blob?n:typeof Blob<`u`?new Blob([n],{type:r}):n;if(navigator.sendBeacon(t,e))return a&&console.log(`${o} Sent via beacon`),!0}catch{}if(typeof fetch!=`function`)return a&&console.warn(`${o} Failed to send`),!1;try{let e=await fetch(t,{method:`POST`,body:n,headers:{"Content-Type":r,...i},keepalive:c}),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 O(e){for(;e;){if(e instanceof HTMLAnchorElement&&e.href)return e;e=e.parentNode}return null}function k(){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 A=class{webEndpoint;baseUrl;featureFlagsBaseUrl;debug;started=!1;destroyed=!1;pageKey=``;navTimer=null;heartbeatTimer=null;scrollDepth=0;pageEntryTime=0;pagePath=``;pageUrl=``;pageHash=``;hasLeftCurrentPage=!1;scrollHandler=null;consentMode;cookielessWhilePending;cleanupCallbacks=[];childTrackers=[];pendingReportedErrors=[];errorTracker=null;handleVisibilityChange=()=>{document.visibilityState===`hidden`?(this.leavePage(),this.stopHeartbeat()):this.startHeartbeat()};handlePageHide=()=>{this.leavePage()};handlePopState=()=>{this.navigate()};handleHashChange=()=>{this.navigate()};constructor(e){this.options=e,this.baseUrl=n(e.baseUrl),this.featureFlagsBaseUrl=r(e.featureFlagsBaseUrl),this.webEndpoint=o(this.baseUrl),this.debug=e.debug??!1,this.consentMode=e.consent?.mode??`granted`,this.cookielessWhilePending=e.consent?.cookielessWhilePending??!0,_!==void 0&&(this.consentMode=_,_=void 0),(e.autoTrack??!0)&&this.init()}log(e){this.debug&&console.log(`[Analytics] ${e}`)}init(){if(!(typeof window>`u`)){if(E()){this.log(`disabled`);return}g=this,t(this.isCookielessMode()),setTimeout(()=>void this.start(),0)}}registerCleanup(e){this.cleanupCallbacks.push(e)}addWindowListener(e,t){window.addEventListener(e,t),this.registerCleanup(()=>window.removeEventListener(e,t))}addDocumentListener(e,t){document.addEventListener(e,t),this.registerCleanup(()=>document.removeEventListener(e,t))}patchHistory(){let e=history.pushState.bind(history),t=history.replaceState.bind(history);history.pushState=(t,n,r)=>{e(t,n,r),this.navigate()},history.replaceState=(e,n,r)=>{t(e,n,r),this.navigate()},this.registerCleanup(()=>{history.pushState=e,history.replaceState=t})}ensureStarted(){return typeof window>`u`||this.destroyed||E()?!1:(this.started||this.start(),!0)}stopHeartbeat(){this.heartbeatTimer&&=(clearInterval(this.heartbeatTimer),null)}stopNavigationTimer(){this.navTimer&&=(clearTimeout(this.navTimer),null)}stopChildTrackers(){for(let e of this.childTrackers.splice(0))e.stop?.();this.errorTracker=null}registerChildTracker(e){return!this.started||this.destroyed||g!==this?(e.stop?.(),!1):(this.childTrackers.push(e),!0)}async startErrorTracker(){try{let{default:e}=await import(`./error-asweBGYd.js`);if(!this.started||this.destroyed||g!==this)return;let t=new e({siteKey:this.options.siteKey,baseUrl:this.baseUrl,debug:this.debug});if(t.start(),!this.registerChildTracker(t))return;for(this.errorTracker=t;this.pendingReportedErrors.length>0;){let e=this.pendingReportedErrors.shift();e&&t.captureError(e)}this.log(`error loaded`)}catch(e){this.log(`failed to initialize error tracker: ${String(e)}`)}}async startWebVitalsTracker(){try{let{default:e}=await import(`./web-vitals-gcUR-TX_.js`);if(!this.started||this.destroyed||g!==this)return;let t=new e({siteKey:this.options.siteKey,baseUrl:this.baseUrl,debug:this.debug,samplingPercentage:d(this.options.webVitals?.sampling?.percentage)});if(t.start(),!this.registerChildTracker(t))return;this.log(`web-vitals loaded`)}catch(e){this.log(`failed to initialize web-vitals tracker: ${String(e)}`)}}async startReplayTracker(e){try{let{default:t}=await import(`./replay-DUA5c3Jf.js`);if(!this.started||this.destroyed||g!==this)return;let n=new t(e);if(n.start(),!this.registerChildTracker(n))return;this.log(`replay loaded`)}catch(e){this.log(`failed to initialize replay tracker: ${String(e)}`)}}async start(){if(this.started||this.destroyed||typeof window>`u`)return;if(g&&g!==this){this.log(`already started by another instance`);return}if(E()){this.log(`disabled`);return}this.started=!0,g=this,t(this.isCookielessMode()),l();let e=this.options,n=h(e,this.baseUrl,this.debug);n&&this.startReplayTracker(n),e.trackErrors&&this.startErrorTracker(),e.trackWebVitals&&this.startWebVitalsTracker(),this.enterPage(),this.pageview({trigger:`load`}),this.links(),this.trackScroll(),this.startHeartbeat(),this.addDocumentListener(`visibilitychange`,this.handleVisibilityChange),this.addWindowListener(`pagehide`,this.handlePageHide),this.addWindowListener(`popstate`,this.handlePopState),e.trackHash&&this.addWindowListener(`hashchange`,this.handleHashChange),this.patchHistory()}destroy(){if(!this.destroyed){for(this.started&&typeof window<`u`&&this.leavePage(),this.pendingReportedErrors.length=0,this.stopNavigationTimer(),this.stopHeartbeat(),this.scrollHandler&&typeof window<`u`&&(window.removeEventListener(`scroll`,this.scrollHandler),this.scrollHandler=null);this.cleanupCallbacks.length>0;)this.cleanupCallbacks.pop()?.();this.stopChildTrackers(),g===this&&(g=null),this.started=!1,this.destroyed=!0}}pageview(e={}){if(!this.ensureStarted())return;let t=`${location.pathname}|${this.options.trackHash??!1?location.hash:``}`;t!==this.pageKey&&(this.pageKey=t,this.send(`pageview`,e))}track(e,t={}){this.ensureStarted()&&this.send(e,t)}identify(e,t,n={}){if(!this.ensureStarted()||this.isCookielessMode())return;let r=e.trim(),i=t.trim();!r||!i||D({url:f(this.baseUrl),data:JSON.stringify({token:this.options.siteKey,identifier:s(!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){this.ensureStarted()&&(e&&c(this.isCookielessMode()),u())}setConsentMode(e){this.consentMode=e,t(this.isCookielessMode())}optIn(){this.setConsentMode(`granted`)}optOut(){this.setConsentMode(`denied`)}getConsentMode(){return this.consentMode}getAnonymousId(){return s(this.isCookielessMode())}getSessionId(){return l()}async checkFeatureFlag(e,t,n){if(typeof window>`u`||E())return{value:`false`};let r=n?.externalId?.trim();if(r)return p(e,{baseUrl:this.featureFlagsBaseUrl,projectToken:this.options.siteKey,externalId:r,attributes:t,signal:n?.signal});let i=this.getAnonymousId();return i?p(e,{baseUrl:this.featureFlagsBaseUrl,projectToken:this.options.siteKey,identifier:i,attributes:t,signal:n?.signal}):{value:`false`}}reportError(e){if(this.destroyed||typeof window>`u`||E()||!(this.options.trackErrors??!1)||!this.ensureStarted())return;let t=this.errorTracker;if(t){t.captureError(e);return}this.pendingReportedErrors.length>=50&&this.pendingReportedErrors.shift(),this.pendingReportedErrors.push(e)}isCookielessMode(){return this.options.cookieless||this.consentMode===`denied`?!0:this.consentMode===`pending`?this.cookielessWhilePending:!1}send(e,t={}){if(typeof window>`u`||this.destroyed||E())return;let n=s(this.isCookielessMode()),r=JSON.stringify({token:this.options.siteKey,...n?{userId:n}:{},sessionId:l(),data:{event:e,page:location.pathname,referrer:document.referrer||null,title:document.title||``,url:location.href,...k(),...t}});this.log(e),D({url:this.webEndpoint,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.destroyed||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-i()})}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.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{if(document.visibilityState===`hidden`){this.stopHeartbeat();return}e()},300*1e3)}navigate(){!this.started||this.destroyed||(this.stopNavigationTimer(),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))}links(){let e=e=>{let t=O(e.target);t&&t.host!==location.host&&this.track(`outbound_link`,{outbound_link:t.href})};this.addDocumentListener(`click`,e),this.addDocumentListener(`auxclick`,e)}};export{x as a,T as c,y as d,p as f,E as i,D as l,v as n,C as o,b as r,w as s,A as t,S as u};
@@ -1,2 +1,2 @@
1
- import{m as e,n as t,r as n}from"./types-Df70G0eM.js";import{l as r}from"./analytics-CEej2XLw.js";const i=/(?:^|\()(chrome|moz|ms-browser|safari-web)-extension:\/\//,a=e=>e?.split(`
1
+ import{m as e,n as t,r as n}from"./types-Df70G0eM.js";import{l as r}from"./analytics-Ct-lghlf.js";const i=/(?:^|\()(chrome|moz|ms-browser|safari-web)-extension:\/\//,a=e=>e?.split(`
2
2
  `).map(e=>e.trim()).filter(Boolean);function o(e){if(e instanceof Error)return{error:e.name?.trim()||`Error`,message:e.message,stack:a(e.stack),cause:o(e.cause)};if(typeof e==`string`)return{error:`Error`,message:e}}function s(e){return e?`${e.error}\0${e.message??``}\0${s(e.cause)}`:``}function c(e){let t=[e.kind,e.handled?`handled`:`unhandled`,e.message,e.filename??``,e.lineno??``,s(e.cause)].join(`\0`),n=2166136261,r=3598710387;for(let e=0;e<t.length;e++){let i=t.charCodeAt(e);n=Math.imul(n^i,16777619),r=Math.imul(r^i,2246822519)}return`err_${(n>>>0).toString(16).padStart(8,`0`)}${(r>>>0).toString(16).padStart(8,`0`)}`}var l=class{endpoint;handled=new WeakSet;queue=new Map;timer=null;started=!1;constructor(t){this.options=t,this.endpoint=e(t.baseUrl)}get debug(){return this.options.debug??!1}get flushInterval(){return this.options.flushInterval??5e3}get maxQueueSize(){return this.options.maxQueueSize??50}log(...e){this.debug&&console.log(`[ErrorTracker]`,...e)}start(){this.started||typeof window>`u`||(this.started=!0,window.addEventListener(`error`,this.onError),window.addEventListener(`unhandledrejection`,this.onRejection),document.addEventListener(`visibilitychange`,this.onVisibilityChange),window.addEventListener(`pagehide`,this.flush),this.timer=setInterval(this.flush,this.flushInterval),this.log(`Started listening for errors`))}stop(){!this.started||typeof window>`u`||(this.started=!1,window.removeEventListener(`error`,this.onError),window.removeEventListener(`unhandledrejection`,this.onRejection),document.removeEventListener(`visibilitychange`,this.onVisibilityChange),window.removeEventListener(`pagehide`,this.flush),this.timer&&clearInterval(this.timer),this.timer=null,this.flush(),this.log(`Stopped listening for errors`))}captureError(e){this.record({kind:`error`,message:e.message,stack:e.stack,handled:!0,cause:o(e.cause)},e)}onError=e=>{let t=e.error;this.record({kind:`error`,message:e.message||`Unknown error`,filename:e.filename||void 0,lineno:e.lineno||void 0,stack:t instanceof Error?t.stack:void 0,handled:!1,cause:t instanceof Error?o(t.cause):void 0},t)};onRejection=e=>{let t=e.reason;this.record({kind:`unhandledrejection`,message:t.message||`Unhandled promise rejection`,stack:t.stack||void 0,handled:!1,cause:o(t.cause)},t)};onVisibilityChange=()=>{document.visibilityState===`hidden`&&this.flush()};record(e,t){if(t instanceof Error){if(this.handled.has(t)){this.log(`Skipping duplicate:`,t.message);return}this.handled.add(t)}if(i.test(`${e.filename??``}\n${e.stack??``}`))return;let n=c(e),r=this.queue.get(n);r?r.count+=1:this.queue.set(n,{hash:n,count:1,error:e.kind===`unhandledrejection`?`UnhandledRejection`:`Error`,message:e.message,stack:a(e.stack),handled:e.handled,...e.cause?{cause:e.cause}:{}}),this.log(`Captured error:`,e),this.queue.size>=this.maxQueueSize&&this.flush()}flush=()=>{if(this.queue.size===0)return;let e=[...this.queue.values()];this.queue.clear();let i=t(),a=globalThis.__SOURCEMAPS_BUILD__,o=typeof a?.buildId==`string`&&a.buildId.trim()?a.buildId:void 0,s=JSON.stringify({token:this.options.siteKey,...i?{userId:i}:{},sessionId:n(),...o?{buildId:o}:{},data:{url:location.href,page:location.pathname,referrer:document.referrer||null,title:document.title},errors:e});this.log(`Flushing errors:`,e),this.log(`Payload:`,s),r({url:this.endpoint,data:s,debug:this.debug,debugPrefix:`[ErrorTracker]`})}};export{l as default};
package/dist/module.d.ts CHANGED
@@ -374,6 +374,7 @@ interface ReplayTrackerOptions {
374
374
  siteKey: string;
375
375
  baseUrl?: string;
376
376
  debug?: boolean;
377
+ compress?: boolean;
377
378
  samplingPercentage?: number;
378
379
  flushInterval?: number;
379
380
  maxEvents?: number;
@@ -492,6 +493,10 @@ declare class WebAnalytics {
492
493
  private stopHeartbeat;
493
494
  private stopNavigationTimer;
494
495
  private stopChildTrackers;
496
+ private registerChildTracker;
497
+ private startErrorTracker;
498
+ private startWebVitalsTracker;
499
+ private startReplayTracker;
495
500
  start(): Promise<void>;
496
501
  destroy(): void;
497
502
  pageview(extra?: Dict): void;
package/dist/module.js CHANGED
@@ -1 +1 @@
1
- import{f as e,l as t}from"./types-Df70G0eM.js";import{a as n,c as r,d as i,f as a,i as o,l as s,n as c,o as l,r as u,s as d,t as f,u as p}from"./analytics-CEej2XLw.js";export{f as WebAnalytics,t as featureFlagsCheckUrl,a as fetchFeatureFlagEvaluation,c as getInstance,u as identify,o as isTrackingDisabled,n as logout,e as normalizeFeatureFlagsBaseUrl,l as optIn,d as optOut,r as reportError,s as sendData,p as setConsentMode,i as trackEvent};
1
+ import{f as e,l as t}from"./types-Df70G0eM.js";import{a as n,c as r,d as i,f as a,i as o,l as s,n as c,o as l,r as u,s as d,t as f,u as p}from"./analytics-Ct-lghlf.js";export{f as WebAnalytics,t as featureFlagsCheckUrl,a as fetchFeatureFlagEvaluation,c as getInstance,u as identify,o as isTrackingDisabled,n as logout,e as normalizeFeatureFlagsBaseUrl,l as optIn,d as optOut,r as reportError,s as sendData,p as setConsentMode,i as trackEvent};
@@ -0,0 +1 @@
1
+ import{i as e,n as t,p as n,r,t as i}from"./types-Df70G0eM.js";import{l as a}from"./analytics-Ct-lghlf.js";import{getRecordConsolePlugin as o}from"@rrweb/rrweb-plugin-console-record";import{getRecordSequentialIdPlugin as s}from"@rrweb/rrweb-plugin-sequential-id-record";import{EventType as c,record as l}from"rrweb";const u={mousemove:50,mouseInteraction:!0,scroll:150,media:800,input:`last`},d={script:!0,comment:!0,headFavicon:!0,headWhitespace:!0,headMetaDescKeywords:!0,headMetaSocial:!0,headMetaRobots:!0,headMetaHttpEquiv:!0,headMetaAuthorship:!0};var f=class{endpoint;compressionSupported=typeof window<`u`&&`CompressionStream`in window;sampled;events=[];pending=[];sessionId;started=!1;startTime=0;sequence=0;intervalId=null;flushTask=null;retryTask=null;minLengthFlushTask=null;stopRecording;sending=!1;constructor(e){this.options=e,this.endpoint=n(e.baseUrl),this.sampled=Math.random()*100<i(e.samplingPercentage)}get debug(){return this.options.debug??!1}get flushInterval(){return this.options.flushInterval??1e4}get maxEvents(){return this.options.maxEvents??500}get maxPendingBatches(){return this.options.maxPendingBatches??30}get minReplayLengthMs(){return this.options.minReplayLengthMs??3e3}get shouldCompress(){return this.options.compress??!1}log(...e){this.debug&&console.log(`[Replay]`,...e)}start(){this.started||typeof window>`u`||!this.sampled||(this.started=!0,this.sessionId=r(),this.startTime=e(),this.stopRecording=l({emit:this.onEvent,sampling:this.options.sampling??u,slimDOMOptions:this.options.slimDOMOptions??d,maskAllInputs:this.options.maskAllInputs??!0,maskInputOptions:this.options.maskInputOptions??{password:!0,email:!0,tel:!0},blockClass:this.options.blockClass,blockSelector:this.options.blockSelector,maskTextClass:this.options.maskTextClass,maskTextSelector:this.options.maskTextSelector,checkoutEveryNms:this.options.checkoutEveryNms??6e4,checkoutEveryNth:this.options.checkoutEveryNth,plugins:[s({key:`_faststatsSeqId`}),...this.options.recordConsole??!0?[o()]:[]]}),this.intervalId=setInterval(this.requestFlush,this.flushInterval),window.addEventListener(`beforeunload`,this.onUnload),window.addEventListener(`pagehide`,this.onUnload),document.addEventListener(`visibilitychange`,this.onVisibilityChange),this.log(`Recording started`))}stop(){if(this.started){if(this.started=!1,this.stopRecording?.(),this.stopRecording=void 0,this.intervalId&&clearInterval(this.intervalId),this.flushTask&&clearTimeout(this.flushTask),this.retryTask&&clearTimeout(this.retryTask),this.minLengthFlushTask&&clearTimeout(this.minLengthFlushTask),this.intervalId=null,this.flushTask=null,this.retryTask=null,this.minLengthFlushTask=null,window.removeEventListener(`beforeunload`,this.onUnload),window.removeEventListener(`pagehide`,this.onUnload),document.removeEventListener(`visibilitychange`,this.onVisibilityChange),!this.hasReachedMinLength()){this.events.length=0,this.sessionId=void 0,this.log(`Session too short (${Date.now()-this.startTime}ms), discarding events`);return}this.flush(!0),this.sessionId=void 0,this.log(`Recording stopped`)}}getSessionId(){return this.sessionId??r()}onEvent=(e,t)=>{if(this.events.push(e),t||this.events.length>=this.maxEvents||e.type===c.FullSnapshot&&this.hasReachedMinLength()){this.requestFlush();return}this.scheduleMinLengthFlush()};onUnload=()=>{this.flush(!0)};onVisibilityChange=()=>{document.visibilityState===`hidden`&&this.flush(!0)};hasReachedMinLength(){return this.minReplayLengthMs<=0||Date.now()-this.startTime>=this.minReplayLengthMs}requestFlush=()=>{this.flushTask||this.sending||this.events.length===0||(this.minLengthFlushTask&&=(clearTimeout(this.minLengthFlushTask),null),this.flushTask=setTimeout(()=>{this.flushTask=null,this.flush(!1)},0))};scheduleMinLengthFlush(){if(this.minLengthFlushTask||this.events.length===0||this.hasReachedMinLength())return;let e=Math.max(0,this.minReplayLengthMs-(Date.now()-this.startTime)),t=Math.min(e,2147483647);this.minLengthFlushTask=setTimeout(()=>{this.minLengthFlushTask=null,this.requestFlush()},t)}createBatch(e){let n=t();return{token:this.options.siteKey,sessionId:this.getSessionId(),...n?{identifier:n}:{},sequence:this.sequence++,timestamp:Date.now(),url:window.location.href,events:e}}async encodeBatch(e,t){let n=JSON.stringify(e);if(!this.shouldCompress||!this.compressionSupported||t)return{data:n,isCompressed:!1};try{return{data:await this.compress(n),isCompressed:!0}}catch{return this.log(`Compression failed, using uncompressed`),{data:new Blob([n],{type:`application/json`}),isCompressed:!1}}}async flush(e){if(!this.sending){if(this.events.length>0)if(!this.hasReachedMinLength())this.scheduleMinLengthFlush();else{let e=this.createBatch(this.events.splice(0));this.pending.push(e)}if(this.pending.length!==0){this.sending=!0;try{for(;this.pending.length>0;){let t=this.pending[0];if(!t)break;let n=await this.encodeBatch(t,e);if(!await this.send(n.data,n.isCompressed,e)){this.pending.length>=this.maxPendingBatches&&(this.log(`Pending buffer full, dropping batch ${t.sequence}`),this.pending.shift()),this.log(`Failed to send replay batch ${t.sequence}, retrying`),this.scheduleRetry();break}this.pending.shift(),e=!1}}finally{this.sending=!1}}}}scheduleRetry(){this.retryTask||=setTimeout(()=>{this.retryTask=null,this.flush(!1)},1e3)}async compress(e){let t=new TextEncoder().encode(e),n=new CompressionStream(`gzip`),r=n.writable.getWriter();await r.write(t),await 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;return new Blob([s],{type:`application/octet-stream`})}send(e,t,n){return a({url:t?`${this.endpoint}?encoding=gzip`:this.endpoint,data:e,contentType:t?`application/octet-stream`:`application/json`,debug:!1,useBeacon:!1,keepalive:n})??Promise.resolve(!1)}};export{f as default};
package/package.json CHANGED
@@ -19,7 +19,7 @@
19
19
  "publishConfig": {
20
20
  "access": "public"
21
21
  },
22
- "version": "0.2.5",
22
+ "version": "0.2.7",
23
23
  "scripts": {
24
24
  "build": "tsdown",
25
25
  "dev": "bun run build",
package/src/analytics.ts CHANGED
@@ -407,6 +407,81 @@ export class WebAnalytics {
407
407
  this.errorTracker = null;
408
408
  }
409
409
 
410
+ private registerChildTracker(tracker: ChildTracker): boolean {
411
+ if (!this.started || this.destroyed || _instance !== this) {
412
+ tracker.stop?.();
413
+ return false;
414
+ }
415
+
416
+ this.childTrackers.push(tracker);
417
+ return true;
418
+ }
419
+
420
+ private async startErrorTracker(): Promise<void> {
421
+ try {
422
+ const { default: ErrorTrackerClass } = await import("./error");
423
+ if (!this.started || this.destroyed || _instance !== this) return;
424
+
425
+ const errorTracker: ErrorTracker = new ErrorTrackerClass({
426
+ siteKey: this.options.siteKey,
427
+ baseUrl: this.baseUrl,
428
+ debug: this.debug,
429
+ });
430
+ errorTracker.start();
431
+ if (!this.registerChildTracker(errorTracker)) return;
432
+
433
+ this.errorTracker = errorTracker;
434
+ while (this.pendingReportedErrors.length > 0) {
435
+ const pending = this.pendingReportedErrors.shift();
436
+ if (pending) errorTracker.captureError(pending);
437
+ }
438
+ this.log("error loaded");
439
+ } catch (error) {
440
+ this.log(`failed to initialize error tracker: ${String(error)}`);
441
+ }
442
+ }
443
+
444
+ private async startWebVitalsTracker(): Promise<void> {
445
+ try {
446
+ const { default: WebVitalsTrackerClass } = await import("./web-vitals");
447
+ if (!this.started || this.destroyed || _instance !== this) return;
448
+
449
+ const webVitalsTracker: WebVitalsTracker = new WebVitalsTrackerClass({
450
+ siteKey: this.options.siteKey,
451
+ baseUrl: this.baseUrl,
452
+ debug: this.debug,
453
+ samplingPercentage: normalizeSamplingPercentage(
454
+ this.options.webVitals?.sampling?.percentage,
455
+ ),
456
+ });
457
+ webVitalsTracker.start();
458
+ if (!this.registerChildTracker(webVitalsTracker)) return;
459
+
460
+ this.log("web-vitals loaded");
461
+ } catch (error) {
462
+ this.log(`failed to initialize web-vitals tracker: ${String(error)}`);
463
+ }
464
+ }
465
+
466
+ private async startReplayTracker(
467
+ replayOptions: ReplayTrackerOptions,
468
+ ): Promise<void> {
469
+ try {
470
+ const { default: ReplayTrackerClass } = await import("./replay");
471
+ if (!this.started || this.destroyed || _instance !== this) return;
472
+
473
+ const replayTracker: ReplayTracker = new ReplayTrackerClass(
474
+ replayOptions,
475
+ );
476
+ replayTracker.start();
477
+ if (!this.registerChildTracker(replayTracker)) return;
478
+
479
+ this.log("replay loaded");
480
+ } catch (error) {
481
+ this.log(`failed to initialize replay tracker: ${String(error)}`);
482
+ }
483
+ }
484
+
410
485
  async start(): Promise<void> {
411
486
  if (this.started || this.destroyed || typeof window === "undefined") return;
412
487
  if (_instance && _instance !== this) {
@@ -421,63 +496,22 @@ export class WebAnalytics {
421
496
  this.started = true;
422
497
  _instance = this;
423
498
  setCookielessMode(this.isCookielessMode());
499
+ getOrCreateSessionId();
424
500
 
425
501
  const opts = this.options;
426
- if (opts.trackErrors) {
427
- try {
428
- const { default: ErrorTrackerClass } = await import("./error");
429
- const errorTracker: ErrorTracker = new ErrorTrackerClass({
430
- siteKey: opts.siteKey,
431
- baseUrl: this.baseUrl,
432
- debug: this.debug,
433
- });
434
- errorTracker.start();
435
- this.errorTracker = errorTracker;
436
- this.childTrackers.push(errorTracker);
437
- while (this.pendingReportedErrors.length > 0) {
438
- const pending = this.pendingReportedErrors.shift();
439
- if (pending) errorTracker.captureError(pending);
440
- }
441
- this.log("error loaded");
442
- } catch (error) {
443
- this.log(`failed to initialize error tracker: ${String(error)}`);
444
- }
445
- }
446
- if (opts.trackWebVitals) {
447
- try {
448
- const { default: WebVitalsTrackerClass } = await import("./web-vitals");
449
- const webVitalsTracker: WebVitalsTracker = new WebVitalsTrackerClass({
450
- siteKey: opts.siteKey,
451
- baseUrl: this.baseUrl,
452
- debug: this.debug,
453
- samplingPercentage: normalizeSamplingPercentage(
454
- opts.webVitals?.sampling?.percentage,
455
- ),
456
- });
457
- webVitalsTracker.start();
458
- this.childTrackers.push(webVitalsTracker);
459
- this.log("web-vitals loaded");
460
- } catch (error) {
461
- this.log(`failed to initialize web-vitals tracker: ${String(error)}`);
462
- }
463
- }
464
502
  const replayOptions = resolveReplayTrackerOptions(
465
503
  opts,
466
504
  this.baseUrl,
467
505
  this.debug,
468
506
  );
469
507
  if (replayOptions) {
470
- try {
471
- const { default: ReplayTrackerClass } = await import("./replay");
472
- const replayTracker: ReplayTracker = new ReplayTrackerClass(
473
- replayOptions,
474
- );
475
- replayTracker.start();
476
- this.childTrackers.push(replayTracker);
477
- this.log("replay loaded");
478
- } catch (error) {
479
- this.log(`failed to initialize replay tracker: ${String(error)}`);
480
- }
508
+ void this.startReplayTracker(replayOptions);
509
+ }
510
+ if (opts.trackErrors) {
511
+ void this.startErrorTracker();
512
+ }
513
+ if (opts.trackWebVitals) {
514
+ void this.startWebVitalsTracker();
481
515
  }
482
516
 
483
517
  this.enterPage();
package/src/replay.ts CHANGED
@@ -6,15 +6,21 @@ import type { recordOptions } from "rrweb/typings/types";
6
6
  import type { SlimDOMOptions } from "rrweb-snapshot";
7
7
  import { sendData } from "./analytics";
8
8
  import { replayEventsUrl } from "./utils/api-urls";
9
- import { getAnonymousId, getOrCreateSessionId } from "./utils/identifiers";
9
+ import {
10
+ getAnonymousId,
11
+ getOrCreateSessionId,
12
+ getSessionStart,
13
+ } from "./utils/identifiers";
10
14
  import { normalizeSamplingPercentage } from "./utils/types";
11
15
 
12
16
  const RRWEB_SEQUENTIAL_ID_KEY = "_faststatsSeqId";
17
+ const MAX_TIMEOUT_MS = 2_147_483_647;
13
18
 
14
19
  export interface ReplayTrackerOptions {
15
20
  siteKey: string;
16
21
  baseUrl?: string;
17
22
  debug?: boolean;
23
+ compress?: boolean;
18
24
  samplingPercentage?: number;
19
25
  flushInterval?: number;
20
26
  maxEvents?: number;
@@ -72,12 +78,14 @@ export default class ReplayTracker {
72
78
  private readonly events: eventWithTime[] = [];
73
79
  private readonly pending: ReplayBatch[] = [];
74
80
 
81
+ private sessionId: string | undefined;
75
82
  private started = false;
76
83
  private startTime = 0;
77
84
  private sequence = 0;
78
85
  private intervalId: ReturnType<typeof setInterval> | null = null;
79
86
  private flushTask: ReturnType<typeof setTimeout> | null = null;
80
87
  private retryTask: ReturnType<typeof setTimeout> | null = null;
88
+ private minLengthFlushTask: ReturnType<typeof setTimeout> | null = null;
81
89
  private stopRecording?: listenerHandler;
82
90
  private sending = false;
83
91
 
@@ -108,6 +116,10 @@ export default class ReplayTracker {
108
116
  return this.options.minReplayLengthMs ?? 3000;
109
117
  }
110
118
 
119
+ private get shouldCompress(): boolean {
120
+ return this.options.compress ?? false;
121
+ }
122
+
111
123
  private log(...args: unknown[]): void {
112
124
  if (this.debug) console.log("[Replay]", ...args);
113
125
  }
@@ -116,7 +128,8 @@ export default class ReplayTracker {
116
128
  if (this.started || typeof window === "undefined" || !this.sampled) return;
117
129
 
118
130
  this.started = true;
119
- this.startTime = Date.now();
131
+ this.sessionId = getOrCreateSessionId();
132
+ this.startTime = getSessionStart();
120
133
 
121
134
  this.stopRecording = record({
122
135
  emit: this.onEvent,
@@ -161,10 +174,12 @@ export default class ReplayTracker {
161
174
  if (this.intervalId) clearInterval(this.intervalId);
162
175
  if (this.flushTask) clearTimeout(this.flushTask);
163
176
  if (this.retryTask) clearTimeout(this.retryTask);
177
+ if (this.minLengthFlushTask) clearTimeout(this.minLengthFlushTask);
164
178
 
165
179
  this.intervalId = null;
166
180
  this.flushTask = null;
167
181
  this.retryTask = null;
182
+ this.minLengthFlushTask = null;
168
183
 
169
184
  window.removeEventListener("beforeunload", this.onUnload);
170
185
  window.removeEventListener("pagehide", this.onUnload);
@@ -172,6 +187,7 @@ export default class ReplayTracker {
172
187
 
173
188
  if (!this.hasReachedMinLength()) {
174
189
  this.events.length = 0;
190
+ this.sessionId = undefined;
175
191
  this.log(
176
192
  `Session too short (${Date.now() - this.startTime}ms), discarding events`,
177
193
  );
@@ -179,11 +195,12 @@ export default class ReplayTracker {
179
195
  }
180
196
 
181
197
  void this.flush(true);
198
+ this.sessionId = undefined;
182
199
  this.log("Recording stopped");
183
200
  }
184
201
 
185
202
  getSessionId(): string | undefined {
186
- return getOrCreateSessionId();
203
+ return this.sessionId ?? getOrCreateSessionId();
187
204
  }
188
205
 
189
206
  private onEvent = (event: eventWithTime, isCheckout?: boolean): void => {
@@ -195,7 +212,10 @@ export default class ReplayTracker {
195
212
  (event.type === EventType.FullSnapshot && this.hasReachedMinLength())
196
213
  ) {
197
214
  this.requestFlush();
215
+ return;
198
216
  }
217
+
218
+ this.scheduleMinLengthFlush();
199
219
  };
200
220
 
201
221
  private onUnload = (): void => {
@@ -218,18 +238,44 @@ export default class ReplayTracker {
218
238
  private requestFlush = (): void => {
219
239
  if (this.flushTask || this.sending || this.events.length === 0) return;
220
240
 
241
+ if (this.minLengthFlushTask) {
242
+ clearTimeout(this.minLengthFlushTask);
243
+ this.minLengthFlushTask = null;
244
+ }
245
+
221
246
  this.flushTask = setTimeout(() => {
222
247
  this.flushTask = null;
223
248
  void this.flush(false);
224
249
  }, 0);
225
250
  };
226
251
 
252
+ private scheduleMinLengthFlush(): void {
253
+ if (
254
+ this.minLengthFlushTask ||
255
+ this.events.length === 0 ||
256
+ this.hasReachedMinLength()
257
+ ) {
258
+ return;
259
+ }
260
+
261
+ const remainingMs = Math.max(
262
+ 0,
263
+ this.minReplayLengthMs - (Date.now() - this.startTime),
264
+ );
265
+ const timeoutMs = Math.min(remainingMs, MAX_TIMEOUT_MS);
266
+
267
+ this.minLengthFlushTask = setTimeout(() => {
268
+ this.minLengthFlushTask = null;
269
+ this.requestFlush();
270
+ }, timeoutMs);
271
+ }
272
+
227
273
  private createBatch(events: eventWithTime[]): ReplayBatch {
228
274
  const identifier = getAnonymousId();
229
275
 
230
276
  return {
231
277
  token: this.options.siteKey,
232
- sessionId: getOrCreateSessionId(),
278
+ sessionId: this.getSessionId(),
233
279
  ...(identifier ? { identifier } : {}),
234
280
  sequence: this.sequence++,
235
281
  timestamp: Date.now(),
@@ -241,12 +287,12 @@ export default class ReplayTracker {
241
287
  private async encodeBatch(
242
288
  batch: ReplayBatch,
243
289
  lowLatency: boolean,
244
- ): Promise<{ data: Blob; isCompressed: boolean }> {
290
+ ): Promise<{ data: string | Blob; isCompressed: boolean }> {
245
291
  const json = JSON.stringify(batch);
246
292
 
247
- if (!this.compressionSupported || lowLatency) {
293
+ if (!this.shouldCompress || !this.compressionSupported || lowLatency) {
248
294
  return {
249
- data: new Blob([json], { type: "application/json" }),
295
+ data: json,
250
296
  isCompressed: false,
251
297
  };
252
298
  }
@@ -270,11 +316,10 @@ export default class ReplayTracker {
270
316
 
271
317
  if (this.events.length > 0) {
272
318
  if (!this.hasReachedMinLength()) {
273
- this.log(
274
- `Too short (${Date.now() - this.startTime}ms), skipping flush`,
275
- );
319
+ this.scheduleMinLengthFlush();
276
320
  } else {
277
- this.pending.push(this.createBatch(this.events.splice(0)));
321
+ const batch = this.createBatch(this.events.splice(0));
322
+ this.pending.push(batch);
278
323
  }
279
324
  }
280
325
 
@@ -298,11 +343,10 @@ export default class ReplayTracker {
298
343
  this.log(`Pending buffer full, dropping batch ${batch.sequence}`);
299
344
  this.pending.shift();
300
345
  }
346
+ this.log(`Failed to send replay batch ${batch.sequence}, retrying`);
301
347
  this.scheduleRetry();
302
348
  break;
303
349
  }
304
-
305
- this.log(`Sent ${batch.events.length} events (seq: ${batch.sequence})`);
306
350
  this.pending.shift();
307
351
  lowLatency = false;
308
352
  }
@@ -313,7 +357,6 @@ export default class ReplayTracker {
313
357
 
314
358
  private scheduleRetry(): void {
315
359
  if (this.retryTask) return;
316
-
317
360
  this.retryTask = setTimeout(() => {
318
361
  this.retryTask = null;
319
362
  void this.flush(false);
@@ -346,17 +389,11 @@ export default class ReplayTracker {
346
389
  offset += chunk.length;
347
390
  }
348
391
 
349
- this.log(
350
- `Compressed: ${input.length} → ${output.length} bytes (${(
351
- (output.length / input.length) * 100
352
- ).toFixed(1)}%)`,
353
- );
354
-
355
392
  return new Blob([output], { type: "application/octet-stream" });
356
393
  }
357
394
 
358
395
  private send(
359
- data: Blob,
396
+ data: string | Blob,
360
397
  isCompressed: boolean,
361
398
  lowLatency: boolean,
362
399
  ): Promise<boolean> {
@@ -367,9 +404,8 @@ export default class ReplayTracker {
367
404
  contentType: isCompressed
368
405
  ? "application/octet-stream"
369
406
  : "application/json",
370
- debug: this.debug,
371
- debugPrefix: `[Replay] ${(data.size / 1024).toFixed(1)}KB`,
372
- useBeacon: lowLatency,
407
+ debug: false,
408
+ useBeacon: false,
373
409
  keepalive: lowLatency,
374
410
  }) ?? Promise.resolve(false)
375
411
  );
@@ -1,5 +1,6 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
2
  import ReplayTracker from "../src/replay";
3
+ import { getOrCreateSessionId } from "../src/utils/identifiers";
3
4
 
4
5
  class MockStorage {
5
6
  private readonly data = new Map<string, string>();
@@ -36,12 +37,13 @@ type ReplayBatch = {
36
37
  type ReplayTrackerInternals = {
37
38
  events: ReplayEvent[];
38
39
  pending: ReplayBatch[];
40
+ minLengthFlushTask: ReturnType<typeof setTimeout> | null;
39
41
  retryTask: ReturnType<typeof setTimeout> | null;
40
42
  startTime: number;
41
43
  onEvent: (event: ReplayEvent, isCheckout?: boolean) => void;
42
44
  flush: (lowLatency: boolean) => Promise<void>;
43
45
  send: (
44
- data: Blob,
46
+ data: string | Blob,
45
47
  isCompressed: boolean,
46
48
  lowLatency: boolean,
47
49
  ) => Promise<boolean>;
@@ -136,6 +138,28 @@ describe("ReplayTracker", () => {
136
138
  expect(sent).toBe(0);
137
139
  expect(tracker.events.length).toBe(1);
138
140
  expect(tracker.pending.length).toBe(0);
141
+
142
+ if (tracker.minLengthFlushTask) clearTimeout(tracker.minLengthFlushTask);
143
+ });
144
+
145
+ test("flushes buffered events once minimum replay length is reached", async () => {
146
+ const tracker = createTracker({
147
+ minReplayLengthMs: 20,
148
+ });
149
+
150
+ let sent = 0;
151
+ tracker.send = async () => {
152
+ sent++;
153
+ return true;
154
+ };
155
+ tracker.startTime = Date.now();
156
+ tracker.onEvent({ type: 2, timestamp: Date.now(), data: {} }, false);
157
+
158
+ await new Promise((resolve) => setTimeout(resolve, 40));
159
+
160
+ expect(sent).toBe(1);
161
+ expect(tracker.events.length).toBe(0);
162
+ expect(tracker.pending.length).toBe(0);
139
163
  });
140
164
 
141
165
  test("emits replay payload when flushed", async () => {
@@ -146,7 +170,9 @@ describe("ReplayTracker", () => {
146
170
  let payload: ReplayBatch | null = null;
147
171
  let lowLatency = false;
148
172
  tracker.send = async (data, _isCompressed, nextLowLatency) => {
149
- payload = JSON.parse(await data.text()) as ReplayBatch;
173
+ payload = JSON.parse(
174
+ typeof data === "string" ? data : await data.text(),
175
+ ) as ReplayBatch;
150
176
  lowLatency = nextLowLatency;
151
177
  return true;
152
178
  };
@@ -163,6 +189,56 @@ describe("ReplayTracker", () => {
163
189
  expect(tracker.pending.length).toBe(0);
164
190
  });
165
191
 
192
+ test("keeps the same replay session id across later batches", async () => {
193
+ const tracker = createTracker({
194
+ minReplayLengthMs: 0,
195
+ });
196
+
197
+ const payloads: ReplayBatch[] = [];
198
+ tracker.send = async (data) => {
199
+ payloads.push(
200
+ JSON.parse(
201
+ typeof data === "string" ? data : await data.text(),
202
+ ) as ReplayBatch,
203
+ );
204
+ return true;
205
+ };
206
+
207
+ (tracker as unknown as { sessionId?: string }).sessionId =
208
+ getOrCreateSessionId();
209
+ tracker.startTime = Date.now() - 1000;
210
+ tracker.onEvent({ type: 2, timestamp: Date.now(), data: {} }, false);
211
+ await tracker.flush(false);
212
+
213
+ const storage = globalThis.sessionStorage as unknown as MockStorage;
214
+ storage.setItem("session_timestamp", "0");
215
+
216
+ tracker.onEvent({ type: 3, timestamp: Date.now(), data: {} }, false);
217
+ await tracker.flush(false);
218
+
219
+ expect(payloads).toHaveLength(2);
220
+ expect(payloads[0]?.sessionId).toBeTruthy();
221
+ expect(payloads[1]?.sessionId).toBe(payloads[0]?.sessionId);
222
+ });
223
+
224
+ test("sends replay as plain JSON by default", async () => {
225
+ const tracker = createTracker({
226
+ minReplayLengthMs: 0,
227
+ });
228
+
229
+ let bodyType: "string" | "blob" | null = null;
230
+ tracker.send = async (data) => {
231
+ bodyType = typeof data === "string" ? "string" : "blob";
232
+ return true;
233
+ };
234
+ tracker.startTime = Date.now() - 1000;
235
+ tracker.onEvent({ type: 2, timestamp: Date.now(), data: {} }, false);
236
+
237
+ await tracker.flush(false);
238
+
239
+ expect(bodyType).toBe("string");
240
+ });
241
+
166
242
  test("keeps failed batches queued for retry", async () => {
167
243
  const tracker = createTracker({
168
244
  minReplayLengthMs: 0,
@@ -1 +0,0 @@
1
- import{a as e,c as t,d as n,f as r,i,l as a,m as o,n as s,o as c,r as l,s as u,t as d,u as f}from"./types-Df70G0eM.js";async function p(e,t){if(t.projectToken&&t.projectId)throw Error(`provide either projectToken or projectId, not both`);if(t.identifier&&t.externalId)throw Error(`provide either serverId or externalId, not both`);let n=t.projectToken,r=t.projectId,i=t.identifier,o=t.externalId;if(!n&&!r)throw Error(`feature flag check requires projectToken or projectId`);if(!i&&!o)throw Error(`feature flag check requires serverId or externalId`);let s={key:e,...r?{projectId:r}:{},...i?{identifier:i}:{externalId:o}};t.attributes&&Object.keys(t.attributes).length>0&&(s.attributes=t.attributes);let c={"Content-Type":`application/json`};n&&(c.Authorization=`Bearer ${n}`);let l=a(t.baseUrl),u=await fetch(l,{method:`POST`,headers:c,body:JSON.stringify(s),credentials:`omit`,signal:t.signal});if(!u.ok){let e=``;try{let t=await u.json();typeof t.error==`string`&&t.error.length>0&&(e=` — ${t.error}`)}catch{}throw Error(`feature flag check failed: HTTP ${u.status}${e}`)}return await u.json()}function m(e){return e.replayOptions?.samplingPercentage!==void 0||e.sessionReplays?.sampling?.percentage!==void 0}function h(e,t,n){if(!(e.sessionReplays?.enabled??e.trackReplay??m(e)))return null;let r=e.replayOptions??{};return{siteKey:e.siteKey,baseUrl:t,debug:n,...r,samplingPercentage:d(r.samplingPercentage??e.sessionReplays?.sampling?.percentage)}}let g=null,_;function v(){return g}function y(e,t){typeof window>`u`||E()||g?.track(e,t??{})}function b(e,t,n){typeof window>`u`||E()||g?.identify(e,t,n??{})}function x(e=!0){typeof window>`u`||E()||g?.logout(e)}function S(e){if(g){g.setConsentMode(e);return}_=e}function C(){S(`granted`)}function w(){S(`denied`)}function T(e){typeof window>`u`||E()||g?.reportError(e)}function E(){if(typeof localStorage>`u`)return!1;let e=localStorage.getItem(`disable-faststats`);return e===`true`||e===`1`}async function D(e){let{url:t,data:n,contentType:r=`application/json`,headers:i={},debug:a=!1,debugPrefix:o=`[Analytics]`,useBeacon:s=!0,keepalive:c=!0}=e;if(s&&typeof navigator<`u`&&typeof navigator.sendBeacon==`function`)try{let e=n instanceof Blob?n:typeof Blob<`u`?new Blob([n],{type:r}):n;if(navigator.sendBeacon(t,e))return a&&console.log(`${o} Sent via beacon`),!0}catch{}if(typeof fetch!=`function`)return a&&console.warn(`${o} Failed to send`),!1;try{let e=await fetch(t,{method:`POST`,body:n,headers:{"Content-Type":r,...i},keepalive:c}),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 O(e){for(;e;){if(e instanceof HTMLAnchorElement&&e.href)return e;e=e.parentNode}return null}function k(){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 A=class{webEndpoint;baseUrl;featureFlagsBaseUrl;debug;started=!1;destroyed=!1;pageKey=``;navTimer=null;heartbeatTimer=null;scrollDepth=0;pageEntryTime=0;pagePath=``;pageUrl=``;pageHash=``;hasLeftCurrentPage=!1;scrollHandler=null;consentMode;cookielessWhilePending;cleanupCallbacks=[];childTrackers=[];pendingReportedErrors=[];errorTracker=null;handleVisibilityChange=()=>{document.visibilityState===`hidden`?(this.leavePage(),this.stopHeartbeat()):this.startHeartbeat()};handlePageHide=()=>{this.leavePage()};handlePopState=()=>{this.navigate()};handleHashChange=()=>{this.navigate()};constructor(e){this.options=e,this.baseUrl=n(e.baseUrl),this.featureFlagsBaseUrl=r(e.featureFlagsBaseUrl),this.webEndpoint=o(this.baseUrl),this.debug=e.debug??!1,this.consentMode=e.consent?.mode??`granted`,this.cookielessWhilePending=e.consent?.cookielessWhilePending??!0,_!==void 0&&(this.consentMode=_,_=void 0),(e.autoTrack??!0)&&this.init()}log(e){this.debug&&console.log(`[Analytics] ${e}`)}init(){if(!(typeof window>`u`)){if(E()){this.log(`disabled`);return}g=this,t(this.isCookielessMode()),setTimeout(()=>void this.start(),0)}}registerCleanup(e){this.cleanupCallbacks.push(e)}addWindowListener(e,t){window.addEventListener(e,t),this.registerCleanup(()=>window.removeEventListener(e,t))}addDocumentListener(e,t){document.addEventListener(e,t),this.registerCleanup(()=>document.removeEventListener(e,t))}patchHistory(){let e=history.pushState.bind(history),t=history.replaceState.bind(history);history.pushState=(t,n,r)=>{e(t,n,r),this.navigate()},history.replaceState=(e,n,r)=>{t(e,n,r),this.navigate()},this.registerCleanup(()=>{history.pushState=e,history.replaceState=t})}ensureStarted(){return typeof window>`u`||this.destroyed||E()?!1:(this.started||this.start(),!0)}stopHeartbeat(){this.heartbeatTimer&&=(clearInterval(this.heartbeatTimer),null)}stopNavigationTimer(){this.navTimer&&=(clearTimeout(this.navTimer),null)}stopChildTrackers(){for(let e of this.childTrackers.splice(0))e.stop?.();this.errorTracker=null}async start(){if(this.started||this.destroyed||typeof window>`u`)return;if(g&&g!==this){this.log(`already started by another instance`);return}if(E()){this.log(`disabled`);return}this.started=!0,g=this,t(this.isCookielessMode());let e=this.options;if(e.trackErrors)try{let{default:t}=await import(`./error-uRd9K5Py.js`),n=new t({siteKey:e.siteKey,baseUrl:this.baseUrl,debug:this.debug});for(n.start(),this.errorTracker=n,this.childTrackers.push(n);this.pendingReportedErrors.length>0;){let e=this.pendingReportedErrors.shift();e&&n.captureError(e)}this.log(`error loaded`)}catch(e){this.log(`failed to initialize error tracker: ${String(e)}`)}if(e.trackWebVitals)try{let{default:t}=await import(`./web-vitals-gcUR-TX_.js`),n=new t({siteKey:e.siteKey,baseUrl:this.baseUrl,debug:this.debug,samplingPercentage:d(e.webVitals?.sampling?.percentage)});n.start(),this.childTrackers.push(n),this.log(`web-vitals loaded`)}catch(e){this.log(`failed to initialize web-vitals tracker: ${String(e)}`)}let n=h(e,this.baseUrl,this.debug);if(n)try{let{default:e}=await import(`./replay-DkZI1bwa.js`),t=new e(n);t.start(),this.childTrackers.push(t),this.log(`replay loaded`)}catch(e){this.log(`failed to initialize replay tracker: ${String(e)}`)}this.enterPage(),this.pageview({trigger:`load`}),this.links(),this.trackScroll(),this.startHeartbeat(),this.addDocumentListener(`visibilitychange`,this.handleVisibilityChange),this.addWindowListener(`pagehide`,this.handlePageHide),this.addWindowListener(`popstate`,this.handlePopState),e.trackHash&&this.addWindowListener(`hashchange`,this.handleHashChange),this.patchHistory()}destroy(){if(!this.destroyed){for(this.started&&typeof window<`u`&&this.leavePage(),this.pendingReportedErrors.length=0,this.stopNavigationTimer(),this.stopHeartbeat(),this.scrollHandler&&typeof window<`u`&&(window.removeEventListener(`scroll`,this.scrollHandler),this.scrollHandler=null);this.cleanupCallbacks.length>0;)this.cleanupCallbacks.pop()?.();this.stopChildTrackers(),g===this&&(g=null),this.started=!1,this.destroyed=!0}}pageview(e={}){if(!this.ensureStarted())return;let t=`${location.pathname}|${this.options.trackHash??!1?location.hash:``}`;t!==this.pageKey&&(this.pageKey=t,this.send(`pageview`,e))}track(e,t={}){this.ensureStarted()&&this.send(e,t)}identify(e,t,n={}){if(!this.ensureStarted()||this.isCookielessMode())return;let r=e.trim(),i=t.trim();!r||!i||D({url:f(this.baseUrl),data:JSON.stringify({token:this.options.siteKey,identifier:s(!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){this.ensureStarted()&&(e&&c(this.isCookielessMode()),u())}setConsentMode(e){this.consentMode=e,t(this.isCookielessMode())}optIn(){this.setConsentMode(`granted`)}optOut(){this.setConsentMode(`denied`)}getConsentMode(){return this.consentMode}getAnonymousId(){return s(this.isCookielessMode())}getSessionId(){return l()}async checkFeatureFlag(e,t,n){if(typeof window>`u`||E())return{value:`false`};let r=n?.externalId?.trim();if(r)return p(e,{baseUrl:this.featureFlagsBaseUrl,projectToken:this.options.siteKey,externalId:r,attributes:t,signal:n?.signal});let i=this.getAnonymousId();return i?p(e,{baseUrl:this.featureFlagsBaseUrl,projectToken:this.options.siteKey,identifier:i,attributes:t,signal:n?.signal}):{value:`false`}}reportError(e){if(this.destroyed||typeof window>`u`||E()||!(this.options.trackErrors??!1)||!this.ensureStarted())return;let t=this.errorTracker;if(t){t.captureError(e);return}this.pendingReportedErrors.length>=50&&this.pendingReportedErrors.shift(),this.pendingReportedErrors.push(e)}isCookielessMode(){return this.options.cookieless||this.consentMode===`denied`?!0:this.consentMode===`pending`?this.cookielessWhilePending:!1}send(e,t={}){if(typeof window>`u`||this.destroyed||E())return;let n=s(this.isCookielessMode()),r=JSON.stringify({token:this.options.siteKey,...n?{userId:n}:{},sessionId:l(),data:{event:e,page:location.pathname,referrer:document.referrer||null,title:document.title||``,url:location.href,...k(),...t}});this.log(e),D({url:this.webEndpoint,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.destroyed||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-i()})}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.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{if(document.visibilityState===`hidden`){this.stopHeartbeat();return}e()},300*1e3)}navigate(){!this.started||this.destroyed||(this.stopNavigationTimer(),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))}links(){let e=e=>{let t=O(e.target);t&&t.host!==location.host&&this.track(`outbound_link`,{outbound_link:t.href})};this.addDocumentListener(`click`,e),this.addDocumentListener(`auxclick`,e)}};export{x as a,T as c,y as d,p as f,E as i,D as l,v as n,C as o,b as r,w as s,A as t,S as u};
@@ -1 +0,0 @@
1
- import{n as e,p as t,r as n,t as r}from"./types-Df70G0eM.js";import{l as i}from"./analytics-CEej2XLw.js";import{getRecordConsolePlugin as a}from"@rrweb/rrweb-plugin-console-record";import{getRecordSequentialIdPlugin as o}from"@rrweb/rrweb-plugin-sequential-id-record";import{EventType as s,record as c}from"rrweb";const l={mousemove:50,mouseInteraction:!0,scroll:150,media:800,input:`last`},u={script:!0,comment:!0,headFavicon:!0,headWhitespace:!0,headMetaDescKeywords:!0,headMetaSocial:!0,headMetaRobots:!0,headMetaHttpEquiv:!0,headMetaAuthorship:!0};var d=class{endpoint;compressionSupported=typeof window<`u`&&`CompressionStream`in window;sampled;events=[];pending=[];started=!1;startTime=0;sequence=0;intervalId=null;flushTask=null;retryTask=null;stopRecording;sending=!1;constructor(e){this.options=e,this.endpoint=t(e.baseUrl),this.sampled=Math.random()*100<r(e.samplingPercentage)}get debug(){return this.options.debug??!1}get flushInterval(){return this.options.flushInterval??1e4}get maxEvents(){return this.options.maxEvents??500}get maxPendingBatches(){return this.options.maxPendingBatches??30}get minReplayLengthMs(){return this.options.minReplayLengthMs??3e3}log(...e){this.debug&&console.log(`[Replay]`,...e)}start(){this.started||typeof window>`u`||!this.sampled||(this.started=!0,this.startTime=Date.now(),this.stopRecording=c({emit:this.onEvent,sampling:this.options.sampling??l,slimDOMOptions:this.options.slimDOMOptions??u,maskAllInputs:this.options.maskAllInputs??!0,maskInputOptions:this.options.maskInputOptions??{password:!0,email:!0,tel:!0},blockClass:this.options.blockClass,blockSelector:this.options.blockSelector,maskTextClass:this.options.maskTextClass,maskTextSelector:this.options.maskTextSelector,checkoutEveryNms:this.options.checkoutEveryNms??6e4,checkoutEveryNth:this.options.checkoutEveryNth,plugins:[o({key:`_faststatsSeqId`}),...this.options.recordConsole??!0?[a()]:[]]}),this.intervalId=setInterval(this.requestFlush,this.flushInterval),window.addEventListener(`beforeunload`,this.onUnload),window.addEventListener(`pagehide`,this.onUnload),document.addEventListener(`visibilitychange`,this.onVisibilityChange),this.log(`Recording started`))}stop(){if(this.started){if(this.started=!1,this.stopRecording?.(),this.stopRecording=void 0,this.intervalId&&clearInterval(this.intervalId),this.flushTask&&clearTimeout(this.flushTask),this.retryTask&&clearTimeout(this.retryTask),this.intervalId=null,this.flushTask=null,this.retryTask=null,window.removeEventListener(`beforeunload`,this.onUnload),window.removeEventListener(`pagehide`,this.onUnload),document.removeEventListener(`visibilitychange`,this.onVisibilityChange),!this.hasReachedMinLength()){this.events.length=0,this.log(`Session too short (${Date.now()-this.startTime}ms), discarding events`);return}this.flush(!0),this.log(`Recording stopped`)}}getSessionId(){return n()}onEvent=(e,t)=>{this.events.push(e),(t||this.events.length>=this.maxEvents||e.type===s.FullSnapshot&&this.hasReachedMinLength())&&this.requestFlush()};onUnload=()=>{this.flush(!0)};onVisibilityChange=()=>{document.visibilityState===`hidden`&&this.flush(!0)};hasReachedMinLength(){return this.minReplayLengthMs<=0||Date.now()-this.startTime>=this.minReplayLengthMs}requestFlush=()=>{this.flushTask||this.sending||this.events.length===0||(this.flushTask=setTimeout(()=>{this.flushTask=null,this.flush(!1)},0))};createBatch(t){let r=e();return{token:this.options.siteKey,sessionId:n(),...r?{identifier:r}:{},sequence:this.sequence++,timestamp:Date.now(),url:window.location.href,events:t}}async encodeBatch(e,t){let n=JSON.stringify(e);if(!this.compressionSupported||t)return{data:new Blob([n],{type:`application/json`}),isCompressed:!1};try{return{data:await this.compress(n),isCompressed:!0}}catch{return this.log(`Compression failed, using uncompressed`),{data:new Blob([n],{type:`application/json`}),isCompressed:!1}}}async flush(e){if(!this.sending&&(this.events.length>0&&(this.hasReachedMinLength()?this.pending.push(this.createBatch(this.events.splice(0))):this.log(`Too short (${Date.now()-this.startTime}ms), skipping flush`)),this.pending.length!==0)){this.sending=!0;try{for(;this.pending.length>0;){let t=this.pending[0];if(!t)break;let n=await this.encodeBatch(t,e);if(!await this.send(n.data,n.isCompressed,e)){this.pending.length>=this.maxPendingBatches&&(this.log(`Pending buffer full, dropping batch ${t.sequence}`),this.pending.shift()),this.scheduleRetry();break}this.log(`Sent ${t.events.length} events (seq: ${t.sequence})`),this.pending.shift(),e=!1}}finally{this.sending=!1}}}scheduleRetry(){this.retryTask||=setTimeout(()=>{this.retryTask=null,this.flush(!1)},1e3)}async compress(e){let t=new TextEncoder().encode(e),n=new CompressionStream(`gzip`),r=n.writable.getWriter();await r.write(t),await 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;return this.log(`Compressed: ${t.length} → ${s.length} bytes (${(s.length/t.length*100).toFixed(1)}%)`),new Blob([s],{type:`application/octet-stream`})}send(e,t,n){return i({url:t?`${this.endpoint}?encoding=gzip`:this.endpoint,data:e,contentType:t?`application/octet-stream`:`application/json`,debug:this.debug,debugPrefix:`[Replay] ${(e.size/1024).toFixed(1)}KB`,useBeacon:n,keepalive:n})??Promise.resolve(!1)}};export{d as default};