// Типы для обмена сообщениями между компонентами export type MessageType = | 'REQUEST_PERMISSIONS' | 'PERMISSIONS_GRANTED' | 'PERMISSIONS_DENIED' | 'GET_VIDEO_BUFFER' | 'VIDEO_BUFFER_RESPONSE' | 'GET_RRWEB_EVENTS' | 'RRWEB_EVENTS_RESPONSE' | 'SAVE_ARCHIVE' | 'ARCHIVE_SAVED' | 'START_RECORDING' | 'STOP_RECORDING' | 'RECORDING_ERROR' | 'OFFSCREEN_READY'; // IN-05 fix: Message instead of `T = any`. Call sites that // access `(msg as Message).data` MUST narrow before use — `unknown` // forces that discipline, which `any` silently bypassed (every // downstream access was implicitly `any` and skipped type checking). // The Message inbox in src/background/index.ts and src/offscreen/recorder.ts // already destructure via `msg.type` switch and never read `.data` // directly, so the migration was safe (no widening needed). export interface Message { type: MessageType; data?: T; tabId?: number; } // Типы сообщений в long-lived port (offscreen ↔ SW; D-17 / Plan 04 / D-13) // // Option C (debug session empty-archive-port-race) extends the surface: // - 'PONG' completes the health-probe loop: offscreen pings, SW echoes, // offscreen tracks last-pong time. Replaces the 290 s pre-emptive // `setTimeout` reconnect (whose race window weaponised the upstream // silent-skip in createArchive — see the bisect notes in the debug // session for full lineage). // - REQUEST_BUFFER + BUFFER carry an architectural `requestId` so the // SW can match a response to the in-flight request that issued it. // This retires the silent-cross-talk failure mode where a stale // BUFFER from a prior request would route into a newer Promise. // // D-P2-01 Blob URL migration triplet (P0-6 fix, Phase 02 Plan 02-02): // - CREATE_DOWNLOAD_URL + DOWNLOAD_URL + REVOKE_DOWNLOAD_URL. // SW posts CREATE_DOWNLOAD_URL with the archive bytes as base64; // offscreen mints URL.createObjectURL and responds with DOWNLOAD_URL; // SW calls chrome.downloads.download(url); when chrome.downloads // .onChanged reports 'complete' or 'interrupted', SW posts // REVOKE_DOWNLOAD_URL so offscreen can free the URL. The base64 // wire-format reuses the D-12 precedent from src/shared/binary.ts — // chrome.runtime.Port JSON-serializes payloads, so Blob → empty // object; base64 round-trips cleanly. See // .planning/phases/02-stabilize-export-pipeline/02-CONTEXT.md // D-P2-01 for the full architectural rationale. export type PortMessageType = | 'PING' | 'PONG' | 'REQUEST_BUFFER' | 'BUFFER' | 'CREATE_DOWNLOAD_URL' // SW → offscreen: "here is a Blob as base64; mint a URL for it" | 'DOWNLOAD_URL' // offscreen → SW: "here is the minted blob:URL" | 'REVOKE_DOWNLOAD_URL'; // SW → offscreen: "you can revoke this URL now" export interface PortMessage { type: PortMessageType; // Per-request correlation id (Option C). The SW generates a uuid for // each REQUEST_BUFFER call and only resolves on BUFFER responses that // echo the same id. PING/PONG do not carry a requestId — they are // pure liveness signals. // Also used by the D-P2-01 triplet: CREATE_DOWNLOAD_URL → DOWNLOAD_URL // pair carries a per-mint requestId so concurrent mints (theoretically // possible across two SAVE flows) cannot cross-talk. REVOKE_DOWNLOAD_URL // is fire-and-forget — no requestId required. requestId?: string; // Wire-format (D-12 base64 transfer + D-13 segment lifecycle): // segments travel as TransferredVideoSegment[] because // chrome.runtime.Port JSON-serializes payloads across extension // contexts and JSON.stringify(blob) === "{}" loses binary content. // Each entry is one self-contained ~10 s WebM segment (EBML header // + seed keyframe). The receive side reconstructs VideoSegment[] // via src/shared/binary.ts. segments?: TransferredVideoSegment[]; // D-P2-01 (P0-6 fix): archive bytes as base64 + MIME type for Blob // reconstruction in the offscreen document on CREATE_DOWNLOAD_URL. // Reuses src/shared/binary.ts blobToBase64/base64ToBlob helpers; same // wire-format precedent as the BUFFER segment transfer. dataBase64?: string; mimeType?: string; // D-P2-01: the minted blob:URL string on DOWNLOAD_URL (offscreen → SW) // OR the URL to free on REVOKE_DOWNLOAD_URL (SW → offscreen). The // string is a `blob:chrome-extension:///` URL minted by // URL.createObjectURL in the offscreen document origin. url?: string; } // In-memory segment shape used by mergeVideoSegments after the SW // decodes the wire format. Под D-13 каждый сегмент — самодостаточный // WebM-блок ≈ 10 секунд (свой EBML-заголовок и стартовый keyframe). export interface VideoSegment { data: Blob; timestamp: number; } // Wire-format for video segments traveling across the offscreen↔SW // chrome.runtime.Port boundary. Replaces the previous Blob payload, // which failed because Blob is not JSON-serializable. // See debug session d12-blob-port-transfer-fails (resolved) and the // GREEN block of tests/offscreen/port-serialization.test.ts for the // pinned shape. The header-pin flag from the D-09..D-11 era is gone // — under D-13 every segment IS implicitly its own header. export interface TransferredVideoSegment { data: string; // base64-encoded blob bytes (no data: prefix) type: string; // MIME type to apply when reconstructing the Blob timestamp: number; } // Лог событий пользователя // IN-05-companion: `meta` uses `unknown` values instead of `any`. The // field carries free-form diagnostic data (HTTP status, stack frames, // XHR method, etc.); none of the SW consumers READ from meta — it // only flows from content script → archive JSON. `unknown` documents // "treat me opaquely" honestly; `any` silently allowed any access. export interface UserEvent { timestamp: number; type: 'click' | 'input' | 'navigation' | 'js_error' | 'network_error'; target: string; value?: string; url: string; meta?: Record; } // Метаданные сессии. // // Phase 2 Plan 02-03 — D-P2-02 + D-P2-03 schema-breaking amendment // (2026-05-20; closes audit P1 #10): // // - `url: string` REPLACED by `urls: string[]`. Captures the operator's // multi-tab context during the rolling 30 s recording window — not // just the active-at-save tab. Fed by the new // `src/background/tab-url-tracker.ts` module via chrome.tabs.onActivated // + chrome.tabs.onUpdated listeners + a SAVE-time // chrome.tabs.query({}) snapshot (DEC-011 Amendment 1 grants the // `tabs` permission). Empty array IS permitted (F2 — whole-desktop- // no-tab session). // // - `schemaVersion: string` ADDED as the 8th field. Value '2' marks the // D-P2-02 url→urls cutover; future schema bumps increment. The 8th // field name was planner-suggested in Plan 02-01 Task 3 and ratified // here as the lockstep for tests/build/strict-meta-json-validation.test.ts // EXPECTED_KEYS. // // Total field count: 8 (per D-P2-03 strict exact-shape rule). // // Field-emission order follows source-declaration order: TypeScript object // literals preserve insertion order, and JSON.stringify emits in insertion // order per ECMA-262 §9.1.11.1 — so the meta.json output mirrors this // interface line-by-line. export interface SessionMetadata { schemaVersion: string; timestamp: string; urls: string[]; userAgent: string; extensionVersion: string; videoBufferSeconds: number; logDurationMinutes: number; totalEvents: number; } // Сообщения для popup export interface PopupState { isRecording: boolean; hasPermissions: boolean; status: 'idle' | 'saving' | 'done'; } // Ответы от Service Worker export interface VideoBufferResponse { segments: VideoSegment[]; } // IN-05-companion: `events: unknown[]` instead of `any[]`. The events // array carries rrweb's `EventWithTime` shape which is too large to // import here without pulling rrweb into the shared module; the // consumers (background/index.ts) already type it as `unknown[]` and // pass through to JSZip. `any[]` would have silently bypassed all // downstream type-checking. export interface RrwebEventsResponse { events: unknown[]; }