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