This plugin allows you to create loxberry plugins easily with nodeJS and ExpressJS as the backend server. Normally plugins can be written using Pearl or PHP. NodeJs was possible even before this plugin by providing everything yourself. Websockets can be handled as well.
Attention
The functionality comes as a pluin to the loxberry. If you want to rely on this in your plugin, you need to check if the express plugin is installed on the target loxberry system. In case it's not installed, the installation routine should not be interruped. A check should be done while accessing the plugin. Here is a small example.
webfrontend/htmlauth/index.cgi
#!/usr/bin/perl
require LoxBerry::Web;
use LoxBerry::System;
use CGI;
# This is to check if the express plugin is installed and in case it's not
# it will print an error with the hint that the unifi_presence plugin requires
# the express plugin.
my $minRequiredVersion = "0.0.4";
my $unvalidVersion = "0.1.0";
my $version = LoxBerry::System::pluginversion("express");
if ($version && $version ge $minRequiredVersion && $version lt $unvalidVersion) {
my $q = CGI->new;
print $q->header(-status => 307, -location => '/admin/express/plugins/<Plugin name>');
exit(0);
}
my $template = HTML::Template->new(
filename => "$lbptemplatedir/error.html",
global_vars => 1,
loop_context_vars => 1,
die_on_bad_params => 0,
);
$template->param( REQUIRED_VERSION => $minRequiredVersion);
$template->param( MAX_VERSION => $unvalidVersion);
%L = LoxBerry::System::readlanguage($template, "language.ini");
LoxBerry::Web::lbheader("<Plugin name>", "", "");
print $template->output();
LoxBerry::Web::lbfooter();
templates/error.html
<h3><TMPL_VAR COMMON.MISSING_PLUGIN></h3>
<p style="color: red">
<TMPL_VAR COMMON.REQUIRE_EXPRESS_1>
<a href="https://loxwiki.atlassian.net/wiki/spaces/LOXBERRY/pages/1673527328/Express+Server
" target="_blank"><TMPL_VAR COMMON.EXPRESS_PLUGIN> (>=<TMPL_VAR REQUIRED_VERSION> & < <TMPL_VAR MAX_VERSION>)</a>
<TMPL_VAR COMMON.REQUIRE_EXPRESS_2>
<p>
language_files
// templates/lang/language_de.ini
[COMMON]
MISSING_PLUGIN="Fehlendes Plugin"
REQUIRE_EXPRESS_1="Dieses Plugin benötigt das"
REQUIRE_EXPRESS_2="Plugin. Du kannst es entweder installieren oder das Plugin deinstallieren."
EXPRESS_PLUGIN="Express Server"
// templates/lang/language_en.ini
[COMMON]
MISSING_PLUGIN="Missing Plugin"
REQUIRE_EXPRESS_1="This plugin reguires the"
REQUIRE_EXPRESS_2="plugin. Either install the required plugin or uninstall this one."
EXPRESS_PLUGIN="Express Server"
For installation you can check the releases page of GitHub or the official documentation in the Loxwiki.
The idea of the plugin is to provide an ExpressJs Server where you can hook into. To do that, your plugin needs an express.uth.js
and/or a express.js
file in webfrontend/htmlauth/express
. Those file is automatically picked up by the server as soon as it's receiving a request for your plugin. The difference to those files is, that for express.auth
you need to be logged in into loxberry and for express
not. It's basically the same as html
and htmlauth
for typical plugins.
To let Express handle your requests you need to apply the index.cgi
as mentioned in the pervious chapter. There we check for the existence of Express and the correct versions.
The Express Server runs at port 3300 by default and can be changed after installation and allows your plugin to hook into the url path /admin/express/plugins/:name
for authoried requests and /express/plugins/:name
for unauthorized requests. The Name is the plugin defined folder_name
from the plugin configuration file plugin.cfg
. You should only use those 2 urls and never use a port directly. The ports can be changed and could be different for each installation.
Attention:
Your express.js file is cached during the execution time of the server. Every server restart clears the cache und picks up the file again. During
postupgrade
your plugin should and have to send a curl request to the express server to invalidate the cache. This needs to be done after the dependencies are installed. `curl -X POST http://localhost/admin/express/system/plugin/
The module provides also some metrics and the possibility to start
, stop
and restart
the express server. On top all the live logs are provided that you can check for errors and issues easily while developing.
The ExpressJs server comes with the Handlebars template enginge and the Loxberry layout is provided by default.
Sometime you want to use Websockets, and now that's as easy as defining a route. You can even provide multiple websockets for different purposes in case you want to. Attention:
Websockets do only work in
express.js
and per url/express/plugins/<plugin_name>/<path>
do to limitations of websocket with basic auth.
To hook into the express server, the handler file needs to export a function. There is an object passed as a parameter list. The function needs to return the router to be able to work. The paramers are equal for express.js
and express.auth.js
Parameters:
info
, debug
, warn
and error
methods. See Logger section.translate('hello')
You can decide which parameter you need by using destructing. Let's assume you just want to use the router: module.exports = ({router}) => {...
. An example with router logger and lodash would look like this: module.exports = ({router, logger, _}) => { ...
.
module.exports = ({router, static}) => {
return router;
};
module.exports = ({router}) => {
router.get('/', (req, res) => {
res.send('ok'); // for normal text content
res.json({hello: 'world'}); // for json content
res.render('index', {title: 'MyPluginTitle'}); // to use handlebars template engine
});
return router;
};
You can of course return more than just one route
module.exports = ({router}) => {
router.get('/', (req, res) => {
res.render('index', {title: 'MyPluginTitle'});
});
router.get('/hello/:name', (req, res) => {
res.json({hello: req.params.name});
});
return router;
};
Everything you can do on router level in express you can also do in your plugin.
The Websocket implementation is a custom one inspired by the express-ws
library. Websockets only work unauthorized at the moment due to limitations with websocket and basic auth. Every websocket definition in express.auth.js
will not work. To define a websocket handler in your express file, you can use router.ws
instead of router.get
. The provided arguments are:
// IMPORTANT: express.js NOT express.auth.js
const clients = [];
module.exports = ({router, logger}) => {
router.ws('/foo', (ws, request, next) => {
ws.on('open', () => clients.push(ws));
ws.on('message', (message) => {
logger.debug(`received message: ${message}`);
ws.send(message.toString());
});
});
return router;
};
with express you also have the possibility to use translations if you like to. Like other plugins your translation files
need to be placed into templates/lang/
. Every js file in there get's picked up and the name of the file represents
the language. For english you need to place a file named en.js
. The File is a normal js file and returns a json format
with key value pairs. The key is then used to access the translation using the translate
function in node or {{t 'key'}}
in handlebars.
module.exports = {
key: 'value'
anotherKey: 'another value {{name}}'
}
translate('key') // value
translate('anotherKey', {name: 'foo'}) // another value foo
This is the default template engine for the express server and currently the only one. The view/template files are
located in templates
directory in your plugin. Every file need the filextension *.hbs
.
templates/index.hbs
<h1>This is my First Template</h1>
To render the file you ned to use res.render
in the the express.js
file: res.render('index', {title: 'Foobar'})
.
The template is then redered within the loxberry layout. When you use the layout, always provide at least the title.
You can also render your own page, without the layout. Therefore you need to specify {layout: false}
in the render method.
res.render('index', {layout: false})
.
The LoxBerry layout has 3 variables you can specify:
Those properties are equal to Loxberry::Web::lbheader($template_title, $helpurl, $helptemplate);
.
More on this on the Loxberry Documentation
To know how the syntax for Handlebars works, i'd recomment to checkout their documentation. Here is a basic example:
// templates/index.hbs
<h1>{{myTitle}}</h1>
<div>Hello {{name}}</div>
// express.js
res.render('index', {title:'My Page title', myTitle: 'Hello World Demo', name: 'Foobar'});
// output wrapped in Layout
<h1>Hello World Demo<h1>
<div>Hello Foobar</div>
The simplified layout would look like this: (title is replaced with "My Page Title" and body is replaced with your index.hbs view.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{title}}</title>
</head>
<body>
{{{body}}}
</body>
</html>
To access the translation files there is a magic helper named t
. With that helper you cann access the translations.
i18next is used as a translation helper and you can take advantage of their documentation.
module.exports = {
helloWorld: 'Hello World'
hello: 'Hello {{name}}'
}
<h1>{{t 'helloWorld'}}</h1>
<h2>{{t 'hello' name='Foo'}}</h2>
The plugin uses a custom Logger class for a unified logfile. This logfile is than used to show the logs on the plugin page. Loggers are separated by the plugin who uses the logger. The express plugin flags all entries with "Express" whereas a plugin "my_cool_plugin" would be flaged with "My Cool Plugin". This works by default, you and don't need to think about this.
The logfiles are stored in LBHOME/logs/plugins/express/
and separated.
Errors will be written into express-errors.log
and normal logs into express.log
.
In the interface of the express server you can set a loglevel which is given for all plugins. It's maybe not the best approach, but accepted for now.