mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-15 15:24:52 +09:00
25 files changed
+154
-99
No files matched your search
+1
-2
@@ -1,2 +1 @@
|
||||
packages/master/static
|
||||
plugins/*/static
|
||||
dist/
|
||||
@@ -46,10 +46,6 @@ static/export
|
||||
saves
|
||||
/temp/
|
||||
|
||||
# Web build
|
||||
/packages/master/static
|
||||
/plugins/*/static
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vs/
|
||||
@@ -161,11 +161,13 @@ which accepts a table and calls `add_lib` for each value in the table.
|
||||
Communicating with Clusterio
|
||||
----------------------------
|
||||
|
||||
There's a poorly to virtuarly undocumented HTTP interface that can be
|
||||
used to communicate with Clusterio to do things like get the status of
|
||||
instances, grab items from the store, run commands on instances, etc,
|
||||
etc. For now you can read the source code, and maybe ask on the Discord
|
||||
for some pointers if you get stuck.
|
||||
Clusterio uses a homebrew protocol based on sending JSON payloads over
|
||||
a WebSocket connection. See [socket.md](socket.md) for the
|
||||
implementation details of it.
|
||||
|
||||
It's also possible to write a plugin for Clusterio that exposes a custom
|
||||
interface over HTTP, WebSocket or any other technology supported by
|
||||
Node.js.
|
||||
|
||||
|
||||
Clusterio Modules
|
||||
@@ -177,7 +179,9 @@ modules that are loaded into the game if you don't need the capabilites
|
||||
of the plugin system. The save patcher puts modules into the `modules`
|
||||
folder of the Factorio save and adds code to `control.lua` to load the
|
||||
module according to the `load` and `require` options to the module.json
|
||||
file.
|
||||
file. Like with scenarios for Clusterio the [event_handler
|
||||
interface](#event_handler-interface) has to be used for any event
|
||||
subscriptions in modules.
|
||||
|
||||
Stand alone modules are placed into the modules folder of Clusterio,
|
||||
plugin modules are located in the module folder of the plugin. In
|
||||
|
||||
@@ -621,7 +621,7 @@ until the connection is re-established. This means that if your plugin
|
||||
sends a lot of events or requests, they can end up being queued up in a
|
||||
buffer for a long time and sent out all at once. To avoid this you
|
||||
should be throtteling and/or stopping your requests/events after `drop`
|
||||
has been raised, and continue back as normal when `connect` is raised.
|
||||
has been raised, and continue back as normal when `resume` is raised.
|
||||
|
||||
|
||||
Collecting Statistics
|
||||
@@ -660,7 +660,7 @@ set it accordingly, for example:
|
||||
);
|
||||
|
||||
// Somewhere in the instance plugin code
|
||||
barMetric.labels(String(this.instance.config.get("instance.id"))).set(someValue);
|
||||
barMetric.labels(String(this.instance.id)).set(someValue);
|
||||
|
||||
Metrics are automatically registered to the default registry, and this
|
||||
default registry is automatically polled by the master server on slaves.
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "root",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "mocha --exit --check-leaks -R spec --recursive test plugins/*/test",
|
||||
"test": "mocha --exit --check-leaks -R spec --recursive test plugins/global_chat/test plugins/research_sync/test plugins/statistics_exporter/test plugins/subspace_storage/test",
|
||||
"fast-test": "FAST_TEST=y npm test",
|
||||
"cover": "nyc npm test",
|
||||
"fast-cover": "FAST_TEST=y nyc npm test",
|
||||
|
||||
+18
-4
@@ -368,7 +368,11 @@ roleCommands.add(new libCommand.Command({
|
||||
yargs.options({
|
||||
"name": { describe: "New name for role", nargs: 1, type: "string" },
|
||||
"description": { describe: "New description for role", nargs: 1, type: "string" },
|
||||
"permissions": { describe: "New permissions for role", array: true, type: "string" },
|
||||
"set-perms": { describe: "Set permissions for role", array: true, type: "string" },
|
||||
"add-perms": { describe: "Add permissions to role", array: true, type: "string", conflicts: "set-perms" },
|
||||
"remove-perms": {
|
||||
describe: "Remove permissions from role", array: true, type: "string", conflicts: "set-perms",
|
||||
},
|
||||
"grant-default": { describe: "Add default permissions to role", nargs: 0, type: "boolean" },
|
||||
});
|
||||
}],
|
||||
@@ -381,8 +385,18 @@ roleCommands.add(new libCommand.Command({
|
||||
if (args.description !== undefined) {
|
||||
role.description = args.description;
|
||||
}
|
||||
if (args.permissions !== undefined) {
|
||||
role.permissions = args.permissions;
|
||||
if (args.addPerms) {
|
||||
role.permissions = role.permissions.concat(args.addPerms);
|
||||
}
|
||||
if (args.removePerms) {
|
||||
let perms = new Set(role.permissions);
|
||||
for (let perm of args.removePerms) {
|
||||
perms.delete(perm);
|
||||
}
|
||||
role.permissions = [...perms];
|
||||
}
|
||||
if (args.setPerms !== undefined) {
|
||||
role.permissions = args.setPerms;
|
||||
}
|
||||
await libLink.messages.updateRole.send(control, role);
|
||||
|
||||
@@ -621,7 +635,7 @@ class Control extends libLink.Link {
|
||||
*/
|
||||
this.plugins = controlPlugins;
|
||||
for (let controlPlugin of controlPlugins.values()) {
|
||||
libPlugin.attachPluginMessages(this, controlPlugin.info, controlPlugin);
|
||||
libPlugin.attachPluginMessages(this, controlPlugin);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,24 +11,9 @@ const ini = require("ini");
|
||||
const rconClient = require("rcon-client");
|
||||
const libErrors = require("@clusterio/lib/errors");
|
||||
const { logger } = require("@clusterio/lib/logging");
|
||||
const { escapeRegExp } = require("@clusterio/lib/helpers");
|
||||
|
||||
|
||||
/**
|
||||
* Escapes text for inclusion in a RegExp
|
||||
*
|
||||
* Adds \ character in front of special meta characters in the passsed in
|
||||
* text so that it can be embedded into a RegExp and only match the text.
|
||||
*
|
||||
* See https://stackoverflow.com/a/9310752
|
||||
*
|
||||
* @param {string} text - Text to escape RegExp meta chars in.
|
||||
* @returns {string} escaped text.
|
||||
* @inner
|
||||
*/
|
||||
function escapeRegExp(text) {
|
||||
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the version of Factorio the datadir is pointing to by
|
||||
* reading the changelog.txt in it.
|
||||
|
||||
@@ -75,9 +75,26 @@ async function readStream(stream) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Escapes text for inclusion in a RegExp
|
||||
*
|
||||
* Adds \ character in front of special meta characters in the passsed in
|
||||
* text so that it can be embedded into a RegExp and only match the text.
|
||||
*
|
||||
* See https://stackoverflow.com/a/9310752
|
||||
*
|
||||
* @param {string} text - Text to escape RegExp meta chars in.
|
||||
* @returns {string} escaped text.
|
||||
*/
|
||||
function escapeRegExp(text) {
|
||||
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
basicType,
|
||||
wait,
|
||||
timeout,
|
||||
readStream,
|
||||
escapeRegExp,
|
||||
};
|
||||
@@ -404,7 +404,7 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
|
||||
}
|
||||
this._startedReconnect = null;
|
||||
this._connected = true;
|
||||
this.emit("connect");
|
||||
this.emit("resume");
|
||||
|
||||
} else if (type === "invalidate") {
|
||||
this._heartbeatInterval = null;
|
||||
|
||||
+26
-19
@@ -148,13 +148,12 @@ class BaseInstancePlugin {
|
||||
/**
|
||||
* Called when an event on the master connection happens
|
||||
*
|
||||
* The event param may be one of connect, drop and close and has the
|
||||
* following meaning:
|
||||
* The event param may be one of connect, drop, resume and close and has
|
||||
* the following meaning:
|
||||
*
|
||||
* ##### connect
|
||||
*
|
||||
* Invoked when a new connection to the master has been established, or
|
||||
* the existing one that have previously been dropped is re-established.
|
||||
* Invoked when a new connection to the master has been established.
|
||||
*
|
||||
* ##### drop
|
||||
*
|
||||
@@ -166,6 +165,11 @@ class BaseInstancePlugin {
|
||||
* memory on the slave and sent all in one go when the connection is
|
||||
* re-established again.
|
||||
*
|
||||
* ##### resume
|
||||
*
|
||||
* Invoked when the connection that had previously dropped is
|
||||
* re-established.
|
||||
*
|
||||
* ##### close
|
||||
*
|
||||
* Invoked when the connection to the master has been closed. This
|
||||
@@ -173,7 +177,7 @@ class BaseInstancePlugin {
|
||||
* send any messages that goes to or via the master server after the
|
||||
* connection has been closed and before a new one is established.
|
||||
*
|
||||
* @param {string} event - one of connect, drop, and close
|
||||
* @param {string} event - one of connect, drop, resume and close
|
||||
*/
|
||||
onMasterConnectionEvent(event) { }
|
||||
|
||||
@@ -293,13 +297,12 @@ class BaseMasterPlugin {
|
||||
/**
|
||||
* Called when an event on a slave connection happens
|
||||
*
|
||||
* The event param may be one of connect, drop and close and has the
|
||||
* following meaning:
|
||||
* The event param may be one of connect, drop, resume and close and has
|
||||
* the following meaning:
|
||||
*
|
||||
* ##### connect
|
||||
*
|
||||
* Invoked when a new connection to the slave has been established, or
|
||||
* an existing one that had previously been dropped is re-established.
|
||||
* Invoked when a new connection to the slave has been established.
|
||||
*
|
||||
* ##### drop
|
||||
*
|
||||
@@ -311,13 +314,18 @@ class BaseMasterPlugin {
|
||||
* memory on the master and sent all in one go when the connection is
|
||||
* re-established again.
|
||||
*
|
||||
* ##### resume
|
||||
*
|
||||
* Invoked when a connection that had previously been dropped is
|
||||
* re-established
|
||||
*
|
||||
* ##### close
|
||||
*
|
||||
* Invoked when the connection to the slave has been closed.
|
||||
*
|
||||
* @param {module:master/master~SlaveConnection} connection -
|
||||
* The connection the event occured on.
|
||||
* @param {string} event - one of connect, drop, and close
|
||||
* @param {string} event - one of connect, drop, resume and close
|
||||
*/
|
||||
onSlaveConnectionEvent(connection, event) { }
|
||||
|
||||
@@ -418,19 +426,18 @@ class BaseControlPlugin {
|
||||
/**
|
||||
* Attach plugin messages
|
||||
*
|
||||
* Attaches all messages defined in the `.message` property of the
|
||||
* `pluginInfo` passed with handlers taken from `pluin`.
|
||||
* Attaches all messages defined in the `.info.message` property of the
|
||||
* `plugin` passed with handlers taken from the `pluin`.
|
||||
*
|
||||
* @param {module:lib/link.Link} link - Link to attach handlers to.
|
||||
* @param {object} pluginInfo - Plugin info object.
|
||||
* @param {module:lib/plugin.BasePlugin} plugin -
|
||||
* The instance of the plugin to use handlers from.
|
||||
* The instance of the plugin attach messages and handlers from.
|
||||
*/
|
||||
function attachPluginMessages(link, pluginInfo, plugin) {
|
||||
let messageDefinitions = pluginInfo.messages || [];
|
||||
function attachPluginMessages(link, plugin) {
|
||||
let messageDefinitions = plugin.info.messages || [];
|
||||
for (let [name, messageFormat] of Object.entries(messageDefinitions)) {
|
||||
if (messageFormat.plugin !== pluginInfo.name) {
|
||||
throw new Error(`Type of ${name} message must start with "${pluginInfo.name}:"`);
|
||||
if (messageFormat.plugin !== plugin.info.name) {
|
||||
throw new Error(`Type of ${name} message must start with "${plugin.info.name}:"`);
|
||||
}
|
||||
|
||||
let handler = name + messageFormat.handlerSuffix;
|
||||
@@ -444,7 +451,7 @@ function attachPluginMessages(link, pluginInfo, plugin) {
|
||||
|
||||
} catch (err) {
|
||||
if (err.code === "MISSING_LINK_HANDLER") {
|
||||
err.plugin = pluginInfo.name;
|
||||
err.plugin = plugin.info.name;
|
||||
err.handler = handler;
|
||||
}
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ function serveWeb(route) {
|
||||
}
|
||||
|
||||
// Set folder to serve static content from (the website)
|
||||
app.use(express.static(path.join(__dirname, "static")));
|
||||
app.use(express.static(path.join(__dirname, "dist", "web")));
|
||||
app.use(express.static("static")); // Used for data export files
|
||||
|
||||
const slaveMappingGauge = new libPrometheus.Gauge(
|
||||
@@ -562,7 +562,7 @@ class BaseConnection extends libLink.Link {
|
||||
super("master", target, connector);
|
||||
libLink.attachAllMessages(this);
|
||||
for (let masterPlugin of masterPlugins.values()) {
|
||||
libPlugin.attachPluginMessages(this, masterPlugin.info, masterPlugin);
|
||||
libPlugin.attachPluginMessages(this, masterPlugin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1168,7 +1168,7 @@ class SlaveConnection extends BaseConnection {
|
||||
}
|
||||
});
|
||||
|
||||
for (let event of ["connect", "drop", "close"]) {
|
||||
for (let event of ["connect", "drop", "resume", "close"]) {
|
||||
// eslint-disable-next-line no-loop-func
|
||||
this.connector.on(event, () => {
|
||||
for (let masterPlugin of masterPlugins.values()) {
|
||||
@@ -1397,7 +1397,7 @@ class WebSocketServerConnector extends libLink.WebSocketBaseConnector {
|
||||
for (let message of this._sendBuffer) {
|
||||
this._socket.send(JSON.stringify(message));
|
||||
}
|
||||
this.emit("connect");
|
||||
this.emit("resume");
|
||||
}
|
||||
|
||||
setTimeout(timeout) {
|
||||
@@ -2025,8 +2025,8 @@ async function startServer(args) {
|
||||
}
|
||||
|
||||
let pluginPackagePath = require.resolve(path.posix.join(pluginInfo.requirePath, "package.json"));
|
||||
let staticPath = path.join(path.dirname(pluginPackagePath), "static");
|
||||
app.use(`/plugin/${pluginInfo.name}`, express.static(staticPath));
|
||||
let webPath = path.join(path.dirname(pluginPackagePath), "dist", "web");
|
||||
app.use(`/plugin/${pluginInfo.name}`, express.static(webPath));
|
||||
}
|
||||
|
||||
// Load plugins
|
||||
|
||||
@@ -9,11 +9,11 @@ module.exports = (env = {}) => merge(common(env), {
|
||||
context: __dirname,
|
||||
entry: "./web/index.jsx",
|
||||
devServer: {
|
||||
contentBase: "./static",
|
||||
contentBase: "./dist/web",
|
||||
},
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "static"),
|
||||
path: path.resolve(__dirname, "dist", "web"),
|
||||
},
|
||||
plugins: [
|
||||
new webpack.container.ModuleFederationPlugin({
|
||||
|
||||
+23
-18
@@ -128,9 +128,15 @@ class Instance extends libLink.Link {
|
||||
this.plugins = new Map();
|
||||
this.config = instanceConfig;
|
||||
|
||||
/**
|
||||
* ID of this instance, equivalenet to `instance.config.get("instance.id")`.
|
||||
* @constant {number}
|
||||
*/
|
||||
this.id = this.config.get("instance.id");
|
||||
|
||||
this.logger = logger.child({
|
||||
instance_id: this.config.get("instance.id"),
|
||||
instance_name: this.config.get("instance.name"),
|
||||
instance_id: this.id,
|
||||
instance_name: this.name,
|
||||
});
|
||||
|
||||
this._configFieldChanged = (group, field, prev) => {
|
||||
@@ -168,7 +174,7 @@ class Instance extends libLink.Link {
|
||||
);
|
||||
|
||||
this.server.on("output", (parsed, line) => {
|
||||
this.logger.log("server", { message: line, instance_id: this.config.get("instance.id"), parsed });
|
||||
this.logger.log("server", { message: line, instance_id: this.id, parsed });
|
||||
|
||||
libPlugin.invokeHook(this.plugins, "onOutput", parsed, line);
|
||||
});
|
||||
@@ -185,7 +191,7 @@ class Instance extends libLink.Link {
|
||||
|
||||
this.server.on("ipc-player_event", event => {
|
||||
libLink.messages.playerEvent.send(this, {
|
||||
instance_id: this.config.get("instance.id"),
|
||||
instance_id: this.id,
|
||||
...event,
|
||||
});
|
||||
libPlugin.invokeHook(this.plugins, "onPlayerEvent", event);
|
||||
@@ -193,7 +199,7 @@ class Instance extends libLink.Link {
|
||||
}
|
||||
|
||||
async sendRcon(message, expectEmpty, plugin = "") {
|
||||
let instanceId = String(this.config.get("instance.id"));
|
||||
let instanceId = String(this.id);
|
||||
let observeDuration = instanceRconCommandDuration.labels(instanceId).startTimer();
|
||||
try {
|
||||
return await this.server.sendRcon(message, expectEmpty);
|
||||
@@ -205,13 +211,13 @@ class Instance extends libLink.Link {
|
||||
|
||||
async _autosave(name) {
|
||||
let stat = await fs.stat(this.path("saves", `${name}.zip`));
|
||||
instanceFactorioAutosaveSize.labels(String(this.config.get("instance.id"))).set(stat.size);
|
||||
instanceFactorioAutosaveSize.labels(String(this.id)).set(stat.size);
|
||||
}
|
||||
|
||||
notifyStatus(status) {
|
||||
this._status = status;
|
||||
libLink.messages.instanceStatusChanged.send(this, {
|
||||
instance_id: this.config.get("instance.id"), status,
|
||||
instance_id: this.id, status,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -238,7 +244,7 @@ class Instance extends libLink.Link {
|
||||
collector instanceof libPrometheus.ValueCollector
|
||||
&& collector.metric.labels.includes("instance_id")
|
||||
) {
|
||||
collector.removeAll({ instance_id: String(this.config.get("instance.id")) });
|
||||
collector.removeAll({ instance_id: String(this.id) });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +260,7 @@ class Instance extends libLink.Link {
|
||||
let instancePlugin = new InstancePluginClass(pluginInfo, this, slave);
|
||||
this.plugins.set(pluginInfo.name, instancePlugin);
|
||||
await instancePlugin.init();
|
||||
libPlugin.attachPluginMessages(this, pluginInfo, instancePlugin);
|
||||
libPlugin.attachPluginMessages(this, instancePlugin);
|
||||
|
||||
this.logger.info(`Loaded plugin ${pluginInfo.name} in ${Date.now() - pluginLoadStarted}ms`);
|
||||
}
|
||||
@@ -285,7 +291,7 @@ class Instance extends libLink.Link {
|
||||
for (let [name, plugin] of this.plugins) {
|
||||
plugins[name] = plugin.info.version;
|
||||
}
|
||||
libLink.messages.instanceInitialized.send(this, { instance_id: this.config.get("instance.id"), plugins });
|
||||
libLink.messages.instanceInitialized.send(this, { instance_id: this.id, plugins });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -543,9 +549,8 @@ class Instance extends libLink.Link {
|
||||
* Update instance information on the Factorio side
|
||||
*/
|
||||
async updateInstanceData() {
|
||||
let name = libLuaTools.escapeString(this.config.get("instance.name"));
|
||||
let id = this.config.get("instance.id");
|
||||
await this.sendRcon(`/sc clusterio_private.update_instance(${id}, "${name}")`, true);
|
||||
let name = libLuaTools.escapeString(this.name);
|
||||
await this.sendRcon(`/sc clusterio_private.update_instance(${this.id}, "${name}")`, true);
|
||||
}
|
||||
|
||||
async updateFactorioSettings(current, previous) {
|
||||
@@ -648,8 +653,8 @@ class Instance extends libLink.Link {
|
||||
let pid = this.server.pid;
|
||||
if (pid) {
|
||||
let stats = await pidusage(pid);
|
||||
instanceFactorioCpuTime.labels(String(this.config.get("instance.id"))).set(stats.ctime / 1000);
|
||||
instanceFactorioMemoryUsage.labels(String(this.config.get("instance.id"))).set(stats.memory);
|
||||
instanceFactorioCpuTime.labels(String(this.id)).set(stats.ctime / 1000);
|
||||
instanceFactorioMemoryUsage.labels(String(this.id)).set(stats.memory);
|
||||
}
|
||||
|
||||
return { results };
|
||||
@@ -830,7 +835,7 @@ class InstanceConnection extends libLink.Link {
|
||||
libLink.attachAllMessages(this);
|
||||
|
||||
for (let pluginInfo of slave.pluginInfos) {
|
||||
libPlugin.attachPluginMessages(this, pluginInfo, null);
|
||||
libPlugin.attachPluginMessages(this, { info: pluginInfo });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -977,7 +982,7 @@ class Slave extends libLink.Link {
|
||||
|
||||
this.pluginInfos = pluginInfos;
|
||||
for (let pluginInfo of pluginInfos) {
|
||||
libPlugin.attachPluginMessages(this, pluginInfo, null);
|
||||
libPlugin.attachPluginMessages(this, { info: pluginInfo });
|
||||
}
|
||||
|
||||
this.config = slaveConfig;
|
||||
@@ -1028,7 +1033,7 @@ class Slave extends libLink.Link {
|
||||
}
|
||||
});
|
||||
|
||||
for (let event of ["connect", "drop", "close"]) {
|
||||
for (let event of ["connect", "drop", "resume", "close"]) {
|
||||
this.connector.on(event, () => {
|
||||
for (let instanceConnection of this.instanceConnections.values()) {
|
||||
libLink.messages.masterConnectionEvent.send(instanceConnection, { event });
|
||||
|
||||
@@ -10,7 +10,7 @@ module.exports = (env = {}) => merge(common(env), {
|
||||
entry: "./web/index.jsx",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "static"),
|
||||
path: path.resolve(__dirname, "dist", "web"),
|
||||
},
|
||||
plugins: [
|
||||
new webpack.container.ModuleFederationPlugin({
|
||||
|
||||
@@ -10,7 +10,7 @@ module.exports = (env = {}) => merge(common(env), {
|
||||
entry: "./web/index.jsx",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "static"),
|
||||
path: path.resolve(__dirname, "dist", "web"),
|
||||
},
|
||||
plugins: [
|
||||
new webpack.container.ModuleFederationPlugin({
|
||||
|
||||
@@ -53,7 +53,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
|
||||
throw new Error(`Error parsing statistics JSON: ${err.message}, content "${string}"`);
|
||||
}
|
||||
|
||||
let instanceId = this.instance.config.get("instance.id");
|
||||
let instanceId = this.instance.id;
|
||||
instanceGameTicksTotal.labels(String(instanceId)).set(stats.game_tick);
|
||||
instancePlayerCount.labels(String(instanceId)).set(stats.player_count);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ module.exports = (env = {}) => merge(common(env), {
|
||||
entry: "./web/index.jsx",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "static"),
|
||||
path: path.resolve(__dirname, "dist", "web"),
|
||||
},
|
||||
plugins: [
|
||||
new webpack.container.ModuleFederationPlugin({
|
||||
|
||||
@@ -39,7 +39,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
|
||||
async provideItems(items) {
|
||||
this.info.messages.place.send(this.instance, {
|
||||
items,
|
||||
instance_id: this.instance.config.get("instance.id"),
|
||||
instance_id: this.instance.id,
|
||||
});
|
||||
|
||||
if (this.instance.config.get("subspace_storage.log_item_transfers")) {
|
||||
@@ -52,7 +52,7 @@ class InstancePlugin extends libPlugin.BaseInstancePlugin {
|
||||
async requestItems(items) {
|
||||
// Request the items all at once
|
||||
let response = await this.info.messages.remove.send(this.instance, {
|
||||
instance_id: this.instance.config.get("instance.id"),
|
||||
instance_id: this.instance.id,
|
||||
items,
|
||||
});
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ module.exports = (env = {}) => merge(common(env), {
|
||||
entry: "./web/index.jsx",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
path: path.resolve(__dirname, "static"),
|
||||
path: path.resolve(__dirname, "dist", "web"),
|
||||
},
|
||||
plugins: [
|
||||
new webpack.container.ModuleFederationPlugin({
|
||||
|
||||
@@ -4,6 +4,7 @@ const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
|
||||
const libLink = require("@clusterio/lib/link");
|
||||
const libUsers = require("@clusterio/lib/users");
|
||||
|
||||
const { slowTest, get, exec, execCtl, sendRcon, getControl, instancesDir } = require("./index");
|
||||
|
||||
@@ -282,16 +283,40 @@ describe("Integration of Clusterio", function() {
|
||||
|
||||
describe("role edit", function() {
|
||||
it("should modify the given role", async function() {
|
||||
let args = "--name new --description \"A new role\" --permissions";
|
||||
let args = "--name new --description \"A new role\" --set-perms";
|
||||
await execCtl(`role edit temp ${args}`);
|
||||
let result = await libLink.messages.listRoles.send(getControl());
|
||||
let newRole = result.list.find(role => role.name === "new");
|
||||
assert.deepEqual(newRole, { id: 5, name: "new", description: "A new role", permissions: [] });
|
||||
});
|
||||
it("should add permissions with --add-perms", async function() {
|
||||
let args = "--name new --add-perms core.slave.list core.instance.list";
|
||||
await execCtl(`role edit new ${args}`);
|
||||
let result = await libLink.messages.listRoles.send(getControl());
|
||||
let newRole = result.list.find(role => role.name === "new");
|
||||
assert.deepEqual(newRole.permissions, ["core.slave.list", "core.instance.list"]);
|
||||
});
|
||||
it("should remove permissions with --remove-perms", async function() {
|
||||
let args = "--name new --remove-perms core.slave.list";
|
||||
await execCtl(`role edit new ${args}`);
|
||||
let result = await libLink.messages.listRoles.send(getControl());
|
||||
let newRole = result.list.find(role => role.name === "new");
|
||||
assert.deepEqual(newRole.permissions, ["core.instance.list"]);
|
||||
});
|
||||
it("should grant default permissions with --grant-default", async function() {
|
||||
await execCtl("role edit new --grant-default");
|
||||
let result = await libLink.messages.listRoles.send(getControl());
|
||||
let newRole = result.list.find(role => role.name === "new");
|
||||
let defaultPermissions = [...libUsers.permissions.values()]
|
||||
.filter(p => p.grantByDefault)
|
||||
.map(p => p.name)
|
||||
;
|
||||
assert.deepEqual(new Set(newRole.permissions), new Set(defaultPermissions));
|
||||
});
|
||||
});
|
||||
|
||||
describe("role delete", function() {
|
||||
it("should modify the given role", async function() {
|
||||
it("should delete the given role", async function() {
|
||||
await execCtl("role delete new");
|
||||
let result = await libLink.messages.listRoles.send(getControl());
|
||||
let newRole = result.list.find(role => role.name === "new");
|
||||
|
||||
@@ -34,17 +34,17 @@ describe("Integration of lib/link", function() {
|
||||
|
||||
it("should reconnect on connection lost", async function() {
|
||||
controlConnector._socket.close(1008, "Test");
|
||||
await events.once(controlConnector, "connect");
|
||||
await events.once(controlConnector, "resume");
|
||||
});
|
||||
|
||||
it("should reconnect on connection terminated", async function() {
|
||||
controlConnector._socket.terminate();
|
||||
await events.once(controlConnector, "connect");
|
||||
await events.once(controlConnector, "resume");
|
||||
});
|
||||
|
||||
it("should reconnect on connection heartbeat timeout", async function() {
|
||||
controlConnector.stopHeartbeat();
|
||||
await events.once(controlConnector, "connect");
|
||||
await events.once(controlConnector, "resume");
|
||||
});
|
||||
|
||||
it("should invalidate on reconnection with bad session token", async function() {
|
||||
|
||||
+2
-1
@@ -23,7 +23,8 @@ describe("lib/helpers", function() {
|
||||
await libHelpers.wait(100);
|
||||
let duration = Number(hrtime() - startNs) / 1e6;
|
||||
|
||||
assert(duration > 90 && duration < 110, `duration waited (${duration}ms) 10% of expected (100ms)`);
|
||||
// On windows the time waited is normally up to 15 ms late with outliers in the 20ms range.
|
||||
assert(duration > 90 && duration < 130, `duration (${duration}ms) not within (90-130)`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+4
-4
@@ -43,24 +43,24 @@ describe("lib/plugin", function() {
|
||||
let mockLink = new libLink.Link("source", "target", new mock.MockConnector());
|
||||
let mockEvent = new libLink.Event({ type: "test:test", links: ["target-source"] });
|
||||
it("should accept pluginInfo without messages", function() {
|
||||
libPlugin.attachPluginMessages(mockLink, {}, null);
|
||||
libPlugin.attachPluginMessages(mockLink, { info: {} });
|
||||
});
|
||||
it("should attach handler for the given message", function() {
|
||||
function mockEventEventHandler() { };
|
||||
libPlugin.attachPluginMessages(
|
||||
mockLink, { name: "test", messages: { mockEvent }}, { mockEventEventHandler }
|
||||
mockLink, { mockEventEventHandler, info: { name: "test", messages: { mockEvent }}}
|
||||
);
|
||||
assert(mockLink._handlers.get("test:test_event"), "handler was not registered");
|
||||
});
|
||||
it("should throw if missing handler for the given message", function() {
|
||||
assert.throws(
|
||||
() => libPlugin.attachPluginMessages(mockLink, { name: "test", messages: { mockEvent }}, {}),
|
||||
() => libPlugin.attachPluginMessages(mockLink, { info: { name: "test", messages: { mockEvent }}}),
|
||||
new Error("Missing mockEventEventHandler on plugin test for test:test_event on source-target link")
|
||||
);
|
||||
});
|
||||
it("should throw if message starts with the wrong prefix", function() {
|
||||
assert.throws(
|
||||
() => libPlugin.attachPluginMessages(mockLink, { name: "foo", messages: { mockEvent }}, {}),
|
||||
() => libPlugin.attachPluginMessages(mockLink, { info: { name: "foo", messages: { mockEvent }}}),
|
||||
new Error('Type of mockEvent message must start with "foo:"')
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
|
||||
const libPluginLoader = require("@clusterio/lib/plugin_loader");
|
||||
const { escapeRegExp } = require("@clusterio/lib/helpers");
|
||||
|
||||
|
||||
describe("lib/plugin_loader", function() {
|
||||
@@ -34,7 +35,7 @@ describe("lib/plugin_loader", function() {
|
||||
it("should throw on missing plugin", async function() {
|
||||
await assert.rejects(
|
||||
libPluginLoader.loadPluginInfos(new Map([["missing", missingPlugin]]), []),
|
||||
new RegExp(`^Error: PluginError: Cannot find module '${missingPlugin}/info'`)
|
||||
new RegExp(`^Error: PluginError: Cannot find module '${escapeRegExp(missingPlugin)}/info'`)
|
||||
);
|
||||
});
|
||||
it("should load test plugin", async function() {
|
||||
|
||||
@@ -103,6 +103,7 @@ class MockInstance extends libLink.Link {
|
||||
this.logger = new MockLogger();
|
||||
this.server = new MockServer();
|
||||
this.name = "test";
|
||||
this.id = 7357;
|
||||
this.mockConfigEntries = new Map([
|
||||
["instance.id", 7357],
|
||||
["factorio.enable_save_patching", true],
|
||||
|
||||
Reference in new issue
Block a user