@tracelog/lib 0.4.0 → 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 +620 -658
- 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 +1 -46
- package/dist/cjs/managers/event.manager.js +15 -70
- 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 +42 -83
- 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 -4
- 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 +1 -46
- package/dist/esm/managers/event.manager.js +15 -70
- 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 +42 -83
- 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 -4
- 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,12 +142,11 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
153
142
|
try {
|
|
154
143
|
this.set('sessionId', sessionId);
|
|
155
144
|
this.persistSession(sessionId);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
// Initialize components
|
|
145
|
+
if (!isRecovered) {
|
|
146
|
+
this.eventManager.track({
|
|
147
|
+
type: types_1.EventType.SESSION_START,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
162
150
|
this.initCrossTabSync();
|
|
163
151
|
this.shareSession(sessionId);
|
|
164
152
|
this.setupSessionTimeout();
|
|
@@ -176,15 +164,9 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
176
164
|
throw error;
|
|
177
165
|
}
|
|
178
166
|
}
|
|
179
|
-
/**
|
|
180
|
-
* Generate unique session ID
|
|
181
|
-
*/
|
|
182
167
|
generateSessionId() {
|
|
183
168
|
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
|
|
184
169
|
}
|
|
185
|
-
/**
|
|
186
|
-
* Setup session timeout
|
|
187
|
-
*/
|
|
188
170
|
setupSessionTimeout() {
|
|
189
171
|
this.clearSessionTimeout();
|
|
190
172
|
const sessionTimeout = this.get('config')?.sessionTimeout ?? constants_1.DEFAULT_SESSION_TIMEOUT;
|
|
@@ -192,9 +174,6 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
192
174
|
this.endSession('inactivity');
|
|
193
175
|
}, sessionTimeout);
|
|
194
176
|
}
|
|
195
|
-
/**
|
|
196
|
-
* Reset session timeout and update activity
|
|
197
|
-
*/
|
|
198
177
|
resetSessionTimeout() {
|
|
199
178
|
this.setupSessionTimeout();
|
|
200
179
|
const sessionId = this.get('sessionId');
|
|
@@ -202,27 +181,18 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
202
181
|
this.persistSession(sessionId);
|
|
203
182
|
}
|
|
204
183
|
}
|
|
205
|
-
/**
|
|
206
|
-
* Clear session timeout
|
|
207
|
-
*/
|
|
208
184
|
clearSessionTimeout() {
|
|
209
185
|
if (this.sessionTimeoutId) {
|
|
210
186
|
clearTimeout(this.sessionTimeoutId);
|
|
211
187
|
this.sessionTimeoutId = null;
|
|
212
188
|
}
|
|
213
189
|
}
|
|
214
|
-
/**
|
|
215
|
-
* Setup activity listeners to track user engagement
|
|
216
|
-
*/
|
|
217
190
|
setupActivityListeners() {
|
|
218
191
|
this.activityHandler = () => this.resetSessionTimeout();
|
|
219
192
|
document.addEventListener('click', this.activityHandler, { passive: true });
|
|
220
193
|
document.addEventListener('keydown', this.activityHandler, { passive: true });
|
|
221
194
|
document.addEventListener('scroll', this.activityHandler, { passive: true });
|
|
222
195
|
}
|
|
223
|
-
/**
|
|
224
|
-
* Clean up activity listeners
|
|
225
|
-
*/
|
|
226
196
|
cleanupActivityListeners() {
|
|
227
197
|
if (this.activityHandler) {
|
|
228
198
|
document.removeEventListener('click', this.activityHandler);
|
|
@@ -231,9 +201,6 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
231
201
|
this.activityHandler = null;
|
|
232
202
|
}
|
|
233
203
|
}
|
|
234
|
-
/**
|
|
235
|
-
* Setup page lifecycle listeners (visibility and unload)
|
|
236
|
-
*/
|
|
237
204
|
setupLifecycleListeners() {
|
|
238
205
|
if (this.visibilityChangeHandler || this.beforeUnloadHandler) {
|
|
239
206
|
return;
|
|
@@ -252,9 +219,7 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
252
219
|
this.beforeUnloadHandler = () => {
|
|
253
220
|
this.endSession('page_unload');
|
|
254
221
|
};
|
|
255
|
-
// Handle tab visibility changes
|
|
256
222
|
document.addEventListener('visibilitychange', this.visibilityChangeHandler);
|
|
257
|
-
// Handle page unload
|
|
258
223
|
window.addEventListener('beforeunload', this.beforeUnloadHandler);
|
|
259
224
|
}
|
|
260
225
|
cleanupLifecycleListeners() {
|
|
@@ -267,14 +232,11 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
267
232
|
this.beforeUnloadHandler = null;
|
|
268
233
|
}
|
|
269
234
|
}
|
|
270
|
-
|
|
271
|
-
* End current session
|
|
272
|
-
*/
|
|
273
|
-
endSession(reason) {
|
|
235
|
+
async endSession(reason) {
|
|
274
236
|
const sessionId = this.get('sessionId');
|
|
275
237
|
if (!sessionId) {
|
|
276
238
|
logging_1.debugLog.warn('SessionManager', 'endSession called without active session', { reason });
|
|
277
|
-
this.resetSessionState();
|
|
239
|
+
this.resetSessionState(reason);
|
|
278
240
|
return;
|
|
279
241
|
}
|
|
280
242
|
logging_1.debugLog.info('SessionManager', 'Ending session', { sessionId, reason });
|
|
@@ -284,42 +246,39 @@ class SessionManager extends state_manager_1.StateManager {
|
|
|
284
246
|
});
|
|
285
247
|
const finalize = () => {
|
|
286
248
|
this.broadcastSessionEnd(sessionId, reason);
|
|
287
|
-
this.resetSessionState();
|
|
249
|
+
this.resetSessionState(reason);
|
|
288
250
|
};
|
|
289
251
|
const flushResult = this.eventManager.flushImmediatelySync();
|
|
290
252
|
if (flushResult) {
|
|
291
253
|
finalize();
|
|
292
254
|
return;
|
|
293
255
|
}
|
|
294
|
-
|
|
295
|
-
.flushImmediately()
|
|
296
|
-
|
|
297
|
-
|
|
256
|
+
try {
|
|
257
|
+
await this.eventManager.flushImmediately();
|
|
258
|
+
finalize();
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
298
261
|
logging_1.debugLog.warn('SessionManager', 'Async flush failed during session end', {
|
|
299
262
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
300
263
|
});
|
|
301
264
|
finalize();
|
|
302
|
-
}
|
|
265
|
+
}
|
|
303
266
|
}
|
|
304
|
-
resetSessionState() {
|
|
267
|
+
resetSessionState(reason) {
|
|
305
268
|
this.clearSessionTimeout();
|
|
306
269
|
this.cleanupActivityListeners();
|
|
307
270
|
this.cleanupLifecycleListeners();
|
|
308
271
|
this.cleanupCrossTabSync();
|
|
309
|
-
|
|
272
|
+
if (reason !== 'page_unload') {
|
|
273
|
+
this.clearStoredSession();
|
|
274
|
+
}
|
|
310
275
|
this.set('sessionId', null);
|
|
311
276
|
this.set('hasStartSession', false);
|
|
312
277
|
this.isTracking = false;
|
|
313
278
|
}
|
|
314
|
-
/**
|
|
315
|
-
* Stop session tracking
|
|
316
|
-
*/
|
|
317
279
|
async stopTracking() {
|
|
318
|
-
this.endSession('manual_stop');
|
|
280
|
+
await this.endSession('manual_stop');
|
|
319
281
|
}
|
|
320
|
-
/**
|
|
321
|
-
* Clean up all resources
|
|
322
|
-
*/
|
|
323
282
|
destroy() {
|
|
324
283
|
this.clearSessionTimeout();
|
|
325
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 = {}));
|