From 631b74ff50a8de209baf9333ccaae5a59409cd57 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Wed, 27 Jul 2022 15:04:55 -0300 Subject: [PATCH] Create `Email.sendAsync` method without using Fibers. --- packages/email/email.js | 75 ++++++++++++++++++++++++++++------- packages/email/email_tests.js | 38 ++++++++++++++++++ 2 files changed, 98 insertions(+), 15 deletions(-) diff --git a/packages/email/email.js b/packages/email/email.js index e0daf26384..0240b186b5 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -140,6 +140,10 @@ const getTransport = function () { let nextDevModeMailId = 0; let output_stream = process.stdout; +EmailTest._getAndIncNextDevModeMailId = function () { + return nextDevModeMailId++; +}; + // Testing hooks EmailTest.overrideOutputStream = function (stream) { nextDevModeMailId = 0; @@ -150,11 +154,9 @@ EmailTest.restoreOutputStream = function () { output_stream = process.stdout; }; -const devModeSend = async function (mail) { +const devModeSendAsync = async function (mail, stream) { return new Promise((resolve, reject) => { - let devModeMailId = nextDevModeMailId++; - - const stream = output_stream; + let devModeMailId = EmailTest._getAndIncNextDevModeMailId(); // This approach does not prevent other writers to stdout from interleaving. stream.write('====== BEGIN MAIL #' + devModeMailId + ' ======\n'); @@ -166,7 +168,7 @@ const devModeSend = async function (mail) { readStream.pipe(stream, { end: false }); readStream.on('end', function () { stream.write('====== END MAIL #' + devModeMailId + ' ======\n'); - resolve(); + resolve(stream); }); readStream.on('error', (err) => reject(err)); }); @@ -235,6 +237,7 @@ Email.customTransport = undefined; * `new EmailInternals.NpmModules.mailcomposer.module`. */ Email.sendAsync = async function (options) { + const stream = output_stream; return new Promise((resolve, reject) => { if (options.mailComposer) { options = options.mailComposer.mail; @@ -277,12 +280,34 @@ Email.sendAsync = async function (options) { resolve(); return; } - devModeSend(options) - .then(() => resolve()) + devModeSendAsync(options, stream) + .then((currentStream) => resolve(currentStream)) .catch((err) => reject(err)); }); }; +// TODO To remove in future versions (3.0.0 ????) +const devModeSend = function (mail) { + let devModeMailId = EmailTest._getAndIncNextDevModeMailId(); + + const stream = output_stream; + + // This approach does not prevent other writers to stdout from interleaving. + stream.write('====== BEGIN MAIL #' + devModeMailId + ' ======\n'); + stream.write( + '(Mail not sent; to enable sending, set the MAIL_URL ' + + 'environment variable.)\n' + ); + const readStream = new MailComposer(mail).compile().createReadStream(); + readStream.pipe(stream, { end: false }); + const future = new Future(); + readStream.on('end', function () { + stream.write('====== END MAIL #' + devModeMailId + ' ======\n'); + future.return(); + }); + future.wait(); +}; + /** * @summary Send an email. Throws an `Error` on failure to contact mail server * or if mail server returns an error. All fields should match @@ -316,12 +341,32 @@ Email.sendAsync = async function (options) { * You can create a `MailComposer` object via * `new EmailInternals.NpmModules.mailcomposer.module`. */ -Email.send = async function (options) { - const future = new Future(); - Email.sendAsync(options) - .then(() => future.return()) - .catch((err) => { - throw err; - }); - future.wait(); +Email.send = function (options) { + if (options.mailComposer) { + options = options.mailComposer.mail; + } + + let send = true; + sendHooks.forEach((hook) => { + send = hook(options); + return send; + }); + if (!send) return; + + const customTransport = Email.customTransport; + if (customTransport) { + const packageSettings = Meteor.settings.packages?.email || {}; + customTransport({ packageSettings, ...options }); + return; + } + if ( + Meteor.isProduction || + process.env.MAIL_URL || + Meteor.settings.packages?.email + ) { + const transport = getTransport(); + smtpSend(transport, options); + return; + } + devModeSend(options); }; diff --git a/packages/email/email_tests.js b/packages/email/email_tests.js index 877264ce95..bbba84205e 100644 --- a/packages/email/email_tests.js +++ b/packages/email/email_tests.js @@ -25,6 +25,44 @@ function canonicalize(string) { .replace(/(boundary="|^--)--[^\s"]+?(-Part|")/mg, "$1--...$2"); } +Tinytest.addAsync('[Async] email - fully customizable', function (test, onComplete) { + smokeEmailTest(function () { + Email.sendAsync({ + from: 'foo@example.com', + to: 'bar@example.com', + cc: ['friends@example.com', 'enemies@example.com'], + subject: 'This is the subject', + text: 'This is the body\nof the message\nFrom us.', + headers: { + 'X-Meteor-Test': 'a custom header', + Date: 'dummy', + }, + }).then((currentStream) => { + test.equal( + canonicalize(currentStream.getContentsAsString('utf8')), + '====== BEGIN MAIL #0 ======\n' + + devWarningBanner + + 'Content-Type: text/plain; charset=utf-8\r\n' + + 'X-Meteor-Test: a custom header\r\n' + + 'Date: dummy\r\n' + + 'From: foo@example.com\r\n' + + 'To: bar@example.com\r\n' + + 'Cc: friends@example.com, enemies@example.com\r\n' + + 'Subject: This is the subject\r\n' + + 'Message-ID: <...>\r\n' + + 'Content-Transfer-Encoding: 7bit\r\n' + + 'MIME-Version: 1.0\r\n' + + '\r\n' + + 'This is the body\n' + + 'of the message\n' + + 'From us.\r\n' + + '====== END MAIL #0 ======\n' + ); + onComplete(); + }); + }); +}); + Tinytest.add("email - fully customizable", function (test) { smokeEmailTest(function(stream) { Email.send({