Files
mokosh/vite.config.ts
Mark 555eb0543f chore: import broken Phase-1 extension as received
Snapshot of /home/parf/Downloads/manifest.zip as delivered, before any
GSD-driven remediation. Contains a partially-broken first attempt at the
Russian SPEC "Тз расширение фаза1.md" (Phase 1 of operator-session-recorder).

Source layout:
- manifest.json — MV3 declaration with tabCapture/activeTab/downloads/etc.
- src/background/index.ts — service worker (video buffer + archive packaging)
- src/content/index.ts — rrweb + user-event logger
- src/popup/{index.html,index.ts,style.css} — Russian popup UI
- offscreen/{index.html,index.ts} — orphaned offscreen (see audit)
- vite.config.ts — inline plugin emitting a separate live offscreen.js
- generate-icons.js, icons/ — minimal PNG icons
- "Тз расширение фаза1.md" — authoritative Russian SPEC

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 15:16:23 +02:00

227 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { defineConfig } from 'vite'
import { crx } from '@crxjs/vite-plugin'
import manifest from './manifest.json'
export default defineConfig({
plugins: [
crx({
manifest,
contentScripts: {
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
}
},
copyPublicDir: true,
publicDir: 'public',
}
})