Node.js base for creating Domapic Modules and Plugins.
- Modules
- Plugins
- Start the server
- Connecting with Domapic Controller
- Options
- Security
- Extending API
- Logs
A Domapic Module is an action-event-state based REST API Micro-service. Can be, as example, a little program that controls a relay, has an action
to switch it, triggers an event
when changes, and an state
that can be consulted. Modules can be paired with a Domapic Controller, and be controlled through it, making them interact automatically ones with anothers, or with plugins, such as third-party integrations, as Amazon's Alexa, Apple's HomeKit, etc.
This package provides you all the API infrastructure, authentication, data validation, process and logs management, etc. The only thing you have to care about is about the implementation of the module logic itself. When the server starts, an api is automatically created and you, or other applications, such as Domapic Controller, will be able to interact with the module.
If you are going to publish your module, add the domapic-module
suffix to the name, in order to allow npm users finding it easily searching in the website that keyword.
NOTE: The next schema includes some Domapic pieces that are still not released. The web ui for the Controller, Domapic Cloud, mobile apps, as well as Homekit and Alexa plugins will be available soon.
Above, an example of two modules in a Domapic System. Now, the relay can be controlled using the web or mobile applications, or interacting with "Alexa" or "HomeKit". Automatisms can be configured in the Domapic Controller Web UI to make the Phillips Hue bulb be switched off automatically when the relay bulb is switched on, for example.
Modules can be created with few lines of code. Here is an example of a module controlling a fake relay:
package.json file:
{
"name": "example-domapic-module",
"version": "1.0.0",
"description": "Domapic module controlling a relay",
"dependencies": {
"domapic-service": "1.0.0-alpha.5"
}
}
server.js file:
const path = require('path')
const domapic = require('domapic-service')
domapic.createModule({ packagePath: path.resolve(__dirname) })
.then(async dmpcModule => {
let status = false
await dmpcModule.register({
switch: {
description: 'Handle the relay status',
data: {
type: 'boolean'
},
event: {
description: 'The relay status has changed'
},
state: {
description: 'Current relay status',
handler: () => status
},
action: {
description: 'Switch on/off the relay',
handler: newStatus => {
status = newStatus
dmpcModule.events.emit('switch', status)
return Promise.resolve(status)
}
}
}
})
return dmpcModule.start()
})
When started, a REST api will be available to connect with controller, authenticate using api key, dispatch the switch action, consulting the switch state, the module configuration, etc.
options
<object>
containing:packagePath
<string>
Path to the folder where the module'spackage.json
is.customConfig
<object>
Domapic provides some common configuration options. Custom options for a module can be added defining them in this property. In this way, for example, you can add a "gpio" option, that will be received when the module instance is started, and can be modified through arguments in the start command:npm start -- --gpio=12
. For further info about defining custom configuration options, please refer to the "custom options" chapter
Returns a module instance, containing:
tracer
<object>
containing methods for tracing with different log levels (log
,trace
,debug
,info
,warn
,error
). Read the "traces" chapter in the domapic-base documentation for further info.config
<object>
containing methods for getting and setting configuration.get([key])
- Returns a promise resolved with the module configuration. Resolved with specific property value if argumentkey
is provided.set(key [, value])
- Setsvalue
for providedkey
into module configuration. Returns a promise.
storage
<object>
containing methods for getting and setting data in the built-in file system storage.get([key])
- Returns a promise resolved with the module storage. Resolved with specific property value if argumentkey
is provided.set(key [, value])
- Setsvalue
for providedkey
into module storage. Returns a promise.
api
- Object containing methods for extending the built-in api.register(abilitiesData)
- Register provided abilities into the module. Read the abilities chapter for further info.addPluginConfig(pluginConfigs)
- Add module default configurations for domapic plugins. Read the Plugins configurations chapter for further info.start
- Starts the server.events
- Node.js emitter object. Used to emit abilities events to the controller.errors
- Domapic errors constructors. Useful for rejecting abilities handlers with specific http errors. For further info read the errors chapter in the domapic-base documentation
Each Module can has many abilities, each one with its own action, state and event. Continuing with the example above, the module could have another ability called "toggle", that would change the status based on current status, without needing to receive any data.
- Register provided abilities into the module, and create correspondants api resources.
Each ability must be an object, which key will be the name of the ability (will be converted to snake case when referenced in api uris). Properties of each ability must be:
description
<string>
Description of the ability.data
<object>
Defines the type of data that the ability will handle, and will be used to execute data validation for the action, state and event related api resources. If the ability don't needs any type of data, this property should be omitted (in that case, the ability can't have an state, because it has no sense).type
<string>
Type of data. Can be one ofstring
,boolean
,number
enum
<array>
Used to restrict data to a fixed set of values. It must be an array with at least one element, where each element is unique.minLength
<number>
Minimum length of the data string.maxLength
<number>
Maximum length of the data string.pattern
<string>
Used to restrict the data to a particular regular expression.format
<string>
Basic semantic validation on certain kinds of string values that are commonly used. Can be one ofdate-time
,email
,hostname
,ipv4
,ipv6
oruri
.multipleOf
<number>
Used to restrict numbers to a multiple of given number.minimum
<number>
Minimum value of numeric data.maximum
<number>
Maximum value of numeric data.exclusiveMaximum
boolean
Indicates if providedmaximum
is exclusive of the value.exclusiveMinimum
boolean
Indicates if providedminimum
is exclusive of the value.
event
<object>
Optional. If present, the ability will be able to emit events.description
<string>
Description of the ability event.
state
<object>
Optional. If present, the ability will have an api resource for consulting the state.description
<string>
Description of the ability state.handler
<function>
Handler for the state api resource. The status, or a promise resolving the status must be returned.auth
<boolean>
If false, the state api resource will not demand authentication. Default istrue
.
action
<object>
Optional. If present, the ability will have an api resource for dispatching the action.description
<string>
Description of the ability action.handler
<function>
Handler for the action api resource. Will receive the action data as argument. The status, or a promise resolving the status must be returned.auth
<boolean>
If false, the action api resource will not demand authentication. Default istrue
.
When an ability has an action
property defined, an api resource will be created with the uri /api/[ability_key]/action
. It has to be requested using POST
method, and data (if needed) has to be provided in the request body, contained in the "data" property:
{
"data": false
}
In this example, the action's handler
method will be called with false
.
If ability has an state
property defined, an api resource will be created with the uri /api/[ability_key]/state
. It has to be requested using GET
method. Will return the value resolved by the returned promise in the state's handler
method.
{
"data": true
}
In this example, the action's handler
method returned Promise.resolve(true)
.
When ability has an event
property defined, the emit
method of the module's events
object can be used to emit the ability event, passing the correspondant data. This will produce module calling to controller api to inform about the trigered event.
dmpcModule.events.emit('switch', true)
In this example, the module will call to the correspondant controller api resource, passing true
as data.
Some Domapic Plugins require extra configurations for modules (as an example, the homebridge-domapic-plugin
needs to map the module abilities to an specific HomeKit accesory type, in order to be controlled by Siri). The modules can define a default configuration for certain types of Domapic Plugins. This configurations can be modified afterwards by users through the Controller UI in order to allow more customization, (for example, a contact-sensor-domapic-module
sometimes needs to be mapped to a Door
accesory, and sometimes to a Window
accesory)
For defining default plugins configurations, the addPluginConfig
method is available in the module instances:
Add plugin configuration or configurations (can receive an array of configurations too). Each configuration must have the next properties:
pluginPackageName
<string>
Package name of the plugin for which the configuration is destined.config
<object>
Object containing the plugin configuration. Its format depends of each specific plugin configuration requisites.
dmpcModule.addPluginConfig({
pluginPackageName: 'homebridge-domapic-plugin',
config: {
foo: 'foo-plugin-config'
}
})
A Domapic Plugin is an action-event-state based REST API Micro-service that can "extend" the Domapic Controller functionality. Once it is connected with the Controller, it will receive events each time an entity is created, modified or deleted in the Controller. Plugins will be informed too about all module events or actions received by the Controller. Plugins also have an interface to the Controller, that allows to perform actions such as consult module states, dispatch module actions, create users, etc.
If you are going to publish your plugin, add the domapic-plugin
suffix to the name, in order to allow npm users finding it easily searching in the website that keyword.
Here is an example of a plugin implementation that receives all Controller events, and gets all modules' states once it is connected:
{
"name": "example-domapic-plugin",
"version": "1.0.0",
"description": "Domapic plugin that receives all Controller events and print logs",
"dependencies": {
"domapic-service": "1.0.0-alpha.2"
}
}
server.js file:
const path = require('path')
const domapic = require('domapic-service')
domapic.createPlugin({ packagePath: path.resolve(__dirname) })
.then(async plugin => {
plugin.events.on('*', eventData => {
plugin.tracer.info(`Received ${eventData.entity}:${eventData.operation} event. Data: ${JSON.stringify(eventData.data)}`)
})
plugin.events.on('connection', async () => {
const abilities = await plugin.controller.abilities.get()
abilities.map(async ability => {
if (ability.state) {
const state = await plugin.controller.abilities.state(ability._id)
console.log(`Ability "${ability.name}" of module "${ability._module}" has state "${state.data}"`)
}
})
})
return plugin.start()
})
When started and connected with Controller, the plugin will receive all Controller events an print their data. It will also request all registered abilities and print their current states.
options
<object>
containing:packagePath
<string>
Path to the folder where the plugin'spackage.json
is.customConfig
<object>
Domapic provides some common configuration options. Custom options for a plugin can be added defining them in this property. For further info about defining custom configuration options, please refer to the "custom options" chapter
Returns a plugin instance, containing:
tracer
<object>
containing methods for tracing with different log levels (log
,trace
,debug
,info
,warn
,error
). Read the "traces" chapter in the domapic-base documentation for further info.config
<object>
containing methods for getting and setting configuration.get([key])
- Returns a promise resolved with the plugin configuration. Resolved with specific property value if argumentkey
is provided.set(key [, value])
- Setsvalue
for providedkey
into module configuration. Returns a promise.
api
- Object containing methods for extending the built-in api.start
- Starts the server.events
- Node.js emitter object. Used to subscribe to events from the Controller.controller
- Object containing an interface to make requests to Controller. Read the Controller interface chapter for further info.
Controller emit all module received events and actions, as well as other internal entities events to all registered plugins. From a Plugin, you can subscribe to an specific entity events, or to an specific operation, etc.
plugin.events.on('ability:created', eventData => {
console.log('A new ability has been created with data:')
console.log(eventData.data)
})
All available events are:
- ability:updated
- ability:created
- ability:deleted
- ability:action
- ability:event
- service:created
- service:updated
- service:deleted
- servicePluginConfig:created
- servicePluginConfig:updated
- servicePluginConfig:deleted
- user:created
- user:updated
- user:deleted
Received event data has the next format, where data
contains all details about the performed operation:
{
"entity": "ability",
"operation": "created",
"data": {}
}
Wildcards are available for subscribing to all events of an specific entity
, or to all entities of an specific operation
, or to all events:
*
- All eventsservice:*
- All events of "service" entity.*:created
- All operations "created" of any entity.
A Domapic Plugin provides an api client that allows to perform operations into the Controller. All methods returns a Promise, and are available under the plugin.controller
object, which contains:
users
- Interface for Controller's "user" entities:me()
- Returns data about plugin userget([id][,filter])
- Returns users data. Because of security reasons, only "operator" users are allowed to be returned until plugin has "adminPermissions". For plugins without admin permissions granted, request must be filtered providing a role filter as{role:'operator'}
. Extra filters can be provided too, id as <String>, or an <Object> containing any other api supported filter (such as{name:'foo-name'}
).create(userData)
- Creates an user, and returns the newid
. Only creating users with "operator" role is supported.
services
- Interface for Controller's "service" entities:get([id][,filter])
- Returns services data. Request can be filtered providing an specific service id as <String>, or an <Object> containing any other api supported filter (such as{type:'module'}
).
servicePluginConfigs
- Interface for Controller's "servicePluginConfigs" entities:get([id][,filter])
- Returns servicePluginConfigs data. Request can be filtered providing an specific servicePluginConfig id as <String>, or an <Object> containing any other api supported filter (such as{service:'service-id', 'plugin-package-name': 'foo-plugin-package-name'}
).create(configData)
- Creates a service plugin configuratin, and returns the newid
.update(id, configData)
- Updates an specific service plugin configuration.
abilities
- Interface for Controller's "ability" entities:get([id][,filter])
- Returns abilities data. Request can be filtered providing an specific ability id as <String>, or an <Object> containing any other api supported filter (such as{service:'foo-module-id'}
).state(id)
- Returns state of provided ability.action(id, data)
- Dispatches ability action with provided data.
logs
- Interface for Controller's "log" entity:get([filter])
- Returns Controller logs with all modules' actions and events. A query filter can be provided.
config
- Interface for Controller's configuration.get()
- Returns Controller configuration.
apiKeys
- Interface for Controller's "securityTokens" entity, filtered by "apiKey" type. For security reasons, only plugins with admin permissions granted can access to this resource, otherwise requests will return a forbidden error.get()
- Returns Controller user's api keys.create(userData)
- Creates an api key for the given user, and returns it.
Consult the Controller Swagger interface to get more info about supported filters (queries) and requested data for each api interface.
Plugins are registered into the Controller with a "plugin" role, that, for security reasons, can't perform certain types of operations and have limited access to some resources, such as security tokens, etc. Depending of the plugin behavior, it may need accessing to this resources to can work. If a plugin need special permissions to work, you can use the Controller web interface to modify it, and check the "grant admin permissions" option.
First of all, remember to start the server programatically after adding the abilities in your code:
dmpcModule.start()
Once the module code is ready, the server can be started calling directly to the npm start
command
npm start
If you want to implement the built-in Domapic CLI into your module, that provides process and logs management, you'll need to add a "cli.js" file to your module:
#!/usr/bin/env node
const path = require('path')
const domapic = require('domapic-service')
domapic.cli({
packagePath: path.resolve(__dirname),
script: path.resolve(__dirname, 'server.js')
})
Add bin
and scripts
properties to your package.json, that will make available the CLI:
"bin": {
"relay": "./cli.js"
},
"scripts": {
"relay": "./cli.js"
}
Now, you can use start
, stop
and logs
commands to start and manage your module in background:
# Starts the module (with debug log level)
npm run relay start -- --logs=debug
# Stops the module
npm run relay stop
# Displays module's logs
npm run relay logs -- --lines=300
#If the module is installed globally (using -g), next commands will be available:
relay start
relay stop
relay logs
You can define custom CLI commands for your module too. For further info read the custom options and commands chapter in the domapic-base documentation
Connect your module or plugin with Domapic Controller inside your local network to get the most of it.
Doing this, you'll can use the Domapic Controller Web Interface to control all your modules, and make them interact through automatisms. Domapic plugins will have access to the module at same time, so you´ll can control it with your voice using the Amazon's Alexa plugin, or the Homebridge plugin, for example.
Use the controller
option to define the controller url, and the controller-api-key
option to define the authorized api key (you can get it from the controller logs when started, indicated with "Use the next api key to register services"). Remember to use the save
option to make the module remember them for next executions:
npm start -- --controller=http://192.168.1.100:3000 --controller-api-key=foo-controller-key --save
In this way, the module will connect automatically with controller when started.
If Controller has the authentication disabled, the controller-api-key option will not be necessary to perform the connection.
The connection can be executed using the provided api as well, through the controller web ui. When the module or plugin is started, an api key for connecting is displayed in logs:
Try adding connection from Controller, using the next service Api Key: xxxxx-foo-api-key-xxxx
Use the controller web user interface to authorize the connection with your module or plugin using the specified api key.
Domapic-service provides a set of command line options that are available when starting the module:
# Displays help with detailed information about all available options
npm start -- --help
name
- Service instance name. You can start many instances of the same module or plugin defining different names for each one.port
- Http port used by the server. Default is 3000.hostName
- Hostname for the server.sslCert
- Path to an ssl certificate. The server will start using https protocol if provided.sslKey
- Path to an ssl key.controller
- Url of the Domapic Controller to connect with.controllerApiKey
- Api key that allows connection with controller.authDisabled
- Array of IPs or CIDR IP ranges with authentication disabled (separated with whitespace). Default is ['127.0.0.1', '::1/128'], so authentication will be disabled for localhost by default. To enable authentication even for localhost, specify--authDisabled
without any ip.auth
- If false, authentication will be disabled for all origins. Default is true.color
- Use ANSI colors in traces.logLevel
- Tracing level. Choices are 'log', 'trace', 'debug', 'info', 'warn' and 'error'. Default isinfo
.path
- Path to be used as home path for the process, instead of user´s default (a.domapic
folder will be created inside).saveConfig
- Save current options for next execution (exceptname
andpath
). Default is false.rejectUntrusted
- Reject untrusted ssl certificates when making requests to modules or plugins. Use it if you are using a self-signed certificate in your Controller. Default is false.
Example of defining options from command line:
npm start -- --name=room-light-switch --logLevel=debug --port=3100 --authDisabled --save
You can add your own custom configuration options. They will be seteable from command line execution, displayed in help and validated as the rest of options. Use customConfig
option in the createModule
method and in the cli
method to define them.
Yargs is used as underlayer to manage options, so you can read its documentation for more details about how to define them.
Following with the example, add an
options.js
file to the module:
module.exports = {
initialStatus: {
type: 'boolean',
alias: ['status'],
describe: 'Set initial status of the relay when module is started',
default: false
}
}
Use it in the
server.js
file:
const path = require('path')
const domapic = require('domapic-service')
const options = require('./options')
domapic.createModule({
packagePath: path.resolve(__dirname),
customConfig: options
}).then(async dmpcModule => {
let status = await dmpcModule.config.get('initialStatus')
//...
})
Now, the custom
initialStatus
option can be used from command line when starting the module:
npm start -- --initialStatus=true
# The module will be started, and "initialStatus" value in config will be true
Custom options defined for a service should be defined in CLI implementation too. Add them to the
cli.js
file:
const path = require('path')
const domapic = require('domapic-service')
const options = require('./options')
domapic.cli({
packagePath: path.resolve(__dirname),
script: path.resolve(__dirname, 'server.js'),
customConfig: options
})
Now, the custom
initialStatus
option can be used from CLI too:
relay start --initialStatus=true
# The module will be started, and "initialStatus" value in config will be true
The built-in api of Modules and Plugins can be extended to implement your own api resources. For this purpose, the api
object is provided to module and plugins instances. This object contains methods:
extendOpenApi(openApiDefinition)
addOperations(operationsDefinitions)
All custom api methods implemented with these methods will require authentication as well as built-in api methods, as long as you remember to define the "security": [{"apiKey": []}]
property to each openapi path definition.
For further info about how to define api resources using these methods, please refer to the "Adding api resources" chapter of the Domapic-base documentation
The Domapic Modules are intended to be used only in your local network by the Domapic Controller, without exposing them to the Internet, but, despiting this fact, all HTTP communications are secured as well using OAUTH Api Keys in order to make the system more robust, and to provide a role based securization. Only requests coming from localhost are not secured by default, to make easier the configuration process, but this behavior can be modified too.
-
Enable ssl: Enable ssl for your module generating an SSL certificate. Use options
--sslCert
and--sslKey
to define the paths to each file, and remember to use the--save
option to store that settings for next server restarts. From now, your server will start using https instead of http. -
Disabling authentication: Authentication can be disabled for desired IPs or IP ranges using the
--authDisabled
option, or for all origins using the--auth=false
option. By default, authentication is disabled only for the 172.0.0.1 IP in order to make easier the first configuration, but you can disable it for your whole your local network, etc. Because of security reasons, this is not recommended, take into account that users accessing to services with authentication disabled will have equivalent permissions to an user with "admin" role. If you want to force the authentication requirement even for localhost, use the--authDisabled
as a flag, without specifying any IP.
If you used the built-in CLI to start the module, you can display the logs using the logs
command:
npm run relay logs -- --lines=300
# --lines is optional. Default is 30
This command will display last logs of server, and will continue displaying logs until CTRL-C is pressed.
Service process and logs are managed by PM2 when using CLI, so, it is recommended to install PM2 log rotate to avoid pm2 logs file growing too much.
Service logs are saved too into a daily file. These files are rotated automatically and only last ten days files are kept. You´ll find these files in the ~/.domapic/[module-name]/logs
folder.