mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'devel' into shark
Conflicts: packages/templating/deftemplate.js packages/templating/package.js packages/templating/plugin/html_scanner.js packages/test-in-browser/driver.js tools/packages.js
This commit is contained in:
14
History.md
14
History.md
@@ -1,6 +1,8 @@
|
||||
|
||||
## vNEXT
|
||||
|
||||
* Fix Mongo selectors of the form: {$regex: /foo/}.
|
||||
|
||||
* Calling `findOne()` on the server no longer loads the full query result
|
||||
into memory.
|
||||
|
||||
@@ -18,6 +20,18 @@
|
||||
|
||||
* Delete login tokens from server when user logs out.
|
||||
|
||||
* Renames (may require doc updates):
|
||||
- `Meteor.default_connection` - `Meteor.connection`
|
||||
- `Meteor.default_server` - `Meteor.server`
|
||||
- `Meteor.connect` - `DDP.connect`
|
||||
- `Meteor.http` - `HTTP`
|
||||
|
||||
|
||||
## v0.6.4.1
|
||||
|
||||
* Update mongodb driver to use version 0.2.1 of the bson module.
|
||||
|
||||
|
||||
## v0.6.4
|
||||
|
||||
* Separate OAuth flow logic from Accounts into separate packages. The
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
jquery
|
||||
underscore
|
||||
showdown
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.6.4
|
||||
0.6.5-rc2
|
||||
|
||||
@@ -12,6 +12,15 @@ on the client, just on the server, or *Anywhere*.
|
||||
|
||||
{{> api_box isClient}}
|
||||
{{> api_box isServer}}
|
||||
|
||||
{{#note}}
|
||||
`Meteor.isServer` can be used to limit where code runs, but it does not
|
||||
prevent code from being sent to the client. Any sensitive code that you
|
||||
don't want served to the client, such as code containing passwords or
|
||||
authentication mechanisms, should be kept in the `server` directory.
|
||||
{{/note}}
|
||||
|
||||
|
||||
{{> api_box startup}}
|
||||
|
||||
On a server, the function will run as soon as the server process is
|
||||
@@ -413,8 +422,8 @@ the server. The return value is an object with the following fields:
|
||||
values are `connected` (the connection is up and
|
||||
running), `connecting` (disconnected and trying to open a
|
||||
new connection), `failed` (permanently failed to connect; e.g., the client
|
||||
and server support different versions of DDP) and `waiting` (failed
|
||||
to connect and waiting to try to reconnect).
|
||||
and server support different versions of DDP), `waiting` (failed
|
||||
to connect and waiting to try to reconnect) and `offline` (user has disconnected the connection).
|
||||
{{/dtdd}}
|
||||
|
||||
{{#dtdd name="retryCount" type="Number"}}
|
||||
@@ -441,11 +450,24 @@ to get realtime updates.
|
||||
|
||||
{{> api_box reconnect}}
|
||||
|
||||
{{> api_box disconnect}}
|
||||
|
||||
Call this method to temporarily disconnect from the server and stop all
|
||||
live data updates. While the client is disconnected it will not receive
|
||||
updates to collections, method calls will be queued until the
|
||||
connection is reestablished, and hot code push will be disabled.
|
||||
|
||||
Call [Meteor.reconnect](#meteor_reconnect) to reestablish the connection
|
||||
and resume data transfer.
|
||||
|
||||
This can be used to save battery on mobile devices when real time
|
||||
updates are not required.
|
||||
|
||||
{{> api_box connect}}
|
||||
|
||||
To call methods on another Meteor application or subscribe to its data
|
||||
sets, call `Meteor.connect` with the URL of the application.
|
||||
`Meteor.connect` returns an object which provides:
|
||||
sets, call `DDP.connect` with the URL of the application.
|
||||
`DDP.connect` returns an object which provides:
|
||||
|
||||
* `subscribe` -
|
||||
Subscribe to a record set. See
|
||||
@@ -463,6 +485,8 @@ sets, call `Meteor.connect` with the URL of the application.
|
||||
[Meteor.status](#meteor_status).
|
||||
* `reconnect` -
|
||||
See [Meteor.reconnect](#meteor_reconnect).
|
||||
* `disconnect` -
|
||||
See [Meteor.disconnect](#meteor_disconnect).
|
||||
* `onReconnect` - Set this to a function to be called as the first step of
|
||||
reconnecting. This function can call methods which will be executed before
|
||||
any other outstanding methods. For example, this can be used to re-establish
|
||||
@@ -473,11 +497,6 @@ When you call `Meteor.subscribe`, `Meteor.status`, `Meteor.call`, and
|
||||
`Meteor.apply`, you are using a connection back to that default
|
||||
server.
|
||||
|
||||
{{#warning}}
|
||||
In this release, `Meteor.connect` can only be called on the client.
|
||||
Servers can not yet connect to other servers.
|
||||
{{/warning}}
|
||||
|
||||
<h2 id="collections"><span>Collections</span></h2>
|
||||
|
||||
Meteor stores data in *collections*. To get started, declare a
|
||||
@@ -2795,9 +2814,9 @@ For example, the `toJSONValue` method for
|
||||
return this.toHexString();
|
||||
};
|
||||
|
||||
<h2 id="meteor_http"><span>Meteor.http</span></h2>
|
||||
<h2 id="HTTP"><span>HTTP</span></h2>
|
||||
|
||||
`Meteor.http` provides an HTTP API on the client and server. To use
|
||||
`HTTP` provides an HTTP request API on the client and server. To use
|
||||
these functions, add the HTTP package to your project with `$ meteor add
|
||||
http`.
|
||||
|
||||
@@ -2868,8 +2887,8 @@ Example server method:
|
||||
Meteor.methods({checkTwitter: function (userId) {
|
||||
check(userId, String);
|
||||
this.unblock();
|
||||
var result = Meteor.http.call("GET", "http://api.twitter.com/xyz",
|
||||
{params: {user: userId}});
|
||||
var result = HTTP.call("GET", "http://api.twitter.com/xyz",
|
||||
{params: {user: userId}});
|
||||
if (result.statusCode === 200)
|
||||
return true
|
||||
return false;
|
||||
@@ -2877,13 +2896,13 @@ Example server method:
|
||||
|
||||
Example asynchronous HTTP call:
|
||||
|
||||
Meteor.http.call("POST", "http://api.twitter.com/xyz",
|
||||
{data: {some: "json", stuff: 1}},
|
||||
function (error, result) {
|
||||
if (result.statusCode === 200) {
|
||||
Session.set("twizzled", true);
|
||||
}
|
||||
});
|
||||
HTTP.call("POST", "http://api.twitter.com/xyz",
|
||||
{data: {some: "json", stuff: 1}},
|
||||
function (error, result) {
|
||||
if (result.statusCode === 200) {
|
||||
Session.set("twizzled", true);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
{{> api_box http_get}}
|
||||
|
||||
@@ -447,9 +447,17 @@ Template.api.reconnect = {
|
||||
"This method does nothing if the client is already connected."]
|
||||
};
|
||||
|
||||
Template.api.disconnect = {
|
||||
id: "meteor_disconnect",
|
||||
name: "Meteor.disconnect()",
|
||||
locus: "Client",
|
||||
descr: [
|
||||
"Disconnect the client from the server."]
|
||||
};
|
||||
|
||||
Template.api.connect = {
|
||||
id: "meteor_connect",
|
||||
name: "Meteor.connect(url)",
|
||||
name: "DDP.connect(url)",
|
||||
locus: "Client",
|
||||
descr: ["Connect to the server of a different Meteor application to subscribe to its document sets and invoke its remote methods."],
|
||||
args: [
|
||||
@@ -1066,6 +1074,11 @@ Template.api.loginWithExternalService = {
|
||||
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."
|
||||
},
|
||||
{
|
||||
name: "forceApprovalPrompt",
|
||||
type: "Boolean",
|
||||
descr: "If true, forces the user to approve the app's permissions, even if previously approved. Currently only supported with Google."
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -1572,7 +1585,7 @@ Template.api.equals = {
|
||||
|
||||
Template.api.httpcall = {
|
||||
id: "meteor_http_call",
|
||||
name: "Meteor.http.call(method, url [, options] [, asyncCallback])",
|
||||
name: "HTTP.call(method, url [, options] [, asyncCallback])",
|
||||
locus: "Anywhere",
|
||||
descr: ["Perform an outbound HTTP request."],
|
||||
args: [
|
||||
@@ -1619,30 +1632,30 @@ Template.api.httpcall = {
|
||||
|
||||
Template.api.http_get = {
|
||||
id: "meteor_http_get",
|
||||
name: "Meteor.http.get(url, [options], [asyncCallback])",
|
||||
name: "HTTP.get(url, [options], [asyncCallback])",
|
||||
locus: "Anywhere",
|
||||
descr: ["Send an HTTP GET request. Equivalent to `Meteor.http.call(\"GET\", ...)`."]
|
||||
descr: ["Send an HTTP GET request. Equivalent to `HTTP.call(\"GET\", ...)`."]
|
||||
};
|
||||
|
||||
Template.api.http_post = {
|
||||
id: "meteor_http_post",
|
||||
name: "Meteor.http.post(url, [options], [asyncCallback])",
|
||||
name: "HTTP.post(url, [options], [asyncCallback])",
|
||||
locus: "Anywhere",
|
||||
descr: ["Send an HTTP POST request. Equivalent to `Meteor.http.call(\"POST\", ...)`."]
|
||||
descr: ["Send an HTTP POST request. Equivalent to `HTTP.call(\"POST\", ...)`."]
|
||||
};
|
||||
|
||||
Template.api.http_put = {
|
||||
id: "meteor_http_put",
|
||||
name: "Meteor.http.put(url, [options], [asyncCallback])",
|
||||
name: "HTTP.put(url, [options], [asyncCallback])",
|
||||
locus: "Anywhere",
|
||||
descr: ["Send an HTTP PUT request. Equivalent to `Meteor.http.call(\"PUT\", ...)`."]
|
||||
descr: ["Send an HTTP PUT request. Equivalent to `HTTP.call(\"PUT\", ...)`."]
|
||||
};
|
||||
|
||||
Template.api.http_del = {
|
||||
id: "meteor_http_del",
|
||||
name: "Meteor.http.del(url, [options], [asyncCallback])",
|
||||
name: "HTTP.del(url, [options], [asyncCallback])",
|
||||
locus: "Anywhere",
|
||||
descr: ["Send an HTTP DELETE request. Equivalent to `Meteor.http.call(\"DELETE\", ...)`. (Named `del` to avoid conflict with JavaScript's `delete`.)"]
|
||||
descr: ["Send an HTTP DELETE request. Equivalent to `HTTP.call(\"DELETE\", ...)`. (Named `del` to avoid conflict with JavaScript's `delete`.)"]
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -684,12 +684,13 @@ To get started, run
|
||||
|
||||
$ meteor bundle myapp.tgz
|
||||
|
||||
This command will generate a fully-contained Node.js application in
|
||||
the form of a tarball. To run this application, you need to provide
|
||||
Node.js 0.8 and a MongoDB server. You can then run the application by
|
||||
invoking node, specifying the HTTP port for the application to listen
|
||||
on, and the MongoDB endpoint. If you don't already have a MongoDB
|
||||
server, we can recommend our friends at [MongoHQ](http://mongohq.com).
|
||||
This command will generate a fully-contained Node.js application in the form of
|
||||
a tarball. To run this application, you need to provide Node.js 0.8 and a
|
||||
MongoDB server. (The current release of Meteor has been tested with Node
|
||||
0.8.24.) You can then run the application by invoking node, specifying the HTTP
|
||||
port for the application to listen on, and the MongoDB endpoint. If you don't
|
||||
already have a MongoDB server, we can recommend our friends at
|
||||
[MongoHQ](http://mongohq.com).
|
||||
|
||||
$ PORT=3000 MONGO_URL=mongodb://localhost:27017/myapp node bundle/main.js
|
||||
|
||||
@@ -704,7 +705,7 @@ have `npm` available, and run the following:
|
||||
|
||||
$ cd bundle/server/node_modules
|
||||
$ rm -r fibers
|
||||
$ npm install fibers@1.0.0
|
||||
$ npm install fibers@1.0.1
|
||||
{{/warning}}
|
||||
|
||||
{{/better_markdown}}
|
||||
|
||||
@@ -139,7 +139,8 @@ var toc = [
|
||||
{name: "Server connections", id: "connections"}, [
|
||||
"Meteor.status",
|
||||
"Meteor.reconnect",
|
||||
"Meteor.connect"
|
||||
"Meteor.disconnect",
|
||||
"DDP.connect"
|
||||
],
|
||||
|
||||
{name: "Collections", id: "collections"}, [
|
||||
@@ -305,12 +306,12 @@ var toc = [
|
||||
],
|
||||
|
||||
|
||||
"Meteor.http", [
|
||||
"Meteor.http.call",
|
||||
{name: "Meteor.http.get", id: "meteor_http_get"},
|
||||
{name: "Meteor.http.post", id: "meteor_http_post"},
|
||||
{name: "Meteor.http.put", id: "meteor_http_put"},
|
||||
{name: "Meteor.http.del", id: "meteor_http_del"}
|
||||
"HTTP", [
|
||||
"HTTP.call",
|
||||
{name: "HTTP.get", id: "meteor_http_get"},
|
||||
{name: "HTTP.post", id: "meteor_http_post"},
|
||||
{name: "HTTP.put", id: "meteor_http_put"},
|
||||
{name: "HTTP.del", id: "meteor_http_del"}
|
||||
],
|
||||
"Email", [
|
||||
"Email.send"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
autopublish
|
||||
insecure
|
||||
preserve-inputs
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.6.4
|
||||
0.6.4.1
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
|
||||
preserve-inputs
|
||||
accounts-google
|
||||
standard-app-packages
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
insecure
|
||||
preserve-inputs
|
||||
random
|
||||
standard-app-packages
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
# but you can also edit it by hand.
|
||||
|
||||
autopublish
|
||||
standard-app-packages
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
preserve-inputs
|
||||
accounts-ui
|
||||
accounts-password
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.6.4
|
||||
0.6.4.1
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
underscore
|
||||
backbone
|
||||
spiderable
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.6.4
|
||||
0.6.4.1
|
||||
|
||||
@@ -13,3 +13,4 @@ accounts-github
|
||||
accounts-password
|
||||
underscore
|
||||
accounts-facebook
|
||||
standard-app-packages
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
underscore
|
||||
jquery
|
||||
jquery-layout
|
||||
standard-app-packages
|
||||
|
||||
@@ -7,3 +7,4 @@ insecure
|
||||
preserve-inputs
|
||||
bootstrap
|
||||
random
|
||||
standard-app-packages
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
|
||||
less
|
||||
coffeescript
|
||||
standard-app-packages
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
# but you can also edit it by hand.
|
||||
|
||||
autopublish
|
||||
standard-app-packages
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
# but you can also edit it by hand.
|
||||
|
||||
autopublish
|
||||
standard-app-packages
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
if (Meteor.is_client) {
|
||||
if (Meteor.isClient) {
|
||||
Template.page.nodespec = function (fn) {
|
||||
var parts = [fn()];
|
||||
var replaceParts = function(regex, replacementFunc) {
|
||||
@@ -64,4 +64,4 @@ if (Meteor.is_client) {
|
||||
return new Handlebars.SafeString('<div class="spacer"> </div>');
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
# but you can also edit it by hand.
|
||||
|
||||
autopublish
|
||||
standard-app-packages
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Leaderboard = Meteor.connect("http://leader2.meteor.com/sockjs");
|
||||
Leaderboard = DDP.connect("http://leader2.meteor.com/sockjs");
|
||||
|
||||
// XXX I'd rather this be Leaderboard.Players.. can this API be easier?
|
||||
Players = new Meteor.Collection("players", {manager: Leaderboard});
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
autopublish
|
||||
preserve-inputs
|
||||
jsparse
|
||||
standard-app-packages
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
|
||||
if (Meteor.is_client) {
|
||||
if (Meteor.isClient) {
|
||||
Meteor.startup(function () {
|
||||
if (! Session.get("input"))
|
||||
Session.set("input", "var x = 3");
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
autopublish
|
||||
insecure
|
||||
preserve-inputs
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
# but you can also edit it by hand.
|
||||
|
||||
jquery
|
||||
backbone
|
||||
backbone
|
||||
standard-app-packages
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
jquery
|
||||
jquery-layout
|
||||
jquery-history
|
||||
standard-app-packages
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
standard-app-packages
|
||||
insecure
|
||||
jquery
|
||||
preserve-inputs
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.6.4
|
||||
0.6.4.1
|
||||
|
||||
2
meteor
2
meteor
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
BUNDLE_VERSION=0.3.11
|
||||
BUNDLE_VERSION=0.3.13
|
||||
|
||||
# OS Check. Put here because here is where we download the precompiled
|
||||
# bundles that are arch specific.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
// This is reactive.
|
||||
Meteor.userId = function () {
|
||||
return Meteor.default_connection.userId();
|
||||
return Meteor.connection.userId();
|
||||
};
|
||||
|
||||
var loggingIn = false;
|
||||
@@ -45,7 +45,7 @@ Meteor.user = function () {
|
||||
// - Updating the Meteor.loggingIn() reactive data source
|
||||
// - Calling the method in 'wait' mode
|
||||
// - On success, saving the resume token to localStorage
|
||||
// - On success, calling Meteor.default_connection.setUserId()
|
||||
// - On success, calling Meteor.connection.setUserId()
|
||||
// - Setting up an onReconnect handler which logs in with
|
||||
// the resume token
|
||||
//
|
||||
@@ -57,6 +57,7 @@ Meteor.user = function () {
|
||||
// its error will be passed to the callback).
|
||||
// - userCallback: Will be called with no arguments once the user is fully
|
||||
// logged in, or with the error on error.
|
||||
//
|
||||
Accounts.callLoginMethod = function (options) {
|
||||
options = _.extend({
|
||||
methodName: 'login',
|
||||
@@ -78,19 +79,19 @@ Accounts.callLoginMethod = function (options) {
|
||||
// getting the results of subscription rerun, we WILL NOT re-send this
|
||||
// method (because we never re-send methods whose results we've received)
|
||||
// but we WILL call loggedInAndDataReadyCallback at "reconnect quiesce"
|
||||
// time. This will lead to _makeClientLoggedIn(result.id) even though we
|
||||
// time. This will lead to makeClientLoggedIn(result.id) even though we
|
||||
// haven't actually sent a login method!
|
||||
//
|
||||
// But by making sure that we send this "resume" login in that case (and
|
||||
// calling _makeClientLoggedOut if it fails), we'll end up with an accurate
|
||||
// calling makeClientLoggedOut if it fails), we'll end up with an accurate
|
||||
// client-side userId. (It's important that livedata_connection guarantees
|
||||
// that the "reconnect quiesce"-time call to loggedInAndDataReadyCallback
|
||||
// will occur before the callback from the resume login call.)
|
||||
var onResultReceived = function (err, result) {
|
||||
if (err || !result || !result.token) {
|
||||
Meteor.default_connection.onReconnect = null;
|
||||
Meteor.connection.onReconnect = null;
|
||||
} else {
|
||||
Meteor.default_connection.onReconnect = function() {
|
||||
Meteor.connection.onReconnect = function() {
|
||||
reconnected = true;
|
||||
Accounts.callLoginMethod({
|
||||
methodArguments: [{resume: result.token}],
|
||||
@@ -100,7 +101,7 @@ Accounts.callLoginMethod = function (options) {
|
||||
_suppressLoggingIn: true,
|
||||
userCallback: function (error) {
|
||||
if (error) {
|
||||
Accounts._makeClientLoggedOut();
|
||||
makeClientLoggedOut();
|
||||
}
|
||||
options.userCallback(error);
|
||||
}});
|
||||
@@ -138,7 +139,7 @@ Accounts.callLoginMethod = function (options) {
|
||||
}
|
||||
|
||||
// Make the client logged in. (The user data should already be loaded!)
|
||||
Accounts._makeClientLoggedIn(result.id, result.token);
|
||||
makeClientLoggedIn(result.id, result.token);
|
||||
options.userCallback();
|
||||
};
|
||||
|
||||
@@ -151,15 +152,15 @@ Accounts.callLoginMethod = function (options) {
|
||||
loggedInAndDataReadyCallback);
|
||||
};
|
||||
|
||||
Accounts._makeClientLoggedOut = function() {
|
||||
Accounts._unstoreLoginToken();
|
||||
Meteor.default_connection.setUserId(null);
|
||||
Meteor.default_connection.onReconnect = null;
|
||||
makeClientLoggedOut = function() {
|
||||
unstoreLoginToken();
|
||||
Meteor.connection.setUserId(null);
|
||||
Meteor.connection.onReconnect = null;
|
||||
};
|
||||
|
||||
Accounts._makeClientLoggedIn = function(userId, token) {
|
||||
Accounts._storeLoginToken(userId, token);
|
||||
Meteor.default_connection.setUserId(userId);
|
||||
makeClientLoggedIn = function(userId, token) {
|
||||
storeLoginToken(userId, token);
|
||||
Meteor.connection.setUserId(userId);
|
||||
};
|
||||
|
||||
Meteor.logout = function (callback) {
|
||||
@@ -167,7 +168,7 @@ Meteor.logout = function (callback) {
|
||||
if (error) {
|
||||
callback && callback(error);
|
||||
} else {
|
||||
Accounts._makeClientLoggedOut();
|
||||
makeClientLoggedOut();
|
||||
callback && callback();
|
||||
}
|
||||
});
|
||||
@@ -182,6 +183,7 @@ var loginServicesHandle = Meteor.subscribe("meteor.loginServiceConfiguration");
|
||||
// A reactive function returning whether the loginServiceConfiguration
|
||||
// subscription is ready. Used by accounts-ui to hide the login button
|
||||
// until we have all the configuration loaded
|
||||
//
|
||||
Accounts.loginServicesConfigured = function () {
|
||||
return loginServicesHandle.ready();
|
||||
};
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
// @export Accounts
|
||||
if (typeof Accounts === 'undefined')
|
||||
Accounts = {};
|
||||
Accounts = {};
|
||||
|
||||
if (!Accounts._options) {
|
||||
Accounts._options = {};
|
||||
}
|
||||
// Currently this is read directly by packages like accounts-password
|
||||
// and accounts-ui-unstyled.
|
||||
Accounts._options = {};
|
||||
|
||||
// Set up config for the accounts system. Call this on both the client
|
||||
// and the server.
|
||||
@@ -21,6 +19,7 @@ if (!Accounts._options) {
|
||||
// client signups.
|
||||
// - forbidClientAccountCreation {Boolean}
|
||||
// Do not allow clients to create accounts directly.
|
||||
//
|
||||
Accounts.config = function(options) {
|
||||
// validate option keys
|
||||
var VALID_KEYS = ["sendVerificationEmail", "forbidClientAccountCreation"];
|
||||
@@ -45,6 +44,7 @@ Accounts.config = function(options) {
|
||||
// Users table. Don't use the normal autopublish, since we want to hide
|
||||
// some fields. Code to autopublish this is in accounts_server.js.
|
||||
// XXX Allow users to configure this collection name.
|
||||
//
|
||||
Meteor.users = new Meteor.Collection("users", {_preventAutopublish: true});
|
||||
// There is an allow call in accounts_server that restricts this
|
||||
// collection.
|
||||
|
||||
@@ -12,7 +12,7 @@ Meteor.userId = function () {
|
||||
// user expects. The way to make this work in a publish is to do
|
||||
// Meteor.find(this.userId()).observe and recompute when the user
|
||||
// record changes.
|
||||
var currentInvocation = Meteor._CurrentInvocation.get();
|
||||
var currentInvocation = DDP._CurrentInvocation.get();
|
||||
if (!currentInvocation)
|
||||
throw new Error("Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.");
|
||||
return currentInvocation.userId;
|
||||
@@ -36,20 +36,21 @@ Meteor.user = function () {
|
||||
// - `undefined`, meaning don't handle;
|
||||
// - {id: userId, token: *}, if the user logged in successfully.
|
||||
// - throw an error, if the user failed to log in.
|
||||
//
|
||||
Accounts.registerLoginHandler = function(handler) {
|
||||
Accounts._loginHandlers.push(handler);
|
||||
loginHandlers.push(handler);
|
||||
};
|
||||
|
||||
// list of all registered handlers.
|
||||
Accounts._loginHandlers = [];
|
||||
loginHandlers = [];
|
||||
|
||||
|
||||
// Try all of the registered login handlers until one of them doesn'
|
||||
// return `undefined`, meaning it handled this call to `login`. Return
|
||||
// that return value, which ought to be a {id/token} pair.
|
||||
var tryAllLoginHandlers = function (options) {
|
||||
for (var i = 0; i < Accounts._loginHandlers.length; ++i) {
|
||||
var handler = Accounts._loginHandlers[i];
|
||||
for (var i = 0; i < loginHandlers.length; ++i) {
|
||||
var handler = loginHandlers[i];
|
||||
var result = handler(options);
|
||||
if (result !== undefined)
|
||||
return result;
|
||||
@@ -80,7 +81,7 @@ Meteor.methods({
|
||||
|
||||
logout: function() {
|
||||
if (this._sessionData.loginToken && this.userId)
|
||||
Accounts._removeLoginToken(this.userId, this._sessionData.loginToken);
|
||||
removeLoginToken(this.userId, this._sessionData.loginToken);
|
||||
this.setUserId(null);
|
||||
}
|
||||
});
|
||||
@@ -109,11 +110,12 @@ Accounts.registerLoginHandler(function(options) {
|
||||
});
|
||||
|
||||
// Semi-public. Used by other login methods to generate tokens.
|
||||
//
|
||||
Accounts._generateStampedLoginToken = function () {
|
||||
return {token: Random.id(), when: +(new Date)};
|
||||
};
|
||||
|
||||
Accounts._removeLoginToken = function (userId, loginToken) {
|
||||
removeLoginToken = function (userId, loginToken) {
|
||||
Meteor.users.update(userId, {
|
||||
$pull: {
|
||||
"services.resume.loginTokens": { "token": loginToken }
|
||||
@@ -125,6 +127,7 @@ Accounts._removeLoginToken = function (userId, loginToken) {
|
||||
///
|
||||
/// CREATE USER HOOKS
|
||||
///
|
||||
|
||||
var onCreateUserHook = null;
|
||||
Accounts.onCreateUser = function (func) {
|
||||
if (onCreateUserHook)
|
||||
@@ -140,6 +143,8 @@ var defaultCreateUserHook = function (options, user) {
|
||||
user.profile = options.profile;
|
||||
return user;
|
||||
};
|
||||
|
||||
// Called by accounts-password
|
||||
Accounts.insertUserDoc = function (options, user) {
|
||||
// - clone user document, to protect from modification
|
||||
// - add createdAt timestamp
|
||||
@@ -223,6 +228,7 @@ Accounts.validateNewUser = function (func) {
|
||||
// (eg, profile)
|
||||
// @returns {Object} Object with token and id keys, like the result
|
||||
// of the "login" method.
|
||||
//
|
||||
Accounts.updateOrCreateUserFromExternalService = function(
|
||||
serviceName, serviceData, options) {
|
||||
options = _.clone(options || {});
|
||||
@@ -306,60 +312,64 @@ Meteor.publish(null, function() {
|
||||
// Accounts.addAutopublishFields Notably, this isn't implemented with
|
||||
// multiple publishes since DDP only merges only across top-level
|
||||
// fields, not subfields (such as 'services.facebook.accessToken')
|
||||
Accounts._autopublishFields = {
|
||||
var autopublishFields = {
|
||||
loggedInUser: ['profile', 'username', 'emails'],
|
||||
otherUsers: ['profile', 'username']
|
||||
};
|
||||
|
||||
// Add to the list of fields or subfields to be automatically
|
||||
// published if autopublish is on
|
||||
// published if autopublish is on. Must be called from top-level
|
||||
// code (ie, before Meteor.startup hooks run).
|
||||
//
|
||||
// @param opts {Object} with:
|
||||
// - forLoggedInUser {Array} Array of fields published to the logged-in user
|
||||
// - forOtherUsers {Array} Array of fields published to users that aren't logged in
|
||||
Accounts.addAutopublishFields = function(opts) {
|
||||
Accounts._autopublishFields.loggedInUser.push.apply(
|
||||
Accounts._autopublishFields.loggedInUser, opts.forLoggedInUser);
|
||||
Accounts._autopublishFields.otherUsers.push.apply(
|
||||
Accounts._autopublishFields.otherUsers, opts.forOtherUsers);
|
||||
autopublishFields.loggedInUser.push.apply(
|
||||
autopublishFields.loggedInUser, opts.forLoggedInUser);
|
||||
autopublishFields.otherUsers.push.apply(
|
||||
autopublishFields.otherUsers, opts.forOtherUsers);
|
||||
};
|
||||
|
||||
Meteor.default_server.onAutopublish(function () {
|
||||
// ['profile', 'username'] -> {profile: 1, username: 1}
|
||||
var toFieldSelector = function(fields) {
|
||||
return _.object(_.map(fields, function(field) {
|
||||
return [field, 1];
|
||||
}));
|
||||
};
|
||||
if (Package.autopublish) {
|
||||
// Use Meteor.startup to give other packages a chance to call
|
||||
// addAutopublishFields.
|
||||
Meteor.startup(function () {
|
||||
// ['profile', 'username'] -> {profile: 1, username: 1}
|
||||
var toFieldSelector = function(fields) {
|
||||
return _.object(_.map(fields, function(field) {
|
||||
return [field, 1];
|
||||
}));
|
||||
};
|
||||
|
||||
Meteor.server.publish(null, function () {
|
||||
if (this.userId) {
|
||||
return Meteor.users.find(
|
||||
{_id: this.userId},
|
||||
{fields: toFieldSelector(autopublishFields.loggedInUser)});
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, /*suppress autopublish warning*/{is_auto: true});
|
||||
|
||||
// XXX this publish is neither dedup-able nor is it optimized by our special
|
||||
// treatment of queries on a specific _id. Therefore this will have O(n^2)
|
||||
// run-time performance every time a user document is changed (eg someone
|
||||
// logging in). If this is a problem, we can instead write a manual publish
|
||||
// function which filters out fields based on 'this.userId'.
|
||||
Meteor.server.publish(null, function () {
|
||||
var selector;
|
||||
if (this.userId)
|
||||
selector = {_id: {$ne: this.userId}};
|
||||
else
|
||||
selector = {};
|
||||
|
||||
Meteor.default_server.publish(null, function () {
|
||||
if (this.userId) {
|
||||
return Meteor.users.find(
|
||||
{_id: this.userId},
|
||||
{fields: toFieldSelector(Accounts._autopublishFields.loggedInUser)});
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, /*suppress autopublish warning*/{is_auto: true});
|
||||
|
||||
// XXX this publish is neither dedup-able nor is it optimized by our
|
||||
// special treatment of queries on a specific _id. Therefore this
|
||||
// will have O(n^2) run-time performance every time a user document
|
||||
// is changed (eg someone logging in). If this is a problem, we can
|
||||
// instead write a manual publish function which filters out fields
|
||||
// based on 'this.userId'.
|
||||
Meteor.default_server.publish(null, function () {
|
||||
var selector;
|
||||
if (this.userId)
|
||||
selector = {_id: {$ne: this.userId}};
|
||||
else
|
||||
selector = {};
|
||||
|
||||
return Meteor.users.find(
|
||||
selector,
|
||||
{fields: toFieldSelector(Accounts._autopublishFields.otherUsers)});
|
||||
}, /*suppress autopublish warning*/{is_auto: true});
|
||||
});
|
||||
selector,
|
||||
{fields: toFieldSelector(autopublishFields.otherUsers)});
|
||||
}, /*suppress autopublish warning*/{is_auto: true});
|
||||
});
|
||||
}
|
||||
|
||||
// Publish all login service configuration fields other than secret.
|
||||
Meteor.publish("meteor.loginServiceConfiguration", function () {
|
||||
@@ -374,8 +384,13 @@ Meteor.methods({
|
||||
// Don't let random users configure a service we haven't added yet (so
|
||||
// that when we do later add it, it's set up with their configuration
|
||||
// instead of ours).
|
||||
if (!Accounts[options.service])
|
||||
// XXX if service configuration is oauth-specific then this code should
|
||||
// be in accounts-oauth; if it's not then the registry should be
|
||||
// in this package
|
||||
if (!(Accounts.oauth
|
||||
&& _.contains(Accounts.oauth.serviceNames(), options.service))) {
|
||||
throw new Meteor.Error(403, "Service unknown");
|
||||
}
|
||||
if (ServiceConfiguration.configurations.findOne({service: options.service}))
|
||||
throw new Meteor.Error(403, "Service " + options.service + " already configured");
|
||||
ServiceConfiguration.configurations.insert(options);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// seconds to synchronize login state between multiple tabs in the same
|
||||
// browser.
|
||||
|
||||
var lastLoginTokenWhenPolled;
|
||||
|
||||
// Login with a Meteor access token. This is the only public function
|
||||
// here.
|
||||
Meteor.loginWithToken = function (token, callback) {
|
||||
@@ -14,8 +16,8 @@ Meteor.loginWithToken = function (token, callback) {
|
||||
// Semi-internal API. Call this function to re-enable auto login after
|
||||
// if it was disabled at startup.
|
||||
Accounts._enableAutoLogin = function () {
|
||||
Accounts._preventAutoLogin = false;
|
||||
Accounts._pollStoredLoginToken();
|
||||
autoLoginEnabled = true;
|
||||
pollStoredLoginToken();
|
||||
};
|
||||
|
||||
|
||||
@@ -35,29 +37,32 @@ Accounts._isolateLoginTokenForTest = function () {
|
||||
userIdKey = userIdKey + Random.id();
|
||||
};
|
||||
|
||||
Accounts._storeLoginToken = function(userId, token) {
|
||||
storeLoginToken = function(userId, token) {
|
||||
Meteor._localStorage.setItem(userIdKey, userId);
|
||||
Meteor._localStorage.setItem(loginTokenKey, token);
|
||||
|
||||
// to ensure that the localstorage poller doesn't end up trying to
|
||||
// connect a second time
|
||||
Accounts._lastLoginTokenWhenPolled = token;
|
||||
lastLoginTokenWhenPolled = token;
|
||||
};
|
||||
|
||||
Accounts._unstoreLoginToken = function() {
|
||||
unstoreLoginToken = function() {
|
||||
Meteor._localStorage.removeItem(userIdKey);
|
||||
Meteor._localStorage.removeItem(loginTokenKey);
|
||||
|
||||
// to ensure that the localstorage poller doesn't end up trying to
|
||||
// connect a second time
|
||||
Accounts._lastLoginTokenWhenPolled = null;
|
||||
lastLoginTokenWhenPolled = null;
|
||||
};
|
||||
|
||||
Accounts._storedLoginToken = function() {
|
||||
// This is private, but it is exported for now because it is used by a
|
||||
// test in accounts-password.
|
||||
//
|
||||
var storedLoginToken = Accounts._storedLoginToken = function() {
|
||||
return Meteor._localStorage.getItem(loginTokenKey);
|
||||
};
|
||||
|
||||
Accounts._storedUserId = function() {
|
||||
var storedUserId = function() {
|
||||
return Meteor._localStorage.getItem(userIdKey);
|
||||
};
|
||||
|
||||
@@ -66,19 +71,19 @@ Accounts._storedUserId = function() {
|
||||
/// AUTO-LOGIN
|
||||
///
|
||||
|
||||
if (!Accounts._preventAutoLogin) {
|
||||
if (autoLoginEnabled) {
|
||||
// Immediately try to log in via local storage, so that any DDP
|
||||
// messages are sent after we have established our user account
|
||||
var token = Accounts._storedLoginToken();
|
||||
var token = storedLoginToken();
|
||||
if (token) {
|
||||
// On startup, optimistically present us as logged in while the
|
||||
// request is in flight. This reduces page flicker on startup.
|
||||
var userId = Accounts._storedUserId();
|
||||
userId && Meteor.default_connection.setUserId(userId);
|
||||
var userId = storedUserId();
|
||||
userId && Meteor.connection.setUserId(userId);
|
||||
Meteor.loginWithToken(token, function (err) {
|
||||
if (err) {
|
||||
Meteor._debug("Error logging in with token: " + err);
|
||||
Accounts._makeClientLoggedOut();
|
||||
makeClientLoggedOut();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -86,21 +91,21 @@ if (!Accounts._preventAutoLogin) {
|
||||
|
||||
// Poll local storage every 3 seconds to login if someone logged in in
|
||||
// another tab
|
||||
Accounts._lastLoginTokenWhenPolled = token;
|
||||
Accounts._pollStoredLoginToken = function() {
|
||||
if (Accounts._preventAutoLogin)
|
||||
lastLoginTokenWhenPolled = token;
|
||||
var pollStoredLoginToken = function() {
|
||||
if (! autoLoginEnabled)
|
||||
return;
|
||||
|
||||
var currentLoginToken = Accounts._storedLoginToken();
|
||||
var currentLoginToken = storedLoginToken();
|
||||
|
||||
// != instead of !== just to make sure undefined and null are treated the same
|
||||
if (Accounts._lastLoginTokenWhenPolled != currentLoginToken) {
|
||||
if (lastLoginTokenWhenPolled != currentLoginToken) {
|
||||
if (currentLoginToken)
|
||||
Meteor.loginWithToken(currentLoginToken); // XXX should we pass a callback here?
|
||||
else
|
||||
Meteor.logout();
|
||||
}
|
||||
Accounts._lastLoginTokenWhenPolled = currentLoginToken;
|
||||
lastLoginTokenWhenPolled = currentLoginToken;
|
||||
};
|
||||
|
||||
setInterval(Accounts._pollStoredLoginToken, 3000);
|
||||
setInterval(pollStoredLoginToken, 3000);
|
||||
|
||||
@@ -5,12 +5,14 @@ Package.describe({
|
||||
Package.on_use(function (api) {
|
||||
api.use('underscore', ['client', 'server']);
|
||||
api.use('localstorage', 'client');
|
||||
api.use('accounts-urls', ['client', 'server']);
|
||||
api.use('deps', 'client');
|
||||
api.use('check', 'server');
|
||||
api.use('random', ['client', 'server']);
|
||||
api.use('service-configuration', ['client', 'server']);
|
||||
|
||||
// needed for getting the currently logged-in user
|
||||
api.use('livedata', ['client', 'server']);
|
||||
|
||||
// need this because of the Meteor.users collection but in the future
|
||||
// we'd probably want to abstract this away
|
||||
api.use('mongo-livedata', ['client', 'server']);
|
||||
@@ -19,12 +21,21 @@ Package.on_use(function (api) {
|
||||
// {{currentUser}}. If not, no biggie.
|
||||
api.use('handlebars', 'client', {weak: true});
|
||||
|
||||
// Allow us to detect 'autopublish', and publish some Meteor.users fields if
|
||||
// it's loaded.
|
||||
api.use('autopublish', 'server', {weak: true});
|
||||
|
||||
api.export('Accounts');
|
||||
|
||||
api.add_files('accounts_common.js', ['client', 'server']);
|
||||
api.add_files('accounts_server.js', 'server');
|
||||
api.add_files('url_client.js', 'client');
|
||||
api.add_files('url_server.js', 'server');
|
||||
|
||||
// accounts_client must be before localstorage_token, because
|
||||
// localstorage_token attempts to call functions in accounts_client (eg
|
||||
// Accounts.callLoginMethod) on startup.
|
||||
// Accounts.callLoginMethod) on startup. And localstorage_token must be after
|
||||
// url_client, which sets autoLoginEnabled.
|
||||
api.add_files('accounts_client.js', 'client');
|
||||
api.add_files('localstorage_token.js', 'client');
|
||||
});
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// @export Accounts
|
||||
if (typeof Accounts === 'undefined')
|
||||
Accounts = {};
|
||||
autoLoginEnabled = true;
|
||||
|
||||
// reads a reset password token from the url's hash fragment, if it's
|
||||
// there. if so prevent automatically logging in since it could be
|
||||
@@ -13,7 +11,7 @@ if (typeof Accounts === 'undefined')
|
||||
var match;
|
||||
match = window.location.hash.match(/^\#\/reset-password\/(.*)$/);
|
||||
if (match) {
|
||||
Accounts._preventAutoLogin = true;
|
||||
autoLoginEnabled = false;
|
||||
Accounts._resetPasswordToken = match[1];
|
||||
window.location.hash = '';
|
||||
}
|
||||
@@ -30,7 +28,7 @@ if (match) {
|
||||
// in line with the hash fragment approach)
|
||||
match = window.location.hash.match(/^\#\/verify-email\/(.*)$/);
|
||||
if (match) {
|
||||
Accounts._preventAutoLogin = true;
|
||||
autoLoginEnabled = false;
|
||||
Accounts._verifyEmailToken = match[1];
|
||||
window.location.hash = '';
|
||||
}
|
||||
@@ -40,7 +38,7 @@ if (match) {
|
||||
// reset password links.
|
||||
match = window.location.hash.match(/^\#\/enroll-account\/(.*)$/);
|
||||
if (match) {
|
||||
Accounts._preventAutoLogin = true;
|
||||
autoLoginEnabled = false;
|
||||
Accounts._enrollAccountToken = match[1];
|
||||
window.location.hash = '';
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
// @export Accounts
|
||||
if (typeof Accounts === 'undefined')
|
||||
Accounts = {};
|
||||
// XXX These should probably not actually be public?
|
||||
|
||||
if (!Accounts.urls)
|
||||
Accounts.urls = {};
|
||||
Accounts.urls = {};
|
||||
|
||||
Accounts.urls.resetPassword = function (token) {
|
||||
return Meteor.absoluteUrl('#/reset-password/' + token);
|
||||
26
packages/accounts-facebook/facebook.js
Normal file
26
packages/accounts-facebook/facebook.js
Normal file
@@ -0,0 +1,26 @@
|
||||
Accounts.oauth.registerService('facebook');
|
||||
|
||||
if (Meteor.isClient) {
|
||||
Meteor.loginWithFacebook = function(options, callback) {
|
||||
// support a callback without options
|
||||
if (! callback && typeof options === "function") {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
|
||||
Facebook.requestCredential(options, credentialRequestCompleteCallback);
|
||||
};
|
||||
} else {
|
||||
Accounts.addAutopublishFields({
|
||||
// publish all fields including access token, which can legitimately
|
||||
// be used from the client (if transmitted over ssl or on
|
||||
// localhost). https://developers.facebook.com/docs/concepts/login/access-tokens-and-types/,
|
||||
// "Sharing of Access Tokens"
|
||||
forLoggedInUser: ['services.facebook'],
|
||||
forOtherUsers: [
|
||||
// https://www.facebook.com/help/167709519956542
|
||||
'services.facebook.id', 'services.facebook.username', 'services.facebook.gender'
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
Meteor.loginWithFacebook = function(options, callback) {
|
||||
var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
|
||||
Facebook.requestCredential(options, credentialRequestCompleteCallback);
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
if (!Accounts.facebook) {
|
||||
Accounts.facebook = {};
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
Accounts.oauth.registerService('facebook');
|
||||
|
||||
Accounts.addAutopublishFields({
|
||||
// publish all fields including access token, which can legitimately
|
||||
// be used from the client (if transmitted over ssl or on
|
||||
// localhost). https://developers.facebook.com/docs/concepts/login/access-tokens-and-types/,
|
||||
// "Sharing of Access Tokens"
|
||||
forLoggedInUser: ['services.facebook'],
|
||||
forOtherUsers: [
|
||||
// https://www.facebook.com/help/167709519956542
|
||||
'services.facebook.id', 'services.facebook.username', 'services.facebook.gender'
|
||||
]
|
||||
});
|
||||
@@ -11,7 +11,5 @@ Package.on_use(function(api) {
|
||||
|
||||
api.add_files('facebook_login_button.css', 'client');
|
||||
|
||||
api.add_files('facebook_common.js', ['client', 'server']);
|
||||
api.add_files('facebook_server.js', 'server');
|
||||
api.add_files('facebook_client.js', 'client');
|
||||
api.add_files("facebook.js");
|
||||
});
|
||||
|
||||
22
packages/accounts-github/github.js
Normal file
22
packages/accounts-github/github.js
Normal file
@@ -0,0 +1,22 @@
|
||||
Accounts.oauth.registerService('github');
|
||||
|
||||
if (Meteor.isClient) {
|
||||
Meteor.loginWithGithub = function(options, callback) {
|
||||
// support a callback without options
|
||||
if (! callback && typeof options === "function") {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
|
||||
Github.requestCredential(options, credentialRequestCompleteCallback);
|
||||
};
|
||||
} else {
|
||||
Accounts.addAutopublishFields({
|
||||
// not sure whether the github api can be used from the browser,
|
||||
// thus not sure if we should be sending access tokens; but we do it
|
||||
// for all other oauth2 providers, and it may come in handy.
|
||||
forLoggedInUser: ['services.github'],
|
||||
forOtherUsers: ['services.github.username']
|
||||
});
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
Meteor.loginWithGithub = function(options, callback) {
|
||||
var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
|
||||
Github.requestCredential(options, credentialRequestCompleteCallback);
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
if (!Accounts.github) {
|
||||
Accounts.github = {};
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
Accounts.oauth.registerService('github');
|
||||
|
||||
Accounts.addAutopublishFields({
|
||||
// not sure whether the github api can be used from the browser,
|
||||
// thus not sure if we should be sending access tokens; but we do it
|
||||
// for all other oauth2 providers, and it may come in handy.
|
||||
forLoggedInUser: ['services.github'],
|
||||
forOtherUsers: ['services.github.username']
|
||||
});
|
||||
@@ -11,7 +11,5 @@ Package.on_use(function(api) {
|
||||
|
||||
api.add_files('github_login_button.css', 'client');
|
||||
|
||||
api.add_files('github_common.js', ['client', 'server']);
|
||||
api.add_files('github_server.js', 'server');
|
||||
api.add_files('github_client.js', 'client');
|
||||
api.add_files("github.js");
|
||||
});
|
||||
|
||||
30
packages/accounts-google/google.js
Normal file
30
packages/accounts-google/google.js
Normal file
@@ -0,0 +1,30 @@
|
||||
Accounts.oauth.registerService('google');
|
||||
|
||||
if (Meteor.isClient) {
|
||||
Meteor.loginWithGoogle = function(options, callback) {
|
||||
// support a callback without options
|
||||
if (! callback && typeof options === "function") {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
|
||||
Google.requestCredential(options, credentialRequestCompleteCallback);
|
||||
};
|
||||
} else {
|
||||
Accounts.addAutopublishFields({
|
||||
forLoggedInUser: _.map(
|
||||
// publish access token since it can be used from the client (if
|
||||
// transmitted over ssl or on
|
||||
// localhost). https://developers.google.com/accounts/docs/OAuth2UserAgent
|
||||
// refresh token probably shouldn't be sent down.
|
||||
Google.whitelistedFields.concat(['accessToken', 'expiresAt']), // don't publish refresh token
|
||||
function (subfield) { return 'services.google.' + subfield; }),
|
||||
|
||||
forOtherUsers: _.map(
|
||||
// even with autopublish, no legitimate web app should be
|
||||
// publishing all users' emails
|
||||
_.without(Google.whitelistedFields, 'email', 'verified_email'),
|
||||
function (subfield) { return 'services.google.' + subfield; })
|
||||
});
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
Meteor.loginWithGoogle = function(options, callback) {
|
||||
var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
|
||||
Google.requestCredential(options, credentialRequestCompleteCallback);
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
if (!Accounts.google) {
|
||||
Accounts.google = {};
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
Accounts.oauth.registerService('google');
|
||||
|
||||
Accounts.addAutopublishFields({
|
||||
forLoggedInUser: _.map(
|
||||
// publish access token since it can be used from the client (if
|
||||
// transmitted over ssl or on
|
||||
// localhost). https://developers.google.com/accounts/docs/OAuth2UserAgent
|
||||
// refresh token probably shouldn't be sent down.
|
||||
Google.whitelistedFields.concat(['accessToken', 'expiresAt']), // don't publish refresh token
|
||||
function (subfield) { return 'services.google.' + subfield; }),
|
||||
|
||||
forOtherUsers: _.map(
|
||||
// even with autopublish, no legitimate web app should be
|
||||
// publishing all users' emails
|
||||
_.without(Google.whitelistedFields, 'email', 'verified_email'),
|
||||
function (subfield) { return 'services.google.' + subfield; })
|
||||
});
|
||||
@@ -12,7 +12,5 @@ Package.on_use(function(api) {
|
||||
|
||||
api.add_files('google_login_button.css', 'client');
|
||||
|
||||
api.add_files('google_common.js', ['client', 'server']);
|
||||
api.add_files('google_server.js', 'server');
|
||||
api.add_files('google_client.js', 'client');
|
||||
api.add_files("google.js");
|
||||
});
|
||||
|
||||
22
packages/accounts-meetup/meetup.js
Normal file
22
packages/accounts-meetup/meetup.js
Normal file
@@ -0,0 +1,22 @@
|
||||
Accounts.oauth.registerService('meetup');
|
||||
|
||||
if (Meteor.isClient) {
|
||||
Meteor.loginWithMeetup = function(options, callback) {
|
||||
// support a callback without options
|
||||
if (! callback && typeof options === "function") {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
|
||||
Meetup.requestCredential(options, credentialRequestCompleteCallback);
|
||||
};
|
||||
} else {
|
||||
Accounts.addAutopublishFields({
|
||||
// publish all fields including access token, which can legitimately
|
||||
// be used from the client (if transmitted over ssl or on
|
||||
// localhost). http://www.meetup.com/meetup_api/auth/#oauth2implicit
|
||||
forLoggedInUser: ['services.meetup'],
|
||||
forOtherUsers: ['services.meetup.id']
|
||||
});
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
Meteor.loginWithMeetup = function(options, callback) {
|
||||
var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
|
||||
Meetup.requestCredential(options, credentialRequestCompleteCallback);
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
if (!Accounts.meetup) {
|
||||
Accounts.meetup = {};
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
Accounts.oauth.registerService('meetup');
|
||||
|
||||
Accounts.addAutopublishFields({
|
||||
// publish all fields including access token, which can legitimately
|
||||
// be used from the client (if transmitted over ssl or on
|
||||
// localhost). http://www.meetup.com/meetup_api/auth/#oauth2implicit
|
||||
forLoggedInUser: ['services.meetup'],
|
||||
forOtherUsers: ['services.meetup.id']
|
||||
});
|
||||
|
||||
|
||||
@@ -11,7 +11,5 @@ Package.on_use(function(api) {
|
||||
|
||||
api.add_files('meetup_login_button.css', 'client');
|
||||
|
||||
api.add_files('meetup_common.js', ['client', 'server']);
|
||||
api.add_files('meetup_server.js', 'server');
|
||||
api.add_files('meetup_client.js', 'client');
|
||||
api.add_files("meetup.js");
|
||||
});
|
||||
|
||||
@@ -19,9 +19,9 @@ Accounts.oauth.tryLoginAfterPopupClosed = function(credentialToken, callback) {
|
||||
Accounts.oauth.credentialRequestCompleteHandler = function(callback) {
|
||||
return function (credentialTokenOrError) {
|
||||
if(credentialTokenOrError && credentialTokenOrError instanceof Error) {
|
||||
callback(credentialTokenOrError);
|
||||
callback && callback(credentialTokenOrError);
|
||||
} else {
|
||||
Accounts.oauth.tryLoginAfterPopupClosed(credentialTokenOrError, callback);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1 +1,24 @@
|
||||
Accounts.oauth = {};
|
||||
Accounts.oauth = {};
|
||||
|
||||
var services = {};
|
||||
|
||||
// Helper for registering OAuth based accounts packages.
|
||||
// On the server, adds an index to the user collection.
|
||||
Accounts.oauth.registerService = function (name) {
|
||||
if (_.has(services, name))
|
||||
throw new Error("Duplicate service: " + name);
|
||||
services[name] = true;
|
||||
|
||||
if (Meteor.server) {
|
||||
// Accounts.updateOrCreateUserFromExternalService does a lookup by this id,
|
||||
// so this should be a unique index. You might want to add indexes for other
|
||||
// fields returned by your service (eg services.github.login) but you can do
|
||||
// that in your app.
|
||||
Meteor.users._ensureIndex('services.' + name + '.id',
|
||||
{unique: 1, sparse: 1});
|
||||
}
|
||||
};
|
||||
|
||||
Accounts.oauth.serviceNames = function () {
|
||||
return _.keys(services);
|
||||
};
|
||||
|
||||
@@ -1,24 +1,3 @@
|
||||
// Helper for registering OAuth based accounts packages.
|
||||
// Adds an index to the user collection.
|
||||
Accounts.oauth.registerService = function (name) {
|
||||
// Accounts.updateOrCreateUserFromExternalService does a lookup by this id,
|
||||
// so this should be a unique index. You might want to add indexes for other
|
||||
// fields returned by your service (eg services.github.login) but you can do
|
||||
// that in your app.
|
||||
Meteor.users._ensureIndex('services.' + name + '.id',
|
||||
{unique: 1, sparse: 1});
|
||||
|
||||
};
|
||||
|
||||
// For test cleanup only. (Mongo has a limit as to how many indexes it can have
|
||||
// per collection.)
|
||||
Accounts.oauth._unregisterService = function (name) {
|
||||
var index = {};
|
||||
index['services.' + name + '.id'] = 1;
|
||||
Meteor.users._dropIndex(index);
|
||||
};
|
||||
|
||||
|
||||
// Listen to calls to `login` with an oauth option set. This is where
|
||||
// users actually get logged in to meteor via oauth.
|
||||
Accounts.registerLoginHandler(function (options) {
|
||||
|
||||
@@ -13,7 +13,7 @@ Package.on_use(function (api) {
|
||||
api.imply('accounts-base', ['client', 'server']);
|
||||
api.use('oauth', 'server');
|
||||
|
||||
api.add_files('oauth_common.js', ['client', 'server']);
|
||||
api.add_files('oauth_common.js');
|
||||
api.add_files('oauth_client.js', 'client');
|
||||
api.add_files('oauth_server.js', 'server');
|
||||
});
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
// the string "intercept", storing them in an array that can then
|
||||
// be retrieved using the getInterceptedEmails method
|
||||
//
|
||||
var oldEmailSend = Email.send;
|
||||
var interceptedEmails = {}; // (email address) -> (array of contents)
|
||||
|
||||
Email.send = function (options) {
|
||||
EmailTest.hookSend(function (options) {
|
||||
var to = options.to;
|
||||
if (to.indexOf('intercept') === -1) {
|
||||
oldEmailSend(options);
|
||||
return true; // go ahead and send
|
||||
} else {
|
||||
if (!interceptedEmails[to])
|
||||
interceptedEmails[to] = [];
|
||||
|
||||
interceptedEmails[to].push(options.text);
|
||||
return false; // skip sending
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
getInterceptedEmails: function (email) {
|
||||
|
||||
@@ -11,16 +11,17 @@ Package.on_use(function(api) {
|
||||
api.use('random', ['server']);
|
||||
api.use('check', ['server']);
|
||||
api.use('underscore');
|
||||
api.use('livedata', ['client', 'server']);
|
||||
|
||||
api.add_files('email_templates.js', 'server');
|
||||
api.add_files('password_server.js', 'server');
|
||||
api.add_files('password_client.js', 'client');
|
||||
api.add_files('password_common.js', ['server', 'client']);
|
||||
});
|
||||
|
||||
Package.on_test(function(api) {
|
||||
api.use(['accounts-password', 'tinytest', 'test-helpers', 'deps',
|
||||
'accounts-base', 'random', 'email', 'underscore', 'check']);
|
||||
'accounts-base', 'random', 'email', 'underscore', 'check',
|
||||
'livedata']);
|
||||
api.add_files('password_tests_setup.js', 'server');
|
||||
api.add_files('password_tests.js', ['client', 'server']);
|
||||
api.add_files('email_tests_setup.js', 'server');
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// @param password {String}
|
||||
// @param callback {Function(error|undefined)}
|
||||
Meteor.loginWithPassword = function (selector, password, callback) {
|
||||
var srp = new Meteor._srp.Client(password);
|
||||
var srp = new SRP.Client(password);
|
||||
var request = srp.startExchange();
|
||||
|
||||
if (typeof selector === 'string')
|
||||
@@ -50,7 +50,7 @@ Accounts.createUser = function (options, callback) {
|
||||
|
||||
if (!options.password)
|
||||
throw new Error("Must set options.password");
|
||||
var verifier = Meteor._srp.generateVerifier(options.password);
|
||||
var verifier = SRP.generateVerifier(options.password);
|
||||
// strip old password, replacing with the verifier object
|
||||
delete options.password;
|
||||
options.srp = verifier;
|
||||
@@ -77,7 +77,7 @@ Accounts.changePassword = function (oldPassword, newPassword, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
var verifier = Meteor._srp.generateVerifier(newPassword);
|
||||
var verifier = SRP.generateVerifier(newPassword);
|
||||
|
||||
if (!oldPassword) {
|
||||
Meteor.apply('changePassword', [{srp: verifier}], function (error, result) {
|
||||
@@ -89,7 +89,7 @@ Accounts.changePassword = function (oldPassword, newPassword, callback) {
|
||||
}
|
||||
});
|
||||
} else { // oldPassword
|
||||
var srp = new Meteor._srp.Client(oldPassword);
|
||||
var srp = new SRP.Client(oldPassword);
|
||||
var request = srp.startExchange();
|
||||
request.user = {id: Meteor.user()._id};
|
||||
Meteor.apply('beginPasswordExchange', [request], function (error, result) {
|
||||
@@ -142,7 +142,7 @@ Accounts.resetPassword = function(token, newPassword, callback) {
|
||||
if (!newPassword)
|
||||
throw new Error("Need to pass newPassword");
|
||||
|
||||
var verifier = Meteor._srp.generateVerifier(newPassword);
|
||||
var verifier = SRP.generateVerifier(newPassword);
|
||||
Accounts.callLoginMethod({
|
||||
methodName: 'resetPassword',
|
||||
methodArguments: [token, verifier],
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Accounts.password = {};
|
||||
@@ -62,7 +62,7 @@ Meteor.methods({beginPasswordExchange: function (request) {
|
||||
throw new Meteor.Error(403, "User has no password set");
|
||||
|
||||
var verifier = user.services.password.srp;
|
||||
var srp = new Meteor._srp.Server(verifier);
|
||||
var srp = new SRP.Server(verifier);
|
||||
var challenge = srp.issueChallenge({A: request.A});
|
||||
|
||||
// save off results in the current session so we can verify them
|
||||
@@ -82,7 +82,7 @@ Accounts.registerLoginHandler(function (options) {
|
||||
|
||||
// we're always called from within a 'login' method, so this should
|
||||
// be safe.
|
||||
var currentInvocation = Meteor._CurrentInvocation.get();
|
||||
var currentInvocation = DDP._CurrentInvocation.get();
|
||||
var serialized = currentInvocation._sessionData.srpChallenge;
|
||||
if (!serialized || serialized.M !== options.srp.M)
|
||||
throw new Meteor.Error(403, "Incorrect password");
|
||||
@@ -127,7 +127,7 @@ Accounts.registerLoginHandler(function (options) {
|
||||
// Just check the verifier output when the same identity and salt
|
||||
// are passed. Don't bother with a full exchange.
|
||||
var verifier = user.services.password.srp;
|
||||
var newVerifier = Meteor._srp.generateVerifier(options.password, {
|
||||
var newVerifier = SRP.generateVerifier(options.password, {
|
||||
identity: verifier.identity, salt: verifier.salt});
|
||||
|
||||
if (verifier.verifier !== newVerifier.verifier)
|
||||
@@ -155,7 +155,7 @@ Meteor.methods({changePassword: function (options) {
|
||||
// password. For now, we don't allow changePassword without knowing the old
|
||||
// password.
|
||||
M: String,
|
||||
srp: Match.Optional(Meteor._srp.matchVerifier),
|
||||
srp: Match.Optional(SRP.matchVerifier),
|
||||
password: Match.Optional(String)
|
||||
});
|
||||
|
||||
@@ -170,7 +170,7 @@ Meteor.methods({changePassword: function (options) {
|
||||
|
||||
var verifier = options.srp;
|
||||
if (!verifier && options.password) {
|
||||
verifier = Meteor._srp.generateVerifier(options.password);
|
||||
verifier = SRP.generateVerifier(options.password);
|
||||
}
|
||||
if (!verifier)
|
||||
throw new Meteor.Error(400, "Invalid verifier");
|
||||
@@ -192,7 +192,7 @@ Accounts.setPassword = function (userId, newPassword) {
|
||||
var user = Meteor.users.findOne(userId);
|
||||
if (!user)
|
||||
throw new Meteor.Error(403, "User not found");
|
||||
var newVerifier = Meteor._srp.generateVerifier(newPassword);
|
||||
var newVerifier = SRP.generateVerifier(newPassword);
|
||||
|
||||
Meteor.users.update({_id: user._id}, {
|
||||
$set: {'services.password.srp': newVerifier}});
|
||||
@@ -217,6 +217,7 @@ Meteor.methods({forgotPassword: function (options) {
|
||||
|
||||
// send the user an email with a link that when opened allows the user
|
||||
// to set a new password, without the old password.
|
||||
//
|
||||
Accounts.sendResetPasswordEmail = function (userId, email) {
|
||||
// Make sure the user exists, and email is one of their addresses.
|
||||
var user = Meteor.users.findOne(userId);
|
||||
@@ -252,8 +253,9 @@ Accounts.sendResetPasswordEmail = function (userId, email) {
|
||||
// to choose their password. The email must be one of the addresses in the
|
||||
// user's emails field, or undefined to pick the first email automatically.
|
||||
//
|
||||
// This is not called automatically, it must be called manually if you
|
||||
// This is not called automatically. It must be called manually if you
|
||||
// want to use enrollment emails.
|
||||
//
|
||||
Accounts.sendEnrollmentEmail = function (userId, email) {
|
||||
// XXX refactor! This is basically identical to sendResetPasswordEmail.
|
||||
|
||||
@@ -293,7 +295,7 @@ Accounts.sendEnrollmentEmail = function (userId, email) {
|
||||
// the users password, and log them in.
|
||||
Meteor.methods({resetPassword: function (token, newVerifier) {
|
||||
check(token, String);
|
||||
check(newVerifier, Meteor._srp.matchVerifier);
|
||||
check(newVerifier, SRP.matchVerifier);
|
||||
|
||||
var user = Meteor.users.findOne({
|
||||
"services.password.reset.token": ""+token});
|
||||
@@ -329,6 +331,7 @@ Meteor.methods({resetPassword: function (token, newVerifier) {
|
||||
|
||||
// send the user an email with a link that when opened marks that
|
||||
// address as verified
|
||||
//
|
||||
Accounts.sendVerificationEmail = function (userId, address) {
|
||||
// XXX Also generate a link using which someone can delete this
|
||||
// account if they own said address but weren't those who created
|
||||
@@ -428,7 +431,7 @@ var createUser = function (options) {
|
||||
username: Match.Optional(String),
|
||||
email: Match.Optional(String),
|
||||
password: Match.Optional(String),
|
||||
srp: Match.Optional(Meteor._srp.matchVerifier)
|
||||
srp: Match.Optional(SRP.matchVerifier)
|
||||
}));
|
||||
|
||||
var username = options.username;
|
||||
@@ -442,7 +445,7 @@ var createUser = function (options) {
|
||||
if (options.password) {
|
||||
if (options.srp)
|
||||
throw new Meteor.Error(400, "Don't pass both password and srp in options");
|
||||
options.srp = Meteor._srp.generateVerifier(options.password);
|
||||
options.srp = SRP.generateVerifier(options.password);
|
||||
}
|
||||
|
||||
var user = {services: {}};
|
||||
@@ -493,6 +496,7 @@ Meteor.methods({createUser: function (options) {
|
||||
// which is always empty when called from the createUser method? eg, "admin:
|
||||
// true", which we want to prevent the client from setting, but which a custom
|
||||
// method calling Accounts.createUser could set?
|
||||
//
|
||||
Accounts.createUser = function (options, callback) {
|
||||
options = _.clone(options);
|
||||
options.generateLoginToken = false;
|
||||
|
||||
@@ -11,11 +11,8 @@ Package.on_use(function(api) {
|
||||
api.use('twitter', ['client', 'server']);
|
||||
|
||||
api.use('http', ['client', 'server']);
|
||||
api.use('templating', 'client');
|
||||
|
||||
api.add_files('twitter_login_button.css', 'client');
|
||||
|
||||
api.add_files('twitter_common.js', ['client', 'server']);
|
||||
api.add_files('twitter_server.js', 'server');
|
||||
api.add_files('twitter_client.js', 'client');
|
||||
api.add_files("twitter.js");
|
||||
});
|
||||
|
||||
24
packages/accounts-twitter/twitter.js
Normal file
24
packages/accounts-twitter/twitter.js
Normal file
@@ -0,0 +1,24 @@
|
||||
Accounts.oauth.registerService('twitter');
|
||||
|
||||
if (Meteor.isClient) {
|
||||
Meteor.loginWithTwitter = function(options, callback) {
|
||||
// support a callback without options
|
||||
if (! callback && typeof options === "function") {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
|
||||
Twitter.requestCredential(options, credentialRequestCompleteCallback);
|
||||
};
|
||||
} else {
|
||||
var autopublishedFields = _.map(
|
||||
// don't send access token. https://dev.twitter.com/discussions/5025
|
||||
Twitter.whitelistedFields.concat(['id', 'screenName']),
|
||||
function (subfield) { return 'services.twitter.' + subfield; });
|
||||
|
||||
Accounts.addAutopublishFields({
|
||||
forLoggedInUser: autopublishedFields,
|
||||
forOtherUsers: autopublishedFields
|
||||
});
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
Meteor.loginWithTwitter = function(options, callback) {
|
||||
var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
|
||||
Twitter.requestCredential(options, credentialRequestCompleteCallback);
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
if (!Accounts.twitter) {
|
||||
Accounts.twitter = {};
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
Accounts.oauth.registerService('twitter');
|
||||
|
||||
var autopublishedFields = _.map(
|
||||
// don't send access token. https://dev.twitter.com/discussions/5025
|
||||
Twitter.whitelistedFields.concat(['id', 'screenName']),
|
||||
function (subfield) { return 'services.twitter.' + subfield; });
|
||||
|
||||
Accounts.addAutopublishFields({
|
||||
forLoggedInUser: autopublishedFields,
|
||||
forOtherUsers: autopublishedFields
|
||||
});
|
||||
@@ -1,13 +1,9 @@
|
||||
if (!Accounts.ui)
|
||||
Accounts.ui = {};
|
||||
|
||||
if (!Accounts.ui._options) {
|
||||
Accounts.ui._options = {
|
||||
requestPermissions: {},
|
||||
requestOfflineToken: {}
|
||||
};
|
||||
}
|
||||
Accounts.ui = {};
|
||||
|
||||
Accounts.ui._options = {
|
||||
requestPermissions: {},
|
||||
requestOfflineToken: {}
|
||||
};
|
||||
|
||||
Accounts.ui.config = function(options) {
|
||||
// validate options keys
|
||||
@@ -62,7 +58,7 @@ Accounts.ui.config = function(options) {
|
||||
}
|
||||
};
|
||||
|
||||
Accounts.ui._passwordSignupFields = function () {
|
||||
passwordSignupFields = function () {
|
||||
return Accounts.ui._options.passwordSignupFields || "EMAIL_ONLY";
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
if (!Accounts._loginButtons)
|
||||
Accounts._loginButtons = {};
|
||||
|
||||
// for convenience
|
||||
var loginButtonsSession = Accounts._loginButtonsSession;
|
||||
|
||||
@@ -26,20 +23,105 @@ Template._loginButtons.preserve({
|
||||
'input[id]': Spark._labelFromIdOrName
|
||||
});
|
||||
|
||||
//
|
||||
// helpers
|
||||
//
|
||||
|
||||
displayName = function () {
|
||||
var user = Meteor.user();
|
||||
if (!user)
|
||||
return '';
|
||||
|
||||
if (user.profile && user.profile.name)
|
||||
return user.profile.name;
|
||||
if (user.username)
|
||||
return user.username;
|
||||
if (user.emails && user.emails[0] && user.emails[0].address)
|
||||
return user.emails[0].address;
|
||||
|
||||
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}}.
|
||||
//
|
||||
// don't cache the output of this function: if called during startup (before
|
||||
// oauth packages load) it might not include them all.
|
||||
//
|
||||
// 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
|
||||
getLoginServices = function () {
|
||||
var self = this;
|
||||
|
||||
// First look for OAuth services.
|
||||
var services = Package['accounts-oauth'] ? Accounts.oauth.serviceNames() : [];
|
||||
|
||||
// Be equally kind to all login services. This also preserves
|
||||
// backwards-compatibility. (But maybe order should be
|
||||
// configurable?)
|
||||
services.sort();
|
||||
|
||||
// Add password, if it's there; it must come last.
|
||||
if (hasPasswordService())
|
||||
services.push('password');
|
||||
|
||||
return _.map(services, function(name) {
|
||||
return {name: name};
|
||||
});
|
||||
};
|
||||
|
||||
hasPasswordService = function () {
|
||||
return !!Package['accounts-password'];
|
||||
};
|
||||
|
||||
dropdown = function () {
|
||||
return hasPasswordService() || getLoginServices().length > 1;
|
||||
};
|
||||
|
||||
// XXX improve these. should this be in accounts-password instead?
|
||||
//
|
||||
// XXX these will become configurable, and will be validated on
|
||||
// the server as well.
|
||||
validateUsername = function (username) {
|
||||
if (username.length >= 3) {
|
||||
return true;
|
||||
} else {
|
||||
loginButtonsSession.errorMessage("Username must be at least 3 characters long");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
validateEmail = function (email) {
|
||||
if (passwordSignupFields() === "USERNAME_AND_OPTIONAL_EMAIL" && email === '')
|
||||
return true;
|
||||
|
||||
if (email.indexOf('@') !== -1) {
|
||||
return true;
|
||||
} else {
|
||||
loginButtonsSession.errorMessage("Invalid email");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
validatePassword = function (password) {
|
||||
if (password.length >= 6) {
|
||||
return true;
|
||||
} else {
|
||||
loginButtonsSession.errorMessage("Password must be at least 6 characters long");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// loginButtonLoggedOut template
|
||||
//
|
||||
|
||||
Template._loginButtonsLoggedOut.dropdown = function () {
|
||||
return Accounts._loginButtons.dropdown();
|
||||
};
|
||||
Template._loginButtonsLoggedOut.dropdown = dropdown;
|
||||
|
||||
Template._loginButtonsLoggedOut.services = function () {
|
||||
return Accounts._loginButtons.getLoginServices();
|
||||
};
|
||||
Template._loginButtonsLoggedOut.services = getLoginServices;
|
||||
|
||||
Template._loginButtonsLoggedOut.singleService = function () {
|
||||
var services = Accounts._loginButtons.getLoginServices();
|
||||
var services = getLoginServices();
|
||||
if (services.length !== 1)
|
||||
throw new Error(
|
||||
"Shouldn't be rendering this template with more than one configured service");
|
||||
@@ -57,9 +139,7 @@ Template._loginButtonsLoggedOut.configurationLoaded = function () {
|
||||
|
||||
// decide whether we should show a dropdown rather than a row of
|
||||
// buttons
|
||||
Template._loginButtonsLoggedIn.dropdown = function () {
|
||||
return Accounts._loginButtons.dropdown();
|
||||
};
|
||||
Template._loginButtonsLoggedIn.dropdown = dropdown;
|
||||
|
||||
|
||||
|
||||
@@ -67,9 +147,7 @@ Template._loginButtonsLoggedIn.dropdown = function () {
|
||||
// loginButtonsLoggedInSingleLogoutButton template
|
||||
//
|
||||
|
||||
Template._loginButtonsLoggedInSingleLogoutButton.displayName = function () {
|
||||
return Accounts._loginButtons.displayName();
|
||||
};
|
||||
Template._loginButtonsLoggedInSingleLogoutButton.displayName = displayName;
|
||||
|
||||
|
||||
|
||||
@@ -90,113 +168,5 @@ Template._loginButtonsMessages.infoMessage = function () {
|
||||
// loginButtonsLoggingInPadding template
|
||||
//
|
||||
|
||||
Template._loginButtonsLoggingInPadding.dropdown = function () {
|
||||
return Accounts._loginButtons.dropdown();
|
||||
};
|
||||
Template._loginButtonsLoggingInPadding.dropdown = dropdown;
|
||||
|
||||
|
||||
//
|
||||
// helpers
|
||||
//
|
||||
|
||||
Accounts._loginButtons.displayName = function () {
|
||||
var user = Meteor.user();
|
||||
if (!user)
|
||||
return '';
|
||||
|
||||
if (user.profile && user.profile.name)
|
||||
return user.profile.name;
|
||||
if (user.username)
|
||||
return user.username;
|
||||
if (user.emails && user.emails[0] && user.emails[0].address)
|
||||
return user.emails[0].address;
|
||||
|
||||
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 self = this;
|
||||
var services = [];
|
||||
|
||||
// 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 () {
|
||||
return Accounts.password;
|
||||
};
|
||||
|
||||
Accounts._loginButtons.dropdown = function () {
|
||||
return Accounts._loginButtons.hasPasswordService() || Accounts._loginButtons.getLoginServices().length > 1;
|
||||
};
|
||||
|
||||
// XXX improve these. should this be in accounts-password instead?
|
||||
//
|
||||
// XXX these will become configurable, and will be validated on
|
||||
// the server as well.
|
||||
Accounts._loginButtons.validateUsername = function (username) {
|
||||
if (username.length >= 3) {
|
||||
return true;
|
||||
} else {
|
||||
loginButtonsSession.errorMessage("Username must be at least 3 characters long");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
Accounts._loginButtons.validateEmail = function (email) {
|
||||
if (Accounts.ui._passwordSignupFields() === "USERNAME_AND_OPTIONAL_EMAIL" && email === '')
|
||||
return true;
|
||||
|
||||
if (email.indexOf('@') !== -1) {
|
||||
return true;
|
||||
} else {
|
||||
loginButtonsSession.errorMessage("Invalid email");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
Accounts._loginButtons.validatePassword = function (password) {
|
||||
if (password.length >= 6) {
|
||||
return true;
|
||||
} else {
|
||||
loginButtonsSession.errorMessage("Password must be at least 6 characters long");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ Template._resetPasswordDialog.events({
|
||||
var resetPassword = function () {
|
||||
loginButtonsSession.resetMessages();
|
||||
var newPassword = document.getElementById('reset-password-new-password').value;
|
||||
if (!Accounts._loginButtons.validatePassword(newPassword))
|
||||
if (!validatePassword(newPassword))
|
||||
return;
|
||||
|
||||
Accounts.resetPassword(
|
||||
@@ -94,7 +94,7 @@ Template._enrollAccountDialog.events({
|
||||
var enrollAccount = function () {
|
||||
loginButtonsSession.resetMessages();
|
||||
var password = document.getElementById('enroll-account-password').value;
|
||||
if (!Accounts._loginButtons.validatePassword(password))
|
||||
if (!validatePassword(password))
|
||||
return;
|
||||
|
||||
Accounts.resetPassword(
|
||||
@@ -141,7 +141,7 @@ Template._loginButtonsMessagesDialog.events({
|
||||
|
||||
Template._loginButtonsMessagesDialog.visible = function () {
|
||||
var hasMessage = loginButtonsSession.get('infoMessage') || loginButtonsSession.get('errorMessage');
|
||||
return !Accounts._loginButtons.dropdown() && hasMessage;
|
||||
return !dropdown() && hasMessage;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -26,9 +26,7 @@ Template._loginButtonsLoggedInDropdown.events({
|
||||
}
|
||||
});
|
||||
|
||||
Template._loginButtonsLoggedInDropdown.displayName = function () {
|
||||
return Accounts._loginButtons.displayName();
|
||||
};
|
||||
Template._loginButtonsLoggedInDropdown.displayName = displayName;
|
||||
|
||||
Template._loginButtonsLoggedInDropdown.inChangePasswordFlow = function () {
|
||||
return loginButtonsSession.get('inChangePasswordFlow');
|
||||
@@ -175,26 +173,21 @@ Template._loginButtonsLoggedOutDropdown.dropdownVisible = function () {
|
||||
return loginButtonsSession.get('dropdownVisible');
|
||||
};
|
||||
|
||||
Template._loginButtonsLoggedOutDropdown.hasPasswordService = function () {
|
||||
return Accounts._loginButtons.hasPasswordService();
|
||||
};
|
||||
Template._loginButtonsLoggedOutDropdown.hasPasswordService = hasPasswordService;
|
||||
|
||||
// return all login services, with password last
|
||||
Template._loginButtonsLoggedOutAllServices.services = function () {
|
||||
return Accounts._loginButtons.getLoginServices();
|
||||
};
|
||||
Template._loginButtonsLoggedOutAllServices.services = getLoginServices;
|
||||
|
||||
Template._loginButtonsLoggedOutAllServices.isPasswordService = function () {
|
||||
return this.name === 'password';
|
||||
};
|
||||
|
||||
Template._loginButtonsLoggedOutAllServices.hasOtherServices = function () {
|
||||
return Accounts._loginButtons.getLoginServices().length > 1;
|
||||
return getLoginServices().length > 1;
|
||||
};
|
||||
|
||||
Template._loginButtonsLoggedOutAllServices.hasPasswordService = function () {
|
||||
return Accounts._loginButtons.hasPasswordService();
|
||||
};
|
||||
Template._loginButtonsLoggedOutAllServices.hasPasswordService =
|
||||
hasPasswordService;
|
||||
|
||||
Template._loginButtonsLoggedOutPasswordService.fields = function () {
|
||||
var loginFields = [
|
||||
@@ -202,15 +195,15 @@ Template._loginButtonsLoggedOutPasswordService.fields = function () {
|
||||
visible: function () {
|
||||
return _.contains(
|
||||
["USERNAME_AND_EMAIL", "USERNAME_AND_OPTIONAL_EMAIL"],
|
||||
Accounts.ui._passwordSignupFields());
|
||||
passwordSignupFields());
|
||||
}},
|
||||
{fieldName: 'username', fieldLabel: 'Username',
|
||||
visible: function () {
|
||||
return Accounts.ui._passwordSignupFields() === "USERNAME_ONLY";
|
||||
return passwordSignupFields() === "USERNAME_ONLY";
|
||||
}},
|
||||
{fieldName: 'email', fieldLabel: 'Email', inputType: 'email',
|
||||
visible: function () {
|
||||
return Accounts.ui._passwordSignupFields() === "EMAIL_ONLY";
|
||||
return passwordSignupFields() === "EMAIL_ONLY";
|
||||
}},
|
||||
{fieldName: 'password', fieldLabel: 'Password', inputType: 'password',
|
||||
visible: function () {
|
||||
@@ -223,17 +216,17 @@ Template._loginButtonsLoggedOutPasswordService.fields = function () {
|
||||
visible: function () {
|
||||
return _.contains(
|
||||
["USERNAME_AND_EMAIL", "USERNAME_AND_OPTIONAL_EMAIL", "USERNAME_ONLY"],
|
||||
Accounts.ui._passwordSignupFields());
|
||||
passwordSignupFields());
|
||||
}},
|
||||
{fieldName: 'email', fieldLabel: 'Email', inputType: 'email',
|
||||
visible: function () {
|
||||
return _.contains(
|
||||
["USERNAME_AND_EMAIL", "EMAIL_ONLY"],
|
||||
Accounts.ui._passwordSignupFields());
|
||||
passwordSignupFields());
|
||||
}},
|
||||
{fieldName: 'email', fieldLabel: 'Email (optional)', inputType: 'email',
|
||||
visible: function () {
|
||||
return Accounts.ui._passwordSignupFields() === "USERNAME_AND_OPTIONAL_EMAIL";
|
||||
return passwordSignupFields() === "USERNAME_AND_OPTIONAL_EMAIL";
|
||||
}},
|
||||
{fieldName: 'password', fieldLabel: 'Password', inputType: 'password',
|
||||
visible: function () {
|
||||
@@ -247,7 +240,7 @@ Template._loginButtonsLoggedOutPasswordService.fields = function () {
|
||||
// the "forgot password" flow.
|
||||
return _.contains(
|
||||
["USERNAME_AND_OPTIONAL_EMAIL", "USERNAME_ONLY"],
|
||||
Accounts.ui._passwordSignupFields());
|
||||
passwordSignupFields());
|
||||
}}
|
||||
];
|
||||
|
||||
@@ -273,7 +266,7 @@ Template._loginButtonsLoggedOutPasswordService.showCreateAccountLink = function
|
||||
Template._loginButtonsLoggedOutPasswordService.showForgotPasswordLink = function () {
|
||||
return _.contains(
|
||||
["USERNAME_AND_EMAIL", "USERNAME_AND_OPTIONAL_EMAIL", "EMAIL_ONLY"],
|
||||
Accounts.ui._passwordSignupFields());
|
||||
passwordSignupFields());
|
||||
};
|
||||
|
||||
Template._loginButtonsFormField.inputType = function () {
|
||||
@@ -313,7 +306,7 @@ Template._loginButtonsChangePassword.fields = function () {
|
||||
// the "forgot password" flow.
|
||||
return _.contains(
|
||||
["USERNAME_AND_OPTIONAL_EMAIL", "USERNAME_ONLY"],
|
||||
Accounts.ui._passwordSignupFields());
|
||||
passwordSignupFields());
|
||||
}}
|
||||
];
|
||||
};
|
||||
@@ -357,19 +350,19 @@ var login = function () {
|
||||
|
||||
var loginSelector;
|
||||
if (username !== null) {
|
||||
if (!Accounts._loginButtons.validateUsername(username))
|
||||
if (!validateUsername(username))
|
||||
return;
|
||||
else
|
||||
loginSelector = {username: username};
|
||||
} else if (email !== null) {
|
||||
if (!Accounts._loginButtons.validateEmail(email))
|
||||
if (!validateEmail(email))
|
||||
return;
|
||||
else
|
||||
loginSelector = {email: email};
|
||||
} else if (usernameOrEmail !== null) {
|
||||
// XXX not sure how we should validate this. but this seems good enough (for now),
|
||||
// since an email must have at least 3 characters anyways
|
||||
if (!Accounts._loginButtons.validateUsername(usernameOrEmail))
|
||||
if (!validateUsername(usernameOrEmail))
|
||||
return;
|
||||
else
|
||||
loginSelector = usernameOrEmail;
|
||||
@@ -393,7 +386,7 @@ var signup = function () {
|
||||
|
||||
var username = trimmedElementValueById('login-username');
|
||||
if (username !== null) {
|
||||
if (!Accounts._loginButtons.validateUsername(username))
|
||||
if (!validateUsername(username))
|
||||
return;
|
||||
else
|
||||
options.username = username;
|
||||
@@ -401,7 +394,7 @@ var signup = function () {
|
||||
|
||||
var email = trimmedElementValueById('login-email');
|
||||
if (email !== null) {
|
||||
if (!Accounts._loginButtons.validateEmail(email))
|
||||
if (!validateEmail(email))
|
||||
return;
|
||||
else
|
||||
options.email = email;
|
||||
@@ -409,7 +402,7 @@ var signup = function () {
|
||||
|
||||
// notably not trimmed. a password could (?) start or end with a space
|
||||
var password = elementValueById('login-password');
|
||||
if (!Accounts._loginButtons.validatePassword(password))
|
||||
if (!validatePassword(password))
|
||||
return;
|
||||
else
|
||||
options.password = password;
|
||||
@@ -450,7 +443,7 @@ var changePassword = function () {
|
||||
|
||||
// notably not trimmed. a password could (?) start or end with a space
|
||||
var password = elementValueById('login-password');
|
||||
if (!Accounts._loginButtons.validatePassword(password))
|
||||
if (!validatePassword(password))
|
||||
return;
|
||||
|
||||
if (!matchPasswordAgainIfPresent())
|
||||
|
||||
@@ -27,7 +27,10 @@ var validateKey = function (key) {
|
||||
|
||||
var KEY_PREFIX = "Meteor.loginButtons.";
|
||||
|
||||
// XXX we should have a better pattern for code private to a package like this one
|
||||
// XXX This should probably be package scope rather than exported
|
||||
// (there was even a comment to that effect here from before we had
|
||||
// namespacing) but accounts-ui-viewer uses it, so leave it as is for
|
||||
// now
|
||||
Accounts._loginButtonsSession = {
|
||||
set: function(key, value) {
|
||||
validateKey(key);
|
||||
|
||||
@@ -3,12 +3,19 @@ Package.describe({
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.use(['deps', 'service-configuration', 'accounts-urls', 'accounts-base',
|
||||
api.use(['deps', 'service-configuration', 'accounts-base',
|
||||
'underscore', 'templating',
|
||||
'handlebars', 'spark', 'session'], 'client');
|
||||
// Export Accounts (etc) to packages using this one.
|
||||
api.imply('accounts-base', ['client', 'server']);
|
||||
|
||||
// Allow us to call Accounts.oauth.serviceNames, if there are any OAuth
|
||||
// services.
|
||||
api.use('accounts-oauth', {weak: true});
|
||||
// Allow us to directly test if accounts-password (which doesn't use
|
||||
// Accounts.oauth.registerService) exists.
|
||||
api.use('accounts-password', {weak: true});
|
||||
|
||||
api.add_files([
|
||||
'accounts_ui.js',
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
Package.describe({
|
||||
summary: "Generate and consume reset password and verify account URLs",
|
||||
internal: true
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.add_files('url_client.js', 'client');
|
||||
api.add_files('url_server.js', 'server');
|
||||
});
|
||||
@@ -11,7 +11,5 @@ Package.on_use(function(api) {
|
||||
|
||||
api.add_files('weibo_login_button.css', 'client');
|
||||
|
||||
api.add_files('weibo_common.js', ['client', 'server']);
|
||||
api.add_files('weibo_server.js', 'server');
|
||||
api.add_files('weibo_client.js', 'client');
|
||||
api.add_files("weibo.js");
|
||||
});
|
||||
|
||||
21
packages/accounts-weibo/weibo.js
Normal file
21
packages/accounts-weibo/weibo.js
Normal file
@@ -0,0 +1,21 @@
|
||||
Accounts.oauth.registerService('weibo');
|
||||
|
||||
if (Meteor.isClient) {
|
||||
Meteor.loginWithWeibo = function(options, callback) {
|
||||
// support a callback without options
|
||||
if (! callback && typeof options === "function") {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
|
||||
var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
|
||||
Weibo.requestCredential(options, credentialRequestCompleteCallback);
|
||||
};
|
||||
} else {
|
||||
Accounts.addAutopublishFields({
|
||||
// publish all fields including access token, which can legitimately
|
||||
// be used from the client (if transmitted over ssl or on localhost)
|
||||
forLoggedInUser: ['services.weibo'],
|
||||
forOtherUsers: ['services.weibo.screenName']
|
||||
});
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
Meteor.loginWithWeibo = function(options, callback) {
|
||||
var credentialRequestCompleteCallback = Accounts.oauth.credentialRequestCompleteHandler(callback);
|
||||
Weibo.requestCredential(options, credentialRequestCompleteCallback);
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
if (!Accounts.weibo) {
|
||||
Accounts.weibo = {};
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
Accounts.oauth.registerService('weibo');
|
||||
|
||||
Accounts.addAutopublishFields({
|
||||
// publish all fields including access token, which can legitimately
|
||||
// be used from the client (if transmitted over ssl or on localhost)
|
||||
forLoggedInUser: ['services.weibo'],
|
||||
forOtherUsers: ['services.weibo.screenName']
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ var updatingAppcache = false;
|
||||
var reloadRetry = null;
|
||||
var appcacheUpdated = false;
|
||||
|
||||
Meteor._reload.onMigrate('appcache', function(retry) {
|
||||
Reload._onMigrate('appcache', function(retry) {
|
||||
if (appcacheUpdated)
|
||||
return [true];
|
||||
|
||||
@@ -61,7 +61,7 @@ window.applicationCache.addEventListener('obsolete', (function() {
|
||||
}
|
||||
else {
|
||||
appcacheUpdated = true;
|
||||
Meteor._reload.reload();
|
||||
Reload._reload();
|
||||
}
|
||||
}), false);
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ Package.on_use(function (api) {
|
||||
api.use('webapp', 'server');
|
||||
api.use('reload', 'client');
|
||||
api.use('routepolicy', 'server');
|
||||
api.use('startup', 'client');
|
||||
api.use('underscore', 'server');
|
||||
api.add_files('appcache-client.js', 'client');
|
||||
api.add_files('appcache-server.js', 'server');
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Meteor._LivedataServer._auditArgumentChecks = true;
|
||||
@@ -2,7 +2,4 @@ Package.describe({
|
||||
summary: "Try to detect inadequate input sanitization"
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.use(['livedata'], ['server']);
|
||||
api.add_files(['audit_argument_checks.js'], 'server');
|
||||
});
|
||||
// This package is empty; its presence is detected by livedata.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Meteor.default_server.autopublish();
|
||||
@@ -2,7 +2,5 @@ Package.describe({
|
||||
summary: "Automatically publish the entire database to all clients"
|
||||
});
|
||||
|
||||
Package.on_use(function (api, where) {
|
||||
api.use('livedata', 'server');
|
||||
api.add_files("autopublish.js", "server");
|
||||
});
|
||||
// This package is empty; its presence is detected by livedata and
|
||||
// accounts-base.
|
||||
|
||||
@@ -37,8 +37,10 @@
|
||||
Backbone.VERSION = '0.9.2';
|
||||
|
||||
// Require Underscore, if we're on the server, and it's not already present.
|
||||
var _ = root._;
|
||||
if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
|
||||
// <METEOR> Commented these lines out; we have _ via api.use.
|
||||
// var _ = root._;
|
||||
// if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
|
||||
// </METEOR>
|
||||
|
||||
// For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
|
||||
var $ = root.jQuery || root.Zepto || root.ender;
|
||||
|
||||
@@ -2,10 +2,9 @@ Package.describe({
|
||||
summary: "A minimalist client-side MVC framework"
|
||||
});
|
||||
|
||||
Package.on_use(function (api, where) {
|
||||
Package.on_use(function (api) {
|
||||
// XXX Backbone requires either jquery or zepto
|
||||
api.use(["jquery", "json", "underscore"]);
|
||||
|
||||
where = where || ['client', 'server'];
|
||||
api.add_files("backbone.js", where);
|
||||
api.add_files("backbone.js");
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
var currentArgumentChecker = new Meteor.EnvironmentVariable;
|
||||
|
||||
// @export check
|
||||
check = function (value, pattern) {
|
||||
// Record that check got called, if somebody cared.
|
||||
var argChecker = currentArgumentChecker.get();
|
||||
@@ -16,26 +15,20 @@ check = function (value, pattern) {
|
||||
};
|
||||
|
||||
Match = {
|
||||
// @export Match.Optional
|
||||
Optional: function (pattern) {
|
||||
return new Optional(pattern);
|
||||
},
|
||||
// @export Match.OneOf
|
||||
OneOf: function (/*arguments*/) {
|
||||
return new OneOf(_.toArray(arguments));
|
||||
},
|
||||
// @export Match.Any
|
||||
Any: ['__any__'],
|
||||
// @export Match.Where
|
||||
Where: function (condition) {
|
||||
return new Where(condition);
|
||||
},
|
||||
// @export Match.ObjectIncluding
|
||||
ObjectIncluding: function (pattern) {
|
||||
return new ObjectIncluding(pattern);
|
||||
},
|
||||
|
||||
// @export Match.Error
|
||||
// XXX should we record the path down the tree in the error message?
|
||||
// XXX matchers should know how to describe themselves for errors
|
||||
Error: Meteor.makeErrorType("Match.Error", function (msg) {
|
||||
@@ -45,7 +38,6 @@ Match = {
|
||||
this.sanitizedError = new Meteor.Error(400, "Match failed");
|
||||
}),
|
||||
|
||||
// @export Match.test
|
||||
// Tests to see if value matches pattern. Unlike check, it merely returns true
|
||||
// or false (unless an error other than Match.Error was thrown). It does not
|
||||
// interact with _failIfArgumentsAreNotAllChecked.
|
||||
@@ -68,7 +60,6 @@ Match = {
|
||||
// `args` (either directly or in the first level of an array), throws an error
|
||||
// (using `description` in the message).
|
||||
//
|
||||
// @export Match._failIfArgumentsAreNotAllChecked
|
||||
_failIfArgumentsAreNotAllChecked: function (f, context, args, description) {
|
||||
var argChecker = new ArgumentChecker(args, description);
|
||||
var result = currentArgumentChecker.withValue(argChecker, function () {
|
||||
|
||||
@@ -6,6 +6,8 @@ Package.describe({
|
||||
Package.on_use(function (api) {
|
||||
api.use(['underscore', 'ejson'], ['client', 'server']);
|
||||
|
||||
api.export(['check', 'Match']);
|
||||
|
||||
api.add_files('match.js', ['client', 'server']);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
### @export COFFEESCRIPT_EXPORTED ###
|
||||
|
||||
COFFEESCRIPT_EXPORTED = 123
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
Package.describe({
|
||||
summary: "Used by the coffeescript package's @export tests",
|
||||
summary: "Used by the coffeescript package's tests",
|
||||
internal: true
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.use('coffeescript', ['client', 'server']);
|
||||
api.export('COFFEESCRIPT_EXPORTED');
|
||||
api.add_files("exporting.coffee", ['client', 'server']);
|
||||
});
|
||||
|
||||
@@ -28,6 +28,8 @@ var stripExportedVars = function (source, exports) {
|
||||
// up on subsequent lines.)
|
||||
// XXX relax these assumptions by doing actual JS parsing (eg with jsparse).
|
||||
// I'd do this now, but there's no easy way to "unparse" a jsparse AST.
|
||||
// Or alternatively, hack the compiler to allow us to specify unbound
|
||||
// symbols directly.
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i];
|
||||
@@ -83,8 +85,7 @@ var addSharedHeader = function (source, sourceMap) {
|
||||
// as a var in the package closure, and in "app" mode where it will end up as
|
||||
// a global.
|
||||
//
|
||||
// This ends in a newline in case the first line is a linker @comment, which
|
||||
// should be at the beginning of a line.
|
||||
// This ends in a newline to make the source map easier to adjust.
|
||||
var header = ("__coffeescriptShare = typeof __coffeescriptShare === 'object' " +
|
||||
"? __coffeescriptShare : {}; " +
|
||||
"var share = __coffeescriptShare;\n");
|
||||
@@ -140,15 +141,14 @@ var handler = function (compileStep) {
|
||||
);
|
||||
}
|
||||
|
||||
var stripped = stripExportedVars(output.js, compileStep.declaredExports);
|
||||
var sourceWithMap = addSharedHeader(stripped, output.v3SourceMap);
|
||||
|
||||
compileStep.addJavaScript({
|
||||
path: outputFile,
|
||||
sourcePath: compileStep.inputPath,
|
||||
data: output.js,
|
||||
linkerFileTransform: function (sourceWithMap, exports) {
|
||||
var stripped = stripExportedVars(sourceWithMap.source, exports);
|
||||
return addSharedHeader(stripped, sourceWithMap.sourceMap);
|
||||
},
|
||||
sourceMap: output.v3SourceMap
|
||||
data: sourceWithMap.source,
|
||||
sourceMap: sourceWithMap.sourceMap
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user