refactor(01-06): delete inline copy-offscreen plugin and orphan offscreen/ directory

- Delete vite.config.ts inline copy-offscreen plugin (lines 13-216):
  the 174-line plugin that emitFile'd both offscreen HTML and a stringified
  JS module wired to tabCapture-era chromeMediaSource + IndexedDB pipeline
  (audit P0 #1 root cause; D-08 deletion target)
- Delete vite.config.ts misplaced publicDir/copyPublicDir (no public/ dir
  exists; audit P2 #17) and the manualChunks=undefined shape
- Rewrite vite.config.ts to RESEARCH.md Example B form: crx() + a single
  rollupOptions.input.offscreen pointing at src/offscreen/index.html
  (the crxjs-managed entry Plan 03 created); 21 lines total
- Delete orphan offscreen/index.ts (audit P2 #18 dead-code, D-08)
- Delete orphan offscreen/index.html (replaced by src/offscreen/index.html
  per D-07; runtime URL semantics preserved through crxjs entry binding)
- T-1-NEW-06-01 grep gate green (this.emitFile = 0)
- T-1-NEW-06-02 grep gate green (offscreen/ directory absent)
- tsc --noEmit clean; 9/9 vitest tests still green

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-15 18:10:00 +02:00
parent 9e236cbc7b
commit 23e69d0b77
3 changed files with 8 additions and 284 deletions

View File

@@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Offscreen Page</title>
</head>
<body>
<script src="./index.ts" type="module"></script>
</body>
</html>

View File

@@ -1,60 +0,0 @@
let mediaRecorder: MediaRecorder | null = null;
let videoChunks: Blob[] = [];
// Запись видео
async function startRecording(streamId: string) {
const stream = await navigator.mediaDevices.getUserMedia({
video: {
mandatory: {
chromeMediaSource: 'tab',
chromeMediaSourceId: streamId
}
} as any,
audio: false
});
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = (event) => {
if (event.data && event.data.size > 0) {
videoChunks.push(event.data);
chrome.runtime.sendMessage({
type: 'VIDEO_CHUNK',
data: event.data,
timestamp: Date.now()
});
}
};
mediaRecorder.start(1000);
}
// Остановка записи
function stopRecording() {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
}
}
// Получение чанков
function getChunks(): Blob[] {
return videoChunks;
}
// Обработка сообщений
chrome.runtime.onMessage.addListener((message) => {
switch (message.type) {
case 'START_RECORDING':
startRecording(message.streamId);
break;
case 'STOP_RECORDING':
stopRecording();
break;
case 'GET_CHUNKS':
chrome.runtime.sendMessage({
type: 'CHUNKS_RESPONSE',
chunks: getChunks()
});
break;
}
});

View File

@@ -1,6 +1,6 @@
import { defineConfig } from 'vite'
import { crx } from '@crxjs/vite-plugin'
import manifest from './manifest.json'
import { defineConfig } from 'vite';
import { crx } from '@crxjs/vite-plugin';
import manifest from './manifest.json';
export default defineConfig({
plugins: [
@@ -10,218 +10,12 @@ export default defineConfig({
injectCss: false,
},
}),
{
name: 'copy-offscreen',
generateBundle() {
this.emitFile({
type: 'asset',
fileName: 'offscreen/index.html',
source: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Offscreen Page</title>
</head>
<body>
<script type="module" src="../assets/offscreen.js"></script>
</body>
</html>
`
});
this.emitFile({
type: 'asset',
fileName: 'assets/offscreen.js',
source: `
let mediaRecorder = null;
let videoChunks = [];
let chunkCount = 0;
// IndexedDB для хранения видеочанков
let db = null;
function openIndexedDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('VideoRecorderDB', 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
db = request.result;
resolve(db);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('chunks')) {
db.createObjectStore('chunks', { keyPath: 'id' });
}
};
});
}
async function saveChunkToIndexedDB(blob, chunkId) {
if (!db) {
await openIndexedDB();
}
return new Promise((resolve, reject) => {
const transaction = db.transaction(['chunks'], 'readwrite');
const store = transaction.objectStore('chunks');
const request = store.put({
id: chunkId,
data: blob,
timestamp: Date.now()
});
request.onerror = () => reject(request.error);
request.onsuccess = () => {
console.log('[Offscreen] Chunk', chunkId, 'saved to IndexedDB, size:', blob.size);
// Отправляем уведомление в Service Worker
chrome.runtime.sendMessage({
type: 'VIDEO_CHUNK_SAVED',
chunkId: chunkId,
size: blob.size
});
resolve();
};
});
}
async function clearOldChunks() {
if (!db) {
await openIndexedDB();
}
return new Promise((resolve, reject) => {
const transaction = db.transaction(['chunks'], 'readwrite');
const store = transaction.objectStore('chunks');
const request = store.clear();
request.onerror = () => reject(request.error);
request.onsuccess = () => {
console.log('[Offscreen] Cleared old chunks from IndexedDB');
resolve();
};
});
}
chrome.runtime.onMessage.addListener((message) => {
console.log('[Offscreen] Received message:', message.type);
switch (message.type) {
case 'START_RECORDING':
startRecording(message.streamId);
break;
case 'STOP_RECORDING':
stopRecording();
break;
}
});
async function startRecording(streamId) {
console.log('[Offscreen] Starting recording with streamId:', streamId);
// Инициализируем IndexedDB
await openIndexedDB();
console.log('[Offscreen] IndexedDB initialized');
// Очищаем старые чанки
await clearOldChunks();
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: {
mandatory: {
chromeMediaSource: 'tab',
chromeMediaSourceId: streamId
}
},
audio: false
});
console.log('[Offscreen] Stream created, tracks:', stream.getTracks().length);
const videoTrack = stream.getVideoTracks()[0];
if (videoTrack) {
console.log('[Offscreen] Video track settings:', videoTrack.getSettings());
console.log('[Offscreen] Video track readyState:', videoTrack.readyState);
console.log('[Offscreen] Video track enabled:', videoTrack.enabled);
}
// Пробуем разные кодеки
const codecs = [
'video/webm; codecs=vp9',
'video/webm; codecs=vp8',
'video/webm; codecs=h264',
'video/webm'
];
let mediaRecorder = null;
for (const codec of codecs) {
try {
mediaRecorder = new MediaRecorder(stream, { mimeType: codec });
console.log('[Offscreen] MediaRecorder created with codec:', codec, 'state:', mediaRecorder.state);
break;
} catch (e) {
console.log('[Offscreen] Codec', codec, 'not supported:', e.message);
}
}
if (!mediaRecorder) {
mediaRecorder = new MediaRecorder(stream);
console.log('[Offscreen] Using default MediaRecorder');
}
mediaRecorder.ondataavailable = (event) => {
console.log('[Offscreen] Data available:', event.data.size, 'bytes, type:', event.data.type);
if (event.data && event.data.size > 0) {
chunkCount++;
console.log('[Offscreen] Sending chunk', chunkCount, 'to background, size:', event.data.size);
// Сохраняем в indexedDB вместо передачи через сообщения
saveChunkToIndexedDB(event.data, chunkCount);
} else {
console.log('[Offscreen] Data available but size is 0, skipping');
}
};
mediaRecorder.onerror = (event) => {
console.error('[Offscreen] MediaRecorder error:', event);
};
mediaRecorder.onstart = () => {
console.log('[Offscreen] MediaRecorder started, state:', mediaRecorder.state);
};
mediaRecorder.onstop = () => {
console.log('[Offscreen] MediaRecorder stopped, state:', mediaRecorder.state, 'total chunks:', chunkCount);
};
// Используем интервал 200мс для более частого получения чанков с ключевыми кадрами
mediaRecorder.start(200);
console.log('[Offscreen] Recording started with interval 200ms');
} catch (error) {
console.error('[Offscreen] Error starting recording:', error);
}
}
function stopRecording() {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
console.log('[Offscreen] Recording stopped');
}
}
`
});
}
}
],
build: {
rollupOptions: {
output: {
manualChunks: undefined
}
input: {
offscreen: 'src/offscreen/index.html',
},
},
copyPublicDir: true,
publicDir: 'public',
}
})
},
});