RedMap icon indicating copy to clipboard operation
RedMap copied to clipboard

Add code injection option

Open zafrirron opened this issue 2 years ago • 3 comments

Use Case:

Enable custom code injection to red-map frontend using worldmap web socket (for example, adding leaflet additional plugins, user scripts, call injected functions or run pure javascript code injected from backend nodes).

reasoning - For custom RedMap implementations, customization requires forking and maintaining separate branches, resync and merge changes. adding custom map commands requires worldmap code base changes. With dynamic code injection option, customized code can be maintained separatly and frontend map can be extended with addional custom commands directly from noe red editor/run time.

Code Changes:

Added two additional commands to worldmap ws messages:

  1. Inject static file list - 'loasStatic' will load the file (js or css) to the worldmap frontend (files will be loaded by frontend as part of page HEAD section).
  2. Inject new commands - 'customCmd', can include pure javascript snippets, including references to previously loaded static files.

Usage Example:

simple example:

'loadStatic' - Static files loading (use in function node routed to worldmap node):

msg.payload = {};
msg.payload.command = {};


msg.payload.command.loadStatic = { "fileNames": ["/static/test.js", "/static/test1.js"]};

return msg;

test1.js, test2.js are static javascript files, need to enable static serving if served from node red server

test.js - javascript example file loaded by 'loadStatic'

console.log("inside test.js");
var fn1 = function() {
        console.log("test.js - inside fn1");
}
var fn2 = function(param) {
        console.log("test.js - inside fn2 param: ",param);
}

test1.js - the second javascript example file loaded by 'loadStatic'

console.log("inside test1.js");
var fn11 = function() {
        console.log("test1.js - inside fn1");
}
var fn12 = function(param) {
        console.log("test1.js - inside fn2 param: ",param);
}

'customCmd'- (use in function node routed to worldmap node):

msg.payload = {};
msg.payload.command = {};

msg.payload.command.customCmd = "fn1()";
node.send(msg);
msg.payload.command.customCmd = "fn11()";
node.send(msg);
msg.payload.command.customCmd = "fn2(2)";
node.send(msg);
msg.payload.command.customCmd = "fn12(4)";
node.send(msg);
msg.payload.command.customCmd = "console.log(\"direct\");fn1();fn11();";
return msg;

Leaflet.PolylineMeasure plugin loading Example

'loadStatic' Command

msg.payload = {};
msg.payload.command = {};


msg.payload.command.loadStatic = {
    "fileNames": ["https://ppete2.github.io/Leaflet.PolylineMeasure/Leaflet.PolylineMeasure.css", "https://ppete2.github.io/Leaflet.PolylineMeasure/Leaflet.PolylineMeasure.js"]};

return msg;

'customCmd' Command

msg.payload = {};
msg.payload.command = {};

msg.payload.command.customCmd = "L.control.polylineMeasure({position:'topleft', unit:'kilometres', showBearings:true, clearMeasurementsOnStop: false, showClearControl: true, showUnitControl: true}).addTo(map);";
return msg;

Leaflet.PolylineMeasure plugin loading Node Red Import Format

[{"id":"f6f2187d.f17ca8","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"c643e022.1816c","type":"worldmap","z":"f6f2187d.f17ca8","name":"","lat":"33","lon":"34","zoom":"","layer":"OSMC","cluster":"0","maxage":"","usermenu":"show","layers":"show","panit":"true","panlock":"false","zoomlock":"false","hiderightclick":"false","coords":"utm","showgrid":"false","allowFileDrop":"true","path":"","overlist":"DR,CO,RA,DN,HM","maplist":"OSMG,OSMC,EsriC,EsriS,EsriT,EsriO,EsriDG,NatGeo,UKOS,OpTop","mapname":"","mapurl":"","mapopt":"","mapwms":false,"x":1230,"y":400,"wires":[]},{"id":"010fbb84e79852c9","type":"function","z":"f6f2187d.f17ca8","name":"Load Static Files","func":"msg.payload = {};\nmsg.payload.command = {};\n\n\nmsg.payload.command.loadStatic = {\n    \"fileNames\": [\"https://ppete2.github.io/Leaflet.PolylineMeasure/Leaflet.PolylineMeasure.css\", \"https://ppete2.github.io/Leaflet.PolylineMeasure/Leaflet.PolylineMeasure.js\"]};\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1010,"y":360,"wires":[["c643e022.1816c"]]},{"id":"87ee6ec6d2ad0a20","type":"inject","z":"f6f2187d.f17ca8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"str","x":770,"y":360,"wires":[["010fbb84e79852c9"]]},{"id":"c55504df5f57510e","type":"inject","z":"f6f2187d.f17ca8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"str","x":770,"y":420,"wires":[["fd96b7e00f6951bc"]]},{"id":"fd96b7e00f6951bc","type":"function","z":"f6f2187d.f17ca8","name":"Custom Comman Inject","func":"msg.payload = {};\nmsg.payload.command = {};\n\nmsg.payload.command.customCmd = \"L.control.polylineMeasure({position:'topleft', unit:'kilometres', showBearings:true, clearMeasurementsOnStop: false, showClearControl: true, showUnitControl: true}).addTo(map);\";\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":990,"y":420,"wires":[["c643e022.1816c"]]}]
* this plugin could replace (for example) the provided measure ruler, however enabling/disabling it is not part of the woldmap node options for some reason, also for unknown reason it is linked to the grid disabling enabling code (a good reason for a separate pull request)

current master branch code

                if (showGrid) { Lgrid.addTo(map); rulerButton.addTo(map); }
                else { Lgrid.removeFrom(map); rulerButton.remove(); }

zafrirron avatar Sep 19 '22 05:09 zafrirron

Interesting. I can see that this could be useful - but I am also wary that it does open up a rather obvious attack point. If we are going to consider adding these I think they should at least be enabled by a flag (or flags) in the main Node-RED settings.js file with default being disabled, so naive users won't accidentally expose this capability.

dceejay avatar Sep 23 '22 12:09 dceejay

Ok.... the contextmenu injection hoever also may have the same vanurability as it allows html forms including javascript....

zafrirron avatar Sep 23 '22 14:09 zafrirron

Good point.

dceejay avatar Sep 23 '22 16:09 dceejay

@zafrirron Surprise surprise... I've finally caught up to the same page and need this capability also... so merging now. Thanks. (and apologies for taking so long to "get it")

dceejay avatar Oct 07 '23 21:10 dceejay