mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-10 11:37:21 +00:00
fix: harden media handoff and live logs
Reject stale extension media cookie headers before yt-dlp metadata work, preserve ordinary capture cookies, and advance the companion extension. Stream redacted diagnostic logs only while the visible Logs view is active, with bounded batched updates and race-safe snapshot handoff.
This commit is contained in:
@@ -94,7 +94,6 @@ describe('add download metadata workflow', () => {
|
||||
status: 'ready',
|
||||
generation: 4,
|
||||
requestContextVersion: 1,
|
||||
requestCookiesOmitted: true,
|
||||
formats: [{
|
||||
name: '1080p MP4',
|
||||
selector: '137+140',
|
||||
@@ -121,7 +120,6 @@ describe('add download metadata workflow', () => {
|
||||
status: 'loading',
|
||||
generation: 5,
|
||||
requestContextVersion: 2,
|
||||
requestCookiesOmitted: false,
|
||||
formats: undefined,
|
||||
selectedFormat: undefined
|
||||
});
|
||||
|
||||
@@ -27,7 +27,6 @@ export interface AddDownloadDraftRow {
|
||||
status: MetadataStatus;
|
||||
generation: number;
|
||||
requestContextVersion?: number;
|
||||
requestCookiesOmitted?: boolean;
|
||||
isMedia: boolean;
|
||||
resumable?: boolean;
|
||||
formats?: AddMediaFormat[];
|
||||
@@ -94,7 +93,6 @@ export const reconcileDownloadRows = (
|
||||
status: 'loading',
|
||||
generation: preserved.generation + 1,
|
||||
requestContextVersion,
|
||||
requestCookiesOmitted: false,
|
||||
isMedia: preserved.isMedia || forcedMedia,
|
||||
formats: preserved.isMedia || forcedMedia ? undefined : preserved.formats,
|
||||
selectedFormat: preserved.isMedia || forcedMedia ? undefined : preserved.selectedFormat
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
appendBoundedLogEntries,
|
||||
liveLogEntry,
|
||||
mergeLogSnapshotAndLiveEntries,
|
||||
persistedLogEntry,
|
||||
pushBoundedLogEntry,
|
||||
type LogEntry
|
||||
} from './logEntries';
|
||||
|
||||
const entry = (message: string): LogEntry => ({ level: 'Info', message });
|
||||
|
||||
describe('log entry streaming', () => {
|
||||
it('derives levels from persisted formatted lines', () => {
|
||||
expect(persistedLogEntry('[2026-07-10][18:00:00][ERROR][firelink] failed')).toEqual({
|
||||
level: 'Error',
|
||||
message: '[2026-07-10][18:00:00][ERROR][firelink] failed'
|
||||
});
|
||||
});
|
||||
|
||||
it('does not double-format backend Webview log lines', () => {
|
||||
const message = '[2026-07-10][18:00:00][WARN][firelink] retrying';
|
||||
expect(liveLogEntry(4, message).message).toBe(message);
|
||||
});
|
||||
|
||||
it('formats an unformatted plugin event with its numeric level', () => {
|
||||
expect(liveLogEntry(3, 'download started', new Date('2026-07-10T14:30:00Z'))).toEqual({
|
||||
level: 'Info',
|
||||
message: '[2026-07-10 14:30:00] [INFO] download started'
|
||||
});
|
||||
});
|
||||
|
||||
it('merges only the ordered snapshot-to-stream overlap', () => {
|
||||
expect(mergeLogSnapshotAndLiveEntries(
|
||||
[entry('one'), entry('repeat'), entry('three')],
|
||||
[entry('three'), entry('repeat'), entry('four')]
|
||||
).map(item => item.message)).toEqual(['one', 'repeat', 'three', 'repeat', 'four']);
|
||||
});
|
||||
|
||||
it('bounds burst updates to the newest entries', () => {
|
||||
expect(appendBoundedLogEntries(
|
||||
[entry('old')],
|
||||
Array.from({ length: 5 }, (_, index) => entry(`new-${index}`)),
|
||||
3
|
||||
).map(item => item.message)).toEqual(['new-2', 'new-3', 'new-4']);
|
||||
});
|
||||
|
||||
it('bounds the mutable pre-render queue without copying on every event', () => {
|
||||
const queue = [entry('old')];
|
||||
for (let index = 0; index < 5; index += 1) {
|
||||
pushBoundedLogEntry(queue, entry(`new-${index}`), 3);
|
||||
}
|
||||
expect(queue.map(item => item.message)).toEqual(['new-2', 'new-3', 'new-4']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
export type LogLevel = 'Trace' | 'Debug' | 'Info' | 'Warn' | 'Error';
|
||||
|
||||
export interface LogEntry {
|
||||
level: LogLevel;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export const MAX_LOG_LINES = 2000;
|
||||
|
||||
const LEVEL_NAMES: LogLevel[] = ['Trace', 'Debug', 'Info', 'Warn', 'Error'];
|
||||
const LIVE_LEVELS: Record<number, LogLevel> = {
|
||||
1: 'Trace',
|
||||
2: 'Debug',
|
||||
3: 'Info',
|
||||
4: 'Warn',
|
||||
5: 'Error'
|
||||
};
|
||||
|
||||
const levelFromMessage = (message: string): LogLevel | undefined =>
|
||||
LEVEL_NAMES.find(level => message.includes(`[${level.toUpperCase()}]`));
|
||||
|
||||
export const persistedLogEntry = (message: string): LogEntry => ({
|
||||
level: levelFromMessage(message) || 'Debug',
|
||||
message
|
||||
});
|
||||
|
||||
export const liveLogEntry = (
|
||||
numericLevel: number,
|
||||
message: string,
|
||||
now: Date = new Date()
|
||||
): LogEntry => {
|
||||
const level = levelFromMessage(message) || LIVE_LEVELS[numericLevel] || 'Debug';
|
||||
const alreadyFormatted = /^\[\d{4}-\d{2}-\d{2}\]\[\d{2}:\d{2}:\d{2}\]\[(TRACE|DEBUG|INFO|WARN|ERROR)\]/.test(message);
|
||||
|
||||
return {
|
||||
level,
|
||||
message: alreadyFormatted
|
||||
? message
|
||||
: `[${now.toISOString().replace('T', ' ').substring(0, 19)}] [${level.toUpperCase()}] ${message}`
|
||||
};
|
||||
};
|
||||
|
||||
export const appendBoundedLogEntries = (
|
||||
current: LogEntry[],
|
||||
additions: LogEntry[],
|
||||
limit = MAX_LOG_LINES
|
||||
): LogEntry[] => {
|
||||
if (additions.length === 0) return current;
|
||||
const combined = [...current, ...additions];
|
||||
return combined.length > limit ? combined.slice(combined.length - limit) : combined;
|
||||
};
|
||||
|
||||
export const pushBoundedLogEntry = (
|
||||
queue: LogEntry[],
|
||||
entry: LogEntry,
|
||||
limit = MAX_LOG_LINES
|
||||
): void => {
|
||||
queue.push(entry);
|
||||
if (queue.length > limit) {
|
||||
queue.splice(0, queue.length - limit);
|
||||
}
|
||||
};
|
||||
|
||||
// The live target writes after the file target, so entries received while the
|
||||
// initial disk snapshot is loading can also appear at the snapshot's tail.
|
||||
// Remove only the exact ordered overlap; global message de-duplication would
|
||||
// incorrectly hide legitimate repeated log lines.
|
||||
export const mergeLogSnapshotAndLiveEntries = (
|
||||
snapshot: LogEntry[],
|
||||
liveEntries: LogEntry[],
|
||||
limit = MAX_LOG_LINES
|
||||
): LogEntry[] => {
|
||||
const maxOverlap = Math.min(snapshot.length, liveEntries.length);
|
||||
let overlap = 0;
|
||||
|
||||
for (let candidate = maxOverlap; candidate > 0; candidate -= 1) {
|
||||
const snapshotStart = snapshot.length - candidate;
|
||||
let matches = true;
|
||||
for (let index = 0; index < candidate; index += 1) {
|
||||
if (snapshot[snapshotStart + index].message !== liveEntries[index].message) {
|
||||
matches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matches) {
|
||||
overlap = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return appendBoundedLogEntries(snapshot, liveEntries.slice(overlap), limit);
|
||||
};
|
||||
Reference in New Issue
Block a user