mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'devel' into ddp-pre1
This commit is contained in:
@@ -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(); });
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
|
||||
27
packages/bootstrap/css/bootstrap-override.css
vendored
27
packages/bootstrap/css/bootstrap-override.css
vendored
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
12
packages/routepolicy/package.js
Normal file
12
packages/routepolicy/package.js
Normal 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');
|
||||
});
|
||||
89
packages/routepolicy/routepolicy.js
Normal file
89
packages/routepolicy/routepolicy.js
Normal 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();
|
||||
|
||||
})();
|
||||
39
packages/routepolicy/routepolicy_tests.js
Normal file
39
packages/routepolicy/routepolicy_tests.js
Normal 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/"
|
||||
);
|
||||
});
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user