@faststats/web 0.1.9 → 0.2.1
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 +16 -0
- package/dist/analytics-Bi40uydz.js +1 -0
- package/dist/error-i6Z4Acxj.js +2 -0
- package/dist/module.d.ts +29 -2
- package/dist/module.js +1 -1
- package/dist/replay-CLQirdnl.js +1 -0
- package/dist/types-Df70G0eM.js +1 -0
- package/dist/web-vitals-gcUR-TX_.js +1 -0
- package/package.json +1 -3
- package/src/analytics.ts +42 -5
- package/src/error.ts +131 -175
- package/src/feature-flags.ts +80 -0
- package/src/module.ts +9 -0
- package/src/replay.ts +242 -467
- package/src/utils/api-urls.ts +11 -0
- package/src/web-vitals.ts +58 -120
- package/dist/analytics-B1kgY_Yf.js +0 -1
- package/dist/error-Cs5wnTbZ.js +0 -2
- package/dist/replay-DWCkIH4r.js +0 -1
- package/dist/types-8FXsUqbi.js +0 -1
- package/dist/web-vitals-C7H69Xv3.js +0 -1
- package/tests/replay-stress.test.ts +0 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @faststats/web
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d80a042: fix: use new check endpoint
|
|
8
|
+
|
|
9
|
+
## 0.2.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 55032f8: feat: add feature flags
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 98385da: refactor: reduce bundle size and make results more stable
|
|
18
|
+
|
|
3
19
|
## 0.1.9
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e,c as t,d as n,f as r,i,l as a,m as o,n as s,o as c,r as l,s as u,t as d,u as f}from"./types-Df70G0eM.js";async function p(e,t){if(t.projectToken&&t.projectId)throw Error(`provide either projectToken or projectId, not both`);if(t.serverId&&t.externalId)throw Error(`provide either serverId or externalId, not both`);let n=t.projectToken,r=t.projectId,i=t.serverId,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,...n?{projectToken:n}:{projectId:r},...i?{serverId:i}:{externalId:o}};t.attributes&&Object.keys(t.attributes).length>0&&(s.attributes=t.attributes);let c=a(t.baseUrl),l=await fetch(c,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(s),credentials:`omit`,signal:t.signal});if(!l.ok){let e=``;try{let t=await l.json();typeof t.error==`string`&&t.error.length>0&&(e=` — ${t.error}`)}catch{}throw Error(`feature flag check failed: HTTP ${l.status}${e}`)}let u=(await l.json()).evaluation;return u?.enabled===!0||u?.enabled===!1?u:{enabled:!1}}let m=null,h;function g(){return m}function _(e,t){typeof window>`u`||w()||m?.track(e,t??{})}function v(e,t,n){typeof window>`u`||w()||m?.identify(e,t,n??{})}function y(e=!0){typeof window>`u`||w()||m?.logout(e)}function b(e){if(m){m.setConsentMode(e);return}h=e}function x(){b(`granted`)}function S(){b(`denied`)}function C(e){typeof window>`u`||w()||m?.reportError(e)}function w(){if(typeof localStorage>`u`)return!1;let e=localStorage.getItem(`disable-faststats`);return e===`true`||e===`1`}async function T(e){let{url:t,data:n,contentType:r=`application/json`,headers:i={},debug:a=!1,debugPrefix:o=`[Analytics]`,useBeacon:s=!0,keepalive:c=!0}=e;if(s&&typeof navigator<`u`&&typeof navigator.sendBeacon==`function`)try{let e=n instanceof Blob?n:typeof Blob<`u`?new Blob([n],{type:r}):n;if(navigator.sendBeacon(t,e))return a&&console.log(`${o} Sent via beacon`),!0}catch{}if(typeof fetch!=`function`)return a&&console.warn(`${o} Failed to send`),!1;try{let e=await fetch(t,{method:`POST`,body:n,headers:{"Content-Type":r,...i},keepalive:c}),s=e.ok;return a&&(s?console.log(`${o} Sent via fetch`):console.warn(`${o} Failed: ${e.status}`)),s}catch{return a&&console.warn(`${o} Failed to send`),!1}}function E(e){for(;e;){if(e instanceof HTMLAnchorElement&&e.href)return e;e=e.parentNode}return null}function D(){let e={};if(!location.search)return e;let t=new URLSearchParams(location.search);for(let n of[`utm_source`,`utm_medium`,`utm_campaign`,`utm_term`,`utm_content`]){let r=t.get(n);r&&(e[n]=r)}return e}var O=class{webEndpoint;baseUrl;featureFlagsBaseUrl;debug;started=!1;destroyed=!1;pageKey=``;navTimer=null;heartbeatTimer=null;scrollDepth=0;pageEntryTime=0;pagePath=``;pageUrl=``;pageHash=``;hasLeftCurrentPage=!1;scrollHandler=null;consentMode;cookielessWhilePending;cleanupCallbacks=[];childTrackers=[];pendingReportedErrors=[];errorTracker=null;handleVisibilityChange=()=>{document.visibilityState===`hidden`?(this.leavePage(),this.stopHeartbeat()):this.startHeartbeat()};handlePageHide=()=>{this.leavePage()};handlePopState=()=>{this.navigate()};handleHashChange=()=>{this.navigate()};constructor(e){this.options=e,this.baseUrl=n(e.baseUrl),this.featureFlagsBaseUrl=r(e.featureFlagsBaseUrl),this.webEndpoint=o(this.baseUrl),this.debug=e.debug??!1,this.consentMode=e.consent?.mode??`granted`,this.cookielessWhilePending=e.consent?.cookielessWhilePending??!0,h!==void 0&&(this.consentMode=h,h=void 0),(e.autoTrack??!0)&&this.init()}log(e){this.debug&&console.log(`[Analytics] ${e}`)}init(){if(!(typeof window>`u`)){if(w()){this.log(`disabled`);return}m=this,t(this.isCookielessMode()),setTimeout(()=>void this.start(),0)}}registerCleanup(e){this.cleanupCallbacks.push(e)}addWindowListener(e,t){window.addEventListener(e,t),this.registerCleanup(()=>window.removeEventListener(e,t))}addDocumentListener(e,t){document.addEventListener(e,t),this.registerCleanup(()=>document.removeEventListener(e,t))}patchHistory(){let e=history.pushState.bind(history),t=history.replaceState.bind(history);history.pushState=(t,n,r)=>{e(t,n,r),this.navigate()},history.replaceState=(e,n,r)=>{t(e,n,r),this.navigate()},this.registerCleanup(()=>{history.pushState=e,history.replaceState=t})}ensureStarted(){return typeof window>`u`||this.destroyed||w()?!1:(this.started||this.start(),!0)}stopHeartbeat(){this.heartbeatTimer&&=(clearInterval(this.heartbeatTimer),null)}stopNavigationTimer(){this.navTimer&&=(clearTimeout(this.navTimer),null)}stopChildTrackers(){for(let e of this.childTrackers.splice(0))e.stop?.();this.errorTracker=null}async start(){if(this.started||this.destroyed||typeof window>`u`)return;if(m&&m!==this){this.log(`already started by another instance`);return}if(w()){this.log(`disabled`);return}this.started=!0,m=this,t(this.isCookielessMode());let e=this.options;if(e.trackErrors)try{let{default:t}=await import(`./error-i6Z4Acxj.js`),n=new t({siteKey:e.siteKey,baseUrl:this.baseUrl,debug:this.debug});for(n.start(),this.errorTracker=n,this.childTrackers.push(n);this.pendingReportedErrors.length>0;){let e=this.pendingReportedErrors.shift();e&&n.captureError(e)}this.log(`error loaded`)}catch(e){this.log(`failed to initialize error tracker: ${String(e)}`)}if(e.trackWebVitals)try{let{default:t}=await import(`./web-vitals-gcUR-TX_.js`),n=new t({siteKey:e.siteKey,baseUrl:this.baseUrl,debug:this.debug,samplingPercentage:d(e.webVitals?.sampling?.percentage)});n.start(),this.childTrackers.push(n),this.log(`web-vitals loaded`)}catch(e){this.log(`failed to initialize web-vitals tracker: ${String(e)}`)}if(e.trackReplay)try{let{default:t}=await import(`./replay-CLQirdnl.js`),n=new t({siteKey:e.siteKey,baseUrl:this.baseUrl,debug:this.debug,...e.replayOptions});n.start(),this.childTrackers.push(n),this.log(`replay loaded`)}catch(e){this.log(`failed to initialize replay tracker: ${String(e)}`)}this.enterPage(),this.pageview({trigger:`load`}),this.links(),this.trackScroll(),this.startHeartbeat(),this.addDocumentListener(`visibilitychange`,this.handleVisibilityChange),this.addWindowListener(`pagehide`,this.handlePageHide),this.addWindowListener(`popstate`,this.handlePopState),e.trackHash&&this.addWindowListener(`hashchange`,this.handleHashChange),this.patchHistory()}destroy(){if(!this.destroyed){for(this.started&&typeof window<`u`&&this.leavePage(),this.pendingReportedErrors.length=0,this.stopNavigationTimer(),this.stopHeartbeat(),this.scrollHandler&&typeof window<`u`&&(window.removeEventListener(`scroll`,this.scrollHandler),this.scrollHandler=null);this.cleanupCallbacks.length>0;)this.cleanupCallbacks.pop()?.();this.stopChildTrackers(),m===this&&(m=null),this.started=!1,this.destroyed=!0}}pageview(e={}){if(!this.ensureStarted())return;let t=`${location.pathname}|${this.options.trackHash??!1?location.hash:``}`;t!==this.pageKey&&(this.pageKey=t,this.send(`pageview`,e))}track(e,t={}){this.ensureStarted()&&this.send(e,t)}identify(e,t,n={}){if(!this.ensureStarted()||this.isCookielessMode())return;let r=e.trim(),i=t.trim();!r||!i||T({url:f(this.baseUrl),data:JSON.stringify({token:this.options.siteKey,identifier:s(!1),externalId:r,email:i,name:n.name?.trim()||void 0,phone:n.phone?.trim()||void 0,avatarUrl:n.avatarUrl?.trim()||void 0,traits:n.traits??{}}),contentType:`text/plain`,debug:this.debug,debugPrefix:`[Analytics] identify`})}logout(e=!0){this.ensureStarted()&&(e&&c(this.isCookielessMode()),u())}setConsentMode(e){this.consentMode=e,t(this.isCookielessMode())}optIn(){this.setConsentMode(`granted`)}optOut(){this.setConsentMode(`denied`)}getConsentMode(){return this.consentMode}getAnonymousId(){return s(this.isCookielessMode())}getSessionId(){return l()}async checkFeatureFlag(e,t,n){if(typeof window>`u`||w())return{enabled:!1};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,serverId:i,attributes:t,signal:n?.signal}):{enabled:!1}}reportError(e){if(this.destroyed||typeof window>`u`||w()||!(this.options.trackErrors??!1)||!this.ensureStarted())return;let t=this.errorTracker;if(t){t.captureError(e);return}this.pendingReportedErrors.length>=50&&this.pendingReportedErrors.shift(),this.pendingReportedErrors.push(e)}isCookielessMode(){return this.options.cookieless||this.consentMode===`denied`?!0:this.consentMode===`pending`?this.cookielessWhilePending:!1}send(e,t={}){if(typeof window>`u`||this.destroyed||w())return;let n=s(this.isCookielessMode()),r=JSON.stringify({token:this.options.siteKey,...n?{userId:n}:{},sessionId:l(),data:{event:e,page:location.pathname,referrer:document.referrer||null,title:document.title||``,url:location.href,...D(),...t}});this.log(e),T({url:this.webEndpoint,data:r,contentType:`text/plain`,debug:this.debug,debugPrefix:`[Analytics] ${e}`})}enterPage(){this.pageEntryTime=Date.now(),this.pagePath=location.pathname,this.pageUrl=location.href,this.pageHash=location.hash,this.scrollDepth=0,this.hasLeftCurrentPage=!1}leavePage(){if(this.destroyed||this.hasLeftCurrentPage)return;this.hasLeftCurrentPage=!0;let e=Date.now();this.send(`page_leave`,{page:this.pagePath,url:this.pageUrl,time_on_page:e-this.pageEntryTime,scroll_depth:this.scrollDepth,session_duration:e-i()})}trackScroll(){this.scrollHandler&&window.removeEventListener(`scroll`,this.scrollHandler);let e=()=>{let e=document.documentElement,t=document.body,n=window.innerHeight,r=Math.max(e.scrollHeight,t.scrollHeight);if(r<=n){this.scrollDepth=100;return}let i=Math.min(100,Math.round(((window.scrollY||e.scrollTop)+n)/r*100));i>this.scrollDepth&&(this.scrollDepth=i)};this.scrollHandler=e,e(),window.addEventListener(`scroll`,e,{passive:!0})}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{if(document.visibilityState===`hidden`){this.stopHeartbeat();return}e()},300*1e3)}navigate(){!this.started||this.destroyed||(this.stopNavigationTimer(),this.navTimer=setTimeout(()=>{this.navTimer=null;let e=location.pathname!==this.pagePath,t=(this.options.trackHash??!1)&&location.hash!==this.pageHash;!e&&!t||(this.leavePage(),this.enterPage(),this.trackScroll(),this.pageview({trigger:`navigation`}))},300))}links(){let e=e=>{let t=E(e.target);t&&t.host!==location.host&&this.track(`outbound_link`,{outbound_link:t.href})};this.addDocumentListener(`click`,e),this.addDocumentListener(`auxclick`,e)}};export{y as a,C as c,_ as d,p as f,w as i,T as l,g as n,x as o,v as r,S as s,O as t,b as u};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{m as e,n as t,r as n}from"./types-Df70G0eM.js";import{l as r}from"./analytics-Bi40uydz.js";const i=/(?:^|\()(chrome|moz|ms-browser|safari-web)-extension:\/\//,a=e=>e?.split(`
|
|
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
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { recordOptions } from "rrweb/typings/types";
|
|
2
2
|
import { SlimDOMOptions } from "rrweb-snapshot";
|
|
3
3
|
|
|
4
|
+
//#region src/feature-flags.d.ts
|
|
5
|
+
type FeatureFlagEvaluation = {
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
value?: string;
|
|
8
|
+
variantKey?: string;
|
|
9
|
+
variantValue?: string;
|
|
10
|
+
};
|
|
11
|
+
type FeatureFlagCheckContext = {
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
projectToken?: string;
|
|
14
|
+
projectId?: string;
|
|
15
|
+
serverId?: string;
|
|
16
|
+
externalId?: string;
|
|
17
|
+
attributes?: Record<string, unknown>;
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
};
|
|
20
|
+
declare function fetchFeatureFlagEvaluation(flagKey: string, context: FeatureFlagCheckContext): Promise<FeatureFlagEvaluation>;
|
|
21
|
+
//#endregion
|
|
4
22
|
//#region ../../node_modules/.bun/@rrweb+types@2.0.0-alpha.20/node_modules/@rrweb/types/dist/index.d.ts
|
|
5
23
|
declare type addedNodeMutation = {
|
|
6
24
|
parentId: number;
|
|
@@ -412,6 +430,7 @@ interface IdentifyOptions {
|
|
|
412
430
|
interface WebAnalyticsOptions {
|
|
413
431
|
siteKey: string;
|
|
414
432
|
baseUrl?: string;
|
|
433
|
+
featureFlagsBaseUrl?: string;
|
|
415
434
|
debug?: boolean;
|
|
416
435
|
autoTrack?: boolean;
|
|
417
436
|
trackHash?: boolean;
|
|
@@ -438,6 +457,7 @@ declare class WebAnalytics {
|
|
|
438
457
|
private readonly options;
|
|
439
458
|
private readonly webEndpoint;
|
|
440
459
|
private readonly baseUrl;
|
|
460
|
+
private readonly featureFlagsBaseUrl;
|
|
441
461
|
private readonly debug;
|
|
442
462
|
private started;
|
|
443
463
|
private destroyed;
|
|
@@ -472,7 +492,6 @@ declare class WebAnalytics {
|
|
|
472
492
|
private stopHeartbeat;
|
|
473
493
|
private stopNavigationTimer;
|
|
474
494
|
private stopChildTrackers;
|
|
475
|
-
private getErrorTracker;
|
|
476
495
|
start(): Promise<void>;
|
|
477
496
|
destroy(): void;
|
|
478
497
|
pageview(extra?: Dict): void;
|
|
@@ -485,6 +504,10 @@ declare class WebAnalytics {
|
|
|
485
504
|
getConsentMode(): ConsentMode;
|
|
486
505
|
getAnonymousId(): string;
|
|
487
506
|
getSessionId(): string;
|
|
507
|
+
checkFeatureFlag(key: string, attributes?: Record<string, unknown>, opts?: {
|
|
508
|
+
externalId?: string;
|
|
509
|
+
signal?: AbortSignal;
|
|
510
|
+
}): Promise<FeatureFlagEvaluation>;
|
|
488
511
|
reportError(error: Error): void;
|
|
489
512
|
private isCookielessMode;
|
|
490
513
|
private send;
|
|
@@ -496,4 +519,8 @@ declare class WebAnalytics {
|
|
|
496
519
|
private links;
|
|
497
520
|
}
|
|
498
521
|
//#endregion
|
|
499
|
-
|
|
522
|
+
//#region src/utils/api-urls.d.ts
|
|
523
|
+
declare function normalizeFeatureFlagsBaseUrl(input?: string): string;
|
|
524
|
+
declare function featureFlagsCheckUrl(baseUrl?: string): string;
|
|
525
|
+
//#endregion
|
|
526
|
+
export { type ConsentMode, type FeatureFlagCheckContext, type FeatureFlagEvaluation, type IdentifyOptions, WebAnalytics, type WebAnalyticsOptions, featureFlagsCheckUrl, fetchFeatureFlagEvaluation, getInstance, identify, isTrackingDisabled, logout, normalizeFeatureFlagsBaseUrl, optIn, optOut, reportError, sendData, setConsentMode, trackEvent };
|
package/dist/module.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./types-
|
|
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-Bi40uydz.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{n as e,p as t,r as n,t as r}from"./types-Df70G0eM.js";import{l as i}from"./analytics-Bi40uydz.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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=`https://metrics.faststats.dev`,t=`https://flags.faststats.dev`;function n(e){return e.replace(/\/+$/,``)}function r(t){return t===void 0||t===``?e:n(t)||e}function i(e){return`${r(e)}/v1/web`}function a(e){return`${r(e)}/v1/identify`}function o(e){return`${r(e)}/v1/replay`}function s(e){return`${r(e)}/v1/vitals`}function c(e){return e===void 0||e===``?t:n(e)||t}function l(e){return`${c(e)}/v1/check`}let u=!1;function d(e){u=e}function f(e){return e??u?``:p()}function p(){if(typeof localStorage>`u`)return``;let e=localStorage.getItem(`faststats_anon_id`);if(e)return e;let t=crypto.randomUUID();return localStorage.setItem(`faststats_anon_id`,t),t}function m(e){return e||typeof localStorage>`u`?``:(localStorage.removeItem(`faststats_anon_id`),p())}function h(){if(typeof sessionStorage>`u`)return``;let e=sessionStorage.getItem(`session_id`),t=sessionStorage.getItem(`session_timestamp`);if(e&&t){if(Date.now()-Number.parseInt(t,10)<18e5)return sessionStorage.setItem(`session_timestamp`,Date.now().toString()),e;sessionStorage.removeItem(`session_id`),sessionStorage.removeItem(`session_timestamp`),sessionStorage.removeItem(`session_start`)}let n=Date.now().toString(),r=crypto.randomUUID();return sessionStorage.setItem(`session_id`,r),sessionStorage.setItem(`session_timestamp`,n),sessionStorage.setItem(`session_start`,n),r}function g(){return typeof sessionStorage>`u`?``:(sessionStorage.removeItem(`session_id`),sessionStorage.removeItem(`session_timestamp`),sessionStorage.removeItem(`session_start`),h())}function _(){typeof sessionStorage>`u`||sessionStorage.getItem(`session_id`)&&sessionStorage.setItem(`session_timestamp`,Date.now().toString())}function v(){if(typeof sessionStorage>`u`)return Date.now();let e=sessionStorage.getItem(`session_start`);if(e)return Number.parseInt(e,10);let t=sessionStorage.getItem(`session_timestamp`);return t?Number.parseInt(t,10):Date.now()}function y(e){return typeof e!=`number`||!Number.isFinite(e)?100:Math.max(0,Math.min(100,e))}export{_ as a,d as c,r as d,c as f,s as h,v as i,l,i as m,f as n,m as o,o as p,h as r,g as s,y as t,a as u};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{h as e,r as t,t as n}from"./types-Df70G0eM.js";import{onCLS as r,onFCP as i,onINP as a,onLCP as o,onTTFB as s}from"web-vitals/attribution";const c=[r,a,o,i,s];var l=class{endpoint;metrics=new Map;sampled;started=!1;flushed=!1;constructor(t){this.options=t,this.endpoint=e(t.baseUrl),this.sampled=Math.random()*100<n(t.samplingPercentage)}get debug(){return this.options.debug??!1}log(...e){this.debug&&console.log(`[WebVitals]`,...e)}start(){if(!(this.started||typeof window>`u`)){this.started=!0,this.flushed=!1,document.addEventListener(`visibilitychange`,this.onVisibilityChange),window.addEventListener(`pagehide`,this.flush),this.log(`Tracking started`);for(let e of c)e(this.captureMetric)}}stop(){!this.started||typeof window>`u`||(this.started=!1,document.removeEventListener(`visibilitychange`,this.onVisibilityChange),window.removeEventListener(`pagehide`,this.flush),this.flush())}onVisibilityChange=()=>{document.visibilityState===`hidden`&&this.flush()};captureMetric=e=>{if(this.flushed||!this.sampled)return;let t=e.name;this.metrics.set(t,{value:e.value,attributes:{id:e.id,rating:e.rating,delta:e.delta,navigationType:e.navigationType,...e.attribution??{}}}),this.log(`${t} captured: ${e.value}`)};flush=()=>{if(this.flushed||this.metrics.size===0)return;this.flushed=!0;let e=[...this.metrics.entries()].map(([e,t])=>({metric:e,value:t.value,attributes:t.attributes})),n=e.map(e=>e.metric).join(`, `);this.metrics.clear();let r=JSON.stringify({sessionId:t(),vitals:e,metadata:{url:window.location.href}});this.log(`Sending final metrics (${e.length}): ${n}`),typeof fetch==`function`&&fetch(this.endpoint,{method:`POST`,body:r,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${this.options.siteKey}`},keepalive:!0}).catch(()=>{this.log(`Failed to send metrics`)})}};export{l as default};
|
package/package.json
CHANGED
|
@@ -19,14 +19,12 @@
|
|
|
19
19
|
"publishConfig": {
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
|
-
"version": "0.1
|
|
22
|
+
"version": "0.2.1",
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "tsdown",
|
|
25
25
|
"dev": "bun run build",
|
|
26
26
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
27
27
|
"test": "bun test",
|
|
28
|
-
"test:perf": "bun test tests/replay-stress.test.ts",
|
|
29
|
-
"check:bundle-size": "node scripts/check-bundle-size.mjs",
|
|
30
28
|
"deploy:worker": "wrangler deploy"
|
|
31
29
|
},
|
|
32
30
|
"devDependencies": {
|
package/src/analytics.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type ErrorTracker from "./error";
|
|
2
|
+
import {
|
|
3
|
+
type FeatureFlagEvaluation,
|
|
4
|
+
fetchFeatureFlagEvaluation,
|
|
5
|
+
} from "./feature-flags";
|
|
2
6
|
import type ReplayTracker from "./replay";
|
|
3
7
|
import type { ReplayTrackerOptions } from "./replay";
|
|
4
8
|
import {
|
|
5
9
|
identifyEventsUrl,
|
|
6
10
|
normalizeAnalyticsBaseUrl,
|
|
11
|
+
normalizeFeatureFlagsBaseUrl,
|
|
7
12
|
webEventsUrl,
|
|
8
13
|
} from "./utils/api-urls";
|
|
9
14
|
import {
|
|
@@ -59,6 +64,7 @@ export interface IdentifyOptions {
|
|
|
59
64
|
export interface WebAnalyticsOptions {
|
|
60
65
|
siteKey: string;
|
|
61
66
|
baseUrl?: string;
|
|
67
|
+
featureFlagsBaseUrl?: string;
|
|
62
68
|
debug?: boolean;
|
|
63
69
|
autoTrack?: boolean;
|
|
64
70
|
trackHash?: boolean;
|
|
@@ -223,6 +229,7 @@ function getUTM(): Record<string, string> {
|
|
|
223
229
|
export class WebAnalytics {
|
|
224
230
|
private readonly webEndpoint: string;
|
|
225
231
|
private readonly baseUrl: string;
|
|
232
|
+
private readonly featureFlagsBaseUrl: string;
|
|
226
233
|
private readonly debug: boolean;
|
|
227
234
|
private started = false;
|
|
228
235
|
private destroyed = false;
|
|
@@ -262,6 +269,9 @@ export class WebAnalytics {
|
|
|
262
269
|
|
|
263
270
|
constructor(private readonly options: WebAnalyticsOptions) {
|
|
264
271
|
this.baseUrl = normalizeAnalyticsBaseUrl(options.baseUrl);
|
|
272
|
+
this.featureFlagsBaseUrl = normalizeFeatureFlagsBaseUrl(
|
|
273
|
+
options.featureFlagsBaseUrl,
|
|
274
|
+
);
|
|
265
275
|
this.webEndpoint = webEventsUrl(this.baseUrl);
|
|
266
276
|
this.debug = options.debug ?? false;
|
|
267
277
|
this.consentMode = options.consent?.mode ?? "granted";
|
|
@@ -363,10 +373,6 @@ export class WebAnalytics {
|
|
|
363
373
|
this.errorTracker = null;
|
|
364
374
|
}
|
|
365
375
|
|
|
366
|
-
private getErrorTracker(): ErrorTracker | null {
|
|
367
|
-
return this.errorTracker;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
376
|
async start(): Promise<void> {
|
|
371
377
|
if (this.started || this.destroyed || typeof window === "undefined") return;
|
|
372
378
|
if (_instance && _instance !== this) {
|
|
@@ -561,6 +567,37 @@ export class WebAnalytics {
|
|
|
561
567
|
return getOrCreateSessionId();
|
|
562
568
|
}
|
|
563
569
|
|
|
570
|
+
async checkFeatureFlag(
|
|
571
|
+
key: string,
|
|
572
|
+
attributes?: Record<string, unknown>,
|
|
573
|
+
opts?: { externalId?: string; signal?: AbortSignal },
|
|
574
|
+
): Promise<FeatureFlagEvaluation> {
|
|
575
|
+
if (typeof window === "undefined" || isTrackingDisabled()) {
|
|
576
|
+
return { enabled: false };
|
|
577
|
+
}
|
|
578
|
+
const externalId = opts?.externalId?.trim();
|
|
579
|
+
if (externalId) {
|
|
580
|
+
return fetchFeatureFlagEvaluation(key, {
|
|
581
|
+
baseUrl: this.featureFlagsBaseUrl,
|
|
582
|
+
projectToken: this.options.siteKey,
|
|
583
|
+
externalId,
|
|
584
|
+
attributes,
|
|
585
|
+
signal: opts?.signal,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
const serverId = this.getAnonymousId();
|
|
589
|
+
if (!serverId) {
|
|
590
|
+
return { enabled: false };
|
|
591
|
+
}
|
|
592
|
+
return fetchFeatureFlagEvaluation(key, {
|
|
593
|
+
baseUrl: this.featureFlagsBaseUrl,
|
|
594
|
+
projectToken: this.options.siteKey,
|
|
595
|
+
serverId,
|
|
596
|
+
attributes,
|
|
597
|
+
signal: opts?.signal,
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
564
601
|
reportError(error: Error): void {
|
|
565
602
|
if (
|
|
566
603
|
this.destroyed ||
|
|
@@ -571,7 +608,7 @@ export class WebAnalytics {
|
|
|
571
608
|
}
|
|
572
609
|
if (!(this.options.trackErrors ?? false)) return;
|
|
573
610
|
if (!this.ensureStarted()) return;
|
|
574
|
-
const tracker = this.
|
|
611
|
+
const tracker = this.errorTracker;
|
|
575
612
|
if (tracker) {
|
|
576
613
|
tracker.captureError(error);
|
|
577
614
|
return;
|