Did some changes of the remote api and added documentation

This commit is contained in:
Olivier Gagnon
2022-08-23 17:50:31 -04:00
parent efeb37fa52
commit 3d8616b3a7
13 changed files with 281 additions and 76 deletions

View File

@ -24,6 +24,7 @@ secrets that you've been searching for.
Basic documentation <netscript>
Basic Gameplay <basicgameplay>
Advanced Gameplay <advancedgameplay>
Remote API <remoteapi.rst>
Keyboard Shortcuts <shortcuts>
Game Frozen or Stuck? <gamefrozen>
Guides & Tips <guidesandtips>

203
doc/source/remoteapi.rst Normal file
View File

@ -0,0 +1,203 @@
Remote API
==========
All versions of Bitburner can use websockets to connect to a server.
That server can then perform a number of actions.
Most commonly this is used in conjunction with an external text editor or API
in order to make writing scripts easier, or even use typescript.
This API uses the JSON RCP 2.0 protocol. Inputs are in the following form:
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"method": string,
"params": any
}
Outputs:
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"result": any,
"error": any
}
Methods
-------
`pushFile`
^^^^^^^^^^
Create or update a file.
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"method": "pushFile",
"params": {
filename: string;
content: string;
server: string;
}
}
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"result": "OK"
}
`getFile`
^^^^^^^^^
Read a file and it's content.
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"method": "getFile",
"params": {
filename: string;
server: string;
}
}
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"result": string
}
`deleteFile`
^^^^^^^^^^^^
Delete a file.
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"method": "deleteFile",
"params": {
filename: string;
server: string;
}
}
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"result": "OK"
}
`getFileNames`
^^^^^^^^^^^^^^
List all file names on a server.
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"method": "getFileNames",
"params": {
server: string;
}
}
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"result": string[]
}
`getAllFiles`
^^^^^^^^^^^^^
Get the content of all files on a server.
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"method": "getAllFiles",
"params": {
server: string;
}
}
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"result": {
filename: string,
content: string
}[]
}
`calculateRam`
^^^^^^^^^^^^^^
Calculate the in-game ram cost of a script.
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"method": "calculateRam",
"params": {
filename: string;
server: string;
}
}
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"result": number
}
`getDefinitionFile`
^^^^^^^^^^^^^^^^^^^
Get the definition file of the API.
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"method": "getDefinitionFile"
}
.. code-block:: javascript
{
"jsonrpc": "2.0",
"id": number,
"result": string
}

View File

@ -3,4 +3,5 @@ export enum GameOptionsTab {
INTERFACE,
GAMEPLAY,
MISC,
REMOTE_API,
}

View File

@ -1,5 +1,7 @@
import { Typography } from "@mui/material";
import React, { useState, useEffect } from "react";
import WifiIcon from "@mui/icons-material/Wifi";
import WifiOffIcon from "@mui/icons-material/WifiOff";
interface baubleProps {
isConnected: () => boolean;
@ -15,5 +17,24 @@ export const ConnectionBauble = (props: baubleProps): React.ReactElement => {
return () => clearInterval(timer);
});
return <Typography>{connection ? "Connected" : "Disconnected"}</Typography>;
return (
<>
<Typography>
Status:&nbsp;
<Typography component="span" color={connection ? "primary" : "error"}>
{connection ? (
<>
Online&nbsp;
<WifiIcon />
</>
) : (
<>
Offline&nbsp;
<WifiOffIcon />
</>
)}
</Typography>
</Typography>
</>
);
};

View File

@ -1,4 +1,4 @@
import { MenuItem, Select, SelectChangeEvent, TextField, Tooltip, Typography, Box } from "@mui/material";
import { MenuItem, Select, SelectChangeEvent, TextField, Tooltip, Typography, Link } from "@mui/material";
import React, { useState } from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { isRemoteFileApiConnectionLive, newRemoteFileApiConnection } from "../../RemoteFileAPI/RemoteFileAPI";
@ -370,12 +370,25 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
</>
}
/>
</GameOptionsPage>
),
[GameOptionsTab.REMOTE_API]: (
<GameOptionsPage title="Remote API">
<Typography>
These settings control the Remote API for bitburner. This is typically used to write scripts using an external
text editor and then upload files to the home server.
</Typography>
<Typography>
<Link href="#" target="_blank">
Documentation
</Link>
</Typography>
<ConnectionBauble isConnected={isRemoteFileApiConnectionLive} />
<Tooltip
title={
<Typography>
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.
This port number is used to connect to a Remote API port, please ensure that it matches with your Remote
API server port. Set to 0 to disable the feature.
</Typography>
}
>
@ -383,15 +396,10 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
InputProps={{
startAdornment: (
<Typography color={remoteFileApiPort > 0 && remoteFileApiPort <= 65535 ? "success" : "error"}>
Remote File API port:
Port:&nbsp;
</Typography>
),
endAdornment: (
<Box>
<Button onClick={newRemoteFileApiConnection}>Reconnect</Button>
<ConnectionBauble isConnected={isRemoteFileApiConnectionLive} />
</Box>
),
endAdornment: <Button onClick={newRemoteFileApiConnection}>Connect</Button>,
}}
value={remoteFileApiPort}
onChange={handleRemoteFileApiPortChange}

View File

@ -107,6 +107,7 @@ export const GameOptionsSidebar = (props: IProps): React.ReactElement => {
<SideBarTab sideBarProps={props} tab={GameOptionsTab.GAMEPLAY} tabName="Gameplay" />
<SideBarTab sideBarProps={props} tab={GameOptionsTab.INTERFACE} tabName="Interface" />
<SideBarTab sideBarProps={props} tab={GameOptionsTab.MISC} tabName="Misc" />
<SideBarTab sideBarProps={props} tab={GameOptionsTab.REMOTE_API} tabName="Remote API" />
</List>
</Paper>
<Box

View File

@ -1,12 +1,14 @@
export class RFAMessage {
jsonrpc = "2.0"; // Transmits version of JSON-RPC. Compliance maybe allows some funky interaction with external tools?
public method?: string; // Is defined when it's a request/notification, otherwise undefined
public result?: string; // Is defined when it's a response, otherwise undefined
public result?: string | number; // Is defined when it's a response, otherwise undefined
public params?: FileMetadata; // Optional parameters to method
public error?: string; // Only defined on error
public id?: number; // ID to keep track of request -> response interaction, undefined with notifications, defined with request/response
constructor(obj: { method?: string; result?: string; params?: FileMetadata; error?: string; id?: number } = {}) {
constructor(
obj: { method?: string; result?: string | number; params?: FileMetadata; error?: string; id?: number } = {},
) {
this.method = obj.method;
this.result = obj.result;
this.params = obj.params;

View File

@ -12,12 +12,10 @@ import {
FileLocation,
isFileData,
} from "./MessageDefinitions";
//@ts-ignore: Complaint of import ending with .d.ts
import libSource from "!!raw-loader!../ScriptEditor/NetscriptDefinitions.d.ts";
import { RFALogger } from "./RFALogger";
function error(errorMsg: string, { id }: RFAMessage): RFAMessage {
RFALogger.error((typeof id === "undefined" ? "" : `Request ${id}: `) + errorMsg);
return new RFAMessage({ error: errorMsg, id: id });
}
@ -129,12 +127,11 @@ export const RFARequestHandler: Record<string, (message: RFAMessage) => void | R
if (!script) return error("File doesn't exist", msg);
const ramUsage = script.ramUsage;
return new RFAMessage({ result: String(ramUsage), id: msg.id });
return new RFAMessage({ result: ramUsage, id: msg.id });
},
getDefinitionFile: function (msg: RFAMessage): RFAMessage {
const source = (libSource + "").replace(/export /g, "");
console.log(source);
return new RFAMessage({ result: source, id: msg.id });
},
};

View File

@ -1,27 +0,0 @@
class RemoteFileAPILogger {
_enabled = true;
_prefix = "[RFA]";
_error_prefix = "[RFA-ERROR]";
constructor(enabled: boolean) {
this._enabled = enabled;
}
public error(...message: any[]): void {
if (this._enabled) console.error(this._error_prefix, ...message);
}
public log(...message: any[]): void {
if (this._enabled) console.log(this._prefix, ...message);
}
public disable(): void {
this._enabled = false;
}
public enable(): void {
this._enabled = true;
}
}
export const RFALogger = new RemoteFileAPILogger(true);

View File

@ -1,10 +1,10 @@
import { RFALogger } from "./RFALogger";
import { RFAMessage } from "./MessageDefinitions";
import { RFARequestHandler } from "./MessageHandlers";
import { SnackbarEvents, ToastVariant } from "../ui/React/Snackbar";
export class Remote {
connection?: WebSocket;
protocol = "ws";
static protocol = "ws";
ipaddr: string;
port: number;
@ -18,32 +18,35 @@ export class Remote {
}
public startConnection(): void {
RFALogger.log("Trying to connect.");
this.connection = new WebSocket(this.protocol + "://" + this.ipaddr + ":" + this.port);
const address = Remote.protocol + "://" + this.ipaddr + ":" + this.port;
this.connection = new WebSocket(address);
this.connection.addEventListener("error", (e: Event) => RFALogger.error(e));
this.connection.addEventListener("error", (e: Event) =>
SnackbarEvents.emit(`Error with websocket ${address}, details: ${JSON.stringify(e)}`, ToastVariant.ERROR, 5000),
);
this.connection.addEventListener("message", handleMessageEvent);
this.connection.addEventListener("open", () =>
RFALogger.log("Connection established: ", this.ipaddr, ":", this.port),
SnackbarEvents.emit(
`Remote API connection established on ${this.ipaddr}:${this.port}`,
ToastVariant.SUCCESS,
2000,
),
);
this.connection.addEventListener("close", () =>
SnackbarEvents.emit("Remote API connection closed", ToastVariant.WARNING, 2000),
);
this.connection.addEventListener("close", () => RFALogger.log("Connection closed"));
}
}
function handleMessageEvent(this: WebSocket, e: MessageEvent): void {
const msg: RFAMessage = JSON.parse(e.data);
RFALogger.log("Message received:", msg);
if (msg.method) {
if (!RFARequestHandler[msg.method]) {
const response = new RFAMessage({ error: "Unknown message received", id: msg.id });
this.send(JSON.stringify(response));
return;
}
const response = RFARequestHandler[msg.method](msg);
RFALogger.log("Sending response: ", response);
if (response) this.send(JSON.stringify(response));
} else if (msg.result) RFALogger.log("Somehow retrieved a result message.");
else if (msg.error) RFALogger.error("Received an error from server", msg);
else RFALogger.error("Incorrect Message", msg);
if (!msg.method || !RFARequestHandler[msg.method]) {
const response = new RFAMessage({ error: "Unknown message received", id: msg.id });
this.send(JSON.stringify(response));
return;
}
const response = RFARequestHandler[msg.method](msg);
if (!response) return;
this.send(JSON.stringify(response));
}

View File

@ -4,14 +4,9 @@ import { Remote } from "./Remote";
let server: Remote;
export function newRemoteFileApiConnection(): void {
if (server == undefined) {
server = new Remote("localhost", Settings.RemoteFileApiPort);
server.startConnection();
} else {
server.stopConnection();
server = new Remote("localhost", Settings.RemoteFileApiPort);
server.startConnection();
}
if (server) server.stopConnection();
server = new Remote("localhost", Settings.RemoteFileApiPort);
server.startConnection();
}
export function isRemoteFileApiConnectionLive(): boolean {

View File

@ -211,7 +211,7 @@ export const defaultSettings: IDefaultSettings = {
MaxLogCapacity: 50,
MaxPortCapacity: 50,
MaxTerminalCapacity: 500,
RemoteFileApiPort: 12525,
RemoteFileApiPort: 0,
SaveGameOnFileSave: true,
SuppressBuyAugmentationConfirmation: false,
SuppressFactionInvites: false,

View File

@ -17,7 +17,7 @@ ReactDOM.render(
document.getElementById("root"),
);
newRemoteFileApiConnection();
setTimeout(newRemoteFileApiConnection, 2000);
function rerender(): void {
refreshTheme();