Milestone v1 (v2.0.0): Mokosh — Session Capture #1
@@ -612,6 +612,22 @@ async function downloadArchive(archiveBlob: Blob) {
|
||||
}
|
||||
|
||||
// Сохранение архива (полный процесс)
|
||||
//
|
||||
// Plan 01-13 Task 9 (debug session 01-09-save-stops-recording): SAVE is
|
||||
// one-shot per SPEC intent (`Тз расширение фаза1.md`: "one click MUST
|
||||
// produce a self-contained archive"). After the zip lands (success path)
|
||||
// OR an EmptyVideoBufferError surfaces (empty-buffer path), the SW
|
||||
// auto-stops the recording — dispatches STOP_RECORDING to offscreen,
|
||||
// flips isRecording=false, transitions to IDLE. This mirrors the Bug B
|
||||
// `user-stopped-sharing` branch in the RECORDING_ERROR handler: deliberate
|
||||
// stop, NOT an error — no recovery notification.
|
||||
//
|
||||
// The post-save teardown is invoked from a `finally` block so both
|
||||
// branches share the same teardown semantics: the operator perceives
|
||||
// SAVE click as ALWAYS stopping the session, success or empty-buffer
|
||||
// alike. Otherwise an empty-buffer error would leave the recording live
|
||||
// + an ERR badge + a recovery notification, defeating the operator UX
|
||||
// contract.
|
||||
async function saveArchive() {
|
||||
try {
|
||||
logger.log('Starting archive save process');
|
||||
@@ -648,9 +664,9 @@ async function saveArchive() {
|
||||
|
||||
rrwebEvents = response?.events ?? [];
|
||||
userEvents = response?.userEvents ?? [];
|
||||
|
||||
|
||||
logger.log(`✓ Received ${rrwebEvents.length} rrweb events, ${userEvents.length} user events`);
|
||||
|
||||
|
||||
// Логируем первые несколько событий для отладки
|
||||
if (rrwebEvents.length > 0) {
|
||||
logger.log('First rrweb event sample:', JSON.stringify(rrwebEvents[0]).substring(0, 200) + '...');
|
||||
@@ -658,7 +674,7 @@ async function saveArchive() {
|
||||
if (userEvents.length > 0) {
|
||||
logger.log('First user event:', userEvents[0]);
|
||||
}
|
||||
|
||||
|
||||
} catch (messageError) {
|
||||
logger.warn('✗ Failed to get events from content script:', messageError);
|
||||
logger.warn('This may happen on special pages (chrome://, about:blank) or if content script is not injected');
|
||||
@@ -700,6 +716,52 @@ async function saveArchive() {
|
||||
}
|
||||
}
|
||||
return { success: false, error };
|
||||
} finally {
|
||||
// Plan 01-13 Task 9 post-save auto-stop. Runs on BOTH the success
|
||||
// path (zip downloaded) and the catch path (EmptyVideoBufferError +
|
||||
// any other createArchive failure). The operator clicked SAVE — the
|
||||
// session is over from their perspective regardless of internal
|
||||
// success/failure. Releasing the MediaStream + transitioning to
|
||||
// IDLE is the consistent operator-facing UX contract.
|
||||
//
|
||||
// Three steps, each independently best-effort:
|
||||
// 1. STOP_RECORDING → offscreen via chrome.runtime.sendMessage.
|
||||
// The offscreen recorder's onMessage handler at
|
||||
// src/offscreen/recorder.ts:848 dispatches STOP_RECORDING
|
||||
// through `stopRecording()`, which nulls mediaStream, stops
|
||||
// the MediaRecorder, releases all tracks (closes Chrome's
|
||||
// sharing banner), and clears the rotation timer.
|
||||
// 2. isRecording = false on the SW side so the next
|
||||
// chrome.action.onClicked toolbar gesture isn't blocked by
|
||||
// the guard at line ~877.
|
||||
// 3. setIdleMode → empties the popup (re-enables onClicked
|
||||
// restart path per MV3 contract) and paints the OFF badge.
|
||||
//
|
||||
// RECORDING_ERROR path note: if the catch branch above dispatched
|
||||
// RECORDING_ERROR{error:'empty-video-buffer'}, the SW's own
|
||||
// RECORDING_ERROR handler at line ~756 would run setErrorMode
|
||||
// (yellow ERR badge + recovery notification). The current ordering
|
||||
// — error broadcast in catch, then setIdleMode in finally — means
|
||||
// the FINAL state is IDLE: setIdleMode lands AFTER the RECORDING_ERROR
|
||||
// self-message because the handler is synchronous and runs
|
||||
// immediately on dispatch (chrome.runtime.sendMessage to self in
|
||||
// SW context is dispatched within the same event loop turn). The
|
||||
// post-save IDLE state machine wins; the operator sees a clean
|
||||
// OFF badge + empty popup + sharing banner closed. The
|
||||
// empty-buffer notification still fires (operator sees the
|
||||
// recovery notif briefly) but the badge resolves to OFF — a
|
||||
// documented trade-off keeping the empty-buffer surface visible
|
||||
// while preserving the SAVE=stop contract.
|
||||
try {
|
||||
chrome.runtime.sendMessage({ type: 'STOP_RECORDING' });
|
||||
} catch (sendErr) {
|
||||
// Offscreen may be unreachable mid-teardown; non-fatal — SW state
|
||||
// still transitions to IDLE so the operator regains the restart
|
||||
// path.
|
||||
logger.warn('STOP_RECORDING post-save dispatch failed:', sendErr);
|
||||
}
|
||||
isRecording = false;
|
||||
setIdleMode();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user