Compare commits

...

3 Commits

Author SHA1 Message Date
Marc Brooks b3ce634860
Merge 695c876174 into d952480c2a 2025-08-24 11:55:18 +02:00
Marc Brooks d952480c2a
fix: useJsonRpc "any" issue
PR #743 didn't have all the files included in the commit.
Mea culpa, many apologies.
2025-08-24 11:40:43 +02:00
Marc Brooks 695c876174
fix/timesync: Ensure that auto-update waits for time sync
- Added check to not attempt auto update if time sync is needed and not yet successful (delays 30 second to recheck).
- Added resync of time when DHCP or link state changes if online
- Added conditional* fallback from configured* NTP servers to the IP-named NTP servers, and then to the DNS named ones if that fails
- Added conditional* fallback from the configured* HTTP servers to the default DNS named ones.
- Uses the configuration* option for how many queries to run in parallel
- Added known static IPs for time servers (in case DNS resolution isn't up yet)
- Added time.cloudflare.com to fall-back NTP servers
- Added fallback to NTP via hostnames
- Logs the resultant time (and mode)
2025-08-04 23:41:26 -05:00
32 changed files with 202 additions and 160 deletions

View File

@ -48,7 +48,7 @@ type NetworkInterfaceOptions struct {
DefaultHostname string
OnStateChange func(state *NetworkInterfaceState)
OnInitialCheck func(state *NetworkInterfaceState)
OnDhcpLeaseChange func(lease *udhcpc.Lease)
OnDhcpLeaseChange func(lease *udhcpc.Lease, state *NetworkInterfaceState)
OnConfigChange func(config *NetworkConfig)
NetworkConfig *NetworkConfig
}
@ -94,7 +94,7 @@ func NewNetworkInterfaceState(opts *NetworkInterfaceOptions) (*NetworkInterfaceS
_ = s.updateNtpServersFromLease(lease)
_ = s.setHostnameIfNotSame()
opts.OnDhcpLeaseChange(lease)
opts.OnDhcpLeaseChange(lease, s)
},
})

View File

@ -9,17 +9,32 @@ import (
"github.com/beevik/ntp"
)
var defaultNTPServers = []string{
var defaultNTPServerIPs = []string{
// These servers are known by static IP and as such don't need DNS lookups
// These are from Google and Cloudflare since if they're down, the internet
// is broken anyway
"162.159.200.1", // time.cloudflare.com IPv4
"162.159.200.123", // time.cloudflare.com IPv4
"2606:4700:f1::1", // time.cloudflare.com IPv6
"2606:4700:f1::123", // time.cloudflare.com IPv6
"216.239.35.0", // time.google.com IPv4
"216.239.35.4", // time.google.com IPv4
"216.239.35.8", // time.google.com IPv4
"216.239.35.12", // time.google.com IPv4
"2001:4860:4806::", // time.google.com IPv6
"2001:4860:4806:4::", // time.google.com IPv6
"2001:4860:4806:8::", // time.google.com IPv6
"2001:4860:4806:c::", // time.google.com IPv6
}
var defaultNTPServerHostnames = []string{
// should use something from https://github.com/jauderho/public-ntp-servers
"time.apple.com",
"time.aws.com",
"time.windows.com",
"time.google.com",
"162.159.200.123", // time.cloudflare.com IPv4
"2606:4700:f1::123", // time.cloudflare.com IPv6
"0.pool.ntp.org",
"1.pool.ntp.org",
"2.pool.ntp.org",
"3.pool.ntp.org",
"time.cloudflare.com",
"pool.ntp.org",
}
func (t *TimeSync) queryNetworkTime(ntpServers []string) (now *time.Time, offset *time.Duration) {

View File

@ -158,6 +158,7 @@ func (t *TimeSync) Sync() error {
var (
now *time.Time
offset *time.Duration
log zerolog.Logger
)
metricTimeSyncCount.Inc()
@ -166,54 +167,54 @@ func (t *TimeSync) Sync() error {
Orders:
for _, mode := range syncMode.Ordering {
log = t.l.With().Str("mode", mode).Logger()
switch mode {
case "ntp_user_provided":
if syncMode.Ntp {
t.l.Info().Msg("using NTP custom servers")
log.Info().Msg("using NTP custom servers")
now, offset = t.queryNetworkTime(t.networkConfig.TimeSyncNTPServers)
if now != nil {
t.l.Info().Str("source", "NTP").Time("now", *now).Msg("time obtained")
break Orders
}
}
case "ntp_dhcp":
if syncMode.Ntp {
t.l.Info().Msg("using NTP servers from DHCP")
log.Info().Msg("using NTP servers from DHCP")
now, offset = t.queryNetworkTime(t.dhcpNtpAddresses)
if now != nil {
t.l.Info().Str("source", "NTP DHCP").Time("now", *now).Msg("time obtained")
break Orders
}
}
case "ntp":
if syncMode.Ntp && syncMode.NtpUseFallback {
t.l.Info().Msg("using NTP fallback")
now, offset = t.queryNetworkTime(defaultNTPServers)
log.Info().Msg("using NTP fallback IPs")
now, offset = t.queryNetworkTime(defaultNTPServerIPs)
if now == nil {
log.Info().Msg("using NTP fallback hostnames")
now, offset = t.queryNetworkTime(defaultNTPServerHostnames)
}
if now != nil {
t.l.Info().Str("source", "NTP fallback").Time("now", *now).Msg("time obtained")
break Orders
}
}
case "http_user_provided":
if syncMode.Http {
t.l.Info().Msg("using HTTP custom URLs")
log.Info().Msg("using HTTP custom URLs")
now = t.queryAllHttpTime(t.networkConfig.TimeSyncHTTPUrls)
if now != nil {
t.l.Info().Str("source", "HTTP").Time("now", *now).Msg("time obtained")
break Orders
}
}
case "http":
if syncMode.Http && syncMode.HttpUseFallback {
t.l.Info().Msg("using HTTP fallback")
log.Info().Msg("using HTTP fallback")
now = t.queryAllHttpTime(defaultHTTPUrls)
if now != nil {
t.l.Info().Str("source", "HTTP fallback").Time("now", *now).Msg("time obtained")
break Orders
}
}
default:
t.l.Warn().Str("mode", mode).Msg("unknown time sync mode, skipping")
log.Warn().Msg("unknown time sync mode, skipping")
}
}
@ -226,6 +227,8 @@ Orders:
now = &newNow
}
log.Info().Time("now", *now).Msg("time obtained")
err := t.setSystemTime(*now)
if err != nil {
return fmt.Errorf("failed to set system time: %w", err)

View File

@ -96,16 +96,25 @@ func Main() {
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)
continue
}
if currentSession != nil {
logger.Debug().Msg("skipping update since a session is active")
time.Sleep(1 * time.Minute)
continue
}
includePreRelease := config.IncludePreRelease
err = TryUpdate(context.Background(), GetDeviceID(), includePreRelease)
if err != nil {
logger.Warn().Err(err).Msg("failed to auto update")
}
time.Sleep(1 * time.Hour)
}
}()

View File

@ -15,7 +15,7 @@ var (
networkState *network.NetworkInterfaceState
)
func networkStateChanged() {
func networkStateChanged(isOnline bool) {
// do not block the main thread
go waitCtrlAndRequestDisplayUpdate(true)
@ -37,6 +37,13 @@ func networkStateChanged() {
networkState.GetFQDN(),
}, true)
}
// if the network is now online, trigger an NTP sync if still needed
if isOnline && timeSync != nil && (isTimeSyncNeeded() || !timeSync.IsSyncSuccess()) {
if err := timeSync.Sync(); err != nil {
logger.Warn().Str("error", err.Error()).Msg("unable to sync time on network state change")
}
}
}
func initNetwork() error {
@ -48,13 +55,13 @@ func initNetwork() error {
NetworkConfig: config.NetworkConfig,
Logger: networkLogger,
OnStateChange: func(state *network.NetworkInterfaceState) {
networkStateChanged()
networkStateChanged(state.IsOnline())
},
OnInitialCheck: func(state *network.NetworkInterfaceState) {
networkStateChanged()
networkStateChanged(state.IsOnline())
},
OnDhcpLeaseChange: func(lease *udhcpc.Lease) {
networkStateChanged()
OnDhcpLeaseChange: func(lease *udhcpc.Lease, state *network.NetworkInterfaceState) {
networkStateChanged(state.IsOnline())
if currentSession == nil {
return
@ -64,7 +71,15 @@ func initNetwork() error {
},
OnConfigChange: func(networkConfig *network.NetworkConfig) {
config.NetworkConfig = networkConfig
networkStateChanged()
networkStateChanged(false)
if mDNS != nil {
_ = mDNS.SetListenOptions(networkConfig.GetMDNSMode())
_ = mDNS.SetLocalNames([]string{
networkState.GetHostname(),
networkState.GetFQDN(),
}, true)
}
},
})

View File

@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from "react";
import { LuExternalLink } from "react-icons/lu";
import { Button, LinkButton } from "@components/Button";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { InputFieldWithLabel } from "./InputField";
import { SelectMenuBasic } from "./SelectMenuBasic";
@ -34,7 +34,7 @@ export function JigglerSetting({
const [timezones, setTimezones] = useState<string[]>([]);
useEffect(() => {
send("getTimezones", {}, resp => {
send("getTimezones", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
setTimezones(resp.result as string[]);
});

View File

@ -10,7 +10,7 @@ import { useJsonRpc } from "@/hooks/useJsonRpc";
export default function MacroBar() {
const { macros, initialized, loadMacros, setSendFn } = useMacrosStore();
const { executeMacro } = useKeyboard();
const [send] = useJsonRpc();
const { send } = useJsonRpc();
useEffect(() => {
setSendFn(send);

View File

@ -1,6 +1,6 @@
import { useCallback , useEffect, useState } from "react";
import { useJsonRpc } from "../hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "../hooks/useJsonRpc";
import notifications from "../notifications";
import { SettingsItem } from "../routes/devices.$id.settings";
@ -59,7 +59,7 @@ const usbPresets = [
];
export function UsbDeviceSetting() {
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const [loading, setLoading] = useState(false);
const [usbDeviceConfig, setUsbDeviceConfig] =
@ -67,7 +67,7 @@ export function UsbDeviceSetting() {
const [selectedPreset, setSelectedPreset] = useState<string>("default");
const syncUsbDeviceConfig = useCallback(() => {
send("getUsbDevices", {}, resp => {
send("getUsbDevices", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
console.error("Failed to load USB devices:", resp.error);
notifications.error(
@ -97,7 +97,7 @@ export function UsbDeviceSetting() {
const handleUsbConfigChange = useCallback(
(devices: UsbDeviceConfig) => {
setLoading(true);
send("setUsbDevices", { devices }, async resp => {
send("setUsbDevices", { devices }, async (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to set usb devices: ${resp.error.data || "Unknown error"}`,

View File

@ -4,7 +4,7 @@ import { Button } from "@components/Button";
import { UsbConfigState } from "../hooks/stores";
import { useJsonRpc } from "../hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "../hooks/useJsonRpc";
import notifications from "../notifications";
import { SettingsItem } from "../routes/devices.$id.settings";
@ -54,7 +54,7 @@ const usbConfigs = [
type UsbConfigMap = Record<string, USBConfig>;
export function UsbInfoSetting() {
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const [loading, setLoading] = useState(false);
const [usbConfigProduct, setUsbConfigProduct] = useState("");
@ -94,7 +94,7 @@ export function UsbInfoSetting() {
);
const syncUsbConfigProduct = useCallback(() => {
send("getUsbConfig", {}, resp => {
send("getUsbConfig", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
console.error("Failed to load USB Config:", resp.error);
notifications.error(
@ -114,7 +114,7 @@ export function UsbInfoSetting() {
const handleUsbConfigChange = useCallback(
(usbConfig: USBConfig) => {
setLoading(true);
send("setUsbConfig", { usbConfig }, async resp => {
send("setUsbConfig", { usbConfig }, async (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to set usb config: ${resp.error.data || "Unknown error"}`,
@ -137,7 +137,7 @@ export function UsbInfoSetting() {
);
useEffect(() => {
send("getDeviceID", {}, async resp => {
send("getDeviceID", {}, async (resp: JsonRpcResponse) => {
if ("error" in resp) {
return notifications.error(
`Failed to get device ID: ${resp.error.data || "Unknown error"}`,
@ -205,10 +205,10 @@ function USBConfigDialog({
product: "",
});
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const syncUsbConfig = useCallback(() => {
send("getUsbConfig", {}, resp => {
send("getUsbConfig", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
console.error("Failed to load USB Config:", resp.error);
} else {

View File

@ -74,7 +74,7 @@ export default function WebRTCVideo() {
const [blockWheelEvent, setBlockWheelEvent] = useState(false);
// Misc states and hooks
const [send] = useJsonRpc();
const { send } = useJsonRpc();
// Video-related
useResizeObserver({

View File

@ -7,7 +7,7 @@ import { SettingsPageHeader } from "@components/SettingsPageheader";
import notifications from "@/notifications";
import LoadingSpinner from "@/components/LoadingSpinner";
import { useJsonRpc } from "../../hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "../../hooks/useJsonRpc";
const LONG_PRESS_DURATION = 3000; // 3 seconds for long press
@ -23,7 +23,7 @@ export function ATXPowerControl() {
> | null>(null);
const [atxState, setAtxState] = useState<ATXState | null>(null);
const [send] = useJsonRpc(function onRequest(resp) {
const { send } = useJsonRpc(function onRequest(resp) {
if (resp.method === "atxState") {
setAtxState(resp.params as ATXState);
}
@ -31,7 +31,7 @@ export function ATXPowerControl() {
// Request initial state
useEffect(() => {
send("getATXState", {}, resp => {
send("getATXState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to get ATX state: ${resp.error.data || "Unknown error"}`,
@ -54,7 +54,7 @@ export function ATXPowerControl() {
const timer = setTimeout(() => {
// Send long press action
console.log("Sending long press ATX power action");
send("setATXPowerAction", { action: "power-long" }, resp => {
send("setATXPowerAction", { action: "power-long" }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to send ATX power action: ${resp.error.data || "Unknown error"}`,
@ -75,7 +75,7 @@ export function ATXPowerControl() {
// Send short press action
console.log("Sending short press ATX power action");
send("setATXPowerAction", { action: "power-short" }, resp => {
send("setATXPowerAction", { action: "power-short" }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to send ATX power action: ${resp.error.data || "Unknown error"}`,
@ -127,7 +127,7 @@ export function ATXPowerControl() {
LeadingIcon={LuRotateCcw}
text="Reset"
onClick={() => {
send("setATXPowerAction", { action: "reset" }, resp => {
send("setATXPowerAction", { action: "reset" }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to send ATX power action: ${resp.error.data || "Unknown error"}`,

View File

@ -4,7 +4,7 @@ import { useCallback, useEffect, useState } from "react";
import { Button } from "@components/Button";
import Card from "@components/Card";
import { SettingsPageHeader } from "@components/SettingsPageheader";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import notifications from "@/notifications";
import FieldLabel from "@components/FieldLabel";
import LoadingSpinner from "@components/LoadingSpinner";
@ -19,11 +19,11 @@ interface DCPowerState {
}
export function DCPowerControl() {
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const [powerState, setPowerState] = useState<DCPowerState | null>(null);
const getDCPowerState = useCallback(() => {
send("getDCPowerState", {}, resp => {
send("getDCPowerState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to get DC power state: ${resp.error.data || "Unknown error"}`,
@ -35,7 +35,7 @@ export function DCPowerControl() {
}, [send]);
const handlePowerToggle = (enabled: boolean) => {
send("setDCPowerState", { enabled }, resp => {
send("setDCPowerState", { enabled }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to set DC power state: ${resp.error.data || "Unknown error"}`,
@ -47,7 +47,7 @@ export function DCPowerControl() {
};
const handleRestoreChange = (state: number) => {
// const state = powerState?.restoreState === 0 ? 1 : powerState?.restoreState === 1 ? 2 : 0;
send("setDCRestoreState", { state }, resp => {
send("setDCRestoreState", { state }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to set DC power state: ${resp.error.data || "Unknown error"}`,

View File

@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
import { Button } from "@components/Button";
import Card from "@components/Card";
import { SettingsPageHeader } from "@components/SettingsPageheader";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import notifications from "@/notifications";
import { useUiStore } from "@/hooks/stores";
import { SelectMenuBasic } from "@components/SelectMenuBasic";
@ -17,7 +17,7 @@ interface SerialSettings {
}
export function SerialConsole() {
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const [settings, setSettings] = useState<SerialSettings>({
baudRate: "9600",
dataBits: "8",
@ -26,7 +26,7 @@ export function SerialConsole() {
});
useEffect(() => {
send("getSerialSettings", {}, resp => {
send("getSerialSettings", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to get serial settings: ${resp.error.data || "Unknown error"}`,
@ -39,7 +39,7 @@ export function SerialConsole() {
const handleSettingChange = (setting: keyof SerialSettings, value: string) => {
const newSettings = { ...settings, [setting]: value };
send("setSerialSettings", { settings: newSettings }, resp => {
send("setSerialSettings", { settings: newSettings }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to update serial settings: ${resp.error.data || "Unknown error"}`,

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { LuPower, LuTerminal, LuPlugZap } from "react-icons/lu";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import Card, { GridCard } from "@components/Card";
import { SettingsPageHeader } from "@components/SettingsPageheader";
import { ATXPowerControl } from "@components/extensions/ATXPowerControl";
@ -39,12 +39,12 @@ const AVAILABLE_EXTENSIONS: Extension[] = [
];
export default function ExtensionPopover() {
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const [activeExtension, setActiveExtension] = useState<Extension | null>(null);
// Load active extension on component mount
useEffect(() => {
send("getActiveExtension", {}, resp => {
send("getActiveExtension", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
const extensionId = resp.result as string;
if (extensionId) {
@ -57,7 +57,7 @@ export default function ExtensionPopover() {
}, [send]);
const handleSetActiveExtension = (extension: Extension | null) => {
send("setActiveExtension", { extensionId: extension?.id || "" }, resp => {
send("setActiveExtension", { extensionId: extension?.id || "" }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to set active extension: ${resp.error.data || "Unknown error"}`,

View File

@ -16,13 +16,13 @@ import Card, { GridCard } from "@components/Card";
import { formatters } from "@/utils";
import { RemoteVirtualMediaState, useMountMediaStore, useRTCStore } from "@/hooks/stores";
import { SettingsPageHeader } from "@components/SettingsPageheader";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
import notifications from "@/notifications";
const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
const diskDataChannelStats = useRTCStore(state => state.diskDataChannelStats);
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const { remoteVirtualMediaState, setModalView, setRemoteVirtualMediaState } =
useMountMediaStore();
@ -47,7 +47,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
}, [diskDataChannelStats]);
const syncRemoteVirtualMediaState = useCallback(() => {
send("getVirtualMediaState", {}, response => {
send("getVirtualMediaState", {}, (response: JsonRpcResponse) => {
if ("error" in response) {
notifications.error(
`Failed to get virtual media state: ${response.error.message}`,
@ -59,7 +59,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
}, [send, setRemoteVirtualMediaState]);
const handleUnmount = () => {
send("unmountImage", {}, response => {
send("unmountImage", {}, (response: JsonRpcResponse) => {
if ("error" in response) {
notifications.error(`Failed to unmount image: ${response.error.message}`);
} else {

View File

@ -7,7 +7,7 @@ import { Button } from "@components/Button";
import { GridCard } from "@components/Card";
import { TextAreaWithLabel } from "@components/TextArea";
import { SettingsPageHeader } from "@components/SettingsPageheader";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { useHidStore, useRTCStore, useUiStore, useSettingsStore } from "@/hooks/stores";
import { keys, modifiers } from "@/keyboardMappings";
import { KeyStroke, KeyboardLayout, selectedKeyboard } from "@/keyboardLayouts";
@ -28,7 +28,7 @@ export default function PasteModal() {
const setPasteMode = useHidStore(state => state.setPasteModeEnabled);
const setDisableVideoFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap);
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
const [invalidChars, setInvalidChars] = useState<string[]>([]);
@ -47,7 +47,7 @@ export default function PasteModal() {
}, [keyboardLayout]);
useEffect(() => {
send("getKeyboardLayout", {}, resp => {
send("getKeyboardLayout", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
setKeyboardLayout(resp.result as string);
});

View File

@ -3,7 +3,7 @@ import { useClose } from "@headlessui/react";
import { GridCard } from "@components/Card";
import { SettingsPageHeader } from "@components/SettingsPageheader";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { useRTCStore, useUiStore } from "@/hooks/stores";
import notifications from "@/notifications";
@ -18,7 +18,7 @@ export default function WakeOnLanModal() {
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const close = useClose();
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [addDeviceErrorMessage, setAddDeviceErrorMessage] = useState<string | null>(null);
@ -33,7 +33,7 @@ export default function WakeOnLanModal() {
setErrorMessage(null);
if (rpcDataChannel?.readyState !== "open") return;
send("sendWOLMagicPacket", { macAddress }, resp => {
send("sendWOLMagicPacket", { macAddress }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
const isInvalid = resp.error.data?.includes("invalid MAC address");
if (isInvalid) {
@ -52,7 +52,7 @@ export default function WakeOnLanModal() {
);
const syncStoredDevices = useCallback(() => {
send("getWakeOnLanDevices", {}, resp => {
send("getWakeOnLanDevices", {}, (resp: JsonRpcResponse) => {
if ("result" in resp) {
setStoredDevices(resp.result as StoredDevice[]);
} else {
@ -70,7 +70,7 @@ export default function WakeOnLanModal() {
(index: number) => {
const updatedDevices = storedDevices.filter((_, i) => i !== index);
send("setWakeOnLanDevices", { params: { devices: updatedDevices } }, resp => {
send("setWakeOnLanDevices", { params: { devices: updatedDevices } }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
console.error("Failed to update Wake-on-LAN devices:", resp.error);
} else {
@ -86,7 +86,7 @@ export default function WakeOnLanModal() {
if (!name || !macAddress) return;
const updatedDevices = [...storedDevices, { name, macAddress }];
console.log("updatedDevices", updatedDevices);
send("setWakeOnLanDevices", { params: { devices: updatedDevices } }, resp => {
send("setWakeOnLanDevices", { params: { devices: updatedDevices } }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
console.error("Failed to add Wake-on-LAN device:", resp.error);
setAddDeviceErrorMessage("Failed to add device");

View File

@ -833,7 +833,7 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
try {
await new Promise<void>((resolve, reject) => {
sendFn("getKeyboardMacros", {}, response => {
sendFn("getKeyboardMacros", {}, (response: JsonRpcResponse) => {
if (response.error) {
console.error("Error loading macros:", response.error);
reject(new Error(response.error.message));

View File

@ -78,5 +78,5 @@ export function useJsonRpc(onRequest?: (payload: JsonRpcRequest) => void) {
};
}, [rpcDataChannel, onRequest]);
return [send];
return { send };
}

View File

@ -5,7 +5,7 @@ import { useJsonRpc } from "@/hooks/useJsonRpc";
import { keys, modifiers } from "@/keyboardMappings";
export default function useKeyboard() {
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
const updateActiveKeysAndModifiers = useHidStore(

View File

@ -27,7 +27,7 @@ import NetBootIcon from "@/assets/netboot-icon.svg";
import Fieldset from "@/components/Fieldset";
import { DEVICE_API } from "@/ui.config";
import { useJsonRpc } from "../hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "../hooks/useJsonRpc";
import notifications from "../notifications";
import { isOnDevice } from "../main";
import { cx } from "../cva.config";
@ -64,10 +64,10 @@ export function Dialog({ onClose }: { onClose: () => void }) {
setRemoteVirtualMediaState(null);
}
const [send] = useJsonRpc();
const { send } = useJsonRpc();
async function syncRemoteVirtualMediaState() {
return new Promise((resolve, reject) => {
send("getVirtualMediaState", {}, resp => {
send("getVirtualMediaState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
reject(new Error(resp.error.message));
} else {
@ -89,7 +89,7 @@ export function Dialog({ onClose }: { onClose: () => void }) {
console.log(`Mounting ${url} as ${mode}`);
setMountInProgress(true);
send("mountWithHTTP", { url, mode }, async resp => {
send("mountWithHTTP", { url, mode }, async (resp: JsonRpcResponse) => {
if ("error" in resp) triggerError(resp.error.message);
clearMountMediaState();
@ -108,7 +108,7 @@ export function Dialog({ onClose }: { onClose: () => void }) {
console.log(`Mounting ${fileName} as ${mode}`);
setMountInProgress(true);
send("mountWithStorage", { filename: fileName, mode }, async resp => {
send("mountWithStorage", { filename: fileName, mode }, async (resp: JsonRpcResponse) => {
if ("error" in resp) triggerError(resp.error.message);
clearMountMediaState();
@ -689,7 +689,7 @@ function DeviceFileView({
const [currentPage, setCurrentPage] = useState(1);
const filesPerPage = 5;
const [send] = useJsonRpc();
const { send } = useJsonRpc();
interface StorageSpace {
bytesUsed: number;
@ -718,12 +718,12 @@ function DeviceFileView({
}, [storageSpace]);
const syncStorage = useCallback(() => {
send("listStorageFiles", {}, res => {
if ("error" in res) {
notifications.error(`Error listing storage files: ${res.error}`);
send("listStorageFiles", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(`Error listing storage files: ${resp.error}`);
return;
}
const { files } = res.result as StorageFiles;
const { files } = resp.result as StorageFiles;
const formattedFiles = files.map(file => ({
name: file.filename,
size: formatters.bytes(file.size),
@ -733,13 +733,13 @@ function DeviceFileView({
setOnStorageFiles(formattedFiles);
});
send("getStorageSpace", {}, res => {
if ("error" in res) {
notifications.error(`Error getting storage space: ${res.error}`);
send("getStorageSpace", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(`Error getting storage space: ${resp.error}`);
return;
}
const space = res.result as StorageSpace;
const space = resp.result as StorageSpace;
setStorageSpace(space);
});
}, [send, setOnStorageFiles, setStorageSpace]);
@ -762,9 +762,9 @@ function DeviceFileView({
function handleDeleteFile(file: { name: string; size: string; createdAt: string }) {
console.log("Deleting file:", file);
send("deleteStorageFile", { filename: file.name }, res => {
if ("error" in res) {
notifications.error(`Error deleting file: ${res.error}`);
send("deleteStorageFile", { filename: file.name }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(`Error deleting file: ${resp.error}`);
return;
}
@ -1001,7 +1001,7 @@ function UploadFileView({
const [fileError, setFileError] = useState<string | null>(null);
const [uploadError, setUploadError] = useState<string | null>(null);
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const rtcDataChannelRef = useRef<RTCDataChannel | null>(null);
useEffect(() => {
@ -1216,7 +1216,7 @@ function UploadFileView({
setUploadState("uploading");
console.log("Upload state set to 'uploading'");
send("startStorageFileUpload", { filename: file.name, size: file.size }, resp => {
send("startStorageFileUpload", { filename: file.name, size: file.size }, (resp: JsonRpcResponse) => {
console.log("startStorageFileUpload response:", resp);
if ("error" in resp) {
console.error("Upload error:", resp.error.message);

View File

@ -12,7 +12,7 @@ import { SettingsSectionHeader } from "@/components/SettingsSectionHeader";
import { useDeviceUiNavigation } from "@/hooks/useAppNavigation";
import notifications from "@/notifications";
import { DEVICE_API } from "@/ui.config";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { isOnDevice } from "@/main";
import { TextAreaWithLabel } from "@components/TextArea";
@ -42,7 +42,7 @@ export default function SettingsAccessIndexRoute() {
const { navigateTo } = useDeviceUiNavigation();
const navigate = useNavigate();
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const [isAdopted, setAdopted] = useState(false);
const [deviceId, setDeviceId] = useState<string | null>(null);
@ -56,7 +56,7 @@ export default function SettingsAccessIndexRoute() {
const [tlsKey, setTlsKey] = useState<string>("");
const getCloudState = useCallback(() => {
send("getCloudState", {}, resp => {
send("getCloudState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return console.error(resp.error);
const cloudState = resp.result as CloudState;
setAdopted(cloudState.connected);
@ -77,7 +77,7 @@ export default function SettingsAccessIndexRoute() {
}, [send]);
const getTLSState = useCallback(() => {
send("getTLSState", {}, resp => {
send("getTLSState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return console.error(resp.error);
const tlsState = resp.result as TLSState;
@ -88,7 +88,7 @@ export default function SettingsAccessIndexRoute() {
}, [send]);
const deregisterDevice = async () => {
send("deregisterDevice", {}, resp => {
send("deregisterDevice", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to de-register device: ${resp.error.data || "Unknown error"}`,
@ -110,7 +110,7 @@ export default function SettingsAccessIndexRoute() {
return;
}
send("setCloudUrl", { apiUrl: cloudApiUrl, appUrl: cloudAppUrl }, resp => {
send("setCloudUrl", { apiUrl: cloudApiUrl, appUrl: cloudAppUrl }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to update cloud URL: ${resp.error.data || "Unknown error"}`,
@ -156,7 +156,7 @@ export default function SettingsAccessIndexRoute() {
state.privateKey = key;
}
send("setTLSState", { state }, resp => {
send("setTLSState", { state }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to update TLS settings: ${resp.error.data || "Unknown error"}`,
@ -198,7 +198,7 @@ export default function SettingsAccessIndexRoute() {
getCloudState();
getTLSState();
send("getDeviceID", {}, async resp => {
send("getDeviceID", {}, async (resp: JsonRpcResponse) => {
if ("error" in resp) return console.error(resp.error);
setDeviceId(resp.result as string);
});

View File

@ -8,14 +8,14 @@ import { ConfirmDialog } from "../components/ConfirmDialog";
import { SettingsPageHeader } from "../components/SettingsPageheader";
import { TextAreaWithLabel } from "../components/TextArea";
import { useSettingsStore } from "../hooks/stores";
import { useJsonRpc } from "../hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "../hooks/useJsonRpc";
import { isOnDevice } from "../main";
import notifications from "../notifications";
import { SettingsItem } from "./devices.$id.settings";
export default function SettingsAdvancedRoute() {
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const [sshKey, setSSHKey] = useState<string>("");
const setDeveloperMode = useSettingsStore(state => state.setDeveloperMode);
@ -27,35 +27,35 @@ export default function SettingsAdvancedRoute() {
const settings = useSettingsStore();
useEffect(() => {
send("getDevModeState", {}, resp => {
send("getDevModeState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
const result = resp.result as { enabled: boolean };
setDeveloperMode(result.enabled);
});
send("getSSHKeyState", {}, resp => {
send("getSSHKeyState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
setSSHKey(resp.result as string);
});
send("getUsbEmulationState", {}, resp => {
send("getUsbEmulationState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
setUsbEmulationEnabled(resp.result as boolean);
});
send("getDevChannelState", {}, resp => {
send("getDevChannelState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
setDevChannel(resp.result as boolean);
});
send("getLocalLoopbackOnly", {}, resp => {
send("getLocalLoopbackOnly", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
setLocalLoopbackOnly(resp.result as boolean);
});
}, [send, setDeveloperMode]);
const getUsbEmulationState = useCallback(() => {
send("getUsbEmulationState", {}, resp => {
send("getUsbEmulationState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
setUsbEmulationEnabled(resp.result as boolean);
});
@ -63,7 +63,7 @@ export default function SettingsAdvancedRoute() {
const handleUsbEmulationToggle = useCallback(
(enabled: boolean) => {
send("setUsbEmulationState", { enabled: enabled }, resp => {
send("setUsbEmulationState", { enabled: enabled }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to ${enabled ? "enable" : "disable"} USB emulation: ${resp.error.data || "Unknown error"}`,
@ -78,7 +78,7 @@ export default function SettingsAdvancedRoute() {
);
const handleResetConfig = useCallback(() => {
send("resetConfig", {}, resp => {
send("resetConfig", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to reset configuration: ${resp.error.data || "Unknown error"}`,
@ -90,7 +90,7 @@ export default function SettingsAdvancedRoute() {
}, [send]);
const handleUpdateSSHKey = useCallback(() => {
send("setSSHKeyState", { sshKey }, resp => {
send("setSSHKeyState", { sshKey }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to update SSH key: ${resp.error.data || "Unknown error"}`,
@ -103,7 +103,7 @@ export default function SettingsAdvancedRoute() {
const handleDevModeChange = useCallback(
(developerMode: boolean) => {
send("setDevModeState", { enabled: developerMode }, resp => {
send("setDevModeState", { enabled: developerMode }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to set dev mode: ${resp.error.data || "Unknown error"}`,
@ -118,7 +118,7 @@ export default function SettingsAdvancedRoute() {
const handleDevChannelChange = useCallback(
(enabled: boolean) => {
send("setDevChannelState", { enabled }, resp => {
send("setDevChannelState", { enabled }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to set dev channel state: ${resp.error.data || "Unknown error"}`,
@ -133,7 +133,7 @@ export default function SettingsAdvancedRoute() {
const applyLoopbackOnlyMode = useCallback(
(enabled: boolean) => {
send("setLocalLoopbackOnly", { enabled }, resp => {
send("setLocalLoopbackOnly", { enabled }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to ${enabled ? "enable" : "disable"} loopback-only mode: ${resp.error.data || "Unknown error"}`,

View File

@ -1,7 +1,7 @@
import { useState , useEffect } from "react";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { SettingsPageHeader } from "../components/SettingsPageheader";
import { Button } from "../components/Button";
@ -13,7 +13,7 @@ import { useDeviceStore } from "../hooks/stores";
import { SettingsItem } from "./devices.$id.settings";
export default function SettingsGeneralRoute() {
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const { navigateTo } = useDeviceUiNavigation();
const [autoUpdate, setAutoUpdate] = useState(true);
@ -24,14 +24,14 @@ export default function SettingsGeneralRoute() {
});
useEffect(() => {
send("getAutoUpdateState", {}, resp => {
send("getAutoUpdateState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
setAutoUpdate(resp.result as boolean);
});
}, [send]);
const handleAutoUpdateChange = (enabled: boolean) => {
send("setAutoUpdateState", { enabled }, resp => {
send("setAutoUpdateState", { enabled }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to set auto-update: ${resp.error.data || "Unknown error"}`,

View File

@ -6,7 +6,7 @@ import { Button } from "@components/Button";
export default function SettingsGeneralRebootRoute() {
const navigate = useNavigate();
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const onConfirmUpdate = useCallback(() => {
// This is where we send the RPC to the golang binary

View File

@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
import { CheckCircleIcon } from "@heroicons/react/20/solid";
import Card from "@/components/Card";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { Button } from "@components/Button";
import { UpdateState, useDeviceStore, useUpdateStore } from "@/hooks/stores";
import notifications from "@/notifications";
@ -16,7 +16,7 @@ export default function SettingsGeneralUpdateRoute() {
const { updateSuccess } = location.state || {};
const { setModalView, otaState } = useUpdateStore();
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const onConfirmUpdate = useCallback(() => {
send("tryUpdate", {});
@ -134,14 +134,14 @@ function LoadingState({
}) {
const [progressWidth, setProgressWidth] = useState("0%");
const abortControllerRef = useRef<AbortController | null>(null);
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const setAppVersion = useDeviceStore(state => state.setAppVersion);
const setSystemVersion = useDeviceStore(state => state.setSystemVersion);
const getVersionInfo = useCallback(() => {
return new Promise<SystemVersionInfo>((resolve, reject) => {
send("getUpdateStatus", {}, async resp => {
send("getUpdateStatus", {}, async (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(`Failed to check for updates: ${resp.error}`);
reject(new Error("Failed to check for updates"));

View File

@ -3,7 +3,7 @@ import { useEffect } from "react";
import { SettingsPageHeader } from "@components/SettingsPageheader";
import { SettingsItem } from "@routes/devices.$id.settings";
import { BacklightSettings, useSettingsStore } from "@/hooks/stores";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { SelectMenuBasic } from "@components/SelectMenuBasic";
import { UsbDeviceSetting } from "@components/UsbDeviceSetting";
@ -12,7 +12,7 @@ import { UsbInfoSetting } from "../components/UsbInfoSetting";
import { FeatureFlag } from "../components/FeatureFlag";
export default function SettingsHardwareRoute() {
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const settings = useSettingsStore();
const setDisplayRotation = useSettingsStore(state => state.setDisplayRotation);
@ -23,7 +23,7 @@ export default function SettingsHardwareRoute() {
};
const handleDisplayRotationSave = () => {
send("setDisplayRotation", { params: { rotation: settings.displayRotation } }, resp => {
send("setDisplayRotation", { params: { rotation: settings.displayRotation } }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to set display orientation: ${resp.error.data || "Unknown error"}`,
@ -48,7 +48,7 @@ export default function SettingsHardwareRoute() {
};
const handleBacklightSettingsSave = () => {
send("setBacklightSettings", { params: settings.backlightSettings }, resp => {
send("setBacklightSettings", { params: settings.backlightSettings }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to set backlight settings: ${resp.error.data || "Unknown error"}`,
@ -60,7 +60,7 @@ export default function SettingsHardwareRoute() {
};
useEffect(() => {
send("getBacklightSettings", {}, resp => {
send("getBacklightSettings", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
return notifications.error(
`Failed to get backlight settings: ${resp.error.data || "Unknown error"}`,

View File

@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo } from "react";
import { KeyboardLedSync, useSettingsStore } from "@/hooks/stores";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import notifications from "@/notifications";
import { SettingsPageHeader } from "@components/SettingsPageheader";
import { keyboardOptions } from "@/keyboardLayouts";
@ -39,10 +39,10 @@ export default function SettingsKeyboardRoute() {
{ value: "host", label: "Host Only" },
];
const [send] = useJsonRpc();
const { send } = useJsonRpc();
useEffect(() => {
send("getKeyboardLayout", {}, resp => {
send("getKeyboardLayout", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
setKeyboardLayout(resp.result as string);
});
@ -51,7 +51,7 @@ export default function SettingsKeyboardRoute() {
const onKeyboardLayoutChange = useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => {
const layout = e.target.value;
send("setKeyboardLayout", { layout }, resp => {
send("setKeyboardLayout", { layout }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to set keyboard layout: ${resp.error.data || "Unknown error"}`,

View File

@ -6,7 +6,7 @@ import PointingFinger from "@/assets/pointing-finger.svg";
import { GridCard } from "@/components/Card";
import { Checkbox } from "@/components/Checkbox";
import { useSettingsStore } from "@/hooks/stores";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { SettingsPageHeader } from "@components/SettingsPageheader";
import { SelectMenuBasic } from "@components/SelectMenuBasic";
import { JigglerSetting } from "@components/JigglerSetting";
@ -87,17 +87,17 @@ export default function SettingsMouseRoute() {
{ value: "100", label: "Very High" },
];
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const syncJigglerSettings = useCallback(() => {
send("getJigglerState", {}, resp => {
send("getJigglerState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
const isEnabled = resp.result as boolean;
// If the jiggler is disabled, set the selected option to "disabled" and nothing else
if (!isEnabled) return setSelectedJigglerOption("disabled");
send("getJigglerConfig", {}, resp => {
send("getJigglerConfig", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
const result = resp.result as JigglerConfig;
setCurrentJigglerConfig(result);
@ -121,7 +121,7 @@ export default function SettingsMouseRoute() {
const saveJigglerConfig = useCallback(
(jigglerConfig: JigglerConfig) => {
// We assume the jiggler should be set to enabled if the config is being updated
send("setJigglerState", { enabled: true }, async resp => {
send("setJigglerState", { enabled: true }, async (resp: JsonRpcResponse) => {
if ("error" in resp) {
return notifications.error(
`Failed to set jiggler state: ${resp.error.data || "Unknown error"}`,
@ -129,7 +129,7 @@ export default function SettingsMouseRoute() {
}
});
send("setJigglerConfig", { jigglerConfig }, async resp => {
send("setJigglerConfig", { jigglerConfig }, async (resp: JsonRpcResponse) => {
if ("error" in resp) {
const errorMsg = resp.error.data || "Unknown error";
@ -163,7 +163,7 @@ export default function SettingsMouseRoute() {
// We don't need to update the device jiggler state when the option is "disabled"
if (option === "disabled") {
send("setJigglerState", { enabled: false }, async resp => {
send("setJigglerState", { enabled: false }, async (resp: JsonRpcResponse) => {
if ("error" in resp) {
return notifications.error(
`Failed to set jiggler state: ${resp.error.data || "Unknown error"}`,

View File

@ -13,7 +13,7 @@ import {
TimeSyncMode,
useNetworkStateStore,
} from "@/hooks/stores";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { Button } from "@components/Button";
import { GridCard } from "@components/Card";
import InputField, { InputFieldWithLabel } from "@components/InputField";
@ -72,7 +72,7 @@ export function LifeTimeLabel({ lifetime }: { lifetime: string }) {
}
export default function SettingsNetworkRoute() {
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const [networkState, setNetworkState] = useNetworkStateStore(state => [
state,
state.setNetworkState,
@ -104,7 +104,7 @@ export default function SettingsNetworkRoute() {
const getNetworkSettings = useCallback(() => {
setNetworkSettingsLoaded(false);
send("getNetworkSettings", {}, resp => {
send("getNetworkSettings", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
console.log(resp.result);
setNetworkSettings(resp.result as NetworkSettings);
@ -117,7 +117,7 @@ export default function SettingsNetworkRoute() {
}, [send]);
const getNetworkState = useCallback(() => {
send("getNetworkState", {}, resp => {
send("getNetworkState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
console.log(resp.result);
setNetworkState(resp.result as NetworkState);
@ -127,7 +127,7 @@ export default function SettingsNetworkRoute() {
const setNetworkSettingsRemote = useCallback(
(settings: NetworkSettings) => {
setNetworkSettingsLoaded(false);
send("setNetworkSettings", { settings }, resp => {
send("setNetworkSettings", { settings }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
"Failed to save network settings: " +
@ -148,7 +148,7 @@ export default function SettingsNetworkRoute() {
);
const handleRenewLease = useCallback(() => {
send("renewDHCPLease", {}, resp => {
send("renewDHCPLease", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error("Failed to renew lease: " + resp.error.message);
} else {

View File

@ -2,7 +2,7 @@ import { useState, useEffect } from "react";
import { Button } from "@/components/Button";
import { TextAreaWithLabel } from "@/components/TextArea";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import { SettingsPageHeader } from "@components/SettingsPageheader";
import { useSettingsStore } from "@/hooks/stores";
@ -41,7 +41,7 @@ const streamQualityOptions = [
];
export default function SettingsVideoRoute() {
const [send] = useJsonRpc();
const { send } = useJsonRpc();
const [streamQuality, setStreamQuality] = useState("1");
const [customEdidValue, setCustomEdidValue] = useState<string | null>(null);
const [edid, setEdid] = useState<string | null>(null);
@ -55,12 +55,12 @@ export default function SettingsVideoRoute() {
const setVideoContrast = useSettingsStore(state => state.setVideoContrast);
useEffect(() => {
send("getStreamQualityFactor", {}, resp => {
send("getStreamQualityFactor", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
setStreamQuality(String(resp.result));
});
send("getEDID", {}, resp => {
send("getEDID", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(`Failed to get EDID: ${resp.error.data || "Unknown error"}`);
return;
@ -85,7 +85,7 @@ export default function SettingsVideoRoute() {
}, [send]);
const handleStreamQualityChange = (factor: string) => {
send("setStreamQualityFactor", { factor: Number(factor) }, resp => {
send("setStreamQualityFactor", { factor: Number(factor) }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
`Failed to set stream quality: ${resp.error.data || "Unknown error"}`,
@ -99,7 +99,7 @@ export default function SettingsVideoRoute() {
};
const handleEDIDChange = (newEdid: string) => {
send("setEDID", { edid: newEdid }, resp => {
send("setEDID", { edid: newEdid }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(`Failed to set EDID: ${resp.error.data || "Unknown error"}`);
return;

View File

@ -37,7 +37,7 @@ import WebRTCVideo from "@components/WebRTCVideo";
import { checkAuth, isInCloud, isOnDevice } from "@/main";
import DashboardNavbar from "@components/Header";
import ConnectionStatsSidebar from "@/components/sidebar/connectionStats";
import { JsonRpcRequest, useJsonRpc } from "@/hooks/useJsonRpc";
import { JsonRpcRequest, JsonRpcResponse, useJsonRpc } from "@/hooks/useJsonRpc";
import Terminal from "@components/Terminal";
import { CLOUD_API, DEVICE_API } from "@/ui.config";
@ -646,11 +646,11 @@ export default function KvmIdRoute() {
}
const rpcDataChannel = useRTCStore(state => state.rpcDataChannel);
const [send] = useJsonRpc(onJsonRpcRequest);
const { send } = useJsonRpc(onJsonRpcRequest);
useEffect(() => {
if (rpcDataChannel?.readyState !== "open") return;
send("getVideoState", {}, resp => {
send("getVideoState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) return;
setHdmiState(resp.result as Parameters<VideoState["setHdmiState"]>[0]);
});
@ -662,7 +662,7 @@ export default function KvmIdRoute() {
if (keyboardLedState !== undefined) return;
console.log("Requesting keyboard led state");
send("getKeyboardLedState", {}, resp => {
send("getKeyboardLedState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
// -32601 means the method is not supported
if (resp.error.code === -32601) {
@ -735,7 +735,7 @@ export default function KvmIdRoute() {
useEffect(() => {
if (appVersion) return;
send("getUpdateStatus", {}, async resp => {
send("getUpdateStatus", {}, async (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(`Failed to get device version: ${resp.error}`);
return