diff --git a/app/lib/bundler.js b/app/lib/bundler.js index c622c8b96b..5da3715b6d 100644 --- a/app/lib/bundler.js +++ b/app/lib/bundler.js @@ -20,10 +20,9 @@ // matches exclude, and if it doesn't exist yet, you should watch for // it to appear) // -// The application launcher is expected to execute /main.js with node, -// setting the PORT and MONGO_URL environment variables. The enclosed -// node application is expected to do the rest, including serving -// /static. +// The application launcher is expected to execute /main.js with node, setting +// various environment variables (such as PORT and MONGO_URL). The enclosed node +// application is expected to do the rest, including serving /static. var files = require('./files.js'); var packages = require('./packages.js'); @@ -578,6 +577,7 @@ _.extend(Bundle.prototype, { " $ npm install fibers\n" + " $ export MONGO_URL='mongodb://user:password@host:port/databasename'\n" + " $ export ROOT_URL='http://example.com'\n" + +" $ export MAIL_URL='smtp://user:password@mailhost:port/'\n" + " $ node main.js\n" + "\n" + "Use the PORT environment variable to set the port where the\n" + diff --git a/app/server/server.js b/app/server/server.js index df50c844da..8376f52cbd 100644 --- a/app/server/server.js +++ b/app/server/server.js @@ -66,8 +66,9 @@ var run = function () { // check environment var port = process.env.PORT ? parseInt(process.env.PORT) : 80; - var mongo_url = process.env.MONGO_URL; - if (!mongo_url) + + // check for a valid MongoDB URL right away + if (!process.env.MONGO_URL) throw new Error("MONGO_URL must be set in environment"); // webserver @@ -88,9 +89,6 @@ var run = function () { Fiber(function () { // (put in a fiber to let Meteor.db operations happen during loading) - // pass in database info - __meteor_bootstrap__.mongo_url = mongo_url; - // load app code _.each(info.load, function (filename) { var code = fs.readFileSync(path.join(bundle_dir, filename)); diff --git a/docs/client/concepts.html b/docs/client/concepts.html index b76f6614b1..0d3a159e44 100644 --- a/docs/client/concepts.html +++ b/docs/client/concepts.html @@ -539,6 +539,9 @@ server, we can recommend our friends at [MongoHQ](http://mongohq.com). $ PORT=3000 MONGO_URL=mongodb://localhost:27017/myapp node bundle/main.js +Other packages may require other environment variables (for example, the `email` +package requires a `MAIL_URL` environment variable). + {{#warning}} For now, bundles will only run on the platform that the bundle was created on. To run on a different platform, you'll need to rebuild diff --git a/packages/email/email.js b/packages/email/email.js new file mode 100644 index 0000000000..7aa5600822 --- /dev/null +++ b/packages/email/email.js @@ -0,0 +1,111 @@ +Email = {}; + +(function () { + var Future = __meteor_bootstrap__.require('fibers/future'); + // js2-mode AST blows up when parsing 'future.return()', so alias. + Future.prototype.ret = Future.prototype.return; + var urlModule = __meteor_bootstrap__.require('url'); + var MailComposer = __meteor_bootstrap__.require('mailcomposer').MailComposer; + + var makePool = function (mailUrlString) { + var mailUrl = urlModule.parse(mailUrlString); + if (mailUrl.protocol !== 'smtp:') + throw new Error("Email protocol in $MAIL_URL (" + + mailUrlString + ") must be 'smtp'"); + + var port = +(mailUrl.port); + var auth = false; + if (mailUrl.auth) { + var parts = mailUrl.auth.split(':', 2); + auth = {user: parts[0] && decodeURIComponent(parts[0]), + pass: parts[1] && decodeURIComponent(parts[1])}; + } + + var simplesmtp = __meteor_bootstrap__.require('simplesmtp'); + return simplesmtp.createClientPool( + port, // Defaults to 25 + mailUrl.hostname, // Defaults to "localhost" + { secureConnection: (port === 465), + // XXX allow maxConnections to be configured? + auth: auth }); + }; + + var smtpPool = null; + if (process.env.MAIL_URL) { + smtpPool = makePool(process.env.MAIL_URL); + }; + + Email._next_devmode_mail_id = 0; + + // Overridden by tests. + Email._output_stream = process.stdout; + + var devModeSend = function (mc) { + var devmode_mail_id = Email._next_devmode_mail_id++; + + // This approach does not prevent other writers to stdout from interleaving. + Email._output_stream.write("====== BEGIN MAIL #" + devmode_mail_id + + " ======\n"); + mc.streamMessage(); + mc.pipe(Email._output_stream, {end: false}); + var future = new Future; + mc.on('end', function () { + Email._output_stream.write("====== END MAIL #" + devmode_mail_id + + " ======\n"); + future.ret(); + }); + future.wait(); + }; + + var smtpSend = function (mc) { + var future = new Future; + smtpPool.sendMail(mc, function (err, responseObj) { + future.ret([err, responseObj]); + }); + var errAndResponse = future.wait(); + // XXX figure out error handling + if (errAndResponse[0]) + throw errAndResponse[0]; + console.log(errAndResponse[1]); + }; + + /** + * Send an email. + * + * Connects to the mail server configured via the MAIL_URL environment + * variable. If unset, prints formatted message to stdout. May yield. + * + * @param options + * @param options.from {String} RFC5322 "From:" address + * @param options.to {String|String[]} RFC5322 "To:" address[es] + * @param options.cc {String|String[]} RFC5322 "Cc:" address[es] + * @param options.bcc {String|String[]} RFC5322 "Bcc:" address[es] + * @param options.replyTo {String|String[]} RFC5322 "Reply-To:" address[es] + * @param options.subject {String} RFC5322 "Subject:" line + * @param options.text {String} RFC5322 mail body (plain text) + */ + Email.send = function (options) { + var mc = new MailComposer(); + + // setup message data + // XXX support HTML body + // XXX support attachments + // XXX support arbitrary headers + mc.setMessageOption({ + from: options.from, + to: options.to, + cc: options.cc, + bcc: options.bcc, + replyTo: options.replyTo, + subject: options.subject, + text: options.text + }); + + if (smtpPool) { + smtpSend(mc); + } else { + devModeSend(mc); + } + }; + +})(); diff --git a/packages/email/email_tests.js b/packages/email/email_tests.js new file mode 100644 index 0000000000..a02d8b2aa8 --- /dev/null +++ b/packages/email/email_tests.js @@ -0,0 +1,33 @@ +streamBuffers = __meteor_bootstrap__.require('stream-buffers'); + +Tinytest.add("email - dev mode smoke test", function (test) { + var old_stream = Email._output_stream; + try { + Email._output_stream = new streamBuffers.WritableStreamBuffer; + Email._next_devmode_mail_id = 0; + 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." + }); + // XXX brittle if mailcomposer changes header order, etc + test.equal(Email._output_stream.getContentsAsString("utf8"), + "====== BEGIN MAIL #0 ======\n" + + "MIME-Version: 1.0\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" + + "Content-Type: text/plain; charset=utf-8\r\n" + + "Content-Transfer-Encoding: quoted-printable\r\n" + + "\r\n" + + "This is the body\r\n" + + "of the message\r\n" + + "From us.\r\n" + + "====== END MAIL #0 ======\n"); + } finally { + Email._output_stream = old_stream; + } +}); \ No newline at end of file diff --git a/packages/email/package.js b/packages/email/package.js new file mode 100644 index 0000000000..4fca3d88c9 --- /dev/null +++ b/packages/email/package.js @@ -0,0 +1,13 @@ +Package.describe({ + summary: "Send email messages" +}); + +Package.on_use(function (api) { + api.add_files('email.js', 'server'); +}); + +Package.on_test(function (api) { + api.use('email', 'server'); + api.use('tinytest'); + api.add_files('email_tests.js', 'server'); +}); diff --git a/packages/mongo-livedata/remote_collection_driver.js b/packages/mongo-livedata/remote_collection_driver.js index fc4294f986..a827fe38ad 100644 --- a/packages/mongo-livedata/remote_collection_driver.js +++ b/packages/mongo-livedata/remote_collection_driver.js @@ -17,4 +17,4 @@ _.extend(Meteor._RemoteCollectionDriver.prototype, { // singleton // XXX kind of hacky -Meteor._RemoteCollectionDriver = new Meteor._RemoteCollectionDriver(__meteor_bootstrap__.mongo_url); +Meteor._RemoteCollectionDriver = new Meteor._RemoteCollectionDriver(process.env.MONGO_URL);