@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
@@ -1,64 +0,0 @@
1
- import { createBaseEvent } from './pageview';
2
- import { queueEvent } from '../events';
3
-
4
- /**
5
- * Initialize form submission tracking
6
- */
7
- export function initFormTracking(): void {
8
- if (typeof document === 'undefined') {
9
- return;
10
- }
11
-
12
- document.addEventListener(
13
- 'submit',
14
- (e) => {
15
- const form = e.target as HTMLFormElement | null;
16
- if (!form || form.tagName !== 'FORM') {
17
- return;
18
- }
19
-
20
- const event = createBaseEvent('form_submit');
21
- event.event_name = 'form_submit';
22
-
23
- const eventData: Record<string, unknown> = {};
24
-
25
- // Form identification
26
- if (form.id) {
27
- eventData.form_id = form.id;
28
- }
29
- if (form.name) {
30
- eventData.form_name = form.name;
31
- }
32
- if (form.action) {
33
- eventData.form_action = form.action;
34
- }
35
- eventData.form_method = form.method || 'get';
36
-
37
- // Count form fields (don't capture values for privacy)
38
- const inputs = form.querySelectorAll('input, select, textarea');
39
- eventData.field_count = inputs.length;
40
-
41
- // Check for common form types
42
- const hasEmail = form.querySelector('input[type="email"]') !== null;
43
- const hasPassword = form.querySelector('input[type="password"]') !== null;
44
- const hasSearch = form.querySelector('input[type="search"]') !== null;
45
-
46
- if (hasEmail && hasPassword) {
47
- eventData.form_type = 'auth';
48
- } else if (hasEmail) {
49
- eventData.form_type = 'email';
50
- } else if (hasSearch) {
51
- eventData.form_type = 'search';
52
- } else if (hasPassword) {
53
- eventData.form_type = 'password';
54
- } else {
55
- eventData.form_type = 'other';
56
- }
57
-
58
- event.event_data = eventData;
59
-
60
- queueEvent(event);
61
- },
62
- { capture: true }
63
- );
64
- }
@@ -1,76 +0,0 @@
1
- import type { AnalyticsEvent } from '../types';
2
- import { queueEvent } from '../events';
3
- import { getUTMParams } from '../utils/utm';
4
-
5
- /**
6
- * Create a base event with common properties
7
- */
8
- export function createBaseEvent(eventType: AnalyticsEvent['event_type']): AnalyticsEvent {
9
- const utm = getUTMParams();
10
-
11
- return {
12
- id: crypto.randomUUID
13
- ? crypto.randomUUID()
14
- : `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
15
- timestamp: Date.now(),
16
- timezone_offset: new Date().getTimezoneOffset(),
17
-
18
- event_type: eventType,
19
-
20
- url: window.location.href,
21
- path: window.location.pathname,
22
- referrer: document.referrer || '',
23
- title: document.title || '',
24
-
25
- screen_width: window.screen?.width || 0,
26
- screen_height: window.screen?.height || 0,
27
- viewport_width: window.innerWidth || 0,
28
- viewport_height: window.innerHeight || 0,
29
- device_pixel_ratio: window.devicePixelRatio || 1,
30
- user_agent: navigator.userAgent || '',
31
- language: navigator.language || '',
32
-
33
- ...utm,
34
- };
35
- }
36
-
37
- /**
38
- * Track a pageview event
39
- */
40
- export function trackPageview(customPath?: string): void {
41
- const event = createBaseEvent('pageview');
42
-
43
- if (customPath) {
44
- event.path = customPath;
45
- event.url = window.location.origin + customPath;
46
- }
47
-
48
- // Add performance timing if available
49
- if (typeof performance !== 'undefined') {
50
- const timing = performance.getEntriesByType('navigation')[0] as
51
- | PerformanceNavigationTiming
52
- | undefined;
53
- if (timing) {
54
- event.load_time = Math.round(timing.loadEventEnd - timing.startTime);
55
- event.dom_ready = Math.round(timing.domContentLoadedEventEnd - timing.startTime);
56
- event.ttfb = Math.round(timing.responseStart - timing.requestStart);
57
- }
58
- }
59
-
60
- queueEvent(event);
61
- }
62
-
63
- /**
64
- * Initialize pageview tracking
65
- * Tracks initial pageview when called
66
- */
67
- export function initPageviewTracking(): void {
68
- // Track initial pageview after DOM is ready
69
- if (document.readyState === 'complete') {
70
- trackPageview();
71
- } else {
72
- window.addEventListener('load', () => {
73
- trackPageview();
74
- });
75
- }
76
- }
@@ -1,112 +0,0 @@
1
- import { createBaseEvent } from './pageview';
2
- import { queueEvent } from '../events';
3
-
4
- const SCROLL_MILESTONES = [25, 50, 75, 100];
5
- let trackedMilestones: Set<number> = new Set();
6
- let maxScrollDepth = 0;
7
- let isScrollTrackingInitialized = false;
8
- let scrollHandler: (() => void) | null = null;
9
-
10
- /**
11
- * Calculate current scroll depth percentage
12
- */
13
- function getScrollDepth(): number {
14
- if (typeof window === 'undefined' || typeof document === 'undefined') {
15
- return 0;
16
- }
17
-
18
- const scrollTop = window.scrollY || document.documentElement.scrollTop;
19
- const scrollHeight =
20
- document.documentElement.scrollHeight - document.documentElement.clientHeight;
21
-
22
- if (scrollHeight <= 0) {
23
- return 100; // Page doesn't scroll
24
- }
25
-
26
- return Math.min(100, Math.round((scrollTop / scrollHeight) * 100));
27
- }
28
-
29
- /**
30
- * Handle scroll event
31
- */
32
- function handleScroll(): void {
33
- const depth = getScrollDepth();
34
-
35
- if (depth > maxScrollDepth) {
36
- maxScrollDepth = depth;
37
- }
38
-
39
- // Check for milestone crossings
40
- for (const milestone of SCROLL_MILESTONES) {
41
- if (depth >= milestone && !trackedMilestones.has(milestone)) {
42
- trackedMilestones.add(milestone);
43
-
44
- const event = createBaseEvent('scroll');
45
- event.event_name = `scroll_${milestone}`;
46
- event.scroll_depth = milestone;
47
-
48
- queueEvent(event);
49
- }
50
- }
51
- }
52
-
53
- /**
54
- * Initialize scroll depth tracking
55
- */
56
- export function initScrollTracking(): void {
57
- if (typeof window === 'undefined') {
58
- return;
59
- }
60
-
61
- if (isScrollTrackingInitialized) {
62
- return;
63
- }
64
- isScrollTrackingInitialized = true;
65
-
66
- // Reset on page load
67
- trackedMilestones = new Set();
68
- maxScrollDepth = 0;
69
-
70
- // Throttled scroll handler
71
- let ticking = false;
72
- scrollHandler = () => {
73
- if (!ticking) {
74
- requestAnimationFrame(() => {
75
- handleScroll();
76
- ticking = false;
77
- });
78
- ticking = true;
79
- }
80
- };
81
-
82
- window.addEventListener('scroll', scrollHandler, { passive: true });
83
-
84
- // Check initial scroll position (for pages that load scrolled)
85
- handleScroll();
86
- }
87
-
88
- /**
89
- * Remove scroll tracking listener
90
- */
91
- export function removeScrollTracking(): void {
92
- if (scrollHandler) {
93
- window.removeEventListener('scroll', scrollHandler);
94
- scrollHandler = null;
95
- }
96
- isScrollTrackingInitialized = false;
97
- }
98
-
99
- /**
100
- * Get max scroll depth (for time on page events)
101
- */
102
- export function getMaxScrollDepth(): number {
103
- return maxScrollDepth;
104
- }
105
-
106
- /**
107
- * Reset tracked milestones (for SPA navigation)
108
- */
109
- export function resetScrollTracking(): void {
110
- trackedMilestones = new Set();
111
- maxScrollDepth = 0;
112
- }
@@ -1,60 +0,0 @@
1
- import { trackPageview } from './pageview';
2
-
3
- let currentPath = '';
4
- let originalPushState: typeof history.pushState | null = null;
5
- let originalReplaceState: typeof history.replaceState | null = null;
6
-
7
- /**
8
- * Handle URL change for SPA navigation
9
- */
10
- function handleUrlChange(): void {
11
- const newPath = window.location.pathname;
12
- if (newPath !== currentPath) {
13
- currentPath = newPath;
14
- trackPageview(newPath);
15
- }
16
- }
17
-
18
- /**
19
- * Initialize SPA navigation tracking
20
- * Hooks into history.pushState, history.replaceState, and popstate event
21
- */
22
- export function initSPATracking(): void {
23
- if (typeof window === 'undefined' || typeof history === 'undefined') {
24
- return;
25
- }
26
-
27
- currentPath = window.location.pathname;
28
-
29
- // Hook into history.pushState
30
- originalPushState = history.pushState.bind(history);
31
- history.pushState = function (...args) {
32
- originalPushState?.apply(this, args);
33
- handleUrlChange();
34
- };
35
-
36
- // Hook into history.replaceState
37
- originalReplaceState = history.replaceState.bind(history);
38
- history.replaceState = function (...args) {
39
- originalReplaceState?.apply(this, args);
40
- handleUrlChange();
41
- };
42
-
43
- // Listen for popstate (back/forward navigation)
44
- window.addEventListener('popstate', handleUrlChange);
45
- }
46
-
47
- /**
48
- * Cleanup SPA tracking (for testing)
49
- */
50
- export function cleanupSPATracking(): void {
51
- if (originalPushState) {
52
- history.pushState = originalPushState;
53
- originalPushState = null;
54
- }
55
- if (originalReplaceState) {
56
- history.replaceState = originalReplaceState;
57
- originalReplaceState = null;
58
- }
59
- window.removeEventListener('popstate', handleUrlChange);
60
- }
@@ -1,94 +0,0 @@
1
- import { createBaseEvent } from './pageview';
2
- import { queueEvent } from '../events';
3
- import { getMaxScrollDepth } from './scroll';
4
-
5
- let pageEntryTime = 0;
6
- let hiddenTime = 0;
7
- let lastHiddenTimestamp = 0;
8
- let visibilityTrackingInitialized = false;
9
- let visibilityChangeHandler: (() => void) | null = null;
10
-
11
- /**
12
- * Get total time spent on page (excluding hidden time)
13
- */
14
- function getTimeOnPage(): number {
15
- if (pageEntryTime === 0) {
16
- return 0;
17
- }
18
-
19
- const totalTime = Date.now() - pageEntryTime;
20
- return Math.max(0, totalTime - hiddenTime);
21
- }
22
-
23
- /**
24
- * Initialize visibility tracking
25
- * Tracks when user leaves/returns to the page
26
- */
27
- export function initVisibilityTracking(): void {
28
- if (typeof document === 'undefined') {
29
- return;
30
- }
31
-
32
- if (visibilityTrackingInitialized) {
33
- return;
34
- }
35
- visibilityTrackingInitialized = true;
36
-
37
- pageEntryTime = Date.now();
38
- hiddenTime = 0;
39
- lastHiddenTimestamp = 0;
40
-
41
- visibilityChangeHandler = () => {
42
- if (document.visibilityState === 'hidden') {
43
- lastHiddenTimestamp = Date.now();
44
-
45
- // Track page leave with engagement metrics
46
- const event = createBaseEvent('visibility');
47
- event.event_name = 'page_hidden';
48
- event.time_on_page = getTimeOnPage();
49
- event.scroll_depth = getMaxScrollDepth();
50
-
51
- queueEvent(event);
52
- } else if (document.visibilityState === 'visible') {
53
- // Calculate hidden duration
54
- if (lastHiddenTimestamp > 0) {
55
- hiddenTime += Date.now() - lastHiddenTimestamp;
56
- lastHiddenTimestamp = 0;
57
- }
58
-
59
- const event = createBaseEvent('visibility');
60
- event.event_name = 'page_visible';
61
-
62
- queueEvent(event);
63
- }
64
- };
65
-
66
- document.addEventListener('visibilitychange', visibilityChangeHandler);
67
- }
68
-
69
- /**
70
- * Remove visibility tracking listener
71
- */
72
- export function removeVisibilityTracking(): void {
73
- if (visibilityChangeHandler) {
74
- document.removeEventListener('visibilitychange', visibilityChangeHandler);
75
- visibilityChangeHandler = null;
76
- }
77
- visibilityTrackingInitialized = false;
78
- }
79
-
80
- /**
81
- * Reset visibility tracking (for SPA navigation)
82
- */
83
- export function resetVisibilityTracking(): void {
84
- pageEntryTime = Date.now();
85
- hiddenTime = 0;
86
- lastHiddenTimestamp = 0;
87
- }
88
-
89
- /**
90
- * Get current time on page
91
- */
92
- export function getCurrentTimeOnPage(): number {
93
- return getTimeOnPage();
94
- }
@@ -1,129 +0,0 @@
1
- import { createBaseEvent } from './pageview';
2
- import { queueEvent, flushEvents } from '../events';
3
-
4
- /**
5
- * Initialize Core Web Vitals tracking
6
- * Uses PerformanceObserver to track LCP, FCP, CLS, INP
7
- */
8
- export function initWebVitalsTracking(): void {
9
- if (typeof window === 'undefined' || typeof PerformanceObserver === 'undefined') {
10
- return;
11
- }
12
-
13
- // Track First Contentful Paint (FCP)
14
- try {
15
- const fcpObserver = new PerformanceObserver((list) => {
16
- for (const entry of list.getEntries()) {
17
- if (entry.name === 'first-contentful-paint') {
18
- const event = createBaseEvent('web_vital');
19
- event.event_name = 'fcp';
20
- event.fcp = Math.round(entry.startTime);
21
- queueEvent(event);
22
- flushEvents();
23
- fcpObserver.disconnect();
24
- }
25
- }
26
- });
27
- fcpObserver.observe({ type: 'paint', buffered: true });
28
- } catch {
29
- // PerformanceObserver not supported for this entry type
30
- }
31
-
32
- // Track Largest Contentful Paint (LCP)
33
- try {
34
- let lcpValue = 0;
35
- const lcpObserver = new PerformanceObserver((list) => {
36
- const entries = list.getEntries();
37
- const lastEntry = entries[entries.length - 1];
38
- if (lastEntry) {
39
- lcpValue = lastEntry.startTime;
40
- }
41
- });
42
- lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true });
43
-
44
- // Report LCP when page becomes hidden
45
- document.addEventListener(
46
- 'visibilitychange',
47
- () => {
48
- if (document.visibilityState === 'hidden' && lcpValue > 0) {
49
- const event = createBaseEvent('web_vital');
50
- event.event_name = 'lcp';
51
- event.lcp = Math.round(lcpValue);
52
- queueEvent(event);
53
- flushEvents();
54
- lcpObserver.disconnect();
55
- }
56
- },
57
- { once: true }
58
- );
59
- } catch {
60
- // PerformanceObserver not supported for this entry type
61
- }
62
-
63
- // Track Cumulative Layout Shift (CLS)
64
- try {
65
- let clsValue = 0;
66
- const clsObserver = new PerformanceObserver((list) => {
67
- for (const entry of list.getEntries()) {
68
- const layoutShift = entry as PerformanceEntry & {
69
- hadRecentInput?: boolean;
70
- value?: number;
71
- };
72
- if (!layoutShift.hadRecentInput && layoutShift.value) {
73
- clsValue += layoutShift.value;
74
- }
75
- }
76
- });
77
- clsObserver.observe({ type: 'layout-shift', buffered: true });
78
-
79
- // Report CLS when page becomes hidden
80
- document.addEventListener(
81
- 'visibilitychange',
82
- () => {
83
- if (document.visibilityState === 'hidden') {
84
- const event = createBaseEvent('web_vital');
85
- event.event_name = 'cls';
86
- event.cls = Math.round(clsValue * 1000) / 1000; // Round to 3 decimal places
87
- queueEvent(event);
88
- flushEvents();
89
- clsObserver.disconnect();
90
- }
91
- },
92
- { once: true }
93
- );
94
- } catch {
95
- // PerformanceObserver not supported for this entry type
96
- }
97
-
98
- // Track Interaction to Next Paint (INP)
99
- try {
100
- let inpValue = 0;
101
- const inpObserver = new PerformanceObserver((list) => {
102
- for (const entry of list.getEntries()) {
103
- const eventEntry = entry as PerformanceEntry & { duration?: number };
104
- if (eventEntry.duration && eventEntry.duration > inpValue) {
105
- inpValue = eventEntry.duration;
106
- }
107
- }
108
- });
109
- inpObserver.observe({ type: 'event', buffered: true });
110
-
111
- // Report INP when page becomes hidden
112
- document.addEventListener(
113
- 'visibilitychange',
114
- () => {
115
- if (document.visibilityState === 'hidden' && inpValue > 0) {
116
- const event = createBaseEvent('web_vital');
117
- event.event_name = 'inp';
118
- event.inp = Math.round(inpValue);
119
- queueEvent(event);
120
- flushEvents();
121
- inpObserver.disconnect();
122
- }
123
- },
124
- { once: true }
125
- );
126
- } catch {
127
- // PerformanceObserver not supported for this entry type
128
- }
129
- }
@@ -1,146 +0,0 @@
1
- import type { AnalyticsEvent, AnalyticsBatchPayload, AnalyticsPageConfig } from './types';
2
- import { getVisitorId } from './utils/storage';
3
-
4
- const BATCH_SIZE = 10;
5
- const BATCH_TIMEOUT_MS = 5000;
6
- const COLLECT_ENDPOINT = '/_agentuity/webanalytics/collect';
7
-
8
- let eventQueue: AnalyticsEvent[] = [];
9
- let batchTimeout: ReturnType<typeof setTimeout> | null = null;
10
- let config: AnalyticsPageConfig | null = null;
11
-
12
- /**
13
- * Initialize the event queue with config
14
- */
15
- export function initEventQueue(pageConfig: AnalyticsPageConfig): void {
16
- config = pageConfig;
17
-
18
- // Flush on page unload
19
- if (typeof window !== 'undefined') {
20
- window.addEventListener('visibilitychange', () => {
21
- if (document.visibilityState === 'hidden') {
22
- flushEvents();
23
- }
24
- });
25
-
26
- window.addEventListener('pagehide', () => {
27
- flushEvents();
28
- });
29
- }
30
- }
31
-
32
- /**
33
- * Queue an event for sending
34
- */
35
- export function queueEvent(event: AnalyticsEvent): void {
36
- if (!config) {
37
- return;
38
- }
39
-
40
- // Apply sample rate (default: 1 = 100%)
41
- const sampleRate = config.sampleRate ?? 1;
42
- if (sampleRate < 1 && Math.random() > sampleRate) {
43
- return;
44
- }
45
-
46
- // Check exclude patterns
47
- const excludePatterns = config.excludePatterns ?? [];
48
- if (excludePatterns.length > 0) {
49
- const currentPath = window.location.pathname;
50
- for (const pattern of excludePatterns) {
51
- try {
52
- if (new RegExp(pattern).test(currentPath)) {
53
- return;
54
- }
55
- } catch {
56
- // Invalid regex, skip
57
- }
58
- }
59
- }
60
-
61
- // Merge global properties with event data and serialize to JSON string for Catalyst
62
- const mergedData =
63
- config.globalProperties && Object.keys(config.globalProperties).length > 0
64
- ? { ...config.globalProperties, ...event.event_data }
65
- : event.event_data;
66
-
67
- if (mergedData) {
68
- (event as unknown as { event_data: string }).event_data = JSON.stringify(mergedData);
69
- }
70
-
71
- eventQueue.push(event);
72
-
73
- // Flush if batch size reached
74
- if (eventQueue.length >= BATCH_SIZE) {
75
- flushEvents();
76
- } else if (!batchTimeout) {
77
- // Set timeout for batch flush
78
- batchTimeout = setTimeout(() => {
79
- flushEvents();
80
- }, BATCH_TIMEOUT_MS);
81
- }
82
- }
83
-
84
- /**
85
- * Flush all queued events
86
- */
87
- export function flushEvents(): void {
88
- if (batchTimeout) {
89
- clearTimeout(batchTimeout);
90
- batchTimeout = null;
91
- }
92
-
93
- if (eventQueue.length === 0 || !config) {
94
- return;
95
- }
96
-
97
- const events = eventQueue;
98
- eventQueue = [];
99
-
100
- // In dev mode, log to console instead of sending
101
- if (config.isDevmode) {
102
- console.debug('[Agentuity Analytics] Events:', events);
103
- return;
104
- }
105
-
106
- const payload: AnalyticsBatchPayload = {
107
- org_id: config.orgId,
108
- project_id: config.projectId,
109
- session_id: config.sessionId,
110
- thread_id: config.threadId,
111
- visitor_id: getVisitorId(),
112
- is_devmode: config.isDevmode,
113
- events,
114
- };
115
-
116
- const body = JSON.stringify(payload);
117
-
118
- // Use sendBeacon for reliable delivery
119
- if (typeof navigator !== 'undefined' && navigator.sendBeacon) {
120
- const sent = navigator.sendBeacon(COLLECT_ENDPOINT, body);
121
- if (sent) {
122
- return;
123
- }
124
- }
125
-
126
- // Fallback to fetch with keepalive
127
- if (typeof fetch !== 'undefined') {
128
- fetch(COLLECT_ENDPOINT, {
129
- method: 'POST',
130
- headers: {
131
- 'Content-Type': 'application/json',
132
- },
133
- body,
134
- keepalive: true,
135
- }).catch(() => {
136
- // Silent failure - analytics is best effort
137
- });
138
- }
139
- }
140
-
141
- /**
142
- * Get current queue length (for testing)
143
- */
144
- export function getQueueLength(): number {
145
- return eventQueue.length;
146
- }