Merge branch 'devel' into ddp-pre1

This commit is contained in:
Naomi Seyfer
2013-02-14 18:31:10 -08:00
11 changed files with 211 additions and 18 deletions

View File

@@ -67,6 +67,29 @@ var runtime_config = function (app_html) {
return app_html;
};
// Serve app HTML for this URL?
var appUrl = function (url) {
if (url === '/favicon.ico' || url === '/robots.txt')
return false;
// NOTE: app.manifest is not a web standard like favicon.ico and
// robots.txt. It is a file name we have chosen to use for HTML5
// appcache URLs. It is included here to prevent using an appcache
// then removing it from poisoning an app permanently. Eventually,
// once we have server side routing, this won't be needed as
// unknown URLs with return a 404 automatically.
if (url === '/app.manifest')
return false;
// Avoid serving app HTML for declared network routes such as /sockjs/.
if (__meteor_bootstrap__._routePolicy &&
__meteor_bootstrap__._routePolicy.classify(url) === 'network')
return false;
// we currently return app HTML on all URLs by default
return true;
}
var run = function () {
var bundle_dir = path.join(__dirname, '..');
@@ -131,21 +154,9 @@ var run = function () {
app_html = runtime_config(app_html);
app.use(function (req, res) {
// prevent these URLs from returning app_html
//
// NOTE: app.manifest is not a web standard like favicon.ico and
// robots.txt. It is a file name we have chosen to use for HTML5
// appcache URLs. It is included here to prevent using an appcache
// then removing it from poisoning an app permanently. Eventually,
// once we have server side routing, this won't be needed as
// unknown URLs with return a 404 automatically.
if (_.indexOf(['/app.manifest', '/favicon.ico', '/robots.txt'], req.url)
!== -1) {
res.writeHead(404);
res.end();
return;
}
app.use(function (req, res, next) {
if (! appUrl(req.url))
return next();
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
if (supported_browser(req.headers['user-agent']))
@@ -155,6 +166,13 @@ var run = function () {
res.end();
});
// Return 404 by default, if no other handlers serve this URL.
app.use(function (req, res) {
res.writeHead(404);
res.end();
return;
});
// run the user startup hooks.
_.each(__meteor_bootstrap__.startup_hooks, function (x) { x(); });

View File

@@ -1,6 +1,8 @@
(function () {
var connect = __meteor_bootstrap__.require("connect");
Meteor._routePolicy.declare('/_oauth/', 'network');
Accounts.oauth._services = {};
// Register a handler for an OAuth service. The handler will be called

View File

@@ -5,6 +5,7 @@ Package.describe({
Package.on_use(function (api) {
api.use('accounts-base', ['client', 'server']);
api.use('routepolicy', 'server');
api.add_files('oauth_common.js', ['client', 'server']);
api.add_files('oauth_client.js', 'client');

View File

@@ -1,4 +1,4 @@
/*
/*
* XXX Hack to make bootstrap work when bundled. This needs to be included
* _after_ the standard bootstrap css files.
*
@@ -10,7 +10,28 @@
[class*=" icon-"] {
background-image: url("/packages/bootstrap/img/glyphicons-halflings.png");
}
.icon-white {
/*
* Selectors borrowed from bootstrap.css. For all releases of bootstrap, when
* we upgrade, update this file to borrow the selectors from where bootstrap.css
* references any .png. When we update to using .less instead, use the less
* directives in a less file instead.
*/
.icon-white,
.nav-pills > .active > a > [class^="icon-"],
.nav-pills > .active > a > [class*=" icon-"],
.nav-list > .active > a > [class^="icon-"],
.nav-list > .active > a > [class*=" icon-"],
.navbar-inverse .nav > .active > a > [class^="icon-"],
.navbar-inverse .nav > .active > a > [class*=" icon-"],
.dropdown-menu > li > a:hover > [class^="icon-"],
.dropdown-menu > li > a:focus > [class^="icon-"],
.dropdown-menu > li > a:hover > [class*=" icon-"],
.dropdown-menu > li > a:focus > [class*=" icon-"],
.dropdown-menu > .active > a > [class^="icon-"],
.dropdown-menu > .active > a > [class*=" icon-"],
.dropdown-submenu:hover > a > [class^="icon-"],
.dropdown-submenu:focus > a > [class^="icon-"],
.dropdown-submenu:hover > a > [class*=" icon-"],
.dropdown-submenu:focus > a > [class*=" icon-"] {
background-image: url("/packages/bootstrap/img/glyphicons-halflings-white.png");
}

View File

@@ -516,6 +516,10 @@ Tinytest.add("minimongo - selector_compiler", function (test) {
nomatch({a: {$regex: 'a'}}, {a: 'cut'});
nomatch({a: {$regex: 'a'}}, {a: 'CAT'});
match({a: {$regex: 'a', $options: 'i'}}, {a: 'CAT'});
nomatch({a: /undefined/}, {});
nomatch({a: {$regex: 'undefined'}}, {});
nomatch({a: /xxx/}, {});
nomatch({a: {$regex: 'xxx'}}, {});
match({a: {$options: 'i'}}, {a: 12});
match({b: {$options: 'i'}}, {a: 12});

View File

@@ -45,6 +45,8 @@ var compileValueSelector = function (valueSelector) {
if (valueSelector instanceof RegExp) {
return function (value) {
if (value === undefined)
return false;
return _anyIfArray(value, function (x) {
return valueSelector.test(x);
});
@@ -267,6 +269,8 @@ var VALUE_OPERATORS = {
}
return function (value) {
if (value === undefined)
return false;
return _anyIfArray(value, function (x) {
return operand.test(x);
});

View File

@@ -0,0 +1,12 @@
Package.describe({
summary: "route policy declarations",
internal: true
});
Package.on_use(function (api) {
api.add_files('routepolicy.js', 'server');
});
Package.on_test(function (api) {
api.add_files(['routepolicy_tests.js'], 'server');
});

View File

@@ -0,0 +1,89 @@
(function () {
// The route policy is a singleton in a running application, but we
// can't unit test the real singleton because messing with the real
// routes would break tinytest... so allow policy instances to be
// constructed for testing.
Meteor.__RoutePolicyConstructor = function () {
var self = this;
self.urlPrefixTypes = {};
};
_.extend(Meteor.__RoutePolicyConstructor.prototype, {
urlPrefixMatches: function (urlPrefix, url) {
return url.substr(0, urlPrefix.length) === urlPrefix;
},
checkType: function (type) {
if (! _.contains(['network'], type))
return 'the route type must be "network"';
return null;
},
checkUrlPrefix: function (urlPrefix) {
var self = this;
if (urlPrefix.charAt(0) !== '/')
return 'a route URL prefix must begin with a slash';
if (urlPrefix === '/')
return 'a route URL prefix cannot be /';
if (self.urlPrefixTypes[urlPrefix] && self.urlPrefixTypes[urlPrefix] !== type)
return 'the route URL prefix ' + urlPrefix + ' has already been declared to be of type ' + type;
return null;
},
checkForConflictWithStatic: function (urlPrefix, type, _testManifest) {
var self = this;
var manifest = _testManifest || __meteor_bootstrap__.bundle.manifest;
var conflict = _.find(manifest, function (resource) {
return (resource.type === 'static' &&
resource.where === 'client' &&
self.urlPrefixMatches(urlPrefix, resource.url));
});
if (conflict)
return ('static resource ' + conflict.url + ' conflicts with ' +
type + ' route ' + urlPrefix);
else
return null;
},
declare: function (urlPrefix, type) {
var self = this;
var problem = self.checkType(type) ||
self.checkUrlPrefix(urlPrefix) ||
self.checkForConflictWithStatic(urlPrefix, type);
if (problem)
throw new Error(problem);
// TODO overlapping prefixes, e.g. /foo/ and /foo/bar/
self.urlPrefixTypes[urlPrefix] = type;
},
classify: function (url) {
var self = this;
if (url.charAt(0) !== '/')
throw new Error('url must be a relative URL: ' + url);
var prefix = _.find(_.keys(self.urlPrefixTypes), function (_prefix) {
return self.urlPrefixMatches(_prefix, url);
});
if (prefix)
return self.urlPrefixTypes[prefix];
else
return null;
},
urlPrefixesFor: function (type) {
var self = this;
var prefixes = [];
_.each(self.urlPrefixTypes, function (_type, _prefix) {
if (_type === type)
prefixes.push(_prefix);
});
return prefixes.sort();
}
});
__meteor_bootstrap__._routePolicy = Meteor._routePolicy =
new Meteor.__RoutePolicyConstructor();
})();

View File

@@ -0,0 +1,39 @@
Tinytest.add("routepolicy", function (test) {
var policy = new Meteor.__RoutePolicyConstructor();
policy.declare('/sockjs/', 'network');
// App routes might look like this...
// policy.declare('/posts/', 'app');
// policy.declare('/about', 'app');
test.equal(policy.classify('/'), null);
test.equal(policy.classify('/foo'), null);
test.equal(policy.classify('/sockjs'), null);
test.equal(policy.classify('/sockjs/'), 'network');
test.equal(policy.classify('/sockjs/foo'), 'network');
// test.equal(policy.classify('/posts/'), 'app');
// test.equal(policy.classify('/posts/1234'), 'app');
test.equal(policy.urlPrefixesFor('network'), ['/sockjs/']);
// test.equal(policy.urlPrefixesFor('app'), ['/about', '/posts/']);
});
Tinytest.add("routepolicy - static conflicts", function (test) {
var manifest = [
{
"path": "static/sockjs/socks-are-comfy.jpg",
"type": "static",
"where": "client",
"cacheable": false,
"url": "/sockjs/socks-are-comfy.jpg"
},
];
var policy = new Meteor.__RoutePolicyConstructor();
test.equal(
policy.checkForConflictWithStatic('/sockjs/', 'network', manifest),
"static resource /sockjs/socks-are-comfy.jpg conflicts with network route /sockjs/"
);
});

View File

@@ -6,6 +6,7 @@ Package.describe({
Package.on_use(function (api) {
api.use(['underscore', 'logging', 'random', 'json'], ['client', 'server']);
api.use('reload', 'client');
api.use('routepolicy', 'server');
api.add_files('sockjs-0.3.4.js', 'client');

View File

@@ -1,3 +1,5 @@
Meteor._routePolicy.declare('/sockjs/', 'network');
// unique id for this instantiation of the server. If this changes
// between client reconnects, the client will reload. You can set the
// environment variable "SERVER_ID" to control this. For example, if