fix: la extensión vuelve a capturar (B1/B2) + OPFS async + pausa/continuar + tests honestos#2
fix: la extensión vuelve a capturar (B1/B2) + OPFS async + pausa/continuar + tests honestos#2ctala wants to merge 13 commits into
Conversation
- git rm tarballs commiteados (dist/*.tar.gz 1.4M + raíz v1.2.2) - git rm drafts de proceso .pr-body-*.md (4) - git rm store-assets/screenshots-v2/ (duplicado exacto de screenshots/) - .gitignore: agregar *.tar.gz, dist/, .pr-body*, playwright-report/, test-results/ Co-Authored-By: Claude <noreply@anthropic.com>
Investigación multi-agente: la extensión no captura nada en Chrome real (buffers null en prod, tests verdes contra mock que pre-inyecta deps). 24 bugs verificados, arquitectura objetivo, testing automatizado sin humano. Co-Authored-By: Claude <noreply@anthropic.com>
…turar (B1)
Causa raíz del 'captura cero en Chrome real': el SW es script clásico
(manifest sin type:module) y Chrome carga SOLO background.js. Los módulos
opfs-buffer/memory-buffer nunca se cargaban → self.OpfsBuffer/MemoryBuffer
null → activeBuffer null → toda captura descartada en silencio. Los 71
tests pasaban porque el mock pre-inyectaba los buffers a globalThis.
- background.js: importScripts('/src/memory-buffer.js','/src/opfs-buffer.js')
antes del IIFE (guarded; no-op en el harness CJS legacy).
- opfs-buffer.js: el UMD no tenía rama worker → no attachaba a self en el
SW. Agregada rama self/globalThis (memory-buffer ya la tenía).
Harness honesto (Fase 0): package.json + node:vm loader (test/_sw-loader.mjs)
que carga el SW como Chrome (solo background.js + importScripts real, SIN
pre-inyectar globals) + test/sw-wiring.test.mjs que reproduce B1 en puro
Node. Rojo antes del fix, verde después. 74 tests, 74 pass.
Co-Authored-By: Claude <noreply@anthropic.com>
…p (B9) + versión drift (B24) - content.js (B2): el filtro legacy de substring corría aun con captureConfig estructurado activo. Para presets regex/glob, `filter` es el patrón crudo y url.includes(rawRegex) nunca matchea → descartaba TODA la captura. Ahora el filtro legacy SOLO aplica al path sin captureConfig (keyword simple). - injected.js (B9): guard window.__ARE_PATCHED__ — la re-inyección en cada START duplicaba wrappers de fetch/XHR → capturas duplicadas. Idempotente. - content.js (B24): versión del PING derivada del manifest (era '1.4.0' hardcodeado vs manifest '1.4.2'). Co-Authored-By: Claude <noreply@anthropic.com>
Capa e2e que carga la extensión unpacked en Chromium real (channel:chromium + --headless=new) y valida el flujo completo de captura SIN intervención humana — la red que faltaba para romper el 'arreglo un fix y aparece otro': - test/e2e/fixtures-server.mjs: servidor local determinista que imita Voyager (/voyager/api/me con x-restli + included[].access_token, /messaging XHR). Sin tocar linkedin.com, sin data privada, replicable en CI. - test/e2e/record-download.spec.mjs: carga extensión → START vía popup→SW → dispara fetch+XHR → STOP → DOWNLOAD → asserta el JSONL. Valida B1 (buffers cableados en el SW REAL) + B2 (filtro no descarta) end-to-end. VERDE (3.5s). - scripts/build-dist.mjs: empaqueta dist/unpacked (= lo que ships; chequea refs del manifest). scripts/check-version-consistency.mjs: gate anti-drift. - playwright.config.mjs + .github/workflows/test.yml: unit + e2e headless (xvfb) en cada push; bloquea el build si algo falla. Validado local: unit 74/74 + e2e 1/1 verde. Co-Authored-By: Claude <noreply@anthropic.com>
…ering .claude/agents/ (el repo no tenía): dos subagentes anclados al levantamiento y al código real: - Chrome MV3 Extension Engineer: el MOTOR (4 contextos, lifecycle SW, OPFS, costuras, tests honestos). Custodio de R1/R2/R3 y los 8 key features. - API Reverse Engineer: el MÉTODO genérico (mapear cualquier API, criterio redacción sesión-vs-replay, armar presets). Voyager = primer caso, no su def. Co-Authored-By: Claude <noreply@anthropic.com>
…sobrevive al restart del SW Fase 2. Al construir pausa/continuar, un e2e en Chromium real reveló que OPFS NUNCA funcionó en producción: createSyncAccessHandle() no existe en MV3 service workers (solo en dedicated workers) → desde v1.4.0 todo corría en memoria-fallback, el archivo OPFS quedaba en 0 líneas. El mock lo ocultaba. - opfs-buffer.js: reescrito a la API OPFS ASYNC (createWritable + getFile), que sí funciona en el SW. Append sync + flush batcheado por microtask. flush() fuerza durabilidad en STOP/PAUSE/lectura. - background.js: verbos PAUSE/RESUME + restore cablea restoreFromExisting al wake del SW (reconstruye contador+dedup desde disco). START trunca (sesión nueva), RESUME appendea. Descarga OPFS normalizada al shape canónico _toJsonlLine. - popup: botones Pausar/Continuar (3 estados), guards lastError (B6), fix B13. - mocks: createWritable async (antes createSyncAccessHandle — el sync que ocultaba el bug). Tests: pausa-resume.test.mjs + e2e sw-restart-resume (CDP stopAllWorkers). - ADR-0003 (supersede ADR-0002 en el write mechanism). Bump 1.4.2 → 1.5.0. Validado: unit 78/78 + e2e 2/2 (la grabación sobrevive al teardown del SW). Co-Authored-By: Claude <noreply@anthropic.com>
Actualización — Fase 2 (pausa/continuar) + hallazgo OPFSConstruyendo pausa/continuar, un e2e en Chromium real destapó algo grande: OPFS nunca funcionó en producción. Fix (ADR-0003, supersede ADR-0002 en el write mechanism): reescrito a la API OPFS async ( Pausa/continuar: verbos Validado: unit 78/78 + e2e 2/2, incluyendo un test que mata el SW con CDP |
… Copy Cookies + B7/B8/B10 + badge contador Informado por una captura real en LinkedIn (58 reqs): - FILTRO ARREGLADO: el preset no narrowaba (capturaba todo) porque el popup guardaba las patterns como string pero las aplicaba como array → vaciaba el filtro. Consolidado a FUENTE ÚNICA: el popup carga capture-config.js y usa sus PRESETS + parser canónicos (mata B19). Patterns del preset ya no round-trippean por el textarea (origen del bug); textarea = filtros extra opcionales. - Preset LinkedIn a endpoints REALES 2026: /voyager/api/ + /rsc-action/ (flagship-web RSC) + /api/graphql; EXCLUDE de telemetría/estáticos (trackO11y, sensorCollect, /li/track, static.licdn.com). shouldCapture acepta exclude. - B10: x-restli-protocol-version ya no se redacta (constante, para replay). - B7: XHR captura headers (setRequestHeader + getAllResponseHeaders). - B8: fetch(Request) lee method/headers del Request. - URLs relativas resueltas a absolutas (injected.js) antes de filtrar. - Default preset = Generic. - Badge: contador de requests EN VIVO restaurado (rojo/ámbar, sin parpadeo). COPY COOKIES (feature nueva): botón en popup que copia las cookies de auth del sitio incluyendo httpOnly (li_at/JSESSIONID que fetch no puede leer) vía chrome.cookies, para replay. Permission cookies. NO se guardan en la captura. Tests: capture-config exclude + preset real; e2e nuevos (filtro narrowea/excluye, popup fuente única + B10, Copy Cookies httpOnly). Unit 78/78 + e2e 5/5. Bump 1.6.0. Co-Authored-By: Claude <noreply@anthropic.com>
- Cookies: el botón ahora DESCARGA un .json estructurado (url, host, count, cookieHeader listo para curl/Postman, cookies[]) en vez de copiar al portapapeles — asset reusable para replay. - Quitado el formato JSON array legacy v1.2.x: salida siempre JSON-Lines (selector removido del popup + rama json-array del SW). - PRIVACY-POLICY actualizada: declara el permiso cookies (+ unlimitedStorage) con justificación read-only/on-demand/local/no-transmitido — requisito de revisión del Chrome Web Store al agregar 'cookies'. Unit 78/78 + e2e 5/5. Co-Authored-By: Claude <noreply@anthropic.com>
… Store) La política (PRIVACY-POLICY.md + store-assets/privacy-policy-hosteable.html) afirmaba 'Does NOT use cookies' / 'no request cookies' — contradice el feature Download Cookies y causaría rechazo del store review. Corregido: el permiso cookies se declara con justificación (read-only, on-demand al click del usuario, guardado local, nunca transmitido) + sección dedicada en ambos docs. Co-Authored-By: Claude <noreply@anthropic.com>
- README: badge 1.7.0, What It Does preciso (JSONL, no json-array), sección Features (pausa/continuar, cookies, redacción, presets, OPFS), puntero a CHANGELOG. - STORE-LISTING: descripción actualizada (JSONL + features nuevas), output format JSONL (no el json-array viejo), justificaciones de permisos cookies + unlimitedStorage para la pestaña Privacy practices del Chrome Web Store. Co-Authored-By: Claude <noreply@anthropic.com>
Captura el diseño acordado de WebSocket/SSE capture (modelo conexión+frames, JSONL ordenado por connId+seq, auth vía cookies/URL/subprotocolo, scope honesto) + principios (100% local, single-purpose, sin backend) + lo descartado con razón (copy-as-cURL lo cubre DevTools; export Postman/OpenAPI es la diferenciación real). Alinea el roadmap de STORE-LISTING + puntero en README. Co-Authored-By: Claude <noreply@anthropic.com>
…tización La extensión reversea el protocolo; NO implica meter una conexión WS persistente en el actor de Apify (run-based, mal fit). Aclarado: enviar suele ser HTTP POST (→ write action en el actor), escuchar realtime → microservicio always-on (Coolify/n8n/Spark), nunca Apify. Co-Authored-By: Claude <noreply@anthropic.com>
Por qué
El refactor OPFS (v1.3.0→v1.4.2) rompió la captura en Chrome real: el service worker es script clásico y Chrome carga solo
background.js, pero el refactor partió la lógica en módulos (opfs-buffer.js,memory-buffer.js) y nunca los cableó (sinimportScripts) →self.OpfsBuffer/MemoryBuffernull → captura cero. Los 71 tests pasaban porque el mock pre-inyectaba esos buffers que Chrome nunca inyecta. El verde medía el mock, no producción — de ahí el patrón "arreglo un fix y aparece otro".La v1.2.x (pre-refactor,
background.jsautocontenido) sí funcionaba: es la referencia known-good.Qué arregla (verificado contra el código; file:line en el levantamiento)
importScriptsen el SW + rama worker faltante en el UMD deopfs-buffer.jscontent.js(url.includes(regexCrudo)) descartaba TODA la captura con presetscaptureConfig(keyword simple)window.__ARE_PATCHED__1.4.0) vs manifest (1.4.2)chrome.runtime.getManifest()La extensión vuelve a capturar — confirmado en Chromium real (el e2e graba fetch+XHR y descarga un JSONL con datos reales).
Testing sin intervención humana (la red que faltaba)
test/_sw-loader.mjsvíanode:vm): carga el SW como lo carga Chrome (solobackground.js+importScriptsreal, sin pre-inyectar globals).test/sw-wiring.test.mjsreproduce B1 en puro Node — rojo antes del fix, verde después. Esa clase de bug ya no pasa desapercibida.channel:chromium+--headless=new): carga la extensión unpacked, graba fetch+XHR, STOP, DOWNLOAD, asserta el JSONL. Fixtures locales que imitan Voyager (sin tocar linkedin.com, sin data privada)..github/workflows/test.yml): unit + e2e headless (xvfb) en cada push; bloquea el build si algo falla.Además
dist/*.tar.gz,.pr-body-*,screenshots-v2/duplicado) +.gitignorearreglado..claude/agents/: Chrome MV3 Extension Engineer (el motor) + API Reverse Engineer (el método genérico — Voyager es su primer caso, no su definición).docs/spec/levantamiento-2026-06-24.md(causa raíz + 24 bugs verificados + arquitectura objetivo + plan por fases).Pendiente (próximas fases, no en este PR)
restoreFromExisting, ya existe con 0 callers + ADR-0003).world:MAIN, document_start, headers de XHR B7,fetch(Request)B8, redacción).🤖 Generated with Claude Code