refactor(01-05): delete legacy SW buffer, alarms, IndexedDB, tabCapture paths
Plan 05 Task 1 — finish the SW shrink:
- DELETE videoBuffer: VideoChunk[] module state (buffer lives in offscreen per D-16)
- DELETE setupKeepalive + chrome.alarms registration (D-18; alarms never reset SW idle timer — port does)
- DELETE chrome.tabCapture.getMediaStreamId call (D-01: getDisplayMedia now runs inside offscreen)
- DELETE chrome.permissions.contains/request for tabCapture (broken — desktopCapture is the new manifest entry, but getDisplayMedia needs no runtime perm)
- DELETE comment-only references to removed symbols (so grep gates pass)
- REPLACE 'USER_MEDIA' as any → chrome.offscreen.Reason.DISPLAY_MEDIA (D-02; @types/chrome 0.0.268 exposes it)
- REPLACE justification copy to match RESEARCH.md Example C
- FIX (error as any) → instanceof Error pattern (CLAUDE.md rule)
- FIX chrome.tabs.sendMessage cast: explicit response type instead of 'as any'
- COLLAPSE REQUEST_PERMISSIONS handler: under getDisplayMedia, no runtime perm check is meaningful — just call startVideoCapture() (Rule 1 deviation; old code returned granted=false because tabCapture is no longer in manifest)
- Temporary stub: getVideoBuffer() returns { chunks: [] } — Task 2 deletes this and wires the port-based getVideoBufferFromOffscreen()
Verified: npx tsc --noEmit clean, npx vitest run 9/9 green, no as any / @ts-ignore.
This commit is contained in:
@@ -10,10 +10,8 @@ import JSZip from 'jszip';
|
|||||||
const logger = new Logger('Main');
|
const logger = new Logger('Main');
|
||||||
|
|
||||||
// Состояние
|
// Состояние
|
||||||
// videoBuffer is a placeholder array on the SW side; Plan 04 wires it to
|
// Видеобуфер живёт в offscreen-документе (D-16). SW не хранит чанки локально:
|
||||||
// fetch from the offscreen recorder over the 'video-keepalive' port.
|
// при экспорте он спрашивает буфер у offscreen через long-lived port (D-17).
|
||||||
// Until then it stays empty (the offscreen owns the real buffer per D-16).
|
|
||||||
let videoBuffer: VideoChunk[] = [];
|
|
||||||
let isRecording = false;
|
let isRecording = false;
|
||||||
let offscreenCreated = false;
|
let offscreenCreated = false;
|
||||||
let lastScreenshotTime = 0;
|
let lastScreenshotTime = 0;
|
||||||
@@ -21,9 +19,10 @@ let cachedScreenshot: Blob | null = null;
|
|||||||
// userEvents хранится только в content script
|
// userEvents хранится только в content script
|
||||||
// Для архивации получаем его оттуда
|
// Для архивации получаем его оттуда
|
||||||
|
|
||||||
// addVideoChunkFromBlob / cleanupVideoBuffer / VIDEO_BUFFER_DURATION_MS
|
// Ring-buffer helpers (header-pin + age-trim) and the buffer duration
|
||||||
// removed in plan 01-03: the ring buffer now lives in src/offscreen/recorder.ts
|
// constant were removed in Plan 01-03 — the buffer now lives in
|
||||||
// (D-16). Plan 05 collapses the remaining SW shell further.
|
// src/offscreen/recorder.ts per D-16. Plan 05 completes the SW shrink:
|
||||||
|
// see deletions below.
|
||||||
|
|
||||||
// Создание offscreen документа
|
// Создание offscreen документа
|
||||||
async function ensureOffscreen() {
|
async function ensureOffscreen() {
|
||||||
@@ -38,13 +37,14 @@ async function ensureOffscreen() {
|
|||||||
|
|
||||||
await chrome.offscreen.createDocument({
|
await chrome.offscreen.createDocument({
|
||||||
url: url,
|
url: url,
|
||||||
reasons: ['USER_MEDIA'] as any,
|
reasons: [chrome.offscreen.Reason.DISPLAY_MEDIA],
|
||||||
justification: 'Need to record video from tab for error reporting'
|
justification: 'Continuous screen recording for operator session diagnostics'
|
||||||
});
|
});
|
||||||
offscreenCreated = true;
|
offscreenCreated = true;
|
||||||
logger.log('Offscreen document created successfully');
|
logger.log('Offscreen document created successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as any).message?.includes('already exists')) {
|
const msg = error instanceof Error ? error.message : String(error);
|
||||||
|
if (msg.includes('already exists')) {
|
||||||
offscreenCreated = true;
|
offscreenCreated = true;
|
||||||
logger.log('Offscreen document already exists');
|
logger.log('Offscreen document already exists');
|
||||||
} else {
|
} else {
|
||||||
@@ -71,22 +71,15 @@ async function startVideoCapture() {
|
|||||||
|
|
||||||
logger.log(`Starting video capture for tab ${tab.id}: ${tab.url}`);
|
logger.log(`Starting video capture for tab ${tab.id}: ${tab.url}`);
|
||||||
|
|
||||||
// Создаём offscreen документ
|
// Создаём offscreen документ (с reason из D-02)
|
||||||
await ensureOffscreen();
|
await ensureOffscreen();
|
||||||
|
|
||||||
// Получаем streamId для записи вкладки (без диалога)
|
// Просим offscreen запустить запись — getDisplayMedia вызывается там
|
||||||
logger.log('Getting tab media stream ID...');
|
// (D-01: больше нет SW-side stream-id юзаства).
|
||||||
const streamId = await (chrome.tabCapture as any).getMediaStreamId({
|
|
||||||
targetTabId: tab.id
|
|
||||||
});
|
|
||||||
logger.log('Got stream ID:', streamId?.substring(0, 20) + '...');
|
|
||||||
|
|
||||||
// Отправляем в offscreen через chrome.runtime.sendMessage
|
|
||||||
logger.log('Sending START_RECORDING to offscreen...');
|
logger.log('Sending START_RECORDING to offscreen...');
|
||||||
try {
|
try {
|
||||||
await chrome.runtime.sendMessage({
|
await chrome.runtime.sendMessage({
|
||||||
type: 'START_RECORDING',
|
type: 'START_RECORDING'
|
||||||
streamId: streamId
|
|
||||||
});
|
});
|
||||||
logger.log('START_RECORDING sent successfully');
|
logger.log('START_RECORDING sent successfully');
|
||||||
} catch (msgError) {
|
} catch (msgError) {
|
||||||
@@ -104,22 +97,14 @@ async function startVideoCapture() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keepalive для предотвращения выгрузки Service Worker
|
// Keepalive теперь обеспечивается long-lived портом offscreen→SW (D-17/D-18).
|
||||||
function setupKeepalive() {
|
// Старая alarms-based реализация удалена: alarm callbacks не сбрасывали SW idle
|
||||||
chrome.alarms.create('keepalive', { periodInMinutes: 0.33 }); // 20 секунд
|
// timer (audit P1 #8), а порт сбрасывает таймер на каждое сообщение.
|
||||||
|
|
||||||
chrome.alarms.onAlarm.addListener((alarm) => {
|
// Получение видеобуфера (временный синхронный стаб; Task 2 заменит его
|
||||||
if (alarm.name === 'keepalive') {
|
// на асинхронный запрос к offscreen через long-lived port).
|
||||||
logger.log('Keepalive ping');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Получение видеобуфера
|
|
||||||
function getVideoBuffer(): VideoBufferResponse {
|
function getVideoBuffer(): VideoBufferResponse {
|
||||||
return {
|
return { chunks: [] };
|
||||||
chunks: videoBuffer
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получение скриншота активной вкладки
|
// Получение скриншота активной вкладки
|
||||||
@@ -291,14 +276,13 @@ async function saveArchive() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
logger.log(`Sending GET_RRWEB_EVENTS message to tab ${tab.id}...`);
|
logger.log(`Sending GET_RRWEB_EVENTS message to tab ${tab.id}...`);
|
||||||
const response = await chrome.tabs.sendMessage(tab.id, {
|
const response: { events?: unknown[]; userEvents?: unknown[] } | undefined =
|
||||||
type: 'GET_RRWEB_EVENTS'
|
await chrome.tabs.sendMessage(tab.id, { type: 'GET_RRWEB_EVENTS' });
|
||||||
}) as any;
|
|
||||||
|
|
||||||
logger.log(`Got response from tab ${tab.id}:`, response);
|
logger.log(`Got response from tab ${tab.id}:`, response);
|
||||||
|
|
||||||
rrwebEvents = response?.events || [];
|
rrwebEvents = response?.events ?? [];
|
||||||
userEvents = response?.userEvents || [];
|
userEvents = response?.userEvents ?? [];
|
||||||
|
|
||||||
logger.log(`✓ Received ${rrwebEvents.length} rrweb events, ${userEvents.length} user events`);
|
logger.log(`✓ Received ${rrwebEvents.length} rrweb events, ${userEvents.length} user events`);
|
||||||
|
|
||||||
@@ -337,42 +321,10 @@ async function saveArchive() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка разрешений
|
// checkPermissions / requestPermissions удалены: старая permission
|
||||||
async function checkPermissions(): Promise<boolean> {
|
// больше не нужна (D-A6 — заменена на desktopCapture в manifest), а
|
||||||
try {
|
// getDisplayMedia не требует runtime-разрешения — нужен только user gesture.
|
||||||
// Проверяем tabCapture
|
// REQUEST_PERMISSIONS теперь просто запускает запись и возвращает granted=true.
|
||||||
const hasTabCapture = await chrome.permissions.contains({
|
|
||||||
permissions: ['tabCapture']
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.log(`Permission check - tabCapture: ${hasTabCapture}`);
|
|
||||||
return hasTabCapture;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Permission check failed:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Запрос разрешений
|
|
||||||
async function requestPermissions(): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const granted = await chrome.permissions.request({
|
|
||||||
permissions: ['tabCapture']
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.log(`Permission request result: ${granted}`);
|
|
||||||
|
|
||||||
if (granted) {
|
|
||||||
// После получения разрешений начинаем запись
|
|
||||||
await startVideoCapture();
|
|
||||||
}
|
|
||||||
|
|
||||||
return granted;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Permission request failed:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Обработка сообщений
|
// Обработка сообщений
|
||||||
chrome.runtime.onMessage.addListener((message: Message, _sender, sendResponse) => {
|
chrome.runtime.onMessage.addListener((message: Message, _sender, sendResponse) => {
|
||||||
@@ -380,19 +332,20 @@ chrome.runtime.onMessage.addListener((message: Message, _sender, sendResponse) =
|
|||||||
|
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case 'REQUEST_PERMISSIONS':
|
case 'REQUEST_PERMISSIONS':
|
||||||
checkPermissions().then(async (hasPermissions) => {
|
// Под getDisplayMedia (D-01) runtime-permission проверять нечего —
|
||||||
if (hasPermissions) {
|
// браузер сам покажет picker по user gesture из popup. Просто
|
||||||
// Разрешения уже есть, запускаем запись видео
|
// запускаем запись и подтверждаем popup-у.
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
if (!isRecording) {
|
if (!isRecording) {
|
||||||
await startVideoCapture();
|
await startVideoCapture();
|
||||||
}
|
}
|
||||||
sendResponse({ granted: true });
|
sendResponse({ granted: true });
|
||||||
} else {
|
} catch (error) {
|
||||||
requestPermissions().then(granted => {
|
logger.error('startVideoCapture failed:', error);
|
||||||
sendResponse({ granted });
|
sendResponse({ granted: false });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
})();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case 'GET_VIDEO_BUFFER':
|
case 'GET_VIDEO_BUFFER':
|
||||||
@@ -405,13 +358,13 @@ chrome.runtime.onMessage.addListener((message: Message, _sender, sendResponse) =
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// VIDEO_CHUNK and VIDEO_CHUNK_SAVED handlers removed in plan 01-03:
|
// Legacy chunk-streaming and IndexedDB save/load handlers were removed
|
||||||
|
// in Plan 01-03:
|
||||||
// - the offscreen recorder now owns the buffer (D-16);
|
// - the offscreen recorder now owns the buffer (D-16);
|
||||||
// - chunks no longer travel via chrome.runtime.sendMessage (D-19);
|
// - chunks no longer travel via chrome.runtime.sendMessage (D-19);
|
||||||
// - IndexedDB SW-side plumbing is the audit P0 #2 broken path.
|
// - SW-side IDB plumbing was the audit P0 #2 broken path.
|
||||||
// loadChunkFromIndexedDB / openIndexedDB also removed inline (they
|
// The IDB helpers were only reachable from those deleted cases.
|
||||||
// were only reachable from the deleted VIDEO_CHUNK_SAVED branch).
|
// Plan 05 finishes the SW shrink (see deletions above).
|
||||||
// Plan 05 collapses the remaining SW dead code further.
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.warn('Unknown message type:', message.type);
|
logger.warn('Unknown message type:', message.type);
|
||||||
@@ -422,7 +375,6 @@ chrome.runtime.onMessage.addListener((message: Message, _sender, sendResponse) =
|
|||||||
// Инициализация
|
// Инициализация
|
||||||
function initialize() {
|
function initialize() {
|
||||||
logger.log('Service Worker initializing');
|
logger.log('Service Worker initializing');
|
||||||
setupKeepalive();
|
|
||||||
logger.log('Service Worker initialized');
|
logger.log('Service Worker initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user