O Loco é um PWA (Progressive Web App) de mensagens descentralizado com interface Material Design 3, comunicação híbrida (Web Push + WebRTC) e arquitetura de armazenamento robusta offline-first. O app prioriza a privacidade, o controle granular de dados pelo usuário e a resistência à evicção automática pelo navegador.
Neste estágio, o núcleo implementa a comunicação utilizando a API Web Push (especificamente via FCM) como transporte. Dois ou mais navegadores trocam mensagens diretamente, sem um banco de dados central para armazenar mensagens ou gerenciar contatos.
Cada navegador atua como um ponto autônomo:
- Emissor: envia mensagens criptografadas para outro usuário.
- Receptor: recebe mensagens, emite recibos de entrega (handshakes) e pode responder.
A infraestrutura mínima é um servidor proxy (Deno) que fornece uma chave pública RSA usada para cifrar a chave privada VAPID durante a troca de perfis e reencaminha as requisições push ao serviço (FCM).
Para manter a sanidade da base de código e garantir a performance, siga estas regras rigorosamente:
- Runtime e Ecossistema: Obrigatório o uso do Deno 2.x. Nunca usar Node, npm ou dependências que exijam Node nativo. O build é feito de forma customizada em
build.tsusandoDeno.bundle(). - Zero
localStorage: É estritamente proibido o uso delocalStorage. Todo e qualquer dado deve passar pelosrc/utils/storage.ts(wrapper doidb-keyval) ou OPFS. - Gerenciamento de Estado: Os Signals devem ser importados exclusivamente de
@preact/signals. Nunca instancie signals em nível de módulo global se eles forem exclusivos de um componente; crie-os dentro do escopo adequado. - Isolamento Assíncrono: Todas as operações de leitura/escrita de dados devem ser
async/await. Processamentos pesados (WebTorrent, I/O de arquivos, criptografia massiva) devem rodar em Web Workers (p2p-transfer.worker.js). - Comentários Táticos: Comente apenas para explicar o "porquê" de uma decisão complexa, nunca o "o quê" o código está fazendo.
- Degradação Graciosa (Fallback): O sistema deve tentar conexões P2P (
RTCDataChannel) primeiro. O Web Push atua como fallback silencioso e garantido.
A identidade de um usuário, armazenada no IndexedDB (AppConfig_DB). Pode ser compartilhada através de um JWT (JSON Web Token) com a claim sub: "contact".
{
"iss": "email@exemplo.com", // Identificador único do dono
"sub": "contact", // Tipo de token
"nm": "Nome do Usuário", // Nome legível
"kid": { ... }, // Chave pública VAPID (ECDSA P-256) em JWK
"p": { ... }, // Chave pública RSA (RSA-OAEP-256) em JWK
"s": { // Subscription do Web Push
"endpoint": "https://fcm.googleapis.com/...",
"keys": {
"p256dh": "base64...",
"auth": "base64..."
},
"k": "base64..." // Chave privada VAPID cifrada (envelope)
},
"iat": 1738765432 // Timestamp de emissão
}
Segurança VAPID: O campo k contém a chave privada VAPID cifrada (AES-GCM + RSA-OAEP) com a chave pública do servidor proxy. Apenas o servidor pode decifrá-la para disparar o push, garantindo que ela nunca vaze em texto puro. Ao compartilhar o perfil, o sistema recria automaticamente esse envelope.
Quando um usuário recebe uma mensagem ou importa um perfil via JWT, o emissor é salvo localmente. Contatos importados via JWT já recebem a flag homologado: true.
interface Contato {
publicKeyVapid: JsonWebKey; // Chave pública VAPID (ECDSA)
email: string;
nome: string;
publicKeyRSA: JsonWebKey; // Chave pública RSA (para cifrar a resposta)
subscription: {
endpoint: string;
keys: { p256dh: string; auth: string };
};
vapidPrivateKey: string; // Chave privada VAPID cifrada
homologado: boolean; // Controle de lista branca
createdAt: number;
updatedAt: number;
}As mensagens transitam dentro de um JWT assinado e comprimido para respeitar o limite de 4096 bytes.
- Mensagem Recebida: Possui
id,contatoPublicKeyVapid(Hash SHA-256 da chave VAPID),conteudo,status('nao_lida','lida','notificada') e timestamp. - Mensagem Enviada (Fila): Mantida offline-first com os campos
contatoHash,conteudo,status('pendente','enviando','enviada','falha','entregue'), e limite deMAX_TENTATIVAS = 3.
O receptor de uma mensagem (sub: "msg") notifica o emissor invisivelmente usando um JWT do tipo sub: "hand".
interface Handshake {
id: string; // NanoID (12 caracteres)
mensagemId: string; // ID da mensagem confirmada
tipo: 'confirmacao_entrega'; // Expansível para leitura, etc.
direcao: 'out' | 'in'; // Fluxo de saída ou entrada
status: 'pendente' | 'enviado' | 'falha' | 'entregue';
tentativas: number;
payload: any;
createdAt: number;
updatedAt: number;
}A aplicação usa uma arquitetura de dados híbrida para resistir a limitações do navegador.
Store (DB_NAMES) |
Chave Primária | Entidade | Descrição |
|---|---|---|---|
AppConfig_DB |
"profile" |
ProfileConfig |
Store unificada com perfil, chaves e subscriptions. |
BrowserB_Contatos_DB |
Hash SHA-256 (hex) | Contato |
Contatos. Chave é hash para evitar erros de serialização. |
BrowserB_MensagensRecebidas_DB |
ID da Mensagem | MensagemRecebida |
Histórico local de entrada. |
BrowserA_MensagensEnviadas_DB |
ID da Mensagem | MensagemEnviada |
Fila local de saída. |
Handshake_DB |
ID do Handshake | Handshake |
Rastreamento de confirmações e recibos. |
Destinado a arquivos binários grandes (fotos, vídeos, PDF) recebidos ou enviados via WebTorrent/WebRTC. Arquivos serão nomeados como {messageId}.{ext} e o usuário poderá excluí-los granularmente sem afetar o histórico de texto no IndexedDB.
1. Geração do Perfil (gerarProfileCompleto)
Solicita permissão de notificação -> Registra Service Worker -> Gera pares ECDSA (VAPID) e RSA-OAEP (E2E) -> Obtém Subscription no PushManager -> Busca chave pública do Proxy -> Cifra chave VAPID privada (Envelope) -> Salva no IndexedDB.
2. Envio de Mensagem (processarFilaEnvio no SW)
Interface salva na fila como 'pendente' e avisa o SW -> SW acorda e filtra fila -> Monta payload e cifra com AES-GCM + RSA-OAEP -> Constrói JWT (sub: "msg") -> Envia payload, subscription e envelope VAPID para /api/proxy-push -> Atualiza status local para 'enviada'.
3. Recebimento e Handshake (processarMensagemRecebida)
Evento push acorda o SW -> Valida JWT e aud -> Decifra envelope -> Atualiza ou cria contato -> Salva mensagem recebida -> Cria registro 'pendente' no Handshake_DB -> Aciona processarFilaHandshake() para enviar recibo ao emissor original via /api/proxy-push (sub: "hand").
4. Confirmação de Entrega (Recepção do Handshake)
Evento push acorda o emissor original -> SW valida JWT (sub: "hand") -> Atualiza Handshake_DB para 'entregue' -> Encontra a mensagem original e altera status final para 'entregue' -> Manda postMessage atualizando a UI caso o usuário esteja online.
| Caminho / Arquivo | Responsabilidade Principal |
|---|---|
src/app.tsx |
Ponto de entrada do Preact. Roteamento de telas e actions baseadas em hash. |
src/service-worker.ts |
Orquestrador principal. Registra processadores e acorda filas. |
src/sw/push.ts |
Router do SW. Faz triagem pelo claim sub do JWT (msg ou hand). |
src/sw/sw-mensagens.ts |
Descriptografa mensagens e gerencia a fila principal de envio. |
src/sw/sw-handshakes.ts |
Decodifica confirmações e gerencia a fila de recibos de saída. |
src/utils/push-utils.ts |
Helpers pesados de criptografia híbrida (AES-GCM + RSA-OAEP). |
src/utils/storage.ts |
(A ser criado) Wrapper absoluto para IDB, OPFS e proteção de quota. |
main.ts |
Servidor Deno (Proxy Web Push). Endpoints: /api/server-public-key e /api/proxy-push. |
build.ts |
Ferramenta de CLI. Gera os bundles via Deno.bundle, injeta variáveis e atualiza HTML. |
Use os comandos integrados definidos no deno.json.
Gerar o Bundle de Produção (HTML, SW e Workers):
deno task build
Iniciar Servidor Local:
deno task start
Disponível em http://localhost:8000. Testes de push exigem múltiplas instâncias de navegadores diferentes.
Rodar Testes Unitários:
deno task test --no-check
A aplicação está transicionando de um protótipo estrito de Web Push para um mensageiro moderno abrangente:
- P2P First (WebRTC & WebTorrent): Implementação de
RTCDataChannelpara envio de texto direto, deixando o push apenas para acordar o Worker. Criação dop2p-transfer.worker.jspara tráfego pesado focado diretamente no disco virtual (OPFS). - Media & APIs PWA: Implementação do leitor de QR Code (
BarcodeDetector), Picture-in-Picture nativo para chamadas (CallScreen.tsx) eScreen Wake Lockdurante uploads ativos. - Proteção de Evicção: Automação de solicitações
navigator.storage.persist()para assegurar os dados do usuário. - Web Share Target: Permitir que o Loco receba conteúdos diretos do Android share sheet.
- Backup (fflate): Exportação completa do estado (IDB + OPFS) criptografada em um arquivo ZIP.
- Evicção: Processo em que o SO apaga o IndexedDB para liberar espaço. Evitado usando
persist(). - VAPID: Voluntary Application Server Identification. Assegura ao provedor (FCM) quem está emitindo o push.
- Rate Limiting: Falhas
HTTP 429ou410do FCM ao sobrecarregar a fila de Push. Resolvido caindo para WebRTC sempre que a aba estiver aberta. - "Não foi possível extrair o código do SW": Erro do
build.ts. Certifique-se de que não há erros de sintaxe explícitos nos arquivos importados peloservice-worker.ts.