diff --git a/.nycrc.json b/.nycrc.json index 01e5255..98e0817 100644 --- a/.nycrc.json +++ b/.nycrc.json @@ -1,4 +1,5 @@ { "extends": "@istanbuljs/nyc-config-typescript", - "all": true + "all": true, + "include": ["src/**"] } diff --git a/npx/bitburner-filesync.ts b/npx/bitburner-filesync.ts index abd9301..ae65e4b 100755 --- a/npx/bitburner-filesync.ts +++ b/npx/bitburner-filesync.ts @@ -1,4 +1,9 @@ #!/usr/bin/env -S node --experimental-specifier-resolution=node -import { start } from "../src/index"; +import { start, push, pull } from "../src/index"; +import parser from "yargs-parser"; -await start(); +const argv = parser(process.argv.slice(2)); + +if (argv.push == true) await push(); +else if (argv.pull == true) await pull(); +else await start(); diff --git a/package-lock.json b/package-lock.json index 3a52277..fd8e53b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "convict": "^6.2.3", "signal-js": "^3.0.1", "typescript": "^4.8.4", - "ws": "^8.8.1" + "ws": "^8.8.1", + "yargs-parser": "^21.1.1" }, "bin": { "bitburner-filesync": "dist/npx/bitburner-filesync.js" @@ -1245,6 +1246,14 @@ "node": ">=6" } }, + "node_modules/convict/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" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -1863,6 +1872,15 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -3194,15 +3212,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -3319,11 +3328,11 @@ } }, "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==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs-unparser": { @@ -3341,6 +3350,15 @@ "node": ">=10" } }, + "node_modules/yargs/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==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -4318,6 +4336,13 @@ "requires": { "lodash.clonedeep": "^4.5.0", "yargs-parser": "^20.2.7" + }, + "dependencies": { + "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==" + } } }, "create-require": { @@ -4757,6 +4782,14 @@ "p-map": "^3.0.0", "rimraf": "^3.0.0", "uuid": "^8.3.2" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + } } }, "istanbul-lib-report": { @@ -5744,12 +5777,6 @@ "picocolors": "^1.0.0" } }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true - }, "v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -5831,12 +5858,20 @@ "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" + }, + "dependencies": { + "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==", + "dev": true + } } }, "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==" + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" }, "yargs-unparser": { "version": "2.0.0", diff --git a/package.json b/package.json index c1e4a71..b609e2c 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "convict": "^6.2.3", "signal-js": "^3.0.1", "typescript": "^4.8.4", - "ws": "^8.8.1" + "ws": "^8.8.1", + "yargs-parser": "^21.1.1" }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", diff --git a/src/config.ts b/src/config.ts index 458fdf5..097f1b1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -63,6 +63,13 @@ export let config = convict({ default: false, arg: "pushAllOnConnection", }, + disableWatch: { + doc: "Disable everything besides push/pulling through the external calls.", + format: "Boolean", + env: "BB_MANUAL", + default: false, + arg: "manual", + }, }); export function loadConfig() { diff --git a/src/eventTypes.ts b/src/eventTypes.ts index b2231fc..6edca49 100644 --- a/src/eventTypes.ts +++ b/src/eventTypes.ts @@ -1,7 +1,7 @@ -export class EventType { - static ConnectionMade = "ConnectionMade"; - static FileChanged = "FileChanged"; - static FileDeleted = "FileDeleted"; - static MessageReceived = "MessageReceived"; - static MessageSend = "MessageSend"; +export enum EventType { + ConnectionMade, + FileChanged, + FileDeleted, + MessageReceived, + MessageSend, } diff --git a/src/index.ts b/src/index.ts index c256948..ba0ed8e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import { setupWatch } from "./fileWatch"; import { config, loadConfig } from "./config"; import { setupSocket } from "./networking/webSocket"; +import { WebSocket } from "ws"; import signal from "signal-js"; import { RawData } from "ws"; import { @@ -12,6 +13,8 @@ import { import { EventType } from "./eventTypes"; import { messageHandler } from "./networking/messageHandler"; import { FileEvent } from "./interfaces"; +import { exit } from "process"; +import CheapWatch from "cheap-watch"; export async function start() { loadConfig(); @@ -21,6 +24,18 @@ export async function start() { // Add a handler for received messages. signal.on(EventType.MessageReceived, (msg: RawData) => messageHandler(signal, msg, watch.paths)); + console.log(`Server is ready, running on ${config.get("port")}!`); + + process.on("SIGINT", function () { + console.log("Shutting down!"); + + watch.close(); + socket.close(); + process.exit(); + }); +} + +async function watchMode(watch: CheapWatch) { // Add a handler for when a connection to a game is made. signal.on(EventType.ConnectionMade, () => { console.log("Connection made!"); @@ -52,14 +67,26 @@ export async function start() { signal.on(EventType.FileDeleted, (fileEvent: FileEvent) => signal.emit(EventType.MessageSend, fileRemovalEventToMsg(fileEvent)), ); - - console.log(`Server is ready, running on ${config.get("port")}!`); - - process.on("SIGINT", function () { - console.log("Shutting down!"); - - watch.close(); - socket.close(); - process.exit(); - }); +} + +export async function pull() { + loadConfig(); + const ws = new WebSocket(`ws://localhost:${config.get("port")}`); + + ws.onopen = () => { + ws.send(JSON.stringify({ jsonrpc: "2.0", method: "manualPullFromGame", id: Number.MAX_SAFE_INTEGER })); + ws.close(); + exit(0); + }; +} + +export async function push() { + loadConfig(); + const ws = new WebSocket(`ws://localhost:${config.get("port")}`); + + ws.onopen = () => { + ws.send(JSON.stringify({ jsonrpc: "2.0", method: "manualPushToGame", id: Number.MAX_SAFE_INTEGER })); + ws.close(); + exit(0); + }; } diff --git a/src/networking/messageGenerators.ts b/src/networking/messageGenerators.ts index e8db805..d0cd3f7 100644 --- a/src/networking/messageGenerators.ts +++ b/src/networking/messageGenerators.ts @@ -49,6 +49,17 @@ export function requestFilenames(): Message { }; } +export function requestAllFiles(): Message { + return { + jsonrpc: "2.0", + method: "getAllFiles", + params: { + server: "home", + }, + id: messageCounter++, + }; +} + function addLeadingSlash(path: string): string { const slashes = path.match("/"); if (slashes) return `/${path}`; diff --git a/src/networking/messageHandler.ts b/src/networking/messageHandler.ts index 48d122d..b706b0e 100644 --- a/src/networking/messageHandler.ts +++ b/src/networking/messageHandler.ts @@ -3,9 +3,30 @@ import { Stats, writeFile } from "fs"; import { RawData } from "ws"; import { config } from "../config"; import { EventType } from "../eventTypes"; -import { fileChangeEventToMsg } from "./messageGenerators"; +import { fileChangeEventToMsg, requestAllFiles } from "./messageGenerators"; import type { Signal } from "signal-js"; -import { Message } from "../interfaces"; +import { FileContent, Message } from "../interfaces"; + +/*function deserialize(data: RawData): Message { + const msg = JSON.parse(data.toString()); + + if (typeof msg.jsonrpc !== "string" || msg.jsonrpc !== "2.0" || typeof msg.id !== "number") { + throw Error("Malformed data received."); + } + + const id: number = msg.id; + let request = messageTracker.get(id); + + console.log(msg); + + if (msg.error != null) { + throw Error(msg.error); + } else if (msg.result == null) { + throw Error("Malformed JSON received."); + } + + return { jsonrpc: "2.0", method: !request? msg.method : request.method, result: msg.result, id }; +}*/ function deserialize(data: RawData): Message { const msg = JSON.parse(data.toString()); @@ -15,23 +36,26 @@ function deserialize(data: RawData): Message { } const id: number = msg.id; - const request = messageTracker.get(id); + const request = id === Number.MAX_SAFE_INTEGER ? { method: msg.method } : messageTracker.get(id); - if (typeof request?.method !== "string") { - throw Error("Malformed JSON received."); - } else if (msg.error != null) { + console.log("M:", msg); + console.log("R:", request); + + if (msg.error != null) { throw Error(msg.error); - } else if (msg.result == null) { - throw Error("Malformed JSON received."); } - return { jsonrpc: "2.0", method: request.method, result: msg.result, id }; + return { jsonrpc: "2.0", method: request?.method, result: msg.result, id }; } export function isStringArray(s: Array): s is string[] { return s.every((s) => typeof s === "string"); } +export function isFileContentArray(a: Array): a is FileContent[] { + return a.every((f) => !!f && Object.hasOwn(f, "filename") && Object.hasOwn(f, "content")); +} + export function messageHandler(signaller: Signal, data: RawData, paths: Map) { let incoming; @@ -49,9 +73,9 @@ export function messageHandler(signaller: Signal, data: RawData, paths: Map { if (err) return console.log(err); }); - break; - case "getFileNames": { + + case "getFileNames": if (!Array.isArray(incoming.result) || !isStringArray(incoming.result)) return console.log("Malformed data received."); @@ -61,7 +85,30 @@ export function messageHandler(signaller: Signal, data: RawData, paths: Map { + return { filename: removeLeadingSlash(f.filename), content: f.content }; + }); + + gameFiles2.forEach((f) => console.log(f)); + break; + + case "manualPullFromGame": + console.log("PULL"); + signaller.emit(EventType.MessageSend, requestAllFiles()); + break; + + case "manualPushToGame": + console.log("PUSH"); + paths.forEach((stats, fileName) => { + if (!stats.isDirectory()) signaller.emit(EventType.MessageSend, fileChangeEventToMsg({ path: fileName })); + }); + break; } } diff --git a/src/signals.d.ts b/src/signals.d.ts index 0128df4..eaaaeb3 100644 --- a/src/signals.d.ts +++ b/src/signals.d.ts @@ -1,8 +1,8 @@ declare module "signal-js" { export type Signal = typeof signal; export default class signal { - static on(event: string, callback: (data: T) => void): void; - static emit(event: string, data: T): void; - static trigger(event: string): void; + static on(event: number, callback: (data: T) => void): void; + static emit(event: number, data: T): void; + static trigger(event: number): void; } }