🌐 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
🛠️ 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.
🔍 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.
| Scanner | Cadência | O que faz |
|---|---|---|
| whatsapp_scanner | 2s DOM + 30s IDB | Lê message stores, puxa metadata de mídia |
| telegram_scanner | 2s DOM + 30s IDB | + QR-login hand-off para Telegram Desktop nativo |
| slack_scanner | 30s IDB walk | Puro IDB — sem DOM scrape |
| discord_scanner | Periódico | Channel + DM state via CDP |
| meet_scanner | Periódico | Live captions + participant state |
| imessage_scanner | Periódico | Sem 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.
🚫 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
.jsnovo emapp/src-tauri/src/webview_accounts/ - ✗Acrescentar bloco a
build_init_script/RUNTIME_JS - ✗Disparar script via CDP
Page.addScriptToEvaluateOnNewDocumentouRuntime.evaluatenesses 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.
⚙️ 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.
Network
Interceptar request/response, ler payloads, falsear headers.
Network.enable Network.requestWillBeSent → log + filtro Network.responseReceived → captura body via getResponseBody
Emulation
Mudar user-agent, timezone, locale, viewport.
Emulation.setUserAgentOverride { userAgent, platform, ... }
Emulation.setTimezoneOverride { timezoneId: "America/Sao_Paulo" }
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" }
Page
Ciclo de vida da página: navegação, recarregar, screenshot.
Page.navigate { url }
Page.captureScreenshot { format: "png" }
Page.lifecycleEvent → DOMContentLoaded / load
🔌 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".
✅ Resumo do módulo
Próximo módulo:
4.3 — 📱 Cliente iOS (experimental)