Files
Remote-Terminal-for-MeshCore/frontend/src/hooks/useRadioControl.ts
2026-03-11 17:17:03 -07:00

124 lines
3.3 KiB
TypeScript

import { useState, useCallback, useEffect, useRef } from 'react';
import { api } from '../api';
import { takePrefetchOrFetch } from '../prefetch';
import { toast } from '../components/ui/sonner';
import type { HealthStatus, RadioConfig, RadioConfigUpdate } from '../types';
export function useRadioControl() {
const [health, setHealth] = useState<HealthStatus | null>(null);
const [config, setConfig] = useState<RadioConfig | null>(null);
const prevHealthRef = useRef<HealthStatus | null>(null);
const rebootPollTokenRef = useRef(0);
// Cancel any in-flight reboot polling on unmount
useEffect(() => {
return () => {
rebootPollTokenRef.current += 1;
};
}, []);
const fetchConfig = useCallback(async () => {
try {
const data = await takePrefetchOrFetch('config', api.getRadioConfig);
setConfig(data);
} catch (err) {
console.error('Failed to fetch config:', err);
}
}, []);
const handleSaveConfig = useCallback(
async (update: RadioConfigUpdate) => {
await api.updateRadioConfig(update);
await fetchConfig();
},
[fetchConfig]
);
const handleSetPrivateKey = useCallback(
async (key: string) => {
await api.setPrivateKey(key);
await fetchConfig();
},
[fetchConfig]
);
const handleReboot = useCallback(async () => {
await api.rebootRadio();
setHealth((prev) =>
prev ? { ...prev, radio_connected: false, radio_initializing: false } : prev
);
const pollToken = ++rebootPollTokenRef.current;
const pollUntilReconnected = async () => {
for (let i = 0; i < 30; i++) {
await new Promise((r) => setTimeout(r, 1000));
if (rebootPollTokenRef.current !== pollToken) return;
try {
const data = await api.getHealth();
if (rebootPollTokenRef.current !== pollToken) return;
setHealth(data);
if (data.radio_connected) {
fetchConfig();
return;
}
} catch {
// Keep polling
}
}
};
pollUntilReconnected();
}, [fetchConfig]);
const handleDisconnect = useCallback(async () => {
await api.disconnectRadio();
const pausedHealth = await api.getHealth();
setHealth(pausedHealth);
}, []);
const handleReconnect = useCallback(async () => {
await api.reconnectRadio();
const refreshedHealth = await api.getHealth();
setHealth(refreshedHealth);
if (refreshedHealth.radio_connected) {
await fetchConfig();
}
}, [fetchConfig]);
const handleAdvertise = useCallback(async () => {
try {
await api.sendAdvertisement();
toast.success('Advertisement sent');
} catch (err) {
console.error('Failed to send advertisement:', err);
toast.error('Failed to send advertisement', {
description: err instanceof Error ? err.message : 'Check radio connection',
});
}
}, []);
const handleHealthRefresh = useCallback(async () => {
try {
const data = await api.getHealth();
setHealth(data);
} catch (err) {
console.error('Failed to refresh health:', err);
}
}, []);
return {
health,
setHealth,
config,
setConfig,
prevHealthRef,
fetchConfig,
handleSaveConfig,
handleSetPrivateKey,
handleReboot,
handleDisconnect,
handleReconnect,
handleAdvertise,
handleHealthRefresh,
};
}