@faststats/web 0.2.12 → 0.2.14

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/REPLAY_PAYLOAD.md +64 -0
  3. package/dist/chunks/api-urls-DaeYkG0_.js +1 -0
  4. package/dist/chunks/error-Cd9PTS5v.js +2 -0
  5. package/dist/chunks/{feature-flags-BxZz_lNm.d.ts → feature-flags-BClx56v5.d.ts} +1 -0
  6. package/dist/chunks/feature-flags-DSOCIZHK.js +1 -0
  7. package/dist/chunks/{replay-BuX3_0xs.d.ts → replay-DvJYurEC.d.ts} +10 -2
  8. package/dist/chunks/replay-rTqcjOo2.js +1 -0
  9. package/dist/chunks/send-data-B2fYGj6v.js +1 -0
  10. package/dist/chunks/session-manager-Cy63ptPF.js +1 -0
  11. package/dist/chunks/web-vitals-Be-Cg4Po.js +1 -0
  12. package/dist/error.d.ts +5 -8
  13. package/dist/error.js +1 -1
  14. package/dist/feature-flags.d.ts +1 -1
  15. package/dist/feature-flags.js +1 -1
  16. package/dist/index.d.ts +3 -2
  17. package/dist/index.js +1 -1
  18. package/dist/replay.d.ts +1 -1
  19. package/dist/replay.js +1 -1
  20. package/dist/web-vitals.d.ts +7 -15
  21. package/dist/web-vitals.js +1 -1
  22. package/package.json +4 -7
  23. package/src/analytics.ts +25 -14
  24. package/src/error.ts +110 -158
  25. package/src/feature-flags.ts +5 -2
  26. package/src/replay.ts +179 -52
  27. package/src/utils/api-urls.ts +7 -20
  28. package/src/utils/identifiers.ts +30 -72
  29. package/src/utils/session-manager.ts +416 -0
  30. package/src/web-vitals.ts +66 -168
  31. package/tests/analytics.test.ts +8 -4
  32. package/tests/identifiers.test.ts +15 -32
  33. package/tests/replay.test.ts +180 -10
  34. package/tests/session-manager.test.ts +161 -0
  35. package/dist/chunks/api-urls-BrkcoElX.js +0 -1
  36. package/dist/chunks/error-CttYL43D.js +0 -2
  37. package/dist/chunks/feature-flags-CjnLZGxp.js +0 -1
  38. package/dist/chunks/identifiers-CQeWm7wi.js +0 -1
  39. package/dist/chunks/replay-BrMLCiBF.js +0 -1
  40. package/dist/chunks/send-data-DL_GlsQw.js +0 -1
  41. package/dist/chunks/web-vitals-CjA1bFLG.js +0 -1
  42. package/wrangler.toml +0 -8
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @faststats/web
2
2
 
3
+ ## 0.2.14
4
+
5
+ ### Patch Changes
6
+
7
+ - bebbb67: chore: update web-vitals to 5.2.0
8
+ - ab51be7: fix: tag replay batches with per-tab window and page ids instead of rrweb sequential ids
9
+
10
+ ## 0.2.13
11
+
12
+ ### Patch Changes
13
+
14
+ - 28fb1ce: chore: provide session_id to flag validation service
15
+
3
16
  ## 0.2.12
4
17
 
5
18
  ### Patch Changes
@@ -0,0 +1,64 @@
1
+ # `/v1/replay` payload
2
+
3
+ Session replay batches are sent as `POST` requests to `/v1/replay`, optionally gzip-compressed via `?encoding=gzip` with `Content-Type: application/octet-stream`.
4
+
5
+ ## Request body (JSON)
6
+
7
+ ```json
8
+ {
9
+ "token": "site_xxx",
10
+ "sessionId": "uuid",
11
+ "windowId": "uuid",
12
+ "viewId": "uuid",
13
+ "sessionStart": 1730000000000,
14
+ "identifier": "optional-anon-id",
15
+ "batchId": "{sessionId}-{sequence}-{random}",
16
+ "sequence": 0,
17
+ "timestamp": 1730000000500,
18
+ "url": "https://example.com/path",
19
+ "isFinal": false,
20
+ "events": []
21
+ }
22
+ ```
23
+
24
+ ## Fields
25
+
26
+ | Field | Type | Description |
27
+ |-------|------|-------------|
28
+ | `token` | string | Site / project key |
29
+ | `sessionId` | string | User session (localStorage, shared across tabs; 30m idle / 24h max rotation) |
30
+ | `windowId` | string | Browser tab (sessionStorage per site key; stable across SDK remounts in same tab) |
31
+ | `viewId` | string | Page view within tab (new UUID on each SPA/full navigation) |
32
+ | `sessionStart` | number | Session start time in ms since epoch |
33
+ | `identifier` | string? | Anonymous user id (omitted in cookieless mode) |
34
+ | `batchId` | string | Idempotent key for retries (`{sessionId}-{sequence}-{uuid}`) |
35
+ | `sequence` | number | Monotonic batch index per recorder instance |
36
+ | `timestamp` | number | Batch creation time (ms since epoch) |
37
+ | `url` | string | Page URL when the batch was created |
38
+ | `isFinal` | boolean? | `true` on last batch before session rotation or tab unload |
39
+ | `events` | array | rrweb `eventWithTime` records |
40
+
41
+ ## Custom events in `events`
42
+
43
+ SPA navigations inject a custom rrweb event:
44
+
45
+ ```json
46
+ {
47
+ "type": 5,
48
+ "timestamp": 1730000001000,
49
+ "data": {
50
+ "tag": "faststats:view",
51
+ "payload": {
52
+ "href": "https://example.com/about",
53
+ "viewId": "uuid"
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## Ingestion guidelines
60
+
61
+ 1. Stitch batches by `(token, sessionId, windowId, sequence)`; dedupe retries with `batchId`.
62
+ 2. When `sessionId` changes and the prior batch had `isFinal: true`, finalize the previous recording segment.
63
+ 3. Group events within a tab by `viewId` (and `faststats:view` meta events for href changes).
64
+ 4. Legacy clients may send `pageId` instead of `viewId` and omit `sessionStart` / `isFinal` — treat as older SDK versions.
@@ -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}const i={events:`/v1/web`,identify:`/v1/identify`,replay:`/v1/replay`,vitals:`/v1/vitals`,flags:`/v1/check`};function a(e){return e===void 0||e===``?t:n(e)||t}export{r as n,a as r,i as t};
@@ -0,0 +1,2 @@
1
+ import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{n as t,t as n}from"./api-urls-DaeYkG0_.js";import{n as r,r as i}from"./session-manager-Cy63ptPF.js";import{n as a,t as o}from"./send-data-B2fYGj6v.js";var s=e({default:()=>m});const c=/(?:^|\()(chrome|moz|ms-browser|safari-web)-extension:\/\//,l=e=>e?.split(`
2
+ `).map(e=>e.trim()).filter(Boolean);function u(e){if(e instanceof Error)return{error:e.name?.trim()||`Error`,message:e.message,stack:l(e.stack),cause:u(e.cause)};if(typeof e==`string`)return{error:`Error`,message:e}}function d(e){let t=``;for(let n=e;n;n=n.cause)t+=`${n.error}\0${n.message??``}\0`;return t}function f(e){let t=2166136261,n=3598710387;for(let r=0;r<e.length;r++){let i=e.charCodeAt(r);t=Math.imul(t^i,16777619),n=Math.imul(n^i,2246822519)}return`err_${(t>>>0).toString(16).padStart(8,`0`)}${(n>>>0).toString(16).padStart(8,`0`)}`}function p(){let e=globalThis.__SOURCEMAPS_BUILD__?.buildId;return typeof e==`string`&&e.trim()?e:void 0}var m=class{endpoint;seen=new WeakSet;queue=new Map;timer=null;started=!1;flushing=!1;constructor(e){this.options=e,this.endpoint=`${t(e.baseUrl)}${n.events}`}log(...e){this.options.debug&&console.log(`[ErrorTracker]`,...e)}start(){this.started||typeof window>`u`||(this.started=!0,window.addEventListener(`error`,this.onError),window.addEventListener(`unhandledrejection`,this.onRejection),window.addEventListener(`pagehide`,this.scheduleFlush),document.addEventListener(`visibilitychange`,this.onVisibility),this.timer=setInterval(this.scheduleFlush,this.options.flushInterval??5e3),this.log(`Started`))}stop(){!this.started||typeof window>`u`||(this.started=!1,window.removeEventListener(`error`,this.onError),window.removeEventListener(`unhandledrejection`,this.onRejection),window.removeEventListener(`pagehide`,this.scheduleFlush),document.removeEventListener(`visibilitychange`,this.onVisibility),this.timer&&clearInterval(this.timer),this.timer=null,this.scheduleFlush(),this.log(`Stopped`))}captureError(e){this.capture(`error`,e.message,!0,e)}onError=e=>{this.capture(`error`,e.message||`Unknown error`,!1,e.error,e.filename||``,e.lineno)};onRejection=e=>{let t=e.reason,n=t instanceof Error?t.message:typeof t==`string`?t:`Unhandled promise rejection`;this.capture(`unhandledrejection`,n,!1,t)};onVisibility=()=>{document.visibilityState===`hidden`&&this.scheduleFlush()};scheduleFlush=()=>{this.flush()};capture(e,t,n,r,i=``,a=``){let o=r instanceof Error?r.stack??``:``;if(c.test(`${i}\n${o}`))return;if(typeof r==`object`&&r){if(this.seen.has(r)){this.log(`Skipping duplicate:`,t);return}this.seen.add(r)}let s={error:e===`unhandledrejection`?`UnhandledRejection`:`Error`,message:t,handled:n};if(r instanceof Error){s.stack=l(r.stack);let e=u(r.cause);e&&(s.cause=e)}let p=f(`${e}\0${n?`handled`:`unhandled`}\0${t}\0${i}\0${a}\0${d(s.cause)}`),m=this.queue.get(p);m?m.count++:this.queue.set(p,{hash:p,count:1,...s}),this.log(`Captured:`,s),this.queue.size>=(this.options.maxQueueSize??50)&&this.scheduleFlush()}async flush(){if(this.flushing||this.queue.size===0)return;let e=[...this.queue.values()];this.queue.clear(),this.flushing=!0;let t=a(),n=p(),s=JSON.stringify({token:this.options.siteKey,...t?{userId:t}:{},sessionId:r(),windowId:i(this.options.siteKey),...n?{buildId:n}:{},sdkName:this.options.sdkName??`@faststats/web`,sdkVersion:this.options.sdkVersion??`0.2.14`,data:{url:location.href,page:location.pathname,referrer:document.referrer||null,title:document.title},errors:e});this.log(`Flushing:`,e);try{await o({url:this.endpoint,data:s,debug:this.options.debug,debugPrefix:`[ErrorTracker]`})||this.requeue(e)}catch{this.requeue(e)}finally{this.flushing=!1}}requeue(e){for(let t of e){let e=this.queue.get(t.hash);e?e.count+=t.count:this.queue.set(t.hash,t)}}};export{s as n,m as t};
@@ -10,6 +10,7 @@ type FeatureFlagCheckContext = {
10
10
  projectId?: string;
11
11
  identifier?: string;
12
12
  externalId?: string;
13
+ sessionId?: string;
13
14
  attributes?: Record<string, unknown>;
14
15
  signal?: AbortSignal;
15
16
  };
@@ -0,0 +1 @@
1
+ import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{r as t,t as n}from"./api-urls-DaeYkG0_.js";var r=e({fetchFeatureFlagEvaluation:()=>i});async function i(e,r){if(r.projectToken&&r.projectId)throw Error(`provide either projectToken or projectId, not both`);let i=r.projectToken,a=r.projectId,o=r.identifier,s=r.externalId,c=r.sessionId;if(!i&&!a)throw Error(`feature flag check requires projectToken or projectId`);if(!o&&!s)throw Error(`feature flag check requires serverId or externalId`);let l={key:e,...a?{projectId:a}:{},...o?{identifier:o}:{},...s?{externalId:s}:{},...c?{sessionId:c}:{}};r.attributes&&Object.keys(r.attributes).length>0&&(l.attributes=r.attributes);let u={"Content-Type":`application/json`};i&&(u.Authorization=`Bearer ${i}`);let d=`${t(r.baseUrl)}${n.flags}`,f=await fetch(d,{method:`POST`,headers:u,body:JSON.stringify(l),credentials:`omit`,signal:r.signal});if(!f.ok){let e=``;try{let t=await f.json();typeof t.error==`string`&&t.error.length>0&&(e=` — ${t.error}`)}catch{}throw Error(`feature flag check failed: HTTP ${f.status}${e}`)}return await f.json()}export{i as n,r as t};
@@ -8,6 +8,7 @@ interface ReplayTrackerOptions {
8
8
  baseUrl?: string;
9
9
  debug?: boolean;
10
10
  compress?: boolean;
11
+ cookieless?: boolean;
11
12
  samplingPercentage?: number;
12
13
  flushInterval?: number;
13
14
  maxEvents?: number;
@@ -34,7 +35,8 @@ declare class ReplayTracker {
34
35
  private readonly events;
35
36
  private readonly pending;
36
37
  private pendingSizeBytes;
37
- private sessionId;
38
+ private viewId;
39
+ private readonly windowId;
38
40
  private started;
39
41
  private startTime;
40
42
  private sequence;
@@ -44,7 +46,9 @@ declare class ReplayTracker {
44
46
  private minLengthFlushTask;
45
47
  private stopRecording?;
46
48
  private sending;
49
+ private unsubscribeRotation?;
47
50
  constructor(options: ReplayTrackerOptions);
51
+ private get cookieless();
48
52
  private get debug();
49
53
  private get flushInterval();
50
54
  private get maxEvents();
@@ -53,10 +57,14 @@ declare class ReplayTracker {
53
57
  private get minReplayLengthMs();
54
58
  private get shouldCompress();
55
59
  private log;
60
+ private sessionContext;
56
61
  start(): void;
62
+ trackPageChange(url?: string): void;
57
63
  private beginRecording;
58
64
  stop(): void;
59
- getSessionId(): string | undefined;
65
+ getSessionId(): string;
66
+ getWindowId(): string;
67
+ private onSessionRotated;
60
68
  private onEvent;
61
69
  private onUnload;
62
70
  private onVisibilityChange;
@@ -0,0 +1 @@
1
+ import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{n as t,t as n}from"./api-urls-DaeYkG0_.js";import{a as r,f as i,i as a,s as o,t as s}from"./session-manager-Cy63ptPF.js";import{n as c,t as l}from"./send-data-B2fYGj6v.js";import{t as u}from"./types-CYzR5xtT.js";import{EventType as d}from"@rrweb/types";import{record as f}from"rrweb";var p=e({default:()=>v});const m={mousemove:50,mouseInteraction:!0,scroll:150,media:800,input:`last`};function h(e){return`faststats_replay_sampled_${e}`}function g(e){let t=0;for(let n=0;n<e.length;n++)t=(t<<5)-t+e.charCodeAt(n),t|=0;return Math.abs(t)}function _(e,t,n){let r=u(t);if(r>=100)return!0;if(r<=0)return!1;let i=a(e,n).sessionId,o=h(e),s=`${i}:${r}`;try{let e=sessionStorage;if(e){let t=e.getItem(o);if(t===s)return!0;if(t?.startsWith(`${i}:`))return!1}}catch{}let c=g(i)%100<r;try{sessionStorage?.setItem(o,c?s:`${i}:out`)}catch{}return c}var v=class{endpoint;compressionSupported=typeof window<`u`&&`CompressionStream`in window;sampled;events=[];pending=[];pendingSizeBytes=0;viewId=s();windowId=s();started=!1;startTime=0;sequence=0;intervalId=null;flushTask=null;retryTask=null;minLengthFlushTask=null;stopRecording;sending=!1;unsubscribeRotation;constructor(e){this.options=e,this.endpoint=`${t(e.baseUrl)}${n.replay}`,this.sampled=_(e.siteKey,e.samplingPercentage??100,e.cookieless??!1)}get cookieless(){return this.options.cookieless??!1}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)}sessionContext(){return a(this.options.siteKey,this.cookieless)}start(){this.started||typeof window>`u`||!this.sampled||(this.started=!0,this.viewId=s(),this.startTime=r(this.cookieless),this.unsubscribeRotation=o(e=>{this.onSessionRotated(e)}),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`))}trackPageChange(e){if(!this.started)return;let t=e??window.location.href;this.viewId=s();let n={type:5,timestamp:Date.now(),data:{tag:`faststats:view`,payload:{href:t,viewId:this.viewId}}};this.onEvent(n,!1)}async beginRecording(){let e=this.options.recordConsole??!0?await import(`@rrweb/rrweb-plugin-console-record`):null;if(!this.started)return;let t=[];e&&t.push(e.getRecordConsolePlugin()),this.stopRecording=f({emit:this.onEvent,sampling:this.options.sampling??m,slimDOMOptions:this.options.slimDOMOptions??{script:!0,comment:!0,headFavicon:!0,headWhitespace:!0,headMetaDescKeywords:!0,headMetaSocial:!0,headMetaRobots:!0,headMetaHttpEquiv:!0,headMetaAuthorship:!0},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:t})}stop(){if(this.started){if(this.started=!1,this.unsubscribeRotation?.(),this.unsubscribeRotation=void 0,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,void 0,!0),this.log(`Recording stopped`)}}getSessionId(){return this.sessionContext().sessionId}getWindowId(){return this.sessionContext().windowId}onSessionRotated(e){if(this.started){if(this.events.length>0)if(!this.hasReachedMinLength())this.scheduleMinLengthFlush();else{let t=this.createBatch(this.events.splice(0),e,!0);this.enqueueBatch(t)}this.pending.length>0&&queueMicrotask(()=>void this.flush(!1))}}onEvent=(e,t)=>{if(this.events.push(e),t||this.events.length>=this.maxEvents||e.type===d.FullSnapshot&&this.hasReachedMinLength()){this.requestFlush();return}this.scheduleMinLengthFlush()};onUnload=()=>{this.flush(!0,void 0,!0)};onVisibilityChange=()=>{document.visibilityState===`hidden`&&this.flush(!0,void 0,!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,t,n){let r=c(this.cookieless),i=this.sequence++;return{token:this.options.siteKey,sessionId:t.sessionId,windowId:this.windowId,viewId:this.viewId,sessionStart:t.sessionStart,...r?{identifier:r}:{},batchId:this.createBatchId(t.sessionId,i),sequence:i,timestamp:Date.now(),url:window.location.href,...n?{isFinal:!0}:{},events:e}}createBatchId(e,t){return`${e}-${t}-${typeof crypto<`u`&&`randomUUID`in crypto?crypto.randomUUID():`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}`}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,t,n){if(this.sending)return;let r=t?null:i(this.cookieless);if(r&&this.events.length>0)if(!this.hasReachedMinLength())this.scheduleMinLengthFlush();else{let e=this.createBatch(this.events.splice(0),r.prev,!0);this.enqueueBatch(e)}let a=t??this.sessionContext();if(this.events.length>0)if(!this.hasReachedMinLength())this.scheduleMinLengthFlush();else{let e=this.createBatch(this.events.splice(0),a,n);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 l({url:t?`${this.endpoint}?encoding=gzip`:this.endpoint,data:e,contentType:t?`application/octet-stream`:`application/json`,debug:!1,useBeacon:n,keepalive:n})??Promise.resolve(!1)}};export{p as n,v as t};
@@ -0,0 +1 @@
1
+ import{o as e,t}from"./session-manager-Cy63ptPF.js";const n=`faststats_anon_id`;function r(){try{return globalThis.localStorage}catch{return}}function i(t){return t??e()?``:a()}function a(){let e=r();if(!e)return``;try{let t=e.getItem(n);if(t)return t}catch{return``}let i=t();try{e.setItem(n,i)}catch{return``}return i}function o(e){if(e)return``;try{let e=r();if(!e)return``;e.removeItem(n)}catch{return``}return a()}async function s(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{i as n,o as r,s as t};
@@ -0,0 +1 @@
1
+ const e=`faststats_session_id`,t=`faststats_session_activity`,n=`faststats_session_start`,r=`session_id`,i=`session_timestamp`,a=`session_start`;let o=!1,s=``,c=null;const l=new Set;function u(e,t){try{return e?.getItem(t)??null}catch{return null}}function d(e,t,n){if(!e)return!1;try{return e.setItem(t,n),!0}catch{return!1}}function f(e,t){try{e?.removeItem(t)}catch{}}function p(){try{return globalThis.localStorage}catch{return}}function m(){try{return globalThis.sessionStorage}catch{return}}function h(e,t){for(let n of l)n(e,t)}function g(){return typeof crypto<`u`&&`randomUUID`in crypto?crypto.randomUUID():`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}function _(e){o=e,e&&(c=null)}function v(){return o}function y(e){s=e}function b(e){return`faststats_window_id_${e}`}function x(e){if(!e)return null;let t=Number.parseInt(e,10);return Number.isFinite(t)?t:null}function S(){let o=m(),s=p(),c=u(o,r);if(!c||u(s,e)){c&&(f(o,r),f(o,i),f(o,a));return}let l=x(u(o,i)),h=x(u(o,a)),g=Date.now(),_=d(s,e,c);d(s,t,String(l??g)),d(s,n,String(h??l??g)),_&&(f(o,r),f(o,i),f(o,a))}function C(){S();let r=p(),i=u(r,e),a=x(u(r,t)),o=x(u(r,n));return!i||a===null?null:{sessionId:i,activity:a,sessionStart:o??a}}function w(r,i,a){let o=p(),s=d(o,e,r);d(o,t,i.toString()),d(o,n,a.toString()),s||(c={sessionId:r,activity:i,sessionStart:a})}function T(){let r=p();f(r,e),f(r,t),f(r,n)}function E(e,t,n){return n-e>=18e5?!0:n-t>=864e5}function D(e){return c||={sessionId:g(),sessionStart:e,activity:e},c}function O(e,t){let n=Date.now();if(e){let e=D(n);return t&&(e.activity=n),{sessionId:e.sessionId,sessionStart:e.sessionStart,rotated:!1,previous:null}}let r=C()??c;if(r&&!E(r.activity,r.sessionStart,n))return t&&w(r.sessionId,n,r.sessionStart),{sessionId:r.sessionId,sessionStart:r.sessionStart,rotated:!1,previous:null};let i=r===null?null:{sessionId:r.sessionId,sessionStart:r.sessionStart},a=g();return w(a,n,n),{sessionId:a,sessionStart:n,rotated:i!==null,previous:i}}function k(e,t){if(t??o)return O(!0,!1).sessionId;let n=b(e),r=m();if(!r)return O(!1,!1).sessionId;let i=u(r,n);if(i)return i;let a=g();return d(r,n,a),a}function A(e){f(m(),b(e))}function j(e,t){let n=t??o,r=O(n,!1),i=k(e,n),a={sessionId:r.sessionId,windowId:i,sessionStart:r.sessionStart};return r.rotated&&r.previous&&h({sessionId:r.previous.sessionId,windowId:i,sessionStart:r.previous.sessionStart},a),a}function M(e){return O(e??o,!0).sessionId}function N(e){return O(e??o,!1).sessionStart}function P(e){let t=e??o,n=O(t,!0);if(n.rotated&&n.previous){let e=s||`_default`,r={prev:{sessionId:n.previous.sessionId,windowId:k(e,t),sessionStart:n.previous.sessionStart},next:{sessionId:n.sessionId,windowId:k(e,t),sessionStart:n.sessionStart}};return h(r.prev,r.next),r}return null}function F(e){let t=e??o,n=Date.now();if(t){c&&(c.activity=n);return}let r=C()??c;r&&w(r.sessionId,n,r.sessionStart)}function I(e){let t=e??s??`_default`,n=Date.now(),r=null;if(o)c&&(r={sessionId:c.sessionId,windowId:c.sessionId,sessionStart:c.sessionStart}),c={sessionId:g(),sessionStart:n,activity:n};else{let e=C();e&&(r={sessionId:e.sessionId,windowId:k(t,!1),sessionStart:e.sessionStart}),T(),c=null}A(t);let i=j(t,o);return r&&r.sessionId!==i.sessionId&&h(r,i),i}function L(e){return l.add(e),()=>{l.delete(e)}}export{N as a,F as c,y as d,P as f,j as i,I as l,M as n,v as o,k as r,L as s,g as t,_ as u};
@@ -0,0 +1 @@
1
+ import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{n as t,t as n}from"./api-urls-DaeYkG0_.js";import{n as r}from"./session-manager-Cy63ptPF.js";import{t as i}from"./types-CYzR5xtT.js";var a=e({default:()=>o}),o=class{endpoint;sampled;metrics=new Map;started=!1;url=``;constructor(e){this.options=e,this.endpoint=`${t(e.baseUrl)}${n.vitals}`,this.sampled=Math.random()*100<i(e.samplingPercentage)}get debug(){return this.options.debug??!1}log(...e){this.debug&&console.log(`[WebVitals]`,...e)}async start(){if(this.started||typeof window>`u`)return;this.started=!0,this.url=window.location.href,document.addEventListener(`visibilitychange`,this.onHidden,{passive:!0}),window.addEventListener(`pagehide`,this.onPageHide,{passive:!0});let e=this.options.attribution?await import(`web-vitals/attribution`):await import(`web-vitals`);this.started&&(e.onCLS(this.capture,{reportAllChanges:!0}),e.onINP(this.capture,{reportAllChanges:!0}),e.onLCP(this.capture,{reportAllChanges:!0}),e.onFCP(this.capture),e.onTTFB(this.capture),this.log(`Tracking started`))}stop(){!this.started||typeof window>`u`||(this.started=!1,this.removeListeners(),this.flush())}trackPageChange(e){if(!this.started||typeof window>`u`)return;let t=e??window.location.href;t!==this.url&&(this.flush(),this.url=t)}onHidden=()=>{document.visibilityState===`hidden`&&this.flush()};onPageHide=()=>{this.flush()};capture=e=>{if(!this.started||!this.sampled)return;let t=e.name,n=e.attribution;this.metrics.set(t,{metric:t,value:e.value,attributes:{id:e.id,rating:e.rating,delta:e.delta,navigationType:e.navigationType,...n??{}}})};async flush(){if(typeof window>`u`||typeof fetch!=`function`||!this.metrics.size)return;let e=Array.from(this.metrics.values()),t=this.url;this.metrics.clear();try{let n=new AbortController,i=window.setTimeout(()=>n.abort(),3e3),a=await fetch(this.endpoint,{method:`POST`,body:JSON.stringify({sessionId:r(),vitals:e,metadata:{url:t}}),headers:{"Content-Type":`application/json`,Authorization:`Bearer ${this.options.siteKey}`},keepalive:!0,signal:n.signal});if(clearTimeout(i),!a.ok)throw Error(`HTTP ${a.status}`)}catch(e){this.log(`Failed to send metrics`,e)}}removeListeners(){document.removeEventListener(`visibilitychange`,this.onHidden),window.removeEventListener(`pagehide`,this.onPageHide)}};export{a as n,o as t};
package/dist/error.d.ts CHANGED
@@ -22,26 +22,23 @@ interface ErrorTrackingOptions {
22
22
  declare class ErrorTracker {
23
23
  private readonly options;
24
24
  private readonly endpoint;
25
- private readonly handled;
25
+ private readonly seen;
26
26
  private readonly queue;
27
27
  private timer;
28
28
  private started;
29
29
  private flushing;
30
30
  constructor(options: ErrorTrackingOptions);
31
- private get debug();
32
- private get flushInterval();
33
- private get maxQueueSize();
34
31
  private log;
35
32
  start(): void;
36
33
  stop(): void;
37
34
  captureError(error: Error): void;
38
35
  private onError;
39
36
  private onRejection;
40
- private onVisibilityChange;
41
- private requestFlush;
42
- private restoreErrors;
43
- private record;
37
+ private onVisibility;
38
+ private scheduleFlush;
39
+ private capture;
44
40
  private flush;
41
+ private requeue;
45
42
  }
46
43
  //#endregion
47
44
  export { type ErrorEntry, type ErrorTracking, type ErrorTrackingOptions, ErrorTracker as default };
package/dist/error.js CHANGED
@@ -1 +1 @@
1
- import{t as e}from"./chunks/error-CttYL43D.js";export{e as default};
1
+ import{t as e}from"./chunks/error-Cd9PTS5v.js";export{e as default};
@@ -1,2 +1,2 @@
1
- import { n as FeatureFlagEvaluation, r as fetchFeatureFlagEvaluation, t as FeatureFlagCheckContext } from "./chunks/feature-flags-BxZz_lNm.js";
1
+ import { n as FeatureFlagEvaluation, r as fetchFeatureFlagEvaluation, t as FeatureFlagCheckContext } from "./chunks/feature-flags-BClx56v5.js";
2
2
  export { type FeatureFlagCheckContext, type FeatureFlagEvaluation, fetchFeatureFlagEvaluation };
@@ -1 +1 @@
1
- import{n as e}from"./chunks/feature-flags-CjnLZGxp.js";export{e as fetchFeatureFlagEvaluation};
1
+ import{n as e}from"./chunks/feature-flags-DSOCIZHK.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-BxZz_lNm.js";
2
- import { n as ReplayTrackerOptions } from "./chunks/replay-BuX3_0xs.js";
1
+ import { n as FeatureFlagEvaluation, r as fetchFeatureFlagEvaluation, t as FeatureFlagCheckContext } from "./chunks/feature-flags-BClx56v5.js";
2
+ import { n as ReplayTrackerOptions } from "./chunks/replay-DvJYurEC.js";
3
3
 
4
4
  //#region src/utils/types.d.ts
5
5
  interface SendDataOptions {
@@ -125,6 +125,7 @@ declare class WebAnalytics {
125
125
  getConsentMode(): ConsentMode;
126
126
  getAnonymousId(): string;
127
127
  getSessionId(): string;
128
+ getWindowId(): string;
128
129
  checkFeatureFlag(key: string, attributes?: Record<string, unknown>, opts?: {
129
130
  externalId?: string;
130
131
  signal?: AbortSignal;
package/dist/index.js CHANGED
@@ -1 +1 @@
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};
1
+ import{n as e,r as t,t as n}from"./chunks/api-urls-DaeYkG0_.js";import{a as r,c as i,d as a,i as o,l as s,n as c,u as l}from"./chunks/session-manager-Cy63ptPF.js";import{n as u,r as d,t as f}from"./chunks/send-data-B2fYGj6v.js";import{t as p}from"./chunks/types-CYzR5xtT.js";import{n as m}from"./chunks/feature-flags-DSOCIZHK.js";function h(e){return e.replayOptions?.samplingPercentage!==void 0||e.sessionReplays?.sampling?.percentage!==void 0}function g(e,t,n){if(!(e.sessionReplays?.enabled??e.trackReplay??h(e)))return null;let r=e.replayOptions??{};return{siteKey:e.siteKey,baseUrl:t,debug:n,...r,samplingPercentage:p(r.samplingPercentage??e.sessionReplays?.sampling?.percentage)}}const _={instance:null,pendingConsentMode:void 0};function v(){return _.instance}function y(e,t){typeof window>`u`||E()||_.instance?.track(e,t??{})}function b(e,t,n){return typeof window>`u`||E()?Promise.resolve(!1):_.instance?.identify(e,t,n??{})??Promise.resolve(!1)}function x(e=!0){typeof window>`u`||E()||_.instance?.logout(e)}function S(e){if(_.instance){_.instance.setConsentMode(e);return}_.pendingConsentMode=e}function C(){S(`granted`)}function w(){S(`denied`)}function T(e){typeof window>`u`||E()||_.instance?.reportError(e)}function E(){if(typeof localStorage>`u`)return!1;let e=localStorage.getItem(`disable-faststats`);return e===`true`||e===`1`}function D(e){for(;e;){if(e instanceof HTMLAnchorElement&&e.href)return e;e=e.parentNode}return null}function O(){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 k=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`?(i(),this.leavePage(),this.stopHeartbeat()):(i(),this.enterPage(),this.startHeartbeat())};handlePageHide=()=>{this.leavePage()};handlePopState=()=>{this.navigate()};handleHashChange=()=>{this.navigate()};constructor(r){this.options=r,this.baseUrl=e(r.baseUrl),this.featureFlagsBaseUrl=t(r.featureFlagsBaseUrl),this.webEndpoint=`${this.baseUrl}${n.events}`,this.debug=r.debug??!1,this.consentMode=r.consent?.mode??`granted`,this.cookielessWhilePending=r.consent?.cookielessWhilePending??!0,_.pendingConsentMode!==void 0&&(this.consentMode=_.pendingConsentMode,_.pendingConsentMode=void 0),(r.autoTrack??!0)&&this.init()}log(e){this.debug&&console.log(`[Analytics] ${e}`)}init(){if(!(typeof window>`u`)){if(E()){this.log(`disabled`);return}_.instance=this,a(this.options.siteKey),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||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||_.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||_.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-Cd9PTS5v.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-Be-Cg4Po.js`).then(e=>e.n);return new e({siteKey:this.options.siteKey,baseUrl:this.baseUrl,debug:this.debug,samplingPercentage:p(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-rTqcjOo2.js`).then(e=>e.n);return new t(e)})}async start(){if(this.started||this.destroyed||typeof window>`u`)return;if(_.instance&&_.instance!==this){this.log(`already started by another instance`);return}if(E()){this.log(`disabled`);return}this.started=!0,_.instance=this,a(this.options.siteKey),l(this.isCookielessMode()),c();let e=this.options,t=g(e,this.baseUrl,this.debug);t&&this.startReplayTracker({...t,cookieless:this.isCookielessMode()}),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(),_.instance===this&&(_.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,t,r={}){if(!this.ensureStarted()||this.isCookielessMode())return Promise.resolve(!1);let i=e.trim(),a=t.trim();return!i||!a?Promise.resolve(!1):f({url:`${this.baseUrl}${n.identify}`,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&&d(this.isCookielessMode()),s(this.options.siteKey))}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 c()}getWindowId(){return o(this.options.siteKey,this.isCookielessMode()).windowId}async checkFeatureFlag(e,t,n){if(typeof window>`u`||E())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-DSOCIZHK.js`).then(e=>e.t);return a(e,{baseUrl:this.featureFlagsBaseUrl,projectToken:this.options.siteKey,...i?{identifier:i}:{},...r?{externalId:r}:{},sessionId:this.getSessionId(),attributes:t,signal:n?.signal})}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=u(this.isCookielessMode()),r=JSON.stringify({token:this.options.siteKey,...n?{userId:n}:{},sessionId:c(),data:{event:e,page:location.pathname,referrer:document.referrer||null,title:document.title||``,url:location.href,...O(),...t}});this.log(e),f({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-r()})}trackScroll(){this.scrollHandler&&window.removeEventListener(`scroll`,this.scrollHandler);let e=()=>{let e=document.documentElement,t=document.body,n=window.innerHeight,r=Math.max(e.scrollHeight,t.scrollHeight);if(r<=n){this.scrollDepth=100;return}let i=Math.min(100,Math.round(((window.scrollY||e.scrollTop)+n)/r*100));i>this.scrollDepth&&(this.scrollDepth=i)};this.scrollHandler=e,e(),window.addEventListener(`scroll`,e,{passive:!0})}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{if(document.visibilityState===`hidden`){this.stopHeartbeat();return}i()},300*1e3)}navigate(){!this.started||this.destroyed||(this.stopNavigationTimer(),this.navTimer=setTimeout(()=>{this.navTimer=null;let e=location.pathname!==this.pagePath,t=(this.options.trackHash??!1)&&location.hash!==this.pageHash;if(!(!e&&!t)){for(let e of this.childTrackers)e.trackPageChange?.(location.href);this.leavePage(),this.enterPage(),this.trackScroll(),this.pageview({trigger:`navigation`})}},300))}links(){let e=e=>{let t=D(e.target);t&&t.host!==location.host&&this.track(`outbound_link`,{outbound_link:t.href})};this.addDocumentListener(`click`,e),this.addDocumentListener(`auxclick`,e)}};export{k as WebAnalytics,m as fetchFeatureFlagEvaluation,v as getInstance,b as identify,E as isTrackingDisabled,x as logout,C as optIn,w as optOut,T as reportError,f as sendData,S as setConsentMode,y as trackEvent};
package/dist/replay.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as ReplayTrackerOptions, t as ReplayTracker } from "./chunks/replay-BuX3_0xs.js";
1
+ import { n as ReplayTrackerOptions, t as ReplayTracker } from "./chunks/replay-DvJYurEC.js";
2
2
  export { type ReplayTrackerOptions, ReplayTracker as default };
package/dist/replay.js CHANGED
@@ -1 +1 @@
1
- import{t as e}from"./chunks/replay-BrMLCiBF.js";export{e as default};
1
+ import{t as e}from"./chunks/replay-rTqcjOo2.js";export{e as default};
@@ -9,29 +9,21 @@ interface WebVitalsOptions {
9
9
  declare class WebVitalsTracker {
10
10
  private readonly options;
11
11
  private readonly endpoint;
12
- private readonly metricsByUrl;
13
- private readonly pendingFlushUrls;
14
12
  private readonly sampled;
13
+ private readonly metrics;
15
14
  private started;
16
- private flushing;
17
- private finalFlushRequested;
18
- private currentUrl;
15
+ private url;
19
16
  constructor(options: WebVitalsOptions);
20
17
  private get debug();
21
18
  private log;
22
- start(): void;
19
+ start(): Promise<void>;
23
20
  stop(): void;
24
21
  trackPageChange(url?: string): void;
25
- private cleanupListeners;
26
- private observe;
27
- private onVisibilityChange;
22
+ private onHidden;
28
23
  private onPageHide;
29
- private captureMetric;
30
- private metricsForUrl;
31
- private queueFlush;
32
- private flushPending;
33
- private sendMetricsForUrl;
34
- private finishFinalFlushIfNeeded;
24
+ private capture;
25
+ private flush;
26
+ private removeListeners;
35
27
  }
36
28
  //#endregion
37
29
  export { type WebVitalsOptions, WebVitalsTracker as default };
@@ -1 +1 @@
1
- import{t as e}from"./chunks/web-vitals-CjA1bFLG.js";export{e as default};
1
+ import{t as e}from"./chunks/web-vitals-Be-Cg4Po.js";export{e as default};
package/package.json CHANGED
@@ -39,28 +39,25 @@
39
39
  "publishConfig": {
40
40
  "access": "public"
41
41
  },
42
- "version": "0.2.12",
42
+ "version": "0.2.14",
43
43
  "scripts": {
44
44
  "build": "tsdown && bun run check-size",
45
45
  "dev": "bun run build",
46
46
  "typecheck": "tsc --noEmit -p tsconfig.json",
47
47
  "test": "bun test",
48
- "check-size": "node scripts/check-bundle-size.mjs",
49
- "deploy:worker": "wrangler deploy"
48
+ "check-size": "node scripts/check-bundle-size.mjs"
50
49
  },
51
50
  "devDependencies": {
52
51
  "@biomejs/biome": "2.4.2",
53
52
  "@types/bun": "latest",
54
53
  "tsdown": "^0.21.4",
55
- "typescript": "^5.9.3",
56
- "wrangler": "^4.66.0"
54
+ "typescript": "^5.9.3"
57
55
  },
58
56
  "dependencies": {
59
57
  "@rrweb/rrweb-plugin-console-record": "^2.0.0-alpha.20",
60
- "@rrweb/rrweb-plugin-sequential-id-record": "^2.0.0-alpha.20",
61
58
  "@rrweb/types": "^2.0.0-alpha.20",
62
59
  "rrweb": "^2.0.0-alpha.4",
63
60
  "rrweb-snapshot": "^2.0.0-alpha.4",
64
- "web-vitals": "^5.1.0"
61
+ "web-vitals": "^5.2.0"
65
62
  }
66
63
  }
package/src/analytics.ts CHANGED
@@ -2,21 +2,21 @@ import type ErrorTracker from "./error";
2
2
  import type { FeatureFlagEvaluation } from "./feature-flags";
3
3
  import type { ReplayTrackerOptions } from "./replay";
4
4
  import {
5
- identifyEventsUrl,
6
5
  normalizeAnalyticsBaseUrl,
7
6
  normalizeFeatureFlagsBaseUrl,
8
- webEventsUrl,
7
+ URLS,
9
8
  } from "./utils/api-urls";
9
+ import { getAnonymousId, resetAnonymousId } from "./utils/identifiers";
10
+ import { sendData } from "./utils/send-data";
10
11
  import {
11
- getAnonymousId,
12
12
  getOrCreateSessionId,
13
+ getSessionContext,
13
14
  getSessionStart,
14
15
  refreshSessionTimestamp,
15
- resetAnonymousId,
16
- resetSessionId,
16
+ resetSession,
17
17
  setCookielessMode,
18
- } from "./utils/identifiers";
19
- import { sendData } from "./utils/send-data";
18
+ setDefaultSiteKey,
19
+ } from "./utils/session-manager";
20
20
  import {
21
21
  normalizeSamplingPercentage,
22
22
  type SendDataOptions,
@@ -256,7 +256,7 @@ export class WebAnalytics {
256
256
  this.featureFlagsBaseUrl = normalizeFeatureFlagsBaseUrl(
257
257
  options.featureFlagsBaseUrl,
258
258
  );
259
- this.webEndpoint = webEventsUrl(this.baseUrl);
259
+ this.webEndpoint = `${this.baseUrl}${URLS.events}`;
260
260
  this.debug = options.debug ?? false;
261
261
  this.consentMode = options.consent?.mode ?? "granted";
262
262
  this.cookielessWhilePending =
@@ -279,6 +279,7 @@ export class WebAnalytics {
279
279
  return;
280
280
  }
281
281
  moduleState.instance = this;
282
+ setDefaultSiteKey(this.options.siteKey);
282
283
  setCookielessMode(this.isCookielessMode());
283
284
  setTimeout(() => void this.start(), 0);
284
285
  }
@@ -452,6 +453,7 @@ export class WebAnalytics {
452
453
 
453
454
  this.started = true;
454
455
  moduleState.instance = this;
456
+ setDefaultSiteKey(this.options.siteKey);
455
457
  setCookielessMode(this.isCookielessMode());
456
458
  getOrCreateSessionId();
457
459
 
@@ -462,7 +464,10 @@ export class WebAnalytics {
462
464
  this.debug,
463
465
  );
464
466
  if (replayOptions) {
465
- void this.startReplayTracker(replayOptions);
467
+ void this.startReplayTracker({
468
+ ...replayOptions,
469
+ cookieless: this.isCookielessMode(),
470
+ });
466
471
  }
467
472
  if (opts.trackErrors) {
468
473
  void this.startErrorTracker();
@@ -540,7 +545,7 @@ export class WebAnalytics {
540
545
  const trimmedEmail = email.trim();
541
546
  if (!trimmedExternalId || !trimmedEmail) return Promise.resolve(false);
542
547
 
543
- const identifyEndpoint = identifyEventsUrl(this.baseUrl);
548
+ const identifyEndpoint = `${this.baseUrl}${URLS.identify}`;
544
549
  const payload = JSON.stringify({
545
550
  token: this.options.siteKey,
546
551
  identifier: getAnonymousId(false),
@@ -567,7 +572,7 @@ export class WebAnalytics {
567
572
  if (resetAnonymousIdentity) {
568
573
  resetAnonymousId(this.isCookielessMode());
569
574
  }
570
- resetSessionId();
575
+ resetSession(this.options.siteKey);
571
576
  }
572
577
 
573
578
  setConsentMode(mode: ConsentMode): void {
@@ -595,6 +600,11 @@ export class WebAnalytics {
595
600
  return getOrCreateSessionId();
596
601
  }
597
602
 
603
+ getWindowId(): string {
604
+ return getSessionContext(this.options.siteKey, this.isCookielessMode())
605
+ .windowId;
606
+ }
607
+
598
608
  async checkFeatureFlag(
599
609
  key: string,
600
610
  attributes?: Record<string, unknown>,
@@ -615,6 +625,7 @@ export class WebAnalytics {
615
625
  projectToken: this.options.siteKey,
616
626
  ...(identifier ? { identifier } : {}),
617
627
  ...(externalId ? { externalId } : {}),
628
+ sessionId: this.getSessionId(),
618
629
  attributes,
619
630
  signal: opts?.signal,
620
631
  });
@@ -753,11 +764,11 @@ export class WebAnalytics {
753
764
  const hashChanged =
754
765
  (this.options.trackHash ?? false) && location.hash !== this.pageHash;
755
766
  if (!pathChanged && !hashChanged) return;
756
- this.leavePage();
757
- this.enterPage();
758
767
  for (const tracker of this.childTrackers) {
759
- tracker.trackPageChange?.(this.pageUrl);
768
+ tracker.trackPageChange?.(location.href);
760
769
  }
770
+ this.leavePage();
771
+ this.enterPage();
761
772
  this.trackScroll();
762
773
  this.pageview({ trigger: "navigation" });
763
774
  }, 300);