diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f5acf3a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,69 @@ +{ + "name": "bitburner-remote", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "bitburner-remote", + "version": "1.0.0", + "license": "Unlicense", + "dependencies": { + "cheap-watch": "^1.0.4", + "signal-js": "^3.0.1", + "ws": "^8.8.1" + } + }, + "node_modules/cheap-watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cheap-watch/-/cheap-watch-1.0.4.tgz", + "integrity": "sha512-QR/9FrtRL5fjfUJBhAKCdi0lSRQ3rVRRum3GF9wDKp2TJbEIMGhUEr2yU8lORzm9Isdjx7/k9S0DFDx+z5VGtw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/signal-js/-/signal-js-3.0.1.tgz", + "integrity": "sha512-etMzOR3k2GT8I2AoBUzYHuJ3QipKARXkuM1KxbcOGjgpuGyaXVXWPn61Aezsei9FL34DdfHISMbIu2wZYlIw9w==" + }, + "node_modules/ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + }, + "dependencies": { + "cheap-watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cheap-watch/-/cheap-watch-1.0.4.tgz", + "integrity": "sha512-QR/9FrtRL5fjfUJBhAKCdi0lSRQ3rVRRum3GF9wDKp2TJbEIMGhUEr2yU8lORzm9Isdjx7/k9S0DFDx+z5VGtw==" + }, + "signal-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/signal-js/-/signal-js-3.0.1.tgz", + "integrity": "sha512-etMzOR3k2GT8I2AoBUzYHuJ3QipKARXkuM1KxbcOGjgpuGyaXVXWPn61Aezsei9FL34DdfHISMbIu2wZYlIw9w==" + }, + "ws": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", + "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "requires": {} + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c44ee0e --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "bitburner-remote", + "version": "1.0.0", + "description": "Official implementation of the Bitburner Remote Server", + "type": "module", + "main": "src/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node src/index.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Hoekstraa/bitburner-remote.git" + }, + "keywords": [ + "bitburner", + "rfa", + "remote" + ], + "author": "Zoƫ Hoekstra", + "license": "Unlicense", + "bugs": { + "url": "https://github.com/Hoekstraa/bitburner-remote/issues" + }, + "homepage": "https://github.com/Hoekstraa/bitburner-remote#readme", + "dependencies": { + "cheap-watch": "^1.0.4", + "signal-js": "^3.0.1", + "ws": "^8.8.1" + } +} diff --git a/src/fileWatch.js b/src/fileWatch.js new file mode 100644 index 0000000..9abaf13 --- /dev/null +++ b/src/fileWatch.js @@ -0,0 +1,23 @@ +import CheapWatch from "cheap-watch"; +import * as settings from "./settings.js"; + +function fileFilter(event) { + if(settings.allowedFiletypes.some(extension => event.path.endsWith(extension))) + return true; +} + +export async function setupWatch(signaller) { + + const watch = new CheapWatch({ + dir: settings.scriptsFolder, + filter: fileFilter + }); + + 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(); + + return watch; +} \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..a028f4e --- /dev/null +++ b/src/index.js @@ -0,0 +1,29 @@ +"use strict" +import { setupWatch } from "./fileWatch.js"; +import * as settings from "./settings.js"; +import { setupSocket } from "./webSocket.js"; +import signal from "signal-js"; +import { fileChangeEventToMsg, fileRemovalEventToMsg, requestDefinitionFile } from "./messageGenerators.js"; + +const watch = await setupWatch(signal); +const socket = setupSocket(signal); + +signal.on("fileChange", fileEvent => { + console.log(fileEvent.path + " changed"); + signal.emit(EventType.SendMessage, fileChangeEventToMsg(fileEvent)) +}); + +if(settings.allowDeletingFiles) + signal.on("fileDeletion", fileEvent => + signal.emit(EventType.SendMessage, fileRemovalEventToMsg(fileEvent))); + + +console.log(`Server is ready, running on ${settings.port}!`) + +process.on('SIGINT', function() { + console.log("Shutting down!"); + + watch.close(); + socket.close(); + process.exit(); +}); diff --git a/src/messageGenerators.js b/src/messageGenerators.js new file mode 100644 index 0000000..6455bd4 --- /dev/null +++ b/src/messageGenerators.js @@ -0,0 +1,38 @@ +import * as fs from "fs"; + +let messageCounter = 0; + +export function fileChangeEventToMsg({path}){ + const message = { + "jsonrpc":"2.0", + "method":"pushFile", + "params":{ + "server":"home", + "filename":path, + "content":fs.readFileSync(path).toString() + }, + "id":messageCounter++ + } + return JSON.stringify(message); +} + +export function fileRemovalEventToMsg({path}){ + const message = { + "jsonrpc":"2.0", + "method": "deleteFile", + "params":{ + "filename": path, + }, + "id":messageCounter++ + } + return JSON.stringify(message); +} + +export function requestDefinitionFile(){ + const message = { + "jsonrpc": "2.0", + "method": "getDefinitionFile", + "id":messageCounter++ + } + return JSON.stringify(message); +} \ No newline at end of file diff --git a/src/messages.js b/src/messages.js new file mode 100644 index 0000000..c056770 --- /dev/null +++ b/src/messages.js @@ -0,0 +1,6 @@ +class EventType { + static FileChanged = "FileChanged"; + static FileDeleted = "FileDeleted"; + static MessageReceived = "MessageReceived"; + static SendMessage = "SendMessage"; +} \ No newline at end of file diff --git a/src/settings.js b/src/settings.js new file mode 100644 index 0000000..c2f42ec --- /dev/null +++ b/src/settings.js @@ -0,0 +1,8 @@ +// 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; diff --git a/src/webSocket.js b/src/webSocket.js new file mode 100644 index 0000000..e12612b --- /dev/null +++ b/src/webSocket.js @@ -0,0 +1,17 @@ +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; +}