Milestone v1 (v2.0.0): Mokosh — Session Capture #1
@@ -16,6 +16,20 @@ const VIDEO_MIME_FALLBACK = 'video/webm;codecs=vp9';
|
|||||||
|
|
||||||
const logger = new Logger('Main');
|
const logger = new Logger('Main');
|
||||||
|
|
||||||
|
// Option C: a typed error so saveArchive can distinguish the empty-video
|
||||||
|
// failure (operator-facing — shows a clear RECORDING_ERROR popup) from
|
||||||
|
// generic createArchive failures (zip/manifest/etc.). The 'empty-video-
|
||||||
|
// buffer' code joins the CaptureErrorCode union surfaced by the
|
||||||
|
// offscreen recorder (see src/offscreen/recorder.ts: classifyCaptureError)
|
||||||
|
// — same operator-facing vocabulary, different production point.
|
||||||
|
export class EmptyVideoBufferError extends Error {
|
||||||
|
readonly code = 'empty-video-buffer' as const;
|
||||||
|
constructor(detail: string) {
|
||||||
|
super(`empty-video-buffer: ${detail}`);
|
||||||
|
this.name = 'EmptyVideoBufferError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Состояние
|
// Состояние
|
||||||
// Видеобуфер живёт в offscreen-документе (D-16). SW не хранит чанки локально:
|
// Видеобуфер живёт в offscreen-документе (D-16). SW не хранит чанки локально:
|
||||||
// при экспорте он спрашивает буфер у offscreen через long-lived port (D-17).
|
// при экспорте он спрашивает буфер у offscreen через long-lived port (D-17).
|
||||||
@@ -418,14 +432,28 @@ async function createArchive(
|
|||||||
|
|
||||||
const zip = new JSZip();
|
const zip = new JSZip();
|
||||||
|
|
||||||
// Добавляем видео (D-13: каждая запись — самодостаточный WebM-сегмент)
|
// Добавляем видео (D-13: каждая запись — самодостаточный WebM-сегмент).
|
||||||
if (videoBufferResponse.segments.length > 0) {
|
//
|
||||||
const videoBlob = mergeVideoSegments(videoBufferResponse.segments);
|
// Option C (debug session empty-archive-port-race): the upstream
|
||||||
zip.file('video/last_30sec.webm', videoBlob);
|
// silent-skip branch is GONE. Shipping a zip with no video defeats the
|
||||||
logger.log(`✓ Added video: ${videoBlob.size} bytes`);
|
// entire purpose of the operator-save flow (Phase 1 goal); the operator
|
||||||
} else {
|
// must see a clear failure instead of receiving a 88 KB archive with no
|
||||||
logger.warn('✗ No video segments to add');
|
// last_30sec.webm. saveArchive's catch translates the throw into the
|
||||||
|
// {success: false, error: 'empty-video-buffer'} response shape the
|
||||||
|
// popup already handles via the RECORDING_ERROR surface.
|
||||||
|
if (videoBufferResponse.segments.length === 0) {
|
||||||
|
throw new EmptyVideoBufferError(
|
||||||
|
'no video segments available — buffer fetch returned empty (port replacement timed out, or recorder never started)',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
const videoBlob = mergeVideoSegments(videoBufferResponse.segments);
|
||||||
|
if (videoBlob.size === 0) {
|
||||||
|
throw new EmptyVideoBufferError(
|
||||||
|
`merged video blob is zero bytes (segment count=${videoBufferResponse.segments.length})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
zip.file('video/last_30sec.webm', videoBlob);
|
||||||
|
logger.log(`✓ Added video: ${videoBlob.size} bytes`);
|
||||||
|
|
||||||
// Добавляем rrweb события
|
// Добавляем rrweb события
|
||||||
const rrwebJson = JSON.stringify(rrwebEvents, null, 2);
|
const rrwebJson = JSON.stringify(rrwebEvents, null, 2);
|
||||||
@@ -573,6 +601,23 @@ async function saveArchive() {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to save archive:', error);
|
logger.error('Failed to save archive:', error);
|
||||||
|
// Option C: the empty-video failure is operator-visible. Emit
|
||||||
|
// RECORDING_ERROR so the popup's existing handler can surface it
|
||||||
|
// (same channel codec-unsupported, user-cancelled, etc. ride).
|
||||||
|
// Other createArchive failures (zip libs, JSZip internals) stay
|
||||||
|
// SW-side only — they're not actionable by the operator.
|
||||||
|
if (error instanceof EmptyVideoBufferError) {
|
||||||
|
try {
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
type: 'RECORDING_ERROR',
|
||||||
|
error: error.code,
|
||||||
|
});
|
||||||
|
} catch (sendErr) {
|
||||||
|
// Best-effort notification — if the popup is closed we have
|
||||||
|
// nothing else to do.
|
||||||
|
logger.warn('Failed to broadcast RECORDING_ERROR:', sendErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
return { success: false, error };
|
return { success: false, error };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user