diff --git a/admin/generate-dev-bundle.sh b/admin/generate-dev-bundle.sh index 78663532f2..34ec6feb03 100755 --- a/admin/generate-dev-bundle.sh +++ b/admin/generate-dev-bundle.sh @@ -2,7 +2,7 @@ set -e -BUNDLE_VERSION=0.1.6 +BUNDLE_VERSION=0.1.7 UNAME=$(uname) ARCH=$(uname -m) @@ -143,6 +143,9 @@ npm install fibers@0.6.5 npm install useragent@1.0.6 npm install request@2.9.202 npm install http-proxy@0.8.0 +npm install simplesmtp@0.1.19 +npm install mailcomposer@0.1.15 +npm install stream-buffers@0.2.3 # unused, but kept in bundle for compatibility for a while. npm install connect-gzip@0.1.5 diff --git a/app/lib/app.html.in b/app/lib/app.html.in index 0149601a24..3694303c2c 100644 --- a/app/lib/app.html.in +++ b/app/lib/app.html.in @@ -5,7 +5,6 @@ {{/each}} diff --git a/app/lib/bundler.js b/app/lib/bundler.js index b4d832cbc2..c622c8b96b 100644 --- a/app/lib/bundler.js +++ b/app/lib/bundler.js @@ -577,6 +577,7 @@ _.extend(Bundle.prototype, { "\n" + " $ npm install fibers\n" + " $ export MONGO_URL='mongodb://user:password@host:port/databasename'\n" + +" $ export ROOT_URL='http://example.com'\n" + " $ node main.js\n" + "\n" + "Use the PORT environment variable to set the port where the\n" + diff --git a/app/meteor/run.js b/app/meteor/run.js index 26c21bafd5..ce48d3881a 100644 --- a/app/meteor/run.js +++ b/app/meteor/run.js @@ -168,14 +168,15 @@ var log_to_clients = function (msg) { ////////// Launch server process ////////// -var start_server = function (bundle_path, port, mongo_url, +var start_server = function (bundle_path, outer_port, inner_port, mongo_url, on_exit_callback, on_listen_callback) { // environment var env = {}; for (var k in process.env) env[k] = process.env[k]; - env.PORT = port; + env.PORT = inner_port; env.MONGO_URL = mongo_url; + env.ROOT_URL = 'http://localhost:' + outer_port; var proc = spawn(process.execPath, [path.join(bundle_path, 'main.js'), '--keepalive'], @@ -187,12 +188,13 @@ var start_server = function (bundle_path, port, mongo_url, proc.stdout.on('data', function (data) { if (!data) return; + var originalLength = data.length; // string must match server.js - if (data.match(/^LISTENING\s*$/)) { + data = data.replace(/^LISTENING\s*(?:\n|$)/m, ''); + if (data.length != originalLength) on_listen_callback && on_listen_callback(); - } else { + if (data) log_to_clients({stdout: data}); - } }); proc.stderr.setEncoding('utf8'); @@ -544,19 +546,21 @@ exports.run = function (app_dir, bundle_opts, port) { start_watching(); Status.running = true; - server_handle = start_server(bundle_path, inner_port, mongo_url, function () { - // on server exit - Status.running = false; - Status.listening = false; - Status.soft_crashed(); - if (!Status.crashing) - restart_server(); - }, function () { - // on listen - Status.listening = true; - _.each(request_queue, function (f) { f(); }); - request_queue = []; - }); + server_handle = start_server( + bundle_path, outer_port, inner_port, mongo_url, + function () { + // on server exit + Status.running = false; + Status.listening = false; + Status.soft_crashed(); + if (!Status.crashing) + restart_server(); + }, function () { + // on listen + Status.listening = true; + _.each(request_queue, function (f) { f(); }); + request_queue = []; + }); // launch test bundle and server if needed. diff --git a/app/server/server.js b/app/server/server.js index 8738ccb9d9..df50c844da 100644 --- a/app/server/server.js +++ b/app/server/server.js @@ -50,11 +50,13 @@ var supported_browser = function (user_agent) { // add any runtime configuration options needed to app_html var runtime_config = function (app_html) { var insert = ''; - if (process.env.DEFAULT_DDP_ENDPOINT) - insert += "__meteor_runtime_config__.DEFAULT_DDP_ENDPOINT = '" + - process.env.DEFAULT_DDP_ENDPOINT + "';"; + if (typeof __meteor_runtime_config__ === 'undefined') + return app_html; - app_html = app_html.replace("// ##RUNTIME_CONFIG##", insert); + app_html = app_html.replace( + "// ##RUNTIME_CONFIG##", + "__meteor_runtime_config__ = " + + JSON.stringify(__meteor_runtime_config__) + ";"); return app_html; }; @@ -75,12 +77,6 @@ var run = function () { app.use(gzippo.staticGzip(static_cacheable_path, {clientMaxAge: 1000 * 60 * 60 * 24 * 365})); app.use(gzippo.staticGzip(path.join(bundle_dir, 'static'))); - var app_html = fs.readFileSync(path.join(bundle_dir, 'app.html'), 'utf8'); - var unsupported_html = fs.readFileSync(path.join(bundle_dir, 'unsupported.html')); - - app_html = runtime_config(app_html); - - // read bundle config file var info_raw = fs.readFileSync(path.join(bundle_dir, 'app.json'), 'utf8'); @@ -88,6 +84,7 @@ var run = function () { // start up app __meteor_bootstrap__ = {require: require, startup_hooks: [], app: app}; + __meteor_runtime_config__ = {}; Fiber(function () { // (put in a fiber to let Meteor.db operations happen during loading) @@ -112,6 +109,15 @@ var run = function () { require('vm').runInThisContext(code, filename, true); }); + + // Actually serve HTML. This happens after user code, so that + // packages can insert connect middlewares and update + // __meteor_runtime_config__ + var app_html = fs.readFileSync(path.join(bundle_dir, 'app.html'), 'utf8'); + var unsupported_html = fs.readFileSync(path.join(bundle_dir, 'unsupported.html')); + + app_html = runtime_config(app_html); + app.use(function (req, res) { // prevent favicon.ico and robots.txt from returning app_html if (_.indexOf(['/favicon.ico', '/robots.txt'], req.url) !== -1) { diff --git a/docs/client/docs.js b/docs/client/docs.js index c79d83b199..c7dc28497e 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -186,6 +186,7 @@ var toc = [ ], "Packages", [ [ + "absolute-url", "amplify", "backbone", "bootstrap", diff --git a/docs/client/packages.html b/docs/client/packages.html index 1c12157077..51eddc0373 100644 --- a/docs/client/packages.html +++ b/docs/client/packages.html @@ -16,6 +16,7 @@ and removed with: $ meteor remove +{{> pkg_absolute_url}} {{> pkg_amplify}} {{> pkg_backbone}} {{> pkg_bootstrap}} diff --git a/docs/client/packages/absolute-url.html b/docs/client/packages/absolute-url.html new file mode 100644 index 0000000000..19a0401e63 --- /dev/null +++ b/docs/client/packages/absolute-url.html @@ -0,0 +1,15 @@ + diff --git a/docs/client/packages/absolute-url.js b/docs/client/packages/absolute-url.js new file mode 100644 index 0000000000..7a4ba1d036 --- /dev/null +++ b/docs/client/packages/absolute-url.js @@ -0,0 +1,24 @@ +Template.pkg_absolute_url.absoluteUrl = { + id: "meteor_absoluteUrl", + name: "Meteor.absoluteUrl([path], [options])", + locus: "Anywhere", + descr: ["Generate an absolute URL pointing to the application."], + args: [ + {name: "path", + type: "String", + descr: 'A path to append to the root URL. Do not include a leading "`/`".' + } + ], + options: [ + {name: "secure", + type: "Boolean", + descr: "Create an HTTPS URL." + }, + {name: "rootUrl", + type: "String", + descr: "Override the default ROOT_URL from the server environment. For example: \"`http://foo.example.com`\"" + } + ] + +}; + diff --git a/meteor b/meteor index 6d12ecc158..4381110127 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/bin/bash -BUNDLE_VERSION=0.1.6 +BUNDLE_VERSION=0.1.7 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/packages/absolute-url/package.js b/packages/absolute-url/package.js new file mode 100644 index 0000000000..796cba69b8 --- /dev/null +++ b/packages/absolute-url/package.js @@ -0,0 +1,17 @@ +Package.describe({ + summary: "Generate absolute URLs pointing to the application" +}); + +Package.on_use(function (api) { + // note server before common. usually it is the other way around, but + // in this case server must load first. + api.add_files('url_server.js', 'server'); + api.add_files('url_common.js', ['client', 'server']); +}); + +Package.on_test(function (api) { + api.use('absolute-url', ['client', 'server']); + api.use('tinytest'); + + api.add_files('url_tests.js', ['client', 'server']); +}); diff --git a/packages/absolute-url/url_common.js b/packages/absolute-url/url_common.js new file mode 100644 index 0000000000..4c65f71170 --- /dev/null +++ b/packages/absolute-url/url_common.js @@ -0,0 +1,38 @@ +(function () { + + Meteor.absoluteUrl = function (path, options) { + // path is optional + if (!options && typeof path === 'object') { + options = path; + path = undefined; + } + // merge options with defaults + options = _.extend({}, Meteor.absoluteUrl.defaultOptions, options || {}); + + var url = options.rootUrl; + if (!url) + throw new Error("Must pass options.rootUrl or set ROOT_URL in the server environment"); + + if (!/\/$/.test(url)) // url ends with '/' + url += '/'; + + if (path) + url += path; + + // turn http to http if secure option is set, and we're not talking + // to localhost. + if (options.secure && + /^http:/.test(url) && // url starts with 'http:' + !/http:\/\/localhost[:\/]/.test(url) && // doesn't match localhost + !/http:\/\/127\.0\.0\.1[:\/]/.test(url)) // or 127.0.0.1 + url = url.replace(/^http:/, 'https:'); + + return url; + }; + + // allow later packages to override default options + Meteor.absoluteUrl.defaultOptions = { }; + if (__meteor_runtime_config__ && __meteor_runtime_config__.ROOT_URL) + Meteor.absoluteUrl.defaultOptions.rootUrl = __meteor_runtime_config__.ROOT_URL; + +})(); diff --git a/packages/absolute-url/url_server.js b/packages/absolute-url/url_server.js new file mode 100644 index 0000000000..1c672cf044 --- /dev/null +++ b/packages/absolute-url/url_server.js @@ -0,0 +1,2 @@ +if (process.env.ROOT_URL) + __meteor_runtime_config__.ROOT_URL = process.env.ROOT_URL; diff --git a/packages/absolute-url/url_tests.js b/packages/absolute-url/url_tests.js new file mode 100644 index 0000000000..6548dfc7ab --- /dev/null +++ b/packages/absolute-url/url_tests.js @@ -0,0 +1,46 @@ +Tinytest.add("absolute-url - basics", function(test) { + + test.equal(Meteor.absoluteUrl({rootUrl: 'http://asdf.com'}), + 'http://asdf.com/'); + test.equal(Meteor.absoluteUrl(undefined, {rootUrl: 'http://asdf.com'}), + 'http://asdf.com/'); + test.equal(Meteor.absoluteUrl(undefined, {rootUrl: 'http://asdf.com/'}), + 'http://asdf.com/'); + + test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'http://asdf.com/'}), + 'http://asdf.com/foo'); + test.equal(Meteor.absoluteUrl('/foo', {rootUrl: 'http://asdf.com'}), + 'http://asdf.com//foo'); + test.equal(Meteor.absoluteUrl('#foo', {rootUrl: 'http://asdf.com'}), + 'http://asdf.com/#foo'); + + test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'http://asdf.com', + secure: true}), + 'https://asdf.com/foo'); + test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'https://asdf.com', + secure: true}), + 'https://asdf.com/foo'); + test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'https://asdf.com', + secure: false}), + 'https://asdf.com/foo'); + + test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'http://localhost', + secure: true}), + 'http://localhost/foo'); + test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'http://localhost:3000', + secure: true}), + 'http://localhost:3000/foo'); + test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'https://localhost:3000', + secure: true}), + 'https://localhost:3000/foo'); + test.equal(Meteor.absoluteUrl('foo', {rootUrl: 'http://127.0.0.1:3000', + secure: true}), + 'http://127.0.0.1:3000/foo'); +}); + + +Tinytest.add("absolute-url - environment", function(test) { + // make sure our test runner set the runtime configuration, and this + // propagates to the client. + test.isTrue(/^http/.test(__meteor_runtime_config__.ROOT_URL)); +}); diff --git a/packages/force-ssl/force_ssl_common.js b/packages/force-ssl/force_ssl_common.js new file mode 100644 index 0000000000..00b121f8b4 --- /dev/null +++ b/packages/force-ssl/force_ssl_common.js @@ -0,0 +1 @@ +_.extend(Meteor.absoluteUrl.defaultOptions, {secure: true}); diff --git a/packages/force-ssl/package.js b/packages/force-ssl/package.js index 451e1795f9..90b5037578 100644 --- a/packages/force-ssl/package.js +++ b/packages/force-ssl/package.js @@ -7,6 +7,13 @@ Package.on_use(function (api) { // make sure we come after livedata, so we load after the sockjs // server has been instantiated. api.use('livedata', 'server'); + + // we don't really depend on absolute-url, but we do modify its + // behavior. If there were a way to say "if the other package is + // loaded, make sure we come after it", we should do that here. + api.use('absolute-url', ['client', 'server']); + + api.add_files('force_ssl_common.js', ['client', 'server']); api.add_files('force_ssl_server.js', 'server'); // Another thing we could do is add a force_ssl_client.js file that diff --git a/packages/livedata/server_convenience.js b/packages/livedata/server_convenience.js index a3470b39dc..547181ab66 100644 --- a/packages/livedata/server_convenience.js +++ b/packages/livedata/server_convenience.js @@ -1,3 +1,7 @@ +if (process.env.DEFAULT_DDP_ENDPOINT) + __meteor_runtime_config__.DEFAULT_DDP_ENDPOINT = process.env.DEFAULT_DDP_ENDPOINT; + + _.extend(Meteor, { default_server: new Meteor._LivedataServer,