From 6d7caa7e264c31b72a1c3d7732e901cd8207806a Mon Sep 17 00:00:00 2001 From: afrokick Date: Thu, 7 Jul 2022 23:03:22 +0300 Subject: [PATCH 01/99] modernize run-updater.js fix a little bug in stop --- .eslintignore | 1 - .../eslint-plugin-meteor/.eslintignore | 1 - tools/runners/run-all.js | 4 +- tools/runners/run-updater.js | 56 ++++++++----------- 4 files changed, 26 insertions(+), 36 deletions(-) diff --git a/.eslintignore b/.eslintignore index 5228e27ede..3653ab7c32 100644 --- a/.eslintignore +++ b/.eslintignore @@ -67,7 +67,6 @@ tools/runners/run-app.js tools/runners/run-mongo.js tools/runners/run-proxy.js tools/runners/run-selenium.js -tools/runners/run-updater.js tools/packaging/package-client.js tools/packaging/package-map.js diff --git a/npm-packages/eslint-plugin-meteor/.eslintignore b/npm-packages/eslint-plugin-meteor/.eslintignore index 5228e27ede..3653ab7c32 100644 --- a/npm-packages/eslint-plugin-meteor/.eslintignore +++ b/npm-packages/eslint-plugin-meteor/.eslintignore @@ -67,7 +67,6 @@ tools/runners/run-app.js tools/runners/run-mongo.js tools/runners/run-proxy.js tools/runners/run-selenium.js -tools/runners/run-updater.js tools/packaging/package-client.js tools/packaging/package-map.js diff --git a/tools/runners/run-all.js b/tools/runners/run-all.js index 805b81365c..04fa462eff 100644 --- a/tools/runners/run-all.js +++ b/tools/runners/run-all.js @@ -14,7 +14,7 @@ const Selenium = require('./run-selenium.js').Selenium; const AppRunner = require('./run-app.js').AppRunner; const MongoRunner = require('./run-mongo.js').MongoRunner; const HMRServer = require('./run-hmr').HMRServer; -const Updater = require('./run-updater.js').Updater; +const Updater = require('./run-updater').Updater; class Runner { constructor({ @@ -123,7 +123,7 @@ class Runner { hmrPath: HMRPath, secret: hmrSecret, projectContext: self.projectContext, - cordovaServerPort + cordovaServerPort }); } diff --git a/tools/runners/run-updater.js b/tools/runners/run-updater.js index dac475e9d0..c4a51be525 100644 --- a/tools/runners/run-updater.js +++ b/tools/runners/run-updater.js @@ -1,60 +1,52 @@ -var Console = require('../console/console.js').Console; +import { Console } from '../console/console'; -var Updater = function () { - var self = this; - self.timer = null; -}; +const CHECK_UPDATE_INTERVAL = 3 * 60 * 60 * 1000; // every 3 hours // XXX make it take a runLog? // XXX need to deal with updater writing messages (bypassing old // stdout interception.. maybe it should be global after all..) -Object.assign(Updater.prototype, { - start: function () { - var self = this; +export class Updater { + constructor() { + this.timer = null; + } - if (self.timer) { - throw new Error("already running?"); + start() { + if (this.timer) { + throw new Error('already running?'); } + const self = this; // Check every 3 hours. (Should not share buildmessage state with // the main fiber.) async function check() { self._check(); } - self.timer = setInterval(check, 3 * 60 * 60 * 1000); + this.timer = setInterval(check, CHECK_UPDATE_INTERVAL); // Also start a check now, but don't block on it. (This should // not share buildmessage state with the main fiber.) check(); - }, + } - _check: function () { - var self = this; - var updater = require('../packaging/updater.js'); + _check() { + const updater = require('../packaging/updater'); try { - updater.tryToDownloadUpdate({showBanner: true}); + updater.tryToDownloadUpdate({ showBanner: true }); } catch (e) { // oh well, this was the background. Only show errors if we are in debug // mode. - Console.debug("Error inside updater."); + Console.debug('Error inside updater.'); Console.debug(e.stack); - return; } - }, - - // Returns immediately. However if an update check is currently - // running it will complete in the background. Idempotent. - stop: function () { - var self = this; - - if (self.timer) { - return; - } - clearInterval(self.timer); - self.timer = null; } -}); + // Returns immediately. However, if an update check is currently + // running it will complete in the background. Idempotent. + stop() { + if (!this.timer) return; -exports.Updater = Updater; + clearInterval(this.timer); + this.timer = null; + } +} From 815bcd980e5bad5ec6ca3ae094d2027888fb54b5 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Wed, 27 Jul 2022 13:13:31 -0300 Subject: [PATCH 02/99] Create `Email.sendAsync` method without using Fibers. --- packages/email/email.js | 175 ++++++++++++++++++++++++++-------------- 1 file changed, 116 insertions(+), 59 deletions(-) diff --git a/packages/email/email.js b/packages/email/email.js index 3f64e23692..e0daf26384 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -25,7 +25,7 @@ export const EmailInternals = { const MailComposer = EmailInternals.NpmModules.mailcomposer.module; -const makeTransport = function(mailUrlString) { +const makeTransport = function (mailUrlString) { const mailUrl = new URL(mailUrlString); if (mailUrl.protocol !== 'smtp:' && mailUrl.protocol !== 'smtps:') { @@ -60,7 +60,7 @@ const makeTransport = function(mailUrlString) { }; // More info: https://nodemailer.com/smtp/well-known/ -const knownHostsTransport = function(settings = undefined, url = undefined) { +const knownHostsTransport = function (settings = undefined, url = undefined) { let service, user, password; const hasSettings = settings && Object.keys(settings).length; @@ -110,7 +110,7 @@ const knownHostsTransport = function(settings = undefined, url = undefined) { }; EmailTest.knowHostsTransport = knownHostsTransport; -const getTransport = function() { +const getTransport = function () { const packageSettings = Meteor.settings.packages?.email || {}; // We delay this check until the first call to Email.send, in case someone // set process.env.MAIL_URL in startup code. Then we store in a cache until @@ -141,37 +141,38 @@ let nextDevModeMailId = 0; let output_stream = process.stdout; // Testing hooks -EmailTest.overrideOutputStream = function(stream) { +EmailTest.overrideOutputStream = function (stream) { nextDevModeMailId = 0; output_stream = stream; }; -EmailTest.restoreOutputStream = function() { +EmailTest.restoreOutputStream = function () { output_stream = process.stdout; }; -const devModeSend = function(mail) { - let devModeMailId = nextDevModeMailId++; +const devModeSend = async function (mail) { + return new Promise((resolve, reject) => { + let devModeMailId = nextDevModeMailId++; - const stream = output_stream; + 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(); + // 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 }); + readStream.on('end', function () { + stream.write('====== END MAIL #' + devModeMailId + ' ======\n'); + resolve(); + }); + readStream.on('error', (err) => reject(err)); }); - future.wait(); }; -const smtpSend = function(transport, mail) { +const smtpSend = function (transport, mail) { transport._syncSendMail(mail); }; @@ -186,7 +187,7 @@ const sendHooks = new Hook(); * false to skip sending. * @returns {{ stop: function, callback: function }} */ -Email.hookSend = function(f) { +Email.hookSend = function (f) { return sendHooks.register(f); }; @@ -199,6 +200,89 @@ Email.hookSend = function(f) { */ Email.customTransport = undefined; +// TODO Rewrite summary. +/** + * @summary Send an email with asyncronous method. Capture Throws an `Error` on failure to contact mail server + * or if mail server returns an error. All fields should match + * [RFC5322](http://tools.ietf.org/html/rfc5322) specification. + * + * If the `MAIL_URL` environment variable is set, actually sends the email. + * Otherwise, prints the contents of the email to standard out. + * + * Note that this package is based on **nodemailer**, so make sure to refer to + * [the documentation](http://nodemailer.com/) + * when using the `attachments` or `mailComposer` options. + * + * @locus Server + * @return {Promise} + * @param {Object} options + * @param {String} [options.from] "From:" address (required) + * @param {String|String[]} options.to,cc,bcc,replyTo + * "To:", "Cc:", "Bcc:", and "Reply-To:" addresses + * @param {String} [options.inReplyTo] Message-ID this message is replying to + * @param {String|String[]} [options.references] Array (or space-separated string) of Message-IDs to refer to + * @param {String} [options.messageId] Message-ID for this message; otherwise, will be set to a random value + * @param {String} [options.subject] "Subject:" line + * @param {String} [options.text|html] Mail body (in plain text and/or HTML) + * @param {String} [options.watchHtml] Mail body in HTML specific for Apple Watch + * @param {String} [options.icalEvent] iCalendar event attachment + * @param {Object} [options.headers] Dictionary of custom headers - e.g. `{ "header name": "header value" }`. To set an object under a header name, use `JSON.stringify` - e.g. `{ "header name": JSON.stringify({ tracking: { level: 'full' } }) }`. + * @param {Object[]} [options.attachments] Array of attachment objects, as + * described in the [nodemailer documentation](https://nodemailer.com/message/attachments/). + * @param {MailComposer} [options.mailComposer] A [MailComposer](https://nodemailer.com/extras/mailcomposer/#e-mail-message-fields) + * object representing the message to be sent. Overrides all other options. + * You can create a `MailComposer` object via + * `new EmailInternals.NpmModules.mailcomposer.module`. + */ +Email.sendAsync = async function (options) { + return new Promise((resolve, reject) => { + if (options.mailComposer) { + options = options.mailComposer.mail; + } + + let send = true; + sendHooks.forEach((hook) => { + send = hook(options); + return send; + }); + if (!send) { + resolve(); + return; + } + + if (Email.customTransport) { + const packageSettings = Meteor.settings.packages?.email || {}; + Email.customTransport({ packageSettings, ...options }); + resolve(); + return; + } + + const mailUrlEnv = process.env.MAIL_URL; + const mailUrlSettings = Meteor.settings.packages?.email; + + if (Meteor.isProduction && !mailUrlEnv && !mailUrlSettings) { + // This check is mostly necessary when using the flag --production when running locally. + // And it works as a reminder to properly set the mail URL when running locally. + reject( + new Error( + 'You have not provided a mail URL. You can provide it by using the environment variable MAIL_URL or your settings. You can read more about it here: https://docs.meteor.com/api/email.html.' + ) + ); + return; + } + + if (mailUrlEnv || mailUrlSettings) { + const transport = getTransport(); + smtpSend(transport, options); + resolve(); + return; + } + devModeSend(options) + .then(() => resolve()) + .catch((err) => reject(err)); + }); +}; + /** * @summary Send an email. Throws an `Error` on failure to contact mail server * or if mail server returns an error. All fields should match @@ -227,44 +311,17 @@ Email.customTransport = undefined; * @param {Object[]} [options.attachments] Array of attachment objects, as * described in the [nodemailer documentation](https://nodemailer.com/message/attachments/). * @param {MailComposer} [options.mailComposer] A [MailComposer](https://nodemailer.com/extras/mailcomposer/#e-mail-message-fields) + * @deprecated in 2.8 * object representing the message to be sent. Overrides all other options. * You can create a `MailComposer` object via * `new EmailInternals.NpmModules.mailcomposer.module`. */ -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; - } - - const mailUrlEnv = process.env.MAIL_URL; - const mailUrlSettings = Meteor.settings.packages?.email; - - if (Meteor.isProduction && !mailUrlEnv && !mailUrlSettings) { - // This check is mostly necessary when using the flag --production when running locally. - // And it works as a reminder to properly set the mail URL when running locally. - throw new Error( - 'You have not provided a mail URL. You can provide it by using the environment variable MAIL_URL or your settings. You can read more about it here: https://docs.meteor.com/api/email.html.' - ); - } - - if (mailUrlEnv || mailUrlSettings) { - const transport = getTransport(); - smtpSend(transport, options); - return; - } - devModeSend(options); +Email.send = async function (options) { + const future = new Future(); + Email.sendAsync(options) + .then(() => future.return()) + .catch((err) => { + throw err; + }); + future.wait(); }; From 631b74ff50a8de209baf9333ccaae5a59409cd57 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Wed, 27 Jul 2022 15:04:55 -0300 Subject: [PATCH 03/99] 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({ From 537cadf7b4cb2c42c189d2ca194155950ca233cb Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Wed, 27 Jul 2022 16:09:12 -0300 Subject: [PATCH 04/99] Remove Promise on Email.sendAsync --- packages/email/email.js | 72 ++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/packages/email/email.js b/packages/email/email.js index 0240b186b5..d0fe7788f1 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -238,51 +238,43 @@ Email.customTransport = undefined; */ Email.sendAsync = async function (options) { const stream = output_stream; - return new Promise((resolve, reject) => { - if (options.mailComposer) { - options = options.mailComposer.mail; - } + if (options.mailComposer) { + options = options.mailComposer.mail; + } - let send = true; - sendHooks.forEach((hook) => { - send = hook(options); - return send; - }); - if (!send) { - resolve(); - return; - } + let send = true; + sendHooks.forEach((hook) => { + send = hook(options); + return send; + }); + if (!send) { + return; + } - if (Email.customTransport) { - const packageSettings = Meteor.settings.packages?.email || {}; - Email.customTransport({ packageSettings, ...options }); - resolve(); - return; - } + if (Email.customTransport) { + const packageSettings = Meteor.settings.packages?.email || {}; + Email.customTransport({ packageSettings, ...options }); + return; + } - const mailUrlEnv = process.env.MAIL_URL; - const mailUrlSettings = Meteor.settings.packages?.email; + const mailUrlEnv = process.env.MAIL_URL; + const mailUrlSettings = Meteor.settings.packages?.email; - if (Meteor.isProduction && !mailUrlEnv && !mailUrlSettings) { - // This check is mostly necessary when using the flag --production when running locally. - // And it works as a reminder to properly set the mail URL when running locally. - reject( - new Error( - 'You have not provided a mail URL. You can provide it by using the environment variable MAIL_URL or your settings. You can read more about it here: https://docs.meteor.com/api/email.html.' - ) - ); - return; - } + if (Meteor.isProduction && !mailUrlEnv && !mailUrlSettings) { + // This check is mostly necessary when using the flag --production when running locally. + // And it works as a reminder to properly set the mail URL when running locally. + new Error( + 'You have not provided a mail URL. You can provide it by using the environment variable MAIL_URL or your settings. You can read more about it here: https://docs.meteor.com/api/email.html.' + ); + } - if (mailUrlEnv || mailUrlSettings) { - const transport = getTransport(); - smtpSend(transport, options); - resolve(); - return; - } - devModeSendAsync(options, stream) - .then((currentStream) => resolve(currentStream)) - .catch((err) => reject(err)); + if (mailUrlEnv || mailUrlSettings) { + const transport = getTransport(); + smtpSend(transport, options); + return; + } + return devModeSendAsync(options, stream).catch((err) => { + throw err; }); }; From 2a3b3ef9da88fd45fee27b08b0323c9f3e5f18c0 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Wed, 27 Jul 2022 18:03:46 -0300 Subject: [PATCH 05/99] Create async method `CssTools.minifyCssAsync` to replace method `CssTools.minifyCss` in future --- packages/minifier-css/minifier-async-tests.js | 51 +++++++++++++++++++ packages/minifier-css/minifier.js | 41 ++++++++++----- packages/minifier-css/package.js | 1 + .../plugin/minify-css.js | 2 +- 4 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 packages/minifier-css/minifier-async-tests.js diff --git a/packages/minifier-css/minifier-async-tests.js b/packages/minifier-css/minifier-async-tests.js new file mode 100644 index 0000000000..232ca247a2 --- /dev/null +++ b/packages/minifier-css/minifier-async-tests.js @@ -0,0 +1,51 @@ +import { CssTools } from './minifier'; +const TEST_CASES = [ + ['a \t\n{ color: red } \n', 'a{color:red}', 'whitespace check'], + [ + 'a \t\n{ color: red; margin: 1; } \n', + 'a{color:red;margin:1}', + 'only last one loses semicolon', + ], + [ + 'a \t\n{ color: red;;; margin: 1;;; } \n', + 'a{color:red;margin:1}', + 'more semicolons than needed', + ], + ['a , p \t\n{ color: red; } \n', 'a,p{color:red}', 'multiple selectors'], + ['body {}', '', 'removing empty rules'], + [ + '*.my-class { color: #fff; }', + '.my-class{color:#fff}', + 'removing universal selector', + ], + [ + 'p > *.my-class { color: #fff; }', + 'p>.my-class{color:#fff}', + 'removing optional whitespace around ">" in selector', + ], + [ + 'p + *.my-class { color: #fff; }', + 'p+.my-class{color:#fff}', + 'removing optional whitespace around "+" in selector', + ], + [ + 'a {\n\ + font:12px \'Helvetica\',"Arial",\'Nautica\';\n\ + background:url("/some/nice/picture.png");\n}', + 'a{font:12px Helvetica,Arial,Nautica;background:url(/some/nice/picture.png)}', + 'removing quotes in font and url (if possible)', + ], + ['/* no comments */ a { color: red; }', 'a{color:red}', 'remove comments'], +]; + +Tinytest.addAsync( + '[Async] minifier-css - simple CSS minification', + (test, onComplete) => { + const promises = TEST_CASES.map(([css, expected, desc]) => + CssTools.minifyCssAsync(css).then((minifiedCss) => { + test.equal(minifiedCss[0], expected, desc); + }) + ); + Promise.all(promises).then(() => onComplete()); + } +); diff --git a/packages/minifier-css/minifier.js b/packages/minifier-css/minifier.js index 174452f1ee..2fa610519a 100644 --- a/packages/minifier-css/minifier.js +++ b/packages/minifier-css/minifier.js @@ -63,25 +63,39 @@ const CssTools = { * * @param {string} cssText CSS string to minify. * @return {String[]} Array containing the minified CSS. + * @deprecated on 2.8 */ minifyCss(cssText) { - const f = new Future; - postcss([ - cssnano({ safe: true }), - ]).process(cssText, { - from: void 0, - }).then(result => { - f.return(result.css); - }).catch(error => { - f.throw(error); - }); - const minifiedCss = f.wait(); - + const f = new Future(); + CssTools.minifyCssAsync(cssText) + .then((res) => f.return(res)) + .catch((error) => f.throw(error)); // Since this function has always returned an array, we'll wrap the // minified css string in an array before returning, even though we're // only ever returning one minified css string in that array (maintaining // backwards compatibility). - return [minifiedCss]; + return f.wait(); + }, + + /** + * Minify the passed in CSS string. + * + * @param {string} cssText CSS string to minify. + * @return {Promise} Array containing the minified CSS. + */ + async minifyCssAsync(cssText) { + return new Promise((resolve, reject) => { + postcss([cssnano({ safe: true })]) + .process(cssText, { + from: void 0, + }) + .then((result) => { + resolve([result.css]); + }) + .catch((error) => { + reject(error); + }); + }); }, /** @@ -187,6 +201,7 @@ if (typeof Profile !== 'undefined') { 'parseCss', 'stringifyCss', 'minifyCss', + 'minifyCssAsync', 'mergeCssAsts', 'rewriteCssUrls', ].forEach(funcName => { diff --git a/packages/minifier-css/package.js b/packages/minifier-css/package.js index 3447c9e4bf..ffdaf5d5d6 100644 --- a/packages/minifier-css/package.js +++ b/packages/minifier-css/package.js @@ -19,6 +19,7 @@ Package.onTest(function (api) { api.use('tinytest'); api.addFiles([ 'minifier-tests.js', + 'minifier-async-tests.js', 'urlrewriting-tests.js' ], 'server'); }); diff --git a/packages/standard-minifier-css/plugin/minify-css.js b/packages/standard-minifier-css/plugin/minify-css.js index 2b8c4d5e44..8ac2b0db75 100644 --- a/packages/standard-minifier-css/plugin/minify-css.js +++ b/packages/standard-minifier-css/plugin/minify-css.js @@ -60,7 +60,7 @@ class CssToolsMinifier { path: 'merged-stylesheets.css' }]; } else { - const minifiedFiles = CssTools.minifyCss(merged.code); + const minifiedFiles = await CssTools.minifyCssAsync(merged.code); result = minifiedFiles.map(minified => ({ data: minified From 470ae492b0ad7570c626cd3950ba03bfe0b2c79d Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Wed, 27 Jul 2022 18:17:28 -0300 Subject: [PATCH 06/99] Remove unnecessary promise --- packages/minifier-css/minifier.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/minifier-css/minifier.js b/packages/minifier-css/minifier.js index 2fa610519a..d22aa02705 100644 --- a/packages/minifier-css/minifier.js +++ b/packages/minifier-css/minifier.js @@ -84,18 +84,14 @@ const CssTools = { * @return {Promise} Array containing the minified CSS. */ async minifyCssAsync(cssText) { - return new Promise((resolve, reject) => { - postcss([cssnano({ safe: true })]) - .process(cssText, { - from: void 0, - }) - .then((result) => { - resolve([result.css]); - }) - .catch((error) => { - reject(error); - }); - }); + return postcss([cssnano({ safe: true })]) + .process(cssText, { + from: void 0, + }) + .then((result) => [result.css]) + .catch((error) => { + throw new Error(error); + }); }, /** From bcba3ebe47cf732b376f91377c6045132e982735 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Wed, 27 Jul 2022 18:24:35 -0300 Subject: [PATCH 07/99] Preserve original formatting and method order. --- packages/email/email.js | 222 +++++++++++++++++++++------------------- 1 file changed, 114 insertions(+), 108 deletions(-) diff --git a/packages/email/email.js b/packages/email/email.js index d0fe7788f1..e877226f79 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -25,7 +25,7 @@ export const EmailInternals = { const MailComposer = EmailInternals.NpmModules.mailcomposer.module; -const makeTransport = function (mailUrlString) { +const makeTransport = function(mailUrlString) { const mailUrl = new URL(mailUrlString); if (mailUrl.protocol !== 'smtp:' && mailUrl.protocol !== 'smtps:') { @@ -60,7 +60,7 @@ const makeTransport = function (mailUrlString) { }; // More info: https://nodemailer.com/smtp/well-known/ -const knownHostsTransport = function (settings = undefined, url = undefined) { +const knownHostsTransport = function(settings = undefined, url = undefined) { let service, user, password; const hasSettings = settings && Object.keys(settings).length; @@ -110,7 +110,7 @@ const knownHostsTransport = function (settings = undefined, url = undefined) { }; EmailTest.knowHostsTransport = knownHostsTransport; -const getTransport = function () { +const getTransport = function() { const packageSettings = Meteor.settings.packages?.email || {}; // We delay this check until the first call to Email.send, in case someone // set process.env.MAIL_URL in startup code. Then we store in a cache until @@ -145,36 +145,37 @@ EmailTest._getAndIncNextDevModeMailId = function () { }; // Testing hooks -EmailTest.overrideOutputStream = function (stream) { +EmailTest.overrideOutputStream = function(stream) { nextDevModeMailId = 0; output_stream = stream; }; -EmailTest.restoreOutputStream = function () { +EmailTest.restoreOutputStream = function() { output_stream = process.stdout; }; -const devModeSendAsync = async function (mail, stream) { - return new Promise((resolve, reject) => { - let devModeMailId = EmailTest._getAndIncNextDevModeMailId(); +const devModeSend = function(mail) { + let devModeMailId = nextDevModeMailId++; - // 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 }); - readStream.on('end', function () { - stream.write('====== END MAIL #' + devModeMailId + ' ======\n'); - resolve(stream); - }); - readStream.on('error', (err) => reject(err)); + 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(); }; -const smtpSend = function (transport, mail) { +const smtpSend = function(transport, mail) { transport._syncSendMail(mail); }; @@ -189,7 +190,7 @@ const sendHooks = new Hook(); * false to skip sending. * @returns {{ stop: function, callback: function }} */ -Email.hookSend = function (f) { +Email.hookSend = function(f) { return sendHooks.register(f); }; @@ -202,6 +203,96 @@ Email.hookSend = function (f) { */ Email.customTransport = undefined; +/** + * @summary Send an email. Throws an `Error` on failure to contact mail server + * or if mail server returns an error. All fields should match + * [RFC5322](http://tools.ietf.org/html/rfc5322) specification. + * + * If the `MAIL_URL` environment variable is set, actually sends the email. + * Otherwise, prints the contents of the email to standard out. + * + * Note that this package is based on **nodemailer**, so make sure to refer to + * [the documentation](http://nodemailer.com/) + * when using the `attachments` or `mailComposer` options. + * + * @locus Server + * @param {Object} options + * @param {String} [options.from] "From:" address (required) + * @param {String|String[]} options.to,cc,bcc,replyTo + * "To:", "Cc:", "Bcc:", and "Reply-To:" addresses + * @param {String} [options.inReplyTo] Message-ID this message is replying to + * @param {String|String[]} [options.references] Array (or space-separated string) of Message-IDs to refer to + * @param {String} [options.messageId] Message-ID for this message; otherwise, will be set to a random value + * @param {String} [options.subject] "Subject:" line + * @param {String} [options.text|html] Mail body (in plain text and/or HTML) + * @param {String} [options.watchHtml] Mail body in HTML specific for Apple Watch + * @param {String} [options.icalEvent] iCalendar event attachment + * @param {Object} [options.headers] Dictionary of custom headers - e.g. `{ "header name": "header value" }`. To set an object under a header name, use `JSON.stringify` - e.g. `{ "header name": JSON.stringify({ tracking: { level: 'full' } }) }`. + * @param {Object[]} [options.attachments] Array of attachment objects, as + * described in the [nodemailer documentation](https://nodemailer.com/message/attachments/). + * @param {MailComposer} [options.mailComposer] A [MailComposer](https://nodemailer.com/extras/mailcomposer/#e-mail-message-fields) + * @deprecated in 2.8 + * object representing the message to be sent. Overrides all other options. + * You can create a `MailComposer` object via + * `new EmailInternals.NpmModules.mailcomposer.module`. + */ +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; + } + + const mailUrlEnv = process.env.MAIL_URL; + const mailUrlSettings = Meteor.settings.packages?.email; + + if (Meteor.isProduction && !mailUrlEnv && !mailUrlSettings) { + // This check is mostly necessary when using the flag --production when running locally. + // And it works as a reminder to properly set the mail URL when running locally. + throw new Error( + 'You have not provided a mail URL. You can provide it by using the environment variable MAIL_URL or your settings. You can read more about it here: https://docs.meteor.com/api/email.html.' + ); + } + + if (mailUrlEnv || mailUrlSettings) { + const transport = getTransport(); + smtpSend(transport, options); + return; + } + devModeSend(options); +}; + +const devModeSendAsync = async function (mail, stream) { + return new Promise((resolve, reject) => { + let devModeMailId = EmailTest._getAndIncNextDevModeMailId(); + + // 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 }); + readStream.on('end', function () { + stream.write('====== END MAIL #' + devModeMailId + ' ======\n'); + resolve(stream); + }); + readStream.on('error', (err) => reject(err)); + }); +}; // TODO Rewrite summary. /** * @summary Send an email with asyncronous method. Capture Throws an `Error` on failure to contact mail server @@ -277,88 +368,3 @@ Email.sendAsync = async function (options) { throw 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 - * [RFC5322](http://tools.ietf.org/html/rfc5322) specification. - * - * If the `MAIL_URL` environment variable is set, actually sends the email. - * Otherwise, prints the contents of the email to standard out. - * - * Note that this package is based on **nodemailer**, so make sure to refer to - * [the documentation](http://nodemailer.com/) - * when using the `attachments` or `mailComposer` options. - * - * @locus Server - * @param {Object} options - * @param {String} [options.from] "From:" address (required) - * @param {String|String[]} options.to,cc,bcc,replyTo - * "To:", "Cc:", "Bcc:", and "Reply-To:" addresses - * @param {String} [options.inReplyTo] Message-ID this message is replying to - * @param {String|String[]} [options.references] Array (or space-separated string) of Message-IDs to refer to - * @param {String} [options.messageId] Message-ID for this message; otherwise, will be set to a random value - * @param {String} [options.subject] "Subject:" line - * @param {String} [options.text|html] Mail body (in plain text and/or HTML) - * @param {String} [options.watchHtml] Mail body in HTML specific for Apple Watch - * @param {String} [options.icalEvent] iCalendar event attachment - * @param {Object} [options.headers] Dictionary of custom headers - e.g. `{ "header name": "header value" }`. To set an object under a header name, use `JSON.stringify` - e.g. `{ "header name": JSON.stringify({ tracking: { level: 'full' } }) }`. - * @param {Object[]} [options.attachments] Array of attachment objects, as - * described in the [nodemailer documentation](https://nodemailer.com/message/attachments/). - * @param {MailComposer} [options.mailComposer] A [MailComposer](https://nodemailer.com/extras/mailcomposer/#e-mail-message-fields) - * @deprecated in 2.8 - * object representing the message to be sent. Overrides all other options. - * You can create a `MailComposer` object via - * `new EmailInternals.NpmModules.mailcomposer.module`. - */ -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); -}; From 6bc5b0a628aae20a5ab803889c5cfc5127a8850e Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Wed, 27 Jul 2022 18:27:02 -0300 Subject: [PATCH 08/99] Preserve original formatting and method order. --- packages/email/email.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/email/email.js b/packages/email/email.js index e877226f79..af07a8766a 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -155,7 +155,7 @@ EmailTest.restoreOutputStream = function() { }; const devModeSend = function(mail) { - let devModeMailId = nextDevModeMailId++; + let devModeMailId = EmailTest._getAndIncNextDevModeMailId(); const stream = output_stream; From 866fa2093dda9695f276e7105c99b5437f134308 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 28 Jul 2022 11:57:53 -0300 Subject: [PATCH 09/99] Remove incorrect @deprecated annotation. The correct strategy is: 1. Only sync. (Current) 2. Both sync and async (with a suffix). 3. Only async (without a suffix). Thank you! @StorytellerCZ and @radekmie --- packages/email/email.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/email/email.js b/packages/email/email.js index af07a8766a..20a04aa72f 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -231,7 +231,6 @@ Email.customTransport = undefined; * @param {Object[]} [options.attachments] Array of attachment objects, as * described in the [nodemailer documentation](https://nodemailer.com/message/attachments/). * @param {MailComposer} [options.mailComposer] A [MailComposer](https://nodemailer.com/extras/mailcomposer/#e-mail-message-fields) - * @deprecated in 2.8 * object representing the message to be sent. Overrides all other options. * You can create a `MailComposer` object via * `new EmailInternals.NpmModules.mailcomposer.module`. From b78c0471b601011832e9d8d8c1849a40eece13c4 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 28 Jul 2022 17:57:43 -0300 Subject: [PATCH 10/99] Including all send synchronous tests to async tests email - multiple e-mails same stream: Broken in async test --- packages/email/email.js | 5 +- packages/email/email_test_helpers.js | 27 ++ packages/email/email_tests.js | 554 +++++++++++---------------- packages/email/email_tests_data.js | 254 ++++++++++++ 4 files changed, 500 insertions(+), 340 deletions(-) create mode 100644 packages/email/email_test_helpers.js create mode 100644 packages/email/email_tests_data.js diff --git a/packages/email/email.js b/packages/email/email.js index 20a04aa72f..68056e1000 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -328,6 +328,7 @@ const devModeSendAsync = async function (mail, stream) { */ Email.sendAsync = async function (options) { const stream = output_stream; + const { isTestMode } = Email; if (options.mailComposer) { options = options.mailComposer.mail; } @@ -338,13 +339,13 @@ Email.sendAsync = async function (options) { return send; }); if (!send) { - return; + return isTestMode && stream; } if (Email.customTransport) { const packageSettings = Meteor.settings.packages?.email || {}; Email.customTransport({ packageSettings, ...options }); - return; + return isTestMode && stream; } const mailUrlEnv = process.env.MAIL_URL; diff --git a/packages/email/email_test_helpers.js b/packages/email/email_test_helpers.js new file mode 100644 index 0000000000..c0c7d0c847 --- /dev/null +++ b/packages/email/email_test_helpers.js @@ -0,0 +1,27 @@ +import streamBuffers from 'stream-buffers'; + +export const devWarningBanner = + '(Mail not sent; to enable ' + + 'sending, set the MAIL_URL environment variable.)\n'; + +export const smokeEmailTest = (testFunction) => { + // This only tests dev mode, so don't run the test if this is deployed. + if (process.env.MAIL_URL) return; + + try { + const stream = new streamBuffers.WritableStreamBuffer(); + EmailTest.overrideOutputStream(stream); + + testFunction(stream); + } finally { + EmailTest.restoreOutputStream(); + } +}; + +export const canonicalize = (string) => { + // Remove generated content for test.equal to succeed. + return string + .replace(/Message-ID: <[^<>]*>\r\n/, 'Message-ID: <...>\r\n') + .replace(/Date: (?!dummy).*\r\n/, 'Date: ...\r\n') + .replace(/(boundary="|^--)--[^\s"]+?(-Part|")/gm, '$1--...$2'); +}; diff --git a/packages/email/email_tests.js b/packages/email/email_tests.js index bbba84205e..472d33131b 100644 --- a/packages/email/email_tests.js +++ b/packages/email/email_tests.js @@ -1,342 +1,78 @@ -import streamBuffers from 'stream-buffers'; +import { smokeEmailTest } from './email_test_helpers'; +import { TEST_CASES } from './email_tests_data'; -const devWarningBanner = "(Mail not sent; to enable " + - "sending, set the MAIL_URL environment variable.)\n"; +Email.isTestMode = true; -function smokeEmailTest(testFunction) { - // This only tests dev mode, so don't run the test if this is deployed. - if (process.env.MAIL_URL) return; +// Create dynamic sync tests +TEST_CASES.forEach(({ title, options, testCalls }) => { + Tinytest.add(`[Sync] ${title}`, function (test) { + smokeEmailTest((stream) => { + Object.entries(options).forEach(([key, option]) => { + const testCall = testCalls[key]; + Email.send(option); + testCall(test, stream); + }); + }); + }); +}); - try { - const stream = new streamBuffers.WritableStreamBuffer; - EmailTest.overrideOutputStream(stream); +// Create dynamic async tests +TEST_CASES.forEach(({ title, options, testCalls }) => { + Tinytest.addAsync(`[Async] ${title}`, function (test, onComplete) { + smokeEmailTest(() => { + const allPromises = Object.entries(options).map(([key, option]) => { + const testCall = testCalls[key]; + return Email.sendAsync(option).then((stream) => { + testCall(test, stream); + }); + }); + Promise.all(allPromises).then(() => onComplete()); + }); + }); +}); - testFunction(stream); +// Individual sync tests - } finally { - EmailTest.restoreOutputStream(); +Tinytest.add( + '[Sync] email - alternate API is used for sending gets data', + function (test) { + smokeEmailTest(function (stream) { + Email.customTransport = (options) => { + test.equal(options.from, 'foo@example.com'); + }; + Email.send({ + from: 'foo@example.com', + to: 'bar@example.com', + text: '*Cool*, man', + html: 'Cool, man', + }); + test.equal(stream.getContentsAsString('utf8'), false); + }); + + smokeEmailTest(function (stream) { + Meteor.settings.packages = { + email: { service: '1on1', user: 'test', password: 'pwd' }, + }; + Email.customTransport = (options) => { + test.equal(options.from, 'foo@example.com'); + test.equal(options.packageSettings?.service, '1on1'); + }; + + Email.send({ + from: 'foo@example.com', + to: 'bar@example.com', + text: '*Cool*, man', + html: 'Cool, man', + }); + + test.equal(stream.getContentsAsString('utf8'), false); + }); + Email.customTransport = undefined; + Meteor.settings.packages = undefined; } -} +); -function canonicalize(string) { - // Remove generated content for test.equal to succeed. - return string.replace(/Message-ID: <[^<>]*>\r\n/, "Message-ID: <...>\r\n") - .replace(/Date: (?!dummy).*\r\n/, "Date: ...\r\n") - .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({ - 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', - }, - }); - // XXX brittle if mailcomposer changes header order, etc - test.equal(canonicalize(stream.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"); - }); -}); - -Tinytest.add("email - undefined headers sends properly", function (test) { - smokeEmailTest(function (stream) { - Email.send({ - from: "foo@example.com", - to: "bar@example.com", - subject: "This is the subject", - text: "This is the body\nof the message\nFrom us.", - }); - - test.matches(canonicalize(stream.getContentsAsString("utf8")), - /^====== BEGIN MAIL #0 ======$[\s\S]+^To: bar@example.com$/m); - }); -}); - -Tinytest.add("email - multiple e-mails same stream", function (test) { - smokeEmailTest(function (stream) { - Email.send({ - from: "foo@example.com", - to: "bar@example.com", - subject: "This is the subject", - text: "This is the body\nof the message\nFrom us.", - }); - - const contents = canonicalize(stream.getContentsAsString("utf8")); - test.matches(contents, /^====== BEGIN MAIL #0 ======$/m); - test.matches(contents, /^From: foo@example.com$/m); - test.matches(contents, /^To: bar@example.com$/m); - - Email.send({ - from: "qux@example.com", - to: "baz@example.com", - subject: "This is important", - text: "This is another message\nFrom Qux.", - }); - - const contents2 = canonicalize(stream.getContentsAsString("utf8")); - test.matches(contents2, /^====== BEGIN MAIL #1 ======$/m); - test.matches(contents2, /^From: qux@example.com$/m); - test.matches(contents2, /^To: baz@example.com$/m); - - }); -}); - -Tinytest.add("email - using mail composer", function (test) { - smokeEmailTest(function (stream) { - // Test direct MailComposer usage. - const mc = new EmailInternals.NpmModules.mailcomposer.module({ - from: "a@b.com", - text: "body" - }); - Email.send({mailComposer: mc}); - test.equal(canonicalize(stream.getContentsAsString("utf8")), - "====== BEGIN MAIL #0 ======\n" + - devWarningBanner + - "Content-Type: text/plain; charset=utf-8\r\n" + - "From: a@b.com\r\n" + - "Message-ID: <...>\r\n" + - "Content-Transfer-Encoding: 7bit\r\n" + - "Date: ...\r\n" + - "MIME-Version: 1.0\r\n" + - "\r\n" + - "body\r\n" + - "====== END MAIL #0 ======\n"); - }); -}); - -Tinytest.add("email - date auto generated", function (test) { - smokeEmailTest(function (stream) { - // Test if date header is automatically generated, if not specified - Email.send({ - from: "foo@example.com", - to: "bar@example.com", - subject: "This is the subject", - text: "This is the body\nof the message\nFrom us.", - headers: { - 'X-Meteor-Test': 'a custom header', - }, - }); - - test.matches(canonicalize(stream.getContentsAsString("utf8")), - /^Date: .+$/m); - }); -}); - -Tinytest.add("email - long lines", function (test) { - smokeEmailTest(function (stream) { - // Test that long header lines get wrapped with single leading whitespace, - // and that long body lines get wrapped with quoted-printable conventions. - Email.send({ - from: "foo@example.com", - to: "bar@example.com", - subject: "This is a very very very very very very very very very very very very long subject", - text: "This is a very very very very very very very very very very very very long text", - }); - - test.equal(canonicalize(stream.getContentsAsString("utf8")), - "====== BEGIN MAIL #0 ======\n" + - devWarningBanner + - "Content-Type: text/plain; charset=utf-8\r\n" + - "From: foo@example.com\r\n" + - "To: bar@example.com\r\n" + - "Subject: This is a very very very very very very very very " + - "very very very\r\n very long subject\r\n" + - "Message-ID: <...>\r\n" + - "Content-Transfer-Encoding: quoted-printable\r\n" + - "Date: ...\r\n" + - "MIME-Version: 1.0\r\n" + - "\r\n" + - "This is a very very very very very very very very very very " + - "very very long =\r\ntext\r\n" + - "====== END MAIL #0 ======\n"); - }); -}); - -Tinytest.add("email - unicode", function (test) { - smokeEmailTest(function (stream) { - // Test that unicode characters in header and body get encoded. - Email.send({ - from: "foo@example.com", - to: "bar@example.com", - subject: "\u263a", - text: "I \u2665 Meteor", - }); - - test.equal(canonicalize(stream.getContentsAsString("utf8")), - "====== BEGIN MAIL #0 ======\n" + - devWarningBanner + - "Content-Type: text/plain; charset=utf-8\r\n" + - "From: foo@example.com\r\n" + - "To: bar@example.com\r\n" + - "Subject: =?UTF-8?B?4pi6?=\r\n" + - "Message-ID: <...>\r\n" + - "Content-Transfer-Encoding: quoted-printable\r\n" + - "Date: ...\r\n" + - "MIME-Version: 1.0\r\n" + - "\r\n" + - "I =E2=99=A5 Meteor\r\n" + - "====== END MAIL #0 ======\n"); - }); -}); - -Tinytest.add("email - text and html", function (test) { - smokeEmailTest(function (stream) { - // Test including both text and HTML versions of message. - Email.send({ - from: "foo@example.com", - to: "bar@example.com", - text: "*Cool*, man", - html: "Cool, man", - }); - - test.equal(canonicalize(stream.getContentsAsString("utf8")), - "====== BEGIN MAIL #0 ======\n" + - devWarningBanner + - "Content-Type: multipart/alternative;\r\n" + - ' boundary="--...-Part_1"\r\n' + - "From: foo@example.com\r\n" + - "To: bar@example.com\r\n" + - "Message-ID: <...>\r\n" + - "Date: ...\r\n" + - "MIME-Version: 1.0\r\n" + - "\r\n" + - "----...-Part_1\r\n" + - "Content-Type: text/plain; charset=utf-8\r\n" + - "Content-Transfer-Encoding: 7bit\r\n" + - "\r\n" + - "*Cool*, man\r\n" + - "----...-Part_1\r\n" + - "Content-Type: text/html; charset=utf-8\r\n" + - "Content-Transfer-Encoding: 7bit\r\n" + - "\r\n" + - "Cool, man\r\n" + - "----...-Part_1--\r\n" + - "====== END MAIL #0 ======\n"); - }); -}); - -Tinytest.add("email - alternate API is used for sending gets data", function(test) { - smokeEmailTest(function(stream) { - Email.customTransport = (options) => { - test.equal(options.from, 'foo@example.com'); - }; - Email.send({ - from: "foo@example.com", - to: "bar@example.com", - text: "*Cool*, man", - html: "Cool, man", - }); - test.equal(stream.getContentsAsString("utf8"), false); - }); - - smokeEmailTest(function(stream) { - Meteor.settings.packages = { email: { service: '1on1', user: 'test', password: 'pwd' } }; - Email.customTransport = (options) => { - test.equal(options.from, 'foo@example.com'); - test.equal(options.packageSettings?.service, '1on1'); - }; - - Email.send({ - from: "foo@example.com", - to: "bar@example.com", - text: "*Cool*, man", - html: "Cool, man", - }); - - test.equal(stream.getContentsAsString("utf8"), false); - }); - Email.customTransport = undefined; - Meteor.settings.packages = undefined; -}); - -Tinytest.add("email - URL string for known hosts", function(test) { - const oneTransport = EmailTest.knowHostsTransport({ service: '1und1', user: 'test', password: 'pwd' }); - test.equal(oneTransport.transporter.auth.type, 'LOGIN'); - test.equal(oneTransport.transporter.auth.user, 'test'); - - const aolUrlTransport = EmailTest.knowHostsTransport(null, 'AOL://test:pwd@aol.com'); - test.equal(aolUrlTransport.transporter.auth.user, 'test'); - test.equal(aolUrlTransport.transporter.auth.type, 'LOGIN'); - - const outlookTransport = EmailTest.knowHostsTransport(null, 'Outlook365://firstname.lastname%40hotmail.com:password@hotmail.com'); - const outlookTransport2 = EmailTest.knowHostsTransport(undefined, 'Outlook365://firstname.lastname@hotmail.com:password@hotmail.com'); - test.equal(outlookTransport.transporter.auth.user, 'firstname.lastname%40hotmail.com'); - test.equal(outlookTransport.options.auth.user, 'firstname.lastname%40hotmail.com'); - test.equal(outlookTransport.transporter.options.service, 'outlook365'); - test.equal(outlookTransport2.transporter.auth.user, 'firstname.lastname%40hotmail.com'); - test.equal(outlookTransport2.transporter.options.service, 'outlook365'); - - const hotmailTransport = EmailTest.knowHostsTransport(undefined, 'Hotmail://firstname.lastname@hotmail.com:password@hotmail.com'); - console.dir(hotmailTransport); - test.equal(hotmailTransport.transporter.options.service, 'hotmail'); - - const falseService = { service: '1on1', user: 'test', password: 'pwd' }; - const errorMsg = 'Could not recognize e-mail service. See list at https://nodemailer.com/smtp/well-known/ for services that we can configure for you.'; - test.throws(() => EmailTest.knowHostsTransport(falseService), errorMsg); - test.throws(() => EmailTest.knowHostsTransport(null, 'smtp://bbb:bb@bb.com'), errorMsg); -}); - -Tinytest.add("email - hooks stop the sending", function(test) { +Tinytest.add('[Sync] email - hooks stop the sending', function (test) { // Register hooks const hook1 = Email.hookSend((options) => { // Test that we get options through @@ -351,17 +87,159 @@ Tinytest.add("email - hooks stop the sending", function(test) { const hook3 = Email.hookSend(() => { console.log('FAIL'); }); - smokeEmailTest(function(stream) { + smokeEmailTest(function (stream) { Email.send({ - from: "foo@example.com", - to: "bar@example.com", - text: "*Cool*, man", - html: "Cool, man", + from: 'foo@example.com', + to: 'bar@example.com', + text: '*Cool*, man', + html: 'Cool, man', }); - test.equal(stream.getContentsAsString("utf8"), false); + test.equal(stream.getContentsAsString('utf8'), false); }); hook1.stop(); hook2.stop(); hook3.stop(); }); + +// Individual Async tests + +Tinytest.addAsync( + '[Async] email - alternate API is used for sending gets data', + function (test, onComplete) { + const allPromises = []; + smokeEmailTest(() => { + Email.customTransport = (options) => { + test.equal(options.from, 'foo@example.com'); + }; + allPromises.push( + Email.sendAsync({ + from: 'foo@example.com', + to: 'bar@example.com', + text: '*Cool*, man', + html: 'Cool, man', + }).then((stream) => { + test.equal(stream.getContentsAsString('utf8'), false); + }) + ); + }); + + smokeEmailTest(function () { + Meteor.settings.packages = { + email: { service: '1on1', user: 'test', password: 'pwd' }, + }; + Email.customTransport = (options) => { + test.equal(options.from, 'foo@example.com'); + test.equal(options.packageSettings?.service, '1on1'); + }; + + allPromises.push( + Email.sendAsync({ + from: 'foo@example.com', + to: 'bar@example.com', + text: '*Cool*, man', + html: 'Cool, man', + }).then((stream) => { + test.equal(stream.getContentsAsString('utf8'), false); + }) + ); + }); + Promise.all(allPromises).then(() => { + Email.customTransport = undefined; + Meteor.settings.packages = undefined; + onComplete(); + }); + } +); + +Tinytest.addAsync( + '[Async] email - hooks stop the sending', + function (test, onComplete) { + // Register hooks + const hook1 = Email.hookSend((options) => { + // Test that we get options through + test.equal(options.from, 'foo@example.com'); + console.log('EXECUTE'); + return true; + }); + const hook2 = Email.hookSend(() => { + console.log('STOP'); + return false; + }); + const hook3 = Email.hookSend(() => { + console.log('FAIL'); + }); + smokeEmailTest(() => { + Email.sendAsync({ + from: 'foo@example.com', + to: 'bar@example.com', + text: '*Cool*, man', + html: 'Cool, man', + }).then((stream) => { + test.equal(stream.getContentsAsString('utf8'), false); + hook1.stop(); + hook2.stop(); + hook3.stop(); + onComplete(); + }); + }); + } +); + +// Another tests + +Tinytest.add('[Sync] email - URL string for known hosts', function (test) { + const oneTransport = EmailTest.knowHostsTransport({ + service: '1und1', + user: 'test', + password: 'pwd', + }); + test.equal(oneTransport.transporter.auth.type, 'LOGIN'); + test.equal(oneTransport.transporter.auth.user, 'test'); + + const aolUrlTransport = EmailTest.knowHostsTransport( + null, + 'AOL://test:pwd@aol.com' + ); + test.equal(aolUrlTransport.transporter.auth.user, 'test'); + test.equal(aolUrlTransport.transporter.auth.type, 'LOGIN'); + + const outlookTransport = EmailTest.knowHostsTransport( + null, + 'Outlook365://firstname.lastname%40hotmail.com:password@hotmail.com' + ); + const outlookTransport2 = EmailTest.knowHostsTransport( + undefined, + 'Outlook365://firstname.lastname@hotmail.com:password@hotmail.com' + ); + test.equal( + outlookTransport.transporter.auth.user, + 'firstname.lastname%40hotmail.com' + ); + test.equal( + outlookTransport.options.auth.user, + 'firstname.lastname%40hotmail.com' + ); + test.equal(outlookTransport.transporter.options.service, 'outlook365'); + test.equal( + outlookTransport2.transporter.auth.user, + 'firstname.lastname%40hotmail.com' + ); + test.equal(outlookTransport2.transporter.options.service, 'outlook365'); + + const hotmailTransport = EmailTest.knowHostsTransport( + undefined, + 'Hotmail://firstname.lastname@hotmail.com:password@hotmail.com' + ); + console.dir(hotmailTransport); + test.equal(hotmailTransport.transporter.options.service, 'hotmail'); + + const falseService = { service: '1on1', user: 'test', password: 'pwd' }; + const errorMsg = + 'Could not recognize e-mail service. See list at https://nodemailer.com/smtp/well-known/ for services that we can configure for you.'; + test.throws(() => EmailTest.knowHostsTransport(falseService), errorMsg); + test.throws( + () => EmailTest.knowHostsTransport(null, 'smtp://bbb:bb@bb.com'), + errorMsg + ); +}); diff --git a/packages/email/email_tests_data.js b/packages/email/email_tests_data.js new file mode 100644 index 0000000000..095c1fb9d2 --- /dev/null +++ b/packages/email/email_tests_data.js @@ -0,0 +1,254 @@ +import { canonicalize, devWarningBanner } from './email_test_helpers'; + +export const TEST_CASES = [ + { + title: 'email - fully customizable', + options: { + 0: { + 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', + }, + }, + }, + testCalls: { + 0: (test, stream) => { + // XXX brittle if mailcomposer changes header order, etc + test.equal( + canonicalize(stream.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' + ); + }, + }, + }, + { + title: 'email - undefined headers sends properly', + options: { + 0: { + from: 'foo@example.com', + to: 'bar@example.com', + subject: 'This is the subject', + text: 'This is the body\nof the message\nFrom us.', + }, + }, + testCalls: { + 0: (test, stream) => { + test.matches( + canonicalize(stream.getContentsAsString('utf8')), + /^====== BEGIN MAIL #0 ======$[\s\S]+^To: bar@example.com$/m + ); + }, + }, + }, + { + title: 'email - multiple e-mails same stream', + options: { + 0: { + from: 'foo@example.com', + to: 'bar@example.com', + subject: 'This is the subject', + text: 'This is the body\nof the message\nFrom us.', + }, + 1: { + from: 'qux@example.com', + to: 'baz@example.com', + subject: 'This is important', + text: 'This is another message\nFrom Qux.', + }, + }, + + testCalls: { + 0: (test, stream) => { + const contents = canonicalize(stream.getContentsAsString('utf8')); + test.matches(contents, /^====== BEGIN MAIL #0 ======$/m); + test.matches(contents, /^From: foo@example.com$/m); + test.matches(contents, /^To: bar@example.com$/m); + }, + 1: (test, stream) => { + const contents2 = canonicalize(stream.getContentsAsString('utf8')); + test.matches(contents2, /^====== BEGIN MAIL #1 ======$/m); + test.matches(contents2, /^From: qux@example.com$/m); + test.matches(contents2, /^To: baz@example.com$/m); + }, + }, + }, + { + title: 'email - using mail composer', + options: { + 0: { + mailComposer: new EmailInternals.NpmModules.mailcomposer.module({ + from: 'a@b.com', + text: 'body', + }), + }, + }, + + testCalls: { + 0: (test, stream) => { + test.equal( + canonicalize(stream.getContentsAsString('utf8')), + '====== BEGIN MAIL #0 ======\n' + + devWarningBanner + + 'Content-Type: text/plain; charset=utf-8\r\n' + + 'From: a@b.com\r\n' + + 'Message-ID: <...>\r\n' + + 'Content-Transfer-Encoding: 7bit\r\n' + + 'Date: ...\r\n' + + 'MIME-Version: 1.0\r\n' + + '\r\n' + + 'body\r\n' + + '====== END MAIL #0 ======\n' + ); + }, + }, + }, + { + title: 'email - date auto generated', + options: { + 0: { + from: 'foo@example.com', + to: 'bar@example.com', + subject: 'This is the subject', + text: 'This is the body\nof the message\nFrom us.', + headers: { + 'X-Meteor-Test': 'a custom header', + }, + }, + }, + testCalls: { + 0: (test, stream) => { + test.matches( + canonicalize(stream.getContentsAsString('utf8')), + /^Date: .+$/m + ); + }, + }, + }, + { + title: 'email - long lines', + options: { + 0: { + from: 'foo@example.com', + to: 'bar@example.com', + subject: + 'This is a very very very very very very very very very very very very long subject', + text: 'This is a very very very very very very very very very very very very long text', + }, + }, + testCalls: { + 0: (test, stream) => { + test.equal( + canonicalize(stream.getContentsAsString('utf8')), + '====== BEGIN MAIL #0 ======\n' + + devWarningBanner + + 'Content-Type: text/plain; charset=utf-8\r\n' + + 'From: foo@example.com\r\n' + + 'To: bar@example.com\r\n' + + 'Subject: This is a very very very very very very very very ' + + 'very very very\r\n very long subject\r\n' + + 'Message-ID: <...>\r\n' + + 'Content-Transfer-Encoding: quoted-printable\r\n' + + 'Date: ...\r\n' + + 'MIME-Version: 1.0\r\n' + + '\r\n' + + 'This is a very very very very very very very very very very ' + + 'very very long =\r\ntext\r\n' + + '====== END MAIL #0 ======\n' + ); + }, + }, + }, + { + title: 'email - unicode', + options: { + 0: { + from: 'foo@example.com', + to: 'bar@example.com', + subject: '\u263a', + text: 'I \u2665 Meteor', + }, + }, + testCalls: { + 0: (test, stream) => { + test.equal( + canonicalize(stream.getContentsAsString('utf8')), + '====== BEGIN MAIL #0 ======\n' + + devWarningBanner + + 'Content-Type: text/plain; charset=utf-8\r\n' + + 'From: foo@example.com\r\n' + + 'To: bar@example.com\r\n' + + 'Subject: =?UTF-8?B?4pi6?=\r\n' + + 'Message-ID: <...>\r\n' + + 'Content-Transfer-Encoding: quoted-printable\r\n' + + 'Date: ...\r\n' + + 'MIME-Version: 1.0\r\n' + + '\r\n' + + 'I =E2=99=A5 Meteor\r\n' + + '====== END MAIL #0 ======\n' + ); + }, + }, + }, + { + title: 'email - text and html', + options: { + 0: { + from: 'foo@example.com', + to: 'bar@example.com', + text: '*Cool*, man', + html: 'Cool, man', + }, + }, + testCalls: { + 0: (test, stream) => { + test.equal( + canonicalize(stream.getContentsAsString('utf8')), + '====== BEGIN MAIL #0 ======\n' + + devWarningBanner + + 'Content-Type: multipart/alternative;\r\n' + + ' boundary="--...-Part_1"\r\n' + + 'From: foo@example.com\r\n' + + 'To: bar@example.com\r\n' + + 'Message-ID: <...>\r\n' + + 'Date: ...\r\n' + + 'MIME-Version: 1.0\r\n' + + '\r\n' + + '----...-Part_1\r\n' + + 'Content-Type: text/plain; charset=utf-8\r\n' + + 'Content-Transfer-Encoding: 7bit\r\n' + + '\r\n' + + '*Cool*, man\r\n' + + '----...-Part_1\r\n' + + 'Content-Type: text/html; charset=utf-8\r\n' + + 'Content-Transfer-Encoding: 7bit\r\n' + + '\r\n' + + 'Cool, man\r\n' + + '----...-Part_1--\r\n' + + '====== END MAIL #0 ======\n' + ); + }, + }, + }, +]; + From e76009507d8543b87b189e69840d7fd97be14de9 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 28 Jul 2022 18:50:53 -0300 Subject: [PATCH 11/99] Refactor email send methods and organize tests - Make Email.send using Email.sendAsync with Promisse.await() (Fibers) - Allow send method to receive a stream object and use in the devSendMode method for tests. - Remove usage of Fibers Future - Code formatting --- packages/email/email.js | 131 +++++++-------------------- packages/email/email_test_helpers.js | 12 +-- packages/email/email_tests.js | 27 +++--- 3 files changed, 54 insertions(+), 116 deletions(-) diff --git a/packages/email/email.js b/packages/email/email.js index 68056e1000..cbe588c2ae 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -2,7 +2,6 @@ import { Meteor } from 'meteor/meteor'; import { Log } from 'meteor/logging'; import { Hook } from 'meteor/callback-hook'; -import Future from 'fibers/future'; import url from 'url'; import nodemailer from 'nodemailer'; import wellKnow from 'nodemailer/lib/well-known'; @@ -25,7 +24,7 @@ export const EmailInternals = { const MailComposer = EmailInternals.NpmModules.mailcomposer.module; -const makeTransport = function(mailUrlString) { +const makeTransport = function (mailUrlString) { const mailUrl = new URL(mailUrlString); if (mailUrl.protocol !== 'smtp:' && mailUrl.protocol !== 'smtps:') { @@ -60,7 +59,7 @@ const makeTransport = function(mailUrlString) { }; // More info: https://nodemailer.com/smtp/well-known/ -const knownHostsTransport = function(settings = undefined, url = undefined) { +const knownHostsTransport = function (settings = undefined, url = undefined) { let service, user, password; const hasSettings = settings && Object.keys(settings).length; @@ -110,7 +109,7 @@ const knownHostsTransport = function(settings = undefined, url = undefined) { }; EmailTest.knowHostsTransport = knownHostsTransport; -const getTransport = function() { +const getTransport = function () { const packageSettings = Meteor.settings.packages?.email || {}; // We delay this check until the first call to Email.send, in case someone // set process.env.MAIL_URL in startup code. Then we store in a cache until @@ -138,44 +137,37 @@ const getTransport = function() { }; let nextDevModeMailId = 0; -let output_stream = process.stdout; EmailTest._getAndIncNextDevModeMailId = function () { return nextDevModeMailId++; }; // Testing hooks -EmailTest.overrideOutputStream = function(stream) { +EmailTest.resetNextDevModeMailId = function () { nextDevModeMailId = 0; - output_stream = stream; }; -EmailTest.restoreOutputStream = function() { - output_stream = process.stdout; -}; +const devModeSendAsync = async function (mail, stream) { + return new Promise((resolve, reject) => { + let devModeMailId = EmailTest._getAndIncNextDevModeMailId(); -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(); + // 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 }); + readStream.on('end', function () { + stream.write('====== END MAIL #' + devModeMailId + ' ======\n'); + resolve(); + }); + readStream.on('error', (err) => reject(err)); }); - future.wait(); }; -const smtpSend = function(transport, mail) { +const smtpSend = function (transport, mail) { transport._syncSendMail(mail); }; @@ -190,7 +182,7 @@ const sendHooks = new Hook(); * false to skip sending. * @returns {{ stop: function, callback: function }} */ -Email.hookSend = function(f) { +Email.hookSend = function (f) { return sendHooks.register(f); }; @@ -231,67 +223,16 @@ Email.customTransport = undefined; * @param {Object[]} [options.attachments] Array of attachment objects, as * described in the [nodemailer documentation](https://nodemailer.com/message/attachments/). * @param {MailComposer} [options.mailComposer] A [MailComposer](https://nodemailer.com/extras/mailcomposer/#e-mail-message-fields) + * @param {Object} [options.stream] Output stream to write email on development environment * object representing the message to be sent. Overrides all other options. * You can create a `MailComposer` object via * `new EmailInternals.NpmModules.mailcomposer.module`. */ -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; - } - - const mailUrlEnv = process.env.MAIL_URL; - const mailUrlSettings = Meteor.settings.packages?.email; - - if (Meteor.isProduction && !mailUrlEnv && !mailUrlSettings) { - // This check is mostly necessary when using the flag --production when running locally. - // And it works as a reminder to properly set the mail URL when running locally. - throw new Error( - 'You have not provided a mail URL. You can provide it by using the environment variable MAIL_URL or your settings. You can read more about it here: https://docs.meteor.com/api/email.html.' - ); - } - - if (mailUrlEnv || mailUrlSettings) { - const transport = getTransport(); - smtpSend(transport, options); - return; - } - devModeSend(options); +Email.send = function (options) { + // Using Fibers Promise.await + Promise.await(Email.sendAsync(options)); }; -const devModeSendAsync = async function (mail, stream) { - return new Promise((resolve, reject) => { - let devModeMailId = EmailTest._getAndIncNextDevModeMailId(); - - // 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 }); - readStream.on('end', function () { - stream.write('====== END MAIL #' + devModeMailId + ' ======\n'); - resolve(stream); - }); - readStream.on('error', (err) => reject(err)); - }); -}; // TODO Rewrite summary. /** * @summary Send an email with asyncronous method. Capture Throws an `Error` on failure to contact mail server @@ -322,30 +263,28 @@ const devModeSendAsync = async function (mail, stream) { * @param {Object[]} [options.attachments] Array of attachment objects, as * described in the [nodemailer documentation](https://nodemailer.com/message/attachments/). * @param {MailComposer} [options.mailComposer] A [MailComposer](https://nodemailer.com/extras/mailcomposer/#e-mail-message-fields) + * @param {Object} [options.stream] Output stream to write email on development environment * object representing the message to be sent. Overrides all other options. * You can create a `MailComposer` object via * `new EmailInternals.NpmModules.mailcomposer.module`. */ Email.sendAsync = async function (options) { - const stream = output_stream; - const { isTestMode } = Email; - if (options.mailComposer) { - options = options.mailComposer.mail; - } + const { stream = process.stdout, ...rest } = options; + const email = rest.mailComposer ? rest.mailComposer.mail : rest; let send = true; sendHooks.forEach((hook) => { - send = hook(options); + send = hook(email); return send; }); if (!send) { - return isTestMode && stream; + return; } if (Email.customTransport) { const packageSettings = Meteor.settings.packages?.email || {}; - Email.customTransport({ packageSettings, ...options }); - return isTestMode && stream; + Email.customTransport({ packageSettings, ...email }); + return; } const mailUrlEnv = process.env.MAIL_URL; @@ -361,10 +300,10 @@ Email.sendAsync = async function (options) { if (mailUrlEnv || mailUrlSettings) { const transport = getTransport(); - smtpSend(transport, options); + smtpSend(transport, email); return; } - return devModeSendAsync(options, stream).catch((err) => { + return devModeSendAsync(email, stream).catch((err) => { throw err; }); }; diff --git a/packages/email/email_test_helpers.js b/packages/email/email_test_helpers.js index c0c7d0c847..a8706ab1c9 100644 --- a/packages/email/email_test_helpers.js +++ b/packages/email/email_test_helpers.js @@ -7,15 +7,9 @@ export const devWarningBanner = export const smokeEmailTest = (testFunction) => { // This only tests dev mode, so don't run the test if this is deployed. if (process.env.MAIL_URL) return; - - try { - const stream = new streamBuffers.WritableStreamBuffer(); - EmailTest.overrideOutputStream(stream); - - testFunction(stream); - } finally { - EmailTest.restoreOutputStream(); - } + const stream = new streamBuffers.WritableStreamBuffer(); + EmailTest.resetNextDevModeMailId(); + testFunction(stream); }; export const canonicalize = (string) => { diff --git a/packages/email/email_tests.js b/packages/email/email_tests.js index 472d33131b..c01bd8f377 100644 --- a/packages/email/email_tests.js +++ b/packages/email/email_tests.js @@ -1,15 +1,14 @@ +import { Email } from 'meteor/email'; import { smokeEmailTest } from './email_test_helpers'; import { TEST_CASES } from './email_tests_data'; -Email.isTestMode = true; - // Create dynamic sync tests TEST_CASES.forEach(({ title, options, testCalls }) => { Tinytest.add(`[Sync] ${title}`, function (test) { smokeEmailTest((stream) => { Object.entries(options).forEach(([key, option]) => { const testCall = testCalls[key]; - Email.send(option); + Email.send({ ...option, stream }); testCall(test, stream); }); }); @@ -19,10 +18,10 @@ TEST_CASES.forEach(({ title, options, testCalls }) => { // Create dynamic async tests TEST_CASES.forEach(({ title, options, testCalls }) => { Tinytest.addAsync(`[Async] ${title}`, function (test, onComplete) { - smokeEmailTest(() => { + smokeEmailTest((stream) => { const allPromises = Object.entries(options).map(([key, option]) => { const testCall = testCalls[key]; - return Email.sendAsync(option).then((stream) => { + return Email.sendAsync({ ...option, stream }).then(() => { testCall(test, stream); }); }); @@ -45,6 +44,7 @@ Tinytest.add( to: 'bar@example.com', text: '*Cool*, man', html: 'Cool, man', + stream, }); test.equal(stream.getContentsAsString('utf8'), false); }); @@ -63,6 +63,7 @@ Tinytest.add( to: 'bar@example.com', text: '*Cool*, man', html: 'Cool, man', + stream, }); test.equal(stream.getContentsAsString('utf8'), false); @@ -93,6 +94,7 @@ Tinytest.add('[Sync] email - hooks stop the sending', function (test) { to: 'bar@example.com', text: '*Cool*, man', html: 'Cool, man', + stream, }); test.equal(stream.getContentsAsString('utf8'), false); @@ -108,7 +110,7 @@ Tinytest.addAsync( '[Async] email - alternate API is used for sending gets data', function (test, onComplete) { const allPromises = []; - smokeEmailTest(() => { + smokeEmailTest((stream) => { Email.customTransport = (options) => { test.equal(options.from, 'foo@example.com'); }; @@ -118,13 +120,14 @@ Tinytest.addAsync( to: 'bar@example.com', text: '*Cool*, man', html: 'Cool, man', - }).then((stream) => { + stream, + }).then(() => { test.equal(stream.getContentsAsString('utf8'), false); }) ); }); - smokeEmailTest(function () { + smokeEmailTest(function (stream) { Meteor.settings.packages = { email: { service: '1on1', user: 'test', password: 'pwd' }, }; @@ -139,7 +142,8 @@ Tinytest.addAsync( to: 'bar@example.com', text: '*Cool*, man', html: 'Cool, man', - }).then((stream) => { + stream, + }).then(() => { test.equal(stream.getContentsAsString('utf8'), false); }) ); @@ -169,13 +173,14 @@ Tinytest.addAsync( const hook3 = Email.hookSend(() => { console.log('FAIL'); }); - smokeEmailTest(() => { + smokeEmailTest((stream) => { Email.sendAsync({ from: 'foo@example.com', to: 'bar@example.com', text: '*Cool*, man', html: 'Cool, man', - }).then((stream) => { + stream, + }).then(() => { test.equal(stream.getContentsAsString('utf8'), false); hook1.stop(); hook2.stop(); From ff0da119b7096765c8d50e6b97f3a6b512440af4 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 28 Jul 2022 18:54:50 -0300 Subject: [PATCH 12/99] Include missed throw error. --- packages/email/email.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/email/email.js b/packages/email/email.js index cbe588c2ae..350109bb59 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -293,7 +293,7 @@ Email.sendAsync = async function (options) { if (Meteor.isProduction && !mailUrlEnv && !mailUrlSettings) { // This check is mostly necessary when using the flag --production when running locally. // And it works as a reminder to properly set the mail URL when running locally. - new Error( + throw new Error( 'You have not provided a mail URL. You can provide it by using the environment variable MAIL_URL or your settings. You can read more about it here: https://docs.meteor.com/api/email.html.' ); } From 6e73d192e8abe2dd863567e300e19b7e152e19d0 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 29 Jul 2022 09:17:44 -0300 Subject: [PATCH 13/99] Fix broken test. --- packages/email/email.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/email/email.js b/packages/email/email.js index 350109bb59..a7183a9731 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -152,16 +152,18 @@ const devModeSendAsync = async function (mail, stream) { let devModeMailId = EmailTest._getAndIncNextDevModeMailId(); // This approach does not prevent other writers to stdout from interleaving. - stream.write('====== BEGIN MAIL #' + devModeMailId + ' ======\n'); - stream.write( + const output = ['====== BEGIN MAIL #' + devModeMailId + ' ======\n']; + output.push( '(Mail not sent; to enable sending, set the MAIL_URL ' + - 'environment variable.)\n' + 'environment variable.)\n' ); const readStream = new MailComposer(mail).compile().createReadStream(); - readStream.pipe(stream, { end: false }); + readStream.on('data', buffer => { + output.push(buffer.toString()); + }); readStream.on('end', function () { - stream.write('====== END MAIL #' + devModeMailId + ' ======\n'); - resolve(); + output.push('====== END MAIL #' + devModeMailId + ' ======\n'); + stream.write(output.join(''), () => resolve()); }); readStream.on('error', (err) => reject(err)); }); From 996ab0b831bfff60e325e1895a7c35f1cc0e2f24 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 29 Jul 2022 09:25:30 -0300 Subject: [PATCH 14/99] Using `Promise.await` to resolve the async method. Suggested in Code Review --- packages/minifier-css/minifier.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/minifier-css/minifier.js b/packages/minifier-css/minifier.js index d22aa02705..77058554e2 100644 --- a/packages/minifier-css/minifier.js +++ b/packages/minifier-css/minifier.js @@ -1,6 +1,5 @@ import path from 'path'; import url from 'url'; -import Future from 'fibers/future'; import postcss from 'postcss'; import cssnano from 'cssnano'; @@ -66,15 +65,7 @@ const CssTools = { * @deprecated on 2.8 */ minifyCss(cssText) { - const f = new Future(); - CssTools.minifyCssAsync(cssText) - .then((res) => f.return(res)) - .catch((error) => f.throw(error)); - // Since this function has always returned an array, we'll wrap the - // minified css string in an array before returning, even though we're - // only ever returning one minified css string in that array (maintaining - // backwards compatibility). - return f.wait(); + return Promise.await(CssTools.minifyCssAsync(cssText)); }, /** From cbffccb0b9d1b14bb6c5ca0ac3f93919401b9449 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 29 Jul 2022 09:30:12 -0300 Subject: [PATCH 15/99] Remove incorrect @deprecated annotation. --- packages/minifier-css/minifier.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/minifier-css/minifier.js b/packages/minifier-css/minifier.js index 77058554e2..c1cb595e23 100644 --- a/packages/minifier-css/minifier.js +++ b/packages/minifier-css/minifier.js @@ -62,7 +62,6 @@ const CssTools = { * * @param {string} cssText CSS string to minify. * @return {String[]} Array containing the minified CSS. - * @deprecated on 2.8 */ minifyCss(cssText) { return Promise.await(CssTools.minifyCssAsync(cssText)); From 5ebf8ff3b2f63877ee665e46bfcf16a5db39a30c Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 29 Jul 2022 10:17:03 -0300 Subject: [PATCH 16/99] Use await and remove unnecessary catch. --- packages/minifier-css/minifier.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/minifier-css/minifier.js b/packages/minifier-css/minifier.js index c1cb595e23..a4c662e9e5 100644 --- a/packages/minifier-css/minifier.js +++ b/packages/minifier-css/minifier.js @@ -74,14 +74,11 @@ const CssTools = { * @return {Promise} Array containing the minified CSS. */ async minifyCssAsync(cssText) { - return postcss([cssnano({ safe: true })]) + return await postcss([cssnano({ safe: true })]) .process(cssText, { from: void 0, }) - .then((result) => [result.css]) - .catch((error) => { - throw new Error(error); - }); + .then((result) => [result.css]); }, /** From e7060c29f4a3fbaf91d0fe291700f1fb46e6e9e5 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 29 Jul 2022 10:27:06 -0300 Subject: [PATCH 17/99] Make method test async and return promise to handle rejection. --- packages/minifier-css/minifier-async-tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/minifier-css/minifier-async-tests.js b/packages/minifier-css/minifier-async-tests.js index 232ca247a2..755595ba6e 100644 --- a/packages/minifier-css/minifier-async-tests.js +++ b/packages/minifier-css/minifier-async-tests.js @@ -40,12 +40,12 @@ const TEST_CASES = [ Tinytest.addAsync( '[Async] minifier-css - simple CSS minification', - (test, onComplete) => { + async (test) => { const promises = TEST_CASES.map(([css, expected, desc]) => CssTools.minifyCssAsync(css).then((minifiedCss) => { test.equal(minifiedCss[0], expected, desc); }) ); - Promise.all(promises).then(() => onComplete()); + return Promise.all(promises); } ); From b1ec9419bf43029c4c4a22e6e135730271207e66 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Fri, 5 Aug 2022 14:59:13 -0300 Subject: [PATCH 18/99] feat(error message): Especified error messages --- packages/modules-runtime-hot/installer.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/modules-runtime-hot/installer.js b/packages/modules-runtime-hot/installer.js index 3c7c7df494..b2197fb8ea 100644 --- a/packages/modules-runtime-hot/installer.js +++ b/packages/modules-runtime-hot/installer.js @@ -204,6 +204,17 @@ makeInstaller = function (options) { } function makeMissingError(id) { + var path = String(id) + .split('/'); + var importsFromServer = path.some(function (id) { + return id.indexOf('server') !== -1; + }); + var importsFromClient = path.some(function (id) { + return id.indexOf('client') !== -1; + }); + if (importsFromServer || importsFromClient) { + return new Error('Cannot import module ' + id + ' \n (cross-boundary import) \n see: https://guide.meteor.com/structure.html#special-directories'); + } return new Error("Cannot find module '" + id + "'"); } From 3e22eb0a1c5d2725460ec85f72aca452007fafbb Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Fri, 5 Aug 2022 16:49:23 -0300 Subject: [PATCH 19/99] Fix(installer): moved to ES6 syntax --- packages/modules-runtime-hot/installer.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/modules-runtime-hot/installer.js b/packages/modules-runtime-hot/installer.js index b2197fb8ea..93745f749d 100644 --- a/packages/modules-runtime-hot/installer.js +++ b/packages/modules-runtime-hot/installer.js @@ -204,17 +204,16 @@ makeInstaller = function (options) { } function makeMissingError(id) { - var path = String(id) + const path = String(id) .split('/'); - var importsFromServer = path.some(function (id) { - return id.indexOf('server') !== -1; - }); - var importsFromClient = path.some(function (id) { - return id.indexOf('client') !== -1; - }); - if (importsFromServer || importsFromClient) { + + const importsFrom = (location) => + path.some((id) => id.indexOf(location) !== -1); + + if (importsFrom('server') || importsFrom('client')) { return new Error('Cannot import module ' + id + ' \n (cross-boundary import) \n see: https://guide.meteor.com/structure.html#special-directories'); } + return new Error("Cannot find module '" + id + "'"); } From 1927d2bffdeb1308c05286d1172935e6b2047fb8 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Fri, 5 Aug 2022 16:59:08 -0300 Subject: [PATCH 20/99] Fix(installer): renamed variables for better understanding --- packages/modules-runtime-hot/installer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules-runtime-hot/installer.js b/packages/modules-runtime-hot/installer.js index 93745f749d..3ef9f9157c 100644 --- a/packages/modules-runtime-hot/installer.js +++ b/packages/modules-runtime-hot/installer.js @@ -208,7 +208,7 @@ makeInstaller = function (options) { .split('/'); const importsFrom = (location) => - path.some((id) => id.indexOf(location) !== -1); + path.some((subPath) => subPath === location); if (importsFrom('server') || importsFrom('client')) { return new Error('Cannot import module ' + id + ' \n (cross-boundary import) \n see: https://guide.meteor.com/structure.html#special-directories'); From 932ab0b40adecb0a5ba2b694b3d72c56f74f6f8f Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Mon, 15 Aug 2022 09:22:30 -0300 Subject: [PATCH 21/99] fix(err-messages): especified more the err message --- packages/modules-runtime-hot/installer.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/modules-runtime-hot/installer.js b/packages/modules-runtime-hot/installer.js index 3ef9f9157c..fff539fec5 100644 --- a/packages/modules-runtime-hot/installer.js +++ b/packages/modules-runtime-hot/installer.js @@ -210,8 +210,18 @@ makeInstaller = function (options) { const importsFrom = (location) => path.some((subPath) => subPath === location); - if (importsFrom('server') || importsFrom('client')) { - return new Error('Cannot import module ' + id + ' \n (cross-boundary import) \n see: https://guide.meteor.com/structure.html#special-directories'); + if (importsFrom('client')) { + return new Error( + `Unable to import on the client a module from a server directory: ${id} + (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories` + ); + } + + if (importsFrom('server')) { + return new Error( + `Unable to import on the server a module from a client directory: ${id} + (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories` + ); } return new Error("Cannot find module '" + id + "'"); From 995ef9677e4aa4dc1ec6883a2e664f21bcaf2402 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Mon, 15 Aug 2022 09:22:51 -0300 Subject: [PATCH 22/99] wip(tests): started adding tests --- packages/modules-runtime/modules-runtime-tests.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/modules-runtime/modules-runtime-tests.js b/packages/modules-runtime/modules-runtime-tests.js index cebc27a5c7..a46f51b9d7 100644 --- a/packages/modules-runtime/modules-runtime-tests.js +++ b/packages/modules-runtime/modules-runtime-tests.js @@ -3,3 +3,9 @@ Tinytest.add('modules', function (test) { var require = meteorInstall(); test.equal(typeof require, "function"); }); + +Tinytest.add('modules - error', function (test) { + + const require = meteorInstall('non_existent_module'); + test.throws(require(), /Cannot find package "meteor". Try "meteor add meteor"./); +}); From 6c72eea4066f31656214ccc45515e0ce0a97dd8a Mon Sep 17 00:00:00 2001 From: Christopher Heschong Date: Wed, 17 Aug 2022 16:41:17 -0400 Subject: [PATCH 23/99] Add https proxy support --- npm-packages/meteor-installer/README.md | 5 +++++ npm-packages/meteor-installer/install.js | 13 +++++++++++++ npm-packages/meteor-installer/package.json | 1 + 3 files changed, 19 insertions(+) diff --git a/npm-packages/meteor-installer/README.md b/npm-packages/meteor-installer/README.md index 9ab8a2de6a..2d74de733a 100644 --- a/npm-packages/meteor-installer/README.md +++ b/npm-packages/meteor-installer/README.md @@ -52,3 +52,8 @@ npm install -g meteor --ignore-meteor-setup-exec-path ``` (or by setting the environment variable `npm_config_ignore_meteor_setup_exec_path=true`) + +### Proxy configuration + +Setting the `https_proxy` or `HTTPS_PROXY` environment variable to a valid proxy URL will cause the +downloader to use the configured proxy to retrieve the Meteor files. \ No newline at end of file diff --git a/npm-packages/meteor-installer/install.js b/npm-packages/meteor-installer/install.js index 25b252a525..f52fd003f2 100644 --- a/npm-packages/meteor-installer/install.js +++ b/npm-packages/meteor-installer/install.js @@ -1,4 +1,5 @@ const { DownloaderHelper } = require('node-downloader-helper'); +const HttpsProxyAgent = require('https-proxy-agent'); const cliProgress = require('cli-progress'); const Seven = require('node-7z'); const path = require('path'); @@ -133,6 +134,15 @@ try { download(); +function generateProxyAgent() { + const proxyUrl = process.env.https_proxy || process.env.HTTPS_PROXY; + if (!proxyUrl) { + return undefined; + } + + return new HttpsProxyAgent(proxyUrl); +} + function download() { const start = Date.now(); const downloadProgress = new cliProgress.SingleBar( @@ -148,6 +158,9 @@ function download() { retry: { maxRetries: 5, delay: 5000 }, override: true, fileName: tarGzName, + httpsRequestOptions: { + agent: generateProxyAgent() + } }); dl.on('progress', ({ progress }) => { diff --git a/npm-packages/meteor-installer/package.json b/npm-packages/meteor-installer/package.json index 9ca969a6c6..4e716a5acf 100644 --- a/npm-packages/meteor-installer/package.json +++ b/npm-packages/meteor-installer/package.json @@ -11,6 +11,7 @@ "dependencies": { "7zip-bin": "^5.0.3", "cli-progress": "^3.5.0", + "https-proxy-agent": "^5.0.1", "node-7z": "^2.0.5", "node-downloader-helper": "^1.0.11", "rimraf": "^3.0.2", From 55d593437707115397b3656e881c715548997bf6 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Thu, 18 Aug 2022 15:36:52 -0300 Subject: [PATCH 24/99] chore(installer): removed from runtime-hot errors --- packages/modules-runtime-hot/installer.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/packages/modules-runtime-hot/installer.js b/packages/modules-runtime-hot/installer.js index fff539fec5..3c7c7df494 100644 --- a/packages/modules-runtime-hot/installer.js +++ b/packages/modules-runtime-hot/installer.js @@ -204,26 +204,6 @@ makeInstaller = function (options) { } function makeMissingError(id) { - const path = String(id) - .split('/'); - - const importsFrom = (location) => - path.some((subPath) => subPath === location); - - if (importsFrom('client')) { - return new Error( - `Unable to import on the client a module from a server directory: ${id} - (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories` - ); - } - - if (importsFrom('server')) { - return new Error( - `Unable to import on the server a module from a client directory: ${id} - (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories` - ); - } - return new Error("Cannot find module '" + id + "'"); } From 2c2038978d4252b894c0d87144d9f19d597383d2 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Thu, 18 Aug 2022 15:37:37 -0300 Subject: [PATCH 25/99] feat(module-errors): created custom errors for modules --- .../errors/cannotFindMeteorPackage.js | 12 ++++++ .../errors/cannotImportFrom.js | 40 +++++++++++++++++++ packages/modules-runtime/errors/index.ts | 7 ++++ packages/modules-runtime/legacy.js | 15 +++---- packages/modules-runtime/modern.js | 15 +++---- packages/modules-runtime/verifyErrors.js | 21 ++++++++++ 6 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 packages/modules-runtime/errors/cannotFindMeteorPackage.js create mode 100644 packages/modules-runtime/errors/cannotImportFrom.js create mode 100644 packages/modules-runtime/errors/index.ts create mode 100644 packages/modules-runtime/verifyErrors.js diff --git a/packages/modules-runtime/errors/cannotFindMeteorPackage.js b/packages/modules-runtime/errors/cannotFindMeteorPackage.js new file mode 100644 index 0000000000..c6575a4167 --- /dev/null +++ b/packages/modules-runtime/errors/cannotFindMeteorPackage.js @@ -0,0 +1,12 @@ +/** + * @description Default error message for when a package is not found + * @param id{string} + * @return {Error} + */ +export const cannotFindMeteorPackage = (id) => { + const packageName = id.split('/', 2)[1]; + return new Error( + 'Cannot find package "' + packageName + '". ' + + 'Try "meteor add ' + packageName + '".' + ); +}; diff --git a/packages/modules-runtime/errors/cannotImportFrom.js b/packages/modules-runtime/errors/cannotImportFrom.js new file mode 100644 index 0000000000..5700481139 --- /dev/null +++ b/packages/modules-runtime/errors/cannotImportFrom.js @@ -0,0 +1,40 @@ +/** + * + * @param id{string} + * @return {{fromServer: (function(): Error), from: (function(location: string): boolean), fromClient: (function(): Error)}} + */ +export const cannotImport = (id) => { + /** + * + * @param location{string} + * @return {boolean} + */ + const from = + (location) => { + if (!id) { + return false; + } + return String(id) + .split('/') + .some((subPath) => subPath === location); + }; + + const fromClient = + () => new Error( + `Unable to import on the client a module from a server directory: ${id} + (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories` + ); + + + const fromServer = + () => new Error( + `Unable to import on the server a module from a client directory: ${id} + (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories` + ); + + return { + from, + fromClient, + fromServer + }; +}; diff --git a/packages/modules-runtime/errors/index.ts b/packages/modules-runtime/errors/index.ts new file mode 100644 index 0000000000..dedb37c38a --- /dev/null +++ b/packages/modules-runtime/errors/index.ts @@ -0,0 +1,7 @@ +import {cannotFindMeteorPackage} from './cannotFindMeteorPackage.js'; +import {cannotImport} from './cannotImportFrom.js'; + +export { + cannotFindMeteorPackage, + cannotImport +} diff --git a/packages/modules-runtime/legacy.js b/packages/modules-runtime/legacy.js index b76f1d36b3..1c226f6458 100644 --- a/packages/modules-runtime/legacy.js +++ b/packages/modules-runtime/legacy.js @@ -1,3 +1,5 @@ +import { verifyErrors } from './verifyErrors'; + meteorInstall = makeInstaller({ // On the client, make package resolution prefer the "browser" field of // package.json over the "module" field over the "main" field. @@ -5,17 +7,10 @@ meteorInstall = makeInstaller({ // The difference between legacy.js and modern.js is that this module // prefers "main" over "module" (see issue #10658). - mainFields: ["browser", "main", "module"], - - fallback: function(id, parentId, error) { - if (id && id.startsWith('meteor/')) { - var packageName = id.split('/', 2)[1]; - throw new Error( - 'Cannot find package "' + packageName + '". ' + - 'Try "meteor add ' + packageName + '".' - ); - } + mainFields: ['browser', 'main', 'module'], + fallback: function (id, parentId, error) { + verifyErrors(id); throw error; } }); diff --git a/packages/modules-runtime/modern.js b/packages/modules-runtime/modern.js index c26a129da0..5461dbf5c3 100644 --- a/packages/modules-runtime/modern.js +++ b/packages/modules-runtime/modern.js @@ -1,18 +1,13 @@ +import { verifyErrors } from './verifyErrors'; + meteorInstall = makeInstaller({ // On the client, make package resolution prefer the "browser" field of // package.json over the "module" field over the "main" field. browser: true, - mainFields: ["browser", "module", "main"], - - fallback: function(id, parentId, error) { - if (id && id.startsWith('meteor/')) { - var packageName = id.split('/', 2)[1]; - throw new Error( - 'Cannot find package "' + packageName + '". ' + - 'Try "meteor add ' + packageName + '".' - ); - } + mainFields: ['browser', 'module', 'main'], + fallback: function (id, parentId, error) { + verifyErrors(id); throw error; } }); diff --git a/packages/modules-runtime/verifyErrors.js b/packages/modules-runtime/verifyErrors.js new file mode 100644 index 0000000000..03df701a7c --- /dev/null +++ b/packages/modules-runtime/verifyErrors.js @@ -0,0 +1,21 @@ +import * as E from './errors'; + +/** + * + * @param id{string} + */ +export const verifyErrors = (id) => { + if (id && id.startsWith('meteor/')) { + throw E.cannotFindMeteorPackage(id); + } + if (E.cannotImport(id) + .from('client')) { + throw E.cannotImport(id) + .fromClient(); + } + if (E.cannotImport(id) + .from('server')) { + throw E.cannotImport(id) + .fromServer(); + } +}; From 968eb71800ee0830f54cf315d166e9fceb15ce9b Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Thu, 18 Aug 2022 15:37:54 -0300 Subject: [PATCH 26/99] tests(module-erros): added tests --- .../modules-runtime/modules-runtime-tests.js | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/modules-runtime/modules-runtime-tests.js b/packages/modules-runtime/modules-runtime-tests.js index a46f51b9d7..665c540a38 100644 --- a/packages/modules-runtime/modules-runtime-tests.js +++ b/packages/modules-runtime/modules-runtime-tests.js @@ -1,11 +1,22 @@ Tinytest.add('modules', function (test) { - test.equal(typeof meteorInstall, "function"); + test.equal(typeof meteorInstall, 'function'); var require = meteorInstall(); - test.equal(typeof require, "function"); + test.equal(typeof require, 'function'); }); -Tinytest.add('modules - error', function (test) { - - const require = meteorInstall('non_existent_module'); - test.throws(require(), /Cannot find package "meteor". Try "meteor add meteor"./); +Tinytest.add('modules - meteor/ - error', function (test) { + const require = meteorInstall(); + test.throws(require('meteor/foo'), /Cannot find package "meteor". Try "meteor add meteor"./); +}); + +Tinytest.add('modules - client calling server', function (test) { + const require = meteorInstall(); + test.throws(require('./../server/main.js'), `Unable to import on the client a module from a server directory: './../server/main.js' + (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories`); +}); + +Tinytest.add('modules - server - error', function (test) { + const require = meteorInstall(); + test.throws(require('./../client/main.js'), `Unable to import on the server a module from a client directory: './../client/main.js' + (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories`); }); From 438b8f5eeaaa811c867576f4369699477f51f8b0 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Thu, 18 Aug 2022 22:09:02 -0300 Subject: [PATCH 27/99] chore(module-runtime): added files in package.js --- packages/modules-runtime-hot/installer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules-runtime-hot/installer.js b/packages/modules-runtime-hot/installer.js index 3c7c7df494..6ad56981a4 100644 --- a/packages/modules-runtime-hot/installer.js +++ b/packages/modules-runtime-hot/installer.js @@ -220,8 +220,8 @@ makeInstaller = function (options) { var file = fileResolve(filesByModuleId[this.id], id); if (file) return file.module.id; var error = makeMissingError(id); - if (fallback && isFunction(fallback.resolve)) { - return fallback.resolve(id, this.id, error); + if (fallback && isFunction(fallback)) { + return fallback(id, this.id, error); } throw error; }; From bfd81cc616f8fc79259e1ba03dfccddc96dadceb Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Thu, 18 Aug 2022 22:09:45 -0300 Subject: [PATCH 28/99] chore: removed index.js from module-runtime --- packages/modules-runtime/errors/index.ts | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 packages/modules-runtime/errors/index.ts diff --git a/packages/modules-runtime/errors/index.ts b/packages/modules-runtime/errors/index.ts deleted file mode 100644 index dedb37c38a..0000000000 --- a/packages/modules-runtime/errors/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {cannotFindMeteorPackage} from './cannotFindMeteorPackage.js'; -import {cannotImport} from './cannotImportFrom.js'; - -export { - cannotFindMeteorPackage, - cannotImport -} From 1fe48b607fe683b5f2574cd7e8af2d24343c202a Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Thu, 18 Aug 2022 22:10:13 -0300 Subject: [PATCH 29/99] Revert "chore(module-runtime): added files in package.js" This reverts commit 438b8f5eeaaa811c867576f4369699477f51f8b0. --- packages/modules-runtime-hot/installer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules-runtime-hot/installer.js b/packages/modules-runtime-hot/installer.js index 6ad56981a4..3c7c7df494 100644 --- a/packages/modules-runtime-hot/installer.js +++ b/packages/modules-runtime-hot/installer.js @@ -220,8 +220,8 @@ makeInstaller = function (options) { var file = fileResolve(filesByModuleId[this.id], id); if (file) return file.module.id; var error = makeMissingError(id); - if (fallback && isFunction(fallback)) { - return fallback(id, this.id, error); + if (fallback && isFunction(fallback.resolve)) { + return fallback.resolve(id, this.id, error); } throw error; }; From 4888a5dbb3b304fe416eb8fba32f4d23e0a36ef3 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Thu, 18 Aug 2022 22:10:40 -0300 Subject: [PATCH 30/99] chore(modules-runtime): added files in package.js --- packages/modules-runtime/package.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/modules-runtime/package.js b/packages/modules-runtime/package.js index 7afb76434b..8361e5073f 100644 --- a/packages/modules-runtime/package.js +++ b/packages/modules-runtime/package.js @@ -18,12 +18,16 @@ Package.onUse(function(api) { bare: true }); - api.addFiles("modern.js", "modern"); - api.addFiles("legacy.js", "legacy"); - api.addFiles("server.js", "server"); - api.addFiles("profile.js"); + api.addFiles(['./errors/cannotImportFrom.js', + './errors/cannotFindMeteorPackage.js']); + api.addFiles('modern.js', 'modern'); + api.addFiles('legacy.js', 'legacy'); + api.addFiles('server.js', 'server'); + api.addFiles('profile.js'); + api.addFiles('verifyErrors.js'); - api.export("meteorInstall"); + api.export('meteorInstall'); + api.export('verifyErrors'); }); Package.onTest(function(api) { From 49c5f9283dbc528a3952832012b13475eb3ab762 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Thu, 18 Aug 2022 22:11:12 -0300 Subject: [PATCH 31/99] Revert "Revert "chore(module-runtime): added files in package.js"" This reverts commit 1fe48b607fe683b5f2574cd7e8af2d24343c202a. --- packages/modules-runtime-hot/installer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules-runtime-hot/installer.js b/packages/modules-runtime-hot/installer.js index 3c7c7df494..6ad56981a4 100644 --- a/packages/modules-runtime-hot/installer.js +++ b/packages/modules-runtime-hot/installer.js @@ -220,8 +220,8 @@ makeInstaller = function (options) { var file = fileResolve(filesByModuleId[this.id], id); if (file) return file.module.id; var error = makeMissingError(id); - if (fallback && isFunction(fallback.resolve)) { - return fallback.resolve(id, this.id, error); + if (fallback && isFunction(fallback)) { + return fallback(id, this.id, error); } throw error; }; From 156e86815c2b141411ca074109833955f796fc37 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Thu, 18 Aug 2022 22:12:07 -0300 Subject: [PATCH 32/99] chore(verifyErrors): esnext -> es5 --- .../errors/cannotFindMeteorPackage.js | 4 +- .../errors/cannotImportFrom.js | 40 +++++++++++-------- packages/modules-runtime/verifyErrors.js | 22 +++++----- 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/packages/modules-runtime/errors/cannotFindMeteorPackage.js b/packages/modules-runtime/errors/cannotFindMeteorPackage.js index c6575a4167..fd94ab813a 100644 --- a/packages/modules-runtime/errors/cannotFindMeteorPackage.js +++ b/packages/modules-runtime/errors/cannotFindMeteorPackage.js @@ -3,8 +3,8 @@ * @param id{string} * @return {Error} */ -export const cannotFindMeteorPackage = (id) => { - const packageName = id.split('/', 2)[1]; +cannotFindMeteorPackage = function(id) { + var packageName = id.split('/', 2)[1]; return new Error( 'Cannot find package "' + packageName + '". ' + 'Try "meteor add ' + packageName + '".' diff --git a/packages/modules-runtime/errors/cannotImportFrom.js b/packages/modules-runtime/errors/cannotImportFrom.js index 5700481139..967517282b 100644 --- a/packages/modules-runtime/errors/cannotImportFrom.js +++ b/packages/modules-runtime/errors/cannotImportFrom.js @@ -3,38 +3,44 @@ * @param id{string} * @return {{fromServer: (function(): Error), from: (function(location: string): boolean), fromClient: (function(): Error)}} */ -export const cannotImport = (id) => { +cannotImport = function (id) { /** * * @param location{string} * @return {boolean} */ - const from = - (location) => { + var from = + function (location) { if (!id) { return false; } return String(id) .split('/') - .some((subPath) => subPath === location); + .some(function (subPath) { + return subPath === location; + }); }; - const fromClient = - () => new Error( - `Unable to import on the client a module from a server directory: ${id} - (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories` - ); + var fromClient = + function () { + return new Error( + 'Unable to import on the client a module from a server directory: ' + + id + ' \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' + ); + }; - const fromServer = - () => new Error( - `Unable to import on the server a module from a client directory: ${id} - (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories` - ); + var fromServer = + function () { + return new Error( + 'Unable to import on the server a module from a client directory: ' + + id + ' \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' + ); + }; return { - from, - fromClient, - fromServer + from: from, + fromClient: fromClient, + fromServer: fromServer }; }; diff --git a/packages/modules-runtime/verifyErrors.js b/packages/modules-runtime/verifyErrors.js index 03df701a7c..3bcf983830 100644 --- a/packages/modules-runtime/verifyErrors.js +++ b/packages/modules-runtime/verifyErrors.js @@ -1,21 +1,21 @@ -import * as E from './errors'; /** * * @param id{string} + * @param parentId{string} + * @param err {Error} */ -export const verifyErrors = (id) => { +verifyErrors = function (id, parentId,err) { if (id && id.startsWith('meteor/')) { - throw E.cannotFindMeteorPackage(id); + throw cannotFindMeteorPackage(id); } - if (E.cannotImport(id) - .from('client')) { - throw E.cannotImport(id) - .fromClient(); + if (cannotImport(id).from('client')) { + throw cannotImport(id).fromClient(); } - if (E.cannotImport(id) - .from('server')) { - throw E.cannotImport(id) - .fromServer(); + if (cannotImport(id).from('server')) { + throw cannotImport(id).fromServer(); + } + if (err) { + throw err; } }; From ce179fa55ceb814578a1212d568169e7638c98d5 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Thu, 18 Aug 2022 22:13:55 -0300 Subject: [PATCH 33/99] chore(modules.fallback): all goes to varifyErrors --- packages/modules-runtime-hot/legacy.js | 12 +++--------- packages/modules-runtime-hot/modern.js | 12 +++--------- packages/modules-runtime/legacy.js | 5 +---- packages/modules-runtime/modern.js | 5 +---- 4 files changed, 8 insertions(+), 26 deletions(-) diff --git a/packages/modules-runtime-hot/legacy.js b/packages/modules-runtime-hot/legacy.js index 77ef37d5c6..c4b1263ec4 100644 --- a/packages/modules-runtime-hot/legacy.js +++ b/packages/modules-runtime-hot/legacy.js @@ -1,3 +1,5 @@ +let verifyErrors = Package['modules-runtime'].verifyErrors; + meteorInstall = makeInstaller({ // On the client, make package resolution prefer the "browser" field of // package.json over the "module" field over the "main" field. @@ -8,15 +10,7 @@ meteorInstall = makeInstaller({ mainFields: ["browser", "main", "module"], fallback: function (id, parentId, error) { - if (id && id.startsWith('meteor/')) { - var packageName = id.split('/', 2)[1]; - throw new Error( - 'Cannot find package "' + packageName + '". ' + - 'Try "meteor add ' + packageName + '".' - ); - } - - throw error; + verifyErrors(id, parentId, error); } }); diff --git a/packages/modules-runtime-hot/modern.js b/packages/modules-runtime-hot/modern.js index 2445856d2d..48282a28f3 100644 --- a/packages/modules-runtime-hot/modern.js +++ b/packages/modules-runtime-hot/modern.js @@ -1,3 +1,5 @@ +let verifyErrors = Package['modules-runtime'].verifyErrors; + meteorInstall = makeInstaller({ // On the client, make package resolution prefer the "browser" field of // package.json over the "module" field over the "main" field. @@ -5,15 +7,7 @@ meteorInstall = makeInstaller({ mainFields: ["browser", "module", "main"], fallback: function (id, parentId, error) { - if (id && id.startsWith('meteor/')) { - var packageName = id.split('/', 2)[1]; - throw new Error( - 'Cannot find package "' + packageName + '". ' + - 'Try "meteor add ' + packageName + '".' - ); - } - - throw error; + verifyErrors(id, parentId, error); } }); diff --git a/packages/modules-runtime/legacy.js b/packages/modules-runtime/legacy.js index 1c226f6458..b75822a116 100644 --- a/packages/modules-runtime/legacy.js +++ b/packages/modules-runtime/legacy.js @@ -1,5 +1,3 @@ -import { verifyErrors } from './verifyErrors'; - meteorInstall = makeInstaller({ // On the client, make package resolution prefer the "browser" field of // package.json over the "module" field over the "main" field. @@ -10,7 +8,6 @@ meteorInstall = makeInstaller({ mainFields: ['browser', 'main', 'module'], fallback: function (id, parentId, error) { - verifyErrors(id); - throw error; + verifyErrors(id, parentId, error); } }); diff --git a/packages/modules-runtime/modern.js b/packages/modules-runtime/modern.js index 5461dbf5c3..06b6390a6f 100644 --- a/packages/modules-runtime/modern.js +++ b/packages/modules-runtime/modern.js @@ -1,5 +1,3 @@ -import { verifyErrors } from './verifyErrors'; - meteorInstall = makeInstaller({ // On the client, make package resolution prefer the "browser" field of // package.json over the "module" field over the "main" field. @@ -7,7 +5,6 @@ meteorInstall = makeInstaller({ mainFields: ['browser', 'module', 'main'], fallback: function (id, parentId, error) { - verifyErrors(id); - throw error; + verifyErrors(id, parentId, error); } }); From d4e9f85ee73420876df2a653bc9ccc7d996069ce Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Thu, 18 Aug 2022 22:14:18 -0300 Subject: [PATCH 34/99] tests(module-runtime): added verifyErrors tests --- packages/modules-runtime/modules-runtime-tests.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/modules-runtime/modules-runtime-tests.js b/packages/modules-runtime/modules-runtime-tests.js index 665c540a38..44da8b9a34 100644 --- a/packages/modules-runtime/modules-runtime-tests.js +++ b/packages/modules-runtime/modules-runtime-tests.js @@ -11,12 +11,12 @@ Tinytest.add('modules - meteor/ - error', function (test) { Tinytest.add('modules - client calling server', function (test) { const require = meteorInstall(); - test.throws(require('./../server/main.js'), `Unable to import on the client a module from a server directory: './../server/main.js' - (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories`); + test.throws(require('./../server/main.js'), 'Unable to import on the client a module from a server directory: ' + id + ' \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories`' + ); }); -Tinytest.add('modules - server - error', function (test) { +Tinytest.add('modules - server calling client', function (test) { const require = meteorInstall(); - test.throws(require('./../client/main.js'), `Unable to import on the server a module from a client directory: './../client/main.js' - (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories`); + test.throws(require('./../client/main.js'), 'Unable to import on the server a module from a client directory: ' + id + ' \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories`' + ); }); From 9f904ba48b1144078e2acfa36adb532b3b028c62 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Mon, 22 Aug 2022 21:26:38 -0300 Subject: [PATCH 35/99] Change accounts and oauth to use async --- packages/accounts-base/accounts_server.js | 33 ++-- packages/accounts-password/password_server.js | 12 +- packages/google-oauth/google_server.js | 163 ++++++++++-------- packages/oauth1/oauth1_server.js | 4 +- packages/oauth2/oauth2_server.js | 4 +- 5 files changed, 121 insertions(+), 95 deletions(-) diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 9088bbbea9..fea56b222a 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -434,7 +434,7 @@ export class AccountsServer extends AccountsCommon { // If the login is allowed and isn't aborted by a validate login hook // callback, log in the user. // - _attemptLogin( + async _attemptLogin( methodInvocation, methodName, methodArgs, @@ -494,18 +494,18 @@ export class AccountsServer extends AccountsCommon { // Ensure that thrown exceptions are caught and that login hook // callbacks are still called. // - _loginMethod( + async _loginMethod( methodInvocation, methodName, methodArgs, type, fn ) { - return this._attemptLogin( + return await this._attemptLogin( methodInvocation, methodName, methodArgs, - tryLoginMethod(type, fn) + await tryLoginMethod(type, fn) ); }; @@ -587,11 +587,10 @@ export class AccountsServer extends AccountsCommon { // Try all of the registered login handlers until one of them doesn't // return `undefined`, meaning it handled this call to `login`. Return // that return value. - _runLoginHandlers(methodInvocation, options) { - for (let handler of this._loginHandlers) { - const result = tryLoginMethod( - handler.name, - () => handler.handler.call(methodInvocation, options) + async _runLoginHandlers(methodInvocation, options) { + for await (let handler of this._loginHandlers) { + const result = await tryLoginMethod(handler.name, async () => + await handler.handler.call(methodInvocation, options) ); if (result) { @@ -599,7 +598,10 @@ export class AccountsServer extends AccountsCommon { } if (result !== undefined) { - throw new Meteor.Error(400, "A login handler should return a result or undefined"); + throw new Meteor.Error( + 400, + 'A login handler should return a result or undefined' + ); } } @@ -644,14 +646,15 @@ export class AccountsServer extends AccountsCommon { // If successful, returns {token: reconnectToken, id: userId} // If unsuccessful (for example, if the user closed the oauth login popup), // throws an error describing the reason - methods.login = function (options) { + methods.login = async function (options) { // Login handlers should really also check whatever field they look at in // options, but we don't enforce it. check(options, Object); - const result = accounts._runLoginHandlers(this, options); + const result = await accounts._runLoginHandlers(this, options); + //console.log({result}); - return accounts._attemptLogin(this, "login", arguments, result); + return await accounts._attemptLogin(this, "login", arguments, result); }; methods.logout = function () { @@ -1512,10 +1515,10 @@ const cloneAttemptWithConnection = (connection, attempt) => { return clonedAttempt; }; -const tryLoginMethod = (type, fn) => { +const tryLoginMethod = async (type, fn) => { let result; try { - result = fn(); + result = await fn(); } catch (e) { result = {error: e}; diff --git a/packages/accounts-password/password_server.js b/packages/accounts-password/password_server.js index ea1236313c..75c7c30e0c 100644 --- a/packages/accounts-password/password_server.js +++ b/packages/accounts-password/password_server.js @@ -560,10 +560,10 @@ Accounts.sendEnrollmentEmail = (userId, email, extraTokenData, extraParams) => { // Take token from sendResetPasswordEmail or sendEnrollmentEmail, change // the users password, and log them in. -Meteor.methods({resetPassword: function (...args) { +Meteor.methods({resetPassword: async function (...args) { const token = args[0]; const newPassword = args[1]; - return Accounts._loginMethod( + return await Accounts._loginMethod( this, "resetPassword", args, @@ -712,9 +712,9 @@ Accounts.sendVerificationEmail = (userId, email, extraTokenData, extraParams) => // Take token from sendVerificationEmail, mark the email as verified, // and log them in. -Meteor.methods({verifyEmail: function (...args) { +Meteor.methods({verifyEmail: async function (...args) { const token = args[0]; - return Accounts._loginMethod( + return await Accounts._loginMethod( this, "verifyEmail", args, @@ -911,9 +911,9 @@ const createUser = options => { }; // method for create user. Requests come from the client. -Meteor.methods({createUser: function (...args) { +Meteor.methods({createUser: async function (...args) { const options = args[0]; - return Accounts._loginMethod( + return await Accounts._loginMethod( this, "createUser", args, diff --git a/packages/google-oauth/google_server.js b/packages/google-oauth/google_server.js index d13c285914..5e470fbc6b 100644 --- a/packages/google-oauth/google_server.js +++ b/packages/google-oauth/google_server.js @@ -5,40 +5,46 @@ import { fetch } from 'meteor/fetch'; const hasOwn = Object.prototype.hasOwnProperty; // https://developers.google.com/accounts/docs/OAuth2Login#userinfocall -Google.whitelistedFields = ['id', 'email', 'verified_email', 'name', 'given_name', - 'family_name', 'picture', 'locale', 'timezone', 'gender']; +Google.whitelistedFields = [ + 'id', + 'email', + 'verified_email', + 'name', + 'given_name', + 'family_name', + 'picture', + 'locale', + 'timezone', + 'gender', +]; -const getServiceDataFromTokens = tokens => { +const getServiceDataFromTokens = async (tokens, callback) => { const { accessToken, idToken } = tokens; - const scopesCall = Meteor.wrapAsync(getScopes); - let scopes; - try { - scopes = scopesCall(accessToken); - } catch (err) { - throw Object.assign( + const scopes = await getScopes(accessToken).catch((err) => { + const error = Object.assign( new Error(`Failed to fetch tokeninfo from Google. ${err.message}`), { response: err.response } ); - } - const identityCall = Meteor.wrapAsync(getIdentity); - let identity; - try { - identity = identityCall(accessToken); - } catch (err) { - throw Object.assign( + callback && callback(error); + throw error; + }); + + let identity = await getIdentity(accessToken).catch((err) => { + const error = Object.assign( new Error(`Failed to fetch identity from Google. ${err.message}`), { response: err.response } ); - } + callback && callback(error); + throw error; + }); const serviceData = { accessToken, idToken, - scope: scopes + scope: scopes, }; - if (hasOwn.call(tokens, "expiresIn")) { - serviceData.expiresAt = - Date.now() + 1000 * parseInt(tokens.expiresIn, 10); + if (hasOwn.call(tokens, 'expiresIn')) { + serviceData.expiresAt = Date.now() + 1000 * parseInt(tokens.expiresIn, 10); } const fields = Object.create(null); @@ -56,22 +62,25 @@ const getServiceDataFromTokens = tokens => { if (tokens.refreshToken) { serviceData.refreshToken = tokens.refreshToken; } - - return { + const returnValue = { serviceData, options: { profile: { - name: identity.name - } - } + name: identity.name, + }, + }, }; + console.log({ returnValue }); + callback && callback(undefined, returnValue); + + return returnValue; }; -Accounts.registerLoginHandler(request => { +Accounts.registerLoginHandler(async (request) => { if (request.googleSignIn !== true) { return; } - + console.log({ request }); const tokens = { accessToken: request.accessToken, refreshToken: request.refreshToken, @@ -79,29 +88,38 @@ Accounts.registerLoginHandler(request => { }; if (request.serverAuthCode) { - Object.assign(tokens, getTokens({ - code: request.serverAuthCode - })); + Object.assign( + tokens, + await getTokens({ + code: request.serverAuthCode, + }) + ); } let result; try { - result = getServiceDataFromTokens(tokens); + result = await getServiceDataFromTokens(tokens); } catch (err) { throw Object.assign( - new Error(`Failed to complete OAuth handshake with Google. ${err.message}`), + new Error( + `Failed to complete OAuth handshake with Google. ${err.message}` + ), { response: err.response } ); } - - return Accounts.updateOrCreateUserFromExternalService("google", { - id: request.userId, - idToken: request.idToken, - accessToken: request.accessToken, - email: request.email, - picture: request.imageUrl, - ...result.serviceData, - }, result.options); + console.log({ result }); + return Accounts.updateOrCreateUserFromExternalService( + 'google', + { + id: request.userId, + idToken: request.idToken, + accessToken: request.accessToken, + email: request.email, + picture: request.imageUrl, + ...result.serviceData, + }, + result.options + ); }); // returns an object containing: @@ -109,45 +127,48 @@ Accounts.registerLoginHandler(request => { // - expiresIn: lifetime of token in seconds // - refreshToken, if this is the first authorization request const getTokens = async (query, callback) => { - const config = ServiceConfiguration.configurations.findOne({service: 'google'}); - if (!config) - throw new ServiceConfiguration.ConfigError(); + const config = ServiceConfiguration.configurations.findOne({ + service: 'google', + }); + if (!config) throw new ServiceConfiguration.ConfigError(); const content = new URLSearchParams({ code: query.code, client_id: config.clientId, client_secret: OAuth.openSecret(config.secret), redirect_uri: OAuth._redirectUri('google', config), - grant_type: 'authorization_code' + grant_type: 'authorization_code', + }); + const request = await fetch('https://accounts.google.com/o/oauth2/token', { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: content, }); - const request = await fetch( - "https://accounts.google.com/o/oauth2/token", { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: content, - }); const response = await request.json(); - if (response.error) { // if the http response was a json object with an error attribute - callback(response.error); - throw new Meteor.Error(`Failed to complete OAuth handshake with Google. ${response.error}`); + if (response.error) { + // if the http response was a json object with an error attribute + callback && callback(response.error); + throw new Meteor.Error( + `Failed to complete OAuth handshake with Google. ${response.error}` + ); } else { const data = { accessToken: response.access_token, refreshToken: response.refresh_token, expiresIn: response.expires_in, - idToken: response.id_token + idToken: response.id_token, }; - callback(undefined, data); + callback && callback(undefined, data); return data; } }; -const getTokensCall = Meteor.wrapAsync(getTokens); -const getServiceData = query => getServiceDataFromTokens(getTokensCall(query)); +const getServiceData = async (query) => + getServiceDataFromTokens(await getTokens(query)); OAuth.registerService('google', 2, null, getServiceData); @@ -159,14 +180,15 @@ const getIdentity = async (accessToken, callback) => { `https://www.googleapis.com/oauth2/v1/userinfo?${content.toString()}`, { method: 'GET', - headers: { Accept: 'application/json' } - }); + headers: { Accept: 'application/json' }, + } + ); response = await request.json(); } catch (e) { - callback(e); + callback && callback(e); throw new Meteor.Error(e.reason); } - callback(undefined, response); + callback && callback(undefined, response); return response; }; @@ -178,14 +200,15 @@ const getScopes = async (accessToken, callback) => { `https://www.googleapis.com/oauth2/v1/tokeninfo?${content.toString()}`, { method: 'GET', - headers: { Accept: 'application/json' } - }); + headers: { Accept: 'application/json' }, + } + ); response = await request.json(); } catch (e) { - callback(e); + callback && callback(e); throw new Meteor.Error(e.reason); } - callback(undefined, response.scope.split(' ')); + callback && callback(undefined, response.scope.split(' ')); return response.scope.split(' '); }; diff --git a/packages/oauth1/oauth1_server.js b/packages/oauth1/oauth1_server.js index eb54458825..8ad132c198 100644 --- a/packages/oauth1/oauth1_server.js +++ b/packages/oauth1/oauth1_server.js @@ -6,7 +6,7 @@ OAuth._queryParamsWithAuthTokenUrl = (authUrl, oauthBinding, params = {}, whitel Object.assign( redirectUrlObj.query, - whitelistedQueryParams.reduce((prev, param) => + whitelistedQueryParams.reduce((prev, param) => params.query[param] ? { ...prev, param: params.query[param] } : prev, {} ), @@ -25,7 +25,7 @@ OAuth._queryParamsWithAuthTokenUrl = (authUrl, oauthBinding, params = {}, whitel }; // connect middleware -OAuth._requestHandlers['1'] = (service, query, res) => { +OAuth._requestHandlers['1'] = async (service, query, res) => { const config = ServiceConfiguration.configurations.findOne({service: service.serviceName}); if (! config) { throw new ServiceConfiguration.ConfigError(service.serviceName); diff --git a/packages/oauth2/oauth2_server.js b/packages/oauth2/oauth2_server.js index cf990d8691..86eead93ba 100644 --- a/packages/oauth2/oauth2_server.js +++ b/packages/oauth2/oauth2_server.js @@ -1,5 +1,5 @@ // connect middleware -OAuth._requestHandlers['2'] = (service, query, res) => { +OAuth._requestHandlers['2'] = async (service, query, res) => { let credentialSecret; // check if user authorized access @@ -7,7 +7,7 @@ OAuth._requestHandlers['2'] = (service, query, res) => { // Prepare the login results before returning. // Run service-specific handler. - const oauthResult = service.handleOauthRequest(query); + const oauthResult = await service.handleOauthRequest(query); credentialSecret = Random.secret(); const credentialToken = OAuth._credentialTokenFromQuery(query); From d0b5687b43d67b5c0785ed0079402d1574589c8f Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Mon, 22 Aug 2022 21:45:22 -0300 Subject: [PATCH 36/99] Changes requested by code review. Thank you @radekmie --- packages/email/email.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/email/email.js b/packages/email/email.js index a7183a9731..3ef97331ad 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -147,7 +147,7 @@ EmailTest.resetNextDevModeMailId = function () { nextDevModeMailId = 0; }; -const devModeSendAsync = async function (mail, stream) { +const devModeSendAsync = function (mail, stream) { return new Promise((resolve, reject) => { let devModeMailId = EmailTest._getAndIncNextDevModeMailId(); @@ -305,7 +305,5 @@ Email.sendAsync = async function (options) { smtpSend(transport, email); return; } - return devModeSendAsync(email, stream).catch((err) => { - throw err; - }); + return devModeSendAsync(email, stream); }; From 0a21b597bc9ca820be19208c764e2063a6d2638c Mon Sep 17 00:00:00 2001 From: eduwr Date: Wed, 24 Aug 2022 06:12:36 -0300 Subject: [PATCH 37/99] convert github_server to async - remove wrapAsync from registerService method - remove callback calls on async methods --- packages/github-oauth/github_server.js | 29 ++++++++------------------ 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/packages/github-oauth/github_server.js b/packages/github-oauth/github_server.js index b71995d1c0..7b4f36f5f6 100644 --- a/packages/github-oauth/github_server.js +++ b/packages/github-oauth/github_server.js @@ -1,12 +1,9 @@ Github = {}; -OAuth.registerService('github', 2, null, (query) => { - const accessTokenCall = Meteor.wrapAsync(getAccessToken); - const accessToken = accessTokenCall(query); - const identityCall = Meteor.wrapAsync(getIdentity); - const identity = identityCall(accessToken); - const emailsCall = Meteor.wrapAsync(getEmails); - const emails = emailsCall(accessToken); +OAuth.registerService('github', 2, null, async (query) => { + const accessToken = await getAccessToken(query); + const identity = await getIdentity(accessToken); + const emails = await getEmails(accessToken); const primaryEmail = emails.find((email) => email.primary); return { @@ -31,7 +28,7 @@ OAuth.registerService('github', 2, null, (query) => { let userAgent = 'Meteor'; if (Meteor.release) userAgent += `/${Meteor.release}`; -const getAccessToken = async (query, callback) => { +const getAccessToken = async (query) => { const config = ServiceConfiguration.configurations.findOne({ service: 'github' }); @@ -68,18 +65,16 @@ const getAccessToken = async (query, callback) => { ); } if (response.error) { - callback(response.error); // if the http response was a json object with an error attribute throw new Error( `Failed to complete OAuth handshake with GitHub. ${response.error}` ); } else { - callback(null, response.access_token); return response.access_token; } }; -const getIdentity = async (accessToken, callback) => { +const getIdentity = async (accessToken) => { try { const request = await fetch('https://api.github.com/user', { method: 'GET', @@ -89,11 +84,8 @@ const getIdentity = async (accessToken, callback) => { Authorization: `token ${accessToken}` } // http://developer.github.com/v3/#user-agent-required }); - const response = await request.json(); - callback(null, response); - return response; + return await request.json(); } catch (err) { - callback(err.message); throw Object.assign( new Error(`Failed to fetch identity from Github. ${err.message}`), { response: err.response } @@ -101,7 +93,7 @@ const getIdentity = async (accessToken, callback) => { } }; -const getEmails = async (accessToken, callback) => { +const getEmails = async (accessToken) => { try { const request = await fetch('https://api.github.com/user/emails', { method: 'GET', @@ -111,11 +103,8 @@ const getEmails = async (accessToken, callback) => { Authorization: `token ${accessToken}` } // http://developer.github.com/v3/#user-agent-required }); - const response = await request.json(); - callback(null, response); - return response; + return await request.json(); } catch (err) { - callback(err.message, []); return []; } }; From 8b2b9dc398368774d8c9a781b65998b4cf03d003 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Wed, 24 Aug 2022 08:31:11 -0300 Subject: [PATCH 38/99] Return value from Email.customTransport. --- packages/email/email.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/email/email.js b/packages/email/email.js index 3ef97331ad..016d2a12c1 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -285,8 +285,7 @@ Email.sendAsync = async function (options) { if (Email.customTransport) { const packageSettings = Meteor.settings.packages?.email || {}; - Email.customTransport({ packageSettings, ...email }); - return; + return Email.customTransport({ packageSettings, ...email }); } const mailUrlEnv = process.env.MAIL_URL; From 01ccdb0d0d3a9af19c9b839973f28ccdcdf3ae29 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Wed, 24 Aug 2022 09:17:05 -0300 Subject: [PATCH 39/99] Change Oauth middleware to the async method. Changing tests to async because the current test flow capture data from mock data. --- packages/oauth/oauth_server.js | 4 ++-- packages/oauth1/oauth1_tests.js | 16 ++++++++-------- packages/oauth2/oauth2_tests.js | 16 ++++++++-------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/oauth/oauth_server.js b/packages/oauth/oauth_server.js index 6d7b0cb578..ce2524349b 100644 --- a/packages/oauth/oauth_server.js +++ b/packages/oauth/oauth_server.js @@ -136,7 +136,7 @@ OAuth._checkRedirectUrlOrigin = redirectUrl => { ); }; -const middleware = (req, res, next) => { +const middleware = async (req, res, next) => { let requestData; // Make sure to catch any exceptions because otherwise we'd crash @@ -168,7 +168,7 @@ const middleware = (req, res, next) => { requestData = req.body; } - handler(service, requestData, res); + await handler(service, requestData, res); } catch (err) { // if we got thrown an error, save it off, it will get passed to // the appropriate login call (if any) and reported there. diff --git a/packages/oauth1/oauth1_tests.js b/packages/oauth1/oauth1_tests.js index a9f266af02..c8a65fa86e 100644 --- a/packages/oauth1/oauth1_tests.js +++ b/packages/oauth1/oauth1_tests.js @@ -1,7 +1,7 @@ import http from 'http'; import { OAuth1Binding } from './oauth1_binding'; -const testPendingCredential = (test, method) => { +const testPendingCredential = async (test, method) => { const twitterfooId = Random.id(); const twitterfooName = `nickname${Random.id()}`; const twitterfooAccessToken = Random.id(); @@ -71,7 +71,7 @@ const testPendingCredential = (test, method) => { respData += args[0]; return end.apply(this, arguments); }; - OAuthTest.middleware(req, res); + await OAuthTest.middleware(req, res); const credentialSecret = respData; // Test that the result for the token is available @@ -94,17 +94,17 @@ const testPendingCredential = (test, method) => { } }; -Tinytest.add("oauth1 - pendingCredential is stored and can be retrieved (without oauth encryption)", test => { +Tinytest.addAsync("oauth1 - pendingCredential is stored and can be retrieved (without oauth encryption)", async test => { OAuthEncryption.loadKey(null); - testPendingCredential(test, "GET"); - testPendingCredential(test, "POST"); + await testPendingCredential(test, "GET"); + await testPendingCredential(test, "POST"); }); -Tinytest.add("oauth1 - pendingCredential is stored and can be retrieved (with oauth encryption)", test => { +Tinytest.add("oauth1 - pendingCredential is stored and can be retrieved (with oauth encryption)", async test => { try { OAuthEncryption.loadKey(Buffer.from([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]).toString("base64")); - testPendingCredential(test, "GET"); - testPendingCredential(test, "POST"); + await testPendingCredential(test, "GET"); + await testPendingCredential(test, "POST"); } finally { OAuthEncryption.loadKey(null); } diff --git a/packages/oauth2/oauth2_tests.js b/packages/oauth2/oauth2_tests.js index 1ce47813b4..49b94f4eb0 100644 --- a/packages/oauth2/oauth2_tests.js +++ b/packages/oauth2/oauth2_tests.js @@ -1,6 +1,6 @@ import http from 'http'; -const testPendingCredential = function (test, method) { +const testPendingCredential = async function (test, method) { const foobookId = Random.id(); const foobookOption1 = Random.id(); const credentialToken = Random.id(); @@ -51,7 +51,7 @@ const testPendingCredential = function (test, method) { return end.apply(this, args); }; - OAuthTest.middleware(req, res); + await OAuthTest.middleware(req, res); const credentialSecret = respData; // Test that the result for the token is available @@ -72,17 +72,17 @@ const testPendingCredential = function (test, method) { } }; -Tinytest.add("oauth2 - pendingCredential is stored and can be retrieved (without oauth encryption)", test => { +Tinytest.addAsync("oauth2 - pendingCredential is stored and can be retrieved (without oauth encryption)", async test => { OAuthEncryption.loadKey(null); - testPendingCredential(test, "GET"); - testPendingCredential(test, "POST"); + await testPendingCredential(test, "GET"); + await testPendingCredential(test, "POST"); }); -Tinytest.add("oauth2 - pendingCredential is stored and can be retrieved (with oauth encryption)", test => { +Tinytest.addAsync("oauth2 - pendingCredential is stored and can be retrieved (with oauth encryption)", async test => { try { OAuthEncryption.loadKey(Buffer.from([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]).toString("base64")); - testPendingCredential(test, "GET"); - testPendingCredential(test, "POST"); + await testPendingCredential(test, "GET"); + await testPendingCredential(test, "POST"); } finally { OAuthEncryption.loadKey(null); } From 7a6accc489365b5c61e1c22f28b6d62a77917fea Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Wed, 24 Aug 2022 21:53:06 -0300 Subject: [PATCH 40/99] Change accounts and oauth to use async for Meetup provider --- packages/meetup-oauth/meetup_server.js | 91 ++++++++++++++++---------- packages/meetup-oauth/package.js | 2 +- 2 files changed, 57 insertions(+), 36 deletions(-) diff --git a/packages/meetup-oauth/meetup_server.js b/packages/meetup-oauth/meetup_server.js index cffa8da9e5..5aafbc0439 100644 --- a/packages/meetup-oauth/meetup_server.js +++ b/packages/meetup-oauth/meetup_server.js @@ -1,10 +1,10 @@ Meetup = {}; -OAuth.registerService('meetup', 2, null, query => { - const response = getAccessToken(query); +OAuth.registerService('meetup', 2, null, async query => { + const response = await getAccessToken(query); const accessToken = response.access_token; const expiresAt = (+new Date) + (1000 * response.expires_in); - const identity = getIdentity(accessToken); + const identity = await getIdentity(accessToken); const { id, name, @@ -33,50 +33,71 @@ OAuth.registerService('meetup', 2, null, query => { }; }); -const getAccessToken = query => { +const toFormUrlencoded = data => { + return Object.entries(data) + .map(([key, value]) => `${key}=${value}`) + .join('&'); +}; + +const getAccessToken = async query => { const config = ServiceConfiguration.configurations.findOne({service: 'meetup'}); if (!config) throw new ServiceConfiguration.ConfigError(); - let response; - try { - response = HTTP.post( - "https://secure.meetup.com/oauth2/access", {headers: {Accept: 'application/json'}, params: { - code: query.code, - client_id: config.clientId, - client_secret: OAuth.openSecret(config.secret), - grant_type: 'authorization_code', - redirect_uri: OAuth._redirectUri('meetup', config), - state: query.state - }}); - } catch (err) { - throw Object.assign( - new Error(`Failed to complete OAuth handshake with Meetup. ${err.message}`), - { response: err.response } - ); - } + const bodyFormEncoded = toFormUrlencoded({ + code: query.code, + client_id: config.clientId, + client_secret: OAuth.openSecret(config.secret), + grant_type: 'authorization_code', + redirect_uri: OAuth._redirectUri('meetup', config), + state: query.state + }); - if (response.data.error) { // if the http response was a json object with an error attribute - throw new Error(`Failed to complete OAuth handshake with Meetup. ${response.data.error}`); - } else { - return response.data; - } + return await fetch('https://secure.meetup.com/oauth2/access', { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-type': 'application/x-www-form-urlencoded', + }, + body: bodyFormEncoded, + }) + .then(data => data.json()) + .then(data => { + if (data.error) { + throw new Error(`Failed to complete OAuth handshake with Meetup. ${data.error.message}`); + } + return data; + }) + .catch(err => { + throw Object.assign( + new Error(`Failed to complete OAuth handshake with Meetup. ${err.message}`), + { response: err.response }, + ); + }); }; -const getIdentity = accessToken => { - try { - const response = HTTP.get( - "https://api.meetup.com/2/members", - {params: {member_id: 'self', access_token: accessToken}}); - return response.data.results && response.data.results[0]; - } catch (err) { +const getIdentity = async accessToken => { + const bodyFormEncoded = toFormUrlencoded({ + member_id: 'self', + access_token: accessToken + }); + + return await fetch('https://api.meetup.com/2/members', { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-type': 'application/x-www-form-urlencoded', + }, + body: bodyFormEncoded, + }).then(data => data.json()) + .then(({results = []}) => results.length && results[0]) + .catch(err => { throw Object.assign( new Error(`Failed to fetch identity from Meetup. ${err.message}`), { response: err.response } ); - } + }); }; - Meetup.retrieveCredential = (credentialToken, credentialSecret) => OAuth.retrieveCredential(credentialToken, credentialSecret); diff --git a/packages/meetup-oauth/package.js b/packages/meetup-oauth/package.js index 83df9f74a3..f678cb4386 100644 --- a/packages/meetup-oauth/package.js +++ b/packages/meetup-oauth/package.js @@ -7,8 +7,8 @@ Package.onUse(api => { api.use('ecmascript'); api.use('oauth2', ['client', 'server']); api.use('oauth', ['client', 'server']); - api.use('http@1.4.4 || 2.0.0', 'server'); api.use('random', 'client'); + api.use('fetch', 'server'); api.use('service-configuration', ['client', 'server']); api.addFiles('meetup_server.js', 'server'); From ef508087bb923cf4c769efacfa845c5d6b14d27f Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Wed, 24 Aug 2022 22:28:24 -0300 Subject: [PATCH 41/99] Change accounts and oauth to use async for Meteor developer account --- .../meteor_developer_server.js | 128 ++++++++++-------- packages/meteor-developer-oauth/package.js | 2 +- 2 files changed, 70 insertions(+), 60 deletions(-) diff --git a/packages/meteor-developer-oauth/meteor_developer_server.js b/packages/meteor-developer-oauth/meteor_developer_server.js index c563ba47e8..0702508a33 100644 --- a/packages/meteor-developer-oauth/meteor_developer_server.js +++ b/packages/meteor-developer-oauth/meteor_developer_server.js @@ -1,7 +1,7 @@ -OAuth.registerService("meteor-developer", 2, null, query => { - const response = getTokens(query); +OAuth.registerService("meteor-developer", 2, null, async query => { + const response = await getTokens(query); const { accessToken } = response; - const identity = getIdentity(accessToken); + const identity = await getIdentity(accessToken); const serviceData = { accessToken: OAuth.sealSecret(accessToken), @@ -23,74 +23,84 @@ OAuth.registerService("meteor-developer", 2, null, query => { }; }); +const toFormUrlencoded = data => { + return Object.entries(data) + .map(([key, value]) => `${key}=${value}`) + .join('&'); +}; + // returns an object containing: // - accessToken // - expiresIn: lifetime of token in seconds // - refreshToken, if this is the first authorization request and we got a // refresh token from the server -const getTokens = query => { +const getTokens = async query => { const config = ServiceConfiguration.configurations.findOne({ - service: 'meteor-developer' + service: 'meteor-developer', }); - if (!config) + if (!config) { throw new ServiceConfiguration.ConfigError(); + } - let response; - try { - response = HTTP.post( - MeteorDeveloperAccounts._server + "/oauth2/token", { - params: { - grant_type: "authorization_code", - code: query.code, - client_id: config.clientId, - client_secret: OAuth.openSecret(config.secret), - redirect_uri: OAuth._redirectUri('meteor-developer', config) - } + const bodyFormEncoded = toFormUrlencoded({ + grant_type: 'authorization_code', + code: query.code, + client_id: config.clientId, + client_secret: OAuth.openSecret(config.secret), + redirect_uri: OAuth._redirectUri('meteor-developer', config), + }); + + return await fetch(MeteorDeveloperAccounts._server + '/oauth2/token', { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-type': 'application/x-www-form-urlencoded', + }, + body: bodyFormEncoded, + }) + .then(data => data.json()) + .then(data => { + if (data.error) { + throw new Error( + 'Failed to complete OAuth handshake with Meteor developer accounts. ' + + (data ? data.error : + 'No response data'), + ); } - ); - } catch (err) { - throw Object.assign( - new Error( - "Failed to complete OAuth handshake with Meteor developer accounts. " - + err.message - ), - {response: err.response} - ); - } - - if (! response.data || response.data.error) { - // if the http response was a json object with an error attribute - throw new Error( - "Failed to complete OAuth handshake with Meteor developer accounts. " + - (response.data ? response.data.error : - "No response data") - ); - } else { - return { - accessToken: response.data.access_token, - refreshToken: response.data.refresh_token, - expiresIn: response.data.expires_in - }; - } + return { + accessToken: data.access_token, + refreshToken: data.refresh_token, + expiresIn: data.expires_in, + }; + }) + .catch(err => { + throw Object.assign( + new Error( + `Failed to complete OAuth handshake with Meteor developer accounts. ${err.message}` + ), + { response: err.response }, + ); + }); }; -const getIdentity = accessToken => { - try { - return HTTP.get( - `${MeteorDeveloperAccounts._server}/api/v1/identity`, - { - headers: { Authorization: `Bearer ${accessToken}`} - } - ).data; - } catch (err) { - throw Object.assign( - new Error("Failed to fetch identity from Meteor developer accounts. " + - err.message), - {response: err.response} - ); - } +const getIdentity = async accessToken => { + return fetch( + `${MeteorDeveloperAccounts._server}/api/v1/identity`, + { + method: 'GET', + headers: { Authorization: `Bearer ${accessToken}` }, + }, + ) + .then(data => data.json()) + .catch(err => { + throw Object.assign( + new Error('Failed to fetch identity from Meteor developer accounts. ' + + err.message), + { response: err.response }, + ); + }); }; -MeteorDeveloperAccounts.retrieveCredential = - (credentialToken, credentialSecret) => +MeteorDeveloperAccounts.retrieveCredential = + (credentialToken, credentialSecret) => OAuth.retrieveCredential(credentialToken, credentialSecret); diff --git a/packages/meteor-developer-oauth/package.js b/packages/meteor-developer-oauth/package.js index b1463542d5..112964d289 100644 --- a/packages/meteor-developer-oauth/package.js +++ b/packages/meteor-developer-oauth/package.js @@ -6,7 +6,7 @@ Package.describe({ Package.onUse(api => { api.use('oauth2', ['client', 'server']); api.use('oauth', ['client', 'server']); - api.use('http@1.4.4 || 2.0.0', ['server']); + api.use('fetch', ['server']); api.use(['ecmascript', 'service-configuration'], ['client', 'server']); api.use('random', 'client'); From b55ccd7cb9552205410d4182c671795644491bba Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Thu, 25 Aug 2022 13:48:27 -0300 Subject: [PATCH 42/99] test(adjusted test naming) --- .../modules-runtime/modules-runtime-tests.js | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/modules-runtime/modules-runtime-tests.js b/packages/modules-runtime/modules-runtime-tests.js index 44da8b9a34..4a569e5253 100644 --- a/packages/modules-runtime/modules-runtime-tests.js +++ b/packages/modules-runtime/modules-runtime-tests.js @@ -4,19 +4,23 @@ Tinytest.add('modules', function (test) { test.equal(typeof require, 'function'); }); -Tinytest.add('modules - meteor/ - error', function (test) { +Tinytest.add('modules.throwStandardError', function (test) { const require = meteorInstall(); test.throws(require('meteor/foo'), /Cannot find package "meteor". Try "meteor add meteor"./); }); -Tinytest.add('modules - client calling server', function (test) { - const require = meteorInstall(); - test.throws(require('./../server/main.js'), 'Unable to import on the client a module from a server directory: ' + id + ' \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories`' - ); -}); +if (Meteor.isClient) { + Tinytest.add('modules.throwClientError', function (test) { + const require = meteorInstall(); + test.throws(require('./../server/main.js'), 'Unable to import on the client a module from a server directory: ../server/main.js \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories`' + ); + }); +} -Tinytest.add('modules - server calling client', function (test) { - const require = meteorInstall(); - test.throws(require('./../client/main.js'), 'Unable to import on the server a module from a client directory: ' + id + ' \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories`' - ); -}); +if (Meteor.isServer) { + Tinytest.add('modules.throwServerError', function (test) { + const require = meteorInstall(); + test.throws(require('./../client/main.js'), 'Unable to import on the server a module from a client directory: ../client/main.js \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories`' + ); + }); +} From 550e752cb3b4032c27ff97fb1f9c76b50dfe0b2e Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Thu, 25 Aug 2022 15:37:42 -0300 Subject: [PATCH 43/99] tests(runtime-errors): adjusted the ci --- .../modules-runtime/modules-runtime-tests.js | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/modules-runtime/modules-runtime-tests.js b/packages/modules-runtime/modules-runtime-tests.js index 4a569e5253..d87150d995 100644 --- a/packages/modules-runtime/modules-runtime-tests.js +++ b/packages/modules-runtime/modules-runtime-tests.js @@ -5,22 +5,30 @@ Tinytest.add('modules', function (test) { }); Tinytest.add('modules.throwStandardError', function (test) { - const require = meteorInstall(); - test.throws(require('meteor/foo'), /Cannot find package "meteor". Try "meteor add meteor"./); + var require = meteorInstall(); + test.throws(() => { + require('meteor/foo') + }, 'Cannot find package "foo". Try "meteor add foo".'); }); if (Meteor.isClient) { Tinytest.add('modules.throwClientError', function (test) { - const require = meteorInstall(); - test.throws(require('./../server/main.js'), 'Unable to import on the client a module from a server directory: ../server/main.js \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories`' + var require = meteorInstall(); + test.throws(() => { + require('./../server/main.js') + }, + 'Unable to import on the server a module from a client directory: "./../server/main.js" \n' + + ' (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' ); }); } if (Meteor.isServer) { Tinytest.add('modules.throwServerError', function (test) { - const require = meteorInstall(); - test.throws(require('./../client/main.js'), 'Unable to import on the server a module from a client directory: ../client/main.js \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories`' + var require = meteorInstall(); + test.throws(() => { + require('./../client/main.js') + }, "Cannot find module './../client/main.js'" ); }); } From 51d4d3dbc172afaa7ea3d83cfdfe0a46f6b46a94 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Thu, 25 Aug 2022 15:38:35 -0300 Subject: [PATCH 44/99] chore(runtime-errors): adjusted error text --- packages/modules-runtime/errors/cannotImportFrom.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/modules-runtime/errors/cannotImportFrom.js b/packages/modules-runtime/errors/cannotImportFrom.js index 967517282b..313cf3f222 100644 --- a/packages/modules-runtime/errors/cannotImportFrom.js +++ b/packages/modules-runtime/errors/cannotImportFrom.js @@ -24,8 +24,7 @@ cannotImport = function (id) { var fromClient = function () { return new Error( - 'Unable to import on the client a module from a server directory: ' + - id + ' \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' + 'Unable to import on the client a module from a server directory: "' + id + '" \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' ); }; @@ -33,8 +32,7 @@ cannotImport = function (id) { var fromServer = function () { return new Error( - 'Unable to import on the server a module from a client directory: ' + - id + ' \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' + 'Unable to import on the server a module from a client directory: "' + id + '" \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' ); }; From 009cbc4baad13de38624c666ab2d2c7cd29c2d17 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 25 Aug 2022 17:53:19 -0300 Subject: [PATCH 45/99] Change accounts and oauth to use async for Twitter account --- packages/oauth1/oauth1_binding.js | 101 +++++++++++++++++------ packages/oauth1/oauth1_server.js | 6 +- packages/oauth1/oauth1_tests.js | 8 +- packages/oauth1/package.js | 5 +- packages/twitter-oauth/twitter_server.js | 6 +- 5 files changed, 86 insertions(+), 40 deletions(-) diff --git a/packages/oauth1/oauth1_binding.js b/packages/oauth1/oauth1_binding.js index aab9629605..45bdb07f80 100644 --- a/packages/oauth1/oauth1_binding.js +++ b/packages/oauth1/oauth1_binding.js @@ -2,6 +2,14 @@ import crypto from 'crypto'; import querystring from 'querystring'; import urlModule from 'url'; +// TODO Create helper for use in all oauth packages + +const toFormUrlencoded = data => { + return Object.entries(data) + .map(([key, value]) => `${key}=${value}`) + .join('&'); +}; + // An OAuth1 wrapper around http calls which helps get tokens and // takes care of HTTP headers // @@ -19,12 +27,12 @@ export class OAuth1Binding { this._urls = urls; } - prepareRequestToken(callbackUrl) { + async prepareRequestToken(callbackUrl) { const headers = this._buildHeader({ oauth_callback: callbackUrl }); - const response = this._call('POST', this._urls.requestToken, headers); + const response = await this._call({method: 'POST', url: this._urls.requestToken, headers}); const tokens = querystring.parse(response.content); if (! tokens.oauth_callback_confirmed) @@ -35,7 +43,7 @@ export class OAuth1Binding { this.requestTokenSecret = tokens.oauth_token_secret; } - prepareAccessToken(query, requestTokenSecret) { + async prepareAccessToken(query, requestTokenSecret) { // support implementations that use request token secrets. This is // read by this._call. // @@ -50,7 +58,7 @@ export class OAuth1Binding { oauth_verifier: query.oauth_verifier }); - const response = this._call('POST', this._urls.accessToken, headers); + const response = await this._call({ method: 'POST', url: this._urls.accessToken, headers }); const tokens = querystring.parse(response.content); if (! tokens.oauth_token || ! tokens.oauth_token_secret) { @@ -66,7 +74,7 @@ export class OAuth1Binding { this.accessTokenSecret = tokens.oauth_token_secret; } - call(method, url, params, callback) { + async callAsync(method, url, params, callback) { const headers = this._buildHeader({ oauth_token: this.accessToken }); @@ -75,14 +83,29 @@ export class OAuth1Binding { params = {}; } - return this._call(method, url, headers, params, callback); + return this._call({ method, url, headers, params, callback }); + } + + async getAsync(url, params, callback) { + return this.callAsync('GET', url, params, callback); + } + + async postAsync(url, params, callback) { + return this.callAsync('POST', url, params, callback); + } + + call(method, url, params, callback) { + // Require changes when remove Fibers. Exposed to public api. + return Promise.await(this.callAsync(method, url, params, callback)); } get(url, params, callback) { + // Require changes when remove Fibers. Exposed to public api. return this.call('GET', url, params, callback); } post(url, params, callback) { + // Require changes when remove Fibers. Exposed to public api. return this.call('POST', url, params, callback); } @@ -118,7 +141,7 @@ export class OAuth1Binding { return crypto.createHmac('SHA1', signingKey).update(signatureBase).digest('base64'); }; - _call(method, url, headers = {}, params = {}, callback) { + async _call({method, url, headers = {}, params = {}, callback}) { // all URLs to be functions to support parameters/customization if(typeof url === "function") { url = url(this); @@ -141,29 +164,55 @@ export class OAuth1Binding { // Make a authorization string according to oauth1 spec const authString = this._getAuthHeaderString(headers); - // Make signed request - try { - const response = HTTP.call(method, url, { - params, + return fetch( + method.toUpperCase() === 'POST' ? url : `${url}?${toFormUrlencoded(params)}`, + { + method, headers: { - Authorization: authString + Authorization: authString, + ...(method.toUpperCase() === 'POST' ? { 'Content-Type': 'application/x-www-form-urlencoded' } : {}), + }, + ...(method.toUpperCase() === 'POST' ? { body: toFormUrlencoded(params) } : {}), + } + ) + .then((res) => + res.text().then((content) => { + const responseHeaders = Array.from(res.headers.entries()).reduce( + (acc, [key, val]) => { + return { ...acc, [key.toLowerCase()]: val }; + }, + {} + ); + const data = responseHeaders['content-type'].includes('application/json') ? + JSON.parse(content) : undefined; + return { + content: data ? '' : content, + data, + headers: { ...responseHeaders, nonce: headers.oauth_nonce }, + redirected: res.redirected, + ok: res.ok, + statusCode: res.status, + }; + }) + ) + .then((response) => { + if (callback) { + callback(undefined, response); } - }, callback && ((error, response) => { - if (! error) { - response.nonce = headers.oauth_nonce; + return response; + }) + .catch((err) => { + if (callback) { + callback(err); } - callback(error, response); - })); - // We store nonce so that JWTs can be validated - if (response) - response.nonce = headers.oauth_nonce; - return response; - } catch (err) { - throw Object.assign(new Error(`Failed to send OAuth1 request to ${url}. ${err.message}`), - {response: err.response}); - } - }; + console.log({ err }); + throw Object.assign( + new Error(`Failed to send OAuth1 request to ${url}. ${err.message}`), + { response: err.response } + ); + }); + } _encodeHeader(header) { return Object.keys(header).reduce((memo, key) => { diff --git a/packages/oauth1/oauth1_server.js b/packages/oauth1/oauth1_server.js index 8ad132c198..d0c8e3732a 100644 --- a/packages/oauth1/oauth1_server.js +++ b/packages/oauth1/oauth1_server.js @@ -45,7 +45,7 @@ OAuth._requestHandlers['1'] = async (service, query, res) => { }); // Get a request token to start auth process - oauthBinding.prepareRequestToken(callbackUrl); + await oauthBinding.prepareRequestToken(callbackUrl); // Keep track of request token so we can verify it on the next step OAuth._storeRequestToken( @@ -91,10 +91,10 @@ OAuth._requestHandlers['1'] = async (service, query, res) => { // subsequent call to the `login` method will be immediate. // Get the access token for signing requests - oauthBinding.prepareAccessToken(query, requestTokenInfo.requestTokenSecret); + await oauthBinding.prepareAccessToken(query, requestTokenInfo.requestTokenSecret); // Run service-specific handler. - const oauthResult = service.handleOauthRequest( + const oauthResult = await service.handleOauthRequest( oauthBinding, { query: query }); const credentialToken = OAuth._credentialTokenFromQuery(query); diff --git a/packages/oauth1/oauth1_tests.js b/packages/oauth1/oauth1_tests.js index c8a65fa86e..d4b283a97a 100644 --- a/packages/oauth1/oauth1_tests.js +++ b/packages/oauth1/oauth1_tests.js @@ -17,8 +17,8 @@ const testPendingCredential = async (test, method) => { authenticate: "https://example.com/oauth/authenticate" }; - OAuth1Binding.prototype.prepareRequestToken = () => {}; - OAuth1Binding.prototype.prepareAccessToken = function() { + OAuth1Binding.prototype.prepareRequestToken = async () => {}; + OAuth1Binding.prototype.prepareAccessToken = async function() { this.accessToken = twitterfooAccessToken; this.accessTokenSecret = twitterfooAccessTokenSecret; }; @@ -27,7 +27,7 @@ const testPendingCredential = async (test, method) => { try { // register a fake login service - OAuth.registerService(serviceName, 1, urls, query => ({ + OAuth.registerService(serviceName, 1, urls, async query => ({ serviceData: { id: twitterfooId, screenName: twitterfooName, @@ -100,7 +100,7 @@ Tinytest.addAsync("oauth1 - pendingCredential is stored and can be retrieved (wi await testPendingCredential(test, "POST"); }); -Tinytest.add("oauth1 - pendingCredential is stored and can be retrieved (with oauth encryption)", async test => { +Tinytest.addAsync("oauth1 - pendingCredential is stored and can be retrieved (with oauth encryption)", async test => { try { OAuthEncryption.loadKey(Buffer.from([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]).toString("base64")); await testPendingCredential(test, "GET"); diff --git a/packages/oauth1/package.js b/packages/oauth1/package.js index 550fdc2448..dcbf9b4642 100644 --- a/packages/oauth1/package.js +++ b/packages/oauth1/package.js @@ -8,10 +8,7 @@ Package.onUse(api => { api.use('random'); api.use('service-configuration', ['client', 'server']); api.use('oauth', ['client', 'server']); - api.use([ - 'check', - 'http@1.4.4 || 2.0.0' - ], 'server'); + api.use(['check', 'fetch'], 'server'); api.use('mongo'); diff --git a/packages/twitter-oauth/twitter_server.js b/packages/twitter-oauth/twitter_server.js index d597f0db1e..393c3a3da3 100644 --- a/packages/twitter-oauth/twitter_server.js +++ b/packages/twitter-oauth/twitter_server.js @@ -15,9 +15,9 @@ var urls = { // https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials Twitter.whitelistedFields = ['profile_image_url', 'profile_image_url_https', 'lang', 'email']; -OAuth.registerService('twitter', 1, urls, function(oauthBinding) { - var identity = oauthBinding.get('https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true').data; - +OAuth.registerService('twitter', 1, urls, async function(oauthBinding) { + const response = await oauthBinding.getAsync('https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true'); + const { data: identity } = response; var serviceData = { id: identity.id_str, screenName: identity.screen_name, From 172caa7bf35b4f45f30858a30d9cb16b94264c10 Mon Sep 17 00:00:00 2001 From: eduwr Date: Thu, 25 Aug 2022 22:47:57 -0300 Subject: [PATCH 46/99] convert getTokenResponse method to async - use fetch instead of Meteor.HTTP method --- packages/facebook-oauth/facebook_server.js | 86 +++++++++++++--------- packages/facebook-oauth/package.js | 1 + 2 files changed, 51 insertions(+), 36 deletions(-) diff --git a/packages/facebook-oauth/facebook_server.js b/packages/facebook-oauth/facebook_server.js index c2964cf842..7398e27470 100644 --- a/packages/facebook-oauth/facebook_server.js +++ b/packages/facebook-oauth/facebook_server.js @@ -1,6 +1,6 @@ Facebook = {}; import crypto from 'crypto'; -import { Accounts } from 'meteor/accounts-base'; +import {Accounts} from 'meteor/accounts-base'; const API_VERSION = Meteor.settings?.public?.packages?.['facebook-oauth']?.apiVersion || '13.0'; @@ -34,10 +34,10 @@ Accounts.registerLoginHandler(request => { return Accounts.updateOrCreateUserFromExternalService('facebook', facebookData.serviceData, facebookData.options); }); -OAuth.registerService('facebook', 2, null, query => { - const response = getTokenResponse(query); - const { accessToken } = response; - const { expiresIn } = response; +OAuth.registerService('facebook', 2, null, async query => { + const response = await getTokenResponse(query); + const {accessToken} = response; + const {expiresIn} = response; return Facebook.handleAuthFromAccessToken(accessToken, (+new Date) + (1000 * expiresIn)); }); @@ -61,50 +61,62 @@ function getAbsoluteUrlOptions(query) { } } -// returns an object containing: -// - accessToken -// - expiresIn: lifetime of token in seconds -const getTokenResponse = query => { +/** + * @typedef {Object} UserAccessToken + * @property {string} accessToken - User access Token + * @property {number} expiresIn - lifetime of token in seconds + */ +/** + * @async + * @function getTokenResponse + * @param {Object} query - An object with the code. + * @returns {Promise} - Promise with an Object containing the accessToken and expiresIn (lifetime of token in seconds) + */ +const getTokenResponse = async query => { const config = ServiceConfiguration.configurations.findOne({service: 'facebook'}); if (!config) throw new ServiceConfiguration.ConfigError(); - let responseContent; try { - const absoluteUrlOptions = getAbsoluteUrlOptions(query); const redirectUri = OAuth._redirectUri('facebook', config, undefined, absoluteUrlOptions); - // Request an access token - responseContent = HTTP.get( - `https://graph.facebook.com/v${API_VERSION}/oauth/access_token`, { - params: { - client_id: config.appId, - redirect_uri: redirectUri, - client_secret: OAuth.openSecret(config.secret), - code: query.code - } - }).data; - } catch (err) { + + const params = new URLSearchParams(); + + params.append("client_id", config.appId) + params.append("redirect_uri", redirectUri) + params.append("client_secret", OAuth.openSecret(config.secret)) + params.append("code", query.code) + + const uri = `https://graph.facebook.com/v${API_VERSION}/oauth/access_token?${params.toString()}` + + const response = await fetch(uri, { + method: "GET", + headers: { + Accept: 'application/json', + 'Content-type': 'application/x-www-form-urlencoded', + }, + }) + + const data = await response.json(); + + const fbAccessToken = data.access_token; + const fbExpires = data.expires_in; + + return { + accessToken: fbAccessToken, + expiresIn: fbExpires + }; + } catch (e) { throw Object.assign( new Error(`Failed to complete OAuth handshake with Facebook. ${err.message}`), - { response: err.response }, + {response: err.response}, ); } - - const fbAccessToken = responseContent.access_token; - const fbExpires = responseContent.expires_in; - - if (!fbAccessToken) { - throw new Error("Failed to complete OAuth handshake with facebook " + - `-- can't find access token in HTTP response. ${responseContent}`); - } - return { - accessToken: fbAccessToken, - expiresIn: fbExpires - }; }; const getIdentity = (accessToken, fields) => { + console.log({accessToken}) const config = ServiceConfiguration.configurations.findOne({service: 'facebook'}); if (!config) throw new ServiceConfiguration.ConfigError(); @@ -115,6 +127,8 @@ const getIdentity = (accessToken, fields) => { hmac.update(accessToken); try { + + //Replace HTTP to fetch here return HTTP.get(`https://graph.facebook.com/v${API_VERSION}/me`, { params: { access_token: accessToken, @@ -125,7 +139,7 @@ const getIdentity = (accessToken, fields) => { } catch (err) { throw Object.assign( new Error(`Failed to fetch identity from Facebook. ${err.message}`), - { response: err.response }, + {response: err.response}, ); } }; diff --git a/packages/facebook-oauth/package.js b/packages/facebook-oauth/package.js index 67536065cb..79db14954c 100644 --- a/packages/facebook-oauth/package.js +++ b/packages/facebook-oauth/package.js @@ -8,6 +8,7 @@ Package.onUse(api => { api.use('oauth2', ['client', 'server']); api.use('oauth', ['client', 'server']); api.use('http@1.4.4 || 2.0.0', ['server']); + api.use('fetch', ['server']); api.use('random', 'client'); api.use('service-configuration', ['client', 'server']); From 24845c482569ae22b5d0deabd62d49d15d30ffad Mon Sep 17 00:00:00 2001 From: eduwr Date: Thu, 25 Aug 2022 23:03:34 -0300 Subject: [PATCH 47/99] convert getIdentity method to async - use fetch instead of Meteor.HTTP method --- packages/facebook-oauth/facebook_server.js | 36 +++++++++++++--------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/facebook-oauth/facebook_server.js b/packages/facebook-oauth/facebook_server.js index 7398e27470..08dd582549 100644 --- a/packages/facebook-oauth/facebook_server.js +++ b/packages/facebook-oauth/facebook_server.js @@ -4,13 +4,13 @@ import {Accounts} from 'meteor/accounts-base'; const API_VERSION = Meteor.settings?.public?.packages?.['facebook-oauth']?.apiVersion || '13.0'; -Facebook.handleAuthFromAccessToken = (accessToken, expiresAt) => { +Facebook.handleAuthFromAccessToken = async (accessToken, expiresAt) => { // include basic fields from facebook // https://developers.facebook.com/docs/facebook-login/permissions/ const whitelisted = ['id', 'email', 'name', 'first_name', 'last_name', 'middle_name', 'name_format', 'picture', 'short_name']; - const identity = getIdentity(accessToken, whitelisted); + const identity = await getIdentity(accessToken, whitelisted); const fields = {}; whitelisted.forEach(field => fields[field] = identity[field]); @@ -39,7 +39,7 @@ OAuth.registerService('facebook', 2, null, async query => { const {accessToken} = response; const {expiresIn} = response; - return Facebook.handleAuthFromAccessToken(accessToken, (+new Date) + (1000 * expiresIn)); + return await Facebook.handleAuthFromAccessToken(accessToken, (+new Date) + (1000 * expiresIn)); }); function getAbsoluteUrlOptions(query) { @@ -94,11 +94,10 @@ const getTokenResponse = async query => { method: "GET", headers: { Accept: 'application/json', - 'Content-type': 'application/x-www-form-urlencoded', }, }) - const data = await response.json(); + const data = await response.json(); const fbAccessToken = data.access_token; const fbExpires = data.expires_in; @@ -115,8 +114,7 @@ const getTokenResponse = async query => { } }; -const getIdentity = (accessToken, fields) => { - console.log({accessToken}) +const getIdentity = async (accessToken, fields) => { const config = ServiceConfiguration.configurations.findOne({service: 'facebook'}); if (!config) throw new ServiceConfiguration.ConfigError(); @@ -128,14 +126,22 @@ const getIdentity = (accessToken, fields) => { try { - //Replace HTTP to fetch here - return HTTP.get(`https://graph.facebook.com/v${API_VERSION}/me`, { - params: { - access_token: accessToken, - appsecret_proof: hmac.digest('hex'), - fields: fields.join(",") - } - }).data; + const params = new URLSearchParams(); + + params.append("access_token", accessToken) + params.append("appsecret_proof", hmac.digest('hex')) + params.append("fields", fields.join(",")) + + const uri = `https://graph.facebook.com/v${API_VERSION}/me?${params.toString()}` + + const response = await fetch(uri, { + method: "GET", + headers: { + Accept: 'application/json', + }, + }) + + return response.json(); } catch (err) { throw Object.assign( new Error(`Failed to fetch identity from Facebook. ${err.message}`), From 90e19a2f0713f50371f7b56df7417b7784ff2bb5 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 26 Aug 2022 09:32:50 -0300 Subject: [PATCH 48/99] Change accounts and oauth to use async for Weibo account --- packages/oauth/oauth_server.js | 20 +++++++ packages/oauth/package.js | 1 + packages/weibo-oauth/package.js | 2 +- packages/weibo-oauth/weibo_server.js | 82 ++++++++++++++-------------- 4 files changed, 64 insertions(+), 41 deletions(-) diff --git a/packages/oauth/oauth_server.js b/packages/oauth/oauth_server.js index ce2524349b..9bad505320 100644 --- a/packages/oauth/oauth_server.js +++ b/packages/oauth/oauth_server.js @@ -473,3 +473,23 @@ OAuth.openSecrets = (serviceData, userId) => { ); return result; }; + +OAuth._fetch = async ( + url, + method = 'GET', + { headers = {}, queryParams = {}, body, ...options } = {} +) => { + const urlWithParams = new URL(url); + + Object.entries(queryParams).forEach(([key, value]) => { + urlWithParams.searchParams.set(key, `${value}`); + }); + + const requestOptions = { + method: method.toUpperCase(), + headers, + ...(body ? { body } : {}), + ...options, + }; + return fetch(urlWithParams.toString(), requestOptions); +}; diff --git a/packages/oauth/package.js b/packages/oauth/package.js index 8962aeb282..421be1d506 100644 --- a/packages/oauth/package.js +++ b/packages/oauth/package.js @@ -11,6 +11,7 @@ Package.onUse(api => { api.use(['reload', 'base64'], 'client'); api.use('oauth-encryption', 'server', {weak: true}); + api.use('fetch', 'server'); api.export('OAuth'); diff --git a/packages/weibo-oauth/package.js b/packages/weibo-oauth/package.js index 02cea0b9c6..df3b3458f0 100644 --- a/packages/weibo-oauth/package.js +++ b/packages/weibo-oauth/package.js @@ -7,7 +7,7 @@ Package.onUse(api => { api.use('oauth1', ['client', 'server']); api.use('oauth', ['client', 'server']); api.use('random', 'client'); - api.use('http@1.4.4 || 2.0.0', 'server'); + api.use('fetch', 'server'); api.use(['service-configuration', 'ecmascript'], ['client', 'server']); api.addFiles('weibo_client.js', 'client'); diff --git a/packages/weibo-oauth/weibo_server.js b/packages/weibo-oauth/weibo_server.js index 539022aa8d..24d56438fd 100644 --- a/packages/weibo-oauth/weibo_server.js +++ b/packages/weibo-oauth/weibo_server.js @@ -1,8 +1,8 @@ Weibo = {}; -OAuth.registerService('weibo', 2, null, query => { +OAuth.registerService('weibo', 2, null, async query => { - const response = getTokenResponse(query); + const response = await getTokenResponse(query); const uid = parseInt(response.uid, 10); // different parts of weibo's api seem to expect numbers, or strings @@ -11,7 +11,7 @@ OAuth.registerService('weibo', 2, null, query => { throw new Error(`Expected 'uid' to parse to an integer: ${JSON.stringify(response)}`); } - const identity = getIdentity(response.access_token, uid); + const identity = await getIdentity(response.access_token, uid); return { serviceData: { @@ -31,46 +31,48 @@ OAuth.registerService('weibo', 2, null, query => { // - uid // - access_token // - expires_in: lifetime of this token in seconds (5 years(!) right now) -const getTokenResponse = query => { - const config = ServiceConfiguration.configurations.findOne({service: 'weibo'}); - if (!config) - throw new ServiceConfiguration.ConfigError(); +const getTokenResponse = async (query) => { + const config = ServiceConfiguration.configurations.findOne({ + service: 'weibo', + }); + if (!config) throw new ServiceConfiguration.ConfigError(); - let response; - try { - response = HTTP.post( - "https://api.weibo.com/oauth2/access_token", {params: { - code: query.code, - client_id: config.clientId, - client_secret: OAuth.openSecret(config.secret), - redirect_uri: OAuth._redirectUri('weibo', config, null, {replaceLocalhost: true}), - grant_type: 'authorization_code' - }}); - } catch (err) { - throw Object.assign(new Error(`Failed to complete OAuth handshake with Weibo. ${err.message}`), - {response: err.response}); - } - - // result.headers["content-type"] is 'text/plain;charset=UTF-8', so - // the http package doesn't automatically populate result.data - response.data = JSON.parse(response.content); - - if (response.data.error) { // if the http response was a json object with an error attribute - throw new Error(`Failed to complete OAuth handshake with Weibo. ${response.data.error}`); - } else { - return response.data; - } + return OAuth._fetch('https://api.weibo.com/oauth2/access_token', 'POST', { + queryParams: { + code: query.code, + client_id: config.clientId, + client_secret: OAuth.openSecret(config.secret), + redirect_uri: OAuth._redirectUri('weibo', config, null, { + replaceLocalhost: true, + }), + grant_type: 'authorization_code', + }, + }) + .then((res) => res.json()) + .catch((err) => { + throw Object.assign( + new Error( + `Failed to complete OAuth handshake with Weibo. ${err.message}` + ), + { response: err.response } + ); + }); }; -const getIdentity = (accessToken, userId) => { - try { - return HTTP.get( - "https://api.weibo.com/2/users/show.json", - {params: {access_token: accessToken, uid: userId}}).data; - } catch (err) { - throw Object.assign(new Error("Failed to fetch identity from Weibo. " + err.message), - {response: err.response}); - } +const getIdentity = async (accessToken, userId) => { + return OAuth._fetch('https://api.weibo.com/2/users/show.json', 'GET', { + queryParams: { + access_token: accessToken, + uid: userId, + }, + }) + .then((res) => res.json()) + .catch((err) => { + throw Object.assign( + new Error('Failed to fetch identity from Weibo. ' + err.message), + { response: err.response } + ); + }); }; Weibo.retrieveCredential = (credentialToken, credentialSecret) => From 16b2f47d1e7c2d7e3082975a9eb9eea484bc30d8 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 26 Aug 2022 10:35:16 -0300 Subject: [PATCH 49/99] Create method Oauth._fetch to use in Oauth packages and centralize calls. --- .../meteor_developer_server.js | 62 +++++++++---------- packages/meteor-developer-oauth/package.js | 1 - packages/oauth/oauth_server.js | 14 ++++- packages/oauth1/oauth1_binding.js | 29 +++------ packages/oauth1/package.js | 2 +- packages/weibo-oauth/package.js | 1 - 6 files changed, 51 insertions(+), 58 deletions(-) diff --git a/packages/meteor-developer-oauth/meteor_developer_server.js b/packages/meteor-developer-oauth/meteor_developer_server.js index 0702508a33..57dd193ae1 100644 --- a/packages/meteor-developer-oauth/meteor_developer_server.js +++ b/packages/meteor-developer-oauth/meteor_developer_server.js @@ -23,18 +23,12 @@ OAuth.registerService("meteor-developer", 2, null, async query => { }; }); -const toFormUrlencoded = data => { - return Object.entries(data) - .map(([key, value]) => `${key}=${value}`) - .join('&'); -}; - // returns an object containing: // - accessToken // - expiresIn: lifetime of token in seconds // - refreshToken, if this is the first authorization request and we got a // refresh token from the server -const getTokens = async query => { +const getTokens = async (query) => { const config = ServiceConfiguration.configurations.findOne({ service: 'meteor-developer', }); @@ -42,29 +36,31 @@ const getTokens = async query => { throw new ServiceConfiguration.ConfigError(); } - const bodyFormEncoded = toFormUrlencoded({ + const body = OAuth._addValuesToQueryParams({ grant_type: 'authorization_code', code: query.code, client_id: config.clientId, client_secret: OAuth.openSecret(config.secret), redirect_uri: OAuth._redirectUri('meteor-developer', config), - }); + }).toString(); - return await fetch(MeteorDeveloperAccounts._server + '/oauth2/token', { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-type': 'application/x-www-form-urlencoded', - }, - body: bodyFormEncoded, - }) - .then(data => data.json()) - .then(data => { + return OAuth._fetch( + MeteorDeveloperAccounts._server + '/oauth2/token', + 'POST', + { + headers: { + Accept: 'application/json', + 'Content-type': 'application/x-www-form-urlencoded', + }, + body, + } + ) + .then((data) => data.json()) + .then((data) => { if (data.error) { throw new Error( 'Failed to complete OAuth handshake with Meteor developer accounts. ' + - (data ? data.error : - 'No response data'), + (data ? data.error : 'No response data') ); } return { @@ -73,30 +69,32 @@ const getTokens = async query => { expiresIn: data.expires_in, }; }) - .catch(err => { + .catch((err) => { throw Object.assign( new Error( `Failed to complete OAuth handshake with Meteor developer accounts. ${err.message}` ), - { response: err.response }, + { response: err.response } ); }); }; -const getIdentity = async accessToken => { - return fetch( +const getIdentity = async (accessToken) => { + return OAuth._fetch( `${MeteorDeveloperAccounts._server}/api/v1/identity`, + 'GET', { - method: 'GET', headers: { Authorization: `Bearer ${accessToken}` }, - }, + } ) - .then(data => data.json()) - .catch(err => { + .then((data) => data.json()) + .catch((err) => { throw Object.assign( - new Error('Failed to fetch identity from Meteor developer accounts. ' + - err.message), - { response: err.response }, + new Error( + 'Failed to fetch identity from Meteor developer accounts. ' + + err.message + ), + { response: err.response } ); }); }; diff --git a/packages/meteor-developer-oauth/package.js b/packages/meteor-developer-oauth/package.js index 112964d289..db38232d52 100644 --- a/packages/meteor-developer-oauth/package.js +++ b/packages/meteor-developer-oauth/package.js @@ -6,7 +6,6 @@ Package.describe({ Package.onUse(api => { api.use('oauth2', ['client', 'server']); api.use('oauth', ['client', 'server']); - api.use('fetch', ['server']); api.use(['ecmascript', 'service-configuration'], ['client', 'server']); api.use('random', 'client'); diff --git a/packages/oauth/oauth_server.js b/packages/oauth/oauth_server.js index 9bad505320..1b591a455b 100644 --- a/packages/oauth/oauth_server.js +++ b/packages/oauth/oauth_server.js @@ -474,6 +474,16 @@ OAuth.openSecrets = (serviceData, userId) => { return result; }; +OAuth._addValuesToQueryParams = ( + values = {}, + queryParams = new URLSearchParams() +) => { + Object.entries(values).forEach(([key, value]) => { + queryParams.set(key, `${value}`); + }); + return queryParams; +}; + OAuth._fetch = async ( url, method = 'GET', @@ -481,9 +491,7 @@ OAuth._fetch = async ( ) => { const urlWithParams = new URL(url); - Object.entries(queryParams).forEach(([key, value]) => { - urlWithParams.searchParams.set(key, `${value}`); - }); + OAuth._addValuesToQueryParams(queryParams, urlWithParams.searchParams); const requestOptions = { method: method.toUpperCase(), diff --git a/packages/oauth1/oauth1_binding.js b/packages/oauth1/oauth1_binding.js index 45bdb07f80..015553611e 100644 --- a/packages/oauth1/oauth1_binding.js +++ b/packages/oauth1/oauth1_binding.js @@ -2,14 +2,6 @@ import crypto from 'crypto'; import querystring from 'querystring'; import urlModule from 'url'; -// TODO Create helper for use in all oauth packages - -const toFormUrlencoded = data => { - return Object.entries(data) - .map(([key, value]) => `${key}=${value}`) - .join('&'); -}; - // An OAuth1 wrapper around http calls which helps get tokens and // takes care of HTTP headers // @@ -165,18 +157,15 @@ export class OAuth1Binding { // Make a authorization string according to oauth1 spec const authString = this._getAuthHeaderString(headers); // Make signed request - return fetch( - method.toUpperCase() === 'POST' ? url : `${url}?${toFormUrlencoded(params)}`, - { - method, - headers: { - Authorization: authString, - ...(method.toUpperCase() === 'POST' ? { 'Content-Type': 'application/x-www-form-urlencoded' } : {}), - }, - ...(method.toUpperCase() === 'POST' ? { body: toFormUrlencoded(params) } : {}), - } - ) - .then((res) => + return OAuth._fetch(url, method, { + headers: { + Authorization: authString, + ...(method.toUpperCase() === 'POST' ? { 'Content-Type': 'application/x-www-form-urlencoded' } : {}) + }, + ...(method.toUpperCase() === 'POST' ? + { body: OAuth._addValuesToQueryParams(params).toString() } + : { queryParams: params }) + }).then((res) => res.text().then((content) => { const responseHeaders = Array.from(res.headers.entries()).reduce( (acc, [key, val]) => { diff --git a/packages/oauth1/package.js b/packages/oauth1/package.js index dcbf9b4642..bb07c66774 100644 --- a/packages/oauth1/package.js +++ b/packages/oauth1/package.js @@ -8,7 +8,7 @@ Package.onUse(api => { api.use('random'); api.use('service-configuration', ['client', 'server']); api.use('oauth', ['client', 'server']); - api.use(['check', 'fetch'], 'server'); + api.use('check', 'server'); api.use('mongo'); diff --git a/packages/weibo-oauth/package.js b/packages/weibo-oauth/package.js index df3b3458f0..9ce2620c89 100644 --- a/packages/weibo-oauth/package.js +++ b/packages/weibo-oauth/package.js @@ -7,7 +7,6 @@ Package.onUse(api => { api.use('oauth1', ['client', 'server']); api.use('oauth', ['client', 'server']); api.use('random', 'client'); - api.use('fetch', 'server'); api.use(['service-configuration', 'ecmascript'], ['client', 'server']); api.addFiles('weibo_client.js', 'client'); From de4b47b5c364c5f38c1a738595add68f48d47e80 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 26 Aug 2022 10:41:13 -0300 Subject: [PATCH 50/99] Create method Oauth._fetch to use in Oauth packages and centralize calls. --- packages/meetup-oauth/meetup_server.js | 20 ++++++-------------- packages/meetup-oauth/package.js | 1 - 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/meetup-oauth/meetup_server.js b/packages/meetup-oauth/meetup_server.js index 5aafbc0439..bfc465c7b3 100644 --- a/packages/meetup-oauth/meetup_server.js +++ b/packages/meetup-oauth/meetup_server.js @@ -33,18 +33,12 @@ OAuth.registerService('meetup', 2, null, async query => { }; }); -const toFormUrlencoded = data => { - return Object.entries(data) - .map(([key, value]) => `${key}=${value}`) - .join('&'); -}; - const getAccessToken = async query => { const config = ServiceConfiguration.configurations.findOne({service: 'meetup'}); if (!config) throw new ServiceConfiguration.ConfigError(); - const bodyFormEncoded = toFormUrlencoded({ + const body = OAuth._addValuesToQueryParams({ code: query.code, client_id: config.clientId, client_secret: OAuth.openSecret(config.secret), @@ -53,13 +47,12 @@ const getAccessToken = async query => { state: query.state }); - return await fetch('https://secure.meetup.com/oauth2/access', { - method: 'POST', + return OAuth._fetch('https://secure.meetup.com/oauth2/access', 'POST', { headers: { Accept: 'application/json', 'Content-type': 'application/x-www-form-urlencoded', }, - body: bodyFormEncoded, + body, }) .then(data => data.json()) .then(data => { @@ -77,18 +70,17 @@ const getAccessToken = async query => { }; const getIdentity = async accessToken => { - const bodyFormEncoded = toFormUrlencoded({ + const body = OAuth._addValuesToQueryParams({ member_id: 'self', access_token: accessToken }); - return await fetch('https://api.meetup.com/2/members', { - method: 'POST', + return OAuth._fetch('https://api.meetup.com/2/members', 'POST', { headers: { Accept: 'application/json', 'Content-type': 'application/x-www-form-urlencoded', }, - body: bodyFormEncoded, + body, }).then(data => data.json()) .then(({results = []}) => results.length && results[0]) .catch(err => { diff --git a/packages/meetup-oauth/package.js b/packages/meetup-oauth/package.js index f678cb4386..9691528e9f 100644 --- a/packages/meetup-oauth/package.js +++ b/packages/meetup-oauth/package.js @@ -8,7 +8,6 @@ Package.onUse(api => { api.use('oauth2', ['client', 'server']); api.use('oauth', ['client', 'server']); api.use('random', 'client'); - api.use('fetch', 'server'); api.use('service-configuration', ['client', 'server']); api.addFiles('meetup_server.js', 'server'); From 99ad3711adf4d14ab10b784ab1e1573298a80dc7 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 26 Aug 2022 13:59:46 -0300 Subject: [PATCH 51/99] Remove console log. --- packages/google-oauth/google_server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google-oauth/google_server.js b/packages/google-oauth/google_server.js index 5e470fbc6b..a25637be75 100644 --- a/packages/google-oauth/google_server.js +++ b/packages/google-oauth/google_server.js @@ -70,7 +70,7 @@ const getServiceDataFromTokens = async (tokens, callback) => { }, }, }; - console.log({ returnValue }); + callback && callback(undefined, returnValue); return returnValue; From 3ed86412d600124daa0931199ed58cdc4a70fef3 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 26 Aug 2022 14:24:11 -0300 Subject: [PATCH 52/99] Using Oauth._fecth to call Facebook API. --- packages/facebook-oauth/facebook_server.js | 127 ++++++++++----------- packages/facebook-oauth/package.js | 2 - 2 files changed, 60 insertions(+), 69 deletions(-) diff --git a/packages/facebook-oauth/facebook_server.js b/packages/facebook-oauth/facebook_server.js index 08dd582549..2c5c3f4896 100644 --- a/packages/facebook-oauth/facebook_server.js +++ b/packages/facebook-oauth/facebook_server.js @@ -36,10 +36,10 @@ Accounts.registerLoginHandler(request => { OAuth.registerService('facebook', 2, null, async query => { const response = await getTokenResponse(query); - const {accessToken} = response; - const {expiresIn} = response; + const { accessToken } = response; + const { expiresIn } = response; - return await Facebook.handleAuthFromAccessToken(accessToken, (+new Date) + (1000 * expiresIn)); + return Facebook.handleAuthFromAccessToken(accessToken, (+new Date) + (1000 * expiresIn)); }); function getAbsoluteUrlOptions(query) { @@ -52,7 +52,7 @@ function getAbsoluteUrlOptions(query) { const redirectUrl = new URL(state.redirectUrl); return { rootUrl: redirectUrl.origin, - } + }; } catch (e) { console.error( `Failed to complete OAuth handshake with Facebook because it was not able to obtain the redirect url from the state and you are using overrideRootUrlFromStateRedirectUrl.`, e @@ -72,82 +72,75 @@ function getAbsoluteUrlOptions(query) { * @param {Object} query - An object with the code. * @returns {Promise} - Promise with an Object containing the accessToken and expiresIn (lifetime of token in seconds) */ -const getTokenResponse = async query => { - const config = ServiceConfiguration.configurations.findOne({service: 'facebook'}); - if (!config) - throw new ServiceConfiguration.ConfigError(); +const getTokenResponse = async (query) => { + const config = ServiceConfiguration.configurations.findOne({ + service: 'facebook', + }); + if (!config) throw new ServiceConfiguration.ConfigError(); - try { - const absoluteUrlOptions = getAbsoluteUrlOptions(query); - const redirectUri = OAuth._redirectUri('facebook', config, undefined, absoluteUrlOptions); + const absoluteUrlOptions = getAbsoluteUrlOptions(query); + const redirectUri = OAuth._redirectUri('facebook', config, undefined, absoluteUrlOptions); - const params = new URLSearchParams(); - - params.append("client_id", config.appId) - params.append("redirect_uri", redirectUri) - params.append("client_secret", OAuth.openSecret(config.secret)) - params.append("code", query.code) - - const uri = `https://graph.facebook.com/v${API_VERSION}/oauth/access_token?${params.toString()}` - - const response = await fetch(uri, { - method: "GET", - headers: { - Accept: 'application/json', + return OAuth._fetch( + `https://graph.facebook.com/v${API_VERSION}/oauth/access_token`, + 'GET', + { + queryParams: { + client_id: config.appId, + redirect_uri: redirectUri, + client_secret: OAuth.openSecret(config.secret), + code: query.code, }, + } + ) + .then((res) => res.json()) + .then(data => { + const fbAccessToken = data.access_token; + const fbExpires = data.expires_in; + if (!fbAccessToken) { + throw new Error("Failed to complete OAuth handshake with facebook " + + `-- can't find access token in HTTP response. ${data}`); + } + return { + accessToken: fbAccessToken, + expiresIn: fbExpires + }; }) - - const data = await response.json(); - - const fbAccessToken = data.access_token; - const fbExpires = data.expires_in; - - return { - accessToken: fbAccessToken, - expiresIn: fbExpires - }; - } catch (e) { - throw Object.assign( - new Error(`Failed to complete OAuth handshake with Facebook. ${err.message}`), - {response: err.response}, - ); - } + .catch((err) => { + throw Object.assign( + new Error( + `Failed to complete OAuth handshake with Facebook. ${err.message}` + ), + { response: err.response } + ); + }); }; const getIdentity = async (accessToken, fields) => { - const config = ServiceConfiguration.configurations.findOne({service: 'facebook'}); - if (!config) - throw new ServiceConfiguration.ConfigError(); + const config = ServiceConfiguration.configurations.findOne({ + service: 'facebook', + }); + if (!config) throw new ServiceConfiguration.ConfigError(); // Generate app secret proof that is a sha256 hash of the app access token, with the app secret as the key // https://developers.facebook.com/docs/graph-api/securing-requests#appsecret_proof const hmac = crypto.createHmac('sha256', OAuth.openSecret(config.secret)); hmac.update(accessToken); - try { - - const params = new URLSearchParams(); - - params.append("access_token", accessToken) - params.append("appsecret_proof", hmac.digest('hex')) - params.append("fields", fields.join(",")) - - const uri = `https://graph.facebook.com/v${API_VERSION}/me?${params.toString()}` - - const response = await fetch(uri, { - method: "GET", - headers: { - Accept: 'application/json', - }, - }) - - return response.json(); - } catch (err) { - throw Object.assign( - new Error(`Failed to fetch identity from Facebook. ${err.message}`), - {response: err.response}, - ); - } + return OAuth._fetch(`https://graph.facebook.com/v${API_VERSION}/me`, 'GET', { + queryParams: { + access_token: accessToken, + appsecret_proof: hmac.digest('hex'), + fields: fields.join(','), + }, + }) + .then((res) => res.json()) + .catch((err) => { + throw Object.assign( + new Error(`Failed to fetch identity from Facebook. ${err.message}`), + { response: err.response } + ); + }); }; Facebook.retrieveCredential = (credentialToken, credentialSecret) => diff --git a/packages/facebook-oauth/package.js b/packages/facebook-oauth/package.js index 79db14954c..de073e9b0c 100644 --- a/packages/facebook-oauth/package.js +++ b/packages/facebook-oauth/package.js @@ -7,8 +7,6 @@ Package.onUse(api => { api.use('ecmascript', ['client', 'server']); api.use('oauth2', ['client', 'server']); api.use('oauth', ['client', 'server']); - api.use('http@1.4.4 || 2.0.0', ['server']); - api.use('fetch', ['server']); api.use('random', 'client'); api.use('service-configuration', ['client', 'server']); From 68a5e66435cb92cd085411201648ca13afc1a6b2 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 26 Aug 2022 15:37:19 -0300 Subject: [PATCH 53/99] Changing accounts-password to no more use Meteor.wrapAsync. --- packages/accounts-password/password_server.js | 101 +++++++++++------- packages/accounts-password/password_tests.js | 4 +- 2 files changed, 62 insertions(+), 43 deletions(-) diff --git a/packages/accounts-password/password_server.js b/packages/accounts-password/password_server.js index 75c7c30e0c..c5852493f8 100644 --- a/packages/accounts-password/password_server.js +++ b/packages/accounts-password/password_server.js @@ -1,8 +1,5 @@ -import bcrypt from 'bcrypt' -import {Accounts} from "meteor/accounts-base"; - -const bcryptHash = Meteor.wrapAsync(bcrypt.hash); -const bcryptCompare = Meteor.wrapAsync(bcrypt.compare); +import bcrypt from 'bcrypt'; +import { Accounts } from "meteor/accounts-base"; // Utility for grabbing user const getUserById = (id, options) => Meteor.users.findOne(id, Accounts._addDefaultFieldSelector(options)); @@ -48,9 +45,9 @@ const getPasswordString = password => { // SHA256 before bcrypt) or an object with properties `digest` and // `algorithm` (in which case we bcrypt `password.digest`). // -const hashPassword = password => { +const hashPassword = async password => { password = getPasswordString(password); - return bcryptHash(password, Accounts._bcryptRounds()); + return await bcrypt.hash(password, Accounts._bcryptRounds()); }; // Extract the number of rounds used in the specified bcrypt hash. @@ -74,7 +71,7 @@ const getRoundsFromBcryptHash = hash => { // The user parameter needs at least user._id and user.services Accounts._checkPasswordUserFields = {_id: 1, services: 1}; // -Accounts._checkPassword = (user, password) => { +const checkPassword = async (user, password) => { const result = { userId: user._id }; @@ -83,15 +80,16 @@ Accounts._checkPassword = (user, password) => { const hash = user.services.password.bcrypt; const hashRounds = getRoundsFromBcryptHash(hash); - if (! bcryptCompare(formattedPassword, hash)) { + if (! await bcrypt.compare(formattedPassword, hash)) { result.error = Accounts._handleError("Incorrect password", false); } else if (hash && Accounts._bcryptRounds() != hashRounds) { // The password checks out, but the user's bcrypt hash needs to be updated. - Meteor.defer(() => { + + Meteor.defer(async () => { Meteor.users.update({ _id: user._id }, { $set: { 'services.password.bcrypt': - bcryptHash(formattedPassword, Accounts._bcryptRounds()) + await bcrypt.hash(formattedPassword, Accounts._bcryptRounds()) } }); }); @@ -99,7 +97,8 @@ Accounts._checkPassword = (user, password) => { return result; }; -const checkPassword = Accounts._checkPassword; + +Accounts._checkPassword = checkPassword; /// /// LOGIN @@ -163,7 +162,7 @@ const passwordValidator = Match.OneOf( // // Note that neither password option is secure without SSL. // -Accounts.registerLoginHandler("password", options => { +Accounts.registerLoginHandler("password", async options => { if (!options.password) return undefined; // don't handle @@ -188,7 +187,7 @@ Accounts.registerLoginHandler("password", options => { Accounts._handleError("User has no password set"); } - const result = checkPassword(user, options.password); + const result = await checkPassword(user, options.password); // This method is added by the package accounts-2fa // First the login is validated, then the code situation is checked if ( @@ -258,7 +257,7 @@ Accounts.setUsername = (userId, newUsername) => { // Let the user change their own password if they know the old // password. `oldPassword` and `newPassword` should be objects with keys // `digest` and `algorithm` (representing the SHA256 of the password). -Meteor.methods({changePassword: function (oldPassword, newPassword) { +Meteor.methods({changePassword: async function (oldPassword, newPassword) { check(oldPassword, passwordValidator); check(newPassword, passwordValidator); @@ -278,12 +277,12 @@ Meteor.methods({changePassword: function (oldPassword, newPassword) { Accounts._handleError("User has no password set"); } - const result = checkPassword(user, oldPassword); + const result = await checkPassword(user, oldPassword); if (result.error) { throw result.error; } - const hashed = hashPassword(newPassword); + const hashed = await hashPassword(newPassword); // It would be better if this removed ALL existing tokens and replaced // the token for the current connection with a new one, but that would @@ -317,9 +316,9 @@ Meteor.methods({changePassword: function (oldPassword, newPassword) { * @importFromPackage accounts-base */ Accounts.setPassword = (userId, newPlaintextPassword, options) => { - check(userId, String) - check(newPlaintextPassword, Match.Where(str => Match.test(str, String) && str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength || 256)) - check(options, Match.Maybe({ logout: Boolean })) + check(userId, String); + check(newPlaintextPassword, Match.Where(str => Match.test(str, String) && str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength || 256)); + check(options, Match.Maybe({ logout: Boolean })); options = { logout: true , ...options }; const user = getUserById(userId, {fields: {_id: 1}}); @@ -327,18 +326,20 @@ Accounts.setPassword = (userId, newPlaintextPassword, options) => { throw new Meteor.Error(403, "User not found"); } - const update = { - $unset: { - 'services.password.reset': 1 - }, - $set: {'services.password.bcrypt': hashPassword(newPlaintextPassword)} - }; + return hashPassword(newPlaintextPassword).then(hash => { + const update = { + $unset: { + 'services.password.reset': 1 + }, + $set: {'services.password.bcrypt': hash} + }; - if (options.logout) { - update.$unset['services.resume.loginTokens'] = 1; - } + if (options.logout) { + update.$unset['services.resume.loginTokens'] = 1; + } - Meteor.users.update({_id: user._id}, update); + Meteor.users.update({_id: user._id}, update); + }); }; @@ -568,7 +569,7 @@ Meteor.methods({resetPassword: async function (...args) { "resetPassword", args, "password", - () => { + async () => { check(token, String); check(newPassword, passwordValidator); @@ -617,7 +618,7 @@ Meteor.methods({resetPassword: async function (...args) { error: new Meteor.Error(403, "Token has invalid email address") }; - const hashed = hashPassword(newPassword); + const hashed = await hashPassword(newPassword); // NOTE: We're about to invalidate tokens on the user, who we might be // logged in as. Make sure to avoid logging ourselves out if this @@ -888,7 +889,7 @@ Accounts.removeEmail = (userId, email) => { // does the actual user insertion. // // returns the user id -const createUser = options => { +const createUser = async options => { // Unknown keys allowed, because a onCreateUserHook can take arbitrary // options. check(options, Match.ObjectIncluding({ @@ -903,11 +904,11 @@ const createUser = options => { const user = {services: {}}; if (password) { - const hashed = hashPassword(password); + const hashed = await hashPassword(password); user.services.password = { bcrypt: hashed }; } - return Accounts._createUserCheckingDuplicates({ user, email, username, options }) + return Accounts._createUserCheckingDuplicates({ user, email, username, options }); }; // method for create user. Requests come from the client. @@ -918,7 +919,7 @@ Meteor.methods({createUser: async function (...args) { "createUser", args, "password", - () => { + async () => { // createUser() above does more checking. check(options, Object); if (Accounts._options.forbidClientAccountCreation) @@ -926,7 +927,7 @@ Meteor.methods({createUser: async function (...args) { error: new Meteor.Error(403, "Signups forbidden") }; - const userId = Accounts.createUserVerifyingEmail(options); + const userId = await Accounts.createUserVerifyingEmail(options); // client gets logged in as the new user afterwards. return {userId: userId}; @@ -948,10 +949,10 @@ Meteor.methods({createUser: async function (...args) { * @param {Object} options.profile The user's profile, typically including the `name` field. * @importFromPackage accounts-base * */ -Accounts.createUserVerifyingEmail = (options) => { +Accounts.createUserVerifyingEmail = async (options) => { options = { ...options }; // Create user. result contains id and token. - const userId = createUser(options); + const userId = await createUser(options); // safety belt. createUser is supposed to throw on error. send 500 error // instead of sending a verification email with empty userid. if (! userId) @@ -976,14 +977,15 @@ Accounts.createUserVerifyingEmail = (options) => { // Unlike the client version, this does not log you in as this user // after creation. // -// returns userId or throws an error if it can't create +// returns Promise or throws an error if it can't create // // XXX add another argument ("server options") that gets sent to onCreateUser, // which is always empty when called from the createUser method? eg, "admin: // true", which we want to prevent the client from setting, but which a custom // method calling Accounts.createUser could set? // -Accounts.createUser = (options, callback) => { + +Accounts.createUserAsync = async (options, callback) => { options = { ...options }; // XXX allow an optional callback? @@ -994,6 +996,23 @@ Accounts.createUser = (options, callback) => { return createUser(options); }; +// Create user directly on the server. +// +// Unlike the client version, this does not log you in as this user +// after creation. +// +// returns userId or throws an error if it can't create +// +// XXX add another argument ("server options") that gets sent to onCreateUser, +// which is always empty when called from the createUser method? eg, "admin: +// true", which we want to prevent the client from setting, but which a custom +// method calling Accounts.createUser could set? +// + +Accounts.createUser = (options, callback) => { + return Promise.await(Accounts.createUserAsync(options, callback)); +}; + /// /// PASSWORD-SPECIFIC INDEXES ON USERS /// diff --git a/packages/accounts-password/password_tests.js b/packages/accounts-password/password_tests.js index 23e7e6ca8c..033c988101 100644 --- a/packages/accounts-password/password_tests.js +++ b/packages/accounts-password/password_tests.js @@ -1747,7 +1747,7 @@ if (Meteor.isServer) (() => { Tinytest.addAsync( 'passwords - allow custom bcrypt rounds', - (test, done) => { + async (test, done) => { const getUserHashRounds = user => Number(user.services.password.bcrypt.substring(4, 6)); @@ -1768,7 +1768,7 @@ if (Meteor.isServer) (() => { const defaultRounds = Accounts._bcryptRounds(); const customRounds = 11; Accounts._options.bcryptRounds = customRounds; - Accounts._checkPassword(user1, password); + await Accounts._checkPassword(user1, password); Meteor.setTimeout(() => { user1 = Meteor.users.findOne(userId1); rounds = getUserHashRounds(user1); From d08790778a984e28ac73d81a5f42cd366b82ceb0 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 26 Aug 2022 15:53:40 -0300 Subject: [PATCH 54/99] Changing accounts-password to no more use Meteor.wrapAsync. --- packages/accounts-password/password_server.js | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/packages/accounts-password/password_server.js b/packages/accounts-password/password_server.js index c5852493f8..fb11d51a05 100644 --- a/packages/accounts-password/password_server.js +++ b/packages/accounts-password/password_server.js @@ -1,4 +1,4 @@ -import bcrypt from 'bcrypt'; +import { hash as bcryptHash, compare as bcryptCompare } from 'bcrypt'; import { Accounts } from "meteor/accounts-base"; // Utility for grabbing user @@ -47,7 +47,7 @@ const getPasswordString = password => { // const hashPassword = async password => { password = getPasswordString(password); - return await bcrypt.hash(password, Accounts._bcryptRounds()); + return await bcryptHash(password, Accounts._bcryptRounds()); }; // Extract the number of rounds used in the specified bcrypt hash. @@ -80,7 +80,7 @@ const checkPassword = async (user, password) => { const hash = user.services.password.bcrypt; const hashRounds = getRoundsFromBcryptHash(hash); - if (! await bcrypt.compare(formattedPassword, hash)) { + if (! await bcryptCompare(formattedPassword, hash)) { result.error = Accounts._handleError("Incorrect password", false); } else if (hash && Accounts._bcryptRounds() != hashRounds) { // The password checks out, but the user's bcrypt hash needs to be updated. @@ -89,7 +89,7 @@ const checkPassword = async (user, password) => { Meteor.users.update({ _id: user._id }, { $set: { 'services.password.bcrypt': - await bcrypt.hash(formattedPassword, Accounts._bcryptRounds()) + await bcryptHash(formattedPassword, Accounts._bcryptRounds()) } }); }); @@ -315,7 +315,7 @@ Meteor.methods({changePassword: async function (oldPassword, newPassword) { * @param {Object} options.logout Logout all current connections with this userId (default: true) * @importFromPackage accounts-base */ -Accounts.setPassword = (userId, newPlaintextPassword, options) => { +Accounts.setPasswordAsync = async (userId, newPlaintextPassword, options) => { check(userId, String); check(newPlaintextPassword, Match.Where(str => Match.test(str, String) && str.length <= Meteor.settings?.packages?.accounts?.passwordMaxLength || 256)); check(options, Match.Maybe({ logout: Boolean })); @@ -326,20 +326,31 @@ Accounts.setPassword = (userId, newPlaintextPassword, options) => { throw new Meteor.Error(403, "User not found"); } - return hashPassword(newPlaintextPassword).then(hash => { - const update = { - $unset: { - 'services.password.reset': 1 - }, - $set: {'services.password.bcrypt': hash} - }; + const update = { + $unset: { + 'services.password.reset': 1 + }, + $set: {'services.password.bcrypt': await hashPassword(newPlaintextPassword)} + }; - if (options.logout) { - update.$unset['services.resume.loginTokens'] = 1; - } + if (options.logout) { + update.$unset['services.resume.loginTokens'] = 1; + } - Meteor.users.update({_id: user._id}, update); - }); + Meteor.users.update({_id: user._id}, update); +}; + +/** + * @summary Forcibly change the password for a user. + * @locus Server + * @param {String} userId The id of the user to update. + * @param {String} newPassword A new password for the user. + * @param {Object} [options] + * @param {Object} options.logout Logout all current connections with this userId (default: true) + * @importFromPackage accounts-base + */ +Accounts.setPassword = (userId, newPlaintextPassword, options) => { + return Promise.await(Accounts.setPasswordAsync(userId, newPlaintextPassword, options)); }; From cd55c0f5d14cd778433f209a88fa05d0b00b50c2 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 26 Aug 2022 16:09:24 -0300 Subject: [PATCH 55/99] Create API doc. --- docs/source/api/email.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/source/api/email.md b/docs/source/api/email.md index e801e6a709..0e822ab2c9 100644 --- a/docs/source/api/email.md +++ b/docs/source/api/email.md @@ -83,6 +83,27 @@ Meteor.call( 'This is a test of Email.send.' ); ``` +{% apibox "Email.sendAsync" %} + +`sendAsync` only works on the server. It has the same behavior as `Email.send`, but returns a Promise. + +```js +// Server: Define a method that the client can call. +Meteor.methods({ + sendEmail(to, from, subject, text) { + // Make sure that all arguments are strings. + check([to, from, subject, text], [String]); + + // Let other method calls from the same client start running, without + // waiting for the email sending to complete. + this.unblock(); + + Email.sendAsync({ to, from, subject, text }).catch(err => { + // + }); + } +}); +``` {% apibox "Email.hookSend" %} From 9149cad069adc78dbe71d75197dae67b1adfa7ee Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Fri, 26 Aug 2022 16:11:32 -0300 Subject: [PATCH 56/99] Remove TODO --- packages/email/email.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/email/email.js b/packages/email/email.js index 016d2a12c1..1029e13156 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -235,7 +235,6 @@ Email.send = function (options) { Promise.await(Email.sendAsync(options)); }; -// TODO Rewrite summary. /** * @summary Send an email with asyncronous method. Capture Throws an `Error` on failure to contact mail server * or if mail server returns an error. All fields should match From 1712c4c5aeb9dfdd1c01a7cbc087f9f3b9a459a0 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Sat, 27 Aug 2022 14:52:35 -0300 Subject: [PATCH 57/99] fix(errors): cross-boud. message --- packages/modules-runtime/errors/cannotImportFrom.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules-runtime/errors/cannotImportFrom.js b/packages/modules-runtime/errors/cannotImportFrom.js index 313cf3f222..82e68e84df 100644 --- a/packages/modules-runtime/errors/cannotImportFrom.js +++ b/packages/modules-runtime/errors/cannotImportFrom.js @@ -24,7 +24,7 @@ cannotImport = function (id) { var fromClient = function () { return new Error( - 'Unable to import on the client a module from a server directory: "' + id + '" \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' + 'Unable to import on the server a module from a client directory: "' + id + '" \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' ); }; @@ -32,7 +32,7 @@ cannotImport = function (id) { var fromServer = function () { return new Error( - 'Unable to import on the server a module from a client directory: "' + id + '" \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' + 'Unable to import on the client a module from a server directory: "' + id + '" \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' ); }; From 21e26678317827ba82d4107be780fc37ab5c2189 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Sat, 27 Aug 2022 14:53:08 -0300 Subject: [PATCH 58/99] tests(errors): created tests for client and server --- .../modules-runtime/modules-runtime-tests.js | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/packages/modules-runtime/modules-runtime-tests.js b/packages/modules-runtime/modules-runtime-tests.js index d87150d995..b050747ab1 100644 --- a/packages/modules-runtime/modules-runtime-tests.js +++ b/packages/modules-runtime/modules-runtime-tests.js @@ -7,7 +7,7 @@ Tinytest.add('modules', function (test) { Tinytest.add('modules.throwStandardError', function (test) { var require = meteorInstall(); test.throws(() => { - require('meteor/foo') + require('meteor/foo'); }, 'Cannot find package "foo". Try "meteor add foo".'); }); @@ -15,20 +15,37 @@ if (Meteor.isClient) { Tinytest.add('modules.throwClientError', function (test) { var require = meteorInstall(); test.throws(() => { - require('./../server/main.js') + require('./../server/main.js'); }, - 'Unable to import on the server a module from a client directory: "./../server/main.js" \n' + + 'Unable to import on the client a module from a server directory: "./../server/main.js" \n' + + ' (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' + ); + }); + Tinytest.add('modules.throwServerError', function (test) { + var require = meteorInstall(); + test.throws(() => { + require('./../client/main.js'); + }, + 'Unable to import on the server a module from a client directory: "./../client/main.js" \n' + ' (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' ); }); } if (Meteor.isServer) { + Tinytest.add('modules.throwClientError', function (test) { + var require = meteorInstall(); + test.throws(() => { + require('./../server/main.js'); + }, "Cannot find module './../server/main.js'" + ); + }); Tinytest.add('modules.throwServerError', function (test) { var require = meteorInstall(); test.throws(() => { - require('./../client/main.js') - }, "Cannot find module './../client/main.js'" + require('./../client/main.js'); + },"Cannot find module './../client/main.js'" ); }); } + From ebff7901e00116b64dd7a5a729157e447428f7e5 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Tue, 30 Aug 2022 16:09:35 -0300 Subject: [PATCH 59/99] feat(importErrors): adjusted file name --- .../errors/{cannotImportFrom.js => importsErrors.js} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename packages/modules-runtime/errors/{cannotImportFrom.js => importsErrors.js} (86%) diff --git a/packages/modules-runtime/errors/cannotImportFrom.js b/packages/modules-runtime/errors/importsErrors.js similarity index 86% rename from packages/modules-runtime/errors/cannotImportFrom.js rename to packages/modules-runtime/errors/importsErrors.js index 82e68e84df..09a34e2e21 100644 --- a/packages/modules-runtime/errors/cannotImportFrom.js +++ b/packages/modules-runtime/errors/importsErrors.js @@ -3,7 +3,7 @@ * @param id{string} * @return {{fromServer: (function(): Error), from: (function(location: string): boolean), fromClient: (function(): Error)}} */ -cannotImport = function (id) { +imports = function (id) { /** * * @param location{string} @@ -21,7 +21,7 @@ cannotImport = function (id) { }); }; - var fromClient = + var fromClientError = function () { return new Error( 'Unable to import on the server a module from a client directory: "' + id + '" \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' @@ -29,7 +29,7 @@ cannotImport = function (id) { }; - var fromServer = + var fromServerError = function () { return new Error( 'Unable to import on the client a module from a server directory: "' + id + '" \n (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' @@ -38,7 +38,7 @@ cannotImport = function (id) { return { from: from, - fromClient: fromClient, - fromServer: fromServer + fromClientError: fromClientError, + fromServerError: fromServerError }; }; From 7a73da98c862e54b407782aed26101e03242a04f Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Tue, 30 Aug 2022 16:10:05 -0300 Subject: [PATCH 60/99] fix(module-runtime): adjusted file declaration --- packages/modules-runtime/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules-runtime/package.js b/packages/modules-runtime/package.js index 8361e5073f..f7ba69f134 100644 --- a/packages/modules-runtime/package.js +++ b/packages/modules-runtime/package.js @@ -18,7 +18,7 @@ Package.onUse(function(api) { bare: true }); - api.addFiles(['./errors/cannotImportFrom.js', + api.addFiles(['./errors/importsErrors.js', './errors/cannotFindMeteorPackage.js']); api.addFiles('modern.js', 'modern'); api.addFiles('legacy.js', 'legacy'); From 8bfe20990bca668ef14c1721296e5e817505b05f Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Tue, 30 Aug 2022 16:11:11 -0300 Subject: [PATCH 61/99] feat(module-runtime): added error validation to server.js --- packages/modules-runtime/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules-runtime/server.js b/packages/modules-runtime/server.js index 018a61e49b..b2d66af281 100644 --- a/packages/modules-runtime/server.js +++ b/packages/modules-runtime/server.js @@ -28,7 +28,7 @@ makeInstallerOptions.fallback = function (id, parentId, error) { return Npm.require(id, error); } } - + verifyErrors(id, parentId, error); throw error; }; From a385903a9f9890498d01e5bd9436af6bfa8c0e8b Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Tue, 30 Aug 2022 16:17:01 -0300 Subject: [PATCH 62/99] chore: added extra validations --- packages/modules-runtime/verifyErrors.js | 26 ++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/modules-runtime/verifyErrors.js b/packages/modules-runtime/verifyErrors.js index 3bcf983830..0dfaaadcac 100644 --- a/packages/modules-runtime/verifyErrors.js +++ b/packages/modules-runtime/verifyErrors.js @@ -6,15 +6,33 @@ * @param err {Error} */ verifyErrors = function (id, parentId,err) { + if (id && id.startsWith('meteor/')) { throw cannotFindMeteorPackage(id); } - if (cannotImport(id).from('client')) { - throw cannotImport(id).fromClient(); + + if(!(id.startsWith('.') || id.startsWith('/'))) { + throw err; } - if (cannotImport(id).from('server')) { - throw cannotImport(id).fromServer(); + + if (id.endsWith('client') || id.endsWith('server')) { + // We don't know for sure what client wants to do so throw standard error + throw err; } + + if (imports(id).from('node_modules')) { + // Problem with node modules + throw err; + } + + // custom errors + if (Meteor.isServer && imports(id).from('client')) { + throw imports(id).fromClientError(); + } + if (Meteor.isClient && imports(id).from('server')) { + throw imports(id).fromServerError(); + } + if (err) { throw err; } From 0255d06728474352fe1105dd2a186d47cf7d466b Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Tue, 30 Aug 2022 16:17:40 -0300 Subject: [PATCH 63/99] tests(module-runtime): adjusted test organization --- .../modules-runtime/modules-runtime-tests.js | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/modules-runtime/modules-runtime-tests.js b/packages/modules-runtime/modules-runtime-tests.js index b050747ab1..2d771efcc6 100644 --- a/packages/modules-runtime/modules-runtime-tests.js +++ b/packages/modules-runtime/modules-runtime-tests.js @@ -4,24 +4,24 @@ Tinytest.add('modules', function (test) { test.equal(typeof require, 'function'); }); -Tinytest.add('modules.throwStandardError', function (test) { +Tinytest.add('errors - standard', function (test) { var require = meteorInstall(); test.throws(() => { require('meteor/foo'); }, 'Cannot find package "foo". Try "meteor add foo".'); }); -if (Meteor.isClient) { - Tinytest.add('modules.throwClientError', function (test) { + + +if (Meteor.isServer) { + Tinytest.add('server - throwClientError', function (test) { var require = meteorInstall(); test.throws(() => { require('./../server/main.js'); - }, - 'Unable to import on the client a module from a server directory: "./../server/main.js" \n' + - ' (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' + }, "Cannot find module './../server/main.js'" ); }); - Tinytest.add('modules.throwServerError', function (test) { + Tinytest.add('server - throwServerError', function (test) { var require = meteorInstall(); test.throws(() => { require('./../client/main.js'); @@ -32,20 +32,22 @@ if (Meteor.isClient) { }); } -if (Meteor.isServer) { - Tinytest.add('modules.throwClientError', function (test) { +if (Meteor.isClient) { + Tinytest.add('client - throwClientError', function (test) { var require = meteorInstall(); test.throws(() => { require('./../server/main.js'); - }, "Cannot find module './../server/main.js'" + }, + 'Unable to import on the client a module from a server directory: "./../server/main.js" \n' + + ' (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' ); }); - Tinytest.add('modules.throwServerError', function (test) { + Tinytest.add('client - throwServerError', function (test) { var require = meteorInstall(); test.throws(() => { require('./../client/main.js'); - },"Cannot find module './../client/main.js'" - ); + }, "Cannot find module './../client/main.js'"); }); } + From 4317e423f10035da868e4031bb39c54cf555feff Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Tue, 30 Aug 2022 16:18:15 -0300 Subject: [PATCH 64/99] tests(module-runtime): added node_module error validation --- packages/modules-runtime/modules-runtime-tests.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/modules-runtime/modules-runtime-tests.js b/packages/modules-runtime/modules-runtime-tests.js index 2d771efcc6..267b059520 100644 --- a/packages/modules-runtime/modules-runtime-tests.js +++ b/packages/modules-runtime/modules-runtime-tests.js @@ -11,7 +11,12 @@ Tinytest.add('errors - standard', function (test) { }, 'Cannot find package "foo". Try "meteor add foo".'); }); - +Tinytest.add('errors - node_modules', function (test) { + var require = meteorInstall(); + test.throws(() => { + require('./node_modules/foo'); + }, "Cannot find module './node_modules/foo'"); +}); if (Meteor.isServer) { Tinytest.add('server - throwClientError', function (test) { From 2fcd55827dd7914d47a7dd225d248d17e3fc3015 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Fri, 2 Sep 2022 12:36:51 -0300 Subject: [PATCH 65/99] fix(errors): adjusted how it handles paths --- packages/modules-runtime/errors/importsErrors.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/modules-runtime/errors/importsErrors.js b/packages/modules-runtime/errors/importsErrors.js index 09a34e2e21..d82f62cb1f 100644 --- a/packages/modules-runtime/errors/importsErrors.js +++ b/packages/modules-runtime/errors/importsErrors.js @@ -14,11 +14,13 @@ imports = function (id) { if (!id) { return false; } - return String(id) - .split('/') - .some(function (subPath) { - return subPath === location; - }); + + // XXX: removed last part of path so that it does not trigger false positives + var path = String(id).split('/').slice(0, -1); + + return path.some(function (subPath) { + return subPath === location; + }); }; var fromClientError = From c73fea7ec4485d6db261afbc6fc94d2f57b8907c Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Fri, 2 Sep 2022 12:37:15 -0300 Subject: [PATCH 66/99] tests(errors): added case when there is client & server --- .../modules-runtime/modules-runtime-tests.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/modules-runtime/modules-runtime-tests.js b/packages/modules-runtime/modules-runtime-tests.js index 267b059520..78abd74037 100644 --- a/packages/modules-runtime/modules-runtime-tests.js +++ b/packages/modules-runtime/modules-runtime-tests.js @@ -26,6 +26,15 @@ if (Meteor.isServer) { }, "Cannot find module './../server/main.js'" ); }); + Tinytest.add('server - client and server in path', function (test) { + var require = meteorInstall(); + test.throws(() => { + require('/client/graphql/client'); + }, + 'Unable to import on the server a module from a client directory: "/client/graphql/client" \n' + + ' (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' + ); + }); Tinytest.add('server - throwServerError', function (test) { var require = meteorInstall(); test.throws(() => { @@ -47,6 +56,15 @@ if (Meteor.isClient) { ' (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' ); }); + Tinytest.add('client - client and server in path', function (test) { + var require = meteorInstall(); + test.throws(() => { + require('/server/graphql/client'); + }, + 'Unable to import on the client a module from a server directory: "/server/graphql/client" \n' + + ' (cross-boundary import) see: https://guide.meteor.com/structure.html#special-directories' + ); + }); Tinytest.add('client - throwServerError', function (test) { var require = meteorInstall(); test.throws(() => { From 71927cd140cf901b231af2ab32df52f28f3913a5 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba <70247653+Grubba27@users.noreply.github.com> Date: Fri, 2 Sep 2022 12:37:37 -0300 Subject: [PATCH 67/99] fix(errors): moved logic to function imports --- packages/modules-runtime/verifyErrors.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/modules-runtime/verifyErrors.js b/packages/modules-runtime/verifyErrors.js index 0dfaaadcac..d0fe827e8d 100644 --- a/packages/modules-runtime/verifyErrors.js +++ b/packages/modules-runtime/verifyErrors.js @@ -15,11 +15,6 @@ verifyErrors = function (id, parentId,err) { throw err; } - if (id.endsWith('client') || id.endsWith('server')) { - // We don't know for sure what client wants to do so throw standard error - throw err; - } - if (imports(id).from('node_modules')) { // Problem with node modules throw err; From eb7a50c61006282d1e93c8e78b741fb885b295d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Po=C5=9Bpiech?= Date: Fri, 9 Sep 2022 14:04:11 +0200 Subject: [PATCH 68/99] Add types for fetch package --- packages/fetch/fetch.d.ts | 4 ++++ packages/fetch/package-types.json | 3 +++ packages/fetch/package.js | 1 + 3 files changed, 8 insertions(+) create mode 100644 packages/fetch/fetch.d.ts create mode 100644 packages/fetch/package-types.json diff --git a/packages/fetch/fetch.d.ts b/packages/fetch/fetch.d.ts new file mode 100644 index 0000000000..8d6eb289ad --- /dev/null +++ b/packages/fetch/fetch.d.ts @@ -0,0 +1,4 @@ +export declare function fetch(): typeof globalThis.fetch; +export declare var Headers: typeof globalThis.Headers; +export declare var Request: typeof globalThis.Request; +export declare var Response: typeof globalThis.Response; diff --git a/packages/fetch/package-types.json b/packages/fetch/package-types.json new file mode 100644 index 0000000000..3fcb42c31a --- /dev/null +++ b/packages/fetch/package-types.json @@ -0,0 +1,3 @@ +{ + "typesEntry": "fetch.d.ts" +} diff --git a/packages/fetch/package.js b/packages/fetch/package.js index 9c3969ff7b..53830c6a2c 100644 --- a/packages/fetch/package.js +++ b/packages/fetch/package.js @@ -19,6 +19,7 @@ Package.onUse(function(api) { api.mainModule("legacy.js", "legacy"); api.mainModule("server.js", "server"); + api.addAssets("fetch.d.ts", ["client", "server"]); // The other exports (Headers, Request, Response) can be imported // explicitly from the "meteor/fetch" package. api.export("fetch"); From 559a1c622f40dc06711408c640d5ded73c8d384a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Po=C5=9Bpiech?= Date: Fri, 9 Sep 2022 14:04:37 +0200 Subject: [PATCH 69/99] Add types for ejson package --- packages/ejson/ejson.d.ts | 71 +++++++++++++++++++++++++++++++ packages/ejson/package-types.json | 3 ++ packages/ejson/package.js | 1 + 3 files changed, 75 insertions(+) create mode 100644 packages/ejson/ejson.d.ts create mode 100644 packages/ejson/package-types.json diff --git a/packages/ejson/ejson.d.ts b/packages/ejson/ejson.d.ts new file mode 100644 index 0000000000..61c3a7674c --- /dev/null +++ b/packages/ejson/ejson.d.ts @@ -0,0 +1,71 @@ +export interface EJSONableCustomType { + clone?(): EJSONableCustomType; + equals?(other: Object): boolean; + toJSONValue(): JSONable; + typeName(): string; +} + +export type EJSONableProperty = + | number + | string + | boolean + | Object + | number[] + | string[] + | Object[] + | Date + | Uint8Array + | EJSONableCustomType + | undefined + | null; + +export interface EJSONable { + [key: string]: EJSONableProperty; +} + +export interface JSONable { + [key: string]: + | number + | string + | boolean + | Object + | number[] + | string[] + | Object[] + | undefined + | null; +} + +export interface EJSON extends EJSONable {} + +export namespace EJSON { + function addType( + name: string, + factory: (val: JSONable) => EJSONableCustomType + ): void; + + function clone(val: T): T; + + function equals( + a: EJSON, + b: EJSON, + options?: { keyOrderSensitive?: boolean | undefined } + ): boolean; + + function fromJSONValue(val: JSONable): any; + + function isBinary(x: Object): x is Uint8Array; + function newBinary(size: number): Uint8Array; + + function parse(str: string): EJSON; + + function stringify( + val: EJSON, + options?: { + indent?: boolean | number | string | undefined; + canonical?: boolean | undefined; + } + ): string; + + function toJSONValue(val: EJSON): JSONable; +} diff --git a/packages/ejson/package-types.json b/packages/ejson/package-types.json new file mode 100644 index 0000000000..3f2149b4c0 --- /dev/null +++ b/packages/ejson/package-types.json @@ -0,0 +1,3 @@ +{ + "typesEntry": "ejson.d.ts" +} diff --git a/packages/ejson/package.js b/packages/ejson/package.js index 2a10d49443..7ed5099790 100644 --- a/packages/ejson/package.js +++ b/packages/ejson/package.js @@ -5,6 +5,7 @@ Package.describe({ Package.onUse(function onUse(api) { api.use(['ecmascript', 'base64']); + api.addAssets('ejson.d.ts', ['client', 'server']); api.mainModule('ejson.js'); api.export('EJSON'); }); From a91016fbcb99d7881dada756db6ad7f770dc5f15 Mon Sep 17 00:00:00 2001 From: Zack Date: Thu, 15 Sep 2022 17:14:03 -0400 Subject: [PATCH 70/99] Update mongo_driver.js --- packages/mongo/mongo_driver.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 02dcb994cd..4bcc5686ec 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -63,6 +63,10 @@ var unmakeMongoLegal = function (name) { return name.substr(5); }; var replaceMongoAtomWithMeteor = function (document) { if (document instanceof MongoDB.Binary) { + // for backwards compatibility + if (document.sub_type !== 0) { + return document; + } var buffer = document.value(true); return new Uint8Array(buffer); } @@ -92,6 +96,9 @@ var replaceMeteorAtomWithMongo = function (document) { // serialize it correctly). return new MongoDB.Binary(Buffer.from(document)); } + if (document instanceof Mongo.Binary) { + return document; + } if (document instanceof Mongo.ObjectID) { return new MongoDB.ObjectID(document.toHexString()); } From 454c40fb96db65a713472f9c36854b2d7d411d71 Mon Sep 17 00:00:00 2001 From: Zack Date: Thu, 15 Sep 2022 17:18:25 -0400 Subject: [PATCH 71/99] Update mongo_driver.js --- packages/mongo/mongo_driver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mongo/mongo_driver.js b/packages/mongo/mongo_driver.js index 4bcc5686ec..27b0e33248 100644 --- a/packages/mongo/mongo_driver.js +++ b/packages/mongo/mongo_driver.js @@ -96,7 +96,7 @@ var replaceMeteorAtomWithMongo = function (document) { // serialize it correctly). return new MongoDB.Binary(Buffer.from(document)); } - if (document instanceof Mongo.Binary) { + if (document instanceof MongoDB.Binary) { return document; } if (document instanceof Mongo.ObjectID) { From a86296e47a24e55592e42ef72db7422ef2b43e8d Mon Sep 17 00:00:00 2001 From: Zack Newsham Date: Mon, 19 Sep 2022 07:45:06 -0400 Subject: [PATCH 72/99] add tests for Binary atoms --- packages/mongo/collection_tests.js | 181 +++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/packages/mongo/collection_tests.js b/packages/mongo/collection_tests.js index da34c62792..4c6b7f11b2 100644 --- a/packages/mongo/collection_tests.js +++ b/packages/mongo/collection_tests.js @@ -1,3 +1,6 @@ + +var MongoDB = NpmModuleMongodb; + Tinytest.add( 'collection - call Mongo.Collection without new', function (test) { @@ -203,3 +206,181 @@ Tinytest.add('collection - calling find with an invalid readPreference', } } ); + +Tinytest.add('collection - inserting a document with a binary should return a document with a binary', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary1'); + const _id = Random.id(); + collection.insert({ + _id, + binary: new MongoDB.Binary(Buffer.from('hello world'), 6) + }); + + const doc = collection.findOne({ _id }); + test.ok( + doc.binary instanceof MongoDB.Binary + ); + test.equal( + doc.binary.buffer, + Buffer.from('hello world') + ); + } + } +); + +Tinytest.add('collection - inserting a document with a binary (sub type 0) should return a document with a uint8array', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary8'); + const _id = Random.id(); + collection.insert({ + _id, + binary: new MongoDB.Binary(Buffer.from('hello world'), 0) + }); + + const doc = collection.findOne({ _id }); + test.ok( + doc.binary instanceof Uint8Array + ); + test.equal( + doc.binary, + new Uint8Array(Buffer.from('hello world')) + ); + } + } +); + +Tinytest.add('collection - updating a document with a binary should return a document with a binary', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary2'); + const _id = Random.id(); + collection.insert({ + _id + }); + + collection.update({ _id }, { $set: { binary: new MongoDB.Binary(Buffer.from('hello world'), 6) } }); + + const doc = collection.findOne({ _id }); + test.ok( + doc.binary instanceof MongoDB.Binary + ); + test.equal( + doc.binary.buffer, + Buffer.from('hello world') + ); + } + } +); + +Tinytest.add('collection - updating a document with a binary (sub type 0) should return a document with a uint8array', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary7'); + const _id = Random.id(); + collection.insert({ + _id + }); + + collection.update({ _id }, { $set: { binary: new MongoDB.Binary(Buffer.from('hello world'), 0) } }); + + const doc = collection.findOne({ _id }); + test.ok( + doc.binary instanceof Uint8Array + ); + test.equal( + doc.binary, + new Uint8Array(Buffer.from('hello world')) + ); + } + } +); + +Tinytest.add('collection - inserting a document with a uint8array should return a document with a uint8array', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary3'); + const _id = Random.id(); + collection.insert({ + _id, + binary: new Uint8Array(Buffer.from('hello world')) + }); + + const doc = collection.findOne({ _id }); + test.ok( + doc.binary instanceof Uint8Array + ); + test.equal( + doc.binary, + new Uint8Array(Buffer.from('hello world')) + ); + } + } +); + +Tinytest.add('collection - updating a document with a uint8array should return a document with a uint8array', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary4'); + const _id = Random.id(); + collection.insert({ + _id + }); + + collection.update( + { _id }, + { $set: { binary: new Uint8Array(Buffer.from('hello world')) } } + ) + + const doc = collection.findOne({ _id }); + test.ok( + doc.binary instanceof Uint8Array + ); + test.equal( + doc.binary, + new Uint8Array(Buffer.from('hello world')) + ); + } + } +); + +Tinytest.add('collection - finding with a query with a uint8array field should return the correct document', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary5'); + const _id = Random.id(); + collection.insert({ + _id, + binary: new Uint8Array(Buffer.from('hello world')) + }); + + const doc = collection.findOne({ binary: new Uint8Array(Buffer.from('hello world')) }); + test.equal( + doc._id, + _id + ); + collection.remove({}); + } + } +); + +Tinytest.add('collection - finding with a query with a binary field should return the correct document', + function(test) { + if (Meteor.isServer) { + const collection = new Mongo.Collection('testBinary6'); + const _id = Random.id(); + collection.insert({ + _id, + binary: new MongoDB.Binary(Buffer.from('hello world'), 6) + }); + + const doc = collection.findOne({ binary: new MongoDB.Binary(Buffer.from('hello world'), 6) }); + test.equal( + doc._id, + _id + ); + collection.remove({}); + } + } +); From 0a643dd95f269bd80d7a652151f196fcd520c633 Mon Sep 17 00:00:00 2001 From: Zack Newsham Date: Mon, 19 Sep 2022 07:46:16 -0400 Subject: [PATCH 73/99] add npm-mongo dependency for testing --- packages/mongo/package.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/mongo/package.js b/packages/mongo/package.js index fc6703c9b4..ff811d0d63 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.15.0' + version: '1.14.4' }); Npm.depends({ @@ -88,6 +88,7 @@ Package.onTest(function (api) { api.use('mongo'); api.use('check'); api.use('ecmascript'); + api.use('npm-mongo', 'server'); api.use(['tinytest', 'underscore', 'test-helpers', 'ejson', 'random', 'ddp', 'base64']); // XXX test order dependency: the allow_tests "partial allow" test From 4485201f4f28edbfa24fd39cd2fb9dd92a0b6ac1 Mon Sep 17 00:00:00 2001 From: Zack Date: Mon, 19 Sep 2022 11:22:13 -0400 Subject: [PATCH 74/99] Update package.js --- packages/mongo/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mongo/package.js b/packages/mongo/package.js index ff811d0d63..57ec61c98b 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.14.4' + version: '1.15.0' }); Npm.depends({ From 43077687e3f61479f00b9408d84bdba8b3276304 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Tue, 20 Sep 2022 10:54:55 -0300 Subject: [PATCH 75/99] Preserve compatibility mode for account method `Accounts._checkPassword` --- packages/accounts-password/password_server.js | 11 ++++++++--- packages/accounts-password/password_tests.js | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/accounts-password/password_server.js b/packages/accounts-password/password_server.js index fb11d51a05..812bf848d1 100644 --- a/packages/accounts-password/password_server.js +++ b/packages/accounts-password/password_server.js @@ -71,7 +71,7 @@ const getRoundsFromBcryptHash = hash => { // The user parameter needs at least user._id and user.services Accounts._checkPasswordUserFields = {_id: 1, services: 1}; // -const checkPassword = async (user, password) => { +const checkPasswordAsync = async (user, password) => { const result = { userId: user._id }; @@ -98,7 +98,12 @@ const checkPassword = async (user, password) => { return result; }; +const checkPassword = async (user, password) => { + return Promise.await(checkPasswordAsync(user, password)); +}; + Accounts._checkPassword = checkPassword; +Accounts._checkPasswordAsync = checkPasswordAsync; /// /// LOGIN @@ -187,7 +192,7 @@ Accounts.registerLoginHandler("password", async options => { Accounts._handleError("User has no password set"); } - const result = await checkPassword(user, options.password); + const result = await checkPasswordAsync(user, options.password); // This method is added by the package accounts-2fa // First the login is validated, then the code situation is checked if ( @@ -277,7 +282,7 @@ Meteor.methods({changePassword: async function (oldPassword, newPassword) { Accounts._handleError("User has no password set"); } - const result = await checkPassword(user, oldPassword); + const result = await checkPasswordAsync(user, oldPassword); if (result.error) { throw result.error; } diff --git a/packages/accounts-password/password_tests.js b/packages/accounts-password/password_tests.js index 033c988101..0266c977f2 100644 --- a/packages/accounts-password/password_tests.js +++ b/packages/accounts-password/password_tests.js @@ -1768,7 +1768,7 @@ if (Meteor.isServer) (() => { const defaultRounds = Accounts._bcryptRounds(); const customRounds = 11; Accounts._options.bcryptRounds = customRounds; - await Accounts._checkPassword(user1, password); + await Accounts._checkPasswordAsync(user1, password); Meteor.setTimeout(() => { user1 = Meteor.users.findOne(userId1); rounds = getUserHashRounds(user1); From efbffc95f00d3873583314458ea73ab664c6f31c Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Thu, 22 Sep 2022 09:09:22 -0300 Subject: [PATCH 76/99] Changes by code review. --- packages/accounts-base/accounts_server.js | 2 +- packages/facebook-oauth/facebook_server.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index fea56b222a..10120614b4 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -588,7 +588,7 @@ export class AccountsServer extends AccountsCommon { // return `undefined`, meaning it handled this call to `login`. Return // that return value. async _runLoginHandlers(methodInvocation, options) { - for await (let handler of this._loginHandlers) { + for (let handler of this._loginHandlers) { const result = await tryLoginMethod(handler.name, async () => await handler.handler.call(methodInvocation, options) ); diff --git a/packages/facebook-oauth/facebook_server.js b/packages/facebook-oauth/facebook_server.js index 2c5c3f4896..d9c824f27f 100644 --- a/packages/facebook-oauth/facebook_server.js +++ b/packages/facebook-oauth/facebook_server.js @@ -1,6 +1,6 @@ Facebook = {}; import crypto from 'crypto'; -import {Accounts} from 'meteor/accounts-base'; +import { Accounts } from 'meteor/accounts-base'; const API_VERSION = Meteor.settings?.public?.packages?.['facebook-oauth']?.apiVersion || '13.0'; From 1cb299f840a593e47c78ba1c7a5e4fc9851872bb Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Tue, 27 Sep 2022 17:27:09 -0300 Subject: [PATCH 77/99] Changes suggested in code review - Include return on sendAsync example; - Change method send (sync) to preserve current behavior using the `customTransport` option. --- docs/source/api/email.md | 2 +- packages/email/email.js | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/source/api/email.md b/docs/source/api/email.md index 0e822ab2c9..23884f65ac 100644 --- a/docs/source/api/email.md +++ b/docs/source/api/email.md @@ -98,7 +98,7 @@ Meteor.methods({ // waiting for the email sending to complete. this.unblock(); - Email.sendAsync({ to, from, subject, text }).catch(err => { + return Email.sendAsync({ to, from, subject, text }).catch(err => { // }); } diff --git a/packages/email/email.js b/packages/email/email.js index 1029e13156..4a26bca89b 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -147,7 +147,8 @@ EmailTest.resetNextDevModeMailId = function () { nextDevModeMailId = 0; }; -const devModeSendAsync = function (mail, stream) { +const devModeSendAsync = function (mail, options) { + const stream = options?.stream || process.stdout; return new Promise((resolve, reject) => { let devModeMailId = EmailTest._getAndIncNextDevModeMailId(); @@ -225,14 +226,17 @@ Email.customTransport = undefined; * @param {Object[]} [options.attachments] Array of attachment objects, as * described in the [nodemailer documentation](https://nodemailer.com/message/attachments/). * @param {MailComposer} [options.mailComposer] A [MailComposer](https://nodemailer.com/extras/mailcomposer/#e-mail-message-fields) - * @param {Object} [options.stream] Output stream to write email on development environment * object representing the message to be sent. Overrides all other options. * You can create a `MailComposer` object via * `new EmailInternals.NpmModules.mailcomposer.module`. */ Email.send = function (options) { + if (Email.customTransport) { + // Don't wait for sending process. Preserve current behavior + return Email.sendAsync(options); + } // Using Fibers Promise.await - Promise.await(Email.sendAsync(options)); + return Promise.await(Email.sendAsync(options)); }; /** @@ -264,14 +268,13 @@ Email.send = function (options) { * @param {Object[]} [options.attachments] Array of attachment objects, as * described in the [nodemailer documentation](https://nodemailer.com/message/attachments/). * @param {MailComposer} [options.mailComposer] A [MailComposer](https://nodemailer.com/extras/mailcomposer/#e-mail-message-fields) - * @param {Object} [options.stream] Output stream to write email on development environment * object representing the message to be sent. Overrides all other options. * You can create a `MailComposer` object via * `new EmailInternals.NpmModules.mailcomposer.module`. */ Email.sendAsync = async function (options) { - const { stream = process.stdout, ...rest } = options; - const email = rest.mailComposer ? rest.mailComposer.mail : rest; + + const email = options.mailComposer ? options.mailComposer.mail : options; let send = true; sendHooks.forEach((hook) => { @@ -303,5 +306,5 @@ Email.sendAsync = async function (options) { smtpSend(transport, email); return; } - return devModeSendAsync(email, stream); + return devModeSendAsync(email, options); }; From f82d1d8c000238d2a62786699e614af3909280e6 Mon Sep 17 00:00:00 2001 From: Edimar Cardoso Date: Tue, 4 Oct 2022 15:28:41 -0300 Subject: [PATCH 78/99] Changes suggested in the code review - Preserve current behavior from `Email.send` method, if use `customTransport`; - Added documentation information about `customTransport` in `Email.sendAsync` method; - Added more tests to `customTransport` use case. --- docs/source/api/email.md | 1 + packages/email/email.js | 15 ++++++- packages/email/email_tests.js | 75 +++++++++++++++++++++++++++++++---- 3 files changed, 82 insertions(+), 9 deletions(-) diff --git a/docs/source/api/email.md b/docs/source/api/email.md index 23884f65ac..a4dc913954 100644 --- a/docs/source/api/email.md +++ b/docs/source/api/email.md @@ -86,6 +86,7 @@ Meteor.call( {% apibox "Email.sendAsync" %} `sendAsync` only works on the server. It has the same behavior as `Email.send`, but returns a Promise. +If you defined `Email.customTransport`, the `callAsync` method returns the return value from the `customTransport` method or a Promise, if this method is async. ```js // Server: Define a method that the client can call. diff --git a/packages/email/email.js b/packages/email/email.js index 4a26bca89b..eed8dbd9b3 100644 --- a/packages/email/email.js +++ b/packages/email/email.js @@ -232,8 +232,19 @@ Email.customTransport = undefined; */ Email.send = function (options) { if (Email.customTransport) { - // Don't wait for sending process. Preserve current behavior - return Email.sendAsync(options); + // Preserve current behavior + const email = options.mailComposer ? options.mailComposer.mail : options; + let send = true; + sendHooks.forEach((hook) => { + send = hook(email); + return send; + }); + if (!send) { + return; + } + const packageSettings = Meteor.settings.packages?.email || {}; + Email.customTransport({ packageSettings, ...email }); + return; } // Using Fibers Promise.await return Promise.await(Email.sendAsync(options)); diff --git a/packages/email/email_tests.js b/packages/email/email_tests.js index c01bd8f377..6f016f26b9 100644 --- a/packages/email/email_tests.js +++ b/packages/email/email_tests.js @@ -2,6 +2,14 @@ import { Email } from 'meteor/email'; import { smokeEmailTest } from './email_test_helpers'; import { TEST_CASES } from './email_tests_data'; +const CUSTOM_TRANSPORT_SETTINGS = { + email: { service: '1on1', user: 'test', password: 'pwd' }, +}; + +const sleep = (ms) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; + // Create dynamic sync tests TEST_CASES.forEach(({ title, options, testCalls }) => { Tinytest.add(`[Sync] ${title}`, function (test) { @@ -50,9 +58,7 @@ Tinytest.add( }); smokeEmailTest(function (stream) { - Meteor.settings.packages = { - email: { service: '1on1', user: 'test', password: 'pwd' }, - }; + Meteor.settings.packages = CUSTOM_TRANSPORT_SETTINGS; Email.customTransport = (options) => { test.equal(options.from, 'foo@example.com'); test.equal(options.packageSettings?.service, '1on1'); @@ -128,9 +134,7 @@ Tinytest.addAsync( }); smokeEmailTest(function (stream) { - Meteor.settings.packages = { - email: { service: '1on1', user: 'test', password: 'pwd' }, - }; + Meteor.settings.packages = CUSTOM_TRANSPORT_SETTINGS; Email.customTransport = (options) => { test.equal(options.from, 'foo@example.com'); test.equal(options.packageSettings?.service, '1on1'); @@ -239,7 +243,7 @@ Tinytest.add('[Sync] email - URL string for known hosts', function (test) { console.dir(hotmailTransport); test.equal(hotmailTransport.transporter.options.service, 'hotmail'); - const falseService = { service: '1on1', user: 'test', password: 'pwd' }; + const falseService = CUSTOM_TRANSPORT_SETTINGS.email; const errorMsg = 'Could not recognize e-mail service. See list at https://nodemailer.com/smtp/well-known/ for services that we can configure for you.'; test.throws(() => EmailTest.knowHostsTransport(falseService), errorMsg); @@ -248,3 +252,60 @@ Tinytest.add('[Sync] email - URL string for known hosts', function (test) { errorMsg ); }); + +Tinytest.addAsync( + '[Async] email - with custom transport exception', + async function (test) { + Meteor.settings.packages = CUSTOM_TRANSPORT_SETTINGS; + Email.customTransport = (options) => { + test.equal(options.from, 'foo@example.com'); + test.equal(options.packageSettings?.service, '1on1'); + throw new Meteor.Error('Expected error'); + }; + await Email.sendAsync({ + from: 'foo@example.com', + to: 'bar@example.com', + }).catch((err) => { + test.equal(err.error, 'Expected error'); + }); + Meteor.settings.packages = undefined; + Email.customTransport = undefined; + } +); + +Tinytest.addAsync( + '[Async] email - with custom transport long time running', + async function (test) { + Meteor.settings.packages = CUSTOM_TRANSPORT_SETTINGS; + Email.customTransport = async (options) => { + await sleep(3000); + test.equal(options.from, 'foo@example.com'); + test.equal(options.packageSettings?.service, '1on1'); + }; + await Email.sendAsync({ + from: 'foo@example.com', + to: 'bar@example.com', + }); + Meteor.settings.packages = undefined; + Email.customTransport = undefined; + } +); + +Tinytest.addAsync( + '[Sync] email - with custom transport long time running', + function (test, onComplete) { + Meteor.settings.packages = CUSTOM_TRANSPORT_SETTINGS; + Email.customTransport = async (options) => { + await sleep(3000); + test.equal(options.from, 'foo@example.com'); + test.equal(options.packageSettings?.service, '1on1'); + Meteor.settings.packages = undefined; + Email.customTransport = undefined; + onComplete(); + }; + Email.send({ + from: 'foo@example.com', + to: 'bar@example.com', + }); + } +); From 7f070b83c1e43a2122a41039d8785559599c6378 Mon Sep 17 00:00:00 2001 From: Jan Dvorak Date: Wed, 5 Oct 2022 08:54:51 +0200 Subject: [PATCH 79/99] Update default Facebook API to v15 and fix local changelog --- docs/history.md | 12 ++++++++++ packages/facebook-oauth/CHANGELOG.md | 26 +++++++++++++++++----- packages/facebook-oauth/facebook_client.js | 2 +- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/docs/history.md b/docs/history.md index e20218863c..f51be3d9d7 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,3 +1,15 @@ +## 2.X.X, Unreleased + +#### Highlights + +#### Breaking Changes + +#### Migration Steps + +#### Meteor Version Release +* `facebook-oauth@1.12.0` + - Updated default version of Facebook GraphAPI to v15 + ## 2.7.3, 2022-05-31 #### Highlights diff --git a/packages/facebook-oauth/CHANGELOG.md b/packages/facebook-oauth/CHANGELOG.md index a772af6e78..b492fe1f09 100644 --- a/packages/facebook-oauth/CHANGELOG.md +++ b/packages/facebook-oauth/CHANGELOG.md @@ -1,10 +1,26 @@ # Changelog -## 1.8.0 - unreleased -### Breaking changes -- N/A - +## 1.12.0 - UNRELEASED +### Changes +- Updated default version of Facebook GraphAPI to v15 + +## 1.11.0 - 2022-03-24 +### Changes +- Updated default version of Facebook GraphAPI to v12 + +## 1.10.0 - 2021-09-14 +### Changes +- Added login handler hook, like in the Google package for easier management in React Native and similar apps. [PR](https://github.com/meteor/meteor/pull/11603) + +## 1.9.1 - 2021-08-12 +### Changes +- Allow usage of `http` package both v1 and v2 for backward compatibility + +## 1.9.0 - 2021-06-24 +### Changes +- Upgrade default Facebook API to v10 [#11362](https://github.com/meteor/meteor/pull/11362) + +## 1.8.0 - 2021-04-15 ### Changes -- Updated to use Facebook GraphAPI v10 - You can now override the default API version by setting `Meteor.settings.public.packages.facebook-oauth.apiVersion` to for example `8.0` ## 1.7.3 - 2020-10-05 diff --git a/packages/facebook-oauth/facebook_client.js b/packages/facebook-oauth/facebook_client.js index 771e1d6828..582d63bf04 100644 --- a/packages/facebook-oauth/facebook_client.js +++ b/packages/facebook-oauth/facebook_client.js @@ -30,7 +30,7 @@ Facebook.requestCredential = (options, credentialRequestCompleteCallback) => { const loginStyle = OAuth._loginStyle('facebook', config, options); - const API_VERSION = Meteor.settings?.public?.packages?.['facebook-oauth']?.apiVersion || '13.0'; + const API_VERSION = Meteor.settings?.public?.packages?.['facebook-oauth']?.apiVersion || '15.0'; let loginUrl = `https://www.facebook.com/v${API_VERSION}/dialog/oauth?client_id=${config.appId}` + From 55690f7328938f1e6d4cba2d253b77f8b97e8dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Po=C5=9Bpiech?= Date: Wed, 5 Oct 2022 14:55:55 +0200 Subject: [PATCH 80/99] updated adding assets --- packages/ejson/package.js | 2 +- packages/fetch/package.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ejson/package.js b/packages/ejson/package.js index 7ed5099790..654f16c568 100644 --- a/packages/ejson/package.js +++ b/packages/ejson/package.js @@ -5,7 +5,7 @@ Package.describe({ Package.onUse(function onUse(api) { api.use(['ecmascript', 'base64']); - api.addAssets('ejson.d.ts', ['client', 'server']); + api.addAssets('ejson.d.ts', 'server'); api.mainModule('ejson.js'); api.export('EJSON'); }); diff --git a/packages/fetch/package.js b/packages/fetch/package.js index 53830c6a2c..dcba22f913 100644 --- a/packages/fetch/package.js +++ b/packages/fetch/package.js @@ -19,7 +19,7 @@ Package.onUse(function(api) { api.mainModule("legacy.js", "legacy"); api.mainModule("server.js", "server"); - api.addAssets("fetch.d.ts", ["client", "server"]); + api.addAssets("fetch.d.ts", "server"); // The other exports (Headers, Request, Response) can be imported // explicitly from the "meteor/fetch" package. api.export("fetch"); From 43394e18559960b9f7c3aa31e79ecfdc8d5b7006 Mon Sep 17 00:00:00 2001 From: ToyboxZach <64285391+ToyboxZach@users.noreply.github.com> Date: Wed, 5 Oct 2022 11:55:22 -0700 Subject: [PATCH 81/99] Update accounts_server.js --- packages/accounts-base/accounts_server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 9088bbbea9..76da1fe072 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -318,7 +318,7 @@ export class AccountsServer extends AccountsCommon { // If user is not found, try a case insensitive lookup if (!user) { selector = this._selectorForFastCaseInsensitiveLookup(fieldName, fieldValue); - const candidateUsers = Meteor.users.find(selector, options).fetch(); + const candidateUsers = Meteor.users.find(selector, { ...options, limit: 2 }).fetch(); // No match if multiple candidates are found if (candidateUsers.length === 1) { user = candidateUsers[0]; From a8aeeeea3862ae59bdf5da276b7af9cb16ca6ca8 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 10 Oct 2022 20:12:35 +0530 Subject: [PATCH 82/99] add docs for registering login handler --- docs/source/api/accounts-multi.md | 9 +++++++++ packages/accounts-base/accounts_server.js | 21 ++++++++------------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/docs/source/api/accounts-multi.md b/docs/source/api/accounts-multi.md index 644a28970f..8689b15859 100644 --- a/docs/source/api/accounts-multi.md +++ b/docs/source/api/accounts-multi.md @@ -315,6 +315,15 @@ Accounts.setAdditionalFindUserOnExternalLogin(({serviceName, serviceData}) => { } }) ``` +{% apibox "AccountsServer#registerLoginHandler" %} + +Use this to register your own custom authentication method. This is also used by all of the other inbuilt accounts packages to integrate with the accounts system. + +There can be multiple login handlers that are registered. When a login request is made, it will go through all these handlers to find its own handler. + +The registered handler callback is called with a single argument, the `options` object which comes from the login method. For example, if you want to login with a plaintext password, `options` could be `{ user: { username: }, password: }`,or `{ user: { email: }, password: }`. + +The login handler should return `undefined` if it's not going to handle the login request or else the login result object.

Rate Limiting

diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 9088bbbea9..35d6df57e7 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -547,19 +547,14 @@ export class AccountsServer extends AccountsCommon { /// LOGIN HANDLERS /// - // The main entry point for auth packages to hook in to login. - // - // A login handler is a login method which can return `undefined` to - // indicate that the login request is not handled by this handler. - // - // @param name {String} Optional. The service name, used by default - // if a specific service name isn't returned in the result. - // - // @param handler {Function} A function that receives an options object - // (as passed as an argument to the `login` method) and returns one of: - // - `undefined`, meaning don't handle; - // - a login method result object - + /** + * @summary Registers a new login handler. + * @locus Server + * @param {String} [name] The type of login method like oauth, password, etc. + * @param {Function} handler A function that receives an options object + * (as passed as an argument to the `login` method) and returns one of + * `undefined`, meaning don't handle or a login method result object. + */ registerLoginHandler(name, handler) { if (! handler) { handler = name; From ad77b6d89bad5629bc5604069a8ca9782e7c7764 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Thu, 13 Oct 2022 23:33:31 -0300 Subject: [PATCH 83/99] Don't use Future directly on TinyTest package. Also adding helpers that will be used when adding async tests. --- packages/test-helpers/async_multi.js | 30 +++++- packages/test-helpers/package.js | 5 +- packages/tinytest/tinytest.js | 153 ++++++++++++++++----------- packages/tinytest/tinytest_server.js | 4 +- 4 files changed, 124 insertions(+), 68 deletions(-) diff --git a/packages/test-helpers/async_multi.js b/packages/test-helpers/async_multi.js index e5ec3cb43c..04be6aedfe 100644 --- a/packages/test-helpers/async_multi.js +++ b/packages/test-helpers/async_multi.js @@ -142,8 +142,13 @@ testAsyncMulti = function (name, funcs, { isOnly = false } = {}) { test.extraDetails.asyncBlock = i++; new Promise(resolve => { - resolve(func.apply(context, [test, _.bind(em.expect, em)])); - }).then(result => { + const result = func.apply(context, [test, _.bind(em.expect, em)]); + if (result && typeof result.then === "function") { + return result.then((r) => resolve(r)) + } + + return resolve(result); + }).then(() => { em.done(); }, exception => { if (em.cancel()) { @@ -191,3 +196,24 @@ pollUntil = function (expect, f, timeout, step, noFail) { step ); }; + +/** + * Helper that is used on the async tests. + * Just run the function and assert if we have an error or not. + * @param fn + * @param test + * @param shouldErrorOut + * @returns {Promise<*>} + */ +runAndThrowIfNeeded = async (fn, test, shouldErrorOut) => { + let err, result; + try { + result = await fn(); + } catch (e) { + err = e; + } + + test[shouldErrorOut ? "isTrue" : "isFalse"](err); + + return result; +}; diff --git a/packages/test-helpers/package.js b/packages/test-helpers/package.js index 17b6e0f37a..399e768cbe 100644 --- a/packages/test-helpers/package.js +++ b/packages/test-helpers/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Utility functions for tests", - version: '1.3.0' + version: '1.3.1' }); Package.onUse(function (api) { @@ -28,7 +28,8 @@ Package.onUse(function (api) { 'SeededRandom', 'clickElement', 'blurElement', 'focusElement', 'simulateEvent', 'getStyleProperty', 'canonicalizeHtml', 'renderToDiv', 'clickIt', - 'withCallbackLogger', 'testAsyncMulti', 'simplePoll', + 'withCallbackLogger', 'testAsyncMulti', + 'simplePoll', 'runAndThrowIfNeeded', 'makeTestConnection', 'DomUtils']); api.addFiles('try_all_permutations.js'); diff --git a/packages/tinytest/tinytest.js b/packages/tinytest/tinytest.js index 045548c6de..f5cd025b98 100644 --- a/packages/tinytest/tinytest.js +++ b/packages/tinytest/tinytest.js @@ -1,5 +1,3 @@ -const Future = Meteor.isServer && require('fibers/future'); - /******************************************************************************/ /* TestCaseResults */ /******************************************************************************/ @@ -186,6 +184,43 @@ export class TestCaseResults { this.ok(); } + _assertActual(actual, predicate, message) { + if (actual && predicate(actual)) + this.ok(); + else + this.fail({ + type: "throws", + message: (actual ? + "wrong error thrown: " + actual.message : + "did not throw an error as expected") + (message ? ": " + message : ""), + }); + } + + _guessPredicate(expected) { + let predicate; + + if (expected === undefined) { + predicate = function () { + return true; + }; + } else if (typeof expected === "string") { + predicate = function (actual) { + return typeof actual.message === "string" && + actual.message.indexOf(expected) !== -1; + }; + } else if (expected instanceof RegExp) { + predicate = function (actual) { + return expected.test(actual.message); + }; + } else if (typeof expected === 'function') { + predicate = expected; + } else { + throw new Error('expected should be a string, regexp, or predicate function'); + } + + return predicate; + } + // expected can be: // undefined: accept any exception. // string: pass if the string is a substring of the exception message. @@ -204,26 +239,8 @@ export class TestCaseResults { // particular class, use a predicate function. // throws(f, expected, message) { - var actual, predicate; - - if (expected === undefined) { - predicate = function (actual) { - return true; - }; - } else if (typeof expected === "string") { - predicate = function (actual) { - return typeof actual.message === "string" && - actual.message.indexOf(expected) !== -1; - }; - } else if (expected instanceof RegExp) { - predicate = function (actual) { - return expected.test(actual.message); - }; - } else if (typeof expected === 'function') { - predicate = expected; - } else { - throw new Error('expected should be a string, regexp, or predicate function'); - } + let actual; + const predicate = this._guessPredicate(expected); try { f(); @@ -231,15 +248,27 @@ export class TestCaseResults { actual = exception; } - if (actual && predicate(actual)) - this.ok(); - else - this.fail({ - type: "throws", - message: (actual ? - "wrong error thrown: " + actual.message : - "did not throw an error as expected") + (message ? ": " + message : ""), - }); + this._assertActual(actual, predicate, message); + } + + /** + * Same as throw, but accepts an async function as a parameter. + * @param f + * @param expected + * @param message + * @returns {Promise} + */ + async throwsAsync(f, expected, message) { + let actual; + const predicate = this._guessPredicate(expected); + + try { + await f(); + } catch (exception) { + actual = exception; + } + + this._assertActual(actual, predicate, message); } isTrue(v, msg) { @@ -309,7 +338,7 @@ export class TestCaseResults { pass = true; } } else { - /* fail -- not something that contains other things */; + /* fail -- not something that contains other things */ } if (pass === ! not) { @@ -546,37 +575,37 @@ export class TestRun { } if (Meteor.isServer) { - // On the server, ensure that only one test runs at a time, even - // with multiple clients. this.manager.testQueue.queueTask(() => { - // The future resolves when the test completes or times out. - var future = new Future(); - Meteor.setTimeout( - () => { - if (future.isResolved()) - // If the future has resolved the test has completed. - return; - test.timedOut = true; - this._report(test, { - type: "exception", - details: { - message: "test timed out" - } - }); - future['return'](); - }, - 3 * 60 * 1000 // 3 minutes - ); - this._runTest(test, () => { - // The test can complete after it has timed out (it might - // just be slow), so only resolve the future if the test - // hasn't timed out. - if (! future.isResolved()) - future['return'](); - }, stop_at_offset); - // Wait for the test to complete or time out. - future.wait(); - onComplete && onComplete(); + // On the server, ensure that only one test runs at a time, even + // with multiple clients. + let hasRan = false; + const timeoutPromise = new Promise((resolve) => { + Meteor.setTimeout(() => { + if (!hasRan) { + test.timedOut = true; + this._report(test, { + type: "exception", + details: { + message: "test timed out" + } + }); + } + + resolve(); + }, 3 * 60 * 1000); + }); + const runnerPromise = new Promise((resolve) => { + this._runTest(test, () => { + if (!hasRan) { + hasRan = true; + } + resolve(); + }, stop_at_offset); + }); + + Promise.race([runnerPromise, timeoutPromise]).finally(() => { + onComplete && onComplete(); + }); }); } else { // client diff --git a/packages/tinytest/tinytest_server.js b/packages/tinytest/tinytest_server.js index c43fb12b34..331a7007e7 100644 --- a/packages/tinytest/tinytest_server.js +++ b/packages/tinytest/tinytest_server.js @@ -9,7 +9,7 @@ import { export { Tinytest }; -const Fiber = require('fibers'); +const Fiber = Meteor._isFibersEnabled && require('fibers'); const handlesForRun = new Map; const reportsForRun = new Map; @@ -58,7 +58,7 @@ Meteor.methods({ } function onReport(report) { - if (! Fiber.current) { + if (Fiber && !Fiber.current) { Meteor._debug("Trying to report a test not in a fiber! "+ "You probably forgot to wrap a callback in bindEnvironment."); console.trace(); From 12e867f89b15d56ecee807817e6028e086ce81fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Miernik?= Date: Tue, 18 Oct 2022 22:31:19 +0200 Subject: [PATCH 84/99] Updated MongoDB driver to 4.10. --- .../.npm/package/npm-shrinkwrap.json | 30 +++++++++---------- packages/npm-mongo/package.js | 4 +-- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json index bdf03af949..67cb84d7f3 100644 --- a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json +++ b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json @@ -2,14 +2,14 @@ "lockfileVersion": 1, "dependencies": { "@types/node": { - "version": "18.7.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.13.tgz", - "integrity": "sha512-46yIhxSe5xEaJZXWdIBP7GU4HDTG8/eo0qd9atdiL+lFpA03y8KS+lkTN834TWJj5767GbWv4n/P6efyTFt1Dw==" + "version": "18.11.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.0.tgz", + "integrity": "sha512-IOXCvVRToe7e0ny7HpT/X9Rb2RYtElG1a+VshjwT00HxrM2dWBApHQoqsI6WiY7Q03vdf2bCrIGzVrkF/5t10w==" }, "@types/webidl-conversions": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", - "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==" }, "@types/whatwg-url": { "version": "8.2.2", @@ -52,14 +52,14 @@ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "mongodb": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.9.0.tgz", - "integrity": "sha512-tJJEFJz7OQTQPZeVHZJIeSOjMRqc5eSyXTt86vSQENEErpkiG7279tM/GT5AVZ7TgXNh9HQxoa2ZkbrANz5GQw==" + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.10.0.tgz", + "integrity": "sha512-My2QxLTw0Cc1O9gih0mz4mqo145Jq4rLAQx0Glk/Ha9iYBzYpt4I2QFNRIh35uNFNfe8KFQcdwY1/HKxXBkinw==" }, "mongodb-connection-string-url": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.3.tgz", - "integrity": "sha512-f+/WsED+xF4B74l3k9V/XkTVj5/fxFH2o5ToKXd8Iyi5UhM+sO9u0Ape17Mvl/GkZaFtM0HQnzAG5OTmhKw+tQ==" + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.4.tgz", + "integrity": "sha512-SeAxuWs0ez3iI3vvmLk/j2y+zHwigTDKQhtdxTgt5ZCOQQS5+HW4g45/Xw5vzzbn7oQXCNQ24Z40AkJsizEy7w==" }, "punycode": { "version": "2.1.1", @@ -77,9 +77,9 @@ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" }, "socks": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.0.tgz", - "integrity": "sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA==" + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==" }, "sparse-bitfield": { "version": "3.0.3", diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index b8519fa86a..bab293fd71 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,12 +3,12 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "4.9.0", + version: "4.10.0", documentation: null }); Npm.depends({ - mongodb: "4.9.0" + mongodb: "4.10.0" }); Package.onUse(function (api) { From c951192ff83d7a01f20f5ec95763324bf8d37fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Miernik?= Date: Thu, 20 Oct 2022 08:34:04 +0200 Subject: [PATCH 85/99] Updated MongoDB driver to 4.11. --- .../.npm/package/npm-shrinkwrap.json | 377 +++++++++++++++++- packages/npm-mongo/package.js | 4 +- 2 files changed, 373 insertions(+), 8 deletions(-) diff --git a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json index 67cb84d7f3..11662ebe99 100644 --- a/packages/npm-mongo/.npm/package/npm-shrinkwrap.json +++ b/packages/npm-mongo/.npm/package/npm-shrinkwrap.json @@ -1,10 +1,350 @@ { "lockfileVersion": 1, "dependencies": { + "@aws-crypto/ie11-detection": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz", + "integrity": "sha512-5XDMQY98gMAf/WRTic5G++jfmS/VLM0rwpiOpaainKi4L0nqWMSB1SzsrEG5rjFZGYN6ZAefO+/Yta2dFM0kMw==", + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@aws-crypto/sha256-browser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-2.0.0.tgz", + "integrity": "sha512-rYXOQ8BFOaqMEHJrLHul/25ckWH6GTJtdLSajhlqGMx0PmSueAuvboCuZCTqEKlxR8CQOwRarxYMZZSYlhRA1A==", + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@aws-crypto/sha256-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-2.0.0.tgz", + "integrity": "sha512-VZY+mCY4Nmrs5WGfitmNqXzaE873fcIZDu54cbaDaaamsaTOP1DBImV9F4pICc3EHjQXujyE8jig+PFCaew9ig==", + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@aws-crypto/supports-web-crypto": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-2.0.2.tgz", + "integrity": "sha512-6mbSsLHwZ99CTOOswvCRP3C+VCWnzBf+1SnbWxzzJ9lR0mA0JnY2JEAhp8rqmTE0GPFy88rrM27ffgp62oErMQ==", + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@aws-crypto/util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-2.0.2.tgz", + "integrity": "sha512-Lgu5v/0e/BcrZ5m/IWqzPUf3UYFTy/PpeED+uc9SWUR1iZQL8XXbGQg10UfllwwBryO3hFF5dizK+78aoXC1eA==", + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@aws-sdk/abort-controller": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/abort-controller/-/abort-controller-3.190.0.tgz", + "integrity": "sha512-M6qo2exTzEfHT5RuW7K090OgesUojhb2JyWiV4ulu7ngY4DWBUBMKUqac696sHRUZvGE5CDzSi0606DMboM+kA==" + }, + "@aws-sdk/client-cognito-identity": { + "version": "3.192.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.192.0.tgz", + "integrity": "sha512-nIRmiv5JY8wWGUadhG7yLx8o8aVETj5CAgO8e8UJIwwqfue/Yv9bHi2mvkUphO1pj0TeBatAtvu79neJQtsR5g==" + }, + "@aws-sdk/client-sso": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.190.0.tgz", + "integrity": "sha512-joEKRjJEzgvXnEih/x2UDDCPlvXWCO3MAHmqi44yJ36Ph4YsFS299mOjPdVLuzUtpQ+cST1nRO7hXNFrulW2jQ==" + }, + "@aws-sdk/client-sts": { + "version": "3.192.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.192.0.tgz", + "integrity": "sha512-iv72dmRxbZ1cN5jGn4KIVzzu11eduS2fXHbNgd7JsFd5hLBV5TvJaugQzUdXNmy2gN4HiRJr+qa9WkD5b39lsA==" + }, + "@aws-sdk/config-resolver": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/config-resolver/-/config-resolver-3.190.0.tgz", + "integrity": "sha512-K+VnDtjTgjpf7yHEdDB0qgGbHToF0pIL0pQMSnmk2yc8BoB3LGG/gg1T0Ki+wRlrFnDCJ6L+8zUdawY2qDsbyw==" + }, + "@aws-sdk/credential-provider-cognito-identity": { + "version": "3.192.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.192.0.tgz", + "integrity": "sha512-CWo+KyHCGyYtvjlmDIGtnwBEkdiondergZADiStbFFvie8pPI7IsdTXNVssQQ1VxKIBGGHVebgZGSklHBqthwA==" + }, + "@aws-sdk/credential-provider-env": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.190.0.tgz", + "integrity": "sha512-GTY7l3SJhTmRGFpWddbdJOihSqoMN8JMo3CsCtIjk4/h3xirBi02T4GSvbrMyP7FP3Fdl4NAdT+mHJ4q2Bvzxw==" + }, + "@aws-sdk/credential-provider-imds": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-imds/-/credential-provider-imds-3.190.0.tgz", + "integrity": "sha512-gI5pfBqGYCKdmx8igPvq+jLzyE2kuNn9Q5u73pdM/JZxiq7GeWYpE/MqqCubHxPtPcTFgAwxCxCFoXlUTBh/2g==" + }, + "@aws-sdk/credential-provider-ini": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.190.0.tgz", + "integrity": "sha512-Z7NN/evXJk59hBQlfOSWDfHntwmxwryu6uclgv7ECI6SEVtKt1EKIlPuCLUYgQ4lxb9bomyO5lQAl/1WutNT5w==" + }, + "@aws-sdk/credential-provider-node": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.190.0.tgz", + "integrity": "sha512-ctCG5+TsIK2gVgvvFiFjinPjc5nGpSypU3nQKCaihtPh83wDN6gCx4D0p9M8+fUrlPa5y+o/Y7yHo94ATepM8w==" + }, + "@aws-sdk/credential-provider-process": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.190.0.tgz", + "integrity": "sha512-sIJhICR80n5XY1kW/EFHTh5ZzBHb5X+744QCH3StcbKYI44mOZvNKfFdeRL2fQ7yLgV7npte2HJRZzQPWpZUrw==" + }, + "@aws-sdk/credential-provider-sso": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.190.0.tgz", + "integrity": "sha512-uarU9vk471MHHT+GJj3KWFSmaaqLNL5n1KcMer2CCAZfjs+mStAi8+IjZuuKXB4vqVs5DxdH8cy5aLaJcBlXwQ==" + }, + "@aws-sdk/credential-provider-web-identity": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.190.0.tgz", + "integrity": "sha512-nlIBeK9hGHKWC874h+ITAfPZ9Eaok+x/ydZQVKsLHiQ9PH3tuQ8AaGqhuCwBSH0hEAHZ/BiKeEx5VyWAE8/x+Q==" + }, + "@aws-sdk/credential-providers": { + "version": "3.192.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.192.0.tgz", + "integrity": "sha512-iBTrEPkfOHlfgQyk7EeUCmZnhUKXsGcc/hhxBbc6Z/Xc7Y8LqRVLbEmHq9lruXraFuvs26xV9oZi1s1UMXneQA==" + }, + "@aws-sdk/fetch-http-handler": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/fetch-http-handler/-/fetch-http-handler-3.190.0.tgz", + "integrity": "sha512-5riRpKydARXAPLesTZm6eP6QKJ4HJGQ3k0Tepi3nvxHVx3UddkRNoX0pLS3rvbajkykWPNC2qdfRGApWlwOYsA==" + }, + "@aws-sdk/hash-node": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/hash-node/-/hash-node-3.190.0.tgz", + "integrity": "sha512-DNwVT3O8zc9Jk/bXiXcN0WsD98r+JJWryw9F1/ZZbuzbf6rx2qhI8ZK+nh5X6WMtYPU84luQMcF702fJt/1bzg==" + }, + "@aws-sdk/invalid-dependency": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/invalid-dependency/-/invalid-dependency-3.190.0.tgz", + "integrity": "sha512-crCh63e8d/Uw9y3dQlVTPja7+IZiXpNXyH6oSuAadTDQwMq6KK87Av1/SDzVf6bAo2KgAOo41MyO2joaCEk0dQ==" + }, + "@aws-sdk/is-array-buffer": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.188.0.tgz", + "integrity": "sha512-n69N4zJZCNd87Rf4NzufPzhactUeM877Y0Tp/F3KiHqGeTnVjYUa4Lv1vLBjqtfjYb2HWT3NKlYn5yzrhaEwiQ==" + }, + "@aws-sdk/middleware-content-length": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-content-length/-/middleware-content-length-3.190.0.tgz", + "integrity": "sha512-sSU347SuC6I8kWum1jlJlpAqeV23KP7enG+ToWcEcgFrJhm3AvuqB//NJxDbkKb2DNroRvJjBckBvrwNAjQnBQ==" + }, + "@aws-sdk/middleware-host-header": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.190.0.tgz", + "integrity": "sha512-cL7Vo/QSpGx/DDmFxjeV0Qlyi1atvHQDPn3MLBBmi1icu+3GKZkCMAJwzsrV3U4+WoVoDYT9FJ9yMQf2HaIjeQ==" + }, + "@aws-sdk/middleware-logger": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.190.0.tgz", + "integrity": "sha512-rrfLGYSZCBtiXNrIa8pJ2uwUoUMyj6Q82E8zmduTvqKWviCr6ZKes0lttGIkWhjvhql2m4CbjG5MPBnY7RXL4A==" + }, + "@aws-sdk/middleware-recursion-detection": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.190.0.tgz", + "integrity": "sha512-5tc1AIIZe5jDNdyuJW+7vIFmQOxz3q031ZVrEtUEIF7cz2ySho2lkOWziz+v+UGSLhjHGKMz3V26+aN1FLZNxQ==" + }, + "@aws-sdk/middleware-retry": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-retry/-/middleware-retry-3.190.0.tgz", + "integrity": "sha512-h1bPopkncf2ue/erJdhqvgR2AEh0bIvkNsIHhx93DckWKotZd/GAVDq0gpKj7/f/7B+teHH8Fg5GDOwOOGyKcg==" + }, + "@aws-sdk/middleware-sdk-sts": { + "version": "3.192.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.192.0.tgz", + "integrity": "sha512-xzTV7MyG5ipWYTvekWX1tQc5ExsUvCYsDTBCD3LR5hBrP8assUDPo52zGSe+QMcjgnQv7BcYIzeikTkLEG0dUw==" + }, + "@aws-sdk/middleware-serde": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-serde/-/middleware-serde-3.190.0.tgz", + "integrity": "sha512-S132hEOK4jwbtZ1bGAgSuQ0DMFG4TiD4ulAwbQRBYooC7tiWZbRiR0Pkt2hV8d7WhOHgUpg7rvqlA7/HXXBAsA==" + }, + "@aws-sdk/middleware-signing": { + "version": "3.192.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.192.0.tgz", + "integrity": "sha512-qTRIU/TL/dvtTrNj+AkZkgYeTIFslib3Y3XnQNNM6RCm4cMxIgs2K/lnhaUmLdbzHrpOQb4cISkY8yiHo+pNsw==" + }, + "@aws-sdk/middleware-stack": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-stack/-/middleware-stack-3.190.0.tgz", + "integrity": "sha512-h1mqiWNJdi1OTSEY8QovpiHgDQEeRG818v8yShpqSYXJKEqdn54MA3Z1D2fg/Wv/8ZJsFrBCiI7waT1JUYOmCg==" + }, + "@aws-sdk/middleware-user-agent": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.190.0.tgz", + "integrity": "sha512-y/2cTE1iYHKR0nkb3DvR3G8vt12lcTP95r/iHp8ZO+Uzpc25jM/AyMHWr2ZjqQiHKNlzh8uRw1CmQtgg4sBxXQ==" + }, + "@aws-sdk/node-config-provider": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-config-provider/-/node-config-provider-3.190.0.tgz", + "integrity": "sha512-TJPUchyeK5KeEXWrwb6oW5/OkY3STCSGR1QIlbPcaTGkbo4kXAVyQmmZsY4KtRPuDM6/HlfUQV17bD716K65rQ==" + }, + "@aws-sdk/node-http-handler": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.190.0.tgz", + "integrity": "sha512-3Klkr73TpZkCzcnSP+gmFF0Baluzk3r7BaWclJHqt2LcFUWfIJzYlnbBQNZ4t3EEq7ZlBJX85rIDHBRlS+rUyA==" + }, + "@aws-sdk/property-provider": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/property-provider/-/property-provider-3.190.0.tgz", + "integrity": "sha512-uzdKjHE2blbuceTC5zeBgZ0+Uo/hf9pH20CHpJeVNtrrtF3GALtu4Y1Gu5QQVIQBz8gjHnqANx0XhfYzorv69Q==" + }, + "@aws-sdk/protocol-http": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/protocol-http/-/protocol-http-3.190.0.tgz", + "integrity": "sha512-s5MVfeONpfZYRzCSbqQ+wJ3GxKED+aSS7+CQoeaYoD6HDTDxaMGNv9aiPxVCzW02sgG7py7f29Q6Vw+5taZXZA==" + }, + "@aws-sdk/querystring-builder": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-builder/-/querystring-builder-3.190.0.tgz", + "integrity": "sha512-w9mTKkCsaLIBC8EA4RAHrqethNGbf60CbpPzN/QM7yCV3ZZJAXkppFfjTVVOMbPaI8GUEOptJtzgqV68CRB7ow==" + }, + "@aws-sdk/querystring-parser": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/querystring-parser/-/querystring-parser-3.190.0.tgz", + "integrity": "sha512-vCKP0s33VtS47LSYzEWRRr2aTbi3qNkUuQyIrc5LMqBfS5hsy79P1HL4Q7lCVqZB5fe61N8fKzOxDxWRCF0sXg==" + }, + "@aws-sdk/service-error-classification": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/service-error-classification/-/service-error-classification-3.190.0.tgz", + "integrity": "sha512-g+s6xtaMa5fCMA2zJQC4BiFGMP7FN5/L1V/UwxCnKy8skCwaN0K5A1tFffBjjbYiPI7Gu7LVorWD2A0Y4xl01Q==" + }, + "@aws-sdk/shared-ini-file-loader": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/shared-ini-file-loader/-/shared-ini-file-loader-3.190.0.tgz", + "integrity": "sha512-CZC/xsGReUEl5w+JgfancrxfkaCbEisyIFy6HALUYrioWQe80WMqLAdUMZSXHWjIaNK9mH0J/qvcSV2MuIoMzQ==" + }, + "@aws-sdk/signature-v4": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.190.0.tgz", + "integrity": "sha512-L/R/1X2T+/Kg2k/sjoYyDFulVUGrVcRfyEKKVFIUNg0NwUtw5UKa1/gS7geTKcg4q8M2pd/v+OCBrge2X7phUw==" + }, + "@aws-sdk/smithy-client": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.190.0.tgz", + "integrity": "sha512-f5EoCwjBLXMyuN491u1NmEutbolL0cJegaJbtgK9OJw2BLuRHiBknjDF4OEVuK/WqK0kz2JLMGi9xwVPl4BKCA==" + }, + "@aws-sdk/types": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.190.0.tgz", + "integrity": "sha512-mkeZ+vJZzElP6OdRXvuLKWHSlDQxZP9u8BjQB9N0Rw0pCXTzYS0vzIhN1pL0uddWp5fMrIE68snto9xNR6BQuA==" + }, + "@aws-sdk/url-parser": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/url-parser/-/url-parser-3.190.0.tgz", + "integrity": "sha512-FKFDtxA9pvHmpfWmNVK5BAVRpDgkWMz3u4Sg9UzB+WAFN6UexRypXXUZCFAo8S04FbPKfYOR3O0uVlw7kzmj9g==" + }, + "@aws-sdk/util-base64-browser": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-browser/-/util-base64-browser-3.188.0.tgz", + "integrity": "sha512-qlH+5NZBLiyKziL335BEPedYxX6j+p7KFRWXvDQox9S+s+gLCayednpK+fteOhBenCcR9fUZOVuAPScy1I8qCg==" + }, + "@aws-sdk/util-base64-node": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-node/-/util-base64-node-3.188.0.tgz", + "integrity": "sha512-r1dccRsRjKq+OhVRUfqFiW3sGgZBjHbMeHLbrAs9jrOjU2PTQ8PSzAXLvX/9lmp7YjmX17Qvlsg0NCr1tbB9OA==" + }, + "@aws-sdk/util-body-length-browser": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-browser/-/util-body-length-browser-3.188.0.tgz", + "integrity": "sha512-8VpnwFWXhnZ/iRSl9mTf+VKOX9wDE8QtN4bj9pBfxwf90H1X7E8T6NkiZD3k+HubYf2J94e7DbeHs7fuCPW5Qg==" + }, + "@aws-sdk/util-body-length-node": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-body-length-node/-/util-body-length-node-3.188.0.tgz", + "integrity": "sha512-XwqP3vxk60MKp4YDdvDeCD6BPOiG2e+/Ou4AofZOy5/toB6NKz2pFNibQIUg2+jc7mPMnGnvOW3MQEgSJ+gu/Q==" + }, + "@aws-sdk/util-buffer-from": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.188.0.tgz", + "integrity": "sha512-NX1WXZ8TH20IZb4jPFT2CnLKSqZWddGxtfiWxD9M47YOtq/SSQeR82fhqqVjJn4P8w2F5E28f+Du4ntg/sGcxA==" + }, + "@aws-sdk/util-config-provider": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-config-provider/-/util-config-provider-3.188.0.tgz", + "integrity": "sha512-LBA7tLbi7v4uvbOJhSnjJrxbcRifKK/1ZVK94JTV2MNSCCyNkFotyEI5UWDl10YKriTIUyf7o5cakpiDZ3O4xg==" + }, + "@aws-sdk/util-defaults-mode-browser": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.190.0.tgz", + "integrity": "sha512-FKxTU4tIbFk2pdUbBNneStF++j+/pB4NYJ1HRSEAb/g4D2+kxikR/WKIv3p0JTVvAkwcuX/ausILYEPUyDZ4HQ==" + }, + "@aws-sdk/util-defaults-mode-node": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.190.0.tgz", + "integrity": "sha512-qBiIMjNynqAP7p6urG1+ZattYkFaylhyinofVcLEiDvM9a6zGt6GZsxru2Loq0kRAXXGew9E9BWGt45HcDc20g==" + }, + "@aws-sdk/util-hex-encoding": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.188.0.tgz", + "integrity": "sha512-QyWovTtjQ2RYxqVM+STPh65owSqzuXURnfoof778spyX4iQ4z46wOge1YV2ZtwS8w5LWd9eeVvDrLu5POPYOnA==" + }, + "@aws-sdk/util-locate-window": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.188.0.tgz", + "integrity": "sha512-SxobBVLZkkLSawTCfeQnhVX3Azm9O+C2dngZVe1+BqtF8+retUbVTs7OfYeWBlawVkULKF2e781lTzEHBBjCzw==" + }, + "@aws-sdk/util-middleware": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.190.0.tgz", + "integrity": "sha512-qzTJ/qhFDzHZS+iXdHydQ/0sWAuNIB5feeLm55Io/I8Utv3l3TKYOhbgGwTsXY+jDk7oD+YnAi7hLN5oEBCwpg==" + }, + "@aws-sdk/util-uri-escape": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.188.0.tgz", + "integrity": "sha512-4Y6AYZMT483Tiuq8dxz5WHIiPNdSFPGrl6tRTo2Oi2FcwypwmFhqgEGcqxeXDUJktvaCBxeA08DLr/AemVhPCg==" + }, + "@aws-sdk/util-user-agent-browser": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.190.0.tgz", + "integrity": "sha512-c074wjsD+/u9vT7DVrBLkwVhn28I+OEHuHaqpTVCvAIjpueZ3oms0e99YJLfpdpEgdLavOroAsNFtAuRrrTZZw==" + }, + "@aws-sdk/util-user-agent-node": { + "version": "3.190.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.190.0.tgz", + "integrity": "sha512-R36BMvvPX8frqFhU4lAsrOJ/2PJEHH/Jz1WZzO3GWmVSEAQQdHmo8tVPE3KOM7mZWe5Hj1dZudFAIxWHHFYKJA==" + }, + "@aws-sdk/util-utf8-browser": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.188.0.tgz", + "integrity": "sha512-jt627x0+jE+Ydr9NwkFstg3cUvgWh56qdaqAMDsqgRlKD21md/6G226z/Qxl7lb1VEW2LlmCx43ai/37Qwcj2Q==" + }, + "@aws-sdk/util-utf8-node": { + "version": "3.188.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-node/-/util-utf8-node-3.188.0.tgz", + "integrity": "sha512-hCgP4+C0Lekjpjt2zFJ2R/iHes5sBGljXa5bScOFAEkRUc0Qw0VNgTv7LpEbIOAwGmqyxBoCwBW0YHPW1DfmYQ==" + }, "@types/node": { - "version": "18.11.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.0.tgz", - "integrity": "sha512-IOXCvVRToe7e0ny7HpT/X9Rb2RYtElG1a+VshjwT00HxrM2dWBApHQoqsI6WiY7Q03vdf2bCrIGzVrkF/5t10w==" + "version": "18.11.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.2.tgz", + "integrity": "sha512-BWN3M23gLO2jVG8g/XHIRFWiiV4/GckeFIqbU/C4V3xpoBBWSMk4OZomouN0wCkfQFPqgZikyLr7DOYDysIkkw==" }, "@types/webidl-conversions": { "version": "7.0.0", @@ -21,6 +361,11 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, "bson": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.0.tgz", @@ -36,6 +381,11 @@ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" }, + "fast-xml-parser": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz", + "integrity": "sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==" + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -52,9 +402,9 @@ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" }, "mongodb": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.10.0.tgz", - "integrity": "sha512-My2QxLTw0Cc1O9gih0mz4mqo145Jq4rLAQx0Glk/Ha9iYBzYpt4I2QFNRIh35uNFNfe8KFQcdwY1/HKxXBkinw==" + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.11.0.tgz", + "integrity": "sha512-9l9n4Nk2BYZzljW3vHah3Z0rfS5npKw6ktnkmFgTcnzaXH1DRm3pDl6VMHu84EVb1lzmSaJC4OzWZqTkB5i2wg==" }, "mongodb-connection-string-url": { "version": "2.5.4", @@ -86,11 +436,26 @@ "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==" }, + "strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "tr46": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==" }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, "webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/packages/npm-mongo/package.js b/packages/npm-mongo/package.js index bab293fd71..2222c52f7a 100644 --- a/packages/npm-mongo/package.js +++ b/packages/npm-mongo/package.js @@ -3,12 +3,12 @@ Package.describe({ summary: "Wrapper around the mongo npm package", - version: "4.10.0", + version: "4.11.0", documentation: null }); Npm.depends({ - mongodb: "4.10.0" + mongodb: "4.11.0" }); Package.onUse(function (api) { From a4ca60e819b59a5466b078bf2590a9ec189a657a Mon Sep 17 00:00:00 2001 From: harryadel Date: Thu, 20 Oct 2022 14:48:11 +0200 Subject: [PATCH 86/99] Append port to restart message --- tools/runners/run-app.js | 2 +- tools/runners/run-log.js | 4 ++-- tools/tests/server-restart-port.js | 25 +++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 tools/tests/server-restart-port.js diff --git a/tools/runners/run-app.js b/tools/runners/run-app.js index d02d044dc5..fbc3b03af5 100644 --- a/tools/runners/run-app.js +++ b/tools/runners/run-app.js @@ -947,7 +947,7 @@ Object.assign(AppRunner.prototype, { var runResult = self._runOnce({ onListen: function () { if (! self.noRestartBanner && ! firstRun) { - runLog.logRestart(); + runLog.logRestart(self); Console.enableProgressDisplay(false); } }, diff --git a/tools/runners/run-log.js b/tools/runners/run-log.js index a661f69963..103c4a7e9a 100644 --- a/tools/runners/run-log.js +++ b/tools/runners/run-log.js @@ -145,7 +145,7 @@ Object.assign(RunLog.prototype, { self.temporaryMessageLength = msg.length; }, - logRestart: function () { + logRestart: function (options) { var self = this; if (self.consecutiveRestartMessages) { @@ -159,7 +159,7 @@ Object.assign(RunLog.prototype, { self.consecutiveRestartMessages = 1; } - var message = "=> Meteor server restarted"; + var message = "=> Meteor server restarted on port " + options.proxy.listenPort; if (self.consecutiveRestartMessages > 1) { message += " (x" + self.consecutiveRestartMessages + ")"; } diff --git a/tools/tests/server-restart-port.js b/tools/tests/server-restart-port.js new file mode 100644 index 0000000000..fddea0cc06 --- /dev/null +++ b/tools/tests/server-restart-port.js @@ -0,0 +1,25 @@ +import * as selftest from '../tool-testing/selftest'; + +selftest.define("server outputs port number on restarting", () => testHelper({ + path: "server/main.js", + id: "server/main.js" +})); + +function testHelper(server) { + const s = new selftest.Sandbox(); + s.createApp("myapp", "client-refresh"); + s.cd("myapp"); + + let run = s.run("--port", "21000"); + run.match("Started proxy"); + run.waitSecs(15); + + run.match(server.id + " 0"); + + s.write(server.path, s.read(server.path).replace( + /module.id, (\d+)/, + (match, n) => `module.id, ${ ++n }`, + )); + + run.match("Meteor server restarted on port 21000"); +} From 1c2210a39b91459c1c982e3a52825d89062f0172 Mon Sep 17 00:00:00 2001 From: harryadel Date: Thu, 20 Oct 2022 16:03:03 +0200 Subject: [PATCH 87/99] Source hello-myfonts locally instead of cdn --- .../non-core/less/tests/hello-myfonts.css | 22 +++++++++++++++++++ packages/non-core/less/tests/top.import.less | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 packages/non-core/less/tests/hello-myfonts.css diff --git a/packages/non-core/less/tests/hello-myfonts.css b/packages/non-core/less/tests/hello-myfonts.css new file mode 100644 index 0000000000..8a1a820d41 --- /dev/null +++ b/packages/non-core/less/tests/hello-myfonts.css @@ -0,0 +1,22 @@ + +/* + FILE ARCHIVED ON 22:48:06 Jan 31, 2021 AND RETRIEVED FROM THE + INTERNET ARCHIVE ON 14:01:15 Oct 20, 2022. + JAVASCRIPT APPENDED BY WAYBACK MACHINE, COPYRIGHT INTERNET ARCHIVE. + + ALL OTHER CONTENT MAY ALSO BE PROTECTED BY COPYRIGHT (17 U.S.C. + SECTION 108(a)(3)). +*/ +/* +playback timings (ms): + captures_list: 273.718 + exclusion.robots: 0.088 + exclusion.robots.policy: 0.08 + cdx.remote: 0.063 + esindex: 0.009 + LoadShardBlock: 52.202 (3) + PetaboxLoader3.datanode: 93.37 (5) + CDXLines.iter: 13.615 (3) + load_resource: 130.222 (2) + PetaboxLoader3.resolve: 65.34 (2) +*/ \ No newline at end of file diff --git a/packages/non-core/less/tests/top.import.less b/packages/non-core/less/tests/top.import.less index b7f1071eda..32fb5035ff 100644 --- a/packages/non-core/less/tests/top.import.less +++ b/packages/non-core/less/tests/top.import.less @@ -6,4 +6,4 @@ // Make sure regular CSS import doesn't make the compiler explode - this was // a regression from 1.1.0.3 caught in QA -@import url("http://hello.myfonts.net/count/2c4b9d"); +@import url("./hello-myfonts.css"); From b3aa0e0196290f4ab3b7218d346330db627be87a Mon Sep 17 00:00:00 2001 From: harryadel Date: Fri, 21 Oct 2022 10:43:28 +0200 Subject: [PATCH 88/99] Revert hello-myfonts.css changes --- .../non-core/less/tests/hello-myfonts.css | 22 ------------------- packages/non-core/less/tests/top.import.less | 2 +- 2 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 packages/non-core/less/tests/hello-myfonts.css diff --git a/packages/non-core/less/tests/hello-myfonts.css b/packages/non-core/less/tests/hello-myfonts.css deleted file mode 100644 index 8a1a820d41..0000000000 --- a/packages/non-core/less/tests/hello-myfonts.css +++ /dev/null @@ -1,22 +0,0 @@ - -/* - FILE ARCHIVED ON 22:48:06 Jan 31, 2021 AND RETRIEVED FROM THE - INTERNET ARCHIVE ON 14:01:15 Oct 20, 2022. - JAVASCRIPT APPENDED BY WAYBACK MACHINE, COPYRIGHT INTERNET ARCHIVE. - - ALL OTHER CONTENT MAY ALSO BE PROTECTED BY COPYRIGHT (17 U.S.C. - SECTION 108(a)(3)). -*/ -/* -playback timings (ms): - captures_list: 273.718 - exclusion.robots: 0.088 - exclusion.robots.policy: 0.08 - cdx.remote: 0.063 - esindex: 0.009 - LoadShardBlock: 52.202 (3) - PetaboxLoader3.datanode: 93.37 (5) - CDXLines.iter: 13.615 (3) - load_resource: 130.222 (2) - PetaboxLoader3.resolve: 65.34 (2) -*/ \ No newline at end of file diff --git a/packages/non-core/less/tests/top.import.less b/packages/non-core/less/tests/top.import.less index 32fb5035ff..ce2c98e250 100644 --- a/packages/non-core/less/tests/top.import.less +++ b/packages/non-core/less/tests/top.import.less @@ -6,4 +6,4 @@ // Make sure regular CSS import doesn't make the compiler explode - this was // a regression from 1.1.0.3 caught in QA -@import url("./hello-myfonts.css"); +@import url("http://hello.myfonts.net/count/2c4b9d"); \ No newline at end of file From ec48d0d7626aa29a6fc9c612c8dc0dccd9118bec Mon Sep 17 00:00:00 2001 From: harryadel Date: Fri, 21 Oct 2022 11:06:47 +0200 Subject: [PATCH 89/99] Use rootUrl to display port number on rebooting --- tools/runners/run-log.js | 2 +- tools/tests/server-restart-port.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/runners/run-log.js b/tools/runners/run-log.js index 103c4a7e9a..d7a2f812f6 100644 --- a/tools/runners/run-log.js +++ b/tools/runners/run-log.js @@ -159,7 +159,7 @@ Object.assign(RunLog.prototype, { self.consecutiveRestartMessages = 1; } - var message = "=> Meteor server restarted on port " + options.proxy.listenPort; + var message = "=> Meteor server restarted at: " + options.rootUrl; if (self.consecutiveRestartMessages > 1) { message += " (x" + self.consecutiveRestartMessages + ")"; } diff --git a/tools/tests/server-restart-port.js b/tools/tests/server-restart-port.js index fddea0cc06..ef6a985541 100644 --- a/tools/tests/server-restart-port.js +++ b/tools/tests/server-restart-port.js @@ -21,5 +21,5 @@ function testHelper(server) { (match, n) => `module.id, ${ ++n }`, )); - run.match("Meteor server restarted on port 21000"); + run.match("Meteor server restarted at: http://localhost:21000/"); } From 80305ddfc0fe07dc023da04b3a8137ebb5d07c78 Mon Sep 17 00:00:00 2001 From: harryadel Date: Fri, 21 Oct 2022 12:22:15 +0200 Subject: [PATCH 90/99] [webapp-hashing] Remove underscore --- packages/webapp-hashing/package.js | 2 +- packages/webapp-hashing/webapp-hashing.js | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/webapp-hashing/package.js b/packages/webapp-hashing/package.js index af8f7e762d..b2353e90c3 100644 --- a/packages/webapp-hashing/package.js +++ b/packages/webapp-hashing/package.js @@ -5,7 +5,7 @@ Package.describe({ Package.onUse(function(api) { api.use('ecmascript'); - api.use('underscore', 'server'); + api.use('server'); api.addFiles('webapp-hashing.js', 'server'); api.export('WebAppHashing'); }); diff --git a/packages/webapp-hashing/webapp-hashing.js b/packages/webapp-hashing/webapp-hashing.js index f2fc190449..0968b45323 100644 --- a/packages/webapp-hashing/webapp-hashing.js +++ b/packages/webapp-hashing/webapp-hashing.js @@ -1,4 +1,4 @@ -var crypto = Npm.require("crypto"); +import { createHash } from "crypto"; WebAppHashing = {}; @@ -13,13 +13,11 @@ WebAppHashing = {}; WebAppHashing.calculateClientHash = function (manifest, includeFilter, runtimeConfigOverride) { - var hash = crypto.createHash('sha1'); + var hash = createHash('sha1'); // Omit the old hashed client values in the new hash. These may be // modified in the new boilerplate. - var runtimeCfg = _.omit(__meteor_runtime_config__, - ['autoupdateVersion', 'autoupdateVersionRefreshable', - 'autoupdateVersionCordova']); + var { autoupdateVersion, autoupdateVersionRefreshable, autoupdateVersionCordova, ...runtimeCfg } = __meteor_runtime_config__; if (runtimeConfigOverride) { runtimeCfg = runtimeConfigOverride; @@ -27,7 +25,7 @@ WebAppHashing.calculateClientHash = hash.update(JSON.stringify(runtimeCfg, 'utf8')); - _.each(manifest, function (resource) { + manifest.forEach(function (resource) { if ((! includeFilter || includeFilter(resource.type, resource.replaceable)) && (resource.where === 'client' || resource.where === 'internal')) { hash.update(resource.path); @@ -39,7 +37,7 @@ WebAppHashing.calculateClientHash = WebAppHashing.calculateCordovaCompatibilityHash = function(platformVersion, pluginVersions) { - const hash = crypto.createHash('sha1'); + const hash = createHash('sha1'); hash.update(platformVersion); From 355fde896a4577bb2d009bca0e63332815278142 Mon Sep 17 00:00:00 2001 From: harryadel Date: Fri, 21 Oct 2022 12:27:14 +0200 Subject: [PATCH 91/99] [twitter-oauth] Remove underscore --- packages/twitter-oauth/package.js | 1 - packages/twitter-oauth/twitter_server.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/twitter-oauth/package.js b/packages/twitter-oauth/package.js index 3f5cde5730..34e0d8bff8 100644 --- a/packages/twitter-oauth/package.js +++ b/packages/twitter-oauth/package.js @@ -7,7 +7,6 @@ Package.onUse(function(api) { api.use('oauth1', ['client', 'server']); api.use('oauth', ['client', 'server']); api.use('random', 'client'); - api.use('underscore', 'server'); api.use('service-configuration', ['client', 'server']); api.addFiles('twitter_common.js', ['server', 'client']); diff --git a/packages/twitter-oauth/twitter_server.js b/packages/twitter-oauth/twitter_server.js index d597f0db1e..6a973ac57d 100644 --- a/packages/twitter-oauth/twitter_server.js +++ b/packages/twitter-oauth/twitter_server.js @@ -26,8 +26,8 @@ OAuth.registerService('twitter', 1, urls, function(oauthBinding) { }; // include helpful fields from twitter - var fields = _.pick(identity, Twitter.whitelistedFields); - _.extend(serviceData, fields); + const { identity: fields } = Twitter.whitelistedFields; + Object.assign(serviceData, fields); return { serviceData: serviceData, From 22403ce8c43831ce68eec4c163ccd660b0d58945 Mon Sep 17 00:00:00 2001 From: harryadel Date: Fri, 21 Oct 2022 12:28:59 +0200 Subject: [PATCH 92/99] [webapp-hashing] Remove api.use('server') --- packages/webapp-hashing/package.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/webapp-hashing/package.js b/packages/webapp-hashing/package.js index b2353e90c3..4098a5b937 100644 --- a/packages/webapp-hashing/package.js +++ b/packages/webapp-hashing/package.js @@ -5,7 +5,6 @@ Package.describe({ Package.onUse(function(api) { api.use('ecmascript'); - api.use('server'); api.addFiles('webapp-hashing.js', 'server'); api.export('WebAppHashing'); }); From adb0184c24ec4ff8c83755a8aa54e87cb130d961 Mon Sep 17 00:00:00 2001 From: harryadel Date: Fri, 21 Oct 2022 12:59:20 +0200 Subject: [PATCH 93/99] [test-in-browser] Remove underscore --- packages/test-in-browser/driver.js | 54 ++++++++++++++--------------- packages/test-in-browser/package.js | 1 - 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/packages/test-in-browser/driver.js b/packages/test-in-browser/driver.js index 5e6120d04a..d0d5fa4423 100644 --- a/packages/test-in-browser/driver.js +++ b/packages/test-in-browser/driver.js @@ -88,7 +88,7 @@ var reportResults = function(results) { } // Now process the current report - if (_.isArray(results.events)) { + if (Array.isArray(results.events)) { // append events, if present Array.prototype.push.apply((test.events || (test.events = [])), results.events); @@ -97,7 +97,7 @@ var reportResults = function(results) { return a.sequence - b.sequence; }); var out = []; - _.each(test.events, function (e) { + test.events.forEach(function (e) { if (out.length === 0 || out[out.length - 1].sequence !== e.sequence) out.push(e); }); @@ -110,7 +110,7 @@ var reportResults = function(results) { // test name yet). if (test.expanded === undefined) test.expanded = true; - if (!_.contains(failedTests, test.fullName)) + if (!failedTests.includes(test.fullName)) failedTests.push(test.fullName); countDep.changed(); @@ -147,16 +147,16 @@ var forgetEvents = function (results) { // possibly 'events'. var _findTestForResults = function (results) { var groupPath = results.groupPath; // array - if ((! _.isArray(groupPath)) || (groupPath.length < 1)) { + if ((! Array.isArray(groupPath)) || (groupPath.length < 1)) { throw new Error("Test must be part of a group"); } var group; var i = 0; - _.each(groupPath, function(gname) { + groupPath.forEach(function(gname) { var array = (group ? (group.groups || (group.groups = [])) : resultTree); - var newGroup = _.find(array, function(g) { return g.name === gname; }); + var newGroup = array.find(function(g) { return g.name === gname; }); if (! newGroup) { newGroup = { name: gname, @@ -177,12 +177,12 @@ var _findTestForResults = function (results) { var testName = results.test; var server = !!results.server; - var test = _.find(group.tests || (group.tests = []), + var test = (group.tests || (group.tests = [])).find( function(t) { return t.name === testName && t.server === server; }); if (! test) { // create test - var nameParts = _.clone(groupPath); + var nameParts = [...groupPath]; nameParts.push(testName); var fullName = nameParts.join(' - '); test = { @@ -209,7 +209,7 @@ var _findTestForResults = function (results) { var _testTime = function(t) { if (t.events && t.events.length > 0) { - var lastEvent = _.last(t.events); + var lastEvent = t.events[t.events.length - 1]; if (lastEvent.type === "finish") { if ((typeof lastEvent.timeMs) === "number") { return lastEvent.timeMs; @@ -221,15 +221,15 @@ var _testTime = function(t) { var _testStatus = function(t) { var events = t.events || []; - if (_.find(events, function(x) { return x.type === "exception"; })) { + if (events.find(function(x) { return x.type === "exception"; })) { // "exception" should be last event, except race conditions on the // server can make this not the case. Technically we can't tell // if the test is still running at this point, but it can only // result in FAIL. return "failed"; - } else if (events.length == 0 || (_.last(events).type != "finish")) { + } else if (events.length == 0 || (events[events.length - 1].type != "finish")) { return "running"; - } else if (_.any(events, function(e) { + } else if (events.some(function(e) { return e.type == "fail" || e.type == "exception"; })) { return "failed"; } else { @@ -261,8 +261,8 @@ Template.navBar.helpers({ var walk = function (groups) { var total = 0; - _.each(groups || [], function (group) { - _.each(group.tests || [], function (t) { + (groups || []).forEach(function (group) { + (group.tests || []).forEach(function (t) { total += _testTime(t); }); @@ -450,14 +450,14 @@ Template.test.helpers({ }, eventsArray: function() { - var events = _.filter(this.events, function(e) { - return e.type != "finish"; + var events = this.events.filter(function(e) { + return e[type] != "finish"; }); var partitionBy = function(seq, func) { var result = []; var lastValue = {}; - _.each(seq, function(x) { + seq.forEach(function(x) { var newValue = func(x); if (newValue === lastValue) { result[result.length-1].push(x); @@ -470,17 +470,17 @@ Template.test.helpers({ }; var dupLists = partitionBy( - _.map(events, function(e) { + events.map(function(e) { // XXX XXX We need something better than stringify! // stringify([undefined]) === "[null]" - e = _.clone(e); + e = Object.assign({}, e); delete e.sequence; return {obj: e, str: JSON.stringify(e)}; }), function(x) { return x.str; }); - return _.map(dupLists, function(L) { + return dupLists.map(function(L) { var obj = L[0].obj; - return (L.length > 1) ? _.extend({times: L.length}, obj) : obj; + return (L.length > 1) ? Object.assign({times: L.length}, obj) : obj; }); } }); @@ -525,7 +525,7 @@ Template.event.helpers({ var type = details.type; var stack = details.stack; - details = _.clone(details); + details = Array.isArray(details) && [...details] || Object.assign({}, details); delete details.type; delete details.stack; @@ -535,14 +535,14 @@ Template.event.helpers({ details.expected); } - return _.compact(_.map(details, function(val, key) { + return Object.entries(details).map(function([key, val]) { // make test._stringEqual results print nicely, // in particular for multiline strings if (type === 'string_equal' && (key === 'actual' || key === 'expected')) { var html = '
';
-            _.each(diff, function (piece) {
+            diff.forEach(function (piece) {
               var which = piece[0];
               var text = piece[1];
               if (which === 0 ||
@@ -561,7 +561,7 @@ Template.event.helpers({
           // You can end up with a an undefined value, e.g. using
           // isNull without providing a message attribute: isNull(1).
           // No need to display those.
-          if (!_.isUndefined(val)) {
+          if (typeof val !== 'undefined') {
             return {
               key: key,
               val: val
@@ -569,7 +569,7 @@ Template.event.helpers({
           } else {
             return undefined;
           }
-        }));
+        }).filter(Boolean);
       };
 
       return {
@@ -583,4 +583,4 @@ Template.event.helpers({
   is_debuggable: function() {
     return !!this.cookie;
   }
-});
+});
\ No newline at end of file
diff --git a/packages/test-in-browser/package.js b/packages/test-in-browser/package.js
index a92fb4206a..7bc88d5cf7 100644
--- a/packages/test-in-browser/package.js
+++ b/packages/test-in-browser/package.js
@@ -13,7 +13,6 @@ Package.onUse(function (api) {
   // XXX this should go away, and there should be a clean interface
   // that tinytest and the driver both implement?
   api.use('tinytest');
-  api.use('underscore');
 
   api.use('session');
   api.use('reload');

From 2f2baea89b7bf0cf7f773951b2871578a6bf42a3 Mon Sep 17 00:00:00 2001
From: harryadel 
Date: Sat, 22 Oct 2022 07:36:23 +0200
Subject: [PATCH 94/99] [geojson-utils] Remove underscore

---
 packages/geojson-utils/geojson-utils.tests.js | 4 ++--
 packages/geojson-utils/package.js             | 1 -
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/packages/geojson-utils/geojson-utils.tests.js b/packages/geojson-utils/geojson-utils.tests.js
index ea7ab39462..2d97d7a472 100644
--- a/packages/geojson-utils/geojson-utils.tests.js
+++ b/packages/geojson-utils/geojson-utils.tests.js
@@ -85,8 +85,8 @@ Tinytest.add("geojson-utils - points distance generated tests", function (test)
     6846704.0253010122, 1368055.9401701286, 14041503.0409814864,
     18560499.7346975356, 3793112.6186894816];
 
-  _.each(tests, function (pair, testN) {
-    var distance = GeoJSON.pointDistance.apply(this, _.map(pair, toGeoJSONPoint));
+    tests.forEach(function (pair, testN) {
+    var distance = GeoJSON.pointDistance.apply(this, pair.map(toGeoJSONPoint));
     test.isTrue(Math.abs(distance - answers[testN]) < 0.000001,
       "Wrong distance between points " + JSON.stringify(pair) + ": " + distance + ", " + Math.abs(distance - answers[testN]) + " differenc");
   });
diff --git a/packages/geojson-utils/package.js b/packages/geojson-utils/package.js
index 5ae045b046..052cdc64bc 100644
--- a/packages/geojson-utils/package.js
+++ b/packages/geojson-utils/package.js
@@ -11,7 +11,6 @@ Package.onUse(function (api) {
 
 Package.onTest(function (api) {
   api.use('tinytest');
-  api.use('underscore');
   api.use('geojson-utils');
   api.addFiles(['geojson-utils.tests.js'], 'client');
 });

From 8130ff1d27bfceafa28057f9b9abd9266d0c1005 Mon Sep 17 00:00:00 2001
From: harryadel 
Date: Sat, 22 Oct 2022 07:39:29 +0200
Subject: [PATCH 95/99] [facts-ui] Remove underscore

---
 packages/facts-ui/facts_ui_client.js | 2 +-
 packages/facts-ui/package.js         | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/packages/facts-ui/facts_ui_client.js b/packages/facts-ui/facts_ui_client.js
index 9542c5f5f9..6731cfe466 100644
--- a/packages/facts-ui/facts_ui_client.js
+++ b/packages/facts-ui/facts_ui_client.js
@@ -6,7 +6,7 @@ Template.serverFacts.helpers({
   factsByPackage: () => Facts.server.find(),
   facts: function () {
     const factArray = [];
-    _.each(this, function (value, name) {
+    Object.entries(this).forEach(function ([name, value]) {
       if (name !== '_id')
         factArray.push({name: name, value: value});
     });
diff --git a/packages/facts-ui/package.js b/packages/facts-ui/package.js
index 20d72c3b10..5f367eb09d 100644
--- a/packages/facts-ui/package.js
+++ b/packages/facts-ui/package.js
@@ -8,8 +8,7 @@ Package.onUse(function (api) {
     'ecmascript',
     'facts-base',
     'mongo',
-    'templating@1.2.13',
-    'underscore',
+    'templating@1.2.13'
   ], 'client');
 
   api.imply('facts-base');

From 80e09739fd96d1c9f5a141f25af1944198e3f4a6 Mon Sep 17 00:00:00 2001
From: harryadel 
Date: Sat, 22 Oct 2022 07:44:38 +0200
Subject: [PATCH 96/99] [ecmascript] Remove underscore

---
 packages/ecmascript/package.js       | 2 +-
 packages/ecmascript/runtime-tests.js | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/ecmascript/package.js b/packages/ecmascript/package.js
index eed53fefd0..37378b5163 100644
--- a/packages/ecmascript/package.js
+++ b/packages/ecmascript/package.js
@@ -31,7 +31,7 @@ Package.onUse(function(api) {
 });
 
 Package.onTest(function(api) {
-  api.use(['tinytest', 'underscore']);
+  api.use(['tinytest']);
   api.use(['es5-shim', 'ecmascript', 'babel-compiler']);
   api.addFiles('runtime-tests.js');
   api.addFiles('transpilation-tests.js', 'server');
diff --git a/packages/ecmascript/runtime-tests.js b/packages/ecmascript/runtime-tests.js
index f0cb308a97..c4ddfe227d 100644
--- a/packages/ecmascript/runtime-tests.js
+++ b/packages/ecmascript/runtime-tests.js
@@ -216,7 +216,7 @@ Tinytest.add('ecmascript - runtime - block scope', test => {
       });
     }
 
-    _.each(thunks, f => f());
+    thunks.forEach(f => f());
     test.equal(buf, [0, 1, 2]);
   }
 });

From 96fac46c6202e9aea2c9e421671e13a41a19b0f2 Mon Sep 17 00:00:00 2001
From: harryadel 
Date: Sat, 22 Oct 2022 07:48:47 +0200
Subject: [PATCH 97/99] [diff-sequence] Remove underscore

---
 packages/diff-sequence/package.js |  1 -
 packages/diff-sequence/tests.js   | 17 ++++++++---------
 2 files changed, 8 insertions(+), 10 deletions(-)

diff --git a/packages/diff-sequence/package.js b/packages/diff-sequence/package.js
index 18c673049b..ff143f3e8a 100644
--- a/packages/diff-sequence/package.js
+++ b/packages/diff-sequence/package.js
@@ -14,7 +14,6 @@ Package.onUse(function (api) {
 Package.onTest(function (api) {
   api.use([
     'tinytest',
-    'underscore',
     'ejson'
   ]);
 
diff --git a/packages/diff-sequence/tests.js b/packages/diff-sequence/tests.js
index a8cc9c3c58..8c593baee0 100644
--- a/packages/diff-sequence/tests.js
+++ b/packages/diff-sequence/tests.js
@@ -1,6 +1,6 @@
 Tinytest.add("diff-sequence - diff changes ordering", function (test) {
   var makeDocs = function (ids) {
-    return _.map(ids, function (id) { return {_id: id};});
+    return ids.map(function (id) { return {_id: id};});
   };
   var testMutation = function (a, b) {
     var aa = makeDocs(a);
@@ -10,12 +10,12 @@ Tinytest.add("diff-sequence - diff changes ordering", function (test) {
 
       addedBefore: function (id, doc, before) {
         if (before === null) {
-          aaCopy.push( _.extend({_id: id}, doc));
+          aaCopy.push( Object.assign({_id: id}, doc));
           return;
         }
         for (var i = 0; i < aaCopy.length; i++) {
           if (aaCopy[i]._id === before) {
-            aaCopy.splice(i, 0, _.extend({_id: id}, doc));
+            aaCopy.splice(i, 0, Object.assign({_id: id}, doc));
             return;
           }
         }
@@ -29,12 +29,12 @@ Tinytest.add("diff-sequence - diff changes ordering", function (test) {
           }
         }
         if (before === null) {
-          aaCopy.push( _.extend({_id: id}, found));
+          aaCopy.push( Object.assign({_id: id}, found));
           return;
         }
         for (i = 0; i < aaCopy.length; i++) {
           if (aaCopy[i]._id === before) {
-            aaCopy.splice(i, 0, _.extend({_id: id}, found));
+            aaCopy.splice(i, 0, Object.assign({_id: id}, found));
             return;
           }
         }
@@ -75,7 +75,7 @@ Tinytest.add("diff-sequence - diff", function (test) {
     for (var i = 1; i <= origLen; i++)
       oldResults[i-1] = {_id: i};
 
-    var newResults = _.map(newOldIdx, function(n) {
+    var newResults = newOldIdx.map(function(n) {
       var doc = {_id: Math.abs(n)};
       if (n < 0)
         doc.changed = true;
@@ -89,7 +89,7 @@ Tinytest.add("diff-sequence - diff", function (test) {
       return -1;
     };
 
-    var results = _.clone(oldResults);
+    var results = [...oldResults];
     var observer = {
       addedBefore: function(id, fields, before) {
         var before_idx;
@@ -97,7 +97,7 @@ Tinytest.add("diff-sequence - diff", function (test) {
           before_idx = results.length;
         else
           before_idx = find (results, before);
-        var doc = _.extend({_id: id}, fields);
+        var doc = Object.assign({_id: id}, fields);
         test.isFalse(before_idx < 0 || before_idx > results.length);
         results.splice(before_idx, 0, doc);
       },
@@ -157,4 +157,3 @@ Tinytest.add("diff-sequence - diff", function (test) {
   diffTest(3, [-3, -2, -1]);
   diffTest(10, [-2, 7, 4, 6, 11, -3, -8, 9]);
 });
-

From 236ffc2a5a29a324be64d78a1bde76924342218c Mon Sep 17 00:00:00 2001
From: harryadel 
Date: Sat, 22 Oct 2022 09:07:54 +0200
Subject: [PATCH 98/99] [browser-policy] Remove underscore

---
 .../browser-policy/browser-policy-test.js     | 39 +++++++++++++------
 packages/browser-policy/package.js            |  2 +-
 2 files changed, 29 insertions(+), 12 deletions(-)

diff --git a/packages/browser-policy/browser-policy-test.js b/packages/browser-policy/browser-policy-test.js
index cd2e453a74..4ba437f761 100644
--- a/packages/browser-policy/browser-policy-test.js
+++ b/packages/browser-policy/browser-policy-test.js
@@ -1,17 +1,34 @@
 BrowserPolicy._setRunningTest();
 
+var toObject = function(list, values) {
+  if (list == null) return {};
+  var result = {};
+  for (var i = 0, length = list.length; i < length; i++) {
+    if (values) {
+      result[list[i]] = values[i];
+    } else {
+      result[list[i][0]] = list[i][1];
+    }
+  }
+  return result;
+};
+
 var cspsEqual = function (csp1, csp2) {
   var cspToObj = function (csp) {
     csp = csp.substring(0, csp.length - 1);
-    var parts = _.map(csp.split("; "), function (part) {
+    var parts = csp.split("; ").map(function (part) {
       return part.split(" ");
     });
-    var keys = _.map(parts, _.first);
-    var values = _.map(parts, _.rest);
-    _.each(values, function (value) {
+    var keys = parts.map(part => part[0]);
+    var values = parts.map((part) => {
+      const [head, ...tail] = part;
+      return tail;
+    });
+    values.forEach(function (value) {
       value.sort();
     });
-    return _.object(keys, values);
+    
+    return toObject(keys, values);
   };
 
   return EJSON.equals(cspToObj(csp1), cspToObj(csp2));
@@ -137,11 +154,11 @@ Tinytest.add("browser-policy - csp", function (test) {
                         "default-src 'none'; frame-src https://foo.com; " +
                         "object-src http://foo.com https://foo.com;"));
 
-  // Check that frame-ancestors property is set correctly.

-  BrowserPolicy.content.allowFrameAncestorsOrigin("https://foo.com/");

-  test.isTrue(cspsEqual(BrowserPolicy.content._constructCsp(),

-                        "default-src 'none'; frame-src https://foo.com; " +

-                        "object-src http://foo.com https://foo.com; " +

+  // Check that frame-ancestors property is set correctly.
+  BrowserPolicy.content.allowFrameAncestorsOrigin("https://foo.com/");
+  test.isTrue(cspsEqual(BrowserPolicy.content._constructCsp(),
+                        "default-src 'none'; frame-src https://foo.com; " +
+                        "object-src http://foo.com https://foo.com; " +
                         "frame-ancestors https://foo.com;"));
 
   // CSP2 options: nonce
@@ -188,4 +205,4 @@ Tinytest.add("browser-policy - X-Content-Type-Options", function (test) {
   test.equal(BrowserPolicy.content._xContentTypeOptions(), "nosniff");
   BrowserPolicy.content.allowContentTypeSniffing();
   test.equal(BrowserPolicy.content._xContentTypeOptions(), undefined);
-});
+});
\ No newline at end of file
diff --git a/packages/browser-policy/package.js b/packages/browser-policy/package.js
index 8d370afc66..a44f8ba6b4 100644
--- a/packages/browser-policy/package.js
+++ b/packages/browser-policy/package.js
@@ -11,6 +11,6 @@ Package.onUse(function (api) {
 });
 
 Package.onTest(function (api) {
-  api.use(["tinytest", "browser-policy", "ejson", "underscore"], "server");
+  api.use(["tinytest", "browser-policy", "ejson"], "server");
   api.addFiles("browser-policy-test.js", "server");
 });

From 312f0f07939c5d6ec1e115207599eb2b7a6587f2 Mon Sep 17 00:00:00 2001
From: harryadel 
Date: Sat, 22 Oct 2022 09:18:57 +0200
Subject: [PATCH 99/99] [browser-policy-framing] Remove underscore

---
 packages/browser-policy-framing/browser-policy-framing.js | 2 +-
 packages/browser-policy-framing/package.js                | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/browser-policy-framing/browser-policy-framing.js b/packages/browser-policy-framing/browser-policy-framing.js
index eb90a3778b..1027492c57 100644
--- a/packages/browser-policy-framing/browser-policy-framing.js
+++ b/packages/browser-policy-framing/browser-policy-framing.js
@@ -12,7 +12,7 @@ var xFrameOptions = defaultXFrameOptions;
 const BrowserPolicy = require("meteor/browser-policy-common").BrowserPolicy;
 BrowserPolicy.framing = {};
 
-_.extend(BrowserPolicy.framing, {
+Object.assign(BrowserPolicy.framing, {
   // Exported for tests and browser-policy-common.
   _constructXFrameOptions: function () {
     return xFrameOptions;
diff --git a/packages/browser-policy-framing/package.js b/packages/browser-policy-framing/package.js
index 4b982f0967..7dc9041568 100644
--- a/packages/browser-policy-framing/package.js
+++ b/packages/browser-policy-framing/package.js
@@ -5,7 +5,7 @@ Package.describe({
 
 Package.onUse(function (api) {
   api.use("modules");
-  api.use(["underscore", "browser-policy-common"], "server");
+  api.use(["browser-policy-common"], "server");
   api.imply(["browser-policy-common"], "server");
   api.mainModule("browser-policy-framing.js", "server");
 });