Initial commit

This commit is contained in:
Dorian Niemiec 2024-08-27 07:15:53 +02:00
commit eadd4aa007
13 changed files with 5566 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules
dist

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018-2024 SVR.JS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

29
README Normal file
View file

@ -0,0 +1,29 @@
This repository contains SVR.JS mod starter code and its build system
The mod will work for SVR.JS Nightly-GitNext.
Before doing anything, run "npm install".
To build SVR.JS mod, run "npm run build".
To check SVR.JS mod code for errors with ESLint, run "npm run lint".
To fix and beautify SVR.JS mod code with ESLint and Prettier, run "npm run lint:fix".
To perform unit tests with Jest, run "npm test".
To test the mod:
1. Clone the SVR.JS repository with "git clone https://git.svrjs.org/svrjs/svrjs.git -b next" command.
2. Change the working directory to "svrjs" using "cd svr.js".
3. Build SVR.JS by first running "npm install" and then running "npm run build".
4. Copy the mod into mods directory in the dist directory using "cp ../dist/mod.js dist/mods" (GNU/Linux, Unix, BSD) or "copy ..\dist\mod.js dist\mods" (Windows).
5. Do the necessary mod configuration if the mod requires it.
6. Run SVR.JS by running "npm start".
7. Do some requests to the endpoints covered by the mod.
Structure:
- dist - contains the built SVR.JS mod
- src - contains SVR.JS mod source code
- index.js - entry point
- utils - utility functions
- tests - Jest unit tests
- utils - unit tests for utility functions
- esbuild.config.js - the build script
- eslint.config.js - ESLint configuration
- jest.config.js - Jest configuration
- modInfo.json - SVR.JS mod name and version

11
esbuild.config.js Normal file
View file

@ -0,0 +1,11 @@
const esbuild = require("esbuild");
esbuild.build({
entryPoints: ["src/index.js"],
bundle: true,
outfile: "dist/mod.js", // Mod output file
platform: "node",
target: "es2017",
}).catch((err) => {
throw err;
});

30
eslint.config.js Normal file
View file

@ -0,0 +1,30 @@
const globals = require("globals");
const pluginJs = require("@eslint/js");
const eslintPluginPrettierRecommended = require("eslint-plugin-prettier/recommended");
const jest = require("eslint-plugin-jest");
module.exports = [
{
files: ["**/*.js"],
languageOptions: {
sourceType: "commonjs"
}
},
{
files: ["tests/*.test.js", "tests/**/*.test.js"],
...jest.configs['flat/recommended'],
rules: {
...jest.configs['flat/recommended'].rules,
'jest/prefer-expect-assertions': 'off',
}
},
{
languageOptions: {
globals: {
...globals.node
}
}
},
pluginJs.configs.recommended,
eslintPluginPrettierRecommended
];

5
jest.config.js Normal file
View file

@ -0,0 +1,5 @@
module.exports = {
testEnvironment: 'node',
testMatch: ['**/tests/**/*.test.js'],
verbose: true,
};

4
modInfo.json Normal file
View file

@ -0,0 +1,4 @@
{
"name": "Example mod",
"version": "0.0.0"
}

5128
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

22
package.json Normal file
View file

@ -0,0 +1,22 @@
{
"name": "svrjs-mod-starter",
"version": "0.0.0",
"private": true,
"scripts": {
"build": "node esbuild.config.js",
"lint": "eslint --no-error-on-unmatched-pattern src/**/*.js src/*.js tests/**/*.test.js tests/**/*.js",
"lint:fix": "npm run lint -- --fix",
"test": "jest"
},
"devDependencies": {
"@eslint/js": "^9.9.1",
"esbuild": "^0.23.1",
"eslint": "^9.9.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^28.8.0",
"eslint-plugin-prettier": "^5.2.1",
"globals": "^15.9.0",
"jest": "^29.7.0",
"prettier": "^3.3.3"
}
}

68
src/index.js Normal file
View file

@ -0,0 +1,68 @@
const cluster = require("./utils/clusterBunShim.js"); // Cluster shim for Bun
const { add } = require("./utils/helper.js"); // Require the addition module
const modInfo = require("../modInfo.json"); // SVR.JS mod information
// Exported SVR.JS mod callback
module.exports = (req, res, logFacilities, config, next) => {
if (req.parsedURL.pathname == "/test.svr") {
res.writeHead(200, "OK", {
"Content-Type": "text/plain"
});
res.end("2 + 2 = " + add(2,2));
} else if (req.parsedURL.pathname == "/ping.svr") {
if (!cluster.isWorker) {
// Invoke 500 Internal Server Error status code, if the process is not a worker
res.error(500, new Error("SVR.JS is running single-threaded, so this request is not supported"));
return;
}
// Ping OK message listener
const pingOKListener = (message) => {
if (message == "\x14MODPINGOK") {
process.removeListener("message", pingOKListener);
res.writeHead(200, "OK", {
"Content-Type": "text/plain"
});
res.end("OK");
}
};
// Listen to Ping OK messages
process.on("message", pingOKListener);
// Send Ping message
process.send("\x12MODPING");
} else {
next(); // Invoke other request handlers
}
};
// Exported command
module.exports.commands = {
somecmd: (args, log, passCommand) => {
log("Arguments: " + args.toString()); // Print arguments
passCommand(args, log) // Invoke other command handlers
}
};
// Uncomment, if you want the callback to cover the forward proxy requests
//module.exports.proxySafe = true;
// Uncomment, if you want to handle proxy requests
//module.exports.proxy = (req, socket, head, logFacilities, config, next) => {
// next(); // Invoke other proxy handlers
//}
// IPC listener for main process
// Control messages received by main process begin with 0x12 control character
// Control messages sent by main process begin with 0x14 control character
process.messageEventListeners.push((worker, serverconsole) => {
return (message) => {
if (message == "\x12MODPING") {
// Ping back
worker.send("\x14MODPINGOK");
}
}
});
module.exports.modInfo = modInfo;

233
src/utils/clusterBunShim.js Normal file
View file

@ -0,0 +1,233 @@
const net = require("net");
const os = require("os");
const path = require("path");
let cluster = {};
if (!process.singleThreaded) {
try {
// Import cluster module
cluster = require("cluster");
} catch (err) {
// Clustering is not supported!
}
// Cluster & IPC shim for Bun
cluster.bunShim = function () {
cluster.isMaster = !process.env.NODE_UNIQUE_ID;
cluster.isPrimary = cluster.isMaster;
cluster.isWorker = !cluster.isMaster;
cluster.__shimmed__ = true;
if (cluster.isWorker) {
// Shim the cluster.worker object for worker processes
cluster.worker = {
id: parseInt(process.env.NODE_UNIQUE_ID),
process: process,
isDead: function () {
return false;
},
send: function (message, b, c, d) {
process.send(message, b, c, d);
},
};
if (!process.send) {
// Shim the process.send function for worker processes
// Create a fake IPC server to receive messages
let fakeIPCServer = net.createServer(function (socket) {
let receivedData = "";
socket.on("data", function (data) {
receivedData += data.toString();
});
socket.on("end", function () {
process.emit("message", receivedData);
});
});
fakeIPCServer.listen(
os.platform() === "win32"
? path.join(
"\\\\?\\pipe",
process.dirname,
"temp/.W" + process.pid + ".ipc",
)
: process.dirname + "/temp/.W" + process.pid + ".ipc",
);
process.send = function (message) {
// Create a fake IPC connection to send messages
let fakeIPCConnection = net.createConnection(
os.platform() === "win32"
? path.join(
"\\\\?\\pipe",
process.dirname,
"temp/.P" + process.pid + ".ipc",
)
: process.dirname + "/temp/.P" + process.pid + ".ipc",
function () {
fakeIPCConnection.end(message);
},
);
};
process.removeFakeIPC = function () {
// Close IPC server
process.send = function () {};
fakeIPCServer.close();
};
}
}
// Custom implementation for cluster.fork()
cluster._workersCounter = 1;
cluster.workers = {};
cluster.fork = function (env) {
const child_process = require("child_process");
let newEnvironment = Object.assign(env ? env : process.env);
newEnvironment.NODE_UNIQUE_ID = cluster._workersCounter;
let newArguments = Object.assign(process.argv);
let command = newArguments.shift();
let newWorker = child_process.spawn(command, newArguments, {
env: newEnvironment,
stdio: ["inherit", "inherit", "inherit", "ipc"],
});
newWorker.process = newWorker;
newWorker.isDead = function () {
return newWorker.exitCode !== null || newWorker.killed;
};
newWorker.id = newEnvironment.NODE_UNIQUE_ID;
function checkSendImplementation(worker) {
let sendImplemented = true;
if (
!(
process.versions &&
process.versions.bun &&
process.versions.bun[0] != "0"
)
) {
if (!worker.send) {
sendImplemented = false;
}
let oldLog = console.log;
console.log = function (a, b, c, d, e, f) {
if (
a == "ChildProcess.prototype.send() - Sorry! Not implemented yet"
) {
throw new Error("NOT IMPLEMENTED");
} else {
oldLog(a, b, c, d, e, f);
}
};
try {
worker.send(undefined);
} catch (err) {
if (err.message === "NOT IMPLEMENTED") {
sendImplemented = false;
}
console.log(err);
}
console.log = oldLog;
}
return sendImplemented;
}
if (!checkSendImplementation(newWorker)) {
// Create a fake IPC server for worker process to receive messages
let fakeWorkerIPCServer = net.createServer(function (socket) {
let receivedData = "";
socket.on("data", function (data) {
receivedData += data.toString();
});
socket.on("end", function () {
newWorker.emit("message", receivedData);
});
});
fakeWorkerIPCServer.listen(
os.platform() === "win32"
? path.join(
"\\\\?\\pipe",
process.dirname,
"temp/.P" + newWorker.process.pid + ".ipc",
)
: process.dirname + "/temp/.P" + newWorker.process.pid + ".ipc",
);
// Cleanup when worker process exits
newWorker.on("exit", function () {
fakeWorkerIPCServer.close();
delete cluster.workers[newWorker.id];
});
newWorker.send = function (
message,
fakeParam2,
fakeParam3,
fakeParam4,
tries,
) {
if (!tries) tries = 0;
try {
// Create a fake IPC connection to send messages to worker process
let fakeWorkerIPCConnection = net.createConnection(
os.platform() === "win32"
? path.join(
"\\\\?\\pipe",
process.dirname,
"temp/.W" + newWorker.process.pid + ".ipc",
)
: process.dirname + "/temp/.W" + newWorker.process.pid + ".ipc",
function () {
fakeWorkerIPCConnection.end(message);
},
);
} catch (err) {
if (tries > 50) throw err;
newWorker.send(
message,
fakeParam2,
fakeParam3,
fakeParam4,
tries + 1,
);
}
};
} else {
newWorker.on("exit", function () {
delete cluster.workers[newWorker.id];
});
}
cluster.workers[newWorker.id] = newWorker;
cluster._workersCounter++;
return newWorker;
};
};
if (
process.isBun &&
(cluster.isMaster === undefined ||
(cluster.isMaster && process.env.NODE_UNIQUE_ID))
) {
cluster.bunShim();
}
// Shim cluster.isPrimary field
if (cluster.isPrimary === undefined && cluster.isMaster !== undefined)
cluster.isPrimary = cluster.isMaster;
}
module.exports = cluster;

8
src/utils/helper.js Normal file
View file

@ -0,0 +1,8 @@
// src/utils/helper.js
function add(a, b) {
return a + b;
}
module.exports = {
add,
};

View file

@ -0,0 +1,5 @@
const { add } = require("../../src/utils/helper");
test("adds 1 + 2 to equal 3", () => {
expect(add(1, 2)).toBe(3);
});