MÓDULO 4.2

🖼️ CEF e webview accounts

Por que o OpenHuman empacota Chromium inteiro, o que o Chrome DevTools Protocol destrava, e a regra de ouro que mantém os webviews de terceiros longe de virar superfície de ataque.

6
Tópicos
55
Minutos
Avançado
Nível
Runtime
Tipo
1

🌐 CEF vs WebKit/WebView2

Tauri padrão usa o webview nativo do sistema: WKWebView no macOS, WebView2 no Windows, WebKitGTK no Linux. Funciona bem para renderizar o app. Tem uma limitação fatal: nenhum deles expõe Chrome DevTools Protocol. Sem CDP, todo o "OpenHuman sabe o que tá acontecendo no Slack" morre.

🎯 O que CDP destrava

  • Target.getTargets — descobre toda página e service worker no processo.
  • IndexedDB.requestData — anda o storage local de um app terceiro sem rodar JS na página.
  • DOMSnapshot.captureSnapshot — leitura read-only do DOM sem disparar reatividade do framework.
  • Runtime.evaluate — one-shot ephemeral reads (um único JSON serializer, nunca uma bridge permanente).
  • Page.addScriptToEvaluateOnNewDocument — shim renderer-side antes do JS da página rodar (raro, justificado).

WKWebView

macOS nativo

✗ Sem CDP

WebView2

Windows nativo

✗ Sem CDP exposto

CEF

Chromium embedded

✓ CDP em :19222

Conceito-chave
Por que CDP é load-bearing
Conceito-chave
--remote-debugging-port=19222
Conceito-chave
CEF 146.4.1
Conceito-chave
Fork tinyhumansai/tauri-cef
2

🛠️ Vendored tauri-cli

CEF não chega no app por mágica. O cargo-tauri stock NÃO sabe empacotar Chromium em Contents/Frameworks/. Resultado: bundle quebrado, panic em cef::library_loader::LibraryLoader::new. Por isso o repo vendoriza uma CLI customizada em app/src-tauri/vendor/tauri-cef/crates/tauri-cli.

⚠️ Sintoma quando você usa stock CLI

$ pnpm tauri build
thread 'main' panicked at 'failed to load CEF library':
  cef::library_loader::LibraryLoader::new
  Resource not found in Contents/Frameworks/Chromium Embedded Framework.framework

Diagnosticar essa mensagem custa horas. scripts/ensure-tauri-cli.sh existe pra você nunca cair nela.

A guard rail automática

Todo script Tauri (pnpm dev:app, cargo tauri build) chama antes:

pnpm tauri:ensure
# equivale a:
bash scripts/ensure-tauri-cli.sh
# instala/atualiza:
cargo install --locked --path app/src-tauri/vendor/tauri-cef/crates/tauri-cli

💡 Por que [patch.crates-io]

Em app/src-tauri/Cargo.toml todo crate Tauri é apontado para o fork via [patch.crates-io]. Sem isso, cargo baixaria os crates upstream e o CEF runtime não bateria com o que a CLI bundla.

Conceito-chave
Stock CLI quebra bundle
Conceito-chave
ensure-tauri-cli.sh
Conceito-chave
[patch.crates-io]
Conceito-chave
iOS NÃO usa CEF CLI
3

🔍 Scanners por provider

Cada provider que roda como webview embedded tem um scanner Rust com WebSocket de longa duração para o CDP em :19222. Tick em cadência fixa, lê o que precisa via CDP, posta direto no core como openhuman.memory_doc_ingest. A memória cresce mesmo com a janela do app fechada.

ScannerCadênciaO que faz
whatsapp_scanner2s DOM + 30s IDBLê message stores, puxa metadata de mídia
telegram_scanner2s DOM + 30s IDB+ QR-login hand-off para Telegram Desktop nativo
slack_scanner30s IDB walkPuro IDB — sem DOM scrape
discord_scannerPeriódicoChannel + DM state via CDP
meet_scannerPeriódicoLive captions + participant state
imessage_scannerPeriódicoSem webview. Lê chat.db direto no macOS

Por que isolamento por account

Cada account ganha um diretório próprio em {app_local_data_dir}/webview_accounts/{id}/. Dois workspaces Slack = dois browser profiles separados. Cookies, IndexedDB, service workers — tudo isolado.

Sem isso, logar duas contas Slack faria as duas competirem pelo mesmo storage e a sessão pulava aleatoriamente. Código: app/src-tauri/src/webview_accounts/mod.rs.

Conceito-chave
WebSocket longa duração
Conceito-chave
memory_doc_ingest direto
Conceito-chave
webview:event payload
Conceito-chave
Account isolation por dir
4

🚫 Regra zero-injection

A regra mais importante deste módulo. Os webviews acct_* carregando origens de terceiros (web.telegram.org, slack.com, web.whatsapp.com, discord.com, browserscan) não devem crescer NENHUM novo JavaScript injetado. Os providers migrados rodam com ZERO JS injetado sob CEF, por design.

✓ O que FAZER

  • Comportamento novo nos CEF handlers: on_navigation, on_new_window, LoadHandler::OnLoadStart, CefRequestHandler
  • CDP do scanner: Network.*, Emulation.*, Input.*, Page.*
  • Hooks Rust de notificação/IPC — nunca cruzar para o renderer
  • Quando não der pra fazer assim, levantar a limitação, não shipar init script

✗ O que NÃO fazer

  • Criar .js novo em app/src-tauri/src/webview_accounts/
  • Acrescentar bloco a build_init_script / RUNTIME_JS
  • Disparar script via CDP Page.addScriptToEvaluateOnNewDocument ou Runtime.evaluate nesses webviews
  • "Só esse caso vai ser exceção" — não vai, vira norma

🛡️ Por que essa regra existe

Qualquer script controlado pelo host que roda dentro de origem terceira é:

  • Superfície de ataque: bug nosso vira XSS no Slack do usuário.
  • Estado contaminado: page state muda em runtime de forma não-determinística.
  • Quebra de garantia: o usuário acha que tá num Slack puro. Não está.
  • Detectável: providers fingerprintam scripts injetados e bloqueiam contas.

Grandfathering

Providers gmail, linkedin e google-meet têm injeção legacy (recipe files + runtime.js bridge). Está grandfathered: pode existir, deve encolher, nunca crescer. PRs que adicionarem aí vão pegar review pesado.

Conceito-chave
Zero JS = design choice
Conceito-chave
CEF handlers + CDP only
Conceito-chave
Limitação > workaround
Conceito-chave
Legacy só pode encolher
5

⚙️ CDP handlers — Network, Emulation, Input, Page

Quase tudo que você quer fazer "de fora" num webview tem domínio CDP equivalente. Pensa em CDP como a API oficial do Chromium — qualquer coisa que o DevTools faz, você faz programaticamente.

N

Network

Interceptar request/response, ler payloads, falsear headers.

Network.enable
Network.requestWillBeSent → log + filtro
Network.responseReceived  → captura body via getResponseBody
E

Emulation

Mudar user-agent, timezone, locale, viewport.

Emulation.setUserAgentOverride { userAgent, platform, ... }
Emulation.setTimezoneOverride { timezoneId: "America/Sao_Paulo" }
I

Input

Sintetizar mouse/teclado direto no renderer. O usuário não vê.

Input.dispatchMouseEvent { type: "mousePressed", x, y, button: "left" }
Input.dispatchKeyEvent   { type: "keyDown", text: "a" }
P

Page

Ciclo de vida da página: navegação, recarregar, screenshot.

Page.navigate { url }
Page.captureScreenshot { format: "png" }
Page.lifecycleEvent → DOMContentLoaded / load
Conceito-chave
CDP = API oficial
Conceito-chave
Sintetiza input sem JS
Conceito-chave
CefRequestHandler nativo
Conceito-chave
Network intercept > fetch hook
6

🔌 Audit de plugins Tauri

A regra zero-injection morre se você adiciona um plugin Tauri sem auditar. Vários plugins oficiais injetam JS por padrão em todos os webviews — incluindo os de terceiros. O caso famoso é tauri-plugin-opener.

🚨 A pegadinha do tauri-plugin-opener

Por padrão, este plugin instala init-iife.js: um listener global de click que chama plugin:opener|open_url via HTTP-IPC. Adiciona-se em TODOS os webviews registrados no app.

// init-iife.js (injetado em CADA webview)
(function() {
  document.addEventListener('click', (e) => {
    const a = e.target.closest('a[href]');
    if (a && shouldOpenExternal(a.href)) {
      window.__TAURI_INTERNALS__.invoke('plugin:opener|open_url', { url: a.href });
    }
  });
})();

Resultado: cada page click no WhatsApp Web passa por código nosso. Quebra zero-injection sem aviso.

A correção

// app/src-tauri/src/lib.rs
.plugin(
    tauri_plugin_opener::Builder::new()
        .open_js_links_on_click(false)  // opta out do init-iife
        .build()
)

🔎 Checklist ao adicionar qualquer plugin

  • 1. Leia o source: procure por js_init_script, init-iife.js, build_init_script.
  • 2. Se houver, ache a flag de opt-out (geralmente .no_* ou .disable_*).
  • 3. Se não houver opt-out, considere não usar o plugin e reimplementar à mão.
  • 4. Documente no PR: "plugin X auditado, JS init: SIM/NÃO, opt-out aplicado: SIM".
Conceito-chave
js_init_script é vetor
Conceito-chave
open_js_links_on_click(false)
Conceito-chave
Auditar todo plugin novo
Conceito-chave
PR body: declarar audit

Resumo do módulo

CEF é load-bearing — única runtime que expõe CDP.
Vendored tauri-cli — stock quebra bundle, ensure-tauri-cli.sh corrige.
Scanners por provider — Rust + WebSocket CDP, tick fixo.
Zero JS injection — regra dura para webviews migrados.
CDP é a API — Network, Emulation, Input, Page para tudo.
Audit todo plugin — opener é a pegadinha, outros existem.

Próximo módulo:

4.3 — 📱 Cliente iOS (experimental)