UI: Add configurable option for auto-reconnecting to RFA client (#2297)

This commit is contained in:
catloversg
2025-08-27 13:58:38 +07:00
committed by GitHub
parent bd3c6c10ef
commit af75bd96e7
3 changed files with 73 additions and 7 deletions

View File

@@ -13,9 +13,14 @@ export const RemoteAPIPage = (): React.ReactElement => {
);
const [remoteFileApiPort, setRemoteFileApiPort] = useState(Settings.RemoteFileApiPort.toString());
const [portError, setPortError] = useState(isValidConnectionPort(Settings.RemoteFileApiPort).message ?? "");
const [remoteFileApiReconnectionDelay, setRemoteFileApiReconnectionDelay] = useState(
Settings.RemoteFileApiReconnectionDelay.toString(),
);
const [reconnectionDelayError, setReconnectionDelayError] = useState("");
const isValidHostname = hostnameError === "";
const isValidPort = portError === "";
const isValidReconnectionDelay = reconnectionDelayError === "";
function handleRemoteFileApiHostnameChange(event: React.ChangeEvent<HTMLInputElement>): void {
const newValue = event.target.value.trim();
@@ -32,7 +37,7 @@ export const RemoteAPIPage = (): React.ReactElement => {
function handleRemoteFileApiPortChange(event: React.ChangeEvent<HTMLInputElement>): void {
const newValue = event.target.value.trim();
setRemoteFileApiPort(newValue);
const port = Number.parseInt(newValue);
const port = Number(newValue);
const result = isValidConnectionPort(port);
if (!result.success) {
setPortError(result.message);
@@ -42,6 +47,18 @@ export const RemoteAPIPage = (): React.ReactElement => {
setPortError("");
}
function handleRemoteFileApiReconnectionDelayChange(event: React.ChangeEvent<HTMLInputElement>): void {
const newValue = event.target.value.trim();
setRemoteFileApiReconnectionDelay(newValue);
const reconnectionDelay = Number(newValue);
if (!Number.isFinite(reconnectionDelay) || reconnectionDelay < 0) {
setReconnectionDelayError("Invalid reconnection delay");
return;
}
Settings.RemoteFileApiReconnectionDelay = reconnectionDelay;
setReconnectionDelayError("");
}
return (
<GameOptionsPage title="Remote API">
<Typography>
@@ -73,7 +90,7 @@ export const RemoteAPIPage = (): React.ReactElement => {
<TextField
error={!isValidHostname}
InputProps={{
startAdornment: <Typography>Hostname:&nbsp;</Typography>,
startAdornment: <Typography style={{ minWidth: "200px" }}>Hostname:&nbsp;</Typography>,
}}
value={remoteFileApiHostname}
onChange={handleRemoteFileApiHostnameChange}
@@ -97,7 +114,11 @@ export const RemoteAPIPage = (): React.ReactElement => {
<TextField
error={!isValidPort}
InputProps={{
startAdornment: <Typography color={isValidPort ? "success" : "error"}>Port:&nbsp;</Typography>,
startAdornment: (
<Typography color={isValidPort ? "success" : "error"} style={{ minWidth: "200px" }}>
Port:&nbsp;
</Typography>
),
}}
value={remoteFileApiPort}
onChange={handleRemoteFileApiPortChange}
@@ -107,6 +128,33 @@ export const RemoteAPIPage = (): React.ReactElement => {
{portError && <Typography color={Settings.theme.error}>{portError}</Typography>}
</div>
</Tooltip>
<Tooltip
title={
<Typography>
When the connection is closed, Bitburner will automatically reconnect after this delay.
<br />
The value must be in seconds. Set it to 0 to disable the feature.
</Typography>
}
>
<div>
<TextField
error={!isValidReconnectionDelay}
InputProps={{
startAdornment: (
<Typography color={isValidReconnectionDelay ? "success" : "error"} style={{ minWidth: "200px" }}>
Reconnection delay:&nbsp;
</Typography>
),
}}
value={remoteFileApiReconnectionDelay}
onChange={handleRemoteFileApiReconnectionDelayChange}
placeholder="0"
size={"medium"}
/>
{reconnectionDelayError && <Typography color={Settings.theme.error}>{reconnectionDelayError}</Typography>}
</div>
</Tooltip>
<OptionSwitch
checked={Settings.UseWssForRemoteFileApi}
onChange={(newValue) => (Settings.UseWssForRemoteFileApi = newValue)}

View File

@@ -8,6 +8,8 @@ function showErrorMessage(address: string, detail: string) {
SnackbarEvents.emit(`Error with websocket ${address}, details: ${detail}`, ToastVariant.ERROR, 5000);
}
const eventCodeWhenIntentionallyStoppingConnection = 3000;
export class Remote {
connection?: WebSocket;
ipaddr: string;
@@ -19,7 +21,7 @@ export class Remote {
}
public stopConnection(): void {
this.connection?.close();
this.connection?.close(eventCodeWhenIntentionallyStoppingConnection);
}
public startConnection(): void {
@@ -40,9 +42,23 @@ export class Remote {
2000,
),
);
this.connection.addEventListener("close", () =>
SnackbarEvents.emit("Remote API connection closed", ToastVariant.WARNING, 2000),
);
this.connection.addEventListener("close", (event) => {
/**
* On Bitburner side, we may intentionally close the connection. For example, we do that before starting a new
* connection. In this event handler, we do things that are only necessary when the connection is closed
* unexpectedly (e.g., show a warning, reconnect after a delay), so we need to check whether the close event is
* unexpected.
*/
if (event.code === eventCodeWhenIntentionallyStoppingConnection) {
return;
}
SnackbarEvents.emit(`Remote API connection closed. Code: ${event.code}.`, ToastVariant.WARNING, 2000);
if (Settings.RemoteFileApiReconnectionDelay > 0) {
setTimeout(() => {
this.startConnection();
}, Settings.RemoteFileApiReconnectionDelay * 1000);
}
});
}
}

View File

@@ -114,6 +114,8 @@ export const Settings = {
RemoteFileApiAddress: "localhost",
/** Port the Remote File API client will try to connect to. 0 to disable. */
RemoteFileApiPort: 0,
/** Automatically reconnect to the Remote File API client after this delay. Set it 0 to disable. */
RemoteFileApiReconnectionDelay: 0,
/** Use wss instead of ws when connecting to RFA clients */
UseWssForRemoteFileApi: false,
/** Whether to save the game when the player saves any file. */