From 862a3bbcbd71965057263e72a5bacafd7800e726 Mon Sep 17 00:00:00 2001 From: TekMonksGitHub Date: Wed, 19 May 2021 14:21:55 +0900 Subject: [PATCH] Add SMTP node. Allow expanding properties. --- .vscode/launch.json | 3 +- conf/crypt.json | 3 +- flows/pipeline_editojson.json | 2 +- flows/pipeline_editojson2.json | 2 +- flows/pipeline_emailFileNotification.json | 31 ++++++++++++++++++ lib/crypt.js | 2 +- lib/mailer.js | 35 ++++++++++++++++++++ lib/main.js | 2 +- lib/utils.js | 8 ++++- outputs/email.js | 7 ++++ routes/email.js | 40 +++++++++++++++++++++++ routes/httpclient.js | 4 +-- routes/js.js | 3 +- 13 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 flows/pipeline_emailFileNotification.json create mode 100644 lib/mailer.js create mode 100644 outputs/email.js create mode 100644 routes/email.js diff --git a/.vscode/launch.json b/.vscode/launch.json index f92516f..a3e5aa7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,8 @@ "type": "node", "request": "launch", "name": "Launch ESB Clustered", - "program": "${workspaceFolder}/esb.js" + "program": "${workspaceFolder}/esb.js", + "env": {"JAVA_HOME":"${workspaceFolder}/../../Programs/jdk-16.0.1"} } ] } \ No newline at end of file diff --git a/conf/crypt.json b/conf/crypt.json index d616138..ee7a23d 100644 --- a/conf/crypt.json +++ b/conf/crypt.json @@ -1,3 +1,4 @@ { - "key":"C77AE67918DAF5A819EE6EF2E9930554W2" + "key":"C77AE67918DAF5A819EE6EF2E9930554W2", + "crypt_algo":"aes-256-ctr" } diff --git a/flows/pipeline_editojson.json b/flows/pipeline_editojson.json index 5575684..a046812 100644 --- a/flows/pipeline_editojson.json +++ b/flows/pipeline_editojson.json @@ -18,7 +18,7 @@ "route1": { "type":"ediparser", "dependencies":["route0"], - "java":"c:/Users/Rohit Kapoor/source/jdk8/bin/java.exe" + "java":"{{process.env.JAVA_HOME}}/bin/java.exe" }, "output": { "type": "filewriter", diff --git a/flows/pipeline_editojson2.json b/flows/pipeline_editojson2.json index 491a1d4..190bc03 100644 --- a/flows/pipeline_editojson2.json +++ b/flows/pipeline_editojson2.json @@ -23,7 +23,7 @@ "route2": { "type":"ediparser", "dependencies":["route1.1"], - "java":"c:/Users/Rohit Kapoor/source/jdk8/bin/java.exe" + "java":"{{process.env.JAVA_HOME}}/bin/java.exe" }, "route3": { "type":"filewriter", diff --git a/flows/pipeline_emailFileNotification.json b/flows/pipeline_emailFileNotification.json new file mode 100644 index 0000000..76e4e17 --- /dev/null +++ b/flows/pipeline_emailFileNotification.json @@ -0,0 +1,31 @@ +{ + "flow":{ + "name":"Email file notification", + "disabled":false + }, + "listener": { + "type":"file", + "isMessageGenerator": true, + "path":"C:/test/in/*", + "donePath":"C:/test/done" + }, + "route0":{ + "type": "js", + "dependencies":"listener", + "module":"{{ESB_DIR}}/custom/email_custom.js" + }, + "output": { + "type": "email", + "dependencies":"route0", + "host":"smtp.mailgun.org", + "port": 465, + "secure": true, + "user": "noreply@teleworkr.com", + "password": "", + "to": "rvkapoor@tekmonks.com", + "from": "noreply@tekmonks.com", + "title": "New request was submitted", + "text": "New file has been submitted for processing.", + "html": "

New file has been submitted for processing.

" + } +} \ No newline at end of file diff --git a/lib/crypt.js b/lib/crypt.js index 625f5c7..3fab751 100644 --- a/lib/crypt.js +++ b/lib/crypt.js @@ -36,4 +36,4 @@ if (require.main === module) { console.log(eval(args[0])(args[1])); } -module.exports = { encrypt, decrypt }; \ No newline at end of file +module.exports = { encrypt, decrypt }; diff --git a/lib/mailer.js b/lib/mailer.js new file mode 100644 index 0000000..83e6627 --- /dev/null +++ b/lib/mailer.js @@ -0,0 +1,35 @@ +/** + * Email module. + * + * (C) 2020 TekMonks. All rights reserved. + * See enclosed LICENSE file. + */ +const nodemailer = require("nodemailer"); + +/** + * Sends an email. + * @param {*} to To email address + * @param {*} from From email address + * @param {*} title The subject of the email + * @param {*} email_html The content of the email as HTML + * @param {*} email_text The content of the email as Text + * @param {*} attachments The set of attachments, format [{path: path/to/file},...] + * @param {*} conf The config contains {host, port, secure, user, pass} for SMTP + * @returns {result:true|false, response: Mailer response on true, error: Error on false} + */ +module.exports.email = async function(to, from, title, email_html, email_text, attachments, conf) { + const smtpConfig = { pool: true, host: conf.server, port: conf.port, secure: conf.secure, + auth: {user: conf.user, pass: conf.pass} }, + transporter = nodemailer.createTransport(smtpConfig); + + const nodeMailerAttachments = []; for (const attachment of attachments) nodeMailerAttachments.push({path:attachment}); + + try { + const response = await transporter.sendMail({"from": from, "to": to, "subject": title, "text": email_text, + "html": email_html, attachments: nodeMailerAttachments}); + return {result: true, response}; + } catch (err) { + LOG.error(`Email send failed due to ${err}`); + return {result: false, error: err}; + } +} \ No newline at end of file diff --git a/lib/main.js b/lib/main.js index 82654f6..6404745 100644 --- a/lib/main.js +++ b/lib/main.js @@ -57,7 +57,7 @@ function launchNewFlowInstance(flow) { let routeNames = Object.keys(flow); routeNames.splice(routeNames.indexOf("flow"), 1); routeNames.splice(routeNames.indexOf("env"), 1); - LOG.info(`[ESB] Starting flow: ${flow.flow.name}`); + LOG.info(`[ESB] Starting the flow: ${flow.flow.name}`); routeNames.forEach(route => flow[route].flow = flow); runFlow(flow, routeNames); diff --git a/lib/utils.js b/lib/utils.js index b90211c..49c2c35 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,6 +1,7 @@ /* * (C) 2015 - 2018 TekMonks. All rights reserved. */ +const mustache = require("mustache"); function getDateTime() { @@ -36,4 +37,9 @@ function getObjectKeyValueCaseInsensitive(obj, key) { return null; } -module.exports = { getDateTime, getTimeStamp, getObjectKeyValueCaseInsensitive }; +function expandProperty(property, flow, message) { + const data = {esb: global.ESB, flow, message, constants: global.CONSTANTS, ESB_DIR: CONSTANTS.ROOTDIR, process: global.process}; + return mustache.render(property, data); +} + +module.exports = { getDateTime, getTimeStamp, getObjectKeyValueCaseInsensitive, expandProperty }; diff --git a/outputs/email.js b/outputs/email.js new file mode 100644 index 0000000..89fbd28 --- /dev/null +++ b/outputs/email.js @@ -0,0 +1,7 @@ +/** + * email.js - Sends email, output node + * + * (C) 2020 TekMonks. All rights reserved. + */ + +exports.start = require(`${CONSTANTS.ROOTDIR}/routes/email.js`).start; \ No newline at end of file diff --git a/routes/email.js b/routes/email.js new file mode 100644 index 0000000..226566a --- /dev/null +++ b/routes/email.js @@ -0,0 +1,40 @@ +/* + * email.js - Sends an email via SMTP + * + * (C) 2018 TekMonks. All rights reserved. + */ +const crypt = require(`${CONSTANTS.LIBDIR}/crypt.js`); +const mailer = require(`${CONSTANTS.LIBDIR}/mailer.js`); + +exports.start = async (routeName, email, _messageContainer, message) => { + if (message.env[routeName]?.isProcessing) return; + if (!message.env[routeName]) message.env[routeName] = {isProcessing: true}; + message.setGCEligible(false); + + // transfer email and email routing data, if specified, from the message content + email.to = message.content.to || email.to; email.from = message.content.from || email.from; + email.title = message.content.title || email.title; email.html = message.content.html; email.text = message.content.text; + email.attachments = email.attachments || []; if (message.content.attachments) for (const attachment of message.content.attachments) email.attachments.push(attachment); + + LOG.info(`[EMAIL] Emailing ${email.to}, from ${email.from} with incoming message with timestamp: ${message.timestamp}`); + + if (!email.port) email.port = (email.secure?587:25); // handle ports + + const result = await mailer.email(email.to, email.from, email.title, + email.html, email.text, email.attachments, { user: email.user, + pass: crypt.decrypt(email.password), server: email.host, port: email.port, secure: email.secure }); + + if (result.result) { + message.addRouteDone(routeName); + delete message.env[routeName]; // clean up our mess + message.setGCEligible(true); + message.content = result.response; + LOG.info(`[EMAIL] Email sent for message with timestamp: ${message.timestamp}`); + LOG.debug(`[EMAIL] Response data is: ${result.response}`); + } else { + LOG.error(`[EMAIL] Email failed with error: ${result.error}, for message with timestamp: ${message.timestamp}`); + message.addRouteError(routeName); + delete message.env[routeName]; // clean up our mess + message.setGCEligible(true); + } +} \ No newline at end of file diff --git a/routes/httpclient.js b/routes/httpclient.js index cb3756e..e488695 100644 --- a/routes/httpclient.js +++ b/routes/httpclient.js @@ -29,12 +29,12 @@ exports.start = (routeName, httpclient, _messageContainer, message) => { httpclient.timeout, (error, result) => { if (error) { - LOG.error(`[HTTP] Call failed with error: ${error}`); + LOG.error(`[HTTP] Call failed with error: ${error}, for message with timestamp: ${message.timestamp}`); message.addRouteError(routeName); delete message.env[routeName]; // clean up our mess message.setGCEligible(true); } else { - message.addRouteDone(`${routeName}`); + message.addRouteDone(routeName); delete message.env[routeName]; // clean up our mess message.setGCEligible(true); message.content = result.response; diff --git a/routes/js.js b/routes/js.js index da69031..c29aeb8 100644 --- a/routes/js.js +++ b/routes/js.js @@ -3,11 +3,12 @@ * * (C) 2018 TekMonks. All rights reserved. */ +const utils = require(`${CONSTANTS.LIBDIR}/utils.js`); exports.start = (routeName, js, messageContainer, message) => { LOG.info(`[ROUTE_JS] Processing message with timestamp: ${message.timestamp}`); - if (js.module) {require(js.module).start(routeName, js, messageContainer, message);} else { + if (js.module) {require(utils.expandProperty(js.module, js.flow, message)).start(routeName, js, messageContainer, message);} else { try { const flow = js.flow; // allows JS code below to call the flow. eval(js.js);