From 21b91b3522d46e7e0fc06234836f4ae4c7445d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?PontualTech=20/=20Karl=C3=A3o?= Date: Tue, 28 Apr 2026 06:51:35 -0300 Subject: [PATCH] feat(sync): WebSocket realtime + auto-push/pull entre PC e celular v1.5.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend (server/src/realtime.js): - WebSocket server em /ws via lib `ws` - Auth por JWT ou BOAT_TOKEN (mesmo middleware do REST) - Broadcast de notificações state:changed por user (skip device origem) - Heartbeat ping/pong + cleanup de conexões mortas - Presença: avisa todos os devices do user quantos estão online - POST /api/data agora dispara broadcast pra outros devices em tempo real Frontend (app/diario-bordo.html): - Cliente WS com reconnect exponencial (1s→2s→5s→15s→30s→60s) - deviceId persistente em localStorage (gerado no primeiro boot) - Heartbeat 25s pra manter NAT/proxy abertos - Auto-push debounced 2.5s no saveState (acumula edições rápidas) - Auto-pull debounced 300ms no recebimento de state:changed - Reconnect ao voltar pro foreground + ao recuperar conexão - Indicador visual no header: 🟢 online · 🟡 syncing · 🔴 offline · ⚫ disabled · ⚠️ erro Echo prevention em 3 camadas: 1) Server skip por originDeviceId (header X-Device-Id) 2) Cliente ignora notif do próprio device 3) Guard temporal: pull rejeita se updated_at < lastPushAt Co-Authored-By: Claude Opus 4.7 (1M context) --- app/diario-bordo.html | 214 +++++++++++++++++++++++++++++++- mobile/android/app/build.gradle | 4 +- mobile/package.json | 2 +- server/package-lock.json | 22 ++++ server/package.json | 1 + server/public/index.html | 214 +++++++++++++++++++++++++++++++- server/src/index.js | 14 ++- server/src/realtime.js | 126 +++++++++++++++++++ 8 files changed, 581 insertions(+), 16 deletions(-) create mode 100644 server/src/realtime.js diff --git a/app/diario-bordo.html b/app/diario-bordo.html index 1da2e13..cf56ff6 100644 --- a/app/diario-bordo.html +++ b/app/diario-bordo.html @@ -528,6 +528,15 @@ header{ .fleet-units-toggle button.active{background:var(--brass);color:var(--bg-paper)} .fleet-units-toggle button:hover:not(.active){background:var(--bg-aged)} +/* === Sync Indicator === */ +.sync-indicator{ + display:inline-block;font-size:10px; + margin-left:6px;cursor:help; + vertical-align:1px; +} +.sync-indicator[data-status="syncing"]{animation:syncPulse 1.2s infinite} +@keyframes syncPulse{0%,100%{opacity:1}50%{opacity:.4}} + /* === Boat Photo === */ .boat-photo-row{display:flex;gap:14px;align-items:flex-start} .boat-photo-preview{ @@ -1368,7 +1377,7 @@ header{
-
Diário de Bordo · Logbook
+
Diário de Bordo · Logbook