diff --git a/src/GameOptions/ui/ConnectionBauble.tsx b/src/GameOptions/ui/ConnectionBauble.tsx new file mode 100644 index 000000000..a7e6cd4f6 --- /dev/null +++ b/src/GameOptions/ui/ConnectionBauble.tsx @@ -0,0 +1,46 @@ +import React from "react"; + +interface baubleState { + connection: boolean; + callback: () => boolean; +} + +interface baubleProps { + callback: () => boolean; +} + +export class ConnectionBauble extends React.Component { + timerID: NodeJS.Timer; + state: baubleState; + + constructor(props: baubleProps) { + super(props); + this.state = { + connection: false, + callback: props.callback + }; + } + + componentDidMount() : void { + this.timerID = setInterval( + () => this.tick(), + 1000 + ); + } + + componentWillUnmount() : void { + clearInterval(this.timerID); + } + + tick() : void { + this.setState({ + connection: this.state.callback() + }); + } + + render() : string { + return ( + this.state.connection? "Connected" : "Disconnected" + ); + } +} diff --git a/src/GameOptions/ui/CurrentOptionsPage.tsx b/src/GameOptions/ui/CurrentOptionsPage.tsx index 639ba21a8..f5831ed02 100644 --- a/src/GameOptions/ui/CurrentOptionsPage.tsx +++ b/src/GameOptions/ui/CurrentOptionsPage.tsx @@ -1,12 +1,15 @@ -import { MenuItem, Select, SelectChangeEvent, TextField, Tooltip, Typography } from "@mui/material"; +import { MenuItem, Select, SelectChangeEvent, TextField, Tooltip, Typography, Box } from "@mui/material"; import React, { useState } from "react"; import { IPlayer } from "../../PersonObjects/IPlayer"; +import { isRemoteFileApiConnectionLive, newRemoteFileApiConnection } from "../../RemoteFileAPI/RemoteFileAPI"; import { Settings } from "../../Settings/Settings"; import { OptionSwitch } from "../../ui/React/OptionSwitch"; import { formatTime } from "../../utils/helpers/formatTime"; import { GameOptionsTab } from "../GameOptionsTab"; import { GameOptionsPage } from "./GameOptionsPage"; import { OptionsSlider } from "./OptionsSlider"; +import Button from "@mui/material/Button"; +import { ConnectionBauble } from "./ConnectionBauble"; interface IProps { currentTab: GameOptionsTab; @@ -21,6 +24,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => { const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity); const [autosaveInterval, setAutosaveInterval] = useState(Settings.AutosaveInterval); const [timestampFormat, setTimestampFormat] = useState(Settings.TimestampsFormat); + const [remoteFileApiPort, setRemoteFileApiPort] = useState(Settings.RemoteFileApiPort); const [locale, setLocale] = useState(Settings.Locale); function handleExecTimeChange( @@ -81,6 +85,11 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => { Settings.TimestampsFormat = event.target.value; } + function handleRemoteFileApiPortChange(event: React.ChangeEvent): void { + setRemoteFileApiPort(Number(event.target.value) as number); + Settings.RemoteFileApiPort = Number(event.target.value); + } + const pages = { [GameOptionsTab.SYSTEM]: ( @@ -361,6 +370,35 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => { } /> + + This port number is used to connect to a Remote File API port, + please ensure that it matches with the port the Remote File API server is publishing on (12525 by default). + Click the reconnect button to try and re-establish connection. + The little colored bauble shows whether the connection is live or not. + + }> + 0 && remoteFileApiPort <= 65535? "success" : "error"} + > + Remote File API port: + + ), + endAdornment: ( + + + + + ), + }} + value={remoteFileApiPort} + onChange={handleRemoteFileApiPortChange} + placeholder="12525" + /> + ), }; diff --git a/src/RemoteFileAPI/MessageHandlers.ts b/src/RemoteFileAPI/MessageHandlers.ts index 0075052ad..14860269b 100644 --- a/src/RemoteFileAPI/MessageHandlers.ts +++ b/src/RemoteFileAPI/MessageHandlers.ts @@ -77,26 +77,12 @@ export const RFARequestHandler: Record void | R return new RFAMessage({ result: "OK", id: msg.id }); }, - getFileNames: function (msg: RFAMessage): RFAMessage { + getFileNames: function (msg: RFAMessage): RFAMessage { if (!isFileServer(msg.params)) return error("getFileNames message misses parameters", msg); const server = GetServer(msg.params.server); if (server == null) return error("Server hostname invalid", msg); - const fileList: FileContent[] = [ - ...server.textFiles.map((txt): FileContent => { return { filename: txt.filename, content: txt.text } }), - ...server.scripts.map((scr): FileContent => { return { filename: scr.filename, content: scr.code } }) - ]; - - return new RFAMessage({ result: JSON.stringify(fileList), id: msg.id }); - }, - - getAllFiles: function (msg: RFAMessage): RFAMessage { - if (!isFileServer(msg.params)) return error("getAllFiles message misses parameters", msg); - - const server = GetServer(msg.params.server); - if (server == null) return error("Server hostname invalid", msg); - const fileNameList: string[] = [ ...server.textFiles.map((txt): string => txt.filename), ...server.scripts.map((scr): string => scr.filename) @@ -105,6 +91,20 @@ export const RFARequestHandler: Record void | R return new RFAMessage({ result: JSON.stringify(fileNameList), id: msg.id }); }, + getAllFiles: function (msg: RFAMessage): RFAMessage { + if (!isFileServer(msg.params)) return error("getAllFiles message misses parameters", msg); + + const server = GetServer(msg.params.server); + if (server == null) return error("Server hostname invalid", msg); + + const fileList: FileContent[] = [ + ...server.textFiles.map((txt): FileContent => { return { filename: txt.filename, content: txt.text } }), + ...server.scripts.map((scr): FileContent => { return { filename: scr.filename, content: scr.code } }) + ]; + + return new RFAMessage({ result: JSON.stringify(fileList), id: msg.id }); + }, + calculateRam: function (msg: RFAMessage): RFAMessage { if (!isFileLocation(msg.params)) return error("calculateRam message misses parameters", msg); const fileData: FileLocation = msg.params; diff --git a/src/RemoteFileAPI/Remote.ts b/src/RemoteFileAPI/Remote.ts index 3cb689201..311eb8a0b 100644 --- a/src/RemoteFileAPI/Remote.ts +++ b/src/RemoteFileAPI/Remote.ts @@ -13,36 +13,18 @@ export class Remote { this.port = port; } + public stopConnection() : void { + this.connection?.close(); + } + public startConnection() : void { - this.startConnectionRecurse(1, 5); - } - - handleCloseEvent():void { - delete this.connection; - RFALogger.log("Connection closed."); - } - - startConnectionRecurse(retryN : number, retryMax : number) : void { RFALogger.log("Trying to connect."); this.connection = new WebSocket(this.protocol + "://" + this.ipaddr + ":" + this.port); - this.connection.addEventListener("error", (e:Event) => { - if(!this.connection) return; - - // When having trouble connecting, try again. - if (this.connection.readyState === 3) { - RFALogger.log(`Connection lost, retrying (try #${retryN}).`); - if (retryN <= retryMax) this.startConnectionRecurse(retryN + 1, retryMax); - return; - } - - // Else handle the error normally - RFALogger.error(e); - }); - + this.connection.addEventListener("error", (e:Event) => RFALogger.error(e)); this.connection.addEventListener("message", handleMessageEvent); this.connection.addEventListener("open", () => RFALogger.log("Connection established: ", this.ipaddr, ":", this.port)); - this.connection.addEventListener("close", this.handleCloseEvent); + this.connection.addEventListener("close", () => RFALogger.log("Connection closed")); } } diff --git a/src/RemoteFileAPI/RemoteFileAPI.ts b/src/RemoteFileAPI/RemoteFileAPI.ts index 2bd0c894b..ceda66007 100644 --- a/src/RemoteFileAPI/RemoteFileAPI.ts +++ b/src/RemoteFileAPI/RemoteFileAPI.ts @@ -1,16 +1,19 @@ +import { Settings } from "../Settings/Settings"; import { Remote } from "./Remote"; -class RemoteFileAPI { - server : Remote; - constructor(){ - this.server = new Remote("localhost", 12525); - return; - } +let server: Remote; - enable() : void { - this.server.startConnection(); +export function newRemoteFileApiConnection() : void { + if(server == undefined) + server = new Remote("localhost", Settings.RemoteFileApiPort); + else { + server.stopConnection(); + server = new Remote("localhost", Settings.RemoteFileApiPort); + server.startConnection(); } } -export const RFA = new RemoteFileAPI; +export function isRemoteFileApiConnectionLive() : boolean { + return server.connection != undefined && server.connection.readyState == 1; +} diff --git a/src/Settings/Settings.ts b/src/Settings/Settings.ts index c8d1cfd0c..3160dfa3c 100644 --- a/src/Settings/Settings.ts +++ b/src/Settings/Settings.ts @@ -84,6 +84,11 @@ interface IDefaultSettings { */ MaxTerminalCapacity: number; + /** + * Port the Remote File API client will try to connect to. + */ + RemoteFileApiPort: number; + /** * Save the game when you save any file. */ @@ -206,6 +211,7 @@ export const defaultSettings: IDefaultSettings = { MaxLogCapacity: 50, MaxPortCapacity: 50, MaxTerminalCapacity: 500, + RemoteFileApiPort: 12525, SaveGameOnFileSave: true, SuppressBuyAugmentationConfirmation: false, SuppressFactionInvites: false, @@ -248,6 +254,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = { MaxTerminalCapacity: defaultSettings.MaxTerminalCapacity, OwnedAugmentationsOrder: OwnedAugmentationsOrderSetting.AcquirementTime, PurchaseAugmentationsOrder: PurchaseAugmentationsOrderSetting.Default, + RemoteFileApiPort: defaultSettings.RemoteFileApiPort, SaveGameOnFileSave: defaultSettings.SaveGameOnFileSave, SuppressBuyAugmentationConfirmation: defaultSettings.SuppressBuyAugmentationConfirmation, SuppressFactionInvites: defaultSettings.SuppressFactionInvites, diff --git a/src/index.tsx b/src/index.tsx index 661d784b1..83b2692f6 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -5,7 +5,7 @@ import { TTheme as Theme, ThemeEvents, refreshTheme } from "./Themes/ui/Theme"; import { LoadingScreen } from "./ui/LoadingScreen"; import { initElectron } from "./Electron"; -import { RFA } from "./RemoteFileAPI/RemoteFileAPI"; +import { newRemoteFileApiConnection } from "./RemoteFileAPI/RemoteFileAPI"; initElectron(); globalThis["React"] = React; @@ -17,8 +17,7 @@ ReactDOM.render( document.getElementById("root"), ); -console.log("[RFA] Fix this hack ASAP"); -RFA.enable(); +newRemoteFileApiConnection(); function rerender(): void { refreshTheme();