diff --git a/History.md b/History.md
index 3db302fed6..b38f30a6d5 100644
--- a/History.md
+++ b/History.md
@@ -1,5 +1,11 @@
## vNEXT
+* `restrictCreationByEmail` option in `Accounts.config` to restrict new users to
+ emails of specific domain (eg. only users with @meteor.com emails).
+
+* Pass an index and the cursor itself to the callbacks in `cursor.forEach` and
+ `cursor.map`, just like the corresponding `Array` methods. #63
+
* Better error when passing a string to {{#each}}. #722
* Write dates to Mongo as ISODate rather than Integer; existing data can be
@@ -14,10 +20,22 @@
0.6.5. (A bug prevented the 0.6.5 reimplementation of `register_extension`
from working properly anyway.)
+* Support using an HTTP proxy in the `meteor` command line tool. This
+ allows the `update`, `deploy`, `logs`, and `mongo` commands to work
+ behind a proxy. Use the standard `http_proxy` environment variable to
+ specify your proxy endpoint. #429, #689, #1338
+
* Build Linux binaries on an older Linux machine. Meteor now supports
running on Linux machines with glibc 2.9 or newer (Ubuntu 10.04+, RHEL
and CentOS 6+, Fedora 10+, Debian 6+).
+* Support OAuth1 services that require request token secrets as well as
+ authentication token secrets. #1253
+
+* Add `browser-policy` package for configuring and sending Content Security
+ Policy and X-Frame-Options HTTP headers.
+
+
## v0.6.5.1
* Fix syntax errors on lines that end with a backslash. #1326
diff --git a/docs/client/api.html b/docs/client/api.html
index 3aa992a512..9fa58c4fbb 100644
--- a/docs/client/api.html
+++ b/docs/client/api.html
@@ -62,9 +62,9 @@ that Meteor will call each time a client subscribes to the name.
Publish functions can return a
[`Collection.Cursor`](#meteor_collection_cursor), in which case Meteor
-will publish that cursor's documents. You can also return an array of
-`Collection.Cursor`s, in which case Meteor will publish all of the
-cursors.
+will publish that cursor's documents to each subscribed client. You can
+also return an array of `Collection.Cursor`s, in which case Meteor will
+publish all of the cursors.
{{#warning}}
If you return multiple cursors in an array, they currently must all be from
@@ -92,16 +92,15 @@ different collections. We hope to lift this restriction in a future release.
];
});
-Otherwise, the publish function should call the functions
-[`added`](#publish_added) (when a new document is added to the published record
-set), [`changed`](#publish_changed) (when some fields on a document in the
-record set are changed or cleared), and [`removed`](#publish_removed) (when
-documents are removed from the published record set) to inform subscribers about
-documents. These methods are provided by `this` in your publish function.
-
-
-
-
+Alternatively, a publish function can directly control its published
+record set by calling the functions [`added`](#publish_added) (to add a
+new document to the published record set), [`changed`](#publish_changed)
+(to change or clear some fields on a document already in the published
+record set), and [`removed`](#publish_removed) (to remove documents from
+the published record set). Publish functions that use these functions
+should also call [`ready`](#publish_ready) once the initial record set
+is complete. These methods are provided by `this` in your publish
+function.
Example:
@@ -966,6 +965,8 @@ cursor, use [`forEach`](#foreach), [`map`](#map), or [`fetch`](#fetch).
{{> api_box cursor_foreach}}
+This interface is compatible with [Array.forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach).
+
When called from a reactive computation, `forEach` registers dependencies on
the matching documents.
@@ -981,6 +982,8 @@ Examples:
{{> api_box cursor_map}}
+This interface is compatible with [Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map).
+
When called from a reactive computation, `map` registers dependencies on
the matching documents.
diff --git a/docs/client/api.js b/docs/client/api.js
index 1ca9cbce0f..4a7402824a 100644
--- a/docs/client/api.js
+++ b/docs/client/api.js
@@ -482,7 +482,7 @@ Template.api.meteor_collection = {
options: [
{name: "connection",
type: "Object",
- descr: "The Meteor connection that will manage this collection. Uses the default connection if not specified. Pass `null` to specify no connection. Unmanaged (`name` is null) collections cannot specify a connection."
+ descr: "The server connection that will manage this collection. Uses the default connection if not specified. Pass the return value of calling [`DDP.connect`](#ddp_connect) to specify a different server. Pass `null` to specify no connection. Unmanaged (`name` is null) collections cannot specify a connection."
},
{name: "idGeneration",
type: "String",
@@ -675,25 +675,31 @@ Template.api.cursor_fetch = {
Template.api.cursor_foreach = {
id: "foreach",
- name: "cursor.forEach(callback)",
+ name: "cursor.forEach(callback, [thisArg])",
locus: "Anywhere",
descr: ["Call `callback` once for each matching document, sequentially and synchronously."],
args: [
{name: "callback",
type: "Function",
- descr: "Function to call."}
+ descr: "Function to call. It will be called with three arguments: the document, a 0-based index, and cursor itself."},
+ {name: "thisArg",
+ type: "Any",
+ descr: "An object which will be the value of `this` inside `callback`."}
]
};
Template.api.cursor_map = {
id: "map",
- name: "cursor.map(callback)",
+ name: "cursor.map(callback, [thisArg])",
locus: "Anywhere",
descr: ["Map callback over all matching documents. Returns an Array."],
args: [
{name: "callback",
type: "Function",
- descr: "Function to call."}
+ descr: "Function to call. It will be called with three arguments: the document, a 0-based index, and cursor itself."},
+ {name: "thisArg",
+ type: "Any",
+ descr: "An object which will be the value of `this` inside `callback`."}
]
};
@@ -1100,6 +1106,11 @@ Template.api.accounts_config = {
name: "forbidClientAccountCreation",
type: "Boolean",
descr: "Calls to [`createUser`](#accounts_createuser) from the client will be rejected. In addition, if you are using [accounts-ui](#accountsui), the \"Create account\" link will not be available."
+ },
+ {
+ name: "restrictCreationByEmail",
+ type: "String",
+ descr: "If set, only allow new users with an email in the specified domain. Works with password-based sign-in and external services that expose email addresses (Google, Facebook, GitHub). All existing users still can log in after enabling this option. Example: `Accounts.config({ restrictCreationByEmail: 'school.edu' })`."
}
]
};
diff --git a/docs/client/concepts.html b/docs/client/concepts.html
index 61d47d7c18..67e13a9852 100644
--- a/docs/client/concepts.html
+++ b/docs/client/concepts.html
@@ -824,9 +824,9 @@ To get started, run
$ meteor bundle myapp.tgz
This command will generate a fully-contained Node.js application in the form of
-a tarball. To run this application, you need to provide Node.js 0.8 and a
+a tarball. To run this application, you need to provide Node.js 0.10 and a
MongoDB server. (The current release of Meteor has been tested with Node
-0.8.24.) You can then run the application by invoking node, specifying the HTTP
+0.10.19.) You can then run the application by invoking node, specifying the HTTP
port for the application to listen on, and the MongoDB endpoint. If you don't
already have a MongoDB server, we can recommend our friends at
[MongoHQ](http://mongohq.com).
diff --git a/docs/client/docs.js b/docs/client/docs.js
index c057ef71bd..3ac001e554 100644
--- a/docs/client/docs.js
+++ b/docs/client/docs.js
@@ -335,6 +335,7 @@ var toc = [
"audit-argument-checks",
"backbone",
"bootstrap",
+ "browser-policy",
"coffeescript",
"d3",
"force-ssl",
diff --git a/docs/client/packages.html b/docs/client/packages.html
index 0ee701bbe8..077e4f1cd1 100644
--- a/docs/client/packages.html
+++ b/docs/client/packages.html
@@ -22,6 +22,7 @@ and removed with:
{{> pkg_audit_argument_checks}}
{{> pkg_backbone}}
{{> pkg_bootstrap}}
+{{> pkg_browser_policy}}
{{> pkg_coffeescript}}
{{> pkg_d3}}
{{> pkg_force_ssl}}
diff --git a/docs/client/packages/browser-policy.html b/docs/client/packages/browser-policy.html
new file mode 100644
index 0000000000..f7676e7445
--- /dev/null
+++ b/docs/client/packages/browser-policy.html
@@ -0,0 +1,156 @@
+
+{{#better_markdown}}
+## `browser-policy`
+
+The `browser-policy` package lets you set security-related policies that will be
+enforced by newer browsers. These policies help you prevent and mitigate common
+attacks like cross-site scripting and clickjacking.
+
+`browser-policy` lets you configure the HTTP headers X-Frame-Options and
+Content-Security-Policy. X-Frame-Options tells the browser which websites are
+allowed to frame your app. You should only let trusted websites frame your app,
+because malicious sites could harm your users
+with clickjacking
+attacks.
+Content-Security-Policy
+tells the browser where your app can load content from, which encourages safe
+practices and mitigates the damage of a cross-site-scripting attack.
+
+For most apps, we recommend that you take the following steps when using
+`browser-policy`:
+
+* Call `BrowserPolicy.enableContentSecurityPolicy()` to enable a starter policy
+for your app. With this starter policy, your app's client code will be able to
+load content (images, scripts, fonts, etc.) only from its own origin, except
+that XMLHttpRequests and WebSocket connections can go to any origin. Further,
+your app's client code will not be able to use functions such as `eval()` that
+convert strings to code.
+* You can use the functions described below to customize the content
+security policy. If your app does not need any inline Javascript such as inline
+`");
+ } else {
+ boilerplateHtml = boilerplateHtml.replace(
+ /##RUNTIME_CONFIG##/,
+ ""
+ );
+ }
+ boilerplateHtml = boilerplateHtml.replace(
+ /##ROOT_URL_PATH_PREFIX##/g,
+ __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || "");
// only start listening after all the startup code has run.
var localPort = parseInt(process.env.PORT) || 0;
diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh
index 0091c5a388..86bea06977 100755
--- a/scripts/generate-dev-bundle.sh
+++ b/scripts/generate-dev-bundle.sh
@@ -74,9 +74,9 @@ cd build
git clone git://github.com/joyent/node.git
cd node
# When upgrading node versions, also update the values of MIN_NODE_VERSION at
-# the top of tools/meteor.js and tools/server/server.js, and the text in
+# the top of tools/meteor.js and tools/server/boot.js, and the text in
# docs/client/concepts.html and the README in tools/bundler.js.
-git checkout v0.8.24
+git checkout v0.10.19
./configure --prefix="$DIR"
make -j4
@@ -102,7 +102,6 @@ npm install semver@1.1.0
npm install handlebars@1.0.7
npm install request@2.12.0
npm install keypress@0.1.0
-npm install http-proxy@0.10.1 # not 0.10.2, which contains a sketchy websocket change
npm install underscore@1.5.1
npm install fstream@0.1.21
npm install tar@0.1.14
@@ -111,6 +110,11 @@ npm install shell-quote@0.0.1 # now at 1.3.3, which adds plenty of options to
npm install byline@2.0.3 # v3 requires node 0.10
npm install source-map@0.1.26
+# Using the unreleased "caronte" rewrite of http-proxy (which is even called
+# 'caronte', though this may change when this eventually hopefully becomes
+# http-proxy 1.0).
+npm install https://github.com/nodejitsu/node-http-proxy/tarball/94ec6fa5ce6826ca1e8974f7e99b31541aaad76a
+
# Using the unreleased 1.1 branch. We can probably switch to a built NPM version
# when it gets released.
npm install https://github.com/ariya/esprima/tarball/5044b87f94fb802d9609f1426c838874ec2007b3
diff --git a/tools/app.html.in b/tools/app.html.in
index bf821f6054..e2bc250d0f 100644
--- a/tools/app.html.in
+++ b/tools/app.html.in
@@ -4,9 +4,7 @@
{{#each stylesheets}}
{{/each}}
-
+##RUNTIME_CONFIG##
{{#each scripts}}
{{/each}}
diff --git a/tools/bundler.js b/tools/bundler.js
index 3216ef8c2b..22527a7cca 100644
--- a/tools/bundler.js
+++ b/tools/bundler.js
@@ -1442,8 +1442,8 @@ var writeSiteArchive = function (targets, outputPath, options) {
builder.write('README', { data: new Buffer(
"This is a Meteor application bundle. It has only one dependency:\n" +
-"Node.js 0.8 (with the 'fibers' package). The current release of Meteor\n" +
-"has been tested with Node 0.8.24. To run the application:\n" +
+"Node.js 0.10 (with the 'fibers' package). The current release of Meteor\n" +
+"has been tested with Node 0.10.19. To run the application:\n" +
"\n" +
" $ npm install fibers@1.0.1\n" +
" $ export MONGO_URL='mongodb://user:password@host:port/databasename'\n" +
diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js
index 12efda1bb2..228fd616ec 100644
--- a/tools/deploy-galaxy.js
+++ b/tools/deploy-galaxy.js
@@ -5,7 +5,7 @@ var fs = require('fs');
var unipackage = require('./unipackage.js');
var fiberHelpers = require('./fiber-helpers.js');
var Fiber = require('fibers');
-var request = require('request');
+var httpHelpers = require('./http-helpers.js');
var _ = require('underscore');
// a bit of a hack
@@ -90,7 +90,7 @@ exports.discoverGalaxy = function (app) {
// At some point we may want to send a version in the request so that galaxy
// can respond differently to different versions of meteor.
- request({
+ httpHelpers.request({
url: url,
json: true,
strictSSL: true,
@@ -210,7 +210,8 @@ exports.deploy = function (options) {
var fileSize = fs.statSync(starball).size;
var fileStream = fs.createReadStream(starball);
var future = new Future;
- var req = request.put({
+ var req = httpHelpers.request({
+ method: "PUT",
url: info.put,
headers: { 'content-length': fileSize,
'content-type': 'application/octet-stream' },
diff --git a/tools/deploy.js b/tools/deploy.js
index 90e00d0811..caf8ccc627 100644
--- a/tools/deploy.js
+++ b/tools/deploy.js
@@ -7,6 +7,7 @@
var qs = require('querystring');
var path = require('path');
var files = require('./files.js');
+var httpHelpers = require('./http-helpers.js');
var warehouse = require('./warehouse.js');
var buildmessage = require('./buildmessage.js');
var _ = require('underscore');
@@ -43,15 +44,16 @@ var meteor_rpc = function (rpc_name, method, site, query_params, callback) {
url += '?' + qs.stringify(query_params);
}
- var request = require('request');
- var r = request({method: method, url: url}, function (error, response, body) {
- if (error || ((response.statusCode !== 200)
- && (response.statusCode !== 201)))
- // pass some non-falsy error back to callback
- callback(error || response.statusCode, body);
- else
- callback(null, body);
- });
+ var r = httpHelpers.request(
+ {method: method, url: url},
+ function (error, response, body) {
+ if (error || ((response.statusCode !== 200)
+ && (response.statusCode !== 201)))
+ // pass some non-falsy error back to callback
+ callback(error || response.statusCode, body);
+ else
+ callback(null, body);
+ });
return r;
};
@@ -345,8 +347,7 @@ var with_password = function (site, callback) {
// Future.throw. Basically, what Future.wrap does.
callback = inFiber(callback);
- var request = require('request');
- request(check_url, function (error, response, body) {
+ httpHelpers.request(check_url, function (error, response, body) {
if (error || response.statusCode !== 200) {
callback();
diff --git a/tools/files.js b/tools/files.js
index 935051ad60..e14b9a311c 100644
--- a/tools/files.js
+++ b/tools/files.js
@@ -436,59 +436,6 @@ _.extend(exports, {
future.wait();
},
- // A synchronous wrapper around request(...) that returns the response "body"
- // or throws.
- getUrl: function (urlOrOptions, callback) {
- var future = new Future;
- // can't just use Future.wrap, because we want to return "body", not
- // "response".
-
- urlOrOptions = _.clone(urlOrOptions); // we are going to change it
- var appVersion;
- try {
- appVersion = getToolsVersion();
- } catch(e) {
- appVersion = 'checkout';
- }
-
- // meteorReleaseContext - an option with information about app directory
- // release versions, etc, is used to get exact Meteor version used.
- if (urlOrOptions.hasOwnProperty('meteorReleaseContext')) {
- // Get meteor app release version: if specified in command line args, take
- // releaseVersion, if not specified, try global meteor version
- var meteorReleaseContext = urlOrOptions.meteorReleaseContext;
- appVersion = meteorReleaseContext.releaseVersion;
-
- if (appVersion === 'none')
- appVersion = meteorReleaseContext.appReleaseVersion;
- if (appVersion === 'none')
- appVersion = 'checkout';
-
- delete urlOrOptions.meteorReleaseContext;
- }
-
- // Get some kind of User Agent: environment information.
- var ua = util.format('Meteor/%s OS/%s (%s; %s; %s;)',
- appVersion, os.platform(), os.type(), os.release(), os.arch());
-
- var headers = {'User-Agent': ua };
-
- if (_.isObject(urlOrOptions))
- urlOrOptions.headers = _.extend(headers, urlOrOptions.headers);
- else
- urlOrOptions = { url: urlOrOptions, headers: headers };
-
- var request = require('request');
- request(urlOrOptions, function (error, response, body) {
- if (error)
- future.throw(new files.OfflineError(error));
- else if (response.statusCode >= 400 && response.statusCode < 600)
- future.throw(response);
- else
- future.return(body);
- });
- return future.wait();
- },
// Use this if you'd like to replace a directory with another directory as
// close to atomically as possible. It's better than recursively deleting the
diff --git a/tools/http-helpers.js b/tools/http-helpers.js
new file mode 100644
index 0000000000..68e8279a56
--- /dev/null
+++ b/tools/http-helpers.js
@@ -0,0 +1,95 @@
+///
+/// utility functions for dealing with urls and http
+///
+
+var os = require('os');
+var util = require('util');
+
+var _ = require('underscore');
+var request = require('request');
+var Future = require('fibers/future');
+
+var files = require('./files.js');
+
+
+var httpHelpers = exports;
+_.extend(exports, {
+
+ // A wrapper around request that sets http proxy.
+ request: function (urlOrOptions, callback) {
+
+ if (!_.isObject(urlOrOptions))
+ urlOrOptions = { url: urlOrOptions };
+
+ var url = urlOrOptions.url;
+
+ // try to get proxy from environment
+ var proxy = process.env.HTTP_PROXY || process.env.http_proxy || null;
+ // if we're going to an https url, try the https_proxy env variable first.
+ if (/^https/i.test(url)) {
+ proxy = process.env.HTTPS_PROXY || process.env.https_proxy || proxy;
+ }
+ if (proxy && !urlOrOptions.proxy) {
+ urlOrOptions.proxy = proxy;
+ }
+
+ return request(urlOrOptions, callback);
+ },
+
+
+
+ // A synchronous wrapper around request(...) that returns the response "body"
+ // or throws.
+ getUrl: function (urlOrOptions, callback) {
+ var future = new Future;
+ // can't just use Future.wrap, because we want to return "body", not
+ // "response".
+
+ urlOrOptions = _.clone(urlOrOptions); // we are going to change it
+ var appVersion;
+ try {
+ appVersion = files.getToolsVersion();
+ } catch(e) {
+ appVersion = 'checkout';
+ }
+
+ // meteorReleaseContext - an option with information about app directory
+ // release versions, etc, is used to get exact Meteor version used.
+ if (urlOrOptions.hasOwnProperty('meteorReleaseContext')) {
+ // Get meteor app release version: if specified in command line args, take
+ // releaseVersion, if not specified, try global meteor version
+ var meteorReleaseContext = urlOrOptions.meteorReleaseContext;
+ appVersion = meteorReleaseContext.releaseVersion;
+
+ if (appVersion === 'none')
+ appVersion = meteorReleaseContext.appReleaseVersion;
+ if (appVersion === 'none')
+ appVersion = 'checkout';
+
+ delete urlOrOptions.meteorReleaseContext;
+ }
+
+ // Get some kind of User Agent: environment information.
+ var ua = util.format('Meteor/%s OS/%s (%s; %s; %s;)',
+ appVersion, os.platform(), os.type(), os.release(), os.arch());
+
+ var headers = {'User-Agent': ua };
+
+ if (_.isObject(urlOrOptions))
+ urlOrOptions.headers = _.extend(headers, urlOrOptions.headers);
+ else
+ urlOrOptions = { url: urlOrOptions, headers: headers };
+
+ httpHelpers.request(urlOrOptions, function (error, response, body) {
+ if (error)
+ future.throw(new files.OfflineError(error));
+ else if (response.statusCode >= 400 && response.statusCode < 600)
+ future.throw(response);
+ else
+ future.return(body);
+ });
+ return future.wait();
+ }
+
+
+});
diff --git a/tools/meteor.js b/tools/meteor.js
index 290729f6b7..db2d770aa7 100644
--- a/tools/meteor.js
+++ b/tools/meteor.js
@@ -24,7 +24,7 @@ Fiber(function () {
var Future = require('fibers/future');
// This code is duplicated in app/server/server.js.
- var MIN_NODE_VERSION = 'v0.8.24';
+ var MIN_NODE_VERSION = 'v0.10.19';
if (require('semver').lt(process.version, MIN_NODE_VERSION)) {
process.stderr.write(
'Meteor requires Node ' + MIN_NODE_VERSION + ' or later.\n');
diff --git a/tools/meteor_npm.js b/tools/meteor_npm.js
index cf07689d95..b70a6c3ca2 100644
--- a/tools/meteor_npm.js
+++ b/tools/meteor_npm.js
@@ -10,6 +10,7 @@ var path = require('path');
var fs = require('fs');
var cleanup = require(path.join(__dirname, 'cleanup.js'));
var files = require(path.join(__dirname, 'files.js'));
+var httpHelpers = require('./http-helpers.js');
var buildmessage = require('./buildmessage.js');
var _ = require('underscore');
@@ -186,6 +187,26 @@ _.extend(exports, {
throw new Error(
"Corrupted .npm directory -- can't find npm-shrinkwrap.json in " + packageNpmDir);
+ // We need to rebuild all node modules when the Node version changes, in
+ // case there are some binary ones. Technically this is racey, but it
+ // shouldn't fail very often.
+ if (fs.existsSync(path.join(packageNpmDir, 'node_modules'))) {
+ var oldNodeVersion;
+ try {
+ oldNodeVersion = fs.readFileSync(
+ path.join(packageNpmDir, 'node_modules', '.node_version'), 'utf8');
+ } catch (e) {
+ if (e.code !== 'ENOENT')
+ throw e;
+ // Use the Node version from the last release where we didn't drop this
+ // file.
+ oldNodeVersion = 'v0.8.24';
+ }
+
+ if (oldNodeVersion !== process.version)
+ files.rm_recursive(path.join(packageNpmDir, 'node_modules'));
+ }
+
var installedDependencies = self._installedDependencies(packageNpmDir);
// If we already have the right things installed, life is good.
@@ -276,6 +297,7 @@ _.extend(exports, {
fs.unlinkSync(path.join(newPackageNpmDir, 'package.json'));
self._createReadme(newPackageNpmDir);
+ self._createNodeVersion(newPackageNpmDir);
files.renameDirAlmostAtomically(newPackageNpmDir, packageNpmDir);
},
@@ -292,6 +314,12 @@ _.extend(exports, {
);
},
+ _createNodeVersion: function(newPackageNpmDir) {
+ fs.writeFileSync(
+ path.join(newPackageNpmDir, 'node_modules', '.node_version'),
+ process.version);
+ },
+
// Returns object with keys 'stdout', 'stderr', and 'success' (true
// for clean exit with exit code 0, else false)
_execFileSync: function(file, args, opts) {
@@ -407,9 +435,18 @@ _.extend(exports, {
// We don't use npm.commands.install since we couldn't
// figure out how to silence all output (specifically the
// installed tree which is printed out with `console.log`)
+ //
+ // We use --force, because the NPM cache is broken! See
+ // https://github.com/isaacs/npm/issues/3265 Basically, switching back and
+ // forth between a tarball fork of version X and the real version X can
+ // confuse NPM. But the main reason to use tarball URLs is to get a fork of
+ // the latest version with some fix, so it's easy to trigger this! So
+ // instead, always use --force. (Even with --force, we still WRITE to the
+ // cache, so we can corrupt the cache for other invocations of npm... ah
+ // well.)
var result =
this._execFileSync(path.join(files.get_dev_bundle(), "bin", "npm"),
- ["install", installArg],
+ ["install", "--force", installArg],
{cwd: dir});
if (! result.success) {
@@ -436,10 +473,11 @@ _.extend(exports, {
this._ensureConnected();
- // `npm install`, which reads npm-shrinkwrap.json
+ // `npm install`, which reads npm-shrinkwrap.json. See above for why
+ // --force.
var result =
this._execFileSync(path.join(files.get_dev_bundle(), "bin", "npm"),
- ["install"], {cwd: dir});
+ ["install", "--force"], {cwd: dir});
if (! result.success) {
@@ -454,7 +492,7 @@ _.extend(exports, {
// dependencies. `npm install` times out after more than a minute.
_ensureConnected: function () {
try {
- files.getUrl("http://registry.npmjs.org");
+ httpHelpers.getUrl("http://registry.npmjs.org");
} catch (e) {
buildmessage.error("Can't install npm dependencies. " +
"Are you connected to the internet?");
diff --git a/tools/mongo_runner.js b/tools/mongo_runner.js
index c39e2e64cf..b2c1b55093 100644
--- a/tools/mongo_runner.js
+++ b/tools/mongo_runner.js
@@ -161,6 +161,7 @@ exports.launch_mongo = function (app_dir, port, launch_callback, on_exit_callbac
var proc = child_process.spawn(mongod_path, [
'--bind_ip', '127.0.0.1',
'--smallfiles',
+ '--nohttpinterface',
'--port', port,
'--dbpath', data_path
]);
diff --git a/tools/packages.js b/tools/packages.js
index 41832c04fd..677cb40d00 100644
--- a/tools/packages.js
+++ b/tools/packages.js
@@ -26,7 +26,7 @@ var sourcemap = require('source-map');
// end up as watched dependencies. (At least for now, packages only used in
// target creation (eg minifiers and dev-bundle-fetcher) don't require you to
// update BUILT_BY, though you will need to quit and rerun "meteor run".)
-exports.BUILT_BY = 'meteor/8';
+exports.BUILT_BY = 'meteor/9';
// Like Perl's quotemeta: quotes all regexp metacharacters. See
// https://github.com/substack/quotemeta/blob/master/index.js
diff --git a/tools/run.js b/tools/run.js
index e2348459e0..311bcbbb40 100644
--- a/tools/run.js
+++ b/tools/run.js
@@ -108,8 +108,20 @@ var requestQueue = [];
var startProxy = function (outerPort, innerPort, callback) {
callback = callback || function () {};
- var httpProxy = require('http-proxy');
- var p = httpProxy.createServer(function (req, res, proxy) {
+ var http = require('http');
+ // caronte is the code name for http-proxy 1.0 while it's under
+ // development. Once it's released, we may need to adjust the APIs slightly.
+ // (eg, the name of the event on proxy.ev will probably no longer say
+ // "caronte")
+ var caronte = require('caronte');
+
+ var proxy = caronte.createProxyServer({
+ // agent is required to handle keep-alive, and caronte is a little buggy
+ // without it: https://github.com/nodejitsu/node-http-proxy/pull/488
+ agent: new http.Agent({maxSockets: 100})
+ });
+
+ var server = http.createServer(function (req, res) {
if (Status.crashing) {
// sad face. send error logs.
// XXX formatting! text/plain is bad
@@ -126,43 +138,35 @@ var startProxy = function (outerPort, innerPort, callback) {
});
res.end();
- } else if (Status.listening) {
- // server is listening. things are hunky dory!
- proxy.proxyRequest(req, res, {
- host: '127.0.0.1', port: innerPort
- });
- } else {
- // Not listening yet. Queue up request.
- var buffer = httpProxy.buffer(req);
- requestQueue.push(function () {
- proxy.proxyRequest(req, res, {
- host: '127.0.0.1', port: innerPort,
- buffer: buffer
- });
- });
+ return;
}
- });
-
- // Proxy websocket requests using same buffering logic as for regular HTTP requests
- p.on('upgrade', function(req, socket, head) {
+ var proxyIt = function () {
+ proxy.web(req, res, {target: 'http://127.0.0.1:' + innerPort});
+ };
if (Status.listening) {
// server is listening. things are hunky dory!
- p.proxy.proxyWebSocketRequest(req, socket, head, {
- host: '127.0.0.1', port: innerPort
- });
+ proxyIt();
} else {
- // Not listening yet. Queue up request.
- var buffer = httpProxy.buffer(req);
- requestQueue.push(function () {
- p.proxy.proxyWebSocketRequest(req, socket, head, {
- host: '127.0.0.1', port: innerPort,
- buffer: buffer
- });
- });
+ requestQueue.push(proxyIt);
}
});
- p.on('error', function (err) {
+ // Proxy websocket requests using same buffering logic as for regular HTTP
+ // requests
+ server.on('upgrade', function(req, socket, head) {
+ var proxyIt = function () {
+ proxy.ws(req, socket, head, { target: 'http://127.0.0.1:' + innerPort});
+ };
+ if (Status.listening) {
+ // server is listening. things are hunky dory!
+ proxyIt();
+ } else {
+ // Not listening yet. Queue up request.
+ requestQueue.push(proxyIt);
+ }
+ });
+
+ server.on('error', function (err) {
if (err.code == 'EADDRINUSE') {
process.stderr.write("Can't listen on port " + outerPort
+ ". Perhaps another Meteor is running?\n");
@@ -177,17 +181,19 @@ var startProxy = function (outerPort, innerPort, callback) {
process.exit(1);
});
- // don't spin forever if the app doesn't respond. instead return an
- // error immediately. This shouldn't happen much since we try to not
- // send requests if the app is down.
- p.proxy.on('proxyError', function (err, req, res) {
+ // don't crash if the app doesn't respond. instead return an error
+ // immediately. This shouldn't happen much since we try to not send requests
+ // if the app is down.
+ // XXX should we also handle caronte:outgoing:ws:error, for a failed
+ // websocket?
+ proxy.ee.on('caronte:outgoing:web:error', function (err, req, res) {
res.writeHead(503, {
'Content-Type': 'text/plain'
});
res.end('Unexpected error.');
});
- p.listen(outerPort, callback);
+ server.listen(outerPort, callback);
};
var saveLog = function (msg) {
@@ -402,29 +408,6 @@ exports.run = function (context, options) {
("mongodb://127.0.0.1:" + mongoPort + "/meteor");
var firstRun = true;
- // node-http-proxy doesn't properly handle errors if it has a problem writing
- // to the proxy target. While we try to not proxy requests when we don't think
- // the target is listening, there are race conditions here, and in any case
- // those attempts don't take effect for pre-existing websocket connections.
- // Error handling in node-http-proxy is really convoluted and will change with
- // their ongoing Node 0.10.x compatible rewrite, so rather than trying to
- // debug and send pull request now, we'll wait for them to finish their
- // rewrite. In the meantime, ignore two common exceptions that we sometimes
- // see instead of crashing.
- //
- // See https://github.com/meteor/meteor/issues/513
- //
- // That bug is about "meteor deploy"s use of http-proxy, but it also affects
- // our use here; see
- // https://groups.google.com/d/msg/meteor-core/JgbnfKEa5lA/FJHZtJftfSsJ
- //
- // XXX remove this once we've upgraded and fixed http-proxy
- process.on('uncaughtException', function (e) {
- if (e && (e.errno === 'EPIPE' || e.message === "This socket is closed."))
- return;
- throw e;
- });
-
var serverHandle;
var watcher;
diff --git a/tools/server/boot.js b/tools/server/boot.js
index 0a2fa308a2..091aee0ff1 100644
--- a/tools/server/boot.js
+++ b/tools/server/boot.js
@@ -6,7 +6,7 @@ var _ = require('underscore');
var sourcemap_support = require('source-map-support');
// This code is duplicated in tools/server/server.js.
-var MIN_NODE_VERSION = 'v0.8.24';
+var MIN_NODE_VERSION = 'v0.10.19';
if (require('semver').lt(process.version, MIN_NODE_VERSION)) {
process.stderr.write(
'Meteor requires Node ' + MIN_NODE_VERSION + ' or later.\n');
diff --git a/tools/tests/test_bundler_npm.js b/tools/tests/test_bundler_npm.js
index 82fe94c1dc..e4e1509bc0 100644
--- a/tools/tests/test_bundler_npm.js
+++ b/tools/tests/test_bundler_npm.js
@@ -172,12 +172,12 @@ assert.doesNotThrow(function () {
// while bundling, verify that we don't call `npm install
// name@version unnecessarily` -- calling `npm install` is enough,
- // and installing each package separately coul unintentionally bump
+ // and installing each package separately could unintentionally bump
// subdependency versions. (to intentionally bump subdependencies,
// just remove all of the .npm directory)
var bareExecFileSync = meteorNpm._execFileSync;
meteorNpm._execFileSync = function(file, args, opts) {
- if (args[0] === 'install' && args[1])
+ if (args.length > 2 && args[0] === 'install' && args[1] === '--force')
assert.fail("shouldn't be installing specific npm packages: " + args[1]);
return bareExecFileSync(file, args, opts);
};
diff --git a/tools/updater.js b/tools/updater.js
index 0369ba5ee9..368c95452c 100644
--- a/tools/updater.js
+++ b/tools/updater.js
@@ -6,6 +6,7 @@ var testingUpdater = false;
var inFiber = require('./fiber-helpers.js').inFiber;
var files = require('./files.js');
var warehouse = require('./warehouse.js');
+var httpHelpers = require('./http-helpers.js');
var manifestUrl = testingUpdater
? 'https://s3.amazonaws.com/com.meteor.static/test/update/manifest.json'
@@ -21,7 +22,7 @@ exports.getManifest = function (context) {
if (context)
options.meteorReleaseContext = context;
- return files.getUrl(options);
+ return httpHelpers.getUrl(options);
};
exports.startUpdateChecks = function (context) {
diff --git a/tools/warehouse.js b/tools/warehouse.js
index 91e47b3311..d2ea01838e 100644
--- a/tools/warehouse.js
+++ b/tools/warehouse.js
@@ -28,6 +28,7 @@ var _ = require("underscore");
var files = require('./files.js');
var updater = require('./updater.js');
+var httpHelpers = require('./http-helpers.js');
var fiberHelpers = require('./fiber-helpers.js');
var logging = require('./logging.js');
@@ -235,7 +236,7 @@ _.extend(warehouse, {
// after we're done writing packages
if (!releaseAlreadyExists) {
try {
- releaseManifestText = files.getUrl(
+ releaseManifestText = httpHelpers.getUrl(
WAREHOUSE_URLBASE + "/releases/" + releaseVersion + ".release.json");
} catch (e) {
// just throw, if we're in the background anyway, or if this is the
@@ -303,7 +304,7 @@ _.extend(warehouse, {
// try getting the releases's notices. only blessed releases have one, so
// if we can't find it just proceed.
try {
- var notices = files.getUrl(
+ var notices = httpHelpers.getUrl(
WAREHOUSE_URLBASE + "/releases/" + releaseVersion + ".notices.json");
// Real notices are valid JSON.
@@ -347,7 +348,7 @@ _.extend(warehouse, {
"meteor-tools-" + toolsVersion + "-" + platform + ".tar.gz";
var toolsTarballPath = "/tools/" + toolsVersion + "/"
+ toolsTarballFilename;
- var toolsTarball = files.getUrl({
+ var toolsTarball = httpHelpers.getUrl({
url: WAREHOUSE_URLBASE + toolsTarballPath,
encoding: null
});
@@ -430,7 +431,7 @@ _.extend(warehouse, {
"/" + version +
"/" + name + '-' + version + "-" + platform + ".tar.gz";
- var tarball = files.getUrl({url: packageUrl, encoding: null});
+ var tarball = httpHelpers.getUrl({url: packageUrl, encoding: null});
files.extractTarGz(tarball, packageDir);
if (!dontWriteFreshFile)
fs.writeFileSync(warehouse.getPackageFreshFile(name, version), '');