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:
NimBold
2026-07-10 19:02:39 +03:30
parent 4f4c655de6
commit 248f3869ad
13 changed files with 348 additions and 114 deletions
-2
View File
@@ -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
});
-2
View File
@@ -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
+55
View File
@@ -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']);
});
});
+92
View File
@@ -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);
};