@tracelog/lib 0.4.1 → 0.5.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 +576 -610
- package/dist/cjs/api.d.ts +1 -53
- package/dist/cjs/api.js +0 -59
- package/dist/cjs/app.constants.d.ts +1 -1
- package/dist/cjs/app.d.ts +1 -5
- package/dist/cjs/app.js +4 -12
- package/dist/cjs/constants/api.constants.d.ts +5 -2
- package/dist/cjs/constants/api.constants.js +5 -14
- package/dist/cjs/constants/config.constants.d.ts +3 -3
- package/dist/cjs/constants/config.constants.js +3 -3
- package/dist/cjs/constants/error.constants.d.ts +7 -2
- package/dist/cjs/constants/error.constants.js +13 -2
- package/dist/cjs/handlers/click.handler.js +0 -6
- package/dist/cjs/handlers/error.handler.js +9 -0
- package/dist/cjs/handlers/scroll.handler.js +0 -5
- package/dist/cjs/handlers/session.handler.js +5 -2
- package/dist/cjs/integrations/google-analytics.integration.d.ts +1 -1
- package/dist/cjs/integrations/google-analytics.integration.js +2 -1
- package/dist/cjs/managers/api.manager.d.ts +1 -1
- package/dist/cjs/managers/api.manager.js +3 -3
- package/dist/cjs/managers/config.builder.d.ts +33 -0
- package/dist/cjs/managers/config.builder.js +116 -0
- package/dist/cjs/managers/config.manager.d.ts +13 -14
- package/dist/cjs/managers/config.manager.js +52 -58
- package/dist/cjs/managers/event.manager.d.ts +0 -45
- package/dist/cjs/managers/event.manager.js +14 -67
- package/dist/cjs/managers/sender.manager.d.ts +1 -28
- package/dist/cjs/managers/sender.manager.js +43 -73
- package/dist/cjs/managers/session.manager.d.ts +2 -49
- package/dist/cjs/managers/session.manager.js +37 -79
- package/dist/cjs/managers/state.manager.d.ts +1 -28
- package/dist/cjs/managers/state.manager.js +5 -33
- package/dist/cjs/managers/storage.manager.d.ts +6 -0
- package/dist/cjs/managers/storage.manager.js +18 -1
- package/dist/cjs/public-api.d.ts +1 -1
- package/dist/cjs/test-bridge.d.ts +3 -2
- package/dist/cjs/test-bridge.js +34 -7
- package/dist/cjs/types/api.types.d.ts +24 -8
- package/dist/cjs/types/api.types.js +24 -8
- package/dist/cjs/types/event.types.d.ts +2 -3
- package/dist/cjs/types/event.types.js +0 -1
- package/dist/cjs/types/test-bridge.types.d.ts +2 -1
- package/dist/cjs/utils/logging/debug-logger.utils.d.ts +1 -2
- package/dist/cjs/utils/logging/debug-logger.utils.js +2 -3
- package/dist/cjs/utils/validations/config-validations.utils.d.ts +1 -26
- package/dist/cjs/utils/validations/config-validations.utils.js +5 -117
- package/dist/cjs/utils/validations/event-validations.utils.d.ts +2 -2
- package/dist/cjs/utils/validations/metadata-validations.utils.d.ts +3 -3
- package/dist/cjs/utils/validations/metadata-validations.utils.js +41 -3
- package/dist/esm/api.d.ts +1 -53
- package/dist/esm/api.js +0 -59
- package/dist/esm/app.constants.d.ts +1 -1
- package/dist/esm/app.d.ts +1 -5
- package/dist/esm/app.js +5 -13
- package/dist/esm/constants/api.constants.d.ts +5 -2
- package/dist/esm/constants/api.constants.js +5 -13
- package/dist/esm/constants/config.constants.d.ts +3 -3
- package/dist/esm/constants/config.constants.js +3 -3
- package/dist/esm/constants/error.constants.d.ts +7 -2
- package/dist/esm/constants/error.constants.js +12 -1
- package/dist/esm/handlers/click.handler.js +0 -6
- package/dist/esm/handlers/error.handler.js +10 -1
- package/dist/esm/handlers/scroll.handler.js +0 -5
- package/dist/esm/handlers/session.handler.js +5 -2
- package/dist/esm/integrations/google-analytics.integration.d.ts +1 -1
- package/dist/esm/integrations/google-analytics.integration.js +2 -1
- package/dist/esm/managers/api.manager.d.ts +1 -1
- package/dist/esm/managers/api.manager.js +3 -3
- package/dist/esm/managers/config.builder.d.ts +33 -0
- package/dist/esm/managers/config.builder.js +112 -0
- package/dist/esm/managers/config.manager.d.ts +13 -14
- package/dist/esm/managers/config.manager.js +54 -60
- package/dist/esm/managers/event.manager.d.ts +0 -45
- package/dist/esm/managers/event.manager.js +14 -67
- package/dist/esm/managers/sender.manager.d.ts +1 -28
- package/dist/esm/managers/sender.manager.js +44 -74
- package/dist/esm/managers/session.manager.d.ts +2 -49
- package/dist/esm/managers/session.manager.js +37 -79
- package/dist/esm/managers/state.manager.d.ts +1 -28
- package/dist/esm/managers/state.manager.js +4 -33
- package/dist/esm/managers/storage.manager.d.ts +6 -0
- package/dist/esm/managers/storage.manager.js +18 -1
- package/dist/esm/public-api.d.ts +1 -1
- package/dist/esm/test-bridge.d.ts +3 -2
- package/dist/esm/test-bridge.js +34 -7
- package/dist/esm/types/api.types.d.ts +24 -8
- package/dist/esm/types/api.types.js +24 -8
- package/dist/esm/types/event.types.d.ts +2 -3
- package/dist/esm/types/event.types.js +0 -1
- package/dist/esm/types/test-bridge.types.d.ts +2 -1
- package/dist/esm/utils/logging/debug-logger.utils.d.ts +1 -2
- package/dist/esm/utils/logging/debug-logger.utils.js +3 -4
- package/dist/esm/utils/validations/config-validations.utils.d.ts +1 -26
- package/dist/esm/utils/validations/config-validations.utils.js +5 -114
- package/dist/esm/utils/validations/event-validations.utils.d.ts +2 -2
- package/dist/esm/utils/validations/metadata-validations.utils.d.ts +3 -3
- package/dist/esm/utils/validations/metadata-validations.utils.js +41 -3
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ const types_1 = require("../types");
|
|
|
6
6
|
const logging_1 = require("../utils/logging");
|
|
7
7
|
const state_manager_1 = require("./state.manager");
|
|
8
8
|
class SessionManager extends state_manager_1.StateManager {
|
|
9
|
-
constructor(storageManager, eventManager) {
|
|
9
|
+
constructor(storageManager, eventManager, projectId) {
|
|
10
10
|
super();
|
|
11
11
|
this.sessionTimeoutId = null;
|
|
12
12
|
this.broadcastChannel = null;
|
|
@@ -16,10 +16,8 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
16
16
|
this.isTracking = false;
|
|
17
17
|
this.storageManager = storageManager;
|
|
18
18
|
this.eventManager = eventManager;
|
|
19
|
+
this.projectId = projectId;
|
|
19
20
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Initialize cross-tab synchronization
|
|
22
|
-
*/
|
|
23
21
|
initCrossTabSync() {
|
|
24
22
|
if (typeof BroadcastChannel === 'undefined') {
|
|
25
23
|
logging_1.debugLog.warn('SessionManager', 'BroadcastChannel not supported');
|
|
@@ -48,41 +46,38 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
48
46
|
}
|
|
49
47
|
};
|
|
50
48
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Share session with other tabs
|
|
53
|
-
*/
|
|
54
49
|
shareSession(sessionId) {
|
|
55
|
-
this.broadcastChannel
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
if (this.broadcastChannel && typeof this.broadcastChannel.postMessage === 'function') {
|
|
51
|
+
this.broadcastChannel.postMessage({
|
|
52
|
+
action: 'session_start',
|
|
53
|
+
projectId: this.getProjectId(),
|
|
54
|
+
sessionId,
|
|
55
|
+
timestamp: Date.now(),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
61
58
|
}
|
|
62
59
|
broadcastSessionEnd(sessionId, reason) {
|
|
63
60
|
if (!sessionId) {
|
|
64
61
|
return;
|
|
65
62
|
}
|
|
66
|
-
this.broadcastChannel
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
if (this.broadcastChannel && typeof this.broadcastChannel.postMessage === 'function') {
|
|
64
|
+
this.broadcastChannel.postMessage({
|
|
65
|
+
action: 'session_end',
|
|
66
|
+
projectId: this.getProjectId(),
|
|
67
|
+
sessionId,
|
|
68
|
+
reason,
|
|
69
|
+
timestamp: Date.now(),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
73
72
|
}
|
|
74
|
-
/**
|
|
75
|
-
* Cleanup cross-tab sync
|
|
76
|
-
*/
|
|
77
73
|
cleanupCrossTabSync() {
|
|
78
74
|
if (this.broadcastChannel) {
|
|
79
|
-
this.broadcastChannel.close
|
|
75
|
+
if (typeof this.broadcastChannel.close === 'function') {
|
|
76
|
+
this.broadcastChannel.close();
|
|
77
|
+
}
|
|
80
78
|
this.broadcastChannel = null;
|
|
81
79
|
}
|
|
82
80
|
}
|
|
83
|
-
/**
|
|
84
|
-
* Recover session from localStorage if it exists and hasn't expired
|
|
85
|
-
*/
|
|
86
81
|
recoverSession() {
|
|
87
82
|
const storedSession = this.loadStoredSession();
|
|
88
83
|
if (!storedSession) {
|
|
@@ -97,9 +92,6 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
97
92
|
logging_1.debugLog.info('SessionManager', 'Session recovered from storage', { sessionId: storedSession.id });
|
|
98
93
|
return storedSession.id;
|
|
99
94
|
}
|
|
100
|
-
/**
|
|
101
|
-
* Persist session data to localStorage
|
|
102
|
-
*/
|
|
103
95
|
persistSession(sessionId, lastActivity = Date.now()) {
|
|
104
96
|
this.saveStoredSession({
|
|
105
97
|
id: sessionId,
|
|
@@ -136,11 +128,8 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
136
128
|
return (0, constants_1.SESSION_STORAGE_KEY)(this.getProjectId());
|
|
137
129
|
}
|
|
138
130
|
getProjectId() {
|
|
139
|
-
return this.
|
|
131
|
+
return this.projectId;
|
|
140
132
|
}
|
|
141
|
-
/**
|
|
142
|
-
* Start session tracking
|
|
143
|
-
*/
|
|
144
133
|
async startTracking() {
|
|
145
134
|
if (this.isTracking) {
|
|
146
135
|
logging_1.debugLog.warn('SessionManager', 'Session tracking already active');
|
|
@@ -153,13 +142,11 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
153
142
|
try {
|
|
154
143
|
this.set('sessionId', sessionId);
|
|
155
144
|
this.persistSession(sessionId);
|
|
156
|
-
// Track session start event only for new sessions
|
|
157
145
|
if (!isRecovered) {
|
|
158
146
|
this.eventManager.track({
|
|
159
147
|
type: types_1.EventType.SESSION_START,
|
|
160
148
|
});
|
|
161
149
|
}
|
|
162
|
-
// Initialize components
|
|
163
150
|
this.initCrossTabSync();
|
|
164
151
|
this.shareSession(sessionId);
|
|
165
152
|
this.setupSessionTimeout();
|
|
@@ -177,15 +164,9 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
177
164
|
throw error;
|
|
178
165
|
}
|
|
179
166
|
}
|
|
180
|
-
/**
|
|
181
|
-
* Generate unique session ID
|
|
182
|
-
*/
|
|
183
167
|
generateSessionId() {
|
|
184
168
|
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
185
169
|
}
|
|
186
|
-
/**
|
|
187
|
-
* Setup session timeout
|
|
188
|
-
*/
|
|
189
170
|
setupSessionTimeout() {
|
|
190
171
|
this.clearSessionTimeout();
|
|
191
172
|
const sessionTimeout = this.get('config')?.sessionTimeout ?? constants_1.DEFAULT_SESSION_TIMEOUT;
|
|
@@ -193,9 +174,6 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
193
174
|
this.endSession('inactivity');
|
|
194
175
|
}, sessionTimeout);
|
|
195
176
|
}
|
|
196
|
-
/**
|
|
197
|
-
* Reset session timeout and update activity
|
|
198
|
-
*/
|
|
199
177
|
resetSessionTimeout() {
|
|
200
178
|
this.setupSessionTimeout();
|
|
201
179
|
const sessionId = this.get('sessionId');
|
|
@@ -203,27 +181,18 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
203
181
|
this.persistSession(sessionId);
|
|
204
182
|
}
|
|
205
183
|
}
|
|
206
|
-
/**
|
|
207
|
-
* Clear session timeout
|
|
208
|
-
*/
|
|
209
184
|
clearSessionTimeout() {
|
|
210
185
|
if (this.sessionTimeoutId) {
|
|
211
186
|
clearTimeout(this.sessionTimeoutId);
|
|
212
187
|
this.sessionTimeoutId = null;
|
|
213
188
|
}
|
|
214
189
|
}
|
|
215
|
-
/**
|
|
216
|
-
* Setup activity listeners to track user engagement
|
|
217
|
-
*/
|
|
218
190
|
setupActivityListeners() {
|
|
219
191
|
this.activityHandler = () => this.resetSessionTimeout();
|
|
220
192
|
document.addEventListener('click', this.activityHandler, { passive: true });
|
|
221
193
|
document.addEventListener('keydown', this.activityHandler, { passive: true });
|
|
222
194
|
document.addEventListener('scroll', this.activityHandler, { passive: true });
|
|
223
195
|
}
|
|
224
|
-
/**
|
|
225
|
-
* Clean up activity listeners
|
|
226
|
-
*/
|
|
227
196
|
cleanupActivityListeners() {
|
|
228
197
|
if (this.activityHandler) {
|
|
229
198
|
document.removeEventListener('click', this.activityHandler);
|
|
@@ -232,9 +201,6 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
232
201
|
this.activityHandler = null;
|
|
233
202
|
}
|
|
234
203
|
}
|
|
235
|
-
/**
|
|
236
|
-
* Setup page lifecycle listeners (visibility and unload)
|
|
237
|
-
*/
|
|
238
204
|
setupLifecycleListeners() {
|
|
239
205
|
if (this.visibilityChangeHandler || this.beforeUnloadHandler) {
|
|
240
206
|
return;
|
|
@@ -253,9 +219,7 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
253
219
|
this.beforeUnloadHandler = () => {
|
|
254
220
|
this.endSession('page_unload');
|
|
255
221
|
};
|
|
256
|
-
// Handle tab visibility changes
|
|
257
222
|
document.addEventListener('visibilitychange', this.visibilityChangeHandler);
|
|
258
|
-
// Handle page unload
|
|
259
223
|
window.addEventListener('beforeunload', this.beforeUnloadHandler);
|
|
260
224
|
}
|
|
261
225
|
cleanupLifecycleListeners() {
|
|
@@ -268,14 +232,11 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
268
232
|
this.beforeUnloadHandler = null;
|
|
269
233
|
}
|
|
270
234
|
}
|
|
271
|
-
|
|
272
|
-
* End current session
|
|
273
|
-
*/
|
|
274
|
-
endSession(reason) {
|
|
235
|
+
async endSession(reason) {
|
|
275
236
|
const sessionId = this.get('sessionId');
|
|
276
237
|
if (!sessionId) {
|
|
277
238
|
logging_1.debugLog.warn('SessionManager', 'endSession called without active session', { reason });
|
|
278
|
-
this.resetSessionState();
|
|
239
|
+
this.resetSessionState(reason);
|
|
279
240
|
return;
|
|
280
241
|
}
|
|
281
242
|
logging_1.debugLog.info('SessionManager', 'Ending session', { sessionId, reason });
|
|
@@ -285,42 +246,39 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
285
246
|
});
|
|
286
247
|
const finalize = () => {
|
|
287
248
|
this.broadcastSessionEnd(sessionId, reason);
|
|
288
|
-
this.resetSessionState();
|
|
249
|
+
this.resetSessionState(reason);
|
|
289
250
|
};
|
|
290
251
|
const flushResult = this.eventManager.flushImmediatelySync();
|
|
291
252
|
if (flushResult) {
|
|
292
253
|
finalize();
|
|
293
254
|
return;
|
|
294
255
|
}
|
|
295
|
-
|
|
296
|
-
.flushImmediately()
|
|
297
|
-
|
|
298
|
-
|
|
256
|
+
try {
|
|
257
|
+
await this.eventManager.flushImmediately();
|
|
258
|
+
finalize();
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
299
261
|
logging_1.debugLog.warn('SessionManager', 'Async flush failed during session end', {
|
|
300
262
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
301
263
|
});
|
|
302
264
|
finalize();
|
|
303
|
-
}
|
|
265
|
+
}
|
|
304
266
|
}
|
|
305
|
-
resetSessionState() {
|
|
267
|
+
resetSessionState(reason) {
|
|
306
268
|
this.clearSessionTimeout();
|
|
307
269
|
this.cleanupActivityListeners();
|
|
308
270
|
this.cleanupLifecycleListeners();
|
|
309
271
|
this.cleanupCrossTabSync();
|
|
310
|
-
|
|
272
|
+
if (reason !== 'page_unload') {
|
|
273
|
+
this.clearStoredSession();
|
|
274
|
+
}
|
|
311
275
|
this.set('sessionId', null);
|
|
312
276
|
this.set('hasStartSession', false);
|
|
313
277
|
this.isTracking = false;
|
|
314
278
|
}
|
|
315
|
-
/**
|
|
316
|
-
* Stop session tracking
|
|
317
|
-
*/
|
|
318
279
|
async stopTracking() {
|
|
319
|
-
this.endSession('manual_stop');
|
|
280
|
+
await this.endSession('manual_stop');
|
|
320
281
|
}
|
|
321
|
-
/**
|
|
322
|
-
* Clean up all resources
|
|
323
|
-
*/
|
|
324
282
|
destroy() {
|
|
325
283
|
this.clearSessionTimeout();
|
|
326
284
|
this.cleanupActivityListeners();
|
|
@@ -1,38 +1,11 @@
|
|
|
1
1
|
import { State } from '../types';
|
|
2
|
-
|
|
3
|
-
* Resets the global state to its initial empty state.
|
|
4
|
-
* Used primarily for testing and cleanup scenarios.
|
|
5
|
-
*/
|
|
2
|
+
export declare function getGlobalState(): Readonly<State>;
|
|
6
3
|
export declare function resetGlobalState(): void;
|
|
7
|
-
/**
|
|
8
|
-
* Abstract base class providing state management capabilities to TraceLog components.
|
|
9
|
-
*
|
|
10
|
-
* All managers and handlers extend this class to access and modify the shared global state.
|
|
11
|
-
* State operations are synchronous and thread-safe within the single-threaded browser environment.
|
|
12
|
-
*/
|
|
13
4
|
export declare abstract class StateManager {
|
|
14
|
-
/**
|
|
15
|
-
* Gets a value from the global state
|
|
16
|
-
*/
|
|
17
5
|
protected get<T extends keyof State>(key: T): State[T];
|
|
18
|
-
/**
|
|
19
|
-
* Sets a value in the global state
|
|
20
|
-
*/
|
|
21
6
|
protected set<T extends keyof State>(key: T, value: State[T]): void;
|
|
22
|
-
/**
|
|
23
|
-
* Gets the entire state object (for debugging purposes)
|
|
24
|
-
*/
|
|
25
7
|
protected getState(): Readonly<State>;
|
|
26
|
-
/**
|
|
27
|
-
* Checks if a state key is considered critical for logging
|
|
28
|
-
*/
|
|
29
8
|
private isCriticalStateKey;
|
|
30
|
-
/**
|
|
31
|
-
* Determines if a state change should be logged
|
|
32
|
-
*/
|
|
33
9
|
private shouldLog;
|
|
34
|
-
/**
|
|
35
|
-
* Formats values for logging (avoiding large object dumps)
|
|
36
|
-
*/
|
|
37
10
|
private formatLogValue;
|
|
38
11
|
}
|
|
@@ -1,45 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.StateManager = void 0;
|
|
4
|
+
exports.getGlobalState = getGlobalState;
|
|
4
5
|
exports.resetGlobalState = resetGlobalState;
|
|
5
6
|
const logging_1 = require("../utils/logging");
|
|
6
7
|
const constants_1 = require("../constants");
|
|
7
|
-
/**
|
|
8
|
-
* Global state store shared across all TraceLog components
|
|
9
|
-
*/
|
|
10
8
|
const globalState = {};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*/
|
|
9
|
+
function getGlobalState() {
|
|
10
|
+
return globalState;
|
|
11
|
+
}
|
|
15
12
|
function resetGlobalState() {
|
|
16
13
|
Object.keys(globalState).forEach((key) => {
|
|
17
14
|
delete globalState[key];
|
|
18
15
|
});
|
|
19
16
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Abstract base class providing state management capabilities to TraceLog components.
|
|
22
|
-
*
|
|
23
|
-
* All managers and handlers extend this class to access and modify the shared global state.
|
|
24
|
-
* State operations are synchronous and thread-safe within the single-threaded browser environment.
|
|
25
|
-
*/
|
|
26
17
|
class StateManager {
|
|
27
|
-
/**
|
|
28
|
-
* Gets a value from the global state
|
|
29
|
-
*/
|
|
30
18
|
get(key) {
|
|
31
19
|
return globalState[key];
|
|
32
20
|
}
|
|
33
|
-
/**
|
|
34
|
-
* Sets a value in the global state
|
|
35
|
-
*/
|
|
36
21
|
set(key, value) {
|
|
37
22
|
const oldValue = globalState[key];
|
|
38
23
|
if (key === 'config' && value) {
|
|
39
24
|
const configValue = value;
|
|
40
25
|
if (configValue) {
|
|
41
26
|
const samplingRate = configValue.samplingRate ?? constants_1.DEFAULT_SAMPLING_RATE;
|
|
42
|
-
const normalizedSamplingRate = samplingRate
|
|
27
|
+
const normalizedSamplingRate = samplingRate < 0 || samplingRate > 1 ? constants_1.DEFAULT_SAMPLING_RATE : samplingRate;
|
|
43
28
|
const hasNormalizedSampling = normalizedSamplingRate !== samplingRate;
|
|
44
29
|
if (hasNormalizedSampling) {
|
|
45
30
|
const normalizedConfig = { ...configValue, samplingRate: normalizedSamplingRate };
|
|
@@ -56,7 +41,6 @@ class StateManager {
|
|
|
56
41
|
else {
|
|
57
42
|
globalState[key] = value;
|
|
58
43
|
}
|
|
59
|
-
// Log critical state changes for debugging
|
|
60
44
|
if (this.isCriticalStateKey(key) && this.shouldLog(oldValue, globalState[key])) {
|
|
61
45
|
logging_1.debugLog.debug('StateManager', 'State updated', {
|
|
62
46
|
key,
|
|
@@ -65,27 +49,15 @@ class StateManager {
|
|
|
65
49
|
});
|
|
66
50
|
}
|
|
67
51
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Gets the entire state object (for debugging purposes)
|
|
70
|
-
*/
|
|
71
52
|
getState() {
|
|
72
53
|
return { ...globalState };
|
|
73
54
|
}
|
|
74
|
-
/**
|
|
75
|
-
* Checks if a state key is considered critical for logging
|
|
76
|
-
*/
|
|
77
55
|
isCriticalStateKey(key) {
|
|
78
56
|
return key === 'sessionId' || key === 'config' || key === 'hasStartSession';
|
|
79
57
|
}
|
|
80
|
-
/**
|
|
81
|
-
* Determines if a state change should be logged
|
|
82
|
-
*/
|
|
83
58
|
shouldLog(oldValue, newValue) {
|
|
84
59
|
return oldValue !== newValue;
|
|
85
60
|
}
|
|
86
|
-
/**
|
|
87
|
-
* Formats values for logging (avoiding large object dumps)
|
|
88
|
-
*/
|
|
89
61
|
formatLogValue(key, value) {
|
|
90
62
|
if (key === 'config') {
|
|
91
63
|
return value ? '(configured)' : '(not configured)';
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
export declare class StorageManager {
|
|
7
7
|
private readonly storage;
|
|
8
8
|
private readonly fallbackStorage;
|
|
9
|
+
private hasQuotaExceededError;
|
|
9
10
|
constructor();
|
|
10
11
|
/**
|
|
11
12
|
* Retrieves an item from storage
|
|
@@ -27,6 +28,11 @@ export declare class StorageManager {
|
|
|
27
28
|
* Checks if storage is available
|
|
28
29
|
*/
|
|
29
30
|
isAvailable(): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Checks if a QuotaExceededError has occurred
|
|
33
|
+
* This indicates localStorage is full and data may not persist
|
|
34
|
+
*/
|
|
35
|
+
hasQuotaError(): boolean;
|
|
30
36
|
/**
|
|
31
37
|
* Initialize localStorage with feature detection
|
|
32
38
|
*/
|
|
@@ -10,6 +10,7 @@ const debug_logger_utils_1 = require("../utils/logging/debug-logger.utils");
|
|
|
10
10
|
class StorageManager {
|
|
11
11
|
constructor() {
|
|
12
12
|
this.fallbackStorage = new Map();
|
|
13
|
+
this.hasQuotaExceededError = false;
|
|
13
14
|
this.storage = this.initializeStorage();
|
|
14
15
|
if (!this.storage) {
|
|
15
16
|
debug_logger_utils_1.debugLog.warn('StorageManager', 'localStorage not available, using memory fallback');
|
|
@@ -41,7 +42,16 @@ class StorageManager {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
catch (error) {
|
|
44
|
-
|
|
45
|
+
if (error instanceof DOMException && error.name === 'QuotaExceededError') {
|
|
46
|
+
this.hasQuotaExceededError = true;
|
|
47
|
+
debug_logger_utils_1.debugLog.error('StorageManager', 'localStorage quota exceeded - data will not persist after reload', {
|
|
48
|
+
key,
|
|
49
|
+
valueSize: value.length,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
debug_logger_utils_1.debugLog.warn('StorageManager', 'Failed to set item, using fallback', { key, error });
|
|
54
|
+
}
|
|
45
55
|
}
|
|
46
56
|
// Always update fallback for consistency
|
|
47
57
|
this.fallbackStorage.set(key, value);
|
|
@@ -92,6 +102,13 @@ class StorageManager {
|
|
|
92
102
|
isAvailable() {
|
|
93
103
|
return this.storage !== null;
|
|
94
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Checks if a QuotaExceededError has occurred
|
|
107
|
+
* This indicates localStorage is full and data may not persist
|
|
108
|
+
*/
|
|
109
|
+
hasQuotaError() {
|
|
110
|
+
return this.hasQuotaExceededError;
|
|
111
|
+
}
|
|
95
112
|
/**
|
|
96
113
|
* Initialize localStorage with feature detection
|
|
97
114
|
*/
|
package/dist/cjs/public-api.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export * from './app.constants';
|
|
|
2
2
|
export * from './app.types';
|
|
3
3
|
export declare const tracelog: {
|
|
4
4
|
init: (appConfig: import("./app.types").AppConfig) => Promise<void>;
|
|
5
|
-
event: (name: string, metadata?: Record<string, import("./app.types").MetadataType>) => void;
|
|
5
|
+
event: (name: string, metadata?: Record<string, import("./app.types").MetadataType> | Record<string, import("./app.types").MetadataType>[]) => void;
|
|
6
6
|
on: <K extends keyof import("./types").EmitterMap>(event: K, callback: import("./types").EmitterCallback<import("./types").EmitterMap[K]>) => void;
|
|
7
7
|
off: <K extends keyof import("./types").EmitterMap>(event: K, callback: import("./types").EmitterCallback<import("./types").EmitterMap[K]>) => void;
|
|
8
8
|
isInitialized: () => boolean;
|
|
@@ -17,11 +17,12 @@ export declare class TestBridge extends App implements TraceLogTestBridge {
|
|
|
17
17
|
private _isDestroying;
|
|
18
18
|
constructor(isInitializing: boolean, isDestroying: boolean);
|
|
19
19
|
isInitializing(): boolean;
|
|
20
|
-
sendCustomEvent(name: string, data?: Record<string, unknown>): void;
|
|
20
|
+
sendCustomEvent(name: string, data?: Record<string, unknown> | Record<string, unknown>[]): void;
|
|
21
21
|
getSessionData(): Record<string, unknown> | null;
|
|
22
22
|
setSessionTimeout(timeout: number): void;
|
|
23
23
|
getQueueLength(): number;
|
|
24
24
|
forceInitLock(enabled?: boolean): void;
|
|
25
|
+
simulatePersistedEvents(events: any[]): void;
|
|
25
26
|
get<T extends keyof State>(key: T): State[T];
|
|
26
27
|
getStorageManager(): StorageManager | null;
|
|
27
28
|
getEventManager(): EventManager | null;
|
|
@@ -32,7 +33,7 @@ export declare class TestBridge extends App implements TraceLogTestBridge {
|
|
|
32
33
|
getPerformanceHandler(): PerformanceHandler | null;
|
|
33
34
|
getErrorHandler(): ErrorHandler | null;
|
|
34
35
|
getGoogleAnalytics(): GoogleAnalyticsIntegration | null;
|
|
35
|
-
destroy(): Promise<void>;
|
|
36
|
+
destroy(force?: boolean): Promise<void>;
|
|
36
37
|
/**
|
|
37
38
|
* Helper to safely access managers/handlers and convert undefined to null
|
|
38
39
|
*/
|
package/dist/cjs/test-bridge.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TestBridge = void 0;
|
|
4
4
|
const app_1 = require("./app");
|
|
5
|
-
const utils_1 = require("./utils");
|
|
6
5
|
/**
|
|
7
6
|
* Test bridge for E2E testing
|
|
8
7
|
*/
|
|
@@ -17,7 +16,10 @@ class TestBridge extends app_1.App {
|
|
|
17
16
|
return this._isInitializing;
|
|
18
17
|
}
|
|
19
18
|
sendCustomEvent(name, data) {
|
|
20
|
-
|
|
19
|
+
// Silently ignore events after destroy instead of throwing error
|
|
20
|
+
if (!this.initialized) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
21
23
|
super.sendCustomEvent(name, data);
|
|
22
24
|
}
|
|
23
25
|
getSessionData() {
|
|
@@ -30,8 +32,7 @@ class TestBridge extends app_1.App {
|
|
|
30
32
|
setSessionTimeout(timeout) {
|
|
31
33
|
const config = this.get('config');
|
|
32
34
|
if (config) {
|
|
33
|
-
|
|
34
|
-
this.set('config', normalizedConfig);
|
|
35
|
+
this.set('config', { ...config, sessionTimeout: timeout });
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
getQueueLength() {
|
|
@@ -40,6 +41,29 @@ class TestBridge extends app_1.App {
|
|
|
40
41
|
forceInitLock(enabled = true) {
|
|
41
42
|
this._isInitializing = enabled;
|
|
42
43
|
}
|
|
44
|
+
simulatePersistedEvents(events) {
|
|
45
|
+
const storageManager = this.managers?.storage;
|
|
46
|
+
if (!storageManager) {
|
|
47
|
+
throw new Error('Storage manager not available');
|
|
48
|
+
}
|
|
49
|
+
const projectId = this.get('config')?.id;
|
|
50
|
+
const userId = this.get('userId');
|
|
51
|
+
const sessionId = this.get('sessionId');
|
|
52
|
+
if (!projectId || !userId) {
|
|
53
|
+
throw new Error('Project ID or User ID not available. Initialize TraceLog first.');
|
|
54
|
+
}
|
|
55
|
+
// Build the persisted data structure matching what SenderManager expects
|
|
56
|
+
const persistedData = {
|
|
57
|
+
userId,
|
|
58
|
+
sessionId: sessionId || `test-session-${Date.now()}`,
|
|
59
|
+
device: 'desktop',
|
|
60
|
+
events,
|
|
61
|
+
timestamp: Date.now(),
|
|
62
|
+
};
|
|
63
|
+
// Store in the same format as SenderManager.persistEvents()
|
|
64
|
+
const storageKey = `tl:${projectId}:queue:${userId}`;
|
|
65
|
+
storageManager.setItem(storageKey, JSON.stringify(persistedData));
|
|
66
|
+
}
|
|
43
67
|
get(key) {
|
|
44
68
|
return super.get(key);
|
|
45
69
|
}
|
|
@@ -73,12 +97,15 @@ class TestBridge extends app_1.App {
|
|
|
73
97
|
getGoogleAnalytics() {
|
|
74
98
|
return this.safeAccess(this.integrations?.googleAnalytics);
|
|
75
99
|
}
|
|
76
|
-
async destroy() {
|
|
77
|
-
|
|
100
|
+
async destroy(force = false) {
|
|
101
|
+
// If not initialized and not forcing, silently return (no-op)
|
|
102
|
+
if (!this.initialized && !force) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
78
105
|
this.ensureNotDestroying();
|
|
79
106
|
this._isDestroying = true;
|
|
80
107
|
try {
|
|
81
|
-
await super.destroy();
|
|
108
|
+
await super.destroy(force);
|
|
82
109
|
}
|
|
83
110
|
finally {
|
|
84
111
|
this._isDestroying = false;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Special project IDs for testing and development
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* All automatically force mode: 'debug' but differ in HTTP behavior:
|
|
5
5
|
* - Skip: NO network calls (pure offline testing)
|
|
6
6
|
* - Localhost: Makes network calls to local server (integration testing)
|
|
7
|
+
* - Fail: Makes network calls that intentionally fail (persistence testing)
|
|
7
8
|
*/
|
|
8
9
|
export declare enum SpecialProjectId {
|
|
9
10
|
/**
|
|
@@ -20,17 +21,32 @@ export declare enum SpecialProjectId {
|
|
|
20
21
|
*/
|
|
21
22
|
Skip = "skip",
|
|
22
23
|
/**
|
|
23
|
-
* Value: 'localhost:'
|
|
24
|
+
* Value: 'localhost:8080'
|
|
24
25
|
*
|
|
25
|
-
* Makes HTTP calls to local development server
|
|
26
|
-
*
|
|
27
|
-
* Converts to http://localhost:PORT/config for requests
|
|
26
|
+
* Makes HTTP calls to local development server on port 8080
|
|
27
|
+
* Converts to http://localhost:8080/config for requests
|
|
28
28
|
* Requires origin to be in ALLOWED_ORIGINS list, forces debug mode
|
|
29
29
|
* Perfect for local development with running middleware
|
|
30
30
|
*
|
|
31
31
|
* @example
|
|
32
|
-
* await TraceLog.init({ id:
|
|
33
|
-
* //
|
|
32
|
+
* await TraceLog.init({ id: SpecialProjectId.Localhost });
|
|
33
|
+
* // or
|
|
34
|
+
* await TraceLog.init({ id: 'localhost:8080' });
|
|
35
|
+
* // Makes requests to: http://localhost:8080/config
|
|
36
|
+
*/
|
|
37
|
+
Localhost = "localhost:8080",
|
|
38
|
+
/**
|
|
39
|
+
* Value: 'localhost:9999'
|
|
40
|
+
*
|
|
41
|
+
* Makes HTTP calls to non-existent server (port 9999)
|
|
42
|
+
* All HTTP requests will fail naturally, triggering persistence
|
|
43
|
+
* Forces debug mode, perfect for testing event persistence & recovery
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* await TraceLog.init({ id: SpecialProjectId.Fail });
|
|
47
|
+
* // or
|
|
48
|
+
* await TraceLog.init({ id: 'localhost:9999' });
|
|
49
|
+
* // Makes requests to: http://localhost:9999 (will fail)
|
|
34
50
|
*/
|
|
35
|
-
|
|
51
|
+
Fail = "localhost:9999"
|
|
36
52
|
}
|
|
@@ -4,9 +4,10 @@ exports.SpecialProjectId = void 0;
|
|
|
4
4
|
/**
|
|
5
5
|
* Special project IDs for testing and development
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* All automatically force mode: 'debug' but differ in HTTP behavior:
|
|
8
8
|
* - Skip: NO network calls (pure offline testing)
|
|
9
9
|
* - Localhost: Makes network calls to local server (integration testing)
|
|
10
|
+
* - Fail: Makes network calls that intentionally fail (persistence testing)
|
|
10
11
|
*/
|
|
11
12
|
var SpecialProjectId;
|
|
12
13
|
(function (SpecialProjectId) {
|
|
@@ -24,17 +25,32 @@ var SpecialProjectId;
|
|
|
24
25
|
*/
|
|
25
26
|
SpecialProjectId["Skip"] = "skip";
|
|
26
27
|
/**
|
|
27
|
-
* Value: 'localhost:'
|
|
28
|
+
* Value: 'localhost:8080'
|
|
28
29
|
*
|
|
29
|
-
* Makes HTTP calls to local development server
|
|
30
|
-
*
|
|
31
|
-
* Converts to http://localhost:PORT/config for requests
|
|
30
|
+
* Makes HTTP calls to local development server on port 8080
|
|
31
|
+
* Converts to http://localhost:8080/config for requests
|
|
32
32
|
* Requires origin to be in ALLOWED_ORIGINS list, forces debug mode
|
|
33
33
|
* Perfect for local development with running middleware
|
|
34
34
|
*
|
|
35
35
|
* @example
|
|
36
|
-
* await TraceLog.init({ id:
|
|
37
|
-
* //
|
|
36
|
+
* await TraceLog.init({ id: SpecialProjectId.Localhost });
|
|
37
|
+
* // or
|
|
38
|
+
* await TraceLog.init({ id: 'localhost:8080' });
|
|
39
|
+
* // Makes requests to: http://localhost:8080/config
|
|
40
|
+
*/
|
|
41
|
+
SpecialProjectId["Localhost"] = "localhost:8080";
|
|
42
|
+
/**
|
|
43
|
+
* Value: 'localhost:9999'
|
|
44
|
+
*
|
|
45
|
+
* Makes HTTP calls to non-existent server (port 9999)
|
|
46
|
+
* All HTTP requests will fail naturally, triggering persistence
|
|
47
|
+
* Forces debug mode, perfect for testing event persistence & recovery
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* await TraceLog.init({ id: SpecialProjectId.Fail });
|
|
51
|
+
* // or
|
|
52
|
+
* await TraceLog.init({ id: 'localhost:9999' });
|
|
53
|
+
* // Makes requests to: http://localhost:9999 (will fail)
|
|
38
54
|
*/
|
|
39
|
-
SpecialProjectId["
|
|
55
|
+
SpecialProjectId["Fail"] = "localhost:9999";
|
|
40
56
|
})(SpecialProjectId || (exports.SpecialProjectId = SpecialProjectId = {}));
|