desktop: validar token no startup + fallback onboarding; corrigir openSystem e pós-registro
This commit is contained in:
parent
12cbf564a7
commit
f2e25dfe4d
1 changed files with 116 additions and 3 deletions
|
|
@ -181,13 +181,76 @@ function App() {
|
||||||
const p = await invoke<MachineProfile>("collect_machine_profile")
|
const p = await invoke<MachineProfile>("collect_machine_profile")
|
||||||
setProfile(p)
|
setProfile(p)
|
||||||
}
|
}
|
||||||
setStatus(t ? "online" : null)
|
// Não assume online sem validar; valida abaixo em outro efeito
|
||||||
} catch {
|
} catch {
|
||||||
setError("Falha ao carregar estado do agente.")
|
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<MachineProfile>("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(() => {
|
useEffect(() => {
|
||||||
if (!import.meta.env.DEV) return
|
if (!import.meta.env.DEV) return
|
||||||
|
|
||||||
|
|
@ -403,6 +466,21 @@ function App() {
|
||||||
intervalSeconds: 300,
|
intervalSeconds: 300,
|
||||||
})
|
})
|
||||||
setStatus("online")
|
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) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : String(err))
|
setError(err instanceof Error ? err.message : String(err))
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -415,12 +493,46 @@ function App() {
|
||||||
setIsLaunchingSystem(true)
|
setIsLaunchingSystem(true)
|
||||||
try {
|
try {
|
||||||
// Tenta criar a sessão via API (evita dependência de redirecionamento + cookies em 3xx)
|
// 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",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ machineToken: token, rememberMe: true }),
|
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<MachineProfile>("collect_machine_profile")
|
||||||
|
setProfile(p)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignora e segue para handshake
|
||||||
|
}
|
||||||
|
}
|
||||||
// Independente do resultado do POST, seguimos para o handshake em
|
// Independente do resultado do POST, seguimos para o handshake em
|
||||||
// navegação de primeiro plano para garantir gravação de cookies.
|
// navegação de primeiro plano para garantir gravação de cookies.
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -535,10 +647,11 @@ function App() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!token) return
|
if (!token) return
|
||||||
if (autoLaunchRef.current) return
|
if (autoLaunchRef.current) return
|
||||||
|
if (!tokenVerifiedRef.current) return
|
||||||
autoLaunchRef.current = true
|
autoLaunchRef.current = true
|
||||||
setIsLaunchingSystem(true)
|
setIsLaunchingSystem(true)
|
||||||
openSystem()
|
openSystem()
|
||||||
}, [token, config?.accessRole, openSystem])
|
}, [token, status, config?.accessRole, openSystem])
|
||||||
|
|
||||||
if (isLaunchingSystem && token) {
|
if (isLaunchingSystem && token) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue