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 @@
+
+{{#better_markdown}}
+## `absolute-url`
+
+This package allows constructing absolute URLs pointing back to the
+application. The server reads from the `ROOT_URL` environment variable
+to determine where it is running. This is taken care of automatically
+for apps deployed with `meteor deploy`, but must be provided when using
+`meteor bundle`.
+
+{{/better_markdown}}
+
+{{> api_box absoluteUrl}}
+
+
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,