| 1 | 'use strict';
|
|---|
| 2 |
|
|---|
| 3 | const Mailer = require('./mailer');
|
|---|
| 4 | const shared = require('./shared');
|
|---|
| 5 | const SMTPPool = require('./smtp-pool');
|
|---|
| 6 | const SMTPTransport = require('./smtp-transport');
|
|---|
| 7 | const SendmailTransport = require('./sendmail-transport');
|
|---|
| 8 | const StreamTransport = require('./stream-transport');
|
|---|
| 9 | const JSONTransport = require('./json-transport');
|
|---|
| 10 | const SESTransport = require('./ses-transport');
|
|---|
| 11 | const nmfetch = require('./fetch');
|
|---|
| 12 | const packageData = require('../package.json');
|
|---|
| 13 |
|
|---|
| 14 | const ETHEREAL_API = (process.env.ETHEREAL_API || 'https://api.nodemailer.com').replace(/\/+$/, '');
|
|---|
| 15 | const ETHEREAL_WEB = (process.env.ETHEREAL_WEB || 'https://ethereal.email').replace(/\/+$/, '');
|
|---|
| 16 | const ETHEREAL_API_KEY = (process.env.ETHEREAL_API_KEY || '').replace(/\s*/g, '') || null;
|
|---|
| 17 | const ETHEREAL_CACHE = ['true', 'yes', 'y', '1'].includes((process.env.ETHEREAL_CACHE || 'yes').toString().trim().toLowerCase());
|
|---|
| 18 |
|
|---|
| 19 | let testAccount = false;
|
|---|
| 20 |
|
|---|
| 21 | module.exports.createTransport = function (transporter, defaults) {
|
|---|
| 22 | let urlConfig;
|
|---|
| 23 | let options;
|
|---|
| 24 | let mailer;
|
|---|
| 25 |
|
|---|
| 26 | if (
|
|---|
| 27 | // provided transporter is a configuration object, not transporter plugin
|
|---|
| 28 | (typeof transporter === 'object' && typeof transporter.send !== 'function') ||
|
|---|
| 29 | // provided transporter looks like a connection url
|
|---|
| 30 | (typeof transporter === 'string' && /^(smtps?|direct):/i.test(transporter))
|
|---|
| 31 | ) {
|
|---|
| 32 | if ((urlConfig = typeof transporter === 'string' ? transporter : transporter.url)) {
|
|---|
| 33 | // parse a configuration URL into configuration options
|
|---|
| 34 | options = shared.parseConnectionUrl(urlConfig);
|
|---|
| 35 | } else {
|
|---|
| 36 | options = transporter;
|
|---|
| 37 | }
|
|---|
| 38 |
|
|---|
| 39 | if (options.pool) {
|
|---|
| 40 | transporter = new SMTPPool(options);
|
|---|
| 41 | } else if (options.sendmail) {
|
|---|
| 42 | transporter = new SendmailTransport(options);
|
|---|
| 43 | } else if (options.streamTransport) {
|
|---|
| 44 | transporter = new StreamTransport(options);
|
|---|
| 45 | } else if (options.jsonTransport) {
|
|---|
| 46 | transporter = new JSONTransport(options);
|
|---|
| 47 | } else if (options.SES) {
|
|---|
| 48 | transporter = new SESTransport(options);
|
|---|
| 49 | } else {
|
|---|
| 50 | transporter = new SMTPTransport(options);
|
|---|
| 51 | }
|
|---|
| 52 | }
|
|---|
| 53 |
|
|---|
| 54 | mailer = new Mailer(transporter, options, defaults);
|
|---|
| 55 |
|
|---|
| 56 | return mailer;
|
|---|
| 57 | };
|
|---|
| 58 |
|
|---|
| 59 | module.exports.createTestAccount = function (apiUrl, callback) {
|
|---|
| 60 | let promise;
|
|---|
| 61 |
|
|---|
| 62 | if (!callback && typeof apiUrl === 'function') {
|
|---|
| 63 | callback = apiUrl;
|
|---|
| 64 | apiUrl = false;
|
|---|
| 65 | }
|
|---|
| 66 |
|
|---|
| 67 | if (!callback) {
|
|---|
| 68 | promise = new Promise((resolve, reject) => {
|
|---|
| 69 | callback = shared.callbackPromise(resolve, reject);
|
|---|
| 70 | });
|
|---|
| 71 | }
|
|---|
| 72 |
|
|---|
| 73 | if (ETHEREAL_CACHE && testAccount) {
|
|---|
| 74 | setImmediate(() => callback(null, testAccount));
|
|---|
| 75 | return promise;
|
|---|
| 76 | }
|
|---|
| 77 |
|
|---|
| 78 | apiUrl = apiUrl || ETHEREAL_API;
|
|---|
| 79 |
|
|---|
| 80 | let chunks = [];
|
|---|
| 81 | let chunklen = 0;
|
|---|
| 82 |
|
|---|
| 83 | let requestHeaders = {};
|
|---|
| 84 | let requestBody = {
|
|---|
| 85 | requestor: packageData.name,
|
|---|
| 86 | version: packageData.version
|
|---|
| 87 | };
|
|---|
| 88 |
|
|---|
| 89 | if (ETHEREAL_API_KEY) {
|
|---|
| 90 | requestHeaders.Authorization = 'Bearer ' + ETHEREAL_API_KEY;
|
|---|
| 91 | }
|
|---|
| 92 |
|
|---|
| 93 | let req = nmfetch(apiUrl + '/user', {
|
|---|
| 94 | contentType: 'application/json',
|
|---|
| 95 | method: 'POST',
|
|---|
| 96 | headers: requestHeaders,
|
|---|
| 97 | body: Buffer.from(JSON.stringify(requestBody))
|
|---|
| 98 | });
|
|---|
| 99 |
|
|---|
| 100 | req.on('readable', () => {
|
|---|
| 101 | let chunk;
|
|---|
| 102 | while ((chunk = req.read()) !== null) {
|
|---|
| 103 | chunks.push(chunk);
|
|---|
| 104 | chunklen += chunk.length;
|
|---|
| 105 | }
|
|---|
| 106 | });
|
|---|
| 107 |
|
|---|
| 108 | req.once('error', err => callback(err));
|
|---|
| 109 |
|
|---|
| 110 | req.once('end', () => {
|
|---|
| 111 | let res = Buffer.concat(chunks, chunklen);
|
|---|
| 112 | let data;
|
|---|
| 113 | let err;
|
|---|
| 114 | try {
|
|---|
| 115 | data = JSON.parse(res.toString());
|
|---|
| 116 | } catch (E) {
|
|---|
| 117 | err = E;
|
|---|
| 118 | }
|
|---|
| 119 | if (err) {
|
|---|
| 120 | return callback(err);
|
|---|
| 121 | }
|
|---|
| 122 | if (data.status !== 'success' || data.error) {
|
|---|
| 123 | return callback(new Error(data.error || 'Request failed'));
|
|---|
| 124 | }
|
|---|
| 125 | delete data.status;
|
|---|
| 126 | testAccount = data;
|
|---|
| 127 | callback(null, testAccount);
|
|---|
| 128 | });
|
|---|
| 129 |
|
|---|
| 130 | return promise;
|
|---|
| 131 | };
|
|---|
| 132 |
|
|---|
| 133 | module.exports.getTestMessageUrl = function (info) {
|
|---|
| 134 | if (!info || !info.response) {
|
|---|
| 135 | return false;
|
|---|
| 136 | }
|
|---|
| 137 |
|
|---|
| 138 | let infoProps = new Map();
|
|---|
| 139 | info.response.replace(/\[([^\]]+)\]$/, (m, props) => {
|
|---|
| 140 | props.replace(/\b([A-Z0-9]+)=([^\s]+)/g, (m, key, value) => {
|
|---|
| 141 | infoProps.set(key, value);
|
|---|
| 142 | });
|
|---|
| 143 | });
|
|---|
| 144 |
|
|---|
| 145 | if (infoProps.has('STATUS') && infoProps.has('MSGID')) {
|
|---|
| 146 | return (testAccount.web || ETHEREAL_WEB) + '/message/' + infoProps.get('MSGID');
|
|---|
| 147 | }
|
|---|
| 148 |
|
|---|
| 149 | return false;
|
|---|
| 150 | };
|
|---|