@faststats/web 0.2.9 → 0.2.11

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,14 @@
1
1
  # @faststats/web
2
2
 
3
+ ## 0.2.11
4
+
5
+ ### Patch Changes
6
+
7
+ - 754b67f: fix(replays): dedupe secquences to reduce risk of duplicate data sending
8
+ - d21ce42: refactor: more reliable and unified data sending
9
+
10
+ ## 0.2.10
11
+
3
12
  ## 0.2.9
4
13
 
5
14
  ### Patch Changes
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{o as t}from"./api-urls-BrkcoElX.js";import{n,t as r}from"./identifiers-BN-atT_6.js";import{t as i}from"./send-data-DL_GlsQw.js";var a=e({default:()=>d});const o=/(?:^|\()(chrome|moz|ms-browser|safari-web)-extension:\/\//,s=e=>e?.split(`
2
+ `).map(e=>e.trim()).filter(Boolean);function c(e){if(e instanceof Error)return{error:e.name?.trim()||`Error`,message:e.message,stack:s(e.stack),cause:c(e.cause)};if(typeof e==`string`)return{error:`Error`,message:e}}function l(e){return e?`${e.error}\0${e.message??``}\0${l(e.cause)}`:``}function u(e){let t=[e.kind,e.handled?`handled`:`unhandled`,e.message,e.filename??``,e.lineno??``,l(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 d=class{endpoint;handled=new WeakSet;queue=new Map;timer=null;started=!1;flushing=!1;constructor(e){this.options=e,this.endpoint=t(e.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.requestFlush),this.timer=setInterval(this.requestFlush,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.requestFlush),this.timer&&clearInterval(this.timer),this.timer=null,this.requestFlush(),this.log(`Stopped listening for errors`))}captureError(e){this.record({kind:`error`,message:e.message,stack:e.stack,handled:!0,cause:c(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?c(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:c(t.cause)},t)};onVisibilityChange=()=>{document.visibilityState===`hidden`&&this.requestFlush()};requestFlush=()=>{this.flush()};restoreErrors(e){for(let t of e)this.queue.set(t.hash,{...t,count:t.count+(this.queue.get(t.hash)?.count??0)})}record(e,t){if(t instanceof Error){if(this.handled.has(t)){this.log(`Skipping duplicate:`,t.message);return}this.handled.add(t)}if(o.test(`${e.filename??``}\n${e.stack??``}`))return;let n=u(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:s(e.stack),handled:e.handled,...e.cause?{cause:e.cause}:{}}),this.log(`Captured error:`,e),this.queue.size>=this.maxQueueSize&&this.requestFlush()}async flush(){if(this.flushing||this.queue.size===0)return;let e=[...this.queue.values()];this.queue.clear(),this.flushing=!0;let t=r(),a=globalThis.__SOURCEMAPS_BUILD__,o=typeof a?.buildId==`string`&&a.buildId.trim()?a.buildId:void 0,s=JSON.stringify({token:this.options.siteKey,...t?{userId:t}:{},sessionId:n(),...o?{buildId:o}:{},sdkName:this.options.sdkName??`@faststats/web`,sdkVersion:this.options.sdkVersion??`0.2.11`,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);try{await i({url:this.endpoint,data:s,debug:this.debug,debugPrefix:`[ErrorTracker]`})||this.restoreErrors(e)}catch{this.restoreErrors(e)}finally{this.flushing=!1}}};export{a as n,d as t};
@@ -1 +1 @@
1
- let e=!1;function t(t){e=t}function n(t){return t??e?``:r()}function r(){if(typeof localStorage>`u`)return``;let e=localStorage.getItem(`faststats_anon_id`);if(e)return e;let t=crypto.randomUUID();return localStorage.setItem(`faststats_anon_id`,t),t}function i(e){return e||typeof localStorage>`u`?``:(localStorage.removeItem(`faststats_anon_id`),r())}function a(){if(typeof sessionStorage>`u`)return``;let e=sessionStorage.getItem(`session_id`),t=sessionStorage.getItem(`session_timestamp`);if(e&&t){if(Date.now()-Number.parseInt(t,10)<18e5)return sessionStorage.setItem(`session_timestamp`,Date.now().toString()),e;sessionStorage.removeItem(`session_id`),sessionStorage.removeItem(`session_timestamp`),sessionStorage.removeItem(`session_start`)}let n=Date.now().toString(),r=crypto.randomUUID();return sessionStorage.setItem(`session_id`,r),sessionStorage.setItem(`session_timestamp`,n),sessionStorage.setItem(`session_start`,n),r}function o(){return typeof sessionStorage>`u`?``:(sessionStorage.removeItem(`session_id`),sessionStorage.removeItem(`session_timestamp`),sessionStorage.removeItem(`session_start`),a())}function s(){typeof sessionStorage>`u`||sessionStorage.getItem(`session_id`)&&sessionStorage.setItem(`session_timestamp`,Date.now().toString())}function c(){if(typeof sessionStorage>`u`)return Date.now();let e=sessionStorage.getItem(`session_start`);if(e)return Number.parseInt(e,10);let t=sessionStorage.getItem(`session_timestamp`);return t?Number.parseInt(t,10):Date.now()}function l(e){return typeof e!=`number`||!Number.isFinite(e)?100:Math.max(0,Math.min(100,e))}export{s as a,t as c,c as i,n,i as o,a as r,o as s,l as t};
1
+ let e=!1;function t(t){e=t}function n(t){return t??e?``:r()}function r(){if(typeof localStorage>`u`)return``;let e=localStorage.getItem(`faststats_anon_id`);if(e)return e;let t=crypto.randomUUID();return localStorage.setItem(`faststats_anon_id`,t),t}function i(e){return e||typeof localStorage>`u`?``:(localStorage.removeItem(`faststats_anon_id`),r())}function a(){if(typeof sessionStorage>`u`)return``;let e=sessionStorage.getItem(`session_id`),t=sessionStorage.getItem(`session_timestamp`);if(e&&t){if(Date.now()-Number.parseInt(t,10)<18e5)return sessionStorage.setItem(`session_timestamp`,Date.now().toString()),e;sessionStorage.removeItem(`session_id`),sessionStorage.removeItem(`session_timestamp`),sessionStorage.removeItem(`session_start`)}let n=Date.now().toString(),r=crypto.randomUUID();return sessionStorage.setItem(`session_id`,r),sessionStorage.setItem(`session_timestamp`,n),sessionStorage.setItem(`session_start`,n),r}function o(){return typeof sessionStorage>`u`?``:(sessionStorage.removeItem(`session_id`),sessionStorage.removeItem(`session_timestamp`),sessionStorage.removeItem(`session_start`),a())}function s(){typeof sessionStorage>`u`||sessionStorage.getItem(`session_id`)&&sessionStorage.setItem(`session_timestamp`,Date.now().toString())}function c(){if(typeof sessionStorage>`u`)return Date.now();let e=sessionStorage.getItem(`session_start`);if(e)return Number.parseInt(e,10);let t=sessionStorage.getItem(`session_timestamp`);return t?Number.parseInt(t,10):Date.now()}export{i as a,s as i,a as n,o,c as r,t as s,n as t};
@@ -64,6 +64,7 @@ declare class ReplayTracker {
64
64
  private requestFlush;
65
65
  private scheduleMinLengthFlush;
66
66
  private createBatch;
67
+ private createBatchId;
67
68
  private getBatchSizeBytes;
68
69
  private dropOldestPendingBatch;
69
70
  private enqueueBatch;
@@ -0,0 +1 @@
1
+ import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{a as t}from"./api-urls-BrkcoElX.js";import{n,r,t as i}from"./identifiers-BN-atT_6.js";import{t as a}from"./send-data-DL_GlsQw.js";import{t as o}from"./types-CYzR5xtT.js";import{EventType as s}from"@rrweb/types";import{record as c}from"rrweb";var l=e({default:()=>f});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=[];pendingSizeBytes=0;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=t(e.baseUrl),this.sampled=Math.random()*100<o(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 maxQueueSizeBytes(){return this.options.maxQueueSizeBytes??2097152}get minReplayLengthMs(){return this.options.minReplayLengthMs??3e3}get shouldCompress(){return this.options.compress??!0}log(...e){this.debug&&console.log(`[Replay]`,...e)}start(){this.started||typeof window>`u`||!this.sampled||(this.started=!0,this.sessionId=n(),this.startTime=r(),this.beginRecording(),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`))}async beginRecording(){let e=this.options.recordConsole??!0,[{getRecordSequentialIdPlugin:t},n]=await Promise.all([import(`@rrweb/rrweb-plugin-sequential-id-record`),e?import(`@rrweb/rrweb-plugin-console-record`):Promise.resolve(null)]);if(!this.started)return;let r=[t({key:`_faststatsSeqId`})];n&&r.push(n.getRecordConsolePlugin()),this.stopRecording=c({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:r})}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??n()}onEvent=(e,t)=>{if(this.events.push(e),t||this.events.length>=this.maxEvents||e.type===s.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 t=i(),n=this.sequence++;return{token:this.options.siteKey,sessionId:this.getSessionId(),...t?{identifier:t}:{},batchId:this.createBatchId(n),sequence:n,timestamp:Date.now(),url:window.location.href,events:e}}createBatchId(e){let t=typeof crypto<`u`&&`randomUUID`in crypto?crypto.randomUUID():`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;return`${this.getSessionId()??`unknown`}-${e}-${t}`}getBatchSizeBytes(e){return new TextEncoder().encode(JSON.stringify(e)).byteLength}dropOldestPendingBatch(e){let t=this.pending.shift();t&&(this.pendingSizeBytes=Math.max(0,this.pendingSizeBytes-this.getBatchSizeBytes(t)),this.log(`${e}, dropping batch ${t.sequence}`))}enqueueBatch(e){let t=this.getBatchSizeBytes(e);if(t>this.maxQueueSizeBytes){this.log(`Replay batch ${e.sequence} is ${t}B, exceeding ${this.maxQueueSizeBytes}B queue limit; dropping`);return}for(;this.pending.length>0&&this.pendingSizeBytes+t>this.maxQueueSizeBytes;)this.dropOldestPendingBatch(`Pending queue size limit reached`);for(;this.pending.length>=this.maxPendingBatches;)this.dropOldestPendingBatch(`Pending batch limit reached`);this.pending.push(e),this.pendingSizeBytes+=t}async encodeBatch(e){let t=JSON.stringify(e);if(!this.shouldCompress||!this.compressionSupported)return{data:t,isCompressed:!1};try{let e=await this.compress(t);return this.log(`Compressed ${t.length}B -> ${e.byteLength}B (${Math.round(e.byteLength/t.length*100)}%)`),{data:e,isCompressed:!0}}catch{return this.log(`Compression failed, using uncompressed`),{data:t,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.enqueueBatch(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);if(!await this.send(n.data,n.isCompressed,e)){this.log(`Failed to send replay batch ${t.sequence}, retrying`),this.scheduleRetry();break}this.dropOldestPendingBatch(`Sent replay batch`),e=!1}}finally{this.sending=!1}}}}scheduleRetry(){this.retryTask||=setTimeout(()=>{this.retryTask=null,this.flush(!1)},1e3)}async compress(e){let t=new Blob([e]).stream().pipeThrough(new CompressionStream(`gzip`)),n=await new Response(t).arrayBuffer();return new Uint8Array(n)}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{l as n,f as t};
@@ -0,0 +1 @@
1
+ async function e(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 globalThis.navigator?.sendBeacon==`function`)try{let e=n instanceof Blob?n:typeof Blob<`u`?new Blob([n],{type:r}):n;if(globalThis.navigator.sendBeacon(t,e))return a&&console.log(`${o} Sent via beacon`),!0}catch{}try{let e=await fetch(t,{method:`POST`,body:n,headers:{"Content-Type":r,...i},keepalive:c}),s=e.ok;if(a){let t=s?`${o} Sent via fetch`:`${o} Failed: ${e.status}`;console[s?`log`:`warn`](t)}return s}catch{return a&&console.warn(`${o} Failed to send`),!1}}export{e as t};
@@ -0,0 +1 @@
1
+ function e(e){return typeof e!=`number`||!Number.isFinite(e)?100:Math.max(0,Math.min(100,e))}export{e as t};
@@ -0,0 +1 @@
1
+ import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{s as t}from"./api-urls-BrkcoElX.js";import{n}from"./identifiers-BN-atT_6.js";import{t as r}from"./types-CYzR5xtT.js";var i=e({default:()=>s});async function a(e){let{onCLS:t,onFCP:n,onINP:r,onLCP:i,onTTFB:a}=e?await import(`web-vitals/attribution`):await import(`web-vitals`);return o([t,n,r,i,a])}function o([e,t,n,r,i]){return[{observe:e,options:{reportAllChanges:!0}},{observe:t},{observe:n,options:{reportAllChanges:!0}},{observe:r,options:{reportAllChanges:!0}},{observe:i}]}var s=class{endpoint;metricsByUrl=new Map;pendingFlushUrls=new Set;sampled;started=!1;flushing=!1;finalFlushRequested=!1;currentUrl=``;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}log(...e){this.debug&&console.log(`[WebVitals]`,...e)}start(){this.started||typeof window>`u`||(this.started=!0,this.currentUrl=window.location.href,document.addEventListener(`visibilitychange`,this.onVisibilityChange,{passive:!0}),window.addEventListener(`pagehide`,this.onPageHide,{passive:!0}),this.log(`Tracking started`),this.observe())}stop(){!this.started||typeof window>`u`||(this.started=!1,this.cleanupListeners(),this.queueFlush(this.currentUrl,!0))}trackPageChange(e){if(!this.started||typeof window>`u`)return;let t=e??window.location.href;if(t===this.currentUrl)return;let n=this.currentUrl;this.currentUrl=t,this.queueFlush(n)}cleanupListeners(){document.removeEventListener(`visibilitychange`,this.onVisibilityChange),window.removeEventListener(`pagehide`,this.onPageHide)}async observe(){try{let e=await a(this.options.attribution??!1);if(!this.started)return;for(let{observe:t,options:n}of e)t(this.captureMetric,n)}catch(e){this.log(`Failed to load web-vitals`,e)}}onVisibilityChange=()=>{document.visibilityState===`hidden`&&this.queueFlush(this.currentUrl,!0)};onPageHide=()=>{this.queueFlush(this.currentUrl,!0)};captureMetric=e=>{if(!this.started||!this.sampled)return;let t=e.name,n=e.attribution??void 0;this.metricsForUrl(this.currentUrl).set(t,{value:e.value,attributes:{id:e.id,rating:e.rating,delta:e.delta,navigationType:e.navigationType,...n??{}}}),this.log(`${t} captured: ${e.value}`)};metricsForUrl(e){let t=this.metricsByUrl.get(e);return t||(t=new Map,this.metricsByUrl.set(e,t)),t}queueFlush(e,t=!1){this.finalFlushRequested||=t,this.pendingFlushUrls.add(e),this.flushPending()}async flushPending(){if(!this.flushing){if(typeof window>`u`||typeof fetch!=`function`){this.finishFinalFlushIfNeeded();return}this.flushing=!0;try{let e=[...this.pendingFlushUrls];this.pendingFlushUrls.clear();for(let t of e)await this.sendMetricsForUrl(t)||this.pendingFlushUrls.add(t)}finally{this.flushing=!1,this.finishFinalFlushIfNeeded()}}}async sendMetricsForUrl(e){let t=this.metricsByUrl.get(e);if(!t||t.size===0)return!0;let r=[...t.entries()].map(([e,t])=>({metric:e,value:t.value,attributes:t.attributes})),i=JSON.stringify({sessionId:n(),vitals:r,metadata:{url:e}});this.log(`Sending metrics for ${e} (${r.length})`);try{let t=new AbortController,n=window.setTimeout(()=>t.abort(),3e3);try{let n=await fetch(this.endpoint,{method:`POST`,body:i,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${this.options.siteKey}`},keepalive:!0,signal:t.signal});if(!n.ok)throw Error(`HTTP ${n.status}`);this.metricsByUrl.delete(e)}finally{clearTimeout(n)}return!0}catch(e){return this.log(`Failed to send metrics`,e),!1}}finishFinalFlushIfNeeded(){this.finalFlushRequested&&(this.finalFlushRequested=!1,this.started=!1,this.cleanupListeners())}};export{i as n,s as t};
package/dist/error.d.ts CHANGED
@@ -26,6 +26,7 @@ declare class ErrorTracker {
26
26
  private readonly queue;
27
27
  private timer;
28
28
  private started;
29
+ private flushing;
29
30
  constructor(options: ErrorTrackingOptions);
30
31
  private get debug();
31
32
  private get flushInterval();
@@ -37,6 +38,8 @@ declare class ErrorTracker {
37
38
  private onError;
38
39
  private onRejection;
39
40
  private onVisibilityChange;
41
+ private requestFlush;
42
+ private restoreErrors;
40
43
  private record;
41
44
  private flush;
42
45
  }
package/dist/error.js CHANGED
@@ -1 +1 @@
1
- import{t as e}from"./chunks/error-C-fHJ_1D.js";export{e as default};
1
+ import{t as e}from"./chunks/error-3yIt0PgC.js";export{e as default};
@@ -1,2 +1,2 @@
1
- import { n as FeatureFlagEvaluation, r as fetchFeatureFlagEvaluation, t as FeatureFlagCheckContext } from "./chunks/feature-flags-6rZlmhfu.js";
1
+ import { n as FeatureFlagEvaluation, r as fetchFeatureFlagEvaluation, t as FeatureFlagCheckContext } from "./chunks/feature-flags-BxZz_lNm.js";
2
2
  export { type FeatureFlagCheckContext, type FeatureFlagEvaluation, fetchFeatureFlagEvaluation };
@@ -1 +1 @@
1
- import{n as e}from"./chunks/feature-flags-CqVtrpX2.js";export{e as fetchFeatureFlagEvaluation};
1
+ import{n as e}from"./chunks/feature-flags-CjnLZGxp.js";export{e as fetchFeatureFlagEvaluation};
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { n as FeatureFlagEvaluation, r as fetchFeatureFlagEvaluation, t as FeatureFlagCheckContext } from "./chunks/feature-flags-6rZlmhfu.js";
2
- import { n as ReplayTrackerOptions } from "./chunks/replay-IhsP2Ab4.js";
1
+ import { n as FeatureFlagEvaluation, r as fetchFeatureFlagEvaluation, t as FeatureFlagCheckContext } from "./chunks/feature-flags-BxZz_lNm.js";
2
+ import { n as ReplayTrackerOptions } from "./chunks/replay-BuX3_0xs.js";
3
3
 
4
4
  //#region src/utils/types.d.ts
5
5
  interface SendDataOptions {
@@ -13,6 +13,9 @@ interface SendDataOptions {
13
13
  keepalive?: boolean;
14
14
  }
15
15
  //#endregion
16
+ //#region src/utils/send-data.d.ts
17
+ declare function sendData(options: SendDataOptions): Promise<boolean>;
18
+ //#endregion
16
19
  //#region src/analytics.d.ts
17
20
  type Dict = Record<string, unknown>;
18
21
  interface SamplingOptions {
@@ -66,7 +69,6 @@ declare function optIn(): void;
66
69
  declare function optOut(): void;
67
70
  declare function reportError(error: Error): void;
68
71
  declare function isTrackingDisabled(): boolean;
69
- declare function sendData(options: SendDataOptions): Promise<boolean>;
70
72
  declare class WebAnalytics {
71
73
  private readonly options;
72
74
  private readonly webEndpoint;
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{a as e,c as t,d as n,i as r,l as i,n as a,o,r as s,s as c,t as l,u}from"./chunks/analytics-BlPwufvF.js";import{n as d}from"./chunks/feature-flags-CqVtrpX2.js";export{l as WebAnalytics,d as fetchFeatureFlagEvaluation,a as getInstance,s as identify,r as isTrackingDisabled,e as logout,o as optIn,c as optOut,t as reportError,i as sendData,u as setConsentMode,n as trackEvent};
1
+ import{i as e,n as t,o as n,r}from"./chunks/api-urls-BrkcoElX.js";import{a as i,i as a,n as o,o as s,r as c,s as l,t as u}from"./chunks/identifiers-BN-atT_6.js";import{t as d}from"./chunks/send-data-DL_GlsQw.js";import{t as f}from"./chunks/types-CYzR5xtT.js";import{n as p}from"./chunks/feature-flags-CjnLZGxp.js";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:f(r.samplingPercentage??e.sessionReplays?.sampling?.percentage)}}const g={instance:null,pendingConsentMode:void 0};function _(){return g.instance}function v(e,t){typeof window>`u`||T()||g.instance?.track(e,t??{})}function y(e,t,n){return typeof window>`u`||T()?Promise.resolve(!1):g.instance?.identify(e,t,n??{})??Promise.resolve(!1)}function b(e=!0){typeof window>`u`||T()||g.instance?.logout(e)}function x(e){if(g.instance){g.instance.setConsentMode(e);return}g.pendingConsentMode=e}function S(){x(`granted`)}function C(){x(`denied`)}function w(e){typeof window>`u`||T()||g.instance?.reportError(e)}function T(){if(typeof localStorage>`u`)return!1;let e=localStorage.getItem(`disable-faststats`);return e===`true`||e===`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(t){this.options=t,this.baseUrl=r(t.baseUrl),this.featureFlagsBaseUrl=e(t.featureFlagsBaseUrl),this.webEndpoint=n(this.baseUrl),this.debug=t.debug??!1,this.consentMode=t.consent?.mode??`granted`,this.cookielessWhilePending=t.consent?.cookielessWhilePending??!0,g.pendingConsentMode!==void 0&&(this.consentMode=g.pendingConsentMode,g.pendingConsentMode=void 0),(t.autoTrack??!0)&&this.init()}log(e){this.debug&&console.log(`[Analytics] ${e}`)}init(){if(!(typeof window>`u`)){if(T()){this.log(`disabled`);return}g.instance=this,l(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||T()?!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.instance!==this?(e.stop?.(),!1):(this.childTrackers.push(e),!0)}async startErrorTracker(){try{let{default:e}=await import(`./chunks/error-3yIt0PgC.js`).then(e=>e.n);if(!this.started||this.destroyed||g.instance!==this)return;let t=new e({siteKey:this.options.siteKey,baseUrl:this.baseUrl,debug:this.debug,sdkName:this.options.sdkName,sdkVersion:this.options.sdkVersion});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(`./chunks/web-vitals-ClET5qGP.js`).then(e=>e.n);if(!this.started||this.destroyed||g.instance!==this)return;let t=new e({siteKey:this.options.siteKey,baseUrl:this.baseUrl,debug:this.debug,samplingPercentage:f(this.options.webVitals?.sampling?.percentage),attribution:this.options.webVitals?.attribution??!1});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(`./chunks/replay-u8fo4u_R.js`).then(e=>e.n);if(!this.started||this.destroyed||g.instance!==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.instance&&g.instance!==this){this.log(`already started by another instance`);return}if(T()){this.log(`disabled`);return}this.started=!0,g.instance=this,l(this.isCookielessMode()),o();let e=this.options,t=h(e,this.baseUrl,this.debug);t&&this.startReplayTracker(t),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.instance===this&&(g.instance=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,n,r={}){if(!this.ensureStarted()||this.isCookielessMode())return Promise.resolve(!1);let i=e.trim(),a=n.trim();return!i||!a?Promise.resolve(!1):d({url:t(this.baseUrl),data:JSON.stringify({token:this.options.siteKey,identifier:u(!1),externalId:i,email:a,name:r.name?.trim()||void 0,phone:r.phone?.trim()||void 0,avatarUrl:r.avatarUrl?.trim()||void 0,traits:r.traits??{}}),contentType:`text/plain`,debug:this.debug,debugPrefix:`[Analytics] identify`,useBeacon:!1})}logout(e=!0){this.ensureStarted()&&(e&&i(this.isCookielessMode()),s())}setConsentMode(e){this.consentMode=e,l(this.isCookielessMode())}optIn(){this.setConsentMode(`granted`)}optOut(){this.setConsentMode(`denied`)}getConsentMode(){return this.consentMode}getAnonymousId(){return u(this.isCookielessMode())}getSessionId(){return o()}async checkFeatureFlag(e,t,n){if(typeof window>`u`||T())return{value:`false`};let r=n?.externalId?.trim(),i=this.getAnonymousId();if(!r&&!i)return{value:`false`};let{fetchFeatureFlagEvaluation:a}=await import(`./chunks/feature-flags-CjnLZGxp.js`).then(e=>e.t);return a(e,{baseUrl:this.featureFlagsBaseUrl,projectToken:this.options.siteKey,...i?{identifier:i}:{},...r?{externalId:r}:{},attributes:t,signal:n?.signal})}reportError(e){if(this.destroyed||typeof window>`u`||T()||!(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||T())return;let n=u(this.isCookielessMode()),r=JSON.stringify({token:this.options.siteKey,...n?{userId:n}:{},sessionId:o(),data:{event:e,page:location.pathname,referrer:document.referrer||null,title:document.title||``,url:location.href,...D(),...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-c()})}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}a()},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;if(!(!e&&!t)){this.leavePage(),this.enterPage();for(let e of this.childTrackers)e.trackPageChange?.(this.pageUrl);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{O as WebAnalytics,p as fetchFeatureFlagEvaluation,_ as getInstance,y as identify,T as isTrackingDisabled,b as logout,S as optIn,C as optOut,w as reportError,d as sendData,x as setConsentMode,v as trackEvent};
package/dist/replay.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as ReplayTrackerOptions, t as ReplayTracker } from "./chunks/replay-IhsP2Ab4.js";
1
+ import { n as ReplayTrackerOptions, t as ReplayTracker } from "./chunks/replay-BuX3_0xs.js";
2
2
  export { type ReplayTrackerOptions, ReplayTracker as default };
package/dist/replay.js CHANGED
@@ -1 +1 @@
1
- import{t as e}from"./chunks/replay-CKrEvguu.js";export{e as default};
1
+ import{t as e}from"./chunks/replay-u8fo4u_R.js";export{e as default};
@@ -9,19 +9,29 @@ interface WebVitalsOptions {
9
9
  declare class WebVitalsTracker {
10
10
  private readonly options;
11
11
  private readonly endpoint;
12
- private readonly metrics;
12
+ private readonly metricsByUrl;
13
+ private readonly pendingFlushUrls;
13
14
  private readonly sampled;
14
15
  private started;
15
- private flushed;
16
+ private flushing;
17
+ private finalFlushRequested;
18
+ private currentUrl;
16
19
  constructor(options: WebVitalsOptions);
17
20
  private get debug();
18
21
  private log;
19
22
  start(): void;
20
23
  stop(): void;
24
+ trackPageChange(url?: string): void;
25
+ private cleanupListeners;
21
26
  private observe;
22
27
  private onVisibilityChange;
28
+ private onPageHide;
23
29
  private captureMetric;
24
- private flush;
30
+ private metricsForUrl;
31
+ private queueFlush;
32
+ private flushPending;
33
+ private sendMetricsForUrl;
34
+ private finishFinalFlushIfNeeded;
25
35
  }
26
36
  //#endregion
27
37
  export { type WebVitalsOptions, WebVitalsTracker as default };
@@ -1 +1 @@
1
- import{t as e}from"./chunks/web-vitals-BooBPWJC.js";export{e as default};
1
+ import{t as e}from"./chunks/web-vitals-ClET5qGP.js";export{e as default};
package/package.json CHANGED
@@ -39,7 +39,7 @@
39
39
  "publishConfig": {
40
40
  "access": "public"
41
41
  },
42
- "version": "0.2.9",
42
+ "version": "0.2.11",
43
43
  "scripts": {
44
44
  "build": "tsdown && bun run check-size",
45
45
  "dev": "bun run build",
package/src/analytics.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  resetSessionId,
18
18
  setCookielessMode,
19
19
  } from "./utils/identifiers";
20
+ import { sendData } from "./utils/send-data";
20
21
  import {
21
22
  normalizeSamplingPercentage,
22
23
  type SendDataOptions,
@@ -24,10 +25,12 @@ import {
24
25
  import type WebVitalsTracker from "./web-vitals";
25
26
 
26
27
  export type { SendDataOptions };
28
+ export { sendData };
27
29
 
28
30
  type ChildTracker = {
29
31
  start(): void;
30
32
  stop?(): void;
33
+ trackPageChange?(url?: string): void;
31
34
  };
32
35
 
33
36
  type Dict = Record<string, unknown>;
@@ -180,73 +183,6 @@ export function isTrackingDisabled(): boolean {
180
183
  return value === "true" || value === "1";
181
184
  }
182
185
 
183
- export async function sendData(options: SendDataOptions): Promise<boolean> {
184
- const {
185
- url,
186
- data,
187
- contentType = "application/json",
188
- headers = {},
189
- debug = false,
190
- debugPrefix = "[Analytics]",
191
- useBeacon = true,
192
- keepalive = true,
193
- } = options;
194
-
195
- const canUseBeacon =
196
- useBeacon &&
197
- typeof navigator !== "undefined" &&
198
- typeof navigator.sendBeacon === "function";
199
- if (canUseBeacon) {
200
- try {
201
- const beaconBody =
202
- data instanceof Blob
203
- ? data
204
- : typeof Blob !== "undefined"
205
- ? new Blob([data as BlobPart], { type: contentType })
206
- : data;
207
- if (navigator.sendBeacon(url, beaconBody as BodyInit)) {
208
- if (debug) {
209
- console.log(`${debugPrefix} Sent via beacon`);
210
- }
211
- return true;
212
- }
213
- } catch {}
214
- }
215
-
216
- if (typeof fetch !== "function") {
217
- if (debug) {
218
- console.warn(`${debugPrefix} Failed to send`);
219
- }
220
- return false;
221
- }
222
-
223
- try {
224
- const response = await fetch(url, {
225
- method: "POST",
226
- body: data as BodyInit,
227
- headers: {
228
- "Content-Type": contentType,
229
- ...headers,
230
- },
231
- keepalive,
232
- });
233
- const success = response.ok;
234
- if (debug) {
235
- if (success) {
236
- console.log(`${debugPrefix} Sent via fetch`);
237
- } else {
238
- console.warn(`${debugPrefix} Failed: ${response.status}`);
239
- }
240
- }
241
- return success;
242
- } catch {
243
- if (debug) {
244
- console.warn(`${debugPrefix} Failed to send`);
245
- }
246
- return false;
247
- }
248
- }
249
-
250
186
  function getLinkEl(el: Node | null): HTMLAnchorElement | null {
251
187
  while (el) {
252
188
  if (el instanceof HTMLAnchorElement && el.href) return el;
@@ -816,6 +752,9 @@ export class WebAnalytics {
816
752
  if (!pathChanged && !hashChanged) return;
817
753
  this.leavePage();
818
754
  this.enterPage();
755
+ for (const tracker of this.childTrackers) {
756
+ tracker.trackPageChange?.(this.pageUrl);
757
+ }
819
758
  this.trackScroll();
820
759
  this.pageview({ trigger: "navigation" });
821
760
  }, 300);
package/src/error.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { sendData } from "./analytics";
2
1
  import { SDK_NAME, SDK_VERSION } from "./sdk";
3
2
  import { webEventsUrl } from "./utils/api-urls";
4
3
  import { getAnonymousId, getOrCreateSessionId } from "./utils/identifiers";
4
+ import { sendData } from "./utils/send-data";
5
5
 
6
6
  export type ErrorEntry = {
7
7
  error: string;
@@ -98,6 +98,7 @@ export default class ErrorTracker {
98
98
  private readonly queue = new Map<string, ErrorTracking>();
99
99
  private timer: ReturnType<typeof setInterval> | null = null;
100
100
  private started = false;
101
+ private flushing = false;
101
102
 
102
103
  constructor(private readonly options: ErrorTrackingOptions) {
103
104
  this.endpoint = webEventsUrl(options.baseUrl);
@@ -126,9 +127,9 @@ export default class ErrorTracker {
126
127
  window.addEventListener("error", this.onError);
127
128
  window.addEventListener("unhandledrejection", this.onRejection);
128
129
  document.addEventListener("visibilitychange", this.onVisibilityChange);
129
- window.addEventListener("pagehide", this.flush);
130
+ window.addEventListener("pagehide", this.requestFlush);
130
131
 
131
- this.timer = setInterval(this.flush, this.flushInterval);
132
+ this.timer = setInterval(this.requestFlush, this.flushInterval);
132
133
  this.log("Started listening for errors");
133
134
  }
134
135
 
@@ -139,12 +140,12 @@ export default class ErrorTracker {
139
140
  window.removeEventListener("error", this.onError);
140
141
  window.removeEventListener("unhandledrejection", this.onRejection);
141
142
  document.removeEventListener("visibilitychange", this.onVisibilityChange);
142
- window.removeEventListener("pagehide", this.flush);
143
+ window.removeEventListener("pagehide", this.requestFlush);
143
144
 
144
145
  if (this.timer) clearInterval(this.timer);
145
146
  this.timer = null;
146
147
 
147
- this.flush();
148
+ this.requestFlush();
148
149
  this.log("Stopped listening for errors");
149
150
  }
150
151
 
@@ -194,9 +195,22 @@ export default class ErrorTracker {
194
195
  };
195
196
 
196
197
  private onVisibilityChange = (): void => {
197
- if (document.visibilityState === "hidden") this.flush();
198
+ if (document.visibilityState === "hidden") this.requestFlush();
198
199
  };
199
200
 
201
+ private requestFlush = (): void => {
202
+ void this.flush();
203
+ };
204
+
205
+ private restoreErrors(errors: ErrorTracking[]): void {
206
+ for (const error of errors) {
207
+ this.queue.set(error.hash, {
208
+ ...error,
209
+ count: error.count + (this.queue.get(error.hash)?.count ?? 0),
210
+ });
211
+ }
212
+ }
213
+
200
214
  private record(data: NormalizedError, original?: unknown): void {
201
215
  if (original instanceof Error) {
202
216
  if (this.handled.has(original)) {
@@ -231,15 +245,16 @@ export default class ErrorTracker {
231
245
  this.log("Captured error:", data);
232
246
 
233
247
  if (this.queue.size >= this.maxQueueSize) {
234
- this.flush();
248
+ this.requestFlush();
235
249
  }
236
250
  }
237
251
 
238
- private flush = (): void => {
239
- if (this.queue.size === 0) return;
252
+ private async flush(): Promise<void> {
253
+ if (this.flushing || this.queue.size === 0) return;
240
254
 
241
255
  const errors = [...this.queue.values()];
242
256
  this.queue.clear();
257
+ this.flushing = true;
243
258
 
244
259
  const identifier = getAnonymousId();
245
260
  const sourcemapsBuild = (
@@ -273,11 +288,18 @@ export default class ErrorTracker {
273
288
  this.log("Flushing errors:", errors);
274
289
  this.log("Payload:", payload);
275
290
 
276
- sendData({
277
- url: this.endpoint,
278
- data: payload,
279
- debug: this.debug,
280
- debugPrefix: "[ErrorTracker]",
281
- });
282
- };
291
+ try {
292
+ const sent = await sendData({
293
+ url: this.endpoint,
294
+ data: payload,
295
+ debug: this.debug,
296
+ debugPrefix: "[ErrorTracker]",
297
+ });
298
+ if (!sent) this.restoreErrors(errors);
299
+ } catch {
300
+ this.restoreErrors(errors);
301
+ } finally {
302
+ this.flushing = false;
303
+ }
304
+ }
283
305
  }
package/src/replay.ts CHANGED
@@ -6,13 +6,13 @@ import {
6
6
  import { record } from "rrweb";
7
7
  import type { recordOptions } from "rrweb/typings/types";
8
8
  import type { SlimDOMOptions } from "rrweb-snapshot";
9
- import { sendData } from "./analytics";
10
9
  import { replayEventsUrl } from "./utils/api-urls";
11
10
  import {
12
11
  getAnonymousId,
13
12
  getOrCreateSessionId,
14
13
  getSessionStart,
15
14
  } from "./utils/identifiers";
15
+ import { sendData } from "./utils/send-data";
16
16
  import { normalizeSamplingPercentage } from "./utils/types";
17
17
 
18
18
  const RRWEB_SEQUENTIAL_ID_KEY = "_faststatsSeqId";
@@ -47,6 +47,7 @@ type ReplayBatch = {
47
47
  token: string;
48
48
  sessionId: string | undefined;
49
49
  identifier?: string;
50
+ batchId: string;
50
51
  sequence: number;
51
52
  timestamp: number;
52
53
  url: string;
@@ -299,18 +300,28 @@ export default class ReplayTracker {
299
300
 
300
301
  private createBatch(events: eventWithTime[]): ReplayBatch {
301
302
  const identifier = getAnonymousId();
303
+ const sequence = this.sequence++;
302
304
 
303
305
  return {
304
306
  token: this.options.siteKey,
305
307
  sessionId: this.getSessionId(),
306
308
  ...(identifier ? { identifier } : {}),
307
- sequence: this.sequence++,
309
+ batchId: this.createBatchId(sequence),
310
+ sequence,
308
311
  timestamp: Date.now(),
309
312
  url: window.location.href,
310
313
  events,
311
314
  };
312
315
  }
313
316
 
317
+ private createBatchId(sequence: number): string {
318
+ const random =
319
+ typeof crypto !== "undefined" && "randomUUID" in crypto
320
+ ? crypto.randomUUID()
321
+ : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
322
+ return `${this.getSessionId() ?? "unknown"}-${sequence}-${random}`;
323
+ }
324
+
314
325
  private getBatchSizeBytes(batch: ReplayBatch): number {
315
326
  return new TextEncoder().encode(JSON.stringify(batch)).byteLength;
316
327
  }
@@ -0,0 +1,52 @@
1
+ import type { SendDataOptions } from "./types";
2
+
3
+ export async function sendData(options: SendDataOptions): Promise<boolean> {
4
+ const {
5
+ url,
6
+ data,
7
+ contentType = "application/json",
8
+ headers = {},
9
+ debug = false,
10
+ debugPrefix = "[Analytics]",
11
+ useBeacon = true,
12
+ keepalive = true,
13
+ } = options;
14
+
15
+ if (useBeacon && typeof globalThis.navigator?.sendBeacon === "function") {
16
+ try {
17
+ const beaconBody =
18
+ data instanceof Blob
19
+ ? data
20
+ : typeof Blob !== "undefined"
21
+ ? new Blob([data as BlobPart], { type: contentType })
22
+ : data;
23
+ if (globalThis.navigator.sendBeacon(url, beaconBody as BodyInit)) {
24
+ if (debug) console.log(`${debugPrefix} Sent via beacon`);
25
+ return true;
26
+ }
27
+ } catch {}
28
+ }
29
+
30
+ try {
31
+ const response = await fetch(url, {
32
+ method: "POST",
33
+ body: data as BodyInit,
34
+ headers: {
35
+ "Content-Type": contentType,
36
+ ...headers,
37
+ },
38
+ keepalive,
39
+ });
40
+ const success = response.ok;
41
+ if (debug) {
42
+ const message = success
43
+ ? `${debugPrefix} Sent via fetch`
44
+ : `${debugPrefix} Failed: ${response.status}`;
45
+ console[success ? "log" : "warn"](message);
46
+ }
47
+ return success;
48
+ } catch {
49
+ if (debug) console.warn(`${debugPrefix} Failed to send`);
50
+ return false;
51
+ }
52
+ }
package/src/web-vitals.ts CHANGED
@@ -19,25 +19,54 @@ type MetricState = {
19
19
  };
20
20
 
21
21
  type AnyMetric = Metric | MetricWithAttribution;
22
- type Observer = (cb: (metric: AnyMetric) => void) => void;
23
-
24
- async function loadObservers(attribution: boolean): Promise<Observer[]> {
25
- if (attribution) {
26
- const { onCLS, onFCP, onINP, onLCP, onTTFB } = await import(
27
- "web-vitals/attribution"
28
- );
29
- return [onCLS, onFCP, onINP, onLCP, onTTFB] as Observer[];
30
- }
31
- const { onCLS, onFCP, onINP, onLCP, onTTFB } = await import("web-vitals");
32
- return [onCLS, onFCP, onINP, onLCP, onTTFB] as Observer[];
22
+ type ObserverOptions = {
23
+ reportAllChanges?: boolean;
24
+ };
25
+ type Observer = (
26
+ cb: (metric: AnyMetric) => void,
27
+ opts?: ObserverOptions,
28
+ ) => void;
29
+ type ObserverTuple = [Observer, Observer, Observer, Observer, Observer];
30
+ type ObserverConfig = {
31
+ observe: Observer;
32
+ options?: ObserverOptions;
33
+ };
34
+
35
+ async function loadObservers(attribution: boolean): Promise<ObserverConfig[]> {
36
+ const { onCLS, onFCP, onINP, onLCP, onTTFB } = attribution
37
+ ? await import("web-vitals/attribution")
38
+ : await import("web-vitals");
39
+ return createObserverConfigs([onCLS, onFCP, onINP, onLCP, onTTFB]);
40
+ }
41
+
42
+ function createObserverConfigs([
43
+ onCLS,
44
+ onFCP,
45
+ onINP,
46
+ onLCP,
47
+ onTTFB,
48
+ ]: ObserverTuple): ObserverConfig[] {
49
+ return [
50
+ { observe: onCLS, options: { reportAllChanges: true } },
51
+ { observe: onFCP },
52
+ { observe: onINP, options: { reportAllChanges: true } },
53
+ { observe: onLCP, options: { reportAllChanges: true } },
54
+ { observe: onTTFB },
55
+ ];
33
56
  }
34
57
 
35
58
  export default class WebVitalsTracker {
36
59
  private readonly endpoint: string;
37
- private readonly metrics = new Map<MetricName, MetricState>();
60
+ private readonly metricsByUrl = new Map<
61
+ string,
62
+ Map<MetricName, MetricState>
63
+ >();
64
+ private readonly pendingFlushUrls = new Set<string>();
38
65
  private readonly sampled: boolean;
39
66
  private started = false;
40
- private flushed = false;
67
+ private flushing = false;
68
+ private finalFlushRequested = false;
69
+ private currentUrl = "";
41
70
 
42
71
  constructor(private readonly options: WebVitalsOptions) {
43
72
  this.endpoint = webVitalsEventsUrl(options.baseUrl);
@@ -56,33 +85,50 @@ export default class WebVitalsTracker {
56
85
 
57
86
  start(): void {
58
87
  if (this.started || typeof window === "undefined") return;
88
+
59
89
  this.started = true;
60
- this.flushed = false;
90
+ this.currentUrl = window.location.href;
61
91
 
62
- document.addEventListener("visibilitychange", this.onVisibilityChange);
63
- window.addEventListener("pagehide", this.flush);
92
+ document.addEventListener("visibilitychange", this.onVisibilityChange, {
93
+ passive: true,
94
+ });
95
+ window.addEventListener("pagehide", this.onPageHide, { passive: true });
64
96
 
65
97
  this.log("Tracking started");
66
-
67
98
  void this.observe();
68
99
  }
69
100
 
70
101
  stop(): void {
71
102
  if (!this.started || typeof window === "undefined") return;
103
+
72
104
  this.started = false;
105
+ this.cleanupListeners();
106
+ this.queueFlush(this.currentUrl, true);
107
+ }
73
108
 
74
- document.removeEventListener("visibilitychange", this.onVisibilityChange);
75
- window.removeEventListener("pagehide", this.flush);
109
+ trackPageChange(url?: string): void {
110
+ if (!this.started || typeof window === "undefined") return;
111
+ const nextUrl = url ?? window.location.href;
112
+ if (nextUrl === this.currentUrl) return;
76
113
 
77
- this.flush();
114
+ const previousUrl = this.currentUrl;
115
+ this.currentUrl = nextUrl;
116
+ this.queueFlush(previousUrl);
117
+ }
118
+
119
+ private cleanupListeners(): void {
120
+ document.removeEventListener("visibilitychange", this.onVisibilityChange);
121
+ window.removeEventListener("pagehide", this.onPageHide);
78
122
  }
79
123
 
80
124
  private async observe(): Promise<void> {
81
125
  try {
82
126
  const observers = await loadObservers(this.options.attribution ?? false);
127
+
83
128
  if (!this.started) return;
84
- for (const observe of observers) {
85
- observe(this.captureMetric);
129
+
130
+ for (const { observe, options } of observers) {
131
+ observe(this.captureMetric, options);
86
132
  }
87
133
  } catch (error) {
88
134
  this.log("Failed to load web-vitals", error);
@@ -90,17 +136,24 @@ export default class WebVitalsTracker {
90
136
  }
91
137
 
92
138
  private onVisibilityChange = (): void => {
93
- if (document.visibilityState === "hidden") this.flush();
139
+ if (document.visibilityState === "hidden") {
140
+ this.queueFlush(this.currentUrl, true);
141
+ }
142
+ };
143
+
144
+ private onPageHide = (): void => {
145
+ this.queueFlush(this.currentUrl, true);
94
146
  };
95
147
 
96
148
  private captureMetric = (metric: AnyMetric): void => {
97
- if (this.flushed || !this.sampled) return;
149
+ if (!this.started || !this.sampled) return;
98
150
 
99
151
  const name = metric.name as MetricName;
100
152
  const attribution =
101
153
  (metric as MetricWithAttribution).attribution ?? undefined;
154
+ const metrics = this.metricsForUrl(this.currentUrl);
102
155
 
103
- this.metrics.set(name, {
156
+ metrics.set(name, {
104
157
  value: metric.value,
105
158
  attributes: {
106
159
  id: metric.id,
@@ -114,40 +167,95 @@ export default class WebVitalsTracker {
114
167
  this.log(`${name} captured: ${metric.value}`);
115
168
  };
116
169
 
117
- private flush = (): void => {
118
- if (this.flushed || this.metrics.size === 0) return;
119
- this.flushed = true;
170
+ private metricsForUrl(url: string): Map<MetricName, MetricState> {
171
+ let metrics = this.metricsByUrl.get(url);
172
+ if (!metrics) {
173
+ metrics = new Map();
174
+ this.metricsByUrl.set(url, metrics);
175
+ }
176
+ return metrics;
177
+ }
178
+
179
+ private queueFlush(url: string, final = false): void {
180
+ this.finalFlushRequested ||= final;
181
+ this.pendingFlushUrls.add(url);
182
+ void this.flushPending();
183
+ }
120
184
 
121
- const vitals = [...this.metrics.entries()].map(([metric, data]) => ({
185
+ private async flushPending(): Promise<void> {
186
+ if (this.flushing) return;
187
+ if (typeof window === "undefined" || typeof fetch !== "function") {
188
+ this.finishFinalFlushIfNeeded();
189
+ return;
190
+ }
191
+
192
+ this.flushing = true;
193
+
194
+ try {
195
+ const urls = [...this.pendingFlushUrls];
196
+ this.pendingFlushUrls.clear();
197
+ for (const url of urls) {
198
+ if (!(await this.sendMetricsForUrl(url)))
199
+ this.pendingFlushUrls.add(url);
200
+ }
201
+ } finally {
202
+ this.flushing = false;
203
+ this.finishFinalFlushIfNeeded();
204
+ }
205
+ }
206
+
207
+ private async sendMetricsForUrl(url: string): Promise<boolean> {
208
+ const metrics = this.metricsByUrl.get(url);
209
+ if (!metrics || metrics.size === 0) return true;
210
+
211
+ const vitals = [...metrics.entries()].map(([metric, data]) => ({
122
212
  metric,
123
213
  value: data.value,
124
214
  attributes: data.attributes,
125
215
  }));
126
- const metricNames = vitals.map((v) => v.metric).join(", ");
127
- this.metrics.clear();
128
-
129
216
  const body = JSON.stringify({
130
217
  sessionId: getOrCreateSessionId(),
131
218
  vitals,
132
- metadata: {
133
- url: window.location.href,
134
- },
219
+ metadata: { url },
135
220
  });
136
221
 
137
- this.log(`Sending final metrics (${vitals.length}): ${metricNames}`);
222
+ this.log(`Sending metrics for ${url} (${vitals.length})`);
223
+
224
+ try {
225
+ const controller = new AbortController();
226
+ const timeout = window.setTimeout(() => controller.abort(), 3000);
138
227
 
139
- if (typeof fetch !== "function") return;
228
+ try {
229
+ const response = await fetch(this.endpoint, {
230
+ method: "POST",
231
+ body,
232
+ headers: {
233
+ "Content-Type": "application/json",
234
+ Authorization: `Bearer ${this.options.siteKey}`,
235
+ },
236
+ keepalive: true,
237
+ signal: controller.signal,
238
+ });
140
239
 
141
- fetch(this.endpoint, {
142
- method: "POST",
143
- body,
144
- headers: {
145
- "Content-Type": "application/json",
146
- Authorization: `Bearer ${this.options.siteKey}`,
147
- },
148
- keepalive: true,
149
- }).catch(() => {
150
- this.log("Failed to send metrics");
151
- });
152
- };
240
+ if (!response.ok) {
241
+ throw new Error(`HTTP ${response.status}`);
242
+ }
243
+
244
+ this.metricsByUrl.delete(url);
245
+ } finally {
246
+ clearTimeout(timeout);
247
+ }
248
+ return true;
249
+ } catch (error) {
250
+ this.log("Failed to send metrics", error);
251
+ return false;
252
+ }
253
+ }
254
+
255
+ private finishFinalFlushIfNeeded(): void {
256
+ if (!this.finalFlushRequested) return;
257
+ this.finalFlushRequested = false;
258
+ this.started = false;
259
+ this.cleanupListeners();
260
+ }
153
261
  }
@@ -34,6 +34,7 @@ type ReplayBatch = {
34
34
  token: string;
35
35
  sessionId?: string;
36
36
  identifier?: string;
37
+ batchId: string;
37
38
  sequence: number;
38
39
  timestamp: number;
39
40
  url: string;
@@ -191,11 +192,35 @@ describe("ReplayTracker", () => {
191
192
  expect(captured.payload).not.toBeNull();
192
193
  expect(captured.payload?.token).toBe("site_test");
193
194
  expect(captured.payload?.sessionId).toBeTruthy();
195
+ expect(captured.payload?.batchId).toBeTruthy();
194
196
  expect(captured.payload?.events).toHaveLength(1);
195
197
  expect(captured.lowLatency).toBe(true);
196
198
  expect(tracker.pending.length).toBe(0);
197
199
  });
198
200
 
201
+ test("emits stable batch ids while queued for retry", async () => {
202
+ const tracker = createTracker({
203
+ minReplayLengthMs: 0,
204
+ });
205
+
206
+ const payloads: ReplayBatch[] = [];
207
+ tracker.send = async (data) => {
208
+ payloads.push(JSON.parse(decodeBody(data)) as ReplayBatch);
209
+ return payloads.length > 1;
210
+ };
211
+ tracker.startTime = Date.now() - 1000;
212
+ tracker.onEvent({ type: 2, timestamp: Date.now(), data: {} }, false);
213
+
214
+ await tracker.flush(false);
215
+ await tracker.flush(false);
216
+
217
+ expect(payloads).toHaveLength(2);
218
+ expect(payloads[1]?.batchId).toBe(payloads[0]?.batchId);
219
+ expect(payloads[1]?.sequence).toBe(payloads[0]?.sequence);
220
+
221
+ if (tracker.retryTask) clearTimeout(tracker.retryTask);
222
+ });
223
+
199
224
  test("keeps the same replay session id across later batches", async () => {
200
225
  const tracker = createTracker({
201
226
  minReplayLengthMs: 0,
@@ -0,0 +1,208 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import WebVitalsTracker from "../src/web-vitals";
3
+
4
+ type FetchCall = {
5
+ url: string;
6
+ body?: string;
7
+ };
8
+
9
+ type Harness = {
10
+ fetchCalls: FetchCall[];
11
+ fetchStatuses: number[];
12
+ restore(): void;
13
+ };
14
+
15
+ function setGlobal(name: keyof typeof globalThis, value: unknown): void {
16
+ Object.defineProperty(globalThis, name, {
17
+ configurable: true,
18
+ writable: true,
19
+ value,
20
+ });
21
+ }
22
+
23
+ function setupBrowser(): Harness {
24
+ const originalValues = {
25
+ window: globalThis.window,
26
+ document: globalThis.document,
27
+ location: globalThis.location,
28
+ fetch: globalThis.fetch,
29
+ sessionStorage: globalThis.sessionStorage,
30
+ };
31
+ const fetchCalls: FetchCall[] = [];
32
+ const fetchStatuses: number[] = [];
33
+ const listeners = new Map<string, Set<EventListenerOrEventListenerObject>>();
34
+ const addEventListener = (
35
+ type: string,
36
+ listener: EventListenerOrEventListenerObject,
37
+ ): void => {
38
+ const existing = listeners.get(type) ?? new Set();
39
+ existing.add(listener);
40
+ listeners.set(type, existing);
41
+ };
42
+ const removeEventListener = (
43
+ type: string,
44
+ listener: EventListenerOrEventListenerObject,
45
+ ): void => {
46
+ listeners.get(type)?.delete(listener);
47
+ };
48
+ const location = {
49
+ href: "https://example.com/",
50
+ };
51
+ const storage = new Map<string, string>();
52
+ const sessionStorage = {
53
+ getItem: (key: string) => storage.get(key) ?? null,
54
+ setItem: (key: string, value: string) => storage.set(key, value),
55
+ removeItem: (key: string) => storage.delete(key),
56
+ };
57
+
58
+ setGlobal("location", location as Location);
59
+ setGlobal("document", {
60
+ addEventListener,
61
+ removeEventListener,
62
+ visibilityState: "visible",
63
+ } as unknown as Document);
64
+ setGlobal("window", {
65
+ addEventListener,
66
+ removeEventListener,
67
+ location,
68
+ setTimeout,
69
+ clearTimeout,
70
+ } as unknown as Window & typeof globalThis);
71
+ setGlobal("sessionStorage", sessionStorage);
72
+ setGlobal("fetch", (async (
73
+ url: string | URL | Request,
74
+ init?: RequestInit,
75
+ ): Promise<Response> => {
76
+ fetchCalls.push({
77
+ url: String(url),
78
+ body: typeof init?.body === "string" ? init.body : undefined,
79
+ });
80
+ return new Response("", { status: fetchStatuses.shift() ?? 204 });
81
+ }) as typeof fetch);
82
+
83
+ return {
84
+ fetchCalls,
85
+ fetchStatuses,
86
+ restore() {
87
+ setGlobal("window", originalValues.window);
88
+ setGlobal("document", originalValues.document);
89
+ setGlobal("location", originalValues.location);
90
+ setGlobal("fetch", originalValues.fetch);
91
+ setGlobal("sessionStorage", originalValues.sessionStorage);
92
+ },
93
+ };
94
+ }
95
+
96
+ function captureMetric(
97
+ tracker: WebVitalsTracker,
98
+ metric: Parameters<(metric: unknown) => void>[0],
99
+ ): void {
100
+ (
101
+ tracker as unknown as {
102
+ captureMetric(metric: unknown): void;
103
+ }
104
+ ).captureMetric(metric);
105
+ }
106
+
107
+ let harness: Harness;
108
+
109
+ beforeEach(() => {
110
+ harness = setupBrowser();
111
+ });
112
+
113
+ afterEach(() => {
114
+ harness.restore();
115
+ });
116
+
117
+ describe("WebVitalsTracker", () => {
118
+ test("flushes the current vitals bucket when the SPA route changes", async () => {
119
+ const tracker = new WebVitalsTracker({
120
+ siteKey: "site_test",
121
+ baseUrl: "https://analytics.example.com",
122
+ samplingPercentage: 100,
123
+ });
124
+
125
+ tracker.start();
126
+ captureMetric(tracker, {
127
+ name: "CLS",
128
+ value: 0.12,
129
+ id: "v1",
130
+ rating: "needs-improvement",
131
+ delta: 0.12,
132
+ navigationType: "navigate",
133
+ });
134
+
135
+ tracker.trackPageChange("https://example.com/about");
136
+ await new Promise((resolve) => setTimeout(resolve, 0));
137
+
138
+ expect(harness.fetchCalls.length).toBe(1);
139
+ const firstPayload = JSON.parse(harness.fetchCalls[0]?.body ?? "{}") as {
140
+ metadata?: { url?: string };
141
+ vitals?: Array<{ metric?: string }>;
142
+ };
143
+ expect(firstPayload.metadata?.url).toBe("https://example.com/");
144
+ expect(firstPayload.vitals?.[0]?.metric).toBe("CLS");
145
+
146
+ captureMetric(tracker, {
147
+ name: "INP",
148
+ value: 32,
149
+ id: "v2",
150
+ rating: "good",
151
+ delta: 32,
152
+ navigationType: "navigate",
153
+ });
154
+
155
+ tracker.stop();
156
+ await new Promise((resolve) => setTimeout(resolve, 0));
157
+
158
+ expect(harness.fetchCalls.length).toBe(2);
159
+ const secondPayload = JSON.parse(harness.fetchCalls[1]?.body ?? "{}") as {
160
+ metadata?: { url?: string };
161
+ vitals?: Array<{ metric?: string }>;
162
+ };
163
+ expect(secondPayload.metadata?.url).toBe("https://example.com/about");
164
+ expect(secondPayload.vitals?.[0]?.metric).toBe("INP");
165
+ });
166
+
167
+ test("keeps metrics buffered when a route flush fails", async () => {
168
+ harness.fetchStatuses.push(500, 204, 204);
169
+ const tracker = new WebVitalsTracker({
170
+ siteKey: "site_test",
171
+ baseUrl: "https://analytics.example.com",
172
+ samplingPercentage: 100,
173
+ });
174
+
175
+ tracker.start();
176
+ captureMetric(tracker, {
177
+ name: "CLS",
178
+ value: 0.12,
179
+ id: "v1",
180
+ rating: "needs-improvement",
181
+ delta: 0.12,
182
+ navigationType: "navigate",
183
+ });
184
+ tracker.trackPageChange("https://example.com/about");
185
+ await new Promise((resolve) => setTimeout(resolve, 0));
186
+
187
+ expect(harness.fetchCalls.length).toBe(1);
188
+
189
+ captureMetric(tracker, {
190
+ name: "INP",
191
+ value: 32,
192
+ id: "v2",
193
+ rating: "good",
194
+ delta: 32,
195
+ navigationType: "navigate",
196
+ });
197
+ tracker.trackPageChange("https://example.com/contact");
198
+ await new Promise((resolve) => setTimeout(resolve, 0));
199
+
200
+ expect(harness.fetchCalls.length).toBe(3);
201
+ const retriedPayload = JSON.parse(harness.fetchCalls[1]?.body ?? "{}") as {
202
+ metadata?: { url?: string };
203
+ vitals?: Array<{ metric?: string }>;
204
+ };
205
+ expect(retriedPayload.metadata?.url).toBe("https://example.com/");
206
+ expect(retriedPayload.vitals?.[0]?.metric).toBe("CLS");
207
+ });
208
+ });
@@ -1 +0,0 @@
1
- import{i as e,n as t,o as n,r}from"./api-urls-BrkcoElX.js";import{a as i,c as a,i as o,n as s,o as c,r as l,s as u,t as d}from"./types-C3vW7XGe.js";function f(e){return e.replayOptions?.samplingPercentage!==void 0||e.sessionReplays?.sampling?.percentage!==void 0}function p(e,t,n){if(!(e.sessionReplays?.enabled??e.trackReplay??f(e)))return null;let r=e.replayOptions??{};return{siteKey:e.siteKey,baseUrl:t,debug:n,...r,samplingPercentage:d(r.samplingPercentage??e.sessionReplays?.sampling?.percentage)}}const m={instance:null,pendingConsentMode:void 0};function h(){return m.instance}function g(e,t){typeof window>`u`||C()||m.instance?.track(e,t??{})}function _(e,t,n){return typeof window>`u`||C()?Promise.resolve(!1):m.instance?.identify(e,t,n??{})??Promise.resolve(!1)}function v(e=!0){typeof window>`u`||C()||m.instance?.logout(e)}function y(e){if(m.instance){m.instance.setConsentMode(e);return}m.pendingConsentMode=e}function b(){y(`granted`)}function x(){y(`denied`)}function S(e){typeof window>`u`||C()||m.instance?.reportError(e)}function C(){if(typeof localStorage>`u`)return!1;let e=localStorage.getItem(`disable-faststats`);return e===`true`||e===`1`}async function w(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 T(e){for(;e;){if(e instanceof HTMLAnchorElement&&e.href)return e;e=e.parentNode}return null}function E(){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 D=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(t){this.options=t,this.baseUrl=r(t.baseUrl),this.featureFlagsBaseUrl=e(t.featureFlagsBaseUrl),this.webEndpoint=n(this.baseUrl),this.debug=t.debug??!1,this.consentMode=t.consent?.mode??`granted`,this.cookielessWhilePending=t.consent?.cookielessWhilePending??!0,m.pendingConsentMode!==void 0&&(this.consentMode=m.pendingConsentMode,m.pendingConsentMode=void 0),(t.autoTrack??!0)&&this.init()}log(e){this.debug&&console.log(`[Analytics] ${e}`)}init(){if(!(typeof window>`u`)){if(C()){this.log(`disabled`);return}m.instance=this,a(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||C()?!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||m.instance!==this?(e.stop?.(),!1):(this.childTrackers.push(e),!0)}async startErrorTracker(){try{let{default:e}=await import(`./error-C-fHJ_1D.js`).then(e=>e.n);if(!this.started||this.destroyed||m.instance!==this)return;let t=new e({siteKey:this.options.siteKey,baseUrl:this.baseUrl,debug:this.debug,sdkName:this.options.sdkName,sdkVersion:this.options.sdkVersion});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-BooBPWJC.js`).then(e=>e.n);if(!this.started||this.destroyed||m.instance!==this)return;let t=new e({siteKey:this.options.siteKey,baseUrl:this.baseUrl,debug:this.debug,samplingPercentage:d(this.options.webVitals?.sampling?.percentage),attribution:this.options.webVitals?.attribution??!1});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-CKrEvguu.js`).then(e=>e.n);if(!this.started||this.destroyed||m.instance!==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(m.instance&&m.instance!==this){this.log(`already started by another instance`);return}if(C()){this.log(`disabled`);return}this.started=!0,m.instance=this,a(this.isCookielessMode()),l();let e=this.options,t=p(e,this.baseUrl,this.debug);t&&this.startReplayTracker(t),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(),m.instance===this&&(m.instance=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,n,r={}){if(!this.ensureStarted()||this.isCookielessMode())return Promise.resolve(!1);let i=e.trim(),a=n.trim();return!i||!a?Promise.resolve(!1):w({url:t(this.baseUrl),data:JSON.stringify({token:this.options.siteKey,identifier:s(!1),externalId:i,email:a,name:r.name?.trim()||void 0,phone:r.phone?.trim()||void 0,avatarUrl:r.avatarUrl?.trim()||void 0,traits:r.traits??{}}),contentType:`text/plain`,debug:this.debug,debugPrefix:`[Analytics] identify`,useBeacon:!1})}logout(e=!0){this.ensureStarted()&&(e&&c(this.isCookielessMode()),u())}setConsentMode(e){this.consentMode=e,a(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`||C())return{value:`false`};let r=n?.externalId?.trim(),i=this.getAnonymousId();if(!r&&!i)return{value:`false`};let{fetchFeatureFlagEvaluation:a}=await import(`./feature-flags-CqVtrpX2.js`).then(e=>e.t);return a(e,{baseUrl:this.featureFlagsBaseUrl,projectToken:this.options.siteKey,...i?{identifier:i}:{},...r?{externalId:r}:{},attributes:t,signal:n?.signal})}reportError(e){if(this.destroyed||typeof window>`u`||C()||!(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||C())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,...E(),...t}});this.log(e),w({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-o()})}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}i()},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=T(e.target);t&&t.host!==location.host&&this.track(`outbound_link`,{outbound_link:t.href})};this.addDocumentListener(`click`,e),this.addDocumentListener(`auxclick`,e)}};export{v as a,S as c,g as d,C as i,w as l,h as n,b as o,_ as r,x as s,D as t,y as u};
@@ -1,2 +0,0 @@
1
- import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{o as t}from"./api-urls-BrkcoElX.js";import{n,r}from"./types-C3vW7XGe.js";import{l as i}from"./analytics-BlPwufvF.js";var a=e({default:()=>d});const o=/(?:^|\()(chrome|moz|ms-browser|safari-web)-extension:\/\//,s=e=>e?.split(`
2
- `).map(e=>e.trim()).filter(Boolean);function c(e){if(e instanceof Error)return{error:e.name?.trim()||`Error`,message:e.message,stack:s(e.stack),cause:c(e.cause)};if(typeof e==`string`)return{error:`Error`,message:e}}function l(e){return e?`${e.error}\0${e.message??``}\0${l(e.cause)}`:``}function u(e){let t=[e.kind,e.handled?`handled`:`unhandled`,e.message,e.filename??``,e.lineno??``,l(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 d=class{endpoint;handled=new WeakSet;queue=new Map;timer=null;started=!1;constructor(e){this.options=e,this.endpoint=t(e.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:c(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?c(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:c(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(o.test(`${e.filename??``}\n${e.stack??``}`))return;let n=u(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:s(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 t=n(),a=globalThis.__SOURCEMAPS_BUILD__,o=typeof a?.buildId==`string`&&a.buildId.trim()?a.buildId:void 0,s=JSON.stringify({token:this.options.siteKey,...t?{userId:t}:{},sessionId:r(),...o?{buildId:o}:{},sdkName:this.options.sdkName??`@faststats/web`,sdkVersion:this.options.sdkVersion??`0.2.9`,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),i({url:this.endpoint,data:s,debug:this.debug,debugPrefix:`[ErrorTracker]`})}};export{a as n,d as t};
@@ -1 +0,0 @@
1
- import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{a as t}from"./api-urls-BrkcoElX.js";import{i as n,n as r,r as i,t as a}from"./types-C3vW7XGe.js";import{l as o}from"./analytics-BlPwufvF.js";import{EventType as s}from"@rrweb/types";import{record as c}from"rrweb";var l=e({default:()=>f});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=[];pendingSizeBytes=0;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=t(e.baseUrl),this.sampled=Math.random()*100<a(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 maxQueueSizeBytes(){return this.options.maxQueueSizeBytes??2097152}get minReplayLengthMs(){return this.options.minReplayLengthMs??3e3}get shouldCompress(){return this.options.compress??!0}log(...e){this.debug&&console.log(`[Replay]`,...e)}start(){this.started||typeof window>`u`||!this.sampled||(this.started=!0,this.sessionId=i(),this.startTime=n(),this.beginRecording(),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`))}async beginRecording(){let e=this.options.recordConsole??!0,[{getRecordSequentialIdPlugin:t},n]=await Promise.all([import(`@rrweb/rrweb-plugin-sequential-id-record`),e?import(`@rrweb/rrweb-plugin-console-record`):Promise.resolve(null)]);if(!this.started)return;let r=[t({key:`_faststatsSeqId`})];n&&r.push(n.getRecordConsolePlugin()),this.stopRecording=c({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:r})}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??i()}onEvent=(e,t)=>{if(this.events.push(e),t||this.events.length>=this.maxEvents||e.type===s.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 t=r();return{token:this.options.siteKey,sessionId:this.getSessionId(),...t?{identifier:t}:{},sequence:this.sequence++,timestamp:Date.now(),url:window.location.href,events:e}}getBatchSizeBytes(e){return new TextEncoder().encode(JSON.stringify(e)).byteLength}dropOldestPendingBatch(e){let t=this.pending.shift();t&&(this.pendingSizeBytes=Math.max(0,this.pendingSizeBytes-this.getBatchSizeBytes(t)),this.log(`${e}, dropping batch ${t.sequence}`))}enqueueBatch(e){let t=this.getBatchSizeBytes(e);if(t>this.maxQueueSizeBytes){this.log(`Replay batch ${e.sequence} is ${t}B, exceeding ${this.maxQueueSizeBytes}B queue limit; dropping`);return}for(;this.pending.length>0&&this.pendingSizeBytes+t>this.maxQueueSizeBytes;)this.dropOldestPendingBatch(`Pending queue size limit reached`);for(;this.pending.length>=this.maxPendingBatches;)this.dropOldestPendingBatch(`Pending batch limit reached`);this.pending.push(e),this.pendingSizeBytes+=t}async encodeBatch(e){let t=JSON.stringify(e);if(!this.shouldCompress||!this.compressionSupported)return{data:t,isCompressed:!1};try{let e=await this.compress(t);return this.log(`Compressed ${t.length}B -> ${e.byteLength}B (${Math.round(e.byteLength/t.length*100)}%)`),{data:e,isCompressed:!0}}catch{return this.log(`Compression failed, using uncompressed`),{data:t,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.enqueueBatch(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);if(!await this.send(n.data,n.isCompressed,e)){this.log(`Failed to send replay batch ${t.sequence}, retrying`),this.scheduleRetry();break}this.dropOldestPendingBatch(`Sent replay batch`),e=!1}}finally{this.sending=!1}}}}scheduleRetry(){this.retryTask||=setTimeout(()=>{this.retryTask=null,this.flush(!1)},1e3)}async compress(e){let t=new Blob([e]).stream().pipeThrough(new CompressionStream(`gzip`)),n=await new Response(t).arrayBuffer();return new Uint8Array(n)}send(e,t,n){return o({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{l as n,f as t};
@@ -1 +0,0 @@
1
- import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{s as t}from"./api-urls-BrkcoElX.js";import{r as n,t as r}from"./types-C3vW7XGe.js";var i=e({default:()=>o});async function a(e){if(e){let{onCLS:e,onFCP:t,onINP:n,onLCP:r,onTTFB:i}=await import(`web-vitals/attribution`);return[e,t,n,r,i]}let{onCLS:t,onFCP:n,onINP:r,onLCP:i,onTTFB:a}=await import(`web-vitals`);return[t,n,r,i,a]}var o=class{endpoint;metrics=new Map;sampled;started=!1;flushed=!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}log(...e){this.debug&&console.log(`[WebVitals]`,...e)}start(){this.started||typeof window>`u`||(this.started=!0,this.flushed=!1,document.addEventListener(`visibilitychange`,this.onVisibilityChange),window.addEventListener(`pagehide`,this.flush),this.log(`Tracking started`),this.observe())}stop(){!this.started||typeof window>`u`||(this.started=!1,document.removeEventListener(`visibilitychange`,this.onVisibilityChange),window.removeEventListener(`pagehide`,this.flush),this.flush())}async observe(){try{let e=await a(this.options.attribution??!1);if(!this.started)return;for(let t of e)t(this.captureMetric)}catch(e){this.log(`Failed to load web-vitals`,e)}}onVisibilityChange=()=>{document.visibilityState===`hidden`&&this.flush()};captureMetric=e=>{if(this.flushed||!this.sampled)return;let t=e.name,n=e.attribution??void 0;this.metrics.set(t,{value:e.value,attributes:{id:e.id,rating:e.rating,delta:e.delta,navigationType:e.navigationType,...n??{}}}),this.log(`${t} captured: ${e.value}`)};flush=()=>{if(this.flushed||this.metrics.size===0)return;this.flushed=!0;let e=[...this.metrics.entries()].map(([e,t])=>({metric:e,value:t.value,attributes:t.attributes})),t=e.map(e=>e.metric).join(`, `);this.metrics.clear();let r=JSON.stringify({sessionId:n(),vitals:e,metadata:{url:window.location.href}});this.log(`Sending final metrics (${e.length}): ${t}`),typeof fetch==`function`&&fetch(this.endpoint,{method:`POST`,body:r,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${this.options.siteKey}`},keepalive:!0}).catch(()=>{this.log(`Failed to send metrics`)})}};export{i as n,o as t};