🦭 Seal of approval for regular use
This commit is contained in:
parent
7c571687bf
commit
10f274ed52
13 changed files with 257 additions and 61 deletions
4
npx/bitburner-remote.js
Executable file
4
npx/bitburner-remote.js
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env node
|
||||
import {start} from "../src/index.js"
|
||||
|
||||
await start();
|
52
package-lock.json
generated
52
package-lock.json
generated
|
@ -1,17 +1,21 @@
|
|||
{
|
||||
"name": "bitburner-remote",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.6",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bitburner-remote",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.6",
|
||||
"license": "Unlicense",
|
||||
"dependencies": {
|
||||
"cheap-watch": "^1.0.4",
|
||||
"convict": "^6.2.3",
|
||||
"signal-js": "^3.0.1",
|
||||
"ws": "^8.8.1"
|
||||
},
|
||||
"bin": {
|
||||
"bitburner-remote": "npx/bitburner-remote.js"
|
||||
}
|
||||
},
|
||||
"node_modules/cheap-watch": {
|
||||
|
@ -22,6 +26,23 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/convict": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/convict/-/convict-6.2.3.tgz",
|
||||
"integrity": "sha512-mTY04Qr7WrqiXifdeUYXr4/+Te4hPFWDvz6J2FVIKCLc2XBhq63VOSSYAKJ+unhZAYOAjmEdNswTOeHt7s++pQ==",
|
||||
"dependencies": {
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"yargs-parser": "^20.2.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
|
||||
},
|
||||
"node_modules/signal-js": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/signal-js/-/signal-js-3.0.1.tgz",
|
||||
|
@ -46,6 +67,14 @@
|
|||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "20.2.9",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
|
||||
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -54,6 +83,20 @@
|
|||
"resolved": "https://registry.npmjs.org/cheap-watch/-/cheap-watch-1.0.4.tgz",
|
||||
"integrity": "sha512-QR/9FrtRL5fjfUJBhAKCdi0lSRQ3rVRRum3GF9wDKp2TJbEIMGhUEr2yU8lORzm9Isdjx7/k9S0DFDx+z5VGtw=="
|
||||
},
|
||||
"convict": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/convict/-/convict-6.2.3.tgz",
|
||||
"integrity": "sha512-mTY04Qr7WrqiXifdeUYXr4/+Te4hPFWDvz6J2FVIKCLc2XBhq63VOSSYAKJ+unhZAYOAjmEdNswTOeHt7s++pQ==",
|
||||
"requires": {
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"yargs-parser": "^20.2.7"
|
||||
}
|
||||
},
|
||||
"lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
|
||||
},
|
||||
"signal-js": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/signal-js/-/signal-js-3.0.1.tgz",
|
||||
|
@ -64,6 +107,11 @@
|
|||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz",
|
||||
"integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==",
|
||||
"requires": {}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "20.2.9",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
|
||||
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
{
|
||||
"name": "bitburner-remote",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.7",
|
||||
"description": "Official implementation of the Bitburner Remote Server",
|
||||
"type": "module",
|
||||
"main": "src/index.js",
|
||||
"bin": "./npx/bitburner-remote.js",
|
||||
"main": "./npx/bitburner-remote.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node src/index.js"
|
||||
|
@ -25,6 +26,7 @@
|
|||
"homepage": "https://github.com/Hoekstraa/bitburner-remote#readme",
|
||||
"dependencies": {
|
||||
"cheap-watch": "^1.0.4",
|
||||
"convict": "^6.2.3",
|
||||
"signal-js": "^3.0.1",
|
||||
"ws": "^8.8.1"
|
||||
}
|
||||
|
|
74
src/config.js
Normal file
74
src/config.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
import convict from "convict";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
// Define a schema
|
||||
export let config = convict({
|
||||
allowedFiletypes: {
|
||||
doc: 'Filetypes that are synchronized to the game.',
|
||||
format: 'Array',
|
||||
default: [".js", ".script", ".txt"]
|
||||
},
|
||||
allowDeletingFiles: {
|
||||
doc: 'Allow deleting files in game if they get deleted off disk.',
|
||||
format: 'Boolean',
|
||||
default: false
|
||||
},
|
||||
port: {
|
||||
doc: 'The port to bind to.',
|
||||
format: 'Number',
|
||||
default: 12525,
|
||||
env: 'BB_PORT',
|
||||
arg: 'port'
|
||||
},
|
||||
scriptsFolder: {
|
||||
doc: 'The to be synchronized folder.',
|
||||
format: 'String',
|
||||
default: '.',
|
||||
env: 'BB_SCRIPTFOLDER',
|
||||
arg: "folder"
|
||||
},
|
||||
quiet: {
|
||||
doc: 'Log less internal events to stdout.',
|
||||
format: 'Boolean',
|
||||
env: 'BB_VERBOSE',
|
||||
default: false
|
||||
},
|
||||
dry: {
|
||||
doc: 'Only print the files to be synchronised.',
|
||||
format: 'Boolean',
|
||||
env: 'BB_DRY',
|
||||
default: false
|
||||
},
|
||||
definitionFile: {
|
||||
update: {
|
||||
doc: 'Automatically pull the definition file from the game.',
|
||||
format: 'Boolean',
|
||||
env: 'BB_UPDATE_DEF',
|
||||
default: false
|
||||
},
|
||||
location: {
|
||||
doc: 'Location/name of where the definition file gets placed.',
|
||||
format: 'String',
|
||||
env: 'BB_LOCATION_DEF',
|
||||
default: "./NetScriptDefinitions.d.ts"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export function loadConfig() {
|
||||
const configFile = "filesync.json";
|
||||
if (existsSync(configFile)) {
|
||||
try {
|
||||
config.loadFile(configFile);
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to load configuration file at ${configFile}: ${e}`);
|
||||
}
|
||||
} else if (!config.get("quiet")) {
|
||||
console.log("No configuration file found.")
|
||||
}
|
||||
|
||||
// Perform validation
|
||||
config.validate({ allowed: 'strict' });
|
||||
}
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
class EventType {
|
||||
export class EventType {
|
||||
static FileChanged = "FileChanged";
|
||||
static FileDeleted = "FileDeleted";
|
||||
static MessageReceived = "MessageReceived";
|
||||
static SendMessage = "SendMessage";
|
||||
static MessageSend = "MessageSend";
|
||||
}
|
|
@ -1,23 +1,32 @@
|
|||
import CheapWatch from "cheap-watch";
|
||||
import * as settings from "./settings.js";
|
||||
import {config} from "./config.js";
|
||||
import {EventType} from "./eventTypes.js";
|
||||
import {resolve } from "path";
|
||||
|
||||
function fileFilter(event) {
|
||||
if(settings.allowedFiletypes.some(extension => event.path.endsWith(extension)))
|
||||
if(config.get("allowedFiletypes").some(extension => event.path.endsWith(extension)))
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function setupWatch(signaller) {
|
||||
|
||||
const watch = new CheapWatch({
|
||||
dir: settings.scriptsFolder,
|
||||
dir: config.get("scriptsFolder"),
|
||||
filter: fileFilter
|
||||
});
|
||||
|
||||
if(!config.get("quiet")) console.log("Watching folder", resolve(config.get("scriptsFolder")))
|
||||
|
||||
watch.on('+', fileEvent => signaller.emit(EventType.FileChanged, fileEvent));
|
||||
watch.on('-', fileEvent => signaller.emit(EventType.FileDeleted, fileEvent));
|
||||
|
||||
// Wait 'till filewatcher is ready to go
|
||||
await watch.init();
|
||||
|
||||
if(config.get("dry")) {
|
||||
console.log("Watch would've synchronised:\n", watch.paths)
|
||||
process.exit();
|
||||
}
|
||||
|
||||
return watch;
|
||||
}
|
44
src/index.js
44
src/index.js
|
@ -1,29 +1,35 @@
|
|||
"use strict"
|
||||
import { setupWatch } from "./fileWatch.js";
|
||||
import * as settings from "./settings.js";
|
||||
import { setupSocket } from "./webSocket.js";
|
||||
import { config, loadConfig } from "./config.js";
|
||||
import { setupSocket } from "./networking/webSocket.js";
|
||||
import signal from "signal-js";
|
||||
import { fileChangeEventToMsg, fileRemovalEventToMsg, requestDefinitionFile } from "./messageGenerators.js";
|
||||
import { fileChangeEventToMsg, fileRemovalEventToMsg, requestDefinitionFile } from "./networking/messageGenerators.js";
|
||||
import { EventType } from "./eventTypes.js";
|
||||
import { messageHandler } from "./networking/messageHandler.js";
|
||||
|
||||
const watch = await setupWatch(signal);
|
||||
const socket = setupSocket(signal);
|
||||
export async function start() {
|
||||
loadConfig();
|
||||
const watch = await setupWatch(signal);
|
||||
const socket = setupSocket(signal);
|
||||
|
||||
signal.on("fileChange", fileEvent => {
|
||||
console.log(fileEvent.path + " changed");
|
||||
signal.emit(EventType.SendMessage, fileChangeEventToMsg(fileEvent))
|
||||
});
|
||||
signal.on(EventType.MessageReceived, msg => messageHandler(msg));
|
||||
|
||||
if(settings.allowDeletingFiles)
|
||||
signal.on("fileDeletion", fileEvent =>
|
||||
signal.emit(EventType.SendMessage, fileRemovalEventToMsg(fileEvent)));
|
||||
signal.on(EventType.FileChanged, fileEvent => {
|
||||
if (!config.get("quiet")) console.log(fileEvent.path + " changed");
|
||||
signal.emit(EventType.MessageSend, fileChangeEventToMsg(fileEvent))
|
||||
});
|
||||
|
||||
if (config.get("allowDeletingFiles"))
|
||||
signal.on(EventType.FileDeleted, fileEvent =>
|
||||
signal.emit(EventType.MessageSend, fileRemovalEventToMsg(fileEvent)));
|
||||
|
||||
console.log(`Server is ready, running on ${settings.port}!`)
|
||||
console.log(`Server is ready, running on ${config.get("port")}!`)
|
||||
|
||||
process.on('SIGINT', function() {
|
||||
console.log("Shutting down!");
|
||||
process.on('SIGINT', function () {
|
||||
console.log("Shutting down!");
|
||||
|
||||
watch.close();
|
||||
socket.close();
|
||||
process.exit();
|
||||
});
|
||||
watch.close();
|
||||
socket.close();
|
||||
process.exit();
|
||||
});
|
||||
}
|
|
@ -1,23 +1,24 @@
|
|||
import * as fs from "fs";
|
||||
import {readFileSync} from "fs";
|
||||
import {config} from "../config.js";
|
||||
import {join} from "path";
|
||||
|
||||
let messageCounter = 0;
|
||||
|
||||
export function fileChangeEventToMsg({path}){
|
||||
const message = {
|
||||
return {
|
||||
"jsonrpc":"2.0",
|
||||
"method":"pushFile",
|
||||
"params":{
|
||||
"server":"home",
|
||||
"filename":path,
|
||||
"content":fs.readFileSync(path).toString()
|
||||
"content":readFileSync(join(config.get("scriptsFolder"), path)).toString()
|
||||
},
|
||||
"id":messageCounter++
|
||||
}
|
||||
return JSON.stringify(message);
|
||||
}
|
||||
|
||||
export function fileRemovalEventToMsg({path}){
|
||||
const message = {
|
||||
return {
|
||||
"jsonrpc":"2.0",
|
||||
"method": "deleteFile",
|
||||
"params":{
|
||||
|
@ -25,14 +26,12 @@ export function fileRemovalEventToMsg({path}){
|
|||
},
|
||||
"id":messageCounter++
|
||||
}
|
||||
return JSON.stringify(message);
|
||||
}
|
||||
|
||||
export function requestDefinitionFile(){
|
||||
const message = {
|
||||
return {
|
||||
"jsonrpc": "2.0",
|
||||
"method": "getDefinitionFile",
|
||||
"id":messageCounter++
|
||||
}
|
||||
return JSON.stringify(message);
|
||||
}
|
26
src/networking/messageHandler.js
Normal file
26
src/networking/messageHandler.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { messageTracker } from "./messageTracker.js";
|
||||
import {writeFile} from "fs";
|
||||
import { config } from "../config.js";
|
||||
|
||||
export function messageHandler(msg) {
|
||||
let incoming;
|
||||
|
||||
try {incoming = JSON.parse(msg.toString());}
|
||||
catch {return;}
|
||||
console.log(incoming)
|
||||
if (incoming.id == undefined) return;
|
||||
|
||||
if (incoming.result) {
|
||||
const request = messageTracker.get(incoming.id);
|
||||
console.log(messageTracker.data);
|
||||
console.log("REQUEST: ", request);
|
||||
if (request.method &&
|
||||
request.method == "getDefinitionFile"
|
||||
&& incoming.result) {
|
||||
writeFile(config.get("definitionFile").location, incoming.result, (err) => {
|
||||
if (err) return console.log(err);
|
||||
console.log("wrote definition")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
19
src/networking/messageTracker.js
Normal file
19
src/networking/messageTracker.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
class MessageTracker {
|
||||
data = new Map()
|
||||
#maxLength = 200
|
||||
|
||||
push(msg) {
|
||||
this.data.set(msg.id, msg);
|
||||
|
||||
if (this.data.size > this.#maxLength){
|
||||
const [firstKey] = map.keys();
|
||||
this.data.delete(firstKey);
|
||||
}
|
||||
}
|
||||
|
||||
get(index) {
|
||||
return this.data.get(index);
|
||||
}
|
||||
}
|
||||
|
||||
export const messageTracker = new MessageTracker();
|
34
src/networking/webSocket.js
Normal file
34
src/networking/webSocket.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { WebSocketServer } from 'ws';
|
||||
import {config} from "../config.js";
|
||||
import {EventType} from "../eventTypes.js"
|
||||
import { requestDefinitionFile } from './messageGenerators.js';
|
||||
import {messageTracker} from "./messageTracker.js"
|
||||
|
||||
export function setupSocket(signaller){
|
||||
|
||||
const wss = new WebSocketServer({ port: config.get("port") });
|
||||
|
||||
wss.on('connection', function connection(ws) {
|
||||
|
||||
function sendMessage(msg) {
|
||||
messageTracker.push(msg);
|
||||
ws.send(JSON.stringify(msg));
|
||||
}
|
||||
|
||||
ws.on('message', (msg) => {
|
||||
signaller.emit(EventType.MessageReceived, msg);
|
||||
});
|
||||
|
||||
signaller.on(EventType.MessageSend, msg => {
|
||||
sendMessage(msg);
|
||||
});
|
||||
|
||||
if (config.get("definitionFile").update) {
|
||||
sendMessage(requestDefinitionFile());
|
||||
}
|
||||
|
||||
console.log("Connection made!");
|
||||
});
|
||||
|
||||
return wss;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
// Folder your scripts are located in.
|
||||
export const scriptsFolder = "./";
|
||||
// Allowed filetypes to synchronize.
|
||||
export const allowedFiletypes = [".js", ".script", ".txt"];
|
||||
|
||||
export const allowDeletingFiles = false;
|
||||
// Port the websocket is set up on.
|
||||
export const port = 12525;
|
|
@ -1,17 +0,0 @@
|
|||
import { WebSocketServer } from 'ws';
|
||||
import * as settings from "./settings.js";
|
||||
|
||||
export function setupSocket(signaller){
|
||||
|
||||
const wss = new WebSocketServer({ port: settings.port });
|
||||
|
||||
wss.on('connection', function connection(ws) {
|
||||
ws.on('message', function message(msg) {
|
||||
signaller.emit(EventType.MessageReceived, msg);
|
||||
});
|
||||
|
||||
signaller.on(EventType.SendMessage, data => ws.send(data));
|
||||
});
|
||||
|
||||
return wss;
|
||||
}
|
Loading…
Add table
Reference in a new issue