Skip to content

Commit

Permalink
feat(api): Allows input to set/override config values
Browse files Browse the repository at this point in the history
Closes #100
  • Loading branch information
zachowj committed Mar 10, 2019
1 parent 16a9b81 commit 7296cd2
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 47 deletions.
69 changes: 32 additions & 37 deletions nodes/api/api.html
Expand Up @@ -38,39 +38,7 @@

$("#node-input-data")
.typedInput({
types: [
{
value: "json",
label: "JSON",
icon: "red/images/typedInput/json.png",
validate: function(v) {
if (!v) return true;
try {
JSON.parse(v);
return true;
} catch (e) {
return false;
}
},
expand: function() {
const that = this;
const value = this.value();
try {
value = JSON.stringify(JSON.parse(value), null, 4);
} catch (err) {}
RED.editor.editJSON({
value: value,
complete: function(v) {
const value = v;
try {
value = JSON.stringify(JSON.parse(v));
} catch (err) {}
that.value(value);
}
});
}
}
]
types: ["json"]
})
.typedInput("width", "68%");

Expand Down Expand Up @@ -168,12 +136,39 @@ <h3>Configuration</h3>
<dd>JSON Object to send for WebSocket requests and HTTP posts.</dd>

<dt>Results<span class="property-type">string</span></dt>
<dd>Location to saved the API results.</dd>
<dd>Location to save the API results.</dd>
</dl>

<h3>Inputs</h3>
<dl class="message-properties">
<dt>payload.protocol<span class="property-type">[websocket|http] string</span></dt>
<dd>Overrides or sets the protocol property of the config.</dd>

<dt>payload.method<span class="property-type">[get|post] string</span></dt>
<dd>Overrides or sets the method property of the config.</dd>

<dt>Templates</dt>
<dd>Templates can be used in path, params and data fields.</dd>
<dt>payload.path<span class="property-type">string</span></dt>
<dd>Overrides or sets the path property of the config.</dd>

<dt>payload.data<span class="property-type">JSON | string</span></dt>
<dd>Overrides or sets the data/params property of the config.</dd>

<dt>payload.location<span class="property-type">string</span></dt>
<dd>Overrides or sets the results property of the config.</dd>

<dt>payload.locationType<span class="property-type">[msg|flow|global] string</span></dt>
<dd>Overrides or sets the results type property of the config.</dd>
</dl>

<h3>Outputs</h3>
Will output the results received from the API call to the location defined in the config.
<p>Will output the results received from the API call to the location defined in the config.</p>

<h3>Templates</h3>
<p>Templates can be used in path, params and data fields. When using templates the top level is a property of the message object: <code>msg.payload</code> would be <code>{{payload}}</code>.</p>

<h3>References</h3>
<p>
<a href="https://developers.home-assistant.io/docs/en/external_api_rest.html">http api</a><br />
<a href="https://developers.home-assistant.io/docs/en/external_api_websocket.html">websocket api</a>
</p>
</script>
73 changes: 63 additions & 10 deletions nodes/api/api.js
@@ -1,5 +1,6 @@
const RenderTemplate = require('../../lib/mustache-context');
const BaseNode = require('../../lib/base-node');
const Joi = require('joi');

module.exports = function(RED) {
const nodeOptions = {
Expand All @@ -12,28 +13,77 @@ module.exports = function(RED) {
data: {},
location: {},
locationType: {}
},
input: {
protocol: {
messageProp: 'payload.protocol',
configProp: 'protocol',
validation: {
haltOnFail: true,
schema: Joi.string().valid('websocket', 'http')
}
},
method: {
messageProp: 'payload.method',
configProp: 'method',
validation: {
haltOnFail: true,
schema: Joi.string().valid('get', 'post')
}
},
path: {
messageProp: 'payload.path',
configProp: 'path',
validation: {
haltOnFail: true,
schema: Joi.string()
}
},
data: {
messageProp: 'payload.data',
configProp: 'data'
},
location: {
messageProp: 'payload.location',
configProp: 'location',
validation: {
haltOnFail: true,
schema: Joi.string()
}
},
locationType: {
messageProp: 'payload.locationType',
configProp: 'locationType',
validation: {
haltOnFail: true,
schema: Joi.string().valid('msg', 'flow', 'global')
}
}
}
};
class ApiNode extends BaseNode {
constructor(nodeDefinition) {
super(nodeDefinition, RED, nodeOptions);
}

onInput({ message }) {
onInput({ message, parsedMessage }) {
const node = this;
const config = node.nodeConfig;
const serverName = node.utils.toCamelCase(config.server.name);
const data = RenderTemplate(
config.data,
typeof parsedMessage.data.value === 'object'
? JSON.stringify(parsedMessage.data.value)
: parsedMessage.data.value,
message,
node.node.context(),
serverName
);
const method = parsedMessage.method.value;
let apiCall;

if (config.protocol === 'http') {
if (parsedMessage.protocol.value === 'http') {
const path = RenderTemplate(
config.path,
parsedMessage.path.value,
message,
node.node.context(),
serverName
Expand All @@ -45,13 +95,13 @@ module.exports = function(RED) {
return;
}

if (!['get', 'post'].includes(config.method)) {
if (!['get', 'post'].includes(method)) {
node.error('HTTP request requires a valid method');
node.setStatusFailed();
return;
}

apiCall = config.server.http[`_${config.method}`].bind(
apiCall = config.server.http[`_${method}`].bind(
config.server.http,
path,
data
Expand Down Expand Up @@ -83,18 +133,21 @@ module.exports = function(RED) {

return apiCall()
.then(results => {
node.setStatusSuccess(`${config.protocol} called`);
node.setStatusSuccess(
`${parsedMessage.protocol.value} called`
);

const contextKey = RED.util.parseContextStore(
config.location
parsedMessage.location.value
);
contextKey.key = contextKey.key || 'payload';
const locationType = config.location_type || 'msg';
const locationType =
parsedMessage.locationType.value || 'msg';

if (locationType === 'flow' || locationType === 'global') {
node.node
.context()
[locationType].set(
[parsedMessage.locationType.value].set(
contextKey.key,
results,
contextKey.store
Expand Down

1 comment on commit 7296cd2

@Swiftnesses
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @zachowj :)

Please sign in to comment.