@faststats/web 0.2.10 → 0.2.12
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 +13 -0
- package/dist/chunks/error-CttYL43D.js +2 -0
- package/dist/chunks/identifiers-CQeWm7wi.js +1 -0
- package/dist/chunks/replay-BrMLCiBF.js +1 -0
- package/dist/chunks/{replay-IhsP2Ab4.d.ts → replay-BuX3_0xs.d.ts} +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-CjA1bFLG.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 +6 -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 +57 -115
- package/src/error.ts +38 -16
- package/src/replay.ts +13 -2
- package/src/utils/identifiers.ts +38 -26
- package/src/utils/send-data.ts +52 -0
- package/src/web-vitals.ts +157 -49
- package/tests/analytics.test.ts +35 -0
- package/tests/identifiers.test.ts +18 -0
- 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/types-C3vW7XGe.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
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @faststats/web
|
|
2
2
|
|
|
3
|
+
## 0.2.12
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d96a750: fix: refreshSessionTimestamp on replay page hide
|
|
8
|
+
|
|
9
|
+
## 0.2.11
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 754b67f: fix(replays): dedupe secquences to reduce risk of duplicate data sending
|
|
14
|
+
- d21ce42: refactor: more reliable and unified data sending
|
|
15
|
+
|
|
3
16
|
## 0.2.10
|
|
4
17
|
|
|
5
18
|
## 0.2.9
|
|
@@ -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-CQeWm7wi.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.12`,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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=`faststats_anon_id`,t=`session_id`,n=`session_timestamp`,r=`session_start`;let i=!1;function a(e){i=e}function o(e){return e??i?``:s()}function s(){if(!localStorage)return``;let t=localStorage.getItem(e);if(t)return t;let n=crypto.randomUUID();return localStorage.setItem(e,n),n}function c(t){return t||!localStorage?``:(localStorage.removeItem(e),s())}function l(){if(!sessionStorage)return``;let e=sessionStorage.getItem(t),i=sessionStorage.getItem(n);if(e&&i){if(Date.now()-Number.parseInt(i,10)<18e5)return sessionStorage.setItem(n,Date.now().toString()),e;p(sessionStorage)}let a=Date.now().toString(),o=crypto.randomUUID();return sessionStorage.setItem(t,o),sessionStorage.setItem(n,a),sessionStorage.setItem(r,a),o}function u(){return sessionStorage?(p(sessionStorage),l()):``}function d(){sessionStorage&&sessionStorage.getItem(t)&&sessionStorage.setItem(n,Date.now().toString())}function f(){if(!sessionStorage)return Date.now();let e=sessionStorage.getItem(r);if(e)return Number.parseInt(e,10);let t=sessionStorage.getItem(n);return t?Number.parseInt(t,10):Date.now()}function p(e){e.removeItem(t),e.removeItem(n),e.removeItem(r)}export{c as a,d as i,l as n,u as o,f as r,a as s,o 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-CQeWm7wi.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-CQeWm7wi.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-CttYL43D.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;
|
|
@@ -107,6 +109,7 @@ declare class WebAnalytics {
|
|
|
107
109
|
private stopNavigationTimer;
|
|
108
110
|
private stopChildTrackers;
|
|
109
111
|
private registerChildTracker;
|
|
112
|
+
private startChildTracker;
|
|
110
113
|
private startErrorTracker;
|
|
111
114
|
private startWebVitalsTracker;
|
|
112
115
|
private startReplayTracker;
|
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-CQeWm7wi.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`?(a(),this.leavePage(),this.stopHeartbeat()):(a(),this.enterPage(),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 startChildTracker(e,t,n){try{let r=await t();return!this.started||this.destroyed||g.instance!==this?(r.stop?.(),null):(r.start(),this.registerChildTracker(r)?(n?.(r),this.log(`${e} loaded`),r):null)}catch(t){return this.log(`failed to initialize ${e} tracker: ${String(t)}`),null}}async startErrorTracker(){await this.startChildTracker(`error`,async()=>{let{default:e}=await import(`./chunks/error-CttYL43D.js`).then(e=>e.n);return new e({siteKey:this.options.siteKey,baseUrl:this.baseUrl,debug:this.debug,sdkName:this.options.sdkName,sdkVersion:this.options.sdkVersion})},e=>{for(this.errorTracker=e;this.pendingReportedErrors.length>0;){let t=this.pendingReportedErrors.shift();t&&e.captureError(t)}})}async startWebVitalsTracker(){await this.startChildTracker(`web-vitals`,async()=>{let{default:e}=await import(`./chunks/web-vitals-CjA1bFLG.js`).then(e=>e.n);return 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})})}async startReplayTracker(e){await this.startChildTracker(`replay`,async()=>{let{default:t}=await import(`./chunks/replay-BrMLCiBF.js`).then(e=>e.n);return new t(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-BrMLCiBF.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-CjA1bFLG.js";export{e as default};
|
package/package.json
CHANGED
package/src/analytics.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type ErrorTracker from "./error";
|
|
2
2
|
import type { FeatureFlagEvaluation } from "./feature-flags";
|
|
3
|
-
import type ReplayTracker from "./replay";
|
|
4
3
|
import type { ReplayTrackerOptions } from "./replay";
|
|
5
4
|
import {
|
|
6
5
|
identifyEventsUrl,
|
|
@@ -17,18 +16,21 @@ import {
|
|
|
17
16
|
resetSessionId,
|
|
18
17
|
setCookielessMode,
|
|
19
18
|
} from "./utils/identifiers";
|
|
19
|
+
import { sendData } from "./utils/send-data";
|
|
20
20
|
import {
|
|
21
21
|
normalizeSamplingPercentage,
|
|
22
22
|
type SendDataOptions,
|
|
23
23
|
} from "./utils/types";
|
|
24
|
-
import type WebVitalsTracker from "./web-vitals";
|
|
25
24
|
|
|
26
25
|
export type { SendDataOptions };
|
|
26
|
+
export { sendData };
|
|
27
27
|
|
|
28
28
|
type ChildTracker = {
|
|
29
29
|
start(): void;
|
|
30
30
|
stop?(): void;
|
|
31
|
+
trackPageChange?(url?: string): void;
|
|
31
32
|
};
|
|
33
|
+
type TrackerFactory<T extends ChildTracker> = () => Promise<T>;
|
|
32
34
|
|
|
33
35
|
type Dict = Record<string, unknown>;
|
|
34
36
|
|
|
@@ -180,73 +182,6 @@ export function isTrackingDisabled(): boolean {
|
|
|
180
182
|
return value === "true" || value === "1";
|
|
181
183
|
}
|
|
182
184
|
|
|
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
185
|
function getLinkEl(el: Node | null): HTMLAnchorElement | null {
|
|
251
186
|
while (el) {
|
|
252
187
|
if (el instanceof HTMLAnchorElement && el.href) return el;
|
|
@@ -297,9 +232,12 @@ export class WebAnalytics {
|
|
|
297
232
|
private errorTracker: ErrorTracker | null = null;
|
|
298
233
|
private readonly handleVisibilityChange = (): void => {
|
|
299
234
|
if (document.visibilityState === "hidden") {
|
|
235
|
+
refreshSessionTimestamp();
|
|
300
236
|
this.leavePage();
|
|
301
237
|
this.stopHeartbeat();
|
|
302
238
|
} else {
|
|
239
|
+
refreshSessionTimestamp();
|
|
240
|
+
this.enterPage();
|
|
303
241
|
this.startHeartbeat();
|
|
304
242
|
}
|
|
305
243
|
};
|
|
@@ -429,40 +367,58 @@ export class WebAnalytics {
|
|
|
429
367
|
return true;
|
|
430
368
|
}
|
|
431
369
|
|
|
432
|
-
private async
|
|
370
|
+
private async startChildTracker<T extends ChildTracker>(
|
|
371
|
+
name: string,
|
|
372
|
+
createTracker: TrackerFactory<T>,
|
|
373
|
+
onStarted?: (tracker: T) => void,
|
|
374
|
+
): Promise<T | null> {
|
|
433
375
|
try {
|
|
434
|
-
const
|
|
435
|
-
if (!this.started || this.destroyed || moduleState.instance !== this)
|
|
436
|
-
|
|
376
|
+
const tracker = await createTracker();
|
|
377
|
+
if (!this.started || this.destroyed || moduleState.instance !== this) {
|
|
378
|
+
tracker.stop?.();
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
437
381
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
baseUrl: this.baseUrl,
|
|
441
|
-
debug: this.debug,
|
|
442
|
-
sdkName: this.options.sdkName,
|
|
443
|
-
sdkVersion: this.options.sdkVersion,
|
|
444
|
-
});
|
|
445
|
-
errorTracker.start();
|
|
446
|
-
if (!this.registerChildTracker(errorTracker)) return;
|
|
382
|
+
tracker.start();
|
|
383
|
+
if (!this.registerChildTracker(tracker)) return null;
|
|
447
384
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if (pending) errorTracker.captureError(pending);
|
|
452
|
-
}
|
|
453
|
-
this.log("error loaded");
|
|
385
|
+
onStarted?.(tracker);
|
|
386
|
+
this.log(`${name} loaded`);
|
|
387
|
+
return tracker;
|
|
454
388
|
} catch (error) {
|
|
455
|
-
this.log(`failed to initialize
|
|
389
|
+
this.log(`failed to initialize ${name} tracker: ${String(error)}`);
|
|
390
|
+
return null;
|
|
456
391
|
}
|
|
457
392
|
}
|
|
458
393
|
|
|
394
|
+
private async startErrorTracker(): Promise<void> {
|
|
395
|
+
await this.startChildTracker(
|
|
396
|
+
"error",
|
|
397
|
+
async () => {
|
|
398
|
+
const { default: ErrorTrackerClass } = await import("./error");
|
|
399
|
+
return new ErrorTrackerClass({
|
|
400
|
+
siteKey: this.options.siteKey,
|
|
401
|
+
baseUrl: this.baseUrl,
|
|
402
|
+
debug: this.debug,
|
|
403
|
+
sdkName: this.options.sdkName,
|
|
404
|
+
sdkVersion: this.options.sdkVersion,
|
|
405
|
+
});
|
|
406
|
+
},
|
|
407
|
+
(errorTracker) => {
|
|
408
|
+
this.errorTracker = errorTracker;
|
|
409
|
+
|
|
410
|
+
while (this.pendingReportedErrors.length > 0) {
|
|
411
|
+
const pending = this.pendingReportedErrors.shift();
|
|
412
|
+
if (pending) errorTracker.captureError(pending);
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
459
418
|
private async startWebVitalsTracker(): Promise<void> {
|
|
460
|
-
|
|
419
|
+
await this.startChildTracker("web-vitals", async () => {
|
|
461
420
|
const { default: WebVitalsTrackerClass } = await import("./web-vitals");
|
|
462
|
-
|
|
463
|
-
return;
|
|
464
|
-
|
|
465
|
-
const webVitalsTracker: WebVitalsTracker = new WebVitalsTrackerClass({
|
|
421
|
+
return new WebVitalsTrackerClass({
|
|
466
422
|
siteKey: this.options.siteKey,
|
|
467
423
|
baseUrl: this.baseUrl,
|
|
468
424
|
debug: this.debug,
|
|
@@ -471,33 +427,16 @@ export class WebAnalytics {
|
|
|
471
427
|
),
|
|
472
428
|
attribution: this.options.webVitals?.attribution ?? false,
|
|
473
429
|
});
|
|
474
|
-
|
|
475
|
-
if (!this.registerChildTracker(webVitalsTracker)) return;
|
|
476
|
-
|
|
477
|
-
this.log("web-vitals loaded");
|
|
478
|
-
} catch (error) {
|
|
479
|
-
this.log(`failed to initialize web-vitals tracker: ${String(error)}`);
|
|
480
|
-
}
|
|
430
|
+
});
|
|
481
431
|
}
|
|
482
432
|
|
|
483
433
|
private async startReplayTracker(
|
|
484
434
|
replayOptions: ReplayTrackerOptions,
|
|
485
435
|
): Promise<void> {
|
|
486
|
-
|
|
436
|
+
await this.startChildTracker("replay", async () => {
|
|
487
437
|
const { default: ReplayTrackerClass } = await import("./replay");
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const replayTracker: ReplayTracker = new ReplayTrackerClass(
|
|
492
|
-
replayOptions,
|
|
493
|
-
);
|
|
494
|
-
replayTracker.start();
|
|
495
|
-
if (!this.registerChildTracker(replayTracker)) return;
|
|
496
|
-
|
|
497
|
-
this.log("replay loaded");
|
|
498
|
-
} catch (error) {
|
|
499
|
-
this.log(`failed to initialize replay tracker: ${String(error)}`);
|
|
500
|
-
}
|
|
438
|
+
return new ReplayTrackerClass(replayOptions);
|
|
439
|
+
});
|
|
501
440
|
}
|
|
502
441
|
|
|
503
442
|
async start(): Promise<void> {
|
|
@@ -816,6 +755,9 @@ export class WebAnalytics {
|
|
|
816
755
|
if (!pathChanged && !hashChanged) return;
|
|
817
756
|
this.leavePage();
|
|
818
757
|
this.enterPage();
|
|
758
|
+
for (const tracker of this.childTrackers) {
|
|
759
|
+
tracker.trackPageChange?.(this.pageUrl);
|
|
760
|
+
}
|
|
819
761
|
this.trackScroll();
|
|
820
762
|
this.pageview({ trigger: "navigation" });
|
|
821
763
|
}, 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
|
}
|