diff --git a/History.md b/History.md
index 24f763d382..364ca06904 100644
--- a/History.md
+++ b/History.md
@@ -1,8 +1,15 @@
## vNEXT
+## v0.3.9
+
+* Add `spiderable` package to allow web crawlers to index Meteor apps.
+
+* `meteor deploy` uses SSL to protect application deployment.
+
* Fix `stopImmediatePropagation()`. #205
+
## v0.3.8
* HTTPS support
diff --git a/README.md b/README.md
index 6d94e88b0f..db7258e60a 100644
--- a/README.md
+++ b/README.md
@@ -74,3 +74,4 @@ in several ways:
* IRC: ```#meteor``` on ```irc.freenode.net```
* Ask a question: http://stackoverflow.com/questions/tagged/meteor
* Email us: ```contact@meteor.com```
+* How to contribute to Meteor: https://github.com/meteor/meteor/wiki
diff --git a/admin/debian/changelog b/admin/debian/changelog
index 1eec8a34ea..e7af1136c1 100644
--- a/admin/debian/changelog
+++ b/admin/debian/changelog
@@ -1,4 +1,4 @@
-meteor (0.3.8-1) unstable; urgency=low
+meteor (0.3.9-1) unstable; urgency=low
* Automated debian build.
diff --git a/admin/install-s3.sh b/admin/install-s3.sh
index 46f122a00c..a98a5a6f93 100755
--- a/admin/install-s3.sh
+++ b/admin/install-s3.sh
@@ -5,7 +5,7 @@
## example.
URLBASE="https://d3sqy0vbqsdhku.cloudfront.net"
-VERSION="0.3.8"
+VERSION="0.3.9"
PKGVERSION="${VERSION}-1"
UNAME=`uname`
diff --git a/admin/manifest.json b/admin/manifest.json
index 07f0e7ce07..6909f075f4 100644
--- a/admin/manifest.json
+++ b/admin/manifest.json
@@ -1,6 +1,6 @@
{
- "version": "0.3.8",
- "deb_version": "0.3.8-1",
- "rpm_version": "0.3.8-1",
+ "version": "0.3.9",
+ "deb_version": "0.3.9-1",
+ "rpm_version": "0.3.9-1",
"urlbase": "https://d3sqy0vbqsdhku.cloudfront.net"
}
diff --git a/admin/meteor.spec b/admin/meteor.spec
index 3d8fcc5660..b158cf9dfd 100644
--- a/admin/meteor.spec
+++ b/admin/meteor.spec
@@ -5,7 +5,7 @@
Summary: Meteor platform and JavaScript application server
Vendor: Meteor
Name: meteor
-Version: 0.3.8
+Version: 0.3.9
Release: 1
License: MIT
Group: Networking/WWW
diff --git a/app/lib/updater.js b/app/lib/updater.js
index 24762d830b..d4fda9083c 100644
--- a/app/lib/updater.js
+++ b/app/lib/updater.js
@@ -1,4 +1,4 @@
-exports.CURRENT_VERSION = "0.3.8";
+exports.CURRENT_VERSION = "0.3.9";
var fs = require("fs");
var http = require("http");
diff --git a/app/meteor/deploy.js b/app/meteor/deploy.js
index 264a18a562..bcc8be6de1 100644
--- a/app/meteor/deploy.js
+++ b/app/meteor/deploy.js
@@ -23,7 +23,7 @@ var DEPLOY_HOSTNAME = 'deploy.meteor.com';
// interactively prompt for here.
var meteor_rpc = function (rpc_name, method, site, query_params, callback) {
- var url = "http://" + DEPLOY_HOSTNAME + '/' + rpc_name + '/' + site;
+ var url = "https://" + DEPLOY_HOSTNAME + '/' + rpc_name + '/' + site;
if (!_.isEmpty(query_params))
url += '?' + qs.stringify(query_params);
@@ -309,7 +309,7 @@ var read_password = function (callback) {
// called exactly once. Calls callback with the entered password, or
// undefined if no password is required.
var with_password = function (site, callback) {
- var check_url = "http://" + DEPLOY_HOSTNAME + "/has_password/" + site;
+ var check_url = "https://" + DEPLOY_HOSTNAME + "/has_password/" + site;
request(check_url, function (error, response, body) {
if (error || response.statusCode !== 200) {
diff --git a/app/meteor/post-upgrade.js b/app/meteor/post-upgrade.js
index dbb3a2d33b..d1755703ce 100644
--- a/app/meteor/post-upgrade.js
+++ b/app/meteor/post-upgrade.js
@@ -2,7 +2,7 @@ try {
// XXX can't get this from updater.js because in 0.3.7 and before the
// updater didn't have the right NODE_PATH set. At some point we can
// remove this and just use updater.CURRENT_VERSION.
- var VERSION = "0.3.8";
+ var VERSION = "0.3.9";
var fs = require('fs');
var path = require('path');
diff --git a/docs/.meteor/packages b/docs/.meteor/packages
index 78d056fca5..36eb4b5aea 100644
--- a/docs/.meteor/packages
+++ b/docs/.meteor/packages
@@ -9,3 +9,4 @@ showdown
code-prettify
jquery-waypoints
less
+spiderable
diff --git a/docs/client/docs.html b/docs/client/docs.html
index 9c2e4f06ff..7ac14a97f5 100644
--- a/docs/client/docs.html
+++ b/docs/client/docs.html
@@ -11,7 +11,7 @@
-
Meteor 0.3.8
+ Meteor 0.3.9
{{> introduction }}
{{> concepts }}
{{> api }}
diff --git a/docs/client/docs.js b/docs/client/docs.js
index 3018e1cd40..c79d83b199 100644
--- a/docs/client/docs.js
+++ b/docs/client/docs.js
@@ -1,4 +1,4 @@
-METEOR_VERSION = "0.3.8";
+METEOR_VERSION = "0.3.9";
Meteor.startup(function () {
// XXX this is broken by the new multi-page layout. Also, it was
@@ -194,6 +194,7 @@ var toc = [
"jquery",
"less",
"sass",
+ "spiderable",
"stylus",
"showdown",
"underscore"
diff --git a/docs/client/packages.html b/docs/client/packages.html
index 65cbde7bd1..1c12157077 100644
--- a/docs/client/packages.html
+++ b/docs/client/packages.html
@@ -24,6 +24,7 @@ and removed with:
{{> pkg_jquery}}
{{> pkg_less}}
{{> pkg_sass}}
+{{> pkg_spiderable}}
{{> pkg_stylus}}
{{> pkg_showdown}}
{{> pkg_underscore}}
diff --git a/docs/client/packages/spiderable.html b/docs/client/packages/spiderable.html
new file mode 100644
index 0000000000..2255f02978
--- /dev/null
+++ b/docs/client/packages/spiderable.html
@@ -0,0 +1,41 @@
+
+{{#better_markdown}}
+## `spiderable`
+
+
+The `spiderable` package is a temporary solution to allow web search
+engines to index a Meteor application. It uses the AJAX
+Crawling specification published by Google to serve HTML to
+compatible spiders (Google, Bing, Yandex, and more).
+
+When a spider requests an HTML snapshot of a page the Meteor server runs
+the client half of the application inside phantomjs, a headless browser, and
+returns the full HTML generated by the client code.
+
+{{#warning}}
+This is a temporary approach to allow Meteor applications to be
+searchable. Expect significant changes to this package.
+{{/warning}}
+
+In order to have links between multiple pages on a site visible to
+spiders, apps must use real links (eg ``) rather than
+simply re-rendering portions of the page when an element is
+clicked. Apps should render their content based on the URL of the page
+and can use HTML5 push-state to alter the URL on the client without
+triggering a page reload. See the Todos example for a
+demonstration.
+
+
+{{#warning}}
+If you deploy your application with `meteor bundle`, you must install
+`phantomjs` (http://phantomjs.org) somewhere in your
+`$PATH`. If you use `meteor deploy` this is already taken care of.
+{{/warning}}
+
+
+{{/better_markdown}}
+
diff --git a/examples/todos/.meteor/packages b/examples/todos/.meteor/packages
index 388ffe4ce1..c71f4c1a54 100644
--- a/examples/todos/.meteor/packages
+++ b/examples/todos/.meteor/packages
@@ -5,6 +5,7 @@
underscore
backbone
+spiderable
accounts
accounts-ui
accounts-weibo
diff --git a/examples/todos/client/todos.css b/examples/todos/client/todos.css
index f311998666..405d35d494 100644
--- a/examples/todos/client/todos.css
+++ b/examples/todos/client/todos.css
@@ -138,6 +138,8 @@ h3 {
#lists .list-name {
cursor: pointer;
+ color: black;
+ text-decoration: none;
}
#createList {
diff --git a/examples/todos/client/todos.html b/examples/todos/client/todos.html
index 85910a1805..e408e7afb3 100644
--- a/examples/todos/client/todos.html
+++ b/examples/todos/client/todos.html
@@ -30,9 +30,9 @@
{{else}}
{{/if}}
diff --git a/examples/todos/client/todos.js b/examples/todos/client/todos.js
index c18aa57271..307f1fcc69 100644
--- a/examples/todos/client/todos.js
+++ b/examples/todos/client/todos.js
@@ -87,6 +87,10 @@ Template.lists.events = {
'mousedown .list': function (evt) { // select list
Router.setList(this._id);
},
+ 'click .list': function (evt) {
+ // prevent clicks on from refreshing the page.
+ evt.preventDefault();
+ },
'dblclick .list': function (evt) { // start editing list name
Session.set('editing_listname', this._id);
Meteor.flush(); // force DOM redraw, so we can focus the edit field
diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js
index 9e08e96b04..42be89615e 100644
--- a/packages/livedata/livedata_connection.js
+++ b/packages/livedata/livedata_connection.js
@@ -760,7 +760,9 @@ _.extend(Meteor, {
// "http://subdomain.meteor.com/sockjs" (deprecated),
// "/sockjs" (deprecated)
connect: function (url, _restartOnUpdate) {
- return new Meteor._LivedataConnection(url, _restartOnUpdate);
+ var ret = new Meteor._LivedataConnection(url, _restartOnUpdate);
+ Meteor._LivedataConnection._allConnections.push(ret); // hack. see below.
+ return ret;
},
autosubscribe: function (sub_func) {
@@ -789,3 +791,14 @@ _.extend(Meteor, {
}
});
+
+// Hack for `spiderable` package: a way to see if the page is done
+// loading all the data it needs.
+Meteor._LivedataConnection._allConnections = [];
+Meteor._LivedataConnection._allSubscriptionsReady = function () {
+ return _.all(Meteor._LivedataConnection._allConnections, function (conn) {
+ for (var k in conn.sub_ready_callbacks)
+ return false;
+ return true;
+ });
+};
diff --git a/packages/spiderable/package.js b/packages/spiderable/package.js
new file mode 100644
index 0000000000..6951f41fc0
--- /dev/null
+++ b/packages/spiderable/package.js
@@ -0,0 +1,10 @@
+Package.describe({
+ summary: "Makes the application crawlable to web spiders."
+});
+
+Package.on_use(function (api) {
+ api.use(['templating'], 'client');
+
+ api.add_files('spiderable.html', 'client');
+ api.add_files('spiderable.js', 'server');
+});
diff --git a/packages/spiderable/spiderable.html b/packages/spiderable/spiderable.html
new file mode 100644
index 0000000000..7cbdf71b89
--- /dev/null
+++ b/packages/spiderable/spiderable.html
@@ -0,0 +1 @@
+
diff --git a/packages/spiderable/spiderable.js b/packages/spiderable/spiderable.js
new file mode 100644
index 0000000000..153baa4f0e
--- /dev/null
+++ b/packages/spiderable/spiderable.js
@@ -0,0 +1,90 @@
+(function () {
+ var fs = __meteor_bootstrap__.require('fs');
+ var spawn = __meteor_bootstrap__.require('child_process').spawn;
+ var querystring = __meteor_bootstrap__.require('querystring');
+ var app = __meteor_bootstrap__.app;
+
+ // how long to let phantomjs run before we kill it
+ var REQUEST_TIMEOUT = 15*1000;
+
+ app.use(function (req, res, next) {
+ if (/\?.*_escaped_fragment_=/.test(req.url)) {
+ // get escaped fragment out of the url.
+ var idx = req.url.indexOf('?');
+ var preQuery = req.url.substr(0, idx);
+ var queryStr = req.url.substr(idx + 1);
+ var parsed = querystring.parse(queryStr);
+ delete parsed['_escaped_fragment_'];
+ var newQuery = querystring.stringify(parsed);
+ var newPath = preQuery + (newQuery ? "?" + newQuery : "");
+ var url = "http://" + req.headers.host + newPath;
+
+ // run phantomjs
+ //
+ // Use '/dev/stdin' to avoid writing to a temporary file. Can't
+ // just omit the file, as PhantomJS takes that to mean 'use a
+ // REPL' and exits as soon as stdin closes.
+ var cp = spawn('phantomjs', ['--load-images=no', '/dev/stdin']);
+
+ var data = '';
+ cp.stdout.setEncoding('utf8');
+ cp.stdout.on('data', function (chunk) {
+ data += chunk;
+ });
+
+ cp.on('exit', function (code) {
+ if (0 === code && //i.test(data)) {
+ res.writeHead(200, {'Content-Type': 'text/html; charset=UTF-8'});
+ res.end(data);
+ } else {
+ // phantomjs failed. Don't send the error, instead send the
+ // normal page.
+ if (code === 127)
+ Meteor._debug("spiderable: phantomjs not installed. Download and install from http://phantomjs.org/");
+ else
+ Meteor._debug("spiderable: phantomjs failed:", code, data);
+
+ next();
+ }
+ });
+
+ // don't crash w/ EPIPE if phantomjs isn't installed.
+ cp.stdin.on('error', function () {});
+
+ cp.stdin.write(
+ "var url = '" + url + "';" +
+"var page = require('webpage').create();" +
+"page.open(url);" +
+
+"setInterval(function() {" +
+" var ready = page.evaluate(function () {" +
+" if (typeof Meteor !== 'undefined' && Meteor.status().connected) {" +
+" Meteor.flush();" +
+" return Meteor._LivedataConnection._allSubscriptionsReady();" +
+" }" +
+" return false;" +
+" });" +
+
+" if (ready) {" +
+" var out = page.content;" +
+" out = out.replace(/