Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remote Control REST API improvements #292

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
88 changes: 41 additions & 47 deletions API/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const bodyParser = require("body-parser");
const express = require("express");

module.exports = {
/* getApiKey
/* getApiKey
* Middleware method for ExpressJS to check if an API key is provided.
* Only checks for an API key if one is defined in the module's config section.
*/
Expand All @@ -36,7 +36,7 @@ module.exports = {
this.secureEndpoints = true;
}
}

},


Expand Down Expand Up @@ -78,19 +78,17 @@ module.exports = {
let modActions = getActions(Module.notificationHandler[mod.module]);

if (modActions.length > 0) {
let pathGuess = mod.module.replace(/MMM-/g, '').replace(/-/g, '').toLowerCase();

// Generate formatted actions object
let actionsGuess = {};

modActions.forEach(a => {
actionsGuess[a.replace(/[-_]/g, '').toLowerCase()] = { notification: a, guessed: true };
});

if (pathGuess in this.externalApiRoutes) {
this.externalApiRoutes[pathGuess].actions = Object.assign({}, actionsGuess, this.externalApiRoutes[pathGuess].actions);
if (mod.module in this.externalApiRoutes) {
this.externalApiRoutes[mod.module].actions = Object.assign({}, actionsGuess, this.externalApiRoutes[mod.module].actions);
} else {
this.externalApiRoutes[pathGuess] = {
this.externalApiRoutes[mod.module] = {
module: mod.module,
path: mod.module.replace(/MMM-/g, '').replace(/-/g, '').toLowerCase(),
actions: actionsGuess
Expand All @@ -112,11 +110,11 @@ module.exports = {

this.expressApp.use(bodyParser.urlencoded({ extended: true }));
this.expressApp.use(bodyParser.json());

this.expressApp.use('/api/docs', express.static(path.join(__dirname, '../docs'))); // Docs without apikey

this.expressRouter = express.Router();

// Route for testing the api at http://mirror:8080/api/test
this.expressRouter.route(['/test','/']) // Test without apiKey
.get((req, res) => {
Expand Down Expand Up @@ -183,7 +181,7 @@ module.exports = {
console.log(req.path);
self.executeQuery(this.checkDelay({ action: r }, req), res);
});

this.expressRouter.route('/classes/:value')
.get((req, res) => {
var classes = self.getConfig().modules.find(m => m.module === "MMM-Remote-Control").config || {};
Expand All @@ -194,7 +192,7 @@ module.exports = {
res.status(400).json({ success: false, message: `Invalid value ${val} provided in request. Use /api/classes to see actual values` });
}
});

this.expressRouter.route('/command/:value')
.get((req, res) => {
if(!this.apiKey && this.secureEndpoints) return res.status(403).json({ success: false, message: "Forbidden: API Key Not Provided in Config! Use secureEndpoints to bypass this message" });
Expand Down Expand Up @@ -238,7 +236,7 @@ module.exports = {
res.status(400).json({ success: false, message: "Invalid URL provided in request body" });
}
});

//edit config, payload is completely new config object with your changes(edits).
this.expressRouter.route('/config/edit')
.get((req, res) => {
Expand Down Expand Up @@ -326,65 +324,61 @@ module.exports = {
}
return query;
},

mergeData: function() {
var extApiRoutes = this.externalApiRoutes;
var modules = this.configData.moduleData
var query = {success: true, data: []};

modules.forEach((mod) => {
if (extApiRoutes[mod.name] === undefined) {
query.data.push(mod);
} else {
query.data.push(Object.assign({},mod, {actions: extApiRoutes[mod.name].actions}));
query.data.push(Object.assign({}, mod, {actions: extApiRoutes[mod.name].actions, urlpath: extApiRoutes[mod.name].path}));
}
})

return query;
},

answerModuleApi: function(req, res) {
if (!this.checkInititialized(res)) { return; }
var dataMerged = this.mergeData().data

if (!req.params.moduleName) {
res.json({ success: true, data: dataMerged });
return;
}
let modData = [];
if(req.params.moduleName !== 'all') {

let modData;
if (req.params.moduleName !== 'all') {
modData = dataMerged.filter(m => {
return (req.params.moduleName.includes(m.identifier));
const name = req.params.moduleName;
return name.includes(m.identifier) || name.includes(m.name) || name.includes(m.urlpath);
});
if (!modData) {
modData = dataMerged.filter(m => {
return (req.params.moduleName.includes(m.name));
});
}
} else {
modData = dataMerged;
}

if (!modData.length) {
res.status(400).json({ success: false, message: "Module Name or Identifier Not Found!" });
return;
}
}

if (!req.params.action) {
res.json({ success: true, data: modData });
return;
}

var action = req.params.action.toUpperCase();

if (["SHOW", "HIDE", "FORCE", "TOGGLE", "DEFAULTS"].indexOf(action) !== -1) { // /api/modules part of the code

if (action === "DEFAULTS") {
this.answerGet({ data: "defaultConfig", module: mod.name }, res);
return;
}

if (req.params.moduleName === "all") {
let query = { module: "all" };
if (action === "FORCE") {
Expand All @@ -396,7 +390,7 @@ module.exports = {
this.executeQuery(this.checkDelay(query, req), res);
return;
}

modData.forEach(mod => {
let query = { module: mod.identifier };
if (action === "FORCE") {
Expand All @@ -410,19 +404,19 @@ module.exports = {
this.sendSocketNotification("UPDATE");
return;
}

action = modData[0].actions[req.params.action]

if (action) {
if ("method" in action && action.method !== req.method) {
res.status(400).json({ success: false, info: `Method ${req.method} is not allowed for ${moduleName}/${req.params.action}.` });
return;
}
this.answerNotifyApi(req, res, action);
}

},

answerNotifyApi: function(req, res, action) {
// Build the payload to send with our notification.
let n = "";
Expand Down Expand Up @@ -500,26 +494,26 @@ module.exports = {
icon: "window-restore",
items: []
};
Object.keys(this.externalApiRoutes).forEach(r => {
Object.values(this.externalApiRoutes).forEach(r => {
let sub = {
id: "mc-" + r,
id: "mc-" + r.path,
type: "menu",
icon: "bars",
text: this.formatName(this.externalApiRoutes[r].module),
text: this.formatName(r.module),
items: []
};
Object.keys(this.externalApiRoutes[r].actions).forEach(a => {
Object.keys(r.actions).forEach(a => {
let item = {
id: `mc-${r}-${a}`,
id: `mc-${r.path}-${a}`,
menu: "item",
icon: "dot-circle-o",
action: "NOTIFICATION",
content: this.externalApiRoutes[r].actions[a]
content: r.actions[a]
};
if ("prettyName" in this.externalApiRoutes[r].actions[a]) {
item.text = this.translate(this.externalApiRoutes[r].actions[a].prettyName);
if ("prettyName" in r.actions[a]) {
item.text = this.translate(r.actions[a].prettyName);
} else {
item.text = this.translate(this.externalApiRoutes[r].actions[a].notification).toLowerCase().replace(/(^|_)(\w)/g, function($0, $1, $2) {
item.text = this.translate(r.actions[a].notification).toLowerCase().replace(/(^|_)(\w)/g, function($0, $1, $2) {
return ($1 && ' ') + $2.toUpperCase();
});
}
Expand Down
5 changes: 3 additions & 2 deletions MMM-Remote-Control.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Module.register("MMM-Remote-Control", {
socketNotificationReceived: function(notification, payload) {
if (notification === "UPDATE") {
this.sendCurrentData();
}
}
if (notification === "IP_ADDRESSES") {
this.addresses = payload;
if (this.data.position) {
Expand All @@ -68,7 +68,7 @@ Module.register("MMM-Remote-Control", {
this.updateDom();
}
}

if (notification === "USER_PRESENCE") {
this.sendNotification(notification, payload);
}
Expand Down Expand Up @@ -272,6 +272,7 @@ Module.register("MMM-Remote-Control", {
let modData = Object.assign({}, module.data);
modData.hidden = module.hidden;
modData.lockStrings = module.lockStrings;
modData.urlpath = module.name.replace(/MMM-/g, '').replace(/-/g, '').toLowerCase();
modData.config = module.config;
currentModuleData.push(modData);
});
Expand Down
17 changes: 9 additions & 8 deletions node_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ module.exports = NodeHelper.create(Object.assign({

loadTimers: function() {
var delay = 24*3600;

var self = this;

clearTimeout(this.delayedQueryTimers['update'])
this.delayedQueryTimers['update'] = setTimeout(function () {
self.updateModuleList();
Expand Down Expand Up @@ -699,7 +699,7 @@ module.exports = NodeHelper.create(Object.assign({
}
if (query.action === "COMMAND") {
if (this.thisConfig.customCommand && this.thisConfig.customCommand[query.command]) {
exec(this.thisConfig.customCommand[query.command], opts, (error, stdout, stderr) => {
exec(this.thisConfig.customCommand[query.command], opts, (error, stdout, stderr) => {
self.checkForExecError(error, stdout, stderr, res, { stdout: stdout });
});
} else {
Expand Down Expand Up @@ -842,7 +842,7 @@ module.exports = NodeHelper.create(Object.assign({
return;
}
if (query.action === "DELAYED") {
/* Expects a nested query object
/* Expects a nested query object
* {
* action: "DELAYED",
* did: "SOME_UNIQUE_ID",
Expand Down Expand Up @@ -942,7 +942,7 @@ module.exports = NodeHelper.create(Object.assign({
}
});
});

},

checkForExecError: function(error, stdout, stderr, res, data) {
Expand Down Expand Up @@ -1002,6 +1002,7 @@ module.exports = NodeHelper.create(Object.assign({
simpleModuleData[k].identifier = moduleData[k].identifier;
simpleModuleData[k].hidden = moduleData[k].hidden;
simpleModuleData[k].lockStrings = moduleData[k].lockStrings;
simpleModuleData[k].urlpath = moduleData[k].urlpath;
}

var text = JSON.stringify({
Expand Down Expand Up @@ -1136,7 +1137,7 @@ module.exports = NodeHelper.create(Object.assign({
}
var backupPath = path.resolve("config/config.js.backup" + iteration);
var req = require(backupPath)

this.answerPost({ data: "config" }, { body: req }, { isSocket: true });
}
if (notification === "NEW_CONFIG") {
Expand All @@ -1159,10 +1160,10 @@ module.exports = NodeHelper.create(Object.assign({
if (notification === "REGISTER_API") {
if ("module" in payload) {
if ("actions" in payload && payload.actions !== {}) {
this.externalApiRoutes[payload.path] = payload;
this.externalApiRoutes[payload.module] = payload;
} else {
// Blank actions means the module has requested to be removed from API
delete this.externalApiRoutes[payload.path];
delete this.externalApiRoutes[payload.module];
}
this.updateModuleApiMenu();
}
Expand Down