Milestone v1 (v2.0.0): Mokosh — Session Capture #1

Merged
strategy155 merged 297 commits from gsd/phase-04-harden-clean-up-optional into main 2026-05-31 15:34:17 +00:00
Showing only changes of commit d5bb948d95 - Show all commits

View File

@@ -1,12 +1,19 @@
import { Logger } from '../shared/logger'; import { Logger } from '../shared/logger';
import { base64ToBlob } from '../shared/binary';
import type { import type {
Message, Message,
TransferredVideoChunk,
VideoChunk, VideoChunk,
SessionMetadata, SessionMetadata,
VideoBufferResponse VideoBufferResponse
} from '../shared/types'; } from '../shared/types';
import JSZip from 'jszip'; import JSZip from 'jszip';
// Default MIME applied when a wire chunk somehow lacks a type
// field (defense-in-depth: in normal operation the offscreen recorder
// always populates it from chunk.data.type). Matches D-20 strict codec.
const VIDEO_MIME_FALLBACK = 'video/webm;codecs=vp9';
const logger = new Logger('Main'); const logger = new Logger('Main');
// Состояние // Состояние
@@ -88,6 +95,11 @@ chrome.runtime.onConnect.addListener((port) => {
// per-request listener installed in getVideoBufferFromOffscreen). // per-request listener installed in getVideoBufferFromOffscreen).
}); });
// 2 s budget covers the worst-case round-trip: offscreen base64-encodes
// up to ~15 chunks of ~100 KB each (~1.5 MB raw → ~2 MB base64) in
// well under 100 ms, post-message + JSON parse adds < 50 ms, leaving
// plenty of headroom. Bumping later is cheap if real-world recordings
// produce significantly larger buffers; today this is sufficient.
const BUFFER_FETCH_TIMEOUT_MS = 2_000; const BUFFER_FETCH_TIMEOUT_MS = 2_000;
async function getVideoBufferFromOffscreen(): Promise<VideoBufferResponse> { async function getVideoBufferFromOffscreen(): Promise<VideoBufferResponse> {
@@ -110,7 +122,30 @@ async function getVideoBufferFromOffscreen(): Promise<VideoBufferResponse> {
) { ) {
clearTimeout(timer); clearTimeout(timer);
port.onMessage.removeListener(handler); port.onMessage.removeListener(handler);
const chunks = (msg as { chunks?: VideoChunk[] }).chunks ?? []; // D-12 fix: chunks arrive as TransferredVideoChunk[] (base64
// string + MIME). Decode each back into a VideoChunk so
// mergeVideoChunks keeps operating on real Blobs. See
// src/shared/binary.ts and the GREEN block of
// tests/offscreen/port-serialization.test.ts.
const wireChunks =
(msg as { chunks?: TransferredVideoChunk[] }).chunks ?? [];
const chunks: VideoChunk[] = [];
for (const wire of wireChunks) {
try {
chunks.push({
data: base64ToBlob(wire.data, wire.type || VIDEO_MIME_FALLBACK),
timestamp: wire.timestamp,
isFirst: wire.isFirst,
});
} catch (err) {
logger.warn(
'base64ToBlob failed; skipping chunk',
'timestamp:', wire.timestamp,
'isFirst:', wire.isFirst,
'error:', err,
);
}
}
resolve({ chunks }); resolve({ chunks });
} }
}; };