-
Notifications
You must be signed in to change notification settings - Fork 5.2k
/
routepolicy.js
124 lines (108 loc) · 4.21 KB
/
routepolicy.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// In addition to listing specific files to be cached, the browser
// application cache manifest allows URLs to be designated as NETWORK
// (always fetched from the Internet) and FALLBACK (which we use to
// serve app HTML on arbitrary URLs).
//
// The limitation of the manifest file format is that the designations
// are by prefix only: if "/foo" is declared NETWORK then "/foobar"
// will also be treated as a network route.
//
// RoutePolicy is a low-level API for declaring the route type of URL prefixes:
//
// "network": for network routes that should not conflict with static
// resources. (For example, if "/sockjs/" is a network route, we
// shouldn't have "/sockjs/red-sock.jpg" as a static resource).
//
// "static-online": for static resources which should not be cached in
// the app cache. This is implemented by also adding them to the
// NETWORK section (as otherwise the browser would receive app HTML
// for them because of the FALLBACK section), but static-online routes
// don't need to be checked for conflict with static resources.
// 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.
RoutePolicyTest = {};
var RoutePolicyConstructor = RoutePolicyTest.Constructor = function () {
var self = this;
self.urlPrefixTypes = {};
};
_.extend(RoutePolicyConstructor.prototype, {
urlPrefixMatches: function (urlPrefix, url) {
return url.substr(0, urlPrefix.length) === urlPrefix;
},
checkType: function (type) {
if (! _.contains(['network', 'static-online'], type))
return 'the route type must be "network" or "static-online"';
return null;
},
checkUrlPrefix: function (urlPrefix, type) {
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 /';
var existingType = self.urlPrefixTypes[urlPrefix];
if (existingType && existingType !== type)
return 'the route URL prefix ' + urlPrefix + ' has already been declared to be of type ' + existingType;
return null;
},
checkForConflictWithStatic: function (urlPrefix, type, _testManifest) {
var self = this;
if (type === 'static-online')
return null;
if (!Package.webapp || !Package.webapp.WebApp
|| !Package.webapp.WebApp.clientProgram
|| !Package.webapp.WebApp.clientProgram.manifest) {
// Hack: If we don't have a manifest, deal with it
// gracefully. This lets us load livedata into a nodejs
// environment that doesn't have a HTTP server (eg, a
// command-line tool).
return null;
}
var manifest =
_testManifest || Package.webapp.WebApp.clientProgram.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, type) ||
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();
}
});
RoutePolicy = new RoutePolicyConstructor();