diff --git a/.env.example b/.env.example index aa7b291..3bd8cbc 100644 --- a/.env.example +++ b/.env.example @@ -12,8 +12,6 @@ NEXT_PUBLIC_CONVEX_URL=http://127.0.0.1:3210 CONVEX_INTERNAL_URL=http://127.0.0.1:3210 # Intervalo (ms) para aceitar token revogado ao sincronizar acessos remotos (opcional) REMOTE_ACCESS_TOKEN_GRACE_MS=900000 -# Porta do servidor WebSocket de chat (processo dedicado iniciado no container) -CHAT_WS_PORT=3030 # SQLite database (local dev) DATABASE_URL=file:./prisma/db.dev.sqlite diff --git a/apps/desktop/src/chat/convexMachineClient.ts b/apps/desktop/src/chat/convexMachineClient.ts index 991c4fe..7ca9bbe 100644 --- a/apps/desktop/src/chat/convexMachineClient.ts +++ b/apps/desktop/src/chat/convexMachineClient.ts @@ -180,8 +180,3 @@ export async function uploadToConvexStorage(uploadUrl: string, file: Blob | Arra const json = await response.json().catch(() => ({})) return json.storageId || json.storage_id } -const FN_CHECK_UPDATES = "liveChat.checkMachineUpdates" -const FN_LIST_MESSAGES = "liveChat.listMachineMessages" -const FN_POST_MESSAGE = "liveChat.postMachineMessage" -const FN_MARK_READ = "liveChat.markMachineMessagesRead" -const FN_UPLOAD_URL = "liveChat.generateMachineUploadUrl" diff --git a/bun.lock b/bun.lock index b31f37e..f3c7b2d 100644 --- a/bun.lock +++ b/bun.lock @@ -64,7 +64,6 @@ "tippy.js": "^6.3.7", "unicornstudio-react": "^1.4.31", "vaul": "^1.1.2", - "ws": "^8.18.0", "zod": "^4.1.9", }, "devDependencies": { diff --git a/package.json b/package.json index 4f49116..f68ddac 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,6 @@ "three": "0.181.2", "tippy.js": "^6.3.7", "unicornstudio-react": "^1.4.31", - "ws": "^8.18.0", "vaul": "^1.1.2", "zod": "^4.1.9" }, diff --git a/scripts/chat-ws-server.mjs b/scripts/chat-ws-server.mjs deleted file mode 100644 index 0b014ba..0000000 --- a/scripts/chat-ws-server.mjs +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env node -/** - * Servidor WebSocket dedicado para notificações de chat (máquinas). - * - * Por enquanto ele replica a lógica de streaming via SSE/poll: - * - autentica via machineToken (query ?token=) - * - consulta checkMachineUpdates a cada 1s - * - envia eventos "connected", "update" e "heartbeat" - * - fecha em caso de erro de autenticação - * - * Isso permite remover SSE/poll no cliente, mantendo compatibilidade com o - * backend Convex existente. - */ -import { WebSocketServer } from "ws" -import { ConvexHttpClient } from "convex/browser" -import { api } from "../convex/_generated/api.js" - -const PORT = Number(process.env.CHAT_WS_PORT ?? process.env.PORT_WS ?? 3030) -const POLL_MS = Number(process.env.CHAT_WS_POLL_MS ?? 1000) -const HEARTBEAT_MS = Number(process.env.CHAT_WS_HEARTBEAT_MS ?? 30000) - -const convexUrl = - process.env.CONVEX_INTERNAL_URL ?? - process.env.NEXT_PUBLIC_CONVEX_URL ?? - process.env.CONVEX_URL ?? - null - -if (!convexUrl) { - console.error("[chat-ws] ERRO: defina CONVEX_INTERNAL_URL ou NEXT_PUBLIC_CONVEX_URL") - process.exit(1) -} - -const wss = new WebSocketServer({ port: PORT }) -console.log(`[chat-ws] Servidor WebSocket iniciado na porta ${PORT}`) - -function buildClient() { - return new ConvexHttpClient(convexUrl) -} - -function parseToken(urlString) { - try { - const url = new URL(urlString, "http://localhost") - return url.searchParams.get("token") - } catch { - return null - } -} - -wss.on("connection", (ws, req) => { - const token = parseToken(req.url ?? "") - if (!token) { - ws.close(1008, "Missing token") - return - } - - const client = buildClient() - let previousState = null - let closed = false - - const send = (event, data) => { - if (ws.readyState === ws.OPEN) { - ws.send(JSON.stringify({ event, data })) - } - } - - // Heartbeat - const heartbeat = setInterval(() => { - if (closed) return - send("heartbeat", { ts: Date.now() }) - }, HEARTBEAT_MS) - - // Poll - const poll = setInterval(async () => { - if (closed) return - try { - const result = await client.query(api.liveChat.checkMachineUpdates, { - machineToken: token, - }) - - const currentState = JSON.stringify({ - hasActiveSessions: result.hasActiveSessions, - totalUnread: result.totalUnread, - sessions: result.sessions, - }) - - if (currentState !== previousState) { - previousState = currentState - send("update", { ...result, ts: Date.now() }) - } - } catch (error) { - console.error("[chat-ws] Poll error:", error?.message ?? error) - send("error", { message: "Poll failed" }) - ws.close(1011, "Poll failed") - } - }, POLL_MS) - - // Primeira validação + evento inicial - client - .query(api.liveChat.checkMachineUpdates, { machineToken: token }) - .then((result) => { - previousState = JSON.stringify({ - hasActiveSessions: result.hasActiveSessions, - totalUnread: result.totalUnread, - sessions: result.sessions, - }) - send("connected", { ts: Date.now(), ...result }) - }) - .catch((error) => { - console.error("[chat-ws] Token inválido:", error?.message ?? error) - send("error", { message: "Token inválido" }) - ws.close(1008, "Invalid token") - }) - - ws.on("close", () => { - closed = true - clearInterval(poll) - clearInterval(heartbeat) - }) - - ws.on("error", (err) => { - console.error("[chat-ws] WS erro:", err?.message ?? err) - closed = true - clearInterval(poll) - clearInterval(heartbeat) - }) -}) - -wss.on("error", (err) => { - console.error("[chat-ws] Erro no servidor:", err?.message ?? err) -}) diff --git a/scripts/start-web.sh b/scripts/start-web.sh index 2870b96..ccf9616 100644 --- a/scripts/start-web.sh +++ b/scripts/start-web.sh @@ -16,7 +16,6 @@ echo "[start-web] Using bun cache dir: $BUN_INSTALL_CACHE_DIR" echo "[start-web] Using APP_DIR=$(pwd)" echo "[start-web] NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-}" echo "[start-web] NEXT_PUBLIC_CONVEX_URL=${NEXT_PUBLIC_CONVEX_URL:-}" -echo "[start-web] CHAT_WS_PORT=${CHAT_WS_PORT:-3030}" ensure_db_writable() { mkdir -p "$(dirname "$DB_PATH")" @@ -204,19 +203,6 @@ else echo "[start-web] skipping auth seed (SKIP_AUTH_SEED=true)" fi -# Iniciar servidor WebSocket de chat (processo dedicado) -CHAT_WS_PORT="${CHAT_WS_PORT:-3030}" -CHAT_WS_SCRIPT="/app/scripts/chat-ws-server.mjs" -if [ -f "$CHAT_WS_SCRIPT" ]; then - echo "[start-web] iniciando chat-ws-server em :$CHAT_WS_PORT" - node "$CHAT_WS_SCRIPT" & - CHAT_WS_PID=$! - # Garantir cleanup - trap "kill $CHAT_WS_PID 2>/dev/null || true" EXIT -else - echo "[start-web] chat-ws-server não encontrado em $CHAT_WS_SCRIPT" >&2 -fi - echo "[start-web] launching Next.js" PORT=${PORT:-3000} NODE_MAJOR=$(command -v node >/dev/null 2>&1 && node -v | sed -E 's/^v([0-9]+).*/\1/' || echo "") diff --git a/stack.yml b/stack.yml index 4e332a8..a861f6d 100644 --- a/stack.yml +++ b/stack.yml @@ -29,7 +29,6 @@ services: BETTER_AUTH_SECRET: "${BETTER_AUTH_SECRET}" REPORTS_CRON_SECRET: "${REPORTS_CRON_SECRET}" REPORTS_CRON_BASE_URL: "${REPORTS_CRON_BASE_URL}" - CHAT_WS_PORT: "${CHAT_WS_PORT:-3030}" # Mantém o SQLite fora do repositório DATABASE_URL: "file:/app/data/db.sqlite" # Evita apt-get na inicialização porque a imagem já vem com toolchain pronta @@ -68,13 +67,6 @@ services: - traefik.http.routers.sistema_web.tls=true - traefik.http.routers.sistema_web.tls.certresolver=le - traefik.http.services.sistema_web.loadbalancer.server.port=3000 - # Roteador dedicado para WebSocket do chat - - traefik.http.routers.sistema_web_ws.rule=Host(`tickets.esdrasrenan.com.br`) && PathPrefix(`/chat-ws`) - - traefik.http.routers.sistema_web_ws.entrypoints=websecure - - traefik.http.routers.sistema_web_ws.tls=true - - traefik.http.routers.sistema_web_ws.tls.certresolver=le - - traefik.http.routers.sistema_web_ws.service=sistema_web_ws - - traefik.http.services.sistema_web_ws.loadbalancer.server.port=3030 networks: - traefik_public healthcheck: