2022-01-26 23:09:26 +01:00
/ * M a g i c M i r r o r ²
2016-04-08 22:16:22 +02:00
* The Core App ( Server )
*
2020-04-28 23:05:28 +02:00
* By Michael Teeuw https : //michaelteeuw.nl
2016-04-08 22:16:22 +02:00
* MIT Licensed .
* /
2021-02-18 19:14:53 +01:00
// Alias modules mentioned in package.js under _moduleAliases.
require ( "module-alias/register" ) ;
2021-01-05 19:35:11 +01:00
const fs = require ( "fs" ) ;
const path = require ( "path" ) ;
2021-02-18 19:14:53 +01:00
const Log = require ( "logger" ) ;
2021-01-05 19:35:11 +01:00
const Server = require ( ` ${ _ _dirname } /server ` ) ;
const Utils = require ( ` ${ _ _dirname } /utils ` ) ;
const defaultModules = require ( ` ${ _ _dirname } /../modules/default/defaultmodules ` ) ;
2016-04-08 22:16:22 +02:00
2016-10-13 16:42:15 +02:00
// Get version number.
2021-01-05 19:35:11 +01:00
global . version = require ( ` ${ _ _dirname } /../package.json ` ) . version ;
2020-05-11 07:25:42 +02:00
Log . log ( "Starting MagicMirror: v" + global . version ) ;
2016-10-13 16:42:15 +02:00
2016-12-27 14:31:35 -03:00
// global absolute root path
2021-01-05 19:35:11 +01:00
global . root _path = path . resolve ( ` ${ _ _dirname } /../ ` ) ;
2016-12-27 14:31:35 -03:00
2017-01-24 02:59:20 -03:00
if ( process . env . MM _CONFIG _FILE ) {
global . configuration _file = process . env . MM _CONFIG _FILE ;
}
2017-02-24 18:52:33 -03:00
// FIXME: Hotfix Pull Request
// https://github.com/MichMich/MagicMirror/pull/673
2017-01-31 21:58:46 -03:00
if ( process . env . MM _PORT ) {
global . mmPort = process . env . MM _PORT ;
}
2016-04-19 11:25:50 +02:00
// The next part is here to prevent a major exception when there
// is no internet connection. This could probable be solved better.
2016-05-03 19:09:38 -04:00
process . on ( "uncaughtException" , function ( err ) {
2020-06-01 16:40:20 +02:00
Log . error ( "Whoops! There was an uncaught exception..." ) ;
2020-05-11 07:25:42 +02:00
Log . error ( err ) ;
2022-01-26 23:47:51 +01:00
Log . error ( "MagicMirror² will not quit, but it might be a good idea to check why this happened. Maybe no internet connection?" ) ;
2020-06-01 16:40:20 +02:00
Log . error ( "If you think this really is an issue, please open an issue on GitHub: https://github.com/MichMich/MagicMirror/issues" ) ;
2016-04-19 11:25:50 +02:00
} ) ;
2020-07-27 20:10:31 +02:00
/ * *
* The core app .
*
* @ class
2016-04-08 22:16:22 +02:00
* /
2021-01-05 19:35:11 +01:00
function App ( ) {
let nodeHelpers = [ ] ;
2021-09-15 21:09:31 +02:00
let httpServer ;
2016-04-08 22:16:22 +02:00
2020-07-27 20:10:31 +02:00
/ * *
2021-01-20 22:44:37 +01:00
* Loads the config file . Combines it with the defaults , and runs the
2020-07-27 20:10:31 +02:00
* callback with the found config as argument .
2016-04-08 22:16:22 +02:00
*
2020-07-27 20:10:31 +02:00
* @ param { Function } callback Function to be called after loading the config
2016-04-08 22:16:22 +02:00
* /
2021-01-05 19:35:11 +01:00
function loadConfig ( callback ) {
2020-05-11 07:25:42 +02:00
Log . log ( "Loading config ..." ) ;
2021-01-05 19:35:11 +01:00
const defaults = require ( ` ${ _ _dirname } /defaults ` ) ;
2017-01-24 02:59:20 -03:00
// For this check proposed to TestSuite
// https://forum.magicmirror.builders/topic/1456/test-suite-for-magicmirror/8
2021-01-05 19:39:31 +01:00
const configFilename = path . resolve ( global . configuration _file || ` ${ global . root _path } /config/config.js ` ) ;
2017-01-24 02:59:20 -03:00
2016-04-08 22:16:22 +02:00
try {
fs . accessSync ( configFilename , fs . F _OK ) ;
2021-01-05 19:35:11 +01:00
const c = require ( configFilename ) ;
2016-12-14 18:54:44 +01:00
checkDeprecatedOptions ( c ) ;
2021-01-05 19:35:11 +01:00
const config = Object . assign ( defaults , c ) ;
2016-04-08 22:16:22 +02:00
callback ( config ) ;
} catch ( e ) {
2019-06-05 08:48:22 +02:00
if ( e . code === "ENOENT" ) {
2020-05-11 07:25:42 +02:00
Log . error ( Utils . colors . error ( "WARNING! Could not find config file. Please create one. Starting with default configuration." ) ) ;
2016-08-05 19:18:31 +02:00
} else if ( e instanceof ReferenceError || e instanceof SyntaxError ) {
2021-01-05 19:35:11 +01:00
Log . error ( Utils . colors . error ( ` WARNING! Could not validate config file. Starting with default configuration. Please correct syntax errors at or above this line: ${ e . stack } ` ) ) ;
2016-08-05 19:18:31 +02:00
} else {
2021-01-05 19:35:11 +01:00
Log . error ( Utils . colors . error ( ` WARNING! Could not load config file. Starting with default configuration. Error found: ${ e } ` ) ) ;
2016-08-05 19:18:31 +02:00
}
2017-03-05 22:05:54 -03:00
callback ( defaults ) ;
2016-04-08 22:16:22 +02:00
}
2021-01-05 19:35:11 +01:00
}
2016-04-08 22:16:22 +02:00
2020-07-27 20:10:31 +02:00
/ * *
* Checks the config for deprecated options and throws a warning in the logs
* if it encounters one option from the deprecated . js list
*
* @ param { object } userConfig The user config
* /
2021-01-05 19:35:11 +01:00
function checkDeprecatedOptions ( userConfig ) {
const deprecated = require ( ` ${ global . root _path } /js/deprecated ` ) ;
const deprecatedOptions = deprecated . configs ;
2016-12-14 18:54:44 +01:00
2021-01-05 20:04:02 +01:00
const usedDeprecated = deprecatedOptions . filter ( ( option ) => userConfig . hasOwnProperty ( option ) ) ;
2016-12-14 18:54:44 +01:00
if ( usedDeprecated . length > 0 ) {
2021-01-05 19:35:11 +01:00
Log . warn ( Utils . colors . warn ( ` WARNING! Your config is using deprecated options: ${ usedDeprecated . join ( ", " ) } . Check README and CHANGELOG for more up-to-date ways of getting the same functionality. ` ) ) ;
2016-12-14 18:54:44 +01:00
}
2021-01-05 19:35:11 +01:00
}
2016-12-14 18:54:44 +01:00
2020-07-27 20:10:31 +02:00
/ * *
2016-04-08 22:16:22 +02:00
* Loads a specific module .
*
2020-07-27 20:10:31 +02:00
* @ param { string } module The name of the module ( including subpath ) .
* @ param { Function } callback Function to be called after loading
2016-04-08 22:16:22 +02:00
* /
2021-01-05 19:35:11 +01:00
function loadModule ( module , callback ) {
const elements = module . split ( "/" ) ;
const moduleName = elements [ elements . length - 1 ] ;
let moduleFolder = ` ${ _ _dirname } /../modules/ ${ module } ` ;
2016-04-08 22:16:22 +02:00
2021-01-05 19:35:11 +01:00
if ( defaultModules . includes ( moduleName ) ) {
moduleFolder = ` ${ _ _dirname } /../modules/default/ ${ module } ` ;
2016-04-08 22:16:22 +02:00
}
2021-01-05 19:35:11 +01:00
const helperPath = ` ${ moduleFolder } /node_helper.js ` ;
2016-04-08 22:16:22 +02:00
2021-01-05 19:35:11 +01:00
let loadHelper = true ;
2016-04-08 22:16:22 +02:00
try {
fs . accessSync ( helperPath , fs . R _OK ) ;
} catch ( e ) {
2021-01-05 19:35:11 +01:00
loadHelper = false ;
Log . log ( ` No helper found for module: ${ moduleName } . ` ) ;
2016-04-08 22:16:22 +02:00
}
2021-01-05 19:35:11 +01:00
if ( loadHelper ) {
const Module = require ( helperPath ) ;
let m = new Module ( ) ;
2016-10-13 16:42:15 +02:00
if ( m . requiresVersion ) {
2022-01-26 23:47:51 +01:00
Log . log ( ` Check MagicMirror² version for node helper ' ${ moduleName } ' - Minimum version: ${ m . requiresVersion } - Current version: ${ global . version } ` ) ;
2016-10-13 16:42:15 +02:00
if ( cmpVersions ( global . version , m . requiresVersion ) >= 0 ) {
2020-05-11 07:25:42 +02:00
Log . log ( "Version is ok!" ) ;
2016-10-13 16:42:15 +02:00
} else {
2021-01-05 19:35:11 +01:00
Log . warn ( ` Version is incorrect. Skip module: ' ${ moduleName } ' ` ) ;
2016-10-13 16:42:15 +02:00
return ;
}
}
2016-04-08 22:16:22 +02:00
m . setName ( moduleName ) ;
m . setPath ( path . resolve ( moduleFolder ) ) ;
nodeHelpers . push ( m ) ;
2016-12-07 17:19:05 +01:00
m . loaded ( callback ) ;
} else {
callback ( ) ;
2016-04-08 22:16:22 +02:00
}
2021-01-05 19:35:11 +01:00
}
2016-04-08 22:16:22 +02:00
2020-07-27 20:10:31 +02:00
/ * *
2016-04-08 22:16:22 +02:00
* Loads all modules .
*
2020-08-01 16:31:42 +02:00
* @ param { Module [ ] } modules All modules to be loaded
2020-07-27 20:10:31 +02:00
* @ param { Function } callback Function to be called after loading
2016-04-08 22:16:22 +02:00
* /
2021-01-05 19:35:11 +01:00
function loadModules ( modules , callback ) {
2020-05-11 07:25:42 +02:00
Log . log ( "Loading module helpers ..." ) ;
2016-04-08 22:16:22 +02:00
2021-01-20 22:44:37 +01:00
/ * *
*
* /
2021-01-05 19:35:11 +01:00
function loadNextModule ( ) {
2016-12-07 17:19:05 +01:00
if ( modules . length > 0 ) {
2021-01-05 19:35:11 +01:00
const nextModule = modules [ 0 ] ;
2020-05-11 22:22:32 +02:00
loadModule ( nextModule , function ( ) {
2016-12-07 17:19:05 +01:00
modules = modules . slice ( 1 ) ;
loadNextModule ( ) ;
} ) ;
} else {
// All modules are loaded
2020-05-11 07:25:42 +02:00
Log . log ( "All module helpers loaded." ) ;
2016-12-07 17:19:05 +01:00
callback ( ) ;
}
2021-01-05 19:35:11 +01:00
}
2016-04-08 22:16:22 +02:00
2016-12-07 17:19:05 +01:00
loadNextModule ( ) ;
2021-01-05 19:35:11 +01:00
}
2016-04-08 22:16:22 +02:00
2020-07-27 14:24:30 +02:00
/ * *
2019-06-04 09:33:53 +02:00
* Compare two semantic version numbers and return the difference .
2016-10-13 16:42:15 +02:00
*
2020-07-27 14:24:30 +02:00
* @ param { string } a Version number a .
* @ param { string } b Version number b .
2020-07-27 20:10:31 +02:00
* @ returns { number } A positive number if a is larger than b , a negative
* number if a is smaller and 0 if they are the same
2016-10-13 16:42:15 +02:00
* /
function cmpVersions ( a , b ) {
2021-01-05 19:35:11 +01:00
let i , diff ;
const regExStrip0 = /(\.0+)+$/ ;
const segmentsA = a . replace ( regExStrip0 , "" ) . split ( "." ) ;
const segmentsB = b . replace ( regExStrip0 , "" ) . split ( "." ) ;
const l = Math . min ( segmentsA . length , segmentsB . length ) ;
2016-10-13 16:42:15 +02:00
for ( i = 0 ; i < l ; i ++ ) {
diff = parseInt ( segmentsA [ i ] , 10 ) - parseInt ( segmentsB [ i ] , 10 ) ;
if ( diff ) {
return diff ;
}
}
return segmentsA . length - segmentsB . length ;
}
2020-07-27 20:10:31 +02:00
/ * *
* Start the core app .
2016-04-08 22:16:22 +02:00
*
2020-07-27 20:10:31 +02:00
* It loads the config , then it loads all modules . When it ' s done it
* executes the callback with the config as argument .
*
* @ param { Function } callback Function to be called after start
2016-04-08 22:16:22 +02:00
* /
2020-05-11 22:22:32 +02:00
this . start = function ( callback ) {
loadConfig ( function ( c ) {
2016-04-08 22:16:22 +02:00
config = c ;
2020-07-04 22:02:39 +02:00
Log . setLogLevel ( config . logLevel ) ;
2021-01-05 19:35:11 +01:00
let modules = [ ] ;
2016-04-08 22:16:22 +02:00
2021-01-05 19:35:11 +01:00
for ( const module of config . modules ) {
if ( ! modules . includes ( module . module ) && ! module . disabled ) {
2016-04-08 22:16:22 +02:00
modules . push ( module . module ) ;
}
}
2022-10-29 22:34:17 +02:00
loadModules ( modules , async function ( ) {
2022-10-19 21:40:43 +02:00
httpServer = new Server ( config ) ;
2022-10-29 22:34:17 +02:00
const { app , io } = await httpServer . open ( ) ;
2022-10-19 21:40:43 +02:00
Log . log ( "Server started ..." ) ;
2016-04-08 22:16:22 +02:00
2022-10-19 21:40:43 +02:00
const nodePromises = [ ] ;
for ( let nodeHelper of nodeHelpers ) {
nodeHelper . setExpressApp ( app ) ;
nodeHelper . setSocketIO ( io ) ;
2022-10-13 21:38:04 +02:00
2022-10-19 21:40:43 +02:00
try {
2022-10-13 21:38:04 +02:00
nodePromises . push ( nodeHelper . start ( ) ) ;
2022-10-19 21:40:43 +02:00
} catch ( error ) {
Log . error ( ` Error when starting node_helper for module ${ nodeHelper . name } : ` ) ;
Log . error ( error ) ;
2016-12-07 17:19:05 +01:00
}
2022-10-19 21:40:43 +02:00
}
2016-04-08 22:16:22 +02:00
2022-10-19 21:40:43 +02:00
Promise . allSettled ( nodePromises ) . then ( ( results ) => {
// Log errors that happened during async node_helper startup
results . forEach ( ( result ) => {
if ( result . status === "rejected" ) {
Log . error ( result . reason ) ;
2022-10-13 21:38:04 +02:00
}
} ) ;
2022-10-19 21:40:43 +02:00
Log . log ( "Sockets connected & modules started ..." ) ;
if ( typeof callback === "function" ) {
callback ( config ) ;
}
2016-12-07 17:19:05 +01:00
} ) ;
2016-04-08 22:16:22 +02:00
} ) ;
} ) ;
2021-01-05 20:04:02 +01:00
} ;
2017-10-13 16:43:11 -05:00
2020-07-27 20:10:31 +02:00
/ * *
* Stops the core app . This calls each node _helper ' s STOP ( ) function , if it
* exists .
*
2017-10-13 16:43:11 -05:00
* Added to fix # 1056
2022-10-29 22:34:17 +02:00
*
* @ param { Function } callback Function to be called after the app has stopped
2017-10-13 16:43:11 -05:00
* /
2022-10-29 22:34:17 +02:00
this . stop = function ( callback ) {
2021-01-05 19:35:11 +01:00
for ( const nodeHelper of nodeHelpers ) {
2017-10-13 16:43:11 -05:00
if ( typeof nodeHelper . stop === "function" ) {
nodeHelper . stop ( ) ;
}
}
2022-10-29 22:34:17 +02:00
httpServer . close ( ) . then ( callback ) ;
2017-10-13 16:43:11 -05:00
} ;
2020-07-27 20:10:31 +02:00
/ * *
* Listen for SIGINT signal and call stop ( ) function .
2017-10-13 16:43:11 -05:00
*
* Added to fix # 1056
* Note : this is only used if running ` server-only ` . Otherwise
* this . stop ( ) is called by app . on ( "before-quit" ... in ` electron.js `
* /
process . on ( "SIGINT" , ( ) => {
2020-05-11 07:25:42 +02:00
Log . log ( "[SIGINT] Received. Shutting down server..." ) ;
2020-05-25 18:57:15 +02:00
setTimeout ( ( ) => {
process . exit ( 0 ) ;
} , 3000 ) ; // Force quit after 3 seconds
2017-10-13 16:43:11 -05:00
this . stop ( ) ;
process . exit ( 0 ) ;
} ) ;
2019-04-07 16:41:05 +02:00
2020-07-27 20:10:31 +02:00
/ * *
* Listen to SIGTERM signals so we can stop everything when we
* are asked to stop by the OS .
2019-04-06 20:50:54 +02:00
* /
process . on ( "SIGTERM" , ( ) => {
2020-05-11 07:25:42 +02:00
Log . log ( "[SIGTERM] Received. Shutting down server..." ) ;
2020-05-25 18:57:15 +02:00
setTimeout ( ( ) => {
process . exit ( 0 ) ;
} , 3000 ) ; // Force quit after 3 seconds
2019-04-06 20:50:54 +02:00
this . stop ( ) ;
process . exit ( 0 ) ;
} ) ;
2021-01-05 19:35:11 +01:00
}
2016-04-08 22:16:22 +02:00
2016-11-14 14:43:30 -03:00
module . exports = new App ( ) ;