diff --git a/apps/desktop/src/main.tsx b/apps/desktop/src/main.tsx index e274a16..3efdf29 100644 --- a/apps/desktop/src/main.tsx +++ b/apps/desktop/src/main.tsx @@ -181,13 +181,76 @@ function App() { const p = await invoke("collect_machine_profile") setProfile(p) } - setStatus(t ? "online" : null) + // Não assume online sem validar; valida abaixo em outro efeito } catch { setError("Falha ao carregar estado do agente.") } })() }, []) + // Valida token existente ao iniciar o app. Se inválido/expirado, limpa e volta ao onboarding. + useEffect(() => { + if (!store || !token) return + let cancelled = false + ;(async () => { + setIsValidatingToken(true) + try { + const res = await fetch(`${apiBaseUrl}/api/machines/heartbeat`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ machineToken: token, status: "online" }), + }) + if (cancelled) return + if (res.ok) { + tokenVerifiedRef.current = true + setStatus("online") + try { + await invoke("start_machine_agent", { + baseUrl: apiBaseUrl, + token, + status: "online", + intervalSeconds: 300, + }) + } catch (err) { + console.error("Falha ao iniciar heartbeat em segundo plano", err) + } + return + } + const text = await res.text() + const msg = text.toLowerCase() + const isInvalid = + msg.includes("token de máquina inválido") || + msg.includes("token de máquina revogado") || + msg.includes("token de máquina expirado") + if (isInvalid) { + try { + await store.delete("token"); await store.delete("config"); await store.save() + } catch {} + autoLaunchRef.current = false + tokenVerifiedRef.current = false + setToken(null) + setConfig(null) + setStatus(null) + setError("Este dispositivo precisa ser reprovisionado. Informe o código de provisionamento.") + try { + const p = await invoke("collect_machine_profile") + if (!cancelled) setProfile(p) + } catch {} + } else { + // Não limpa token em falhas genéricas (ex.: rede); apenas informa + setError("Falha ao validar sessão da máquina. Tente novamente.") + } + } catch (err) { + if (!cancelled) console.error("Falha ao validar token (rede)", err) + } finally { + if (!cancelled) setIsValidatingToken(false) + } + })() + return () => { + cancelled = true + } + }, [store, token]) + useEffect(() => { if (!import.meta.env.DEV) return @@ -403,6 +466,21 @@ function App() { intervalSeconds: 300, }) setStatus("online") + tokenVerifiedRef.current = true + + // Abre o sistema imediatamente após registrar (evita ficar com token inválido no fluxo antigo) + try { + await fetch(`${apiBaseUrl}/api/machines/sessions`, { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ machineToken: data.machineToken, rememberMe: true }), + }) + } catch {} + const persona = (cfg.accessRole ?? "collaborator") === "manager" ? "manager" : "collaborator" + const redirectTarget = persona === "manager" ? "/dashboard" : "/portal/tickets" + const url = `${resolvedAppUrl}/machines/handshake?token=${encodeURIComponent(data.machineToken)}&redirect=${encodeURIComponent(redirectTarget)}` + window.location.href = url } catch (err) { setError(err instanceof Error ? err.message : String(err)) } finally { @@ -415,12 +493,46 @@ function App() { setIsLaunchingSystem(true) try { // Tenta criar a sessão via API (evita dependência de redirecionamento + cookies em 3xx) - await fetch(`${apiBaseUrl}/api/machines/sessions`, { + const res = await fetch(`${apiBaseUrl}/api/machines/sessions`, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ machineToken: token, rememberMe: true }), }) + if (!res.ok) { + // Se sessão falhar, tenta identificar token inválido/expirado + try { + const hb = await fetch(`${apiBaseUrl}/api/machines/heartbeat`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ machineToken: token }), + }) + if (!hb.ok) { + const text = await hb.text() + const low = text.toLowerCase() + const invalid = + low.includes("token de máquina inválido") || + low.includes("token de máquina revogado") || + low.includes("token de máquina expirado") + if (invalid) { + // Força onboarding + await store?.delete("token"); await store?.delete("config"); await store?.save() + autoLaunchRef.current = false + tokenVerifiedRef.current = false + setToken(null) + setConfig(null) + setStatus(null) + setError("Sessão expirada. Reprovisione a máquina para continuar.") + setIsLaunchingSystem(false) + const p = await invoke("collect_machine_profile") + setProfile(p) + return + } + } + } catch { + // ignora e segue para handshake + } + } // Independente do resultado do POST, seguimos para o handshake em // navegação de primeiro plano para garantir gravação de cookies. } catch { @@ -535,10 +647,11 @@ function App() { useEffect(() => { if (!token) return if (autoLaunchRef.current) return + if (!tokenVerifiedRef.current) return autoLaunchRef.current = true setIsLaunchingSystem(true) openSystem() - }, [token, config?.accessRole, openSystem]) + }, [token, status, config?.accessRole, openSystem]) if (isLaunchingSystem && token) { return (