@faststats/web 0.2.4 → 0.2.6

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.6
4
+
5
+ ### Patch Changes
6
+
7
+ - 7d57d50: fix: replay flushing issues
8
+
9
+ ## 0.2.5
10
+
11
+ ### Patch Changes
12
+
13
+ - dd932a0: fix: replay config mismatch
14
+
3
15
  ## 0.2.4
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-Df3cDcoP.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-alCILAnP.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-B988lkxn.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-D_dgxSVU.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;
@@ -413,6 +414,7 @@ interface WebVitalsConfig {
413
414
  sampling?: SamplingOptions;
414
415
  }
415
416
  interface SessionReplayConfig {
417
+ enabled?: boolean;
416
418
  sampling?: SamplingOptions;
417
419
  }
418
420
  type ConsentMode = "pending" | "granted" | "denied";
@@ -491,6 +493,10 @@ declare class WebAnalytics {
491
493
  private stopHeartbeat;
492
494
  private stopNavigationTimer;
493
495
  private stopChildTrackers;
496
+ private registerChildTracker;
497
+ private startErrorTracker;
498
+ private startWebVitalsTracker;
499
+ private startReplayTracker;
494
500
  start(): Promise<void>;
495
501
  destroy(): void;
496
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-B988lkxn.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-D_dgxSVU.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-D_dgxSVU.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=[];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,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.log(`Session too short (${Date.now()-this.startTime}ms), discarding events`);return}this.flush(!0),this.log(`Recording stopped`)}}getSessionId(){return 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:r(),...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.4",
22
+ "version": "0.2.6",
23
23
  "scripts": {
24
24
  "build": "tsdown",
25
25
  "dev": "bun run build",
package/src/analytics.ts CHANGED
@@ -44,6 +44,7 @@ interface WebVitalsConfig {
44
44
  }
45
45
 
46
46
  interface SessionReplayConfig {
47
+ enabled?: boolean;
47
48
  sampling?: SamplingOptions;
48
49
  }
49
50
 
@@ -78,6 +79,39 @@ export interface WebAnalyticsOptions {
78
79
  replayOptions?: Partial<ReplayTrackerOptions>;
79
80
  }
80
81
 
82
+ function hasReplaySamplingConfig(options: WebAnalyticsOptions): boolean {
83
+ return (
84
+ options.replayOptions?.samplingPercentage !== undefined ||
85
+ options.sessionReplays?.sampling?.percentage !== undefined
86
+ );
87
+ }
88
+
89
+ export function resolveReplayTrackerOptions(
90
+ options: WebAnalyticsOptions,
91
+ baseUrl: string,
92
+ debug: boolean,
93
+ ): ReplayTrackerOptions | null {
94
+ const replayEnabled =
95
+ options.sessionReplays?.enabled ??
96
+ options.trackReplay ??
97
+ hasReplaySamplingConfig(options);
98
+
99
+ if (!replayEnabled) return null;
100
+
101
+ const replayOptions = options.replayOptions ?? {};
102
+
103
+ return {
104
+ siteKey: options.siteKey,
105
+ baseUrl,
106
+ debug,
107
+ ...replayOptions,
108
+ samplingPercentage: normalizeSamplingPercentage(
109
+ replayOptions.samplingPercentage ??
110
+ options.sessionReplays?.sampling?.percentage,
111
+ ),
112
+ };
113
+ }
114
+
81
115
  let _instance: WebAnalytics | null = null;
82
116
  let pendingConsentMode: ConsentMode | undefined;
83
117
 
@@ -373,6 +407,81 @@ export class WebAnalytics {
373
407
  this.errorTracker = null;
374
408
  }
375
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
+
376
485
  async start(): Promise<void> {
377
486
  if (this.started || this.destroyed || typeof window === "undefined") return;
378
487
  if (_instance && _instance !== this) {
@@ -387,61 +496,22 @@ export class WebAnalytics {
387
496
  this.started = true;
388
497
  _instance = this;
389
498
  setCookielessMode(this.isCookielessMode());
499
+ getOrCreateSessionId();
390
500
 
391
501
  const opts = this.options;
502
+ const replayOptions = resolveReplayTrackerOptions(
503
+ opts,
504
+ this.baseUrl,
505
+ this.debug,
506
+ );
507
+ if (replayOptions) {
508
+ void this.startReplayTracker(replayOptions);
509
+ }
392
510
  if (opts.trackErrors) {
393
- try {
394
- const { default: ErrorTrackerClass } = await import("./error");
395
- const errorTracker: ErrorTracker = new ErrorTrackerClass({
396
- siteKey: opts.siteKey,
397
- baseUrl: this.baseUrl,
398
- debug: this.debug,
399
- });
400
- errorTracker.start();
401
- this.errorTracker = errorTracker;
402
- this.childTrackers.push(errorTracker);
403
- while (this.pendingReportedErrors.length > 0) {
404
- const pending = this.pendingReportedErrors.shift();
405
- if (pending) errorTracker.captureError(pending);
406
- }
407
- this.log("error loaded");
408
- } catch (error) {
409
- this.log(`failed to initialize error tracker: ${String(error)}`);
410
- }
511
+ void this.startErrorTracker();
411
512
  }
412
513
  if (opts.trackWebVitals) {
413
- try {
414
- const { default: WebVitalsTrackerClass } = await import("./web-vitals");
415
- const webVitalsTracker: WebVitalsTracker = new WebVitalsTrackerClass({
416
- siteKey: opts.siteKey,
417
- baseUrl: this.baseUrl,
418
- debug: this.debug,
419
- samplingPercentage: normalizeSamplingPercentage(
420
- opts.webVitals?.sampling?.percentage,
421
- ),
422
- });
423
- webVitalsTracker.start();
424
- this.childTrackers.push(webVitalsTracker);
425
- this.log("web-vitals loaded");
426
- } catch (error) {
427
- this.log(`failed to initialize web-vitals tracker: ${String(error)}`);
428
- }
429
- }
430
- if (opts.trackReplay) {
431
- try {
432
- const { default: ReplayTrackerClass } = await import("./replay");
433
- const replayTracker: ReplayTracker = new ReplayTrackerClass({
434
- siteKey: opts.siteKey,
435
- baseUrl: this.baseUrl,
436
- debug: this.debug,
437
- ...opts.replayOptions,
438
- });
439
- replayTracker.start();
440
- this.childTrackers.push(replayTracker);
441
- this.log("replay loaded");
442
- } catch (error) {
443
- this.log(`failed to initialize replay tracker: ${String(error)}`);
444
- }
514
+ void this.startWebVitalsTracker();
445
515
  }
446
516
 
447
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;
@@ -78,6 +84,7 @@ export default class ReplayTracker {
78
84
  private intervalId: ReturnType<typeof setInterval> | null = null;
79
85
  private flushTask: ReturnType<typeof setTimeout> | null = null;
80
86
  private retryTask: ReturnType<typeof setTimeout> | null = null;
87
+ private minLengthFlushTask: ReturnType<typeof setTimeout> | null = null;
81
88
  private stopRecording?: listenerHandler;
82
89
  private sending = false;
83
90
 
@@ -108,6 +115,10 @@ export default class ReplayTracker {
108
115
  return this.options.minReplayLengthMs ?? 3000;
109
116
  }
110
117
 
118
+ private get shouldCompress(): boolean {
119
+ return this.options.compress ?? false;
120
+ }
121
+
111
122
  private log(...args: unknown[]): void {
112
123
  if (this.debug) console.log("[Replay]", ...args);
113
124
  }
@@ -116,7 +127,8 @@ export default class ReplayTracker {
116
127
  if (this.started || typeof window === "undefined" || !this.sampled) return;
117
128
 
118
129
  this.started = true;
119
- this.startTime = Date.now();
130
+ getOrCreateSessionId();
131
+ this.startTime = getSessionStart();
120
132
 
121
133
  this.stopRecording = record({
122
134
  emit: this.onEvent,
@@ -161,10 +173,12 @@ export default class ReplayTracker {
161
173
  if (this.intervalId) clearInterval(this.intervalId);
162
174
  if (this.flushTask) clearTimeout(this.flushTask);
163
175
  if (this.retryTask) clearTimeout(this.retryTask);
176
+ if (this.minLengthFlushTask) clearTimeout(this.minLengthFlushTask);
164
177
 
165
178
  this.intervalId = null;
166
179
  this.flushTask = null;
167
180
  this.retryTask = null;
181
+ this.minLengthFlushTask = null;
168
182
 
169
183
  window.removeEventListener("beforeunload", this.onUnload);
170
184
  window.removeEventListener("pagehide", this.onUnload);
@@ -195,7 +209,10 @@ export default class ReplayTracker {
195
209
  (event.type === EventType.FullSnapshot && this.hasReachedMinLength())
196
210
  ) {
197
211
  this.requestFlush();
212
+ return;
198
213
  }
214
+
215
+ this.scheduleMinLengthFlush();
199
216
  };
200
217
 
201
218
  private onUnload = (): void => {
@@ -218,12 +235,38 @@ export default class ReplayTracker {
218
235
  private requestFlush = (): void => {
219
236
  if (this.flushTask || this.sending || this.events.length === 0) return;
220
237
 
238
+ if (this.minLengthFlushTask) {
239
+ clearTimeout(this.minLengthFlushTask);
240
+ this.minLengthFlushTask = null;
241
+ }
242
+
221
243
  this.flushTask = setTimeout(() => {
222
244
  this.flushTask = null;
223
245
  void this.flush(false);
224
246
  }, 0);
225
247
  };
226
248
 
249
+ private scheduleMinLengthFlush(): void {
250
+ if (
251
+ this.minLengthFlushTask ||
252
+ this.events.length === 0 ||
253
+ this.hasReachedMinLength()
254
+ ) {
255
+ return;
256
+ }
257
+
258
+ const remainingMs = Math.max(
259
+ 0,
260
+ this.minReplayLengthMs - (Date.now() - this.startTime),
261
+ );
262
+ const timeoutMs = Math.min(remainingMs, MAX_TIMEOUT_MS);
263
+
264
+ this.minLengthFlushTask = setTimeout(() => {
265
+ this.minLengthFlushTask = null;
266
+ this.requestFlush();
267
+ }, timeoutMs);
268
+ }
269
+
227
270
  private createBatch(events: eventWithTime[]): ReplayBatch {
228
271
  const identifier = getAnonymousId();
229
272
 
@@ -241,12 +284,12 @@ export default class ReplayTracker {
241
284
  private async encodeBatch(
242
285
  batch: ReplayBatch,
243
286
  lowLatency: boolean,
244
- ): Promise<{ data: Blob; isCompressed: boolean }> {
287
+ ): Promise<{ data: string | Blob; isCompressed: boolean }> {
245
288
  const json = JSON.stringify(batch);
246
289
 
247
- if (!this.compressionSupported || lowLatency) {
290
+ if (!this.shouldCompress || !this.compressionSupported || lowLatency) {
248
291
  return {
249
- data: new Blob([json], { type: "application/json" }),
292
+ data: json,
250
293
  isCompressed: false,
251
294
  };
252
295
  }
@@ -270,11 +313,10 @@ export default class ReplayTracker {
270
313
 
271
314
  if (this.events.length > 0) {
272
315
  if (!this.hasReachedMinLength()) {
273
- this.log(
274
- `Too short (${Date.now() - this.startTime}ms), skipping flush`,
275
- );
316
+ this.scheduleMinLengthFlush();
276
317
  } else {
277
- this.pending.push(this.createBatch(this.events.splice(0)));
318
+ const batch = this.createBatch(this.events.splice(0));
319
+ this.pending.push(batch);
278
320
  }
279
321
  }
280
322
 
@@ -298,11 +340,10 @@ export default class ReplayTracker {
298
340
  this.log(`Pending buffer full, dropping batch ${batch.sequence}`);
299
341
  this.pending.shift();
300
342
  }
343
+ this.log(`Failed to send replay batch ${batch.sequence}, retrying`);
301
344
  this.scheduleRetry();
302
345
  break;
303
346
  }
304
-
305
- this.log(`Sent ${batch.events.length} events (seq: ${batch.sequence})`);
306
347
  this.pending.shift();
307
348
  lowLatency = false;
308
349
  }
@@ -313,7 +354,6 @@ export default class ReplayTracker {
313
354
 
314
355
  private scheduleRetry(): void {
315
356
  if (this.retryTask) return;
316
-
317
357
  this.retryTask = setTimeout(() => {
318
358
  this.retryTask = null;
319
359
  void this.flush(false);
@@ -346,17 +386,11 @@ export default class ReplayTracker {
346
386
  offset += chunk.length;
347
387
  }
348
388
 
349
- this.log(
350
- `Compressed: ${input.length} → ${output.length} bytes (${(
351
- (output.length / input.length) * 100
352
- ).toFixed(1)}%)`,
353
- );
354
-
355
389
  return new Blob([output], { type: "application/octet-stream" });
356
390
  }
357
391
 
358
392
  private send(
359
- data: Blob,
393
+ data: string | Blob,
360
394
  isCompressed: boolean,
361
395
  lowLatency: boolean,
362
396
  ): Promise<boolean> {
@@ -367,9 +401,8 @@ export default class ReplayTracker {
367
401
  contentType: isCompressed
368
402
  ? "application/octet-stream"
369
403
  : "application/json",
370
- debug: this.debug,
371
- debugPrefix: `[Replay] ${(data.size / 1024).toFixed(1)}KB`,
372
- useBeacon: lowLatency,
404
+ debug: false,
405
+ useBeacon: false,
373
406
  keepalive: lowLatency,
374
407
  }) ?? Promise.resolve(false)
375
408
  );
@@ -1,6 +1,7 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
2
  import {
3
3
  getInstance,
4
+ resolveReplayTrackerOptions,
4
5
  sendData,
5
6
  setConsentMode,
6
7
  WebAnalytics,
@@ -358,4 +359,25 @@ describe("WebAnalytics lifecycle", () => {
358
359
  ),
359
360
  ).toBe(true);
360
361
  });
362
+
363
+ test("resolveReplayTrackerOptions honors sessionReplays config", () => {
364
+ const options = resolveReplayTrackerOptions(
365
+ {
366
+ siteKey: "site_test",
367
+ sessionReplays: {
368
+ enabled: true,
369
+ sampling: { percentage: 37 },
370
+ },
371
+ },
372
+ "https://metrics.example.com",
373
+ true,
374
+ );
375
+
376
+ expect(options).toMatchObject({
377
+ siteKey: "site_test",
378
+ baseUrl: "https://metrics.example.com",
379
+ debug: true,
380
+ samplingPercentage: 37,
381
+ });
382
+ });
361
383
  });
@@ -17,6 +17,37 @@ class MockStorage {
17
17
  }
18
18
  }
19
19
 
20
+ type ReplayEvent = {
21
+ type: number;
22
+ timestamp: number;
23
+ data?: Record<string, unknown>;
24
+ };
25
+
26
+ type ReplayBatch = {
27
+ token: string;
28
+ sessionId?: string;
29
+ identifier?: string;
30
+ sequence: number;
31
+ timestamp: number;
32
+ url: string;
33
+ events: ReplayEvent[];
34
+ };
35
+
36
+ type ReplayTrackerInternals = {
37
+ events: ReplayEvent[];
38
+ pending: ReplayBatch[];
39
+ minLengthFlushTask: ReturnType<typeof setTimeout> | null;
40
+ retryTask: ReturnType<typeof setTimeout> | null;
41
+ startTime: number;
42
+ onEvent: (event: ReplayEvent, isCheckout?: boolean) => void;
43
+ flush: (lowLatency: boolean) => Promise<void>;
44
+ send: (
45
+ data: string | Blob,
46
+ isCompressed: boolean,
47
+ lowLatency: boolean,
48
+ ) => Promise<boolean>;
49
+ };
50
+
20
51
  function setGlobal(name: keyof typeof globalThis, value: unknown): void {
21
52
  Object.defineProperty(globalThis, name, {
22
53
  configurable: true,
@@ -66,86 +97,54 @@ afterEach(() => {
66
97
  setGlobal("sessionStorage", original.sessionStorage);
67
98
  });
68
99
 
100
+ function createTracker(
101
+ options: Partial<ConstructorParameters<typeof ReplayTracker>[0]> = {},
102
+ ): ReplayTrackerInternals {
103
+ return new ReplayTracker({
104
+ siteKey: "site_test",
105
+ ...options,
106
+ }) as unknown as ReplayTrackerInternals;
107
+ }
108
+
69
109
  describe("ReplayTracker", () => {
70
- test("collects replay events", () => {
71
- const tracker = new ReplayTracker({
72
- siteKey: "site_test",
110
+ test("buffers replay events", () => {
111
+ const tracker = createTracker({
73
112
  maxEvents: 1000,
74
113
  minReplayLengthMs: Number.MAX_SAFE_INTEGER,
75
- }) as unknown as {
76
- handleEvent: (event: unknown, isCheckout?: boolean) => void;
77
- events: unknown[];
78
- };
114
+ });
79
115
 
80
- for (let i = 0; i < 12; i++) {
81
- tracker.handleEvent({ id: i, timestamp: i }, false);
82
- }
116
+ tracker.onEvent({ type: 2, timestamp: 1, data: {} }, false);
117
+ tracker.onEvent({ type: 3, timestamp: 2, data: {} }, false);
83
118
 
84
- expect(tracker.events.length).toBe(12);
119
+ expect(tracker.events.length).toBe(2);
85
120
  });
86
121
 
87
- test("keeps pending batches for retry", () => {
88
- const tracker = new ReplayTracker({
89
- siteKey: "site_test",
90
- }) as unknown as {
91
- pendingBatches: unknown[];
92
- };
93
-
94
- tracker.pendingBatches.push({
95
- batch: {
96
- token: "site_test",
97
- sessionId: "s1",
98
- sequence: 1,
99
- timestamp: 1,
100
- url: "https://example.com/",
101
- events: [],
102
- },
103
- isCompressed: false,
104
- retries: 0,
122
+ test("does not flush before minimum replay length", async () => {
123
+ const tracker = createTracker({
124
+ minReplayLengthMs: Number.MAX_SAFE_INTEGER,
105
125
  });
106
- expect(tracker.pendingBatches.length).toBe(1);
107
- });
108
126
 
109
- test("deduplicates scheduled flush work", () => {
110
- let idleCallbackRuns = 0;
111
- setGlobal("window", {
112
- location: { href: "https://example.com/" },
113
- addEventListener: () => {},
114
- removeEventListener: () => {},
115
- requestIdleCallback: () => {
116
- idleCallbackRuns++;
117
- return 1;
118
- },
119
- cancelIdleCallback: () => {},
120
- } as unknown as Window);
121
-
122
- const tracker = new ReplayTracker({
123
- siteKey: "site_test",
124
- }) as unknown as {
125
- events: unknown[];
126
- scheduleFlush: () => void;
127
+ let sent = 0;
128
+ tracker.send = async () => {
129
+ sent++;
130
+ return true;
127
131
  };
132
+ tracker.startTime = Date.now();
133
+ tracker.onEvent({ type: 2, timestamp: Date.now(), data: {} }, false);
128
134
 
129
- tracker.events = [{ type: 2 }, { type: 3 }];
130
- tracker.scheduleFlush();
131
- tracker.scheduleFlush();
132
- expect(idleCallbackRuns).toBe(1);
135
+ await tracker.flush(false);
136
+
137
+ expect(sent).toBe(0);
138
+ expect(tracker.events.length).toBe(1);
139
+ expect(tracker.pending.length).toBe(0);
140
+
141
+ if (tracker.minLengthFlushTask) clearTimeout(tracker.minLengthFlushTask);
133
142
  });
134
143
 
135
- test("flushes buffered snapshot when minimum replay length is reached", async () => {
136
- const tracker = new ReplayTracker({
137
- siteKey: "site_test",
138
- minReplayLengthMs: 1,
139
- }) as unknown as {
140
- events: unknown[];
141
- send: (
142
- data: Blob,
143
- isCompressed: boolean,
144
- options?: { useBeacon?: boolean },
145
- ) => Promise<boolean>;
146
- scheduleMinLengthFlush: () => void;
147
- startTime: number;
148
- };
144
+ test("flushes buffered events once minimum replay length is reached", async () => {
145
+ const tracker = createTracker({
146
+ minReplayLengthMs: 20,
147
+ });
149
148
 
150
149
  let sent = 0;
151
150
  tracker.send = async () => {
@@ -153,95 +152,74 @@ describe("ReplayTracker", () => {
153
152
  return true;
154
153
  };
155
154
  tracker.startTime = Date.now();
156
- tracker.events = [{ type: 2, timestamp: Date.now(), data: {} }];
155
+ tracker.onEvent({ type: 2, timestamp: Date.now(), data: {} }, false);
157
156
 
158
- tracker.scheduleMinLengthFlush();
159
- await new Promise((resolve) => setTimeout(resolve, 20));
157
+ await new Promise((resolve) => setTimeout(resolve, 40));
160
158
 
161
159
  expect(sent).toBe(1);
162
160
  expect(tracker.events.length).toBe(0);
161
+ expect(tracker.pending.length).toBe(0);
163
162
  });
164
163
 
165
- test("caps pending batch buffer size", () => {
166
- const tracker = new ReplayTracker({
167
- siteKey: "site_test",
168
- maxPendingBatches: 3,
169
- }) as unknown as {
170
- pendingBatches: unknown[];
171
- queuePendingBatch: (batch: unknown) => void;
164
+ test("emits replay payload when flushed", async () => {
165
+ const tracker = createTracker({
166
+ minReplayLengthMs: 0,
167
+ });
168
+
169
+ let payload: ReplayBatch | null = null;
170
+ let lowLatency = false;
171
+ tracker.send = async (data, _isCompressed, nextLowLatency) => {
172
+ payload = JSON.parse(
173
+ typeof data === "string" ? data : await data.text(),
174
+ ) as ReplayBatch;
175
+ lowLatency = nextLowLatency;
176
+ return true;
172
177
  };
178
+ tracker.startTime = Date.now() - 1000;
179
+ tracker.onEvent({ type: 2, timestamp: Date.now(), data: {} }, false);
173
180
 
174
- for (let i = 0; i < 5; i++) {
175
- tracker.queuePendingBatch({
176
- batch: {
177
- token: "site_test",
178
- sessionId: "s1",
179
- sequence: i,
180
- timestamp: i,
181
- url: "https://example.com/",
182
- events: [],
183
- },
184
- isCompressed: false,
185
- retries: 0,
186
- });
187
- }
188
-
189
- expect(tracker.pendingBatches.length).toBe(3);
181
+ await tracker.flush(true);
182
+
183
+ expect(payload).not.toBeNull();
184
+ expect(payload?.token).toBe("site_test");
185
+ expect(payload?.sessionId).toBeTruthy();
186
+ expect(payload?.events).toHaveLength(1);
187
+ expect(lowLatency).toBe(true);
188
+ expect(tracker.pending.length).toBe(0);
190
189
  });
191
190
 
192
- test("queues new flush behind pending batches to preserve order", async () => {
193
- const tracker = new ReplayTracker({
194
- siteKey: "site_test",
191
+ test("sends replay as plain JSON by default", async () => {
192
+ const tracker = createTracker({
195
193
  minReplayLengthMs: 0,
196
- }) as unknown as {
197
- events: unknown[];
198
- pendingBatches: Array<{
199
- batch: {
200
- token: string;
201
- sessionId: string;
202
- sequence: number;
203
- timestamp: number;
204
- url: string;
205
- events: unknown[];
206
- };
207
- isCompressed: boolean;
208
- retries: number;
209
- }>;
210
- sequenceNumber: number;
211
- flush: (options?: { lowLatency?: boolean }) => Promise<void>;
212
- send: (
213
- data: Blob,
214
- isCompressed: boolean,
215
- options?: { useBeacon?: boolean },
216
- ) => Promise<boolean>;
217
- };
218
-
219
- tracker.sequenceNumber = 2;
220
- tracker.pendingBatches.push({
221
- batch: {
222
- token: "site_test",
223
- sessionId: "s1",
224
- sequence: 1,
225
- timestamp: 1,
226
- url: "https://example.com/",
227
- events: [{ id: "older" }],
228
- },
229
- isCompressed: false,
230
- retries: 0,
231
194
  });
232
- tracker.events = [{ id: "newer" }];
233
195
 
234
- const sentSequences: number[] = [];
196
+ let bodyType: "string" | "blob" | null = null;
235
197
  tracker.send = async (data) => {
236
- const payload = JSON.parse(await data.text()) as { sequence: number };
237
- sentSequences.push(payload.sequence);
198
+ bodyType = typeof data === "string" ? "string" : "blob";
238
199
  return true;
239
200
  };
201
+ tracker.startTime = Date.now() - 1000;
202
+ tracker.onEvent({ type: 2, timestamp: Date.now(), data: {} }, false);
203
+
204
+ await tracker.flush(false);
205
+
206
+ expect(bodyType).toBe("string");
207
+ });
208
+
209
+ test("keeps failed batches queued for retry", async () => {
210
+ const tracker = createTracker({
211
+ minReplayLengthMs: 0,
212
+ });
213
+
214
+ tracker.send = async () => false;
215
+ tracker.startTime = Date.now() - 1000;
216
+ tracker.onEvent({ type: 2, timestamp: Date.now(), data: {} }, false);
217
+
218
+ await tracker.flush(false);
240
219
 
241
- await tracker.flush();
242
- await new Promise((resolve) => setTimeout(resolve, 0));
220
+ expect(tracker.pending.length).toBe(1);
221
+ expect(tracker.retryTask).not.toBeNull();
243
222
 
244
- expect(sentSequences).toEqual([1, 2]);
245
- expect(tracker.pendingBatches.length).toBe(0);
223
+ if (tracker.retryTask) clearTimeout(tracker.retryTask);
246
224
  });
247
225
  });
@@ -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()}let m=null,h;function g(){return m}function _(e,t){typeof window>`u`||w()||m?.track(e,t??{})}function v(e,t,n){typeof window>`u`||w()||m?.identify(e,t,n??{})}function y(e=!0){typeof window>`u`||w()||m?.logout(e)}function b(e){if(m){m.setConsentMode(e);return}h=e}function x(){b(`granted`)}function S(){b(`denied`)}function C(e){typeof window>`u`||w()||m?.reportError(e)}function w(){if(typeof localStorage>`u`)return!1;let e=localStorage.getItem(`disable-faststats`);return e===`true`||e===`1`}async function T(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 E(e){for(;e;){if(e instanceof HTMLAnchorElement&&e.href)return e;e=e.parentNode}return null}function D(){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 O=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,h!==void 0&&(this.consentMode=h,h=void 0),(e.autoTrack??!0)&&this.init()}log(e){this.debug&&console.log(`[Analytics] ${e}`)}init(){if(!(typeof window>`u`)){if(w()){this.log(`disabled`);return}m=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||w()?!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(m&&m!==this){this.log(`already started by another instance`);return}if(w()){this.log(`disabled`);return}this.started=!0,m=this,t(this.isCookielessMode());let e=this.options;if(e.trackErrors)try{let{default:t}=await import(`./error-BLRIDscG.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)}`)}if(e.trackReplay)try{let{default:t}=await import(`./replay-44QeDpKH.js`),n=new t({siteKey:e.siteKey,baseUrl:this.baseUrl,debug:this.debug,...e.replayOptions});n.start(),this.childTrackers.push(n),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(),m===this&&(m=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||T({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`||w())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`||w()||!(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||w())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,...D(),...t}});this.log(e),T({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=E(e.target);t&&t.host!==location.host&&this.track(`outbound_link`,{outbound_link:t.href})};this.addDocumentListener(`click`,e),this.addDocumentListener(`auxclick`,e)}};export{y as a,C as c,_ as d,p as f,w as i,T as l,g as n,x as o,v as r,S as s,O as t,b 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-B988lkxn.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};