Mark f251297256 feat(04-02): Wave 1 — setimmediate polyfill replaced + generate-icons.cjs + deferred-items closure
Coherent 5-edit Wave 1 GREEN landing per Plan 04-02 Task 2; RED gate from
Task 1 (`tests/build/no-new-function-in-sw-chunk.test.ts` 1-hit assertion)
flips GREEN with 0 hits of `new Function` in any SW chunk
(`dist/assets/index.ts-*.js` glob).

## Threat T-04-02-01 mitigation (Elevation of Privilege — `new Function` literal)

Three layered mechanisms cooperate to drop the CSP-unsafe `new Function`
literal from the SW chunk while preserving JSZip's zip-assembly correctness
end-to-end (REVISION iter-2 WARNING 1 empirically pinned at UAT harness 33/33):

1. **Runtime polyfill prelude** at top-of-module of `src/background/index.ts`
   (BEFORE the first `import`): an inline `queueMicrotask`-based polyfill
   installs `globalThis.setImmediate` at SW boot. JSZip's pre-bundled
   `dist/jszip.min.js` IIFE guards its internal setimmediate polyfill behind
   `if(!s.setImmediate){...}`, so the upstream offending body never executes
   at runtime once our prelude has installed the safe fast-path.

2. **`vite-plugin-node-polyfills` `exclude: ['setimmediate']`** in vite.config.ts:
   prevents the plugin from injecting its node-stdlib-browser-aliased
   setimmediate polyfill into the chunk. NOTE: this alone is insufficient
   because JSZip's `dist/jszip.min.js` ships its OWN bundled-in setimmediate
   (via the package.json `"browser"` field that maps `./lib/index` →
   `./dist/jszip.min.js`); the plugin's `exclude` only filters the plugin's
   own contributions.

3. **`resolve.alias.setimmediate`** redirects bare-specifier `setimmediate`
   requires to `src/shared/setimmediate-stub.ts` (a 22-LOC TS module that
   installs the same `queueMicrotask`-based polyfill via side-effect import).
   This catches any future direct `import 'setimmediate'` consumer that
   bypasses the prelude.

4. **`stripSetimmediateNewFunction()` Rollup post-transform plugin** in
   vite.config.ts: surgically replaces the single occurrence of
   `(I=new Function(""+I))` with `(I=function(){})` in any output chunk
   that contains the JSZip-bundled setimmediate IIFE. The replacement is
   observably equivalent in our codepath (the parent `typeof I!="function"&&`
   guard means the body never runs when I is already a function — which is
   the only form JSZip ever uses — AND the runtime prelude makes the entire
   IIFE body unreachable regardless). Without this plugin, JSZip's
   pre-bundled distribution embeds the upstream setimmediate package's
   `setImmediate.js` verbatim inside its internal CJS module registry
   (slot 54), unreachable by Vite's resolve.alias or the polyfill plugin's
   exclude.

## Architecture decision log

**Option α (force JSZip unbundled `lib/index.js` via `resolve.alias.jszip`)
was attempted and reverted 2026-05-21** (between commits 630d40c and this).
Empirically broke UAT harness A30+ because the unbundled entry's transitive
readable-stream-browser browser-field mapping did not propagate correctly
through Vite's resolver — the async zip-write pipeline silently produced
an empty events.json. The post-transform plugin (Option β) is the
minimum-surface fix that preserves JSZip's runtime behavior verbatim while
satisfying the textual `new Function` count = 0 invariant.

## Verification

**Build / static gates:**
- `npm run build` exits 0; SW chunk `dist/assets/index.ts-DfBxWCT9.js`
  (378.92 kB) contains 0 occurrences of `new Function` (was 1 in pre-fix
  `index.ts-8LkXuqac.js`).
- `npx tsc --noEmit` exits 0.
- `grep -rn 'permissions.request' src/` returns 0 hits (Plan 04-02 ROADMAP
  SC #4 regression pin GREEN).
- `node generate-icons.cjs` exits 0; old `generate-icons.js` no longer
  exists (rename via `git mv` preserves history).
- `grep -c "exclude: \\['setimmediate'\\]" vite.config.ts` returns 1.
- `grep -c "queueMicrotask" src/background/index.ts` returns ≥1.
- `grep -c "Resolved in Phase 4 Plan 04-02" .planning/phases/01-stabilize-video-pipeline/deferred-items.md` returns ≥1.

**Test gates:**
- Focused: `npm test -- tests/build/no-new-function-in-sw-chunk.test.ts tests/build/dead-code-grep.test.ts --run` → 3/3 GREEN (Task 1's RED gate flipped GREEN).
- Full vitest: 183/183 GREEN on the clean run (180 baseline + 3 net new
  from Plan 04-02 Task 1's two new files). Pre-existing intermittent flakes
  per 04-01-SUMMARY Issues Encountered (blob-url-download / webm-remux /
  webm-playback ffmpeg dry-run) persist across SUMMARY runs and are owned
  by Plan 04-03.

**Pre-checkpoint bundle gates (per saved memory feedback-pre-checkpoint-bundle-gates.md):**
1. Tier-1 FORBIDDEN_HOOK_STRINGS: 13/13 tests GREEN; inventory unchanged at
   12 strings (Plan 04-02 added no harness hooks).
2. SW CSP-safety grep: `grep -rn 'new Function\\|eval(' dist/assets/` returns
   0 hits — polarity flipped from the pre-existing 1 documented exception
   (the setimmediate literal). T-04-02-01 mitigation pin lands.
3. Node-globals: `Buffer.copy / .isView / .length / .push / .shift / .slice
   / .write` in SW chunk (pre-existing JSZip internals; unchanged from
   04-01-SUMMARY).
4. DOM-globals: `document.createElement / .createTextNode / .documentElement
   / .F` + `window.Math / .console / .localStorage / .process` (pre-existing
   JSZip text encoder fallback paths; unchanged from 04-01-SUMMARY).
5. manifest.json: present, MV3, `name: __MSG_extName__` (chrome.i18n intact).

**Empirical UAT harness (REVISION iter-2 WARNING 1):**
- `HEADLESS=1 SKIP_PROD_REBUILD=0 npm run test:uat` → 33/33 assertions
  passed (verbatim `UAT harness: 33/33 assertions passed` in stdout).
  Confirms JSZip's full SAVE → zip pipeline (A24-A32 inclusive, exercising
  the in-memory MediaRecorder segments + base64 port wire + remux + zip
  assembly + chrome.downloads + events.json + meta.json + screenshot)
  operates correctly under the new bundle. The setimmediate polyfill
  replacement preserves zip-write behavior end-to-end at the empirical
  layer.

## Files

- **vite.config.ts**: imports `node:url` (fileURLToPath/URL) + `Plugin`
  type from vite; adds `nodePolyfills.exclude: ['setimmediate']`;
  adds `resolve.alias.setimmediate` → `src/shared/setimmediate-stub.ts`;
  adds `stripSetimmediateNewFunction()` Rollup post-transform plugin
  with full rationale comment.
- **src/background/index.ts**: 17-line top-of-module prelude inserted
  BEFORE the first `import { Logger } ...` line. Inline `queueMicrotask`-based
  setimmediate polyfill with typed widening cast (no `as any` per
  CLAUDE.md). Reversible by `git revert`.
- **src/shared/setimmediate-stub.ts** (NEW): 50-LOC TS module providing
  the same `queueMicrotask`-based polyfill via side-effect import.
  Documented as the resolve.alias target.
- **generate-icons.js → generate-icons.cjs**: `git mv` preserving history.
  Node 14+ treats `.cjs` as CJS regardless of `package.json` "type":
  "module" per https://nodejs.org/api/packages.html#determining-module-system.
  No code change; `require('fs')` + `require('path')` resolve cleanly.
  No other references to the old `.js` path elsewhere in the codebase
  outside the `.planning/` audit trail.
- **.planning/phases/01-stabilize-video-pipeline/deferred-items.md**:
  appended "Resolved in Phase 4 Plan 04-02" closure block citing this
  commit; details the 4-mechanism layered mitigation; documents the
  Option α attempt + reversion.

References:
  - .planning/phases/04-harden-clean-up-optional/04-RESEARCH.md §Q1
  - .planning/phases/04-harden-clean-up-optional/04-PATTERNS.md
    §vite.config.ts + §src/background/index.ts
  - Plan 04-02 threat model T-04-02-01 (Elevation of Privilege) +
    T-04-02-02 (DoS — JSZip fallback compatibility; verified by UAT 33/33)
  - node_modules/jszip/lib/utils.js:7 (upstream `require("setimmediate")`)
  - node_modules/setimmediate/setImmediate.js (upstream polyfill source)
  - Plan 01-12 Wave 7 deferred-items.md disclosure (Phase 5 → Phase 4 target)

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

Mokosh — Session Capture

Chrome MV3 extension that records operator sessions for bug reports.

Фаза 1 — Локальная запись + экспорт архива

Установка и запуск

Разработка

# Установка зависимостей
npm install

# Сборка для разработки с HMR
npm run dev

# Сборка для продакшена
npm run build

Установка расширения в Chrome

  1. Соберите проект:

    npm run build
    
  2. Откройте Chrome и перейдите по адресу: chrome://extensions/

  3. Включите "Режим разработчика" (Developer mode) в правом верхнем углу

  4. Нажмите кнопку "Загрузить распакованное расширение" (Load unpacked)

  5. Выберите папку dist в корне проекта

  6. Расширение установлено!

Использование

  1. При первом открытии popup расширение запросит разрешение на запись экрана

  2. Разрешение обязательно для работы расширения

  3. Расширение автоматически начнет запись:

    • Видео: последние 30 секунд (кольцевой буфер)
    • DOM-события через rrweb: последние 10 минут
    • Лог действий пользователя: последние 10 минут
  4. Для сохранения отчета об ошибке:

    • Нажмите на иконку расширения
    • Нажмите кнопку "Сохранить отчёт об ошибке"
    • Архив автоматически загрузится в папку "Загрузки"

Структура архива

Архив session_report_YYYY-MM-DD_HH-MM-SS.zip содержит:

session_report_2025-05-15_14-32-10.zip
├── video/
│   └── last_30sec.webm         # склеенные чанки видеобуфера
├── rrweb/
│   └── session.json            # массив DOM-событий rrweb
├── logs/
│   └── events.json             # лог действий пользователя
├── screenshot.png              # скриншот в момент сохранения
└── meta.json                   # метаданные сессии

Технический стек

  • Тип расширения: Chrome Extension, Manifest V3
  • Service Worker: Background script (Manifest V3)
  • Захват экрана: chrome.tabCapture API
  • Захват DOM: rrweb (npm: rrweb)
  • Лог событий: Content Script
  • Упаковка архива: JSZip (npm: jszip)
  • Сохранение файла: chrome.downloads API
  • Хранение буфера: In-memory (Service Worker + Content Script)
  • Build: Vite + crxjs + TypeScript

Особенности

Маскирование чувствительных данных

  • Пароли (input[type=password]) маскируются автоматически в rrweb и логах
  • Поля с атрибутом data-sensitive="true" также маскируются в rrweb

Записываемые события

Пользовательские события

  • click — клик по любому элементу
  • input — изменение значения поля (без паролей)
  • navigation — переходы по страницам (popstate, hashchange, History API)
  • js_error — JavaScript ошибки (window.onerror, unhandledrejection)
  • network_error — сетевые ошибки (fetch/XHR с кодом ответа >= 400)

Кольцевой буфер

  • Видео: 30 секунд, первый чанк (WebM заголовок) хранится всегда
  • rrweb события: 10 минут, максимум 5000 событий
  • Пользовательские события: 10 минут

Память

  • Ожидаемое потребление: ~5-10 МБ в фоновом режиме

Критерии приёмки Фазы 1

  • Расширение устанавливается в Chrome без ошибок
  • Видеобуфер непрерывно работает на любой вкладке
  • В буфере всегда есть не более 30 секунд видео
  • rrweb пишет DOM-события без ошибок на типовых страницах
  • Лог событий фиксирует клики, навигацию и сетевые ошибки
  • При нажатии кнопки архив скачивается в "Загрузки" за < 5 секунд
  • Архив открывается, last_30sec.webm воспроизводится в браузере
  • Пароли не попадают в лог и rrweb-снимки
  • RAM-потребление расширения не превышает 50 МБ в фоне

Отладка

Console Logs

Расширение пишет подробные логи в консоль:

  • Service Worker: Chrome DevTools → Extensions → Service Worker → Console
  • Content Script: Chrome DevTools на любой странице → Console
  • Popup: Правый клик по popup → Проверить

Структура проекта

ai-call-extension/
├── src/
│   ├── background/          # Service Worker
│   │   └── index.ts
│   ├── content/            # Content Script
│   │   └── index.ts
│   ├── popup/              # Popup UI
│   │   ├── index.html
│   │   ├── index.ts
│   │   └── style.css
│   └── shared/             # Общие типы и утилиты
│       ├── types.ts
│       └── logger.ts
├── icons/                  # Иконки расширения
├── dist/                   # Собранные файлы
├── manifest.json           # Manifest расширения
├── vite.config.ts          # Конфигурация Vite
├── tsconfig.json           # Конфигурация TypeScript
└── package.json

Лицензия

MIT

Контакты

Для вопросов и предложений обращайтесь в support.

Description
No description provided
Readme 8 MiB
Languages
TypeScript 91%
HTML 3.4%
CSS 2.8%
Shell 2%
JavaScript 0.8%