# How to create a custom plugin for SenseFlow
SenseFlow is a platform that enables low-code development for integrations between systems. Although focused on industry solutions, it can be used for non-industry solutions as well.
## What are plugins?
Plugins are extensions for the SenseFlow platform, offering new features to users and assisting in the transformation of data inside the flows.
A plugin frequently offers a collection of nodes. A node is a visual representation of a feature for the SenseFlow plataform. A node can receive, process and send messages both to the flow and to external applications.
Plugins are developed using Javascript or Typescript. In the following steps, you will learn how to create and use a new plugin in a SenseFlow instance.
If you are already familiar with the process of creating one, you can skip to [here](#Contributing) to get a template project with most of the configuration set up.
## Starting an example project
For your first project, you are tasked with creating a plugin that allows users to capitalize all first letters in a string, and you will be using Typescript to develop it.
### Prerequisites
- **Node v14.19.0** - this is the default version that we use to run our SenseFlow instances. You could also use a higher version when running locally, but it's better to adhere to the default;
### Create the package with npm
To start, you need to initialize your project with npm. We will be using the `~/examples` folder, but you can choose any folder that you'd like.
By default, the package name of our plugins follows the format `@senseup/senseflow-<name-of-the-plugin>`. In this project, we will use `@senseup/senseflow-plugin-text-functions`.
> ~$ mkdir examples/senseflow-plugin-text-functions
~$ cd examples/senseflow-plugin-text-functions
~$ npm init
...
package name: @senseup/senseflow-plugin-text-functions
version: (1.0.0)
description: This package has many functions to manipulate text.
entry point: (index.js)
test command:
git repository:
keywords:
author: SenseUp <squad@senseup.tech>
license: (ISC)
...
Is this OK? (yes)
~$
### Installing dependencies
```sh
npm i typescript @types/node@^14 @types/node-red@1.2.0 -D
```
### Create the main files of the project
The plugin that you're creating will contain only one node. This node needs to be able to receive a sentence and produce this modified sentence with all first letters capitalized.
- src/capitalize-all-first-letters/capitalize-all-first-letters.ts
```typescript= !
import { Node, NodeDef, NodeInitializer, NodeMessage } from "node-red";
// his is the object that represente the node
// The decision of make this global is to better writer functions
let NODE: Node;
// This function is registered below ( NODE.on('input', nodeOnInput) )
// to be the event listener of a input message.
function nodeOnInput(msg: NodeMessage) {
/**
* Here, you will construct all code to treat the input message,
* construct the message to output, and output it by NODE.send(msg)
*/
NODE.send(msg);
}
const capitalize_all_first_letters: NodeInitializer = function (RED) {
function CapitalizeAllFirstLetters(config: NodeDef) {
RED.nodes.createNode(this, config)
NODE = this
NODE.on('input', nodeOnInput)
}
RED.nodes.registerType("capitalize-all-first-letters", CapitalizeAllFirstLetters)
}
export = capitalize_all_first_letters
```
- src/capitalize-all-first-letters/capitalize-all-first-letters.html
```html=
<script type="text/javascript">
RED.nodes.registerType('capitalize-all-first-letters', {
category: 'Senseup Text',
color: '#a6bbcf',
defaults: {
name: { value: "" }
},
inputs:1,
outputs:1,
icon: "file.png",
label: function() {
return this.name||"capitalize-all-first-letters";
}
});
</script>
<script type="text/html" data-template-name="capitalize-all-first-letters">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/html" data-help-name="capitalize-all-first-letters">
<p>A simple node that converts message payloads into a sentence with the first letters of the words capitalized.</p>
</script>
```
> Tip: further details can be found in https://nodered.org/docs/creating-nodes/first-node
### Adding the SenseFlow node registration
In your `package.json`, add the following setting:
```json=!
"node-red": {
"nodes": {
"capitalize-all-first-letters": "dist/capitalize-all-first-letters/capitalize-all-first-letters.js"
}
}
```
This signals the SenseFlow instance that a node of type `capitalize-all-first-letters` is defined in the file `dist/capitalize-all-first-letters/capitalize-all-first-letters.js`.
### Create the TypeScript configuration file
This file will instruct the tsc (TypeScript compiler) on how to transpile files from the *.ts (TypeScript) format to the *.js (JavaScript) format.
- Create a **tsconfig.json** file in the root of your project and include the configurations below:
```json=
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"alwaysStrict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"typeRoots": [
"node_modules/@types"
],
"rootDir": "src",
"module": "CommonJS",
"target": "ES5",
"importHelpers": true,
"lib": [
"ES5",
"ES6"
],
"outDir": "dist"
},
"include": [
"src"
],
"exclude": [
"node_modules"
]
}
```
> Tip: For more details about `tsconfig.json`: https://www.typescriptlang.org/pt/docs/handbook/tsconfig-json.html
### Including the shell script to aux the build process
The next chapter will config the process to call the traspile. This is a append to aux the build process because the traspile only works with *.ts files. We need that other files and folders be copied to the distribution folder.
* In the root of the project, create a file `copy_files.sh` :
```bash=
#!/bin/bash
src=$(pwd)"/src"
dist=$(pwd)"/dist"
for entry in $(ls src); do
# copy folder icons if exists
# if you want to do it for other folder that you standardized,
# then use some like this code below
if [ -d "$src/$entry/icons" ]; then
cp -r "$src/$entry/icons" "$dist/$entry"
fi
# copy files from the root of the node in src to dist
# not recursively
# if you want to add other extensions, do some like this: (html|xml|json|jpeg)
for file in $(ls $src/$entry); do
if [[ $file =~ .*\.(html) ]]; then
cp $src/$entry/$file $dist/$entry/$file
fi
done
done
```
### Including the build script
- In your `package.json`, inside "scripts", add this line:
```json=
"build": "tsc && ./copy_files.sh"
```
When we run `npm run build`, `tsc` will be called to perform the transpilation of our files and the shell script will do the remaining work.
> Warn: The shell script file must can be set as executable file in operating system.
### Building our plugin
For now, your hierarchy of files should look like this:

> Tip: The files `.gitignore`, `CHANGELOG.md` and `README.md` has no importance for the functioning of the plugin.
Now we have configured the transpiling process, we will execute that first with direct use of **tsc** command in the console.
```bash
$ tsc
```
> Tip: check if you stay in root folder in the console
See that now the folder **dist** was created, but only have the **capitalize-all-first-letters.js** file, but we need that exist too **capitalize-all-first-letters.html** file.
To be more clear to understand the next step, firstly delete **dist** folder. Now, in the console:
```bash
$ npm run build
```
And now see the dist folder with the files extensions *.html and *.js, that are all we have at this moment.
> Tip: If the `*.html` was not created in dist folde or the some message like `./copy_files.sh: Permission denied` is show, then execute `chmod 777 copy_files.sh` and try again.
The project in this moment for the hierarchy of files/folders looks like:

## Installing the plugin
In order to test our node, we need to install it in a SenseFlow instance. The recommended way to do it when developing is to do a local install.
- Clone the SenseFlow repository:
```
$ git clone https://gitlab.com/multicast/senseup/senseflow
$ cd senseflow
```
> Notice: To access the repository you **must be authorized** and have previously **signed the confidentiality agreement** relating to the rights of **SenseUp** projects.
Create a **.env** file in the root of the project and paste the enviroment variables listed in the README.
> Tip: The environment variables that is given in README file are mocked. The real values must be requested from the **SenseUp** support.
> Tip: We recomend to add the environment variable DATAFLOW_TITLE="SenseFlow"

- Install dependencies:
```
$ npm i
```
Start the SenseFlow instance:
```
npm run start
```
> Tip: As some enviroment variables might be required for some of the features in SenseFlow, if you encounter an error while trying to start SenseFlow, please contact **SenseUp** for support.
> Tip: If you happen to encounter an error like "address already in use :::1880", then some other application is already using this port. You can solve this in two ways: the first is to kill the application that is using this port; the second is to change the port used by SenseFlow and restart it. If you are unsure whether the application using it is critical or not, choose the second option.
- **The first** - kill the application - this is by your responsibility, don't do that if you don't know what application is running and your impact.
`$ netstat -lutnp`
`$ sudo kill -9 <PID>`
- **The second** - alter the port
`edit src/index.js`
`line: server.listen(1880);`
If SenseFlow runs correctly, your console will look like this:

You can also see the SenseFlow editor running in: http://localhost:1880/

### Instaling the plugin in the SenseFlow
One time that we continue in the **senseflow** folder we will installing the plugin using the relative or entire path to this, like below:
> It's very important stop the SenseFlow plataform before instaling by command **Ctrl+c** in the console that runs it. After, you will install like below and start again.
```
$ npm i ../senseflow-plugin-text-functions/
```

Se this above image. In left, the pallet exhibit the *capitalize-all-firt-letters* node. In middle, the flow have three nodes, the first is an input with properties name and payload with value `the best solution for low coding integration is senseUp`. The third node is an debug that output the message payload in debug tab at the right. And, the second node, **our node, that for this moment do nothing we want**, only reruns the message genereted by input node. Because our node do nothing, the output in debug tab is the original message. We will implement this in the next chapter.
## Implementing the node functionality
Let's return to the directory of our node project and edit the file **capitalize-all-first-letters.ts** . See the function *nodeOnInput*, this is a function that treat the input message and send a message processed to the output.
> Tip: the input message coming from input node is a JavaScript object that has the key **payload** with contains our setence, and the key **payload** will be use by the output node to show the result of our node.
Now, update your actual code with this:
```typescript=!
function capitalizeAllFirstLetters(str) {
var splitStr = str.split(' ');
for (var i = 0; i < splitStr.length; i++) {
// You do not need to check if i is larger than splitStr length, as your for does that for you
// Assign it back to the array
splitStr[i] = splitStr[i].charAt(0).toUpperCase() + splitStr[i].substring(1);
}
// Directly return the joined string
return splitStr.join(' ');
}
// This function is registered below ( NODE.on('input', nodeOnInput) )
// to be the event listener of a input message.
function nodeOnInput(msg: NodeMessage) {
/**
* Here, you will construct all code to treat the input message,
* construct the message to output, and output it by NODE.send(msg)
*/
msg.payload = capitalizeAllFirstLetters(msg.payload)
NODE.send(msg);
}
```
> Tip: Don't get confused this name (capitalizeAllFirstLetters) with another almost the same name. They are different stuffs.
One time we have installing our plugin by a local npm installation, the npm create a link to our plugin project. With this in mind, it's not necessary to reinstall, otherwise, is necessary to rebuild our plugin and restart the SenseFlow.
- In the root folder of our plugin
```
$ npm run build
```
- In the console where our SenseFlow instance is running
```
Ctrl+C
$ npm run start
```
Now, by refresh our SenseFlow (http://localhost:1880/) and again inject message, the formated output in debug tab show what we want. See the right side, the debug tab, in image below.

## Adding more nodes to the same plugin
Init it with the creation of the new folder `capitalize-all-letters` under the `src` folder.

* in package.json update the SenseFlow node registration to:
```json=11 !
"node-red": {
"nodes": {
"capitalize-all-first-letters": "dist/capitalize-all-first-letters/capitalize-all-first-letters.js",
"capitalize-all-letters": "dist/capitalize-all-letters/capitalize-all-letters.js"
}
},
```
### Implementing the new node
The new node is a simple node that capitalize all letters.
- edit src/capitalize-all-letters/capitalize-all-letters.ts
```typescript=!
import { NodeDef, NodeInitializer, NodeMessage } from "node-red";
// This is the object that represente the node
// The decision of make this global is to better writer functions
let NODE;
// This function is registered below ( NODE.on('input', nodeOnInput) )
// to be the event listener of a input message.
function nodeOnInput(msg: NodeMessage) {
/**
* Here, you will construct all code to treat the input message,
* construct the message to output, and output it by NODE.send(msg)
*/
msg.payload = msg.payload.toString().toUpperCase()
NODE.send(msg);
}
const capitalize_all_letters: NodeInitializer = function (RED) {
function CapitalizeAllLetters(config: NodeDef) {
RED.nodes.createNode(this, config)
NODE = this
NODE.on('input', nodeOnInput)
}
RED.nodes.registerType("capitalize-all-letters", CapitalizeAllLetters)
}
export = capitalize_all_letters
```
- edit src/capitalize-all-letters/capitalize-all-letters.html
```html=
<script type="text/javascript">
RED.nodes.registerType('capitalize-all-letters', {
category: 'Senseup Text',
color: '#a6bbcf',
defaults: {
name: { value: "" }
},
inputs:1,
outputs:1,
icon: "file.png",
label: function() {
return this.name||"capitalize-all-letters";
}
});
</script>
<script type="text/html" data-template-name="capitalize-all-letters">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<script type="text/html" data-help-name="capitalize-all-letters">
<p>A simple node that converts message payloads into a sentence with all letters capitalized.</p>
</script>
```
### Rebuild the project
How are we going to rebuild our project, we can delete the folder **dist** and :
- In the root folder of our plugin
```
$ npm run build
```
- In the console where our SenseFlow instance is running
```
Ctrl+C
$ npm run start
```

Now, as can you see, the two nodes are listed in the palette and we use this to generate two outputs, one with all first letters capitalized and other with all letters capitalized.
## Some UI friendly details
In this chapter, we will only work with one node, the *capitalize-all-firt-letters*. If you want, the same alterations can be do for whatever node that you implements.
### Alter the icon
In the file `src/capitalize-all-first-letters/capitalize-all-first-letters.html `have one `icon: "file.png"`. The SenseFlow allows to set icons from three fonts: own stock, custom icon, and [Font Awesome icon](https://fontawesome.com/v4/icons/).
#### Own Stock
In this case, only change `icon: "file.png"` to a desire icon like `icon: "white-globe.svg"`

#### Custom Icon
If you want a personalized icon you can do your own icon or choose some in a repository like it [image *.svg](https://www.svgrepo.com/svg/85270/text). Download it and put in `src/capitalize-all-first-letters/icons/`.
After, change `src/capitalize-all-first-letters/capitalize-all-first-letters.html` in the line `icon: "file.png"` to `icon: "text-svgrepo-com.svg"`.
In the SenseFlow you will see a new look for you node like this:

#### Font Awesome icon
This is a web repository to *.svg files that the SenseFlow offers a ligthweight manner to access your files. Search a icon that you want, copy your name like `fa-text-width` and put it in this way `icon: "font-awesome/fa-text-width"`.
### Node color
This is more simple than icons. Just change in `src/capitalize-all-first-letters/capitalize-all-first-letters.html` the `color: '#a6bbcf'` with another hexadecimal number color, like color: '#D1BDEB''.
An indicated tool to generate hexadecimal number color is https://htmlcolorcodes.com/ .
### Rebuild the project and restart the SenseFlow
For each one or all alteration done you will need to:
- Rebuild
```
$ npm run build
```
- Restart the SenseFlow
```
Ctrl+c
$ npm run start
```
This is the new appearance of node if you choose Font Awesome icon.

## Adding properties and a input feature
The idea here is add a property **text** in `capitalize-all-firt-letters` and implement a button, like a button in left side of input node, that will send a message to the output of our node with this **text** propertie value in message payload key.
Besides that, add a property **hasInput** that controls if our node will behave like one that have input port or not.
- edit src/capitalize-all-letters/capitalize-all-letters.ts
- Lines 4-7, 13 and 45: Defines a config object that from extended NodeDef class. This carry the properties that exists in `capitalize-all-letters.html` below.
- Line 5: the property **text** that will replace the text from input node.
- Line 6: the property **hasInput** that say wheter our node has or hasn't a input port.
- Lines 33-37: This conditional block verify the value of **hasInput**. If don't have input port, the message input will be provided by `CONFIG.text`, that gets from text property in html file.
```typescript=
import { Node, NodeDef, NodeInitializer, NodeMessage } from "node-red";
// This interface defines the information from node properties
interface EnvVarGetNodeDef extends NodeDef {
text?: string
hasInput?: string | boolean
}
// This is the object that represente the node
// The decision of make this global is to better writer functions
let NODE: Node;
// This is the object that bring it to us the information from node properties
let CONFIG: EnvVarGetNodeDef;
function capitalizeAllFirstLetters(str) {
var splitStr = str.split(' ');
for (var i = 0; i < splitStr.length; i++) {
// You do not need to check if i is larger than splitStr length, as your for does that for you
// Assign it back to the array
splitStr[i] = splitStr[i].charAt(0).toUpperCase() + splitStr[i].substring(1);
}
// Directly return the joined string
return splitStr.join(' ');
}
// This function is registered below ( NODE.on('input', nodeOnInput) )
// to be the event listener of a input message.
function nodeOnInput(msg: NodeMessage) {
/**
* Here, you will construct all code to treat the input message,
* construct the message to output, and output it by NODE.send(msg)
*/
if (CONFIG.hasInput.toString() == 'true') {
msg.payload = capitalizeAllFirstLetters(msg.payload)
} else {
msg.payload = capitalizeAllFirstLetters(CONFIG.text.toString())
}
NODE.send(msg);
}
const capitalize_all_first_letters: NodeInitializer = function (RED) {
function CapitalizeAllFirstLetters(config: NodeDef) {
RED.nodes.createNode(this, config)
NODE = this
CONFIG = config
NODE.on('input', nodeOnInput)
}
RED.nodes.registerType("capitalize-all-first-letters", CapitalizeAllFirstLetters)
}
export = capitalize_all_first_letters
```
- edit src/capitalize-all-letters/capitalize-all-letters.html
- Line 7: declaration of the **text** property
- Line 8: declaration of the **hasInput** property
- Line 10: start **inputs** port quantity of the node with zero
- Lines 75-82: html template that represents the properties **text** and **hasInput**
- Lines 53-66: this register an event that happens before node edit dialog shows. Inside it, the property **hasInput** is configured to be a select box, with predefined values, instead to be a simple text input.
- Lines 45-52: this register an envent that happens when node edit dialog save button is clicked. This evaluates the **hasInput** propertie and set **inputs** port quantity to zero or one.
- Lines 16-44: this is a button like a left button in inject node. The code inside it is the same used in inject node to get properties and send it in CONFIG inside *.ts file.
```html=
<script type="text/javascript">
RED.nodes.registerType('capitalize-all-first-letters', {
category: 'Senseup Text',
color: '#D1BDEB',
defaults: {
name: { value: "" },
text: { value: "" },
hasInput: { value: false }
},
inputs: 0,
outputs: 1,
icon: "font-awesome/fa-text-width",
label: function () {
return this.name || "capitalize-all-first-letters";
},
button: {
onclick: function () {
var label = this._def.label.call(this);
if (label.length > 30) {
label = label.substring(0, 50) + "...";
}
label = label.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
var node = this;
$.ajax({
url: "inject/" + this.id,
type: "POST",
success: function (resp) {
RED.notify(node._("inject.success", { label: label }), { type: "success", id: "inject", timeout: 2000 });
},
error: function (jqXHR, textStatus, errorThrown) {
if (jqXHR.status == 404) {
RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.not-deployed") }), "error");
} else if (jqXHR.status == 500) {
RED.notify(node._("common.notification.error", { message: node._("inject.errors.failed") }), "error");
} else if (jqXHR.status == 0) {
RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.no-response") }), "error");
} else {
RED.notify(node._("common.notification.error", { message: node._("common.notification.errors.unexpected", { status: jqXHR.status, message: textStatus }) }), "error");
}
}
});
},
},
oneditsave: function () {
const hasInput = $("#node-input-hasInput").val() == 'true'
if (hasInput) {
this.inputs = 1
} else {
this.inputs = 0
}
},
oneditprepare: function () {
$("#node-input-hasInput").typedInput({
types: [
{
value: "bool",
options: [
{ value: "true", label: "TRUE" },
{ value: "false", label: "FALSE" },
]
}
]
})
}
});
</script>
<script type="text/html" data-template-name="capitalize-all-first-letters">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-text"><i class="fa fa-usd"></i> Text</label>
<input type="text" id="node-input-text" placeholder="Text">
</div>
<div class="form-row">
<label for="node-input-hasInput"><i class="fa fa-tag"></i> has input?</label>
<input type="text" id="node-input-hasInput" placeholder="HasInput">
</div>
</script>
<script type="text/html" data-help-name="capitalize-all-first-letters">
<p>A simple node that converts message payloads into a sentence with all first letters of words capitalized.</p>
</script>
```
As you can see, the node dialog exhibit the properties **text** and **hasInput**. Observe that the text property value is another that the value in payload property in inject value and hasInput property has false value. The inject node doesn't wire to our node, and if you try it, you won't be able. The output in debug tab is the formated sentence from text property.
> Tip: don't forget to hit the button on the left side of the node

### Download this project
[Link to GitLab](https://gitlab.com/halysson/senseflow.plugin.fext.functions) to download all things that we did from begin to this moment.
## Working with node status and some message details
For this, we will do other project, one of work with a state of connected or disconected. A remote instance of Redis will be used.
> Tip: create you own free remote instance of Redis database with [Redis Labs](https://app.redislabs.com/#/login), if you need some help, see the [how to](https://hackmd.io/@halyssonfreitassenseup/SkoDYjUlc).
The new plugin project has the package name `@senseup/senseflow-redis` and will be three nodes:
```json
"redis-connection": "dist/redis-connection/redis-connection.js",
"redis-set": "dist/redis-set/redis-set.js",
"redis-get": "dist/redis-get/redis-get.js"
```
Only the node `redis-connection` we will implement now, the other two you is able to do with the knowledge acquired so far.
- src/redis-connection/redis-connection.js
```typescript=
import { Node, NodeDef, NodeInitializer, NodeMessage } from "node-red";
import Redis from 'ioredis'
interface redisConnectionNodeDef extends NodeDef {
host?: string | undefined;
port?: string | undefined;
password?: string | undefined;
}
interface Arguments {
arguments: Array<string>
}
interface redisConnectionNodeMessage extends NodeMessage {
command?: string | undefined;
key?: string | undefined;
arguments?: Arguments | undefined;
}
let NODE: Node;
let CONFIG: redisConnectionNodeDef;
function nodeOnInput(msg: redisConnectionNodeMessage) {
const args = [msg.key]
if (arguments) args.push(...msg.arguments.arguments)
redisClient
.send_command(msg.command.toString(), args)
.then(result => {
const response_msg: NodeMessage = {
_msgid: msg._msgid,
payload: result,
topic: (msg.command.toString() + args.reduce((acum, str) => {
acum += str + " "
return acum
}, " ")).trim()
}
NODE.send(response_msg);
})
.catch(err => {
const response_msg: NodeMessage = {
_msgid: msg._msgid,
payload: err,
topic: (msg.command.toString() + args.reduce((acum, str) => {
acum += str + " "
return acum
}, " ")).trim()
}
NODE.send(response_msg);
})
}
let redisClient: Redis.Redis
function connection() {
NODE.status({ fill: "yellow", shape: "dot", text: "Connecting ..." })
if (!CONFIG.host || !CONFIG.port || !CONFIG.password) {
NODE.status({ fill: "red", shape: "dot", text: "Disconnected" })
if (redisClient) redisClient.quit()
return
}
try {
NODE.log(CONFIG.host.toString())
NODE.log(Number(CONFIG.port.toString()))
NODE.log(CONFIG.password.toString())
redisClient = new Redis({
host: CONFIG.host.toString(),
port: Number(CONFIG.port.toString()),
password: CONFIG.password.toString(),
lazyConnect: true,
autoResubscribe: false,
})
redisClient.connect()
.catch(async error => {
await redisClient.quit()
NODE.status({ fill: "red", shape: "dot", text: "Disconnected" })
})
NODE.status({ fill: "green", shape: "dot", text: "Connected" })
} catch (error) {
NODE.status({ fill: "red", shape: "dot", text: "Disconnected" })
}
}
const redis_connection: NodeInitializer = function (RED) {
function RedisConnection(config: NodeDef) {
RED.nodes.createNode(this, config)
NODE = this
CONFIG = config
NODE.on('input', nodeOnInput)
connection()
}
RED.nodes.registerType("redis-connection", RedisConnection)
}
export = redis_connection
```
- src/redis-connection/redis-connection.html
```html=
<script type="text/javascript">
RED.nodes.registerType('redis-connection', {
category: 'Senseup Redis',
color: '#FFAAAA',
defaults: {
name: { value: "" },
host: { value: "" },
port: { value: "" },
password: {type:"password"}
},
inputs:1,
outputs:1,
icon: "font-awesome/fa-plug",
label: function() {
return this.name||"redis-connection";
}
});
</script>
<script type="text/html" data-template-name="redis-connection">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-host"><i class="fa fa-tag"></i> Host</label>
<input type="text" id="node-input-host" placeholder="Host">
</div>
<div class="form-row">
<label for="node-input-port"><i class="fa fa-tag"></i> Port</label>
<input type="text" id="node-input-port" placeholder="Port">
</div>
<div class="form-row">
<label for="node-input-password"><i class="fa fa-tag"></i> Password</label>
<input type="text" id="node-input-password" placeholder="Password">
</div>
</script>
<script type="text/html" data-help-name="redis-connection">
<p>A simple node that connects to a Redis endpoint and comunicate with it.</p>
</script>
```
### Install dependencies
It needs to install this dependencies:
```
$ npm i ioredis@4.27.5
$ npm i @types/ioredis@4.27.6
```
The other tasks to do is the same executed is previus chapters. Now, install this in the SenseFlow and you will see our new node displayed in SenseFlow palette.
### Understanding what the plugin does

The `redis-connection` node has three important properties: **Host**, **Port** and **Password**. In the image, these value are mockeds.
### The node status
If these values are valid, the node will connect to a Redis database and show a little green circle with label 'connected'. This is because the line 79 in our *.ts code:
```typescript=79
NODE.status({ fill: "green", shape: "dot", text: "Connected" })
```
> Tip: try false values in our node properties and find this behavior in the *.ts file.
### The node required message
This node receive a json message thats have the follow format:
```json=
{
"command": "",
"key": "",
"arguments" :{
"arguments": [
""
]
}
}
```
The way we preper this json message is a UI interface providade by input property dialog.
> Tip: the other two nodes yet not implemented are to you replace this actual solution using input node.

The image above show to us the input (named SET) node properties, in the other way, this prepare the follow json:
```json=
{
"command": "set",
"key": "SenseUp",
"arguments" :{
"arguments": [
"The best platform to low coding integration!"
]
}
}
```
The input (named GET) has a more simple json:
```json=
{
"command": "get",
"key": "SenseUp",
}
```
## Contributing
Congratulations, you have finished the tutorial! You can now begin contributing new plugins to the SenseFlow platform.
Any new plugin should be created from the [template project](https://gitlab.com/multicast/senseup/senseflow.template.plugin), as it has most of the configuration set up and helps you focus on the features that need to be developed.