@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
@@ -0,0 +1,161 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+ import {
3
+ createId,
4
+ getOrCreateSessionId,
5
+ getSessionContext,
6
+ getSessionStart,
7
+ onSessionRotated,
8
+ resetSession,
9
+ setCookielessMode,
10
+ setDefaultSiteKey,
11
+ touchActivity,
12
+ } from "../src/utils/session-manager";
13
+
14
+ class MockStorage {
15
+ private readonly data = new Map<string, string>();
16
+
17
+ getItem(key: string): string | null {
18
+ return this.data.get(key) ?? null;
19
+ }
20
+
21
+ setItem(key: string, value: string): void {
22
+ this.data.set(key, value);
23
+ }
24
+
25
+ removeItem(key: string): void {
26
+ this.data.delete(key);
27
+ }
28
+ }
29
+
30
+ function setGlobal(name: keyof typeof globalThis, value: unknown): void {
31
+ Object.defineProperty(globalThis, name, {
32
+ configurable: true,
33
+ writable: true,
34
+ value,
35
+ });
36
+ }
37
+
38
+ const original = {
39
+ localStorage: globalThis.localStorage,
40
+ sessionStorage: globalThis.sessionStorage,
41
+ };
42
+
43
+ beforeEach(() => {
44
+ setGlobal("localStorage", new MockStorage());
45
+ setGlobal("sessionStorage", new MockStorage());
46
+ setCookielessMode(true);
47
+ setCookielessMode(false);
48
+ setDefaultSiteKey("site_test");
49
+ });
50
+
51
+ afterEach(() => {
52
+ setGlobal("localStorage", original.localStorage);
53
+ setGlobal("sessionStorage", original.sessionStorage);
54
+ setCookielessMode(false);
55
+ });
56
+
57
+ describe("session-manager", () => {
58
+ test("rotates session after idle timeout", () => {
59
+ const local = globalThis.localStorage as unknown as MockStorage;
60
+ const idleMs = 30 * 60 * 1000 + 1;
61
+ local.setItem("faststats_session_id", "old-session");
62
+ local.setItem(
63
+ "faststats_session_activity",
64
+ (Date.now() - idleMs).toString(),
65
+ );
66
+ local.setItem("faststats_session_start", (Date.now() - idleMs).toString());
67
+
68
+ const rotations: Array<{ prev: string; next: string }> = [];
69
+ const unsubscribe = onSessionRotated((prev, next) => {
70
+ rotations.push({ prev: prev.sessionId, next: next.sessionId });
71
+ });
72
+
73
+ const next = getSessionContext("site_test");
74
+ unsubscribe();
75
+ expect(next.sessionId).not.toBe("old-session");
76
+ expect(rotations).toHaveLength(1);
77
+ expect(rotations[0]?.prev).toBe("old-session");
78
+ expect(rotations[0]?.next).toBe(next.sessionId);
79
+ });
80
+
81
+ test("resetSession notifies rotation listeners", () => {
82
+ const first = getSessionContext("site_test");
83
+ const rotations: Array<{ prev: string; next: string }> = [];
84
+ const unsubscribe = onSessionRotated((prev, next) => {
85
+ rotations.push({ prev: prev.sessionId, next: next.sessionId });
86
+ });
87
+
88
+ const second = resetSession("site_test");
89
+ unsubscribe();
90
+ expect(second.sessionId).not.toBe(first.sessionId);
91
+ expect(rotations).toHaveLength(1);
92
+ });
93
+
94
+ test("touchActivity extends session without rotating", () => {
95
+ const first = getSessionContext("site_test");
96
+ touchActivity();
97
+ const second = getSessionContext("site_test");
98
+ expect(second.sessionId).toBe(first.sessionId);
99
+ });
100
+
101
+ test("session id is recreated when activity timestamp is invalid", () => {
102
+ const local = globalThis.localStorage as unknown as MockStorage;
103
+ local.setItem("faststats_session_id", "old-session");
104
+ local.setItem("faststats_session_activity", "NaN");
105
+ const id = getOrCreateSessionId();
106
+ expect(id).not.toBe("old-session");
107
+ });
108
+
109
+ test("session start falls back when stored value is invalid", () => {
110
+ const local = globalThis.localStorage as unknown as MockStorage;
111
+ const now = Date.now();
112
+ local.setItem("faststats_session_id", "session-1");
113
+ local.setItem("faststats_session_activity", now.toString());
114
+ local.setItem("faststats_session_start", "NaN");
115
+ expect(getSessionStart()).toBe(now);
116
+ });
117
+
118
+ test("session id is shared across tabs via localStorage", () => {
119
+ const first = getOrCreateSessionId();
120
+ setGlobal("sessionStorage", new MockStorage());
121
+ const second = getOrCreateSessionId();
122
+ expect(second).toBe(first);
123
+ });
124
+
125
+ test("migrates legacy sessionStorage keys into localStorage", () => {
126
+ const session = globalThis.sessionStorage as unknown as MockStorage;
127
+ session.setItem("session_id", "legacy-session");
128
+ session.setItem("session_timestamp", Date.now().toString());
129
+ const id = getOrCreateSessionId();
130
+ expect(id).toBe("legacy-session");
131
+ const local = globalThis.localStorage as unknown as MockStorage;
132
+ expect(local.getItem("faststats_session_id")).toBe("legacy-session");
133
+ expect(session.getItem("session_id")).toBeNull();
134
+ });
135
+
136
+ test("createId generates unique values", () => {
137
+ expect(createId()).not.toBe(createId());
138
+ });
139
+
140
+ test("falls back to ephemeral session when storage access fails", () => {
141
+ Object.defineProperty(globalThis, "localStorage", {
142
+ configurable: true,
143
+ get() {
144
+ throw new Error("storage blocked");
145
+ },
146
+ });
147
+ Object.defineProperty(globalThis, "sessionStorage", {
148
+ configurable: true,
149
+ get() {
150
+ throw new Error("storage blocked");
151
+ },
152
+ });
153
+
154
+ const first = getSessionContext("site_test");
155
+ const second = getSessionContext("site_test");
156
+
157
+ expect(first.sessionId).toBeTruthy();
158
+ expect(second.sessionId).toBe(first.sessionId);
159
+ expect(second.windowId).toBe(first.sessionId);
160
+ });
161
+ });
@@ -1 +0,0 @@
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`}export{o as a,c as i,a as n,i as o,r,s,l as t};
@@ -1,2 +0,0 @@
1
- import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{o as t}from"./api-urls-BrkcoElX.js";import{n,t as r}from"./identifiers-CQeWm7wi.js";import{t as i}from"./send-data-DL_GlsQw.js";var a=e({default:()=>d});const o=/(?:^|\()(chrome|moz|ms-browser|safari-web)-extension:\/\//,s=e=>e?.split(`
2
- `).map(e=>e.trim()).filter(Boolean);function c(e){if(e instanceof Error)return{error:e.name?.trim()||`Error`,message:e.message,stack:s(e.stack),cause:c(e.cause)};if(typeof e==`string`)return{error:`Error`,message:e}}function l(e){return e?`${e.error}\0${e.message??``}\0${l(e.cause)}`:``}function u(e){let t=[e.kind,e.handled?`handled`:`unhandled`,e.message,e.filename??``,e.lineno??``,l(e.cause)].join(`\0`),n=2166136261,r=3598710387;for(let e=0;e<t.length;e++){let i=t.charCodeAt(e);n=Math.imul(n^i,16777619),r=Math.imul(r^i,2246822519)}return`err_${(n>>>0).toString(16).padStart(8,`0`)}${(r>>>0).toString(16).padStart(8,`0`)}`}var d=class{endpoint;handled=new WeakSet;queue=new Map;timer=null;started=!1;flushing=!1;constructor(e){this.options=e,this.endpoint=t(e.baseUrl)}get debug(){return this.options.debug??!1}get flushInterval(){return this.options.flushInterval??5e3}get maxQueueSize(){return this.options.maxQueueSize??50}log(...e){this.debug&&console.log(`[ErrorTracker]`,...e)}start(){this.started||typeof window>`u`||(this.started=!0,window.addEventListener(`error`,this.onError),window.addEventListener(`unhandledrejection`,this.onRejection),document.addEventListener(`visibilitychange`,this.onVisibilityChange),window.addEventListener(`pagehide`,this.requestFlush),this.timer=setInterval(this.requestFlush,this.flushInterval),this.log(`Started listening for errors`))}stop(){!this.started||typeof window>`u`||(this.started=!1,window.removeEventListener(`error`,this.onError),window.removeEventListener(`unhandledrejection`,this.onRejection),document.removeEventListener(`visibilitychange`,this.onVisibilityChange),window.removeEventListener(`pagehide`,this.requestFlush),this.timer&&clearInterval(this.timer),this.timer=null,this.requestFlush(),this.log(`Stopped listening for errors`))}captureError(e){this.record({kind:`error`,message:e.message,stack:e.stack,handled:!0,cause:c(e.cause)},e)}onError=e=>{let t=e.error;this.record({kind:`error`,message:e.message||`Unknown error`,filename:e.filename||void 0,lineno:e.lineno||void 0,stack:t instanceof Error?t.stack:void 0,handled:!1,cause:t instanceof Error?c(t.cause):void 0},t)};onRejection=e=>{let t=e.reason;this.record({kind:`unhandledrejection`,message:t.message||`Unhandled promise rejection`,stack:t.stack||void 0,handled:!1,cause:c(t.cause)},t)};onVisibilityChange=()=>{document.visibilityState===`hidden`&&this.requestFlush()};requestFlush=()=>{this.flush()};restoreErrors(e){for(let t of e)this.queue.set(t.hash,{...t,count:t.count+(this.queue.get(t.hash)?.count??0)})}record(e,t){if(t instanceof Error){if(this.handled.has(t)){this.log(`Skipping duplicate:`,t.message);return}this.handled.add(t)}if(o.test(`${e.filename??``}\n${e.stack??``}`))return;let n=u(e),r=this.queue.get(n);r?r.count+=1:this.queue.set(n,{hash:n,count:1,error:e.kind===`unhandledrejection`?`UnhandledRejection`:`Error`,message:e.message,stack:s(e.stack),handled:e.handled,...e.cause?{cause:e.cause}:{}}),this.log(`Captured error:`,e),this.queue.size>=this.maxQueueSize&&this.requestFlush()}async flush(){if(this.flushing||this.queue.size===0)return;let e=[...this.queue.values()];this.queue.clear(),this.flushing=!0;let t=r(),a=globalThis.__SOURCEMAPS_BUILD__,o=typeof a?.buildId==`string`&&a.buildId.trim()?a.buildId:void 0,s=JSON.stringify({token:this.options.siteKey,...t?{userId:t}:{},sessionId:n(),...o?{buildId:o}:{},sdkName:this.options.sdkName??`@faststats/web`,sdkVersion:this.options.sdkVersion??`0.2.12`,data:{url:location.href,page:location.pathname,referrer:document.referrer||null,title:document.title},errors:e});this.log(`Flushing errors:`,e),this.log(`Payload:`,s);try{await i({url:this.endpoint,data:s,debug:this.debug,debugPrefix:`[ErrorTracker]`})||this.restoreErrors(e)}catch{this.restoreErrors(e)}finally{this.flushing=!1}}};export{a as n,d as t};
@@ -1 +0,0 @@
1
- import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{t}from"./api-urls-BrkcoElX.js";var n=e({fetchFeatureFlagEvaluation:()=>r});async function r(e,n){if(n.projectToken&&n.projectId)throw Error(`provide either projectToken or projectId, not both`);let r=n.projectToken,i=n.projectId,a=n.identifier,o=n.externalId;if(!r&&!i)throw Error(`feature flag check requires projectToken or projectId`);if(!a&&!o)throw Error(`feature flag check requires serverId or externalId`);let s={key:e,...i?{projectId:i}:{},...a?{identifier:a}:{},...o?{externalId:o}:{}};n.attributes&&Object.keys(n.attributes).length>0&&(s.attributes=n.attributes);let c={"Content-Type":`application/json`};r&&(c.Authorization=`Bearer ${r}`);let l=t(n.baseUrl),u=await fetch(l,{method:`POST`,headers:c,body:JSON.stringify(s),credentials:`omit`,signal:n.signal});if(!u.ok){let e=``;try{let t=await u.json();typeof t.error==`string`&&t.error.length>0&&(e=` — ${t.error}`)}catch{}throw Error(`feature flag check failed: HTTP ${u.status}${e}`)}return await u.json()}export{r as n,n as t};
@@ -1 +0,0 @@
1
- const e=`faststats_anon_id`,t=`session_id`,n=`session_timestamp`,r=`session_start`;let i=!1;function a(e){i=e}function o(e){return e??i?``:s()}function s(){if(!localStorage)return``;let t=localStorage.getItem(e);if(t)return t;let n=crypto.randomUUID();return localStorage.setItem(e,n),n}function c(t){return t||!localStorage?``:(localStorage.removeItem(e),s())}function l(){if(!sessionStorage)return``;let e=sessionStorage.getItem(t),i=sessionStorage.getItem(n);if(e&&i){if(Date.now()-Number.parseInt(i,10)<18e5)return sessionStorage.setItem(n,Date.now().toString()),e;p(sessionStorage)}let a=Date.now().toString(),o=crypto.randomUUID();return sessionStorage.setItem(t,o),sessionStorage.setItem(n,a),sessionStorage.setItem(r,a),o}function u(){return sessionStorage?(p(sessionStorage),l()):``}function d(){sessionStorage&&sessionStorage.getItem(t)&&sessionStorage.setItem(n,Date.now().toString())}function f(){if(!sessionStorage)return Date.now();let e=sessionStorage.getItem(r);if(e)return Number.parseInt(e,10);let t=sessionStorage.getItem(n);return t?Number.parseInt(t,10):Date.now()}function p(e){e.removeItem(t),e.removeItem(n),e.removeItem(r)}export{c as a,d as i,l as n,u as o,f as r,a as s,o as t};
@@ -1 +0,0 @@
1
- import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{a as t}from"./api-urls-BrkcoElX.js";import{n,r,t as i}from"./identifiers-CQeWm7wi.js";import{t as a}from"./send-data-DL_GlsQw.js";import{t as o}from"./types-CYzR5xtT.js";import{EventType as s}from"@rrweb/types";import{record as c}from"rrweb";var l=e({default:()=>f});const u={mousemove:50,mouseInteraction:!0,scroll:150,media:800,input:`last`},d={script:!0,comment:!0,headFavicon:!0,headWhitespace:!0,headMetaDescKeywords:!0,headMetaSocial:!0,headMetaRobots:!0,headMetaHttpEquiv:!0,headMetaAuthorship:!0};var f=class{endpoint;compressionSupported=typeof window<`u`&&`CompressionStream`in window;sampled;events=[];pending=[];pendingSizeBytes=0;sessionId;started=!1;startTime=0;sequence=0;intervalId=null;flushTask=null;retryTask=null;minLengthFlushTask=null;stopRecording;sending=!1;constructor(e){this.options=e,this.endpoint=t(e.baseUrl),this.sampled=Math.random()*100<o(e.samplingPercentage)}get debug(){return this.options.debug??!1}get flushInterval(){return this.options.flushInterval??1e4}get maxEvents(){return this.options.maxEvents??500}get maxPendingBatches(){return this.options.maxPendingBatches??30}get maxQueueSizeBytes(){return this.options.maxQueueSizeBytes??2097152}get minReplayLengthMs(){return this.options.minReplayLengthMs??3e3}get shouldCompress(){return this.options.compress??!0}log(...e){this.debug&&console.log(`[Replay]`,...e)}start(){this.started||typeof window>`u`||!this.sampled||(this.started=!0,this.sessionId=n(),this.startTime=r(),this.beginRecording(),this.intervalId=setInterval(this.requestFlush,this.flushInterval),window.addEventListener(`beforeunload`,this.onUnload),window.addEventListener(`pagehide`,this.onUnload),document.addEventListener(`visibilitychange`,this.onVisibilityChange),this.log(`Recording started`))}async beginRecording(){let e=this.options.recordConsole??!0,[{getRecordSequentialIdPlugin:t},n]=await Promise.all([import(`@rrweb/rrweb-plugin-sequential-id-record`),e?import(`@rrweb/rrweb-plugin-console-record`):Promise.resolve(null)]);if(!this.started)return;let r=[t({key:`_faststatsSeqId`})];n&&r.push(n.getRecordConsolePlugin()),this.stopRecording=c({emit:this.onEvent,sampling:this.options.sampling??u,slimDOMOptions:this.options.slimDOMOptions??d,maskAllInputs:this.options.maskAllInputs??!0,maskInputOptions:this.options.maskInputOptions??{password:!0,email:!0,tel:!0},blockClass:this.options.blockClass,blockSelector:this.options.blockSelector,maskTextClass:this.options.maskTextClass,maskTextSelector:this.options.maskTextSelector,checkoutEveryNms:this.options.checkoutEveryNms??6e4,checkoutEveryNth:this.options.checkoutEveryNth,plugins:r})}stop(){if(this.started){if(this.started=!1,this.stopRecording?.(),this.stopRecording=void 0,this.intervalId&&clearInterval(this.intervalId),this.flushTask&&clearTimeout(this.flushTask),this.retryTask&&clearTimeout(this.retryTask),this.minLengthFlushTask&&clearTimeout(this.minLengthFlushTask),this.intervalId=null,this.flushTask=null,this.retryTask=null,this.minLengthFlushTask=null,window.removeEventListener(`beforeunload`,this.onUnload),window.removeEventListener(`pagehide`,this.onUnload),document.removeEventListener(`visibilitychange`,this.onVisibilityChange),!this.hasReachedMinLength()){this.events.length=0,this.sessionId=void 0,this.log(`Session too short (${Date.now()-this.startTime}ms), discarding events`);return}this.flush(!0),this.sessionId=void 0,this.log(`Recording stopped`)}}getSessionId(){return this.sessionId??n()}onEvent=(e,t)=>{if(this.events.push(e),t||this.events.length>=this.maxEvents||e.type===s.FullSnapshot&&this.hasReachedMinLength()){this.requestFlush();return}this.scheduleMinLengthFlush()};onUnload=()=>{this.flush(!0)};onVisibilityChange=()=>{document.visibilityState===`hidden`&&this.flush(!0)};hasReachedMinLength(){return this.minReplayLengthMs<=0||Date.now()-this.startTime>=this.minReplayLengthMs}requestFlush=()=>{this.flushTask||this.sending||this.events.length===0||(this.minLengthFlushTask&&=(clearTimeout(this.minLengthFlushTask),null),this.flushTask=setTimeout(()=>{this.flushTask=null,this.flush(!1)},0))};scheduleMinLengthFlush(){if(this.minLengthFlushTask||this.events.length===0||this.hasReachedMinLength())return;let e=Math.max(0,this.minReplayLengthMs-(Date.now()-this.startTime)),t=Math.min(e,2147483647);this.minLengthFlushTask=setTimeout(()=>{this.minLengthFlushTask=null,this.requestFlush()},t)}createBatch(e){let t=i(),n=this.sequence++;return{token:this.options.siteKey,sessionId:this.getSessionId(),...t?{identifier:t}:{},batchId:this.createBatchId(n),sequence:n,timestamp:Date.now(),url:window.location.href,events:e}}createBatchId(e){let t=typeof crypto<`u`&&`randomUUID`in crypto?crypto.randomUUID():`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;return`${this.getSessionId()??`unknown`}-${e}-${t}`}getBatchSizeBytes(e){return new TextEncoder().encode(JSON.stringify(e)).byteLength}dropOldestPendingBatch(e){let t=this.pending.shift();t&&(this.pendingSizeBytes=Math.max(0,this.pendingSizeBytes-this.getBatchSizeBytes(t)),this.log(`${e}, dropping batch ${t.sequence}`))}enqueueBatch(e){let t=this.getBatchSizeBytes(e);if(t>this.maxQueueSizeBytes){this.log(`Replay batch ${e.sequence} is ${t}B, exceeding ${this.maxQueueSizeBytes}B queue limit; dropping`);return}for(;this.pending.length>0&&this.pendingSizeBytes+t>this.maxQueueSizeBytes;)this.dropOldestPendingBatch(`Pending queue size limit reached`);for(;this.pending.length>=this.maxPendingBatches;)this.dropOldestPendingBatch(`Pending batch limit reached`);this.pending.push(e),this.pendingSizeBytes+=t}async encodeBatch(e){let t=JSON.stringify(e);if(!this.shouldCompress||!this.compressionSupported)return{data:t,isCompressed:!1};try{let e=await this.compress(t);return this.log(`Compressed ${t.length}B -> ${e.byteLength}B (${Math.round(e.byteLength/t.length*100)}%)`),{data:e,isCompressed:!0}}catch{return this.log(`Compression failed, using uncompressed`),{data:t,isCompressed:!1}}}async flush(e){if(!this.sending){if(this.events.length>0)if(!this.hasReachedMinLength())this.scheduleMinLengthFlush();else{let e=this.createBatch(this.events.splice(0));this.enqueueBatch(e)}if(this.pending.length!==0){this.sending=!0;try{for(;this.pending.length>0;){let t=this.pending[0];if(!t)break;let n=await this.encodeBatch(t);if(!await this.send(n.data,n.isCompressed,e)){this.log(`Failed to send replay batch ${t.sequence}, retrying`),this.scheduleRetry();break}this.dropOldestPendingBatch(`Sent replay batch`),e=!1}}finally{this.sending=!1}}}}scheduleRetry(){this.retryTask||=setTimeout(()=>{this.retryTask=null,this.flush(!1)},1e3)}async compress(e){let t=new Blob([e]).stream().pipeThrough(new CompressionStream(`gzip`)),n=await new Response(t).arrayBuffer();return new Uint8Array(n)}send(e,t,n){return a({url:t?`${this.endpoint}?encoding=gzip`:this.endpoint,data:e,contentType:t?`application/octet-stream`:`application/json`,debug:!1,useBeacon:!1,keepalive:n})??Promise.resolve(!1)}};export{l as n,f as t};
@@ -1 +0,0 @@
1
- async function e(e){let{url:t,data:n,contentType:r=`application/json`,headers:i={},debug:a=!1,debugPrefix:o=`[Analytics]`,useBeacon:s=!0,keepalive:c=!0}=e;if(s&&typeof globalThis.navigator?.sendBeacon==`function`)try{let e=n instanceof Blob?n:typeof Blob<`u`?new Blob([n],{type:r}):n;if(globalThis.navigator.sendBeacon(t,e))return a&&console.log(`${o} Sent via beacon`),!0}catch{}try{let e=await fetch(t,{method:`POST`,body:n,headers:{"Content-Type":r,...i},keepalive:c}),s=e.ok;if(a){let t=s?`${o} Sent via fetch`:`${o} Failed: ${e.status}`;console[s?`log`:`warn`](t)}return s}catch{return a&&console.warn(`${o} Failed to send`),!1}}export{e as t};
@@ -1 +0,0 @@
1
- import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{s as t}from"./api-urls-BrkcoElX.js";import{n}from"./identifiers-CQeWm7wi.js";import{t as r}from"./types-CYzR5xtT.js";var i=e({default:()=>s});async function a(e){let{onCLS:t,onFCP:n,onINP:r,onLCP:i,onTTFB:a}=e?await import(`web-vitals/attribution`):await import(`web-vitals`);return o([t,n,r,i,a])}function o([e,t,n,r,i]){return[{observe:e,options:{reportAllChanges:!0}},{observe:t},{observe:n,options:{reportAllChanges:!0}},{observe:r,options:{reportAllChanges:!0}},{observe:i}]}var s=class{endpoint;metricsByUrl=new Map;pendingFlushUrls=new Set;sampled;started=!1;flushing=!1;finalFlushRequested=!1;currentUrl=``;constructor(e){this.options=e,this.endpoint=t(e.baseUrl),this.sampled=Math.random()*100<r(e.samplingPercentage)}get debug(){return this.options.debug??!1}log(...e){this.debug&&console.log(`[WebVitals]`,...e)}start(){this.started||typeof window>`u`||(this.started=!0,this.currentUrl=window.location.href,document.addEventListener(`visibilitychange`,this.onVisibilityChange,{passive:!0}),window.addEventListener(`pagehide`,this.onPageHide,{passive:!0}),this.log(`Tracking started`),this.observe())}stop(){!this.started||typeof window>`u`||(this.started=!1,this.cleanupListeners(),this.queueFlush(this.currentUrl,!0))}trackPageChange(e){if(!this.started||typeof window>`u`)return;let t=e??window.location.href;if(t===this.currentUrl)return;let n=this.currentUrl;this.currentUrl=t,this.queueFlush(n)}cleanupListeners(){document.removeEventListener(`visibilitychange`,this.onVisibilityChange),window.removeEventListener(`pagehide`,this.onPageHide)}async observe(){try{let e=await a(this.options.attribution??!1);if(!this.started)return;for(let{observe:t,options:n}of e)t(this.captureMetric,n)}catch(e){this.log(`Failed to load web-vitals`,e)}}onVisibilityChange=()=>{document.visibilityState===`hidden`&&this.queueFlush(this.currentUrl,!0)};onPageHide=()=>{this.queueFlush(this.currentUrl,!0)};captureMetric=e=>{if(!this.started||!this.sampled)return;let t=e.name,n=e.attribution??void 0;this.metricsForUrl(this.currentUrl).set(t,{value:e.value,attributes:{id:e.id,rating:e.rating,delta:e.delta,navigationType:e.navigationType,...n??{}}}),this.log(`${t} captured: ${e.value}`)};metricsForUrl(e){let t=this.metricsByUrl.get(e);return t||(t=new Map,this.metricsByUrl.set(e,t)),t}queueFlush(e,t=!1){this.finalFlushRequested||=t,this.pendingFlushUrls.add(e),this.flushPending()}async flushPending(){if(!this.flushing){if(typeof window>`u`||typeof fetch!=`function`){this.finishFinalFlushIfNeeded();return}this.flushing=!0;try{let e=[...this.pendingFlushUrls];this.pendingFlushUrls.clear();for(let t of e)await this.sendMetricsForUrl(t)||this.pendingFlushUrls.add(t)}finally{this.flushing=!1,this.finishFinalFlushIfNeeded()}}}async sendMetricsForUrl(e){let t=this.metricsByUrl.get(e);if(!t||t.size===0)return!0;let r=[...t.entries()].map(([e,t])=>({metric:e,value:t.value,attributes:t.attributes})),i=JSON.stringify({sessionId:n(),vitals:r,metadata:{url:e}});this.log(`Sending metrics for ${e} (${r.length})`);try{let t=new AbortController,n=window.setTimeout(()=>t.abort(),3e3);try{let n=await fetch(this.endpoint,{method:`POST`,body:i,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${this.options.siteKey}`},keepalive:!0,signal:t.signal});if(!n.ok)throw Error(`HTTP ${n.status}`);this.metricsByUrl.delete(e)}finally{clearTimeout(n)}return!0}catch(e){return this.log(`Failed to send metrics`,e),!1}}finishFinalFlushIfNeeded(){this.finalFlushRequested&&(this.finalFlushRequested=!1,this.started=!1,this.cleanupListeners())}};export{i as n,s as t};
package/wrangler.toml DELETED
@@ -1,8 +0,0 @@
1
- name = "web-analytics"
2
- main = "worker/index.ts"
3
- compatibility_date = "2024-01-01"
4
-
5
- [assets]
6
- directory = "./dist"
7
- binding = "ASSETS"
8
- run_worker_first = true