Skip to content

App Desktop (Electron)

O PopinaFlow Desktop é um instalador nativo (.exe / .dmg / .AppImage) que embute o NestJS, o MongoDB e o frontend num único executável — sem necessidade de Docker, Node.js ou configuração de servidor. Ideal para restaurantes que precisam de operação offline completa com acesso de outros dispositivos na rede local.

Arquitetura

Electron (modo servidor)
├── MongoDB           → localhost:27018  (interno, mongodb-memory-server)
├── NestJS            → localhost:3001   (interno, processo filho via fork)
└── HTTP Proxy        → 0.0.0.0:3000    (visível na LAN)
    ├── GET /*            serve frontend/dist (arquivos estáticos + SPA fallback)
    ├── /api/*            proxy → localhost:3001 (reescreve Origin header)
    └── WebSocket         proxy upgrade → localhost:3001

Janela Electron       → electron-serve (https://localhost), apiUrl direto em localhost:3001
Browsers na LAN       → http://192.168.x.x:3000 (VITE_API_URL=/api relativo via proxy)

Portas

ServiçoPortaVisibilidade
MongoDB27018localhost apenas
NestJS3001localhost apenas
HTTP Proxy30000.0.0.0 — toda a LAN

O NestJS nunca é exposto diretamente na LAN. Todo o tráfego externo passa pelo proxy na porta 3000, que também serve os arquivos estáticos do frontend.

Por que o proxy reescreve o header Origin?

O NestJS tem CORS configurado para aceitar https://localhost (Electron renderer). Browsers na LAN enviam Origin: http://192.168.x.x:3000. O proxy intercepta e substitui por Origin: https://localhost antes de repassar ao NestJS — sem nenhuma mudança no backend.

Modos de operação

O app inicia na tela de Configuração inicial e pede para escolher um modo. A escolha fica salva em userData/mode-config.json.

Modo Servidor

  • Inicia MongoDB + NestJS + proxy
  • Exibe o IP da LAN, um QR code e links copiáveis para clientes e garçons
  • Outros dispositivos na mesma rede Wi-Fi acessam via browser em http://IP:3000
  • Outros computadores com o app instalado podem conectar como modo cliente

Modo Cliente (terminal)

  • O computador não executa servidor local
  • O usuário informa o IP do computador que está no modo servidor
  • O app faz health check em http://IP:3000/api/health antes de aceitar o IP
  • Útil para caixas ou tablets de garçom usando o app Electron

Armazenamento de dados

ItemLocalização
Dados do MongoDBuserData/mongodb-data/ (wiredTiger, persistente)
JWT SecretuserData/.jwt-secret (gerado na 1ª execução, persistido)
Modo escolhidouserData/mode-config.json

userData resolve para:

  • Windows: %APPDATA%\PopinaFlow
  • macOS: ~/Library/Application Support/PopinaFlow
  • Linux: ~/.config/PopinaFlow

Binários do MongoDB embutidos

O instalador inclui binários pré-compilados do MongoDB 7.0.14:

PlataformaArquivo
Windows x64resources/mongodb-binaries/mongod-x64-windows-7.0.14.exe
macOS x64resources/mongodb-binaries/mongod-x64-macos-7.0.14
Linux x64resources/mongodb-binaries/mongod-x64-ubuntu-7.0.14

Em desenvolvimento (sem app.isPackaged), o mongodb-memory-server faz auto-download do binário na primeira execução.

Frontend no modo Electron

O frontend é buildado com VITE_API_URL=/api (URL relativa):

bash
cd frontend-react && vite build --mode electron

Isso usa frontend-react/.env.electron que define VITE_API_URL=/api. Assim:

  • Janela Electron: usa window.electron.apiUrl = http://localhost:3001/api (tem precedência sobre VITE_API_URL em api.ts)
  • Browsers na LAN: window.electron é null, usam VITE_API_URL=/api → resolves para http://192.168.x.x:3000/api → proxy → NestJS

Build de distribuição

bash
cd electron

# Todas as plataformas (requer ambiente adequado)
npm run dist

# Por plataforma
npm run dist:win     # Windows NSIS installer (.exe)
npm run dist:mac     # macOS DMG (.dmg)
npm run dist:linux   # AppImage + .deb

O electron-builder.yml configura o que vai dentro do instalador:

resources/
├── dist/           ← frontend-react/dist (buildado com --mode electron)
├── backend/        ← backend compilado com ncc (bundle único index.js)
└── mongodb-binaries/ ← binários mongod por plataforma

Desenvolvimento local

bash
# 1. Build do backend (necessário antes do Electron em dev)
cd backend && npm run build

# 2. Build do frontend para Electron
cd frontend-react && vite build --mode electron

# 3. Iniciar o app
cd electron && npm start

Em modo dev (!app.isPackaged):

  • Backend: ../backend/dist/main.js
  • Frontend dist: ../frontend-react/dist
  • MongoDB: auto-download na 1ª execução

IPC disponível no renderer

Exposto via window.electron (contextBridge em preload.js):

MétodoDescrição
isElectrontrue — permite ao frontend detectar o ambiente
apiUrlURL base da API (definida pelo main antes de criar a janela)
wsUrlURL base para WebSocket
getMode()Retorna { mode, serverIp } — modo atual salvo
startServerMode()Inicia MongoDB + NestJS + proxy; resolve com { apiUrl, wsUrl, lanIPs, port }
startClientMode(ip)Valida conectividade e salva modo cliente; resolve com { apiUrl, wsUrl }
resetMode()Limpa o modo salvo e recarrega a janela
onSetupLog(cb)Recebe logs de texto durante o startup (retorna função de unsubscribe)
generateQr(text)Gera QR code como data:image/png;base64,... via Node no processo principal

Fallback de porta

Se a porta 3000 estiver ocupada, startProxyServer() tenta 3001, 3002... até encontrar uma disponível. O port real é retornado em startServerMode() e exibido na UI.

CSP (Content Security Policy)

Em modo local, o CSP permite conexões para:

http://localhost:3001   ws://localhost:3001    ← janela Electron (direto)
http://192.168.0.0/8    ws://192.168.0.0/8    ← subnet típica de LAN

Em modo cloud, apenas https://popinaflow.com, https://api.popinaflow.com e WSS equivalentes.

Comparação: Electron vs Docker

AspectoElectronDocker
Pré-requisitoNenhum (instalador standalone)Docker Desktop instalado
Setup inicial< 1 minuto5–10 minutos (download de imagens)
AtualizaçãoNovo instaladorAtualizar-Servidor.bat
Acesso LANhttp://IP:3000 (automático)http://IP (porta 80)
QR codeGerado automaticamente no appManual
Sincronização cloudSYNC_ENABLED=false (offline puro)Sync a cada 5 min
Memória estimada~400 MB~800 MB (3 containers)
Indicado paraRestaurante único, sem ITMulti-unidade, com sync cloud

Lançado sob a licença MIT.