node-red-contrib-monzo
Version:
This is a wrapper that will allow a connection to the monzo bank API for use within node-red
338 lines (312 loc) • 13 kB
JavaScript
module.exports = function(RED) {
"use strict";
var request = require("request");
var querystring = require("querystring");
var CronJob = require('cron').CronJob;
/*
Setup MonzoCredentialsNode
*/
function MonzoCredentialsNode(n) {
RED.nodes.createNode(this, n);
this.cronjob = null;
var node = this;
/*
Set up cron job and add it to node object so it can be stopped upon node close.
*/
this.cronjob = new CronJob('1 */60 * * * *', function() { // Cron Job Once Per Hour, This Needs to be vairable later on.
const Monzo = require('monzo-js');
var creds = RED.nodes.getCredentials(node.id);
var secret = creds.secret;
var clientid = creds.client_id;
var refreshtoken = creds.refreshtoken;
if (refreshtoken != "" && refreshtoken != undefined) {
console.log("[monzo] - refreshing token");
/*
Using monzojs, refresh the token and retreive the access_token and refresh_token
*/
Monzo.OAuth.refreshToken(clientid, secret, refreshtoken).then(({
access_token,
refresh_token
}) => {
if (access_token) {
console.log("[monzo] - refresh complete");
var credentials = {
client_id: clientid,
secret: secret,
token: access_token,
refreshtoken: refresh_token
};
RED.nodes.addCredentials(node.id, credentials);
} else {
console.log("[monzo] - refresh failed");
}
}).catch(error => {
//console.log("[monzo] - refresh failed, needs reauthenticating");
});
} else {
//no refresh token dont bother trying to refresh it.
}
}, null, true, 'America/Los_Angeles');
}
/*
Upon node close i.e when node-red closes or a user redeploys the flow, stop the cronjob and remove it.
*/
MonzoCredentialsNode.prototype.close = function() {
this.cronjob.stop();
delete this.cronjob;
};
/*
Register the monzo credentials node along with the credentials it will need.
*/
RED.nodes.registerType("monzo-credentials", MonzoCredentialsNode, {
credentials: {
client_id: {
type: "text"
},
secret: {
type: "text"
},
token: {
type: "text"
},
refreshtoken: {
type: "text"
},
redirect_uri:{
type: "text"
}
}
});
/*
Set up endpoint so that we can set the client id and secret when we press authorise with monzo
*/
RED.httpAdmin.get('/monzo-creds-set', function(req, res) {
var secret = req.query.secret;
var clientid = req.query.clientid;
var nodeid = req.query.nodeid;
var redirect = req.query.redirect;
if (secret != "" && clientid != "" && nodeid != "") {
var credentials = {
client_id: clientid,
secret: secret,
redirect_uri:redirect
};
RED.nodes.addCredentials(nodeid, credentials);
res.send("success");
} else {
res.send("fail");
}
});
/*
set up endpoint for monzo to redirect back to.
*/
RED.httpAdmin.get('/monzo-creds', function(req, res) {
//upon redirecting get the auth token and post to monzo to get the refresh token and the access_token
var credential_node_id = req.query.state;
var monzocreds = RED.nodes.getCredentials(credential_node_id);
var opts = {};
opts.url = "https://api.monzo.com/oauth2/token";
opts.timeout = 2000;
opts.method = "POST";
opts.headers = {};
opts.maxRedirects = 21;
var protocol = "";
if (req.connection.encrypted) {
protocol = "https";
} else {
protocol = "http";
}
var auth_token = req.query.code;
var postvars = {
grant_type: "authorization_code",
client_id: monzocreds.client_id,
client_secret: monzocreds.secret,
redirect_uri: monzocreds.redirect_uri,
code: auth_token
}
opts.form = postvars;
request(opts, function(err, ress, body) {
if (err) {
////console.log(err)
res.send(err);
} else {
/*
No errors Set tokens and display to the user it was successful and what the next step is.
*/
var bodyObject = JSON.parse(body);
var additional_text = "";
if (bodyObject.refresh_token) {
additional_text = "<br>This API client has Confidential set to True, because of this, your access_token will automatically refresh itself.";
} else {
//console.log('no refresh token');
additional_text = "<br>This token will expire and you will need to manually refresh it. This is because your Monzo API client is set to (Not Confidential).";
}
if (bodyObject.access_token) {
var credentials = {
client_id: monzocreds.client_id,
secret: monzocreds.secret,
token: bodyObject.access_token,
refreshtoken: bodyObject.refresh_token
};
RED.nodes.addCredentials(credential_node_id, credentials);
res.send("<center><h1>You have successfully authenticated.</h1><h3>Please return to your node-red flow and close this tab/window, you will see that the 'access_token' is now filled in.</h3>" + additional_text + "</center>");
} else {
//something went wrong, show error.
res.send(body);
}
}
});
});
/*
Setup MonzoNodeIn
*/
function MonzoNodeIn(config) {
RED.nodes.createNode(this, config);
this.requesttype = config.requesttype;
this.responsetype = config.responsetype;
this.monzoConfig = RED.nodes.getNode(config.monzocreds);
const Monzo = require('monzo-js');
var node = this;
if (!this.monzoConfig) {
this.status({
fill: "red",
shape: "dot",
text: "no token"
});
} else {
this.status({
fill: "green",
shape: "dot",
text: "ready"
});
}
/*
setup on node input function so that we can handle api requests.
*/
node.on('input', function(msg) {
this.monzoConfig = RED.nodes.getNode(config.monzocreds);
var monzocredentials = RED.nodes.getCredentials(config.monzocreds);
this.status({
fill: "yellow",
shape: "dot",
text: "requesting"
});
if (!this.monzoConfig) {
this.status({
fill: "red",
shape: "dot",
text: "no token"
});
node.error("you have not entered a token", msg);
} else {
/*
We have credentials and access tokens, allow requests to happen.
*/
const monzo = new Monzo(monzocredentials.token);
/*
Accounts request
*/
if (this.requesttype == "accounts") {
monzo.accounts.all().then(accounts => {
for (const [id, acc] of accounts) {
msg.payload = {
"response": acc._account
};
node.send(msg);
this.status({
fill: "green",
shape: "dot",
text: "ready"
});
}
}).catch(error => {
node.error("your token is not authenticated. -> "+error, msg);
this.status({
fill: "red",
shape: "dot",
text: "no auth"
});
});
}
/*
Balances request
*/
if (this.requesttype == "balances") {
monzo.accounts.all().then(accounts => {
for (const [id, acc] of accounts) {
msg.payload = {
"response": acc._balance._balance
};
node.send(msg);
this.status({
fill: "green",
shape: "dot",
text: "ready"
});
}
}).catch(error => {
node.error("your token is not authenticated. -> "+error, msg);
this.status({
fill: "red",
shape: "dot",
text: "no auth"
});
});
}
/*
Pots request
*/
if (this.requesttype == "pots") {
monzo.pots.all().then(pots => {
var outputlist = [];
for (const [id, pot] of pots) {
//check for null and everything else to save backwards compatibility
if(this.responsetype == "stream" || this.responsetype == null || this.responsetype == undefined || this.responsetype == ""){
msg.payload = {
"response": {
"pot_id": pot.id,
"pot_name": pot.name,
"pot_balance": pot.balance
}
};
node.send(msg);
this.status({
fill: "green",
shape: "dot",
text: "ready"
});
} else if(this.responsetype == "single"){
outputlist.push({
"pot_id": pot.id,
"pot_name": pot.name,
"pot_balance": pot.balance
});
}
}
if(this.responsetype == "single"){
msg.payload = {
"response": {
"list": outputlist
}
};
node.send(msg);
this.status({
fill: "green",
shape: "dot",
text: "ready"
});
}
}).catch(error => {
node.error("your token is not authenticated. -> "+error, msg);
this.status({
fill: "red",
shape: "dot",
text: "no auth"
});
});
}
}
});
}
RED.nodes.registerType("monzo-in", MonzoNodeIn);
}