ray-logger-core 0.0.4 → 0.0.6
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/collectors/network/fetch.d.ts.map +1 -1
- package/collectors/network/fetch.js +5 -0
- package/collectors/network/fetch.js.map +1 -1
- package/collectors/network/utils.d.ts +3 -0
- package/collectors/network/utils.d.ts.map +1 -0
- package/collectors/network/utils.js +21 -0
- package/collectors/network/utils.js.map +1 -0
- package/collectors/network/xhr.d.ts.map +1 -1
- package/collectors/network/xhr.js +6 -1
- package/collectors/network/xhr.js.map +1 -1
- package/controller.d.ts +2 -2
- package/controller.d.ts.map +1 -1
- package/controller.js +42 -6
- package/controller.js.map +1 -1
- package/controller.test.d.ts +2 -0
- package/controller.test.d.ts.map +1 -0
- package/controller.test.js +204 -0
- package/controller.test.js.map +1 -0
- package/index.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/index.js +247 -168
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/storage/idb.d.ts +1 -5
- package/storage/idb.d.ts.map +1 -1
- package/storage/idb.js +3 -168
- package/storage/idb.js.map +1 -1
- package/storage/utils.d.ts +31 -0
- package/storage/utils.d.ts.map +1 -0
- package/storage/utils.js +169 -0
- package/storage/utils.js.map +1 -0
- package/timeline.d.ts +1 -0
- package/timeline.d.ts.map +1 -1
- package/timeline.js +13 -0
- package/timeline.js.map +1 -1
- package/types.d.ts +18 -1
- package/types.d.ts.map +1 -1
package/index.js
CHANGED
|
@@ -484,6 +484,27 @@ function noop$4() {
|
|
|
484
484
|
// noop
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
+
const BINARY_RESPONSE_BODY_PLACEHOLDER = 'arraybuffer';
|
|
488
|
+
const TEXTUAL_CONTENT_TYPE_PATTERNS = [
|
|
489
|
+
/^text\//i,
|
|
490
|
+
/^application\/json(?:\s*;|$)/i,
|
|
491
|
+
/^application\/[\w.+-]+\+json(?:\s*;|$)/i,
|
|
492
|
+
/^application\/javascript(?:\s*;|$)/i,
|
|
493
|
+
/^application\/xml(?:\s*;|$)/i,
|
|
494
|
+
/^application\/[\w.+-]+\+xml(?:\s*;|$)/i,
|
|
495
|
+
/^application\/x-www-form-urlencoded(?:\s*;|$)/i,
|
|
496
|
+
];
|
|
497
|
+
function isTextualContentType(contentType) {
|
|
498
|
+
if (!contentType) {
|
|
499
|
+
return undefined;
|
|
500
|
+
}
|
|
501
|
+
const normalized = contentType.trim();
|
|
502
|
+
if (!normalized) {
|
|
503
|
+
return undefined;
|
|
504
|
+
}
|
|
505
|
+
return TEXTUAL_CONTENT_TYPE_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
506
|
+
}
|
|
507
|
+
|
|
487
508
|
function installFetchCollector(options) {
|
|
488
509
|
const originalFetch = options.fetchRef ?? globalThis.fetch;
|
|
489
510
|
if (typeof originalFetch !== 'function' || typeof globalThis.fetch !== 'function') {
|
|
@@ -619,6 +640,10 @@ async function readResponseBody(response, config) {
|
|
|
619
640
|
if (config.enableRecordResponseBody === false) {
|
|
620
641
|
return { bodySkippedReason: 'response-body-disabled' };
|
|
621
642
|
}
|
|
643
|
+
const isTextual = isTextualContentType(response.headers.get('content-type'));
|
|
644
|
+
if (isTextual === false) {
|
|
645
|
+
return { body: BINARY_RESPONSE_BODY_PLACEHOLDER };
|
|
646
|
+
}
|
|
622
647
|
try {
|
|
623
648
|
return { body: await response.clone().text() };
|
|
624
649
|
}
|
|
@@ -821,7 +846,11 @@ function readXhrResponseBody(xhr, config) {
|
|
|
821
846
|
return { bodySkippedReason: 'response-body-disabled' };
|
|
822
847
|
}
|
|
823
848
|
if (xhr.responseType && xhr.responseType !== 'text') {
|
|
824
|
-
return {
|
|
849
|
+
return { body: BINARY_RESPONSE_BODY_PLACEHOLDER };
|
|
850
|
+
}
|
|
851
|
+
const isTextual = isTextualContentType(xhr.getResponseHeader('content-type'));
|
|
852
|
+
if (isTextual === false) {
|
|
853
|
+
return { body: BINARY_RESPONSE_BODY_PLACEHOLDER };
|
|
825
854
|
}
|
|
826
855
|
try {
|
|
827
856
|
return { body: xhr.responseText };
|
|
@@ -1100,6 +1129,19 @@ function hasRrwebFullSnapshot(events) {
|
|
|
1100
1129
|
function isRrwebFullSnapshot(event) {
|
|
1101
1130
|
return event.type === 'rrweb' && event.payload.rrwebEvent.type === 2;
|
|
1102
1131
|
}
|
|
1132
|
+
function filterRepeatedRrwebFullSnapshotsSince(events, sinceMs) {
|
|
1133
|
+
let hasWindowFullSnapshot = false;
|
|
1134
|
+
return events.filter((event) => {
|
|
1135
|
+
if (!isRrwebFullSnapshot(event) || event.timestamp < sinceMs) {
|
|
1136
|
+
return true;
|
|
1137
|
+
}
|
|
1138
|
+
if (hasWindowFullSnapshot) {
|
|
1139
|
+
return false;
|
|
1140
|
+
}
|
|
1141
|
+
hasWindowFullSnapshot = true;
|
|
1142
|
+
return true;
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1103
1145
|
function isRrwebFullSnapshotBefore(event, beforeMs) {
|
|
1104
1146
|
return isRrwebFullSnapshot(event) && event.timestamp < beforeMs;
|
|
1105
1147
|
}
|
|
@@ -1109,6 +1151,169 @@ const SESSION_META_STORE = 'sessionMeta';
|
|
|
1109
1151
|
const EVENT_CHUNKS_STORE = 'eventChunks';
|
|
1110
1152
|
const EVENT_CHUNKS_BY_NAMESPACE_MAX_TIMESTAMP_INDEX = 'byNamespaceMaxTimestamp';
|
|
1111
1153
|
const EVENT_CHUNKS_BY_NAMESPACE_MIN_TIMESTAMP_INDEX = 'byNamespaceMinTimestamp';
|
|
1154
|
+
function normalizeNonNegativeNumber(value, fallback) {
|
|
1155
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
1156
|
+
}
|
|
1157
|
+
function normalizeOptionalNonNegativeNumber(value) {
|
|
1158
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
1159
|
+
}
|
|
1160
|
+
async function collectNamespaceUsage(db) {
|
|
1161
|
+
const meta = await collectSessionMeta(db);
|
|
1162
|
+
const chunks = await collectEventChunks(db);
|
|
1163
|
+
const byNamespace = new Map();
|
|
1164
|
+
for (const record of meta) {
|
|
1165
|
+
byNamespace.set(record.namespaceKey, {
|
|
1166
|
+
namespaceKey: record.namespaceKey,
|
|
1167
|
+
updatedAt: normalizeRecordUpdatedAt(record.updatedAt),
|
|
1168
|
+
chunkCount: 0,
|
|
1169
|
+
estimatedBytes: 0,
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
for (const chunk of chunks) {
|
|
1173
|
+
const current = byNamespace.get(chunk.namespaceKey) ?? {
|
|
1174
|
+
namespaceKey: chunk.namespaceKey,
|
|
1175
|
+
updatedAt: 0,
|
|
1176
|
+
chunkCount: 0,
|
|
1177
|
+
estimatedBytes: 0,
|
|
1178
|
+
};
|
|
1179
|
+
current.chunkCount += 1;
|
|
1180
|
+
current.estimatedBytes += estimateChunkBytes(chunk);
|
|
1181
|
+
byNamespace.set(chunk.namespaceKey, current);
|
|
1182
|
+
}
|
|
1183
|
+
return [...byNamespace.values()];
|
|
1184
|
+
}
|
|
1185
|
+
function collectSessionMeta(db) {
|
|
1186
|
+
return collectCursorValues(db.transaction(SESSION_META_STORE, 'readonly').objectStore(SESSION_META_STORE).openCursor());
|
|
1187
|
+
}
|
|
1188
|
+
function collectEventChunks(db) {
|
|
1189
|
+
return collectCursorValues(db.transaction(EVENT_CHUNKS_STORE, 'readonly').objectStore(EVENT_CHUNKS_STORE).openCursor());
|
|
1190
|
+
}
|
|
1191
|
+
function collectCursorValues(request) {
|
|
1192
|
+
return new Promise((resolve, reject) => {
|
|
1193
|
+
const values = [];
|
|
1194
|
+
request.onerror = () => reject(request.error ?? new Error('IndexedDB cursor failed.'));
|
|
1195
|
+
request.onsuccess = () => {
|
|
1196
|
+
const cursor = request.result;
|
|
1197
|
+
if (!cursor) {
|
|
1198
|
+
resolve(values);
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
values.push(cursor.value);
|
|
1202
|
+
cursor.continue();
|
|
1203
|
+
};
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
async function deleteNamespace(db, namespaceKey) {
|
|
1207
|
+
const tx = db.transaction([SESSION_META_STORE, EVENT_CHUNKS_STORE], 'readwrite');
|
|
1208
|
+
tx.objectStore(SESSION_META_STORE).delete(namespaceKey);
|
|
1209
|
+
const range = IDBKeyRange.bound([namespaceKey, 0], [namespaceKey, Number.MAX_SAFE_INTEGER]);
|
|
1210
|
+
const store = tx.objectStore(EVENT_CHUNKS_STORE);
|
|
1211
|
+
const request = store.openCursor(range);
|
|
1212
|
+
request.onsuccess = () => {
|
|
1213
|
+
const cursor = request.result;
|
|
1214
|
+
if (!cursor) {
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
cursor.delete();
|
|
1218
|
+
cursor.continue();
|
|
1219
|
+
};
|
|
1220
|
+
await transactionDone(tx);
|
|
1221
|
+
}
|
|
1222
|
+
function normalizeRecordUpdatedAt(updatedAt) {
|
|
1223
|
+
return Number.isFinite(updatedAt) ? updatedAt : 0;
|
|
1224
|
+
}
|
|
1225
|
+
function estimateChunkBytes(chunk) {
|
|
1226
|
+
return JSON.stringify(chunk.events).length * 2;
|
|
1227
|
+
}
|
|
1228
|
+
function openDatabase(dbName) {
|
|
1229
|
+
return new Promise((resolve, reject) => {
|
|
1230
|
+
const request = globalThis.indexedDB.open(dbName, IDB_VERSION);
|
|
1231
|
+
request.onupgradeneeded = () => {
|
|
1232
|
+
const db = request.result;
|
|
1233
|
+
if (!db.objectStoreNames.contains(SESSION_META_STORE)) {
|
|
1234
|
+
db.createObjectStore(SESSION_META_STORE, { keyPath: 'namespaceKey' });
|
|
1235
|
+
}
|
|
1236
|
+
let eventChunksStore;
|
|
1237
|
+
if (!db.objectStoreNames.contains(EVENT_CHUNKS_STORE)) {
|
|
1238
|
+
eventChunksStore = db.createObjectStore(EVENT_CHUNKS_STORE, {
|
|
1239
|
+
keyPath: ['namespaceKey', 'chunkSeq'],
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
else {
|
|
1243
|
+
eventChunksStore = request.transaction.objectStore(EVENT_CHUNKS_STORE);
|
|
1244
|
+
}
|
|
1245
|
+
if (!eventChunksStore.indexNames.contains(EVENT_CHUNKS_BY_NAMESPACE_MAX_TIMESTAMP_INDEX)) {
|
|
1246
|
+
eventChunksStore.createIndex(EVENT_CHUNKS_BY_NAMESPACE_MAX_TIMESTAMP_INDEX, [
|
|
1247
|
+
'namespaceKey',
|
|
1248
|
+
'maxTimestamp',
|
|
1249
|
+
]);
|
|
1250
|
+
}
|
|
1251
|
+
if (!eventChunksStore.indexNames.contains(EVENT_CHUNKS_BY_NAMESPACE_MIN_TIMESTAMP_INDEX)) {
|
|
1252
|
+
eventChunksStore.createIndex(EVENT_CHUNKS_BY_NAMESPACE_MIN_TIMESTAMP_INDEX, [
|
|
1253
|
+
'namespaceKey',
|
|
1254
|
+
'minTimestamp',
|
|
1255
|
+
]);
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
1258
|
+
request.onerror = () => reject(request.error ?? new Error('Failed to open IndexedDB.'));
|
|
1259
|
+
request.onsuccess = () => resolve(request.result);
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
function requestToPromise(request) {
|
|
1263
|
+
return new Promise((resolve, reject) => {
|
|
1264
|
+
request.onerror = () => reject(request.error ?? new Error('IndexedDB request failed.'));
|
|
1265
|
+
request.onsuccess = () => resolve(request.result);
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
function transactionDone(transaction) {
|
|
1269
|
+
return new Promise((resolve, reject) => {
|
|
1270
|
+
transaction.oncomplete = () => resolve();
|
|
1271
|
+
transaction.onerror = () => reject(transaction.error ?? new Error('IndexedDB transaction failed.'));
|
|
1272
|
+
transaction.onabort = () => reject(transaction.error ?? new Error('IndexedDB transaction aborted.'));
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
function collectEventsCursor(request, options) {
|
|
1276
|
+
return new Promise((resolve, reject) => {
|
|
1277
|
+
const values = [];
|
|
1278
|
+
const sinceMs = options.sinceMs ?? Number.NEGATIVE_INFINITY;
|
|
1279
|
+
const untilMs = options.untilMs ?? Number.POSITIVE_INFINITY;
|
|
1280
|
+
request.onerror = () => reject(request.error ?? new Error('IndexedDB cursor failed.'));
|
|
1281
|
+
request.onsuccess = () => {
|
|
1282
|
+
const cursor = request.result;
|
|
1283
|
+
if (!cursor) {
|
|
1284
|
+
resolve(values);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
const chunk = cursor.value;
|
|
1288
|
+
for (const event of chunk.events) {
|
|
1289
|
+
if (event.timestamp >= sinceMs && event.timestamp <= untilMs) {
|
|
1290
|
+
values.push(event);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
cursor.continue();
|
|
1294
|
+
};
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
function findLatestRrwebFullSnapshotCursor(request, beforeMs) {
|
|
1298
|
+
return new Promise((resolve, reject) => {
|
|
1299
|
+
request.onerror = () => reject(request.error ?? new Error('IndexedDB cursor failed.'));
|
|
1300
|
+
request.onsuccess = () => {
|
|
1301
|
+
const cursor = request.result;
|
|
1302
|
+
if (!cursor) {
|
|
1303
|
+
resolve(undefined);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
const chunk = cursor.value;
|
|
1307
|
+
const snapshot = latestRrwebFullSnapshotBefore(chunk.events, beforeMs);
|
|
1308
|
+
if (snapshot) {
|
|
1309
|
+
resolve(snapshot);
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
cursor.continue();
|
|
1313
|
+
};
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1112
1317
|
class IndexedDbEventStore {
|
|
1113
1318
|
namespaceKey;
|
|
1114
1319
|
dbName;
|
|
@@ -1276,168 +1481,6 @@ function emptyCleanupResult() {
|
|
|
1276
1481
|
chunksAfter: 0,
|
|
1277
1482
|
};
|
|
1278
1483
|
}
|
|
1279
|
-
function normalizeNonNegativeNumber(value, fallback) {
|
|
1280
|
-
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
1281
|
-
}
|
|
1282
|
-
function normalizeOptionalNonNegativeNumber(value) {
|
|
1283
|
-
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
1284
|
-
}
|
|
1285
|
-
async function collectNamespaceUsage(db) {
|
|
1286
|
-
const meta = await collectSessionMeta(db);
|
|
1287
|
-
const chunks = await collectEventChunks(db);
|
|
1288
|
-
const byNamespace = new Map();
|
|
1289
|
-
for (const record of meta) {
|
|
1290
|
-
byNamespace.set(record.namespaceKey, {
|
|
1291
|
-
namespaceKey: record.namespaceKey,
|
|
1292
|
-
updatedAt: normalizeRecordUpdatedAt(record.updatedAt),
|
|
1293
|
-
chunkCount: 0,
|
|
1294
|
-
estimatedBytes: 0,
|
|
1295
|
-
});
|
|
1296
|
-
}
|
|
1297
|
-
for (const chunk of chunks) {
|
|
1298
|
-
const current = byNamespace.get(chunk.namespaceKey) ?? {
|
|
1299
|
-
namespaceKey: chunk.namespaceKey,
|
|
1300
|
-
updatedAt: 0,
|
|
1301
|
-
chunkCount: 0,
|
|
1302
|
-
estimatedBytes: 0,
|
|
1303
|
-
};
|
|
1304
|
-
current.chunkCount += 1;
|
|
1305
|
-
current.estimatedBytes += estimateChunkBytes(chunk);
|
|
1306
|
-
byNamespace.set(chunk.namespaceKey, current);
|
|
1307
|
-
}
|
|
1308
|
-
return [...byNamespace.values()];
|
|
1309
|
-
}
|
|
1310
|
-
function collectSessionMeta(db) {
|
|
1311
|
-
return collectCursorValues(db.transaction(SESSION_META_STORE, 'readonly').objectStore(SESSION_META_STORE).openCursor());
|
|
1312
|
-
}
|
|
1313
|
-
function collectEventChunks(db) {
|
|
1314
|
-
return collectCursorValues(db.transaction(EVENT_CHUNKS_STORE, 'readonly').objectStore(EVENT_CHUNKS_STORE).openCursor());
|
|
1315
|
-
}
|
|
1316
|
-
function collectCursorValues(request) {
|
|
1317
|
-
return new Promise((resolve, reject) => {
|
|
1318
|
-
const values = [];
|
|
1319
|
-
request.onerror = () => reject(request.error ?? new Error('IndexedDB cursor failed.'));
|
|
1320
|
-
request.onsuccess = () => {
|
|
1321
|
-
const cursor = request.result;
|
|
1322
|
-
if (!cursor) {
|
|
1323
|
-
resolve(values);
|
|
1324
|
-
return;
|
|
1325
|
-
}
|
|
1326
|
-
values.push(cursor.value);
|
|
1327
|
-
cursor.continue();
|
|
1328
|
-
};
|
|
1329
|
-
});
|
|
1330
|
-
}
|
|
1331
|
-
async function deleteNamespace(db, namespaceKey) {
|
|
1332
|
-
const tx = db.transaction([SESSION_META_STORE, EVENT_CHUNKS_STORE], 'readwrite');
|
|
1333
|
-
tx.objectStore(SESSION_META_STORE).delete(namespaceKey);
|
|
1334
|
-
const range = IDBKeyRange.bound([namespaceKey, 0], [namespaceKey, Number.MAX_SAFE_INTEGER]);
|
|
1335
|
-
const store = tx.objectStore(EVENT_CHUNKS_STORE);
|
|
1336
|
-
const request = store.openCursor(range);
|
|
1337
|
-
request.onsuccess = () => {
|
|
1338
|
-
const cursor = request.result;
|
|
1339
|
-
if (!cursor) {
|
|
1340
|
-
return;
|
|
1341
|
-
}
|
|
1342
|
-
cursor.delete();
|
|
1343
|
-
cursor.continue();
|
|
1344
|
-
};
|
|
1345
|
-
await transactionDone(tx);
|
|
1346
|
-
}
|
|
1347
|
-
function normalizeRecordUpdatedAt(updatedAt) {
|
|
1348
|
-
return Number.isFinite(updatedAt) ? updatedAt : 0;
|
|
1349
|
-
}
|
|
1350
|
-
function estimateChunkBytes(chunk) {
|
|
1351
|
-
return JSON.stringify(chunk.events).length * 2;
|
|
1352
|
-
}
|
|
1353
|
-
function openDatabase(dbName) {
|
|
1354
|
-
return new Promise((resolve, reject) => {
|
|
1355
|
-
const request = globalThis.indexedDB.open(dbName, IDB_VERSION);
|
|
1356
|
-
request.onupgradeneeded = () => {
|
|
1357
|
-
const db = request.result;
|
|
1358
|
-
if (!db.objectStoreNames.contains(SESSION_META_STORE)) {
|
|
1359
|
-
db.createObjectStore(SESSION_META_STORE, { keyPath: 'namespaceKey' });
|
|
1360
|
-
}
|
|
1361
|
-
let eventChunksStore;
|
|
1362
|
-
if (!db.objectStoreNames.contains(EVENT_CHUNKS_STORE)) {
|
|
1363
|
-
eventChunksStore = db.createObjectStore(EVENT_CHUNKS_STORE, {
|
|
1364
|
-
keyPath: ['namespaceKey', 'chunkSeq'],
|
|
1365
|
-
});
|
|
1366
|
-
}
|
|
1367
|
-
else {
|
|
1368
|
-
eventChunksStore = request.transaction.objectStore(EVENT_CHUNKS_STORE);
|
|
1369
|
-
}
|
|
1370
|
-
if (!eventChunksStore.indexNames.contains(EVENT_CHUNKS_BY_NAMESPACE_MAX_TIMESTAMP_INDEX)) {
|
|
1371
|
-
eventChunksStore.createIndex(EVENT_CHUNKS_BY_NAMESPACE_MAX_TIMESTAMP_INDEX, [
|
|
1372
|
-
'namespaceKey',
|
|
1373
|
-
'maxTimestamp',
|
|
1374
|
-
]);
|
|
1375
|
-
}
|
|
1376
|
-
if (!eventChunksStore.indexNames.contains(EVENT_CHUNKS_BY_NAMESPACE_MIN_TIMESTAMP_INDEX)) {
|
|
1377
|
-
eventChunksStore.createIndex(EVENT_CHUNKS_BY_NAMESPACE_MIN_TIMESTAMP_INDEX, [
|
|
1378
|
-
'namespaceKey',
|
|
1379
|
-
'minTimestamp',
|
|
1380
|
-
]);
|
|
1381
|
-
}
|
|
1382
|
-
};
|
|
1383
|
-
request.onerror = () => reject(request.error ?? new Error('Failed to open IndexedDB.'));
|
|
1384
|
-
request.onsuccess = () => resolve(request.result);
|
|
1385
|
-
});
|
|
1386
|
-
}
|
|
1387
|
-
function requestToPromise(request) {
|
|
1388
|
-
return new Promise((resolve, reject) => {
|
|
1389
|
-
request.onerror = () => reject(request.error ?? new Error('IndexedDB request failed.'));
|
|
1390
|
-
request.onsuccess = () => resolve(request.result);
|
|
1391
|
-
});
|
|
1392
|
-
}
|
|
1393
|
-
function transactionDone(transaction) {
|
|
1394
|
-
return new Promise((resolve, reject) => {
|
|
1395
|
-
transaction.oncomplete = () => resolve();
|
|
1396
|
-
transaction.onerror = () => reject(transaction.error ?? new Error('IndexedDB transaction failed.'));
|
|
1397
|
-
transaction.onabort = () => reject(transaction.error ?? new Error('IndexedDB transaction aborted.'));
|
|
1398
|
-
});
|
|
1399
|
-
}
|
|
1400
|
-
function collectEventsCursor(request, options) {
|
|
1401
|
-
return new Promise((resolve, reject) => {
|
|
1402
|
-
const values = [];
|
|
1403
|
-
const sinceMs = options.sinceMs ?? Number.NEGATIVE_INFINITY;
|
|
1404
|
-
const untilMs = options.untilMs ?? Number.POSITIVE_INFINITY;
|
|
1405
|
-
request.onerror = () => reject(request.error ?? new Error('IndexedDB cursor failed.'));
|
|
1406
|
-
request.onsuccess = () => {
|
|
1407
|
-
const cursor = request.result;
|
|
1408
|
-
if (!cursor) {
|
|
1409
|
-
resolve(values);
|
|
1410
|
-
return;
|
|
1411
|
-
}
|
|
1412
|
-
const chunk = cursor.value;
|
|
1413
|
-
for (const event of chunk.events) {
|
|
1414
|
-
if (event.timestamp >= sinceMs && event.timestamp <= untilMs) {
|
|
1415
|
-
values.push(event);
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
cursor.continue();
|
|
1419
|
-
};
|
|
1420
|
-
});
|
|
1421
|
-
}
|
|
1422
|
-
function findLatestRrwebFullSnapshotCursor(request, beforeMs) {
|
|
1423
|
-
return new Promise((resolve, reject) => {
|
|
1424
|
-
request.onerror = () => reject(request.error ?? new Error('IndexedDB cursor failed.'));
|
|
1425
|
-
request.onsuccess = () => {
|
|
1426
|
-
const cursor = request.result;
|
|
1427
|
-
if (!cursor) {
|
|
1428
|
-
resolve(undefined);
|
|
1429
|
-
return;
|
|
1430
|
-
}
|
|
1431
|
-
const chunk = cursor.value;
|
|
1432
|
-
const snapshot = latestRrwebFullSnapshotBefore(chunk.events, beforeMs);
|
|
1433
|
-
if (snapshot) {
|
|
1434
|
-
resolve(snapshot);
|
|
1435
|
-
return;
|
|
1436
|
-
}
|
|
1437
|
-
cursor.continue();
|
|
1438
|
-
};
|
|
1439
|
-
});
|
|
1440
|
-
}
|
|
1441
1484
|
|
|
1442
1485
|
function resolveSession(options) {
|
|
1443
1486
|
const storage = getSessionStorage();
|
|
@@ -1581,7 +1624,7 @@ class WebLoggerCoreController {
|
|
|
1581
1624
|
this.#estimatedBufferBytes += estimateEventBytes(recorded);
|
|
1582
1625
|
if (this.#estimatedBufferBytes >= this.config.memoryFlushThresholdBytes) {
|
|
1583
1626
|
void this.flushToIdb().catch(() => {
|
|
1584
|
-
//
|
|
1627
|
+
// Flush errors are exposed through onFlushError and must not break host app execution.
|
|
1585
1628
|
});
|
|
1586
1629
|
}
|
|
1587
1630
|
return recorded;
|
|
@@ -1589,7 +1632,7 @@ class WebLoggerCoreController {
|
|
|
1589
1632
|
async flushToIdb() {
|
|
1590
1633
|
this.#assertActive();
|
|
1591
1634
|
if (!this.#store) {
|
|
1592
|
-
return;
|
|
1635
|
+
return this.#notifyFlushSuccess({ status: 'skipped', reason: 'indexeddb-unavailable' });
|
|
1593
1636
|
}
|
|
1594
1637
|
await this.#waitUntilReady();
|
|
1595
1638
|
if (this.#flushPromise) {
|
|
@@ -1597,13 +1640,18 @@ class WebLoggerCoreController {
|
|
|
1597
1640
|
}
|
|
1598
1641
|
const pendingEvents = this.#timeline.pending();
|
|
1599
1642
|
if (pendingEvents.length === 0) {
|
|
1600
|
-
return;
|
|
1643
|
+
return this.#notifyFlushSuccess({ status: 'skipped', reason: 'empty' });
|
|
1601
1644
|
}
|
|
1602
1645
|
this.#flushPromise = this.#store
|
|
1603
1646
|
.appendChunk(pendingEvents)
|
|
1604
|
-
.then(() => {
|
|
1647
|
+
.then((chunk) => {
|
|
1605
1648
|
this.#timeline.clear(pendingEvents);
|
|
1606
1649
|
this.#estimatedBufferBytes = estimateEventsBytes(this.#timeline.pending());
|
|
1650
|
+
return this.#notifyFlushSuccess(chunk ? createFlushResult(chunk) : { status: 'skipped', reason: 'empty' });
|
|
1651
|
+
})
|
|
1652
|
+
.catch((error) => {
|
|
1653
|
+
this.#notifyFlushError(error);
|
|
1654
|
+
throw error;
|
|
1607
1655
|
})
|
|
1608
1656
|
.finally(() => {
|
|
1609
1657
|
this.#flushPromise = undefined;
|
|
@@ -1628,7 +1676,8 @@ class WebLoggerCoreController {
|
|
|
1628
1676
|
const rrwebAnchor = hasRrwebFullSnapshot(windowEvents)
|
|
1629
1677
|
? undefined
|
|
1630
1678
|
: await this.#readLatestRrwebFullSnapshotBefore(sinceMs);
|
|
1631
|
-
const
|
|
1679
|
+
const mergedEvents = rrwebAnchor ? mergeEvents([rrwebAnchor], windowEvents) : windowEvents;
|
|
1680
|
+
const events = filterRepeatedRrwebFullSnapshotsSince(mergedEvents, sinceMs);
|
|
1632
1681
|
const includesRrwebBaseline = hasRrwebFullSnapshot(events);
|
|
1633
1682
|
return this.#createExport('since', events, {
|
|
1634
1683
|
sinceMs,
|
|
@@ -1677,6 +1726,23 @@ class WebLoggerCoreController {
|
|
|
1677
1726
|
await this.#flushPromise;
|
|
1678
1727
|
}
|
|
1679
1728
|
}
|
|
1729
|
+
#notifyFlushSuccess(result) {
|
|
1730
|
+
try {
|
|
1731
|
+
this.config.onFlushSuccess?.(result);
|
|
1732
|
+
}
|
|
1733
|
+
catch {
|
|
1734
|
+
// Flush observer errors must not affect recording or export flow.
|
|
1735
|
+
}
|
|
1736
|
+
return result;
|
|
1737
|
+
}
|
|
1738
|
+
#notifyFlushError(error) {
|
|
1739
|
+
try {
|
|
1740
|
+
this.config.onFlushError?.(error);
|
|
1741
|
+
}
|
|
1742
|
+
catch {
|
|
1743
|
+
// Flush observer errors must not mask the original flush failure.
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1680
1746
|
async #waitUntilReady() {
|
|
1681
1747
|
await this.#readyPromise;
|
|
1682
1748
|
}
|
|
@@ -1741,6 +1807,19 @@ function estimateEventBytes(event) {
|
|
|
1741
1807
|
function estimateEventsBytes(events) {
|
|
1742
1808
|
return events.reduce((total, event) => total + estimateEventBytes(event), 0);
|
|
1743
1809
|
}
|
|
1810
|
+
function createFlushResult(chunk) {
|
|
1811
|
+
return {
|
|
1812
|
+
status: 'flushed',
|
|
1813
|
+
namespaceKey: chunk.namespaceKey,
|
|
1814
|
+
chunkSeq: chunk.chunkSeq,
|
|
1815
|
+
eventCount: chunk.events.length,
|
|
1816
|
+
minSeq: chunk.minSeq,
|
|
1817
|
+
maxSeq: chunk.maxSeq,
|
|
1818
|
+
minTimestamp: chunk.minTimestamp,
|
|
1819
|
+
maxTimestamp: chunk.maxTimestamp,
|
|
1820
|
+
estimatedBytes: estimateEventsBytes(chunk.events),
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1744
1823
|
function mergeEvents(persistedEvents, memoryEvents) {
|
|
1745
1824
|
const bySeq = new Map();
|
|
1746
1825
|
for (const event of persistedEvents) {
|