diff --git a/CHANGELOG.md b/CHANGELOG.md
index 08cc8850..09c8e0a6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,13 +9,14 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added
- Finnish translation.
- Danish translation.
-- Method to overwrite the module's header. [See documentation.](https://github.com/MichMich/MagicMirror/tree/develop/modules#getheader)
- Option to limit access to certain IP addresses based on the value of `ipWhitelist` in the `config.js`, default is access from localhost only (Issue [#456](https://github.com/MichMich/MagicMirror/issues/456)).
- Added ability to change the point of time when calendar events get relative.
- Add Splash screen on boot.
- Add option to show humidity in currentWeather module.
-- Add Visibility locking to module system. See [documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#visibility-locking) for more information.
- Add VSCode IntelliSense support.
+- Module API: Add Visibility locking to module system. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#visibility-locking) for more information.
+- Module API: Method to overwrite the module's header. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#getheader) for more information.
+- Module API: Option to define the minimumn MagicMirror verion to run a module. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#requiresVersion) for more information.
### Updated
- Modified translations for Frysk.
diff --git a/index.html b/index.html
index 4288eba1..86a69a4a 100644
--- a/index.html
+++ b/index.html
@@ -8,6 +8,10 @@
+
+
diff --git a/js/app.js b/js/app.js
index 14f10e8b..84e89d13 100644
--- a/js/app.js
+++ b/js/app.js
@@ -10,6 +10,10 @@ var Server = require(__dirname + "/server.js");
var defaultModules = require(__dirname + "/../modules/default/defaultmodules.js");
var path = require("path");
+// Get version number.
+global.version = JSON.parse(fs.readFileSync("package.json", "utf8")).version;
+console.log("Starting MagicMirror: v" + global.version);
+
// The next part is here to prevent a major exception when there
// is no internet connection. This could probable be solved better.
process.on("uncaughtException", function (err) {
@@ -82,6 +86,17 @@ var App = function() {
if (loadModule) {
var Module = require(helperPath);
var m = new Module();
+
+ if (m.requiresVersion) {
+ console.log("Check MagicMirror version for node helper '" + moduleName + "' - Minimum version: " + m.requiresVersion + " - Current version: " + global.version);
+ if (cmpVersions(global.version, m.requiresVersion) >= 0) {
+ console.log("Version is ok!");
+ } else {
+ console.log("Version is incorrect. Skip module: '" + moduleName + "'");
+ return;
+ }
+ }
+
m.setName(moduleName);
m.setPath(path.resolve(moduleFolder));
nodeHelpers.push(m);
@@ -103,6 +118,28 @@ var App = function() {
console.log("All module helpers loaded.");
};
+ /* cmpVersions(a,b)
+ * Compare two symantic version numbers and return the difference.
+ *
+ * argument a string - Version number a.
+ * argument a string - Version number b.
+ */
+ function cmpVersions(a, b) {
+ var i, diff;
+ var regExStrip0 = /(\.0+)+$/;
+ var segmentsA = a.replace(regExStrip0, "").split(".");
+ var segmentsB = b.replace(regExStrip0, "").split(".");
+ var l = Math.min(segmentsA.length, segmentsB.length);
+
+ for (i = 0; i < l; i++) {
+ diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
+ if (diff) {
+ return diff;
+ }
+ }
+ return segmentsA.length - segmentsB.length;
+ }
+
/* start(callback)
* This methods starts the core app.
* It loads the config, then it loads all modules.
diff --git a/js/loader.js b/js/loader.js
index 3656c7cd..84e42a0a 100644
--- a/js/loader.js
+++ b/js/loader.js
@@ -117,9 +117,13 @@ var Loader = (function() {
var afterLoad = function() {
var moduleObject = Module.create(module.name);
- bootstrapModule(module, moduleObject, function() {
+ if (moduleObject) {
+ bootstrapModule(module, moduleObject, function() {
+ callback();
+ });
+ } else {
callback();
- });
+ }
};
if (loadedModuleFiles.indexOf(url) !== -1) {
diff --git a/js/module.js b/js/module.js
index fe669cee..17ca9595 100644
--- a/js/module.js
+++ b/js/module.js
@@ -14,6 +14,9 @@ var Module = Class.extend({
* All methods (and properties) below can be subclassed. *
*********************************************************/
+ // Set the minimum MagicMirror module version for this module.
+ requiresVersion: "2.0.0",
+
// Module config defaults.
defaults: {},
@@ -27,14 +30,14 @@ var Module = Class.extend({
/* init()
* Is called when the module is instantiated.
*/
- init: function() {
+ init: function () {
//Log.log(this.defaults);
},
/* start()
* Is called when the module is started.
*/
- start: function() {
+ start: function () {
Log.info("Starting module: " + this.name);
},
@@ -43,7 +46,7 @@ var Module = Class.extend({
*
* return Array - An array with filenames.
*/
- getScripts: function() {
+ getScripts: function () {
return [];
},
@@ -52,7 +55,7 @@ var Module = Class.extend({
*
* return Array - An array with filenames.
*/
- getStyles: function() {
+ getStyles: function () {
return [];
},
@@ -61,7 +64,7 @@ var Module = Class.extend({
*
* return Map - A map with langKeys and filenames.
*/
- getTranslations: function() {
+ getTranslations: function () {
return false;
},
@@ -71,7 +74,7 @@ var Module = Class.extend({
*
* return domobject - The dom to display.
*/
- getDom: function() {
+ getDom: function () {
var nameWrapper = document.createElement("div");
var name = document.createTextNode(this.name);
nameWrapper.appendChild(name);
@@ -95,7 +98,7 @@ var Module = Class.extend({
*
* return string - The header to display above the header.
*/
- getHeader: function() {
+ getHeader: function () {
return this.data.header;
},
@@ -107,7 +110,7 @@ var Module = Class.extend({
* argument payload mixed - The payload of the notification.
* argument sender Module - The module that sent the notification.
*/
- notificationReceived: function(notification, payload, sender) {
+ notificationReceived: function (notification, payload, sender) {
if (sender) {
Log.log(this.name + " received a module notification: " + notification + " from sender: " + sender.name);
} else {
@@ -121,21 +124,21 @@ var Module = Class.extend({
* argument notification string - The identifier of the noitication.
* argument payload mixed - The payload of the notification.
*/
- socketNotificationReceived: function(notification, payload) {
+ socketNotificationReceived: function (notification, payload) {
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
},
/* suspend()
* This method is called when a module is hidden.
*/
- suspend: function() {
+ suspend: function () {
Log.log(this.name + " is suspended.");
},
/* resume()
* This method is called when a module is shown.
*/
- resume: function() {
+ resume: function () {
Log.log(this.name + " is resumed.");
},
@@ -148,7 +151,7 @@ var Module = Class.extend({
*
* argument data obejct - Module data.
*/
- setData: function(data) {
+ setData: function (data) {
this.data = data;
this.name = data.name;
this.identifier = data.identifier;
@@ -162,7 +165,7 @@ var Module = Class.extend({
*
* argument config obejct - Module config.
*/
- setConfig: function(config) {
+ setConfig: function (config) {
this.config = Object.assign(this.defaults, config);
},
@@ -170,13 +173,13 @@ var Module = Class.extend({
* Returns a socket object. If it doesn"t exist, it"s created.
* It also registers the notification callback.
*/
- socket: function() {
+ socket: function () {
if (typeof this._socket === "undefined") {
this._socket = this._socket = new MMSocket(this.name);
}
var self = this;
- this._socket.setNotificationCallback(function(notification, payload) {
+ this._socket.setNotificationCallback(function (notification, payload) {
self.socketNotificationReceived(notification, payload);
});
@@ -190,7 +193,7 @@ var Module = Class.extend({
*
* return string - File path.
*/
- file: function(file) {
+ file: function (file) {
return this.data.path + "/" + file;
},
@@ -199,14 +202,14 @@ var Module = Class.extend({
*
* argument callback function - Function called when done.
*/
- loadStyles: function(callback) {
+ loadStyles: function (callback) {
var self = this;
var styles = this.getStyles();
- var loadNextStyle = function() {
+ var loadNextStyle = function () {
if (styles.length > 0) {
var nextStyle = styles[0];
- Loader.loadFile(nextStyle, self, function() {
+ Loader.loadFile(nextStyle, self, function () {
styles = styles.slice(1);
loadNextStyle();
});
@@ -223,14 +226,14 @@ var Module = Class.extend({
*
* argument callback function - Function called when done.
*/
- loadScripts: function(callback) {
+ loadScripts: function (callback) {
var self = this;
var scripts = this.getScripts();
- var loadNextScript = function() {
+ var loadNextScript = function () {
if (scripts.length > 0) {
var nextScript = scripts[0];
- Loader.loadFile(nextScript, self, function() {
+ Loader.loadFile(nextScript, self, function () {
scripts = scripts.slice(1);
loadNextScript();
});
@@ -247,14 +250,14 @@ var Module = Class.extend({
*
* argument callback function - Function called when done.
*/
- loadTranslations: function(callback) {
+ loadTranslations: function (callback) {
var self = this;
var translations = this.getTranslations();
var lang = config.language.toLowerCase();
// The variable `first` will contain the first
// defined translation after the following line.
- for (var first in translations) {break;}
+ for (var first in translations) { break; }
if (translations) {
var translationFile = translations[lang] || undefined;
@@ -263,7 +266,7 @@ var Module = Class.extend({
// If a translation file is set, load it and then also load the fallback translation file.
// Otherwise only load the fallback translation file.
if (translationFile !== undefined && translationFile !== translationsFallbackFile) {
- Translator.load(self, translationFile, false, function() {
+ Translator.load(self, translationFile, false, function () {
Translator.load(self, translationsFallbackFile, true, callback);
});
} else {
@@ -274,13 +277,13 @@ var Module = Class.extend({
}
},
- /* translate(key, defaultValue)
- * Request the translation for a given key.
- *
- * argument key string - The key of the string to translage
- * argument defaultValue string - The default value if no translation was found. (Optional)
- */
- translate: function(key, defaultValue) {
+ /* translate(key, defaultValue)
+ * Request the translation for a given key.
+ *
+ * argument key string - The key of the string to translage
+ * argument defaultValue string - The default value if no translation was found. (Optional)
+ */
+ translate: function (key, defaultValue) {
return Translator.translate(this, key) || defaultValue || "";
},
@@ -289,7 +292,7 @@ var Module = Class.extend({
*
* argument speed Number - The speed of the animation. (Optional)
*/
- updateDom: function(speed) {
+ updateDom: function (speed) {
MM.updateDom(this, speed);
},
@@ -299,7 +302,7 @@ var Module = Class.extend({
* argument notification string - The identifier of the noitication.
* argument payload mixed - The payload of the notification.
*/
- sendNotification: function(notification, payload) {
+ sendNotification: function (notification, payload) {
MM.sendNotification(notification, payload, this);
},
@@ -309,7 +312,7 @@ var Module = Class.extend({
* argument notification string - The identifier of the noitication.
* argument payload mixed - The payload of the notification.
*/
- sendSocketNotification: function(notification, payload) {
+ sendSocketNotification: function (notification, payload) {
this.socket().sendNotification(notification, payload);
},
@@ -320,17 +323,17 @@ var Module = Class.extend({
* argument callback function - Called when the animation is done.
* argument options object - Optional settings for the hide method.
*/
- hide: function(speed, callback, options) {
+ hide: function (speed, callback, options) {
if (typeof callback === "object") {
options = callback;
- callback = function() {};
+ callback = function () { };
}
- callback = callback || function() {};
+ callback = callback || function () { };
options = options || {};
var self = this;
- MM.hideModule(self, speed, function() {
+ MM.hideModule(self, speed, function () {
self.suspend();
callback();
}, options);
@@ -345,13 +348,13 @@ var Module = Class.extend({
* argument callback function - Called when the animation is done.
* argument options object - Optional settings for the hide method.
*/
- show: function(speed, callback, options) {
+ show: function (speed, callback, options) {
if (typeof callback === "object") {
options = callback;
- callback = function() {};
+ callback = function () { };
}
- callback = callback || function() {};
+ callback = callback || function () { };
options = options || {};
this.resume();
@@ -361,7 +364,12 @@ var Module = Class.extend({
Module.definitions = {};
-Module.create = function(name) {
+Module.create = function (name) {
+
+ // Make sure module definition is available.
+ if (!Module.definitions[name]) {
+ return;
+ }
//Define the clone method for later use.
function cloneObject(obj) {
@@ -387,7 +395,39 @@ Module.create = function(name) {
};
-Module.register = function(name, moduleDefinition) {
+/* cmpVersions(a,b)
+* Compare two symantic version numbers and return the difference.
+*
+* argument a string - Version number a.
+* argument a string - Version number b.
+*/
+function cmpVersions(a, b) {
+ var i, diff;
+ var regExStrip0 = /(\.0+)+$/;
+ var segmentsA = a.replace(regExStrip0, "").split(".");
+ var segmentsB = b.replace(regExStrip0, "").split(".");
+ var l = Math.min(segmentsA.length, segmentsB.length);
+
+ for (i = 0; i < l; i++) {
+ diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
+ if (diff) {
+ return diff;
+ }
+ }
+ return segmentsA.length - segmentsB.length;
+}
+
+Module.register = function (name, moduleDefinition) {
+
+ if (moduleDefinition.requiresVersion) {
+ Log.log("Check MagicMirror version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + version);
+ if (cmpVersions(version, moduleDefinition.requiresVersion) >= 0) {
+ Log.log("Version is ok!");
+ } else {
+ Log.log("Version is incorrect. Skip module: '" + name + "'");
+ return;
+ }
+ }
Log.log("Module registered: " + name);
Module.definitions[name] = moduleDefinition;
};
diff --git a/js/server.js b/js/server.js
index e1a90946..9ab47a7d 100644
--- a/js/server.js
+++ b/js/server.js
@@ -11,6 +11,7 @@ var server = require("http").Server(app);
var io = require("socket.io")(server);
var path = require("path");
var ipfilter = require("express-ipfilter").IpFilter;
+var fs = require("fs");
var Server = function(config, callback) {
console.log("Starting server op port " + config.port + " ... ");
@@ -35,8 +36,15 @@ var Server = function(config, callback) {
app.use("/vendor", express.static(path.resolve(__dirname + "/../vendor")));
app.use("/translations", express.static(path.resolve(__dirname + "/../translations")));
+ app.get("/version", function(req,res) {
+ res.send(global.version);
+ });
+
app.get("/", function(req, res) {
- res.sendFile(path.resolve(__dirname + "/../index.html"));
+ var html = fs.readFileSync(path.resolve(__dirname + "/../index.html"), {encoding: "utf8"});
+ html = html.replace("#VERSION#", global.version);
+
+ res.send(html);
});
if (typeof callback === "function") {
diff --git a/modules/README.md b/modules/README.md
index 52456016..9332d009 100644
--- a/modules/README.md
+++ b/modules/README.md
@@ -78,6 +78,16 @@ The data object contains additional metadata about the module instance:
####`defaults: {}`
Any properties defined in the defaults object, will be merged with the module config as defined in the user's config.js file. This is the best place to set your modules's configuration defaults. Any of the module configuration properties can be accessed using `this.config.propertyName`, but more about that later.
+####'requiresVersion:'
+
+*Introduced in version: 2.1.0.*
+
+A string that defines the minimum version of the MagicMirror framework. If it is set, the system compares the required version with the users version. If the version of the user is out of date, it won't run the module. Make sure to also set this value in the Node helper.
+
+**Note:** Since this check is introduced in version 2.1.0, this check will not be run in older versions. Keep this in mind if you get issue reports on your module.
+
+Example: `"2.1.0"`
+
### Subclassable module methods
####`init()`
@@ -286,7 +296,7 @@ this.sendSocketNotification('SET_CONFIG', this.config);
####`this.hide(speed, callback, options)`
***speed* Number** - Optional (Required when setting callback or options), The speed of the hide animation in milliseconds.
***callback* Function** - Optional, The callback after the hide animation is finished.
-***options* Function** - Optional, Object with additional options for the hide action (see below).
+***options* Function** - Optional, Object with additional options for the hide action (see below). (*Introduced in version: 2.1.0.*)
To hide a module, you can call the `hide(speed, callback)` method. You can call the hide method on the module instance itself using `this.hide()`, but of course you can also hide another module using `anOtherModule.hide()`.
@@ -303,7 +313,7 @@ Possible configurable options:
####`this.show(speed, callback, options)`
***speed* Number** - Optional (Required when setting callback or options), The speed of the show animation in milliseconds.
***callback* Function** - Optional, The callback after the show animation is finished.
-***options* Function** - Optional, Object with additional options for the show action (see below).
+***options* Function** - Optional, Object with additional options for the show action (see below). (*Introduced in version: 2.1.0.*)
To show a module, you can call the `show(speed, callback)` method. You can call the show method on the module instance itself using `this.show()`, but of course you can also show another module using `anOtherModule.show()`.
@@ -317,6 +327,9 @@ Possible configurable options:
**Note 3:** If the dom is not yet created, the show method won't work. Wait for the `DOM_OBJECTS_CREATED` [notification](#notificationreceivednotification-payload-sender).
####Visibility locking
+
+(*Introduced in version: 2.1.0.*)
+
Visiblity locking helps the module system to prevent unwanted hide/show actions. The following scenario explains the concept:
**Module B asks module A to hide:**
@@ -450,6 +463,16 @@ this.expressApp.use("/" + this.name, express.static(this.path + "/public"));
This is a link to the IO instance. It will allow you to do some Socket.IO magic. In most cases you won't need this, since the Node Helper has a few convenience methods to make this simple.
+
+####'requiresVersion:'
+*Introduced in version: 2.1.0.*
+
+A string that defines the minimum version of the MagicMirror framework. If it is set, the system compares the required version with the users version. If the version of the user is out of date, it won't run the module.
+
+**Note:** Since this check is introduced in version 2.1.0, this check will not be run in older versions. Keep this in mind if you get issue reports on your module.
+
+Example: `"2.1.0"`
+
### Subclassable module methods
####`init()`
diff --git a/package.json b/package.json
index cfef5f85..202c3e59 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "magicmirror",
- "version": "2.0.0",
+ "version": "2.1.0",
"description": "A modular interface for smart mirrors.",
"main": "js/electron.js",
"scripts": {