TreeMap
A free, open-source disk-space visualizer. Squarified treemap, duplicate hunting, disk-full forecasting, and cleanup that only ever moves files to the Trash.
Sala de reunião efêmera: áudio e compartilhamento de tela, sem cadastro.
Translation pending
ProductivityTypeScript · CSSGitHub @antonellealvesOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
livestreaming do supra sumo do entretenimento de dota para amigos
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Sala de reunião web, efêmera e sem cadastro: até 20 participantes falam, ouvem, compartilham tela e conversam por texto. Sem banco de dados, sem estado no servidor além do próprio SFU. A especificação técnica completa — incluindo as decisões e os limites conhecidos — está em SPEC.md.Read the full README ↗
Sala de voz e transmissão para assistir junto com os amigos: até 20 pessoas falam, ouvem, compartilham tela com áudio e conversam por texto, com soundboard na sala. Entrar não exige cadastro — digitar um nome basta; conta com Google ou e-mail é opcional.
Abrir no navegador: https://telecord.vercel.app
| Sistema | Instalador |
|---|---|
| Windows 10/11 (64 bits) | telecord-windows-x64-setup.exe |
| macOS Apple Silicon (M1 em diante) | telecord-macos-arm64.dmg |
| macOS Intel | telecord-macos-x64.dmg |
| Linux (qualquer distro) | telecord-linux-x64.AppImage |
| Linux Debian/Ubuntu | telecord-linux-x64.deb |
Os links apontam sempre para a versão mais recente. Todas as versões ficam em Releases.
O app é o mesmo telecord do navegador dentro de uma janela própria, com o que o navegador não dá: atalho global de push-to-talk, áudio do sistema na transmissão de tela (Windows), ícone na bandeja e atualização automática. Ele carrega a versão web publicada, então qualquer novidade do site já aparece nele sem reinstalar.
Os instaladores ainda não são assinados. No Windows, o SmartScreen avisa de "editor desconhecido": clique em Mais informações → Executar assim mesmo. No macOS, se aparecer que o app "está danificado" ou "não pode ser aberto", rode
xattr -cr /Applications/telecord.appno Terminal e abra de novo. No Linux, o AppImage precisa de permissão de execução (chmod +x telecord-linux-x64.AppImage).
Como gerar e publicar uma versão nova do app: desktop/README.md.
A especificação técnica completa — incluindo as decisões e os limites conhecidos — está em SPEC.md.
/
├── apps/web/ Vite + React + TS (SPA)
├── apps/api/ serviço de contas, salas e administração (Nest + Prisma)
├── apps/mediasoup-sfu/ SFU próprio, a segunda opção de transporte de mídia
├── packages/shared/ contrato e validação usados pelos dois lados
├── desktop/ shell Electron: os instaladores para computador
├── api/ funções serverless da Vercel (token do LiveKit, salas ativas)
├── livekit/ mediasoup/ configuração e compose de produção dos dois servidores de mídia
├── infra/ proxy (Caddy) na frente dos dois
├── scripts/ guard-rail que barra credencial no bundle
├── vercel.json
└── pnpm-workspace.yaml
Browser ──POST /api/token──> Vercel (Node) ──assina JWT──┐
│ │
└──────────wss:// + WebRTC──────────> LiveKit Cloud <──┘ (valida o mesmo JWT)
A função serverless é o único lugar que toca no secret do LiveKit. Todo o estado da sala (quem está dentro, quem fala, quem compartilha) vive na memória do SFU e é derivado dos eventos do Room — não há persistência em lugar nenhum.
O segundo transporte é um SFU nosso, em apps/mediasoup-sfu. O desenho é o do Discord e do Meet: um serviço autoriza a entrada uma vez, e daí em diante o navegador conversa direto com o nó que encaminha a mídia.
Browser ──GET /api/mediasoup/rooms/:sala/config──> apps/api autoriza, assina o token
│ (com vários nós: diz em qual a sala mora)
├── wss://<nó>/presence ──> mediasoup-sfu roster, chat, moderação
├── wss://<nó>/signal ──> mediasoup-sfu criar transporte, publicar, assinar, retomar
└── UDP (DTLS/SRTP) ──> Worker áudio e vídeo — um processo C++ por núcleo
/signal é um par de pipelines (apps/mediasoup-sfu/src/signal/): pedidos atendidos em ordem, respostas e avisos numa fila com prioridade, e contrapressão do socket até quem atende. Ligar o microfone não passa mais pela função serverless nem pelo banco./signal não abrir, o cliente usa as mesmas operações por HTTP, através do proxy /api/mediasoup/*.Nada abaixo exige mexer em código.
| Para ter | O que fazer |
|---|---|
| mais gente no mesmo nó | VM com mais núcleos, e a variável MEDIASOUP_WORKERS = auto no Environment Production do GitHub. Sobe um Worker por núcleo; sala que enche um Worker transborda para o seguinte sozinha. |
| mais nós | Outra VM com o mesmo mediasoup/docker-compose.prod.yml e o mesmo MEDIASOUP_INTERNAL_SECRET, atrás de TLS. Depois, MEDIASOUP_INTERNAL_URL passa a ser a lista das URLs, separadas por vírgula. Cada sala mora num nó, e sala com gente não muda de lugar quando a lista muda. |
| saber se está na hora | GET https://<nó>/node com Authorization: Bearer <MEDIASOUP_INTERNAL_SECRET>: CPU e carga de cada Worker, salas, tracks. |
Dois avisos para o dia do segundo nó: o workflow de deploy publica uma VM e monta MEDIASOUP_INTERNAL_URL a partir de MEDIASOUP_PUBLIC_HOST, então a segunda máquina precisa do próprio deploy e essa linha precisa passar a lista; e o painel "Ao vivo" só recebe o roster em tempo real do primeiro nó — nos demais ele atualiza pela consulta periódica.
A VM de hoje fica em um Worker de propósito (o padrão do compose de produção): ela tem ~500 MB e divide o processador com o LiveKit. Cada Worker escuta numa porta fixa a partir de 40101/udp, e todos os participantes dele dividem essa porta — a faixa 40101-40200 já liberada no firewall cobre até cem Workers.
Testes do SFU (sobem o Worker de verdade, em 127.0.0.1):
pnpm --filter @telecord/shared build
pnpm --filter @telecord/mediasoup-sfu build && pnpm --filter @telecord/mediasoup-sfu test
Requisitos: Node 22+ e pnpm 9 (via corepack: corepack enable pnpm).
pnpm install
cp .env.example .env # e preencha com as suas credenciais
pnpm dev # http://localhost:5173
pnpm dev sobe só o Vite — que também executa api/token.ts no próprio middleware, na mesma origem, com o mesmo código que roda na Vercel. Não é preciso a CLI da Vercel para desenvolver.
Scripts da raiz:
| Comando | O que faz |
|---|---|
pnpm dev | Vite + /api/token em localhost:5173 |
pnpm build | compila shared, builda a SPA e roda o guard-rail de credenciais |
pnpm typecheck | tsc --noEmit em shared, web, vite.config.ts e api/ |
pnpm lint | alias de typecheck (não há ESLint: fora da lista de dependências permitidas) |
APIxxxx…) → LIVEKIT_API_KEYLIVEKIT_API_SECRET (só é exibido na criação)wss://<seu-projeto>.livekit.cloud) já está versionada como default em config.ts; troque lá se for outro projetoNenhum outro recurso do LiveKit precisa ser configurado: a sala é criada no primeiro join.
| Nome | Escopo | Obrigatória? | Público? |
|---|---|---|---|
LIVEKIT_API_KEY | runtime, servidor | sim | não |
LIVEKIT_API_SECRET | runtime, servidor | sim | não |
VITE_LIVEKIT_URL | build do Vite | não | sim — vai para o bundle |
VITE_TOKEN_ENDPOINT | build do Vite | não | sim (default /api/token) |
A URL do servidor LiveKit tem default versionado em config.ts: ela é pública por natureza (o navegador precisa dela) e acaba no bundle de qualquer jeito, então versionar só elimina o risco de publicar um app que não conecta por falta de uma variável de build. Defina VITE_LIVEKIT_URL apenas para apontar para outro servidor.
A key e o secret nunca podem ser versionados. Eles são lidos do ambiente pela função serverless, e é por isso que precisam ser cadastrados na Vercel: um .env no repositório não é carregado no runtime das funções — o resultado seria expor o secret e continuar com 500 SERVER_MISCONFIGURED.
Só o prefixo
VITE_expõe uma variável ao navegador — e o que vai para o bundle é legível por qualquer visitante. Nunca prefixe a key ou o secret comVITE_. Se o secret vazar, qualquer pessoa emite token para qualquer sala: rotacione a chave no dashboard do LiveKit.O
pnpm buildrodascripts/check-bundle.mjs, que falha o build se encontrarLIVEKIT_API_ou o valor do secret dentro deapps/web/dist.
O telecord funciona sem contas, e continua funcionando: digitar um nome e entrar é o caminho principal. Ligar a autenticação acrescenta a possibilidade de entrar com Google ou e-mail e senha, e nesse caso o nome deixa de ser auto-declarado — o servidor passa a afirmá-lo (SPEC §2.4).
Enquanto VITE_API_URL estiver vazia, nada disso existe no app publicado:
nenhuma requisição sai, nenhum botão de entrar aparece. É seguro publicar este
código com o serviço de autenticação ainda fora do ar.
cp .env.example .env
node scripts/gen-auth-keys.mjs >> .env # par de chaves Ed25519
docker compose up # TiDB + serviço de autenticação
pnpm dev # front, noutra janela
O docker-compose.yml sobe TiDB de verdade, não MySQL. O protocolo é o
mesmo, mas as diferenças que interessam não são de protocolo: AUTO_INCREMENT
não monotônico, ausência de foreign key efetiva, sintaxe de TTL. Desenvolver
contra MySQL esconderia exatamente o que quebra em produção.
O front fica no host, e não no contêiner: o Vite dentro de um bind mount no Windows só detecta alteração com polling, o que gasta CPU à toa e deixa o recarregamento lento.
Sem credencial do Google e sem provedor de e-mail o serviço sobe assim mesmo — o login social responde 503, o botão some da tela, e o link de confirmação de e-mail é escrito no log em vez de enviado:
docker compose logs -f api # o link de verificação sai aqui
http://localhost:3000/auth/google/callback (local)
https://api.seu-dominio.com/auth/google/callback (produção)
O Google compara caractere a caractere. Um / sobrando já recusa o login.GOOGLE_CLIENT_ID e
GOOGLE_CLIENT_SECRET no .env. As duas vão juntas: metade da credencial
faz o serviço recusar subir, de propósito..tidbcloud.com e a
porta é 4000, não 3306.2abcXYZ.raiz) — copie inteiro.DATABASE_URL; sslaccept=strict e connection_limit não são
opcionais:
mysql://<prefixo>.<usuario>:<senha>@<host>:4000/telecord?sslaccept=strict&connection_limit=5
pnpm --filter @telecord/api exec prisma migrate deploy
Tudo na Vercel: app e API na mesma origem. O serviço NestJS roda como
função, atrás de api/[...nest].ts, que captura tudo sob /api/ que não tenha
função própria — token e rooms continuam sendo arquivos separados, porque
rota com segmento fixo tem precedência sobre rota dinâmica.
Isso resolve de graça o problema que era o maior risco do desenho: com a mesma
origem, o cookie de refresh é SameSite=Lax e não há cookie de terceiro para o
Safari bloquear. Não é preciso domínio próprio.
Em troca, volta o problema de conexão que um processo persistente não teria:
cada instância fria abre o próprio pool. Por isso connection_limit=1 na
DATABASE_URL de produção — a invocação é curta e não tem o que reaproveitar.
O apps/api/Dockerfile continua no repositório, mas só para o desenvolvimento
local com Docker.
Todas as variáveis se criam no GitHub. Elas se criam no GitHub, não na
Vercel: o deploy por git está desligado e quem grava o ambiente do projeto é
scripts/vercel-deploy.mjs, a cada publicação. Editar direto no painel da
Vercel funciona até o próximo deploy sobrescrever.
Em Settings → Secrets and variables → Actions:
Aba Secrets — segredo de verdade:
| Nome | Valor |
|---|---|
DATABASE_URL | a URL do TiDB, com a senha dentro |
AUTH_JWT_PRIVATE_KEY | a metade que assina |
GOOGLE_CLIENT_SECRET | do OAuth Client |
RESEND_API_KEY | só se MAIL_DRIVER=resend |
Aba Variables — não são segredo:
| Nome | Valor |
|---|---|
AUTH_JWT_PUBLIC_KEY | a metade pública: valida o token, não emite nenhum |
APP_URL | https://telecord.vercel.app |
API_URL | https://telecord.vercel.app/api |
VITE_API_URL | https://telecord.vercel.app/api |
GOOGLE_CLIENT_ID | do OAuth Client |
MAIL_DRIVER / MAIL_FROM | log enquanto não houver provedor |
Todas são opcionais. Sem elas o app publicado simplesmente não tem contas, que é o estado anterior a este trabalho — e o passo de verificação do deploy pula o teste do serviço de autenticação sozinho.
VITE_API_URL e API_URL têm o mesmo valor aqui porque app e API dividem a
origem. Continuam separadas porque uma é lida no navegador, em tempo de build, e
a outra no servidor, em tempo de execução.
Duas coisas passaram a existir em volta da sala, sem mudar como se entra
nela — quem abre /sala/qualquer-coisa continua entrando sem conta, sem banco
e com o serviço de contas fora do ar.
Dar nome à sala. Dentro da sala, clique no nome dela no canto superior esquerdo. Quem batiza fica como dono, e passa a poder renomeá-la, escolher emoji, tirá-la do diretório e administrar os membros. Salas com nome aparecem na tela inicial junto das que têm gente agora.
Não existe sala privada: só por link tira do diretório e nada além disso.
Quem emite o token de entrada é uma função sem acesso ao banco, e um cadeado
que ela não consegue conferir seria um cadeado desenhado na porta — o
raciocínio inteiro está em PLANO.md §11.1.
Acrescentar um som à sala. Abra o painel de sons e largue um arquivo de áudio nele (ou use o botão de enviar). O nome do arquivo vira o rótulo. Exige conta, porque o arquivo fica guardado e precisa ter dono para alguém poder apagá-lo depois. Limite de 2 MB por arquivo e 120 sons por sala.
Os sons que ficam em apps/web/src/assets/sons/ continuam existindo e valem
para todas as salas — eles viajam dentro do bundle, funcionam sem conta e
sem rede, e é por isso que não foram substituídos.
O servidor decide o formato pelos bytes do arquivo, não pelo que o
navegador declara. Um .exe renomeado para .mp3 é recusado.
Em /painel, para contas com papel ADMIN. Tem quatro abas:
TTL do TiDB.Não há como ler senha nem entrar como outra pessoa por aqui — nem pela API. As
rotas de /api/admin são recusadas com 403 para conta comum no servidor,
com o guard aplicado no nível da classe: rota nova nasce restrita.
Não há tela para isso, de propósito: a primeira promoção tem que passar pelo banco. Duas formas.
Pelo seed, que é o caminho para uma instalação nova:
cp apps/api/prisma/seed.sample.mjs apps/api/prisma/seed.mjs
# edite o bloco DADOS: e-mail, nome e senha
pnpm --filter @telecord/api build # o seed usa o mesmo hash do login
DATABASE_URL="mysql://…" pnpm --filter @telecord/api prisma:seed
seed.mjs está no .gitignore junto com o .env, e pelo mesmo motivo: ele
carrega a senha da conta administradora. O que se versiona é
seed.sample.mjs — a FORMA do seed continua revisável, sem os valores de
ninguém. É idempotente: rodar de novo atualiza em vez de duplicar.
Numa conta que já existe, direto no banco:
UPDATE `User` SET `role` = 'ADMIN' WHERE `email` = '[email protected]';
Nos dois casos, entre de novo depois — o papel viaja dentro do access token, e
o que já estava emitido continua dizendo USER até vencer.
Rodam sozinhas no deploy, antes de publicar (job migrate do workflow):
publicar primeiro colocaria no ar um código que espera tabelas inexistentes.
Falha de migração trava o deploy de propósito.
À mão, quando precisar:
DATABASE_URL="mysql://…" pnpm --filter @telecord/api prisma:migrate
Estes dois indicadores vêm do SFU, não do navegador: aba que fecha sem avisar e número auto-declarado não servem para medir conversa. Configure em cloud.livekit.io → Project → Settings → Webhooks:
https://telecord.vercel.app/api/livekit/webhook
O serviço confere a assinatura do evento contra o LIVEKIT_API_SECRET que já
existe — nenhuma credencial nova. Sem o webhook, o painel mostra zero nesses
dois indicadores e avisa que não está medindo, em vez de fingir que
ninguém conversou.
Quem publica é o GitHub Actions, não a Vercel. O deploy automático dela está desligado em vercel.json (git.deploymentEnabled: false), e o pipeline está em .github/workflows/deploy.yml.
push na main
│
├─ verify ─── typecheck + build, sem nenhuma credencial
│
└─ deploy ─── scripts/vercel-deploy.mjs
· grava as credenciais no projeto da Vercel
· cria o deployment do commit exato e espera ficar READY
· verifica /api/token, rewrite de SPA e 404 de API
O passo de verificação no fim é o que impede um deploy "verde" mas quebrado: se /api/token não devolver 200, o job falha.
O deploy fala com a API REST da Vercel, não com a CLI, e sem nenhuma dependência além do fetch do Node. Isso não é preferência de estilo: a CLI resolve a conta antes de qualquer comando, então exige um token de conta. A API aceita também token com escopo de projeto (prefixo vcp_), que é o tipo gerado nas configurações do projeto.
A função lê LIVEKIT_API_KEY e LIVEKIT_API_SECRET de process.env, e esse ambiente é fornecido pela plataforma no momento da invocação. Deploy prebuilt não carrega .env para dentro do Lambda — nenhum arquivo do repositório vira ambiente de runtime. Por isso o workflow sincroniza os dois segredos no projeto (vercel env rm + vercel env add) antes de publicar: os valores vivem nos GitHub Secrets, e o dashboard da Vercel nunca precisa ser aberto para isso.
Os valores não passam pelo log — a saída dos comandos é descartada, e o Actions ainda mascara qualquer secret que apareça.
Gere um token da Vercel — serve tanto um token de conta (https://vercel.com/account/tokens) quanto um com escopo de projeto, gerado nas configurações do próprio projeto.
Cadastre 3 valores no GitHub Environment chamado Production (Settings → Environments → Production). O nome precisa bater com o environment: do workflow.
| Nome | Onde cadastrar | De onde vem |
|---|---|---|
VERCEL_TOKEN | secret | o token do passo 1 |
LIVEKIT_API_SECRET | secret | LiveKit Cloud → Settings → Keys (só aparece na criação) |
LIVEKIT_API_KEY | secret ou variable | idem |
Opcional: VITE_LIVEKIT_URL como variable, para sobrescrever o default versionado em config.ts.
Secret e variable não são a mesma coisa. Variables são texto plano: aparecem legíveis na tela de configuração e não são mascaradas nos logs do Actions. Credencial vai em Environment secrets. O workflow aceita as duas formas —
secretstem precedência evarsé o fallback —, e mascara à mão os valores sensíveis que chegarem como variable, mas isso é remendo: o lugar do token e do secret é em secrets.
orgIdeprojectIdnão precisam ser cadastrados: são identificadores, e estão versionados em .vercel/project.json — o mesmo arquivo quevercel linkgera. Com ele no repositório, a CLI já sabe em qual projeto está operando. Apontar para outro projeto é editar esse arquivo (ou rodarnpx vercel linkde novo).
Em Settings → General do projeto na Vercel, confira que Root Directory é a raiz (./) e o é 22.x. Se apontar para , o diretório não é detectado e a função de token não existe.
Depois disso, publicar é dar push na main — ou rodar o workflow à mão em Actions → Deploy → Run workflow.
O push que introduz
git.deploymentEnabled: falseainda pode disparar um último deploy automático, porque a Vercel lê essa configuração do commit que chegou. A partir do seguinte, ela para.
O workflow já faz isso, mas para checar fora dele:
curl -s -X POST https://SEU-APP.vercel.app/api/token \
-H 'Content-Type: application/json' \
-d '{"roomId":"teste-de-deploy","displayName":"Ana"}'
# 200 + { "token": "eyJ…", "identity": "…" } → key e secret OK
# 500 SERVER_MISCONFIGURED → os secrets não chegaram ao projeto
Se o token vier certo mas a sala não conectar, o problema é a URL do servidor: confira o default em config.ts (ou a VITE_LIVEKIT_URL, se você tiver definido uma). Como é valor de build, mudá-la exige um novo deploy.
A lista de salas ativas é pública. Qualquer visitante da página inicial vê o nome de toda sala com gente dentro e entra com um clique. Antes dela, a sala só era alcançável por quem tivesse a URL. Se isso não servir, as saídas são autenticação, senha por sala, ou listar só as salas que a pessoa já visitou — nenhuma está implementada. Ver §9 do SPEC.
Todo mundo entra mutado. O microfone só é publicado no primeiro clique em "Falar" — é aí que o navegador pede permissão.
Áudio de aba só existe em Chromium no desktop. Firefox e Safari não entregam áudio no getDisplayMedia.
iOS não compartilha tela (getDisplayMedia não existe lá): dá para ouvir e falar, não para apresentar.
Banda. Com 20 pessoas e a tela em 1080p@15fps, o SFU envia ~26 GB por hora de reunião. Se a cota do plano free apertar, troque o preset para h720fps15 em apps/web/src/lib/media.ts.
Sons do soundboard ficam em apps/web/src/assets/sons/. Para acrescentar um som, largue o arquivo na pasta e faça o deploy: apps/web/src/lib/sounds.ts monta o catálogo a partir dela, então não há lista para editar. O nome do arquivo vira o rótulo (gemidao-do-zap.mp3 → "Gemidao do zap") e o identificador que trafega pela sala. O som não trafega pela sala: cada cliente toca o próprio arquivo ao receber o aviso, então todo mundo precisa estar na mesma versão do app.
Várias pessoas podem compartilhar tela ao mesmo tempo. Cada tela extra multiplica o egress do SFU — com o preset atual, duas telas já dobram a conta de banda acima.
Trocar de servidor LiveKit (Cloud → self-host) é mudar VITE_LIVEKIT_URL e as credenciais, e refazer o deploy. O código não muda.
apps/webapi/