@agentuity/frontend 0.1.1 → 0.1.3

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 (80) hide show
  1. package/dist/analytics/beacon-standalone.d.ts +106 -0
  2. package/dist/analytics/beacon-standalone.d.ts.map +1 -0
  3. package/dist/analytics/beacon-standalone.js +577 -0
  4. package/dist/analytics/beacon-standalone.js.map +1 -0
  5. package/dist/analytics/index.d.ts +15 -5
  6. package/dist/analytics/index.d.ts.map +1 -1
  7. package/dist/analytics/index.js +21 -5
  8. package/dist/analytics/index.js.map +1 -1
  9. package/dist/analytics/types.d.ts +63 -35
  10. package/dist/analytics/types.d.ts.map +1 -1
  11. package/dist/beacon-script.d.ts +16 -0
  12. package/dist/beacon-script.d.ts.map +1 -0
  13. package/dist/beacon-script.js +3 -0
  14. package/dist/beacon-script.js.map +1 -0
  15. package/dist/beacon.js +1 -0
  16. package/dist/index.d.ts +2 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +5 -2
  19. package/dist/index.js.map +1 -1
  20. package/package.json +4 -4
  21. package/src/analytics/beacon-standalone.ts +718 -0
  22. package/src/analytics/index.ts +29 -8
  23. package/src/analytics/types.ts +78 -49
  24. package/src/beacon-script.ts +24 -0
  25. package/src/index.ts +10 -7
  26. package/dist/analytics/beacon.d.ts +0 -15
  27. package/dist/analytics/beacon.d.ts.map +0 -1
  28. package/dist/analytics/beacon.js +0 -177
  29. package/dist/analytics/beacon.js.map +0 -1
  30. package/dist/analytics/collectors/clicks.d.ts +0 -10
  31. package/dist/analytics/collectors/clicks.d.ts.map +0 -1
  32. package/dist/analytics/collectors/clicks.js +0 -84
  33. package/dist/analytics/collectors/clicks.js.map +0 -1
  34. package/dist/analytics/collectors/errors.d.ts +0 -5
  35. package/dist/analytics/collectors/errors.d.ts.map +0 -1
  36. package/dist/analytics/collectors/errors.js +0 -43
  37. package/dist/analytics/collectors/errors.js.map +0 -1
  38. package/dist/analytics/collectors/forms.d.ts +0 -5
  39. package/dist/analytics/collectors/forms.d.ts.map +0 -1
  40. package/dist/analytics/collectors/forms.js +0 -55
  41. package/dist/analytics/collectors/forms.js.map +0 -1
  42. package/dist/analytics/collectors/pageview.d.ts +0 -15
  43. package/dist/analytics/collectors/pageview.d.ts.map +0 -1
  44. package/dist/analytics/collectors/pageview.js +0 -64
  45. package/dist/analytics/collectors/pageview.js.map +0 -1
  46. package/dist/analytics/collectors/scroll.d.ts +0 -17
  47. package/dist/analytics/collectors/scroll.d.ts.map +0 -1
  48. package/dist/analytics/collectors/scroll.js +0 -93
  49. package/dist/analytics/collectors/scroll.js.map +0 -1
  50. package/dist/analytics/collectors/spa.d.ts +0 -10
  51. package/dist/analytics/collectors/spa.d.ts.map +0 -1
  52. package/dist/analytics/collectors/spa.js +0 -53
  53. package/dist/analytics/collectors/spa.js.map +0 -1
  54. package/dist/analytics/collectors/visibility.d.ts +0 -18
  55. package/dist/analytics/collectors/visibility.d.ts.map +0 -1
  56. package/dist/analytics/collectors/visibility.js +0 -81
  57. package/dist/analytics/collectors/visibility.js.map +0 -1
  58. package/dist/analytics/collectors/webvitals.d.ts +0 -6
  59. package/dist/analytics/collectors/webvitals.d.ts.map +0 -1
  60. package/dist/analytics/collectors/webvitals.js +0 -111
  61. package/dist/analytics/collectors/webvitals.js.map +0 -1
  62. package/dist/analytics/events.d.ts +0 -18
  63. package/dist/analytics/events.d.ts.map +0 -1
  64. package/dist/analytics/events.js +0 -126
  65. package/dist/analytics/events.js.map +0 -1
  66. package/dist/analytics/offline.d.ts +0 -19
  67. package/dist/analytics/offline.d.ts.map +0 -1
  68. package/dist/analytics/offline.js +0 -145
  69. package/dist/analytics/offline.js.map +0 -1
  70. package/src/analytics/beacon.ts +0 -203
  71. package/src/analytics/collectors/clicks.ts +0 -100
  72. package/src/analytics/collectors/errors.ts +0 -49
  73. package/src/analytics/collectors/forms.ts +0 -64
  74. package/src/analytics/collectors/pageview.ts +0 -76
  75. package/src/analytics/collectors/scroll.ts +0 -112
  76. package/src/analytics/collectors/spa.ts +0 -60
  77. package/src/analytics/collectors/visibility.ts +0 -94
  78. package/src/analytics/collectors/webvitals.ts +0 -129
  79. package/src/analytics/events.ts +0 -146
  80. package/src/analytics/offline.ts +0 -163
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Standalone beacon script - this file is bundled and minified
3
+ * to create the production analytics.js served at /_agentuity/webanalytics/analytics.js
4
+ *
5
+ * This is the single source of truth for the beacon logic.
6
+ */
7
+ interface ScrollEvent {
8
+ depth: number;
9
+ timestamp: number;
10
+ }
11
+ interface AnalyticsCustomEvent {
12
+ timestamp: number;
13
+ name: string;
14
+ data: string;
15
+ }
16
+ interface GeoLocation {
17
+ country?: string;
18
+ country_latitude?: string | number;
19
+ country_longitude?: string | number;
20
+ region?: string;
21
+ region_latitude?: string | number;
22
+ region_longitude?: string | number;
23
+ city?: string;
24
+ city_latitude?: string | number;
25
+ city_longitude?: string | number;
26
+ timezone?: string;
27
+ latitude?: string | number;
28
+ longitude?: string | number;
29
+ }
30
+ interface PageViewData {
31
+ id: string;
32
+ timestamp: number;
33
+ timezone_offset: number;
34
+ url: string;
35
+ path: string;
36
+ referrer: string;
37
+ title: string;
38
+ screen_width: number;
39
+ screen_height: number;
40
+ viewport_width: number;
41
+ viewport_height: number;
42
+ device_pixel_ratio: number;
43
+ user_agent: string;
44
+ language: string;
45
+ scroll_depth: number;
46
+ time_on_page: number;
47
+ scroll_events: ScrollEvent[];
48
+ custom_events: AnalyticsCustomEvent[];
49
+ load_time?: number;
50
+ dom_ready?: number;
51
+ ttfb?: number;
52
+ fcp?: number;
53
+ lcp?: number;
54
+ cls?: number;
55
+ inp?: number;
56
+ country?: string;
57
+ country_latitude?: number;
58
+ country_longitude?: number;
59
+ region?: string;
60
+ region_latitude?: number;
61
+ region_longitude?: number;
62
+ city?: string;
63
+ city_latitude?: number;
64
+ city_longitude?: number;
65
+ timezone?: string;
66
+ latitude?: number;
67
+ longitude?: number;
68
+ utm_source?: string;
69
+ utm_medium?: string;
70
+ utm_campaign?: string;
71
+ utm_term?: string;
72
+ utm_content?: string;
73
+ [key: string]: unknown;
74
+ }
75
+ interface AnalyticsConfig {
76
+ enabled: boolean;
77
+ orgId: string;
78
+ projectId: string;
79
+ isDevmode: boolean;
80
+ trackClicks?: boolean;
81
+ trackScroll?: boolean;
82
+ trackWebVitals?: boolean;
83
+ trackErrors?: boolean;
84
+ trackSPANavigation?: boolean;
85
+ sampleRate?: number;
86
+ }
87
+ interface SessionData {
88
+ threadId?: string;
89
+ }
90
+ interface AgentuityWindow {
91
+ __AGENTUITY_ANALYTICS__?: AnalyticsConfig;
92
+ __AGENTUITY_SESSION__?: SessionData;
93
+ agentuityAnalytics?: {
94
+ track: (name: string, properties?: Record<string, unknown>) => void;
95
+ identify: (userId: string, traits?: Record<string, unknown>) => void;
96
+ flush: () => void;
97
+ };
98
+ }
99
+ declare const COLLECT_ENDPOINT = "/_agentuity/webanalytics/collect";
100
+ declare const MAX_CUSTOM_EVENTS = 1000;
101
+ /**
102
+ * Safely stringify an object, handling circular references and other errors.
103
+ * Returns the JSON string on success, or a fallback string on failure.
104
+ */
105
+ declare function safeStringify(obj: unknown): string;
106
+ //# sourceMappingURL=beacon-standalone.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beacon-standalone.d.ts","sourceRoot":"","sources":["../../src/analytics/beacon-standalone.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,UAAU,WAAW;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,oBAAoB;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACb;AAED,UAAU,WAAW;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,iBAAiB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAClC,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B;AAED,UAAU,YAAY;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,WAAW,EAAE,CAAC;IAC7B,aAAa,EAAE,oBAAoB,EAAE,CAAC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,UAAU,eAAe;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,WAAW;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,eAAe;IACxB,uBAAuB,CAAC,EAAE,eAAe,CAAC;IAC1C,qBAAqB,CAAC,EAAE,WAAW,CAAC;IACpC,kBAAkB,CAAC,EAAE;QACpB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QACpE,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QACrE,KAAK,EAAE,MAAM,IAAI,CAAC;KAClB,CAAC;CACF;AAED,QAAA,MAAM,gBAAgB,qCAAqC,CAAC;AAC5D,QAAA,MAAM,iBAAiB,OAAO,CAAC;AAE/B;;;GAGG;AACH,iBAAS,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAoB3C"}
@@ -0,0 +1,577 @@
1
+ "use strict";
2
+ /**
3
+ * Standalone beacon script - this file is bundled and minified
4
+ * to create the production analytics.js served at /_agentuity/webanalytics/analytics.js
5
+ *
6
+ * This is the single source of truth for the beacon logic.
7
+ */
8
+ const COLLECT_ENDPOINT = '/_agentuity/webanalytics/collect';
9
+ const MAX_CUSTOM_EVENTS = 1000;
10
+ /**
11
+ * Safely stringify an object, handling circular references and other errors.
12
+ * Returns the JSON string on success, or a fallback string on failure.
13
+ */
14
+ function safeStringify(obj) {
15
+ if (obj === undefined || obj === null) {
16
+ return '';
17
+ }
18
+ try {
19
+ const seen = new WeakSet();
20
+ return JSON.stringify(obj, (_key, value) => {
21
+ if (typeof value === 'object' && value !== null) {
22
+ if (seen.has(value)) {
23
+ return '[Circular]';
24
+ }
25
+ seen.add(value);
26
+ }
27
+ return value;
28
+ });
29
+ }
30
+ catch (err) {
31
+ const message = err instanceof Error ? err.message : String(err);
32
+ console.warn('[Agentuity Analytics] Failed to stringify properties:', message);
33
+ return `[unserializable: ${message}]`;
34
+ }
35
+ }
36
+ (function () {
37
+ const w = window;
38
+ const d = document;
39
+ const configRaw = w.__AGENTUITY_ANALYTICS__;
40
+ if (!configRaw || !configRaw.enabled)
41
+ return;
42
+ // Prevent duplicate initialization (e.g., from HMR)
43
+ const initFlag = w;
44
+ if (configRaw.isDevmode) {
45
+ console.debug('[Agentuity Analytics] Script loaded, init flag:', initFlag.__AGENTUITY_BEACON_INIT__, 'path:', location.pathname);
46
+ }
47
+ if (initFlag.__AGENTUITY_BEACON_INIT__) {
48
+ if (configRaw.isDevmode) {
49
+ console.debug('[Agentuity Analytics] Already initialized, skipping');
50
+ }
51
+ return;
52
+ }
53
+ initFlag.__AGENTUITY_BEACON_INIT__ = true;
54
+ // Store in a non-nullable variable after the guard
55
+ const c = configRaw;
56
+ let geo = null;
57
+ let sent = false;
58
+ let pageStart = Date.now();
59
+ let userId = '';
60
+ let userTraits = {};
61
+ const pv = {
62
+ id: '',
63
+ timestamp: 0,
64
+ timezone_offset: 0,
65
+ url: '',
66
+ path: '',
67
+ referrer: '',
68
+ title: '',
69
+ screen_width: 0,
70
+ screen_height: 0,
71
+ viewport_width: 0,
72
+ viewport_height: 0,
73
+ device_pixel_ratio: 1,
74
+ user_agent: '',
75
+ language: '',
76
+ scroll_depth: 0,
77
+ time_on_page: 0,
78
+ scroll_events: [],
79
+ custom_events: [],
80
+ };
81
+ function generateId() {
82
+ return crypto.randomUUID
83
+ ? crypto.randomUUID()
84
+ : `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
85
+ }
86
+ function getUTMParams() {
87
+ const params = new URLSearchParams(location.search);
88
+ const utm = {};
89
+ ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'].forEach((key) => {
90
+ const value = params.get(key);
91
+ if (value)
92
+ utm[key] = value;
93
+ });
94
+ return utm;
95
+ }
96
+ // Strip query string from URL to prevent sensitive data leakage
97
+ function stripQueryString(url) {
98
+ if (!url)
99
+ return '';
100
+ try {
101
+ const parsed = new URL(url);
102
+ return parsed.origin + parsed.pathname;
103
+ }
104
+ catch {
105
+ // If URL parsing fails, try simple string split
106
+ return url.split('?')[0];
107
+ }
108
+ }
109
+ // Full init - called on page load and SPA navigation
110
+ function init() {
111
+ pv.id = generateId();
112
+ pv.timestamp = Date.now();
113
+ pv.timezone_offset = new Date().getTimezoneOffset();
114
+ pv.url = stripQueryString(location.href);
115
+ pv.path = location.pathname;
116
+ pv.referrer = stripQueryString(d.referrer);
117
+ pv.title = d.title || '';
118
+ pv.screen_width = screen.width || 0;
119
+ pv.screen_height = screen.height || 0;
120
+ pv.viewport_width = innerWidth || 0;
121
+ pv.viewport_height = innerHeight || 0;
122
+ pv.device_pixel_ratio = devicePixelRatio || 1;
123
+ pv.user_agent = navigator.userAgent || '';
124
+ pv.language = navigator.language || '';
125
+ const utm = getUTMParams();
126
+ for (const k in utm) {
127
+ pv[k] = utm[k];
128
+ }
129
+ pv.scroll_events = [];
130
+ pv.custom_events = [];
131
+ pv.scroll_depth = 0;
132
+ pv.fcp = 0;
133
+ pv.lcp = 0;
134
+ pv.cls = 0;
135
+ pv.inp = 0;
136
+ sent = false;
137
+ pageStart = Date.now();
138
+ if (typeof performance !== 'undefined' && performance.getEntriesByType) {
139
+ const nav = performance.getEntriesByType('navigation')[0];
140
+ if (nav) {
141
+ pv.load_time = Math.round(nav.loadEventEnd - nav.startTime);
142
+ pv.dom_ready = Math.round(nav.domContentLoadedEventEnd - nav.startTime);
143
+ pv.ttfb = Math.round(nav.responseStart - nav.requestStart);
144
+ }
145
+ }
146
+ if (c.isDevmode) {
147
+ console.debug('[Agentuity Analytics] Session started (full init):', pv.id);
148
+ }
149
+ }
150
+ // Soft reset - called when user returns to page (keeps page-level metrics)
151
+ function resetSession() {
152
+ pv.id = generateId();
153
+ pv.timestamp = Date.now();
154
+ pv.scroll_events = [];
155
+ pv.custom_events = [];
156
+ pv.scroll_depth = 0;
157
+ pv.time_on_page = 0;
158
+ sent = false;
159
+ pageStart = Date.now();
160
+ if (c.isDevmode) {
161
+ console.debug('[Agentuity Analytics] Session started (soft reset):', pv.id);
162
+ }
163
+ }
164
+ // Fetch geo data
165
+ fetch('https://agentuity.sh/location')
166
+ .then((r) => r.json())
167
+ .then((g) => {
168
+ geo = g;
169
+ try {
170
+ sessionStorage.setItem('agentuity_geo', JSON.stringify(g));
171
+ }
172
+ catch {
173
+ // Ignore
174
+ }
175
+ })
176
+ .catch(() => {
177
+ try {
178
+ const cached = sessionStorage.getItem('agentuity_geo');
179
+ if (cached)
180
+ geo = JSON.parse(cached);
181
+ }
182
+ catch {
183
+ // Ignore
184
+ }
185
+ });
186
+ // Try to load cached geo immediately
187
+ try {
188
+ const cached = sessionStorage.getItem('agentuity_geo');
189
+ if (cached)
190
+ geo = JSON.parse(cached);
191
+ }
192
+ catch {
193
+ // Ignore
194
+ }
195
+ function getSession() {
196
+ return w.__AGENTUITY_SESSION__;
197
+ }
198
+ function send(force = false) {
199
+ if (sent && !force) {
200
+ if (c.isDevmode) {
201
+ console.debug('[Agentuity Analytics] send() skipped - already sent');
202
+ }
203
+ return;
204
+ }
205
+ if (c.sampleRate !== undefined && c.sampleRate < 1 && Math.random() > c.sampleRate)
206
+ return;
207
+ sent = true;
208
+ pv.time_on_page = Date.now() - pageStart;
209
+ if (geo) {
210
+ pv.country = geo.country || '';
211
+ if (geo.country_latitude)
212
+ pv.country_latitude = parseFloat(String(geo.country_latitude));
213
+ if (geo.country_longitude)
214
+ pv.country_longitude = parseFloat(String(geo.country_longitude));
215
+ pv.region = geo.region || '';
216
+ if (geo.region_latitude)
217
+ pv.region_latitude = parseFloat(String(geo.region_latitude));
218
+ if (geo.region_longitude)
219
+ pv.region_longitude = parseFloat(String(geo.region_longitude));
220
+ pv.city = geo.city || '';
221
+ if (geo.city_latitude)
222
+ pv.city_latitude = parseFloat(String(geo.city_latitude));
223
+ if (geo.city_longitude)
224
+ pv.city_longitude = parseFloat(String(geo.city_longitude));
225
+ pv.timezone = geo.timezone || '';
226
+ if (geo.latitude)
227
+ pv.latitude = parseFloat(String(geo.latitude));
228
+ if (geo.longitude)
229
+ pv.longitude = parseFloat(String(geo.longitude));
230
+ }
231
+ if (pv.cls) {
232
+ pv.cls = Math.round(pv.cls * 1000) / 1000;
233
+ }
234
+ const s = getSession();
235
+ const vid = localStorage.getItem('agentuity_visitor_id') || 'vid_' + generateId();
236
+ try {
237
+ localStorage.setItem('agentuity_visitor_id', vid);
238
+ }
239
+ catch {
240
+ // Ignore
241
+ }
242
+ const payload = {
243
+ org_id: c.orgId,
244
+ project_id: c.projectId,
245
+ thread_id: s?.threadId || '',
246
+ visitor_id: vid,
247
+ user_id: userId,
248
+ user_traits: userTraits,
249
+ is_devmode: c.isDevmode,
250
+ pageview: pv,
251
+ };
252
+ // Clear pending data since we're sending now
253
+ try {
254
+ sessionStorage.removeItem('agentuity_pending_pageview');
255
+ }
256
+ catch {
257
+ // Storage may be unavailable
258
+ }
259
+ if (c.isDevmode) {
260
+ console.debug('[Agentuity Analytics]', JSON.stringify(payload, null, 2));
261
+ return;
262
+ }
263
+ const body = JSON.stringify(payload);
264
+ if (navigator.sendBeacon) {
265
+ navigator.sendBeacon(COLLECT_ENDPOINT, body);
266
+ }
267
+ else {
268
+ fetch(COLLECT_ENDPOINT, {
269
+ method: 'POST',
270
+ body,
271
+ keepalive: true,
272
+ }).catch(() => {
273
+ // Silent failure
274
+ });
275
+ }
276
+ }
277
+ // Send on page hide, reset session on page visible
278
+ d.addEventListener('visibilitychange', () => {
279
+ if (c.isDevmode) {
280
+ console.debug('[Agentuity Analytics] visibilitychange:', d.visibilityState, 'sent:', sent);
281
+ }
282
+ if (d.visibilityState === 'hidden') {
283
+ send();
284
+ }
285
+ else if (d.visibilityState === 'visible') {
286
+ // User returned to the page - start a new attention session
287
+ // Keep page-level metrics (url, geo, vitals) but reset session-level metrics
288
+ resetSession();
289
+ }
290
+ });
291
+ w.addEventListener('pagehide', () => {
292
+ if (c.isDevmode) {
293
+ console.debug('[Agentuity Analytics] pagehide event');
294
+ try {
295
+ sessionStorage.setItem('agentuity_last_event', `pagehide:${Date.now()}:${pv.path}`);
296
+ }
297
+ catch {
298
+ // Storage may be unavailable
299
+ }
300
+ }
301
+ send();
302
+ });
303
+ // Catch hard navigations (URL change, link click to new page, refresh)
304
+ w.addEventListener('beforeunload', () => {
305
+ if (c.isDevmode) {
306
+ console.debug('[Agentuity Analytics] beforeunload event');
307
+ try {
308
+ sessionStorage.setItem('agentuity_last_event', `beforeunload:${Date.now()}:${pv.path}`);
309
+ }
310
+ catch {
311
+ // Storage may be unavailable
312
+ }
313
+ }
314
+ send();
315
+ });
316
+ // In devmode, check if previous page sent data (helps verify unload events work)
317
+ if (c.isDevmode) {
318
+ try {
319
+ const lastEvent = sessionStorage.getItem('agentuity_last_event');
320
+ if (lastEvent) {
321
+ console.debug('[Agentuity Analytics] Previous page event:', lastEvent);
322
+ sessionStorage.removeItem('agentuity_last_event');
323
+ }
324
+ }
325
+ catch {
326
+ // Storage may be unavailable
327
+ }
328
+ }
329
+ // Fallback: check for unsent data from previous page (in case unload events didn't fire)
330
+ try {
331
+ const pendingData = sessionStorage.getItem('agentuity_pending_pageview');
332
+ if (pendingData) {
333
+ sessionStorage.removeItem('agentuity_pending_pageview');
334
+ const pending = JSON.parse(pendingData);
335
+ // Only send if it's from a different page
336
+ if (pending.pageview?.path !== location.pathname) {
337
+ if (c.isDevmode) {
338
+ console.debug('[Agentuity Analytics] Sending unsent data from previous page:', pending.pageview?.path);
339
+ console.debug('[Agentuity Analytics]', JSON.stringify(pending, null, 2));
340
+ }
341
+ else {
342
+ const body = JSON.stringify(pending);
343
+ if (navigator.sendBeacon) {
344
+ navigator.sendBeacon(COLLECT_ENDPOINT, body);
345
+ }
346
+ }
347
+ }
348
+ }
349
+ }
350
+ catch {
351
+ // Storage or JSON parsing may fail
352
+ }
353
+ // Store current pageview data periodically so it can be recovered if unload events don't fire
354
+ function savePendingData() {
355
+ try {
356
+ pv.time_on_page = Date.now() - pageStart;
357
+ const s = getSession();
358
+ const vid = localStorage.getItem('agentuity_visitor_id') || 'vid_' + generateId();
359
+ const payload = {
360
+ org_id: c.orgId,
361
+ project_id: c.projectId,
362
+ thread_id: s?.threadId || '',
363
+ visitor_id: vid,
364
+ user_id: userId,
365
+ user_traits: userTraits,
366
+ is_devmode: c.isDevmode,
367
+ pageview: { ...pv },
368
+ };
369
+ sessionStorage.setItem('agentuity_pending_pageview', JSON.stringify(payload));
370
+ }
371
+ catch {
372
+ // Storage may be unavailable
373
+ }
374
+ }
375
+ // Save pending data every 2 seconds
376
+ setInterval(savePendingData, 2000);
377
+ // Also save on any interaction
378
+ d.addEventListener('click', savePendingData, { passive: true });
379
+ d.addEventListener('scroll', savePendingData, { passive: true, once: true });
380
+ if (c.isDevmode) {
381
+ console.debug('[Agentuity Analytics] Beacon initialized, visibility:', d.visibilityState);
382
+ }
383
+ // Scroll tracking
384
+ if (c.trackScroll !== false) {
385
+ const scrolled = new Set();
386
+ function getScrollDepth() {
387
+ const st = w.scrollY || d.documentElement.scrollTop;
388
+ const sh = d.documentElement.scrollHeight - d.documentElement.clientHeight;
389
+ return sh <= 0 ? 100 : Math.min(100, Math.round((st / sh) * 100));
390
+ }
391
+ w.addEventListener('scroll', () => {
392
+ const dp = getScrollDepth();
393
+ if (dp > pv.scroll_depth)
394
+ pv.scroll_depth = dp;
395
+ [25, 50, 75, 100].forEach((m) => {
396
+ if (dp >= m && !scrolled.has(m)) {
397
+ scrolled.add(m);
398
+ pv.scroll_events.push({
399
+ depth: m,
400
+ timestamp: Date.now() - pageStart,
401
+ });
402
+ }
403
+ });
404
+ }, { passive: true });
405
+ }
406
+ // Web Vitals tracking
407
+ if (c.trackWebVitals !== false && typeof PerformanceObserver !== 'undefined') {
408
+ // FCP
409
+ try {
410
+ const fcpObs = new PerformanceObserver((list) => {
411
+ list.getEntries().forEach((entry) => {
412
+ if (entry.name === 'first-contentful-paint') {
413
+ pv.fcp = Math.round(entry.startTime);
414
+ fcpObs.disconnect();
415
+ }
416
+ });
417
+ });
418
+ fcpObs.observe({ type: 'paint', buffered: true });
419
+ }
420
+ catch {
421
+ // Not supported
422
+ }
423
+ // LCP
424
+ try {
425
+ new PerformanceObserver((list) => {
426
+ const entries = list.getEntries();
427
+ if (entries.length) {
428
+ pv.lcp = Math.round(entries[entries.length - 1].startTime);
429
+ }
430
+ }).observe({ type: 'largest-contentful-paint', buffered: true });
431
+ }
432
+ catch {
433
+ // Not supported
434
+ }
435
+ // CLS
436
+ try {
437
+ new PerformanceObserver((list) => {
438
+ list.getEntries().forEach((entry) => {
439
+ const layoutShift = entry;
440
+ if (!layoutShift.hadRecentInput && layoutShift.value) {
441
+ pv.cls = (pv.cls || 0) + layoutShift.value;
442
+ }
443
+ });
444
+ }).observe({ type: 'layout-shift', buffered: true });
445
+ }
446
+ catch {
447
+ // Not supported
448
+ }
449
+ // INP
450
+ try {
451
+ new PerformanceObserver((list) => {
452
+ list.getEntries().forEach((entry) => {
453
+ const eventEntry = entry;
454
+ if (eventEntry.duration && eventEntry.duration > (pv.inp || 0)) {
455
+ pv.inp = Math.round(eventEntry.duration);
456
+ }
457
+ });
458
+ }).observe({ type: 'event', buffered: true });
459
+ }
460
+ catch {
461
+ // Not supported
462
+ }
463
+ }
464
+ // SPA navigation tracking
465
+ if (c.trackSPANavigation !== false) {
466
+ const origPush = history.pushState;
467
+ const origReplace = history.replaceState;
468
+ let currentPath = location.pathname + location.search;
469
+ let lastHref = location.href;
470
+ if (c.isDevmode) {
471
+ console.debug('[Agentuity Analytics] SPA tracking enabled, initial path:', currentPath);
472
+ }
473
+ function handleNav() {
474
+ const newPath = location.pathname + location.search;
475
+ if (newPath !== currentPath) {
476
+ if (c.isDevmode) {
477
+ console.debug('[Agentuity Analytics] SPA navigation:', currentPath, '->', newPath);
478
+ }
479
+ send(true); // Force send on SPA navigation
480
+ currentPath = newPath;
481
+ lastHref = location.href;
482
+ init();
483
+ }
484
+ }
485
+ history.pushState = function (...args) {
486
+ origPush.apply(this, args);
487
+ setTimeout(handleNav, 0);
488
+ };
489
+ history.replaceState = function (...args) {
490
+ origReplace.apply(this, args);
491
+ setTimeout(handleNav, 0);
492
+ };
493
+ w.addEventListener('popstate', handleNav);
494
+ // Fallback: poll for URL changes in case router bypasses history API
495
+ setInterval(() => {
496
+ if (location.href !== lastHref) {
497
+ lastHref = location.href;
498
+ handleNav();
499
+ }
500
+ }, 200);
501
+ }
502
+ // Click tracking
503
+ if (c.trackClicks !== false) {
504
+ d.addEventListener('click', (e) => {
505
+ const target = e.target;
506
+ if (!target)
507
+ return;
508
+ const analyticsEl = target.closest('[data-analytics]');
509
+ if (!analyticsEl)
510
+ return;
511
+ if (pv.custom_events.length < MAX_CUSTOM_EVENTS) {
512
+ pv.custom_events.push({
513
+ timestamp: Date.now(),
514
+ name: 'click:' + analyticsEl.getAttribute('data-analytics'),
515
+ data: '',
516
+ });
517
+ }
518
+ }, true);
519
+ }
520
+ // Error tracking
521
+ if (c.trackErrors !== false) {
522
+ w.addEventListener('error', (e) => {
523
+ if (pv.custom_events.length < MAX_CUSTOM_EVENTS) {
524
+ pv.custom_events.push({
525
+ timestamp: Date.now(),
526
+ name: 'error:js_error',
527
+ data: JSON.stringify({
528
+ message: e.message || 'Unknown',
529
+ filename: e.filename || '',
530
+ lineno: e.lineno || 0,
531
+ }),
532
+ });
533
+ }
534
+ });
535
+ w.addEventListener('unhandledrejection', (e) => {
536
+ if (pv.custom_events.length < MAX_CUSTOM_EVENTS) {
537
+ pv.custom_events.push({
538
+ timestamp: Date.now(),
539
+ name: 'error:unhandled_rejection',
540
+ data: JSON.stringify({
541
+ message: e.reason instanceof Error ? e.reason.message : String(e.reason),
542
+ }),
543
+ });
544
+ }
545
+ });
546
+ }
547
+ // Initialize on load
548
+ if (d.readyState === 'complete') {
549
+ init();
550
+ }
551
+ else {
552
+ w.addEventListener('load', init);
553
+ }
554
+ // Public API
555
+ w.agentuityAnalytics = {
556
+ track(name, properties) {
557
+ if (pv.custom_events.length < MAX_CUSTOM_EVENTS) {
558
+ pv.custom_events.push({
559
+ timestamp: Date.now(),
560
+ name,
561
+ data: safeStringify(properties),
562
+ });
563
+ }
564
+ },
565
+ identify(id, traits) {
566
+ userId = id;
567
+ if (traits) {
568
+ userTraits = {};
569
+ for (const [key, value] of Object.entries(traits)) {
570
+ userTraits[key] = String(value);
571
+ }
572
+ }
573
+ },
574
+ flush: () => send(true),
575
+ };
576
+ })();
577
+ //# sourceMappingURL=beacon-standalone.js.map