mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'release-0.5.3'
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,3 +6,6 @@
|
||||
\#*\#
|
||||
.\#*
|
||||
.idea
|
||||
*.iml
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
75
History.md
75
History.md
@@ -1,6 +1,81 @@
|
||||
|
||||
## vNEXT
|
||||
|
||||
## v0.5.3
|
||||
|
||||
* Add `--settings` argument to `meteor deploy` and `meteor run`. This
|
||||
allows you to specify deployment-specific information made available
|
||||
to server code in the variable `Meteor.settings`.
|
||||
|
||||
* Support unlimited open tabs in a single browser. Work around the
|
||||
browser per-hostname connection limit by using randomized hostnames
|
||||
for deployed apps. #131
|
||||
|
||||
* minimongo improvements:
|
||||
* Allow observing cursors with `skip` or `limit`. #528
|
||||
* Allow sorting on `dotted.sub.keys`. #533
|
||||
* Allow querying specific array elements (`foo.1.bar`).
|
||||
* `$and`, `$or`, and `$nor` no longer accept empty arrays (for consistency
|
||||
with Mongo)
|
||||
|
||||
* Re-rendering a template with Spark no longer reverts changes made by
|
||||
users to a `preserve`d form element. Instead, the newly rendered value
|
||||
is only applied if it is different from the previously rendered value.
|
||||
Additionally, <INPUT> elements with type other than TEXT can now have
|
||||
reactive values (eg, the labels on submit buttons can now be
|
||||
reactive). #510 #514 #523 #537 #558
|
||||
|
||||
* Support JavaScript RegExp objects in selectors in Collection write
|
||||
methods on the client, eg `myCollection.remove({foo: /bar/})`. #346
|
||||
|
||||
* `meteor` command-line improvements:
|
||||
* Improve error message when mongod fails to start.
|
||||
* The `NODE_OPTIONS` environment variable can be used to pass command-line
|
||||
flags to node (eg, `--debug` or `--debug-brk` to enable the debugger).
|
||||
* Die with error if an app name is mistakenly passed to `meteor reset`.
|
||||
|
||||
* Add support for "offline" access tokens with Google login. #464 #525
|
||||
|
||||
* Don't remove `serviceData` fields from previous logins when logging in
|
||||
with an external service.
|
||||
|
||||
* Improve `OAuth1Binding` to allow making authenticated API calls to
|
||||
OAuth1 providers (eg Twitter). #539
|
||||
|
||||
* New login providers automatically work with `{{loginButtons}}` without
|
||||
needing to edit the `accounts-ui-unstyled` package. #572
|
||||
|
||||
* Use `Content-Type: application/json` by default when sending JSON data
|
||||
with `Meteor.http`.
|
||||
|
||||
* Improvements to `jsparse`: hex literals, keywords as property names, ES5 line
|
||||
continuations, trailing commas in object literals, line numbers in error
|
||||
messages, decimal literals starting with `.`, regex character classes with
|
||||
slashes.
|
||||
|
||||
* Spark improvements:
|
||||
* Improve rendering of <SELECT> elements on IE. #496
|
||||
* Don't lose nested data contexts in IE9/10 after two seconds. #458
|
||||
* Don't print a stack trace if DOM nodes are manually removed
|
||||
from the document without calling `Spark.finalize`. #392
|
||||
|
||||
* Always use the `autoReconnect` flag when connecting to Mongo. #425
|
||||
|
||||
* Fix server-side `observe` with no `added` callback. #589
|
||||
|
||||
* Fix re-sending method calls on reconnect. #538
|
||||
|
||||
* Remove deprecated `/sockjs` URL support from `Meteor.connect`.
|
||||
|
||||
* Avoid losing a few bits of randomness in UUID v4 creation. #519
|
||||
|
||||
* Update clean-css package from 0.8.2 to 0.8.3, fixing minification of `0%`
|
||||
values in `hsl` colors. #515
|
||||
|
||||
Patches contributed by GitHub users Ed-von-Schleck, egtann, jwulf, lvbreda,
|
||||
martin-naumann, meawoppl, nwmartin, timhaines, and zealoushacker.
|
||||
|
||||
|
||||
## v0.5.2
|
||||
|
||||
* Fix 0.5.1 regression: Cursor `observe` works during server startup. #507
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
# NOTE: by default this tests the installed meteor, not the one in your
|
||||
# working copy.
|
||||
# NOTE: by default this tests the working copy, not the installed meteor.
|
||||
# To test the installed meteor, pass in --global
|
||||
|
||||
METEOR=/usr/local/bin/meteor
|
||||
cd `dirname $0`
|
||||
METEOR=`pwd`/../meteor
|
||||
|
||||
if [ -z "$NODE" ]; then
|
||||
NODE=`pwd`/node.sh
|
||||
fi
|
||||
|
||||
#If this ever takes more options, use getopt
|
||||
if [ "$1" == "--global" ]; then
|
||||
METEOR=meteor
|
||||
fi
|
||||
|
||||
DIR=`mktemp -d -t meteor-cli-test-XXXXXXXX`
|
||||
trap 'echo FAILED ; rm -rf "$DIR" >/dev/null 2>&1' EXIT
|
||||
@@ -71,7 +81,8 @@ echo "... run"
|
||||
MONGOMARK='--bind_ip 127.0.0.1 --smallfiles --port 9102'
|
||||
# kill any old test meteor
|
||||
# there is probably a better way to do this, but it is at least portable across macos and linux
|
||||
ps ax | grep -e 'meteor.js -p 9100' | grep -v grep | awk '{print $1}' | xargs kill
|
||||
# (the || true is needed on linux, whose xargs will invoke kill even with no args)
|
||||
ps ax | grep -e 'meteor.js -p 9100' | grep -v grep | awk '{print $1}' | xargs kill || true
|
||||
|
||||
! $METEOR mongo > /dev/null 2>&1
|
||||
$METEOR reset > /dev/null 2>&1
|
||||
@@ -83,7 +94,7 @@ PORT=9100
|
||||
$METEOR -p $PORT > /dev/null 2>&1 &
|
||||
METEOR_PID=$!
|
||||
|
||||
sleep 1 # XXX XXX lame
|
||||
sleep 2 # XXX XXX lame
|
||||
|
||||
test -d .meteor/local/db
|
||||
ps ax | grep -e "$MONGOMARK" | grep -v grep > /dev/null
|
||||
@@ -105,16 +116,51 @@ echo "... rerun"
|
||||
$METEOR -p $PORT > /dev/null 2>&1 &
|
||||
METEOR_PID=$!
|
||||
|
||||
sleep 1 # XXX XXX lame
|
||||
sleep 2 # XXX XXX lame
|
||||
|
||||
ps ax | grep -e "$MONGOMARK" | grep -v grep > /dev/null
|
||||
curl -s "http://localhost:$PORT" > /dev/null
|
||||
|
||||
kill $METEOR_PID
|
||||
ps ax | grep -e "$MONGOMARK" | grep -v grep | awk '{print $1}' | xargs kill
|
||||
sleep 10 # XXX XXX lame. have to wait for inner app to die via keepalive!
|
||||
|
||||
ps ax | grep -e "$MONGOMARK" | grep -v grep | awk '{print $1}' | xargs kill || true
|
||||
sleep 2 # need to make sure these kills take effect
|
||||
|
||||
echo "... mongo message"
|
||||
|
||||
# Run a server on the same port as mongod, so that mongod fails to start up. Rig
|
||||
# it so that a single connection will cause it to exit.
|
||||
$NODE -e 'require("net").createServer(function(){process.exit(0)}).listen('$PORT'+2, "127.0.0.1")' &
|
||||
|
||||
sleep 1
|
||||
|
||||
$METEOR -p $PORT > error.txt || true
|
||||
|
||||
grep 'port was closed' error.txt > /dev/null
|
||||
|
||||
# Kill the server by connecting to it.
|
||||
$NODE -e 'require("net").connect({host:"127.0.0.1",port:'$PORT'+2},function(){process.exit(0);})'
|
||||
|
||||
echo "... settings"
|
||||
|
||||
cat > settings.json <<EOF
|
||||
{ "foo" : "bar",
|
||||
"baz" : "quux"
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > settings.js <<EOF
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(function () {
|
||||
if (!Meteor.settings) process.exit(1);
|
||||
if (Meteor.settings.foo !== "bar") process.exit(1);
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
EOF
|
||||
|
||||
$METEOR -p $PORT --settings='settings.json' --once > /dev/null
|
||||
|
||||
# XXX more tests here!
|
||||
|
||||
|
||||
53
admin/copy-release-from-jenkins.sh
Executable file
53
admin/copy-release-from-jenkins.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Requires s3cmd to be installed and an appropriate ~/.s3cfg.
|
||||
# Usage:
|
||||
# admin/copy-release-from-jenkins.sh [--prod] BUILDNUMBER
|
||||
# where BUILDNUMBER is the small integer Jenkins build number.
|
||||
|
||||
set -e
|
||||
set -u
|
||||
|
||||
cd `dirname $0`
|
||||
|
||||
TARGET="s3://com.meteor.static/test/"
|
||||
TEST=no
|
||||
if [ $# -ge 1 -a $1 = '--prod' ]; then
|
||||
shift
|
||||
TARGET="s3://com.meteor.static/"
|
||||
else
|
||||
TEST=yes
|
||||
fi
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "usage: $0 [--prod] jenkins-build-number" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DIRNAME=$(s3cmd ls s3://com.meteor.jenkins/ | perl -nle 'print $1 if m!/(release-.+--'$1'--.+)/!')
|
||||
|
||||
if [ -z "$DIRNAME" ]; then
|
||||
echo "build not found" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo Found build $DIRNAME
|
||||
|
||||
# Check to make sure the proper number of each kind of file is there.
|
||||
s3cmd ls s3://com.meteor.jenkins/$DIRNAME/ | \
|
||||
perl -nle '++$RPM if /\.rpm/; ++$DEB if /\.deb/; ++$TAR if /\.tar\.gz/; ++$DIR if /DIR/; END { exit !($RPM == 2 && $DEB == 2 && $TAR == 3 && $DIR == 1) }'
|
||||
|
||||
echo Copying to $TARGET
|
||||
s3cmd -P cp -r s3://com.meteor.jenkins/$DIRNAME/ $TARGET
|
||||
|
||||
if [ $TEST = 'yes' ]; then
|
||||
echo Uploading modified install-s3.sh and manifest.json
|
||||
|
||||
OUTDIR=$(mktemp -dt meteor-crfj)
|
||||
perl -pe 's!https://d3sqy0vbqsdhku.cloudfront.net!https://s3.amazonaws.com/com.meteor.static/test!g' install-s3.sh >$OUTDIR/install-s3.sh
|
||||
perl -pe 's!https://d3sqy0vbqsdhku.cloudfront.net!https://s3.amazonaws.com/com.meteor.static/test!g' manifest.json >$OUTDIR/manifest.json
|
||||
|
||||
cd $OUTDIR
|
||||
s3cmd -P put install-s3.sh s3://com.meteor.static/test/update/
|
||||
s3cmd -P put manifest.json s3://com.meteor.static/test/update/
|
||||
fi
|
||||
@@ -1,4 +1,4 @@
|
||||
meteor (0.5.2-1) unstable; urgency=low
|
||||
meteor (0.5.3-1) unstable; urgency=low
|
||||
|
||||
* Automated debian build.
|
||||
|
||||
|
||||
11
admin/find-new-npm-versions.sh
Executable file
11
admin/find-new-npm-versions.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
BASEDIR=`dirname $0`
|
||||
cat $BASEDIR/generate-dev-bundle.sh | grep "npm install" | sed "s/npm install //" | sed "s/@.*//" | while read PACKAGE
|
||||
do
|
||||
CURRENT_VERSION=`cat $BASEDIR/generate-dev-bundle.sh | grep "npm install $PACKAGE" | sed "s/npm install //" | sed "s/.*@//"`
|
||||
LATEST_VERSION=`$BASEDIR/../dev_bundle/bin/npm info $PACKAGE version 2> /dev/null`
|
||||
if [ "$CURRENT_VERSION" != "$LATEST_VERSION" ]
|
||||
then
|
||||
echo "$PACKAGE -- current version: $CURRENT_VERSION, latest version: $LATEST_VERSION"
|
||||
fi
|
||||
done
|
||||
@@ -3,7 +3,7 @@
|
||||
set -e
|
||||
set -u
|
||||
|
||||
BUNDLE_VERSION=0.2.8
|
||||
BUNDLE_VERSION=0.2.12
|
||||
UNAME=$(uname)
|
||||
ARCH=$(uname -m)
|
||||
|
||||
@@ -89,7 +89,7 @@ npm install semver@1.1.0
|
||||
npm install handlebars@1.0.7
|
||||
npm install mongodb@1.1.11
|
||||
npm install uglify-js@1.3.4
|
||||
npm install clean-css@0.8.2
|
||||
npm install clean-css@0.8.3
|
||||
npm install useragent@1.1.0
|
||||
npm install request@2.12.0
|
||||
npm install simplesmtp@0.1.25
|
||||
@@ -97,6 +97,8 @@ npm install stream-buffers@0.2.3
|
||||
npm install keypress@0.1.0
|
||||
npm install sockjs@0.3.4
|
||||
npm install http-proxy@0.8.5
|
||||
npm install underscore@1.4.2
|
||||
npm install tar@0.1.14
|
||||
|
||||
# progress 0.1.0 has a regression where it opens stdin and thus does not
|
||||
# allow the node process to exit cleanly. See
|
||||
@@ -107,6 +109,13 @@ npm install progress@0.0.5
|
||||
# which make the dev bundle much bigger. We need a better solution.
|
||||
npm install mailcomposer@0.1.15
|
||||
|
||||
# Use our version of fstream with a bug fixed. Also have tar use it.
|
||||
# See https://github.com/isaacs/fstream/pull/11 .
|
||||
npm install https://github.com/meteor/fstream/tarball/91c56e7
|
||||
cd tar/node_modules
|
||||
npm install https://github.com/meteor/fstream/tarball/91c56e7
|
||||
cd ../..
|
||||
|
||||
# If you update the version of fibers in the dev bundle, also update the "npm
|
||||
# install" command in docs/client/concepts.html.
|
||||
npm install fibers@0.6.9
|
||||
|
||||
@@ -7,7 +7,7 @@ var semver = require('semver');
|
||||
var optimist = require('optimist');
|
||||
|
||||
var updater = require(path.join(__dirname, '..', 'app', 'lib', 'updater.js'));
|
||||
var _ = require(path.join(__dirname, '..', 'app', 'lib', 'third', 'underscore.js'));
|
||||
var _ = require('underscore');
|
||||
|
||||
// What files to update. Relative to project root.
|
||||
var UPDATE_FILES = [path.join('app', 'lib', 'updater.js'),
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
## example.
|
||||
|
||||
URLBASE="https://d3sqy0vbqsdhku.cloudfront.net"
|
||||
VERSION="0.5.2"
|
||||
VERSION="0.5.3"
|
||||
PKGVERSION="${VERSION}-1"
|
||||
|
||||
UNAME=`uname`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": "0.5.2",
|
||||
"deb_version": "0.5.2-1",
|
||||
"rpm_version": "0.5.2-1",
|
||||
"version": "0.5.3",
|
||||
"deb_version": "0.5.3-1",
|
||||
"rpm_version": "0.5.3-1",
|
||||
"urlbase": "https://d3sqy0vbqsdhku.cloudfront.net"
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
Summary: Meteor platform and JavaScript application server
|
||||
Vendor: Meteor
|
||||
Name: meteor
|
||||
Version: 0.5.2
|
||||
Version: 0.5.3
|
||||
Release: 1
|
||||
License: MIT
|
||||
Group: Networking/WWW
|
||||
|
||||
@@ -20,6 +20,7 @@ if [ "$EMACS" == t ]; then
|
||||
fi
|
||||
|
||||
"$TOPDIR/dev_bundle/bin/node" "$@"
|
||||
EXITSTATUS=$?
|
||||
|
||||
# Node sets stdin to non-blocking, which causes Emacs shell to die after it
|
||||
# exits. Work around this by setting stdin to blocking again.
|
||||
@@ -27,3 +28,5 @@ if [ "$EMACS" == t ]; then
|
||||
perl -MFcntl=F_GETFL,F_SETFL,O_NONBLOCK -e \
|
||||
'fcntl(STDIN, F_SETFL, ~O_NONBLOCK & fcntl(STDIN, F_GETFL, 0))'
|
||||
fi
|
||||
|
||||
exit $EXITSTATUS
|
||||
|
||||
@@ -31,7 +31,7 @@ var crypto = require('crypto');
|
||||
var fs = require('fs');
|
||||
var uglify = require('uglify-js');
|
||||
var cleanCSS = require('clean-css');
|
||||
var _ = require(path.join(__dirname, 'third', 'underscore.js'));
|
||||
var _ = require('underscore');
|
||||
|
||||
// files to ignore when bundling. node has no globs, so use regexps
|
||||
var ignore_files = [
|
||||
@@ -143,7 +143,7 @@ _.extend(PackageInstance.prototype, {
|
||||
// should be the extension of the file without a leading dot.)
|
||||
get_source_handler: function (extension) {
|
||||
var self = this;
|
||||
var candidates = []
|
||||
var candidates = [];
|
||||
|
||||
if (extension in self.pkg.extensions)
|
||||
candidates.push(self.pkg.extensions[extension]);
|
||||
@@ -277,7 +277,7 @@ var Bundle = function () {
|
||||
_.each(where, function (w) {
|
||||
if (options.type === "js") {
|
||||
if (!options.path)
|
||||
throw new Error("Must specify path")
|
||||
throw new Error("Must specify path");
|
||||
|
||||
if (w === "client" || w === "server") {
|
||||
self.files[w][options.path] = data;
|
||||
@@ -292,7 +292,7 @@ var Bundle = function () {
|
||||
// that appear in the server directories in an app tree
|
||||
return;
|
||||
if (!options.path)
|
||||
throw new Error("Must specify path")
|
||||
throw new Error("Must specify path");
|
||||
self.files.client[options.path] = data;
|
||||
self.css.push(options.path);
|
||||
} else if (options.type === "head" || options.type === "body") {
|
||||
@@ -358,8 +358,8 @@ _.extend(Bundle.prototype, {
|
||||
// XXX detect circular dependencies and print an error. (not sure
|
||||
// what the current code will do)
|
||||
|
||||
if (pkg.on_use)
|
||||
pkg.on_use(inst.api, where);
|
||||
if (pkg.on_use_handler)
|
||||
pkg.on_use_handler(inst.api, where);
|
||||
},
|
||||
|
||||
include_tests: function (pkg) {
|
||||
@@ -369,8 +369,8 @@ _.extend(Bundle.prototype, {
|
||||
self.tests_included[pkg.id] = true;
|
||||
|
||||
var inst = self._get_instance(pkg);
|
||||
if (inst.pkg.on_test)
|
||||
inst.pkg.on_test(inst.api);
|
||||
if (inst.pkg.on_test_handler)
|
||||
inst.pkg.on_test_handler(inst.api);
|
||||
},
|
||||
|
||||
// Minify the bundle
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var fs = require("fs");
|
||||
var path = require('path');
|
||||
var _ = require(path.join(__dirname, 'third', 'underscore.js'));
|
||||
var _ = require('underscore');
|
||||
|
||||
var files = module.exports = {
|
||||
// A sort comparator to order files into load order.
|
||||
|
||||
67
app/lib/mongo_exit_codes.js
Normal file
67
app/lib/mongo_exit_codes.js
Normal file
@@ -0,0 +1,67 @@
|
||||
// MongoDB exit codes. This replicates information in
|
||||
// https://github.com/mongodb/docs/blob/master/source/reference/exit-codes.txt
|
||||
// but in a javascript dictionary instead of just a text file.
|
||||
|
||||
// Explanations have been rewritten, not copied, for license reasons.
|
||||
|
||||
|
||||
var path = require("path");
|
||||
var _ = require('underscore');
|
||||
|
||||
exports.Codes = {
|
||||
0 : { code: 0,
|
||||
symbol: "EXIT_CLEAN",
|
||||
longText: "MongoDB exited cleanly"
|
||||
},
|
||||
2 : { code: 2,
|
||||
symbol: "EXIT_BADOPTIONS",
|
||||
longText: "MongoDB was started with erroneous or incompatible command line options"
|
||||
},
|
||||
3 : { code: 3,
|
||||
symbol: "EXIT_REPLICATION_ERROR",
|
||||
longText: "There was an inconsistency between hostnames specified\n" +
|
||||
"on the command line compared with hostnames stored in local.sources"
|
||||
},
|
||||
4 : { code: 4,
|
||||
symbol: "EXIT_NEED_UPGRADE",
|
||||
longText: "MongoDB needs to upgrade to use this database"
|
||||
},
|
||||
5 : { code: 5,
|
||||
symbol: "EXIT_SHARDING_ERROR",
|
||||
longText: "A moveChunk operation failed"
|
||||
},
|
||||
12 : { code: 12,
|
||||
symbol: "EXIT_KILL",
|
||||
longText: "The MongoDB process was killed, on Windows"
|
||||
},
|
||||
14 : { code: 14,
|
||||
symbol: "EXIT_ABRUPT",
|
||||
longText: "Unspecified unrecoverable error. Exit was not clean"
|
||||
},
|
||||
20 : { code: 20,
|
||||
symbol: "EXIT_NTSERVICE_ERROR",
|
||||
longText: "Error managing NT Service on Windows"
|
||||
},
|
||||
45 : { code: 45,
|
||||
symbol: "EXIT_FS",
|
||||
longText: "MongoDB cannot open or obtain a lock on a file"
|
||||
},
|
||||
47 : { code: 47,
|
||||
symbol: "EXIT_CLOCK_SKEW",
|
||||
longText: "MongoDB exited due to excess clock skew"
|
||||
},
|
||||
48 : { code: 48,
|
||||
symbol: "EXIT_NET_ERROR",
|
||||
longText: "MongoDB exited because its port was closed, or was already\n" +
|
||||
"taken by a previous instance of MongoDB"
|
||||
},
|
||||
100 : { code: 100,
|
||||
symbol: "EXIT_UNCAUGHT",
|
||||
longText: "MongoDB had an unspecified uncaught exception.\n" +
|
||||
"Check to make sure that MongoDB is able to write to its database directory."
|
||||
}
|
||||
};
|
||||
|
||||
_.each(exports.Codes, function (value) {
|
||||
exports[value.symbol] = value;
|
||||
});
|
||||
@@ -1,10 +1,10 @@
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var spawn = require('child_process').spawn;
|
||||
var child_process = require('child_process');
|
||||
|
||||
var files = require(path.join(__dirname, '..', 'lib', 'files.js'));
|
||||
|
||||
var _ = require(path.join('..', 'lib', 'third', 'underscore.js'));
|
||||
var _ = require('underscore');
|
||||
|
||||
|
||||
/** Internal.
|
||||
@@ -15,37 +15,34 @@ var _ = require(path.join('..', 'lib', 'third', 'underscore.js'));
|
||||
*/
|
||||
var find_mongo_pids = function (app_dir, port, callback) {
|
||||
// 'ps ax' should be standard across all MacOS and Linux.
|
||||
var proc = spawn('ps', ['ax']);
|
||||
var data = '';
|
||||
proc.stdout.on('data', function (d) {
|
||||
data += d;
|
||||
});
|
||||
child_process.exec('ps ax',
|
||||
function (error, stdout, stderr) {
|
||||
if (error) {
|
||||
callback({reason: error});
|
||||
} else if (stderr) {
|
||||
callback({reason: 'ps produced stderr ' + stderr});
|
||||
} else {
|
||||
var pids = [];
|
||||
|
||||
proc.on('exit', function (code, signal) {
|
||||
if (code === 0) {
|
||||
var pids = [];
|
||||
_.each(stdout.split('\n'), function (ps_line) {
|
||||
// matches mongos we start.
|
||||
var m = ps_line.match(/^\s*(\d+).+mongod .+--port (\d+) --dbpath (.+)(?:\/|\\)\.meteor(?:\/|\\)local(?:\/|\\)db\s*$/);
|
||||
if (m && m.length === 4) {
|
||||
var found_pid = parseInt(m[1]);
|
||||
var found_port = parseInt(m[2]);
|
||||
var found_path = m[3];
|
||||
|
||||
_.each(data.split('\n'), function (ps_line) {
|
||||
// matches mongos we start.
|
||||
var m = ps_line.match(/^\s*(\d+).+mongod .+--port (\d+) --dbpath (.+)(?:\/|\\)\.meteor(?:\/|\\)local(?:\/|\\)db\s*$/);
|
||||
if (m && m.length === 4) {
|
||||
var found_pid = parseInt(m[1]);
|
||||
var found_port = parseInt(m[2]);
|
||||
var found_path = m[3];
|
||||
|
||||
if ( (!port || port === found_port) &&
|
||||
(!app_dir || app_dir === found_path)) {
|
||||
pids.push({
|
||||
pid: found_pid, port: found_port, app_dir: found_path});
|
||||
if ( (!port || port === found_port) &&
|
||||
(!app_dir || app_dir === found_path)) {
|
||||
pids.push({
|
||||
pid: found_pid, port: found_port, app_dir: found_path});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
callback(null, pids);
|
||||
} else {
|
||||
callback({reason: 'ps exit code ' + code});
|
||||
}
|
||||
});
|
||||
callback(null, pids);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -131,17 +128,24 @@ var find_mongo_and_kill_it_dead = function (port, callback) {
|
||||
};
|
||||
|
||||
exports.launch_mongo = function (app_dir, port, launch_callback, on_exit_callback) {
|
||||
var handle = {stop: function (callback) { callback(); } };
|
||||
launch_callback = launch_callback || function () {};
|
||||
on_exit_callback = on_exit_callback || function () {};
|
||||
|
||||
// If we are passed an external mongo, assume it is launched and never
|
||||
// exits. Matches code in run.js:exports.run.
|
||||
|
||||
// Since it is externally managed, asking it to actually stop would be
|
||||
// impolite, so our stoppable handle is a noop
|
||||
if (process.env.MONGO_URL) {
|
||||
launch_callback();
|
||||
return;
|
||||
return handle;
|
||||
}
|
||||
|
||||
var mongod_path = path.join(files.get_dev_bundle(), 'mongodb', 'bin', 'mongod');
|
||||
var mongod_path = path.join(files.get_dev_bundle(),
|
||||
'mongodb',
|
||||
'bin',
|
||||
'mongod');
|
||||
|
||||
// store data in app_dir
|
||||
var data_path = path.join(app_dir, '.meteor', 'local', 'db');
|
||||
@@ -155,21 +159,21 @@ exports.launch_mongo = function (app_dir, port, launch_callback, on_exit_callbac
|
||||
return;
|
||||
}
|
||||
|
||||
var proc = spawn(mongod_path, [
|
||||
var proc = child_process.spawn(mongod_path, [
|
||||
'--bind_ip', '127.0.0.1',
|
||||
'--smallfiles',
|
||||
'--port', port,
|
||||
'--dbpath', data_path
|
||||
]);
|
||||
handle.stop = function (callback) {
|
||||
var tries = 0;
|
||||
var exited = false;
|
||||
proc.removeListener('exit', on_exit_callback);
|
||||
proc.kill('SIGINT');
|
||||
callback && callback(err);
|
||||
};
|
||||
|
||||
proc.on('exit', function (code, signal) {
|
||||
on_exit_callback(code, signal);
|
||||
});
|
||||
|
||||
// proc.stderr.setEncoding('utf8');
|
||||
// proc.stderr.on('data', function (data) {
|
||||
// process.stdout.write(data);
|
||||
// });
|
||||
proc.on('exit', on_exit_callback);
|
||||
|
||||
proc.stdout.setEncoding('utf8');
|
||||
proc.stdout.on('data', function (data) {
|
||||
@@ -178,6 +182,5 @@ exports.launch_mongo = function (app_dir, port, launch_callback, on_exit_callbac
|
||||
launch_callback();
|
||||
});
|
||||
});
|
||||
|
||||
return handle;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
var path = require('path');
|
||||
var _ = require(path.join(__dirname, 'third', 'underscore.js'));
|
||||
var _ = require('underscore');
|
||||
var files = require(path.join(__dirname, 'files.js'));
|
||||
var fs = require('fs');
|
||||
|
||||
@@ -36,14 +36,14 @@ var Package = function () {
|
||||
// package metadata, from describe()
|
||||
self.metadata = {};
|
||||
|
||||
self.on_use = null;
|
||||
self.on_test = null;
|
||||
self.on_use_handler = null;
|
||||
self.on_test_handler = null;
|
||||
|
||||
// registered source file handlers
|
||||
self.extensions = {};
|
||||
|
||||
// functions that can be called when the package is scanned
|
||||
self.api = {
|
||||
self.declarationFuncs = {
|
||||
// keys
|
||||
// - summary: for 'meteor list'
|
||||
// - internal: if true, hide in list
|
||||
@@ -60,15 +60,15 @@ var Package = function () {
|
||||
},
|
||||
|
||||
on_use: function (f) {
|
||||
if (self.on_use)
|
||||
if (self.on_use_handler)
|
||||
throw new Error("A package may have only one on_use handler");
|
||||
self.on_use = f;
|
||||
self.on_use_handler = f;
|
||||
},
|
||||
|
||||
on_test: function (f) {
|
||||
if (self.on_test)
|
||||
if (self.on_test_handler)
|
||||
throw new Error("A package may have only one on_test handler");
|
||||
self.on_test = f;
|
||||
self.on_test_handler = f;
|
||||
},
|
||||
|
||||
register_extension: function (extension, callback) {
|
||||
@@ -86,10 +86,10 @@ _.extend(Package.prototype, {
|
||||
self.name = name;
|
||||
self.source_root = files.get_package_dir(name);
|
||||
self.serve_root = path.join(path.sep, 'packages', name);
|
||||
|
||||
|
||||
if (!self.source_root)
|
||||
throw new Error("The package named " + self.name + " does not exist.");
|
||||
|
||||
|
||||
var fullpath = path.join(self.source_root, 'package.js');
|
||||
var code = fs.readFileSync(fullpath).toString();
|
||||
// \n is necessary in case final line is a //-comment
|
||||
@@ -104,7 +104,7 @@ _.extend(Package.prototype, {
|
||||
// 'templating' use this to load other code to run at
|
||||
// bundle-time. and to pull in, eg, 'fs' and 'path' to access
|
||||
// the file system
|
||||
func(self.api, require);
|
||||
func(self.declarationFuncs, require);
|
||||
},
|
||||
|
||||
init_from_app_dir: function (app_dir, ignore_files) {
|
||||
@@ -124,7 +124,7 @@ _.extend(Package.prototype, {
|
||||
});
|
||||
};
|
||||
|
||||
self.api.on_use(function (api) {
|
||||
self.declarationFuncs.on_use(function (api) {
|
||||
// -- Packages --
|
||||
|
||||
// standard client packages (for now), for the classic meteor
|
||||
@@ -140,7 +140,7 @@ _.extend(Package.prototype, {
|
||||
api.add_files(sources_except(api, "client"), "server");
|
||||
});
|
||||
|
||||
self.api.on_test(function (api) {
|
||||
self.declarationFuncs.on_test(function (api) {
|
||||
api.use(self);
|
||||
api.add_files(sources_except(api, "server", true), "client");
|
||||
api.add_files(sources_except(api, "client", true), "server");
|
||||
@@ -202,7 +202,7 @@ _.extend(Package.prototype, {
|
||||
self.source_root = null;
|
||||
self.serve_root = null;
|
||||
|
||||
self.api.on_test(function (api) {
|
||||
self.declarationFuncs.on_test(function (api) {
|
||||
_.each(fs.readdirSync(collection_dir), function (name) {
|
||||
// only take things that are actually packages
|
||||
if (files.is_package_dir(path.join(collection_dir, name)))
|
||||
@@ -269,13 +269,13 @@ var packages = module.exports = {
|
||||
// a package object.
|
||||
list: function () {
|
||||
var ret = {};
|
||||
|
||||
|
||||
_.each(files.get_package_dirs(), function(dir) {
|
||||
_.each(fs.readdirSync(dir), function (name) {
|
||||
// skip .meteor directory
|
||||
if (fs.existsSync(path.join(dir, name, 'package.js')))
|
||||
ret[name] = packages.get(name);
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var _ = require(path.join(__dirname, 'third', 'underscore.js'));
|
||||
var _ = require('underscore');
|
||||
|
||||
var project = module.exports = {
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,8 @@
|
||||
exports.CURRENT_VERSION = "0.5.2";
|
||||
// During automated QA of the updater, modify this file to set testingUpdater to
|
||||
// true. This will make it act as if it is at version 0.1.0 and use test URLs
|
||||
// for update checks.
|
||||
var testingUpdater = false;
|
||||
exports.CURRENT_VERSION = testingUpdater ? "0.1.0" : "0.5.3";
|
||||
|
||||
var fs = require("fs");
|
||||
var http = require("http");
|
||||
@@ -8,11 +12,10 @@ var semver = require("semver");
|
||||
|
||||
var files = require(path.join(__dirname, 'files.js'));
|
||||
|
||||
var manifest_options = {
|
||||
/* uncomment for testing
|
||||
var manifest_options = testingUpdater ? {
|
||||
host: 's3.amazonaws.com',
|
||||
path: '/com.meteor.static/test/update/manifest.json'
|
||||
*/
|
||||
} : {
|
||||
host: 'update.meteor.com',
|
||||
path: '/manifest.json'
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ var request = require('request');
|
||||
var qs = require('querystring');
|
||||
var path = require('path');
|
||||
var files = require(path.join(__dirname, '..', 'lib', 'files.js'));
|
||||
var _ = require(path.join(__dirname, '..', 'lib', 'third', 'underscore.js'));
|
||||
var _ = require('underscore');
|
||||
var keypress = require('keypress');
|
||||
var child_process = require('child_process');
|
||||
|
||||
@@ -33,10 +33,15 @@ if (process.env.EMACS == "t") {
|
||||
// interactively prompt for here.
|
||||
|
||||
var meteor_rpc = function (rpc_name, method, site, query_params, callback) {
|
||||
var url = "https://" + DEPLOY_HOSTNAME + '/' + rpc_name + '/' + site;
|
||||
var url;
|
||||
if (DEPLOY_HOSTNAME.indexOf("http://") === 0)
|
||||
url = DEPLOY_HOSTNAME + '/' + rpc_name + '/' + site;
|
||||
else
|
||||
url = "https://" + DEPLOY_HOSTNAME + '/' + rpc_name + '/' + site;
|
||||
|
||||
if (!_.isEmpty(query_params))
|
||||
if (!_.isEmpty(query_params)) {
|
||||
url += '?' + qs.stringify(query_params);
|
||||
}
|
||||
|
||||
var r = request({method: method, url: url}, function (error, response, body) {
|
||||
if (error || ((response.statusCode !== 200)
|
||||
@@ -51,26 +56,39 @@ var meteor_rpc = function (rpc_name, method, site, query_params, callback) {
|
||||
};
|
||||
|
||||
var deploy_app = function (url, app_dir, opt_debug, opt_tests,
|
||||
opt_set_password) {
|
||||
opt_set_password, settings) {
|
||||
var parsed_url = parse_url(url);
|
||||
|
||||
// a bit contorted here to make sure we ask for the password before
|
||||
// launching the slow bundle process.
|
||||
|
||||
with_password(parsed_url.hostname, function (password) {
|
||||
var deployOptions = {
|
||||
site: parsed_url.hostname,
|
||||
appDir: app_dir,
|
||||
debug: opt_debug,
|
||||
tests: opt_tests,
|
||||
password: password,
|
||||
settings: settings
|
||||
};
|
||||
if (opt_set_password)
|
||||
get_new_password(function (set_password) {
|
||||
bundle_and_deploy(parsed_url.hostname, app_dir, opt_debug, opt_tests,
|
||||
password, set_password);
|
||||
deployOptions.setPassword = set_password;
|
||||
bundle_and_deploy(deployOptions);
|
||||
});
|
||||
else
|
||||
bundle_and_deploy(parsed_url.hostname, app_dir, opt_debug, opt_tests,
|
||||
password);
|
||||
bundle_and_deploy(deployOptions);
|
||||
});
|
||||
};
|
||||
|
||||
var bundle_and_deploy = function (site, app_dir, opt_debug, opt_tests,
|
||||
password, set_password) {
|
||||
var bundle_and_deploy = function (options) {
|
||||
var site = options.site;
|
||||
var app_dir = options.appDir;
|
||||
var opt_debug = options.debug;
|
||||
var opt_tests = options.tests;
|
||||
var password = options.password;
|
||||
var set_password = options.setPassword;
|
||||
var settings = options.settings;
|
||||
var build_dir = path.join(app_dir, '.meteor', 'local', 'build_tar');
|
||||
var bundle_path = path.join(build_dir, 'bundle');
|
||||
var bundle_opts = { skip_dev_bundle: true, no_minify: !!opt_debug,
|
||||
@@ -90,14 +108,17 @@ var bundle_and_deploy = function (site, app_dir, opt_debug, opt_tests,
|
||||
|
||||
process.stdout.write('uploading ... ');
|
||||
|
||||
var opts = {};
|
||||
if (password) opts.password = password;
|
||||
if (set_password) opts.set_password = set_password;
|
||||
var rpcOptions = {};
|
||||
if (password) rpcOptions.password = password;
|
||||
if (set_password) rpcOptions.set_password = set_password;
|
||||
|
||||
// When it hits the wire, all these opts will be URL-encoded.
|
||||
if (settings !== undefined) rpcOptions.settings = settings;
|
||||
|
||||
var tar = child_process.spawn(
|
||||
'tar', ['czf', '-', 'bundle'], {cwd: build_dir});
|
||||
|
||||
var rpc = meteor_rpc('deploy', 'POST', site, opts, function (err, body) {
|
||||
var rpc = meteor_rpc('deploy', 'POST', site, rpcOptions, function (err, body) {
|
||||
if (err) {
|
||||
var errorMessage = (body || ("Connection error (" + err.message + ")"));
|
||||
process.stderr.write("\nError deploying application: " + errorMessage + "\n");
|
||||
@@ -105,15 +126,34 @@ var bundle_and_deploy = function (site, app_dir, opt_debug, opt_tests,
|
||||
}
|
||||
|
||||
process.stdout.write('done.\n');
|
||||
process.stdout.write('Now serving at ' + site + '\n');
|
||||
|
||||
var hostname = null;
|
||||
var response = null;
|
||||
try {
|
||||
response = JSON.parse(body);
|
||||
} catch (e) {
|
||||
// ... leave null
|
||||
}
|
||||
if (response && response.url) {
|
||||
var url = require('url').parse(response.url);
|
||||
if (url && url.hostname)
|
||||
hostname = url.hostname;
|
||||
}
|
||||
|
||||
if (!hostname) {
|
||||
process.stdout.write('Error receiving hostname from deploy server.\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.stdout.write('Now serving at ' + hostname + '\n');
|
||||
files.rm_recursive(build_dir);
|
||||
|
||||
if (!site.match('meteor.com')) {
|
||||
|
||||
if (hostname && !hostname.match(/meteor\.com$/)) {
|
||||
var dns = require('dns');
|
||||
dns.resolve(site, 'CNAME', function (err, cnames) {
|
||||
dns.resolve(hostname, 'CNAME', function (err, cnames) {
|
||||
if (err || cnames[0] !== 'origin.meteor.com') {
|
||||
dns.resolve(site, 'A', function (err, addresses) {
|
||||
dns.resolve(hostname, 'A', function (err, addresses) {
|
||||
if (err || addresses[0] !== '107.22.210.133') {
|
||||
process.stdout.write('-------------\n');
|
||||
process.stdout.write("You've deployed to a custom domain.\n");
|
||||
@@ -207,9 +247,6 @@ var parse_url = function (url) {
|
||||
|
||||
delete parsed.host; // we use hostname
|
||||
|
||||
if (parsed.hostname && !parsed.hostname.match(/\./))
|
||||
parsed.hostname += '.meteor.com';
|
||||
|
||||
if (!parsed.hostname) {
|
||||
process.stdout.write(
|
||||
"Please specify a domain to connect to, such as www.example.com or\n" +
|
||||
|
||||
1232
app/meteor/meteor.js
1232
app/meteor/meteor.js
File diff suppressed because it is too large
Load Diff
@@ -2,13 +2,13 @@ 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.5.2";
|
||||
var VERSION = "0.5.3";
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var files = require(path.join(__dirname, "..", "lib", "files.js"));
|
||||
|
||||
var _ = require(path.join(__dirname, "..", "lib", "third", "underscore.js"));
|
||||
var _ = require('underscore');
|
||||
|
||||
var topDir = files.get_dev_bundle();
|
||||
var changelogPath = path.join(topDir, 'History.md');
|
||||
|
||||
@@ -10,10 +10,12 @@ var files = require(path.join(__dirname, '..', 'lib', 'files.js'));
|
||||
var updater = require(path.join(__dirname, '..', 'lib', 'updater.js'));
|
||||
var bundler = require(path.join(__dirname, '..', 'lib', 'bundler.js'));
|
||||
var mongo_runner = require(path.join(__dirname, '..', 'lib', 'mongo_runner.js'));
|
||||
var mongoExitCodes = require(path.join(__dirname, '..', 'lib', 'mongo_exit_codes.js'));
|
||||
|
||||
var _ = require(path.join(__dirname, '..', 'lib', 'third', 'underscore.js'));
|
||||
var _ = require('underscore');
|
||||
|
||||
////////// Globals //////////
|
||||
//XXX: Refactor to not have globals anymore?
|
||||
|
||||
// list of log objects from the child process.
|
||||
var server_log = [];
|
||||
@@ -23,18 +25,42 @@ var Status = {
|
||||
crashing: false, // does server crash whenever we start it?
|
||||
listening: false, // do we expect the server to be listening now.
|
||||
counter: 0, // how many crashes in rapid succession
|
||||
code: 0, // exit code last returned
|
||||
shouldRestart: true, // true if we should be restarting the server
|
||||
shuttingDown: false, // true if we're on the way to shutting down the server
|
||||
|
||||
exitNow: function () {
|
||||
var self = this;
|
||||
log_to_clients({'exit': "Your application is exiting."});
|
||||
self.shuttingDown = true;
|
||||
|
||||
self.mongoHandle && self.mongoHandle.stop(function (err) {
|
||||
if (err)
|
||||
process.stdout.write(err.reason + "\n");
|
||||
process.exit(self.code);
|
||||
});
|
||||
},
|
||||
reset: function () {
|
||||
this.crashing = false;
|
||||
this.counter = 0;
|
||||
},
|
||||
|
||||
hard_crashed: function () {
|
||||
var self = this;
|
||||
if (!self.shouldRestart) {
|
||||
self.exitNow();
|
||||
return;
|
||||
}
|
||||
log_to_clients({'exit': "Your application is crashing. Waiting for file change."});
|
||||
this.crashing = true;
|
||||
},
|
||||
|
||||
soft_crashed: function () {
|
||||
var self = this;
|
||||
if (!self.shouldRestart) {
|
||||
self.exitNow();
|
||||
return;
|
||||
}
|
||||
if (this.counter === 0)
|
||||
setTimeout(function () {
|
||||
this.counter = 0;
|
||||
@@ -48,6 +74,18 @@ var Status = {
|
||||
}
|
||||
};
|
||||
|
||||
// Parse out s as if it were a bash command line.
|
||||
var bashParse = function (s) {
|
||||
if (s.search("\"") !== -1 || s.search("'") !== -1) {
|
||||
throw new Error("Meteor cannot currently handle quoted NODE_OPTIONS");
|
||||
}
|
||||
return _.without(s.split(/\s+/), '');
|
||||
};
|
||||
|
||||
var getNodeOptionsFromEnvironment = function () {
|
||||
return bashParse(process.env.NODE_OPTIONS || "");
|
||||
};
|
||||
|
||||
// List of queued requests. Each item in the list is a function to run
|
||||
// when the inner app is ready to receive connections.
|
||||
var request_queue = [];
|
||||
@@ -167,19 +205,48 @@ var log_to_clients = function (msg) {
|
||||
};
|
||||
|
||||
////////// Launch server process //////////
|
||||
// Takes options:
|
||||
// bundlePath
|
||||
// outerPort
|
||||
// innerPort
|
||||
// mongoURL
|
||||
// onExit
|
||||
// [onListen]
|
||||
// [nodeOptions]
|
||||
// [runOnce]: boolean; default false; if true doesn't ever try to restart, and
|
||||
// forwards server exit code.
|
||||
// [settingsFile]
|
||||
|
||||
var start_server = function (bundle_path, outer_port, inner_port, mongo_url,
|
||||
on_exit_callback, on_listen_callback) {
|
||||
var start_server = function (options) {
|
||||
// environment
|
||||
options = _.extend({
|
||||
runOnce: false,
|
||||
nodeOptions: []
|
||||
}, options);
|
||||
if (options.runOnce) {
|
||||
Status.shouldRestart = false;
|
||||
}
|
||||
|
||||
var env = {};
|
||||
for (var k in process.env)
|
||||
env[k] = process.env[k];
|
||||
env.PORT = inner_port;
|
||||
env.MONGO_URL = mongo_url;
|
||||
env.ROOT_URL = env.ROOT_URL || ('http://localhost:' + outer_port);
|
||||
|
||||
env.PORT = options.innerPort;
|
||||
env.MONGO_URL = options.mongoURL;
|
||||
env.ROOT_URL = env.ROOT_URL || ('http://localhost:' + options.outerPort);
|
||||
if (options.settingsFile) {
|
||||
// Re-read the settings file each time we call start_server.
|
||||
var settings = exports.getSettings(options.settingsFile);
|
||||
if (settings)
|
||||
env.METEOR_SETTINGS = settings;
|
||||
}
|
||||
|
||||
var nodeOptions = _.clone(options.nodeOptions);
|
||||
nodeOptions.push(path.join(options.bundlePath, 'main.js'));
|
||||
nodeOptions.push('--keepalive');
|
||||
|
||||
var proc = spawn(process.execPath,
|
||||
[path.join(bundle_path, 'main.js'), '--keepalive'],
|
||||
nodeOptions,
|
||||
{env: env});
|
||||
|
||||
// XXX deal with test server logging differently?!
|
||||
@@ -192,7 +259,7 @@ var start_server = function (bundle_path, outer_port, inner_port, mongo_url,
|
||||
// string must match server.js
|
||||
data = data.replace(/^LISTENING\s*(?:\n|$)/m, '');
|
||||
if (data.length != originalLength)
|
||||
on_listen_callback && on_listen_callback();
|
||||
options.onListen && options.onListen();
|
||||
if (data)
|
||||
log_to_clients({stdout: data});
|
||||
});
|
||||
@@ -209,7 +276,7 @@ var start_server = function (bundle_path, outer_port, inner_port, mongo_url,
|
||||
log_to_clients({'exit': 'Exited with code: ' + code});
|
||||
}
|
||||
|
||||
on_exit_callback();
|
||||
options.onExit(code);
|
||||
});
|
||||
|
||||
// this happens sometimes when we write a keepalive after the app is
|
||||
@@ -245,8 +312,11 @@ var kill_server = function (handle) {
|
||||
////////// Watching dependencies //////////
|
||||
|
||||
// deps is the data from dependencies.json in the bundle
|
||||
// app_dir is the root of the app
|
||||
// relativeFiles are any other files to watch, relative to the current
|
||||
// directory (eg, the --settings file)
|
||||
// on_change is only fired once
|
||||
var DependencyWatcher = function (deps, app_dir, on_change) {
|
||||
var DependencyWatcher = function (deps, app_dir, relativeFiles, on_change) {
|
||||
var self = this;
|
||||
|
||||
self.app_dir = app_dir;
|
||||
@@ -279,6 +349,10 @@ var DependencyWatcher = function (deps, app_dir, on_change) {
|
||||
});
|
||||
};
|
||||
|
||||
_.each(relativeFiles, function (file) {
|
||||
self.specific_files[file] = true;
|
||||
});
|
||||
|
||||
// Things that are never interesting.
|
||||
self.exclude_patterns = _.map((deps.exclude || []), function (pattern) {
|
||||
return new RegExp(pattern);
|
||||
@@ -310,7 +384,7 @@ _.extend(DependencyWatcher.prototype, {
|
||||
return false;
|
||||
|
||||
try {
|
||||
var stats = fs.lstatSync(filepath)
|
||||
var stats = fs.lstatSync(filepath);
|
||||
} catch (e) {
|
||||
// doesn't exist -- leave stats undefined
|
||||
}
|
||||
@@ -444,12 +518,34 @@ var start_update_checks = function () {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Also used by "meteor deploy" in meteor.js.
|
||||
|
||||
exports.getSettings = function (filename) {
|
||||
var str;
|
||||
try {
|
||||
str = fs.readFileSync(filename, "utf8");
|
||||
} catch (e) {
|
||||
throw new Error("Could not find settings file " + filename);
|
||||
}
|
||||
if (str.length > 0x10000) {
|
||||
throw new Error("Settings file must be less than 64 KB long");
|
||||
}
|
||||
// Ensure that the string is parseable in JSON, but there's
|
||||
// no reason to use the object value of it yet.
|
||||
if (str.match(/\S/)) {
|
||||
JSON.parse(str);
|
||||
return str;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
// XXX leave a pidfile and check if we are already running
|
||||
|
||||
// This function never returns and will call process.exit() if it
|
||||
// can't continue. If you change this, remember to call
|
||||
// watcher.destroy() as appropriate.
|
||||
exports.run = function (app_dir, bundle_opts, port) {
|
||||
exports.run = function (app_dir, bundle_opts, port, once, settingsFile) {
|
||||
var outer_port = port || 3000;
|
||||
var inner_port = outer_port + 1;
|
||||
var mongo_port = outer_port + 2;
|
||||
@@ -462,6 +558,7 @@ exports.run = function (app_dir, bundle_opts, port) {
|
||||
var test_mongo_url = "mongodb://127.0.0.1:" + mongo_port + "/meteor_test";
|
||||
|
||||
var test_bundle_opts;
|
||||
|
||||
if (files.is_app_dir(app_dir)) {
|
||||
// If we're an app, make separate test_bundle_opts to trigger a
|
||||
// separate runner.
|
||||
@@ -483,11 +580,19 @@ exports.run = function (app_dir, bundle_opts, port) {
|
||||
var watcher;
|
||||
|
||||
var start_watching = function () {
|
||||
if (!Status.shouldRestart)
|
||||
return;
|
||||
if (deps_info) {
|
||||
if (watcher)
|
||||
watcher.destroy();
|
||||
|
||||
watcher = new DependencyWatcher(deps_info, app_dir, function () {
|
||||
var relativeFiles;
|
||||
if (settingsFile) {
|
||||
relativeFiles = [settingsFile];
|
||||
}
|
||||
|
||||
watcher = new DependencyWatcher(deps_info, app_dir, relativeFiles,
|
||||
function () {
|
||||
if (Status.crashing)
|
||||
log_to_clients({'system': "=> Modified -- restarting."});
|
||||
Status.reset();
|
||||
@@ -546,21 +651,30 @@ exports.run = function (app_dir, bundle_opts, port) {
|
||||
|
||||
start_watching();
|
||||
Status.running = true;
|
||||
server_handle = start_server(
|
||||
bundle_path, outer_port, inner_port, mongo_url,
|
||||
function () {
|
||||
server_handle = start_server({
|
||||
bundlePath: bundle_path,
|
||||
outerPort: outer_port,
|
||||
innerPort: inner_port,
|
||||
mongoURL: mongo_url,
|
||||
onExit: function (code) {
|
||||
// on server exit
|
||||
Status.running = false;
|
||||
Status.listening = false;
|
||||
Status.code = code;
|
||||
Status.soft_crashed();
|
||||
if (!Status.crashing)
|
||||
restart_server();
|
||||
}, function () {
|
||||
},
|
||||
onListen: function () {
|
||||
// on listen
|
||||
Status.listening = true;
|
||||
_.each(request_queue, function (f) { f(); });
|
||||
request_queue = [];
|
||||
});
|
||||
},
|
||||
nodeOptions: getNodeOptionsFromEnvironment(),
|
||||
runOnce: once,
|
||||
settingsFile: settingsFile
|
||||
});
|
||||
|
||||
|
||||
// launch test bundle and server if needed.
|
||||
@@ -574,12 +688,16 @@ exports.run = function (app_dir, bundle_opts, port) {
|
||||
});
|
||||
files.rm_recursive(test_bundle_path);
|
||||
} else {
|
||||
test_server_handle = start_server(
|
||||
test_bundle_path, test_port, test_mongo_url, function () {
|
||||
test_server_handle = start_server({
|
||||
bundlePath: test_bundle_path,
|
||||
outerPort: test_port,
|
||||
innerPort: test_port,
|
||||
mongoURL: test_mongo_url,
|
||||
onExit: function (code) {
|
||||
// No restarting or crash loop prevention on the test server
|
||||
// for now. We'll see how annoying it is.
|
||||
log_to_clients({'system': "Test server crashed."});
|
||||
});
|
||||
}});
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -590,7 +708,7 @@ exports.run = function (app_dir, bundle_opts, port) {
|
||||
var mongo_startup_print_timer;
|
||||
var process_startup_printer;
|
||||
var launch = function () {
|
||||
mongo_runner.launch_mongo(
|
||||
Status.mongoHandle = mongo_runner.launch_mongo(
|
||||
app_dir,
|
||||
mongo_port,
|
||||
function () { // On Mongo startup complete
|
||||
@@ -607,13 +725,22 @@ exports.run = function (app_dir, bundle_opts, port) {
|
||||
restart_server();
|
||||
},
|
||||
function (code, signal) { // On Mongo dead
|
||||
if (Status.shuttingDown) {
|
||||
return;
|
||||
}
|
||||
console.log("Unexpected mongo exit code " + code + ". Restarting.");
|
||||
|
||||
// if mongo dies 3 times with less than 5 seconds between each,
|
||||
// declare it failed and die.
|
||||
mongo_err_count += 1;
|
||||
if (mongo_err_count >= 3) {
|
||||
console.log("Can't start mongod. Check for other processes listening on port " + mongo_port + " or other meteors running in the same project.");
|
||||
var explanation = mongoExitCodes.Codes[code];
|
||||
console.log("Can't start mongod\n");
|
||||
if (explanation)
|
||||
console.log(explanation.longText);
|
||||
if (explanation === mongoExitCodes.EXIT_NET_ERROR)
|
||||
console.log("\nCheck for other processes listening on port " + mongo_port +
|
||||
"\nor other meteors running in the same project.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (mongo_err_timer)
|
||||
|
||||
@@ -10,7 +10,7 @@ var ProgressBar = require('progress');
|
||||
var updater = require(path.join(__dirname, "..", "lib", "updater.js"));
|
||||
var files = require(path.join(__dirname, "..", "lib", "files.js"));
|
||||
|
||||
var _ = require(path.join(__dirname, '..', 'lib', 'third', 'underscore.js'));
|
||||
var _ = require('underscore');
|
||||
|
||||
// refuse to update if we're in a git checkout.
|
||||
if (files.in_checkout()) {
|
||||
|
||||
@@ -12,9 +12,7 @@ var mime = require('mime');
|
||||
var handlebars = require('handlebars');
|
||||
var useragent = require('useragent');
|
||||
|
||||
// this is a copy of underscore that will be shipped just for use by
|
||||
// this file, server.js.
|
||||
var _ = require(path.join(__dirname, 'underscore.js'));
|
||||
var _ = require('underscore');
|
||||
|
||||
// This code is duplicated in app/server/server.js.
|
||||
var MIN_NODE_VERSION = 'v0.8.11';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,8 @@ put on the screen.
|
||||
|
||||
{{> api_box absoluteUrl}}
|
||||
|
||||
{{> api_box settings}}
|
||||
|
||||
<h2 id="publishandsubscribe"><span>Publish and subscribe</span></h2>
|
||||
|
||||
These functions control how Meteor servers publish sets of records and
|
||||
@@ -526,11 +528,9 @@ In this release, Minimongo has some limitations:
|
||||
* `$elemMatch` is not supported in selectors.
|
||||
* `$pull` in modifiers can only accept certain kinds
|
||||
of selectors.
|
||||
* In selectors, dot notation and ordinal indexing may not work correctly.
|
||||
* In selectors, dot notation may not work correctly.
|
||||
* `$` to denote the matched array position is not
|
||||
supported in modifier.
|
||||
* Sort does not support subkeys (you can sort on `a`,
|
||||
but not `a.b`).
|
||||
* `findAndModify`, upsert, aggregate functions, and
|
||||
map/reduce aren't supported.
|
||||
* The supported types are String, Number, Boolean, Array,
|
||||
@@ -1031,8 +1031,8 @@ it's up to you to be sure.
|
||||
{{#api_box_inline fieldspecifiers}}
|
||||
|
||||
On the server, queries can specify a particular set of fields to include
|
||||
or exclude from the result object. Minimongo ignores the field
|
||||
specifier.
|
||||
or exclude from the result object. (The field specifier is currently
|
||||
ignored on the client.)
|
||||
|
||||
To exclude certain fields from the result objects, the field specifier
|
||||
is a dictionary whose keys are field names and whose values are `0`.
|
||||
@@ -1337,6 +1337,9 @@ Example:
|
||||
facebook: ['user_likes'],
|
||||
github: ['user', 'repo']
|
||||
},
|
||||
requestOfflineToken: {
|
||||
google: true
|
||||
},
|
||||
passwordSignupFields: 'USERNAME_AND_OPTIONAL_EMAIL'
|
||||
});
|
||||
|
||||
@@ -2449,6 +2452,35 @@ If `MAIL_URL` is not set (eg, when running your application locally),
|
||||
You must provide the `from` option and at least one of `to`, `cc`, and `bcc`;
|
||||
all other options are optional.
|
||||
|
||||
`Email.send` only works on the server. Here is an example of how a
|
||||
client could use a server method call to send an email. (In an actual
|
||||
application, you'd need to be careful to limit the emails that a
|
||||
client could send, to prevent your server from being used as a relay
|
||||
by spammers.)
|
||||
|
||||
// In your server code: define a method that the client can call
|
||||
Meteor.methods({
|
||||
sendEmail: function (to, from, subject, text) {
|
||||
// Let other method calls from the same client start running,
|
||||
// without waiting for the email sending to complete.
|
||||
this.unblock();
|
||||
|
||||
Email.send({
|
||||
to: to,
|
||||
from: from,
|
||||
subject: subject,
|
||||
text: text
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// In your client code: asynchronously send an email
|
||||
Meteor.call('sendEmail',
|
||||
'alice@example.com',
|
||||
'bob@example.com',
|
||||
'Hello from Meteor!',
|
||||
'This is a test of Email.send.');
|
||||
|
||||
{{/better_markdown}}
|
||||
</template>
|
||||
|
||||
|
||||
@@ -54,6 +54,17 @@ Template.api.absoluteUrl = {
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.settings = {
|
||||
id: "meteor_settings",
|
||||
name: "Meteor.settings",
|
||||
locus: "Server",
|
||||
descr: ["`Meteor.settings` contains any deployment-specific options that were " +
|
||||
"provided using the `--settings` option for `meteor run` or `meteor deploy`. " +
|
||||
"If you provide the `--settings` option, `Meteor.settings` will be the " +
|
||||
"JSON object in the file you specify. Otherwise, `Meteor.settings` will " +
|
||||
"be an empty object."]
|
||||
};
|
||||
|
||||
Template.api.publish = {
|
||||
id: "meteor_publish",
|
||||
name: "Meteor.publish(name, func)",
|
||||
@@ -363,10 +374,10 @@ Template.api.find = {
|
||||
{name: "fields",
|
||||
type: "Object: field specifier",
|
||||
type_link: "fieldspecifiers",
|
||||
descr: "Dictionary of fields to return or exclude."},
|
||||
descr: "(Server only) Dictionary of fields to return or exclude."},
|
||||
{name: "reactive",
|
||||
type: "Boolean",
|
||||
descr: "Default `true`; pass `false` to disable reactivity"}
|
||||
descr: "(Client only) Default `true`; pass `false` to disable reactivity"}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -392,10 +403,10 @@ Template.api.findone = {
|
||||
{name: "fields",
|
||||
type: "Object: field specifier",
|
||||
type_link: "fieldspecifiers",
|
||||
descr: "Dictionary of fields to return or exclude."},
|
||||
descr: "(Server only) Dictionary of fields to return or exclude."},
|
||||
{name: "reactive",
|
||||
type: "Boolean",
|
||||
descr: "Default true; pass false to disable reactivity"}
|
||||
descr: "(Client only) Default true; pass false to disable reactivity"}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -791,6 +802,11 @@ Template.api.loginWithExternalService = {
|
||||
name: "requestPermissions",
|
||||
type: "Array of Strings",
|
||||
descr: "A list of permissions to request from the user."
|
||||
},
|
||||
{
|
||||
name: "requestOfflineToken",
|
||||
type: "Boolean",
|
||||
descr: "If true, asks the user for permission to act on their behalf when offline. This stores an additional offline token in the `services` field of the user document. Currently only supported with Google."
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -827,6 +843,11 @@ Template.api.accounts_ui_config = {
|
||||
type: "Object",
|
||||
descr: "Which [permissions](#requestpermissions) to request from the user for each external service."
|
||||
},
|
||||
{
|
||||
name: "requestOfflineToken",
|
||||
type: "Object",
|
||||
descr: "To ask the user for permission to act on their behalf when offline, map the relevant external service to `true`. Currently only supported with Google. See [Meteor.loginWithExternalService](#meteor_loginwithexternalservice) for more details."
|
||||
},
|
||||
{
|
||||
name: "passwordSignupFields",
|
||||
type: "String",
|
||||
|
||||
@@ -29,6 +29,9 @@ required.
|
||||
This is the default command. Simply running `meteor` is the
|
||||
same as `meteor run`.
|
||||
|
||||
To pass additional options to Node.js use the `NODE_OPTIONS` environment variable.
|
||||
For example: `NODE_OPTIONS='--debug'` or `NODE_OPTIONS='--debug-brk'`
|
||||
|
||||
Run `meteor help run` to see the full list of options.
|
||||
|
||||
|
||||
@@ -90,6 +93,17 @@ domain like myapp.com, you'll need a DNS A record, matching the IP
|
||||
address of origin.meteor.com.
|
||||
{{/warning}}
|
||||
|
||||
|
||||
|
||||
You can add information specific to a particular deployment of your application
|
||||
by using the `--settings` option. The argument to `--settings` is a file
|
||||
containing any JSON string. The object in your settings file will appear on the
|
||||
server side of your application in [`Meteor.settings`](#meteor_settings).
|
||||
|
||||
Settings are persistent. When you redeploy your app, the old value will be
|
||||
preserved unless you explicitly pass new settings using the `--settings` option.
|
||||
To unset `Meteor.settings`, pass an empty settings file.
|
||||
|
||||
<h3 id="meteorlogs">meteor logs <i>site</i></h3>
|
||||
|
||||
Retrieves the server logs for the named Meteor application.
|
||||
|
||||
@@ -43,6 +43,7 @@ pre {
|
||||
code {
|
||||
font-family: monospace;
|
||||
font-size: 1.1em;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
ul {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</div>
|
||||
<div id="main">
|
||||
<div id="top"></div>
|
||||
<h1 class="main-headline">Meteor 0.5.2</h1>
|
||||
<h1 class="main-headline">Meteor 0.5.3</h1>
|
||||
{{> introduction }}
|
||||
{{> concepts }}
|
||||
{{> api }}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
METEOR_VERSION = "0.5.2";
|
||||
METEOR_VERSION = "0.5.3";
|
||||
|
||||
Meteor.startup(function () {
|
||||
// XXX this is broken by the new multi-page layout. Also, it was
|
||||
@@ -94,7 +94,8 @@ var toc = [
|
||||
"Meteor.isClient",
|
||||
"Meteor.isServer",
|
||||
"Meteor.startup",
|
||||
"Meteor.absoluteUrl"
|
||||
"Meteor.absoluteUrl",
|
||||
"Meteor.settings"
|
||||
],
|
||||
|
||||
"Publish and subscribe", [
|
||||
|
||||
@@ -44,7 +44,7 @@ input.chosen {
|
||||
|
||||
.map {
|
||||
position: relative;
|
||||
background-image: url('/soma.jpeg');
|
||||
background-image: url('/soma.png');
|
||||
background-position: -20px -20px;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
|
||||
|
Before Width: | Height: | Size: 288 KiB After Width: | Height: | Size: 288 KiB |
@@ -7,7 +7,7 @@
|
||||
</body>
|
||||
|
||||
<template name="radio">
|
||||
<span class="radio"><input id="{{key}}:{{value}}" {{maybeChecked}} type="radio" name="{{key}}" value="{{value}}" />{{! no whitespace}}<label for="{{key}}:{{value}}">{{label}}</label></span>
|
||||
<span class="radio"><input id="{{key}}:{{value}}" {{{maybeChecked}}} type="radio" name="{{key}}" value="{{value}}" />{{! no whitespace}}<label for="{{key}}:{{value}}">{{label}}</label></span>
|
||||
</template>
|
||||
|
||||
<template name="button">
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
// Parameters for simulation:
|
||||
//
|
||||
// Each document is randomly placed in a collection, with a random
|
||||
// 'bucket' field. Clients sub to 1 bucket in each collection.
|
||||
//
|
||||
// - numCollections
|
||||
// how many collections to spread the documents over
|
||||
// - numBuckets
|
||||
// number of buckets per collection.
|
||||
//
|
||||
// - initialDocuments: Inital documents added by the server. Probably
|
||||
// not usefully combined with maxAgeSeconds
|
||||
//
|
||||
// - maxAgeSeconds: How long to leave documents in the database. This,
|
||||
// combined with all the various rates, determines the steady state
|
||||
// database size. In seconds. falsy to disable.
|
||||
//
|
||||
// Per-client action rates:
|
||||
// - insertsPerSecond
|
||||
// - updatesPerSecond
|
||||
// - removesPerSecond
|
||||
//
|
||||
// - documentSize: bytes of randomness per document.
|
||||
// // XXX make this a random distribution?
|
||||
// - documentNumFields: how many fields of randomness per document.
|
||||
//
|
||||
// XXX also max documents? (count and remove N)
|
||||
|
||||
SCENARIOS = {
|
||||
|
||||
default: {
|
||||
numCollections: 1,
|
||||
numBuckets: 3,
|
||||
initialDocuments: 1,
|
||||
maxAgeSeconds: 60,
|
||||
insertsPerSecond: 1,
|
||||
updatesPerSecond: 1,
|
||||
removesPerSecond: 0.1,
|
||||
documentSize: 1024,
|
||||
documentNumFields: 8
|
||||
},
|
||||
|
||||
nodata: {
|
||||
numCollections: 1,
|
||||
numBuckets: 1,
|
||||
initialDocuments: 0
|
||||
},
|
||||
|
||||
bigdata: {
|
||||
numCollections: 1,
|
||||
numBuckets: 1,
|
||||
initialDocuments: 1024,
|
||||
updatesPerSecond: 1,
|
||||
documentSize: 10240,
|
||||
documentNumFields: 16
|
||||
}
|
||||
|
||||
};
|
||||
@@ -2,14 +2,14 @@
|
||||
// to change this. See 'benchmark-scenarios.js' for the list of
|
||||
// scenarios.
|
||||
|
||||
var PARAMS = {};
|
||||
if (Meteor.isServer) {
|
||||
if (process.env.SCENARIO)
|
||||
__meteor_runtime_config__.SCENARIO = process.env.SCENARIO;
|
||||
else
|
||||
__meteor_runtime_config__.SCENARIO = 'default';
|
||||
if (!Meteor.settings.params)
|
||||
throw new Error("Must set scenario with Meteor.settings");
|
||||
__meteor_runtime_config__.PARAMS = PARAMS = Meteor.settings.params;
|
||||
} else {
|
||||
PARAMS = __meteor_runtime_config__.PARAMS;
|
||||
}
|
||||
var PARAMS = SCENARIOS[__meteor_runtime_config__.SCENARIO];
|
||||
|
||||
|
||||
//////////////////////////////
|
||||
// Helper Functions
|
||||
|
||||
@@ -12,13 +12,15 @@ PROJDIR=`dirname $0`
|
||||
cd "$PROJDIR"
|
||||
PROJDIR=`pwd`
|
||||
|
||||
SCENARIO="${1:-default}"
|
||||
|
||||
# clean up from previous runs
|
||||
# XXX this is gross!
|
||||
pkill -f "$PROJDIR/.meteor/local/db" || true
|
||||
../../../meteor reset || true
|
||||
|
||||
# start the benchmark app
|
||||
../../../meteor --production --port 9000 &
|
||||
../../../meteor --production --settings "scenarios/${SCENARIO}.json" --port 9000 &
|
||||
OUTER_PID=$!
|
||||
|
||||
|
||||
|
||||
27
examples/unfinished/benchmark/scenarios/README.md
Normal file
27
examples/unfinished/benchmark/scenarios/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
Parameters for simulation:
|
||||
|
||||
Each document is randomly placed in a collection, with a random
|
||||
'bucket' field. Clients sub to 1 bucket in each collection.
|
||||
|
||||
- numCollections
|
||||
how many collections to spread the documents over
|
||||
- numBuckets
|
||||
number of buckets per collection.
|
||||
|
||||
- initialDocuments: Inital documents added by the server. Probably
|
||||
not usefully combined with maxAgeSeconds
|
||||
|
||||
- maxAgeSeconds: How long to leave documents in the database. This,
|
||||
combined with all the various rates, determines the steady state
|
||||
database size. In seconds. falsy to disable.
|
||||
|
||||
Per-client action rates:
|
||||
- insertsPerSecond
|
||||
- updatesPerSecond
|
||||
- removesPerSecond
|
||||
|
||||
- documentSize: bytes of randomness per document.
|
||||
// XXX make this a random distribution?
|
||||
- documentNumFields: how many fields of randomness per document.
|
||||
|
||||
XXX also max documents? (count and remove N)
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"params": {
|
||||
"numCollections": 1,
|
||||
"numBuckets": 1,
|
||||
"initialDocuments": 1024,
|
||||
"documentSize": 2048,
|
||||
"documentNumFields": 64
|
||||
}
|
||||
}
|
||||
10
examples/unfinished/benchmark/scenarios/bigdata-updates.json
Normal file
10
examples/unfinished/benchmark/scenarios/bigdata-updates.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"params": {
|
||||
"numCollections": 1,
|
||||
"numBuckets": 1,
|
||||
"initialDocuments": 1024,
|
||||
"updatesPerSecond": 0.2,
|
||||
"documentSize": 1024,
|
||||
"documentNumFields": 32
|
||||
}
|
||||
}
|
||||
13
examples/unfinished/benchmark/scenarios/default.json
Normal file
13
examples/unfinished/benchmark/scenarios/default.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"params": {
|
||||
"numCollections": 1,
|
||||
"numBuckets": 3,
|
||||
"initialDocuments": 1,
|
||||
"maxAgeSeconds": 60,
|
||||
"insertsPerSecond": 1,
|
||||
"updatesPerSecond": 1,
|
||||
"removesPerSecond": 0.1,
|
||||
"documentSize": 1024,
|
||||
"documentNumFields": 8
|
||||
}
|
||||
}
|
||||
7
examples/unfinished/benchmark/scenarios/nodata.json
Normal file
7
examples/unfinished/benchmark/scenarios/nodata.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"params": {
|
||||
"numCollections": 1,
|
||||
"numBuckets": 1,
|
||||
"initialDocuments": 0
|
||||
}
|
||||
}
|
||||
1
examples/unfinished/jsparse-docs/.meteor/.gitignore
vendored
Normal file
1
examples/unfinished/jsparse-docs/.meteor/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
local
|
||||
6
examples/unfinished/jsparse-docs/.meteor/packages
Normal file
6
examples/unfinished/jsparse-docs/.meteor/packages
Normal file
@@ -0,0 +1,6 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
autopublish
|
||||
42
examples/unfinished/jsparse-docs/jsparse-docs.css
Normal file
42
examples/unfinished/jsparse-docs/jsparse-docs.css
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
p, .topnotes .nodespec { margin: 0.8em; }
|
||||
.topnotes {
|
||||
background: #ffd;
|
||||
margin-bottom: 2em;
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
border: 2px solid #ccc;
|
||||
}
|
||||
|
||||
.str { font-weight: bold; }
|
||||
.token { font-family: monospace; font-size: 110%; font-weight: bold; }
|
||||
.token, .tokentype { background: #ddd; padding: 2px 5px; }
|
||||
.tokentype { font-size: 85%; }
|
||||
.ref { font-style: italic; }
|
||||
.punc { font-size: 140%; }
|
||||
.comma { color: #fff; }
|
||||
.or { padding-left: 3px; padding-right: 3px; }
|
||||
|
||||
.nodespec {
|
||||
font-size: 16px;
|
||||
margin: 1em 0;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.indent {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
.explan {
|
||||
margin-left: 20em;
|
||||
margin-right: 2em;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
#page { max-width: 50em; margin: 0 auto; }
|
||||
|
||||
.spacer { text-align: center; font-size:50%; font-weight: bold; border-top: 1px solid #999;
|
||||
margin-left: 6em; margin-right: 6em;
|
||||
}
|
||||
|
||||
code { font-weight: bold; }
|
||||
279
examples/unfinished/jsparse-docs/jsparse-docs.html
Normal file
279
examples/unfinished/jsparse-docs/jsparse-docs.html
Normal file
@@ -0,0 +1,279 @@
|
||||
<head>
|
||||
<title>jsparse Docs</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="page">
|
||||
{{> page}}
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<template name="page">
|
||||
|
||||
<div class="topnotes">
|
||||
<p>This is the spec for the JavaScript syntax tree returned by <code>jsparse</code>.</p>
|
||||
|
||||
<p>It's notable that <em>every token</em> from the source code appears in order in this syntax tree, which
|
||||
is a Concrete Syntax Tree (CST) rather than an abstract AST.</p>
|
||||
|
||||
{{spacer}}
|
||||
|
||||
<p>The tree consists of <strong>nodes</strong>
|
||||
and <strong>tokens</strong>. A node consists of a name followed
|
||||
by zero or more child nodes or tokens. A token consists of
|
||||
one or more characters from the source code.</p>
|
||||
<p>For example, the following tree, which might have come from parsing <code>1 + 2</code>,
|
||||
contains nodes named <span class="str">"binary"</span>,
|
||||
<span class="str">"number"</span>, and <span class="str">"number"</span>
|
||||
and the tokens <span class="token">1</span>, <span class="token">+</span>,
|
||||
and <span class="token">2</span>:</p>
|
||||
|
||||
<p>{{#nodespec}}["binary", ["number", `1`], `+`, ["number", `2`]]{{/nodespec}}</p>
|
||||
|
||||
{{spacer}}
|
||||
|
||||
<p>The following notation is used throughout this document to give schematic definitions of
|
||||
the different types of nodes.</p>
|
||||
|
||||
<p>An <span class="ref">italicized</span> word refers to a list of one or more possible
|
||||
nodes or tokens that could fill a certain spot. For example, the following is the
|
||||
definition of the <span class="str">"binary"</span> node:</p>
|
||||
|
||||
<p>{{#nodespec}}["binary", expression, binaryOp, expression]{{/nodespec}}</p>
|
||||
|
||||
<p>The <span class="ref">expression</span> spot could be filled by one of over 20 types
|
||||
of expression nodes, and if you look up <span class="ref">binaryOp</span> you'll see
|
||||
it refers to any of a list of binary operator tokens.</p>
|
||||
|
||||
<p>A vertical bar (<span class="punc or">|</span>) separates alternatives, of which exactly
|
||||
one must be present in the tree. An ellipsis (<span class="punc">...</span>) stands
|
||||
for a sequence of <em>zero or more</em> of the preceding item. For example:</p>
|
||||
|
||||
{{#nodespec}}["program", (statement | functionDecl), ...]{{/nodespec}}
|
||||
|
||||
<p>This definition says that a program node contains zero or more items, each of which may be either a
|
||||
<span class="ref">statement</span> or a <span class="ref">functionDecl</span>.</p>
|
||||
|
||||
<p>A question mark (<span class="punct">?</span>) indicates that the previous item
|
||||
(or items enclosed in parentheses) may or may not present.</p>
|
||||
|
||||
<p>Token classes like <span class="tokentype">IDENTIFIER</span> match any token in the class. The <span class="tokentype">BOOLEAN</span>, <span class="tokentype">NUMBER</span>, <span class="tokentype">STRING</span>, and <span class="tokentype">REGEX</span> classes are all literals. An example <span class="tokentype">STRING</span> would be <span class="token">"hello"</span>.
|
||||
|
||||
{{spacer}}
|
||||
|
||||
<p>Some definitions with ellipses may seem strangely permissive, such as:</p>
|
||||
|
||||
{{#nodespec}}["varStmnt", `var`, (varDecl | `,`), ..., semi]{{/nodespec}}
|
||||
|
||||
<p>This seems to say that <code>var;</code> and <code>var,,x,</code> are valid statements.
|
||||
The reason for this style of definition is to discourage reliance on exact comma
|
||||
positions, which may occasionally vary between JavaScript implementations and versions.
|
||||
For example, the 5th edition of ECMAScript allows a trailing comma in object literals whereas
|
||||
the 3rd edition doesn't.</p>
|
||||
|
||||
<p>For most applications, the commas aren't that important, and you can read all the varDecls
|
||||
by just skipping any commas you encounter.</p>
|
||||
|
||||
</div>
|
||||
|
||||
{{#nodespec}}program:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}["program", (statement | functionDecl), ...]{{/nodespec}}
|
||||
</div>
|
||||
|
||||
{{#nodespec}}statement:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}["expressionStmnt", expression, semi]{{/nodespec}}
|
||||
<div class="explan">
|
||||
<p>Function calls and assignments are expressions.</p>
|
||||
</div>
|
||||
{{#nodespec}}["emptyStmnt", `;`]{{/nodespec}}
|
||||
<div class="explan">
|
||||
<p>Only an actual <span class="token">;</span> token can create an empty statement.</p>
|
||||
</div>
|
||||
{{#nodespec}}["blockStmnt", `{`, statement, ..., `}`]{{/nodespec}}
|
||||
{{#nodespec}}["varStmnt", `var`, (varDecl | `,`), ..., semi]{{/nodespec}}
|
||||
<div class="explan">
|
||||
<p>You can assume at least one varDecl is present.</p>
|
||||
</div>
|
||||
{{#nodespec}}["ifStmnt", `if`, `(`, expression, `)`, statement, (`else`, statement)?]{{/nodespec}}
|
||||
{{#nodespec}}["whileStmnt", `while`, `(`, expression, `)`, statement]{{/nodespec}}
|
||||
{{#nodespec}}["doStmnt", `do`, statement, `while`, `(`, expression, `)`, semi]{{/nodespec}}
|
||||
{{#nodespec}}["forStmnt", `for`, `(`, forSpec, `)`, statement]{{/nodespec}}
|
||||
{{#nodespec}}["returnStmnt", `return`, (expression | nil), semi]{{/nodespec}}
|
||||
{{#nodespec}}["continueStmnt", `continue`, (IDENTIFIER | nil), semi]{{/nodespec}}
|
||||
{{#nodespec}}["breakStmnt", `break`, (IDENTIFIER | nil), semi]{{/nodespec}}
|
||||
{{#nodespec}}["throwStmnt", `throw`, expression, semi]{{/nodespec}}
|
||||
{{#nodespec}}["withStmnt", `with`, `(`, expression, `)`, statement]{{/nodespec}}
|
||||
{{#nodespec}}["switchStmnt", `switch`, `(`, expression, `)`, `{`, (case | default), ..., `}`]{{/nodespec}}
|
||||
<div class="explan">
|
||||
<p>There's at most one default clause, but it can be anywhere in the list of cases and defaults.</p>
|
||||
</div>
|
||||
{{#nodespec}}["tryStmnt", `try`, statement, (catch | nil), (finally | nil)]{{/nodespec}}
|
||||
<div class="explan">
|
||||
<p>The statement is always a blockStmnt.</p>
|
||||
</div>
|
||||
{{#nodespec}}["labelStmnt", IDENTIFIER, `:`, statement]{{/nodespec}}
|
||||
{{#nodespec}}["debuggerStmnt", `debugger`, semi]{{/nodespec}}
|
||||
</div>
|
||||
|
||||
{{#nodespec}}functionDecl:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}["functionDecl", `function`, IDENTIFIER, `(`, (IDENTIFIER | `,`), ..., `)`, `{`, (statement | functionDecl), ..., `}`]{{/nodespec}}
|
||||
<div class="explan">
|
||||
<p>Different from a functionExpr only in that the function name is required and not optional.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#nodespec}}expression:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}["this", `this`]{{/nodespec}}
|
||||
{{#nodespec}}["null", `null`]{{/nodespec}}
|
||||
{{#nodespec}}["number", NUMBER]{{/nodespec}}
|
||||
{{#nodespec}}["boolean", BOOLEAN]{{/nodespec}}
|
||||
{{#nodespec}}["regex", REGEX]{{/nodespec}}
|
||||
{{#nodespec}}["string", STRING]{{/nodespec}}
|
||||
{{#nodespec}}["identifier", IDENTIFIER]{{/nodespec}}
|
||||
{{#nodespec}}["parens", `(`, expression, `)`]{{/nodespec}}
|
||||
{{#nodespec}}["array", `[`, (expression | `,`), ..., `]`]{{/nodespec}}
|
||||
<div class="explan">
|
||||
<p>All commas are significant because of element elision, and any
|
||||
combination of commas and expressions is possible.
|
||||
The array <code>[,,,7,,8,,]</code> has 7 and 8 as
|
||||
its 3rd and 5th elements.</p>
|
||||
</div>
|
||||
{{#nodespec}}["object", `{`, (prop | `,`), ..., `}`]{{/nodespec}}
|
||||
{{#nodespec}}["functionExpr", `function`, (IDENTIFIER | nil), `(`, (IDENTIFIER | `,`), ..., `)`, `{`, (statement | functionDecl), ..., `}`]{{/nodespec}}
|
||||
{{#nodespec}}["dot", expression, `.`, identifierName]{{/nodespec}}
|
||||
{{#nodespec}}["bracket", expression, `[`, expression, `]`]{{/nodespec}}
|
||||
{{#nodespec}}["call", expression, `(`, (expression | `,`), ..., `)`]{{/nodespec}}
|
||||
{{#nodespec}}["new", `new`, expression]{{/nodespec}}
|
||||
{{#nodespec}}["newcall", `new`, expression, `(`, (expression | `,`), ..., `)`]{{/nodespec}}
|
||||
{{#nodespec}}["unary", unaryOp, expression]{{/nodespec}}
|
||||
{{#nodespec}}["binary", expression, binaryOp, expression]{{/nodespec}}
|
||||
{{#nodespec}}["postfix", expression, postfixOp]{{/nodespec}}
|
||||
{{#nodespec}}["ternary", expression, `?`, expression, `:`, expression]{{/nodespec}}
|
||||
{{#nodespec}}["assignment", expression, assignmentOp, expression]{{/nodespec}}
|
||||
<div class="explan">
|
||||
<p>There is no simple constraint on what the first expression can be.
|
||||
A left-hand-side expression could be a simple identifier like <code>foo</code>,
|
||||
or it could be any expression ending in a property access, such as <code>foo().bar[baz]</code>.
|
||||
Parentheses are also allowed around the left-hand side, so surprisingly <code>((x)) = 3</code>
|
||||
is completely legal and equivalent to <code>x = 3</code>. This is because
|
||||
JavaScript evaluates the left-hand side all the way down to the final variable reference.</p>
|
||||
</div>
|
||||
{{#nodespec}}["comma", (expression | `,`), ...]{{/nodespec}}
|
||||
<div class="explan"><p>You can assume there are at least two expressions, since there would have been no comma otherwise.</p></div>
|
||||
</div>
|
||||
|
||||
{{#nodespec}}nil:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}["nil"]{{/nodespec}}
|
||||
<div class="explan"><p>Serves as a placeholder for optional parts of nodes.</p></div>
|
||||
</div>
|
||||
|
||||
{{#nodespec}}semi:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}[";"] | `;`{{/nodespec}}
|
||||
<div class="explan">
|
||||
<p>An optional semicolon at the end of a statement. Most semicolons in JavaScript can legally
|
||||
be omitted if a line break follows. If the semicolon was omitted, a <span class="str">";"</span>
|
||||
node takes its place.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#nodespec}}prop:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}["prop", propertyName, `:`, expression]{{/nodespec}}
|
||||
</div>
|
||||
|
||||
{{#nodespec}}propertyName:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}["idPropName", identifierName]{{/nodespec}}
|
||||
{{#nodespec}}["strPropName", STRING]{{/nodespec}}
|
||||
{{#nodespec}}["numPropName", NUMBER]{{/nodespec}}
|
||||
<div class="explan">
|
||||
<p>A property name in an object literal can be given as a bare identifier, a quoted string literal,
|
||||
or a number literal. These nodes indicate which it is.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#nodespec}}varDecl:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}["varDecl", IDENTIFIER, (`=`, expression)?]{{/nodespec}}
|
||||
</div>
|
||||
|
||||
{{#nodespec}}forSpec:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}["forSpec", (expression | nil), `;`, (expression | nil), `;`, (expression | nil)]{{/nodespec}}
|
||||
{{#nodespec}}["forVarSpec", `var`, (varDecl | `,`), ..., `;`, (expression | nil), `;`, (expression | nil)]{{/nodespec}}
|
||||
{{#nodespec}}["forInSpec", expression, `in`, expression]{{/nodespec}}
|
||||
{{#nodespec}}["forVarInSpec", `var`, varDecl, `in`, expression]{{/nodespec}}
|
||||
<div class="explan">
|
||||
<p>There are technically four types of for-loops in JavaScript.</p>
|
||||
<p>These definitions suggest some
|
||||
odd possibilities, and interestingly, they work. The form <code>for (x.foo in y)</code> will actually
|
||||
set <code>x.foo</code> each time through the loop, and <code>for (var x = bar() in y)</code> will call
|
||||
<code>bar()</code> and assign it first thing,
|
||||
even if <code>x</code> is immediately overwritten before the first iteration of the loop.</p>
|
||||
<p>The semicolons are mandatory, even at a line break.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#nodespec}}case:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}["case", `case`, expression, `:`, statement, ...]{{/nodespec}}
|
||||
</div>
|
||||
|
||||
{{#nodespec}}default:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}["default", `default`, `:`, statement, ...]{{/nodespec}}
|
||||
</div>
|
||||
|
||||
{{#nodespec}}catch:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}["catch", `catch`, `(`, IDENTIFIER, `)`, statement]{{/nodespec}}
|
||||
<div class="explan">
|
||||
<p>The statement is always a blockStmnt.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#nodespec}}finally:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}["finally", `finally`, statement]{{/nodespec}}
|
||||
<div class="explan">
|
||||
<p>The statement is always a blockStmnt.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#nodespec}}identifierName:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}(IDENTIFIER | KEYWORD | BOOLEAN | `null`){{/nodespec}}
|
||||
<div class="explan">
|
||||
<p>As of ECMAScript 5th edition, keywords and reserved words can be used in some places
|
||||
where an identifier is expected. For example, <code>x.return()</code> or
|
||||
<code>{true: 'yes'}</code>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#nodespec}}unaryOp:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}(`delete` | `void` | `typeof` | `++` | `--` | `+` | `-` | `~` | `!`){{/nodespec}}
|
||||
</div>
|
||||
|
||||
{{#nodespec}}binaryOp:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}(`*` | `/` | `%` | `+` | `-` | `<<` | `>>` | `>>>` | `<` | `>` | `<=` | `>=` | `instanceof` | `in` | `==` | `!=` | `===` | `!==` | `&` | `^` | `|` | `&&` | `||`){{/nodespec}}
|
||||
</div>
|
||||
|
||||
{{#nodespec}}postfixOp:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}(`++` | `--`){{/nodespec}}
|
||||
</div>
|
||||
|
||||
{{#nodespec}}assignmentOp:{{/nodespec}}
|
||||
<div class="indent">
|
||||
{{#nodespec}}(`=` | `*=` | `/=` | `%=` | `+=` | `-=` | `<<=` | `>>=` | `>>>=` | `&=` | `^=` | `|=`){{/nodespec}}
|
||||
</div>
|
||||
|
||||
</template>
|
||||
67
examples/unfinished/jsparse-docs/jsparse-docs.js
Normal file
67
examples/unfinished/jsparse-docs/jsparse-docs.js
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
|
||||
if (Meteor.is_client) {
|
||||
Template.page.nodespec = function (fn) {
|
||||
var parts = [fn()];
|
||||
var replaceParts = function(regex, replacementFunc) {
|
||||
var newParts = [];
|
||||
_.each(parts, function (part) {
|
||||
if (typeof part !== 'string') {
|
||||
newParts.push(part);
|
||||
return;
|
||||
}
|
||||
regex.lastIndex = 0;
|
||||
var charsTaken = 0;
|
||||
var matchResult;
|
||||
while ((matchResult = regex.exec(part))) {
|
||||
var matchIndex = matchResult.index;
|
||||
if (matchIndex > charsTaken)
|
||||
newParts.push(part.substring(charsTaken, matchIndex));
|
||||
charsTaken = regex.lastIndex;
|
||||
var replacementParts = replacementFunc(matchResult);
|
||||
newParts.push.apply(newParts, _.toArray(replacementParts));
|
||||
}
|
||||
if (charsTaken < part.length)
|
||||
newParts.push(part.slice(charsTaken));
|
||||
});
|
||||
parts = newParts;
|
||||
};
|
||||
|
||||
parts.unshift(['<div class="nodespec">']);
|
||||
parts.push(['</div>']);
|
||||
replaceParts(/".*?"/g, function (match) {
|
||||
return [['<span class="str">', Handlebars._escape(match[0]), '</span>']];
|
||||
});
|
||||
replaceParts(/`(.*?)`/g, function (match) {
|
||||
return [['<span class="token">', Handlebars._escape(match[1]), '</span>']];
|
||||
});
|
||||
replaceParts(/[A-Z]{3,}/g, function (match) {
|
||||
return [['<span class="tokentype">', Handlebars._escape(match[0]), '</span>']];
|
||||
});
|
||||
replaceParts(/[a-z]\w*/g, function (match) {
|
||||
return [['<span class="ref">', Handlebars._escape(match[0]), '</span>']];
|
||||
});
|
||||
replaceParts(/[\[\]()|.,*?]/g, function (match) {
|
||||
return [['<span class="punc">'], match[0], ['</span>']];
|
||||
});
|
||||
replaceParts(/,/g, function (match) {
|
||||
return [['<span class="comma">'], match[0], ['</span>']];
|
||||
});
|
||||
replaceParts(/\|/g, function (match) {
|
||||
return [['<span class="or">'], match[0], ['</span>']];
|
||||
});
|
||||
|
||||
var html = _.map(parts, function (part) {
|
||||
if (typeof part === "string")
|
||||
return Handlebars._escape(part);
|
||||
return part.join('');
|
||||
}).join('');
|
||||
|
||||
return new Handlebars.SafeString(html);
|
||||
};
|
||||
|
||||
Template.page.spacer = function () {
|
||||
return new Handlebars.SafeString('<div class="spacer"> </div>');
|
||||
};
|
||||
|
||||
}
|
||||
@@ -60,7 +60,7 @@ Meteor.setInterval(function () {
|
||||
var idle_threshold = now - 70*1000; // 70 sec
|
||||
var remove_threshold = now - 60*60*1000; // 1hr
|
||||
|
||||
Players.update({$lt: {last_keepalive: idle_threshold}},
|
||||
Players.update({last_keepalive: {$lt: idle_threshold}},
|
||||
{$set: {idle: true}});
|
||||
|
||||
// XXX need to deal with people coming back!
|
||||
|
||||
5
meteor
5
meteor
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
BUNDLE_VERSION=0.2.8
|
||||
BUNDLE_VERSION=0.2.12
|
||||
|
||||
# OS Check. Put here because here is where we download the precompiled
|
||||
# bundles that are arch specific.
|
||||
@@ -58,6 +58,8 @@ function install_dev_bundle {
|
||||
test -x "${TMPDIR}/bin/node" # bomb out if it didn't work, eg no net
|
||||
fi
|
||||
|
||||
# Delete old dev bundle and rename the new one on top of it.
|
||||
rm -rf "$SCRIPT_DIR/dev_bundle"
|
||||
mv "$TMPDIR" "$SCRIPT_DIR/dev_bundle"
|
||||
|
||||
echo "Installed dependency kit v${BUNDLE_VERSION} in dev_bundle."
|
||||
@@ -77,7 +79,6 @@ if [ -d "$SCRIPT_DIR/.git" ] || [ -f "$SCRIPT_DIR/.git" ]; then
|
||||
elif [ ! -f "$SCRIPT_DIR/dev_bundle/.bundle_version.txt" ] ||
|
||||
grep -qvx "$BUNDLE_VERSION" "$SCRIPT_DIR/dev_bundle/.bundle_version.txt" ; then
|
||||
echo "Your dependency kit is out of date. I will download the new one."
|
||||
rm -rf "$SCRIPT_DIR/dev_bundle"
|
||||
install_dev_bundle
|
||||
fi
|
||||
|
||||
|
||||
@@ -219,7 +219,10 @@
|
||||
// the profile too
|
||||
var stampedToken = Accounts._generateStampedLoginToken();
|
||||
var setAttrs = {};
|
||||
setAttrs["services." + serviceName] = serviceData;
|
||||
_.each(serviceData, function(value, key) {
|
||||
setAttrs["services." + serviceName + "." + key] = value;
|
||||
});
|
||||
|
||||
// XXX Maybe we should re-use the selector above and notice if the update
|
||||
// touches nothing?
|
||||
Meteor.users.update(
|
||||
|
||||
@@ -32,11 +32,13 @@ Tinytest.add('accounts - updateOrCreateUserFromExternalService', function (test)
|
||||
test.equal(users[0].profile.foo, 1);
|
||||
test.equal(users[0].profile.bar, undefined);
|
||||
test.equal(users[0].services.facebook.llama, 50);
|
||||
test.equal(users[0].services.facebook.monkey, undefined);
|
||||
// make sure we *don't* lose values not passed this call to
|
||||
// updateOrCreateUserFromExternalService
|
||||
test.equal(users[0].services.facebook.monkey, 42);
|
||||
|
||||
// cleanup
|
||||
Meteor.users.remove(uid1);
|
||||
|
||||
|
||||
// users that have different service ids get different users
|
||||
uid1 = Accounts.updateOrCreateUserFromExternalService(
|
||||
'weibo', {id: weiboId1}, {profile: {foo: 1}}).id;
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
|
||||
Accounts.oauth.initiateLogin(state, loginUrl, callback);
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
|
||||
|
||||
3
packages/accounts-facebook/facebook_login_button.css
Normal file
3
packages/accounts-facebook/facebook_login_button.css
Normal file
@@ -0,0 +1,3 @@
|
||||
#login-buttons-image-facebook {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAq0lEQVQ4jWP8//8/AyWAhYGBgcEmauYZBgYGYxL1nj2yLN2ECcohVTNcDwsxKlXlhRm6yzwZRAS5GRgYGBhsombC5ZhwaUIGyJrRAVEuwGYzSS7AB/C64MiydKx8ZJfgNeDN+68MDAwIL8D4RLsgIHsJis0wPjKgOAyoE4hcnGwMGkpiBBUbacvA2TfuvaKiC759/3X23NUnOPMDtgTEwMBwloGBgYGR0uwMAGOPLJS9mkQHAAAAAElFTkSuQmCC);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ Package.on_use(function(api) {
|
||||
api.use('templating', 'client');
|
||||
|
||||
api.add_files(
|
||||
['facebook_configure.html', 'facebook_configure.js'],
|
||||
['facebook_login_button.css', 'facebook_configure.html', 'facebook_configure.js'],
|
||||
'client');
|
||||
|
||||
api.add_files('facebook_common.js', ['client', 'server']);
|
||||
|
||||
3
packages/accounts-github/github_login_button.css
Normal file
3
packages/accounts-github/github_login_button.css
Normal file
@@ -0,0 +1,3 @@
|
||||
#login-buttons-image-github {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9wJGBYxHYxl31wAAAHpSURBVDjLpZI/aFNRFMZ/973bJqGRPopV4qNq/+SpTYnWRhCKilShg9BGcHOM+GfQoZuLk4iLgw4qZNBaHLuIdBNHl7Ta1qdNFI3SihnaNG1MpH3vuiQYQnwZvHCG893zffc751z4z6PX5T5gA1DAKnAaOAQEgAfAVeCpl+CeCrlRuEC6maO4h0A1wl4tPAHMqNUthvrDdHYY7A3t4rDVjeO6rBU2FaABM1WCrBNoi48Mi+nH9yj+KtPibAKwJXfQ5vcRG7soUnYmWEuQgAEIYBv4cGpoILI0Z4tyYYPegS6UguyijZQ6J45GSNmZHzUcJYD2ii2Ajv7efZ8WZ6ZwXFj79hXpayW4O0SL1Nl/8jzZlZ9dQLFS70pgvZKIyGD0yvu5eRmMnrk1PjI81ir1qBACTdPevXj95mVuNX8XKDQc/+T334bZZ104cvzYw2s3J3qAL5WXSsDbf61NNMBu+wOBs+VSyQ84Nfhg028ZGx3/qyy0lC7lgi7lghBitoon03lvB8l0/k7Wnk+8mny0cyXzEcfZxgwfZPTyRMHsOzAFXE9YhtNQIJnOx4FpJXT1eSkn2g0frqMoFrfoCXcqlCOAGwnLuO/l4JymcWl5uRxzXUKghBAiZ5r+WaV4lrCM555zqO+x2d0ftGmpiA/0k70AAAAASUVORK5CYII=);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ Package.on_use(function(api) {
|
||||
api.use('templating', 'client');
|
||||
|
||||
api.add_files(
|
||||
['github_configure.html', 'github_configure.js'],
|
||||
['github_login_button.css', 'github_configure.html', 'github_configure.js'],
|
||||
'client');
|
||||
|
||||
api.add_files('github_common.js', ['client', 'server']);
|
||||
|
||||
@@ -15,25 +15,25 @@
|
||||
var state = Meteor.uuid();
|
||||
|
||||
// always need this to get user id from google.
|
||||
var required_scope = ['https://www.googleapis.com/auth/userinfo.profile'];
|
||||
var requiredScope = ['https://www.googleapis.com/auth/userinfo.profile'];
|
||||
var scope = ['https://www.googleapis.com/auth/userinfo.email'];
|
||||
if (options && options.requestPermissions)
|
||||
scope = options.requestPermissions;
|
||||
scope = _.union(scope, required_scope);
|
||||
var flat_scope = _.map(scope, encodeURIComponent).join('+');
|
||||
scope = _.union(scope, requiredScope);
|
||||
var flatScope = _.map(scope, encodeURIComponent).join('+');
|
||||
|
||||
// Might be good to have a way to set access_type=offline. Need to
|
||||
// both set it here and store the refresh token on the server.
|
||||
// https://developers.google.com/accounts/docs/OAuth2WebServer#formingtheurl
|
||||
var accessType = options.requestOfflineToken ? 'offline' : 'online';
|
||||
|
||||
var loginUrl =
|
||||
'https://accounts.google.com/o/oauth2/auth' +
|
||||
'?response_type=code' +
|
||||
'&client_id=' + config.clientId +
|
||||
'&scope=' + flat_scope +
|
||||
'&scope=' + flatScope +
|
||||
'&redirect_uri=' + Meteor.absoluteUrl('_oauth/google?close') +
|
||||
'&state=' + state;
|
||||
'&state=' + state +
|
||||
'&access_type=' + accessType;
|
||||
|
||||
Accounts.oauth.initiateLogin(state, loginUrl, callback);
|
||||
};
|
||||
|
||||
}) ();
|
||||
|
||||
@@ -7,4 +7,4 @@ Template.configureLoginServiceDialogForGoogle.fields = function () {
|
||||
{property: 'clientId', label: 'Client ID'},
|
||||
{property: 'secret', label: 'Client secret'}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
3
packages/accounts-google/google_login_button.css
Normal file
3
packages/accounts-google/google_login_button.css
Normal file
@@ -0,0 +1,3 @@
|
||||
#login-buttons-image-google {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAADCklEQVQ4jSXSy2ucVRjA4d97zvdNJpPJbTJJE9rYaCINShZtRCFIA1bbLryBUlyoLQjqVl12W7UbN4qb1gtuYhFRRBCDBITaesFbbI3RFBLSptEY05l0ZjLfnMvrov/Bs3gAcF71x6VVHTk+o8nDH+hrH89rUK9Z9Yaen57S3wVtGaMBNGC0IegWKIDxTtVaOHVugZVmH3HX3Zz+4l+W1xvkOjuZfPsspY4CNkZELEgEIJKwYlBjEwjec/mfCMVuorVs76R8+P0KYMmP30U2dT8eIZqAR2ipRcWjEYxGSCRhV08e04oYMoxYLi97EI9YCJ0FHBYbIVGDlUBLwRlLIuYW6chEmQt/rJO09RJjhjEJEYvJYGNhkbUhw43OXtIWDFRq9G87nAaSK6sVRm8r8fzRMWbOX2Xx7ypd7ZET03sQhDOz73DqSJOrd+7HSo4QIu0Nx/4rOzx+cRXZ9+z7+uqJ+3hiepxK3fHZT2tMjXYzOtzL6dmznPzhLexgN0QlxAAYxAlqUqRmkf5j59RlNQ6MFHhgcpCTTx8EUb5e+plD7x4jjg1ANCAgrRQAdR7xKXjBlGyLYi7PxaUmb8z8xcpGHVXLHaXdjI0egKyJiQYTEhSPREVIEUBNC+Mqm+xpz3j0njLPHB2nsh1QgeG+IS48dYbD5YNoo0ZUAbVEuTUoKuBSZOarX/WhyQn6eg2+usDWf0s0tq8zNPYk+WI/Lnge++hlvlyfQ3NdECzGRWKwEEA0qNY251n69kV6+Y0kbaCZoebG2X3oU7pKoyxuXOPe945zs9DCeosGIXoBDyaLdf6ce4Hbk+/Y299ksKtAuaeNsiyw8c1LKIZ95b0MdgxA5giixACpTxEPSau6QdFfI5/2cLPmEW+JAQrtJUJzDXF1dkwHzVodJMX4HFEcQQMaFdPeM0Jb/4PUtzzaLKAhRyJFwo6lbegRNFfk819muV5dR4JBQoQdQ2xFiDmSNDHiaptamR9Gq5cQ18AledrGDpOfeI5Lq8u88smbhMRisoSAgAYghdfn5H/JkHuRZ1owLAAAAABJRU5ErkJggg==);
|
||||
}
|
||||
@@ -2,20 +2,34 @@
|
||||
|
||||
Accounts.oauth.registerService('google', 2, function(query) {
|
||||
|
||||
var accessToken = getAccessToken(query);
|
||||
var response = getTokens(query);
|
||||
var accessToken = response.accessToken;
|
||||
var identity = getIdentity(accessToken);
|
||||
|
||||
var serviceData = {
|
||||
id: identity.id,
|
||||
accessToken: accessToken,
|
||||
email: identity.email,
|
||||
expiresAt: (+new Date) + (1000 * response.expiresIn)
|
||||
};
|
||||
|
||||
// only set the token in serviceData if it's there. this ensures
|
||||
// that we don't lose old ones (since we only get this on the first
|
||||
// log in attempt)
|
||||
if (response.refreshToken)
|
||||
serviceData.refreshToken = response.refreshToken;
|
||||
|
||||
return {
|
||||
serviceData: {
|
||||
id: identity.id,
|
||||
accessToken: accessToken,
|
||||
email: identity.email
|
||||
},
|
||||
serviceData: serviceData,
|
||||
options: {profile: {name: identity.name}}
|
||||
};
|
||||
});
|
||||
|
||||
var getAccessToken = function (query) {
|
||||
// returns an object containing:
|
||||
// - accessToken
|
||||
// - expiresIn: lifetime of token in seconds
|
||||
// - refreshToken, if this is the first authorization request
|
||||
var getTokens = function (query) {
|
||||
var config = Accounts.loginServiceConfiguration.findOne({service: 'google'});
|
||||
if (!config)
|
||||
throw new Accounts.ConfigError("Service not configured");
|
||||
@@ -33,7 +47,12 @@
|
||||
throw result.error;
|
||||
if (result.data.error) // if the http response was a json object with an error attribute
|
||||
throw result.data;
|
||||
return result.data.access_token;
|
||||
|
||||
return {
|
||||
accessToken: result.data.access_token,
|
||||
refreshToken: result.data.refresh_token,
|
||||
expiresIn: result.data.expires_in
|
||||
};
|
||||
};
|
||||
|
||||
var getIdentity = function (accessToken) {
|
||||
|
||||
@@ -9,7 +9,7 @@ Package.on_use(function(api) {
|
||||
api.use('templating', 'client');
|
||||
|
||||
api.add_files(
|
||||
['google_configure.html', 'google_configure.js'],
|
||||
['google_login_button.css', 'google_configure.html', 'google_configure.js'],
|
||||
'client');
|
||||
|
||||
api.add_files('google_common.js', ['client', 'server']);
|
||||
|
||||
@@ -51,19 +51,27 @@ OAuth1Binding.prototype.prepareAccessToken = function(query) {
|
||||
self.accessTokenSecret = tokens.oauth_token_secret;
|
||||
};
|
||||
|
||||
OAuth1Binding.prototype.call = function(method, url) {
|
||||
OAuth1Binding.prototype.call = function(method, url, params) {
|
||||
var self = this;
|
||||
|
||||
var headers = self._buildHeader({
|
||||
oauth_token: self.accessToken
|
||||
});
|
||||
|
||||
var response = self._call(method, url, headers);
|
||||
return response.data;
|
||||
if(!params) {
|
||||
params = {};
|
||||
}
|
||||
|
||||
var response = self._call(method, url, headers, params);
|
||||
return response;
|
||||
};
|
||||
|
||||
OAuth1Binding.prototype.get = function(url) {
|
||||
return this.call('GET', url);
|
||||
OAuth1Binding.prototype.get = function(url, params) {
|
||||
return this.call('GET', url, params);
|
||||
};
|
||||
|
||||
OAuth1Binding.prototype.post = function(url, params) {
|
||||
return this.call('POST', url, params);
|
||||
};
|
||||
|
||||
OAuth1Binding.prototype._buildHeader = function(headers) {
|
||||
@@ -77,9 +85,9 @@ OAuth1Binding.prototype._buildHeader = function(headers) {
|
||||
}, headers);
|
||||
};
|
||||
|
||||
OAuth1Binding.prototype._getSignature = function(method, url, rawHeaders, accessTokenSecret) {
|
||||
OAuth1Binding.prototype._getSignature = function(method, url, rawHeaders, accessTokenSecret, params) {
|
||||
var self = this;
|
||||
var headers = self._encodeHeader(rawHeaders);
|
||||
var headers = self._encodeHeader(_.extend(rawHeaders, params));
|
||||
|
||||
var parameters = _.map(headers, function(val, key) {
|
||||
return key + '=' + val;
|
||||
@@ -87,13 +95,13 @@ OAuth1Binding.prototype._getSignature = function(method, url, rawHeaders, access
|
||||
|
||||
var signatureBase = [
|
||||
method,
|
||||
encodeURIComponent(url),
|
||||
encodeURIComponent(parameters)
|
||||
self._encodeString(url),
|
||||
self._encodeString(parameters)
|
||||
].join('&');
|
||||
|
||||
var signingKey = encodeURIComponent(self._secret) + '&';
|
||||
var signingKey = self._encodeString(self._secret) + '&';
|
||||
if (accessTokenSecret)
|
||||
signingKey += encodeURIComponent(accessTokenSecret);
|
||||
signingKey += self._encodeString(accessTokenSecret);
|
||||
|
||||
return crypto.createHmac('SHA1', signingKey).update(signatureBase).digest('base64');
|
||||
};
|
||||
@@ -102,7 +110,7 @@ OAuth1Binding.prototype._call = function(method, url, headers, params) {
|
||||
var self = this;
|
||||
|
||||
// Get the signature
|
||||
headers.oauth_signature = self._getSignature(method, url, headers, self.accessTokenSecret);
|
||||
headers.oauth_signature = self._getSignature(method, url, headers, self.accessTokenSecret, params);
|
||||
|
||||
// Make a authorization string according to oauth1 spec
|
||||
var authString = self._getAuthHeaderString(headers);
|
||||
@@ -117,6 +125,8 @@ OAuth1Binding.prototype._call = function(method, url, headers, params) {
|
||||
|
||||
if (response.error) {
|
||||
Meteor._debug('Error sending OAuth1 HTTP call', response.content, method, url, params, authString);
|
||||
if (response.statusCode) response.error.statusCode = response.statusCode;
|
||||
if (response.data) response.error.data = response.data;
|
||||
throw response.error;
|
||||
}
|
||||
|
||||
@@ -124,14 +134,20 @@ OAuth1Binding.prototype._call = function(method, url, headers, params) {
|
||||
};
|
||||
|
||||
OAuth1Binding.prototype._encodeHeader = function(header) {
|
||||
var self = this;
|
||||
return _.reduce(header, function(memo, val, key) {
|
||||
memo[encodeURIComponent(key)] = encodeURIComponent(val);
|
||||
memo[self._encodeString(key)] = self._encodeString(val);
|
||||
return memo;
|
||||
}, {});
|
||||
};
|
||||
|
||||
OAuth1Binding.prototype._encodeString = function(str) {
|
||||
return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
|
||||
};
|
||||
|
||||
OAuth1Binding.prototype._getAuthHeaderString = function(headers) {
|
||||
var self = this;
|
||||
return 'OAuth ' + _.map(headers, function(val, key) {
|
||||
return encodeURIComponent(key) + '="' + encodeURIComponent(val) + '"';
|
||||
return self._encodeString(key) + '="' + self._encodeString(val) + '"';
|
||||
}).sort().join(', ');
|
||||
};
|
||||
|
||||
@@ -156,4 +156,3 @@
|
||||
userCallback: callback});
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Package.on_use(function(api) {
|
||||
api.use('templating', 'client');
|
||||
|
||||
api.add_files(
|
||||
['twitter_configure.html', 'twitter_configure.js'],
|
||||
['twitter_login_button.css', 'twitter_configure.html', 'twitter_configure.js'],
|
||||
'client');
|
||||
|
||||
api.add_files('twitter_common.js', ['client', 'server']);
|
||||
|
||||
@@ -30,5 +30,4 @@
|
||||
|
||||
Accounts.oauth.initiateLogin(state, url, callback);
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
3
packages/accounts-twitter/twitter_login_button.css
Normal file
3
packages/accounts-twitter/twitter_login_button.css
Normal file
@@ -0,0 +1,3 @@
|
||||
#login-buttons-image-twitter {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAByklEQVQ4jaVTz0sbQRh92V10l006GaKJCtEtmqMYU0Qpwqb4B6zgXdT0WEr7B0ih4MGLP05CUWMvHkQwglhvGhsvKmJOBhTUQjWU2slilKarrAfdZROTQ8m7fPMx33szb75vXKZpohpwVbEBCNaCMUYopXppAWOMxDNsOPf3H1WIeDoSURYYYwQAKKW6y7KgLe2vam11KyMRZcEpEP6SOkwbUgc4ATAKUF8YW2fXhZejvaHPsc7gvH2DnCfQGEtdxrd/5NRJteUDpVTf+5kLp2WlA6JsCyZv9ChplPKdTfJZkYWhEF3bvnV3fb36NZSY3dP6Q/5V4hFvIAaKPckE8W5pLBIQdwHAthBdPtpJuhpeAwDu74DrP4/R1/Ts4cwBWg/gN+DowoSqTBPezAMAeAHw+suSw4Q7schFApF6af19a+2yLVIB7xR+0Zk75yCveu82FMnMViKHCXcSa3PPVBJAX5BszL2SP2kNwvdy5M1e+S2AogME4HFYPibPpxKZC03nRAp/M+Dx2UWDzTXfpttrx72ikCoVtrrAAwgdXBk9iazxxtpskfhs1O86aHXXpAEcA7ivJGDBDcDnyAsA2FMsi1KB/0bVv/EBBBSY9mZ7PAsAAAAASUVORK5CYII=);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
|
||||
Accounts.oauth.registerService('twitter', 1, function(oauthBinding) {
|
||||
var identity = oauthBinding.get('https://api.twitter.com/1/account/verify_credentials.json');
|
||||
var identity = oauthBinding.get('https://api.twitter.com/1/account/verify_credentials.json').data;
|
||||
|
||||
return {
|
||||
serviceData: {
|
||||
|
||||
@@ -3,14 +3,15 @@ if (!Accounts.ui)
|
||||
|
||||
if (!Accounts.ui._options) {
|
||||
Accounts.ui._options = {
|
||||
requestPermissions: {}
|
||||
requestPermissions: {},
|
||||
requestOfflineToken: {}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Accounts.ui.config = function(options) {
|
||||
// validate options keys
|
||||
var VALID_KEYS = ['passwordSignupFields', 'requestPermissions'];
|
||||
var VALID_KEYS = ['passwordSignupFields', 'requestPermissions', 'requestOfflineToken'];
|
||||
_.each(_.keys(options), function (key) {
|
||||
if (!_.contains(VALID_KEYS, key))
|
||||
throw new Error("Accounts.ui.config: Invalid key: " + key);
|
||||
@@ -45,6 +46,20 @@ Accounts.ui.config = function(options) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// deal with `requestOfflineToken`
|
||||
if (options.requestOfflineToken) {
|
||||
_.each(options.requestOfflineToken, function (value, service) {
|
||||
if (service !== 'google')
|
||||
throw new Error("Accounts.ui.config: `requestOfflineToken` only supported for Google login at the moment.");
|
||||
|
||||
if (Accounts.ui._options.requestOfflineToken[service]) {
|
||||
throw new Error("Accounts.ui.config: Can't set `requestOfflineToken` more than once for " + service);
|
||||
} else {
|
||||
Accounts.ui._options.requestOfflineToken[service] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Accounts.ui._passwordSignupFields = function () {
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
// XXX Most of the testing of accounts-ui is done manually, across
|
||||
// multiple browsers using examples/unfinished/accounts-ui-helper. We
|
||||
// should *definitely* automate this, but Tinytest is generally not
|
||||
// the right abstraction to use for this.
|
||||
|
||||
|
||||
// XXX it'd be cool to also test that the right thing happens if options
|
||||
// *are* validated, but Accouns.ui._options is global state which makes this hard
|
||||
// (impossible?)
|
||||
@@ -14,4 +20,3 @@ Tinytest.add('accounts-ui - config validates keys', function (test) {
|
||||
Accounts.ui.config({requestPermissions: {facebook: "not an array"}});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -115,18 +115,51 @@
|
||||
return '';
|
||||
};
|
||||
|
||||
// returns an array of the login services used by this app. each
|
||||
// element of the array is an object (eg {name: 'facebook'}), since
|
||||
// that makes it useful in combination with handlebars {{#each}}.
|
||||
//
|
||||
// NOTE: It is very important to have this return password last
|
||||
// because of the way we render the different providers in
|
||||
// login_buttons_dropdown.html
|
||||
Accounts._loginButtons.getLoginServices = function () {
|
||||
var ret = [];
|
||||
// make sure to put password last, since this is how it is styled
|
||||
// in the ui as well.
|
||||
_.each(
|
||||
['facebook', 'github', 'google', 'twitter', 'weibo', 'password'],
|
||||
function (service) {
|
||||
if (Accounts[service])
|
||||
ret.push({name: service});
|
||||
});
|
||||
var self = this;
|
||||
var services = [];
|
||||
|
||||
return ret;
|
||||
// find all methods of the form: `Meteor.loginWithFoo`, where
|
||||
// `Foo` corresponds to a login service
|
||||
//
|
||||
// XXX we should consider having a client-side
|
||||
// Accounts.oauth.registerService function which records the
|
||||
// active services and encapsulates boilerplate code now found in
|
||||
// files such as facebook_client.js. This would have the added
|
||||
// benefit of allow us to unify facebook_{client,common,server}.js
|
||||
// into one file, which would encourage people to build more login
|
||||
// services packages.
|
||||
_.each(_.keys(Meteor), function(methodName) {
|
||||
var match;
|
||||
if ((match = methodName.match(/^loginWith(.*)/))) {
|
||||
var serviceName = match[1].toLowerCase();
|
||||
|
||||
// HACKETY HACK. needed to not match
|
||||
// Meteor.loginWithToken. See XXX above.
|
||||
if (Accounts[serviceName])
|
||||
services.push(match[1].toLowerCase());
|
||||
}
|
||||
});
|
||||
|
||||
// Be equally kind to all login services. This also preserves
|
||||
// backwards-compatibility. (But maybe order should be
|
||||
// configurable?)
|
||||
services.sort();
|
||||
|
||||
// ensure password is last
|
||||
if (_.contains(services, 'password'))
|
||||
services = _.without(services, 'password').concat(['password']);
|
||||
|
||||
return _.map(services, function(name) {
|
||||
return {name: name};
|
||||
});
|
||||
};
|
||||
|
||||
Accounts._loginButtons.hasPasswordService = function () {
|
||||
|
||||
@@ -162,6 +162,8 @@
|
||||
var configuration = {
|
||||
service: serviceName
|
||||
};
|
||||
|
||||
// Fetch the value of each input field
|
||||
_.each(configurationFields(), function(field) {
|
||||
configuration[field.property] = document.getElementById(
|
||||
'configure-login-service-dialog-' + field.property).value
|
||||
@@ -229,7 +231,6 @@
|
||||
return loginButtonsSession.get('configureLoginServiceDialogSaveDisabled');
|
||||
};
|
||||
|
||||
|
||||
// XXX from http://epeli.github.com/underscore.string/lib/underscore.string.js
|
||||
var capitalize = function(str){
|
||||
str = str == null ? '' : String(str);
|
||||
|
||||
@@ -156,6 +156,26 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<!--
|
||||
This strategy for login forms means that browsers' "Remember password"
|
||||
functionality does not work. Different browsers have different
|
||||
requirements for remembering passwords:
|
||||
|
||||
- Firefox: Must be an actual form (with a submit button), but you can
|
||||
cancel the submit with onsubmit='return false'.
|
||||
|
||||
- Safari: Must be an actual form, and the form must actually be
|
||||
submitted somewhere (though it can target a hidden iframe and go to a
|
||||
bogus URL)
|
||||
|
||||
- Chrome: Must be an actual form, and the the form elements must be
|
||||
present in the initial HTML, not added to the page with javascript. This
|
||||
basically rules out using normal meteor templates.
|
||||
|
||||
https://gist.github.com/968927
|
||||
-->
|
||||
|
||||
<template name="_loginButtonsFormField">
|
||||
{{#if visible}}
|
||||
<div id="login-{{fieldName}}-label-and-input">
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
return Accounts._loginButtons.hasPasswordService();
|
||||
};
|
||||
|
||||
// return all login services, with password last
|
||||
Template._loginButtonsLoggedOutAllServices.services = function () {
|
||||
return Accounts._loginButtons.getLoginServices();
|
||||
};
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
/* These should be in their respective packages. https://app.asana.com/0/988582960612/1477837179813 */
|
||||
|
||||
#login-buttons-image-google {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAADCklEQVQ4jSXSy2ucVRjA4d97zvdNJpPJbTJJE9rYaCINShZtRCFIA1bbLryBUlyoLQjqVl12W7UbN4qb1gtuYhFRRBCDBITaesFbbI3RFBLSptEY05l0ZjLfnMvrov/Bs3gAcF71x6VVHTk+o8nDH+hrH89rUK9Z9Yaen57S3wVtGaMBNGC0IegWKIDxTtVaOHVugZVmH3HX3Zz+4l+W1xvkOjuZfPsspY4CNkZELEgEIJKwYlBjEwjec/mfCMVuorVs76R8+P0KYMmP30U2dT8eIZqAR2ipRcWjEYxGSCRhV08e04oYMoxYLi97EI9YCJ0FHBYbIVGDlUBLwRlLIuYW6chEmQt/rJO09RJjhjEJEYvJYGNhkbUhw43OXtIWDFRq9G87nAaSK6sVRm8r8fzRMWbOX2Xx7ypd7ZET03sQhDOz73DqSJOrd+7HSo4QIu0Nx/4rOzx+cRXZ9+z7+uqJ+3hiepxK3fHZT2tMjXYzOtzL6dmznPzhLexgN0QlxAAYxAlqUqRmkf5j59RlNQ6MFHhgcpCTTx8EUb5e+plD7x4jjg1ANCAgrRQAdR7xKXjBlGyLYi7PxaUmb8z8xcpGHVXLHaXdjI0egKyJiQYTEhSPREVIEUBNC+Mqm+xpz3j0njLPHB2nsh1QgeG+IS48dYbD5YNoo0ZUAbVEuTUoKuBSZOarX/WhyQn6eg2+usDWf0s0tq8zNPYk+WI/Lnge++hlvlyfQ3NdECzGRWKwEEA0qNY251n69kV6+Y0kbaCZoebG2X3oU7pKoyxuXOPe945zs9DCeosGIXoBDyaLdf6ce4Hbk+/Y299ksKtAuaeNsiyw8c1LKIZ95b0MdgxA5giixACpTxEPSau6QdFfI5/2cLPmEW+JAQrtJUJzDXF1dkwHzVodJMX4HFEcQQMaFdPeM0Jb/4PUtzzaLKAhRyJFwo6lbegRNFfk819muV5dR4JBQoQdQ2xFiDmSNDHiaptamR9Gq5cQ18AledrGDpOfeI5Lq8u88smbhMRisoSAgAYghdfn5H/JkHuRZ1owLAAAAABJRU5ErkJggg==);
|
||||
}
|
||||
|
||||
#login-buttons-image-facebook {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAq0lEQVQ4jWP8//8/AyWAhYGBgcEmauYZBgYGYxL1nj2yLN2ECcohVTNcDwsxKlXlhRm6yzwZRAS5GRgYGBhsombC5ZhwaUIGyJrRAVEuwGYzSS7AB/C64MiydKx8ZJfgNeDN+68MDAwIL8D4RLsgIHsJis0wPjKgOAyoE4hcnGwMGkpiBBUbacvA2TfuvaKiC759/3X23NUnOPMDtgTEwMBwloGBgYGR0uwMAGOPLJS9mkQHAAAAAElFTkSuQmCC);
|
||||
}
|
||||
|
||||
#login-buttons-image-weibo {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAKySURBVDhPY2AgEpR5sjf/nS/6//UkoX+XJltuCvVxkcOp9cyZM1w/r13TuXvmDD9MkYIwg7qrNrubnzFb6J5intPHqrnvCnIwyKIYsmrVKuaFYWEFW2Sk79zX0f6/REHhKFABC0zRsky+rXMSeZdKCTLIHqgUvLAknW8L3IAQDw/RFlbWnQ801P+DNN8D4n0qyk94GRiEjTg5Lbz4+YOCdbhjVmTxbZwex7PUW58t8O1Ukf9gA2IDAoRPWFudfayt9f+mpsb/6yrK/28qKf4/ISf7YZu83K07QMNe6On9nyWusMtVm813azH/UWctZo/vc8TABjB3CApufAzSqKjw/7apyf+nMdH/XxUX/X+RnfX/qY/3/5tqqv/vq6v936KsfB2onltaiEHGx5AteFep4EmGUEHB1Adamv9v6er8fztp0v//79////nr1/+3X778B4N///5/O3jw/0N39//nlBQ/louLd4MMAWImcPhsU1G6DfLvt717wepnz537X0FB4T8fL+//AH///2/evgWL/7l///9dE+P/b4AWTZSWXg/UzAj2/w2gs59mZYEV7d+//z8rE9N/JUXF/w62tiD//a+urIS4BAgeA712Cxg2F40M36alpXGBDTgmI/3hdUU5WEFjff3/wvx8MNvcxARsQE1VFUQ30Et37Oz+P1RV+b/J0nIjUATigmgBvtzH5mb//9++/f/mkyf/A4KC/nv7+oI1W1hb/3/1+fP//9+//39ekP//CVDzTlnZxxtnz1ZBSUDeDAyZh7W13nybOeP/7W1b/09rbf2/FhgWHy9c+P912bL/D11d/l+WEP8/SUR4Ox8DA6pmmEkpHh4ya0JCim4lJGx7kZp8821CwrN7Hh4Pr7m6nDoSET61PjDQichsA3T7//+s/16/5gXSkIAa1AAAh8dhOVd5xHAAAAAASUVORK5CYII=);
|
||||
}
|
||||
|
||||
#login-buttons-image-twitter {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAByklEQVQ4jaVTz0sbQRh92V10l006GaKJCtEtmqMYU0Qpwqb4B6zgXdT0WEr7B0ih4MGLP05CUWMvHkQwglhvGhsvKmJOBhTUQjWU2slilKarrAfdZROTQ8m7fPMx33szb75vXKZpohpwVbEBCNaCMUYopXppAWOMxDNsOPf3H1WIeDoSURYYYwQAKKW6y7KgLe2vam11KyMRZcEpEP6SOkwbUgc4ATAKUF8YW2fXhZejvaHPsc7gvH2DnCfQGEtdxrd/5NRJteUDpVTf+5kLp2WlA6JsCyZv9ChplPKdTfJZkYWhEF3bvnV3fb36NZSY3dP6Q/5V4hFvIAaKPckE8W5pLBIQdwHAthBdPtpJuhpeAwDu74DrP4/R1/Ts4cwBWg/gN+DowoSqTBPezAMAeAHw+suSw4Q7schFApF6af19a+2yLVIB7xR+0Zk75yCveu82FMnMViKHCXcSa3PPVBJAX5BszL2SP2kNwvdy5M1e+S2AogME4HFYPibPpxKZC03nRAp/M+Dx2UWDzTXfpttrx72ikCoVtrrAAwgdXBk9iazxxtpskfhs1O86aHXXpAEcA7ivJGDBDcDnyAsA2FMsi1KB/0bVv/EBBBSY9mZ7PAsAAAAASUVORK5CYII=);
|
||||
}
|
||||
|
||||
#login-buttons-image-github {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9wJGBYxHYxl31wAAAHpSURBVDjLpZI/aFNRFMZ/973bJqGRPopV4qNq/+SpTYnWRhCKilShg9BGcHOM+GfQoZuLk4iLgw4qZNBaHLuIdBNHl7Ta1qdNFI3SihnaNG1MpH3vuiQYQnwZvHCG893zffc751z4z6PX5T5gA1DAKnAaOAQEgAfAVeCpl+CeCrlRuEC6maO4h0A1wl4tPAHMqNUthvrDdHYY7A3t4rDVjeO6rBU2FaABM1WCrBNoi48Mi+nH9yj+KtPibAKwJXfQ5vcRG7soUnYmWEuQgAEIYBv4cGpoILI0Z4tyYYPegS6UguyijZQ6J45GSNmZHzUcJYD2ii2Ajv7efZ8WZ6ZwXFj79hXpayW4O0SL1Nl/8jzZlZ9dQLFS70pgvZKIyGD0yvu5eRmMnrk1PjI81ir1qBACTdPevXj95mVuNX8XKDQc/+T334bZZ104cvzYw2s3J3qAL5WXSsDbf61NNMBu+wOBs+VSyQ84Nfhg028ZGx3/qyy0lC7lgi7lghBitoon03lvB8l0/k7Wnk+8mny0cyXzEcfZxgwfZPTyRMHsOzAFXE9YhtNQIJnOx4FpJXT1eSkn2g0frqMoFrfoCXcqlCOAGwnLuO/l4JymcWl5uRxzXUKghBAiZ5r+WaV4lrCM555zqO+x2d0ftGmpiA/0k70AAAAASUVORK5CYII=);
|
||||
}
|
||||
@@ -23,6 +23,8 @@
|
||||
var options = {}; // use default scope unless specified
|
||||
if (Accounts.ui._options.requestPermissions[serviceName])
|
||||
options.requestPermissions = Accounts.ui._options.requestPermissions[serviceName];
|
||||
if (Accounts.ui._options.requestOfflineToken[serviceName])
|
||||
options.requestOfflineToken = Accounts.ui._options.requestOfflineToken[serviceName];
|
||||
|
||||
loginWithService(options, callback);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ Package.on_use(function (api) {
|
||||
api.add_files([
|
||||
'accounts_ui.js',
|
||||
|
||||
'login_buttons_images.css',
|
||||
'login_buttons.html',
|
||||
'login_buttons_single.html',
|
||||
'login_buttons_dropdown.html',
|
||||
|
||||
@@ -372,7 +372,7 @@
|
||||
width: @configure-login-service-dialog-width;
|
||||
margin-left: -(@configure-login-service-dialog-width
|
||||
+ @meteor-accounts-base-padding) / 2;
|
||||
margin-top: -180px; /* = approximately -height/2, though height can change */
|
||||
margin-top: -220px; /* = approximately -height/2, though height can change */
|
||||
|
||||
table { width: 100%; }
|
||||
input {
|
||||
|
||||
@@ -4,7 +4,7 @@ Package.describe({
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.use('accounts-ui-unstyled', 'client');
|
||||
api.use('less', 'server');
|
||||
api.use('less', 'client');
|
||||
|
||||
api.add_files(['login_buttons.less'], 'client');
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ Package.on_use(function(api) {
|
||||
api.use('templating', 'client');
|
||||
|
||||
api.add_files(
|
||||
['weibo_configure.html', 'weibo_configure.js'],
|
||||
['weibo_login_button.css', 'weibo_configure.html', 'weibo_configure.js'],
|
||||
'client');
|
||||
|
||||
api.add_files('weibo_common.js', ['client', 'server']);
|
||||
|
||||
@@ -24,5 +24,4 @@
|
||||
|
||||
Accounts.oauth.initiateLogin(state, loginUrl, callback);
|
||||
};
|
||||
|
||||
}) ();
|
||||
|
||||
3
packages/accounts-weibo/weibo_login_button.css
Normal file
3
packages/accounts-weibo/weibo_login_button.css
Normal file
@@ -0,0 +1,3 @@
|
||||
#login-buttons-image-weibo {
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAKySURBVDhPY2AgEpR5sjf/nS/6//UkoX+XJltuCvVxkcOp9cyZM1w/r13TuXvmDD9MkYIwg7qrNrubnzFb6J5intPHqrnvCnIwyKIYsmrVKuaFYWEFW2Sk79zX0f6/REHhKFABC0zRsky+rXMSeZdKCTLIHqgUvLAknW8L3IAQDw/RFlbWnQ801P+DNN8D4n0qyk94GRiEjTg5Lbz4+YOCdbhjVmTxbZwex7PUW58t8O1Ukf9gA2IDAoRPWFudfayt9f+mpsb/6yrK/28qKf4/ISf7YZu83K07QMNe6On9nyWusMtVm813azH/UWctZo/vc8TABjB3CApufAzSqKjw/7apyf+nMdH/XxUX/X+RnfX/qY/3/5tqqv/vq6v936KsfB2onltaiEHGx5AteFep4EmGUEHB1Adamv9v6er8fztp0v//79////nr1/+3X778B4N///5/O3jw/0N39//nlBQ/louLd4MMAWImcPhsU1G6DfLvt717wepnz537X0FB4T8fL+//AH///2/evgWL/7l///9dE+P/b4AWTZSWXg/UzAj2/w2gs59mZYEV7d+//z8rE9N/JUXF/w62tiD//a+urIS4BAgeA712Cxg2F40M36alpXGBDTgmI/3hdUU5WEFjff3/wvx8MNvcxARsQE1VFUQ30Et37Oz+P1RV+b/J0nIjUATigmgBvtzH5mb//9++/f/mkyf/A4KC/nv7+oI1W1hb/3/1+fP//9+//39ekP//CVDzTlnZxxtnz1ZBSUDeDAyZh7W13nybOeP/7W1b/09rbf2/FhgWHy9c+P912bL/D11d/l+WEP8/SUR4Ox8DA6pmmEkpHh4ya0JCim4lJGx7kZp8821CwrN7Hh4Pr7m6nDoSET61PjDQichsA3T7//+s/16/5gXSkIAa1AAAh8dhOVd5xHAAAAAASUVORK5CYII=);
|
||||
}
|
||||
@@ -43,6 +43,11 @@ DomUtils = {};
|
||||
|
||||
var testDiv = document.createElement("div");
|
||||
testDiv.innerHTML = " <link/><table></table><select><!----></select>";
|
||||
// Need to wrap in a div rather than directly creating SELECT to avoid
|
||||
// *another* IE bug.
|
||||
var testSelectDiv = document.createElement("div");
|
||||
testSelectDiv.innerHTML = "<select><option selected>Foo</option></select>";
|
||||
testSelectDiv.firstChild.setAttribute("name", "myname");
|
||||
|
||||
// Tests that, if true, indicate browser quirks present.
|
||||
var quirks = {
|
||||
@@ -55,10 +60,16 @@ DomUtils = {};
|
||||
// IE loses some tags in some environments (requiring extra wrapper).
|
||||
tagsLost: testDiv.getElementsByTagName("link").length === 0,
|
||||
|
||||
// IE <= 8 loses HTML comments in <select> and <option> tags.
|
||||
// Assert that we have IE's mergeAttributes to use in our work-around.
|
||||
commentsLost: ((! testDiv.getElementsByTagName("select")[0].firstChild)
|
||||
&& testDiv.mergeAttributes)
|
||||
// IE <= 9 loses HTML comments in <select> and <option> tags.
|
||||
commentsLost: (! testDiv.getElementsByTagName("select")[0].firstChild),
|
||||
|
||||
selectValueMustBeFromAttribute: (testSelectDiv.firstChild.value !== "Foo"),
|
||||
|
||||
// In IE7, setAttribute('name', foo) doesn't show up in rendered HTML.
|
||||
// (In FF3, outerHTML is undefined, but it doesn't have this quirk.)
|
||||
mustSetNameInCreateElement: (
|
||||
testSelectDiv.firstChild.outerHTML &&
|
||||
testSelectDiv.firstChild.outerHTML.indexOf("myname") === -1)
|
||||
};
|
||||
|
||||
// Set up map of wrappers for different nodes.
|
||||
@@ -157,15 +168,41 @@ DomUtils = {};
|
||||
// the DOM.
|
||||
// Here we build an array of fake tags and iterate over that.
|
||||
_.each(container.getElementsByTagName("ins"), function (ins) {
|
||||
if (ins.getAttribute("domutilsrealtagname"))
|
||||
if (ins.getAttribute("domutilsrealtagname")) {
|
||||
fakeTags.push(ins);
|
||||
}
|
||||
});
|
||||
|
||||
_.each(fakeTags, function (fakeTag) {
|
||||
var realTag = document.createElement(
|
||||
fakeTag.getAttribute('domutilsrealtagname'));
|
||||
var tagName = fakeTag.getAttribute('domutilsrealtagname');
|
||||
if (quirks.mustSetNameInCreateElement &&
|
||||
fakeTag.getAttribute('name')) {
|
||||
// IE7 can't set 'name' with setAttribute, but it has this
|
||||
// crazy syntax for setting it at create time.
|
||||
// http://webbugtrack.blogspot.com/2007/10/bug-235-createelement-is-broken-in-ie.html
|
||||
// http://msdn.microsoft.com/en-us/library/ms536389.aspx
|
||||
tagName = "<" + tagName + " name='" +
|
||||
_.escape(fakeTag.getAttribute('name')) + "'/>";
|
||||
}
|
||||
var realTag = document.createElement(tagName);
|
||||
fakeTag.removeAttribute('domutilsrealtagname');
|
||||
// copy all attributes
|
||||
realTag.mergeAttributes(fakeTag, false);
|
||||
// copy all attributes. for some reason mergeAttributes doesn't work
|
||||
// here: eg, it doesn't copy SELECTED or VALUE. (Probably because
|
||||
// these attributes would be expando on INS?)
|
||||
var fakeAttrs = fakeTag.attributes;
|
||||
for (var i = 0; i < fakeAttrs.length; ++i) {
|
||||
var fakeAttr = fakeAttrs.item(i);
|
||||
if (fakeAttr.specified) {
|
||||
var name = fakeAttr.name.toLowerCase();
|
||||
var value = String(fakeAttr.value);
|
||||
// IE7 gets confused if you try to setAttribute('selected', ''),
|
||||
// so be a little more explicit.
|
||||
if (name === 'selected' && value === '')
|
||||
value = 'selected';
|
||||
realTag.setAttribute(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
// move all children
|
||||
while (fakeTag.firstChild)
|
||||
realTag.appendChild(fakeTag.firstChild);
|
||||
@@ -480,5 +517,44 @@ DomUtils = {};
|
||||
return DomUtils.rangeToHtml(node, node);
|
||||
};
|
||||
|
||||
// Sets the value of an element, portably across browsers. There's a special
|
||||
// case for SELECT elements in IE.
|
||||
DomUtils.setElementValue = function (node, value) {
|
||||
// Try to assign the value.
|
||||
node.value = value;
|
||||
if (node.value === value || node.nodeName !== 'SELECT')
|
||||
return;
|
||||
|
||||
// IE (all versions) appears to only let you assign SELECT values which
|
||||
// match valid OPTION values... and moreover, the OPTION value must be
|
||||
// explicitly given as an attribute, not just as the text. So we hunt for
|
||||
// the OPTION and select it.
|
||||
var options = DomUtils.findAll(node, 'option');
|
||||
for (var i = 0; i < options.length; ++i) {
|
||||
if (DomUtils.getElementValue(options[i]) === value) {
|
||||
options[i].selected = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Gets the value of an element, portably across browsers. There's a special
|
||||
// case for SELECT elements in IE.
|
||||
DomUtils.getElementValue = function (node) {
|
||||
if (!quirks.selectValueMustBeFromAttribute)
|
||||
return node.value;
|
||||
|
||||
if (node.nodeName === 'OPTION') {
|
||||
// Inspired by jQuery.valHooks.option.get.
|
||||
var val = node.attributes.value;
|
||||
return !val || val.specified ? node.value : node.text;
|
||||
} else if (node.nodeName === 'SELECT') {
|
||||
if (node.selectedIndex < 0)
|
||||
return null;
|
||||
return DomUtils.getElementValue(node.options[node.selectedIndex]);
|
||||
} else {
|
||||
return node.value;
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
@@ -1 +1,13 @@
|
||||
// TESTS GO HERE
|
||||
Tinytest.add("domutils - setElementValue", function (test) {
|
||||
var div = OnscreenDiv();
|
||||
div.node().appendChild(DomUtils.htmlToFragment(
|
||||
("<select><option>Foo</option><option value='Bar'>Baz</option>" +
|
||||
"<option selected value='Quux'>Quux</option></select>")));
|
||||
|
||||
var select = DomUtils.find(div.node(), 'select');
|
||||
test.equal(DomUtils.getElementValue(select), "Quux");
|
||||
_.each(["Foo", "Bar", "Quux"], function (value) {
|
||||
DomUtils.setElementValue(select, value);
|
||||
test.equal(DomUtils.getElementValue(select), value);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,9 +20,13 @@ Meteor.http = Meteor.http || {};
|
||||
|
||||
method = (method || "").toUpperCase();
|
||||
|
||||
var headers = {};
|
||||
|
||||
var content = options.content;
|
||||
if (options.data)
|
||||
if (options.data) {
|
||||
content = JSON.stringify(options.data);
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
var params_for_url, params_for_body;
|
||||
if (content || method === "GET" || method === "HEAD")
|
||||
@@ -51,6 +55,8 @@ Meteor.http = Meteor.http || {};
|
||||
content = Meteor.http._encodeParams(params_for_body);
|
||||
}
|
||||
|
||||
_.extend(headers, options.headers || {});
|
||||
|
||||
////////// Callback wrapping //////////
|
||||
|
||||
// wrap callback to always return a result object, and always
|
||||
@@ -84,9 +90,8 @@ Meteor.http = Meteor.http || {};
|
||||
|
||||
xhr.open(method, url, true, username, password);
|
||||
|
||||
if (options.headers)
|
||||
for (var k in options.headers)
|
||||
xhr.setRequestHeader(k, options.headers[k]);
|
||||
for (var k in headers)
|
||||
xhr.setRequestHeader(k, headers[k]);
|
||||
|
||||
|
||||
// setup timeout
|
||||
|
||||
@@ -8,11 +8,16 @@ Meteor.http = Meteor.http || {};
|
||||
_.each(params, function(value, key) {
|
||||
if (buf.length)
|
||||
buf.push('&');
|
||||
buf.push(encodeURIComponent(key), '=', encodeURIComponent(value));
|
||||
buf.push(Meteor.http._encodeString(key), '=',
|
||||
Meteor.http._encodeString(value));
|
||||
});
|
||||
return buf.join('').replace(/%20/g, '+');
|
||||
};
|
||||
|
||||
Meteor.http._encodeString = function(str) {
|
||||
return encodeURIComponent(str).replace(/[!'()]/g, escape).replace(/\*/g, "%2A");
|
||||
};
|
||||
|
||||
Meteor.http._buildUrl = function(before_qmark, from_qmark, opt_query, opt_params) {
|
||||
var url_without_query = before_qmark;
|
||||
var query = from_qmark ? from_qmark.slice(1) : null;
|
||||
|
||||
@@ -27,9 +27,13 @@ Meteor.http = Meteor.http || {};
|
||||
|
||||
var url_parts = url_util.parse(url);
|
||||
|
||||
var headers = {};
|
||||
|
||||
var content = options.content;
|
||||
if (options.data)
|
||||
if (options.data) {
|
||||
content = JSON.stringify(options.data);
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
|
||||
var params_for_url, params_for_body;
|
||||
@@ -42,9 +46,6 @@ Meteor.http = Meteor.http || {};
|
||||
url_parts.protocol+"//"+url_parts.host+url_parts.pathname,
|
||||
url_parts.search, options.query, params_for_url);
|
||||
|
||||
|
||||
var headers = {};
|
||||
|
||||
if (options.auth) {
|
||||
if (options.auth.indexOf(':') < 0)
|
||||
throw new Error('auth option should be of the form "username:password"');
|
||||
|
||||
@@ -222,6 +222,22 @@ testAsyncMulti("httpcall - methods", [
|
||||
test.equal(result.statusCode, 200);
|
||||
var data = result.data;
|
||||
test.equal(data.body, {greeting: "Hello World!"});
|
||||
// nb: some browsers include a charset here too.
|
||||
test.matches(data.headers['content-type'], /^application\/json\b/);
|
||||
}));
|
||||
|
||||
Meteor.http.call(
|
||||
"POST", url_prefix()+"/data-test-explicit",
|
||||
{ data: {greeting: "Hello World!"},
|
||||
headers: {'Content-Type': 'text/stupid'} },
|
||||
expect(function(error, result) {
|
||||
test.isFalse(error);
|
||||
test.isTrue(result);
|
||||
test.equal(result.statusCode, 200);
|
||||
var data = result.data;
|
||||
test.equal(data.body, {greeting: "Hello World!"});
|
||||
// nb: some browsers include a charset here too.
|
||||
test.matches(data.headers['content-type'], /^text\/stupid\b/);
|
||||
}));
|
||||
}
|
||||
]);
|
||||
@@ -294,7 +310,6 @@ testAsyncMulti("httpcall - headers", [
|
||||
|
||||
testAsyncMulti("httpcall - params", [
|
||||
function(test, expect) {
|
||||
|
||||
var do_test = function(method, url, params, opt_opts, expect_url, expect_body) {
|
||||
var opts = {};
|
||||
if (typeof opt_opts === "string") {
|
||||
@@ -324,6 +339,8 @@ testAsyncMulti("httpcall - params", [
|
||||
do_test("GET", "/", {foo:"bar", fruit:"apple"}, "/?foo=bar&fruit=apple", "");
|
||||
do_test("POST", "/", {foo:"bar", fruit:"apple"}, "/", "foo=bar&fruit=apple");
|
||||
do_test("POST", "/", {foo:"bar", fruit:"apple"}, "/", "foo=bar&fruit=apple");
|
||||
do_test("GET", "/", {'foo!':"bang!"}, {}, "/?foo%21=bang%21", "");
|
||||
do_test("POST", "/", {'foo!':"bang!"}, {}, "/", "foo%21=bang%21");
|
||||
do_test("POST", "/", {foo:"bar", fruit:"apple"}, {
|
||||
content: "stuff!"}, "/?foo=bar&fruit=apple", "stuff!");
|
||||
do_test("POST", "/", {foo:"bar", greeting:"Hello World"}, {
|
||||
|
||||
@@ -70,10 +70,10 @@ var rPunctuator = new RegExp(
|
||||
.join('|'), 'g');
|
||||
var rDivPunctuator = /\/=?/g;
|
||||
// Section 7.8.3
|
||||
var rHexLiteral = /0x[0-9a-fA-F]+$/g;
|
||||
var rOctLiteral = /0[0-7]+/g; // deprecated
|
||||
var rHexLiteral = /0[xX][0-9a-fA-F]+(?!\w)/g;
|
||||
var rOctLiteral = /0[0-7]+(?!\w)/g; // deprecated
|
||||
var rDecLiteral =
|
||||
/(((0|[1-9][0-9]*)(\.[0-9]*)?)|\.[0-9]+)([Ee][+-]?[0-9]+)?/g;
|
||||
/(((0|[1-9][0-9]*)(\.[0-9]*)?)|\.[0-9]+)([Ee][+-]?[0-9]+)?(?!\w)/g;
|
||||
// Section 7.8.4
|
||||
var rStringQuote = /["']/g;
|
||||
// Match one or more characters besides quotes, backslashes, or line ends
|
||||
@@ -81,14 +81,30 @@ var rStringMiddle = /(?=.)[^"'\\]+?((?!.)|(?=["'\\]))/g;
|
||||
// Match one escape sequence, including the backslash.
|
||||
var rEscapeSequence =
|
||||
/\\(['"\\bfnrtv]|0(?![0-9])|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|(?=.)[^ux0-9])/g;
|
||||
// Match one ES5 line continuation
|
||||
var rLineContinuation =
|
||||
/\\(\r\n|[\u000A\u000D\u2028\u2029])/g;
|
||||
// Section 7.8.5
|
||||
// Match one regex literal, including slashes, not including flags.
|
||||
// XXX Add support for unescaped '/' in character class, allowed by 5th ed.
|
||||
var rRegexLiteral = /\/(?![*\/])(\\.|(?=.)[^\\])+?\//g;
|
||||
// Support unescaped '/' in character classes, per 5th ed.
|
||||
// For example: `/[/]/` will match the string `"/"`.
|
||||
//
|
||||
// Explanation of regex:
|
||||
// - Match `/` not followed by `/` or `*`
|
||||
// - Match one or more of any of these:
|
||||
// - Backslash followed by one non-newline
|
||||
// - One non-newline, not `[` or `\` or `/`
|
||||
// - A character class, beginning with `[` and ending with `]`.
|
||||
// In the middle is zero or more of any of these:
|
||||
// - Backslash followed by one non-newline
|
||||
// - One non-newline, not `]` or `\`
|
||||
// - Match closing `/`
|
||||
var rRegexLiteral =
|
||||
/\/(?![*\/])(\\.|(?=.)[^\[\/\\]|\[(\\.|(?=.)[^\]\\])*\])+\//g;
|
||||
var rRegexFlags = /[a-zA-Z]*/g;
|
||||
|
||||
var rDecider =
|
||||
/((?=.)\s)|(\/[\/\*]?)|([\][{}().;,<>=!+*%&|^~?:-])|(\d)|(["'])|(.)|([\S\s])/g;
|
||||
/((?=.)\s)|(\/[\/\*]?)|([\][{}();,<>=!+*%&|^~?:-]|\.(?![0-9]))|([\d.])|(["'])|(.)|([\S\s])/g;
|
||||
|
||||
var keywordLookup = {
|
||||
' break': 'KEYWORD',
|
||||
@@ -376,7 +392,8 @@ JSLexer.prototype.next = function () {
|
||||
run(rStringQuote);
|
||||
var quote = match[0];
|
||||
do {
|
||||
run(rStringMiddle) || run(rEscapeSequence) || run(rStringQuote);
|
||||
run(rStringMiddle) || run(rEscapeSequence) ||
|
||||
run(rLineContinuation) || run(rStringQuote);
|
||||
} while (match && match[0] !== quote);
|
||||
if (! (match && match[0] === quote))
|
||||
return lexeme('ERROR');
|
||||
@@ -388,12 +405,21 @@ JSLexer.prototype.next = function () {
|
||||
}
|
||||
// dot (any non-line-terminator)
|
||||
run(rIdentifierPrefix);
|
||||
// Use non-short-circuiting OR, '|', to allow matching
|
||||
// Use non-short-circuiting bitwise OR, '|', to always try
|
||||
// both regexes in sequence, returning false only if neither
|
||||
// matched.
|
||||
while (run(rIdentifierMiddle) | run(rIdentifierPrefix)) {/*continue*/}
|
||||
while ((!! run(rIdentifierMiddle)) |
|
||||
(!! run(rIdentifierPrefix))) { /*continue*/ }
|
||||
var word = code.substring(origPos, pos);
|
||||
return lexeme(keywordLookup[' '+word] || 'IDENTIFIER');
|
||||
};
|
||||
|
||||
JSLexer.prettyOffset = function (code, pos) {
|
||||
var codeUpToPos = code.substring(0, pos);
|
||||
var startOfLine = codeUpToPos.lastIndexOf('\n') + 1;
|
||||
var indexInLine = pos - startOfLine; // 0-based
|
||||
var lineNum = codeUpToPos.replace(/[^\n]+/g, '').length + 1; // 1-based
|
||||
return "line " + lineNum + ", offset " + indexInLine;
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
@@ -70,7 +70,8 @@ JSParser.prototype.consumeNewToken = function () {
|
||||
do {
|
||||
lex = lexer.next();
|
||||
if (lex.isError())
|
||||
throw new Error("Bad token at position " + lex.startPos() +
|
||||
throw new Error("Bad token at " +
|
||||
JSLexer.prettyOffset(lexer.code, lex.startPos()) +
|
||||
", text `" + lex.text() + "`");
|
||||
else if (lex.type() === "NEWLINE")
|
||||
self.isLineTerminatorHere = true;
|
||||
@@ -89,7 +90,7 @@ JSParser.prototype.getParseError = function (expecting, found) {
|
||||
if (this.oldToken)
|
||||
msg += " after " + this.oldToken;
|
||||
var pos = this.pos;
|
||||
msg += " at position " + pos;
|
||||
msg += " at " + JSLexer.prettyOffset(this.lexer.code, pos);
|
||||
msg += ", found " + (found || this.newToken);
|
||||
return new Error(msg);
|
||||
};
|
||||
@@ -233,21 +234,42 @@ JSParser.prototype.getSyntaxTree = function () {
|
||||
list(token(',')))),
|
||||
token(']')));
|
||||
|
||||
// "IdentifierName" in ES5 allows reserved words, like in a property access
|
||||
// or a key of an object literal.
|
||||
// Put IDENTIFIER last so it shows up in the error message.
|
||||
var identifierName = or(tokenType('NULL'), tokenType('BOOLEAN'),
|
||||
tokenType('KEYWORD'), tokenType('IDENTIFIER'));
|
||||
|
||||
var propertyName = expecting('propertyName', or(
|
||||
node('idPropName', tokenType('IDENTIFIER')),
|
||||
node('idPropName', identifierName),
|
||||
node('numPropName', tokenType('NUMBER')),
|
||||
node('strPropName', tokenType('STRING'))));
|
||||
var nameColonValue = expecting(
|
||||
'propertyName',
|
||||
node('prop', seq(propertyName, token(':'), assignmentExpression)));
|
||||
|
||||
// Allow trailing comma in object literal, per ES5. Trailing comma
|
||||
// must follow a `name:value`, that is, `{,}` is invalid.
|
||||
//
|
||||
// We can't just use a normal comma list(), because it will seize
|
||||
// on the comma as a sign that the list continues. Instead,
|
||||
// we specify a list of either ',' or nameColonValue, using positive
|
||||
// and negative lookAheads to constrain the sequence. The grammar
|
||||
// is ordered so that error messages will always say
|
||||
// "Expected propertyName" or "Expected ," as appropriate, not
|
||||
// "Expected ," when the look-ahead is negative or "Expected }".
|
||||
var objectLiteral =
|
||||
node('object',
|
||||
seq(token('{'),
|
||||
or(lookAheadToken('}'),
|
||||
list(nameColonValue,
|
||||
token(','))),
|
||||
token('}')));
|
||||
and(not(lookAheadToken(',')),
|
||||
list(or(seq(token(','),
|
||||
expecting('propertyName',
|
||||
not(lookAheadToken(',')))),
|
||||
seq(nameColonValue,
|
||||
or(lookAheadToken('}'),
|
||||
lookAheadToken(','))))))),
|
||||
expecting('propertyName', token('}'))));
|
||||
|
||||
var functionMaybeNameRequired = booleanFlaggedParser(
|
||||
function (nameRequired) {
|
||||
@@ -281,7 +303,8 @@ JSParser.prototype.getSyntaxTree = function () {
|
||||
objectLiteral,
|
||||
functionExpression));
|
||||
|
||||
var dotEnding = seq(token('.'), tokenType('IDENTIFIER'));
|
||||
|
||||
var dotEnding = seq(token('.'), identifierName);
|
||||
var bracketEnding = seq(token('['), expression, token(']'));
|
||||
var callArgs = seq(token('('),
|
||||
or(lookAheadToken(')'),
|
||||
|
||||
@@ -141,7 +141,8 @@ var makeTester = function (test) {
|
||||
// assert that a tokenization error occurred at '@'.
|
||||
badToken: function (code) {
|
||||
var constructMessage = function (pos, text) {
|
||||
return "Bad token at position " + pos + ", text `" + text + "`";
|
||||
var nicePos = JSLexer.prettyOffset(code, pos);
|
||||
return "Bad token at " + nicePos + ", text `" + text + "`";
|
||||
};
|
||||
var pos = code.indexOf('`');
|
||||
var text = code.match(/`(.*?)`/)[1];
|
||||
@@ -176,7 +177,7 @@ var makeTester = function (test) {
|
||||
badParse: function (code) {
|
||||
var constructMessage = function (whatExpected, pos, found, after) {
|
||||
return "Expected " + whatExpected + (after ? " after " + after : "") +
|
||||
" at position " + pos + ", found " + found;
|
||||
" at " + JSLexer.prettyOffset(code, pos) + ", found " + found;
|
||||
};
|
||||
var pos = code.indexOf('`');
|
||||
|
||||
@@ -200,7 +201,8 @@ var makeTester = function (test) {
|
||||
var after = parser.oldToken;
|
||||
found = (found || parser.newToken);
|
||||
test.equal(error.message,
|
||||
constructMessage(whatExpected, pos, found, after));
|
||||
constructMessage(whatExpected, pos, found, after),
|
||||
code);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -240,6 +242,10 @@ Tinytest.add("jsparse - tokenization errors", function (test) {
|
||||
var tester = makeTester(test);
|
||||
tester.badToken("123`@`");
|
||||
tester.badToken("thisIsATestOf = `'unterminated `\n strings'");
|
||||
// make sure newlines aren't quietly included in regex literals
|
||||
tester.badToken("var x = `/`a\nb/;");
|
||||
tester.badToken("var x = `/`a\\\nb/;");
|
||||
tester.badToken("var x = `/`a[\n]b/;");
|
||||
});
|
||||
|
||||
Tinytest.add("jsparse - syntax forms", function (test) {
|
||||
@@ -395,6 +401,20 @@ Tinytest.add("jsparse - syntax forms", function (test) {
|
||||
["null + this - 3 + true",
|
||||
"program(expressionStmnt(binary(binary(binary(null(null) + this(this)) - " +
|
||||
"number(3)) + boolean(true)) ;()))"],
|
||||
["+.5",
|
||||
"program(expressionStmnt(unary(+ number(.5)) ;()))"],
|
||||
["a1a1a",
|
||||
"program(expressionStmnt(identifier(a1a1a) ;()))"],
|
||||
["/abc/mig",
|
||||
"program(expressionStmnt(regex(/abc/mig) ;()))"],
|
||||
["/[]/",
|
||||
"program(expressionStmnt(regex(/[]/) ;()))"],
|
||||
["/[/]/",
|
||||
"program(expressionStmnt(regex(/[/]/) ;()))"],
|
||||
["/[[/]/",
|
||||
"program(expressionStmnt(regex(/[[/]/) ;()))"],
|
||||
["/.\\/[a//b]\\[\\][[\\d/]/",
|
||||
"program(expressionStmnt(regex(/.\\/[a//b]\\[\\][[\\d/]/) ;()))"],
|
||||
["a / /b/mgi / c",
|
||||
"program(expressionStmnt(binary(binary(identifier(a) / " +
|
||||
"regex(/b/mgi)) / identifier(c)) ;()))"],
|
||||
@@ -549,7 +569,36 @@ Tinytest.add("jsparse - syntax forms", function (test) {
|
||||
// comments don't interfere with parse
|
||||
["if (true)\n//comment\nfoo();",
|
||||
"program(ifStmnt(if `(` boolean(true) `)` " +
|
||||
"expressionStmnt(call(identifier(foo) `(` `)`) ;)))"]
|
||||
"expressionStmnt(call(identifier(foo) `(` `)`) ;)))"],
|
||||
// bare keywords allowed in property access and object literal
|
||||
["foo.return();",
|
||||
"program(expressionStmnt(call(dot(identifier(foo) . return) `(` `)`) ;))"],
|
||||
["foo.true();",
|
||||
"program(expressionStmnt(call(dot(identifier(foo) . true) `(` `)`) ;))"],
|
||||
["foo.null();",
|
||||
"program(expressionStmnt(call(dot(identifier(foo) . null) `(` `)`) ;))"],
|
||||
["({true:3})",
|
||||
"program(expressionStmnt(parens(`(` object({ prop(idPropName(true) : number(3)) }) `)`) ;()))"],
|
||||
["({null:3})",
|
||||
"program(expressionStmnt(parens(`(` object({ prop(idPropName(null) : number(3)) }) `)`) ;()))"],
|
||||
["({if:3})",
|
||||
"program(expressionStmnt(parens(`(` object({ prop(idPropName(if) : number(3)) }) `)`) ;()))"],
|
||||
// ES5 line continuations in string literals
|
||||
["var x = 'a\\\nb\\\nc';",
|
||||
"program(varStmnt(var varDecl(x = string(`'a\\\nb\\\nc'`)) ;))"],
|
||||
// ES5 trailing comma in object literal
|
||||
["({});",
|
||||
"program(expressionStmnt(parens(`(` object({ }) `)`) ;))"],
|
||||
["({x:1});",
|
||||
"program(expressionStmnt(parens(`(` object({ prop(idPropName(x) : number(1)) }) `)`) ;))"],
|
||||
["({x:1,});",
|
||||
"program(expressionStmnt(parens(`(` object({ prop(idPropName(x) : number(1)) , }) `)`) ;))"],
|
||||
["({x:1,y:2});",
|
||||
"program(expressionStmnt(parens(`(` object({ prop(idPropName(x) : number(1)) , " +
|
||||
"prop(idPropName(y) : number(2)) }) `)`) ;))"],
|
||||
["({x:1,y:2,});",
|
||||
"program(expressionStmnt(parens(`(` object({ prop(idPropName(x) : number(1)) , " +
|
||||
"prop(idPropName(y) : number(2)) , }) `)`) ;))"]
|
||||
];
|
||||
_.each(trials, function (tr) {
|
||||
tester.goodParse(tr[0], tr[1]);
|
||||
@@ -558,6 +607,8 @@ Tinytest.add("jsparse - syntax forms", function (test) {
|
||||
|
||||
Tinytest.add("jsparse - bad parses", function (test) {
|
||||
var tester = makeTester(test);
|
||||
// string between backticks is pulled out and becomes what's "expected"
|
||||
// at that location, according to the correct error message
|
||||
var trials = [
|
||||
'{`statement`',
|
||||
'if (`expression`)',
|
||||
@@ -590,16 +641,29 @@ Tinytest.add("jsparse - bad parses", function (test) {
|
||||
'foo: `statement`function foo() {}',
|
||||
'[`expression`=',
|
||||
'[,,`expression`=',
|
||||
'({`propertyName`true:3})',
|
||||
'({`propertyName`|:3})',
|
||||
'({1:2,3`:`})',
|
||||
'({1:2,`propertyName`',
|
||||
'x.`IDENTIFIER`true',
|
||||
'x.`IDENTIFIER`,',
|
||||
'foo;`semicolon`:;',
|
||||
'1;`statement`=',
|
||||
'a+b`semicolon`=c;',
|
||||
'for(1+1 `semicolon`in {});',
|
||||
'`statement`=',
|
||||
'for(;`expression`var;) {}'
|
||||
'for(;`expression`var;) {}',
|
||||
'({`propertyName`',
|
||||
'({`propertyName`,})',
|
||||
'({`propertyName`:})',
|
||||
'({x`:`})',
|
||||
'({x:1,`propertyName`',
|
||||
'({x:1,`propertyName`,})',
|
||||
'({x:1`,`',
|
||||
'({x:1,`propertyName`,y:2})',
|
||||
'({x:1,`propertyName`,})',
|
||||
'({x:1,y:2`,`:',
|
||||
'({x:1,y:2,`propertyName`',
|
||||
'({x:1,y:2,`propertyName`:',
|
||||
'({x:1,y:2,`propertyName`,})'
|
||||
];
|
||||
_.each(trials, function (tr) {
|
||||
tester.badParse(tr);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
(function () {
|
||||
// By default, try to connect back to the same endpoint as the page
|
||||
// was served from.
|
||||
var ddp_endpoint = '/';
|
||||
if (typeof __meteor_runtime_config__ !== "undefined" &&
|
||||
__meteor_runtime_config__.DEFAULT_DDP_ENDPOINT)
|
||||
ddp_endpoint = __meteor_runtime_config__.DEFAULT_DDP_ENDPOINT;
|
||||
var ddpUrl = '/';
|
||||
if (typeof __meteor_runtime_config__ !== "undefined") {
|
||||
if (__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL)
|
||||
ddpUrl = __meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL;
|
||||
}
|
||||
|
||||
_.extend(Meteor, {
|
||||
default_connection: Meteor.connect(ddp_endpoint,
|
||||
true /* restart_on_update */),
|
||||
default_connection: Meteor.connect(ddpUrl, true /* restart_on_update */),
|
||||
|
||||
refresh: function (notification) {
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ if (Meteor.isServer) {
|
||||
// we can't do recursive Meteor.autosubscribe().
|
||||
var captureSubs = null;
|
||||
|
||||
// @param url {String|Object} URL to Meteor app or sockjs endpoint (deprecated),
|
||||
// or an object as a test hook (see code)
|
||||
// @param url {String|Object} URL to Meteor app,
|
||||
// or an object as a test hook (see code)
|
||||
// Options:
|
||||
// reloadOnUpdate: should we try to reload when the server says
|
||||
// there's new code available?
|
||||
@@ -30,9 +30,6 @@ Meteor._LivedataConnection = function (url, options) {
|
||||
// as a test hook, allow passing a stream instead of a url.
|
||||
if (typeof url === "object") {
|
||||
self._stream = url;
|
||||
// if we have two test streams, auto reload stuff will break because
|
||||
// the url is used as a key for the migration data.
|
||||
url = "/debug";
|
||||
} else {
|
||||
self._stream = new Meteor._Stream(url);
|
||||
}
|
||||
@@ -217,6 +214,12 @@ Meteor._LivedataConnection = function (url, options) {
|
||||
self._outstandingMethodBlocks.shift();
|
||||
}
|
||||
|
||||
// Mark all messages as unsent, they have not yet been sent on this
|
||||
// connection.
|
||||
_.each(self._methodInvokers, function (m) {
|
||||
m.sentMessage = false;
|
||||
});
|
||||
|
||||
// If an `onReconnect` handler is set, call it first. Go through
|
||||
// some hoops to ensure that methods that are called from within
|
||||
// `onReconnect` get executed _before_ ones that were originally
|
||||
@@ -488,7 +491,7 @@ _.extend(Meteor._LivedataConnection.prototype, {
|
||||
callback = Meteor.bindEnvironment(callback, function (e) {
|
||||
// XXX improve error message (and how we report it)
|
||||
Meteor._debug("Exception while delivering result of invoking '" +
|
||||
name + "'", e.stack);
|
||||
name + "'", e, e.stack);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1155,13 +1158,12 @@ _.extend(Meteor._LivedataConnection.prototype, {
|
||||
});
|
||||
|
||||
_.extend(Meteor, {
|
||||
// @param url {String} URL to Meteor app, or to sockjs endpoint (deprecated),
|
||||
// @param url {String} URL to Meteor app,
|
||||
// e.g.:
|
||||
// "subdomain.meteor.com",
|
||||
// "http://subdomain.meteor.com",
|
||||
// "/",
|
||||
// "http://subdomain.meteor.com/sockjs" (deprecated),
|
||||
// "/sockjs" (deprecated)
|
||||
// "ddp+sockjs://ddp--****-foo.meteor.com/sockjs"
|
||||
connect: function (url, _reloadOnUpdate) {
|
||||
var ret = new Meteor._LivedataConnection(
|
||||
url, {reloadOnUpdate: _reloadOnUpdate});
|
||||
|
||||
@@ -1051,6 +1051,49 @@ Tinytest.add("livedata connection - onReconnect prepends messages correctly with
|
||||
]);
|
||||
});
|
||||
|
||||
Tinytest.add("livedata connection - onReconnect with sent messages", function(test) {
|
||||
var stream = new Meteor._StubStream();
|
||||
var conn = newConnection(stream);
|
||||
startAndConnect(test, stream);
|
||||
|
||||
// setup method
|
||||
conn.methods({do_something: function (x) {}});
|
||||
|
||||
conn.onReconnect = function() {
|
||||
conn.apply('do_something', ['login'], {wait: true});
|
||||
};
|
||||
|
||||
conn.apply('do_something', ['one']);
|
||||
|
||||
// initial connect
|
||||
stream.sent = [];
|
||||
stream.reset();
|
||||
testGotMessage(
|
||||
test, stream, {msg: 'connect', session: conn._lastSessionId});
|
||||
|
||||
// Test that we sent just the login message.
|
||||
var loginId = testGotMessage(
|
||||
test, stream, {msg: 'method', method: 'do_something',
|
||||
params: ['login'], id: '*'});
|
||||
|
||||
// we connect.
|
||||
stream.receive({msg: 'connected', session: Meteor.uuid()});
|
||||
test.length(stream.sent, 0);
|
||||
|
||||
// login got result (but not yet data)
|
||||
stream.receive({msg: 'result', id: loginId, result: 'foo'});
|
||||
test.length(stream.sent, 0);
|
||||
|
||||
// login got data. now we send next method.
|
||||
stream.receive({msg: 'data', methods: [loginId]});
|
||||
|
||||
testGotMessage(
|
||||
test, stream, {msg: 'method', method: 'do_something',
|
||||
params: ['one'], id: '*'});
|
||||
});
|
||||
|
||||
|
||||
|
||||
Tinytest.add("livedata stub - reconnect double wait method", function (test) {
|
||||
var stream = new Meteor._StubStream;
|
||||
var conn = newConnection(stream);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
if (process.env.DEFAULT_DDP_ENDPOINT)
|
||||
__meteor_runtime_config__.DEFAULT_DDP_ENDPOINT = process.env.DEFAULT_DDP_ENDPOINT;
|
||||
if (process.env.DDP_DEFAULT_CONNECTION_URL) {
|
||||
__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL =
|
||||
process.env.DDP_DEFAULT_CONNECTION_URL;
|
||||
}
|
||||
|
||||
|
||||
_.extend(Meteor, {
|
||||
|
||||
@@ -15,8 +15,18 @@
|
||||
try {
|
||||
testElem.test = 123;
|
||||
} catch (exception) { }
|
||||
if (testElem.test !== 123)
|
||||
return false;
|
||||
|
||||
return (testElem.test === 123);
|
||||
// IE9 and 10 have a weird issue with multiple text nodes next to
|
||||
// each other losing their expando attributes. Use the same
|
||||
// workaround as IE8. Not sure how to test this as a feature, so use
|
||||
// browser detection instead.
|
||||
// See https://github.com/meteor/meteor/issues/458
|
||||
if (document.documentMode)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
})();
|
||||
|
||||
var wrapEndpoints = function (start, end) {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
Template.madewith.vote_count = function() {
|
||||
var app = apps.findOne();
|
||||
return app ? app.vote_count : '???';
|
||||
return app ? app.vote_count : '';
|
||||
};
|
||||
|
||||
Template.madewith.shortname = function () {
|
||||
@@ -29,13 +29,13 @@
|
||||
Template.madewith.events({
|
||||
'click .madewith_upvote': function(event) {
|
||||
var app = apps.findOne();
|
||||
if (app) {
|
||||
if (app)
|
||||
server.call('vote', hostname);
|
||||
// stop these so you don't click through the link to go to the
|
||||
// app.
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// stop these so you don't click through the link to go to the
|
||||
// app.
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -22,7 +22,7 @@ Meteor._noYieldsAllowed = function (f) {
|
||||
};
|
||||
|
||||
// js2-mode AST blows up when parsing 'future.return()', so alias.
|
||||
Future.prototype.ret = Future.prototype.return;
|
||||
Future.prototype.ret = Future.prototype['return'];
|
||||
|
||||
// Meteor._SynchronousQueue is a queue which runs task functions serially.
|
||||
// Tasks are assumed to be synchronous: ie, it's assumed that they are
|
||||
@@ -141,4 +141,15 @@ _.extend(Meteor._SynchronousQueue.prototype, {
|
||||
}
|
||||
});
|
||||
|
||||
// Sleep. Mostly used for debugging (eg, inserting latency into server
|
||||
// methods).
|
||||
Meteor._sleepForMs = function (ms) {
|
||||
var fiber = Fiber.current;
|
||||
setTimeout(function() {
|
||||
fiber.run();
|
||||
}, ms);
|
||||
Fiber.yield();
|
||||
};
|
||||
|
||||
|
||||
})();
|
||||
|
||||
@@ -2,3 +2,12 @@ Meteor = {
|
||||
isClient: false,
|
||||
isServer: true
|
||||
};
|
||||
|
||||
try {
|
||||
Meteor.settings = {};
|
||||
if (process.env.METEOR_SETTINGS)
|
||||
Meteor.settings = JSON.parse(process.env.METEOR_SETTINGS);
|
||||
} catch (e) {
|
||||
throw new Error("Settings are not valid JSON");
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user