@editor-x/wme-logstream 0.1.0-alpha.1
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/LICENSE +190 -0
- package/README.md +216 -0
- package/dist/index.cjs.js +980 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.esm.js +954 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.iife.js +2 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/types/core/Dispatcher.d.ts +8 -0
- package/dist/types/core/LogPayload.d.ts +17 -0
- package/dist/types/core/LogStream.d.ts +128 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/pipes/BasePipe.d.ts +5 -0
- package/dist/types/pipes/ConsolePipe.d.ts +14 -0
- package/dist/types/pipes/IndexedDBPipe.d.ts +11 -0
- package/dist/types/storage/IndexedDBManager.d.ts +85 -0
- package/dist/types/storage/SessionManager.d.ts +32 -0
- package/dist/types/storage/ZipStreamer.d.ts +9 -0
- package/dist/types/utils/EventChannel.d.ts +21 -0
- package/dist/types/utils/idb.d.ts +16 -0
- package/dist/types/utils/session.d.ts +4 -0
- package/dist/types/utils/window.d.ts +4 -0
- package/package.json +51 -0
|
@@ -0,0 +1,980 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jsondiffpatch = require('jsondiffpatch');
|
|
4
|
+
var fflate = require('fflate');
|
|
5
|
+
|
|
6
|
+
function _interopNamespaceDefault(e) {
|
|
7
|
+
var n = Object.create(null);
|
|
8
|
+
if (e) {
|
|
9
|
+
Object.keys(e).forEach(function (k) {
|
|
10
|
+
if (k !== 'default') {
|
|
11
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
12
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () { return e[k]; }
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
n.default = e;
|
|
20
|
+
return Object.freeze(n);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
var fflate__namespace = /*#__PURE__*/_interopNamespaceDefault(fflate);
|
|
24
|
+
|
|
25
|
+
class Dispatcher {
|
|
26
|
+
pipes = [];
|
|
27
|
+
addPipe(pipe) {
|
|
28
|
+
this.pipes.push(pipe);
|
|
29
|
+
}
|
|
30
|
+
dispatch(payload) {
|
|
31
|
+
for (const pipe of this.pipes) {
|
|
32
|
+
try {
|
|
33
|
+
const result = pipe.write(payload);
|
|
34
|
+
if (result instanceof Promise) {
|
|
35
|
+
result.catch((err) => {
|
|
36
|
+
console.error('Error writing to pipe:', err);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error('Error writing to pipe:', err);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async flush() {
|
|
46
|
+
const flushPromises = this.pipes.map(async (pipe) => {
|
|
47
|
+
try {
|
|
48
|
+
await pipe.flush();
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.error('Error flushing pipe:', err);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
await Promise.all(flushPromises);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
class BasePipe {
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class ConsolePipe extends BasePipe {
|
|
62
|
+
scriptPrefix;
|
|
63
|
+
brandColor;
|
|
64
|
+
styles = {
|
|
65
|
+
TRACE: 'color: #888888; font-weight: normal;',
|
|
66
|
+
DEBUG: 'color: #03A9F4; font-weight: bold;',
|
|
67
|
+
INFO: 'color: #4CAF50; font-weight: bold;',
|
|
68
|
+
WARN: 'color: #FF9800; font-weight: bold;',
|
|
69
|
+
ERROR: 'color: #F44336; font-weight: bold;',
|
|
70
|
+
FATAL: 'color: #FFFFFF; background-color: #B71C1C; font-weight: bold; padding: 2px 4px; border-radius: 2px;',
|
|
71
|
+
};
|
|
72
|
+
constructor(config) {
|
|
73
|
+
super();
|
|
74
|
+
this.scriptPrefix = config?.scriptPrefix ?? 'EditorX';
|
|
75
|
+
this.brandColor = config?.brandColor ?? '#00E676';
|
|
76
|
+
}
|
|
77
|
+
write(payload) {
|
|
78
|
+
const timestampStr = new Date(payload.timestamp).toISOString().split('T')[1].slice(0, -1);
|
|
79
|
+
const scopesStr = payload.scopes.map((s) => `[${s}]`).join('');
|
|
80
|
+
const levelStr = payload.level;
|
|
81
|
+
const prefixStyle = `color: ${this.brandColor}; font-weight: bold;`;
|
|
82
|
+
const timeStyle = 'color: #9E9E9E;';
|
|
83
|
+
const levelStyle = this.styles[levelStr];
|
|
84
|
+
const scopeStyle = 'color: #9C27B0; font-weight: bold;';
|
|
85
|
+
const format = `%c[${this.scriptPrefix}] %c${timestampStr} %c[${levelStr}]%c%c${scopesStr}%c ${payload.message}`;
|
|
86
|
+
const args = [
|
|
87
|
+
format,
|
|
88
|
+
prefixStyle,
|
|
89
|
+
timeStyle,
|
|
90
|
+
levelStyle,
|
|
91
|
+
'', // reset level style
|
|
92
|
+
scopeStyle,
|
|
93
|
+
'', // reset scope style
|
|
94
|
+
];
|
|
95
|
+
if (payload.data !== undefined) {
|
|
96
|
+
console.log(...args, payload.data);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.log(...args);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
flush() { }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
class IndexedDBPipe extends BasePipe {
|
|
106
|
+
dbManager = null;
|
|
107
|
+
initPromise = null;
|
|
108
|
+
buffer = [];
|
|
109
|
+
constructor(dbManagerOrPromise) {
|
|
110
|
+
super();
|
|
111
|
+
if (dbManagerOrPromise instanceof Promise) {
|
|
112
|
+
this.initPromise = dbManagerOrPromise.then((manager) => {
|
|
113
|
+
this.dbManager = manager;
|
|
114
|
+
for (const payload of this.buffer) {
|
|
115
|
+
manager.writeLog(payload).catch(() => { });
|
|
116
|
+
}
|
|
117
|
+
this.buffer = [];
|
|
118
|
+
return manager;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
this.dbManager = dbManagerOrPromise;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async write(payload) {
|
|
126
|
+
if (this.dbManager) {
|
|
127
|
+
await this.dbManager.writeLog(payload);
|
|
128
|
+
}
|
|
129
|
+
else if (this.initPromise) {
|
|
130
|
+
this.buffer.push(payload);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async flush() {
|
|
134
|
+
if (this.dbManager) {
|
|
135
|
+
await this.dbManager.flush();
|
|
136
|
+
}
|
|
137
|
+
else if (this.initPromise) {
|
|
138
|
+
const manager = await this.initPromise;
|
|
139
|
+
await manager.flush();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Opens an IndexedDB connection and wraps the request in a Promise.
|
|
146
|
+
*/
|
|
147
|
+
function openDB(name, version, options) {
|
|
148
|
+
return new Promise((resolve, reject) => {
|
|
149
|
+
const request = indexedDB.open(name, version);
|
|
150
|
+
if (options?.onUpgradeNeeded) {
|
|
151
|
+
request.onupgradeneeded = (event) => {
|
|
152
|
+
options.onUpgradeNeeded(request.result, event);
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (options?.onBlocked) {
|
|
156
|
+
request.onblocked = (event) => {
|
|
157
|
+
options.onBlocked(event);
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
request.onsuccess = () => {
|
|
161
|
+
resolve(request.result);
|
|
162
|
+
};
|
|
163
|
+
request.onerror = () => {
|
|
164
|
+
reject(request.error);
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Wraps a standard IDBRequest in a Promise.
|
|
170
|
+
*/
|
|
171
|
+
function wrapRequest(request) {
|
|
172
|
+
return new Promise((resolve, reject) => {
|
|
173
|
+
request.onsuccess = () => resolve(request.result);
|
|
174
|
+
request.onerror = () => reject(request.error);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Runs a transaction on the database and resolves when the transaction completes.
|
|
179
|
+
*/
|
|
180
|
+
function runTransaction(db, storeNames, mode, callback) {
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
const tx = db.transaction(storeNames, mode);
|
|
183
|
+
tx.oncomplete = () => resolve();
|
|
184
|
+
tx.onerror = () => reject(tx.error);
|
|
185
|
+
callback(tx);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const DEFAULT_CONFIG = {
|
|
190
|
+
maxArchivedSessions: 15,
|
|
191
|
+
flushIntervalMs: 2000,
|
|
192
|
+
};
|
|
193
|
+
class IndexedDBManager {
|
|
194
|
+
dbPrefix;
|
|
195
|
+
maxArchivedSessions;
|
|
196
|
+
scriptVersion;
|
|
197
|
+
sessionId;
|
|
198
|
+
wmeVersion;
|
|
199
|
+
flushIntervalMs;
|
|
200
|
+
db = null;
|
|
201
|
+
queue = [];
|
|
202
|
+
flushTimer = null;
|
|
203
|
+
heartbeatTimer = null;
|
|
204
|
+
loggerVersion = '1.0.0';
|
|
205
|
+
schemaVersion = '1.0.0';
|
|
206
|
+
constructor(config) {
|
|
207
|
+
const merged = Object.assign({}, DEFAULT_CONFIG, config);
|
|
208
|
+
this.dbPrefix = merged.dbPrefix;
|
|
209
|
+
this.maxArchivedSessions = merged.maxArchivedSessions;
|
|
210
|
+
this.scriptVersion = merged.scriptVersion;
|
|
211
|
+
this.sessionId = merged.sessionId;
|
|
212
|
+
this.wmeVersion = merged.wmeVersion;
|
|
213
|
+
this.flushIntervalMs = merged.flushIntervalMs;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Returns the database name with prefix.
|
|
217
|
+
*/
|
|
218
|
+
getDatabaseName() {
|
|
219
|
+
return `LogStreamDB_${this.dbPrefix}`;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Initializes the database connection and saves session metadata.
|
|
223
|
+
*/
|
|
224
|
+
async init() {
|
|
225
|
+
const dbName = this.getDatabaseName();
|
|
226
|
+
this.db = await openDB(dbName, 1, {
|
|
227
|
+
onUpgradeNeeded: (db) => {
|
|
228
|
+
if (!db.objectStoreNames.contains('sessions')) {
|
|
229
|
+
db.createObjectStore('sessions', { keyPath: 'id' });
|
|
230
|
+
}
|
|
231
|
+
if (!db.objectStoreNames.contains('logs')) {
|
|
232
|
+
const logStore = db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
|
|
233
|
+
logStore.createIndex('sessionId', 'sessionId', { unique: false });
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
const now = Date.now();
|
|
238
|
+
const metadata = {
|
|
239
|
+
id: this.sessionId,
|
|
240
|
+
createdAt: now,
|
|
241
|
+
lastUpdated: now,
|
|
242
|
+
scriptVersion: this.scriptVersion,
|
|
243
|
+
wmeVersion: this.wmeVersion ?? 'unknown',
|
|
244
|
+
loggerVersion: this.loggerVersion,
|
|
245
|
+
schemaVersion: this.schemaVersion,
|
|
246
|
+
};
|
|
247
|
+
try {
|
|
248
|
+
await this.saveSessionMetadata(metadata);
|
|
249
|
+
this.startHeartbeat();
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
if (this.db) {
|
|
253
|
+
this.db.close();
|
|
254
|
+
this.db = null;
|
|
255
|
+
}
|
|
256
|
+
throw err;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Saves a session metadata record.
|
|
261
|
+
*/
|
|
262
|
+
async saveSessionMetadata(metadata) {
|
|
263
|
+
if (!this.db)
|
|
264
|
+
throw new Error('Database not initialized');
|
|
265
|
+
const transaction = this.db.transaction('sessions', 'readwrite');
|
|
266
|
+
const store = transaction.objectStore('sessions');
|
|
267
|
+
await wrapRequest(store.put(metadata));
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Periodically updates the current session's lastUpdated timestamp.
|
|
271
|
+
*/
|
|
272
|
+
async touchSession() {
|
|
273
|
+
if (!this.db)
|
|
274
|
+
return;
|
|
275
|
+
const transaction = this.db.transaction('sessions', 'readwrite');
|
|
276
|
+
const store = transaction.objectStore('sessions');
|
|
277
|
+
const metadata = (await wrapRequest(store.get(this.sessionId)));
|
|
278
|
+
if (metadata) {
|
|
279
|
+
metadata.lastUpdated = Date.now();
|
|
280
|
+
await wrapRequest(store.put(metadata));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Starts the session heartbeat interval.
|
|
285
|
+
*/
|
|
286
|
+
startHeartbeat() {
|
|
287
|
+
this.stopHeartbeat();
|
|
288
|
+
this.heartbeatTimer = setInterval(() => {
|
|
289
|
+
this.touchSession().catch(() => { });
|
|
290
|
+
}, 10000); // 10-second heartbeat
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Stops the session heartbeat interval.
|
|
294
|
+
*/
|
|
295
|
+
stopHeartbeat() {
|
|
296
|
+
if (this.heartbeatTimer) {
|
|
297
|
+
clearInterval(this.heartbeatTimer);
|
|
298
|
+
this.heartbeatTimer = null;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Writes a log entry, scheduling a flush or flushing immediately for high priority.
|
|
303
|
+
*/
|
|
304
|
+
async writeLog(payload) {
|
|
305
|
+
const enrichedPayload = {
|
|
306
|
+
...payload,
|
|
307
|
+
sessionId: this.sessionId,
|
|
308
|
+
};
|
|
309
|
+
const isHighPriority = payload.level === 'WARN' || payload.level === 'ERROR' || payload.level === 'FATAL';
|
|
310
|
+
if (isHighPriority) {
|
|
311
|
+
this.queue.push(enrichedPayload);
|
|
312
|
+
await this.flush();
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
this.queue.push(enrichedPayload);
|
|
316
|
+
if (this.queue.length >= 50) {
|
|
317
|
+
await this.flush();
|
|
318
|
+
}
|
|
319
|
+
else if (!this.flushTimer) {
|
|
320
|
+
this.scheduleFlush();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Schedules a background flush if not already scheduled.
|
|
326
|
+
*/
|
|
327
|
+
scheduleFlush() {
|
|
328
|
+
if (this.flushTimer)
|
|
329
|
+
return;
|
|
330
|
+
this.flushTimer = setTimeout(() => {
|
|
331
|
+
this.flush().catch(() => { });
|
|
332
|
+
}, this.flushIntervalMs);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Instantly flushes the in-memory queue to IndexedDB.
|
|
336
|
+
*/
|
|
337
|
+
async flush() {
|
|
338
|
+
if (this.flushTimer) {
|
|
339
|
+
clearTimeout(this.flushTimer);
|
|
340
|
+
this.flushTimer = null;
|
|
341
|
+
}
|
|
342
|
+
if (this.queue.length === 0) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const batch = [...this.queue];
|
|
346
|
+
this.queue = [];
|
|
347
|
+
await this.writeLogsBatch(batch);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Writes a batch of logs in a single transaction.
|
|
351
|
+
*/
|
|
352
|
+
async writeLogsBatch(logs) {
|
|
353
|
+
if (!this.db)
|
|
354
|
+
throw new Error('Database not initialized');
|
|
355
|
+
await runTransaction(this.db, 'logs', 'readwrite', (tx) => {
|
|
356
|
+
const store = tx.objectStore('logs');
|
|
357
|
+
for (const log of logs) {
|
|
358
|
+
store.add(log);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Retrieves all logs for a specific session.
|
|
364
|
+
*/
|
|
365
|
+
async getLogsForSession(sessionId) {
|
|
366
|
+
if (!this.db)
|
|
367
|
+
throw new Error('Database not initialized');
|
|
368
|
+
const transaction = this.db.transaction('logs', 'readonly');
|
|
369
|
+
const store = transaction.objectStore('logs');
|
|
370
|
+
const index = store.index('sessionId');
|
|
371
|
+
return wrapRequest(index.getAll(IDBKeyRange.only(sessionId)));
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Retrieves all archived session records.
|
|
375
|
+
*/
|
|
376
|
+
async getAllSessions() {
|
|
377
|
+
if (!this.db)
|
|
378
|
+
throw new Error('Database not initialized');
|
|
379
|
+
const transaction = this.db.transaction('sessions', 'readonly');
|
|
380
|
+
const store = transaction.objectStore('sessions');
|
|
381
|
+
const result = await wrapRequest(store.getAll());
|
|
382
|
+
return result || [];
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Prunes older sessions, keeping maxArchivedSessions.
|
|
386
|
+
* Protects active sessions in other tabs (updated in last 30s) and the current active session.
|
|
387
|
+
*/
|
|
388
|
+
async pruneSessions() {
|
|
389
|
+
if (!this.db)
|
|
390
|
+
throw new Error('Database not initialized');
|
|
391
|
+
const sessions = await this.getAllSessions();
|
|
392
|
+
if (sessions.length <= this.maxArchivedSessions) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
// Sort by createdAt ascending (oldest first)
|
|
396
|
+
sessions.sort((a, b) => a.createdAt - b.createdAt);
|
|
397
|
+
const now = Date.now();
|
|
398
|
+
// Exclude active sessions:
|
|
399
|
+
// 1. Current session
|
|
400
|
+
// 2. Active sessions in other tabs (heartbeat within 2m / 120s)
|
|
401
|
+
const candidates = sessions.filter((s) => {
|
|
402
|
+
const isActiveSelf = s.id === this.sessionId;
|
|
403
|
+
const isActiveOther = s.lastUpdated && now - s.lastUpdated < 120000;
|
|
404
|
+
return !isActiveSelf && !isActiveOther;
|
|
405
|
+
});
|
|
406
|
+
const totalToPrune = sessions.length - this.maxArchivedSessions;
|
|
407
|
+
if (totalToPrune <= 0) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const sessionsToPrune = candidates.slice(0, totalToPrune);
|
|
411
|
+
for (const session of sessionsToPrune) {
|
|
412
|
+
const transaction = this.db.transaction('sessions', 'readwrite');
|
|
413
|
+
const store = transaction.objectStore('sessions');
|
|
414
|
+
await wrapRequest(store.delete(session.id));
|
|
415
|
+
await new Promise((resolve, reject) => {
|
|
416
|
+
const transaction = this.db.transaction('logs', 'readwrite');
|
|
417
|
+
const store = transaction.objectStore('logs');
|
|
418
|
+
const index = store.index('sessionId');
|
|
419
|
+
const request = index.openCursor(IDBKeyRange.only(session.id));
|
|
420
|
+
request.onsuccess = () => {
|
|
421
|
+
const cursor = request.result;
|
|
422
|
+
if (cursor) {
|
|
423
|
+
cursor.delete();
|
|
424
|
+
cursor.continue();
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
resolve();
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
request.onerror = () => reject(request.error);
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Streams logs for a session via cursor.
|
|
436
|
+
*/
|
|
437
|
+
async streamLogsForSession(sessionId, onLog) {
|
|
438
|
+
if (!this.db)
|
|
439
|
+
throw new Error('Database not initialized');
|
|
440
|
+
return new Promise((resolve, reject) => {
|
|
441
|
+
const transaction = this.db.transaction('logs', 'readonly');
|
|
442
|
+
const store = transaction.objectStore('logs');
|
|
443
|
+
const index = store.index('sessionId');
|
|
444
|
+
const request = index.openCursor(IDBKeyRange.only(sessionId));
|
|
445
|
+
request.onsuccess = () => {
|
|
446
|
+
const cursor = request.result;
|
|
447
|
+
if (cursor) {
|
|
448
|
+
onLog(cursor.value);
|
|
449
|
+
cursor.continue();
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
resolve();
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
request.onerror = () => reject(request.error);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Flushes outstanding logs, stops heartbeat, and closes connection.
|
|
460
|
+
*/
|
|
461
|
+
async close() {
|
|
462
|
+
this.stopHeartbeat();
|
|
463
|
+
await this.flush();
|
|
464
|
+
if (this.db) {
|
|
465
|
+
this.db.close();
|
|
466
|
+
this.db = null;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Generates a unique, randomized Session ID string.
|
|
473
|
+
*/
|
|
474
|
+
function generateSessionId() {
|
|
475
|
+
return `session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
class EventChannel {
|
|
479
|
+
channel;
|
|
480
|
+
listeners = new Map();
|
|
481
|
+
constructor(name) {
|
|
482
|
+
this.channel = new BroadcastChannel(name);
|
|
483
|
+
this.channel.onmessage = (event) => {
|
|
484
|
+
const data = event.data;
|
|
485
|
+
if (data && typeof data.type === 'string') {
|
|
486
|
+
const callbacks = this.listeners.get(data.type);
|
|
487
|
+
if (callbacks) {
|
|
488
|
+
for (const callback of callbacks) {
|
|
489
|
+
try {
|
|
490
|
+
callback(data.payload);
|
|
491
|
+
}
|
|
492
|
+
catch (e) {
|
|
493
|
+
console.error('Error in EventChannel callback:', e);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Publishes an event to the BroadcastChannel.
|
|
502
|
+
*/
|
|
503
|
+
publish(type, payload) {
|
|
504
|
+
this.channel.postMessage({ type, payload });
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Subscribes to a specific event type. Returns an unsubscribe function.
|
|
508
|
+
*/
|
|
509
|
+
subscribe(type, callback) {
|
|
510
|
+
if (!this.listeners.has(type)) {
|
|
511
|
+
this.listeners.set(type, new Set());
|
|
512
|
+
}
|
|
513
|
+
this.listeners.get(type).add(callback);
|
|
514
|
+
return () => {
|
|
515
|
+
const callbacks = this.listeners.get(type);
|
|
516
|
+
if (callbacks) {
|
|
517
|
+
callbacks.delete(callback);
|
|
518
|
+
if (callbacks.size === 0) {
|
|
519
|
+
this.listeners.delete(type);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Closes the underlying BroadcastChannel.
|
|
526
|
+
*/
|
|
527
|
+
close() {
|
|
528
|
+
this.channel.close();
|
|
529
|
+
this.listeners.clear();
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Returns the actual active window object, prioritizing unsafeWindow in Tampermonkey.
|
|
535
|
+
*/
|
|
536
|
+
function getWazeWindow() {
|
|
537
|
+
if (typeof globalThis.unsafeWindow !== 'undefined') {
|
|
538
|
+
return globalThis.unsafeWindow;
|
|
539
|
+
}
|
|
540
|
+
if (typeof window !== 'undefined') {
|
|
541
|
+
return window;
|
|
542
|
+
}
|
|
543
|
+
return globalThis;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
class SessionManager {
|
|
547
|
+
dbPrefix;
|
|
548
|
+
scriptVersion;
|
|
549
|
+
wmeSDK;
|
|
550
|
+
sessionId = null;
|
|
551
|
+
tabId;
|
|
552
|
+
channel = null;
|
|
553
|
+
constructor(config) {
|
|
554
|
+
this.dbPrefix = config.dbPrefix;
|
|
555
|
+
this.scriptVersion = config.scriptVersion;
|
|
556
|
+
this.wmeSDK = config.wmeSDK;
|
|
557
|
+
this.tabId = `tab_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Initializes the session, handling tab isolation and duplicates.
|
|
561
|
+
*/
|
|
562
|
+
async initSession() {
|
|
563
|
+
const channelName = `wme-logstream-tab-isolation-${this.dbPrefix}`;
|
|
564
|
+
this.channel = new EventChannel(channelName);
|
|
565
|
+
let candidateId = sessionStorage.getItem('wme_logstream_session_id');
|
|
566
|
+
if (!candidateId || (await this._probeConflict(candidateId))) {
|
|
567
|
+
candidateId = generateSessionId();
|
|
568
|
+
sessionStorage.setItem('wme_logstream_session_id', candidateId);
|
|
569
|
+
}
|
|
570
|
+
this.sessionId = candidateId;
|
|
571
|
+
// Listen to respond to other tabs' probes
|
|
572
|
+
this.channel.subscribe('PROBE', (payload) => {
|
|
573
|
+
if (payload.sessionId === this.sessionId && payload.tabId !== this.tabId) {
|
|
574
|
+
// Send conflict response
|
|
575
|
+
this.channel?.publish('CONFLICT', {
|
|
576
|
+
sessionId: this.sessionId,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
return this.sessionId;
|
|
581
|
+
}
|
|
582
|
+
async _probeConflict(sessionId = this.sessionId) {
|
|
583
|
+
if (!sessionId || !this.channel) {
|
|
584
|
+
throw new Error('Session has not been initialized or channel is closed.');
|
|
585
|
+
}
|
|
586
|
+
return new Promise((resolve) => {
|
|
587
|
+
const timeout = setTimeout(() => {
|
|
588
|
+
resolve(false);
|
|
589
|
+
}, 50);
|
|
590
|
+
const unsubscribeConflict = this.channel.subscribe('CONFLICT', (payload) => {
|
|
591
|
+
if (payload.sessionId === sessionId) {
|
|
592
|
+
unsubscribeConflict?.();
|
|
593
|
+
clearTimeout(timeout);
|
|
594
|
+
resolve(true);
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
this.channel.publish('PROBE', {
|
|
598
|
+
sessionId,
|
|
599
|
+
tabId: this.tabId,
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Defensively extract the WME version from passed SDK or global window context.
|
|
605
|
+
*/
|
|
606
|
+
getWmeVersion() {
|
|
607
|
+
try {
|
|
608
|
+
// 1. Try passed SDK
|
|
609
|
+
if (typeof this.wmeSDK?.getWMEVersion === 'function') {
|
|
610
|
+
return this.wmeSDK.getWMEVersion();
|
|
611
|
+
}
|
|
612
|
+
// 2. Try window.W.version or unsafeWindow.W.version via getWazeWindow
|
|
613
|
+
const wazeWin = getWazeWindow();
|
|
614
|
+
if (wazeWin?.W?.version) {
|
|
615
|
+
return wazeWin.W.version;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
catch {
|
|
619
|
+
// Safe fallback
|
|
620
|
+
}
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Returns current session ID if initialized
|
|
625
|
+
*/
|
|
626
|
+
getSessionId() {
|
|
627
|
+
if (!this.sessionId) {
|
|
628
|
+
throw new Error('Session has not been initialized. Call initSession() first.');
|
|
629
|
+
}
|
|
630
|
+
return this.sessionId;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Closes the session event channel
|
|
634
|
+
*/
|
|
635
|
+
close() {
|
|
636
|
+
if (this.channel) {
|
|
637
|
+
this.channel.close();
|
|
638
|
+
this.channel = null;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
class ZipStreamer {
|
|
644
|
+
dbManager;
|
|
645
|
+
constructor(dbManager) {
|
|
646
|
+
this.dbManager = dbManager;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Exports all sessions and their logs from IndexedDB into a Zip compressed Blob.
|
|
650
|
+
*/
|
|
651
|
+
async exportAllSessions() {
|
|
652
|
+
// Flush any pending logs to IndexedDB first
|
|
653
|
+
await this.dbManager.flush();
|
|
654
|
+
const sessions = await this.dbManager.getAllSessions();
|
|
655
|
+
const chunks = [];
|
|
656
|
+
return new Promise((resolve, reject) => {
|
|
657
|
+
const zip = new fflate__namespace.Zip();
|
|
658
|
+
zip.ondata = (err, data, final) => {
|
|
659
|
+
if (err) {
|
|
660
|
+
reject(err);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
chunks.push(data);
|
|
664
|
+
if (final) {
|
|
665
|
+
resolve(new Blob(chunks, { type: 'application/zip' }));
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
const run = async () => {
|
|
669
|
+
const encoder = new TextEncoder();
|
|
670
|
+
for (const session of sessions) {
|
|
671
|
+
const folderName = session.id.startsWith('session_')
|
|
672
|
+
? session.id
|
|
673
|
+
: `session_${session.id}`;
|
|
674
|
+
// 1. Add session details file
|
|
675
|
+
const detailsFile = new fflate__namespace.ZipDeflate(`${folderName}/details.json`);
|
|
676
|
+
zip.add(detailsFile);
|
|
677
|
+
const detailsJson = JSON.stringify(session);
|
|
678
|
+
detailsFile.push(encoder.encode(detailsJson), true);
|
|
679
|
+
// 2. Add session logs file (streamed)
|
|
680
|
+
const logsFile = new fflate__namespace.ZipDeflate(`${folderName}/logs.json`);
|
|
681
|
+
zip.add(logsFile);
|
|
682
|
+
// Write starting bracket
|
|
683
|
+
logsFile.push(encoder.encode('['));
|
|
684
|
+
let first = true;
|
|
685
|
+
// Stream logs directly from IndexedDB cursor
|
|
686
|
+
await this.dbManager.streamLogsForSession(session.id, (log) => {
|
|
687
|
+
const logContent = (first ? '' : ',') + JSON.stringify(log);
|
|
688
|
+
first = false;
|
|
689
|
+
logsFile.push(encoder.encode(logContent));
|
|
690
|
+
});
|
|
691
|
+
// Write closing bracket and finalize this file
|
|
692
|
+
logsFile.push(encoder.encode(']'), true);
|
|
693
|
+
}
|
|
694
|
+
// Finalize the zip archive
|
|
695
|
+
zip.end();
|
|
696
|
+
};
|
|
697
|
+
run().catch(reject);
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const SEVERITY = {
|
|
703
|
+
TRACE: 0,
|
|
704
|
+
DEBUG: 1,
|
|
705
|
+
INFO: 2,
|
|
706
|
+
WARN: 3,
|
|
707
|
+
ERROR: 4,
|
|
708
|
+
FATAL: 5,
|
|
709
|
+
};
|
|
710
|
+
function cloneDeep(val) {
|
|
711
|
+
if (val === undefined)
|
|
712
|
+
return undefined;
|
|
713
|
+
if (typeof structuredClone !== 'undefined') {
|
|
714
|
+
try {
|
|
715
|
+
return structuredClone(val);
|
|
716
|
+
}
|
|
717
|
+
catch {
|
|
718
|
+
// Fallback on serialization error
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return JSON.parse(JSON.stringify(val));
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Prunes an object to a maximum depth, stringifying any nested objects/arrays at that depth limit.
|
|
725
|
+
*/
|
|
726
|
+
function pruneToDepth(val, depth, maxDepth) {
|
|
727
|
+
if (typeof val !== 'object' || val === null) {
|
|
728
|
+
return val;
|
|
729
|
+
}
|
|
730
|
+
if (depth >= maxDepth) {
|
|
731
|
+
try {
|
|
732
|
+
return JSON.stringify(val);
|
|
733
|
+
}
|
|
734
|
+
catch {
|
|
735
|
+
return '[Object]';
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if (Array.isArray(val)) {
|
|
739
|
+
return val.map((item) => pruneToDepth(item, depth + 1, maxDepth));
|
|
740
|
+
}
|
|
741
|
+
const result = {};
|
|
742
|
+
for (const key of Object.keys(val)) {
|
|
743
|
+
result[key] = pruneToDepth(val[key], depth + 1, maxDepth);
|
|
744
|
+
}
|
|
745
|
+
return result;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* The main logger class for WME LogStream.
|
|
749
|
+
* Handles hierarchical scoping, console styling, state diff tracking, and persistent database exports.
|
|
750
|
+
*/
|
|
751
|
+
class LogStream {
|
|
752
|
+
minLogLevel;
|
|
753
|
+
dispatcher;
|
|
754
|
+
scopes;
|
|
755
|
+
states;
|
|
756
|
+
patcher = jsondiffpatch.create();
|
|
757
|
+
dbManagerPromise = null;
|
|
758
|
+
/**
|
|
759
|
+
* Initializes a LogStream instance.
|
|
760
|
+
* Prefer using `LogStream.create()` for a simplified, zero-boilerplate setup.
|
|
761
|
+
*/
|
|
762
|
+
constructor(config, internal) {
|
|
763
|
+
this.minLogLevel = config.minLogLevel ?? 'INFO';
|
|
764
|
+
if (internal) {
|
|
765
|
+
this.dispatcher = internal.dispatcher;
|
|
766
|
+
this.scopes = internal.scopes;
|
|
767
|
+
this.states = internal.states;
|
|
768
|
+
this.dbManagerPromise = internal.dbManagerPromise ?? null;
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
this.dispatcher = new Dispatcher();
|
|
772
|
+
this.scopes = [];
|
|
773
|
+
this.states = new Map();
|
|
774
|
+
if (config.dbManager) {
|
|
775
|
+
this.dbManagerPromise = Promise.resolve(config.dbManager);
|
|
776
|
+
}
|
|
777
|
+
if (config.pipes) {
|
|
778
|
+
for (const pipe of config.pipes) {
|
|
779
|
+
this.dispatcher.addPipe(pipe);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Synchronously creates a configured LogStream instance, initializing console outputs
|
|
786
|
+
* and setting up background persistence/session managers if enabled.
|
|
787
|
+
*
|
|
788
|
+
* @param config The unified configuration options for console branding and persistence.
|
|
789
|
+
* @returns A pre-configured LogStream instance ready to log immediately.
|
|
790
|
+
*/
|
|
791
|
+
static create(config) {
|
|
792
|
+
const pipes = [];
|
|
793
|
+
let dbManagerPromise = null;
|
|
794
|
+
if (config.brand !== null) {
|
|
795
|
+
pipes.push(new ConsolePipe(config.brand));
|
|
796
|
+
}
|
|
797
|
+
if (config.persist) {
|
|
798
|
+
if (!config.dbPrefix || !config.scriptVersion) {
|
|
799
|
+
throw new Error('dbPrefix and scriptVersion are required when persist is true');
|
|
800
|
+
}
|
|
801
|
+
dbManagerPromise = (async () => {
|
|
802
|
+
const sessionManager = new SessionManager({
|
|
803
|
+
dbPrefix: config.dbPrefix,
|
|
804
|
+
scriptVersion: config.scriptVersion,
|
|
805
|
+
wmeSDK: config.wmeSDK,
|
|
806
|
+
});
|
|
807
|
+
const sessionId = await sessionManager.initSession();
|
|
808
|
+
const wmeVersion = sessionManager.getWmeVersion();
|
|
809
|
+
const dbManager = new IndexedDBManager({
|
|
810
|
+
dbPrefix: config.dbPrefix,
|
|
811
|
+
scriptVersion: config.scriptVersion,
|
|
812
|
+
sessionId,
|
|
813
|
+
wmeVersion,
|
|
814
|
+
maxArchivedSessions: config.maxArchivedSessions,
|
|
815
|
+
flushIntervalMs: config.flushIntervalMs,
|
|
816
|
+
});
|
|
817
|
+
await dbManager.init();
|
|
818
|
+
await dbManager.pruneSessions();
|
|
819
|
+
return dbManager;
|
|
820
|
+
})();
|
|
821
|
+
pipes.push(new IndexedDBPipe(dbManagerPromise));
|
|
822
|
+
}
|
|
823
|
+
const dispatcher = new Dispatcher();
|
|
824
|
+
for (const pipe of pipes) {
|
|
825
|
+
dispatcher.addPipe(pipe);
|
|
826
|
+
}
|
|
827
|
+
return new LogStream({
|
|
828
|
+
minLogLevel: config.minLogLevel,
|
|
829
|
+
}, {
|
|
830
|
+
dispatcher,
|
|
831
|
+
scopes: [],
|
|
832
|
+
states: new Map(),
|
|
833
|
+
dbManagerPromise,
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Spawns a child logger with a hierarchical scope prefix appended to the console/db entries.
|
|
838
|
+
*
|
|
839
|
+
* @param name The scope namespace (e.g. 'API', 'Auth').
|
|
840
|
+
* @returns A new LogStream child instance carrying the hierarchical scope.
|
|
841
|
+
*/
|
|
842
|
+
scope(name) {
|
|
843
|
+
return new LogStream({ minLogLevel: this.minLogLevel }, {
|
|
844
|
+
dispatcher: this.dispatcher,
|
|
845
|
+
scopes: [...this.scopes, name],
|
|
846
|
+
states: this.states,
|
|
847
|
+
dbManagerPromise: this.dbManagerPromise,
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
/** Logs a TRACE level message. */
|
|
851
|
+
trace(message, data) {
|
|
852
|
+
this.log('TRACE', message, data);
|
|
853
|
+
}
|
|
854
|
+
/** Logs a DEBUG level message. */
|
|
855
|
+
debug(message, data) {
|
|
856
|
+
this.log('DEBUG', message, data);
|
|
857
|
+
}
|
|
858
|
+
/** Logs an INFO level message. */
|
|
859
|
+
info(message, data) {
|
|
860
|
+
this.log('INFO', message, data);
|
|
861
|
+
}
|
|
862
|
+
/** Logs a WARN level message. */
|
|
863
|
+
warn(message, data) {
|
|
864
|
+
this.log('WARN', message, data);
|
|
865
|
+
}
|
|
866
|
+
/** Logs an ERROR level message. */
|
|
867
|
+
error(message, data) {
|
|
868
|
+
this.log('ERROR', message, data);
|
|
869
|
+
}
|
|
870
|
+
/** Logs a FATAL level message. */
|
|
871
|
+
fatal(message, data) {
|
|
872
|
+
this.log('FATAL', message, data);
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Compares the given state object against its previous version, calculating the deep-diff
|
|
876
|
+
* and logging only modified properties as a DEBUG entry.
|
|
877
|
+
*
|
|
878
|
+
* @param name The state tracking key.
|
|
879
|
+
* @param state The state object to track.
|
|
880
|
+
* @param options Config options, e.g., max depth for nested objects.
|
|
881
|
+
*/
|
|
882
|
+
stateDelta(name, state, options) {
|
|
883
|
+
const maxDepth = options?.maxDepth ?? 3;
|
|
884
|
+
if (!this.states.has(name)) {
|
|
885
|
+
this.states.set(name, cloneDeep(state));
|
|
886
|
+
this.log('DEBUG', `State Delta: ${name} (initial)`, { delta: state });
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
const prevState = this.states.get(name);
|
|
890
|
+
// Prune both objects up to maxDepth before diffing
|
|
891
|
+
const prunedPrev = pruneToDepth(prevState, 0, maxDepth);
|
|
892
|
+
const prunedState = pruneToDepth(state, 0, maxDepth);
|
|
893
|
+
const delta = this.patcher.diff(prunedPrev, prunedState);
|
|
894
|
+
if (delta !== undefined) {
|
|
895
|
+
this.states.set(name, cloneDeep(state));
|
|
896
|
+
this.log('DEBUG', `State Delta: ${name}`, { delta });
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Returns a bound function that tracks changes to a specific state key over time.
|
|
901
|
+
*
|
|
902
|
+
* @param name The state tracking key.
|
|
903
|
+
* @param options Config options, e.g., max depth for nested objects.
|
|
904
|
+
* @returns A function accepting a state object to diff and log.
|
|
905
|
+
*/
|
|
906
|
+
createStateTracker(name, options) {
|
|
907
|
+
return (state) => {
|
|
908
|
+
this.stateDelta(name, state, options);
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Asynchronously exports all stored database sessions and logs into a single compressed ZIP file.
|
|
913
|
+
*
|
|
914
|
+
* @returns A promise resolving to the compressed ZIP Blob.
|
|
915
|
+
*/
|
|
916
|
+
async exportLogs() {
|
|
917
|
+
if (!this.dbManagerPromise) {
|
|
918
|
+
throw new Error('Persistence (IndexedDB) is not enabled on this LogStream.');
|
|
919
|
+
}
|
|
920
|
+
const dbManager = await this.dbManagerPromise;
|
|
921
|
+
const streamer = new ZipStreamer(dbManager);
|
|
922
|
+
return streamer.exportAllSessions();
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Triggers a browser-native file download of the compressed log sessions.
|
|
926
|
+
*
|
|
927
|
+
* @param filename Custom download filename. Defaults to `wme-logs-<timestamp>.xlog`.
|
|
928
|
+
*/
|
|
929
|
+
async downloadLogs(filename) {
|
|
930
|
+
const blob = await this.exportLogs();
|
|
931
|
+
if (typeof document === 'undefined') {
|
|
932
|
+
throw new Error('downloadLogs can only be called in a browser environment.');
|
|
933
|
+
}
|
|
934
|
+
const name = filename || `wme-logs-${Date.now()}.xlog`;
|
|
935
|
+
const url = URL.createObjectURL(blob);
|
|
936
|
+
const a = document.createElement('a');
|
|
937
|
+
a.href = url;
|
|
938
|
+
a.download = name;
|
|
939
|
+
a.click();
|
|
940
|
+
setTimeout(() => URL.revokeObjectURL(url), 100);
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Triggers a flush of all buffered logs on registered pipes.
|
|
944
|
+
*/
|
|
945
|
+
async flush() {
|
|
946
|
+
await this.dispatcher.flush();
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Closes underlying active database connections if persistence is enabled.
|
|
950
|
+
*/
|
|
951
|
+
async close() {
|
|
952
|
+
if (this.dbManagerPromise) {
|
|
953
|
+
const dbManager = await this.dbManagerPromise;
|
|
954
|
+
await dbManager.close();
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
log(level, message, data) {
|
|
958
|
+
if (SEVERITY[level] < SEVERITY[this.minLogLevel]) {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const payload = {
|
|
962
|
+
timestamp: Date.now(),
|
|
963
|
+
level,
|
|
964
|
+
scopes: [...this.scopes],
|
|
965
|
+
message,
|
|
966
|
+
};
|
|
967
|
+
if (data !== undefined) {
|
|
968
|
+
payload.data = data;
|
|
969
|
+
}
|
|
970
|
+
this.dispatcher.dispatch(payload);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
exports.ConsolePipe = ConsolePipe;
|
|
975
|
+
exports.IndexedDBManager = IndexedDBManager;
|
|
976
|
+
exports.IndexedDBPipe = IndexedDBPipe;
|
|
977
|
+
exports.LogStream = LogStream;
|
|
978
|
+
exports.SessionManager = SessionManager;
|
|
979
|
+
exports.ZipStreamer = ZipStreamer;
|
|
980
|
+
//# sourceMappingURL=index.cjs.js.map
|