mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'release-0.5.0' into parties-example
Conflicts: docs/client/packages/d3.html
This commit is contained in:
@@ -40,6 +40,9 @@
|
||||
* Meteor now provides a compatible replacement for the DOM `localStorage`
|
||||
facility that works in IE7, in the `localstorage-polyfill` smart package.
|
||||
|
||||
* Meteor now packages the D3 library for manipulating documents based on data in
|
||||
a smart package called `d3`.
|
||||
|
||||
* `Meteor.Collection` now takes its optional `manager` argument (used to
|
||||
associate a collection with a server you've connected to with
|
||||
`Meteor.connect`) as a named option. (The old call syntax continues to work
|
||||
|
||||
72
LICENSE.txt
72
LICENSE.txt
@@ -356,6 +356,11 @@ Copyright (c) 2011:
|
||||
Tim Koschützki (tim@debuggable.com)
|
||||
Felix Geisendörfer (felix@debuggable.com)
|
||||
|
||||
----------
|
||||
node-form-data: https://github.com/felixge/node-form-data
|
||||
----------
|
||||
|
||||
Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors
|
||||
|
||||
|
||||
==============
|
||||
@@ -622,6 +627,11 @@ npmlog: https://github.com/isaacs/npmlog
|
||||
once: https://github.com/isaacs/once
|
||||
osenv: https://github.com/isaacs/osenv
|
||||
mute-stream: https://github.com/isaacs/mute-stream
|
||||
couch-login: https://github.com/isaacs/couch-login
|
||||
npmconf: https://github.com/isaacs/npmconf
|
||||
read-installed: https://github.com/isaacs/read-installed
|
||||
read-package-json: https://github.com/isaacs/read-package-json
|
||||
promzard: https://github.com/isaacs/promzard
|
||||
----------
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter ("Author")
|
||||
@@ -752,6 +762,38 @@ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISE
|
||||
OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
----------
|
||||
D3: http://d3js.org/
|
||||
----------
|
||||
|
||||
Copyright (c) 2012, Michael Bostock
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* The name Michael Bostock may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
|
||||
=============
|
||||
Public Domain
|
||||
@@ -841,6 +883,36 @@ By Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
||||
|
||||
----------
|
||||
node-stream-buffer: https://github.com/samcday/node-stream-buffer
|
||||
----------
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
|
||||
|
||||
----------
|
||||
mongodb: http://www.mongodb.org/
|
||||
----------
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
|
||||
<h1 id="api">The Meteor API</h1>
|
||||
|
||||
Your Javascript code can run in two environments: the *client*
|
||||
(browser), and the *server* (a Node.js container on a server). For
|
||||
each function in this API reference, we'll indicate if the function is
|
||||
available just on the client, just on the server, or *Anywhere*.
|
||||
Your Javascript code can run in two environments: the *client* (browser), and
|
||||
the *server* (a [Node.js](http://nodejs.org/) container on a server). For each
|
||||
function in this API reference, we'll indicate if the function is available just
|
||||
on the client, just on the server, or *Anywhere*.
|
||||
|
||||
<h2 id="core"><span>Meteor Core</span></h2>
|
||||
|
||||
@@ -47,7 +47,14 @@ will publish that cursor's documents.
|
||||
|
||||
// server: publish the rooms collection, minus secret info.
|
||||
Meteor.publish("rooms", function () {
|
||||
return Rooms.find({}, {fields: {secretInfo: false}});
|
||||
return Rooms.find({}, {fields: {secretInfo: 0}});
|
||||
});
|
||||
|
||||
// ... and publish secret info for rooms where the logged-in user
|
||||
// is an admin. If the client subscribes to both streams, the records
|
||||
// are merged together into the same documents in the Rooms collection.
|
||||
Meteor.publish("adminSecretInfo", function () {
|
||||
return Rooms.find({admin: this.userId}, {fields: {secretInfo: 1}});
|
||||
});
|
||||
|
||||
Otherwise, the publish function can [`set`](#publish_set) and
|
||||
@@ -108,6 +115,11 @@ project that includes the `autopublish` package. Your publish function
|
||||
will still work.
|
||||
{{/warning}}
|
||||
|
||||
{{> api_box subscription_userId}}
|
||||
|
||||
This is constant. However, if the logged-in user changes, the publish
|
||||
function is rerun with the new value.
|
||||
|
||||
{{> api_box subscription_set}}
|
||||
{{> api_box subscription_unset}}
|
||||
{{> api_box subscription_complete}}
|
||||
@@ -120,11 +132,6 @@ is the place to stop the observes.
|
||||
|
||||
{{> api_box subscription_stop}}
|
||||
|
||||
{{> api_box subscription_userId}}
|
||||
|
||||
This is constant. However, if the logged-in user changes, the publish
|
||||
function is rerun with the new value.
|
||||
|
||||
{{> api_box subscribe}}
|
||||
|
||||
When you subscribe to a record set, it tells the server to send records
|
||||
@@ -150,9 +157,9 @@ attribute.)
|
||||
|
||||
If all of the attributes in a document are removed, Meteor
|
||||
will remove the (now empty) document. If you want to publish empty
|
||||
documents, just use a placeholder attribute.
|
||||
documents, just use a placeholder attribute:
|
||||
|
||||
// Clicks.insert({exists: true});
|
||||
Clicks.insert({exists: true});
|
||||
|
||||
{{> api_box autosubscribe}}
|
||||
|
||||
@@ -206,7 +213,7 @@ object, which provides the following:
|
||||
* `isSimulation`: a boolean value, true if this invocation is a stub.
|
||||
* `unblock`: when called, allows the next method from this client to
|
||||
begin running.
|
||||
* `userId`: a function that returns the id of the current user.
|
||||
* `userId`: the id of the current user.
|
||||
* `setUserId`: a function that associates the current client with a user.
|
||||
|
||||
Calling `methods` on the client defines *stub* functions associated with
|
||||
@@ -225,9 +232,9 @@ exception it will be logged to the console.
|
||||
|
||||
{{> api_box method_invocation_userId}}
|
||||
|
||||
The user id is an arbitrary string — typically the id of the user
|
||||
record in the database. You can set it with the `setUserId` function. If
|
||||
you're using the Meteor accounts system then this is handled for you.
|
||||
The user id is an arbitrary string — typically the id of the user record
|
||||
in the database. You can set it with the `setUserId` function. If you're using
|
||||
the [Meteor accounts system](#accounts_api) then this is handled for you.
|
||||
|
||||
{{> api_box method_invocation_setUserId}}
|
||||
|
||||
@@ -236,9 +243,9 @@ connection that made this method call. This simply sets the value of
|
||||
`userId` for future method calls received on this connection. Pass
|
||||
`null` to log out the connection.
|
||||
|
||||
If you are using the built-in Meteor accounts system then this should correspond
|
||||
to the `_id` field of a document in the [`Meteor.users`](#meteor_users)
|
||||
collection.
|
||||
If you are using the [built-in Meteor accounts system](#accounts_api) then this
|
||||
should correspond to the `_id` field of a document in the
|
||||
[`Meteor.users`](#meteor_users) collection.
|
||||
|
||||
`setUserId` is not retroactive. It affects the current method call and
|
||||
any future method calls on the connection. Any previous method calls on
|
||||
@@ -268,7 +275,7 @@ This is how to invoke a method. It will run the method on the server.
|
||||
If a stub is available, it will also run the stub on the client.
|
||||
|
||||
If you include a callback function as the last argument (which can't be
|
||||
an argument to the method, since functions aren't serializeable), the
|
||||
an argument to the method, since functions aren't serializable), the
|
||||
method will run asynchronously: it will return nothing in particular and
|
||||
will not throw an exception. When the method is complete (which may or
|
||||
may not happen before `Meteor.call` returns), the callback will be
|
||||
@@ -312,8 +319,9 @@ only to the server.)
|
||||
|
||||
{{> api_box meteor_apply}}
|
||||
|
||||
`Meteor.apply` is just like `Meteor.call`, but it allows the
|
||||
arguments to be passed as an array.
|
||||
`Meteor.apply` is just like `Meteor.call`, except that the method arguments are
|
||||
passed as an array rather than directly as arguments, and you can specify
|
||||
options about how the client executes the method.
|
||||
|
||||
<h2 id="connections"><span>Server connections</span></h2>
|
||||
|
||||
@@ -382,7 +390,7 @@ sets, call `Meteor.connect` with the URL of the application.
|
||||
Get the current connection status. See
|
||||
[Meteor.status](#meteor_status).
|
||||
* `reconnect` -
|
||||
See <a href="meteor_reconnect">Meteor.reconnect</a>.
|
||||
See [Meteor.reconnect](#meteor_reconnect).
|
||||
* `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
|
||||
@@ -444,7 +452,8 @@ Specifically, when you pass a `name`, here's what happens:
|
||||
|
||||
* On the server, a collection with that name is created on a backend
|
||||
Mongo server. When you call methods on that collection on the server,
|
||||
they translate directly into normal Mongo operations.
|
||||
they translate directly into normal Mongo operations (after checking that
|
||||
they match your [access control rules](#allow)).
|
||||
|
||||
* On the client, a Minimongo instance is
|
||||
created. Minimongo is essentially an in-memory, non-persistent
|
||||
@@ -471,13 +480,8 @@ the package:
|
||||
|
||||
$ meteor remove autopublish
|
||||
|
||||
{{#warning}}
|
||||
Currently the client is given full write access to the collection. They
|
||||
can execute arbitrary Mongo update commands. Once we build
|
||||
authentication, you will be able to limit the client's direct access to
|
||||
insert, update, and remove. We are also considering validators and
|
||||
other ORM-like functionality.
|
||||
{{/warning}}
|
||||
and instead call [`Meteor.publish`](#meteor_publish) to specify which parts of
|
||||
your collection should be published to which users.
|
||||
|
||||
// Create a collection called Posts and put a document in it. The
|
||||
// document will be immediately visible in the local copy of the
|
||||
@@ -546,8 +550,8 @@ those changes may or may not appear in the result set.
|
||||
|
||||
Cursors are a reactive data source. The first time you retrieve a
|
||||
cursor's documents with `fetch`, `map`, or `forEach` inside a
|
||||
reactive context (eg, [`Meteor.render`](#meteor_render),
|
||||
[`Meteor.autosubscribe`](#meteor_autosubscribe), Meteor will register a
|
||||
reactive context (eg, [`Meteor.render`](#meteor_render) or
|
||||
[`Meteor.autosubscribe`](#meteor_autosubscribe)), Meteor will register a
|
||||
dependency on the underlying data. Any change to the collection that
|
||||
changes the documents in a cursor will trigger a recomputation. To
|
||||
disable this behavior, pass `{reactive: false}` as an option to
|
||||
@@ -589,10 +593,9 @@ Example:
|
||||
|
||||
{{> api_box update}}
|
||||
|
||||
Modify documents that match `selector` as
|
||||
given by `modifier` (see <a href="#modifiers">modifier
|
||||
documentation</a>). By default, modify only one matching document.
|
||||
If `multi` is true, modify all matching documents.
|
||||
Modify documents that match `selector` as given by `modifier` (see [modifier
|
||||
documentation](#modifiers)). By default, modify only one matching document. If
|
||||
`multi` is true, modify all matching documents.
|
||||
|
||||
Instead of a selector, you can pass a string, which will be
|
||||
interpreted as an `_id`.
|
||||
@@ -656,7 +659,7 @@ Example:
|
||||
{{> api_box allow}}
|
||||
|
||||
When a client calls `insert`, `update`, or `remove` on a collection, the
|
||||
collection's `allow` and <a href='#deny'>`deny`</a> callbacks are called
|
||||
collection's `allow` and [`deny`](#deny) callbacks are called
|
||||
on the server to determine if the write should be allowed. If at least
|
||||
one `allow` callback allows the write, and no `deny` callbacks deny the
|
||||
write, then the write is allowed to proceed.
|
||||
@@ -718,7 +721,7 @@ actually needed by your functions. This is enabled by the `fetch`
|
||||
option. Set `fetch` to an array of the field names that should be
|
||||
retrieved.
|
||||
|
||||
Example: XXX test me!
|
||||
Example:
|
||||
|
||||
// Create a collection where users can only modify documents that
|
||||
// they own. Ownership is tracked by an 'owner' field on each
|
||||
@@ -772,12 +775,12 @@ having the clients call `insert`, `update`, and `remove` directly on the
|
||||
collection.
|
||||
|
||||
Meteor also has a special "insecure mode" for quickly prototyping new
|
||||
applications. In insecure mode, if you haven't set up any `allow` or
|
||||
`deny` rules on a collection, then all users have full write access to
|
||||
the collection. This is the only effect of insecure mode. If you call
|
||||
`allow` or `deny` at all, even `allow({})`, then access is checked just
|
||||
like normal. __New Meteor projects start in insecure mode by default.__ To
|
||||
turn it off just type `meteor remove insecure`.
|
||||
applications. In insecure mode, if you haven't set up any `allow` or `deny`
|
||||
rules on a collection, then all users have full write access to the
|
||||
collection. This is the only effect of insecure mode. If you call `allow` or
|
||||
`deny` at all on a collection, even `Posts.allow({})`, then access is checked
|
||||
just like normal on that collection. __New Meteor projects start in insecure
|
||||
mode by default.__ To turn it off just type `meteor remove insecure`.
|
||||
|
||||
{{#note}}
|
||||
For `update` and `remove`, documents will be affected only if they match
|
||||
@@ -790,7 +793,7 @@ and checked by allow and deny)]}}]}`.
|
||||
|
||||
{{> api_box deny}}
|
||||
|
||||
This works just like <a href='#allow'>`allow`</a>, except it lets you
|
||||
This works just like [`allow`](#allow), except it lets you
|
||||
make sure that certain writes are definitely denied, even if there is an
|
||||
`allow` rule that says that they should be permitted.
|
||||
|
||||
@@ -943,7 +946,8 @@ But they can also contain more complicated tests:
|
||||
// Matches documents where fruit is one of three possibilities
|
||||
{fruit: {$in: ["peach", "plum", "pear"]}}
|
||||
|
||||
See the <a href="http://www.mongodb.org/display/DOCS/Advanced+Queries" target="_blank">complete documentation.</a>
|
||||
See the [complete
|
||||
documentation](http://www.mongodb.org/display/DOCS/Advanced+Queries).
|
||||
|
||||
{{/api_box_inline}}
|
||||
|
||||
@@ -959,14 +963,17 @@ place by changing some of its fields. Some examples:
|
||||
// 'supporters' array
|
||||
{$inc: {votes: 2}, $push: {supporters: "Traz"}}
|
||||
|
||||
But if a modifier doesn't contain any $-operators, then it is
|
||||
instead interpreted as a literal document, and completely replaces
|
||||
whatever was previously in the database.
|
||||
But if a modifier doesn't contain any $-operators, then it is instead
|
||||
interpreted as a literal document, and completely replaces whatever was
|
||||
previously in the database. (Literal document modifiers are not currently
|
||||
supported by [validated updates](#allow).)
|
||||
|
||||
// Find the document with id "123", and completely replace it.
|
||||
Users.update({_id: "123"}, {name: "Alice", friends: ["Bob"]});
|
||||
|
||||
See the <a href="http://www.mongodb.org/display/DOCS/Updating#Updating-ModifierOperations" target="_blank">full list of modifiers.</a>
|
||||
See the [full list of
|
||||
modifiers](http://www.mongodb.org/display/DOCS/Updating#Updating-ModifierOperations)
|
||||
full list of modifiers.
|
||||
|
||||
{{/api_box_inline}}
|
||||
|
||||
@@ -1101,6 +1108,359 @@ For object and array session values, you cannot use `Session.equals`; instead,
|
||||
you need to use the `underscore` package and write
|
||||
`_.isEqual(Session.get(key), value)`.
|
||||
|
||||
|
||||
|
||||
<h2 id="accounts_api"><span>Accounts</span></h2>
|
||||
|
||||
XXX intro text
|
||||
|
||||
{{> api_box user}}
|
||||
|
||||
Retreives the user record for the current user from
|
||||
the [`Meteor.users`](#meteor_users) collection.
|
||||
|
||||
On the client this will be a subset of the fields in the document, only
|
||||
those that are published from the server are available on the client. By
|
||||
default the server publishes `username`, `emails`, and
|
||||
`profile`. See [`Meteor.users`](#meteor_users) for more on
|
||||
the fields used in user documents.
|
||||
|
||||
If the user is logged in but the user's database record is not fully
|
||||
loaded yet, this returns an object with only the `_id` field set. During
|
||||
this period [`userLoaded`](#meteor_userloaded) will return
|
||||
`false`.
|
||||
|
||||
{{> api_box userId}}
|
||||
|
||||
{{> api_box users}}
|
||||
|
||||
This collection contains one document per registered user. Here's an example
|
||||
user document:
|
||||
|
||||
{
|
||||
_id: "bbca5d6a-2156-41c4-89da-0329e8c99a4f", // Meteor.userId()
|
||||
username: "cool_kid_13", // unique name
|
||||
emails: [
|
||||
// each email address can only belong to one user.
|
||||
{ address: "cool@example.com", verified: true },
|
||||
{ address: "another@different.com", verified: false }
|
||||
],
|
||||
profile: {
|
||||
// The profile is writable by the user by default.
|
||||
name: "Joe Schmoe"
|
||||
},
|
||||
services: {
|
||||
facebook: {
|
||||
id: "709050", // facebook id
|
||||
accessToken: "AAACCgdX7G2...AbV9AZDZD"
|
||||
},
|
||||
resume: {
|
||||
loginTokens: [
|
||||
{ token: "97e8c205-c7e4-47c9-9bea-8e2ccc0694cd",
|
||||
when: 1349761684048 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
A user document can contain any data you want to store about a user. Meteor
|
||||
treats the following fields specially:
|
||||
|
||||
- `username`: a unique String identifying the user.
|
||||
- `emails`: an Array of Objects with keys `address` and `verified`;
|
||||
an email address may belong to at most one user. `verified` is
|
||||
a Boolean which is true if the user has [verified the
|
||||
address](#accounts_verifyemail) with a token sent over email.
|
||||
- `profile`: an Object which (by default) the user can create
|
||||
and update with any data.
|
||||
- `services`: an Object containing data used by particular
|
||||
login services. For example, its `reset` field contains
|
||||
tokens used by [forgot password](#accounts_forgotpassword) links,
|
||||
and its `resume` field contains tokens used to keep you
|
||||
logged in between sessions.
|
||||
|
||||
Like all [Meteor.Collection](#collections)s, you can access all
|
||||
documents on the server, but only those specifically published by the server are
|
||||
available on the client.
|
||||
|
||||
By default, the current user's `username`, `emails` and `profile` are
|
||||
published to the client. You can publish additional fields for the
|
||||
current user with:
|
||||
|
||||
Meteor.publish("userData", function () {
|
||||
return Meteor.users.find({_id: this.userId},
|
||||
{fields: {'other': 1, 'things': 1}});
|
||||
});
|
||||
|
||||
If the `autopublish` package is installed, the `username` and `profile` fields
|
||||
for all users are published to all clients. To publish specific fields from all
|
||||
users:
|
||||
|
||||
Meteor.publish("allUserData", function () {
|
||||
return Meteor.users.find({}, {fields: {'nested.things': 1}});
|
||||
});
|
||||
|
||||
Users are by default allowed to specify their own `profile` field with
|
||||
[`Accounts.createUser`](#accounts_createuser) and modify it with
|
||||
`Meteor.users.update`. To allow users to edit additional fields, use
|
||||
[`Meteor.users.allow`](#allow). To forbid users from making any modifications to
|
||||
their user document:
|
||||
|
||||
Meteor.users.deny({update: function () { return true; }});
|
||||
|
||||
|
||||
{{> api_box userLoaded}}
|
||||
|
||||
There are some cases when the client knows the id of the logged in user
|
||||
but has not yet received the user data from the server. For example, if
|
||||
the user is logged in and reloads the page the user data will be
|
||||
unavailable during initial page load.
|
||||
|
||||
During these periods, `userLoaded` will return false
|
||||
and [`user`](#meteor_user) will return an object with only
|
||||
the `_id` key.
|
||||
|
||||
{{#note}}
|
||||
We realize this is inconvenient. It is a temporary solution. In the
|
||||
future we will either make it unnecessary or fold it into a more
|
||||
general mechanism.
|
||||
{{/note}}
|
||||
|
||||
{{> api_box logout}}
|
||||
|
||||
{{> api_box loginWithPassword}}
|
||||
|
||||
XXX needs link to passwords section and mention the package
|
||||
|
||||
{{> api_box loginWithExternalService}}
|
||||
|
||||
These functions initiate the login process with an external
|
||||
service (eg: Facebook, Google, etc), using OAuth. When called they open a new pop-up
|
||||
window that loads the provider's login page. Once the user has logged in
|
||||
with the provider, the pop-up window is closed and the Meteor client
|
||||
logs in to the Meteor server with the information provided by the external
|
||||
service.
|
||||
|
||||
<a id="requestpermissions" name="requestpermissions" />
|
||||
If the user has not already granted all the permissions requested they will be
|
||||
prompted to grant access to their account in the pop-up dialog. Values for the
|
||||
`requestPermissions` parameter differ for each login service:
|
||||
|
||||
- Facebook: <http://developers.facebook.com/docs/authentication/permissions/>
|
||||
- GitHub: <http://developer.github.com/v3/oauth/#scopes>
|
||||
- Google: <https://developers.google.com/accounts/docs/OAuth2Login#scopeparameter>
|
||||
- Twitter, Weibo: `requestPermissions` currently not supported
|
||||
|
||||
XXX mention provider packages
|
||||
|
||||
{{> api_box accounts_config}}
|
||||
{{> api_box accounts_ui_config}}
|
||||
|
||||
Example:
|
||||
|
||||
Accounts.ui.config({
|
||||
requestPermissions: {
|
||||
facebook: ['user_likes'],
|
||||
github: ['user', 'repo']
|
||||
},
|
||||
passwordSignupFields: 'USERNAME_AND_OPTIONAL_EMAIL'
|
||||
});
|
||||
|
||||
{{> api_box accounts_validateNewUser}}
|
||||
|
||||
This can be called multiple times. If any of the functions return `false` or
|
||||
throw an error, the new user creation is aborted. To set a specific error
|
||||
message (which will be displayed by [`accounts-ui`](#accountsui)), throw a new
|
||||
[`Meteor.Error`](#meteor_error).
|
||||
|
||||
Example:
|
||||
|
||||
// Validate username, sending a specific error message on failure.
|
||||
Accounts.validateNewUser(function (user) {
|
||||
if (user.username && user.username.length >= 3)
|
||||
return true;
|
||||
throw new Meteor.Error(403, "Username must have at least 3 characters");
|
||||
});
|
||||
// Validate username, without a specific error message.
|
||||
Accounts.validateNewUser(function (user) {
|
||||
return user.username !== "root";
|
||||
});
|
||||
|
||||
{{> api_box accounts_onCreateUser}}
|
||||
|
||||
Use this when you need to do more than simply accept or reject new user
|
||||
creation. With this function you can programatically control the
|
||||
contents of new user documents.
|
||||
|
||||
The function you pass will be called with two arguments: `options` and
|
||||
`user`. The `options` argument comes
|
||||
from [`Accounts.createUser`](#accounts_createuser) for
|
||||
password-based users or from an external service login flow. `options` may come
|
||||
from an untrusted client so make sure to validate any values you read from
|
||||
it. The `user` argument is created on the server and contains a
|
||||
proposed user object with all the automatically generated fields
|
||||
required for the user to log in.
|
||||
|
||||
The function should return the user document (either the one passed in or a
|
||||
newly-created object) with whatever modifications are desired. The returned
|
||||
document is inserted directly into the [`Meteor.users`](#meteor_users) collection.
|
||||
|
||||
The default create user function simply copies `options.profile` into
|
||||
the new user document. Calling `onCreateUser` overrides the default
|
||||
hook. This can only be called once.
|
||||
|
||||
Example:
|
||||
|
||||
<!-- XXX replace d6 with _.random once we have underscore 1.4.2 -->
|
||||
|
||||
// Support for playing D&D: Roll 3d6 for dexterity
|
||||
Accounts.onCreateUser(function(options, user) {
|
||||
var d6 = function () { return Math.floor(Math.random() * 6) + 1; };
|
||||
user.dexterity = d6() + d6() + d6();
|
||||
// We still want the default hook's 'profile' behavior.
|
||||
if (options.profile)
|
||||
user.profile = options.profile;
|
||||
return user;
|
||||
});
|
||||
|
||||
|
||||
<h2 id="accounts_passwords"><span>Passwords</span></h2>
|
||||
|
||||
The `accounts-password` package implements a complete system for
|
||||
password based authentication. In addition to the basic username and
|
||||
password based sign-in process it also supports email based sign-in
|
||||
including address verification and password recovery emails.
|
||||
|
||||
Unlike most web applications, the Meteor client does not send the user's
|
||||
password directly to the server. It uses the [Secure Remote Password
|
||||
protocol](http://en.wikipedia.org/wiki/Secure_Remote_Password_protocol)
|
||||
to ensure the server never sees the user's plain-text password. This
|
||||
helps protect against embarrassing password leaks if the server's
|
||||
database is compromised.
|
||||
|
||||
To add password support to your application, run `$ meteor add
|
||||
accounts-password`. You can construct your own user interface using the
|
||||
functions below, or use the [`accounts-ui` package](#accountsui) to
|
||||
include a turn-key user interface for password-based sign-in.
|
||||
|
||||
|
||||
{{> api_box accounts_createUser}}
|
||||
|
||||
On the client this function logs in as the newly created user on
|
||||
successful completion. On the server, it returns the newly created user
|
||||
id.
|
||||
|
||||
On the client, you must pass `password` and one of `username` or `email`
|
||||
— enough information for the user to be able to log in again
|
||||
later. On the server, you can pass any subset of these options, but the
|
||||
user will not be able to log in until it has an identifier and a
|
||||
password.
|
||||
|
||||
To create an account without a password on the server and still let the
|
||||
user pick their own password, call `createUser` with the `email` option
|
||||
and then
|
||||
call [`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail). This
|
||||
will send the user an email with a link to set their initial password.
|
||||
|
||||
By default the `profile` option is added directly to the new user document. To
|
||||
override this behavior, use [`Accounts.onCreateUser`](#accounts_createuser).
|
||||
|
||||
This function is only used for creating users with passwords. The external
|
||||
service login flows do not use this function.
|
||||
|
||||
|
||||
{{> api_box accounts_changePassword}}
|
||||
|
||||
{{> api_box accounts_forgotPassword}}
|
||||
|
||||
This triggers a call
|
||||
to [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail)
|
||||
on the server. Pass the token the user receives in this email
|
||||
to [`Accounts.resetPassword`](#accounts_resetpassword) to
|
||||
complete the password reset process.
|
||||
|
||||
If you are using the [`accounts-ui` package](#pkg_accounts_ui), this is handled
|
||||
automatically. Otherwise, it is your responsiblity to prompt the user for the
|
||||
new password and call `resetPassword`.
|
||||
|
||||
{{> api_box accounts_resetPassword}}
|
||||
|
||||
This function accepts tokens generated
|
||||
by [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail)
|
||||
and
|
||||
[`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail).
|
||||
|
||||
{{> api_box accounts_setPassword}}
|
||||
|
||||
{{> api_box accounts_verifyEmail}}
|
||||
|
||||
This function accepts tokens generated
|
||||
by [`Accounts.sendVerificationEmail`](#accounts_sendverificationemail). It
|
||||
sets the `emails.verified` field in the user record.
|
||||
|
||||
{{> api_box accounts_sendResetPasswordEmail}}
|
||||
|
||||
The token in this email should be passed
|
||||
to [`Accounts.resetPassword`](#accounts_resetpassword).
|
||||
|
||||
To customize the contents of the email, see
|
||||
[`Accounts.emailTemplates`](#accounts_emailtemplates).
|
||||
|
||||
{{> api_box accounts_sendEnrollmentEmail}}
|
||||
|
||||
The token in this email should be passed
|
||||
to [`Accounts.resetPassword`](#accounts_resetpassword).
|
||||
|
||||
To customize the contents of the email, see
|
||||
[`Accounts.emailTemplates`](#accounts_emailtemplates).
|
||||
|
||||
{{> api_box accounts_sendVerificationEmail}}
|
||||
|
||||
The token in this email should be passed
|
||||
to [`Accounts.verifyEmail`](#accounts_verifyemail).
|
||||
|
||||
To customize the contents of the email, see
|
||||
[`Accounts.emailTemplates`](#accounts_emailtemplates).
|
||||
|
||||
{{> api_box accounts_emailTemplates}}
|
||||
|
||||
This is an `Object` with several fields that are used to generate text
|
||||
for the emails by `sendResetPasswordEmail`, `sendEnrollmentEmail`, and
|
||||
`sendVerificationEmail`.
|
||||
|
||||
Override fields of the object by assigning to them:
|
||||
|
||||
- `from`: A `String` with an [RFC5322](http://tools.ietf.org/html/rfc5322) From
|
||||
address. By default email is from `no-reply@meteor.com`. If you wish to
|
||||
receive email from users asking for help with their account, be sure to set
|
||||
this to an email address that you can receive email at.
|
||||
- `siteName`: The public name of your application. Defaults to the DNS name of
|
||||
the application (eg: `awesome.meteor.com`).
|
||||
- `resetPassword`: An `Object` with two fields:
|
||||
- `resetPassword.subject`: A `Function` that takes a user object and returns
|
||||
a `String` for the subject line of a reset password email.
|
||||
- `resetPassword.text`: A `Function` that takes a user object and a url, and
|
||||
returns the body text for a reset password email.
|
||||
- `enrollAccount`: Same as `resetPassword`, but for initial password setup for
|
||||
new accounts.
|
||||
- `verifyEmail`: Same as `resetPassword`, but for verifying the users email
|
||||
address.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
Accounts.emailTemplates.siteName = "AwesomeSite";
|
||||
Accounts.emailTemplates.from = "AwesomeSite Admin <accounts@example.com>";
|
||||
Accounts.emailTemplates.enrollAccount.subject = function (user) {
|
||||
return "Welcome to Awesome Town, " + user.profile.name;
|
||||
};
|
||||
Accounts.emailTemplates.enrollAccount.text = function (user, url) {
|
||||
return "You have been selected to participate in building a better future!"
|
||||
+ " To activate your account, simply click the link below:\n\n"
|
||||
+ url;
|
||||
};
|
||||
|
||||
|
||||
<h2 id="templates_api"><span>Templates</span></h2>
|
||||
|
||||
A template that you declare as `<{{! }}template name="foo"> ... </{{!
|
||||
@@ -1639,306 +1999,6 @@ sub-template.
|
||||
{{/api_box_inline}}
|
||||
|
||||
|
||||
|
||||
<h2 id="accounts_api"><span>Accounts</span></h2>
|
||||
|
||||
XXX intro text
|
||||
|
||||
{{> api_box user}}
|
||||
|
||||
Retreives the user record for the current user from
|
||||
the [`Meteor.users`](#meteor_users) collection.
|
||||
|
||||
On the client this will be a subset of the fields in the document, only
|
||||
those that are published from the server are available on the client. By
|
||||
default the server publishes `username`, `emails`, and
|
||||
`profile`. See [`Meteor.users`](#meteor_users) for more on
|
||||
the fields used user documents.
|
||||
|
||||
If the user is logged in but the user's database record is not fully
|
||||
loaded yet, this returns an object with only the `_id` field set. During
|
||||
this period [`userLoaded`](#meteor_userloaded) will return
|
||||
`false`.
|
||||
|
||||
{{> api_box userId}}
|
||||
|
||||
{{> api_box users}}
|
||||
|
||||
This collection contains one document per user. Example user document:
|
||||
|
||||
{
|
||||
_id: "bbca5d6a-2156-41c4-89da-0329e8c99a4f" // userId
|
||||
username: "cool_kid_13", // unique name
|
||||
emails: [
|
||||
// each email address can only belong to one user.
|
||||
{ address: "cool@example.com", verified: true },
|
||||
{ address: "another@different.com", verified: false }
|
||||
],
|
||||
profile: {
|
||||
// The profile is writable by the user by default.
|
||||
name: "Joe Schmoe"
|
||||
},
|
||||
services: {
|
||||
facebook: {
|
||||
id: "709050", // facebook id
|
||||
accessToken: "AAACCgdX7G2...AbV9AZDZD"
|
||||
},
|
||||
resume: {
|
||||
loginTokens: [
|
||||
{ token: "97e8c205-c7e4-47c9-9bea-8e2ccc0694cd",
|
||||
when: 1349761684048 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Like all <a href='#collections'>Meteor.Collection</a>s, you can access all
|
||||
documents on the server, but only those specifically published by the server are
|
||||
available on the client.
|
||||
|
||||
By default, the current user's `username`, `emails` and `profile` are
|
||||
published to the client. You can publish additional fields for the
|
||||
current user with:
|
||||
|
||||
Meteor.publish("userData", function () {
|
||||
return Meteor.users.find({_id: this.userId},
|
||||
{fields: {'other': 1, 'things': 1}});
|
||||
});
|
||||
|
||||
If the `autopublish` package is installed, the `username` and `profile` fields
|
||||
for all users are published to all clients. To publish specific fields from all
|
||||
users:
|
||||
|
||||
Meteor.publish("allUserData", function () {
|
||||
return Meteor.users.find({}, {fields: {'nested.things': 1}});
|
||||
});
|
||||
|
||||
Users are by default allowed to specify their own `profile` field with
|
||||
[`Accounts.createUser`](#accounts_createuser) and modify it with
|
||||
`Meteor.users.update`. To allow users to edit additional fields, use
|
||||
[`Meteor.users.allow`](#allow). To forbid users from making any modifications to
|
||||
their user document:
|
||||
|
||||
Meteor.users.deny({update: function () { return true; }});
|
||||
|
||||
|
||||
{{> api_box userLoaded}}
|
||||
|
||||
There are some cases when the client knows the id of the logged in user
|
||||
but has not yet received the user data from the server. For example, if
|
||||
the user is logged in and reloads the page the user data will be
|
||||
unavailable during initial page load.
|
||||
|
||||
During these periods, `userLoaded` will return false
|
||||
and [`user`](#meteor_user) will return an object with only
|
||||
the `_id` key.
|
||||
|
||||
{{#note}}
|
||||
We realize this is inconvenient. It is a temporary solution. In the
|
||||
future we will either make it unnecessary or fold it into a more
|
||||
general mechanism.
|
||||
{{/note}}
|
||||
|
||||
{{> api_box logout}}
|
||||
|
||||
{{> api_box loginWithPassword}}
|
||||
|
||||
{{> api_box loginWithOAuth}}
|
||||
|
||||
These functions initiate the login process with a third party OAuth
|
||||
provider (eg: Facebook, Google, etc). When called they open a new pop-up
|
||||
window that loads the provider's login page. Once the user has logged in
|
||||
with the provider, the pop-up window is closed and the Meteor client
|
||||
logs in to the Meteor server with the information provided by the OAuth
|
||||
provider.
|
||||
|
||||
If the user has not already granted all the permissions requested they will be
|
||||
prompted to grant access to their account in the pop-up dialog. Values for the
|
||||
`requestPermissions` parameter differ for each login service:
|
||||
|
||||
- Facebook: <a href="http://developers.facebook.com/docs/authentication/permissions/">http://developers.facebook.com/docs/authentication/permissions/</a>
|
||||
- GitHub: <a href="http://developer.github.com/v3/oauth/#scopes">http://developer.github.com/v3/oauth/#scopes</a>
|
||||
- Google: <a href="https://developers.google.com/accounts/docs/OAuth2Login#scopeparameter">https://developers.google.com/accounts/docs/OAuth2Login#scopeparameter</a>
|
||||
|
||||
Currently, `loginWithTwitter` and `loginWithWeibo` do not support
|
||||
`requestPermissions`.
|
||||
|
||||
|
||||
{{> api_box accounts_createUser}}
|
||||
|
||||
On the client this function logs in as the newly created user on
|
||||
successful completion. On the server, it returns the newly created user
|
||||
id.
|
||||
|
||||
On the client, you must pass `password` and one of `username` or `email`
|
||||
— enough information for the user to be able to log in again
|
||||
later. On the server, you can pass any subset of these options, but the
|
||||
user will not be able to log in until it has an identifier and a
|
||||
password.
|
||||
|
||||
To create an account without a password on the server and still let the
|
||||
user pick their own password, call `createUser` with the `email` option
|
||||
and then
|
||||
call [`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail). This
|
||||
will send the user an email with a link to set their initial password.
|
||||
|
||||
By default the `profile` option is added directly to the new user document. To
|
||||
override this behavior, use [`Accounts.onCreateUser`](#accounts_createuser).
|
||||
|
||||
This function is only used for creating users with passwords. The OAuth
|
||||
login flows do not use this function.
|
||||
|
||||
|
||||
{{> api_box accounts_changePassword}}
|
||||
|
||||
{{> api_box accounts_forgotPassword}}
|
||||
|
||||
This triggers a call
|
||||
to [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail)
|
||||
on the server. Pass the token the user receives in this email
|
||||
to [`Accounts.resetPassword`](#accounts_resetpassword) to
|
||||
complete the password reset process.
|
||||
|
||||
If you are using the <a href="#pkg_accounts_ui">`accounts-ui`
|
||||
package</a>, this is handled automatically. Otherwise, it is your
|
||||
responsiblity to prompt the user for the new password and call `resetPassword`.
|
||||
|
||||
- XXX token goes to `Accounts._resetPasswordToken`.
|
||||
|
||||
{{> api_box accounts_resetPassword}}
|
||||
|
||||
This function accepts tokens generated
|
||||
by [`Accounts.sendResetPasswordEmail`](#accounts_sendresetpasswordemail)
|
||||
and
|
||||
[`Accounts.sendEnrollmentEmail`](#accounts_sendenrollmentemail)
|
||||
|
||||
{{> api_box accounts_setPassword}}
|
||||
|
||||
{{> api_box accounts_verifyEmail}}
|
||||
|
||||
This function accepts tokens generated
|
||||
by [`Accounts.sendVerificationEmail`](#accounts_sendverificationemail). It
|
||||
sets the `emails.verified` field in the user record.
|
||||
|
||||
{{> api_box accounts_sendResetPasswordEmail}}
|
||||
|
||||
The token in this email should be passed
|
||||
to [`Accounts.resetPassword`](#accounts_resetpassword).
|
||||
|
||||
To customize the contents of the email, see
|
||||
[`Accounts.emailTemplates`](#accounts_emailtemplates).
|
||||
|
||||
{{> api_box accounts_sendEnrollmentEmail}}
|
||||
|
||||
The token in this email should be passed
|
||||
to [`Accounts.resetPassword`](#accounts_resetpassword).
|
||||
|
||||
To customize the contents of the email, see
|
||||
[`Accounts.emailTemplates`](#accounts_emailtemplates).
|
||||
|
||||
{{> api_box accounts_sendVerificationEmail}}
|
||||
|
||||
The token in this email should be passed
|
||||
to [`Accounts.verifyEmail`](#accounts_verifyemail).
|
||||
|
||||
To customize the contents of the email, see
|
||||
[`Accounts.emailTemplates`](#accounts_emailtemplates).
|
||||
|
||||
{{> api_box accounts_emailTemplates}}
|
||||
|
||||
This is an `Object` with several fields that are used to generate text
|
||||
for the emails by `sendResetPasswordEmail`, `sendEnrollmentEmail`, and
|
||||
`sendVerificationEmail`.
|
||||
|
||||
Override fields of the object by assigning to them:
|
||||
|
||||
- `from`: A `String` with an <a href="http://tools.ietf.org/html/rfc5322"
|
||||
target="_blank">RFC5322</a> From address. By default email is from
|
||||
`no-reply@meteor.com`. If you wish to receive email from users asking for help
|
||||
with their account, be sure to set this to an email address that you can receive
|
||||
email at.
|
||||
- `siteName`: The public name of your application. Defaults to the DNS name of
|
||||
the application (eg: `awesome.meteor.com`).
|
||||
- `resetPassword`: An `Object` with two fields:
|
||||
- `resetPassword.subject`: A `Function` that takes a user object and returns
|
||||
a `String` for the subject line of a reset password email.
|
||||
- `resetPassword.text`: A `Function` that takes a user object and a url, and
|
||||
returns the body text for a reset password email.
|
||||
- `enrollAccount`: Same as `resetPassword`, but for initial password setup for
|
||||
new accounts.
|
||||
- `verifyEmail`: Same as `resetPassword`, but for verifying the users email
|
||||
address.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
Accounts.emailTemplates.siteName = "AwesomeSite";
|
||||
Accounts.emailTemplates.from = "AwesomeSite Admin <accounts@example.com>";
|
||||
Accounts.emailTemplates.enrollAccount.subject = function (user) {
|
||||
return "Welcome to Awesome Town, " + user.profile.name;
|
||||
};
|
||||
Accounts.emailTemplates.enrollAccount.text = function (user, url) {
|
||||
return "You have been selected to participate in building better future!"
|
||||
+ " To activate your account, simply click the link below:\n\n"
|
||||
+ url;
|
||||
};
|
||||
|
||||
|
||||
{{> api_box accounts_config}}
|
||||
{{> api_box accounts_ui_config}}
|
||||
|
||||
{{> api_box accounts_validateNewUser}}
|
||||
|
||||
This can be called multiple times. If any of the functions return
|
||||
`false` the new user creation is aborted.
|
||||
|
||||
Example:
|
||||
|
||||
// All users must have a username longer than 3 characters.
|
||||
Accounts.validateNewUser(function (user) {
|
||||
return user.username && user.username.length >= 3;
|
||||
});
|
||||
|
||||
{{> api_box accounts_onCreateUser}}
|
||||
|
||||
Use this when you need to do more than simply accept or reject new user
|
||||
creation. With this function you can programatically control the
|
||||
contents of new user documents.
|
||||
|
||||
The function you pass will be called with two arguments: `options` and
|
||||
`user`. The `options` argument comes
|
||||
from [`Accounts.createUser`](#accounts_createuser) for
|
||||
password-based users or from the OAuth login flow. `options` may come
|
||||
from an untrusted client so make to validate any values you read from
|
||||
it. The `user` argument is created on the server and contains a
|
||||
proposed user object with all the automatically generated fields
|
||||
required for the user to log in.
|
||||
|
||||
The function should return a new user object with whatever modifications
|
||||
are desired. The returned object is inserted directly into
|
||||
the [`Meteor.users`](#meteor_users) collection.
|
||||
|
||||
The default create user function simply copies `options.profile` into
|
||||
the new user document. Calling `onCreateUser` overrides the default
|
||||
hook. This can only be called once.
|
||||
|
||||
Example:
|
||||
|
||||
// Make members of the GitHub 'meteor' group into admins.
|
||||
Accounts.onCreateUser(function(options, user) {
|
||||
// http://developer.github.com/v3/orgs/members/#get-member
|
||||
if (!Meteor.http.get(
|
||||
"https://api.github.com/orgs/meteor/members/" +
|
||||
user.services.github.username +
|
||||
"?access_token=" + user.services.github.accessToken
|
||||
).error) {
|
||||
user.admin = true;
|
||||
}
|
||||
return user;
|
||||
});
|
||||
|
||||
|
||||
<h2 id="timers"><span>Timers</span></h2>
|
||||
|
||||
Meteor uses global environment variables
|
||||
@@ -2297,9 +2357,9 @@ send mail. Currently, Meteor supports sending mail over SMTP; the `MAIL_URL`
|
||||
environment variable should be of the form
|
||||
`smtp://USERNAME:PASSWORD@HOST:PORT/`. For apps deployed with `meteor deploy`,
|
||||
`MAIL_URL` defaults to an account (provided by
|
||||
<a href="http://www.mailgun.com/" target="_blank">Mailgun</a>) which allows
|
||||
apps to send up to 200 emails per day; you may override this default by
|
||||
assigning to `process.env.MAIL_URL` before your first call to `Email.send`.
|
||||
[Mailgun](http://www.mailgun.com/)) which allows apps to send up to 200 emails
|
||||
per day; you may override this default by assigning to `process.env.MAIL_URL`
|
||||
before your first call to `Email.send`.
|
||||
|
||||
If `MAIL_URL` is not set (eg, when running your application locally),
|
||||
`Email.send` outputs the message to standard output instead.
|
||||
|
||||
@@ -741,7 +741,7 @@ Template.api.loginWithPassword = {
|
||||
{
|
||||
name: "password",
|
||||
type: "String",
|
||||
descr: "The user's password. This is __not__ sent in plain text over the wire — it is secured with <a href='http://en.wikipedia.org/wiki/Secure_Remote_Password_protocol' target='_blank'>SRP</a>."
|
||||
descr: "The user's password. This is __not__ sent in plain text over the wire — it is secured with [SRP](http://en.wikipedia.org/wiki/Secure_Remote_Password_protocol)."
|
||||
},
|
||||
{
|
||||
name: "callback",
|
||||
@@ -752,11 +752,11 @@ Template.api.loginWithPassword = {
|
||||
};
|
||||
|
||||
|
||||
Template.api.loginWithOAuth = {
|
||||
id: "meteor_loginwithoauth",
|
||||
name: "Meteor.loginWith<i>OAuthProvider</i>([options], [callback])",
|
||||
Template.api.loginWithExternalService = {
|
||||
id: "meteor_loginwithexternalservice",
|
||||
name: "Meteor.loginWith<i>ExternalService</i>([options], [callback])",
|
||||
locus: "Client",
|
||||
descr: ["Log the user in using an external OAuth service."],
|
||||
descr: ["Log the user in using an external service."],
|
||||
args: [
|
||||
{
|
||||
name: "callback",
|
||||
@@ -773,6 +773,76 @@ Template.api.loginWithOAuth = {
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
|
||||
Template.api.accounts_config = {
|
||||
id: "accounts_config",
|
||||
name: "Accounts.config(options)",
|
||||
locus: "Anywhere",
|
||||
descr: ["Set global accounts options."],
|
||||
options: [
|
||||
{
|
||||
name: "sendVerificationEmail",
|
||||
type: "Boolean",
|
||||
descr: "New users with an email address will receive an address verification email."
|
||||
},
|
||||
{
|
||||
name: "forbidClientAccountCreation",
|
||||
type: "Boolean",
|
||||
descr: "[`createUser`](#accounts_createuser) requests from the client will be rejected."
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.accounts_ui_config = {
|
||||
id: "accounts_ui_config",
|
||||
name: "Accounts.ui.config(options)",
|
||||
locus: "Client",
|
||||
descr: ["Configure the behavior of [`{{loginButtons}}`](#accountsui)."],
|
||||
options: [
|
||||
{
|
||||
name: "requestPermissions",
|
||||
type: "Object",
|
||||
descr: "Which [permissions](#requestpermissions) to request from the user for each external service."
|
||||
},
|
||||
{
|
||||
name: "passwordSignupFields",
|
||||
type: "String",
|
||||
descr: "Which fields to display in the user creation form. One of '`USERNAME_AND_EMAIL`', '`USERNAME_AND_OPTIONAL_EMAIL`', '`USERNAME_ONLY`', or '`EMAIL_ONLY`' (default)."
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.accounts_validateNewUser = {
|
||||
id: "accounts_validatenewuser",
|
||||
name: "Accounts.validateNewUser(func)",
|
||||
locus: "Server",
|
||||
descr: ["Set restrictions on new user creation."],
|
||||
args: [
|
||||
{
|
||||
name: "func",
|
||||
type: "Function",
|
||||
descr: "Called whenever a new user is created. Takes the new user object, and returns true to allow the creation or false to abort."
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.accounts_onCreateUser = {
|
||||
id: "accounts_oncreateuser",
|
||||
name: "Accounts.onCreateUser(func)",
|
||||
locus: "Server",
|
||||
descr: ["Customize new user creation."],
|
||||
args: [
|
||||
{
|
||||
name: "func",
|
||||
type: "Function",
|
||||
descr: "Called whenever a new user is created. Return the new user object, or throw an `Error` to abort the creation."
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
|
||||
Template.api.accounts_createUser = {
|
||||
id: "accounts_createuser",
|
||||
name: "Accounts.createUser(options, [callback])",
|
||||
@@ -985,75 +1055,6 @@ Template.api.accounts_emailTemplates = {
|
||||
|
||||
|
||||
|
||||
Template.api.accounts_config = {
|
||||
id: "accounts_config",
|
||||
name: "Accounts.config(options)",
|
||||
locus: "Anywhere",
|
||||
descr: ["Set global accounts options."],
|
||||
options: [
|
||||
{
|
||||
name: "sendVerificationEmail",
|
||||
type: "Boolean",
|
||||
descr: "New users with an email address will receive an address verification email."
|
||||
},
|
||||
{
|
||||
name: "forbidClientAccountCreation",
|
||||
type: "Boolean",
|
||||
descr: "[`createUser`](#accounts_createuser) requests from the client will be rejected."
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.accounts_ui_config = {
|
||||
id: "accounts_ui_config",
|
||||
name: "Accounts.ui.config(options)",
|
||||
locus: "Client",
|
||||
descr: ["Set Accounts UI options for the `loginButtons` template."],
|
||||
options: [
|
||||
{
|
||||
name: "requestPermissions",
|
||||
type: "Object",
|
||||
descr: "Which permissions to request from the user for each OAuth service. For example: `{facebook: ['user_likes'], github: ['user', 'repo']}`"
|
||||
},
|
||||
{
|
||||
name: "passwordSignupFields",
|
||||
type: "String",
|
||||
descr: "Which fields to display in the user creation form. One of '`USERNAME_AND_EMAIL`', '`USERNAME_AND_OPTIONAL_EMAIL`', '`USERNAME_ONLY`', or '`EMAIL_ONLY`' (default)."
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.accounts_validateNewUser = {
|
||||
id: "accounts_validatenewuser",
|
||||
name: "Accounts.validateNewUser(func)",
|
||||
locus: "Server",
|
||||
descr: ["Set restrictions on new user creation."],
|
||||
args: [
|
||||
{
|
||||
name: "func",
|
||||
type: "Function",
|
||||
descr: "Called whenever a new user is created. Takes the new user object, and returns true to allow the creation or false to abort."
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Template.api.accounts_onCreateUser = {
|
||||
id: "accounts_oncreateuser",
|
||||
name: "Accounts.onCreateUser(func)",
|
||||
locus: "Server",
|
||||
descr: ["Customize new user creation."],
|
||||
args: [
|
||||
{
|
||||
name: "func",
|
||||
type: "Function",
|
||||
descr: "Called whenever a new user is created. Return the new user object, or throw an `Error` to abort the creation."
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
Template.api.setTimeout = {
|
||||
id: "meteor_settimeout",
|
||||
name: "Meteor.setTimeout",
|
||||
@@ -1402,8 +1403,7 @@ Template.api.template_data = {
|
||||
};
|
||||
|
||||
var rfc = function (descr) {
|
||||
return ('<a href="http://tools.ietf.org/html/rfc5322" target="_blank">RFC5322'
|
||||
+ '</a> ' + descr);
|
||||
return '[RFC5322](http://tools.ietf.org/html/rfc5322) ' + descr;
|
||||
};
|
||||
|
||||
Template.api.email_send = {
|
||||
|
||||
@@ -22,12 +22,12 @@ when writing those apps.
|
||||
|
||||
<h2 id="structuringyourapp">Structuring your application</h2>
|
||||
|
||||
A Meteor application is a mix of JavaScript that runs inside a
|
||||
client web browser, JavaScript that runs on the Meteor server inside
|
||||
a Node.js container, and all the supporting HTML fragments, CSS rules,
|
||||
and static assets. Meteor automates the packaging and transmission
|
||||
of these different components. And, it is quite flexible about how
|
||||
you choose to structure those components in your file tree.
|
||||
A Meteor application is a mix of JavaScript that runs inside a client web
|
||||
browser, JavaScript that runs on the Meteor server inside a
|
||||
[Node.js](http://nodejs.org/) container, and all the supporting HTML fragments,
|
||||
CSS rules, and static assets. Meteor automates the packaging and transmission
|
||||
of these different components. And, it is quite flexible about how you choose
|
||||
to structure those components in your file tree.
|
||||
|
||||
The only server asset is JavaScript. Meteor gathers all your JavaScript
|
||||
files, excluding anything under the `client`
|
||||
@@ -108,8 +108,7 @@ your client model code.
|
||||
|
||||
Meteor's protocol for distributing document updates is database
|
||||
agnostic. By default, Meteor applications use the
|
||||
familiar <a target="_blank"
|
||||
href="http://www.mongodb.org/display/DOCS/Manual">MongoDB API</a>:
|
||||
familiar [MongoDB API](http://www.mongodb.org/display/DOCS/Manual):
|
||||
servers store documents in MongoDB collections, and clients cache those
|
||||
documents in a client-side cache that implements the same Mongo API for
|
||||
queries and updates.
|
||||
@@ -149,14 +148,8 @@ server-side database driver and/or a client-side cache that implements
|
||||
an alternative API. The `mongo-livedata` is a good starting point for
|
||||
such a project.
|
||||
|
||||
{{#note}}
|
||||
A pre-release version of Meteor includes a user login system and a set of tools
|
||||
for securing read and write access to data based on the logged-in user. For more
|
||||
information, see the
|
||||
<a target="_blank"
|
||||
href="https://github.com/meteor/meteor/wiki/Getting-Started-with-Auth">Getting
|
||||
Started with Auth</a> wiki page.
|
||||
{{/note}}
|
||||
XXX should we mention security/auth at all here, or just expect folks to keep
|
||||
reading until the accounts section?
|
||||
|
||||
{{/better_markdown}}
|
||||
</template>
|
||||
@@ -166,11 +159,10 @@ Started with Auth</a> wiki page.
|
||||
|
||||
<h2 id="reactivity">Reactivity</h2>
|
||||
|
||||
Meteor embraces the concept of
|
||||
<a target="_blank" href="http://en.wikipedia.org/wiki/Reactive_programming">
|
||||
reactive programming</a>. This means that you can write your code in a
|
||||
simple imperative style, and the result will be automatically
|
||||
recalculated whenever data changes that your code depends on.
|
||||
Meteor embraces the concept of [reactive
|
||||
programming](http://en.wikipedia.org/wiki/Reactive_programming). This means that
|
||||
you can write your code in a simple imperative style, and the result will be
|
||||
automatically recalculated whenever data changes that your code depends on.
|
||||
|
||||
Meteor.autosubscribe(function () {
|
||||
Meteor.subscribe("messages", Session.get("currentRoomId"));
|
||||
@@ -214,10 +206,11 @@ And the reactive data sources that can trigger changes are:
|
||||
* [`Meteor.userId`](#meteor_userid)
|
||||
* [`Meteor.userLoaded`](#meteor_userloaded)
|
||||
|
||||
Meteor's <a href="https://github.com/meteor/meteor/blob/master/packages/deps/deps.js" target="_blank">implementation</a>
|
||||
of reactivity is short and sweet, about 50 lines of code. You can
|
||||
hook into it yourself to add new reactive contexts or data sources,
|
||||
using the [`Meteor.deps`](#meteor_deps) module.
|
||||
Meteor's
|
||||
[implementation](https://github.com/meteor/meteor/blob/master/packages/deps/deps.js)
|
||||
of reactivity is short and sweet, about 50 lines of code. You can hook into it
|
||||
yourself to add new reactive contexts or data sources, using the
|
||||
[`Meteor.deps`](#meteor_deps) module.
|
||||
|
||||
{{/better_markdown}}
|
||||
</template>
|
||||
@@ -267,15 +260,14 @@ the new elements using a library like jQuery. In that case, call
|
||||
[`Meteor.flush`](#meteor_flush) to bring the DOM up to date
|
||||
immediately.
|
||||
|
||||
When live-updating DOM elements are taken off the screen, they are
|
||||
automatically cleaned up — their callbacks are torn down, any
|
||||
associated database queries are stopped, and they stop updating. For
|
||||
this reason, you never have to worry about
|
||||
the <a href="http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/"
|
||||
target="_blank">zombie templates</a> that plague hand-written update
|
||||
logic. To protect your elements from cleanup, just make sure that they
|
||||
on-screen before your code returns to the event loop, or before any
|
||||
call you make to [`Meteor.flush`](#meteor_flush).
|
||||
When live-updating DOM elements are taken off the screen, they are automatically
|
||||
cleaned up — their callbacks are torn down, any associated database
|
||||
queries are stopped, and they stop updating. For this reason, you never have to
|
||||
worry about the [zombie
|
||||
templates](http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/)
|
||||
that plague hand-written update logic. To protect your elements from cleanup,
|
||||
just make sure that they on-screen before your code returns to the event loop,
|
||||
or before any call you make to [`Meteor.flush`](#meteor_flush).
|
||||
|
||||
Another thorny problem in hand-written applications is element
|
||||
preservation. Suppose the user is typing text into an `<input>`
|
||||
@@ -313,10 +305,9 @@ available as a function on the global `Template` object.
|
||||
{{#note}}
|
||||
Today, the only templating system that has been packaged for Meteor is
|
||||
Handlebars. Let us know what templating systems you'd like to use with
|
||||
Meteor. Meanwhile, see
|
||||
the <a href="http://www.handlebarsjs.com/" target="_blank">Handlebars documentation</a>
|
||||
and <a href="https://github.com/meteor/meteor/wiki/Handlebars" target="_blank">Meteor
|
||||
Handlebars extensions</a>.
|
||||
Meteor. Meanwhile, see the [Handlebars
|
||||
documentation](http://www.handlebarsjs.com/) and [Meteor Handlebars
|
||||
extensions](https://github.com/meteor/meteor/wiki/Handlebars).
|
||||
{{/note}}
|
||||
|
||||
A template with a `name` of `hello` is rendered by calling the
|
||||
|
||||
@@ -436,11 +436,11 @@ pre {
|
||||
@media (min-width: 1024px) {
|
||||
/* ipad landscape and desktop */
|
||||
#main {
|
||||
width: 600px;
|
||||
margin-left: 310px; /* nav width + padding */
|
||||
width: 610px;
|
||||
margin-left: 330px; /* nav width + padding */
|
||||
}
|
||||
#nav {
|
||||
width: 250px;
|
||||
width: 270px;
|
||||
}
|
||||
.github-ribbon {
|
||||
display: block;
|
||||
|
||||
@@ -48,18 +48,29 @@ Meteor.startup(function () {
|
||||
}
|
||||
});
|
||||
|
||||
window.onhashchange = function () {
|
||||
scrollToSection(location.hash);
|
||||
};
|
||||
|
||||
var scrollToSection = function (section) {
|
||||
ignore_waypoints = true;
|
||||
Session.set("section", section.substr(1));
|
||||
scroller().animate({
|
||||
scrollTop: $(section).offset().top
|
||||
}, 500, 'swing', function () {
|
||||
window.location.hash = section;
|
||||
ignore_waypoints = false;
|
||||
});
|
||||
};
|
||||
|
||||
$('#main, #nav').delegate("a[href^='#']", 'click', function (evt) {
|
||||
evt.preventDefault();
|
||||
var sel = $(this).attr('href');
|
||||
ignore_waypoints = true;
|
||||
Session.set("section", sel.substr(1));
|
||||
scroller().animate({
|
||||
scrollTop: $(sel).offset().top
|
||||
}, 500, 'swing', function () {
|
||||
window.location.hash = sel;
|
||||
ignore_waypoints = false;
|
||||
});
|
||||
scrollToSection(sel);
|
||||
});
|
||||
|
||||
// Make external links open in a new tab.
|
||||
$('a:not([href^="#"])').attr('target', '_blank');
|
||||
});
|
||||
|
||||
var toc = [
|
||||
@@ -89,13 +100,13 @@ var toc = [
|
||||
|
||||
"Publish and subscribe", [
|
||||
"Meteor.publish", [
|
||||
{instance: "this", name: "userId", id: "publish_userId"},
|
||||
{instance: "this", name: "set", id: "publish_set"},
|
||||
{instance: "this", name: "unset", id: "publish_unset"},
|
||||
{instance: "this", name: "complete", id: "publish_complete"},
|
||||
{instance: "this", name: "flush", id: "publish_flush"},
|
||||
{instance: "this", name: "onStop", id: "publish_onstop"},
|
||||
{instance: "this", name: "stop", id: "publish_stop"},
|
||||
{instance: "this", name: "userId", id: "publish_userId"}
|
||||
{instance: "this", name: "stop", id: "publish_stop"}
|
||||
],
|
||||
"Meteor.subscribe",
|
||||
"Meteor.autosubscribe"
|
||||
@@ -141,7 +152,8 @@ var toc = [
|
||||
{type: "spacer"},
|
||||
{name: "Selectors", style: "noncode"},
|
||||
{name: "Modifiers", style: "noncode"},
|
||||
{name: "Sort specifiers", style: "noncode"}
|
||||
{name: "Sort specifiers", style: "noncode"},
|
||||
{name: "Field specifiers", style: "noncode"}
|
||||
],
|
||||
|
||||
"Session", [
|
||||
@@ -150,6 +162,41 @@ var toc = [
|
||||
"Session.equals"
|
||||
],
|
||||
|
||||
{name: "Accounts", id: "accounts_api"}, [
|
||||
"Meteor.user",
|
||||
"Meteor.userId",
|
||||
"Meteor.users",
|
||||
"Meteor.userLoaded",
|
||||
"Meteor.logout",
|
||||
"Meteor.loginWithPassword",
|
||||
{name: "Meteor.loginWithFacebook", id: "meteor_loginwithexternalservice"},
|
||||
{name: "Meteor.loginWithGithub", id: "meteor_loginwithexternalservice"},
|
||||
{name: "Meteor.loginWithGoogle", id: "meteor_loginwithexternalservice"},
|
||||
{name: "Meteor.loginWithTwitter", id: "meteor_loginwithexternalservice"},
|
||||
{name: "Meteor.loginWithWeibo", id: "meteor_loginwithexternalservice"},
|
||||
{type: "spacer"},
|
||||
|
||||
"Accounts.config",
|
||||
"Accounts.ui.config",
|
||||
"Accounts.validateNewUser",
|
||||
"Accounts.onCreateUser"
|
||||
],
|
||||
|
||||
{name: "Passwords", id: "accounts_passwords"}, [
|
||||
"Accounts.createUser",
|
||||
"Accounts.changePassword",
|
||||
"Accounts.forgotPassword",
|
||||
"Accounts.resetPassword",
|
||||
"Accounts.setPassword",
|
||||
"Accounts.verifyEmail",
|
||||
{type: "spacer"},
|
||||
|
||||
"Accounts.sendResetPasswordEmail",
|
||||
"Accounts.sendEnrollmentEmail",
|
||||
"Accounts.sendVerificationEmail",
|
||||
"Accounts.emailTemplates"
|
||||
],
|
||||
|
||||
{name: "Templates", id: "templates_api"}, [
|
||||
{prefix: "Template", instance: "myTemplate", id: "template_call"}, [
|
||||
{name: "rendered", id: "template_rendered"},
|
||||
@@ -174,41 +221,6 @@ var toc = [
|
||||
{name: "Reactivity isolation", style: "noncode", id: "isolate"}
|
||||
],
|
||||
|
||||
{name: "Accounts", id: "accounts_api"}, [
|
||||
"Meteor.user",
|
||||
"Meteor.userId",
|
||||
"Meteor.users",
|
||||
"Meteor.userLoaded",
|
||||
"Meteor.logout",
|
||||
"Meteor.loginWithPassword",
|
||||
{name: "Meteor.loginWithFacebook", id: "meteor_loginwithoauth"},
|
||||
{name: "Meteor.loginWithGithub", id: "meteor_loginwithoauth"},
|
||||
{name: "Meteor.loginWithGoogle", id: "meteor_loginwithoauth"},
|
||||
{name: "Meteor.loginWithTwitter", id: "meteor_loginwithoauth"},
|
||||
{name: "Meteor.loginWithWeibo", id: "meteor_loginwithoauth"},
|
||||
{type: "spacer"},
|
||||
|
||||
"Accounts.createUser",
|
||||
"Accounts.changePassword",
|
||||
"Accounts.forgotPassword",
|
||||
"Accounts.resetPassword",
|
||||
"Accounts.setPassword",
|
||||
"Accounts.verifyEmail",
|
||||
{type: "spacer"},
|
||||
|
||||
"Accounts.sendResetPasswordEmail",
|
||||
"Accounts.sendEnrollmentEmail",
|
||||
"Accounts.sendVerificationEmail",
|
||||
"Accounts.emailTemplates",
|
||||
{type: "spacer"},
|
||||
|
||||
"Accounts.config",
|
||||
"Accounts.ui.config",
|
||||
"Accounts.validateNewUser",
|
||||
"Accounts.onCreateUser"
|
||||
],
|
||||
|
||||
|
||||
"Timers", [
|
||||
"Meteor.setTimeout",
|
||||
"Meteor.setInterval",
|
||||
|
||||
@@ -39,7 +39,8 @@ our thinking. We'd love to hear your feedback.
|
||||
<!-- change colors on these. $ and command output in grey, rest in
|
||||
white -->
|
||||
|
||||
The following works on all <a target="_blank" href="https://github.com/meteor/meteor/wiki/Supported-Platforms">supported platforms</a>.
|
||||
The following works on all [supported
|
||||
platforms](https://github.com/meteor/meteor/wiki/Supported-Platforms).
|
||||
|
||||
Install Meteor:
|
||||
|
||||
@@ -106,25 +107,24 @@ clean, classically beautiful APIs.
|
||||
<h2 id="resources">Developer Resources</h2>
|
||||
|
||||
<!-- https://github.com/blog/273-github-ribbons -->
|
||||
<a href="http://github.com/meteor/meteor" target="_blank"><img class="github-ribbon visible-desktop" style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub"></a>
|
||||
<a href="http://github.com/meteor/meteor"><img class="github-ribbon visible-desktop" style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub"></a>
|
||||
|
||||
If anything in Meteor catches your interest, we hope you'll get involved
|
||||
with the project!
|
||||
|
||||
<dl class="involved">
|
||||
<dt><span>Stack Overflow</span></dt>
|
||||
<dd>The best place to ask (and answer!) technical questions is
|
||||
on <a href="http://stackoverflow.com/questions/tagged/meteor" target="_blank">Stack
|
||||
Overflow</a>. Be sure to add the <code>meteor</code> tag to your
|
||||
question.
|
||||
<dd>The best place to ask (and answer!) technical questions is on [Stack
|
||||
Overflow](http://stackoverflow.com/questions/tagged/meteor). Be sure to add
|
||||
the <code>meteor</code> tag to your question.
|
||||
</dd>
|
||||
|
||||
<dt><span>Mailing lists</span></dt>
|
||||
<dd>
|
||||
We have two mailing lists for Meteor. <nobr><a href="http://groups.google.com/group/meteor-talk" target="_blank"><code>meteor-talk@googlegroups.com</code></a></nobr>
|
||||
We have two mailing lists for Meteor. <nobr><a href="http://groups.google.com/group/meteor-talk"><code>meteor-talk@googlegroups.com</code></a></nobr>
|
||||
is for general questions, requests for help, and new project
|
||||
announcements.
|
||||
<nobr><a href="http://groups.google.com/group/meteor-core" target="_blank"><code>meteor-core@googlegroups.com</code></a></nobr>
|
||||
<nobr><a href="http://groups.google.com/group/meteor-core"><code>meteor-core@googlegroups.com</code></a></nobr>
|
||||
is for discussing Meteor internals and proposed changes.
|
||||
</dd>
|
||||
|
||||
@@ -134,7 +134,7 @@ developers hang out here and will answer your questions whenever they
|
||||
can.</dd>
|
||||
|
||||
<dt><span>GitHub</span></dt>
|
||||
<dd>The code is on <a target="_blank" href="http://github.com/meteor/meteor">GitHub</a>. The best way to send a patch is with a GitHub pull request, and the best way to file a bug is in the GitHub bug tracker.</dd>
|
||||
<dd>The code is on <a href="http://github.com/meteor/meteor">GitHub</a>. The best way to send a patch is with a GitHub pull request, and the best way to file a bug is in the GitHub bug tracker.</dd>
|
||||
</dl>
|
||||
|
||||
{{/markdown}}
|
||||
|
||||
@@ -5,21 +5,25 @@
|
||||
A turn-key user interface for Meteor Accounts.
|
||||
|
||||
To add Accounts and a set of login controls to an application add the
|
||||
`accounts-ui` package and one or more login provider package:
|
||||
`accounts-ui` package and at least one login provider package:
|
||||
`accounts-password`, `accounts-facebook`, `accounts-github`,
|
||||
`accounts-google`, `accounts-twitter`, and `accounts-weibo`.
|
||||
`accounts-google`, `accounts-twitter`, or `accounts-weibo`.
|
||||
|
||||
Then simply add the `{{dstache}}loginButtons}}` helper to an HTML
|
||||
file. This will place a login widget on the page. If there is only one
|
||||
OAuth provider configured, this will add one login/logout button. If you
|
||||
use `accounts-password` or have more than one OAuth provider, this will
|
||||
add a 'Sign in' link which opens a dropdown menu with login options. To
|
||||
make the login dropdown right aligned, use `{{dstache}}loginButtons align=right}}`.
|
||||
Then simply add the `{{dstache}}loginButtons}}` helper to an HTML file. This
|
||||
will place a login widget on the page. If there is only one provider configured
|
||||
and it is an external service, this will add a login/logout button. If you use
|
||||
`accounts-password` or use multiple external login services, this will add
|
||||
a "Sign in" link which opens a dropdown menu with login options. To make the
|
||||
login dropdown right aligned (useful if you position the login buttons
|
||||
at the right edge of the screen), use `{{dstache}}loginButtons align=right}}`.
|
||||
|
||||
To configure the behavior of `{{dstache}}loginButtons}}`, use
|
||||
[`Accounts.ui.config`](#accounts_ui_config).
|
||||
|
||||
`accounts-ui` also includes modal popup dialogs to handle links from
|
||||
[`sendResetPasswordEmail`](#accounts_sendresetpasswordemail), [`sendVerificationEmail`](#accounts_sendverificationemail),
|
||||
and [`sendEnrollmentEmail`](#accounts_sendenrollmentemail). These
|
||||
do not have be manually placed in HTML, they are automatically activated
|
||||
do not have be manually placed in HTML: they are automatically activated
|
||||
when the URLs are loaded.
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ components, and several useful utility functions.
|
||||
Amplify defines a global namespace `amplify` on the client only. It does
|
||||
not run on the server.
|
||||
|
||||
For more information about Amplify, see <a href="http://amplifyjs.com/" target="_blank">http://amplifyjs.com/</a>.
|
||||
For more information about Amplify, see <http://amplifyjs.com/>.
|
||||
|
||||
{{/better_markdown}}
|
||||
</template>
|
||||
|
||||
@@ -8,7 +8,7 @@ functionality, it also provides an API for HTML5 pushState and
|
||||
client-side URL routing.
|
||||
|
||||
For more information about Backbone, see
|
||||
<a href="http://documentcloud.github.com/backbone/" target="_blank">http://documentcloud.github.com/backbone/</a>.
|
||||
<http://documentcloud.github.com/backbone/>.
|
||||
|
||||
{{/better_markdown}}
|
||||
</template>
|
||||
|
||||
@@ -9,7 +9,7 @@ interactions including typography, forms, buttons, tables, grids, and
|
||||
navigation.
|
||||
|
||||
For more information about Bootstrap, see
|
||||
<a href="http://twitter.github.com/bootstrap/" target="_blank">http://twitter.github.com/bootstrap/</a>.
|
||||
<http://twitter.github.com/bootstrap/>.
|
||||
|
||||
{{/better_markdown}}
|
||||
</template>
|
||||
|
||||
@@ -10,8 +10,7 @@ interpretation at runtime.
|
||||
CoffeeScript is supported on both the client and the server. Files
|
||||
ending with `.coffee` are automatically compiled to JavaScript.
|
||||
|
||||
See <a href="http://jashkenas.github.com/coffee-script/" target="_blank">http://jashkenas.github.com/coffee-script/</a>
|
||||
for more information.
|
||||
See <http://jashkenas.github.com/coffee-script/> for more information.
|
||||
|
||||
{{/better_markdown}}
|
||||
</template>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
## `d3`
|
||||
|
||||
[D3.js](http://d3js.org/D3.js) is a JavaScript library for manipulating
|
||||
[D3.js](http://d3js.org/) is a JavaScript library for manipulating
|
||||
documents based on data. D3 helps you bring data to life using HTML, SVG
|
||||
and CSS. D3's emphasis on web standards gives you the full capabilities
|
||||
of modern browsers without tying yourself to a proprietary framework,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
## `jquery`
|
||||
|
||||
<a href="http://jquery.com/" target="_blank">jQuery</a> is a fast and concise JavaScript
|
||||
[jQuery](http://jquery.com/) is a fast and concise JavaScript
|
||||
Library that simplifies HTML document traversing, event handling,
|
||||
animating, and Ajax interactions for rapid web development.
|
||||
|
||||
|
||||
@@ -10,8 +10,7 @@ With the `less` package installed, `.less` files in your application are
|
||||
automatically compiled to CSS and the results are included in the client
|
||||
CSS bundle.
|
||||
|
||||
See <a href="http://lesscss.org/" target="_blank">http://lesscss.org/</a> for
|
||||
documentation of the LESS language.
|
||||
See <http://lesscss.org/> for documentation of the LESS language.
|
||||
|
||||
{{/better_markdown}}
|
||||
</template>
|
||||
|
||||
@@ -7,10 +7,15 @@ expresions. With the `sass` package installed, `.sass` files in your
|
||||
application are automatically compiled to CSS and the results are
|
||||
included in the client CSS bundle.
|
||||
|
||||
See <a href="https://github.com/visionmedia/sass.js" target="_blank">https://github.com/visionmedia/sass.js</a>
|
||||
for the JavaScript implementation of the Sass language
|
||||
and <a href="http://sass-lang.com/" target="_blank">http://sass-lang.com/</a> for the
|
||||
original project.
|
||||
See <https://github.com/visionmedia/sass.js> for the JavaScript implementation
|
||||
of the Sass language and <http://sass-lang.com/> for the original project.
|
||||
|
||||
{{#warning}}
|
||||
The Sass JavaScript implementation used by Node is unmaintained and doesn't
|
||||
implement the newest language syntax documented at <http://sass-lang.com/>. It
|
||||
may be removed from a future version of Meteor; consider using [Less](#less) or
|
||||
[Stylus](#stylus) instead.
|
||||
{{/warning}}
|
||||
|
||||
{{/better_markdown}}
|
||||
</template>
|
||||
|
||||
@@ -3,37 +3,33 @@
|
||||
## `spiderable`
|
||||
|
||||
|
||||
The `spiderable` package is a temporary solution to allow web search
|
||||
engines to index a Meteor application. It uses the <a target="_blank"
|
||||
href="https://developers.google.com/webmasters/ajax-crawling/">AJAX
|
||||
Crawling specification</a> published by Google to serve HTML to
|
||||
compatible spiders (Google, Bing, Yandex, and more).
|
||||
The `spiderable` package is a temporary solution to allow web search engines to
|
||||
index a Meteor application. It uses the [AJAX Crawling
|
||||
specification](https://developers.google.com/webmasters/ajax-crawling/)
|
||||
published by Google to serve HTML to compatible spiders (Google, Bing, Yandex,
|
||||
and more).
|
||||
|
||||
When a spider requests an HTML snapshot of a page the Meteor server runs
|
||||
the client half of the application inside <a target="_blank"
|
||||
href="http://phantomjs.org/">phantomjs</a>, a headless browser, and
|
||||
returns the full HTML generated by the client code.
|
||||
When a spider requests an HTML snapshot of a page the Meteor server runs the
|
||||
client half of the application inside [phantomjs](http://phantomjs.org/), a
|
||||
headless browser, and returns the full HTML generated by the client code.
|
||||
|
||||
{{#warning}}
|
||||
This is a temporary approach to allow Meteor applications to be
|
||||
searchable. Expect significant changes to this package.
|
||||
{{/warning}}
|
||||
|
||||
In order to have links between multiple pages on a site visible to
|
||||
spiders, apps must use real links (eg `<a href="/about">`) rather than
|
||||
simply re-rendering portions of the page when an element is
|
||||
clicked. Apps should render their content based on the URL of the page
|
||||
and can use
|
||||
<a target="_blank" href="https://developer.mozilla.org/en-US/docs/DOM/Manipulating_the_browser_history">HTML5 pushState</a>
|
||||
to alter the URL on the client without triggering a page reload. See the
|
||||
<a target="_blank" href="http://meteor.com/examples/todos">Todos example</a>
|
||||
for a demonstration.
|
||||
In order to have links between multiple pages on a site visible to spiders, apps
|
||||
must use real links (eg `<a href="/about">`) rather than simply re-rendering
|
||||
portions of the page when an element is clicked. Apps should render their
|
||||
content based on the URL of the page and can use [HTML5
|
||||
pushState](https://developer.mozilla.org/en-US/docs/DOM/Manipulating_the_browser_history)
|
||||
to alter the URL on the client without triggering a page reload. See the [Todos
|
||||
example](http://meteor.com/examples/todos) for a demonstration.
|
||||
|
||||
|
||||
{{#warning}}
|
||||
If you deploy your application with `meteor bundle`, you must install
|
||||
`phantomjs` (<a target="_blank"
|
||||
href="http://phantomjs.org/">http://phantomjs.org</a>) somewhere in your
|
||||
`phantomjs` ([http://phantomjs.org](http://phantomjs.org/)) somewhere in your
|
||||
`$PATH`. If you use `meteor deploy` this is already taken care of.
|
||||
{{/warning}}
|
||||
|
||||
|
||||
@@ -14,10 +14,9 @@ The `stylus` package also includes `nib` support. Add `@import 'nib'` to
|
||||
your `.styl` files to enable cross-browser mixins such as
|
||||
`linear-gradient` and `border-radius`.
|
||||
|
||||
See <a href="http://learnboost.github.com/stylus/" target="_blank">http://learnboost.github.com/stylus</a>
|
||||
for documentation of the Stylus language,
|
||||
and <a href="http://visionmedia.github.com/nib/" target="_blank">http://visionmedia.github.com/nib</a>
|
||||
for documentation of the nib extensions.
|
||||
See <http://learnboost.github.com/stylus> for documentation of the Stylus
|
||||
language, and <http://visionmedia.github.com/nib> for documentation of the nib
|
||||
extensions.
|
||||
|
||||
{{/better_markdown}}
|
||||
</template>
|
||||
|
||||
@@ -9,8 +9,8 @@ concise JavaScript in a functional style.
|
||||
The `underscore` package defines the `_` namespace on both the client
|
||||
and the server.
|
||||
|
||||
See <a href="http://documentcloud.github.com/underscore/" target="_blank">http://documentcloud.github.com/underscore/</a>
|
||||
for underscore API documentation.
|
||||
See <http://documentcloud.github.com/underscore/> for underscore API
|
||||
documentation.
|
||||
|
||||
{{#warning}}
|
||||
Currently, underscore is included in all projects, as the Meteor
|
||||
|
||||
@@ -12,12 +12,22 @@ if (!Accounts._options) {
|
||||
// - forbidClientAccountCreation {Boolean}
|
||||
// Do not allow clients to create accounts directly.
|
||||
Accounts.config = function(options) {
|
||||
_.each(["sendVerificationEmail", "forbidClientAccountCreation"], function(key) {
|
||||
// validate option keys
|
||||
var VALID_KEYS = ["sendVerificationEmail", "forbidClientAccountCreation"];
|
||||
_.each(_.keys(options), function (key) {
|
||||
if (!_.contains(VALID_KEYS, key)) {
|
||||
throw new Error("Accounts.config: Invalid key: " + key);
|
||||
}
|
||||
});
|
||||
|
||||
// set values in Accounts._options
|
||||
_.each(VALID_KEYS, function (key) {
|
||||
if (key in options) {
|
||||
if (key in Accounts._options)
|
||||
if (key in Accounts._options) {
|
||||
throw new Error("Can't set `" + key + "` more than once");
|
||||
else
|
||||
} else {
|
||||
Accounts._options[key] = options[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -316,7 +316,7 @@
|
||||
|
||||
return true;
|
||||
},
|
||||
fields: ['_id'] // we only look at _id.
|
||||
fetch: ['_id'] // we only look at _id.
|
||||
});
|
||||
|
||||
/// DEFAULT INDEXES ON USERS
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
// XXX it'd be cool to also test that the right thing happens if options
|
||||
// *are* validated, but Accounts._options is global state which makes this hard
|
||||
// (impossible?)
|
||||
Tinytest.add('accounts - config validates keys', function (test) {
|
||||
test.throws(function () {
|
||||
Accounts.config({foo: "bar"});
|
||||
});
|
||||
});
|
||||
|
||||
Tinytest.add('accounts - updateOrCreateUserFromExternalService', function (test) {
|
||||
var facebookId = Meteor.uuid();
|
||||
var weiboId1 = Meteor.uuid();
|
||||
|
||||
@@ -13,14 +13,13 @@
|
||||
}
|
||||
var state = Meteor.uuid();
|
||||
|
||||
var required_scope = ['user'];
|
||||
var scope = _.union((options && options.requestPermissions) || [], required_scope);
|
||||
var flat_scope = _.map(scope, encodeURIComponent).join('+');
|
||||
var scope = (options && options.requestPermissions) || [];
|
||||
var flatScope = _.map(scope, encodeURIComponent).join('+');
|
||||
|
||||
var loginUrl =
|
||||
'https://github.com/login/oauth/authorize' +
|
||||
'?client_id=' + config.clientId +
|
||||
'&scope=' + flat_scope +
|
||||
'&scope=' + flatScope +
|
||||
'&redirect_uri=' + Meteor.absoluteUrl('_oauth/github?close') +
|
||||
'&state=' + state;
|
||||
|
||||
|
||||
@@ -107,8 +107,8 @@
|
||||
test.equal(Meteor.user().emails.length, 1);
|
||||
test.equal(Meteor.user().emails[0].address, email2);
|
||||
test.isFalse(Meteor.user().emails[0].verified);
|
||||
// We should NOT be publishing verification tokens!
|
||||
test.isFalse(_.has(Meteor.user(), 'emailVerificationTokens'));
|
||||
// We should NOT be publishing things like verification tokens!
|
||||
test.isFalse(_.has(Meteor.user(), 'services'));
|
||||
},
|
||||
function (test, expect) {
|
||||
getVerifyEmailToken(email2, test, expect);
|
||||
|
||||
@@ -138,13 +138,15 @@
|
||||
if (!token)
|
||||
throw new Meteor.Error(400, "Need to pass token");
|
||||
|
||||
var user = Meteor.users.findOne({'emailVerificationTokens.token': token});
|
||||
var user = Meteor.users.findOne(
|
||||
{'services.email.verificationTokens.token': token});
|
||||
if (!user)
|
||||
throw new Meteor.Error(403, "Verify email link expired");
|
||||
|
||||
var tokenRecord = _.find(user.emailVerificationTokens, function (t) {
|
||||
return t.token == token;
|
||||
});
|
||||
var tokenRecord = _.find(user.services.email.verificationTokens,
|
||||
function (t) {
|
||||
return t.token == token;
|
||||
});
|
||||
if (!tokenRecord)
|
||||
throw new Meteor.Error(403, "Verify email link expired");
|
||||
|
||||
@@ -166,7 +168,7 @@
|
||||
{_id: user._id,
|
||||
'emails.address': tokenRecord.address},
|
||||
{$set: {'emails.$.verified': true},
|
||||
$pull: {emailVerificationTokens: {token: token}},
|
||||
$pull: {'services.email.verificationTokens': {token: token}},
|
||||
$push: {'services.resume.loginTokens': stampedLoginToken}});
|
||||
|
||||
this.setUserId(user._id);
|
||||
@@ -234,8 +236,9 @@
|
||||
token: Meteor.uuid(),
|
||||
address: address,
|
||||
when: +(new Date)};
|
||||
Meteor.users.update({_id: userId},
|
||||
{$push: {emailVerificationTokens: tokenRecord}});
|
||||
Meteor.users.update(
|
||||
{_id: userId},
|
||||
{$push: {'services.email.verificationTokens': tokenRecord}});
|
||||
|
||||
var verifyEmailUrl = Accounts.urls.verifyEmail(tokenRecord.token);
|
||||
Email.send({
|
||||
@@ -442,7 +445,7 @@
|
||||
|
||||
// XXX allow an optional callback?
|
||||
if (callback) {
|
||||
throw new Error("Meteor.createUser with callback not supported on the server yet.");
|
||||
throw new Error("Accounts.createUser with callback not supported on the server yet.");
|
||||
}
|
||||
|
||||
var userId = createUser(options).id;
|
||||
|
||||
@@ -9,6 +9,14 @@ if (!Accounts.ui._options) {
|
||||
|
||||
|
||||
Accounts.ui.config = function(options) {
|
||||
// validate options keys
|
||||
var VALID_KEYS = ['passwordSignupFields', 'requestPermissions'];
|
||||
_.each(_.keys(options), function (key) {
|
||||
if (!_.contains(VALID_KEYS, key))
|
||||
throw new Error("Accounts.ui.config: Invalid key: " + key);
|
||||
});
|
||||
|
||||
// deal with `passwordSignupFields`
|
||||
if (options.passwordSignupFields) {
|
||||
if (_.contains([
|
||||
"USERNAME_AND_EMAIL",
|
||||
@@ -17,20 +25,24 @@ Accounts.ui.config = function(options) {
|
||||
"EMAIL_ONLY"
|
||||
], options.passwordSignupFields)) {
|
||||
if (Accounts.ui._options.passwordSignupFields)
|
||||
throw new Error("Can't set `passwordSignupFields` more than once");
|
||||
throw new Error("Accounts.ui.config: Can't set `passwordSignupFields` more than once");
|
||||
else
|
||||
Accounts.ui._options.passwordSignupFields = options.passwordSignupFields;
|
||||
} else {
|
||||
throw new Error("Invalid option for `passwordSignupFields`: " + options.passwordSignupFields);
|
||||
throw new Error("Accounts.ui.config: Invalid option for `passwordSignupFields`: " + options.passwordSignupFields);
|
||||
}
|
||||
}
|
||||
|
||||
// deal with `requestPermissions`
|
||||
if (options.requestPermissions) {
|
||||
_.each(options.requestPermissions, function (scope, service) {
|
||||
if (Accounts.ui._options.requestPermissions[service])
|
||||
throw new Error("Can't set `requestPermissions` more than once for " + service);
|
||||
else
|
||||
if (Accounts.ui._options.requestPermissions[service]) {
|
||||
throw new Error("Accounts.ui.config: Can't set `requestPermissions` more than once for " + service);
|
||||
} else if (!(scope instanceof Array)) {
|
||||
throw new Error("Accounts.ui.config: Value for `requestPermissions` must be an array");
|
||||
} else {
|
||||
Accounts.ui._options.requestPermissions[service] = scope;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
17
packages/accounts-ui-unstyled/accounts_ui_tests.js
Normal file
17
packages/accounts-ui-unstyled/accounts_ui_tests.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// XXX it'd be cool to also test that the right thing happens if options
|
||||
// *are* validated, but Accouns.ui._options is global state which makes this hard
|
||||
// (impossible?)
|
||||
Tinytest.add('accounts-ui - config validates keys', function (test) {
|
||||
test.throws(function () {
|
||||
Accounts.ui.config({foo: "bar"});
|
||||
});
|
||||
|
||||
test.throws(function () {
|
||||
Accounts.ui.config({passwordSignupFields: "not a valid option"});
|
||||
});
|
||||
|
||||
test.throws(function () {
|
||||
Accounts.ui.config({requestPermissions: {facebook: "not an array"}});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -385,7 +385,7 @@
|
||||
var signup = function () {
|
||||
loginButtonsSession.resetMessages();
|
||||
|
||||
var options = {}; // to be passed to Meteor.createUser
|
||||
var options = {}; // to be passed to Accounts.createUser
|
||||
|
||||
var username = trimmedElementValueById('login-username');
|
||||
if (username !== null) {
|
||||
|
||||
@@ -21,3 +21,9 @@ Package.on_use(function (api) {
|
||||
'login_buttons_dropdown.js',
|
||||
'login_buttons_dialogs.js'], 'client');
|
||||
});
|
||||
|
||||
Package.on_test(function (api) {
|
||||
api.use('accounts-ui-unstyled');
|
||||
api.use('tinytest');
|
||||
api.add_files('accounts_ui_tests.js', 'client');
|
||||
});
|
||||
|
||||
@@ -186,6 +186,43 @@
|
||||
//
|
||||
|
||||
if (Meteor.isServer) {
|
||||
Tinytest.add("collection - allow and deny validate options", function (test) {
|
||||
var collection = new Meteor.Collection(null);
|
||||
|
||||
test.throws(function () {
|
||||
collection.allow({invalidOption: true});
|
||||
});
|
||||
test.throws(function () {
|
||||
collection.deny({invalidOption: true});
|
||||
});
|
||||
|
||||
_.each(['insert', 'update', 'remove', 'fetch'], function (key) {
|
||||
var options = {};
|
||||
options[key] = true;
|
||||
test.throws(function () {
|
||||
collection.allow(options);
|
||||
});
|
||||
test.throws(function () {
|
||||
collection.deny(options);
|
||||
});
|
||||
});
|
||||
|
||||
_.each(['insert', 'update', 'remove'], function (key) {
|
||||
var options = {};
|
||||
options[key] = ['an array']; // this should be a function, not an array
|
||||
test.throws(function () {
|
||||
collection.allow(options);
|
||||
});
|
||||
test.throws(function () {
|
||||
collection.deny(options);
|
||||
});
|
||||
});
|
||||
|
||||
test.throws(function () {
|
||||
collection.allow({fetch: function () {}}); // this should be an array
|
||||
});
|
||||
});
|
||||
|
||||
Tinytest.add("collection - calling allow restricts", function (test) {
|
||||
var collection = new Meteor.Collection(null);
|
||||
test.equal(collection._restricted, false);
|
||||
|
||||
@@ -184,7 +184,7 @@ _.each(["insert", "update", "remove"], function (name) {
|
||||
// down.
|
||||
callback = function (err) {
|
||||
if (err)
|
||||
Meteor._debug(name + " failed: " + err.error + " -- " + err.reason);
|
||||
Meteor._debug(name + " failed: " + (err.reason || err.stack));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -281,19 +281,34 @@ Meteor.Collection.prototype._ensureIndex = function (index, options) {
|
||||
|
||||
(function () {
|
||||
var addValidator = function(allowOrDeny, options) {
|
||||
// validate keys
|
||||
var VALID_KEYS = ['insert', 'update', 'remove', 'fetch'];
|
||||
_.each(_.keys(options), function (key) {
|
||||
if (!_.contains(VALID_KEYS, key))
|
||||
throw new Error(allowOrDeny + ": Invalid key: " + key);
|
||||
});
|
||||
|
||||
var self = this;
|
||||
self._restricted = true;
|
||||
|
||||
_.each(['insert', 'update', 'remove'], function (name) {
|
||||
if (options[name])
|
||||
if (options[name]) {
|
||||
if (!(options[name] instanceof Function)) {
|
||||
throw new Error(allowOrDeny + ": Value for `" + name + "` must be a function");
|
||||
}
|
||||
self._validators[name][allowOrDeny].push(options[name]);
|
||||
}
|
||||
});
|
||||
|
||||
// Only update the fetch fields if we're passed things that affect
|
||||
// fetching. This way allow({}) and allow({insert: f}) don't result in
|
||||
// setting fetchAllFields
|
||||
if (options.update || options.remove || options.fetch)
|
||||
if (options.update || options.remove || options.fetch) {
|
||||
if (options.fetch && !(options.fetch instanceof Array)) {
|
||||
throw new Error(allowOrDeny + ": Value for `fetch` must be an array");
|
||||
}
|
||||
self._updateFetch(options.fetch);
|
||||
}
|
||||
};
|
||||
|
||||
Meteor.Collection.prototype.allow = function(options) {
|
||||
|
||||
Reference in New Issue
Block a user