diff --git a/src/background/index.ts b/src/background/index.ts index 0de9b4a..5311aba 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -8,6 +8,12 @@ import type { VideoBufferResponse } from '../shared/types'; import { remuxSegments } from './webm-remux'; +// Plan 02-03 D-P2-02 — tab-url tracker for meta.urls (multi-tab context). +// initTabUrlTracker is called from initialize(); snapshotOpenTabs + +// getTabUrlsSeen are called from createArchive() at SAVE time. The +// always-on charter (Plan 01-09 Amendment 3) is preserved — no +// clearTabUrlsSeen call in the save flow. +import { initTabUrlTracker, snapshotOpenTabs, getTabUrlsSeen } from './tab-url-tracker'; import JSZip from 'jszip'; // ─── Plan 01-13: NO SW-side test hook gate (Approach B) ────────────── @@ -709,10 +715,43 @@ async function createArchive( // the running version once manifest.json bumps to 1.0.1+. The Chrome // runtime API is always available in the SW context, so no fallback // is needed. + // + // Plan 02-03 D-P2-02 + D-P2-03 amendment (2026-05-20; closes audit P1 #10): + // - `url: string` REPLACED by `urls: string[]` sourced from the + // tab-url tracker (chrome.tabs.onActivated + onUpdated event-driven + // accumulation + snapshotOpenTabs SAVE-time defensive enumeration + // via chrome.tabs.query({})). + // - `schemaVersion: '2'` ADDED to mark the schema-breaking cutover. + // - Field-emission order follows the SessionMetadata interface line + // order — TypeScript preserves object-literal insertion order, and + // JSON.stringify emits in insertion order per ECMA-262. + // + // DEC-011 Amendment 1: with `tabs` permission granted, snapshot every + // currently-open tab's URL at SAVE time as a defensive fallback. This + // captures tabs the operator OPENED during the 30 s window but never + // activated (so the onActivated listener never fired for them). Dedup + // + order preservation is handled by the tracker itself. + try { + await snapshotOpenTabs(); + } catch (err) { + logger.warn('snapshotOpenTabs failed at SAVE time (continuing with tracker state):', err); + } + const urls = getTabUrlsSeen(); + if (urls.length === 0) { + // Meaningful state per F2 (plan-checker iteration 1): whole-desktop + // recording with NO browser tabs open at SAVE time. The empty array + // IS the canonical representation — NO fake extension-origin URL + // inserted. tests/background/meta-json-urls-schema.test.ts pins this + // contract. + logger.warn( + 'createArchive: tabUrlsSeen is empty after snapshotOpenTabs — emitting meta.urls=[] (whole-desktop session with no browser tabs)', + ); + } const manifest = chrome.runtime.getManifest(); const metadata: SessionMetadata = { + schemaVersion: '2', timestamp: new Date().toISOString(), - url: new URL(chrome.runtime.getURL('')).origin, + urls, userAgent: navigator.userAgent, extensionVersion: manifest.version, videoBufferSeconds: 30, @@ -1171,6 +1210,20 @@ try { logger.warn('chrome.notifications.onClicked.addListener failed:', e); } +// Plan 02-03 D-P2-02 — tab-url tracker bootstrap. +// Registers chrome.tabs.onActivated + chrome.tabs.onUpdated listeners that +// maintain an internal Set of URLs observed during the SW's lifetime. +// Feeds meta.urls in createArchive (closes audit P1 #10). Defensive +// try/catch matches the surrounding chrome.* listener registration +// pattern; idempotency is handled by the tracker module itself (an +// initialized flag prevents double-registration if some future caller +// invokes initTabUrlTracker more than once). +try { + initTabUrlTracker(); +} catch (e) { + logger.warn('initTabUrlTracker failed:', e); +} + // chrome.downloads.onChanged: D-P2-01 (P0-6 fix) — revoke-on-terminal-state. // Closes the URL.revokeObjectURL lifecycle by routing terminal download // state transitions (`complete` / `interrupted`) into a REVOKE_DOWNLOAD_URL diff --git a/src/shared/types.ts b/src/shared/types.ts index 72f9ba7..4f0f1e6 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -130,10 +130,36 @@ export interface UserEvent { 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; - url: string; + urls: string[]; userAgent: string; extensionVersion: string; videoBufferSeconds: number;