commit 2bd43d870216855801f2138b8e2e73dd8e6bf55f Author: Hri7566 Date: Sat Oct 14 08:23:19 2023 -0400 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a4908c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +node_modules diff --git a/package.json b/package.json new file mode 100644 index 0000000..20523af --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "sync", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "chokidar": "^3.5.3", + "dotenv": "^16.3.1", + "ws": "^8.14.2" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..63ce094 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,141 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + chokidar: + specifier: ^3.5.3 + version: 3.5.3 + dotenv: + specifier: ^16.3.1 + version: 16.3.1 + ws: + specifier: ^8.14.2 + version: 8.14.2 + +packages: + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: false + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: false + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: false + + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + dev: false + + /dotenv@16.3.1: + resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} + engines: {node: '>=12'} + dev: false + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: false + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: false + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: false + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: false + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: false + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: false + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: false + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: false + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: false + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: false + + /ws@8.14.2: + resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==} + 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 + dev: false diff --git a/src/client.js b/src/client.js new file mode 100644 index 0000000..b4b6072 --- /dev/null +++ b/src/client.js @@ -0,0 +1,80 @@ +const WebSocket = require("ws"); +const { EventEmitter } = require("events"); + +class Client extends EventEmitter { + constructor() { + super(); + this.bindEventListeners(); + } + + start() { + if (!this.started) return; + this.started = true; + + this.connect(); + } + + connect() { + this.ws = new WebSocket(process.env.SYNC_URI); + + this.ws.on("open", () => {}); + + this.ws.addEventListener("close", (evt) => { + this.emit("disconnected"); + + setTimeout(() => { + this.connect(); + }, 1000); + }); + + this.ws.addEventListener("error", (evt) => { + this.emit("wserror", evt); + this.ws.close(); + }); + + this.ws.addEventListener("message", (evt) => { + try { + const str = evt.data.toString(); + const msg = JSON.parse(str); + this.emit(msg.m, msg); + } catch (err) { + this.emit("parse_error", err); + } + }); + } + + stop() { + this.started = false; + + if (this.ws) { + this.ws.close(); + delete this.ws; + } + } + + say(msg) { + try { + if (this.isConnected()) this.ws.send(JSON.stringify(msg)); + return true; + } catch (err) { + console.error(err); + return false; + } + } + + log(level, message) { + if (!this.isConnected()) return; + } + + isConnected() { + return this.ws.readyState == WebSocket.OPEN; + } + + bindEventListeners() { + this.on("log", (msg) => { + console.log(`[${msg.level}] ${msg.message}`); + }); + } +} + +module.exports = { Client }; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..da5571d --- /dev/null +++ b/src/index.js @@ -0,0 +1,23 @@ +require("dotenv").config(); + +let isServer; + +process.argv.forEach((val, index, array) => { + if (val == "client") isServer = false; + if (val == "server") isServer = true; +}); + +if (typeof isServer == "undefined") { + // Incorrect arguments + console.log(`Usage: ${process.argv[0]} ${process.argv[1]} [client|server]`); + process.exit(1); +} + +if (isServer) { + const { Server } = require("./server"); + Server.start(); +} else { + const { Client } = require("./client"); + const cl = new Client(); + cl.connect(); +} diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000..b0526b5 --- /dev/null +++ b/src/server.js @@ -0,0 +1,83 @@ +const WebSocket = require("ws"); +const { EventEmitter } = require("events"); + +function broadcast(msg) { + Server.wss.clients.forEach((ws) => { + ws.say(msg); + }); +} + +function log(level, message) { + console.log(`[${level}] ${message}`); + broadcast({ m: "log", level, message }); +} + +class Server { + static start() { + this.wss = new WebSocket.Server({ + port: process.env.PORT || 10911, + }); + + this.wss.on("connection", (ws, req) => { + ws.preauth = new EventEmitter(); + ws.evt = new EventEmitter(); + + ws.say = (msg) => { + ws.send(JSON.stringify(msg)); + }; + + log("INFO", "Client connected at " + req.socket.remoteAddress); + + ws.on("message", (data, isBinary) => { + try { + if (isBinary) return; + + const str = data.toString(); + const converted = JSON.parse(str); + + if (converted.m) { + ws.preauth.emit(converted.m, converted); + + if (ws.authed) { + ws.evt.emit(converted.m, converted); + } + } + } catch (err) { + console.error(err); + } + }); + + ws.preauth.on("hi", (msg) => { + if (!msg.project) return; + + ws.authed = true; + ws.project = msg.project; + }); + + ws.evt.on("log", (msg) => { + if (!msg.level) return; + if (!msg.message) return; + + log(msg.level, `From ${req.socket.remoteAddress}: ${msg.message}`); + }); + + ws.evt.on("broadcast", (msg) => { + if (!msg.msg) return; + if (typeof msg.msg !== "object") return; + if (!msg.msg.m) return; + + broadcast({ + m: "broadcast", + msg: msg.msg, + }); + }); + }); + } + + static stop() { + this.wss.close(); + delete this.wss; + } +} + +module.exports = { Server };