mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'devel' into blaze-refactor
Conflicts: packages/webapp/webapp_server.js
This commit is contained in:
@@ -87,3 +87,7 @@ Interested in contributing to Meteor?
|
||||
|
||||
* Core framework design mailing list: https://groups.google.com/group/meteor-core
|
||||
* Contribution guidelines: https://github.com/meteor/meteor/tree/devel/Contributing.md
|
||||
|
||||
We are hiring! Visit https://www.meteor.com/jobs/working-at-meteor to
|
||||
learn more about working full-time on the Meteor project.
|
||||
|
||||
|
||||
@@ -12,3 +12,4 @@ jquery-waypoints
|
||||
less
|
||||
spiderable
|
||||
appcache
|
||||
reload-safetybelt
|
||||
|
||||
@@ -92,15 +92,17 @@ different collections. We hope to lift this restriction in a future release.
|
||||
];
|
||||
});
|
||||
|
||||
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.
|
||||
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). These methods are provided by `this` in your publish function.
|
||||
|
||||
If a publish function does not return a cursor or array of cursors, it is
|
||||
assumed to be using the low-level `added`/`changed`/`removed` interface, and it
|
||||
**must also call [`ready`](#publish_ready) once the initial record set is
|
||||
complete**.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -156,6 +158,19 @@ Example:
|
||||
Counts.findOne(Session.get("roomId")).count +
|
||||
" messages.");
|
||||
|
||||
// server: sometimes publish a query, sometimes publish nothing
|
||||
Meteor.publish("secretData", function () {
|
||||
if (this.userId === 'superuser') {
|
||||
return SecretData.find();
|
||||
} else {
|
||||
// Declare that no data is being published. If you leave this line
|
||||
// out, Meteor will never consider the subscription ready because
|
||||
// it thinks you're using the added/changed/removed interface where
|
||||
// you have to explicitly call this.ready().
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
{{#warning}}
|
||||
Meteor will emit a warning message if you call `Meteor.publish` in a
|
||||
project that includes the `autopublish` package. Your publish function
|
||||
|
||||
@@ -1087,7 +1087,7 @@ Template.api.loginWithPassword = {
|
||||
{
|
||||
name: "password",
|
||||
type: "String",
|
||||
descr: "The user's password. This is __not__ sent in plain text over the wire — it is secured with [SRP](http://en.wikipedia.org/wiki/Secure_Remote_Password_protocol)."
|
||||
descr: "The user's password."
|
||||
},
|
||||
{
|
||||
name: "callback",
|
||||
|
||||
2
meteor
2
meteor
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
BUNDLE_VERSION=0.3.38
|
||||
BUNDLE_VERSION=0.3.38 # warning! 0.3.39-40 used on packaging branch
|
||||
|
||||
# OS Check. Put here because here is where we download the precompiled
|
||||
# bundles that are arch specific.
|
||||
|
||||
@@ -41,9 +41,9 @@ Meteor.loginWithPassword = function (selector, password, callback) {
|
||||
}, callback);
|
||||
}
|
||||
else if (error) {
|
||||
callback(error);
|
||||
callback && callback(error);
|
||||
} else {
|
||||
callback();
|
||||
callback && callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -69,9 +69,9 @@ var srpUpgradePath = function (options, callback) {
|
||||
details = EJSON.parse(options.upgradeError.details);
|
||||
} catch (e) {}
|
||||
if (!(details && details.format === 'srp')) {
|
||||
callback(new Meteor.Error(400,
|
||||
"Password is old. Please reset your " +
|
||||
"password."));
|
||||
callback && callback(
|
||||
new Meteor.Error(400, "Password is old. Please reset your " +
|
||||
"password."));
|
||||
} else {
|
||||
Accounts.callLoginMethod({
|
||||
methodArguments: [{
|
||||
@@ -133,7 +133,7 @@ Accounts.changePassword = function (oldPassword, newPassword, callback) {
|
||||
plaintextPassword: oldPassword
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
callback && callback(err);
|
||||
} else {
|
||||
// Now that we've successfully migrated from srp to
|
||||
// bcrypt, try changing the password again.
|
||||
|
||||
@@ -127,7 +127,7 @@ ObserveSequence = {
|
||||
});
|
||||
|
||||
diffArray(lastSeqArray, seqArray, callbacks);
|
||||
} else if (isMinimongoCursor(seq)) {
|
||||
} else if (isStoreCursor(seq)) {
|
||||
var cursor = seq;
|
||||
seqArray = [];
|
||||
|
||||
@@ -188,7 +188,7 @@ ObserveSequence = {
|
||||
return [];
|
||||
} else if (seq instanceof Array) {
|
||||
return seq;
|
||||
} else if (isMinimongoCursor(seq)) {
|
||||
} else if (isStoreCursor(seq)) {
|
||||
return seq.fetch();
|
||||
} else {
|
||||
throw badSequenceError();
|
||||
@@ -201,9 +201,9 @@ var badSequenceError = function () {
|
||||
"arrays, cursors or falsey values.");
|
||||
};
|
||||
|
||||
var isMinimongoCursor = function (seq) {
|
||||
var minimongo = Package.minimongo;
|
||||
return !!minimongo && (seq instanceof minimongo.LocalCollection.Cursor);
|
||||
var isStoreCursor = function (cursor) {
|
||||
return cursor && _.isObject(cursor) &&
|
||||
_.isFunction(cursor.observe) && _.isFunction(cursor.fetch);
|
||||
};
|
||||
|
||||
// Calculates the differences between `lastSeqArray` and
|
||||
|
||||
1
packages/reload-safetybelt/.gitignore
vendored
Normal file
1
packages/reload-safetybelt/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.build*
|
||||
16
packages/reload-safetybelt/package.js
Normal file
16
packages/reload-safetybelt/package.js
Normal file
@@ -0,0 +1,16 @@
|
||||
Package.describe({
|
||||
summary: "Reload safety belt for multi-server deployments",
|
||||
internal: true
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.use("webapp", "server");
|
||||
api.add_files("reload-safety-belt.js", "server");
|
||||
api.add_files("safetybelt.js", "server", { isAsset: true });
|
||||
});
|
||||
|
||||
Package.on_test(function (api) {
|
||||
api.add_files("safetybelt.js", "server", { isAsset: true });
|
||||
api.use(["reload-safetybelt", "tinytest", "http", "webapp"]);
|
||||
api.add_files("reload-safety-belt-tests.js", "server");
|
||||
});
|
||||
10
packages/reload-safetybelt/reload-safety-belt-tests.js
Normal file
10
packages/reload-safetybelt/reload-safety-belt-tests.js
Normal file
@@ -0,0 +1,10 @@
|
||||
var script = Assets.getText("safetybelt.js");
|
||||
|
||||
Tinytest.add("reload-safetybelt - safety belt is added", function (test) {
|
||||
test.isTrue(_.some(
|
||||
WebAppInternals.additionalStaticJs,
|
||||
function (js, pathname) {
|
||||
return js === script;
|
||||
}
|
||||
));
|
||||
});
|
||||
6
packages/reload-safetybelt/reload-safety-belt.js
Normal file
6
packages/reload-safetybelt/reload-safety-belt.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// The reload safetybelt is some js that will be loaded after everything else in
|
||||
// the HTML. In some multi-server deployments, when you update, you have a
|
||||
// chance of hitting an old server for the HTML and the new server for the JS or
|
||||
// CSS. This prevents you from displaying the page in that case, and instead
|
||||
// reloads it, presumably all on the new version now.
|
||||
WebAppInternals.addStaticJs(Assets.getText("safetybelt.js"));
|
||||
6
packages/reload-safetybelt/safetybelt.js
Normal file
6
packages/reload-safetybelt/safetybelt.js
Normal file
@@ -0,0 +1,6 @@
|
||||
if (typeof Package === 'undefined' ||
|
||||
! Package.webapp ||
|
||||
! Package.webapp.WebApp ||
|
||||
! Package.webapp.WebApp._isCssLoaded()) {
|
||||
document.location.reload();
|
||||
}
|
||||
@@ -9,11 +9,17 @@
|
||||
{{/if}}
|
||||
{{#each js}} <script type="text/javascript" src="{{../bundledJsCssPrefix}}{{url}}"></script>
|
||||
{{/each}}
|
||||
{{#if inlineScriptsAllowed}}
|
||||
<script type='text/javascript'>{{{reloadSafetyBelt}}}</script>
|
||||
{{else}}
|
||||
<script type='text/javascript' src='{{rootUrlPathPrefix}}/meteor_reload_safetybelt.js'></script>
|
||||
{{/if}}
|
||||
{{#each additionalStaticJs}}
|
||||
{{#if ../inlineScriptsAllowed}}
|
||||
<script type='text/javascript'>
|
||||
{{contents}}
|
||||
</script>
|
||||
{{else}}
|
||||
<script type='text/javascript'
|
||||
src='{{rootUrlPathPrefix}}{{pathname}}'></script>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
|
||||
{{{head}}}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -19,18 +19,6 @@ WebAppInternals = {};
|
||||
|
||||
var bundledJsCssPrefix;
|
||||
|
||||
// The reload safetybelt is some js that will be loaded after everything else in
|
||||
// the HTML. In some multi-server deployments, when you update, you have a
|
||||
// chance of hitting an old server for the HTML and the new server for the JS or
|
||||
// CSS. This prevents you from displaying the page in that case, and instead
|
||||
// reloads it, presumably all on the new version now.
|
||||
var RELOAD_SAFETYBELT = "\n" +
|
||||
"if (typeof Package === 'undefined' ||\n" +
|
||||
" ! Package.webapp ||\n" +
|
||||
" ! Package.webapp.WebApp ||\n" +
|
||||
" ! Package.webapp.WebApp._isCssLoaded())\n" +
|
||||
" document.location.reload(); \n";
|
||||
|
||||
// Keepalives so that when the outer server dies unceremoniously and
|
||||
// doesn't kill us, we quit ourselves. A little gross, but better than
|
||||
// pidfiles.
|
||||
@@ -235,6 +223,158 @@ WebApp._timeoutAdjustmentRequestCallback = function (req, res) {
|
||||
_.each(finishListeners, function (l) { res.on('finish', l); });
|
||||
};
|
||||
|
||||
// Will be updated by main before we listen.
|
||||
var boilerplateFunc = null;
|
||||
var boilerplateBaseData = null;
|
||||
var memoizedBoilerplate = {};
|
||||
|
||||
// Given a request (as returned from `categorizeRequest`), return the
|
||||
// boilerplate HTML to serve for that request. Memoizes on HTML
|
||||
// attributes (used by, eg, appcache) and whether inline scripts are
|
||||
// currently allowed.
|
||||
var getBoilerplate = function (request) {
|
||||
var htmlAttributes = getHtmlAttributes(request);
|
||||
|
||||
// The only thing that changes from request to request (for now) are
|
||||
// the HTML attributes (used by, eg, appcache) and whether inline
|
||||
// scripts are allowed, so we can memoize based on that.
|
||||
var boilerplateKey = JSON.stringify({
|
||||
inlineScriptsAllowed: inlineScriptsAllowed,
|
||||
htmlAttributes: htmlAttributes
|
||||
});
|
||||
|
||||
if (! _.has(memoizedBoilerplate, boilerplateKey)) {
|
||||
var boilerplateData = _.extend({
|
||||
htmlAttributes: htmlAttributes,
|
||||
inlineScriptsAllowed: WebAppInternals.inlineScriptsAllowed()
|
||||
}, boilerplateBaseData);
|
||||
|
||||
memoizedBoilerplate[boilerplateKey] = "<!DOCTYPE html>\n" +
|
||||
Blaze.toHTML(Blaze.With(boilerplateData, boilerplateFunc));
|
||||
}
|
||||
return memoizedBoilerplate[boilerplateKey];
|
||||
};
|
||||
|
||||
// Serve static files from the manifest or added with
|
||||
// `addStaticJs`. Exported for tests.
|
||||
// Options are:
|
||||
// - staticFiles: object mapping pathname of file in manifest -> {
|
||||
// path, cacheable, sourceMapUrl, type }
|
||||
// - clientDir: root directory for static files from client manifest
|
||||
WebAppInternals.staticFilesMiddleware = function (options, req, res, next) {
|
||||
if ('GET' != req.method && 'HEAD' != req.method) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
var pathname = connect.utils.parseUrl(req).pathname;
|
||||
var staticFiles = options.staticFiles;
|
||||
var clientDir = options.clientDir;
|
||||
|
||||
try {
|
||||
pathname = decodeURIComponent(pathname);
|
||||
} catch (e) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
var serveStaticJs = function (s) {
|
||||
res.writeHead(200, {
|
||||
'Content-type': 'application/javascript; charset=UTF-8'
|
||||
});
|
||||
res.write(s);
|
||||
res.end();
|
||||
};
|
||||
|
||||
if (pathname === "/meteor_runtime_config.js" &&
|
||||
! WebAppInternals.inlineScriptsAllowed()) {
|
||||
serveStaticJs("__meteor_runtime_config__ = " +
|
||||
JSON.stringify(__meteor_runtime_config__) + ";");
|
||||
return;
|
||||
} else if (_.has(additionalStaticJs, pathname) &&
|
||||
! WebAppInternals.inlineScriptsAllowed()) {
|
||||
serveStaticJs(additionalStaticJs[pathname]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_.has(staticFiles, pathname)) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't need to call pause because, unlike 'static', once we call into
|
||||
// 'send' and yield to the event loop, we never call another handler with
|
||||
// 'next'.
|
||||
|
||||
var info = staticFiles[pathname];
|
||||
|
||||
// Cacheable files are files that should never change. Typically
|
||||
// named by their hash (eg meteor bundled js and css files).
|
||||
// We cache them ~forever (1yr).
|
||||
//
|
||||
// We cache non-cacheable files anyway. This isn't really correct, as users
|
||||
// can change the files and changes won't propagate immediately. However, if
|
||||
// we don't cache them, browsers will 'flicker' when rerendering
|
||||
// images. Eventually we will probably want to rewrite URLs of static assets
|
||||
// to include a query parameter to bust caches. That way we can both get
|
||||
// good caching behavior and allow users to change assets without delay.
|
||||
// https://github.com/meteor/meteor/issues/773
|
||||
var maxAge = info.cacheable
|
||||
? 1000 * 60 * 60 * 24 * 365
|
||||
: 1000 * 60 * 60 * 24;
|
||||
|
||||
// Set the X-SourceMap header, which current Chrome understands.
|
||||
// (The files also contain '//#' comments which FF 24 understands and
|
||||
// Chrome doesn't understand yet.)
|
||||
//
|
||||
// Eventually we should set the SourceMap header but the current version of
|
||||
// Chrome and no version of FF supports it.
|
||||
//
|
||||
// To figure out if your version of Chrome should support the SourceMap
|
||||
// header,
|
||||
// - go to chrome://version. Let's say the Chrome version is
|
||||
// 28.0.1500.71 and the Blink version is 537.36 (@153022)
|
||||
// - go to http://src.chromium.org/viewvc/blink/branches/chromium/1500/Source/core/inspector/InspectorPageAgent.cpp?view=log
|
||||
// where the "1500" is the third part of your Chrome version
|
||||
// - find the first revision that is no greater than the "153022"
|
||||
// number. That's probably the first one and it probably has
|
||||
// a message of the form "Branch 1500 - blink@r149738"
|
||||
// - If *that* revision number (149738) is at least 151755,
|
||||
// then Chrome should support SourceMap (not just X-SourceMap)
|
||||
// (The change is https://codereview.chromium.org/15832007)
|
||||
//
|
||||
// You also need to enable source maps in Chrome: open dev tools, click
|
||||
// the gear in the bottom right corner, and select "enable source maps".
|
||||
//
|
||||
// Firefox 23+ supports source maps but doesn't support either header yet,
|
||||
// so we include the '//#' comment for it:
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=765993
|
||||
// In FF 23 you need to turn on `devtools.debugger.source-maps-enabled`
|
||||
// in `about:config` (it is on by default in FF 24).
|
||||
if (info.sourceMapUrl)
|
||||
res.setHeader('X-SourceMap', info.sourceMapUrl);
|
||||
|
||||
if (info.type === "js") {
|
||||
res.setHeader("Content-Type", "application/javascript; charset=UTF-8");
|
||||
} else if (info.type === "css") {
|
||||
res.setHeader("Content-Type", "text/css; charset=UTF-8");
|
||||
}
|
||||
|
||||
send(req, path.join(clientDir, info.path))
|
||||
.maxage(maxAge)
|
||||
.hidden(true) // if we specified a dotfile in the manifest, serve it
|
||||
.on('error', function (err) {
|
||||
Log.error("Error serving static file " + err);
|
||||
res.writeHead(500);
|
||||
res.end();
|
||||
})
|
||||
.on('directory', function () {
|
||||
Log.error("Unexpected directory " + info.path);
|
||||
res.writeHead(500);
|
||||
res.end();
|
||||
})
|
||||
.pipe(res);
|
||||
};
|
||||
|
||||
var runWebAppServer = function () {
|
||||
var shuttingDown = false;
|
||||
// read the control for the client we'll be serving up
|
||||
@@ -318,115 +458,10 @@ var runWebAppServer = function () {
|
||||
// Serve static files from the manifest.
|
||||
// This is inspired by the 'static' middleware.
|
||||
app.use(function (req, res, next) {
|
||||
if ('GET' != req.method && 'HEAD' != req.method) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
var pathname = connect.utils.parseUrl(req).pathname;
|
||||
|
||||
try {
|
||||
pathname = decodeURIComponent(pathname);
|
||||
} catch (e) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
var serveStaticJs = function (s) {
|
||||
res.writeHead(200, {
|
||||
'Content-type': 'application/javascript; charset=UTF-8'
|
||||
});
|
||||
res.write(s);
|
||||
res.end();
|
||||
};
|
||||
|
||||
if (pathname === "/meteor_runtime_config.js" &&
|
||||
! WebAppInternals.inlineScriptsAllowed()) {
|
||||
serveStaticJs("__meteor_runtime_config__ = " +
|
||||
JSON.stringify(__meteor_runtime_config__) + ";");
|
||||
return;
|
||||
} else if (pathname === "/meteor_reload_safetybelt.js" &&
|
||||
! WebAppInternals.inlineScriptsAllowed()) {
|
||||
serveStaticJs(RELOAD_SAFETYBELT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_.has(staticFiles, pathname)) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't need to call pause because, unlike 'static', once we call into
|
||||
// 'send' and yield to the event loop, we never call another handler with
|
||||
// 'next'.
|
||||
|
||||
var info = staticFiles[pathname];
|
||||
|
||||
// Cacheable files are files that should never change. Typically
|
||||
// named by their hash (eg meteor bundled js and css files).
|
||||
// We cache them ~forever (1yr).
|
||||
//
|
||||
// We cache non-cacheable files anyway. This isn't really correct, as users
|
||||
// can change the files and changes won't propagate immediately. However, if
|
||||
// we don't cache them, browsers will 'flicker' when rerendering
|
||||
// images. Eventually we will probably want to rewrite URLs of static assets
|
||||
// to include a query parameter to bust caches. That way we can both get
|
||||
// good caching behavior and allow users to change assets without delay.
|
||||
// https://github.com/meteor/meteor/issues/773
|
||||
var maxAge = info.cacheable
|
||||
? 1000 * 60 * 60 * 24 * 365
|
||||
: 1000 * 60 * 60 * 24;
|
||||
|
||||
// Set the X-SourceMap header, which current Chrome understands.
|
||||
// (The files also contain '//#' comments which FF 24 understands and
|
||||
// Chrome doesn't understand yet.)
|
||||
//
|
||||
// Eventually we should set the SourceMap header but the current version of
|
||||
// Chrome and no version of FF supports it.
|
||||
//
|
||||
// To figure out if your version of Chrome should support the SourceMap
|
||||
// header,
|
||||
// - go to chrome://version. Let's say the Chrome version is
|
||||
// 28.0.1500.71 and the Blink version is 537.36 (@153022)
|
||||
// - go to http://src.chromium.org/viewvc/blink/branches/chromium/1500/Source/core/inspector/InspectorPageAgent.cpp?view=log
|
||||
// where the "1500" is the third part of your Chrome version
|
||||
// - find the first revision that is no greater than the "153022"
|
||||
// number. That's probably the first one and it probably has
|
||||
// a message of the form "Branch 1500 - blink@r149738"
|
||||
// - If *that* revision number (149738) is at least 151755,
|
||||
// then Chrome should support SourceMap (not just X-SourceMap)
|
||||
// (The change is https://codereview.chromium.org/15832007)
|
||||
//
|
||||
// You also need to enable source maps in Chrome: open dev tools, click
|
||||
// the gear in the bottom right corner, and select "enable source maps".
|
||||
//
|
||||
// Firefox 23+ supports source maps but doesn't support either header yet,
|
||||
// so we include the '//#' comment for it:
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=765993
|
||||
// In FF 23 you need to turn on `devtools.debugger.source-maps-enabled`
|
||||
// in `about:config` (it is on by default in FF 24).
|
||||
if (info.sourceMapUrl)
|
||||
res.setHeader('X-SourceMap', info.sourceMapUrl);
|
||||
|
||||
if (info.type === "js") {
|
||||
res.setHeader("Content-Type", "application/javascript; charset=UTF-8");
|
||||
} else if (info.type === "css") {
|
||||
res.setHeader("Content-Type", "text/css; charset=UTF-8");
|
||||
}
|
||||
|
||||
send(req, path.join(clientDir, info.path))
|
||||
.maxage(maxAge)
|
||||
.hidden(true) // if we specified a dotfile in the manifest, serve it
|
||||
.on('error', function (err) {
|
||||
Log.error("Error serving static file " + err);
|
||||
res.writeHead(500);
|
||||
res.end();
|
||||
})
|
||||
.on('directory', function () {
|
||||
Log.error("Unexpected directory " + info.path);
|
||||
res.writeHead(500);
|
||||
res.end();
|
||||
})
|
||||
.pipe(res);
|
||||
return WebAppInternals.staticFilesMiddleware({
|
||||
staticFiles: staticFiles,
|
||||
clientDir: clientDir
|
||||
}, req, res, next);
|
||||
});
|
||||
|
||||
// Packages and apps can add handlers to this via WebApp.connectHandlers.
|
||||
@@ -447,10 +482,6 @@ var runWebAppServer = function () {
|
||||
res.end("An error message");
|
||||
});
|
||||
|
||||
// Will be updated by main before we listen.
|
||||
var boilerplateFunc = null;
|
||||
var boilerplateBaseData = null;
|
||||
var boilerplateByAttributes = {};
|
||||
app.use(function (req, res, next) {
|
||||
if (! appUrl(req.url))
|
||||
return next();
|
||||
@@ -460,7 +491,6 @@ var runWebAppServer = function () {
|
||||
if (!boilerplateBaseData)
|
||||
throw new Error("boilerplateBaseData should be set before listening!");
|
||||
|
||||
|
||||
var headers = {
|
||||
'Content-Type': 'text/html; charset=utf-8'
|
||||
};
|
||||
@@ -480,27 +510,18 @@ var runWebAppServer = function () {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var htmlAttributes = getHtmlAttributes(request);
|
||||
|
||||
// The only thing that changes from request to request (for now) are the
|
||||
// HTML attributes (used by, eg, appcache), so we can memoize based on that.
|
||||
var attributeKey = JSON.stringify(htmlAttributes);
|
||||
if (!_.has(boilerplateByAttributes, attributeKey)) {
|
||||
try {
|
||||
var boilerplateData = _.extend({htmlAttributes: htmlAttributes},
|
||||
boilerplateBaseData);
|
||||
boilerplateByAttributes[attributeKey] = "<!DOCTYPE html>\n" +
|
||||
Blaze.toHTML(Blaze.With(boilerplateData, boilerplateFunc));
|
||||
} catch (e) {
|
||||
Log.error("Error running template: " + e.stack);
|
||||
res.writeHead(500, headers);
|
||||
res.end();
|
||||
return undefined;
|
||||
}
|
||||
var boilerplate;
|
||||
try {
|
||||
boilerplate = getBoilerplate(request);
|
||||
} catch (e) {
|
||||
Log.error("Error running template: " + e);
|
||||
res.writeHead(500, headers);
|
||||
res.end();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
res.writeHead(200, headers);
|
||||
res.write(boilerplateByAttributes[attributeKey]);
|
||||
res.write(boilerplate);
|
||||
res.end();
|
||||
return undefined;
|
||||
});
|
||||
@@ -599,13 +620,23 @@ var runWebAppServer = function () {
|
||||
var expectKeepalives = _.contains(argv, '--keepalive');
|
||||
|
||||
boilerplateBaseData = {
|
||||
// 'htmlAttributes' and 'inlineScriptsAllowed' are set at render
|
||||
// time, because they are allowed to change from request to
|
||||
// request.
|
||||
css: [],
|
||||
js: [],
|
||||
head: '',
|
||||
body: '',
|
||||
inlineScriptsAllowed: WebAppInternals.inlineScriptsAllowed(),
|
||||
additionalStaticJs: _.map(
|
||||
additionalStaticJs,
|
||||
function (contents, pathname) {
|
||||
return {
|
||||
pathname: pathname,
|
||||
contents: contents
|
||||
};
|
||||
}
|
||||
),
|
||||
meteorRuntimeConfig: JSON.stringify(__meteor_runtime_config__),
|
||||
reloadSafetyBelt: RELOAD_SAFETYBELT,
|
||||
rootUrlPathPrefix: __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || '',
|
||||
bundledJsCssPrefix: bundledJsCssPrefix ||
|
||||
__meteor_runtime_config__.ROOT_URL_PATH_PREFIX || ''
|
||||
@@ -1002,3 +1033,16 @@ WebAppInternals.setInlineScriptsAllowed = function (value) {
|
||||
WebAppInternals.setBundledJsCssPrefix = function (prefix) {
|
||||
bundledJsCssPrefix = prefix;
|
||||
};
|
||||
|
||||
// Packages can call `WebAppInternals.addStaticJs` to specify static
|
||||
// JavaScript to be included in the app. This static JS will be inlined,
|
||||
// unless inline scripts have been disabled, in which case it will be
|
||||
// served under `/<sha1 of contents>`.
|
||||
var additionalStaticJs = {};
|
||||
WebAppInternals.addStaticJs = function (contents) {
|
||||
additionalStaticJs["/" + sha1(contents) + ".js"] = contents;
|
||||
};
|
||||
|
||||
// Exported for tests
|
||||
WebAppInternals.getBoilerplate = getBoilerplate;
|
||||
WebAppInternals.additionalStaticJs = additionalStaticJs;
|
||||
|
||||
@@ -1,4 +1,48 @@
|
||||
var url = Npm.require("url");
|
||||
var crypto = Npm.require("crypto");
|
||||
var http = Npm.require("http");
|
||||
|
||||
var additionalScript = "(function () { var foo = 1; })";
|
||||
WebAppInternals.addStaticJs(additionalScript);
|
||||
var hash = crypto.createHash('sha1');
|
||||
hash.update(additionalScript);
|
||||
var additionalScriptPathname = hash.digest('hex') + ".js";
|
||||
|
||||
// Mock the 'res' object that gets passed to connect handlers. This mock
|
||||
// just records any utf8 data written to the response and returns it
|
||||
// when you call `mockResponse.getBody()`.
|
||||
var MockResponse = function () {
|
||||
this.buffer = "";
|
||||
this.statusCode = null;
|
||||
};
|
||||
|
||||
MockResponse.prototype.writeHead = function (statusCode) {
|
||||
this.statusCode = statusCode;
|
||||
};
|
||||
|
||||
MockResponse.prototype.setHeader = function (name, value) {
|
||||
// nothing
|
||||
};
|
||||
|
||||
MockResponse.prototype.write = function (data, encoding) {
|
||||
if (! encoding || encoding === "utf8") {
|
||||
this.buffer = this.buffer + data;
|
||||
}
|
||||
};
|
||||
|
||||
MockResponse.prototype.end = function (data, encoding) {
|
||||
if (! encoding || encoding === "utf8") {
|
||||
if (data) {
|
||||
this.buffer = this.buffer + data;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MockResponse.prototype.getBody = function () {
|
||||
return this.buffer;
|
||||
};
|
||||
|
||||
|
||||
|
||||
Tinytest.add("webapp - content-type header", function (test) {
|
||||
var cssResource = _.find(
|
||||
@@ -21,3 +65,64 @@ Tinytest.add("webapp - content-type header", function (test) {
|
||||
test.equal(resp.headers["content-type"].toLowerCase(),
|
||||
"application/javascript; charset=utf-8");
|
||||
});
|
||||
|
||||
Tinytest.add("webapp - additional static javascript", function (test) {
|
||||
var origInlineScriptsAllowed = WebAppInternals.inlineScriptsAllowed();
|
||||
|
||||
var staticFilesOpts = {
|
||||
staticFiles: {},
|
||||
clientDir: "/"
|
||||
};
|
||||
|
||||
// It's okay to set this global state because we're not going to yield
|
||||
// before settng it back to what it was originally.
|
||||
WebAppInternals.setInlineScriptsAllowed(true);
|
||||
|
||||
Meteor._noYieldsAllowed(function () {
|
||||
var boilerplate = WebAppInternals.getBoilerplate({
|
||||
browser: "doesn't-matter",
|
||||
url: "also-doesnt-matter"
|
||||
});
|
||||
|
||||
// When inline scripts are allowed, the script should be inlined.
|
||||
test.isTrue(boilerplate.indexOf(additionalScript) !== -1);
|
||||
|
||||
// And the script should not be served as its own separate resource,
|
||||
// meaning that the static file handler should pass on this request.
|
||||
var res = new MockResponse();
|
||||
var req = new http.IncomingMessage();
|
||||
req.headers = {};
|
||||
req.method = "GET";
|
||||
req.url = "/" + additionalScriptPathname;
|
||||
var nextCalled = false;
|
||||
WebAppInternals.staticFilesMiddleware(
|
||||
staticFilesOpts, req, res, function () {
|
||||
nextCalled = true;
|
||||
});
|
||||
test.isTrue(nextCalled);
|
||||
|
||||
// When inline scripts are disallowed, the script body should not be
|
||||
// inlined, and the script should be included in a <script src="..">
|
||||
// tag.
|
||||
WebAppInternals.setInlineScriptsAllowed(false);
|
||||
boilerplate = WebAppInternals.getBoilerplate({
|
||||
browser: "doesn't-matter",
|
||||
url: "also-doesnt-matter"
|
||||
});
|
||||
|
||||
// The script contents itself should not be present; the pathname
|
||||
// where the script is served should be.
|
||||
test.isTrue(boilerplate.indexOf(additionalScript) === -1);
|
||||
test.isTrue(boilerplate.indexOf(additionalScriptPathname) !== -1);
|
||||
|
||||
// And the static file handler should serve the script at that pathname.
|
||||
res = new MockResponse();
|
||||
WebAppInternals.staticFilesMiddleware(staticFilesOpts, req, res,
|
||||
function () { });
|
||||
var resBody = res.getBody();
|
||||
test.isTrue(resBody.indexOf(additionalScript) !== -1);
|
||||
test.equal(res.statusCode, 200);
|
||||
});
|
||||
|
||||
WebAppInternals.setInlineScriptsAllowed(origInlineScriptsAllowed);
|
||||
});
|
||||
|
||||
@@ -37,5 +37,12 @@ echo Found build $DIRNAME
|
||||
s3cmd ls s3://com.meteor.jenkins/$DIRNAME/ | \
|
||||
perl -nle 'if (/\.tar\.gz/) { ++$TAR } else { die "something weird" } END { exit !($TAR == 3) }'
|
||||
|
||||
for FILE in $(s3cmd ls s3://com.meteor.jenkins/$DIRNAME/ | perl -nlaF/ -e 'print $F[-1]'); do
|
||||
if s3cmd info $TARGET$FILE >/dev/null 2>&1; then
|
||||
echo "$TARGET$FILE already exists (maybe from another branch?)"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo Copying to $TARGET
|
||||
s3cmd -P cp -r s3://com.meteor.jenkins/$DIRNAME/ $TARGET
|
||||
|
||||
@@ -79,7 +79,7 @@ INSTALL_TMPDIR="$HOME/.meteor-install-tmp"
|
||||
rm -rf "$INSTALL_TMPDIR"
|
||||
mkdir "$INSTALL_TMPDIR"
|
||||
echo "Downloading Meteor distribution"
|
||||
curl --progress-bar --fail "$TARBALL_URL" | tar -xzf - -C "$INSTALL_TMPDIR"
|
||||
curl --progress-bar --fail "$TARBALL_URL" | tar -xzf - -C "$INSTALL_TMPDIR" -o
|
||||
# bomb out if it didn't work, eg no net
|
||||
test -x "${INSTALL_TMPDIR}/.meteor/meteor"
|
||||
mv "${INSTALL_TMPDIR}/.meteor" "$HOME"
|
||||
|
||||
@@ -1158,6 +1158,10 @@ _.extend(JsImage.prototype, {
|
||||
);
|
||||
|
||||
var sourceMapFileName = path.basename(loadItem.sourceMap);
|
||||
// Remove any existing sourceMappingURL line. (eg, if roundtripping
|
||||
// through JsImage.readFromDisk, don't end up with two!)
|
||||
item.source = item.source.replace(
|
||||
/\n\/\/# sourceMappingURL=.+\n?$/, '');
|
||||
item.source += "\n//# sourceMappingURL=" + sourceMapFileName + "\n";
|
||||
loadItem.sourceMapRoot = item.sourceMapRoot;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ var selftest = require('../selftest.js');
|
||||
var testUtils = require('../test-utils.js');
|
||||
var files = require('../files.js');
|
||||
var Sandbox = selftest.Sandbox;
|
||||
var httpHelpers = require('../http-helpers.js');
|
||||
|
||||
var commandTimeoutSecs = testUtils.accountsCommandTimeoutSecs;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user