@tracelog/lib 0.0.7 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/tracelog.js +899 -828
- package/dist/cjs/constants/limits.constants.d.ts +2 -0
- package/dist/cjs/constants/limits.constants.js +4 -1
- package/dist/cjs/handlers/scroll.handler.d.ts +10 -1
- package/dist/cjs/handlers/scroll.handler.js +157 -17
- package/dist/cjs/managers/sender.manager.js +2 -2
- package/dist/esm/constants/limits.constants.d.ts +2 -0
- package/dist/esm/constants/limits.constants.js +3 -0
- package/dist/esm/handlers/scroll.handler.d.ts +10 -1
- package/dist/esm/handlers/scroll.handler.js +158 -18
- package/dist/esm/managers/sender.manager.js +2 -2
- package/package.json +1 -1
|
@@ -23,3 +23,5 @@ export declare const SYNC_XHR_TIMEOUT_MS = 2000;
|
|
|
23
23
|
export declare const MAX_FINGERPRINTS = 1000;
|
|
24
24
|
export declare const FINGERPRINT_CLEANUP_MULTIPLIER = 10;
|
|
25
25
|
export declare const CLICK_COORDINATE_PRECISION = 10;
|
|
26
|
+
export declare const MAX_RETRY_ATTEMPTS = 3;
|
|
27
|
+
export declare const RETRY_DELAY_MS = 1000;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CLICK_COORDINATE_PRECISION = exports.FINGERPRINT_CLEANUP_MULTIPLIER = exports.MAX_FINGERPRINTS = exports.SYNC_XHR_TIMEOUT_MS = exports.WEB_VITALS_LONG_TASK_SAMPLING = exports.WEB_VITALS_SAMPLING = exports.PRECISION_FOUR_DECIMALS = exports.PRECISION_TWO_DECIMALS = exports.MAX_OBJECT_DEPTH = exports.MAX_ARRAY_LENGTH = exports.MAX_STRING_LENGTH = exports.MAX_TEXT_LENGTH = exports.MAX_CUSTOM_EVENT_ARRAY_SIZE = exports.MAX_CUSTOM_EVENT_KEYS = exports.MAX_CUSTOM_EVENT_STRING_SIZE = exports.MAX_CUSTOM_EVENT_NAME_LENGTH = exports.MAX_SESSION_TIMEOUT_MS = exports.MIN_SESSION_TIMEOUT_MS = exports.MAX_EVENTS_QUEUE_LENGTH = exports.BATCH_SIZE_THRESHOLD = exports.MAX_SAMPLING_RATE = exports.MIN_SAMPLING_RATE = exports.DEFAULT_SAMPLING_RATE = exports.SIGNIFICANT_SCROLL_DELTA = exports.DEFAULT_MOTION_THRESHOLD = void 0;
|
|
3
|
+
exports.RETRY_DELAY_MS = exports.MAX_RETRY_ATTEMPTS = exports.CLICK_COORDINATE_PRECISION = exports.FINGERPRINT_CLEANUP_MULTIPLIER = exports.MAX_FINGERPRINTS = exports.SYNC_XHR_TIMEOUT_MS = exports.WEB_VITALS_LONG_TASK_SAMPLING = exports.WEB_VITALS_SAMPLING = exports.PRECISION_FOUR_DECIMALS = exports.PRECISION_TWO_DECIMALS = exports.MAX_OBJECT_DEPTH = exports.MAX_ARRAY_LENGTH = exports.MAX_STRING_LENGTH = exports.MAX_TEXT_LENGTH = exports.MAX_CUSTOM_EVENT_ARRAY_SIZE = exports.MAX_CUSTOM_EVENT_KEYS = exports.MAX_CUSTOM_EVENT_STRING_SIZE = exports.MAX_CUSTOM_EVENT_NAME_LENGTH = exports.MAX_SESSION_TIMEOUT_MS = exports.MIN_SESSION_TIMEOUT_MS = exports.MAX_EVENTS_QUEUE_LENGTH = exports.BATCH_SIZE_THRESHOLD = exports.MAX_SAMPLING_RATE = exports.MIN_SAMPLING_RATE = exports.DEFAULT_SAMPLING_RATE = exports.SIGNIFICANT_SCROLL_DELTA = exports.DEFAULT_MOTION_THRESHOLD = void 0;
|
|
4
4
|
// Motion and interaction thresholds
|
|
5
5
|
exports.DEFAULT_MOTION_THRESHOLD = 2;
|
|
6
6
|
exports.SIGNIFICANT_SCROLL_DELTA = 10;
|
|
@@ -38,3 +38,6 @@ exports.MAX_FINGERPRINTS = 1000; // Maximum fingerprints stored before cleanup
|
|
|
38
38
|
exports.FINGERPRINT_CLEANUP_MULTIPLIER = 10; // Cleanup fingerprints older than 10x threshold
|
|
39
39
|
// Click coordinate precision
|
|
40
40
|
exports.CLICK_COORDINATE_PRECISION = 10; // Round click coordinates to nearest 10px
|
|
41
|
+
// Retry limits
|
|
42
|
+
exports.MAX_RETRY_ATTEMPTS = 3;
|
|
43
|
+
exports.RETRY_DELAY_MS = 1000;
|
|
@@ -3,6 +3,9 @@ import { StateManager } from '../managers/state.manager';
|
|
|
3
3
|
export declare class ScrollHandler extends StateManager {
|
|
4
4
|
private readonly eventManager;
|
|
5
5
|
private readonly containers;
|
|
6
|
+
private readonly pendingSelectors;
|
|
7
|
+
private mutationObserver;
|
|
8
|
+
private windowFallbackNeeded;
|
|
6
9
|
constructor(eventManager: EventManager);
|
|
7
10
|
startTracking(): void;
|
|
8
11
|
stopTracking(): void;
|
|
@@ -11,6 +14,12 @@ export declare class ScrollHandler extends StateManager {
|
|
|
11
14
|
private getScrollTop;
|
|
12
15
|
private getViewportHeight;
|
|
13
16
|
private getScrollHeight;
|
|
14
|
-
private
|
|
17
|
+
private getViewportWidth;
|
|
18
|
+
private getScrollWidth;
|
|
19
|
+
private hasScrollableOverflow;
|
|
15
20
|
private safeQuerySelector;
|
|
21
|
+
private setupPendingSelectors;
|
|
22
|
+
private startMutationObserver;
|
|
23
|
+
private checkPendingSelectors;
|
|
24
|
+
private retryPendingSelectors;
|
|
16
25
|
}
|
|
@@ -9,21 +9,44 @@ class ScrollHandler extends state_manager_1.StateManager {
|
|
|
9
9
|
constructor(eventManager) {
|
|
10
10
|
super();
|
|
11
11
|
this.containers = [];
|
|
12
|
+
this.pendingSelectors = [];
|
|
13
|
+
this.mutationObserver = null;
|
|
14
|
+
this.windowFallbackNeeded = false;
|
|
12
15
|
this.eventManager = eventManager;
|
|
13
16
|
}
|
|
14
17
|
startTracking() {
|
|
15
18
|
const raw = this.get('config').scrollContainerSelectors;
|
|
16
19
|
const selectors = Array.isArray(raw) ? raw : typeof raw === 'string' ? [raw] : [];
|
|
17
20
|
logging_1.debugLog.debug('ScrollHandler', 'Starting scroll tracking', { selectorsCount: selectors.length });
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
.
|
|
21
|
-
|
|
22
|
-
elements.push(window);
|
|
21
|
+
// No custom selectors: track window immediately
|
|
22
|
+
if (selectors.length === 0) {
|
|
23
|
+
this.setupScrollContainer(window);
|
|
24
|
+
return;
|
|
23
25
|
}
|
|
24
|
-
|
|
26
|
+
const foundElements = [];
|
|
27
|
+
const notFoundSelectors = [];
|
|
28
|
+
for (const selector of selectors) {
|
|
29
|
+
const element = this.safeQuerySelector(selector);
|
|
30
|
+
if (element instanceof HTMLElement) {
|
|
31
|
+
foundElements.push(element);
|
|
32
|
+
}
|
|
33
|
+
else if (element === null) {
|
|
34
|
+
notFoundSelectors.push(selector);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Setup found elements
|
|
38
|
+
for (const element of foundElements) {
|
|
25
39
|
this.setupScrollContainer(element);
|
|
26
40
|
}
|
|
41
|
+
// If we have pending selectors, set up retry logic
|
|
42
|
+
if (notFoundSelectors.length > 0) {
|
|
43
|
+
this.windowFallbackNeeded = true;
|
|
44
|
+
this.setupPendingSelectors(notFoundSelectors);
|
|
45
|
+
}
|
|
46
|
+
else if (this.containers.length === 0) {
|
|
47
|
+
// No elements found and none pending: use window fallback immediately
|
|
48
|
+
this.setupScrollContainer(window);
|
|
49
|
+
}
|
|
27
50
|
}
|
|
28
51
|
stopTracking() {
|
|
29
52
|
logging_1.debugLog.debug('ScrollHandler', 'Stopping scroll tracking', { containersCount: this.containers.length });
|
|
@@ -39,10 +62,15 @@ class ScrollHandler extends state_manager_1.StateManager {
|
|
|
39
62
|
}
|
|
40
63
|
}
|
|
41
64
|
this.containers.length = 0;
|
|
65
|
+
if (this.mutationObserver) {
|
|
66
|
+
this.mutationObserver.disconnect();
|
|
67
|
+
this.mutationObserver = null;
|
|
68
|
+
}
|
|
69
|
+
this.pendingSelectors.length = 0;
|
|
42
70
|
}
|
|
43
71
|
setupScrollContainer(element) {
|
|
44
|
-
// Skip
|
|
45
|
-
if (
|
|
72
|
+
// Skip if already tracking this element
|
|
73
|
+
if (this.containers.some((c) => c.element === element)) {
|
|
46
74
|
return;
|
|
47
75
|
}
|
|
48
76
|
const container = {
|
|
@@ -84,9 +112,28 @@ class ScrollHandler extends state_manager_1.StateManager {
|
|
|
84
112
|
const scrollTop = this.getScrollTop(element);
|
|
85
113
|
const viewportHeight = this.getViewportHeight(element);
|
|
86
114
|
const scrollHeight = this.getScrollHeight(element);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
115
|
+
const viewportWidth = this.getViewportWidth(element);
|
|
116
|
+
const scrollWidth = this.getScrollWidth(element);
|
|
117
|
+
// Dynamic validation: check if element is scrollable at runtime
|
|
118
|
+
if (element instanceof HTMLElement) {
|
|
119
|
+
// Check if element has scrollable overflow style (can change dynamically)
|
|
120
|
+
if (!this.hasScrollableOverflow(element)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
// Check if content exceeds viewport (vertical OR horizontal)
|
|
124
|
+
const hasVerticalScroll = scrollHeight > viewportHeight;
|
|
125
|
+
const hasHorizontalScroll = scrollWidth > viewportWidth;
|
|
126
|
+
if (!hasVerticalScroll && !hasHorizontalScroll) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// For Window: check if content is scrollable (vertical or horizontal)
|
|
131
|
+
if (element instanceof Window) {
|
|
132
|
+
const hasVerticalScroll = scrollHeight > viewportHeight;
|
|
133
|
+
const hasHorizontalScroll = scrollWidth > viewportWidth;
|
|
134
|
+
if (!hasVerticalScroll && !hasHorizontalScroll) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
90
137
|
}
|
|
91
138
|
const direction = scrollTop > lastScrollPos ? types_1.ScrollDirection.DOWN : types_1.ScrollDirection.UP;
|
|
92
139
|
const depth = scrollHeight > viewportHeight
|
|
@@ -109,17 +156,20 @@ class ScrollHandler extends state_manager_1.StateManager {
|
|
|
109
156
|
getScrollHeight(element) {
|
|
110
157
|
return element instanceof Window ? document.documentElement.scrollHeight : element.scrollHeight;
|
|
111
158
|
}
|
|
112
|
-
|
|
159
|
+
getViewportWidth(element) {
|
|
160
|
+
return element instanceof Window ? window.innerWidth : element.clientWidth;
|
|
161
|
+
}
|
|
162
|
+
getScrollWidth(element) {
|
|
163
|
+
return element instanceof Window ? document.documentElement.scrollWidth : element.scrollWidth;
|
|
164
|
+
}
|
|
165
|
+
hasScrollableOverflow(element) {
|
|
113
166
|
const style = getComputedStyle(element);
|
|
114
|
-
|
|
167
|
+
return (style.overflowY === 'auto' ||
|
|
115
168
|
style.overflowY === 'scroll' ||
|
|
116
169
|
style.overflowX === 'auto' ||
|
|
117
170
|
style.overflowX === 'scroll' ||
|
|
118
171
|
style.overflow === 'auto' ||
|
|
119
|
-
style.overflow === 'scroll';
|
|
120
|
-
// Element must have scrollable overflow AND content that exceeds the container
|
|
121
|
-
const hasOverflowContent = element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
|
|
122
|
-
return hasScrollableOverflow && hasOverflowContent;
|
|
172
|
+
style.overflow === 'scroll');
|
|
123
173
|
}
|
|
124
174
|
safeQuerySelector(selector) {
|
|
125
175
|
try {
|
|
@@ -134,5 +184,95 @@ class ScrollHandler extends state_manager_1.StateManager {
|
|
|
134
184
|
return null;
|
|
135
185
|
}
|
|
136
186
|
}
|
|
187
|
+
setupPendingSelectors(selectors) {
|
|
188
|
+
logging_1.debugLog.debug('ScrollHandler', 'Setting up pending selectors with retry logic', {
|
|
189
|
+
selectors,
|
|
190
|
+
maxRetries: constants_1.MAX_RETRY_ATTEMPTS,
|
|
191
|
+
});
|
|
192
|
+
for (const selector of selectors) {
|
|
193
|
+
this.pendingSelectors.push({ selector, retryCount: 0 });
|
|
194
|
+
}
|
|
195
|
+
this.startMutationObserver();
|
|
196
|
+
this.retryPendingSelectors();
|
|
197
|
+
}
|
|
198
|
+
startMutationObserver() {
|
|
199
|
+
if (this.mutationObserver)
|
|
200
|
+
return;
|
|
201
|
+
this.mutationObserver = new MutationObserver(() => {
|
|
202
|
+
this.checkPendingSelectors();
|
|
203
|
+
});
|
|
204
|
+
this.mutationObserver.observe(document.body, {
|
|
205
|
+
childList: true,
|
|
206
|
+
subtree: true,
|
|
207
|
+
});
|
|
208
|
+
logging_1.debugLog.debug('ScrollHandler', 'MutationObserver started for pending selectors');
|
|
209
|
+
}
|
|
210
|
+
checkPendingSelectors() {
|
|
211
|
+
const remaining = [];
|
|
212
|
+
for (const pending of this.pendingSelectors) {
|
|
213
|
+
const element = this.safeQuerySelector(pending.selector);
|
|
214
|
+
if (element instanceof HTMLElement) {
|
|
215
|
+
logging_1.debugLog.debug('ScrollHandler', 'Found pending selector', { selector: pending.selector });
|
|
216
|
+
this.setupScrollContainer(element);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
remaining.push(pending);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
this.pendingSelectors.length = 0;
|
|
223
|
+
this.pendingSelectors.push(...remaining);
|
|
224
|
+
if (this.pendingSelectors.length === 0 && this.mutationObserver) {
|
|
225
|
+
this.mutationObserver.disconnect();
|
|
226
|
+
this.mutationObserver = null;
|
|
227
|
+
logging_1.debugLog.debug('ScrollHandler', 'All pending selectors resolved, MutationObserver stopped');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
retryPendingSelectors() {
|
|
231
|
+
setTimeout(() => {
|
|
232
|
+
if (this.pendingSelectors.length === 0)
|
|
233
|
+
return;
|
|
234
|
+
const remaining = [];
|
|
235
|
+
for (const pending of this.pendingSelectors) {
|
|
236
|
+
pending.retryCount++;
|
|
237
|
+
const element = this.safeQuerySelector(pending.selector);
|
|
238
|
+
if (element instanceof HTMLElement) {
|
|
239
|
+
logging_1.debugLog.debug('ScrollHandler', 'Retry found pending selector', {
|
|
240
|
+
selector: pending.selector,
|
|
241
|
+
retryCount: pending.retryCount,
|
|
242
|
+
});
|
|
243
|
+
this.setupScrollContainer(element);
|
|
244
|
+
}
|
|
245
|
+
else if (pending.retryCount < constants_1.MAX_RETRY_ATTEMPTS) {
|
|
246
|
+
remaining.push(pending);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
logging_1.debugLog.clientWarn('ScrollHandler', 'Selector not found after max retries', {
|
|
250
|
+
selector: pending.selector,
|
|
251
|
+
maxRetries: constants_1.MAX_RETRY_ATTEMPTS,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
this.pendingSelectors.length = 0;
|
|
256
|
+
this.pendingSelectors.push(...remaining);
|
|
257
|
+
if (this.pendingSelectors.length > 0) {
|
|
258
|
+
this.retryPendingSelectors();
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
// All retries complete
|
|
262
|
+
if (this.mutationObserver) {
|
|
263
|
+
this.mutationObserver.disconnect();
|
|
264
|
+
this.mutationObserver = null;
|
|
265
|
+
}
|
|
266
|
+
// Apply window fallback if needed and no containers were set up
|
|
267
|
+
if (this.windowFallbackNeeded && this.containers.length === 0) {
|
|
268
|
+
logging_1.debugLog.debug('ScrollHandler', 'No scroll containers found, using window fallback');
|
|
269
|
+
this.setupScrollContainer(window);
|
|
270
|
+
}
|
|
271
|
+
logging_1.debugLog.debug('ScrollHandler', 'All pending selectors resolved or timed out', {
|
|
272
|
+
containersCount: this.containers.length,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}, constants_1.RETRY_DELAY_MS);
|
|
276
|
+
}
|
|
137
277
|
}
|
|
138
278
|
exports.ScrollHandler = ScrollHandler;
|
|
@@ -87,7 +87,7 @@ class SenderManager extends state_manager_1.StateManager {
|
|
|
87
87
|
const response = await fetch(url, {
|
|
88
88
|
method: 'POST',
|
|
89
89
|
mode: 'cors',
|
|
90
|
-
credentials: '
|
|
90
|
+
credentials: 'include',
|
|
91
91
|
body: payload,
|
|
92
92
|
headers: {
|
|
93
93
|
'Content-Type': 'application/json',
|
|
@@ -131,7 +131,7 @@ class SenderManager extends state_manager_1.StateManager {
|
|
|
131
131
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
132
132
|
xhr.setRequestHeader('Origin', window.location.origin);
|
|
133
133
|
xhr.setRequestHeader('Referer', window.location.href);
|
|
134
|
-
xhr.withCredentials =
|
|
134
|
+
xhr.withCredentials = true;
|
|
135
135
|
xhr.timeout = constants_1.SYNC_XHR_TIMEOUT_MS;
|
|
136
136
|
xhr.send(payload);
|
|
137
137
|
return xhr.status >= 200 && xhr.status < 300;
|
|
@@ -23,3 +23,5 @@ export declare const SYNC_XHR_TIMEOUT_MS = 2000;
|
|
|
23
23
|
export declare const MAX_FINGERPRINTS = 1000;
|
|
24
24
|
export declare const FINGERPRINT_CLEANUP_MULTIPLIER = 10;
|
|
25
25
|
export declare const CLICK_COORDINATE_PRECISION = 10;
|
|
26
|
+
export declare const MAX_RETRY_ATTEMPTS = 3;
|
|
27
|
+
export declare const RETRY_DELAY_MS = 1000;
|
|
@@ -35,3 +35,6 @@ export const MAX_FINGERPRINTS = 1000; // Maximum fingerprints stored before clea
|
|
|
35
35
|
export const FINGERPRINT_CLEANUP_MULTIPLIER = 10; // Cleanup fingerprints older than 10x threshold
|
|
36
36
|
// Click coordinate precision
|
|
37
37
|
export const CLICK_COORDINATE_PRECISION = 10; // Round click coordinates to nearest 10px
|
|
38
|
+
// Retry limits
|
|
39
|
+
export const MAX_RETRY_ATTEMPTS = 3;
|
|
40
|
+
export const RETRY_DELAY_MS = 1000;
|
|
@@ -3,6 +3,9 @@ import { StateManager } from '../managers/state.manager';
|
|
|
3
3
|
export declare class ScrollHandler extends StateManager {
|
|
4
4
|
private readonly eventManager;
|
|
5
5
|
private readonly containers;
|
|
6
|
+
private readonly pendingSelectors;
|
|
7
|
+
private mutationObserver;
|
|
8
|
+
private windowFallbackNeeded;
|
|
6
9
|
constructor(eventManager: EventManager);
|
|
7
10
|
startTracking(): void;
|
|
8
11
|
stopTracking(): void;
|
|
@@ -11,6 +14,12 @@ export declare class ScrollHandler extends StateManager {
|
|
|
11
14
|
private getScrollTop;
|
|
12
15
|
private getViewportHeight;
|
|
13
16
|
private getScrollHeight;
|
|
14
|
-
private
|
|
17
|
+
private getViewportWidth;
|
|
18
|
+
private getScrollWidth;
|
|
19
|
+
private hasScrollableOverflow;
|
|
15
20
|
private safeQuerySelector;
|
|
21
|
+
private setupPendingSelectors;
|
|
22
|
+
private startMutationObserver;
|
|
23
|
+
private checkPendingSelectors;
|
|
24
|
+
private retryPendingSelectors;
|
|
16
25
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SCROLL_DEBOUNCE_TIME_MS, SIGNIFICANT_SCROLL_DELTA } from '../constants';
|
|
1
|
+
import { MAX_RETRY_ATTEMPTS, RETRY_DELAY_MS, SCROLL_DEBOUNCE_TIME_MS, SIGNIFICANT_SCROLL_DELTA } from '../constants';
|
|
2
2
|
import { EventType, ScrollDirection } from '../types';
|
|
3
3
|
import { StateManager } from '../managers/state.manager';
|
|
4
4
|
import { debugLog } from '../utils/logging';
|
|
@@ -6,21 +6,44 @@ export class ScrollHandler extends StateManager {
|
|
|
6
6
|
constructor(eventManager) {
|
|
7
7
|
super();
|
|
8
8
|
this.containers = [];
|
|
9
|
+
this.pendingSelectors = [];
|
|
10
|
+
this.mutationObserver = null;
|
|
11
|
+
this.windowFallbackNeeded = false;
|
|
9
12
|
this.eventManager = eventManager;
|
|
10
13
|
}
|
|
11
14
|
startTracking() {
|
|
12
15
|
const raw = this.get('config').scrollContainerSelectors;
|
|
13
16
|
const selectors = Array.isArray(raw) ? raw : typeof raw === 'string' ? [raw] : [];
|
|
14
17
|
debugLog.debug('ScrollHandler', 'Starting scroll tracking', { selectorsCount: selectors.length });
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.
|
|
18
|
-
|
|
19
|
-
elements.push(window);
|
|
18
|
+
// No custom selectors: track window immediately
|
|
19
|
+
if (selectors.length === 0) {
|
|
20
|
+
this.setupScrollContainer(window);
|
|
21
|
+
return;
|
|
20
22
|
}
|
|
21
|
-
|
|
23
|
+
const foundElements = [];
|
|
24
|
+
const notFoundSelectors = [];
|
|
25
|
+
for (const selector of selectors) {
|
|
26
|
+
const element = this.safeQuerySelector(selector);
|
|
27
|
+
if (element instanceof HTMLElement) {
|
|
28
|
+
foundElements.push(element);
|
|
29
|
+
}
|
|
30
|
+
else if (element === null) {
|
|
31
|
+
notFoundSelectors.push(selector);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Setup found elements
|
|
35
|
+
for (const element of foundElements) {
|
|
22
36
|
this.setupScrollContainer(element);
|
|
23
37
|
}
|
|
38
|
+
// If we have pending selectors, set up retry logic
|
|
39
|
+
if (notFoundSelectors.length > 0) {
|
|
40
|
+
this.windowFallbackNeeded = true;
|
|
41
|
+
this.setupPendingSelectors(notFoundSelectors);
|
|
42
|
+
}
|
|
43
|
+
else if (this.containers.length === 0) {
|
|
44
|
+
// No elements found and none pending: use window fallback immediately
|
|
45
|
+
this.setupScrollContainer(window);
|
|
46
|
+
}
|
|
24
47
|
}
|
|
25
48
|
stopTracking() {
|
|
26
49
|
debugLog.debug('ScrollHandler', 'Stopping scroll tracking', { containersCount: this.containers.length });
|
|
@@ -36,10 +59,15 @@ export class ScrollHandler extends StateManager {
|
|
|
36
59
|
}
|
|
37
60
|
}
|
|
38
61
|
this.containers.length = 0;
|
|
62
|
+
if (this.mutationObserver) {
|
|
63
|
+
this.mutationObserver.disconnect();
|
|
64
|
+
this.mutationObserver = null;
|
|
65
|
+
}
|
|
66
|
+
this.pendingSelectors.length = 0;
|
|
39
67
|
}
|
|
40
68
|
setupScrollContainer(element) {
|
|
41
|
-
// Skip
|
|
42
|
-
if (
|
|
69
|
+
// Skip if already tracking this element
|
|
70
|
+
if (this.containers.some((c) => c.element === element)) {
|
|
43
71
|
return;
|
|
44
72
|
}
|
|
45
73
|
const container = {
|
|
@@ -81,9 +109,28 @@ export class ScrollHandler extends StateManager {
|
|
|
81
109
|
const scrollTop = this.getScrollTop(element);
|
|
82
110
|
const viewportHeight = this.getViewportHeight(element);
|
|
83
111
|
const scrollHeight = this.getScrollHeight(element);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
112
|
+
const viewportWidth = this.getViewportWidth(element);
|
|
113
|
+
const scrollWidth = this.getScrollWidth(element);
|
|
114
|
+
// Dynamic validation: check if element is scrollable at runtime
|
|
115
|
+
if (element instanceof HTMLElement) {
|
|
116
|
+
// Check if element has scrollable overflow style (can change dynamically)
|
|
117
|
+
if (!this.hasScrollableOverflow(element)) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
// Check if content exceeds viewport (vertical OR horizontal)
|
|
121
|
+
const hasVerticalScroll = scrollHeight > viewportHeight;
|
|
122
|
+
const hasHorizontalScroll = scrollWidth > viewportWidth;
|
|
123
|
+
if (!hasVerticalScroll && !hasHorizontalScroll) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// For Window: check if content is scrollable (vertical or horizontal)
|
|
128
|
+
if (element instanceof Window) {
|
|
129
|
+
const hasVerticalScroll = scrollHeight > viewportHeight;
|
|
130
|
+
const hasHorizontalScroll = scrollWidth > viewportWidth;
|
|
131
|
+
if (!hasVerticalScroll && !hasHorizontalScroll) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
87
134
|
}
|
|
88
135
|
const direction = scrollTop > lastScrollPos ? ScrollDirection.DOWN : ScrollDirection.UP;
|
|
89
136
|
const depth = scrollHeight > viewportHeight
|
|
@@ -106,17 +153,20 @@ export class ScrollHandler extends StateManager {
|
|
|
106
153
|
getScrollHeight(element) {
|
|
107
154
|
return element instanceof Window ? document.documentElement.scrollHeight : element.scrollHeight;
|
|
108
155
|
}
|
|
109
|
-
|
|
156
|
+
getViewportWidth(element) {
|
|
157
|
+
return element instanceof Window ? window.innerWidth : element.clientWidth;
|
|
158
|
+
}
|
|
159
|
+
getScrollWidth(element) {
|
|
160
|
+
return element instanceof Window ? document.documentElement.scrollWidth : element.scrollWidth;
|
|
161
|
+
}
|
|
162
|
+
hasScrollableOverflow(element) {
|
|
110
163
|
const style = getComputedStyle(element);
|
|
111
|
-
|
|
164
|
+
return (style.overflowY === 'auto' ||
|
|
112
165
|
style.overflowY === 'scroll' ||
|
|
113
166
|
style.overflowX === 'auto' ||
|
|
114
167
|
style.overflowX === 'scroll' ||
|
|
115
168
|
style.overflow === 'auto' ||
|
|
116
|
-
style.overflow === 'scroll';
|
|
117
|
-
// Element must have scrollable overflow AND content that exceeds the container
|
|
118
|
-
const hasOverflowContent = element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
|
|
119
|
-
return hasScrollableOverflow && hasOverflowContent;
|
|
169
|
+
style.overflow === 'scroll');
|
|
120
170
|
}
|
|
121
171
|
safeQuerySelector(selector) {
|
|
122
172
|
try {
|
|
@@ -131,4 +181,94 @@ export class ScrollHandler extends StateManager {
|
|
|
131
181
|
return null;
|
|
132
182
|
}
|
|
133
183
|
}
|
|
184
|
+
setupPendingSelectors(selectors) {
|
|
185
|
+
debugLog.debug('ScrollHandler', 'Setting up pending selectors with retry logic', {
|
|
186
|
+
selectors,
|
|
187
|
+
maxRetries: MAX_RETRY_ATTEMPTS,
|
|
188
|
+
});
|
|
189
|
+
for (const selector of selectors) {
|
|
190
|
+
this.pendingSelectors.push({ selector, retryCount: 0 });
|
|
191
|
+
}
|
|
192
|
+
this.startMutationObserver();
|
|
193
|
+
this.retryPendingSelectors();
|
|
194
|
+
}
|
|
195
|
+
startMutationObserver() {
|
|
196
|
+
if (this.mutationObserver)
|
|
197
|
+
return;
|
|
198
|
+
this.mutationObserver = new MutationObserver(() => {
|
|
199
|
+
this.checkPendingSelectors();
|
|
200
|
+
});
|
|
201
|
+
this.mutationObserver.observe(document.body, {
|
|
202
|
+
childList: true,
|
|
203
|
+
subtree: true,
|
|
204
|
+
});
|
|
205
|
+
debugLog.debug('ScrollHandler', 'MutationObserver started for pending selectors');
|
|
206
|
+
}
|
|
207
|
+
checkPendingSelectors() {
|
|
208
|
+
const remaining = [];
|
|
209
|
+
for (const pending of this.pendingSelectors) {
|
|
210
|
+
const element = this.safeQuerySelector(pending.selector);
|
|
211
|
+
if (element instanceof HTMLElement) {
|
|
212
|
+
debugLog.debug('ScrollHandler', 'Found pending selector', { selector: pending.selector });
|
|
213
|
+
this.setupScrollContainer(element);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
remaining.push(pending);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
this.pendingSelectors.length = 0;
|
|
220
|
+
this.pendingSelectors.push(...remaining);
|
|
221
|
+
if (this.pendingSelectors.length === 0 && this.mutationObserver) {
|
|
222
|
+
this.mutationObserver.disconnect();
|
|
223
|
+
this.mutationObserver = null;
|
|
224
|
+
debugLog.debug('ScrollHandler', 'All pending selectors resolved, MutationObserver stopped');
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
retryPendingSelectors() {
|
|
228
|
+
setTimeout(() => {
|
|
229
|
+
if (this.pendingSelectors.length === 0)
|
|
230
|
+
return;
|
|
231
|
+
const remaining = [];
|
|
232
|
+
for (const pending of this.pendingSelectors) {
|
|
233
|
+
pending.retryCount++;
|
|
234
|
+
const element = this.safeQuerySelector(pending.selector);
|
|
235
|
+
if (element instanceof HTMLElement) {
|
|
236
|
+
debugLog.debug('ScrollHandler', 'Retry found pending selector', {
|
|
237
|
+
selector: pending.selector,
|
|
238
|
+
retryCount: pending.retryCount,
|
|
239
|
+
});
|
|
240
|
+
this.setupScrollContainer(element);
|
|
241
|
+
}
|
|
242
|
+
else if (pending.retryCount < MAX_RETRY_ATTEMPTS) {
|
|
243
|
+
remaining.push(pending);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
debugLog.clientWarn('ScrollHandler', 'Selector not found after max retries', {
|
|
247
|
+
selector: pending.selector,
|
|
248
|
+
maxRetries: MAX_RETRY_ATTEMPTS,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
this.pendingSelectors.length = 0;
|
|
253
|
+
this.pendingSelectors.push(...remaining);
|
|
254
|
+
if (this.pendingSelectors.length > 0) {
|
|
255
|
+
this.retryPendingSelectors();
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
// All retries complete
|
|
259
|
+
if (this.mutationObserver) {
|
|
260
|
+
this.mutationObserver.disconnect();
|
|
261
|
+
this.mutationObserver = null;
|
|
262
|
+
}
|
|
263
|
+
// Apply window fallback if needed and no containers were set up
|
|
264
|
+
if (this.windowFallbackNeeded && this.containers.length === 0) {
|
|
265
|
+
debugLog.debug('ScrollHandler', 'No scroll containers found, using window fallback');
|
|
266
|
+
this.setupScrollContainer(window);
|
|
267
|
+
}
|
|
268
|
+
debugLog.debug('ScrollHandler', 'All pending selectors resolved or timed out', {
|
|
269
|
+
containersCount: this.containers.length,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}, RETRY_DELAY_MS);
|
|
273
|
+
}
|
|
134
274
|
}
|
|
@@ -84,7 +84,7 @@ export class SenderManager extends StateManager {
|
|
|
84
84
|
const response = await fetch(url, {
|
|
85
85
|
method: 'POST',
|
|
86
86
|
mode: 'cors',
|
|
87
|
-
credentials: '
|
|
87
|
+
credentials: 'include',
|
|
88
88
|
body: payload,
|
|
89
89
|
headers: {
|
|
90
90
|
'Content-Type': 'application/json',
|
|
@@ -128,7 +128,7 @@ export class SenderManager extends StateManager {
|
|
|
128
128
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
129
129
|
xhr.setRequestHeader('Origin', window.location.origin);
|
|
130
130
|
xhr.setRequestHeader('Referer', window.location.href);
|
|
131
|
-
xhr.withCredentials =
|
|
131
|
+
xhr.withCredentials = true;
|
|
132
132
|
xhr.timeout = SYNC_XHR_TIMEOUT_MS;
|
|
133
133
|
xhr.send(payload);
|
|
134
134
|
return xhr.status >= 200 && xhr.status < 300;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@tracelog/lib",
|
|
3
3
|
"description": "JavaScript library for web analytics and real-time event tracking",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "0.0
|
|
5
|
+
"version": "0.1.0",
|
|
6
6
|
"main": "./dist/cjs/public-api.js",
|
|
7
7
|
"module": "./dist/esm/public-api.js",
|
|
8
8
|
"types": "./dist/esm/public-api.d.ts",
|