mirror of https://github.com/jetkvm/kvm.git
Improves OTA update reporting and process (#838)
This commit is contained in:
parent
1ce63664c0
commit
7955ee9d35
27
main.go
27
main.go
|
|
@ -14,6 +14,7 @@ import (
|
|||
var appCtx context.Context
|
||||
|
||||
func Main() {
|
||||
logger.Log().Msg("JetKVM Starting Up")
|
||||
LoadConfig()
|
||||
|
||||
var cancel context.CancelFunc
|
||||
|
|
@ -79,16 +80,16 @@ func Main() {
|
|||
startVideoSleepModeTicker()
|
||||
|
||||
go func() {
|
||||
// wait for 15 minutes before starting auto-update checks
|
||||
// this is to avoid interfering with initial setup processes
|
||||
// and to ensure the system is stable before checking for updates
|
||||
time.Sleep(15 * time.Minute)
|
||||
for {
|
||||
logger.Debug().Bool("auto_update_enabled", config.AutoUpdateEnabled).Msg("UPDATING")
|
||||
if !config.AutoUpdateEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
if isTimeSyncNeeded() || !timeSync.IsSyncSuccess() {
|
||||
logger.Debug().Msg("system time is not synced, will retry in 30 seconds")
|
||||
time.Sleep(30 * time.Second)
|
||||
for {
|
||||
logger.Info().Bool("auto_update_enabled", config.AutoUpdateEnabled).Msg("auto-update check")
|
||||
if !config.AutoUpdateEnabled {
|
||||
logger.Debug().Msg("auto-update disabled")
|
||||
time.Sleep(5 * time.Minute) // we'll check if auto-updates are enabled in five minutes
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -98,6 +99,12 @@ func Main() {
|
|||
continue
|
||||
}
|
||||
|
||||
if isTimeSyncNeeded() || !timeSync.IsSyncSuccess() {
|
||||
logger.Debug().Msg("system time is not synced, will retry in 30 seconds")
|
||||
time.Sleep(30 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
includePreRelease := config.IncludePreRelease
|
||||
err = TryUpdate(context.Background(), GetDeviceID(), includePreRelease)
|
||||
if err != nil {
|
||||
|
|
@ -107,6 +114,7 @@ func Main() {
|
|||
time.Sleep(1 * time.Hour)
|
||||
}
|
||||
}()
|
||||
|
||||
//go RunFuseServer()
|
||||
go RunWebServer()
|
||||
|
||||
|
|
@ -123,7 +131,8 @@ func Main() {
|
|||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigs
|
||||
logger.Info().Msg("JetKVM Shutting Down")
|
||||
|
||||
logger.Log().Msg("JetKVM Shutting Down")
|
||||
//if fuseServer != nil {
|
||||
// err := setMassStorageImage(" ")
|
||||
// if err != nil {
|
||||
|
|
|
|||
40
ota.go
40
ota.go
|
|
@ -176,7 +176,7 @@ func downloadFile(ctx context.Context, path string, url string, downloadProgress
|
|||
if nr > 0 {
|
||||
nw, ew := file.Write(buf[0:nr])
|
||||
if nw < nr {
|
||||
return fmt.Errorf("short write: %d < %d", nw, nr)
|
||||
return fmt.Errorf("short file write: %d < %d", nw, nr)
|
||||
}
|
||||
written += int64(nw)
|
||||
if ew != nil {
|
||||
|
|
@ -240,7 +240,7 @@ func verifyFile(path string, expectedHash string, verifyProgress *float32, scope
|
|||
if nr > 0 {
|
||||
nw, ew := hash.Write(buf[0:nr])
|
||||
if nw < nr {
|
||||
return fmt.Errorf("short write: %d < %d", nw, nr)
|
||||
return fmt.Errorf("short hash write: %d < %d", nw, nr)
|
||||
}
|
||||
verified += int64(nw)
|
||||
if ew != nil {
|
||||
|
|
@ -260,11 +260,16 @@ func verifyFile(path string, expectedHash string, verifyProgress *float32, scope
|
|||
}
|
||||
}
|
||||
|
||||
hashSum := hash.Sum(nil)
|
||||
scopedLogger.Info().Str("path", path).Str("hash", hex.EncodeToString(hashSum)).Msg("SHA256 hash of")
|
||||
// close the file so we can rename below
|
||||
if err := fileToHash.Close(); err != nil {
|
||||
return fmt.Errorf("error closing file: %w", err)
|
||||
}
|
||||
|
||||
if hex.EncodeToString(hashSum) != expectedHash {
|
||||
return fmt.Errorf("hash mismatch: %x != %s", hashSum, expectedHash)
|
||||
hashSum := hex.EncodeToString(hash.Sum(nil))
|
||||
scopedLogger.Info().Str("path", path).Str("hash", hashSum).Msg("SHA256 hash of")
|
||||
|
||||
if hashSum != expectedHash {
|
||||
return fmt.Errorf("hash mismatch: %s != %s", hashSum, expectedHash)
|
||||
}
|
||||
|
||||
if err := os.Rename(unverifiedPath, path); err != nil {
|
||||
|
|
@ -313,7 +318,7 @@ func triggerOTAStateUpdate() {
|
|||
func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) error {
|
||||
scopedLogger := otaLogger.With().
|
||||
Str("deviceId", deviceId).
|
||||
Str("includePreRelease", fmt.Sprintf("%v", includePreRelease)).
|
||||
Bool("includePreRelease", includePreRelease).
|
||||
Logger()
|
||||
|
||||
scopedLogger.Info().Msg("Trying to update...")
|
||||
|
|
@ -362,8 +367,9 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
|||
otaState.Error = fmt.Sprintf("Error downloading app update: %v", err)
|
||||
scopedLogger.Error().Err(err).Msg("Error downloading app update")
|
||||
triggerOTAStateUpdate()
|
||||
return err
|
||||
return fmt.Errorf("error downloading app update: %w", err)
|
||||
}
|
||||
|
||||
downloadFinished := time.Now()
|
||||
otaState.AppDownloadFinishedAt = &downloadFinished
|
||||
otaState.AppDownloadProgress = 1
|
||||
|
|
@ -379,17 +385,21 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
|||
otaState.Error = fmt.Sprintf("Error verifying app update hash: %v", err)
|
||||
scopedLogger.Error().Err(err).Msg("Error verifying app update hash")
|
||||
triggerOTAStateUpdate()
|
||||
return err
|
||||
return fmt.Errorf("error verifying app update: %w", err)
|
||||
}
|
||||
|
||||
verifyFinished := time.Now()
|
||||
otaState.AppVerifiedAt = &verifyFinished
|
||||
otaState.AppVerificationProgress = 1
|
||||
triggerOTAStateUpdate()
|
||||
|
||||
otaState.AppUpdatedAt = &verifyFinished
|
||||
otaState.AppUpdateProgress = 1
|
||||
triggerOTAStateUpdate()
|
||||
|
||||
scopedLogger.Info().Msg("App update downloaded")
|
||||
rebootNeeded = true
|
||||
triggerOTAStateUpdate()
|
||||
} else {
|
||||
scopedLogger.Info().Msg("App is up to date")
|
||||
}
|
||||
|
|
@ -405,8 +415,9 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
|||
otaState.Error = fmt.Sprintf("Error downloading system update: %v", err)
|
||||
scopedLogger.Error().Err(err).Msg("Error downloading system update")
|
||||
triggerOTAStateUpdate()
|
||||
return err
|
||||
return fmt.Errorf("error downloading system update: %w", err)
|
||||
}
|
||||
|
||||
downloadFinished := time.Now()
|
||||
otaState.SystemDownloadFinishedAt = &downloadFinished
|
||||
otaState.SystemDownloadProgress = 1
|
||||
|
|
@ -422,8 +433,9 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
|||
otaState.Error = fmt.Sprintf("Error verifying system update hash: %v", err)
|
||||
scopedLogger.Error().Err(err).Msg("Error verifying system update hash")
|
||||
triggerOTAStateUpdate()
|
||||
return err
|
||||
return fmt.Errorf("error verifying system update: %w", err)
|
||||
}
|
||||
|
||||
scopedLogger.Info().Msg("System update downloaded")
|
||||
verifyFinished := time.Now()
|
||||
otaState.SystemVerifiedAt = &verifyFinished
|
||||
|
|
@ -439,8 +451,10 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
|||
if err != nil {
|
||||
otaState.Error = fmt.Sprintf("Error starting rk_ota command: %v", err)
|
||||
scopedLogger.Error().Err(err).Msg("Error starting rk_ota command")
|
||||
triggerOTAStateUpdate()
|
||||
return fmt.Errorf("error starting rk_ota command: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
|
|
@ -475,13 +489,15 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
|
|||
Str("output", output).
|
||||
Int("exitCode", cmd.ProcessState.ExitCode()).
|
||||
Msg("Error executing rk_ota command")
|
||||
triggerOTAStateUpdate()
|
||||
return fmt.Errorf("error executing rk_ota command: %w\nOutput: %s", err, output)
|
||||
}
|
||||
|
||||
scopedLogger.Info().Str("output", output).Msg("rk_ota success")
|
||||
otaState.SystemUpdateProgress = 1
|
||||
otaState.SystemUpdatedAt = &verifyFinished
|
||||
triggerOTAStateUpdate()
|
||||
rebootNeeded = true
|
||||
triggerOTAStateUpdate()
|
||||
} else {
|
||||
scopedLogger.Info().Msg("System is up to date")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonPropsType>(
|
|||
Button.displayName = "Button";
|
||||
|
||||
type LinkPropsType = Pick<LinkProps, "to"> &
|
||||
React.ComponentProps<typeof ButtonContent> & { disabled?: boolean };
|
||||
React.ComponentProps<typeof ButtonContent> & { disabled?: boolean, reloadDocument?: boolean };
|
||||
export const LinkButton = ({ to, ...props }: LinkPropsType) => {
|
||||
const classes = cx(
|
||||
"group outline-hidden",
|
||||
|
|
@ -230,7 +230,7 @@ export const LinkButton = ({ to, ...props }: LinkPropsType) => {
|
|||
);
|
||||
} else {
|
||||
return (
|
||||
<Link to={to} className={classes}>
|
||||
<Link to={to} reloadDocument={props.reloadDocument} className={classes}>
|
||||
<ButtonContent {...props} />
|
||||
</Link>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -573,14 +573,18 @@ export interface OtaState {
|
|||
export interface UpdateState {
|
||||
isUpdatePending: boolean;
|
||||
setIsUpdatePending: (isPending: boolean) => void;
|
||||
|
||||
updateDialogHasBeenMinimized: boolean;
|
||||
setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) => void;
|
||||
|
||||
otaState: OtaState;
|
||||
setOtaState: (state: OtaState) => void;
|
||||
setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) => void;
|
||||
|
||||
modalView: UpdateModalViews
|
||||
setModalView: (view: UpdateModalViews) => void;
|
||||
setUpdateErrorMessage: (errorMessage: string) => void;
|
||||
|
||||
updateErrorMessage: string | null;
|
||||
setUpdateErrorMessage: (errorMessage: string) => void;
|
||||
}
|
||||
|
||||
export const useUpdateStore = create<UpdateState>(set => ({
|
||||
|
|
@ -611,8 +615,10 @@ export const useUpdateStore = create<UpdateState>(set => ({
|
|||
updateDialogHasBeenMinimized: false,
|
||||
setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) =>
|
||||
set({ updateDialogHasBeenMinimized: hasBeenMinimized }),
|
||||
|
||||
modalView: "loading",
|
||||
setModalView: (view: UpdateModalViews) => set({ modalView: view }),
|
||||
|
||||
updateErrorMessage: null,
|
||||
setUpdateErrorMessage: (errorMessage: string) => set({ updateErrorMessage: errorMessage }),
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -73,10 +73,10 @@ export async function checkDeviceAuth() {
|
|||
.GET(`${DEVICE_API}/device/status`)
|
||||
.then(res => res.json() as Promise<DeviceStatus>);
|
||||
|
||||
if (!res.isSetup) return redirect("/welcome");
|
||||
if (!res.isSetup) throw redirect("/welcome");
|
||||
|
||||
const deviceRes = await api.GET(`${DEVICE_API}/device`);
|
||||
if (deviceRes.status === 401) return redirect("/login-local");
|
||||
if (deviceRes.status === 401) throw redirect("/login-local");
|
||||
if (deviceRes.ok) {
|
||||
const device = (await deviceRes.json()) as LocalDevice;
|
||||
return { authMode: device.authMode };
|
||||
|
|
@ -86,7 +86,7 @@ export async function checkDeviceAuth() {
|
|||
}
|
||||
|
||||
export async function checkAuth() {
|
||||
return import.meta.env.MODE === "device" ? checkDeviceAuth() : checkCloudAuth();
|
||||
return isOnDevice ? checkDeviceAuth() : checkCloudAuth();
|
||||
}
|
||||
|
||||
let router;
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ const loader: LoaderFunction = async ({ params }: LoaderFunctionArgs) => {
|
|||
return { device, user };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return { devices: [] };
|
||||
return { user };
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ const loader: LoaderFunction = async ({ params }: LoaderFunctionArgs) => {
|
|||
return { device, user };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return { devices: [] };
|
||||
return { user };
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ export default function SettingsAccessIndexRoute() {
|
|||
}
|
||||
|
||||
getCloudState();
|
||||
// In cloud mode, we need to navigate to the device overview page, as we don't a connection anymore
|
||||
// In cloud mode, we need to navigate to the device overview page, as we don't have a connection anymore
|
||||
if (!isOnDevice) navigate("/");
|
||||
return;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,11 +9,17 @@ export default function SettingsGeneralRebootRoute() {
|
|||
const navigate = useNavigate();
|
||||
const { send } = useJsonRpc();
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
navigate(".."); // back to the devices.$id.settings page
|
||||
window.location.reload(); // force a full reload to ensure the current device/cloud UI version is loaded
|
||||
}, [navigate]);
|
||||
|
||||
|
||||
const onConfirmUpdate = useCallback(() => {
|
||||
send("reboot", { force: true});
|
||||
}, [send]);
|
||||
|
||||
return <Dialog onClose={() => navigate("..")} onConfirmUpdate={onConfirmUpdate} />;
|
||||
return <Dialog onClose={onClose} onConfirmUpdate={onConfirmUpdate} />;
|
||||
}
|
||||
|
||||
export function Dialog({
|
||||
|
|
|
|||
|
|
@ -21,6 +21,11 @@ export default function SettingsGeneralUpdateRoute() {
|
|||
const { setModalView, otaState } = useUpdateStore();
|
||||
const { send } = useJsonRpc();
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
navigate(".."); // back to the devices.$id.settings page
|
||||
window.location.reload(); // force a full reload to ensure the current device/cloud UI version is loaded
|
||||
}, [navigate]);
|
||||
|
||||
const onConfirmUpdate = useCallback(() => {
|
||||
send("tryUpdate", {});
|
||||
setModalView("updating");
|
||||
|
|
@ -36,9 +41,9 @@ export default function SettingsGeneralUpdateRoute() {
|
|||
} else {
|
||||
setModalView("loading");
|
||||
}
|
||||
}, [otaState.updating, otaState.error, setModalView, updateSuccess]);
|
||||
}, [otaState.error, otaState.updating, setModalView, updateSuccess]);
|
||||
|
||||
return <Dialog onClose={() => navigate("..")} onConfirmUpdate={onConfirmUpdate} />;
|
||||
return <Dialog onClose={onClose} onConfirmUpdate={onConfirmUpdate} />;
|
||||
}
|
||||
|
||||
export function Dialog({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { lazy, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
Outlet,
|
||||
redirect,
|
||||
useLoaderData,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
|
|
@ -16,7 +15,7 @@ import { motion, AnimatePresence } from "framer-motion";
|
|||
import useWebSocket from "react-use-websocket";
|
||||
|
||||
import { cx } from "@/cva.config";
|
||||
import { CLOUD_API, DEVICE_API } from "@/ui.config";
|
||||
import { CLOUD_API } from "@/ui.config";
|
||||
import api from "@/api";
|
||||
import { checkAuth, isInCloud, isOnDevice } from "@/main";
|
||||
import {
|
||||
|
|
@ -51,9 +50,14 @@ import {
|
|||
RebootingOverlay,
|
||||
} from "@components/VideoOverlay";
|
||||
import { FeatureFlagProvider } from "@providers/FeatureFlagProvider";
|
||||
import { DeviceStatus } from "@routes/welcome-local";
|
||||
import { m } from "@localizations/messages.js";
|
||||
|
||||
export type AuthMode = "password" | "noPassword" | null;
|
||||
|
||||
interface LocalLoaderResp {
|
||||
authMode: AuthMode;
|
||||
}
|
||||
|
||||
interface CloudLoaderResp {
|
||||
deviceName: string;
|
||||
user: User | null;
|
||||
|
|
@ -62,35 +66,20 @@ interface CloudLoaderResp {
|
|||
} | null;
|
||||
}
|
||||
|
||||
export type AuthMode = "password" | "noPassword" | null;
|
||||
export interface LocalDevice {
|
||||
authMode: AuthMode;
|
||||
deviceId: string;
|
||||
}
|
||||
|
||||
const deviceLoader = async () => {
|
||||
const res = await api
|
||||
.GET(`${DEVICE_API}/device/status`)
|
||||
.then(res => res.json() as Promise<DeviceStatus>);
|
||||
|
||||
if (!res.isSetup) return redirect("/welcome");
|
||||
|
||||
const deviceRes = await api.GET(`${DEVICE_API}/device`);
|
||||
if (deviceRes.status === 401) return redirect("/login-local");
|
||||
if (deviceRes.ok) {
|
||||
const device = (await deviceRes.json()) as LocalDevice;
|
||||
return { authMode: device.authMode };
|
||||
}
|
||||
|
||||
throw new Error("Error fetching device");
|
||||
const device = await checkAuth();
|
||||
return { authMode: device.authMode } as LocalLoaderResp;
|
||||
};
|
||||
|
||||
const cloudLoader = async (params: Params<string>): Promise<CloudLoaderResp> => {
|
||||
const user = await checkAuth();
|
||||
|
||||
const iceResp = await api.POST(`${CLOUD_API}/webrtc/ice_config`);
|
||||
const iceConfig = await iceResp.json();
|
||||
|
||||
const deviceResp = await api.GET(`${CLOUD_API}/devices/${params.id}`);
|
||||
|
||||
if (!deviceResp.ok) {
|
||||
|
|
@ -105,11 +94,11 @@ const cloudLoader = async (params: Params<string>): Promise<CloudLoaderResp> =>
|
|||
device: { id: string; name: string; user: { googleId: string } };
|
||||
};
|
||||
|
||||
return { user, iceConfig, deviceName: device.name || device.id };
|
||||
return { user, iceConfig, deviceName: device.name || device.id } as CloudLoaderResp;
|
||||
};
|
||||
|
||||
const loader: LoaderFunction = ({ params }: LoaderFunctionArgs) => {
|
||||
return import.meta.env.MODE === "device" ? deviceLoader() : cloudLoader(params);
|
||||
return isOnDevice ? deviceLoader() : cloudLoader(params);
|
||||
};
|
||||
|
||||
export default function KvmIdRoute() {
|
||||
|
|
@ -185,7 +174,7 @@ export default function KvmIdRoute() {
|
|||
|
||||
try {
|
||||
await pc.setRemoteDescription(new RTCSessionDescription(remoteDescription));
|
||||
console.log("[setRemoteSessionDescription] Remote description set successfully");
|
||||
console.log("[setRemoteSessionDescription] Remote description set successfully to: " + remoteDescription.sdp);
|
||||
setLoadingMessage(m.establishing_secure_connection());
|
||||
} catch (error) {
|
||||
console.error(
|
||||
|
|
@ -230,9 +219,14 @@ export default function KvmIdRoute() {
|
|||
const ignoreOffer = useRef(false);
|
||||
const isSettingRemoteAnswerPending = useRef(false);
|
||||
const makingOffer = useRef(false);
|
||||
|
||||
const reconnectAttemptsRef = useRef(2000);
|
||||
const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
|
||||
const reconnectInterval = (attempt: number) => {
|
||||
// Exponential backoff with a max of 10 seconds between attempts
|
||||
return Math.min(500 * 2 ** attempt, 10000);
|
||||
}
|
||||
|
||||
const { sendMessage, getWebSocket } = useWebSocket(
|
||||
isOnDevice
|
||||
? `${wsProtocol}//${window.location.host}/webrtc/signaling/client`
|
||||
|
|
@ -240,10 +234,10 @@ export default function KvmIdRoute() {
|
|||
{
|
||||
heartbeat: true,
|
||||
retryOnError: true,
|
||||
reconnectAttempts: 2000,
|
||||
reconnectInterval: 1000,
|
||||
reconnectAttempts: reconnectAttemptsRef.current,
|
||||
reconnectInterval: reconnectInterval,
|
||||
onReconnectStop: (numAttempts: number) => {
|
||||
console.debug("Reconnect stopped", numAttempts);
|
||||
console.debug("Reconnect stopped after ", numAttempts, "attempts");
|
||||
cleanupAndStopReconnecting();
|
||||
},
|
||||
|
||||
|
|
@ -261,6 +255,7 @@ export default function KvmIdRoute() {
|
|||
console.error("[Websocket] onError", event);
|
||||
// We don't want to close everything down, we wait for the reconnect to stop instead
|
||||
},
|
||||
|
||||
onOpen() {
|
||||
console.debug("[Websocket] onOpen");
|
||||
// We want to clear the reboot state when the websocket connection is opened
|
||||
|
|
@ -293,6 +288,7 @@ export default function KvmIdRoute() {
|
|||
*/
|
||||
|
||||
const parsedMessage = JSON.parse(message.data);
|
||||
|
||||
if (parsedMessage.type === "device-metadata") {
|
||||
const { deviceVersion } = parsedMessage.data;
|
||||
console.debug("[Websocket] Received device-metadata message");
|
||||
|
|
@ -309,10 +305,12 @@ export default function KvmIdRoute() {
|
|||
console.log("[Websocket] Device is using new signaling");
|
||||
isLegacySignalingEnabled.current = false;
|
||||
}
|
||||
|
||||
setupPeerConnection();
|
||||
}
|
||||
|
||||
if (!peerConnection) return;
|
||||
|
||||
if (parsedMessage.type === "answer") {
|
||||
console.debug("[Websocket] Received answer");
|
||||
const readyForOffer =
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ interface LoaderData {
|
|||
devices: { id: string; name: string; online: boolean; lastSeen: string }[];
|
||||
user: User;
|
||||
}
|
||||
const loader: LoaderFunction = async ()=> {
|
||||
const loader: LoaderFunction = async () => {
|
||||
const user = await checkAuth();
|
||||
|
||||
try {
|
||||
|
|
@ -30,7 +30,7 @@ const loader: LoaderFunction = async ()=> {
|
|||
return { devices, user };
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return { devices: [] };
|
||||
return { devices: [], user };
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue