@faststats/web 0.2.10 → 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 +7 -0
- package/dist/chunks/error-3yIt0PgC.js +2 -0
- package/dist/chunks/{types-C3vW7XGe.js → identifiers-BN-atT_6.js} +1 -1
- package/dist/chunks/{replay-IhsP2Ab4.d.ts → replay-BuX3_0xs.d.ts} +1 -0
- package/dist/chunks/replay-u8fo4u_R.js +1 -0
- package/dist/chunks/send-data-DL_GlsQw.js +1 -0
- package/dist/chunks/types-CYzR5xtT.js +1 -0
- package/dist/chunks/web-vitals-ClET5qGP.js +1 -0
- package/dist/error.d.ts +3 -0
- package/dist/error.js +1 -1
- package/dist/feature-flags.d.ts +1 -1
- package/dist/feature-flags.js +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1 -1
- package/dist/replay.d.ts +1 -1
- package/dist/replay.js +1 -1
- package/dist/web-vitals.d.ts +13 -3
- package/dist/web-vitals.js +1 -1
- package/package.json +1 -1
- package/src/analytics.ts +6 -67
- package/src/error.ts +38 -16
- package/src/replay.ts +13 -2
- package/src/utils/send-data.ts +52 -0
- package/src/web-vitals.ts +157 -49
- package/tests/replay.test.ts +25 -0
- package/tests/web-vitals.test.ts +208 -0
- package/dist/chunks/analytics-ur06JW5D.js +0 -1
- package/dist/chunks/error-ClMugucs.js +0 -2
- package/dist/chunks/replay-DQHUrcod.js +0 -1
- package/dist/chunks/web-vitals-BooBPWJC.js +0 -1
- /package/dist/chunks/{feature-flags-6rZlmhfu.d.ts → feature-flags-BxZz_lNm.d.ts} +0 -0
- /package/dist/chunks/{feature-flags-CqVtrpX2.js → feature-flags-CjnLZGxp.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -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()}
|
|
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};
|
|
@@ -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-
|
|
1
|
+
import{t as e}from"./chunks/error-3yIt0PgC.js";export{e as default};
|
package/dist/feature-flags.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as FeatureFlagEvaluation, r as fetchFeatureFlagEvaluation, t as FeatureFlagCheckContext } from "./chunks/feature-flags-
|
|
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 };
|
package/dist/feature-flags.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./chunks/feature-flags-
|
|
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-
|
|
2
|
-
import { n as ReplayTrackerOptions } from "./chunks/replay-
|
|
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-ur06JW5D.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-
|
|
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-
|
|
1
|
+
import{t as e}from"./chunks/replay-u8fo4u_R.js";export{e as default};
|
package/dist/web-vitals.d.ts
CHANGED
|
@@ -9,19 +9,29 @@ interface WebVitalsOptions {
|
|
|
9
9
|
declare class WebVitalsTracker {
|
|
10
10
|
private readonly options;
|
|
11
11
|
private readonly endpoint;
|
|
12
|
-
private readonly
|
|
12
|
+
private readonly metricsByUrl;
|
|
13
|
+
private readonly pendingFlushUrls;
|
|
13
14
|
private readonly sampled;
|
|
14
15
|
private started;
|
|
15
|
-
private
|
|
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
|
|
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 };
|
package/dist/web-vitals.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{t as e}from"./chunks/web-vitals-
|
|
1
|
+
import{t as e}from"./chunks/web-vitals-ClET5qGP.js";export{e as default};
|
package/package.json
CHANGED
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.
|
|
130
|
+
window.addEventListener("pagehide", this.requestFlush);
|
|
130
131
|
|
|
131
|
-
this.timer = setInterval(this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
248
|
+
this.requestFlush();
|
|
235
249
|
}
|
|
236
250
|
}
|
|
237
251
|
|
|
238
|
-
private flush
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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
|
|
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.
|
|
90
|
+
this.currentUrl = window.location.href;
|
|
61
91
|
|
|
62
|
-
document.addEventListener("visibilitychange", this.onVisibilityChange
|
|
63
|
-
|
|
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
|
-
|
|
75
|
-
window
|
|
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.
|
|
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
|
-
|
|
85
|
-
|
|
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")
|
|
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.
|
|
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
|
-
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
}
|
package/tests/replay.test.ts
CHANGED
|
@@ -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-ClMugucs.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-DQHUrcod.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-ur06JW5D.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.10`,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-ur06JW5D.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};
|
|
File without changes
|
|
File without changes
|