Como Criei Acidentalmente um Chat de Voz Impossível de Espionar
Um desenvolvedor relata como, ao tentar resolver problemas de latência em chamadas de voz, acabou criando um sistema de comunicação P2P que, por sua arquitetura, se tornou intrinsecamente privado e difícil de interceptar.
MundiX News·08 de julho de 2026·10 min de leitura·👁 1 views
Como Criei Acidentalmente um Chat de Voz Impossível de Espionar
Imediatamente, desaponto os amantes de frases de efeito: eu não criei um "mensageiro seguro", não pensei em vigilância, escutas ou privacidade em geral. Minha única intenção era ter a capacidade de ligar rapidamente e sem impedimentos para qualquer pessoa. O resultado foi um chat de voz onde a conversa é fisicamente impossível de ser escutada externamente, e eu só percebi isso muito depois. Portanto, "impossível de escutar" no título não é o objetivo, mas sim um efeito colateral.
A história começou de forma banal: uma noite comum, uma conversa no Discord, mas em algum momento a latência na entrega da voz atingiu 3-5 segundos, tornando a comunicação impossível. Tentamos por cerca de quarenta minutos corrigir a situação: trocamos servidores VPN, reiniciamos o "Запрет" (um serviço russo de acesso à internet) e mudamos estratégias, e, desesperados, reinstalamos o próprio Discord. A latência às vezes desaparecia, mas sempre retornava. Buscamos alternativas: o Telemost, com sua interface corporativa e existindo apenas na web, foi descartado quase imediatamente. A situação piorou: na Rússia, a maioria das alternativas não abre sem VPN (exceto clones francamente vergonhosos do Discord mencionado acima), e o TeamSpeak tem uma barreira de entrada tão alta que é mais fácil ir pessoalmente ao interlocutor: registrar-se, instalar o aplicativo, encontrar um servidor público e descobrir como se conectar. Em cerca de uma hora e meia, vasculhamos tudo o que aparecia e nenhum dos serviços conseguiu resolver a tarefa mais simples: ligar agora mesmo e ouvir um ao outro sem latência e sem a necessidade de gerenciar VPNs/serviços de contorno.
No final, as últimas esperanças se dissiparam: se não há alternativas, vamos criá-las. Em uma noite, esbocei um protótipo – a ideia era primitiva, "nada supérfluo": entre e fale. Sem registro com código de confirmação por e-mail, sem onboarding, sem pop-ups "permita notificações", e depois de algumas semanas percebi que esse "nada supérfluo" era a principal característica. A efemeridade e a ausência de toda a parafernália moderna que descartei por preguiça e minimalismo acabaram sendo exatamente o que os outros não têm. O projeto foi nomeado "Void", agora ele já está em produção e cumpre exatamente a função para a qual foi criado. A seguir, explicarei como ele funciona internamente, por que, devido a uma decisão, ele se tornou rápido, privado e inconveniente para escutas, e onde me queimei no caminho.
Por que a Latência Ocorria
Se analisarmos a origem da latência, a resposta será bastante tediosa. O fato é que a voz não vai diretamente para o interlocutor, mas faz uma curva através de um servidor localizado em algum lugar longe da Rússia. Minha voz ia para o data center do Discord, de lá para um amigo, e a resposta dele voltava pelo mesmo caminho. Milhares de quilômetros extras em cada direção, e a VPN apenas piorava essa situação, adicionando mais uma curva por cima. E essa coisa, o servidor de mídia no meio da conversa, é a raiz de tudo. Como todo o áudio passa por ele, tecnicamente a voz está completamente em suas mãos: eles podem gravar, analisar e, se solicitado educadamente, compartilhar. Além disso, como existe uma conta, há algo a que se vincular: histórico, moderação e, mais recentemente, verificação de idade por passaporte. Remova o servidor do meio da conversa, e metade desses problemas desaparece de uma vez, simplesmente porque sua fonte desaparece. Assim, a questão era puramente de engenharia: como fazer com que duas pessoas falassem diretamente, e o servidor apenas as conectasse – e não participasse mais. Acontece que, para isso, o navegador há muito tempo possui o WebRTC, basta juntar tudo.
O Servidor Conecta e Não Participa Mais
A parte do servidor do Void é Node.js, WS e um pouco de Express. Enquanto a chamada está ativa, o servidor faz exatamente uma coisa: conecta os participantes. Recebe de um a descrição da conexão (SDP), a transmite para o outro, troca candidatos ICE entre eles – ou seja, mensagens do tipo "é assim que você pode me alcançar". Alguns kilobytes de texto de serviço para estabelecer a conexão, e depois disso o servidor sai da conversa. Não há banco de dados. Não "tem, mas nós realmente não olhamos", mas sim não existe como fenômeno. As salas ficam em um Map comum na memória do processo: código da sala e lista de quem está nela. Saiu o último participante – com ele, desapareceu o registro da memória. Um único JSON com estatísticas anônimas é gravado em disco: quantas salas por dia, quantas conexões foram diretas, quantas foram via relay. Não há apelidos, mensagens ou gravações de chamadas lá, porque elas não chegam ao servidor. O código-fonte, literalmente, todo o "banco de dados" do servidor é um único Map na memória do processo: Map { roomCode => { users: Map { userId => { ws, nickname, mic, sound, screen } }, cleanupTimer: setTimeout id | null } }. Saiu o último participante – rooms.delete(code), e da sala não restou absolutamente nada. Daí o "impossível de escutar" do título. Escutar a conversa externamente é simplesmente impossível, ela não está no servidor. No banco de dados, que não existe, também não está. E entre si, os interlocutores criptografam o fluxo diretamente: no WebRTC, isso é DTLS – SRTP, as chaves são negociadas entre os navegadores, ninguém as detém no meio. Mesmo quando um par, devido a uma rede instável, passa por um relay, o relay apenas encaminha pacotes criptografados e não pode descriptografá-los. Metadados, quem se conectou com quem, o servidor vê no momento do contato, mas não grava em lugar nenhum: a sala vive na memória e desaparece com o último participante. E aqui está uma nota honesta: "impossível de escutar" refere-se ao caminho entre os interlocutores, e não aos seus dispositivos. Se o dispositivo de alguém estiver infectado, nenhum P2P salvará: o som será capturado exatamente onde ele soa ou é pronunciado, antes de qualquer criptografia. A criptografia protege o canal, não o endpoint vulnerável. O Void remove a possibilidade de escuta no caminho, mas se o adversário já estiver no seu sistema – ele ouvirá a conversa, e a arquitetura não resolve isso.
Por que Poucas Pessoas na Sala
Como o servidor não mistura o áudio, os participantes devem se conectar uns aos outros. Para dois, é uma conexão; para um grupo, "cada um com cada um", o número de conexões cresce exponencialmente: para cinco pessoas são 10, para dez já são 45, e cada um envia seu áudio para todos os outros em um fluxo separado. Por isso, as salas são mantidas pequenas: o teto é de dez pessoas, e essa, infelizmente, é o preço da solução. Se você precisa de uma sala para cem pessoas, terá que colocar um SFU no meio, e ele novamente começará a ver o fluxo. Eu não queria colocar um SFU, pois para isso teria que descartar exatamente o motivo pelo qual tudo começou. "Perfect negotiation", ou onde o WebRTC morde. Durante os testes, situações em que ambos os lados decidem atualizar a conexão simultaneamente e enviar ofertas um ao outro não foram raras. Isso é chamado de glare, e o código que não espera tal evento morre nesse momento: ambos esperam uma resposta que ninguém enviará. Isso é corrigido por um recurso da especificação W3C chamado "perfect negotiation". Cada conexão recebe um papel: educado e não educado, e é determinado simetricamente pela comparação de IDs – dois lados terão papéis opostos sem qualquer acordo entre eles. O código-fonte: cujo ID é "maior" como string, esse é o educado. O interlocutor tem a comparação espelhada, então exatamente um se torna educado: peer._polite = String(clientId) > String(userId);. O educado, em caso de colisão, reverte sua oferta e aceita a do outro, e o não educado – impõe a sua: const offerCollision = peer.signalingState !== "stable" && !peer._isSettingRemoteAnswerPending; const ignoreOffer = !peer._polite && offerCollision; if (ignoreOffer) return; // não educado: mantemos nossa oferta await peer.setRemoteDescription(new RTCSessionDescription(data.offer)); await peer.setLocalDescription(); // educado: cedemos, rollback implícito. Em seguida, setLocalDescription() sem argumentos entende por si só o que é apropriado agora, oferta ou resposta. Sem "perfect negotiation", a chamada em grupo se desfaz exatamente no momento em que dois mudam algo simultaneamente (e eles mudarão), e eu tropecei nisso classicamente: em duas abas tudo funciona, mas em uma sala real – conexões travadas do nada. A conclusão é bastante banal: "funciona para mim" e "funciona na realidade" no WebRTC – são coisas completamente diferentes.
NAT, VPN e Por Que Ainda é Preciso Chegar ao Interlocutor
Para que duas pessoas se conectem diretamente, cada uma precisa saber seu endereço externo e contornar o NAT. Servidores STUN, leves e públicos, fazem isso, eles simplesmente respondem "é assim que você parece de fora". Para a maioria das pessoas em casa, isso é suficiente, e o áudio segue o caminho mais curto possível entre vocês. O caminho mais curto é a chave para a latência mínima: você ouve como se a pessoa estivesse na casa ao lado, porque o áudio não viaja através de um data center em outro país. Mas tudo quebra no NAT simétrico e especialmente no CG-NAT das operadoras móveis, onde centenas de assinantes compartilham um único endereço externo, onde simplesmente não é possível estabelecer uma conexão direta. De acordo com minhas estatísticas, após uma dúzia de horas de testes em tais redes, cerca de 23% dos pares não se conectam diretamente e recorrem a uma opção de backup – um servidor TURN (no meu caso, é o coturn), que retransmite o tráfego quando não há caminho direto. Sim, para tal par, a chamada fisicamente passa pelo meu servidor. Mas o coturn retransmite pacotes já criptografados (DTLS-SRTP) e não sabe como descriptografá-los. É claro que é possível desativar o TURN completamente, mas nesse caso, os próprios ~23% de pares complexos falham no STUN-only, então ele está em produção. Código-fonte: Não há um banco de dados separado para o TURN também: as credenciais são de curta duração e calculadas dinamicamente pelo esquema padrão do coturn: const expiry = Math.floor(Date.now() / 1000) + 3600; // TTL = 1 hora const username = ${expiry}:${userId}; const credential = crypto.createHmac("sha1", TURN_SECRET).update(username).digest("base64");. E sobre VPN: se o próprio site estiver bloqueado e só abrir com VPN – o túnel será usado apenas para abrir a página e trocar sinalização – apenas alguns segundos na conexão. A conversa em si continua diretamente e a VPN não a afeta: o ping da chamada não depende de por qual país seu túnel passa, porque o áudio não passa por ele.
Quando a Rede Falha – Como Lidar?
A conexão P2P é frágil, e as razões para isso podem ser muitas: o Wi-Fi mudou, pacotes foram perdidos, a operadora mudou a rota, a conexão foi interrompida. Sem um mecanismo para lidar com tais casos, seria uma interrupção da chamada, então, sobre o WebRTC, foi necessário construir uma máquina de recuperação. Funciona mais ou menos assim: o status desconectado ainda não é o fim, esperamos pacientemente alguns segundos, muitas vezes a rede se conserta sozinha. Se não ajudar – o lado faz um ICE-restart, procura a rota novamente. Se não ajudar em alguns segundos – reconstrói a conexão do zero, o outro lado, por um flag, faz o mesmo. Separadamente, há um watchdog para conexões mortas: a cada duas segundos ele verifica o contador de pacotes recebidos, e se a conexão estiver viva em status, mas não houver pacotes por mais de cinco segundos, ele não espera o timeout de meio minuto, mas reconstrói imediatamente. Na prática, isso detecta "parece conectado, mas não há comunicação" significativamente mais rápido. Isso custa um segundo desagradável: no momento da reconstrução, o som cai por um ou dois segundos. O microfone, nesse caso, não é perdido, a conversa continua.
O Que Foi Obtido do Navegador Quase de Graça
Como tudo vive na web, algumas coisas saíram a um custo irrisório. Cancelamento de ruído – é o RNNoise, compilado em WASM e rodando no AudioWorklet: teclado, cooler e rua são cortados diretamente no cliente, antes mesmo de serem enviados ao interlocutor. Chat e arquivos seguem o mesmo P2P, via RTCDataChannel, em pedaços binários de 60 KB (um pouco abaixo do limite do SCTP), arquivos de até 100 MB. Compartilhamento de tela com áudio e em Full HD, pelo mesmo canal direto. Nada disso chega ao servidor, as garantias são as mesmas do áudio.
O Que No Final Não é Perfeito
Sem uma seção de desvantagens – nada: Mesh não suporta escala. Dez pessoas é o teto, e aumentá-lo sem passar para SFU é impossível, e SFU mataria a principal característica. Isso não é "falta de tempo", é uma bifurcação, e eu conscientemente me inclinei para a privacidade. UserId é falsificável. O cliente inventa seu próprio ID, o servidor apenas verifica o formato. Teoricamente, você pode se passar por outro ID. Na prática, não há nada a roubar: entre as sessões não há estado, não há contas. Não construí uma fortaleza em torno de um galinheiro vazio. Não há push na web. Fechou a aba – você não ouvirá a chamada, não há como "puxá-lo", porque não há conta nem sua sessão no servidor. Na versão desktop, isso é parcialmente corrigido pelo tray, na web – de forma alguma. Reiniciar o servidor mata as salas. O outro lado da ausência de BD. Para uma chamada de vinte minutos, ok, mas se você cair no momento da implantação – entre novamente. Desktop apenas no Windows. O aplicativo em Tauri existe para Windows, macOS e Linux ainda não. A web funciona em qualquer lugar onde o WebRTC funcione. Tudo da lista acima – não é um fracasso, mas sim o preço de uma construção onde o servidor não precisa saber nada sobre você. Mas dizer que tudo é perfeito seria uma mentira.
Uma Divulgação Honesta – Não é um "Matador de Discord"
Discord é sobre comunidades, servidores, papéis, online eterno, e essa é uma tarefa separada que o Void não resolve e não tenta resolver. Void é mais como "ligar rapidamente e ir embora", mais próximo de uma ligação telefônica do que de uma rede social. Por que tudo isso? Todo o projeto nasceu de um problema fundamental: eu queria apenas conversar, mas tive que lutar contra a latência e procurar para onde fugir. Então, eu o fiz principalmente para mim e alguns amigos, e a "privacidade" acabou sendo um efeito colateral, pois ao remover tudo o que era supérfluo do esquema, acabou não havendo nada para coletar – porque não há com o quê. Você pode experimentar o Void no navegador em void-room.space, sem instalação e registro: crie uma sala, envie o código para o interlocutor – e falem. Exatamente o que me faltou na época. O código está aberto no GitHub – ficarei feliz com estrelas e issues. P.S. Muitas coisas ficaram nos bastidores que não couberam no artigo: máquina de recuperação em caso de interrupções, eco, screencast-ducking, versão desktop em Tauri. Acumulei muitos problemas durante meses de desenvolvimento e, se houver demanda – ficarei feliz em analisar os mais interessantes separadamente. Paz!
🛡️⚡
Pare de pesquisar. Comece a hackear.
O MundiX é seu copiloto de pentest com IA: comandos exatos, análise de outputs e próximo passo na kill chain — em segundos.
Sem cartão para começar · Planos a partir de R$49/mês
Como Criei Acidentalmente um Chat de Voz Impossível de Espionar
Imediatamente, desaponto os amantes de frases de efeito: eu não criei um "mensageiro seguro", não pensei em vigilância, escutas ou privacidade em geral. Minha única intenção era ter a capacidade de ligar rapidamente e sem impedimentos para qualquer pessoa. O resultado foi um chat de voz onde a conversa é fisicamente impossível de ser escutada externamente, e eu só percebi isso muito depois. Portanto, "impossível de escutar" no título não é o objetivo, mas sim um efeito colateral.
A história começou de forma banal: uma noite comum, uma conversa no Discord, mas em algum momento a latência na entrega da voz atingiu 3-5 segundos, tornando a comunicação impossível. Tentamos por cerca de quarenta minutos corrigir a situação: trocamos servidores VPN, reiniciamos o "Запрет" (um serviço russo de acesso à internet) e mudamos estratégias, e, desesperados, reinstalamos o próprio Discord. A latência às vezes desaparecia, mas sempre retornava. Buscamos alternativas: o Telemost, com sua interface corporativa e existindo apenas na web, foi descartado quase imediatamente. A situação piorou: na Rússia, a maioria das alternativas não abre sem VPN (exceto clones francamente vergonhosos do Discord mencionado acima), e o TeamSpeak tem uma barreira de entrada tão alta que é mais fácil ir pessoalmente ao interlocutor: registrar-se, instalar o aplicativo, encontrar um servidor público e descobrir como se conectar. Em cerca de uma hora e meia, vasculhamos tudo o que aparecia e nenhum dos serviços conseguiu resolver a tarefa mais simples: ligar agora mesmo e ouvir um ao outro sem latência e sem a necessidade de gerenciar VPNs/serviços de contorno.
No final, as últimas esperanças se dissiparam: se não há alternativas, vamos criá-las. Em uma noite, esbocei um protótipo – a ideia era primitiva, "nada supérfluo": entre e fale. Sem registro com código de confirmação por e-mail, sem onboarding, sem pop-ups "permita notificações", e depois de algumas semanas percebi que esse "nada supérfluo" era a principal característica. A efemeridade e a ausência de toda a parafernália moderna que descartei por preguiça e minimalismo acabaram sendo exatamente o que os outros não têm. O projeto foi nomeado "Void", agora ele já está em produção e cumpre exatamente a função para a qual foi criado. A seguir, explicarei como ele funciona internamente, por que, devido a uma decisão, ele se tornou rápido, privado e inconveniente para escutas, e onde me queimei no caminho.
Por que a Latência Ocorria
Se analisarmos a origem da latência, a resposta será bastante tediosa. O fato é que a voz não vai diretamente para o interlocutor, mas faz uma curva através de um servidor localizado em algum lugar longe da Rússia. Minha voz ia para o data center do Discord, de lá para um amigo, e a resposta dele voltava pelo mesmo caminho. Milhares de quilômetros extras em cada direção, e a VPN apenas piorava essa situação, adicionando mais uma curva por cima. E essa coisa, o servidor de mídia no meio da conversa, é a raiz de tudo. Como todo o áudio passa por ele, tecnicamente a voz está completamente em suas mãos: eles podem gravar, analisar e, se solicitado educadamente, compartilhar. Além disso, como existe uma conta, há algo a que se vincular: histórico, moderação e, mais recentemente, verificação de idade por passaporte. Remova o servidor do meio da conversa, e metade desses problemas desaparece de uma vez, simplesmente porque sua fonte desaparece. Assim, a questão era puramente de engenharia: como fazer com que duas pessoas falassem diretamente, e o servidor apenas as conectasse – e não participasse mais. Acontece que, para isso, o navegador há muito tempo possui o WebRTC, basta juntar tudo.
O Servidor Conecta e Não Participa Mais
A parte do servidor do Void é Node.js, WS e um pouco de Express. Enquanto a chamada está ativa, o servidor faz exatamente uma coisa: conecta os participantes. Recebe de um a descrição da conexão (SDP), a transmite para o outro, troca candidatos ICE entre eles – ou seja, mensagens do tipo "é assim que você pode me alcançar". Alguns kilobytes de texto de serviço para estabelecer a conexão, e depois disso o servidor sai da conversa. Não há banco de dados. Não "tem, mas nós realmente não olhamos", mas sim não existe como fenômeno. As salas ficam em um Map comum na memória do processo: código da sala e lista de quem está nela. Saiu o último participante – com ele, desapareceu o registro da memória. Um único JSON com estatísticas anônimas é gravado em disco: quantas salas por dia, quantas conexões foram diretas, quantas foram via relay. Não há apelidos, mensagens ou gravações de chamadas lá, porque elas não chegam ao servidor. O código-fonte, literalmente, todo o "banco de dados" do servidor é um único Map na memória do processo: Map { roomCode => { users: Map { userId => { ws, nickname, mic, sound, screen } }, cleanupTimer: setTimeout id | null } }. Saiu o último participante – rooms.delete(code), e da sala não restou absolutamente nada. Daí o "impossível de escutar" do título. Escutar a conversa externamente é simplesmente impossível, ela não está no servidor. No banco de dados, que não existe, também não está. E entre si, os interlocutores criptografam o fluxo diretamente: no WebRTC, isso é DTLS – SRTP, as chaves são negociadas entre os navegadores, ninguém as detém no meio. Mesmo quando um par, devido a uma rede instável, passa por um relay, o relay apenas encaminha pacotes criptografados e não pode descriptografá-los. Metadados, quem se conectou com quem, o servidor vê no momento do contato, mas não grava em lugar nenhum: a sala vive na memória e desaparece com o último participante. E aqui está uma nota honesta: "impossível de escutar" refere-se ao caminho entre os interlocutores, e não aos seus dispositivos. Se o dispositivo de alguém estiver infectado, nenhum P2P salvará: o som será capturado exatamente onde ele soa ou é pronunciado, antes de qualquer criptografia. A criptografia protege o canal, não o endpoint vulnerável. O Void remove a possibilidade de escuta no caminho, mas se o adversário já estiver no seu sistema – ele ouvirá a conversa, e a arquitetura não resolve isso.
Por que Poucas Pessoas na Sala
Como o servidor não mistura o áudio, os participantes devem se conectar uns aos outros. Para dois, é uma conexão; para um grupo, "cada um com cada um", o número de conexões cresce exponencialmente: para cinco pessoas são 10, para dez já são 45, e cada um envia seu áudio para todos os outros em um fluxo separado. Por isso, as salas são mantidas pequenas: o teto é de dez pessoas, e essa, infelizmente, é o preço da solução. Se você precisa de uma sala para cem pessoas, terá que colocar um SFU no meio, e ele novamente começará a ver o fluxo. Eu não queria colocar um SFU, pois para isso teria que descartar exatamente o motivo pelo qual tudo começou. "Perfect negotiation", ou onde o WebRTC morde. Durante os testes, situações em que ambos os lados decidem atualizar a conexão simultaneamente e enviar ofertas um ao outro não foram raras. Isso é chamado de glare, e o código que não espera tal evento morre nesse momento: ambos esperam uma resposta que ninguém enviará. Isso é corrigido por um recurso da especificação W3C chamado "perfect negotiation". Cada conexão recebe um papel: educado e não educado, e é determinado simetricamente pela comparação de IDs – dois lados terão papéis opostos sem qualquer acordo entre eles. O código-fonte: cujo ID é "maior" como string, esse é o educado. O interlocutor tem a comparação espelhada, então exatamente um se torna educado: peer._polite = String(clientId) > String(userId);. O educado, em caso de colisão, reverte sua oferta e aceita a do outro, e o não educado – impõe a sua: const offerCollision = peer.signalingState !== "stable" && !peer._isSettingRemoteAnswerPending; const ignoreOffer = !peer._polite && offerCollision; if (ignoreOffer) return; // não educado: mantemos nossa oferta await peer.setRemoteDescription(new RTCSessionDescription(data.offer)); await peer.setLocalDescription(); // educado: cedemos, rollback implícito. Em seguida, setLocalDescription() sem argumentos entende por si só o que é apropriado agora, oferta ou resposta. Sem "perfect negotiation", a chamada em grupo se desfaz exatamente no momento em que dois mudam algo simultaneamente (e eles mudarão), e eu tropecei nisso classicamente: em duas abas tudo funciona, mas em uma sala real – conexões travadas do nada. A conclusão é bastante banal: "funciona para mim" e "funciona na realidade" no WebRTC – são coisas completamente diferentes.
NAT, VPN e Por Que Ainda é Preciso Chegar ao Interlocutor
Para que duas pessoas se conectem diretamente, cada uma precisa saber seu endereço externo e contornar o NAT. Servidores STUN, leves e públicos, fazem isso, eles simplesmente respondem "é assim que você parece de fora". Para a maioria das pessoas em casa, isso é suficiente, e o áudio segue o caminho mais curto possível entre vocês. O caminho mais curto é a chave para a latência mínima: você ouve como se a pessoa estivesse na casa ao lado, porque o áudio não viaja através de um data center em outro país. Mas tudo quebra no NAT simétrico e especialmente no CG-NAT das operadoras móveis, onde centenas de assinantes compartilham um único endereço externo, onde simplesmente não é possível estabelecer uma conexão direta. De acordo com minhas estatísticas, após uma dúzia de horas de testes em tais redes, cerca de 23% dos pares não se conectam diretamente e recorrem a uma opção de backup – um servidor TURN (no meu caso, é o coturn), que retransmite o tráfego quando não há caminho direto. Sim, para tal par, a chamada fisicamente passa pelo meu servidor. Mas o coturn retransmite pacotes já criptografados (DTLS-SRTP) e não sabe como descriptografá-los. É claro que é possível desativar o TURN completamente, mas nesse caso, os próprios ~23% de pares complexos falham no STUN-only, então ele está em produção. Código-fonte: Não há um banco de dados separado para o TURN também: as credenciais são de curta duração e calculadas dinamicamente pelo esquema padrão do coturn: const expiry = Math.floor(Date.now() / 1000) + 3600; // TTL = 1 hora const username = ${expiry}:${userId}; const credential = crypto.createHmac("sha1", TURN_SECRET).update(username).digest("base64");. E sobre VPN: se o próprio site estiver bloqueado e só abrir com VPN – o túnel será usado apenas para abrir a página e trocar sinalização – apenas alguns segundos na conexão. A conversa em si continua diretamente e a VPN não a afeta: o ping da chamada não depende de por qual país seu túnel passa, porque o áudio não passa por ele.
Quando a Rede Falha – Como Lidar?
A conexão P2P é frágil, e as razões para isso podem ser muitas: o Wi-Fi mudou, pacotes foram perdidos, a operadora mudou a rota, a conexão foi interrompida. Sem um mecanismo para lidar com tais casos, seria uma interrupção da chamada, então, sobre o WebRTC, foi necessário construir uma máquina de recuperação. Funciona mais ou menos assim: o status desconectado ainda não é o fim, esperamos pacientemente alguns segundos, muitas vezes a rede se conserta sozinha. Se não ajudar – o lado faz um ICE-restart, procura a rota novamente. Se não ajudar em alguns segundos – reconstrói a conexão do zero, o outro lado, por um flag, faz o mesmo. Separadamente, há um watchdog para conexões mortas: a cada duas segundos ele verifica o contador de pacotes recebidos, e se a conexão estiver viva em status, mas não houver pacotes por mais de cinco segundos, ele não espera o timeout de meio minuto, mas reconstrói imediatamente. Na prática, isso detecta "parece conectado, mas não há comunicação" significativamente mais rápido. Isso custa um segundo desagradável: no momento da reconstrução, o som cai por um ou dois segundos. O microfone, nesse caso, não é perdido, a conversa continua.
O Que Foi Obtido do Navegador Quase de Graça
Como tudo vive na web, algumas coisas saíram a um custo irrisório. Cancelamento de ruído – é o RNNoise, compilado em WASM e rodando no AudioWorklet: teclado, cooler e rua são cortados diretamente no cliente, antes mesmo de serem enviados ao interlocutor. Chat e arquivos seguem o mesmo P2P, via RTCDataChannel, em pedaços binários de 60 KB (um pouco abaixo do limite do SCTP), arquivos de até 100 MB. Compartilhamento de tela com áudio e em Full HD, pelo mesmo canal direto. Nada disso chega ao servidor, as garantias são as mesmas do áudio.
O Que No Final Não é Perfeito
Sem uma seção de desvantagens – nada: Mesh não suporta escala. Dez pessoas é o teto, e aumentá-lo sem passar para SFU é impossível, e SFU mataria a principal característica. Isso não é "falta de tempo", é uma bifurcação, e eu conscientemente me inclinei para a privacidade. UserId é falsificável. O cliente inventa seu próprio ID, o servidor apenas verifica o formato. Teoricamente, você pode se passar por outro ID. Na prática, não há nada a roubar: entre as sessões não há estado, não há contas. Não construí uma fortaleza em torno de um galinheiro vazio. Não há push na web. Fechou a aba – você não ouvirá a chamada, não há como "puxá-lo", porque não há conta nem sua sessão no servidor. Na versão desktop, isso é parcialmente corrigido pelo tray, na web – de forma alguma. Reiniciar o servidor mata as salas. O outro lado da ausência de BD. Para uma chamada de vinte minutos, ok, mas se você cair no momento da implantação – entre novamente. Desktop apenas no Windows. O aplicativo em Tauri existe para Windows, macOS e Linux ainda não. A web funciona em qualquer lugar onde o WebRTC funcione. Tudo da lista acima – não é um fracasso, mas sim o preço de uma construção onde o servidor não precisa saber nada sobre você. Mas dizer que tudo é perfeito seria uma mentira.
Uma Divulgação Honesta – Não é um "Matador de Discord"
Discord é sobre comunidades, servidores, papéis, online eterno, e essa é uma tarefa separada que o Void não resolve e não tenta resolver. Void é mais como "ligar rapidamente e ir embora", mais próximo de uma ligação telefônica do que de uma rede social. Por que tudo isso? Todo o projeto nasceu de um problema fundamental: eu queria apenas conversar, mas tive que lutar contra a latência e procurar para onde fugir. Então, eu o fiz principalmente para mim e alguns amigos, e a "privacidade" acabou sendo um efeito colateral, pois ao remover tudo o que era supérfluo do esquema, acabou não havendo nada para coletar – porque não há com o quê. Você pode experimentar o Void no navegador em void-room.space, sem instalação e registro: crie uma sala, envie o código para o interlocutor – e falem. Exatamente o que me faltou na época. O código está aberto no GitHub – ficarei feliz com estrelas e issues. P.S. Muitas coisas ficaram nos bastidores que não couberam no artigo: máquina de recuperação em caso de interrupções, eco, screencast-ducking, versão desktop em Tauri. Acumulei muitos problemas durante meses de desenvolvimento e, se houver demanda – ficarei feliz em analisar os mais interessantes separadamente. Paz!
📤 Compartilhar & Baixar
🧰 Ferramentas recomendadas
Divulgação: alguns links são patrocinados. Podemos receber comissão se você comprar — sem custo extra para você. Só indicamos o que faz sentido para a comunidade.