feat: event-driven rustdesk sync

This commit is contained in:
Esdras Renan 2025-11-11 20:26:29 -03:00
parent e410a4874c
commit e0bb6bb80f
4 changed files with 263 additions and 96 deletions

View file

@ -9,6 +9,10 @@ VITE_APP_URL=http://localhost:3000
# Se não definir, cai no mesmo valor de VITE_APP_URL # Se não definir, cai no mesmo valor de VITE_APP_URL
VITE_API_BASE_URL= VITE_API_BASE_URL=
# RustDesk provisioning (opcionais; se vazios, o app usa o TOML padrão embutido)
VITE_RUSTDESK_CONFIG_STRING=
VITE_RUSTDESK_DEFAULT_PASSWORD=
# Opcional: IP do host para desenvolvimento com HMR fora do localhost # Opcional: IP do host para desenvolvimento com HMR fora do localhost
# Ex.: 192.168.0.10 # Ex.: 192.168.0.10
TAURI_DEV_HOST= TAURI_DEV_HOST=

View file

@ -3,6 +3,7 @@ mod agent;
mod rustdesk; mod rustdesk;
use agent::{collect_inventory_plain, collect_profile, AgentRuntime, MachineProfile}; use agent::{collect_inventory_plain, collect_profile, AgentRuntime, MachineProfile};
use tauri::Emitter;
use tauri_plugin_store::Builder as StorePluginBuilder; use tauri_plugin_store::Builder as StorePluginBuilder;
#[derive(Debug, serde::Serialize)] #[derive(Debug, serde::Serialize)]
@ -12,6 +13,7 @@ pub struct RustdeskProvisioningResult {
pub password: String, pub password: String,
pub installed_version: Option<String>, pub installed_version: Option<String>,
pub updated: bool, pub updated: bool,
pub last_provisioned_at: i64,
} }
#[tauri::command] #[tauri::command]
@ -50,22 +52,45 @@ fn open_devtools(window: tauri::WebviewWindow) -> Result<(), String> {
} }
#[tauri::command] #[tauri::command]
async fn provision_rustdesk(machine_id: Option<String>) -> Result<RustdeskProvisioningResult, String> { async fn ensure_rustdesk_and_emit(
let machine_id = machine_id app: tauri::AppHandle,
.filter(|value| !value.trim().is_empty()) config_string: Option<String>,
.ok_or_else(|| "Informe o identificador da máquina para provisionar o RustDesk.".to_string())?; password: Option<String>,
tauri::async_runtime::spawn_blocking(move || run_rustdesk_provision(machine_id)) machine_id: Option<String>,
) -> Result<RustdeskProvisioningResult, String> {
let result = tauri::async_runtime::spawn_blocking(move || {
run_rustdesk_ensure(config_string, password, machine_id)
})
.await .await
.map_err(|error| error.to_string())? .map_err(|error| error.to_string())??;
if let Err(error) = app.emit("raven://remote-access/provisioned", &result) {
eprintln!("[rustdesk] falha ao emitir evento raven://remote-access/provisioned: {error}");
}
Ok(result)
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn run_rustdesk_provision(machine_id: String) -> Result<RustdeskProvisioningResult, String> { fn run_rustdesk_ensure(
rustdesk::provision(&machine_id).map_err(|error| error.to_string()) config_string: Option<String>,
password: Option<String>,
machine_id: Option<String>,
) -> Result<RustdeskProvisioningResult, String> {
rustdesk::ensure_rustdesk(
config_string.as_deref(),
password.as_deref(),
machine_id.as_deref(),
)
.map_err(|error| error.to_string())
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
fn run_rustdesk_provision(_machine_id: String) -> Result<RustdeskProvisioningResult, String> { fn run_rustdesk_ensure(
_config_string: Option<String>,
_password: Option<String>,
_machine_id: Option<String>,
) -> Result<RustdeskProvisioningResult, String> {
Err("Provisionamento automático do RustDesk está disponível apenas no Windows.".to_string()) Err("Provisionamento automático do RustDesk está disponível apenas no Windows.".to_string())
} }
@ -83,7 +108,7 @@ pub fn run() {
start_machine_agent, start_machine_agent,
stop_machine_agent, stop_machine_agent,
open_devtools, open_devtools,
provision_rustdesk ensure_rustdesk_and_emit
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

View file

@ -1,7 +1,7 @@
#![cfg(target_os = "windows")] #![cfg(target_os = "windows")]
use crate::RustdeskProvisioningResult; use crate::RustdeskProvisioningResult;
use chrono::Local; use chrono::{Local, Utc};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use parking_lot::Mutex; use parking_lot::Mutex;
use reqwest::blocking::Client; use reqwest::blocking::Client;
@ -53,9 +53,13 @@ struct ReleaseResponse {
assets: Vec<ReleaseAsset>, assets: Vec<ReleaseAsset>,
} }
pub fn provision(machine_id: &str) -> Result<RustdeskProvisioningResult, RustdeskError> { pub fn ensure_rustdesk(
config_string: Option<&str>,
password_override: Option<&str>,
machine_id: Option<&str>,
) -> Result<RustdeskProvisioningResult, RustdeskError> {
let _guard = PROVISION_MUTEX.lock(); let _guard = PROVISION_MUTEX.lock();
log_event("Iniciando provisionamento do RustDesk"); log_event("Iniciando preparo do RustDesk");
let exe_path = detect_executable_path(); let exe_path = detect_executable_path();
let (installed_version, freshly_installed) = ensure_installed(&exe_path)?; let (installed_version, freshly_installed) = ensure_installed(&exe_path)?;
@ -65,6 +69,16 @@ pub fn provision(machine_id: &str) -> Result<RustdeskProvisioningResult, Rustdes
"RustDesk já instalado, usando binário existente" "RustDesk já instalado, usando binário existente"
}); });
if let Some(value) = config_string.and_then(|raw| {
let trimmed = raw.trim();
if trimmed.is_empty() { None } else { Some(trimmed) }
}) {
if let Err(error) = run_with_args(&exe_path, &["--config", value]) {
log_event(&format!("Falha ao aplicar configuração inline: {error}"));
} else {
log_event("Configuração aplicada via --config");
}
} else {
let config_path = write_config_files()?; let config_path = write_config_files()?;
log_event(&format!( log_event(&format!(
"Arquivo de configuração atualizado em {}", "Arquivo de configuração atualizado em {}",
@ -76,15 +90,36 @@ pub fn provision(machine_id: &str) -> Result<RustdeskProvisioningResult, Rustdes
} else { } else {
log_event("Configuração aplicada via CLI"); log_event("Configuração aplicada via CLI");
} }
}
if let Err(error) = set_password(&exe_path) { let password = password_override
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
.unwrap_or_else(|| DEFAULT_PASSWORD.to_string());
if let Err(error) = set_password(&exe_path, &password) {
log_event(&format!("Falha ao definir senha padrão: {error}")); log_event(&format!("Falha ao definir senha padrão: {error}"));
} else { } else {
log_event("Senha padrão definida com sucesso"); log_event("Senha padrão definida com sucesso");
} }
let custom_id = set_custom_id(&exe_path, machine_id)?; let custom_id = if let Some(value) = machine_id.and_then(|raw| {
log_event(&format!("ID determinístico definido: {custom_id}")); let trimmed = raw.trim();
if trimmed.is_empty() { None } else { Some(trimmed) }
}) {
match set_custom_id(&exe_path, value) {
Ok(custom) => {
log_event(&format!("ID determinístico definido: {custom}"));
Some(custom)
}
Err(error) => {
log_event(&format!("Falha ao definir ID determinístico: {error}"));
None
}
}
} else {
None
};
if let Err(error) = ensure_service_running() { if let Err(error) = ensure_service_running() {
log_event(&format!("Falha ao reiniciar serviço do RustDesk: {error}")); log_event(&format!("Falha ao reiniciar serviço do RustDesk: {error}"));
@ -92,31 +127,37 @@ pub fn provision(machine_id: &str) -> Result<RustdeskProvisioningResult, Rustdes
log_event("Serviço RustDesk reiniciado/run ativo"); log_event("Serviço RustDesk reiniciado/run ativo");
} }
let reported_id = query_id(&exe_path) let reported_id = match query_id_with_retries(&exe_path, 5) {
.ok() Ok(value) => value,
.filter(|value| !value.is_empty()) Err(error) => {
.or_else(|| { log_event(&format!("Falha ao obter ID após múltiplas tentativas: {error}"));
let fallback = read_remote_id_from_profiles(); match read_remote_id_from_profiles().or_else(|| custom_id.clone()) {
if let Some(value) = &fallback { Some(value) => {
log_event(&format!("ID obtido via arquivos de perfil: {value}")); log_event(&format!("ID obtido via arquivos de perfil: {value}"));
value
} }
fallback None => return Err(error),
}) }
.unwrap_or_else(|| custom_id.clone()); }
if reported_id != custom_id { };
if let Some(expected) = custom_id.as_ref() {
if expected != &reported_id {
log_event(&format!( log_event(&format!(
"ID retornado difere do determinístico ({custom_id}) -> aplicando {reported_id}" "ID retornado difere do determinístico ({expected}) -> aplicando {reported_id}"
)); ));
} }
}
ensure_remote_id_files(&reported_id); ensure_remote_id_files(&reported_id);
let version = query_version(&exe_path).ok().or(installed_version); let version = query_version(&exe_path).ok().or(installed_version);
let result = RustdeskProvisioningResult { let result = RustdeskProvisioningResult {
id: reported_id.clone(), id: reported_id.clone(),
password: DEFAULT_PASSWORD.to_string(), password: password.clone(),
installed_version: version.clone(), installed_version: version.clone(),
updated: freshly_installed, updated: freshly_installed,
last_provisioned_at: Utc::now().timestamp_millis(),
}; };
log_event(&format!("Provisionamento concluído. ID final: {reported_id}. Versão: {:?}", version)); log_event(&format!("Provisionamento concluído. ID final: {reported_id}. Versão: {:?}", version));
@ -266,8 +307,8 @@ fn apply_config(exe_path: &Path, config_path: &Path) -> Result<(), RustdeskError
Ok(()) Ok(())
} }
fn set_password(exe_path: &Path) -> Result<(), RustdeskError> { fn set_password(exe_path: &Path, secret: &str) -> Result<(), RustdeskError> {
run_with_args(exe_path, &["--password", DEFAULT_PASSWORD]) run_with_args(exe_path, &["--password", secret])
} }
fn set_custom_id(exe_path: &Path, machine_id: &str) -> Result<String, RustdeskError> { fn set_custom_id(exe_path: &Path, machine_id: &str) -> Result<String, RustdeskError> {
@ -309,6 +350,25 @@ fn run_sc(args: &[&str]) -> Result<(), RustdeskError> {
Ok(()) Ok(())
} }
fn query_id_with_retries(exe_path: &Path, attempts: usize) -> Result<String, RustdeskError> {
let mut last_error: Option<RustdeskError> = None;
for attempt in 0..attempts {
match query_id(exe_path) {
Ok(value) if !value.trim().is_empty() => return Ok(value),
Ok(_) => {
last_error = Some(RustdeskError::MissingId);
}
Err(error) => {
last_error = Some(error);
}
}
if attempt + 1 < attempts {
thread::sleep(Duration::from_millis(800));
}
}
Err(last_error.unwrap_or(RustdeskError::MissingId))
}
fn query_id(exe_path: &Path) -> Result<String, RustdeskError> { fn query_id(exe_path: &Path) -> Result<String, RustdeskError> {
let output = Command::new(exe_path) let output = Command::new(exe_path)
.arg("--get-id") .arg("--get-id")

View file

@ -2,8 +2,9 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { createRoot } from "react-dom/client" import { createRoot } from "react-dom/client"
import { invoke } from "@tauri-apps/api/core" import { invoke } from "@tauri-apps/api/core"
import { listen } from "@tauri-apps/api/event"
import { Store } from "@tauri-apps/plugin-store" import { Store } from "@tauri-apps/plugin-store"
import { appLocalDataDir, executableDir, join } from "@tauri-apps/api/path" import { appLocalDataDir, join } from "@tauri-apps/api/path"
import { ExternalLink, Eye, EyeOff, Loader2, RefreshCw } from "lucide-react" import { ExternalLink, Eye, EyeOff, Loader2, RefreshCw } from "lucide-react"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs" import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs"
import { cn } from "./lib/utils" import { cn } from "./lib/utils"
@ -82,10 +83,10 @@ type RustdeskProvisioningResult = {
password: string password: string
installedVersion?: string | null installedVersion?: string | null
updated: boolean updated: boolean
lastProvisionedAt: number
} }
type RustdeskInfo = RustdeskProvisioningResult & { type RustdeskInfo = RustdeskProvisioningResult & {
lastProvisionedAt: number
lastSyncedAt?: number | null lastSyncedAt?: number | null
lastError?: string | null lastError?: string | null
} }
@ -96,6 +97,8 @@ declare global {
interface ImportMetaEnv { interface ImportMetaEnv {
readonly VITE_APP_URL?: string readonly VITE_APP_URL?: string
readonly VITE_API_BASE_URL?: string readonly VITE_API_BASE_URL?: string
readonly VITE_RUSTDESK_CONFIG_STRING?: string
readonly VITE_RUSTDESK_DEFAULT_PASSWORD?: string
} }
interface ImportMeta { readonly env: ImportMetaEnv } interface ImportMeta { readonly env: ImportMetaEnv }
} }
@ -111,6 +114,8 @@ function normalizeUrl(value?: string | null, fallback = DEFAULT_APP_URL) {
const appUrl = normalizeUrl(import.meta.env.VITE_APP_URL, DEFAULT_APP_URL) const appUrl = normalizeUrl(import.meta.env.VITE_APP_URL, DEFAULT_APP_URL)
const apiBaseUrl = normalizeUrl(import.meta.env.VITE_API_BASE_URL, appUrl) const apiBaseUrl = normalizeUrl(import.meta.env.VITE_API_BASE_URL, appUrl)
const RUSTDESK_CONFIG_STRING = import.meta.env.VITE_RUSTDESK_CONFIG_STRING?.trim() || null
const RUSTDESK_DEFAULT_PASSWORD = import.meta.env.VITE_RUSTDESK_DEFAULT_PASSWORD?.trim() || null
const RUSTDESK_SYNC_INTERVAL_MS = 60 * 60 * 1000 // 1h const RUSTDESK_SYNC_INTERVAL_MS = 60 * 60 * 1000 // 1h
const TOKEN_SELF_HEAL_DEBOUNCE_MS = 30 * 1000 const TOKEN_SELF_HEAL_DEBOUNCE_MS = 30 * 1000
@ -157,17 +162,9 @@ function buildRemoteAccessSnapshot(info: RustdeskInfo | null) {
} }
async function loadStore(): Promise<Store> { async function loadStore(): Promise<Store> {
// Tenta usar uma pasta "data" ao lado do executável (ex.: C:\Raven\data)
try {
const exeDir = await executableDir()
const storePath = await join(exeDir, "data", STORE_FILENAME)
return await Store.load(storePath)
} catch {
// Fallback: AppData local do usuário
const appData = await appLocalDataDir() const appData = await appLocalDataDir()
const storePath = await join(appData, STORE_FILENAME) const storePath = await join(appData, STORE_FILENAME)
return await Store.load(storePath) return await Store.load(storePath)
}
} }
async function readToken(store: Store): Promise<string | null> { async function readToken(store: Store): Promise<string | null> {
@ -629,6 +626,7 @@ useEffect(() => {
rustdeskInfoRef.current = rustdeskInfo rustdeskInfoRef.current = rustdeskInfo
}, [rustdeskInfo]) }, [rustdeskInfo])
useEffect(() => { useEffect(() => {
if (!store || !config) return if (!store || !config) return
const email = collabEmail.trim() const email = collabEmail.trim()
@ -736,69 +734,124 @@ const resolvedAppUrl = useMemo(() => {
return normalized return normalized
}, [config?.appUrl]) }, [config?.appUrl])
const syncRustdeskAccess = useCallback( const syncRemoteAccessNow = useCallback(
async (machineToken: string, info: RustdeskInfo, allowRetry = true) => { async (info: RustdeskInfo, allowRetry = true) => {
if (!store || !machineToken) return if (!store) return
const payload = buildRemoteAccessPayload(info) const payload = buildRemoteAccessPayload(info)
if (!payload) return if (!payload) return
try {
const resolveToken = async (allowHeal: boolean): Promise<string | null> => {
let currentToken = token
if (!currentToken) {
currentToken = (await readToken(store)) ?? null
if (currentToken) {
setToken(currentToken)
}
}
if (!currentToken && allowHeal) {
const healed = await attemptSelfHeal("remote-access")
if (healed) {
currentToken = (await readToken(store)) ?? null
if (currentToken) {
setToken(currentToken)
}
}
}
return currentToken
}
const sendRequest = async (machineToken: string, retryAllowed: boolean): Promise<void> => {
const response = await fetch(`${apiBaseUrl}/api/machines/remote-access`, { const response = await fetch(`${apiBaseUrl}/api/machines/remote-access`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: {
"Content-Type": "application/json",
"Idempotency-Key": `${config?.machineId ?? "unknown"}:RustDesk:${info.id}`,
},
body: JSON.stringify({ machineToken, ...payload }), body: JSON.stringify({ machineToken, ...payload }),
}) })
if (!response.ok) { if (!response.ok) {
logDesktop("remoteAccess:sync:error", { status: response.status }) logDesktop("remoteAccess:sync:error", { status: response.status })
const text = await response.text() const text = await response.text()
if (allowRetry && isTokenRevokedMessage(text)) { if (retryAllowed && (response.status === 401 || isTokenRevokedMessage(text))) {
const healed = await attemptSelfHeal("remote-access") const healed = await attemptSelfHeal("remote-access")
if (healed) { if (healed) {
const refreshedToken = (await readToken(store)) ?? machineToken const refreshedToken = await resolveToken(false)
return syncRustdeskAccess(refreshedToken, info, false) if (refreshedToken) {
return sendRequest(refreshedToken, false)
}
} }
} }
throw new Error(text.slice(0, 300) || "Falha ao registrar acesso remoto") throw new Error(text.slice(0, 300) || "Falha ao registrar acesso remoto")
} }
const nextInfo: RustdeskInfo = { ...info, lastSyncedAt: Date.now(), lastError: null } const nextInfo: RustdeskInfo = { ...info, lastSyncedAt: Date.now(), lastError: null }
logDesktop("remoteAccess:sync:success", { id: info.id })
await writeRustdeskInfo(store, nextInfo) await writeRustdeskInfo(store, nextInfo)
setRustdeskInfo(nextInfo) setRustdeskInfo(nextInfo)
logDesktop("remoteAccess:sync:success", { id: info.id })
}
try {
const machineToken = await resolveToken(true)
if (!machineToken) {
const failedInfo: RustdeskInfo = {
...info,
lastError: "Token indisponível para sincronizar acesso remoto",
}
await writeRustdeskInfo(store, failedInfo)
setRustdeskInfo(failedInfo)
logDesktop("remoteAccess:sync:skipped", { reason: "missing-token" })
return
}
await sendRequest(machineToken, allowRetry)
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error) const message = error instanceof Error ? error.message : String(error)
if (allowRetry && isTokenRevokedMessage(message)) {
const healed = await attemptSelfHeal("remote-access")
if (healed) {
const refreshedToken = (await readToken(store)) ?? machineToken
return syncRustdeskAccess(refreshedToken, info, false)
}
}
console.error("Falha ao sincronizar acesso remoto com a plataforma", error) console.error("Falha ao sincronizar acesso remoto com a plataforma", error)
const failedInfo: RustdeskInfo = { ...info, lastError: message } const failedInfo: RustdeskInfo = { ...info, lastError: message }
await writeRustdeskInfo(store, failedInfo) await writeRustdeskInfo(store, failedInfo)
setRustdeskInfo(failedInfo) setRustdeskInfo(failedInfo)
if (allowRetry && isTokenRevokedMessage(message)) {
const healed = await attemptSelfHeal("remote-access")
if (healed) {
const refreshedToken = await resolveToken(false)
if (refreshedToken) {
return syncRemoteAccessNow(failedInfo, false)
}
}
}
logDesktop("remoteAccess:sync:failed", { id: info.id, error: message }) logDesktop("remoteAccess:sync:failed", { id: info.id, error: message })
} }
}, },
[store, attemptSelfHeal] [store, token, config?.machineId, attemptSelfHeal, setToken]
) )
const provisionRustdesk = useCallback( const handleRustdeskProvision = useCallback(
async (machineId: string, machineToken: string): Promise<RustdeskInfo | null> => { async (payload: RustdeskProvisioningResult) => {
if (!store || !machineId) return null if (!store) return
const normalized: RustdeskInfo = {
...payload,
installedVersion: payload.installedVersion ?? null,
lastSyncedAt: rustdeskInfoRef.current?.lastSyncedAt ?? null,
lastError: null,
}
await writeRustdeskInfo(store, normalized)
setRustdeskInfo(normalized)
await syncRemoteAccessNow(normalized)
},
[store, syncRemoteAccessNow]
)
const ensureRustdesk = useCallback(async () => {
if (!store) return null
setIsRustdeskProvisioning(true) setIsRustdeskProvisioning(true)
try { try {
const result = await invoke<RustdeskProvisioningResult>("provision_rustdesk", { machineId }) const payload = await invoke<RustdeskProvisioningResult>("ensure_rustdesk_and_emit", {
const info: RustdeskInfo = { configString: RUSTDESK_CONFIG_STRING || null,
...result, password: RUSTDESK_DEFAULT_PASSWORD || null,
lastProvisionedAt: Date.now(), machineId: config?.machineId ?? null,
lastSyncedAt: null, })
} await handleRustdeskProvision(payload)
await writeRustdeskInfo(store, info) return payload
setRustdeskInfo(info)
if (machineToken) {
await syncRustdeskAccess(machineToken, info)
}
return info
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error) const message = error instanceof Error ? error.message : String(error)
if (message.toLowerCase().includes("apenas no windows")) { if (message.toLowerCase().includes("apenas no windows")) {
@ -810,15 +863,40 @@ const provisionRustdesk = useCallback(
} finally { } finally {
setIsRustdeskProvisioning(false) setIsRustdeskProvisioning(false)
} }
}, }, [store, config?.machineId, handleRustdeskProvision])
[store, syncRustdeskAccess]
) useEffect(() => {
if (!store) return
let disposed = false
let unsubscribe: (() => void) | null = null
listen<RustdeskProvisioningResult>("raven://remote-access/provisioned", async (event) => {
try {
await handleRustdeskProvision(event.payload)
} catch (error) {
console.error("Falha ao processar evento de provisioning do RustDesk", error)
}
})
.then((unlisten) => {
if (disposed) {
unlisten()
} else {
unsubscribe = unlisten
}
})
.catch((error) => console.error("Falha ao registrar listener do RustDesk", error))
return () => {
disposed = true
if (unsubscribe) unsubscribe()
}
}, [store, handleRustdeskProvision])
useEffect(() => { useEffect(() => {
if (!store || !config?.machineId || !token) return if (!store) return
if (!rustdeskInfo && !isRustdeskProvisioning && !rustdeskBootstrapRef.current) { if (!rustdeskInfo && !isRustdeskProvisioning && !rustdeskBootstrapRef.current) {
rustdeskBootstrapRef.current = true rustdeskBootstrapRef.current = true
provisionRustdesk(config.machineId, token).finally(() => { ensureRustdesk().finally(() => {
rustdeskBootstrapRef.current = false rustdeskBootstrapRef.current = false
}) })
return return
@ -827,10 +905,10 @@ useEffect(() => {
const lastSync = rustdeskInfo.lastSyncedAt ?? 0 const lastSync = rustdeskInfo.lastSyncedAt ?? 0
const needsSync = Date.now() - lastSync > RUSTDESK_SYNC_INTERVAL_MS const needsSync = Date.now() - lastSync > RUSTDESK_SYNC_INTERVAL_MS
if (needsSync) { if (needsSync) {
syncRustdeskAccess(token, rustdeskInfo) syncRemoteAccessNow(rustdeskInfo)
} }
} }
}, [store, config?.machineId, token, rustdeskInfo, provisionRustdesk, syncRustdeskAccess, isRustdeskProvisioning]) }, [store, rustdeskInfo, ensureRustdesk, syncRemoteAccessNow, isRustdeskProvisioning])
async function register() { async function register() {
if (!profile) return if (!profile) return
@ -907,7 +985,7 @@ useEffect(() => {
}, },
}) })
await provisionRustdesk(data.machineId, data.machineToken) await ensureRustdesk()
logDesktop("register:rustdesk:done", { machineId: data.machineId }) logDesktop("register:rustdesk:done", { machineId: data.machineId })
// Abre o sistema imediatamente após registrar (evita ficar com token inválido no fluxo antigo) // Abre o sistema imediatamente após registrar (evita ficar com token inválido no fluxo antigo)