Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 65 additions & 72 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,80 +183,87 @@ function App () {
* @returns {Promise<object>} the config used
*/
this.start = async function () {
const configObj = Utils.loadConfig();
global.config = configObj.fullConf;
const config = global.config;
Utils.checkConfigFile(configObj);

global.defaultModulesDir = config.defaultModulesDir;
defaultModules = require(`${global.root_path}/${global.defaultModulesDir}/defaultmodules`);

Log.setLogLevel(config.logLevel);

env = getEnvVarsAsObj();
// check for deprecated css/custom.css and move it to new location
if ((!fs.existsSync(`${global.root_path}/${env.customCss}`)) && (fs.existsSync(`${global.root_path}/css/custom.css`))) {
try {
fs.renameSync(`${global.root_path}/css/custom.css`, `${global.root_path}/${env.customCss}`);
Log.warn(`WARNING! Your custom css file was moved from ${global.root_path}/css/custom.css to ${global.root_path}/${env.customCss}`);
} catch {
Log.warn("WARNING! Your custom css file is currently located in the css folder. Please move it to the config folder!");
try {
const configObj = Utils.loadConfig();
global.config = configObj.fullConf;
const config = global.config;
Utils.checkConfigFile(configObj);

global.defaultModulesDir = config.defaultModulesDir;
defaultModules = require(`${global.root_path}/${global.defaultModulesDir}/defaultmodules`);

Log.setLogLevel(config.logLevel);

env = getEnvVarsAsObj();
// check for deprecated css/custom.css and move it to new location
if ((!fs.existsSync(`${global.root_path}/${env.customCss}`)) && (fs.existsSync(`${global.root_path}/css/custom.css`))) {
try {
fs.renameSync(`${global.root_path}/css/custom.css`, `${global.root_path}/${env.customCss}`);
Log.warn(`WARNING! Your custom css file was moved from ${global.root_path}/css/custom.css to ${global.root_path}/${env.customCss}`);
} catch {
Log.warn("WARNING! Your custom css file is currently located in the css folder. Please move it to the config folder!");
}
}
}

// get the used module positions
Utils.getModulePositions();

let modules = [];
for (const module of config.modules) {
if (module.disabled) continue;
if (module.module) {
if (Utils.moduleHasValidPosition(module.position) || typeof (module.position) === "undefined") {
// Only add this module to be loaded if it is not a duplicate (repeated instance of the same module)
if (!modules.includes(module.module)) {
modules.push(module.module);
// get the used module positions
Utils.getModulePositions();

let modules = [];
for (const module of config.modules) {
if (module.disabled) continue;
if (module.module) {
if (Utils.moduleHasValidPosition(module.position) || typeof (module.position) === "undefined") {
// Only add this module to be loaded if it is not a duplicate (repeated instance of the same module)
if (!modules.includes(module.module)) {
modules.push(module.module);
}
} else {
Log.warn("Invalid module position found for this configuration:" + `\n${JSON.stringify(module, null, 2)}`);
}
} else {
Log.warn("Invalid module position found for this configuration:" + `\n${JSON.stringify(module, null, 2)}`);
Log.warn("No module name found for this configuration:" + `\n${JSON.stringify(module, null, 2)}`);
}
} else {
Log.warn("No module name found for this configuration:" + `\n${JSON.stringify(module, null, 2)}`);
}
}

setGlobalDispatcher(new Agent({ connect: { timeout: fetch_timeout } }));
setGlobalDispatcher(new Agent({ connect: { timeout: fetch_timeout } }));

await loadModules(modules);
await loadModules(modules);

httpServer = new Server(configObj);
const { app, io } = await httpServer.open();
Log.log("Server started ...");
httpServer = new Server(configObj);
const { app, io } = await httpServer.open();
Log.log("Server started ...");

const nodePromises = [];
for (let nodeHelper of nodeHelpers) {
nodeHelper.setExpressApp(app);
nodeHelper.setSocketIO(io);
const nodePromises = [];
for (let nodeHelper of nodeHelpers) {
nodeHelper.setExpressApp(app);
nodeHelper.setSocketIO(io);

try {
nodePromises.push(nodeHelper.start());
} catch (error) {
Log.error(`Error when starting node_helper for module ${nodeHelper.name}:`);
Log.error(error);
try {
nodePromises.push(nodeHelper.start());
} catch (error) {
Log.error(`Error when starting node_helper for module ${nodeHelper.name}:`);
Log.error(error);
}
}
}

const results = await Promise.allSettled(nodePromises);
const results = await Promise.allSettled(nodePromises);

// Log errors that happened during async node_helper startup
results.forEach((result) => {
if (result.status === "rejected") {
Log.error(result.reason);
}
});
// Log errors that happened during async node_helper startup
results.forEach((result) => {
if (result.status === "rejected") {
Log.error(result.reason);
}
});

Log.log("Sockets connected & modules started ...");
Log.log("Sockets connected & modules started ...");

return global.config;
return global.config;
} catch {
const int32 = new Int32Array(new SharedArrayBuffer(4));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about adding a log line here, so unexpected errors don't get swallowed silently? Something like:

Suggested change
const int32 = new Int32Array(new SharedArrayBuffer(4));
// planned config exits already logged their message before throwing
if (error?.message !== "process.exit:1") {
Log.error("Unexpected error during startup:", error);
}
const int32 = new Int32Array(new SharedArrayBuffer(4));

Otherwise anything else that throws in start() (a broken require, httpServer.open() failing, …) just turns into exit(1) without a stack.

// wait 1000ms before exiting so that child processes (e.g. systeminformation) have some additional time
Atomics.wait(int32, 0, 0, 1000);
process.exit(1);
}
};

/**
Expand Down Expand Up @@ -328,20 +335,6 @@ function App () {
await this.stop();
process.exit(0);
});

/**
*
* @param {number} ms milliseconds to wait
*/
function blockingSleep (ms) {
const int32 = new Int32Array(new SharedArrayBuffer(4));
Atomics.wait(int32, 0, 0, ms);
}

process.on("exit", () => {
// wait before exiting so that child processes (e.g. systeminformation) have some additional time
blockingSleep(1000);
});
}

module.exports = new App();
13 changes: 6 additions & 7 deletions js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,8 @@ const loadConfig = () => {
} else {
Log.error(`Cannot access config file: ${configFilename}\n${error.message}`);
}
process.exit(1);
throw new Error("process.exit:1", { cause: error });
}
return {};
};

/**
Expand Down Expand Up @@ -220,7 +219,7 @@ const checkConfigFile = (configObject) => {
errorMessage += `\nLine ${error.line} column ${error.column}: ${error.message}`;
}
Log.error(errorMessage);
process.exit(1);
throw new Error("process.exit:1");
}
};

Expand All @@ -242,27 +241,27 @@ const validateModulePositions = (data) => {
// `modules` always exists (defaults.js provides a default array), but guard against it being overridden with a non-array value
if (data.modules !== undefined && !Array.isArray(data.modules)) {
Log.error("This module configuration contains errors:\nmodules must be an array");
process.exit(1);
throw new Error("process.exit:1");
}

// Validate each module entry
for (const [index, mod] of (data.modules ?? []).entries()) {
// Each module entry must be an object so we can safely inspect its fields
if (mod === null || typeof mod !== "object" || Array.isArray(mod)) {
Log.error(`This module configuration contains errors:\n${JSON.stringify(mod, null, 2)}\nmodule entry must be an object`);
process.exit(1);
throw new Error("process.exit:1");
}

// `module` (the module name) is required and must be a string
if (typeof mod.module !== "string") {
Log.error(`This module configuration contains errors:\n${JSON.stringify(mod, null, 2)}\nmodule: must be a string`);
process.exit(1);
throw new Error("process.exit:1");
}

// `position` is optional, but must be a string when provided
if (mod.position !== undefined && typeof mod.position !== "string") {
Log.error(`This module configuration contains errors:\n${JSON.stringify(mod, null, 2)}\nposition: must be a string`);
process.exit(1);
throw new Error("process.exit:1");
}

// `position` is optional, but when set it must match a known region
Expand Down