NRG Web Server

Note: this is a work in progress and has moved to

https://github.com/steveorevo/nrg

About NRG

NRG is an open source web server "wired" completely in Node-RED. It is capable of serving static as well as dynamic web pages. It is designed to leverage the capabilities of Node-RED flows with inline, special server-side tags embedded into regular web pages. The web sever delivers standard HTML, CSS, most Apache 2.0 mime-types, and the special .nrg (dot n-r-g, i.e. index.nrg) files. The .nrg files act as "Node-RED gateway" files like cgi files; but more similar to the popular and prolific ASP and PHP files used by most hosting providers.

NRG accomodates a more MVC-like design pattern allowing web designers to focus on user interface, theme design, stylesheets, and accessibility; while preserving the web developer's power to use Node-RED driven logic, data, conductivity, and functionality.

Getting Started

To startup NRG simply drag the “nrg” subflow to a tab in the Node-RED editor and click “Deploy”. Currently, NRG is implemented as a global singleton object and only one instance of the web server should exist. However, flows that service browser requests may exist on any tab. By default, NRG will serve files from the a subfolder titled “public_html” from the current user account (I.e. /Users/username/public_html). You may change the default configuration by defining an ActionFlows segment and naming it "#config". Also by default (and customizable), the URL path is the same as the Node-RED installation. More information can be found in the followup section "Creating NRG Flows" below. Lastly, you can change the admin path for Node-RED via your Node-RED’s settings.js file.

Creating NRG Flows

At the heart of NRG’s operation are "NRG flows" which are simply Node-RED contributed nodes called ActionFlows; a set of nodes that allow you to define a flow segment. ActonFlows are similar to “virtual wires” and Node-RED’s native Subflows. Two important nodes you will use to define an NRG flow segment are:

  • action in
  • action out

For example, to configure a different web server folder in lieu of the default (/Users/username/public_html), create an action in/out pair of nodes named #config and use a change node to alter the siteRoot property of the msg.payload. The flow segment you defined will be executed immediately after you deploy; telling NRG web server where to begin serving files (see image below).

Configure NRG

The image above shows the flow segment #config with the default settings in msg.payload. A definition for each of the settings are:

  • slash - Platform dependent directory separator. Do not change this.
  • mimeTypes - The default Content-Type/mime types set by extension.
  • siteRoot - The site root folder used to deliver content by the server.
  • urlPath - The starting URL path used to deliver content by the server.

In addition to the #config flow segment are NRG flows for customizing the 404 Page Not Found error page, and the 403, and 500 pages. You can supply your own custom error pages by supplying your own NRG flows named:

  • 403error
  • 404error
  • 500error

You may also invoke NRG flows from within your own webpages by simply naming the file with a .nrg (dot n-r-g) extension (i.e. index.nrg) and using NRG tags. See "NRG Flows in Webpages" below for details.

NRG Flows in Webpages

Creating dynamic web pages with NRG flows is easy; simply create an XHTML compliant file named with a .nrg extension (aka "NRG files"). NRG files are just like HTML files, but they can include both HTML tags and special "NRG tags". The NRG tags contain the name of flow segments (ActionFlows) that are executed by Node-RED when the page is accessed. The resulting output is written as HTML within the webpage. When a user accesses a NRG page, his or her web browser only gets sent the HTML code, since Node-RED has run the NRG flows in the background. A simple "Hello World" web page can illustrate the process:

<html>
  <body>
    <#action/>
  </body>
</html>

The NRG flow self-closing tag <#action/> will run the associated flow segment of the same named prefix action in Node-RED.

NRG Flow Segment

The results of the flow segment's msg.payload property will be output and will replace the tag. In this case, the change node will populate msg.payload with the string "Hello World". The result is a simple white webpage with the text "Hello World".

Tag Syntax for NRG Flows

Authors of .nrg webpages should ensure that their pages are XHTML compliant. NRG will run a basic XML syntax check and report any errors for broken tags or incorrect syntax. The special NRG flow tags follow a strict XHTML standard; internally the # (hashtag) is a shortcut that converts to nrg_ (i.e. <#action /> is the equivilent to <nrg_action />. The shortcut # (hashtag) is the preferred method for embedding server side flows into webpages.

As shown in the example, the contents of the msg.payload at the end of the flow is returned in place of the tag in the webpage. Likewise, a tag can pass a value into a flow as the inital content of msg.payload. The examples above for <#action /> pass nothing and therefore always have an empty payload. However, consider that an XML compliant self-closing tag of <nrg_action payload="The quick brown fox" /> would invoke the action node in Node-RED with a msg.payload containing "The quick brown fox". In the prefered shortcut syntax:

<#action("The quick brown fox"); />

Passing Template Blocks

Often times, a web designer may wish to send a block of HTML to Node-RED for processing or to inject dynamic content into moustache syntax template; such as with the node-red-contrib-render node. In this case, use a XHTML begin and end tag (versus self-closing):

<#comments>
  <div class="comment">
    <span class="comment-user">Visitor {{username}} says:</span>
    <div class="comment-post">
      <p>{{comment}}</p>
    </div>
    <span class="comment-date">{{comment-date}}</span>
  </div>
</#comments>

Using the beginning and ending tag syntax will capture a code block of HTML that appears between the tags. The captured content will replace the msg.payload property and will be sent to the action in node flow segment. I.e.:

Using the beginning and ending syntax (<#comments> and </#comments>) above will capture a code block of HTML that appears between the tags. The code block is provided in the value of the msg.payload context property when the Node-RED flow segment is invoked. In the above example the Node-RED flow segment (defined using ActionFlows) named comments is invoked.

Server-side Scripting

NRG flows reserves a few names for special purposes; the <#script> tag is of special importance. When authored with beginning and ending tags, the contents interpreted as server-side JavaScript; as if they were written inside a native Node-RED function node. This means that your must do a return msg or invoke the node.send(msg); method to pass along the msg object. Remember, the content of the msg.payload will be appended to the msg.outputBuffer property which is the current output that will sent to the client's web browser. Consider the following code block:

<#script>
msg.payload = "The quick brown fox";
node.warn("jumps over the lazy dog")
</#script>

The special #script is a reserved name and you should not attempt to create a flow segment with the same name. The above code will instead execute immediately and append "The quick brown fox" to msg.outputBuffer and subsequently the web browser; followed by sending "jumps over the lazy dog" to Node-RED's debug window.

In lieu of inline code block, you can supply a filename using the self-closing tag syntax:

<#script("example.jss"); />

Note the .jss (dot j-s-s) filename extension (as in "JavaScript Server-side"). This is the preferred filename extension as it is protected from transmission by the NRG web server to the web browser. Attempts to load the page by a web browser will be returned a 403 Forbidden error page.

NRG Directives

As mentioned above, there are a few reserved NRG tag names that are used for specific purposed and should not be used as for flows:

  • #script is used for server-side JavaScript, similar to a function node.
  • #include is used to include one file content into another. Similar to PHP's include function. Care should be taken to ensure that the included file does not break XHTML beginning and ending tags to ensure proper parsing.
  • #require (pending) is used at the top of a .nrg file to dynamically deploy a .flow file before executing a .nrg file.

TODO: incomplete list of items

The following are not implemented yet (but have a good chance that they will be):

  • Support objects to be merged into msg context i.e.
<#action({payload:"Hello World",something:"else"}); />
  • Implement <#require("something.flow"); /> dynamically loads a json flow the tab titled "something" (if it does not already exist) before processing the remainder of the .nrg file. This will facilitate code distribution.
  • Implement <#prefix /> tag to enabled prefix naming actionflows.
  • Implement a session context? Should include destroy session and the ability to set an expiration time.
  • Optimize, optimize, optimize. While it's neat that it's a subflow completely "wired" in Node-RED, consider re-authoring as an actual node.
[{"id":"1c7dd13.e21a82f","type":"tab","label":"Example","disabled":false,"info":""},{"id":"2b744329.f2992c","type":"subflow","name":"nrg","info":"nrg\n===\n\nA web server \"wired\" in Node-RED that serves dynamic & static content.\n#### *Note: this is a work in progress!!!*\n#### *https://github.com/steveorevo/nrg*\n\n### About\nNRG is an open source web server \"wired\" completely in Node-RED. It is capable\nof serving static as well as dynamic web pages. It is designed to leverage the\ncapabilities of Node-RED flows with inline, special server-side tags embedded\ninto regular web pages. The web sever delivers standard HTML, CSS, most Apache\n2.0 mime-types, and the special .nrg (dot n-r-g, i.e. index.nrg) files. The .nrg\nfiles act as \"Node-RED gateway\" files like cgi files; but more similar to the\npopular and prolific ASP and PHP files used by most hosting providers.\n\nNRG accommodates a more MVC-like design pattern allowing web designers\nto focus on user interface, theme design, stylesheets, and accessibility; while\npreserving the web developer's power to use Node-RED driven logic, data,\nconductivity, and functionality.\n\n### Getting Started\nTo startup NRG simply drag the “nrg” subflow to a tab in the Node-RED editor and\nclick “Deploy”. Currently, NRG is implemented as a global singleton object and\nonly one instance of the web server should exist. However, flows that service\nbrowser requests may exist on any tab. By default, NRG will serve files from the\na subfolder titled “public_html” from the current user account (I.e.\n/Users/username/public_html). You may change the default configuration by\ndefining an [ActionFlows](https://flows.nodered.org/node/node-red-contrib-actionflows) segment and naming it \"#config\". Also by default (and\ncustomizable), the URL path is the same as the Node-RED installation. More\ninformation can be found in the followup section \"Creating NRG Flows\" below. Lastly, you\ncan change the admin path for Node-RED via your [Node-RED’s settings.js file](https://nodered.org/docs/configuration).\n\n### Creating NRG Flows\nAt the heart of NRG’s operation are \"NRG flows\" which are simply Node-RED\ncontributed nodes called [ActionFlows](https://flows.nodered.org/node/node-red-contrib-actionflows); a set of nodes that allow you\nto define a flow segment. ActonFlows are similar to “virtual wires” and\nNode-RED’s native Subflows. Two important nodes you will use to define an NRG\nflow segment are:\n\n* action in\n* action out\n\nFor example, to configure a different web server folder in lieu of the default\n(/Users/username/public_html), create an action in/out pair of nodes named\n`#config` and use a change node to alter the siteRoot property of the\n`msg.payload`. The flow segment you defined will be executed immediately after\nyou deploy; telling NRG web server where to begin serving files (see image\nbelow).\n\n![Configure NRG](https://raw.githubusercontent.com/Steveorevo/nrg/master/demo/nrg-config.jpg)\n\nThe image above shows the flow segment `#config` with the default settings in\n`msg.payload`. A definition for each of the settings are:\n\n* slash - Platform dependent directory separator. Do not change this.\n* mimeTypes - The default Content-Type/mime types set by extension.\n* siteRoot - The site root folder used to deliver content by the server.\n* urlPath - The starting URL path used to deliver content by the server.\n\nIn addition to the #config flow segment are NRG flows for customizing the 404\nPage Not Found error page, and the 403, and 500 pages. You can supply your own\ncustom error pages by supplying your own NRG flows named:\n\n* 403error\n* 404error\n* 500error\n\nYou may also invoke NRG flows from within your own webpages by simply naming the\nfile with a .nrg (dot n-r-g) extension (i.e. index.nrg) and using NRG tags. See\n\"NRG Flows in Webpages\" below for details.\n\n### NRG Flows in Webpages\nCreating dynamic web pages with NRG flows is easy; simply create an XHTML\ncompliant file named with a .nrg extension (aka \"NRG files\"). NRG files are just\nlike HTML files, but they can include both HTML tags and special \"NRG tags\".\nThe NRG tags contain the name of flow segments (ActionFlows) that are executed\nby Node-RED when the page is accessed. The resulting output is written as\nHTML within the webpage. When a user accesses a NRG page, his or her web browser\nonly gets sent the HTML code, since Node-RED has run the NRG flows in the\nbackground. A simple \"Hello World\" web page can illustrate the process:\n\n```\n<html>\n  <body>\n    <#action/>\n  </body>\n</html>\n```\n\nThe NRG flow self-closing tag `<#action/>` will run the associated flow segment\nof the same named prefix `action` in Node-RED.\n\n![NRG Flow Segment](https://raw.githubusercontent.com/Steveorevo/nrg/master/demo/nrg-hello-world.jpg)\n\nThe results of the flow segment's `msg.payload` property will be output and will\nreplace the tag. In this case, the change node will populate `msg.payload` with\nthe string \"Hello World\". The result is a simple white webpage with the text\n\"Hello World\".\n\n#### Tag Syntax for NRG Flows\nAuthors of .nrg webpages should ensure that their pages are XHTML compliant. NRG\nwill run a basic XML syntax check and report any errors for broken tags or\nincorrect syntax. The special NRG flow tags follow a strict XHTML standard;\ninternally the # (hashtag) is a shortcut that converts to `nrg_` (i.e.\n`<#action />` is the equivalent to `<nrg_action />`. The shortcut # (hashtag) is\nthe preferred method for embedding server side flows into webpages.\n\nAs shown in the example, the contents of the `msg.payload` at the end of the\nflow is returned in place of the tag in the webpage. Likewise, a tag can pass\na value into a flow as the initial content of `msg.payload`. The examples above\nfor `<#action />` pass nothing and therefore always have an empty payload.\nHowever, consider that an XML compliant self-closing tag of\n`<nrg_action payload=\"The quick brown fox\" />` would invoke the `action` node in Node-RED with a\n`msg.payload` containing \"The quick brown fox\". In the preferred shortcut syntax:\n\n```\n<#action(\"The quick brown fox\"); />\n```\n\n#### Passing Template Blocks\nOften times, a web designer may wish to send a block of HTML to Node-RED for\nprocessing or to inject dynamic content into mustache syntax template; such as\nwith the [node-red-contrib-render](https://flows.nodered.org/node/node-red-contrib-render)\nnode. In this case, use a XHTML begin and end tag (versus self-closing):\n\n```\n<#comments>\n  <div class=\"comment\">\n    <span class=\"comment-user\">Visitor {{username}} says:</span>\n    <div class=\"comment-post\">\n      <p>{{comment}}</p>\n    </div>\n    <span class=\"comment-date\">{{comment-date}}</span>\n  </div>\n</#comments>\n```\n\n\nUsing the beginning and ending tag syntax will capture a code block of HTML that\nappears between the tags. The captured content will replace the `msg.payload`\nproperty and will be sent to the `action in` node flow segment. I.e.:\n\nUsing the beginning and ending syntax (`<#comments>` and `</#comments>`) above\nwill capture a code block of HTML that appears between the tags. The code block\nis provided in the value of the `msg.payload` context property when the Node-RED\nflow segment is invoked. In the above example the Node-RED flow segment\n(defined using [ActionFlows](https://flows.nodered.org/node/node-red-contrib-actionflows)) named `comments` is invoked.\n\n### Server-side Scripting\nNRG flows reserves a few names for special purposes; the `<#script>` tag is of\nspecial importance. When authored with beginning and ending tags, the contents\ninterpreted as server-side JavaScript; as if they were written inside a native\nNode-RED function node. This means that your **must** do a `return msg` or\ninvoke the `node.send(msg);` method to pass along the msg object. Remember, the\ncontent of the `msg.payload` will be appended to the `msg.outputBuffer` property\nwhich is the current output that will sent to the client's web browser. Consider\nthe following code block:\n\n```\n<#script>\nmsg.payload = \"The quick brown fox\";\nnode.warn(\"jumps over the lazy dog\")\n</#script>\n```\n\nThe special `#script` is a reserved name and you should not attempt to create a\nflow segment with the same name. The above code will instead execute immediately\nand append \"The quick brown fox\" to `msg.outputBuffer` and subsequently the web\nbrowser; followed by sending \"jumps over the lazy dog\" to Node-RED's debug window.\n\nIn lieu of inline code block, you can supply a filename using the self-closing\ntag syntax:\n\n```\n<#script(\"example.jss\"); />\n```\n\nNote the .jss (dot j-s-s) filename extension (as in \"JavaScript Server-side\").\nThis is the preferred filename extension as it is protected from transmission\nby the NRG web server to the web browser. Attempts to load the page by a web\nbrowser will be returned a 403 Forbidden error page.\n\n### NRG Directives\nAs mentioned above, there are a few reserved NRG tag names that are used for\nspecific purposed and should not be used as for flows:\n\n* `#script` is used for server-side JavaScript, similar to a function node.\n* `#include` is used to include one file content into another. Similar to PHP's include function. Care should be taken to ensure that the included file does not break XHTML beginning and ending tags to ensure proper parsing.\n* `#require` (pending) is used at the top of a .nrg file to dynamically deploy a .flow file before executing a .nrg file.\n\n### TODO: incomplete list of items\nThe following are not implemented yet (but have a good chance that they will be):\n\n* Support objects to be merged into `msg` context i.e.\n\n```\n<#action({payload:\"Hello World\",something:\"else\"}); />\n```\n\n* Implement `<#require(\"something.flow\"); />` dynamically loads a json flow the tab titled \"something\" (if it does not already exist) before processing the remainder of the .nrg file. This will facilitate code distribution.\n* Implement `<#prefix />` tag to enabled prefix naming actionflows.\n* Implement a session context? Should include destroy session and the ability to set an expiration time.\n* Optimize, optimize, optimize. While it's neat that it's a subflow completely \"wired\" in Node-RED, consider re-authoring as an actual node.\n","in":[],"out":[]},{"id":"ce8c4585.809f48","type":"comment","z":"2b744329.f2992c","name":"Determine default siteRoot via cross-platform flow","info":"","x":1070,"y":80,"wires":[]},{"id":"4acddf15.5a8ee8","type":"exec","z":"2b744329.f2992c","command":"","addpay":true,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":930,"y":240,"wires":[["38114fd0.ea5da8"],[],[]]},{"id":"38114fd0.ea5da8","type":"switch","z":"2b744329.f2992c","name":"","property":"payload","propertyType":"msg","rules":[{"t":"cont","v":"$HOME","vt":"str"},{"t":"else"}],"checkall":"true","outputs":2,"x":930,"y":300,"wires":[["500cde.320d1b24"],["be851e51.8cddd"]]},{"id":"be851e51.8cddd","type":"string","z":"2b744329.f2992c","name":"/public_html","methods":[{"name":"trim","params":[]},{"name":"append","params":[{"type":"str","value":"/public_html"}]}],"prop":"payload","propout":"payload","object":"msg","objectout":"msg","x":950,"y":360,"wires":[["950e5825.b93248"]]},{"id":"6e00c9ac.1e12a","type":"string","z":"2b744329.f2992c","name":"\\public_html","methods":[{"name":"trim","params":[]},{"name":"append","params":[{"type":"str","value":"\\public_html"}]}],"prop":"payload","propout":"payload","object":"msg","objectout":"msg","x":1170,"y":240,"wires":[["950e5825.b93248"]]},{"id":"d4ed7db1.5155a","type":"exec","z":"2b744329.f2992c","command":"","addpay":true,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"","x":1150,"y":180,"wires":[["6e00c9ac.1e12a"],[],[]]},{"id":"500cde.320d1b24","type":"change","z":"2b744329.f2992c","name":"On Windows","rules":[{"t":"set","p":"payload","pt":"msg","to":"echo %HOMEDRIVE%%HOMEPATH%","tot":"str"},{"t":"set","p":"nrg.slash","pt":"flow","to":"\\","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1170,"y":120,"wires":[["d4ed7db1.5155a"]]},{"id":"950e5825.b93248","type":"template","z":"2b744329.f2992c","name":"mime-types","field":"nrg.mimeTypes","fieldType":"flow","format":"json","syntax":"plain","template":"{\n  \"application/andrew-inset\":[\n    \"ez\"\n  ],\n  \"application/mac-binhex40\":[\n    \"hqx\"\n  ],\n  \"application/mac-compactpro\":[\n    \"cpt\"\n  ],\n  \"application/msword\":[\n    \"doc\"\n  ],\n  \"application/octet-stream\":[\n    \"bin\",\n    \"dms\",\n    \"lha\",\n    \"lzh\",\n    \"exe\",\n    \"class\",\n    \"so\",\n    \"dll\"\n  ],\n  \"application/oda\":[\n    \"oda\"\n  ],\n  \"application/pdf\":[\n    \"pdf\"\n  ],\n  \"application/postscript\":[\n    \"ai\",\n    \"eps\",\n    \"ps\"\n  ],\n  \"application/smil\":[\n    \"smi\",\n    \"smil\"\n  ],\n  \"application/vnd.mif\":[\n    \"mif\"\n  ],\n  \"application/vnd.ms-excel\":[\n    \"xls\"\n  ],\n  \"application/vnd.ms-powerpoint\":[\n    \"ppt\"\n  ],\n  \"application/vnd.wap.wbxml\":[\n    \"wbxml\"\n  ],\n  \"application/vnd.wap.wmlc\":[\n    \"wmlc\"\n  ],\n  \"application/vnd.wap.wmlscriptc\":[\n    \"wmlsc\"\n  ],\n  \"application/x-bcpio\":[\n    \"bcpio\"\n  ],\n  \"application/x-cdlink\":[\n    \"vcd\"\n  ],\n  \"application/x-chess-pgn\":[\n    \"pgn\"\n  ],\n  \"application/x-cpio\":[\n    \"cpio\"\n  ],\n  \"application/x-csh\":[\n    \"csh\"\n  ],\n  \"application/x-director\":[\n    \"dcr\",\n    \"dir\",\n    \"dxr\"\n  ],\n  \"application/x-dvi\":[\n    \"dvi\"\n  ],\n  \"application/x-futuresplash\":[\n    \"spl\"\n  ],\n  \"application/x-gtar\":[\n    \"gtar\"\n  ],\n  \"application/x-hdf\":[\n    \"hdf\"\n  ],\n  \"application/x-javascript\":[\n    \"js\"\n  ],\n  \"application/x-koan\":[\n    \"skp\",\n    \"skd\",\n    \"skt\",\n    \"skm\"\n  ],\n  \"application/x-latex\":[\n    \"latex\"\n  ],\n  \"application/x-netcdf\":[\n    \"nc\",\n    \"cdf\"\n  ],\n  \"application/x-sh\":[\n    \"sh\"\n  ],\n  \"application/x-shar\":[\n    \"shar\"\n  ],\n  \"application/x-shockwave-flash\":[\n    \"swf\"\n  ],\n  \"application/x-stuffit\":[\n    \"sit\"\n  ],\n  \"application/x-sv4cpio\":[\n    \"sv4cpio\"\n  ],\n  \"application/x-sv4crc\":[\n    \"sv4crc\"\n  ],\n  \"application/x-tar\":[\n    \"tar\"\n  ],\n  \"application/x-tcl\":[\n    \"tcl\"\n  ],\n  \"application/x-tex\":[\n    \"tex\"\n  ],\n  \"application/x-texinfo\":[\n    \"texinfo\",\n    \"texi\"\n  ],\n  \"application/x-troff\":[\n    \"t\",\n    \"tr\",\n    \"roff\"\n  ],\n  \"application/x-troff-man\":[\n    \"man\"\n  ],\n  \"application/x-troff-me\":[\n    \"me\"\n  ],\n  \"application/x-troff-ms\":[\n    \"ms\"\n  ],\n  \"application/x-ustar\":[\n    \"ustar\"\n  ],\n  \"application/x-wais-source\":[\n    \"src\"\n  ],\n  \"application/xhtml+xml\":[\n    \"xhtml\",\n    \"xht\"\n  ],\n  \"application/zip\":[\n    \"zip\"\n  ],\n  \"audio/basic\":[\n    \"au\",\n    \"snd\"\n  ],\n  \"audio/midi\":[\n    \"mid\",\n    \"midi\",\n    \"kar\"\n  ],\n  \"audio/mpeg\":[\n    \"mpga\",\n    \"mp2\",\n    \"mp3\"\n  ],\n  \"audio/x-aiff\":[\n    \"aif\",\n    \"aiff\",\n    \"aifc\"\n  ],\n  \"audio/x-mpegurl\":[\n    \"m3u\"\n  ],\n  \"audio/x-pn-realaudio\":[\n    \"ram\",\n    \"rm\"\n  ],\n  \"audio/x-pn-realaudio-plugin\":[\n    \"rpm\"\n  ],\n  \"audio/x-realaudio\":[\n    \"ra\"\n  ],\n  \"audio/x-wav\":[\n    \"wav\"\n  ],\n  \"chemical/x-pdb\":[\n    \"pdb\"\n  ],\n  \"chemical/x-xyz\":[\n    \"xyz\"\n  ],\n  \"image/bmp\":[\n    \"bmp\"\n  ],\n  \"image/gif\":[\n    \"gif\"\n  ],\n  \"image/ief\":[\n    \"ief\"\n  ],\n  \"image/jpeg\":[\n    \"jpeg\",\n    \"jpg\",\n    \"jpe\"\n  ],\n  \"image/png\":[\n    \"png\"\n  ],\n  \"image/tiff\":[\n    \"tiff\",\n    \"tif\"\n  ],\n  \"image/vnd.djvu\":[\n    \"djvu\",\n    \"djv\"\n  ],\n  \"image/vnd.wap.wbmp\":[\n    \"wbmp\"\n  ],\n  \"image/x-cmu-raster\":[\n    \"ras\"\n  ],\n  \"image/x-portable-anymap\":[\n    \"pnm\"\n  ],\n  \"image/x-portable-bitmap\":[\n    \"pbm\"\n  ],\n  \"image/x-portable-graymap\":[\n    \"pgm\"\n  ],\n  \"image/x-portable-pixmap\":[\n    \"ppm\"\n  ],\n  \"image/x-rgb\":[\n    \"rgb\"\n  ],\n  \"image/x-xbitmap\":[\n    \"xbm\"\n  ],\n  \"image/x-xpixmap\":[\n    \"xpm\"\n  ],\n  \"image/x-xwindowdump\":[\n    \"xwd\"\n  ],\n  \"model/iges\":[\n    \"igs\",\n    \"iges\"\n  ],\n  \"model/mesh\":[\n    \"msh\",\n    \"mesh\",\n    \"silo\"\n  ],\n  \"model/vrml\":[\n    \"wrl\",\n    \"vrml\"\n  ],\n  \"text/css\":[\n    \"css\"\n  ],\n  \"text/html\":[\n    \"html\",\n    \"htm\",\n    \"nrg\"\n  ],\n  \"text/plain\":[\n    \"asc\",\n    \"txt\"\n  ],\n  \"text/richtext\":[\n    \"rtx\"\n  ],\n  \"text/rtf\":[\n    \"rtf\"\n  ],\n  \"text/sgml\":[\n    \"sgml\",\n    \"sgm\"\n  ],\n  \"text/tab-separated-values\":[\n    \"tsv\"\n  ],\n  \"text/vnd.wap.wml\":[\n    \"wml\"\n  ],\n  \"text/vnd.wap.wmlscript\":[\n    \"wmls\"\n  ],\n  \"text/x-setext\":[\n    \"etx\"\n  ],\n  \"text/xml\":[\n    \"xml\",\n    \"xsl\"\n  ],\n  \"video/mpeg\":[\n    \"mpeg\",\n    \"mpg\",\n    \"mpe\"\n  ],\n  \"video/quicktime\":[\n    \"qt\",\n    \"mov\"\n  ],\n  \"video/vnd.mpegurl\":[\n    \"mxu\"\n  ],\n  \"video/x-msvideo\":[\n    \"avi\"\n  ],\n  \"video/x-sgi-movie\":[\n    \"movie\"\n  ],\n  \"x-conference/x-cooltalk\":[\n    \"ice\"\n  ],\n  \"image/svg+xml\":[\n    \"svg\"\n  ]\n}\n","output":"json","x":1170,"y":300,"wires":[["74e2017b.703d1"]]},{"id":"407d6e60.462258","type":"change","z":"2b744329.f2992c","name":"set flow.nrg defaults","rules":[{"t":"set","p":"nrg.siteRoot","pt":"flow","to":"payload","tot":"msg"},{"t":"set","p":"nrg.urlPath","pt":"flow","to":"","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"nrg","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":1200,"y":420,"wires":[["1ae4f684.f8fb91"]]},{"id":"1ae4f684.f8fb91","type":"actionflows","z":"2b744329.f2992c","info":"This action occurs after the NRG-Flows web server's default configuration settings\nhave been set. You can modify the `msg.payload` in this action flow by defining\na flow segment using the ActionFlows `action in/out` nodes. Changing the \n`msg.payload` contents in this action will update the settings stored in the\n`flow.nrg` context. \n\n## NRG Settings\nThe `msg.payload` object will contain the following properties that you can customize.\n\n* **siteRoot** - determines the document root folder to serve files from. The default is \"public_html\" under the current user folder. I.e. /Users/john/public_html\n* **urlPath** - determines the URL mount point where files will be appear under. The default is an empty string to serve files from the domain root. I.e. http://localhost. Set this value to begin serving under a subfolder. I.e. \"/subfolder\" will serve files under http://localhost/subfolder.\n* **mimeTypes** - determines the supported Content-Types that are set in the HTTP response header when a given file is requested. Content-Types are mapped by filename extension. This value contains an associative array.\n* **slash** - the platform directory separator \"/\" or \"\\\" (Windows) ","untilproptype":"num","proptype":"msg","name":"#config","prop":"loop","untilprop":0,"until":"gt","loop":"none","scope":"global","perf":false,"seq":false,"x":1160,"y":480,"wires":[["8a1e70f5.cbfa58"]]},{"id":"8a1e70f5.cbfa58","type":"change","z":"2b744329.f2992c","name":"copy msg.payload to flow.nrg","rules":[{"t":"set","p":"nrg","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1230,"y":540,"wires":[["827430c7.7019a"]]},{"id":"eae83b6a.d41b68","type":"http in","z":"2b744329.f2992c","name":"http","url":"*","method":"get","upload":false,"swaggerDoc":"","x":90,"y":120,"wires":[["9b2be483.5328f"]]},{"id":"ebf05640.3a435","type":"change","z":"2b744329.f2992c","name":"$HOME","rules":[{"t":"set","p":"payload","pt":"msg","to":"echo $HOME","tot":"str"},{"t":"set","p":"nrg.slash","pt":"flow","to":"/","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":940,"y":180,"wires":[["4acddf15.5a8ee8"]]},{"id":"cf109c5a.fb9408","type":"actionflows_in","z":"2b744329.f2992c","name":"#deployed","priority":"50","links":[],"scope":"private","x":940,"y":120,"wires":[["ebf05640.3a435"]]},{"id":"827430c7.7019a","type":"actionflows_out","z":"2b744329.f2992c","name":"#deployed","links":[],"x":1170,"y":600,"wires":[]},{"id":"df734c25.ec7a88","type":"comment","z":"2b744329.f2992c","name":"Serve files from siteRoot","info":"","x":150,"y":80,"wires":[]},{"id":"152af42.bed718c","type":"function","z":"2b744329.f2992c","name":"resolver","func":"/*\n * Resolve the GET request to a filename and it's content type\n * msg.filename - the filename to be read and sent to client\n * msg.topic - the Content-Type to be sent in the header\n * msg.statusCode - set to default 200, OK\n * msg.headers - initialize headers to empty object {}\n */\n\n// Use string object in our function\nvar S = function(x) {\n  return msg.string.setValue(x);\n}\n\n// Check for invalid urlPath\nvar nrg = flow.get(\"nrg\");\nmsg.statusCode = 200;\nif (!msg.req.url.startsWith(nrg.urlPath)) {\n    msg.statusCode = 403;\n    node.send(msg);\n    return;\n}\n\n// Obtain filepath\nvar file = S(msg.req.url).delLeftMost(nrg.urlPath)\n           .prepend(nrg.siteRoot);\nvar ext = file.getRightMost(\"/\");\n\n// Missing file extension, assume folder and \"/index.nrg\"\nif (ext.indexOf(\".\") == -1) {\n    ext = \"nrg\";\n    file = file.ensureRight(\"/\").concat(\"index.nrg\");\n}else{\n    ext = ext.getRightMost(\".\").s;\n}\nmsg.filename = file.s;\n\n// Prevent forbidden extensions\nif (nrg.forbidden.indexOf(ext) != -1) {\n    msg.statusCode = 403;\n    node.send(msg);\n    return;\n}\n\n// Callback hell; find the file or try index.nrg, index.html, index.htm\nvar af = global.get(\"actionflows\");\nvar p = af.invoke(\"#getFile\", msg);\np.then(function(msg) {\n    if (typeof msg.error != \"undefined\") {\n        msg.statusCode = 404;\n        if (msg.filename.endsWith(\"index.nrg\")) {\n            \n            // Try index.html\n            var s = S(msg.filename);\n            msg.filename = s.delRightMost(\"index.nrg\").concat(\"index.html\").s;\n            delete msg.error;\n            p = af.invoke(\"#getFile\", msg);\n            p.then(function(msg) {\n                if (typeof msg.error != \"undefined\") {\n                    msg.statusCode = 404;\n                    \n                    // Try index.htm\n                    var s = S(msg.filename);\n                    msg.filename = s.delRightMost(\"index.html\").concat(\"index.htm\").s;\n                    delete msg.error;\n                    p = af.invoke(\"#getFile\", msg);\n                    p.then(function(msg) {\n                        if (typeof msg.error != \"undefined\") {\n                            msg.statusCode = 404;\n                            node.send(msg);\n                        }else{\n                            node.send(msg);\n                        }\n                    });\n                }else{\n                    node.send(msg);\n                }\n            });\n        }else{\n            node.send(msg);\n        }\n    }else{\n        node.send(msg);\n    }\n});\n","outputs":1,"noerr":0,"x":100,"y":240,"wires":[["95a1f86a.f56ab8"]]},{"id":"9b2be483.5328f","type":"string","z":"2b744329.f2992c","name":"","methods":[],"prop":"","propout":"string","object":"str","objectout":"msg","x":90,"y":180,"wires":[["152af42.bed718c"]]},{"id":"b44cb9d4.06cc38","type":"http response","z":"2b744329.f2992c","name":"http","statusCode":"","headers":{},"x":90,"y":420,"wires":[]},{"id":"a26777dd.a27588","type":"actionflows_in","z":"2b744329.f2992c","name":"#getFile","priority":"60","links":[],"scope":"private","x":90,"y":540,"wires":[["5047269e.4cb0a"]]},{"id":"16f8063.3a9367a","type":"actionflows_out","z":"2b744329.f2992c","name":"#getFile","links":[],"x":280,"y":600,"wires":[]},{"id":"5047269e.4cb0a","type":"file in","z":"2b744329.f2992c","name":"getFile","filename":"","format":"","chunk":false,"sendError":false,"x":230,"y":540,"wires":[["16f8063.3a9367a"]]},{"id":"855a8fb2.04a638","type":"catch","z":"2b744329.f2992c","name":"getFileError","scope":["5047269e.4cb0a"],"x":110,"y":600,"wires":[["16f8063.3a9367a"]]},{"id":"39ac941.b26966c","type":"comment","z":"2b744329.f2992c","name":"Find the file for resolver, includes, jss","info":"","x":180,"y":500,"wires":[]},{"id":"95a1f86a.f56ab8","type":"function","z":"2b744329.f2992c","name":"find nrg","func":"// Include nrg, actionflows\nvar af = global.get(\"actionflows\");\nvar nrg = flow.get(\"nrg\");\n\n// Use string object in our function\nvar S = function(x) {\n  return msg.string.setValue(x);\n};\n\n// Get Content-Type by extension to mime-types\nvar ext = S(msg.filename).getRightMost(\".\").s;\nmsg.topic = \"text/html\";\nfor (var key in nrg.mimeTypes) {\n    if (nrg.mimeTypes[key].indexOf(ext) != -1) {\n        msg.topic = key;\n        break;\n    }\n}\n\n// Process error templates\nif (msg.statusCode != 200) {\n    var p = af.invoke(\"#\" + msg.statusCode + \"error\", msg);\n    p.then(function(msg) {\n        node.send([msg, null]);\n    });\n}else{\n\n    // Process .nrg files\n    if (ext == \"nrg\") {\n        node.send([null, msg]);\n    }else{\n        node.send([msg, null]);\n    }\n}\n","outputs":"2","noerr":0,"x":100,"y":300,"wires":[["b17e6813.77b8f8"],["2b324510.4fe082"]]},{"id":"b17e6813.77b8f8","type":"change","z":"2b744329.f2992c","name":"Content-Type","rules":[{"t":"set","p":"headers.Content-Type","pt":"msg","to":"topic","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":120,"y":360,"wires":[["b44cb9d4.06cc38"]]},{"id":"9b14c547.fb5e18","type":"actionflows_in","z":"2b744329.f2992c","name":"#404error","priority":"49","links":[],"scope":"private","x":460,"y":480,"wires":[["fe7a412c.0a8998"]]},{"id":"e1199e46.375f9","type":"actionflows_out","z":"2b744329.f2992c","name":"#404error","links":[],"x":740,"y":480,"wires":[]},{"id":"fe7a412c.0a8998","type":"template","z":"2b744329.f2992c","name":"Template","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"404 Not Found <span>{{req.url}}</span>","output":"str","x":600,"y":480,"wires":[["e1199e46.375f9"]]},{"id":"ffdbf37d.fb8668","type":"actionflows_in","z":"2b744329.f2992c","name":"#403error","priority":"49","links":[],"scope":"private","x":460,"y":420,"wires":[["6b9729d9.736b68"]]},{"id":"6b9729d9.736b68","type":"template","z":"2b744329.f2992c","name":"Template","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"403 Forbidden <span>{{req.url}}</span>\n","output":"str","x":600,"y":420,"wires":[["ab4a1907.2d6178"]]},{"id":"ab4a1907.2d6178","type":"actionflows_out","z":"2b744329.f2992c","name":"#403error","links":[],"x":740,"y":420,"wires":[]},{"id":"295eea2b.59fa96","type":"comment","z":"2b744329.f2992c","name":"Default error pages","info":"","x":490,"y":380,"wires":[]},{"id":"284029e2.c88b06","type":"function","z":"2b744329.f2992c","name":"nrgFlows","func":"// Use string object in our function\nvar S = function(x) {\n  return msg.string.setValue(x);\n};\n\n// Check nrg file syntax, report any errors\nif (typeof msg.error != \"undefined\" || msg.statusCode == 500) {\n    var af = global.get(\"actionflows\");\n    node.error(msg.error.message);\n    msg.statusCode = 500;\n    var p = af.invoke(\"#\" + msg.statusCode + \"error\", msg);\n    p.then(function(msg) {\n        node.send(msg);\n    });\n    return;\n}\n\n// Get list of all nrg tags in the order they appear\n// with attributes as object\nvar nrgtag = [];\nfunction getKeys(p) {\n    var keys = [];\n    for (var key in p) {\n        keys.push(key);\n        if (key.startsWith(\"nrg_\")) {\n            if (Array.isArray(p[key])) {\n                for (var i = 0; i < p[key].length; i++) {\n                   nrgtag.push(Object.assign({action:key.substr(4)}, p[key][i].$));\n                }\n            }else{\n                nrgtag.push(Object.assign({action:key.substr(4)}, p[key].$));\n            }\n        }\n        if (typeof p[key] == \"object\") {\n          keys = keys.concat(getKeys(p[key]));  \n        }\n    }\n    return keys;\n}\ngetKeys(msg.payload);\nmsg.payload = \"\";\n\n// Invoke nrgFlows\nmsg.outputBuffer = \"\";\nvar af = global.get(\"actionflows\");\nvar nrg = flow.get(\"nrg\");\nfunction nrgFlows() {\n    var n = nrgtag.shift();\n    msg.outputBuffer += msg.pre.getLeftMost(\"<nrg_\" + n.action);\n    msg.pre = msg.pre.delLeftMost(\"<nrg_\" + n.action);\n    if (n.selfclosing == \"true\") {\n        msg.pre = msg.pre.delLeftMost(\"/>\");\n    }else{\n        // Obtain block for payload\n        msg.pre = msg.pre.delLeftMost(\">\");\n        n.payload = msg.pre.getLeftMost(\"</nrg_\" + n.action + \">\").s;\n        msg.pre = msg.pre.delLeftMost(\"</nrg_\" + n.action + \">\");\n    }\n    msg = Object.assign(msg, n);\n    delete msg.action;\n    if (n.action == \"script\") {\n        n.action = \"#\" + n.action;\n        delete msg.filename;\n        if (n.selfclosing == \"true\") {\n            var file = S(n.payload).replaceAll(\"/\", nrg.slash).replaceAll(\"\\\\\", nrg.slash);\n            if (file.left(1) != nrg.slash) {\n                file = file.prepend(nrg.siteRoot + nrg.slash).s;\n            }\n            msg.filename = file;\n            msg.evalScript = \"\";\n            msg.payload = \"\";\n        }else{\n            msg.evalScript = n.payload;\n            msg.payload = \"\";\n        }\n    }\n    var p = af.invoke(n.action, msg);\n    p.then(function(msg) {\n        // Report any errors\n        if (msg.statusCode != 200) {\n            var p = af.invoke(\"#\" + msg.statusCode + \"error\", msg);\n            p.then(function(msg) {\n                node.send(msg);\n            });\n            return;\n        }\n        if (nrgtag.length > 0) {\n            msg.outputBuffer += msg.payload;\n            nrgFlows();\n        }else{\n            msg.outputBuffer += msg.payload + msg.pre;\n            msg.payload = msg.outputBuffer.substr(5, msg.outputBuffer.length - 11);\n            delete msg.outputBuffer;\n            delete msg.pre;\n            node.send(msg);\n        }\n    });\n}\nnrgFlows();\n\n","outputs":1,"noerr":0,"x":680,"y":180,"wires":[["7c6975ac.e2c9ec"]]},{"id":"41b36ce3.da21ec","type":"comment","z":"2b744329.f2992c","name":"Process .nrg files","info":"","x":480,"y":80,"wires":[]},{"id":"38763afd.6347be","type":"http response","z":"2b744329.f2992c","name":"http","statusCode":"","headers":{},"x":670,"y":300,"wires":[]},{"id":"7c6975ac.e2c9ec","type":"change","z":"2b744329.f2992c","name":"Content-Type","rules":[{"t":"set","p":"headers.Content-Type","pt":"msg","to":"topic","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":240,"wires":[["38763afd.6347be"]]},{"id":"c49a00e7.3990a8","type":"xml","z":"2b744329.f2992c","name":"","attr":"","chr":"","x":670,"y":120,"wires":[["284029e2.c88b06"]]},{"id":"322924fe.0a0d44","type":"catch","z":"2b744329.f2992c","name":"xmlError","scope":["c49a00e7.3990a8"],"x":460,"y":300,"wires":[["284029e2.c88b06"]]},{"id":"e0da60c2.fdca5","type":"actionflows_in","z":"2b744329.f2992c","name":"#500error","priority":"49","links":[],"scope":"private","x":460,"y":540,"wires":[["ac095c79.a72ef"]]},{"id":"2c02f15c.ac5c06","type":"actionflows_out","z":"2b744329.f2992c","name":"#500error","links":[],"x":740,"y":540,"wires":[]},{"id":"ac095c79.a72ef","type":"template","z":"2b744329.f2992c","name":"Template","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"500 Internal Error <span>{{error.message}}</span>","output":"str","x":600,"y":540,"wires":[["2c02f15c.ac5c06"]]},{"id":"74e2017b.703d1","type":"template","z":"2b744329.f2992c","name":"forbidden","field":"nrg.forbidden","fieldType":"flow","format":"json","syntax":"plain","template":"[\"flow\",\"jss\"]","output":"json","x":1160,"y":360,"wires":[["407d6e60.462258"]]},{"id":"8bae2db6.afc1c8","type":"comment","z":"2b744329.f2992c","name":"Reserved names for nrgFlows / directives","info":"","x":200,"y":680,"wires":[]},{"id":"da57a2b1.c686a","type":"actionflows_in","z":"2b744329.f2992c","name":"#prefix","priority":"50","links":[],"scope":"private","x":90,"y":840,"wires":[["1960b8d2.07493f"]]},{"id":"1960b8d2.07493f","type":"actionflows_out","z":"2b744329.f2992c","name":"#prefix","links":[],"x":250,"y":840,"wires":[]},{"id":"998aab78.841798","type":"actionflows_in","z":"2b744329.f2992c","name":"#require","priority":"50","links":[],"scope":"private","x":100,"y":780,"wires":[["be614f71.721b18"]]},{"id":"be614f71.721b18","type":"actionflows_out","z":"2b744329.f2992c","name":"#require","links":[],"x":260,"y":780,"wires":[]},{"id":"68aec0dd.8087c","type":"actionflows_in","z":"2b744329.f2992c","name":"#script","priority":"50","links":[],"scope":"private","x":90,"y":900,"wires":[["c3ac7cf3.0f2a58"]]},{"id":"58ce5d1.adbb724","type":"actionflows_out","z":"2b744329.f2992c","name":"#script","links":[],"x":530,"y":900,"wires":[]},{"id":"348867d.396d098","type":"actionflows_in","z":"2b744329.f2992c","name":"#include","priority":"1","links":[],"scope":"private","x":100,"y":720,"wires":[["e956e303.e09468"]]},{"id":"f0354aa7.34efa8","type":"actionflows_out","z":"2b744329.f2992c","name":"#include","links":[],"x":540,"y":720,"wires":[]},{"id":"2b324510.4fe082","type":"string","z":"2b744329.f2992c","name":"nrgWrap","methods":[{"name":"wrapHTML","params":[{"type":"str","value":"nrg"},{"type":"json","value":""}]}],"prop":"payload","propout":"payload","object":"msg","objectout":"msg","x":460,"y":120,"wires":[["1b4b895e.800daf"]]},{"id":"e956e303.e09468","type":"function","z":"2b744329.f2992c","name":"formatXML","func":"// Preprocess nrg files, convert short format syntax to\n// XML compatible with attributes\n\n// Use string object in our function\nvar S = function(x) {\n  return msg.string.setValue(x);\n};\n\nvar chunks = S(msg.payload).replaceAll(\"<#\", \"<nrg_\").replaceAll(\"</#\", \"</nrg_\").splitLeft(\"<nrg_\", -1);\nvar result = \"\";\nif (typeof msg.includes == \"undefined\") {\n    msg.includes = {};\n}\nwhile(chunks.length > 0) {\n    var chunk = S(chunks.shift());\n    // TODO: replace dirty hack; match brackets ignoring quoted string\n    var tag = chunk.getLeftMost(String.fromCharCode(10))\n        .replaceAll('\">\"', '~|~').getLeftMost(\">\").append(\">\")\n        .replaceAll('~|~', '\">\"'); // This overcomes until=\">\" scenario\n    chunk = chunk.delLeftMost(tag);\n    // Identify self-closing\n    if (tag.right(2) == \"/>\" && tag.indexOf(\"selfclosing\") == -1) {\n        tag = S(tag.slice(0, -2).trim() + \" selfclosing=\\\"true\\\" />\");\n    }\n    \n    // Expand short format\n    if (tag.indexOf(\"(\") != -1 && tag.indexOf(\");\") != -1) {\n        var before = tag.getLeftMost(\"(\");\n        var value = tag.delLeftMost(\"(\").getLeftMost(\");\");\n        if (value.length !== 0) {\n            value = value.ensureLeft('\"').ensureRight('\"');\n        }else{\n            value = '\"\"';\n        }\n        var after = tag.delLeftMost(\");\");\n        tag = before + \" payload=\" + value + after;\n    }\n    if (tag.indexOf(\"include \") != -1) {\n        var file = S(tag).delLeftMost('payload=\"').getLeftMost('\"');\n        if (typeof msg.includes[file] == \"undefined\") {\n            msg.includes[file] = null;\n        }\n    }\n    chunk = tag + chunk;\n    result = result + chunk;\n    if (chunks.length > 0) {\n         result += \"<nrg_\";\n    }else{\n        msg.payload = result;\n        node.send(msg);\n    }    \n}","outputs":1,"noerr":0,"x":250,"y":720,"wires":[["e560ef08.9cd07"]]},{"id":"508eee03.0eb5e8","type":"actionflows","z":"2b744329.f2992c","info":"Describe your action API here.","untilproptype":"bool","proptype":"msg","name":"#include","prop":"loop","untilprop":"false","until":"eq","loop":"watch","scope":"private","perf":false,"seq":false,"x":460,"y":240,"wires":[["c49a00e7.3990a8"]]},{"id":"e560ef08.9cd07","type":"function","z":"2b744329.f2992c","name":"includes","func":"// check that includes are obtained\n// check if includes is in payload, if so, replace it with code and \n// set msg.loop = true else false\n\n// Use string object in our function\nvar S = function(x) {\n  return msg.string.setValue(x);\n};\nvar af = global.get(\"actionflows\");\nvar nrg = flow.get(\"nrg\");\n\n// Read all include files\nvar preads = [];\nfor (var key in msg.includes) {\n    if (msg.includes[key] === null) {\n        var file = S(key).replaceAll(\"/\", nrg.slash).replaceAll(\"\\\\\", nrg.slash);\n        if (file.left(1) != nrg.slash && file.charAt(2) != \":\") {\n            file = file.prepend(nrg.siteRoot + nrg.slash).s;\n        }\n        var m = { filename: file, payload: \"\" };\n        preads.push(af.invoke(\"#getFile\", m).then(function(m) {\n            if (typeof m.error == \"undefined\") {\n                msg.includes[key] = String(m.payload);\n            }else{\n                msg.error = m.error;\n                node.error(msg.error.message);\n                msg.statusCode = 500;\n            }\n        }));\n    }\n}\n\n// Swap out all includes\nPromise.all(preads).then(function() {\n    var result = S(msg.payload);\n    for (var key in msg.includes) {\n        var tag = '<nrg_include payload=\"' + key + '\" selfclosing=\"true\" />';\n        result = result.replaceAll(tag, msg.includes[key]);\n    }\n    msg.payload = result.s;\n    \n    if (msg.payload.indexOf(\"<#include\") == -1) {\n        msg.pre = S(msg.payload); // preload looks too much like payload\n        msg.loop = false;\n    }else{\n        msg.loop = true;\n    }\n    node.send(msg);\n});\n","outputs":"1","noerr":0,"x":400,"y":720,"wires":[["f0354aa7.34efa8"]]},{"id":"1b4b895e.800daf","type":"change","z":"2b744329.f2992c","name":"initLoop","rules":[{"t":"set","p":"loop","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":460,"y":180,"wires":[["508eee03.0eb5e8"]]},{"id":"c3ac7cf3.0f2a58","type":"function","z":"2b744329.f2992c","name":"evalScript","func":"// Execute inline script block\nif (typeof msg.filename == \"undefined\") {\n    return eval(\"(function(){\" + msg.evalScript + \"})();\");\n\n// Obtain JavaScript server-side (.jss)\n}else{\n    var af = global.get(\"actionflows\");\n    var p = af.invoke(\"#getFile\", msg);\n    p.then(function(msg) {\n        if (typeof msg.error == \"undefined\") {\n            msg.evalScript = msg.payload;\n            msg.payload = \"\";\n            var result = eval(\"(function(){\" + msg.evalScript + \"})();\");\n            if (typeof result != \"undefined\") {\n                node.send(result);\n            }\n        }else{\n            node.error(msg.error.message);\n            msg.statusCode = 500;\n            node.send(msg);\n        }\n    });\n}\n","outputs":1,"noerr":0,"x":300,"y":900,"wires":[["58ce5d1.adbb724"]]},{"id":"304aca06.401916","type":"comment","z":"2b744329.f2992c","name":"NRG version 0.0.1, © 2018 Stephen J. Carnam","info":"","x":220,"y":40,"wires":[]},{"id":"7b5782a1.a4a79c","type":"subflow:2b744329.f2992c","z":"1c7dd13.e21a82f","name":"","x":110,"y":60,"wires":[]},{"id":"c9723dd1.21b2a8","type":"inject","z":"1c7dd13.e21a82f","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":120,"y":160,"wires":[["c861d8c8.3f9ac"]]},{"id":"2eacc5a6.9176d2","type":"debug","z":"1c7dd13.e21a82f","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":270,"y":340,"wires":[]},{"id":"c861d8c8.3f9ac","type":"function","z":"1c7dd13.e21a82f","name":"","func":"node.warn({x:this});\nreturn msg;","outputs":1,"noerr":0,"x":190,"y":240,"wires":[["2eacc5a6.9176d2"]]}]

Flow Info

Created 6 years, 9 months ago
Rating: not yet rated

Owner

Actions

Rate:

Node Types

Core
  • catch (x2)
  • change (x7)
  • comment (x7)
  • debug (x1)
  • exec (x2)
  • file in (x1)
  • function (x7)
  • http in (x1)
  • http response (x2)
  • inject (x1)
  • switch (x1)
  • template (x5)
  • xml (x1)
Other

Tags

  • nrg
  • server
  • dynamic
  • html
  • webserver
  • http
  • pages
  • embedding
  • embed
  • nrgflows
  • actionflows
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option