--- url: /api/accounts.md --- # Accounts ## Accounts-base {#accounts-base} The Meteor Accounts system builds on top of the `userId` support in [`publish`](./meteor#Subscription-userId) and [`methods`](./meteor#methods-userId). The core packages add the concept of user documents stored in the database, and additional packages add [secure password authentication](#passwords), [integration with third party login services](#Meteor-loginWith%3CExternalService%3E), and a [pre-built userinterface](/packages/accounts-ui.html). The basic Accounts system is in the `accounts-base` package, but applications typically include this automatically by adding one of the login provider packages: `accounts-password`, `accounts-facebook`, `accounts-github`, `accounts-google`, `accounts-meetup`, `accounts-twitter`, or `accounts-weibo`. Read more about customizing user accounts in the [Accounts](http://guide.meteor.com/accounts.html) article in the Meteor Guide. ### Accounts with Session Storage {#accounts-session-storage} By default, Meteor uses Local Storage to store, among other things, login tokens in your browser session. But, for some applications, it makes sense to use Session Storage instead. Session Storage will not persist across client sessions. You can achieve this by adding this to your settings: ```json { // ... all other settings, "public": { // ... all your public settings "packages": { "accounts": { "clientStorage": "session" } } } } ``` Retrieves the user record for the current user from the [`Meteor.users`](#Meteor-users) collection. On the client, the available fields will be those that are published from the server (other fields won't be available on the client). By default the server publishes `username`, `emails`, and `profile` (writable by user). See [`Meteor.users`](#Meteor-users) for more on the fields used in user documents. On the server, this will fetch the record from the database. To improve the latency of a method that uses the user document multiple times, save the returned record to a variable instead of re-calling `Meteor.user()`. Fetching the full user document can cause unnecessary database usage on the server and over-reactivity on the client, particularly if you store lots of custom data on it. Therefore it is recommended to use the `options` parameter to only fetch the fields you need: ```js import { Meteor } from "meteor/meteor"; const userName = Meteor.user({ fields: { "profile.name": 1 } }).profile.name; ``` Same as [`Meteor.user`](#Meteor-user), but returns a promise and is available on the server. ```js import { Meteor } from "meteor/meteor"; const user = await Meteor.userAsync(); ``` This collection contains one document per registered user. Here's an example user document: ```js { _id: 'QwkSmTCZiw5KDx3L6', // 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 } ], createdAt: new Date('Wed Aug 21 2013 15:16:52 GMT-0700 (PDT)'), 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. - `createdAt`: the Date at which the user document was created. - `profile`: an Object which the user can create and update with any data. Do not store anything on `profile` that you wouldn't want the user to edit unless you have a deny rule on the `Meteor.users` collection. - `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 [Mongo.Collection](./collections.md)s, you can access all documents on the server, but only those specifically published by the server are available on the client. You can also use all Collection methods, for instance `Meteor.users.remove` on the server to delete a user. 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: ::: code-group ```js [server.js] Meteor.publish("userData", function () { if (this.userId) { return Meteor.users.find( { _id: this.userId }, { fields: { other: 1, things: 1 }, } ); } else { this.ready(); } }); ``` ```js [client.js] Meteor.subscribe("userData"); ``` ::: If the autopublish package is installed, information about all users on the system is published to all clients. This includes `username`, `profile`, and any fields in `services` that are meant to be public (eg `services.facebook.id`, `services.twitter.screenName`). Additionally, when using autopublish more information is published for the currently logged in user, including access tokens. This allows making API calls directly from the client for services that allow this. 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`](./collections.md#Mongo-Collection-allow). To forbid users from making any modifications to their user document: ```js import { Meteor } from "meteor/meteor"; Meteor.users.deny({ update: () => true }); ``` For example, [the `accounts-ui` package](../packages/accounts-ui.md) uses this to display an animation while the login request is being processed. For example, when called in a user's browser, connections in that browser remain logged in, but any other browsers or DDP clients logged in as that user will be logged out. If there are multiple users with a username or email only differing in case, a case sensitive match is required. Although `createUser` won't let you create users with ambiguous usernames or emails, this could happen with existing databases or if you modify the users collection directly. This method can fail throwing one of the following errors: - "Unrecognized options for login request [400]" if `user` or `password` is undefined. - "Match failed [400]" if `user` isn't an Object or String, or `password` isn't a String. - "User not found [403]" if the email or username provided in `user` doesn't belong to a registered user. - "Incorrect password [403]" if the password provided is incorrect. - "User has no password set [403]" if `user` doesn't have a password. This function is provided by the `accounts-password` package. See the [Passwords](#passwords) section below. Available functions are: - `Meteor.loginWithMeteorDeveloperAccount` - `Meteor.loginWithFacebook` - `options` may also include [Facebook's `auth_type` parameter](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#reaskperms) - `Meteor.loginWithGithub` - `Meteor.loginWithGoogle` - `options` may also include [Google's additional URI parameters](https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters) - `Meteor.loginWithMeetup` - `Meteor.loginWithTwitter` - `options` may also include [Twitter's `force_login` parameter](https://dev.twitter.com/oauth/reference/get/oauth/authenticate) - `Meteor.loginWithWeibo` 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.

Requesting Permissions

In addition to identifying the user to your application, some services have APIs that allow you to take action on behalf of the user. To request specific permissions from the user, pass the `requestPermissions` option the login function. This will cause the user to be presented with an additional page in the pop-up dialog to permit access to their data. The user's `accessToken` — with permissions to access the service's API — is stored in the `services` field of the user document. The supported values for `requestPermissions` differ for each login service and are documented on their respective developer sites: - Facebook: - GitHub: - Google: - Meetup: - Twitter, Weibo, Meteor developer accounts: `requestPermissions` currently not supported External login services typically require registering and configuring your application before use. The easiest way to do this is with the [`accounts-ui` package](../packages/accounts-ui.md) which presents a step-by-step guide to configuring each service. However, the data can be also be entered manually in the `ServiceConfiguration.configurations` collection, which is exported by the `service-configuration` package. ## Configuring Services {#service-configuration} First, add the service configuration package: ```bash meteor add service-configuration ``` Then, inside the server of your app (this example is for the Weebo service), import `ServiceConfiguration`: ```js import { ServiceConfiguration } from "meteor/service-configuration"; ServiceConfiguration.configurations.upsert( { service: "weibo" }, { $set: { loginStyle: "popup", clientId: "1292962797", // See table below for correct property name! secret: "75a730b58f5691de5522789070c319bc", }, } ); ``` Since Meteor 2.7 you no longer need to manually set the configuration and instead can use Meteor settings by setting your services under `Meteor.settings.packages.service-configuration.`. All the properties can be set under the service and will be added to the database as is, so make sure that they are correct. For the example above, the settings would look like: ```json { "packages": { "service-configuration": { "weibo": { "loginStyle": "popup", "clientId": "1292962797", "secret": "75a730b58f5691de5522789070c319bc" } } } } ``` The correct property name to use for the API identifier (i.e. `clientId` in the above example) depends on the login service being used, so be sure to use the correct one: | Property Name | Services | | ------------- | -------------------------------------------------------- | | `appId` | Facebook | | `clientId` | Github, Google, Meetup, Meteor Developer Accounts, Weibo | | `consumerKey` | Twitter | Additionally, each external service has its own login provider package and login function. For example, to support GitHub login, run the following in your terminal: ```bash meteor add accounts-github ``` and use the `Meteor.loginWithGithub` function: ```js import { Meteor } from "meteor/meteor"; Meteor.loginWithGithub( { requestPermissions: ["user", "public_repo"], }, (error) => { if (error) { Session.set("errorMessage", error.reason || "Unknown error"); } } ); ``` Login service configuration is sent from the server to the client over DDP when your app starts up; you may not call the login function until the configuration is loaded. The function `Accounts.loginServicesConfigured()` is a reactive data source that will return true once the login service is configured; you should not make login buttons visible or active until it is true. Ensure that your [`$ROOT_URL`](./meteor.md#Meteor-absoluteUrl) matches the authorized domain and callback URL that you configure with the external service (for instance, if you are running Meteor behind a proxy server, `$ROOT_URL` should be the externally-accessible URL, not the URL inside your proxy). ## Manual service configuration {#manual-service-configuration} You can use `Accounts.loginServiceConfiguration` to view and edit the settings collection: ```js import { Accounts } from "meteor/accounts-base"; Accounts.loginServiceConfiguration.find(); ``` ## Popup versus redirect flow {#popup-vs-redirect-flow} When configuring OAuth login with a provider (such as Facebook or Google), Meteor lets you choose a popup- or redirect-based flow. In a popup-based flow, when a user logs in, they will be prompted to login at the provider in a popup window. In a redirect-based flow, the user's whole browser window will be redirected to the login provider, and the window will redirect back to your app when the login is completed. You can also pick which type of login to do by passing an option to [`Meteor.loginWith`](#Meteor-loginWith%3CExternalService%3E) Usually, the popup-based flow is preferable because the user will not have to reload your whole app at the end of the login flow. However, the popup-based flow requires browser features such as `window.close` and `window.opener` that are not available in all mobile environments. In particular, we recommend using `Meteor.loginWith({ loginStyle: 'redirect' })` in the following environments: - Inside UIWebViews (when your app is loaded inside a mobile app) - In Safari on iOS8 (`window.close` is not supported due to a bug) Example: ```js import { Accounts } from "meteor/accounts-base"; Accounts.ui.config({ requestPermissions: { facebook: ["user_likes"], github: ["user", "repo"], }, requestOfflineToken: { google: true, }, passwordSignupFields: "USERNAME_AND_OPTIONAL_EMAIL", }); ``` Since Meteor 2.7 you can configure these in your Meteor settings under `Meteor.settings.public.packages.accounts-ui-unstyled`. ## Multi-server {#multi-server} The `accounts-base` package exports two constructors, called `AccountsClient` and `AccountsServer`, which are used to create the `Accounts` object that is available on the client and the server, respectively. This predefined `Accounts` object (along with similar convenience methods of `Meteor`, such as [`Meteor.logout`](#Meteor-logout)) is sufficient to implement most accounts-related logic in Meteor apps. Nevertheless, these two constructors can be instantiated more than once, to create multiple independent connections between different accounts servers and their clients, in more complicated authentication situations. The `AccountsClient` and `AccountsServer` classes share a common superclass, `AccountsCommon`. Methods defined on `AccountsCommon.prototype` will be available on both the client and the server, via the predefined `Accounts` object (most common) or any custom `accountsClientOrServer` object created using the `AccountsClient` or `AccountsServer` constructors (less common). Here are a few of those methods: From Meteor 2.5 you can set these in your Meteor settings under `Meteor.settings.packages.accounts-base`. Note that due to the nature of settings file you won't be able to set parameters that require functions. See description of [AccountsCommon#onLoginFailure](#AccountsCommon-onLoginFailure) for details. Either the `onLogin` or the `onLoginFailure` callbacks will be called for each login attempt. The `onLogin` callbacks are called after the user has been successfully logged in. The `onLoginFailure` callbacks are called after a login attempt is denied. These functions return an object with a single method, `stop`. Calling `stop()` unregisters the callback. On the server, the callbacks get a single argument, the same attempt info object as [`validateLoginAttempt`](#AccountsServer-validateLoginAttempt). On the client, the callback argument is an object containing a single `error` property set to the `Error`-object which was received from the failed login attempt. On the server, the `func` callback receives a single argument with the object below. On the client, no arguments are passed. ```js import { AccountsCommon } from "meteor/accounts-base"; const options = { //... }; const accountsCommon = new AccountsCommon(options); accountsCommon.onLogout(({ user, connection, collection }) => { console.log(user); // ˆˆˆˆˆˆ The Meteor user object of the user which just logged out console.log(connection); // ˆˆˆˆˆˆ The connection object the request came in on. See // `Meteor.onConnection` for details. console.log(collection); // ˆˆˆˆˆˆ The `collection` The name of the Mongo.Collection or the // Mongo.Collection object to hold the users. }); ``` At most one of `options.connection` and `options.ddpUrl` should be provided in any instantiation of `AccountsClient`. If neither is provided, `Meteor.connection` will be used as the `.connection` property of the `AccountsClient` instance. Note that `AccountsClient` is currently available only on the client, due to its use of browser APIs such as `window.localStorage`. In principle, though, it might make sense to establish a client connection from one server to another remote accounts server. Please [let us know](https://github.com/meteor/meteor/wiki/Contributing-to-Meteor#feature-requests) if you find yourself needing this server-to-server functionality. These methods are defined on `AccountsClient.prototype`, and are thus available only on the client: These methods are defined on `AccountsServer.prototype`, and are thus available only on the server: 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`](../packages/accounts-ui.md)), throw a new [`Meteor.Error`](./meteor#meteor-api). Example: ```js import { Accounts } from "meteor/accounts-base"; // Validate username, sending a specific error message on failure. Accounts.validateNewUser((user) => { if (user.username && user.username.length >= 3) { return true; } else { throw new Meteor.Error(403, "Username must have at least 3 characters"); } }); // Validate username, without a specific error message. Accounts.validateNewUser((user) => { return user.username !== "root"; }); ``` If the user is being created as part of a login attempt from a client (eg, calling [`Accounts.createUser`](#Accounts-createUser) from the client, or [logging in for the first time with an external service](#meteor_loginwithexternalservice)), these callbacks are called _before_ the [`Accounts.validateLoginAttempt`](#Accounts-validateLoginAttempt) callbacks. If these callbacks succeed but those fail, the user will still be created but the connection will not be logged in as that user. 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, including the `_id`. 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: ```js import { Accounts } from "meteor/accounts-base"; // Support for playing D&D: Roll 3d6 for dexterity. Accounts.onCreateUser((options, user) => { const customizedUser = Object.assign( { dexterity: _.random(1, 6) + _.random(1, 6) + _.random(1, 6), }, user ); // We still want the default hook's 'profile' behavior. if (options.profile) { customizedUser.profile = options.profile; } return customizedUser; }); ``` Call `validateLoginAttempt` with a callback to be called on login attempts. It returns an object with a single method, `stop`. Calling `stop()` unregisters the callback. When a login attempt is made, the registered validate login callbacks are called with a single argument, you can check the example: ```js import { AccountsServer } from "meteor/accounts-base"; const options = { //... }; const accountsServer = new AccountsServer(options); accountsServer.validateLoginAttempt( ({ type, // String allowed, // Boolean error, // Error user, // Object connection, // Object collection, // Object methodName, // String methodArguments, // Array }) => { console.log(type); // ˆˆˆˆˆˆ The service name, such as "password" or "twitter". console.log(allowed); // ˆˆˆˆˆˆ Whether this login is allowed and will be successful (if not aborted // by any of the validateLoginAttempt callbacks). False if the login // will not succeed (for example, an invalid password or the login was // aborted by a previous validateLoginAttempt callback). console.log(error); // ˆˆˆˆˆˆ When `allowed` is false, the exception describing why the login // failed. It will be a `Meteor.Error` for failures reported to the // user (such as invalid password), and can be a another kind of // exception for internal errors. console.log(user); // ˆˆˆˆˆˆ When it is known which user was attempting to login, // the Meteor user object. This will always be present for successful logins. console.log(connection); // ˆˆˆˆˆˆ The `connection` object the request came in on. See // [`Meteor.onConnection`](#meteor_onconnection) for details. console.log(collection); // ˆˆˆˆˆˆ The `collection` The name of the Mongo.Collection or the // Mongo.Collection object to hold the users. console.log(methodName); // ˆˆˆˆˆˆ The name of the Meteor method being used to login. // For example, "login", "loginWithPassword", or "loginWith". console.log(methodArguments); // ˆˆˆˆˆˆ An array of the arguments passed to the login method. // For example, `["username", "password"]` } ); ``` A validate login callback must return a truthy value for the login to proceed. If the callback returns a falsy value or throws an exception, the login is aborted. Throwing a `Meteor.Error` will report the error reason to the user. All registered validate login callbacks are called, even if one of the callbacks aborts the login. The later callbacks will see the `allowed` field set to `false` since the login will now not be successful. This allows later callbacks to override an error from a previous callback; for example, you could override the "Incorrect password" error with a different message. Validate login callbacks that aren't explicitly trying to override a previous error generally have no need to run if the attempt has already been determined to fail, and should start with ```js if (!attempt.allowed) { return false; } ``` Use this hook if you need to validate that user from an external service should be allowed to login or create account. ```js import { AccountsServer } from "meteor/accounts-base"; const options = { //... }; const accountsServer = new AccountsServer(options); accountsServer.beforeExternalLogin(({ type, data, user }) => { console.log(type); // ˆˆˆˆˆˆ The service name, such as "google" or "twitter". Is a String console.log(data); // ˆˆˆˆˆˆ Data retrieved from the service (eg: email, name, etc) // Is an Object. console.log(user); // ˆˆˆˆˆˆ If user was found in the database that matches the criteria from the service, // their data will be provided here. Is an Object. }); ``` You should return a `Boolean` value, `true` if the login/registration should proceed or `false` if it should terminate. In case of termination the login attempt will throw an error `403`, with the message: `Login forbidden`. When allowing your users to authenticate with an external service, the process will eventually call `Accounts.updateOrCreateUserFromExternalService`. By default, this will search for a user with the `service..id`, and if not found will create a new user. As that is not always desirable, you can use this hook as an escape hatch to look up a user with a different selector, probably by `emails.address` or `username`. Note the function will only be called if no user was found with the `service..id` selector. The function will be called with a single argument, the info object: ```js import { AccountsServer } from "meteor/accounts-base"; const options = { //... }; const accountsServer = new AccountsServer(options); accountsServer.setAdditionalFindUserOnExternalLogin( ({ serviceName, serviceData, options }) => { // serviceName: String // The external service name, such as "google" or "twitter". // serviceData: Object // The data returned by the service oauth request. // options: Object // An optional arugment passed down from the oauth service that may contain // additional user profile information. As the data in `options` comes from an // external source, make sure you validate any values you read from it. } ); ``` The function should return either a user document or `undefined`. Returning a user will result in the populating the `service.` in your user document, while returning `undefined` will result in a new user account being created. If you would prefer that a new account not be created, you could throw an error instead of returning. Example: ```js // If a user has already been created, and used their Google email, this will // allow them to sign in with the Meteor.loginWithGoogle method later, without // creating a new user. Accounts.setAdditionalFindUserOnExternalLogin( ({ serviceName, serviceData }) => { if (serviceName === "google") { // Note: Consider security implications. If someone other than the owner // gains access to the account on the third-party service they could use // the e-mail set there to access the account on your app. // Most often this is not an issue, but as a developer you should be aware // of how bad actors could play. return Accounts.findUserByEmail(serviceData.email); } } ); ``` Use this to register your own custom authentication method. This is also used by all of the other inbuilt accounts packages to integrate with the accounts system. There can be multiple login handlers that are registered. When a login request is made, it will go through all these handlers to find its own handler. The registered handler callback is called with a single argument, the `options` object which comes from the login method. For example, if you want to login with a plaintext password, `options` could be `{ user: { username: }, password: }`,or `{ user: { email: }, password: }`. The login handler should return `undefined` if it's not going to handle the login request or else the login result object.

Rate Limiting

By default, there are rules added to the [`DDPRateLimiter`](./DDPRateLimiter.md) that rate limit logins, new user registration and password reset calls to a limit of 5 requests per 10 seconds per session. These are a basic solution to dictionary attacks where a malicious user attempts to guess the passwords of legitimate users by attempting all possible passwords. These rate limiting rules can be removed by calling `Accounts.removeDefaultRateLimit()`. Please see the [`DDPRateLimiter`](./DDPRateLimiter.md) docs for more information. ## Passwords {#passwords} The `accounts-password` package contains a full 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. ### Password encryption and security Starting from `accounts-passwords:4.0.0`, you can choose which algorithm is used by the Meteor server to store passwords : either [bcrypt](http://en.wikipedia.org/wiki/Bcrypt) or [Argon2](http://en.wikipedia.org/wiki/Argon2) algorithm. Both are robust and contribute to protect against embarrassing password leaks if the server's database is compromised. Before version 4.0.0, `bcrypt` was the only available option. argon2 has been introduced because it is considered the most secure option. This algorithm is specifically designed to resist GPU-based brute force attacks. For more details, see the [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html). As of January 2025, **`bcrypt` is still the default option** to enable a smooth transition. In the future, `argon2` will replace `bcrypt` as default and `bcrypt` option will be deprecated. Passwords are hashed on the client using **SHA-256** algorithm before being sent to the server. This ensures that sensitive data is never transmitted in plain text. Once received by the server, the hashed value is further encrypted and securely stored in the `Meteor.users` collection. **About the migration process from `bcrypt` to `argon2`** The transition from `bcrypt` to `argon2` happens automatically upon user login. If Argon2 encryption is enabled in an existing application, each user's password is re-encrypted during their next successful login. - Step 1: The password is first validated against the existing `bcrypt` hash. - Step 2: If authentication succeeds, the password is re-encrypted using `Argon2`. - Step 3: The new `Argon2` hash replaces the old `bcrypt` hash in the database. To monitor the migration progress, you can count users still using bcrypt: ```js const bcryptUsers = await Meteor.users.find({ "services.password.bcrypt": { $exists: true } }).countAsync(); const totalUsers = await Meteor.users.find({ "services.password": { $exists: true } }).countAsync(); console.log("Remaining users to migrate:", bcryptUsers, "/", totalUsers); ``` Once `bcryptUsers` reaches 0, the migration is complete. **Enabling Argon2 encryption** To enable Argon2 encryption, you need a small configuration change on the server: ```js Accounts.config({ argon2Enabled: true, }); ``` **Configuring `argon2` parameters** One enabled, the `accounts-password` package allows customization of Argon2's parameters. The configurable options include: - `type`: `argon2id` (provides a blend of resistance against GPU and side-channel attacks) - `timeCost` (default: 2) – This controls the computational cost of the hashing process, affecting both the security level and performance. - `memoryCost`: 19456 (19 MiB) - The amount of memory used by the algorithm in KiB per thread - `parallelism`: 1 - The number of threads used by the algorithm To update the values, use the following configuration: ```js Accounts.config({ argon2Enabled: true, argon2Type: "argon2id", argon2TimeCost: 2, argon2MemoryCost: 19456, argon2Parallelism: 1, }); ``` Other Argon2 parameters, such as `hashLength`, are kept to default values: - `hashLength`: 32 bytes - The length of the hash output in bytes The default values are the minimum [OWASP recommendations for Argon2 parameters](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#introduction). When updating these values, consider the trade-offs between security and performance on the target infrastructure. For more information about Argon2's parameters, refer to the [argon2 options documentation](https://github.com/ranisalt/node-argon2/wiki/Options). ### Using passwords To add password support to your application, run this command in your terminal: ```bash meteor add accounts-password ``` > In addition to configuring the [`email`](./email.md) package's `MAIL_URL`, it is critical that you set proper values (specifically the `from` address) in [`Accounts.emailTemplates`](#Accounts-emailTemplates) to ensure proper delivery of e-mails! You can construct your own user interface using the functions below, or use the [`accounts-ui` package](../packages/accounts-ui.md) to include a turn-key user interface for password-based sign-in. 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 at least one of `username` or `email` — enough information for the user to be able to log in again later. If there are existing users with a username or email only differing in case, `createUser` will fail. The callback's `error.reason` will be `'Username already exists.'` or `'Email already exists.'` In the latter case, the user can then either [login](accounts.html#Meteor-loginWithPassword) or [reset their password](#Accounts-resetPassword). On the server, you do not need to specify `password`, but the user will not be able to log in until it has a password (eg, set with [`Accounts.setPasswordAsync`](#Accounts-setPasswordAsync)). 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-onCreateUser). This function is only used for creating users with passwords. The external service login flows do not use this function. Instead of modifying documents in the [`Meteor.users`](#Meteor-users) collection directly, use these convenience functions which correctly check for case insensitive duplicates before updates. By default, an email address is added with `{ verified: false }`. Use [`Accounts.sendVerificationEmail`](#Accounts-sendVerificationEmail) to send an email with a link the user can use to verify their email address. If the user trying to verify the email has 2FA enabled, this error will be thrown: - "Email verified, but user not logged in because 2FA is enabled [2fa-enabled]": No longer signing in the user automatically if the user has 2FA enabled. This function accepts tokens passed into the callback registered with [`Accounts.onEmailVerificationLink`](#Accounts-onEmailVerificationLink). Use the below functions to initiate password changes or resets from the server or the client. This triggers a call to [`Accounts.sendResetPasswordEmail`](#Accounts-sendResetPasswordEmail) on the server. When the user visits the link in this email, the callback registered with [`Accounts.onResetPasswordLink`](#Accounts-onResetPasswordLink) will be called. If you are using the [`accounts-ui` package](../packages/accounts-ui.md), this is handled automatically. Otherwise, it is your responsibility to prompt the user for the new password and call `resetPassword`. This function accepts tokens passed into the callbacks registered with [`AccountsClient#onResetPasswordLink`](#Accounts-onResetPasswordLink) and [`Accounts.onEnrollmentLink`](#Accounts-onEnrollmentLink). If the user trying to reset the password has 2FA enabled, this error will be thrown: - "Changed password, but user not logged in because 2FA is enabled [2fa-enabled]": No longer signing in the user automatically if the user has 2FA enabled. When the user visits the link in this email, the callback registered with [`AccountsClient#onResetPasswordLink`](#Accounts-onResetPasswordLink) will be called. To customize the contents of the email, see [`Accounts.emailTemplates`](#Accounts-emailTemplates). When the user visits the link in this email, the callback registered with [`Accounts.onEnrollmentLink`](#Accounts-onEnrollmentLink) will be called. To customize the contents of the email, see [`Accounts.emailTemplates`](#Accounts-emailTemplates). When the user visits the link in this email, the callback registered with [`Accounts.onEmailVerificationLink`](#Accounts-onEmailVerificationLink) will be called. To customize the contents of the email, see [`Accounts.emailTemplates`](#Accounts-emailTemplates). This is an `Object` with several fields that are used to generate text/html for the emails sent by `sendResetPasswordEmail`, `sendEnrollmentEmail`, and `sendVerificationEmail`. Set the fields of the object by assigning to them: - `from`: (**required**) A `String` with an [RFC5322](http://tools.ietf.org/html/rfc5322) From address. By default, the email is sent from `no-reply@example.com`. **If you want e-mails to send correctly, this should be changed to your own domain as most e-mail providers will reject mail sent from `example.com`.** - `siteName`: The public name of your application. Defaults to the DNS name of the application (eg: `awesome.meteor.com`). - `headers`: An `Object` for custom email headers as described in [`Email.send`](./email.md#Email-send). - `resetPassword`: An `Object` with the fields: - `from`: A `Function` used to override the `from` address defined by the `emailTemplates.from` field. - `subject`: A `Function` that takes a user object and returns a `String` for the subject line of a reset password email. - `text`: An optional `Function` that takes a user object and a url, and returns the body text for a reset password email. - `html`: An optional `Function` that takes a user object and a url, and returns the body html 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: ```js import { Accounts } from "meteor/accounts-base"; Accounts.emailTemplates.siteName = "AwesomeSite"; Accounts.emailTemplates.from = "AwesomeSite Admin "; Accounts.emailTemplates.enrollAccount.subject = (user) => { return `Welcome to Awesome Town, ${user.profile.name}`; }; Accounts.emailTemplates.enrollAccount.text = (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 ); }; Accounts.emailTemplates.resetPassword.from = () => { // Overrides the value set in `Accounts.emailTemplates.from` when resetting // passwords. return "AwesomeSite Password Reset "; }; Accounts.emailTemplates.verifyEmail = { subject() { return "Activate your account now!"; }, text(user, url) { return `Hey ${user}! Verify your e-mail by following this link: ${url}`; }, }; ```

Enable 2FA for this package

You can add 2FA to your login flow by using the package [accounts-2fa](../packages/accounts-2fa.md). You can find an example showing how this would look like [here](../packages/accounts-2fa.md#working-with-accounts-password). --- --- url: /packages/accounts-2fa.md --- # accounts-2fa This package allows you to provide a way for your users to enable 2FA on their accounts, using an authenticator app such as Google Authenticator, or 1Password. When the user is logged in on your app, they will be able to generate a new QR code and read this code on the app they prefer. After that, they'll start receiving their codes. Then, they can finish enabling 2FA on your app, and every time they try to log in to your app, you can redirect them to a place where they can provide a code they received from the authenticator. To provide codes that are exactly compatible with all other Authenticator apps and services that implements TOTP, this package uses [node-2fa](https://www.npmjs.com/package/node-2fa) which works on top of [notp](https://github.com/guyht/notp), **that** implements TOTP ([RFC 6238](https://www.ietf.org/rfc/rfc6238.txt)) (the Authenticator standard), which is based on HOTP ([RFC 4226](https://www.ietf.org/rfc/rfc4226.txt)). > This package is meant to be used with [`accounts-password`](../api/accounts.md#passwords) or [`accounts-passwordless`](./accounts-passwordless.md), so if you don't have either of those in your project, you'll need to add one of them. In the future, we want to enable the use of this package with other login methods, our oauth methods (Google, GitHub, etc...). ## 2FA Activation Flow {#activating-2fa} The first step, in order to enable 2FA, is to generate a QR code so that the user can scan it in an authenticator app and start receiving codes. Receives an `appName` which is the name of your app that will show up when the user scans the QR code. Also, a callback called, on success, with a QR code in SVG format, a QR secret, and the URI that can be used to activate the 2FA in an authenticator app, or a single `Error` argument on failure. On success, this function will also add an object to the logged user's services object containing the QR secret: ```js services: { ... twoFactorAuthentication: { secret: "***" } } ``` Here it's an example on how to call this function: ```js import { Buffer } from "buffer"; import { Accounts } from 'meteor/accounts-base'; // component const [qrCode, setQrCode] = useState(null); ``` This method can fail throwing the following error: - "The 2FA is activated. You need to disable the 2FA first before trying to generate a new activation code [2fa-activated]" if trying to generate an activation when the user already have 2FA enabled. At this point, the 2FA won't be activated just yet. Now that the user has access to the codes generated by their authenticator app, you can call the function `Accounts.enableUser2fa`: It should be called with a code that the users will receive from the authenticator app once they read the QR code. The callback is called with a single `Error` argument on failure. If the code provided is correct, a `type` will be added to the user's `twoFactorAuthentication` object and now 2FA is considered enabled: ```js services: { ... twoFactorAuthentication: { type: "otp", secret: "***", } } ``` To verify whether or not a user has 2FA enabled, you can call the function `Accounts.has2faEnabled`: This function must be called when the user is logged in. ## Disabling 2FA {#disabling-2fa} To disable 2FA for a user use this method: To call this function the user must be already logged in. ## Log in with 2FA {#log-in-with-2fa} Now that you have a way to allow your users to enable 2FA on their accounts, you can create a login flow based on that. As said at the beginning of this guide, this package is currently working with two other packages: `accounts-password` and `accounts-passwordless`. Below there is an explanation on how to use this package with them. ## Working with accounts-password {#working-with-accounts-password} When calling the function `Meteor.loginWithPassword`, if the 2FA is enabled for the user, an error will be returned to the callback, so you can redirect the user to a place where they can provide a code. As an example: ```js ``` If the 2FA is not enabled, the user will be logged in normally. The function you will need to call now to allow the user to login is `Meteor.loginWithPasswordAnd2faCode`: Now you will be able to receive a code from the user and this function will verify if the code is valid. If it is, the user will be logged in. So the call of this function should look something like this: ```js ``` This method can fail throwing one of the following errors: - "2FA code must be informed [no-2fa-code]" if a 2FA code was not provided. - "Invalid 2FA code [invalid-2fa-code]" if the provided 2FA code is invalid. ## Working with accounts-passwordless {#working-with-accounts-passwordless} Following the same logic from the previous package, if the 2FA is enabled, an error will be returned to the callback of the function [`Meteor.passwordlessLoginWithToken`](./accounts-passwordless.md#Meteor-passwordlessLoginWithToken), then you can redirect the user to a place where they can provide a code. Here is an example: ```js ``` Now you can call the function `Meteor.passwordlessLoginWithTokenAnd2faCode` that will allow you to provide a selector, token, and 2FA code: This method can fail throwing one of the following errors: - "2FA code must be informed [no-2fa-code]" if a 2FA code was not provided. - "Invalid 2FA code [invalid-2fa-code]" if the provided 2FA code is invalid. ## Integrating an Authentication Package with accounts-2fa {#integrating-auth-package} To integrate this package with any other existing Login method, it's necessary following two steps: 1 - For the client, create a new method from your current login method. So for example, from the method `Meteor.loginWithPassword` we created a new one called `Meteor.loginWithPasswordAnd2faCode`, and the only difference between them is that the latest one receives one additional parameter, the 2FA code, but we call the same function on the server side. 2 - For the server, inside the function that will log the user in, you verify if the function `Accounts._check2faEnabled` exists, and if yes, you call it providing the user object you want to check if the 2FA is enabled, and if either of these statements are false, you proceed with the login flow. This function exists only when the package `accounts-2fa` is added to the project. If both statements are true, and the login validation succeeds, you verify if a code was provided: if not, throw an error; if it was provided, verify if the code is valid by calling the function `Accounts._isTokenValid`. if `Accounts._isTokenValid` returns false, throw an error. Here it's an example: ```js const result = validateLogin(); if (!result.error && Accounts._check2faEnabled?.(user)) { if (!code) { Accounts._handleError("2FA code must be informed."); } if ( !Accounts._isTokenValid(user.services.twoFactorAuthentication.secret, code) ) { Accounts._handleError("Invalid 2FA code."); } } return result; ``` --- --- url: /packages/accounts-ui.md --- # accounts-ui 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 at least one login provider package: `accounts-password`, `accounts-facebook`, `accounts-github`, `accounts-google`, `accounts-twitter`, or `accounts-weibo`. Then simply add the `{{> 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. If you plan to position the login dropdown in the right edge of the screen, use `{{> loginButtons align="right"}}` in order to get the dropdown to lay itself out without expanding off the edge of the screen. To configure the behavior of `{{> loginButtons}}`, use [`Accounts.ui.config`](../api/accounts.md#loggingIn). `accounts-ui` also includes modal popup dialogs to handle links from [`sendResetPasswordEmail`](../api/accounts.md#Accounts-sendResetPasswordEmail), [`sendVerificationEmail`](../api/accounts.md#Accounts-sendVerificationEmail), and [`sendEnrollmentEmail`](../api/accounts.md#Accounts-sendEnrollmentEmail). These do not have to be manually placed in HTML: they are automatically activated when the URLs are loaded. If you want to control the look and feel of your accounts system a little more, we recommend reading the [useraccounts](http://guide.meteor.com/accounts.html#useraccounts) section of the Meteor Guide. --- --- url: /packages/appcache.md --- # AppCache > This package has been deprecated since [applicationCache](https://developer.mozilla.org/en-US/docs/Web/API/Window/applicationCache), which this package relies on, has been deprecated and is not available on the latest browsers. Plaese consider using [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) as an replacement. The `appcache` package stores the static parts of a Meteor application (the client side Javascript, HTML, CSS, and images) in the browser's [application cache](https://en.wikipedia.org/wiki/AppCache). To enable caching simply add the `appcache` package to your project. * Once a user has visited a Meteor application for the first time and the application has been cached, on subsequent visits the web page loads faster because the browser can load the application out of the cache without contacting the server first. * Hot code pushes are loaded by the browser in the background while the app continues to run. Once the new code has been fully loaded the browser is able to switch over to the new code quickly. * The application cache allows the application to be loaded even when the browser doesn't have an Internet connection, and so enables using the app offline. (Note however that the `appcache` package by itself doesn't make *data* available offline: in an application loaded offline, a Meteor Collection will appear to be empty in the client until the Internet becomes available and the browser is able to establish a DDP connection). To turn AppCache off for specific browsers use: ```js Meteor.AppCache.config({ chrome: false, firefox: false }); ``` The supported browsers that can be enabled or disabled include, but are not limited to, `android`, `chrome`, `chromium`, `chromeMobileIOS`, `firefox`, `ie`, `mobileSafari` and `safari`. Browsers limit the amount of data they will put in the application cache, which can vary due to factors such as how much disk space is free. Unfortunately if your application goes over the limit rather than disabling the application cache altogether and running the application online, the browser will instead fail that particular *update* of the cache, leaving your users running old code. Thus it's best to keep the size of the cache below 5MB. The `appcache` package will print a warning on the Meteor server console if the total size of the resources being cached is over 5MB. Starting from `appcache@1.2.5`, if you need more advanced logic to enable/disable the cache, you can use the `enableCallback` option that is evaluated on a per-request basis. For example: ```js // Enable offline mode using a value from database and certificate validation Meteor.AppCache.config({ // This option is available starting from appcache@1.2.4 enableCallback: () => { if (!getSettingsFromDb("public.appcache_enabled")) { return false; } const validation = validateClientCert({ clientCertPayload: req.headers["x-client-cert"], url: req.url.href, }); return validation.passed; }, }); ``` If you have files too large to fit in the cache you can disable caching by URL prefix. For example, ```js Meteor.AppCache.config({ onlineOnly: ['/online/'] }); ``` causes files in your `public/online` directory to not be cached, and so they will only be available online. You can then move your large files into that directory and refer to them at the new URL: ```html ``` If you'd prefer not to move your files, you can use the file names themselves as the URL prefix: ```js Meteor.AppCache.config({ onlineOnly: [ '/bigimage.jpg', '/largedata.json' ] }); ``` though keep in mind that since the exclusion is by prefix (this is a limitation of the application cache manifest), excluding `/largedata.json` will also exclude such URLs as `/largedata.json.orig` and `/largedata.json/file1`. For more information about how Meteor interacts with the application cache, see the [AppCache page](https://github.com/meteor/meteor/wiki/AppCache) in the Meteor wiki. --- --- url: /community-packages/archive.md --- # Archive - `Who maintains the package` – [Jam](https://github.com/jamauro) [[toc]] ## What is this package? Archive is an easy way to add an archive mechanism to your Meteor app. When a document is archived, it's removed from its original collection and placed in an archive collection so that it can be restored if needed. Its key features are: * Zero config needed (though you can customize) * Isomorphic so that it works with Optimistic UI * Automatically overrides `removeAsync` to perform an archive (can be turned off) * Explicitly archive with `archiveAsync` collection method (optional) * Restore archived docs with `restoreAsync` collection method * Optionally exclude specific collections * Compatible with Meteor `3.0.2+` > **Note:** Alternative to archive is soft deletion. You can use [`jam:soft-delete`](./soft-delete.md) package for that. Be sure to compare those two approaches to pick the solution best suited for your application. ## How to download it? Add the package to your app ```bash meteor add jam:archive ``` ### Sources * [GitHub repository](https://github.com/jamauro/archive) ## How to use it? ### Create your Archives collection Create an Archives collection in your app just as you would any other collection. ```js const Archives = new Mongo.Collection('archives'); ``` Documents in the Archives collection will have this shape: ```js { _id, // auto-generated by Meteor as with other collection _ids _collection, // the name of the collection, e.g. 'todos', that the doc belonged to originally archivedAt, // the timestamp when the document was removed from its original collection and inserted into the archive id, // the original doc _id renamed to prevent conflict with the auto-generated one above. when restored, it will be renamed back to _id automatically by this package /* ...rest of original doc */ } ``` ### Deleting permanently By default, this package overrides the `removeAsync` collection method so that it archives the document(s) rather that removing them from the database. To delete permanently, pass in the option `forever: true`, e.g.: ```js Collection.removeAsync(/* your filter */, { forever: true }) ``` If you prefer, you can prevent overriding the `removeAsync` by setting `overrideRemove: false`. See [Configuring](#configuring-optional) for more details. ### Explicitly archiving If you prefer, you can explicity use `archiveAsync`, e.g.: ```js Collection.archiveAsync(/* your filter */) ``` ### Restoring a document To restore an archived document, use `restoreAsync`, e.g.: ```js Collection.restoreAsync(/* your filter */) ``` ## Configuring (optional) If you like the defaults, then you won't need to configure anything. But there is some flexibility in how you use this package. Here are the global defaults: ```js const config = { name: 'archives', // if your Archives collection uses a different name, you'll want to change this overrideRemove: true, // overrides the Collection.removeAsync method to make it an archive instead exclude: ['roles', 'role-assignment'] // exclude specific collections from using archive so that when they are removed, the are permanently removed from the db. defaults to excluding the collections created by the meteor roles package }; ``` To change the global defaults, use: ```js // put this in a file that's imported on both the client and server import { Archive } from 'meteor/jam:archive'; Archive.configure({ // ... change the defaults here ... // }); ``` --- --- url: /api/assets.md --- # Assets > Currently, it is not possible to import `Assets` as an ES6 module. Any of the `Assets` methods below can simply be called directly in any Meteor server code. `Assets` allows server code in a Meteor application to access static server assets, which are located in the `private` subdirectory of an application's tree. Assets are not processed as source files and are copied directly into your application's bundle. Static server assets are included by placing them in the application's `private` subdirectory. For example, if an application's `private` subdirectory includes a directory called `nested` with a file called `data.txt` inside it, then server code can read `data.txt` by running: ```js const data = await Assets.getTextAsync('nested/data.txt'); ``` Note: Packages can only access their own assets. If you need to read the assets of a different package, or of the enclosing app, you need to get a reference to that package's `Assets` object. --- --- url: /packages/audit-argument-checks.md --- # Audit Argument Checks This package causes Meteor to require that all arguments passed to methods and publish functions are [checked](../api/check.md). Any method that does not pass each one of its arguments to `check` will throw an error, which will be logged on the server and which will appear to the client as a `500 Internal server error`. This is a simple way to help ensure that your app has complete check coverage. Methods and publish functions that do not need to validate their arguments can simply run `check(arguments, [Match.Any])` to satisfy the `audit-argument-checks` coverage checker. --- --- url: /packages/autoupdate.md --- # Autoupdate This is the Meteor package that provides hot code push (HCP) functionality. Every Meteor application that wasn't created with the `--minimal` option has this package already through `meteor-base` and HCP should work out of the box. For those running `--minimal` applications and want to benefit from this package, just add it with `meteor add autoupdate`. > `autoupdate` adds up to 30KB on your client's production bundle. With this package Meteor will use DDP to publish a collection called _'meteor_autoupdate_clientVersions'_. This collection will be subscribed by the user's client and every time the client identifies a change in the published version it will refresh itself. ## Browser Client The refresh will happen in the browser in two different ways: a _soft update_, and a _hard update_. If Meteor identifies that only stylesheets were changed, then it will verify if the user's browser is capable of reloading CSS on the fly, and a soft update will take place. The soft update will replace the old stylesheet with the new stylesheet without triggering a full page reload. In cases where a change in a server's or client's compiled file happens, the hard update will take place: Meteor will force a complete browser reload using the `reload` package. > Among other things, the `reload` package tries do reload the application > preserving some unchanged cached files. ## Cordova Client There is no soft update with Cordova apps, the client is always fully refreshed once a change is detected. ### `usesCleartextTraffic` Starting with Android 9 (API level 28), [cleartext support is disabled](https://developer.android.com/training/articles/security-config) by default. During development `autoupdate` uses cleartext to publish new client versions. If your app targets Android 9 or greater, it will be necessary to create a `mobile-config.js` file enabling the use of cleartext in order to have HCP working: ```js App.appendToConfig(` `); ``` ### `--mobile-server` Additionally, for the HCP functionality to work it is also mandatory to provide the address for the application server with `--mobile-server` option. If you're testing your app on an emulator you should run it with `meteor run android --mobile-server 10.0.2.2:3000`. If you're running it on a real device, the application server and the device should be on the same network, and you should run your app with `meteor run android --mobile-server XXX.XXX.XXX.XXX` where *XXX.XXX.XXX.XXX* is your local development address, _e.g. 192.168.1.4_. > To have a better understanding of how HCP works for mobile apps already > published to production refer to [Hot code push on mobile](https://guide.meteor.com/cordova.html#hot-code-push) --- --- url: /api/blaze.md --- # Blaze How to use Blaze, Meteor's reactive rendering engine. This documentation has moved to the [Blaze Community Site](http://blazejs.org/api/templates). --- --- url: /packages/browser-policy.md --- # Browser Policy The `browser-policy` family of packages, part of [Webapp](https://github.com/meteor/meteor/tree/master/packages/webapp), lets you set security-related policies that will be enforced by newer browsers. These policies help you prevent and mitigate common attacks like cross-site scripting and clickjacking. ## Details When you add `browser-policy` to your app, you get default configurations for the HTTP headers X-Frame-Options and Content-Security-Policy. X-Frame-Options tells the browser which websites are allowed to frame your app. You should only let trusted websites frame your app, because malicious sites could harm your users with [clickjacking attacks](https://www.owasp.org/index.php/Clickjacking). [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Security/CSP/Introducing_Content_Security_Policy) tells the browser where your app can load content from, which encourages safe practices and mitigates the damage of a cross-site-scripting attack. `browser-policy` also provides functions for you to configure these policies if the defaults are not suitable. If you only want to use Content-Security-Policy or X-Frame-Options but not both, you can add the individual packages `browser-policy-content` or `browser-policy-framing` instead of `browser-policy`. For most apps, we recommend that you take the following steps: - Add `browser-policy` to your app to enable a starter policy. With this starter policy, your app's client code will be able to load content (images, scripts, fonts, etc.) only from its own origin, except that XMLHttpRequests and WebSocket connections can go to any origin. Further, your app's client code will not be able to use functions such as `eval()` that convert strings to code. Users' browsers will only let your app be framed by web pages on the same origin as your app. - You can use the functions described below to customize the policies. If your app does not need any inline Javascript such as inline ` ``` ::: Since the `Task` component will be part of a list, it returns a `li` element. For now, we won’t connect to our database, so define some sample data in the `App.vue` file to display a list of tasks. Create an array and a function to return this array. ::: code-group ```vue [imports/ui/App.vue] ``` ::: ### 1.5: Rendering Tasks Now let's add some simple rendering logic with Vue to display our list items using the `Task` component. Update the template of the `App` component to include the list of tasks: ::: code-group ```vue [imports/ui/App.vue] ``` ::: For more information on Vue iterations, you can read [here](https://vuejs.org/api/built-in-directives.html#v-for). In the next step, we will connect to the MongoDB database to store our tasks. ## 2: Collections Meteor already sets up MongoDB for you. In order to use our database we need to create a *collection*, which is where we will store our *documents*, in our case our `tasks`. You can read more about collections [here](http://guide.meteor.com/collections.html). In this step we will implement all the necessary code to have a basic collection for our tasks up and running. ### 2.1: Create Tasks Collection Before creating our collection, let's remove the `links.js` file from the `imports/api` folder because we won't use it. Now, you can create a new collection to store our tasks by creating a new file named `tasksCollection.js` inside the `imports/api` folder. ::: code-group ```javascript [imports/api/tasksCollection.js] import { Mongo } from 'meteor/mongo'; export const TasksCollection = new Mongo.Collection('tasks'); ``` ::: The code above instantiates a new MongoDB collection and exports it. You can read more about app structure and imports/exports [here](https://guide.meteor.com/structure.html). ### 2.2: Initialize Tasks Collection To make our collection work, you need to import it on the server to set things up. You can use import `'./imports/api/tasksCollection'` or `import { TasksCollection } from './imports/api/tasksCollection'` if you plan to use `TasksCollection` in the same file. Just make sure it's imported. Now, it’s easy to check if there is data in our collection, or we can easily add some sample data. Replace the old content in `server/main.js` with the code below. ::: code-group ```javascript [server/main.js] import { Meteor } from 'meteor/meteor'; import { TasksCollection } from '../imports/api/tasksCollection'; const insertTask = async text => await TasksCollection.insertAsync({text}); Meteor.startup(async () => { const tasksCount = await TasksCollection.find().countAsync(); if (tasksCount === 0) { await insertTask('First Task'); await insertTask('Second Task'); await insertTask('Third Task'); await insertTask('Fourth Task'); await insertTask('Fifth Task'); await insertTask('Sixth Task'); await insertTask('Seventh Task'); } }); ``` ::: So you are importing the `TasksCollection` and adding a few tasks on it calling a function to insert this string as our `text` field in our `task` document. ### 2.3: Render Tasks Collection Now comes the fun part, you will render the tasks using a “data container” to feed Meteor’s reactive data into Vue’s component hierarchy. We will use the [vue-meteor-tracker](https://www.npmjs.com/package/vue-meteor-tracker) package for this. ::: info Meteor works with Meteor packages and NPM packages, usually Meteor packages are using Meteor internals or other Meteor packages. ::: The `vue-meteor-tracker` package is already included in the Vue skeleton, so you don’t need to add it. When importing code from a Meteor package the only difference from NPM modules is that you need to prepend `meteor/` in the from part of your import. First we need to implement a subscription at the `App` component to get the tasks updated from the server. It can be done simply by using the `subscribe` and `autorun` functions from `vue-meteor-tracker`. ::: info The `vue-meteor-tracker` package doesn't support async calls yet, so we need to use sync functions. This is not an issue, but it's important to know. ::: ::: code-group ```vue [imports/ui/App.vue] ``` ::: To be able to fetch data in the client, you need to publish it in the server. To do it, create a file called `tasksPublications.js` and add the following code: ::: code-group ```javascript [imports/api/tasksPublications.js] import { Meteor } from 'meteor/meteor'; import { TasksCollection } from './tasksCollection'; Meteor.publish('tasks', function publishTasks() { return TasksCollection.find(); }); ``` ::: Now, import it on the server: ::: code-group ```javascript [server/main.js] import '../imports/api/tasksPublications'; ``` ::: > If you want to learn more about how publications works, you can read [here](/api/meteor.html#pubsub). Now, your app should look like this: ![image](https://vue3-tutorial.meteor.com/simple-todos/assets/step02-task-list.png) You can change your data on MongoDB in the server and your app will react and re-render for you. You can connect to your MongoDB running `meteor mongo` in the terminal from your app folder or using a MongoDB UI client, like [NoSQLBooster](https://nosqlbooster.com/downloads) or [MongoDB Compass](https://www.mongodb.com/products/tools/compass). Your embedded MongoDB is running in the port `3001`. See how to connect: ![image](https://vue3-tutorial.meteor.com/simple-todos/assets/new-screenshots/step02/nosql-new-connection.png) See your database: ![image](https://vue3-tutorial.meteor.com/simple-todos/assets/new-screenshots/step02/nosql-connection-editor.png) You can double-click your collection to see the documents stored on it: ![image](https://vue3-tutorial.meteor.com/simple-todos/assets/new-screenshots/step02/nosql-tasks-query.png) ## 3: Forms and Events Now, we need to let users interact with the stored data. In our case, the first interaction is to add new tasks. One of the main ways in which a user can insert or edit data in a website is through forms, using the `
` tag. ### 3.1: Create Task Form First, we need to create a simple form component to encapsulate our logic. Create a new file `TaskForm.vue` in your `ui/components` folder. ::: code-group ```vue [imports/ui/components/TaskForm.vue] ``` ::: This form will have an input element that has a `v-model` attribute. The `newTask` data field will now be bound via two-way binding to the input element’s value. You can see that the form element has a `@submit.prevent` attribute. This will call the `addTask` method, which will print the text to the console when the form is submitted. The `@` symbol is a shorthand for `v-on:`. The prevent modifier stops the form's default behavior, which is to reload the page. ### 3.2: Update the App component Then we can simply add this to our `App` component above your list of tasks: ::: code-group ```vue [imports/ui/App.vue] ``` ::: ### 3.3: Add Insert Operation Now you can edit the `addTask` function to insert a new task into the database. To do it, we will need to implement a Meteor Method. Methods are essentially RPC calls to the server that let you perform operations on the server side securely. You can read more about Meteor Methods [here](https://guide.meteor.com/methods.html). To create your methods, you can create a file called `tasksMethods.js`. ::: code-group ```javascript [imports/api/tasksMethods.js] import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import { TasksCollection } from './tasksCollection'; async function insertTask(text) { check(text, String); return await TasksCollection.insertAsync({ text, createdAt: new Date, }); } Meteor.methods({ insertTask }); ``` ::: Remember to import your method on the `main.js` server file. ::: code-group ```javascript [server/main.js] import { Meteor } from 'meteor/meteor'; import { TasksCollection } from '../imports/api/tasksCollection'; import '../imports/api/tasksPublications'; import '../imports/api/tasksMethods'; ``` ::: Now, we can call this method from our `TaskForm.vue` component. ::: code-group ```vue [imports/ui/components/TaskForm.vue] ``` ::: Inside the function, we are adding a task to the `tasks` collection by calling `Meteor.callAsync()`. The first argument is the name of the method we want to call, and the second argument is the text of the task. We are also trimming the text to remove any extra spaces. ### 3.5: Show Newest Tasks First Now, you just need to make a change which will improve user experience: we will show the newest tasks first. We can accomplish this quickly by sorting our [MongoDB](https://guide.meteor.com/collections.html#mongo-collections) query. ::: code-group ```javascript [imports/ui/App.vue] ... const tasks = autorun(() => { return TasksCollection.find({}, { sort: { createdAt: -1 } }).fetch(); }).result; ... ``` ::: Your app should look like this: ![image](https://vue3-tutorial.meteor.com/simple-todos/assets/new-screenshots/step03/newest-task.png) ## 4: Update and Remove Until now, you have only inserted documents to our collection. Let’s see how you can update and remove them using the user interface. ### 4.1: Add Checkbox First, you need to add a `checkbox` element to your `Task` component, and we need to add the `v-model` directive to the checkbox. This will allow us to bind the value of the checkbox to the `checked` field of the task document. To do this, we need to add a `ref` to the task document. This will allow us to access the task document in the template. And add a computed property `isChecked` for the state management of the checkbox. We also have a prop called `task` that is passed to the component. This prop is an object that represents the task document. ::: code-group ```vue [imports/ui/components/Task.vue] .. ``` ::: ### 4.2: Toggle Checkbox We need to implement the method to update the task document. So, update the `tasksMethods.js` file with the following code: ::: code-group ```javascript [imports/api/tasksMethods.js] import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import { TasksCollection } from './tasksCollection'; async function insertTask(text) { check(text, String); return await TasksCollection.insertAsync({ text, createdAt: new Date, }); } async function removeTask(taskId) { check(taskId, String); await TasksCollection.removeAsync(taskId); } async function setIsCheckedTask(taskId, checked) { check(taskId, String); check(checked, Boolean); await TasksCollection.updateAsync(taskId, { $set: { checked } }); } Meteor.methods({ insertTask, removeTask, setIsCheckedTask }); ``` ::: The `updateAsync` function on a collection takes two arguments. The first is a selector that identifies a subset of the collection, and the second is an update parameter that specifies what should be done to the matched objects. In this case, the selector is just the `_id` of the relevant task. The update parameter uses `$set`, on our method, to toggle the `checked` field, which will represent whether the task has been completed. On the client side, you need to add a function to handle the checkbox change event. This method updates the local `taskRef.checked` state and handles the Meteor call to update the task's checked status. It encapsulate any logic you want to perform when the checkbox is checked or unchecked. ::: code-group ```vue [imports/ui/components/Task.vue] ``` ::: Your app should look like this: ![image](https://vue3-tutorial.meteor.com/simple-todos/assets/new-screenshots/step04/checked-tasks.png) ### 4.3: Remove tasks You can remove tasks with just a few lines of code. First add a button after the text in your `Task` component and receive a callback function. ::: code-group ```vue [imports/ui/components/Task.vue] ... {{ task.text }} ... ``` ::: Now add the removal logic into methods: ::: code-group ```javascript [imports/ui/components/Task.vue] ... const deleteTask = async () => { try { await Meteor.callAsync('removeTask', taskRef.value._id); } catch (error) { console.error('Error deleting task:', error); } }; ... ``` ::: Your app should look like this: ![image](https://vue3-tutorial.meteor.com/simple-todos/assets/new-screenshots/step04/remove-button.png) ## 5: Styles ### 5.1: Tailwind CSS Our user interface has not looked great so far. Let’s add some basic styling to create a foundation for a more professional app. We'll start with the App component. ::: code-group ```vue [imports/ui/App.vue] ``` ::: > Flexbox is an excellent tool to distribute and align elements in your UI. Check this [article](https://tailwindcss.com/docs/) to learn more about [Tailwind CSS](https://tailwindcss.com/), the CSS framework we are using in this tutorial. We’ll also update the `TaskForm` component and the `Task` component. ::: code-group ```vue [imports/ui/components/TaskForm.vue] ``` ```vue [imports/ui/components/Task.vue] ``` ::: Your app should look like this: ![image](https://vue3-tutorial.meteor.com/simple-todos/assets/new-screenshots/step05/new-ui.png) In the next step we will make this task list more interactive providing a way to filter tasks. ## 6: Filter tasks In this step you will filter your tasks by status and show the quantity of pending tasks. ### 6.1: Filter tasks First, you will add a button to show or hide the completed tasks from the list: ::: code-group ```vue [imports/ui/App.vue]
``` ::: You can see that it reads from `hideCompleted`. We’ll need to initialize the value of `hideCompleted` using `ref` in the script section: ::: code-group ```javascript [imports/ui/App.vue] ... import { ref } from 'vue'; const hideCompleted = ref(false); subscribe('tasks'); ... ``` ::: We can update `hideCompleted` from an event handler directly, which will then cause the component to re-render: ::: code-group ```javascript [imports/ui/App.vue] ... const toggleHideCompleted = () => { hideCompleted.value = !hideCompleted.value }; ... ``` ::: Now, we need to update the list of tasks to filter out completed tasks when `hideCompleted` is `true`. `imports/ui/App.vue` ```javascript ... subscribe('tasks'); const tasks = autorun(() => { const filter = hideCompleted.value ? { checked: { $ne: true } } : {}; return TasksCollection.find(filter, { sort: { createdAt: -1 } }).fetch(); }).result; ... ``` Mark a few tasks as completed so you can check the filter behaviour. ### 6.2: Pending tasks Update the App component in order to show the number of pending tasks in the app bar. You should avoid adding zero to your app bar when there are no pending tasks. ::: code-group ```vue [imports/ui/App.vue] ``` ::: At this point, your app should look like this: ![image](https://vue3-tutorial.meteor.com/simple-todos/assets/new-screenshots/step06/showing-all.png) ## 7: Adding User Accounts ### 7.1: Password Authentication Meteor already comes with authentication and account management system out of the box, so you only need to add the `accounts-password` package to enable username and password authentication: ```shell meteor add accounts-password ``` > There are many more authentication methods supported. You can read more about the accounts system [here](https://docs.meteor.com/api/accounts.html). We also recommend you to install `bcrypt` node module, otherwise you are going to see a warning saying that you are using pure-Javascript implementation of it. ```shell meteor npm install --save bcrypt ``` > You should always use `meteor npm` instead of only `npm` so you always use the `npm` version pinned by Meteor, this helps you to avoid problems due to different versions of npm installing different modules. ### 7.2: Create User Account Now, you can create a default user for our app. We will create a new user when the server starts if we don’t find one in the database. Let's make specific functions for user creation and task creation, and then call both in `Meteor.startup()`. ::: code-group ```javascript [server/main.js] import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; import { TasksCollection } from '../imports/api/tasksCollection'; import '../imports/api/tasksPublications'; import '../imports/api/tasksMethods'; const SEED_USERNAME = 'meteorite'; const SEED_PASSWORD = 'password'; const createFirstUser = async () => { const user = await Accounts.findUserByUsername(SEED_USERNAME); if (!user) { await Accounts.createUserAsync({ username: SEED_USERNAME, password: SEED_PASSWORD, }); } } const insertTask = async text => await TasksCollection.insertAsync({text}); const createTasks = async () => { const tasksCount = await TasksCollection.find().countAsync(); if (tasksCount === 0) { await insertTask('First Task'); await insertTask('Second Task'); await insertTask('Third Task'); await insertTask('Fourth Task'); await insertTask('Fifth Task'); await insertTask('Sixth Task'); await insertTask('Seventh Task'); } } Meteor.startup(async () => { await createFirstUser(); await createTasks(); }); ``` ::: You should not see anything different in your app UI yet. ### 7.3: Login Form You need to give users a way to enter their credentials and log in. For this, we need a form. Create a new file called `LoginForm.vue` and add a form to it. Use `Meteor.loginWithPassword(username, password)` to log in the user with the provided information. ::: code-group ```vue [imports/ui/components/LoginForm.vue] ``` ::: ### 7.4: Require Authentication Our app should only let authenticated users access the task management features. We can achieve this by showing the `LoginForm` component when there is no authenticated user. If the user is logged in, we will show the tasks list. We’ll need a `ref` to check if the user is logged in, a `userId` variable to store user data, and some logic using `watch` to update the `isLogged` `ref` when the user changes. ::: code-group ```javascript [imports/ui/App.vue] ... import { Meteor } from 'meteor/meteor'; import { ref, watch } from 'vue'; const hideCompleted = ref(false); const isLogged = ref(false); const userId = autorun(() => Meteor.userId()).result; watch( () => userId.value, (newUser) => { isLogged.value = !!newUser }, { immediate: true } ); ... ``` ::: Next, we can wrap our user features in a `
` tag and use the `v-if` directive to show these features only when a user is logged in: ::: code-group ```vue [imports/ui/App.vue] ``` ::: ### 7.5: Server startup From now on, every task must have an owner. Please go to your database and remove all the tasks: `db.tasks.remove({});` Then, update your `server/main.js` to add the seed tasks with your `meteoriote` user as the owner. ::: code-group ```javascript [server/main.js] import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; import { TasksCollection } from '../imports/api/tasksCollection'; import '../imports/api/tasksPublications'; import '../imports/api/tasksMethods'; const SEED_USERNAME = 'meteorite'; const SEED_PASSWORD = 'password'; const createFirstUser = async () => { const user = await Accounts.findUserByUsername(SEED_USERNAME); if (!user) { await Accounts.createUserAsync({ username: SEED_USERNAME, password: SEED_PASSWORD, }); } } const insertTask = async (text, userId) => { await TasksCollection.insertAsync({text, userId, createdAt: new Date()}); } const createTasks = async () => { const tasksCount = await TasksCollection.find().countAsync(); if (tasksCount === 0) { const user = await Accounts.findUserByUsername(SEED_USERNAME); await insertTask('First Task', user._id); await insertTask('Second Task', user._id); await insertTask('Third Task', user._id); await insertTask('Fourth Task', user._id); await insertTask('Fifth Task', user._id); await insertTask('Sixth Task', user._id); await insertTask('Seventh Task', user._id); } } Meteor.startup(async () => { await createFirstUser(); await createTasks(); }); ``` ::: See that we are using a new field called `userId` with our user `_id` field, we are also setting `createdAt` field. Go to the tasks publication and add the `userId` to the find selector, so users will be able to see only their own tasks. ::: code-group ```javascript [imports/api/tasksPublications.js] import { Meteor } from 'meteor/meteor'; import { TasksCollection } from './tasksCollection'; Meteor.publish('tasks', function publishTasks() { return TasksCollection.find({ userId: Meteor.userId() }); }); ``` ::: Before testing, make sure to restart the server after making this change so that the `Meteor.startup` block runs again. This will likely happen automatically since you're changing the server-side code. ### 7.7: Task owner Tasks are filtered by their owner in the publication using the authenticated user, but we should also filter the tasks in the UI. Use the user's `_id` to add the `userId` field to your MongoDB selector when retrieving tasks from Minimongo. Your `tasks` function should look like this: ::: code-group ```vue [imports/ui/App.vue] ``` ::: ### 7.8: Log out We can include a new `button` right after our `h1`. On this button you can add an `onClick` handler to logout the user. It is very straightforward, just call `Meteor.logout()` on it. ::: code-group ```vue [imports/ui/App.vue]

🚀 To-Do List ({{ incompleteTasksCount }})

... ``` ::: Now that we have authentication, we can add a check on the server side to ensure only logged-in users can delete, update, or add new tasks. We can do this by adding the code below to each on `tasksMethods`: ```javascript if (!Meteor.userId()) { throw new Meteor.Error('Not authorized.'); } ``` Follow how your `tasksMethods` should look like: ::: code-group ```javascript [/imports/api/tasksMethods.js] import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import { TasksCollection } from './tasksCollection'; async function insertTask(text) { check(text, String); if (!Meteor.userId()) { throw new Meteor.Error('Not authorized.'); } return await TasksCollection.insertAsync({ text, userId: Meteor.userId(), createdAt: new Date, }); } async function removeTask(taskId) { check(taskId, String); if (!Meteor.userId()) { throw new Meteor.Error('Not authorized.'); } await TasksCollection.removeAsync(taskId); } async function setIsCheckedTask(taskId, checked) { check(taskId, String); check(checked, Boolean); if (!Meteor.userId()) { throw new Meteor.Error('Not authorized.'); } await TasksCollection.updateAsync(taskId, { $set: { checked } }); } Meteor.methods({ insertTask, removeTask, setIsCheckedTask }); ``` ::: Phew! You have done quite a lot in this step. Authenticated the user, set the user in the tasks and provided a way for the user to log out. Your app should look like this: ![image](https://vue3-tutorial.meteor.com/simple-todos/assets/new-screenshots/step07/login-page.png) ![image](https://vue3-tutorial.meteor.com/simple-todos/assets/new-screenshots/step07/logged-page.png) ## 8: Deploying Deploying a Meteor application is similar to deploying any other Node.js app that uses websockets. You can find deployment options in [our guide](https://guide.meteor.com/deployment), including Meteor Up, Docker, and our recommended method, Galaxy. In this tutorial, we will deploy our app on [Galaxy](https://www.meteor.com/hosting), which is our own cloud solution. Galaxy offers a free plan, so you can deploy and test your app. Pretty cool, right? ### 8.1: Create your account You need a Meteor account to deploy your apps. If you don’t have one yet, you can [sign up here](https://cloud.meteor.com/?isSignUp=true). With this account, you can access our package manager, [Atmosphere](https://atmospherejs.com/), [Forums](https://forums.meteor.com/) and more. ### 8.2: Set up MongoDB (Optional) As your app uses MongoDB the first step is to set up a MongoDB database, Galaxy offers MongoDB hosting on a free plan for testing purposes, and you can also request for a production ready database that allows you to scale. In any MongoDB provider you will have a MongoDB URL which you must use it. If you use the free option provided by Galaxy, the initial setup is done for you. Galaxy MongoDB URL will be like this: `mongodb://username:@org-dbname-01.mongodb.galaxy-cloud.io` . > You can read more about Galaxy MongoDB [here](https://galaxy-support.meteor.com/en/article/mongodb-general-1syd5af/). ### 8.3: Set up settings You need to create a setting file, it’s a JSON file that Meteor apps can read configurations from. Create this file in a new folder called `private` in the root of your project. It is important to notice that `private` is a special folder that is not going to be published to the client side of your app. Make sure you replace `Your MongoDB URL` by your own MongoDB URL :) ::: code-group ```json [private/settings.json] { "galaxy.meteor.com": { "env": { "MONGO_URL": "Your MongoDB URL" } } } ``` ::: ### 8.4: Deploy it Now you are ready to deploy, run `meteor npm install` before deploying to make sure all your dependencies are installed. You also need to choose a subdomain to publish your app. We are going to use the main domain `meteorapp.com` that is free and included on any Galaxy plan. In this example we are going to use `vue3-meteor-3.meteorapp.com` but make sure you select a different one, otherwise you are going to receive an error. > You can learn how to use custom domains on Galaxy [here](https://galaxy-support.meteor.com/en/article/domains-16cijgc/). Custom domains are available starting with the Essentials plan. Run the deployment command: ```shell meteor deploy vue3-meteor-3.meteorapp.com --free --mongo ``` > If you are not using the free hosting with MongoDB on Galaxy, then remove the `--mongo` flag from the deploy script and add `--settings private/settings.json` with the proper setting for your app. Make sure you replace `vue3-meteor-3` by a custom name that you want as subdomain. You will see a log like this: ```shell meteor deploy vue3-meteor-3.meteorapp.com --settings private/settings.json Talking to Galaxy servers at https://us-east-1.galaxy-deploy.meteor.com Preparing to build your app... Preparing to upload your app... Uploaded app bundle for new app at vue-tutorial.meteorapp.com. Galaxy is building the app into a native image. Waiting for deployment updates from Galaxy... Building app image... Deploying app... You have successfully deployed the first version of your app. For details, visit https://galaxy.meteor.com/app/vue3-meteor-3.meteorapp.com ``` This process usually takes just a few minutes, but it depends on your internet speed as it’s going to send your app bundle to Galaxy servers. > Galaxy builds a new Docker image that contains your app bundle and then deploy containers using it, [read more](https://galaxy-support.meteor.com/en/article/container-environment-lfd6kh/). You can check your logs on Galaxy, including the part that Galaxy is building your Docker image and deploying it. ### 8.5: Access the app and enjoy Now you should be able to access your Galaxy dashboard at `https://galaxy.meteor.com/app/vue3-meteor-3.meteorapp.com`. You can also access your app on Galaxy 2.0 which is currently in beta at `https://galaxy-beta.meteor.com//us-east-1/apps/.meteorapp.com`. Remember to use your own subdomain instead of `vue3-meteor-3`. You can access the app at [vue3-meteor-3.meteorapp.com](https://vue3-meteor-3.meteorapp.com/)! Just use your subdomain to access yours! > We deployed to Galaxy running in the US (us-east-1), we also have Galaxy running in other regions in the world, check the list [here](https://galaxy-support.meteor.com/en/article/regions-1vucejm/). This is huge, you have your app running on Galaxy, ready to be used by anyone in the world! ## 9: Next Steps You have completed the tutorial! By now, you should have a good understanding of working with Meteor and Vue. ::: info You can find the final version of this app in our [GitHub repository](https://github.com/meteor/meteor3-vue3). ::: Here are some options for what you can do next: - Check out the complete [documentation](https://v3-docs.meteor.com/) to learn more about Meteor 3. - Read the [Galaxy Guide](https://galaxy-support.meteor.com/en/article/deploy-to-galaxy-18gd6e2/) to learn more about deploying your app. - Join our community on the [Meteor Forums](https://forums.meteor.com/) and the [Meteor Lounge on Discord](https://discord.gg/hZkTCaVjmT) to ask questions and share your experiences. We can't wait to see what you build next! --- --- url: /api/app.md --- # Mobile Configuration If your Meteor application targets mobile platforms such as iOS or Android, you can configure your app's metadata and build process in a special top-level file called `mobile-config.js` which is *not* included in your application and is used only for this configuration. The code snippet below is an example `mobile-config.js` file. The rest of this section will explain the specific API commands in greater detail. ```js // This section sets up some basic app metadata, the entire section is optional. App.info({ id: 'com.example.matt.uber', name: 'über', description: 'Get über power in one button click', author: 'Matt Development Group', email: 'contact@example.com', website: 'http://example.com' }); // Set up resources such as icons and launch screens. App.icons({ 'iphone_2x': 'icons/icon-60@2x.png', 'iphone_3x': 'icons/icon-60@3x.png', // More screen sizes and platforms... }); // Before Meteor 2.6 we had to pass device specific splash screens for iOS, but this behavior was dropped in favor of story board images. App.launchScreens({ // iOS // For most cases you will only need to use the 'ios_universal' and 'ios_universal_3x'. 'ios_universal': { src: 'splash/Default@2x.png', srcDarkMode: 'splash/Default@2x~dark.png' }, // (2732x2732) - All @2x devices, if device/mode specific is not declared 'ios_universal_3x': 'splash/Default@3x.png', // (2208x2208) - All @3x devices, if device/mode specific is not declared // If you still want to use a universal splash, but want to fine-tune for the device mode (landscape, portrait), then use the following keys: 'Default@2x~universal~comany': 'splash/Default@2x~universal~comany.png', // (1278x2732) - All @2x devices in portrait mode. 'Default@2x~universal~comcom': 'splash/Default@2x~universal~comcom.png', // (1334x750) - All @2x devices in landscape (narrow) mode. 'Default@3x~universal~anycom': 'splash/Default@3x~universal~anycom.png', // (2208x1242) - All @3x devices in landscape (wide) mode. 'Default@3x~universal~comany': 'splash/Default@3x~universal~comany.png', // (1242x2208) - All @3x devices in portrait mode. // However, if you need to fine tune the splash screens for the device idiom (iPhone, iPad, etc). 'Default@2x~iphone~anyany': 'splash/Default@2xiphoneanyany.png', // (1334x1334) - iPhone SE/6s/7/8/XR 'Default@2x~iphone~comany': 'splash/Default@2xiphonecomany.png', // (750x1334) - iPhone SE/6s/7/8/XR - portrait mode 'Default@2x~iphone~comcom': 'splash/Default@2xiphonecomcom.png', // (1334x750) - iPhone SE/6s/7/8/XR - landscape (narrow) mode 'Default@3x~iphone~anyany': 'Default@3xiphoneanyany.png', // (2208x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max 'Default@3x~iphone~anycom': { src: 'splash/Default@3xiphoneanycom.png', srcDarkMode: 'splash/Default@3xiphoneanycom~dark.png' }, // (2208x1242) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max - landscape (wide) mode 'Default@3x~iphone~comany': 'Default@3xiphonecomany.png', // (1242x2208) - iPhone 6s Plus/7 Plus/8 Plus/X/XS/XS Max - portrait mode 'Default@2x~ipad~anyany': 'Default@2xipadanyany.png', // (2732x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9" 'Default@2x~ipad~comany': 'Default@2xipadcomany.png', // (1278x2732) - iPad Pro 12.9"/11"/10.5"/9.7"/7.9" - portrait mode // Android 'android_universal': 'splash/android_universal.png', // (320x480) }); // Set PhoneGap/Cordova preferences. App.setPreference('BackgroundColor', '0xff0000ff'); App.setPreference('HideKeyboardFormAccessoryBar', true); App.setPreference('Orientation', 'default'); App.setPreference('Orientation', 'all', 'ios'); // Pass preferences for a particular PhoneGap/Cordova plugin. App.configurePlugin('com.phonegap.plugins.facebookconnect', { APP_ID: '1234567890', API_KEY: 'supersecretapikey' }); // Add custom tags for a particular PhoneGap/Cordova plugin to the end of the // generated config.xml. 'Universal Links' is shown as an example here. App.appendToConfig(` `); ``` For example this Cordova whitelist syntax: ```xml ``` is equivalent to: ```js App.accessRule('https://www.google-analytics.com'); App.accessRule('https://example.com', { type: 'navigation' }); ``` > Note: When using `App.configurePlugin` to re-configure a plugin which has been previously configured, the changes may not be reflected without manually clearing the existing Cordova build. To clear the existing Cordova build, remove the `.meteor/local/cordova-build` directory and re-build the application using either `meteor run` or `meteor build`. > Note: The resource file is copied in two steps : from the **src** of your meteor project to the root of the cordova project, then to the **target** --- --- url: /packages/modern-browsers.md --- # Modern-browsers API for defining the boundary between modern and legacy JavaScript clients. You can use this package to define the minimum browser versions for which a browser engine will be considered modern. All browsers that do not meet the threshold will receive the legacy bundle. This way you can easily keep on using modern features that you need. You can read more about this in [Meteor 1.7 announcement blog](https://blog.meteor.com/meteor-1-7-and-the-evergreen-dream-a8c1270b0901). ## Configure Unknown Browsers to default to Modern Browsers not explicitly listed in `setMinimumBrowserVersions` are considered "legacy" by default. To change this and treat unknown browsers as "modern," update the relevant option in your settings file: ``` js Meteor.settings.packages = { "modern-browsers": { "unknownBrowsersAssumedModern": true } }; ``` --- --- url: /packages/modules.md --- # Modules This document explains the usage and key features of the module system used by Meteor. > Meteor 1.2 introduced support for [many new ECMAScript 2015 features](https://github.com/meteor/meteor/blob/devel/packages/ecmascript/README.md#supported-es2015-features), one of the most notable omissions was [ES2015 `import` and `export` syntax](http://exploringjs.com/es6/ch_modules.html). > Meteor 1.3 filled the gap with a fully standards-compliant module system that works on both the client and the server. > Meteor 1.7 introduced `meteor.mainModule` and `meteor.testModule` to `package.json` so Meteor doesn't need special folders anymore for js resources. Also doesn't need to eager load js resources. By design, `meteor.mainModule` only affect js resources. For non-js resources, there are still some things that can only be done within imports: - only stylesheets within imports can be dynamically imported - you can only control the load order of stylesheets by importing them in js if the stylesheets are within imports Any non-js resource outside of imports (and some other special folders) are still eagerly loaded. > You can read more about these differences in this [comment](https://github.com/meteor/meteor/pull/11381#issuecomment-818816052). ## Enabling modules It is installed by default for all new apps and packages. Nevertheless, the `modules` package is totally optional. If you want to add it to existent apps or packages: For apps, this is as easy as `meteor add modules`, or (even better) `meteor add ecmascript`, since the `ecmascript` package *implies* the `modules` package. For packages, you can enable `modules` by adding `api.use('modules')` to the `Package.onUse` or `Package.onTest` sections of your `package.js` file. Now, you might be wondering what good the `modules` package is without the `ecmascript` package, since `ecmascript` enables `import` and `export` syntax. By itself, the `modules` package provides the CommonJS `require` and `exports` primitives that may be familiar if you’ve ever written Node code, and the `ecmascript` package simply compiles `import` and `export` statements to CommonJS. The `require` and `export` primitives also allow Node modules to run within Meteor application code without modification. Furthermore, keeping `modules` separate allows us to use `require` and `exports` in places where using `ecmascript` is tricky, such as the implementation of the `ecmascript` package itself. While the `modules` package is useful by itself, we very much encourage using the `ecmascript` package (and thus `import` and `export`) instead of using `require` and `exports` directly. If you need convincing, here's a [presentation](http://benjamn.github.io/empirenode-2015) that explains the differences. ## Basic syntax ### ES2015 Although there are a number of different variations of `import` and `export` syntax, this section describes the essential forms that everyone should know. First, you can `export` any named declaration on the same line where it was declared: ```js // exporter.js export var a = ...; export let b = ...; export const c = ...; export function d() { ... } export function* e() { ... } export class F { ... } ``` These declarations make the variables `a`, `b`, `c` (and so on) available not only within the scope of the `exporter.js` module, but also to other modules that `import` from `exporter.js`. If you prefer, you can `export` variables by name, rather than prefixing their declarations with the `export` keyword: ```js // exporter.js function g() { ... } let h = g(); // At the end of the file export { g, h }; ``` All of these exports are *named*, which means other modules can import them using those names: ```js // importer.js import { a, c, F, h } from './exporter'; new F(a, c).method(h); ``` If you’d rather use different names, you’ll be glad to know `export` and `import` statements can rename their arguments: ```js // exporter.js export { g as x }; g(); // Same as calling `y()` in importer.js ``` ```js // importer.js import { x as y } from './exporter'; y(); // Same as calling `g()` in exporter.js ``` As with CommonJS `module.exports`, it is possible to define a single *default* export: ```js // exporter.js export default any.arbitrary(expression); ``` This default export may then be imported without curly braces, using any name the importing module chooses: ```js // importer.js import Value from './exporter'; // Value is identical to the exported expression ``` Unlike CommonJS `module.exports`, the use of default exports does not prevent the simultaneous use of named exports. Here is how you can combine them: ```js // importer.js import Value, { a, F } from './exporter'; ``` In fact, the default export is conceptually just another named export whose name happens to be "default": ```js // importer.js import { default as Value, a, F } from './exporter'; ``` These examples should get you started with `import` and `export` syntax. For further reading, here is a very detailed [explanation](http://www.2ality.com/2014/09/es6-modules-final.html) by [Axel Rauschmayer](https://twitter.com/rauschma) of every variation of `import` and `export` syntax. ### CommonJS You don’t need to use the `ecmascript` package or ES2015 syntax in order to use modules. Just like Node.js in the pre-ES2015 days, you can use `require` and `module.exports`—that’s what the `import` and `export` statements are compiling into, anyway. ES2015 `import` lines like these: ```js import { AccountsTemplates } from 'meteor/useraccounts:core'; import '../imports/startup/client/routes.js'; ``` can be written with CommonJS like this: ```js var UserAccountsCore = require('meteor/useraccounts:core'); require('../imports/startup/client/routes.js'); ``` and you can access `AccountsTemplates` via `UserAccountsCore.AccountsTemplates`. Note that files don’t need a `module.exports` if they’re required like `routes.js` is in this example, without assignment to any variable. The code in `routes.js` will simply be included and executed in place of the above `require` statement. ES2015 `export` statements like these: ```js export const insert = new ValidatedMethod({ ... }); export default incompleteCountDenormalizer; ``` can be rewritten to use CommonJS `module.exports`: ```js module.exports.insert = new ValidatedMethod({ ... }); module.exports.default = incompleteCountDenormalizer; ``` You can also simply write `exports` instead of `module.exports` if you prefer. If you need to `require` from an ES2015 module with a `default` export, you can access the export with `require('package').default`. There is a case where you might *need* to use CommonJS, even if your project has the `ecmascript` package: if you want to conditionally include a module. `import` statements must be at top-level scope, so they cannot be within an `if` block. If you’re writing a common file, loaded on both client and server, you might want to import a module in only one or the other environment: ```js if (Meteor.isClient) { require('./client-only-file.js'); } ``` Note that dynamic calls to `require()` (where the name being required can change at runtime) cannot be analyzed correctly and may result in broken client bundles. This is also discussed in [the guide](http://guide.meteor.com/structure.html#using-require). ### CoffeeScript CoffeeScript has been a first-class supported language since Meteor’s early days. Even though today we recommend ES2015, we still intend to support CoffeeScript fully. As of CoffeeScript 1.11.0, [CoffeeScript supports `import` and `export` statements natively](http://coffeescript.org/#modules). Make sure you are using the latest version of the [CoffeeScript package](https://atmospherejs.com/meteor/coffeescript) in your project to get this support. New projects created today will get this version with `meteor add coffeescript`. Make sure you don’t forget to include the `ecmascript` and `modules` packages: `meteor add ecmascript`. (The `modules` package is implied by `ecmascript`.) CoffeeScript `import` syntax is nearly identical to the ES2015 syntax you see above: ```coffee import { Meteor } from 'meteor/meteor' import SimpleSchema from 'simpl-schema' import { Lists } from './lists.coffee' ``` You can also use traditional CommonJS syntax with CoffeeScript. ## Modular application structure Use in your application `package.json` file the section `meteor`. > This is available since Meteor 1.7 ```json { "meteor": { "mainModule": { "client": "client/main.js", "server": "server/main.js" } } } ``` When specified, these entry points will define in which files Meteor is going to start the evaluation process for each architecture (client and server). This way Meteor is not going to eager load any other js files. There is also an architecture for the `legacy` client, which is useful if you want to load polyfills or other code for old browsers before importing the main module for the modern client. In addition to `meteor.mainModule`, the `meteor` section of `package.json` may also specify `meteor.testModule` to control which test modules are loaded by `meteor test` or `meteor test --full-app`: ```json { "meteor": { "mainModule": { "client": "client/main.js", "server": "server/main.js" }, "testModule": "tests.js" } } ``` If your client and server test files are different, you can expand the testModule configuration using the same syntax as mainModule: ```json { "meteor": { "mainModule": { "client": "client/main.js", "server": "server/main.js" }, "testModule": { "client": "client/tests.js", "server": "server/tests.js" } } } ``` The same test module will be loaded whether or not you use the `--full-app` option. Any tests that need to detect `--full-app` should check `Meteor.isAppTest`. The module(s) specified by `meteor.testModule` can import other test modules at runtime, so you can still distribute test files across your codebase; just make sure you import the ones you want to run. To disable eager loading of modules on a given architecture, simply provide a mainModule value of false: ```json { "meteor": { "mainModule": { "client": false, "server": "server/main.js" } } } ``` ### Historic behind Modular application structure If you want to understand how Meteor works without `meteor.mainModule` on `package.json` keep reading this section, but we don't recommend this approach anymore. Before the release of Meteor 1.3, the only way to share values between files in an application was to assign them to global variables or communicate through shared variables like `Session` (variables which, while not technically global, sure do feel syntactically identical to global variables). With the introduction of modules, one module can refer precisely to the exports of any other specific module, so global variables are no longer necessary. If you are familiar with modules in Node, you might expect modules not to be evaluated until the first time you import them. However, because earlier versions of Meteor evaluated all of your code when the application started, and we care about backwards compatibility, eager evaluation is still the default behavior. If you would like a module to be evaluated *lazily* (in other words: on demand, the first time you import it, just like Node does it), then you should put that module in an `imports/` directory (anywhere in your app, not just the root directory), and include that directory when you import the module: `import {stuff} from './imports/lazy'`. Note: files contained by `node_modules/` directories will also be evaluated lazily (more on that below). ## Modular package structure If you are a package author, in addition to putting `api.use('modules')` or `api.use('ecmascript')` in the `Package.onUse` section of your `package.js` file, you can also use a new API called `api.mainModule` to specify the main entry point for your package: ```js Package.describe({ name: 'my-modular-package' }); Npm.depends({ moment: '2.10.6' }); Package.onUse((api) => { api.use('modules'); api.mainModule('server.js', 'server'); api.mainModule('client.js', 'client'); api.export('Foo'); }); ``` Now `server.js` and `client.js` can import other files from the package source directory, even if those files have not been added using the `api.addFiles` function. When you use `api.mainModule`, the exports of the main module are exposed globally as `Package['my-modular-package']`, along with any symbols exported by `api.export`, and thus become available to any code that imports the package. In other words, the main module gets to decide what value of `Foo` will be exported by `api.export`, as well as providing other properties that can be explicitly imported from the package: ```js // In an application that uses 'my-modular-package': import { Foo as ExplicitFoo, bar } from 'meteor/my-modular-package'; console.log(Foo); // Auto-imported because of `api.export`. console.log(ExplicitFoo); // Explicitly imported, but identical to `Foo`. console.log(bar); // Exported by server.js or client.js, but not auto-imported. ``` Note that the `import` is `from 'meteor/my-modular-package'`, not `from 'my-modular-package'`. Meteor package identifier strings must include the prefix `meteor/...` to disambiguate them from npm packages. Finally, since this package is using the new `modules` package, and the package `Npm.depends` on the "moment" npm package, modules within the package can `import moment from 'moment'` on both the client and the server. This is great news, because previous versions of Meteor allowed npm imports only on the server, via `Npm.require`. ### Lazy loading modules from a package Packages can also specify a *lazy* main module: ```js Package.onUse(function (api) { api.mainModule("client.js", "client", { lazy: true }); }); ``` This means the `client.js` module will not be evaluated during app startup unless/until another module imports it, and will not even be included in the client bundle if no importing code is found. To import a method named `exportedPackageMethod`, simply: ```js import { exportedPackageMethod } from "meteor/"; ``` > Note: Packages with `lazy` main modules cannot use `api.export` to export global symbols to other packages/apps. Also, prior to Meteor 1.4.4.2 it is necessary to explicitly name the file containing the module: `import "meteor//client.js"`. ## Local `node_modules` Before Meteor 1.3, the contents of `node_modules` directories in Meteor application code were completely ignored. When you enable `modules`, those useless `node_modules` directories suddenly become infinitely more useful: ```sh meteor create modular-app cd modular-app mkdir node_modules npm install moment echo "import moment from 'moment';" >> modular-app.js echo 'console.log(moment().calendar());' >> modular-app.js meteor ``` When you run this app, the `moment` library will be imported on both the client and the server, and both consoles will log output similar to: `Today at 7:51 PM`. Our hope is that the possibility of installing Node modules directly within an app will reduce the need for npm wrapper packages such as https://atmospherejs.com/momentjs/moment. A version of the `npm` command comes bundled with every Meteor installation, and (as of Meteor 1.3) it's quite easy to use: `meteor npm ...` is synonymous with `npm ...`, so `meteor npm install moment` will work in the example above. (Likewise, if you don't have a version of `node` installed, or you want to be sure you're using the exact same version of `node` that Meteor uses, `meteor node ...` is a convenient shortcut.) That said, you can use any version of `npm` that you happen to have available. Meteor's module system only cares about the files installed by `npm`, not the details of how `npm` installs those files. ## File load order Before Meteor 1.3, the order in which application files were evaluated was dictated by a set of rules described in the [Application Structure - Default file load order](http://guide.meteor.com/structure.html#load-order) section of the Meteor Guide. These rules could become frustrating when one file depended on a variable defined by another file, particularly when the first file was evaluated after the second file. Thanks to modules, any load-order dependency you might imagine can be resolved by adding an `import` statement. So if `a.js` loads before `b.js` because of their file names, but `a.js` needs something defined by `b.js`, then `a.js` can simply `import` that value from `b.js`: ```js // a.js import { bThing } from './b'; console.log(bThing, 'in a.js'); ``` ```js // b.js export var bThing = 'a thing defined in b.js'; console.log(bThing, 'in b.js'); ``` Sometimes a module doesn’t actually need to import anything from another module, but you still want to be sure the other module gets evaluated first. In such situations, you can use an even simpler `import` syntax: ```js // c.js import './a'; console.log('in c.js'); ``` No matter which of these modules is imported first, the order of the `console.log` calls will always be: ```js console.log(bThing, 'in b.js'); console.log(bThing, 'in a.js'); console.log('in c.js'); ``` --- --- url: /community-packages/mongo-transactions.md --- # Mongo-transactions - `Who maintains the package` – [Jam](https://github.com/jamauro) [[toc]] ## What is this package? `jam:mongo-transactions` enables an easy way to work with Mongo Transactions in Meteor apps. Here are a few of the benefits: * Write with the standard Meteor collection methods you're accustomed to. You don't need to worry about using `rawCollection()`, though if you need a particular `rawCollection()` method, you can still use it. * You don't need to worry about passing `session` around. This package takes care of that for you. * Because it's a low-level solution, ID generation works as expected. * Works out-of-the-box with other packages that automatically validate on DB writes, like `jam:easy-schema` and `aldeed:collection2`. * One simple API to use. Mongo has made things complicated with two APIs for Transactions, the Callback API and the Core API. This package defaults to using the Callback API as recommended by Mongo, but allows you to use the Core API by passing in `autoRetry: false`. * Can be used isomorphically. * Compatible with Meteor 2.8.1 and up, including support for Meteor 3.0+ > **Important**: This package expects that you'll use the promise-based `*Async` Meteor collection methods introduced in `v2.8.1` though it will technically work with the older syntax without the `*Async` suffix as long as you don't use callbacks. It does **not** cover using callbacks with Meteor collection methods. ## How to download it? Add the package to your app ```bash meteor add jam:mongo-transactions ``` ### Sources * [GitHub repository](https://github.com/jamauro/mongo-transactions) ## How to use it? ### Create a Transaction > **Note**: there's no need to pass `session` into the `*Async` collection methods. This package handles that for you. ```js import { Mongo } from 'meteor/mongo'; async function purchase(purchaseData) { try { const { invoiceId } = await Mongo.withTransaction(async () => { const invoiceId = await Invoices.insertAsync(purchaseData); const changeQuantity = await Items.updateAsync(purchaseData.itemId, { $set: {...} }); return { invoiceId, changeQuantity } // you can return whatever you'd like }); return invoiceId; } catch (error) { // something went wrong with the transaction and it could not be automatically retried // handle the error as you see fit } } ``` ### Passing Transaction options If you want to customize how the Transaction runs, pass in the Transaction options as the second argument, for example: ```js await Mongo.withTransaction(async () => { ... }, { writeConcern: { w: 1 } }); ``` Refer to the [Mongo Node API docs](https://mongodb.github.io/node-mongodb-native/6.3/interfaces/TransactionOptions.html) for more information about Transaction options. ### Preventing automatic retries if the Transaction fails Most of the time, you'll want the default behavior where the Transaction is automatically retried for a `TransientTransactionError` or `UnknownTransactionCommitResult` commit error. But if you don't want that behavior, simply pass in `{ autoRetry: false }` like this: ```js await Mongo.withTransaction(async () => { ... }, { autoRetry: false }); ``` Setting `{ autoRetry: false }`, means the Transactions Core API will be used rather than the Callback API and you'll be responsible for handling all errors. You can read more about the differences in the [Mongo Docs](https://www.mongodb.com/docs/manual/core/transactions-in-applications/). ### Determine if you're in a Transaction To determine if the code is currently running in a Transaction, use: ```js Mongo.inTransaction(); // returns true or false ``` ### Using Isomorphically for Optimistic UI You can write `Mongo.withTransaction` and `Mongo.inTransaction` isomorphically for Optimistic UI however note that the Transaction will only truly be performed on the server. As with any isomorphic code, you should be aware that it may fail because the operation can't succeed on the client but will succeed on the server. For example, let's say you're using `.find` within the Transaction and Minimongo on the client doesn't have that particular data, the client will fail but the server should still succeed. You can wrap specific server-only code with `if (Meteor.isServer)`, but in these cases, you'll likely want to avoid isomorphic code and make sure the entire Transaction code only runs on the server. ## Using Mongo Atlas as your DB? > **Important**: In my experience, you must use a paid tier for Transactions to work as expected with Meteor. The free tier would not tail the oplog for Transactions. So if you're trying this out in production, be sure to use a paid tier. --- --- url: /packages/oauth-encryption.md --- # OAuth Encryption Encrypts sensitive login secrets stored in the database such as a login service's application secret key and users' access tokens. ## Generating a Key The encryption key is 16 bytes, encoded in Base64. To generate a key: ```bash $ meteor node -e 'console.log(require("crypto").randomBytes(16).toString("base64"))' ``` ## Using oauth-encryption with accounts On the server only, use the `oauthSecretKey` option to `Accounts.config`: ```js Accounts.config({ oauthSecretKey: 'onsqJ+1e4iGFlV0nhZYobg==' }); ``` This call to `Accounts.config` should be made at load time (place at the top level of your source file), not called from inside of a `Meteor.startup` block. To avoid storing the secret key in your application's source code, you can use [`Meteor.settings`](../api/meteor.md#Meteor-settings): ```js Accounts.config({ oauthSecretKey: Meteor.settings.oauthSecretKey }); ``` ## Migrating unencrypted user tokens This example for Twitter shows how existing unencrypted user tokens can be encrypted. The query finds user documents which have a Twitter access token but not the `algorithm` field which is created when the token is encrypted. The relevant fields in the service data are then encrypted. ```js const cursor = Meteor.users.find({ $and: [ { 'services.twitter.accessToken': { $exists: true } }, { 'services.twitter.accessToken.algorithm': { $exists: false } } ] }); cursor.forEach((userDoc) => { const set = {}; ['accessToken', 'accessTokenSecret', 'refreshToken'].forEach((field) => { const plaintext = userDoc.services.twitter[field]; if (!_.isString(plaintext)) { return; } set[`services.twitter.${field}`] = OAuthEncryption.seal( plaintext, userDoc._id ); }); Meteor.users.update(userDoc._id, { $set: set }); }); ``` ## Using oauth-encryption without accounts If you're using the oauth packages directly instead of through the Meteor accounts packages, you can load the OAuth encryption key directly using `OAuthEncryption.loadKey`: ```js OAuthEncryption.loadKey('onsqJ+1e4iGFlV0nhZYobg=='); ``` If you call `retrieveCredential` (such as `Twitter.retrieveCredential`) as part of your process, you'll find when using oauth-encryption that the sensitive service data fields will be encrypted. You can decrypt them using `OAuth.openSecrets`: ```js const credentials = Twitter.retrieveCredential(token); const serviceData = OAuth.openSecrets(credentials.serviceData); ``` ## Using oauth-encryption on Windows This package depends on [npm-node-aes-gcm](https://github.com/meteor/meteor/tree/devel/packages/non-core/npm-node-aes-gcm), which requires you to have OpenSSL installed on your system to run. To install OpenSSL on Windows, use one of the binaries on [this page](http://slproweb.com/products/Win32OpenSSL.html). Don't forget to install the Visual Studio 2008 redistributables if you don't have them yet. --- --- url: /community-packages/offline.md --- # Offline - `Who maintains the package` – [Jam](https://github.com/jamauro) [[toc]] ## What is this package? Offline is an easy way to give your Meteor app offline capabilities and make it feel instant. Its key features are: * Offline access – Saves Minimongo data in IndexedDB for offline access and instant reloading when reconnected. * Automatic syncing – Syncs offline actions automatically once reconnected. * Cross-Tab syncing – Keeps changes in sync across multiple tabs while offline. * Seamless reconciliation – Reconciles data with the server when reconnected. ## How to download it? Add the package to your app ```bash meteor add jam:offline ``` ### Sources * [GitHub repository](https://github.com/jamauro/offline) ## How to use it? ### Keeping data offline By default, offline data will be kept for all collections using the global defaults in [Offline.config](#configuring-optional) without any configuration needed. To change the data that is kept for a specific collection, define `.keep` for the collection in a file that's imported on both client and server. If you don't want all collections to be kept, be sure to configure `keepAll: false`. See [Configuring](#configuring-optional) for more details. `.keep` accepts a `filter`, and `sort` and `limit` options much like a `.find`. For example: ```js const Todos = new Mongo.Collection('todos'); Todos.keep({ $or: [ { isPrivate: { $ne: true } }, { owner: Meteor.userId() } ]}, { limit: 200 }) // this will override any global defaults ``` If you have specific restrictions on what should be kept, e.g. permissions to a document like in the example above, these should be included in the `.keep` filter. Be aware that when the user comes back online, this filter will be used to reconcile with the server. In general, it's recommended that you only use the `.keep` filter where absolutely needed. **`Note`**: You can use `Meteor.userId()` in your `.keep` filter if needed. ### Clearing offline data for a specific collection If you ever need to clear offline data for a specific collection, call `clear`: ```js Todos.clear(); ``` ### Clearing all offline data If you ever need to completely clear all offline data, you can use `clearAll`: ```js import { clearAll } from 'meteor/jam:offline'; clearAll(); ``` ### Queuing methods when offline When a user is offline, you can queue any actions they take for replay when they come back online using `queueMethod`: ```js import { queueMethod } from 'meteor/jam:offline'; if (!Meteor.status().connected) { // check that the user is offline queueMethod(name, arg1, arg2...) // the arguments should be the same form that you'd use for Meteor.callAsync } ``` where name is the method's name and the arguments are what the method expects. You'll still want to call the method when offline and it's recommended that you use [Meteor.applyAsync](https://docs.meteor.com/api/methods.html#Meteor-applyAsync) with the option `noRetry: true` to avoid using Meteor's built-in retry mechanism since we'll be handling replay separately. For this to work as intended, the methods that you call should be isomorphic so that they're availble to the client. **`Note`**: If you're using [jam:method](https://github.com/jamauro/method), queuing methods for replay is handled automatically for you. 🎉 **`Note`**: When queuing methods than involve an `insert` to a collection, make sure the method returns the new document's `_id`. By doing this, you ensure that any subsequent changes made to the document while still offline are handled correctly. ### Auto syncing By default, the package auto syncs when the user comes back online. This includes: 1. Replaying queued methods 2. Removing offline data for each collection that no longer belongs because it doesn't match the configured `filter` or the collection's `.keep` filter The benefit of this sequential-replay syncing strategy is any business logic contained in your methods will be respected. For example, if a user makes changes to a document but loses permission to it while offline, when they come back online, that permission will be respected when the attempted replay occurs. If there are any errors during auto sync, they will be made available in the `handleSyncErrors` function. You can use it to make your user aware that their changes failed. See [Configuring](#configuring-optional) for more details on how to customize this. When reconciling with the server, this package assumes that you'll use one of the following mechanisms when removing documents: 1. `archive`. With this mechanism, when a document is removed from its original collection, it's placed in an `archive` collection. If your app doesn't already employ an archive mechanism, check out the [jam:archive](https://github.com/jamauro/archive) package to make this easy. By simply adding the `jam:archive` package, you won't need to do any further configuration. Otherwise, be sure to check out the `archive` configuration options. **`Note`**: it's assumed that the data in your `archive` collection will include the original document data at the root level, e.g.: ```js { _id, // auto-generated by Meteor as with other collection _ids _collection, // the name of the collection, e.g. 'todos', that the doc belonged to originally archivedAt, // the timestamp when the document was removed from its original collection and inserted into the archive id, // the original doc _id renamed to prevent conflict with the auto-generated one above. when restored, it will be renamed back to _id automatically by this package /* ...rest of original doc */ } ``` 2. `soft delete`. If your app doesn't already employ a soft delete mechanism, check out the [jam:soft-delete](https://github.com/jamauro/soft-delete) package to make this easy. If you're using something other than `deleted` as the flag name for your soft deletes, be sure to configure `filter` appropriately. See [Configuring](#configuring-optional) for more details. To know when an auto sync is processing, you can use `isSyncing()` which is a reactive variable. ```js import { isSyncing } from 'meteor/jam:offline'; isSyncing(); // you can wrap this in a Tracker.autorun to detect when it changes ``` If you prefer not to have the behavior provided by auto sync, be sure to configure `autoSync: false`. When `autoSync` is false, Minimongo data from when the user disconnects will be kept offline so you still benefit from faster reloads on reconnects but you'll be responsible for designing any syncing logic needed. If the user hasn't refreshed the page and has a brief disconnect / reconnect, then you'll still benefit from Meteor's built-in retry mechanism for methods. But if they do refresh or exit the app and come back later, any actions performed while offline will be lost. I think it would be great to have better support for custom syncing. If you have ideas here, let me know. At this time, I'm not sure what primitives would be most useful for you. ## Configuring (optional) If you like the defaults, then you won't need to configure anything. But there is some flexibility in how you use this package. You may want to pay special attention to `filter` and `handleSyncErrors` to customize the experience for your users. Here are the global defaults: ```js const config = { filter: /** {} or { deleted: false } **/ // filter the documents to keep across all collections. if you're not using jam:archive or don't have the archive config below set, it will assume you're using soft deletes. sort: { updatedAt: -1 }, // keep the most recent documents assuming you have an updatedAt on each doc. if you're using a different field name for timestamps, you'll want to change this. limit: 100, // limit offline documents to a max of 100 for each collection keepAll: true, // keep data for offline use for all collections using the global filter, sort, limit. to keep data for only certain collections, set this to false and then use collection.keep() for the collections you want to use offline. autoSync: true, // auto sync changes made offline when the user comes back online handleSyncErrors: async ({ replayErrors, keepErrors }) => { if (replayErrors) console.error('replay', replayErrors); // if there are errors when the Meteor methods are replayed, they will be in array here with the name of the method, the method's args, and the error itself. you can use it to alert your user, logging purposes, etc. if (keepErrors) { // when syncing, if you're using a .keep filter or you have a global filter in the config that isn't an empty object, and there are errors reconciling with the server, they will be in an array here with the name of the collection and the error itself. you can customize how you handle these. by default, we clear the offline database for the collection since it could have stale data and reload the page. await Promise.allSettled(keepErrors.map(({ name }) => clear(name))); console.error('keep', keepErrors) } return; }, ...(Archive && { // Archive is provided by jam:archive. if you're using a different archive mechanism, you'll need to configure these manually archive: { name: 'archives', collectionKey: '_collection', primaryIdKey: 'id', timestampKey: 'archivedAt' } }) }; ```` To change the global defaults, use: ```js // put this in a file that's imported on both the client and server import { Offline } from 'meteor/jam:offline'; Offline.configure({ // ... change the defaults here ... // }); ``` ## Adding a service worker You'll likely want to add a service worker as well to cache your HTML, CSS, Javascript so your users can continue to use the app offline, even if they accidentally click refresh. Follow these [instructions](https://github.com/jamauro/pwa-kit) to add a service worker and go even further by making it a PWA (progressive web app). --- --- url: /api/package.md --- # Package.js A package is a directory containing a package.js file, which contains roughly three major sections: a basic description, a package definition, and a test definition. By default, the directory name is the name of the package. The `package.js` file below is an example of how to use the packaging API. The rest of this section will explain the specific API commands in greater detail. ```js // Information about this package: Package.describe({ // Short two-sentence summary summary: 'What this does', // Version number version: '1.0.0', // Optional, default is package directory name name: 'username:package-name', // Optional GitHub URL to your source repository git: 'https://github.com/something/something.git' }); // This defines your actual package: Package.onUse((api) => { // If no version is specified for an `api.use` dependency, use the one defined // in Meteor 1.12.1. api.versionsFrom('1.12.1'); // Use the `underscore` package, but only on the server. Version not // specified, so it will be as of Meteor 1.12.1. api.use('underscore', 'server'); // Use `ostrio:flow-router-extra`, version 3.9.0 or newer. api.use('ostrio:flow-router-extra@3.9.0'); // Give users of this package access to active-route's JavaScript helpers. api.imply('zimme:active-route@2.3.2') // Export the object `Email` to packages or apps that use this package. api.export('Email', 'server'); // Specify the source code for the package. api.addFiles('email.js', 'server'); // When using `ecmascript` or `modules` packages, you can use this instead of // `api.export` and `api.addFiles`. api.mainModule('email.js', 'server'); }); // This defines the tests for the package: Package.onTest((api) => { // Sets up a dependency on this package. api.use('username:package-name'); // Use the Mocha test framework. api.use('practicalmeteor:mocha@2.4.5_6'); // Specify the source code for the package tests. api.addFiles('email_tests.js', 'server'); }); // This lets you use npm packages in your package: Npm.depends({ simplesmtp: '0.3.10', 'stream-buffers': '0.2.5' }); ``` `api.mainModule` is documented in the [modules](../packages/modules.md#modular-application-structure) section. Build plugins are created with [`Package.registerBuildPlugin`](#PackageNamespace-registerBuildPlugin). See the coffeescript package for an example. Build plugins are fully-fledged Meteor programs in their own right and have their own namespace, package dependencies, source files and npm requirements. > You can use local packages to define custom build plugins for your app, with one caveat. In published packages, build plugins are already bundled with their transitive dependencies. So if you want a dependency of a build plugin to be satisfied by a local package, you must use a local copy of the package that defines the plugin (even if you make no changes to that package) so that Meteor will pick up the local dependency. > In a lifecycle of a package there might come time to end the development for various reasons, or it gets superseded. In either case Meteor allows you to easily notify the users of the package by setting the deprecated flag to true: `deprecated: true` in the package description. In addition, you replace it with a string that tells the users where to find replacement or what to do. Provide basic package information with `Package.describe(options)`. To publish a package, you must define `summary` and `version`. Define dependencies and expose package methods with the `Package.onUse` handler. This section lets you define what packages your package depends on, what packages are implied by your package, and what object your package is exported to. > Choose Meteor versions carefully. First determine the minimum version of Meteor you need for the API you use in your package. This should be based on specific needs of your package like needed the *Async calls, which would require minimum version to be at least 2.8. Another example are where packages had a major version bump, for example this has happened with the accounts packages in Meteor 2.3. If you want to be backward and forward compatible it is good to include Meteor version before 2.3 and then 2.3.6 in the array. A general recommendation for most compatibility for accounts packages (unless you need API that was affected in Meteor 2.3) is to have the following array in `versionsFrom`: `['1.12.1', '2.3.6', '2.8.1']`, this gives us the widest range. For general packages you can leave out version `2.3.6`. If you want the widest compatibility range it is recommended that the lowest be `1.12.1` and that you also include another version near the current version of Meteor. Set up your tests with the `Package.onTest` handler, which has an interface that's parallel to that of the `onUse` handler. The tests will need to depend on the package that you have just created. For example, if your package is the `email` package, you have to call `api.use('email')` in order to test the package. If you used `meteor create` to set up your package, Meteor will create the required scaffolding in `package.js`, and you'll only need to add unit test code in the `_test.js` file that was created. Meteor packages can include NPM packages and Cordova plugins by using `Npm.depends` and `Cordova.depends` in the `package.js` file. ## Options In some cases we need to offer options in packages where these options are not going to change in runtime. We prefer to have these options defined in a configuration file instead of using JS code to call specific functions to define options in runtime. For example, in `quave:collections` package you can force collections to be available only in the server like this: ```json "packages": { "quave:collections": { "isServerOnly": true } } ``` We encourage every package author to follow this standard to offer options: 1. Use the official Meteor `settings` file 2. Inside the `settings` file read from a `Meteor`.`packages`.``.`` > If it needs to be available in the client follow the same structure inside the `public` key. You can use [quave:settings](https://github.com/quavedev/settings) package to read options in the format above already merging the private and public options. This way we avoid having to call a specific code before another specific code in a package as the setting is stored in the settings, and the package can load it when necessary instead of relying on a specific order of calls from the developer in the app code. > We've started to adopt this standard also in core packages on Meteor 1.10.2. ## Build Plugins API {#build-plugin-api} Meteor packages can provide build plugins - programs that integrate with the build tool Isobuild used to compile and bundle your application. Starting with Meteor 1.2, the API used to plug into the build process is called "Build Plugins". There are 3 phases when a package's plugin can run: linting, compilation and minification. Here is an overview of operations Isobuild performs on the application and packages source: 1. Gather source files from the app folder or read `package.js` file for a package. 2. Lint all source files and print the linting warnings. 3. Compile the source files like CoffeeScript, ES2015, Less, or Templates to plain JavaScript and CSS. 4. Link the JavaScript files: wrap them into closures and provide necessary package imports. 5. Minify JavaScript and CSS files. Can also include concatenation of all files. Build plugins fill the phases 2, 3 and 5. Usually build plugins implement a class that is given a list of files to process. Commonly, such files have the following methods: - `getContentsAsBuffer` - Returns the full contents of the file as a buffer. - `getContentsAsString` - Returns the full contents of the file as a string. - `getPackageName` - Returns the name of the package or `null` if the file is not in a package. - `getPathInPackage` - Returns the relative path of file to the package or app root directory. The returned path always uses forward slashes. - `getSourceHash` - Returns a hash string for the file that can be used to implement caching. - `getArch` - Returns the architecture that is targeted while processing this file. - `getBasename` - Returns the filename of the file. - `getDirname` - Returns the directory path relative to the package or app root. The returned path always uses forward slashes. - `error` - Call this method to raise a compilation or linting error for the file. ## Linters {#build-plugin-linters} Linters are programs that check the code for undeclared variables or find code that doesn't correspond to certain style guidelines. Some of the popular examples of linters are [JSHint](http://jshint.com/about/) and [ESLint](http://eslint.org/). Some of the non-JavaScript linter examples include [CoffeeLint](https://github.com/clutchski/coffeelint) for CoffeeScript and [CSSLint](http://csslint.net/) for CSS. To register a linter build plugin in your package, you need to do a couple of things in your `package.js`: - depend on the `isobuild:linter-plugin@1.0.0` package - register a build plugin: `Package.registerBuildPlugin({ name, sources, ... });` (see [docs](#PackageNamespace-registerBuildPlugin)) In your build plugin sources, register a Linter Plugin: provide details such as a name, list of extensions and filenames the plugin will handle and a factory function that returns an instance of a linter class. Example: ```js Plugin.registerLinter({ extensions: ['js'], filenames: ['.linterrc'] }, () => new MyLinter); ``` In this example, we register a linter that runs on all `js` files and also reads a file named `.linterrc` to get a configuration. The `MyLinter` class should now implement the `processFilesForPackage` method. The method should accept two arguments: a list of files and an options object. ```js class MyLinter { processFilesForPackage(files, options) { files.forEach((file) => { // Lint the file. const lint = lintFile(file.getContentsAsString()); if (lint) { // If there are linting errors, output them. const { message, line, column } = lint; file.error({ message, line, column }); } }); } } ``` The globals are passed in the options object so that the linters can omit the warnings about the package imports that look like global variables. Each file in the list is an object that has all the methods provided by all build plugins, described above. See an example of a linting plugin implemented in Core: [jshint](https://github.com/meteor/meteor/tree/devel/packages/jshint). ## Compilers {#build-plugin-compilers} Compilers are programs that take the source files and output JavaScript or CSS. They also can output parts of HTML that is added to the `` tag and static assets. Examples for the compiler plugins are: CoffeeScript, Babel.js, JSX compilers, Pug templating compiler and others. To register a compiler plugin in your package, you need to do the following in your `package.js` file: - depend on the `isobuild:compiler-plugin@1.0.0` package - register a build plugin: `Package.registerBuildPlugin({ name, sources, ... });` (see [docs](#PackageNamespace-registerBuildPlugin)) In your build plugin source, register a Compiler Plugin: similar to other types of build plugins, provide the details, extensions and filenames and a factory function that returns an instance of the compiler. Ex.: ```js Plugin.registerCompiler({ extensions: ['pug', 'tpl.pug'], filenames: [] }, () => new PugCompiler); ``` The compiler class must implement the `processFilesForTarget` method that is given the source files for a target (server or client part of the package/app). ```js class PugCompiler { processFilesForTarget(files) { files.forEach((file) => { // Process and add the output. const output = compilePug(file.getContentsAsString()); file.addJavaScript({ data: output, path: `${file.getPathInPackage()}.js` }); }); } } ``` Besides the common methods available on the input files' class, the following methods are available: - `getExtension` - Returns the extension that matched the compiler plugin. The longest prefix is preferred. - `getDeclaredExports` - Returns a list of symbols declared as exports in this target. The result of `api.export('symbol')` calls in target's control file such as package.js. - `getDisplayPath` Returns a relative path that can be used to form error messages or other display properties. Can be used as an input to a source map. - `addStylesheet` - Web targets only. Add a stylesheet to the document. Not available for linter build plugins. - `addJavaScript` - Add JavaScript code. The code added will only see the namespaces imported by this package as runtime dependencies using ['api.use'](#PackageAPI-use). If the file being compiled was added with the bare flag, the resulting JavaScript won't be wrapped in a closure. - `addAsset` - Add a file to serve as-is to the browser or to include on the browser, depending on the target. On the web, it will be served at the exact path requested. For server targets, it can be retrieved using `Assets.getTextAsync` or `Assets.getBinaryAsync`. - `addHtml` - Works in web targets only. Add markup to the `head` or `body` section of the document. - `hmrAvailable` - Returns true if the file can be updated with HMR. Among other things, it checks if HMR supports the current architecture and build mode, and that the unibuild uses the `hot-module-replacement` package. There are rare situations where `hmrAvailable` returns true, but when more information is available later in the build process Meteor decides the file can not be updated with HMR. - `readAndWatchFileWithHash` - Accepts an absolute path, and returns { contents, hash } Makes sure Meteor watches the file so any changes to it will trigger a rebuild Meteor implements a couple of compilers as Core packages, good examples would be the [Blaze templating](https://github.com/meteor/meteor/tree/devel/packages/templating) package and the [ecmascript](https://github.com/meteor/meteor/tree/devel/packages/ecmascript) package (compiles ES2015+ to JavaScript that can run in the browsers). ## Minifiers {#build-plugin-minifiers} Minifiers run last after the sources has been compiled and JavaScript code has been linked. Minifiers are only ran for the client programs (`web.browser` and `web.cordova`). There are two types of minifiers one can add: a minifier processing JavaScript (registered extensions: `['js']`) and a minifier processing CSS (extensions: `['css']`). To register a minifier plugin in your package, add the following in your `package.js` file: - depend on `isobuild:minifier-plugin@1.0.0` package - register a build plugin: `Package.registerBuildPlugin({ name, sources, ... });` (see [docs](#PackageNamespace-registerBuildPlugin)) In your build plugin source, register a Minifier Plugin. Similar to Linter and Compiler plugin, specify the interested extensions (`css` or `js`). The factory function returns an instance of the minifier class. ```js Plugin.registerMinifier({ extensions: ['js'] }, () => new UglifyJsMinifier); ``` The minifier class must implement the method `processFilesForBundle`. The first argument is a list of processed files and the options object specifies if the minifier is ran in production mode or development mode. ::: info This method can be asynchronous. If it returns a Promise, the build process will wait for it to resolve before continuing. ::: ```js class UglifyJsMinifier { processFilesForBundle(files, options) { const { minifyMode } = options; if (minifyMode === 'development') { // Don't minify in development. file.forEach((file) => { file.addJavaScript({ data: file.getContentsAsBuffer(), sourceMap: file.getSourceMap(), path: file.getPathInBundle() }); }); return; } // Minify in production. files.forEach((file) => { file.addJavaScript({ data: uglifyjs.minify(file.getContentsAsBuffer()), path: file.getPathInBundle() }); }); } } ``` In this example, we re-add the same files in the development mode to avoid unnecessary work and then we minify the files in production mode. Besides the common input files' methods, these methods are available: - `getPathInBundle` - returns a path of the processed file in the bundle. - `getSourcePath` - returns absolute path of the input file if available, or null. - `getSourceMap` - returns the source-map for the processed file if there is such. - `addJavaScript` - same as compilers - `addStylesheet` - same as compilers - `readAndWatchFileWithHash` - only available for css minifiers. Same as compilers. Right now, Meteor Core ships with the `standard-minifiers` package that can be replaced with a custom one. The [source](https://github.com/meteor/meteor/tree/devel/packages/standard-minifiers) of the package is a good example how to build your own minification plugin. In development builds, minifiers must meet these requirements to not prevent hot module replacement: - Call `addJavasScript` once for each file to add the file's contents - The contents of the files are not modified In the future Meteor will allow minifiers to concatenate or modify files in development without affected hot module replacement. ## Caching {#build-plugin-caching} Since the API allows build plugins to process multiple files at once, we encourage package authors to implement at least some in-memory caching for their plugins. Using the `getSourceHash` function for linters and compilers will allow quick incremental recompilations if the file is not reprocessed even when the contents didn't change. For the fast rebuilds between the Isobuild process runs, plugins can implement on-disk caching. If a plugin implements the `setDiskCacheDirectory` method, it will be called from time to time with a new path on disk where the plugin can write its offline cache. The folder is correctly reset when the plugin is rebuilt or cache should be invalidated for any reason (for example, picked package versions set has changed). ### Caching Compiler {#build-plugin-caching-compiler} There is a core package called `caching-compiler` that implements most of the common logic of keeping both in-memory and on-disk caches. The easiest way to implement caching correctly is to subclass the `CachingCompiler` or `MultiFileCachingCompiler` class from this package in your build plugin. `CachingCompiler` is for compilers that consider each file completely independently; `MultiFileCachingCompiler` is for compilers that allow files to reference each other. To get this class in your plugin namespace, add a dependency to the plugin definition: ```js Package.registerBuildPlugin({ name: 'compileGG', use: ['caching-compiler@1.0.0'], sources: ['plugin/compile-gg.js'] }); ``` ## Accessing File System {#build-plugin-file-system} Since the build plugins run as part of the Meteor tool, they follow the same file-system access convention - all file system paths always look like a Unix path: using forward slashes and having a root at '/', even on Windows. For example: paths `/usr/bin/program` and `/C/Program Files/Program/program.exe` are valid paths, and `C:\Program Files\Program\program.exe` is not. So whenever you get a path in your build plugin implementation, via `getPathInPackage` or in an argument of the `setDiskCacheDirectory` method, the path will be a Unix path. Now, on running on Windows, the usual node modules `fs` and `path` expect to get a DOS path. To assist you to write correct code, the `Plugin` symbol provides its own versions of `fs` and `path` that you can use instead (note that all methods on `fs` are fiberized and sync versions prefer using Fibers rather than freezing the whole event loop). Also `Plugin` provides helper functions `convertToStandardPath` and `convertToOSPath` to convert to a Unix path or to the path expected by the node libraries regardless of the path origin. Example: ```js // On Windows const fs = Plugin.fs; const path = Plugin.path; const filePath = path.join('/C/Program Files', 'Program/file.txt'); console.log(filePath); // Prints '/C/Program Files/Program/file.txt' fs.writeFileSync(filePath, 'Hello.'); // Writes to 'C:\Program Files\Program\file.txt' console.log(Plugin.convertToOsPath(filePath)); // Prints 'C:\Program Files\Program\file.txt' ``` ## Isobuild Feature Packages {#isobuild-features} Starting with Meteor 1.2, packages can declare that they need a version of the Meteor tool whose Isobuild build system supports a certain feature. For example, packages must write `api.use('isobuild:compiler-plugin@1.0.0')` in order to call `Plugin.registerCompiler`. This means that a package can transition from the old `registerSourceHandler` API to `registerCompiler` and Version Solver will properly prevent the `registerCompiler` version from being chosen by older tools that don't know how to handle it. This is the known Isobuild feature "packages" sorted by the first release of Meteor which supports them. ### Introduced in Meteor 1.2s {#isobuild-features-1.2} - `compiler-plugin@1.0.0`: Allows use of `Plugin.registerCompiler`. - `linter-plugin@1.0.0`: Allows use of `Plugin.registerLinter`. - `minifier-plugin@1.0.0`: Allows use of `Plugin.registerMinifier`. - `isopack-2@1.0.0`: This package is published only in `isopack-2` format and won't work in versions of Meteor that don't support that format. - `prod-only@1.0.0`: Allows use of the `prodOnly` flag in `Package.describe`. - `isobuild:cordova@5.4.0`: This package depends on a specific version of Cordova, most likely as a result of the Cordova plugins it depends on. --- --- url: /packages/accounts-passwordless.md --- # Passwordless Passwordless package allows you to create a login for users without the need for user to provide password. Upon registering or login an email is sent to the user's email with a code to enter to confirm login and a link to login directly. Since the user is responding to the email it will also verify the email. The first step to in the passwordless process is for the user to sign-up or request a token to their email address. You can do that with the following: If the user is signing up you can pass in the `userData` object like in [Accounts.createUser](/api/accounts#Accounts-createUser). The second step in the passwordless flow. Like all the other `loginWith` functions call this method to login the user with the token they have inputted. Use this function if you want to manually send the email to users to login with token from the server. Do note that you will need to create the token/sequence and save it in the DB yourself. This is good if you want to change how the tokens look or are generated, but unless you are sure of what you are doing we don't recommend it.

Settings Options

You can use the function `Accounts.config` in the server to change some settings on this package: - **tokenSequenceLength**: use `Accounts.config({tokenSequenceLength: _Number_})` to the size of the token sequence generated. The default is 6. - **loginTokenExpirationHours**: use `Accounts.config({loginTokenExpirationHours: _Number_})` to set the amount of time a token sent is valid. As it's just a number, you can use, for example, 0.5 to make the token valid for just half hour. The default is 1 hour.

E-mail templates

`accounts-passwordless` brings new templates that you can edit to change the look of emails which send code to users. The email template is named `sendLoginToken` and beside `user` and `url`, the templates also receive a data object with `sequence` which is the user's code. ```javascript sendLoginToken: { text: (user, url, { sequence }) => { /* text template */ }; } ```

Enable 2FA for this package

You can add 2FA to your login flow by using the package [accounts-2fa](./accounts-2fa.md). You can find an example showing how this would look like [here](./accounts-2fa.md#working-with-accounts-passwordless). --- --- url: /community-packages/pub-sub.md --- # Pub-sub - `Who maintains the package` – [Jam](https://github.com/jamauro) [[toc]] ## What is this package? `jam:pub-sub` brings three key features to Meteor apps: 1. Method-based publish / subscribe 2. Change Streams-based publish / subscribe 3. Subscription caching > **Important**: This package expects that you'll use the promise-based `*Async` Meteor collection methods introduced in `v2.8.1`. ## Method-based publish / subscribe Meteor's traditional `publish / subscribe` is truly wonderful. However, there is a cost to pushing updates reactively to all connected clients – it's resource intensive and will eventually place limits on your ability to scale your app. One way to reduce the need for the traditional `publish / subscribe` is to fetch the data via a `Meteor Method` but there's a big problem here: the data **won't** be automatically merged into Minimongo and you completely lose Meteor's magical reactivity. Minimongo is great to work with and makes things easy as the source of truth on the client. Without it, you'll need to create your own stores on the client and essentially duplicate Minimongo. With `jam:pub-sub`, you use `Meteor.publish.once` and the same `Meteor.subscribe` to have the data fetched via a Meteor Method and merged automatically in Minimongo so you can work with it as you're accustomed to. It also automatically preserves reactivity for the user when they make database writes. Note that these writes will **not** be broadcast in realtime to all connected clients by design but in many cases you might find that you don't need that feature of Meteor's traditional `publish / subscribe`. ## How to download it? Add the package to your app ```bash meteor add jam:pub-sub ``` ### Sources * [GitHub repository](https://github.com/jamauro/pub-sub) ## How to use it? ## Change Streams-based publish / subscribe **`Alpha`** With `jam:pub-sub` and MongoDB Change Streams, you can preserve Meteor's magical reactivity for all clients while opting out of the traditional `publish / subscribe` and its use of the `oplog`. Use `Meteor.publish.stream` instead of using `Meteor.publish` and subscribe using the same `Meteor.subscribe` on the client. **Important**: Change Streams will work best when the filter you use can be shared. To that end, if you have a publication that includes a `userId`, this package will filter out that condition when setting up the Change Stream because it will result in too many unique change streams. As an example, lets say you have this publication: ```js Meteor.publish.stream('todos', function() { return Todos.find({ $or: [ { isPrivate: false }, { owner: this.userId } ] }); }); ``` When this publication is invoked, it will pull all the `Todos` that match the filter above and then begin watching a Change Stream with this filter: ```js { isPrivate: false } ``` For this particular filter, it should behave as you'd expect so you wouldn't need to make changes. However, if you have complex filters involving the `userId`, you'll need to be sure that the behavior you expect remains when using `.stream`. If it's not meeting your needs, you could split the one publication into two publications, using a `.stream` with a filter than can be shared and a `.once` for the `userId`: ```js Meteor.publish.stream('todos.public', function() { return Todos.find({ isPrivate: false }); }); Meteor.publish.once('todos.owned', function() { return Todos.find({ owner: this.userId }); }); ``` The downside by splitting into two is it could result in over-fetching but the data will be merged correctly into Minimongo. **Note**: In most cases, you'd likely benefit the most from using `Meteor.publish.once` anywhere you can and using `Meteor.publish.stream` only when you really need it and with a filter than can be shared. **Note**: If you decide to entirely opt-out of using the traditional `Meteor.publish`, then you'll also want to disable the `oplog` entirely — add the `disable-oplog` package with `meteor add disable-oplog`. At the moment, this feature is considered in an `alpha` state. Based on previous [Change Streams experiments](https://github.com/meteor/meteor/discussions/11842#discussioncomment-4061112) by the Meteor Community, it seems that using Change Streams as a wholesale replacement for the traditional `publish / subscribe` could "just work". However, in practice it may be a "Your Mileage May Vary" type of situation depending on the frequency of writes, number of connected clients, how you model your data, and how you set up the cursors inside of `Meteor.publish.stream`. With that said, if you're interested in this feature, I'd encourage you to try it out and share your findings. ## Subscription caching Normally, when a user moves between routes or components, the subscriptions will be stopped. When a user is navigating back and forth in your app, each time will result in a re-subscribe which means more spinners, a slower experience, and is generally a waste. By caching your subscriptions, you can create a better user experience for your users. Since the subscription itself is being cached, the data in Minimongo will be updated in the background until the `cacheDuration` expires for that subscription at which point it will be stopped and the data will be removed from Minimongo as expected. ## Usage ### Add the package to your app `meteor add jam:pub-sub` ### Define a Method-based publication Define a publication using `Meteor.publish.once` and subscribe just as you do currently. `Meteor.publish.once` expects you to return a cursor or an array of cursors just like `Meteor.publish`. ```js // server Meteor.publish.once('notes.all', function() { return Notes.find(); }); ``` ```js // client // Since each view layer (Blaze, React, Svelte, Vue, etc) has a different way of using `Tracker.autorun`, I've omitted it for brevity. You'd subscribe just as you do currently in your view layer of choice. Meteor.subscribe('notes.all') // work with the Notes collection in Minimongo as you're accustomed to Notes.find().fetch(); ``` That's it. By using `Meteor.publish.once`, it will fetch the data initally and automatically merge it into Minimongo. Any database writes to the `Notes` collection will be sent reactively to the user that made the write. > **Important**: when naming your publications be sure to include the collection name(s) in it. This is generally common practice and this package relies on that convention. If you don't do this and you're caching the subscription, Minimongo data may be unexpectedly removed or retained when the subscription stops. It's recommended that you follow this convention for all publications including `Meteor.publish`. Here are some examples of including the collection name in the publication name: ```js // the name you assign inside Mongo.Collection should be in your publication name(s), in this example 'notes' const Notes = new Mongo.Collection('notes') // as long as it appears somewhere in your publication name, you're good to go. here are some examples: Meteor.publish.once('notes'); Meteor.publish.once('notes.all'); Meteor.publish.once('notes/single'); Meteor.publish.once('somethingAndNotes'); ``` It also works just as you'd expect for an array of cursors: ```js // server Meteor.publish.once('notes.todos.all', function() { return [Notes.find(), Todos.find()]; }); ``` ```js // client Meteor.subscribe('notes.todos.all'); // work with the Notes collection in Minimongo as you're accustomed to Notes.find().fetch(); // work with the Todos collection in Minimongo as you're accusomted to Todos.find().fetch(); ``` Inside `Meteor.publish.once`, `this.userId` and [this.added](https://docs.meteor.com/api/pubsub.html#Subscription-added) can still be used. The added document will be included in the final result data. The rest of the low-level `publish` API will be disregarded, as they no longer fit into the context of a Method-based data fetch. ```js Meteor.publish.once('notes.all', function() { // ... // const userId = this.userId; this.added('notes', _id, fields); // ... // return Notes.find(); }) ``` ### Define a Change Streams-based publication Define a publication using `Meteor.publish.stream` and subscribe just as you do currently. `Meteor.publish.stream` expects you to return a cursor or an array of cursors just like `Meteor.publish`. ```js // server Meteor.publish.stream('notes.all', function() { return Notes.find(); }); ``` ```js // client // Since each view layer (Blaze, React, Svelte, Vue, etc) has a different way of using `Tracker.autorun`, I've omitted it for brevity. You'd subscribe just as you do currently in your view layer of choice. Meteor.subscribe('notes.all') // work with the Notes collection in Minimongo as you're accustomed to Notes.find().fetch(); ``` That's it. By using `Meteor.publish.stream`, any database writes to the `Notes` collection will be sent reactively to **all** connected clients just as with `Meteor.publish`. #### Setting the `maxPoolSize` for Change Streams `maxPoolSize` defaults to `100` which may not need adjusting. If you need to adjust it, you can set it in [Meteor.settings](https://docs.meteor.com/api/collections.html#mongo_connection_options_settings) like this: ```js { //...// "packages": { "mongo": { "options": { "maxPoolSize": 200 // or whatever is appropriate for your application } } } // ... // } ``` ### Turn on subscription caching With `jam:pub-sub`, you can enable subscription caching globally or at a per-subscription level. Subscription caching is turned off by default to preserve the current behavior in Meteor. Any subscription can be cached, regardless of how it's published. To enable subscription caching globally for every subscription: ```js // put this in a file that's imported on the client at a minimum. it can be used isomorphically but the configuration only applies to the client. import { PubSub } from 'meteor/jam:pub-sub'; PubSub.configure({ cache: true // defaults to false }); ``` The global `cacheDuration` is set to `60 seconds` by default. This is from when the subscription was originally set to be stopped, i.e. when the component housing the subscription was destroyed because the user navigated away. If the user comes right back, then the cache will be used. If they don't, after `60 seconds`, the subscription cache will be removed. If you want to change the global `cacheDuration`, change it with a value in `seconds`: ```js import { PubSub } from 'meteor/jam:pub-sub'; PubSub.configure({ cacheDuration: 5 * 60 // sets the cacheDuration to 5 minutes. defaults to 1 min }); ``` You can also configure `cache` and `cacheDuration` for each individual subscription when you use `Meteor.subscribe`. For example: ```js Meteor.subscribe('todos.single', _id, { cacheDuration: 30 }) // caches for 30 seconds, overriding the global default Meteor.subscribe('notes.all', { cache: true }) // turns caching on, overriding the global default, and uses the global default cacheDuration ``` > **Note**: the rest of the [Meteor.subscribe](https://docs.meteor.com/api/pubsub.html#Meteor-subscribe) API (e.g. `onStop`, `onReady`) works just as you'd expect. > **Note**: Because the data will remain in Minimongo while the subscription is cached, you should be mindful of your Minimongo `.find` selectors. Be sure to use specific selectors to `.find` the data you need for that particular subscription. This is generally considered [best practice](https://guide.meteor.com/data-loading#fetching) so this is mainly a helpful reminder. #### Clearing the cache Each individual subcription will be automatically removed from the cache when its `cacheDuration` elapses. Though it shouldn't be necessary, you can programmatically clear all cached subscriptions: ```js import { PubSub } from 'meteor/jam:pub-sub'; PubSub.clearCache(); ``` --- --- url: /packages/random.md --- # Random The `random` package provides several functions for generating random numbers. It uses a cryptographically strong pseudorandom number generator when possible, but falls back to a weaker random number generator when cryptographically strong randomness is not available (on older browsers or on servers that don't have enough entropy to seed the cryptographically strong generator). --- --- url: /packages/react-meteor-data.md --- # react-meteor-data This package provides an integration between React and [`Tracker`](https://atmospherejs.com/meteor/tracker), Meteor's reactive data system. ## Table of Contents [[toc]] ## Install ::: tip This package is included with `meteor create` on react options. No need to install it manually. ::: To install the package, use `meteor add`: ```bash meteor add react-meteor-data ``` You'll also need to install `react` if you have not already: ```bash meteor npm install react ``` ### Changelog [check recent changes here](https://github.com/meteor/react-packages/blob/master/packages/react-meteor-data/CHANGELOG.md) ## Usage This package provides two ways to use Tracker reactive data in your React components: - a hook: `useTracker` (v2 only, requires React `^16.8`) - a higher-order component (HOC): `withTracker` (v1 and v2). The `useTracker` hook, introduced in version 2.0.0, embraces the [benefits of hooks](https://reactjs.org/docs/hooks-faq.html). Like all React hooks, it can only be used in function components, not in class components. The `withTracker` HOC can be used with all components, function or class based. It is not necessary to rewrite existing applications to use the `useTracker` hook instead of the existing `withTracker` HOC. ### `useTracker(reactiveFn)` You can use the `useTracker` hook to get the value of a Tracker reactive function in your React "function components." The reactive function will get re-run whenever its reactive inputs change, and the component will re-render with the new value. `useTracker` manages its own state, and causes re-renders when necessary. There is no need to call React state setters from inside your `reactiveFn`. Instead, return the values from your `reactiveFn` and assign those to variables directly. When the `reactiveFn` updates, the variables will be updated, and the React component will re-render. Arguments: - `reactiveFn`: A Tracker reactive function (receives the current computation). The basic way to use `useTracker` is to simply pass it a reactive function, with no further fuss. This is the preferred configuration in many cases. #### `useTracker(reactiveFn, deps)` You can pass an optional deps array as a second value. When provided, the computation will be retained, and reactive updates after the first run will run asynchronously from the react render execution frame. This array typically includes all variables from the outer scope "captured" in the closure passed as the 1st argument. For example, the value of a prop used in a subscription or a minimongo query; see example below. This should be considered a low level optimization step for cases where your computations are somewhat long running - like a complex minimongo query. In many cases it's safe and even preferred to omit deps and allow the computation to run synchronously with render. Arguments: - `reactiveFn` - `deps`: An optional array of "dependencies" of the reactive function. This is very similar to how the `deps` argument for [React's built-in `useEffect`, `useCallback` or `useMemo` hooks](https://reactjs.org/docs/hooks-reference.html) work. ```jsx import { useTracker } from 'meteor/react-meteor-data'; // React function component. function Foo({ listId }) { // This computation uses no value from the outer scope, // and thus does not needs to pass a 'deps' argument. // However, we can optimize the use of the computation // by providing an empty deps array. With it, the // computation will be retained instead of torn down and // rebuilt on every render. useTracker will produce the // same results either way. const currentUser = useTracker(() => Meteor.user(), []); // The following two computations both depend on the // listId prop. When deps are specified, the computation // will be retained. const listLoading = useTracker(() => { // Note that this subscription will get cleaned up // when your component is unmounted or deps change. const handle = Meteor.subscribe('todoList', listId); return !handle.ready(); }, [listId]); const tasks = useTracker(() => Tasks.find({ listId }).fetch(), [listId]); return (

Hello {currentUser.username}

{listLoading ? (
Loading
) : (
Here is the Todo list {listId}:
    {tasks.map(task => (
  • {task.label}
  • ))}
)} ); } ``` **Note:** the [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks) package provides ESLint hints to help detect missing values in the `deps` argument of React built-in hooks. It can be configured to also validate the `deps` argument of the `useTracker` hook or some other hooks, with the following `eslintrc` config: ```json "react-hooks/exhaustive-deps": ["warn", { "additionalHooks": "useTracker|useSomeOtherHook|..." }] ``` #### `useTracker(reactiveFn, deps, skipUpdate)` or `useTracker(reactiveFn, skipUpdate)` You may optionally pass a function as a second or third argument. The `skipUpdate` function can evaluate the return value of `reactiveFn` for changes, and control re-renders in sensitive cases. _Note:_ This is not meant to be used with a deep compare (even fast-deep-equals), as in many cases that may actually lead to worse performance than allowing React to do it's thing. But as an example, you could use this to compare an `updatedAt` field between updates, or a subset of specific fields, if you aren't using the entire document in a subscription. As always with any optimization, measure first, then optimize second. Make sure you really need this before implementing it. Arguments: - `reactiveFn` - `deps?` - optional - you may omit this, or pass a "falsy" value. - `skipUpdate` - A function which receives two arguments: `(prev, next) => (prev === next)`. `prev` and `next` will match the type or data shape as that returned by `reactiveFn`. Note: A return value of `true` means the update will be "skipped". `false` means re-render will occur as normal. So the function should be looking for equivalence. ```jsx import { useTracker } from 'meteor/react-meteor-data'; // React function component. function Foo({ listId }) { const tasks = useTracker( () => Tasks.find({ listId }).fetch(), [listId], (prev, next) => { // prev and next will match the type returned by the reactiveFn return prev.every((doc, i) => ( doc._id === next[i] && doc.updatedAt === next[i] )) && prev.length === next.length; } ); return (

Hello {currentUser.username}

Here is the Todo list {listId}:
    {tasks.map(task => (
  • {task.label}
  • ))}
); } ``` ### `withTracker(reactiveFn)` You can use the `withTracker` HOC to wrap your components and pass them additional props values from a Tracker reactive function. The reactive function will get re-run whenever its reactive inputs change, and the wrapped component will re-render with the new values for the additional props. Arguments: - `reactiveFn`: a Tracker reactive function, getting the props as a parameter, and returning an object of additional props to pass to the wrapped component. ```jsx import { withTracker } from 'meteor/react-meteor-data'; // React component (function or class). function Foo({ listId, currentUser, listLoading, tasks }) { return (

Hello {currentUser.username}

{listLoading ?
Loading
:
Here is the Todo list {listId}:
    {tasks.map(task =>
  • {task.label}
  • )}
{ // Do all your reactive data access in this function. // Note that this subscription will get cleaned up when your component is unmounted const handle = Meteor.subscribe('todoList', listId); return { currentUser: Meteor.user(), listLoading: !handle.ready(), tasks: Tasks.find({ listId }).fetch(), }; })(Foo); ``` The returned component will, when rendered, render `Foo` (the "lower-order" component) with its provided props in addition to the result of the reactive function. So `Foo` will receive `{ listId }` (provided by its parent) as well as `{ currentUser, listLoading, tasks }` (added by the `withTracker` HOC). For more information, see the [React article](http://guide.meteor.com/react.html) in the Meteor Guide. ### `withTracker({ reactiveFn, pure, skipUpdate })` The `withTracker` HOC can receive a config object instead of a simple reactive function. - `getMeteorData` - The `reactiveFn`. - `pure` - `true` by default. Causes the resulting Container to be wrapped with React's `memo()`. - `skipUpdate` - A function which receives two arguments: `(prev, next) => (prev === next)`. `prev` and `next` will match the type or data shape as that returned by `reactiveFn`. Note: A return value of `true` means the update will be "skipped". `false` means re-render will occur as normal. So the function should be looking for equivalence. ```jsx import { withTracker } from 'meteor/react-meteor-data'; // React component (function or class). function Foo({ listId, currentUser, listLoading, tasks }) { return (

Hello {currentUser.username}

{listLoading ?
Loading
:
Here is the Todo list {listId}:
    {tasks.map(task =>
  • {task.label}
  • )}
( doc._id === next[i] && doc.updatedAt === next[i] )) && prev.tasks.length === next.tasks.length ); } })(Foo); ``` ### `useSubscribe(subName, ...args)` `useSubscribe` is a convenient short hand for setting up a subscription. It is particularly useful when working with `useFind`, which should NOT be used for setting up subscriptions. At its core, it is a very simple wrapper around `useTracker` (with no deps) to create the subscription in a safe way, and allows you to avoid some of the ceremony around defining a factory and defining deps. Just pass the name of your subscription, and your arguments. `useSubscribe` returns an `isLoading` function. You can call `isLoading()` to react to changes in the subscription's loading state. The `isLoading` function will both return the loading state of the subscription, and set up a reactivity for the loading state change. If you don't call this function, no re-render will occur when the loading state changes. ```jsx // Note: isLoading is a function! const isLoading = useSubscribe("posts", groupId); const posts = useFind(() => Posts.find({ groupId }), [groupId]); if (isLoading()) { return ; } else { return (
    {posts.map((post) => (
  • {post.title}
  • ))}
); } ``` If you want to conditionally subscribe, you can set the `name` field (the first argument) to a falsy value to bypass the subscription. ```jsx const needsData = false; const isLoading = useSubscribe(needsData ? "my-pub" : null); // When a subscription is not used, isLoading() will always return false ``` ### `useFind(cursorFactory, deps)` The `useFind` hook can substantially speed up the rendering (and rerendering) of lists coming from mongo queries (subscriptions). It does this by controlling document object references. By providing a highly tailored cursor management within the hook, using the `Cursor.observe` API, `useFind` carefully updates only the object references changed during a DDP update. This approach allows a tighter use of core React tools and philosophies to turbo charge your list renders. It is a very different approach from the more general purpose `useTracker`, and it requires a bit more set up. A notable difference is that you should NOT call `.fetch()`. `useFind` requires its factory to return a `Mongo.Cursor` object. You may also return `null`, if you want to conditionally set up the Cursor. Here is an example in code: ```jsx import React, { memo } from "react"; import { useFind } from "meteor/react-meteor-data"; import TestDocs from "/imports/api/collections/TestDocs"; // Memoize the list item const ListItem = memo(({ doc }) => { return (
  • {doc.id},{doc.updated}
  • ); }); const Test = () => { const docs = useFind(() => TestDocs.find(), []); return (
      {docs.map((doc) => ( ))}
    ); }; // Later on, update a single document - notice only that single component is updated in the DOM TestDocs.update({ id: 2 }, { $inc: { someProp: 1 } }); ``` If you want to conditionally call the find method based on some props configuration or anything else, return `null` from the factory. ```jsx const docs = useFind(() => { if (props.skip) { return null; } return TestDocs.find(); }, []); ``` ### Concurrent Mode, Suspense and Error Boundaries There are some additional considerations to keep in mind when using Concurrent Mode, Suspense and Error Boundaries, as each of these can cause React to cancel and discard (toss) a render, including the result of the first run of your reactive function. One of the things React developers often stress is that we should not create "side-effects" directly in the render method or in functional components. There are a number of good reasons for this, including allowing the React runtime to cancel renders. Limiting the use of side-effects allows features such as concurrent mode, suspense and error boundaries to work deterministically, without leaking memory or creating rogue processes. Care should be taken to avoid side effects in your reactive function for these reasons. (Note: this caution does not apply to Meteor specific side-effects like subscriptions, since those will be automatically cleaned up when `useTracker`'s computation is disposed.) Ideally, side-effects such as creating a Meteor computation would be done in `useEffect`. However, this is problematic for Meteor, which mixes an initial data query with setting up the computation to watch those data sources all in one initial run. If we wait to do that in `useEffect`, we'll end up rendering a minimum of 2 times (and using hacks for the first one) for every component which uses `useTracker` or `withTracker`, or not running at all in the initial render and still requiring a minimum of 2 renders, and complicating the API. To work around this and keep things running fast, we are creating the computation in the render method directly, and doing a number of checks later in `useEffect` to make sure we keep that computation fresh and everything up to date, while also making sure to clean things up if we detect the render has been tossed. For the most part, this should all be transparent. The important thing to understand is that your reactive function can be initially called more than once for a single render, because sometimes the work will be tossed. Additionally, `useTracker` will not call your reactive function reactively until the render is committed (until `useEffect` runs). If you have a particularly fast changing data source, this is worth understanding. With this very short possible suspension, there are checks in place to make sure the eventual result is always up to date with the current state of the reactive function. Once the render is "committed", and the component mounted, the computation is kept running, and everything will run as expected. ## Suspendable version of hooks ### `useTracker` This is a version of `useTracker` that can be used with React Suspense. For its first argument, a key is necessary, witch is used to identify the computation and to avoid recreating it when the component is re-rendered. Its second argument is a function that can be async and reactive, this argument works similar to the original `useTracker` that does not suspend. For its _optional_ third argument, the dependency array, works similar to the `useTracker` that does not suspend, you pass in an array of variables that this tracking function depends upon. For its _optional_ fourth argument, the options object, works similar to the `useTracker` that does not suspend, you pass in a function for when should skip the update. ```jsx import { useTracker } from "meteor/react-meteor-data/suspense"; import { useSubscribe } from "meteor/react-meteor-data/suspense"; function Tasks() { // this component will suspend useSubscribe("tasks"); const { username } = useTracker("user", () => Meteor.user()); // Meteor.user() is async meteor 3.0 const tasksByUser = useTracker( "tasksByUser", () => TasksCollection.find( { username }, { sort: { createdAt: -1 } } ).fetchAsync() // async call ); // render the tasks } ``` ### Maintaining the reactive context To maintain a reactive context using the new Meteor Async methods, we are using the new `Tracker.withComputation` API to maintain the reactive context of an async call, this is needed because otherwise it would be only called once, and the computation would never run again, this way, every time we have a new Link being added, this useTracker is ran. ```jsx // needs Tracker.withComputation because otherwise it would be only called once, and the computation would never run again const docs = useTracker("name", async (c) => { const placeholders = await fetch( "https://jsonplaceholder.typicode.com/todos" ).then((x) => x.json()); console.log(placeholders); return await Tracker.withComputation(c, () => LinksCollection.find().fetchAsync() ); }); ``` A rule of thumb is that if you are using a reactive function for example `find` + `fetchAsync`, it is nice to wrap it inside `Tracker.withComputation` to make sure that the computation is kept alive, if you are just calling that function that is not necessary, like the one bellow, will be always reactive. ```jsx const docs = useTracker("name", () => LinksCollection.find().fetchAsync()); ``` ### `useSubscribe` This is a version of `useSubscribe` that can be used with React Suspense. It is similar to `useSubscribe`, it throws a promise and suspends the rendering until the promise is resolved. It does not return a Meteor Handle to control the subscription ```jsx import { useTracker } from "meteor/react-meteor-data/suspense"; import { useSubscribe } from "meteor/react-meteor-data/suspense"; function Tasks() { // this component will suspend useSubscribe("tasks"); const { username } = useTracker("user", () => Meteor.user()); // Meteor.user() is async meteor 3.0 const tasksByUser = useTracker( "tasksByUser", () => TasksCollection.find( { username }, { sort: { createdAt: -1 } } ).fetchAsync() // async call ); // render the tasks } ``` ### `useFind` This is a version of `useFind` that can be used with React Suspense. It has a few differences from the `useFind` without suspense, it throws a promise and suspends the rendering until the promise is resolved. It returns the result and it is reactive. You should pass as the first parameter the collection where is being searched upon and as the second parameter an array with the arguments, the same arguments that you would pass to the `find` method of the collection, third parameter is optional, and it is dependency array object. It's meant for the SSR, you don't have to use it if you're not interested in SSR. ```jsx import { useFind } from "meteor/react-meteor-data/suspense"; import { useSubscribe } from "meteor/react-meteor-data/suspense"; function Tasks() { // this component will suspend useSubscribe("tasks"); const tasksByUser = useFind(TasksCollection, [ {}, { sort: { createdAt: -1 } }, ]); // render the tasks } ``` ## Version compatibility notes - `react-meteor-data` v2.x : - `useTracker` hook + `withTracker` HOC - Requires React `^16.8`. - Implementation is compatible with "React Suspense", concurrent mode and error boundaries. - The `withTracker` HOC is strictly backwards-compatible with the one provided in v1.x, the major version number is only motivated by the bump of React version requirement. Provided a compatible React version, existing Meteor apps leveraging the `withTracker` HOC can freely upgrade from v1.x to v2.x, and gain compatibility with future React versions. - The previously deprecated `createContainer` has been removed. - `react-meteor-data` v0.x : - `withTracker` HOC (+ `createContainer`, kept for backwards compatibility with early v0.x releases) - Requires React `^15.3` or `^16.0`. - Implementation relies on React lifecycle methods (`componentWillMount` / `componentWillUpdate`) that are [marked for deprecation in future React versions](https://reactjs.org/blog/2018/03/29/react-v-16-3.html#component-lifecycle-changes) ("React Suspense"). --- --- url: /api/ReactiveDict.md --- # Reactive Dict A ReactiveDict stores an arbitrary set of key-value pairs. Use it to manage internal state in your components, ie. like the currently selected item in a list. Each key is individully reactive such that calling `set` for a key will invalidate any Computations that called `get` with that key, according to the usual contract for reactive data sources. That means if you call [`ReactiveDict#get`](#ReactiveDict-get)`('currentList')` from inside a Blaze template helper, the template will automatically be rerendered whenever [`ReactiveDict#set`](#ReactiveDict-set)`('currentList', x)` is called. To use `ReactiveDict`, add the `reactive-dict` package to your project by running in your terminal: ```bash meteor add reactive-dict ``` If you provide a name to its constructor, its contents will be saved across Hot Code Push client code updates. Example: ```js import { ReactiveDict } from "meteor/reactive-dict"; import { Tracker } from "meteor/tracker"; import { Meteor } from "meteor/meteor"; const state = new ReactiveDict(); state.set("currentRoomId", "random"); Tracker.autorun(() => { Meteor.subscribe("chatHistory", { room: state.get("currentRoomId") }); }); // Causes the function passed to `Tracker.autorun` to be rerun, so that the // 'chatHistory' subscription is moved to the room 'general'. state.set("currentRoomId", "general"); ``` `ReactiveDict.set` can also be called with an object of keys and values, which is equivalent to calling `ReactiveDict.set` individually on each key/value pair. ```js import { ReactiveDict } from "meteor/reactive-dict"; const state = new ReactiveDict(); state.set({ a: "foo", b: "bar", }); ``` This is useful in initialization code, to avoid re-initializing your state every time a new version of your app is loaded. Example in Blaze: ::: code-group ```html [main.html] ``` ```js [main.js] Template.main.onCreated(function () { this.state = new ReactiveDict(); this.state.set("enemy", "Eastasia"); }); Template.main.helpers({ theEnemy() { const inst = Template.instance(); return inst.state.get("enemy"); }, }); Template.main.events({ "click .change-enemy"(event, inst) { inst.state.set("enemy", "Eurasia"); }, }); // Clicking the button will change the page to say "We've always been at war with Eurasia" ``` ::: If value is a scalar, then these two expressions do the same thing: ```js import { ReactiveDict } from "meteor/reactive-dict"; const state = new ReactiveDict(); // ... state.get("key") === value; state.equals("key", value); ``` However, the second is recommended, as it triggers fewer invalidations (template redraws), making your program more efficient. --- --- url: /api/ReactiveVar.md --- # ReactiveVar To use `ReactiveVar`, add the `reactive-var` package to your project by running in your terminal: ```bash meteor add reactive-var ``` A ReactiveVar holds a single value that can be get and set, such that calling `set` will invalidate any Computations that called `get`, according to the usual contract for reactive data sources. A ReactiveVar is similar to a Session variable, with a few differences: - ReactiveVars don't have global names, like the "foo" in `Session.get('foo')`. Instead, they may be created and used locally, for example attached to a template instance, as in: `this.foo.get()`. - ReactiveVars are not automatically migrated across hot code pushes, whereas Session state is. - ReactiveVars can hold any value, while Session variables are limited to JSON or EJSON. An important property of ReactiveVars — which is sometimes a reason for using one — is that setting the value to the same value as before has no effect; it does not trigger any invalidations. So if one autorun sets a ReactiveVar, and another autorun gets the ReactiveVar, a re-run of the first autorun won't necessarily trigger the second. By default, only primitive values are compared this way, while calling `set` on an argument that is an _object_ (not a primitive) always counts as a change. You can configure this behavior using the `equalsFunc` argument. --- --- url: /about/roadmap.md --- # Roadmap Describes the high-level features and actions for the Meteor project in the near-to-medium term future. ## Introduction **Last updated: March 31th, 2025.** The description of many items includes sentences and ideas from Meteor community members. Contributors are encouraged to focus their efforts on work that aligns with the roadmap then we can work together in these areas. > As with any roadmap, this is a living document that will evolve as priorities and dependencies shift. > If you have new feature requests or ideas, you should open a new [discussion](https://github.com/meteor/meteor/discussions/new). ## Current project: Bundle optimization > We need to improve the bundle size and performance of Meteor apps. We should consider tree-shaking, code-splitting, > and other optimizations to make our apps leaner and faster. > To achieve that we plan to integrate or have an easy way to integrate with modern bundlers like RSPack, ESBuild, or Rollup. **Discussion links:** - [GitHub discussion](https://github.com/meteor/meteor/discussions/11587) - [forums discussion](https://forums.meteor.com/t/join-the-effort-to-speed-up-meteor-bundler/63406/17) ### Implementation plan: #### Phase 1: Profiling **Target Release:** 3.2 ✅ **Goal:** Add a command([meteor profile](/cli/#meteorprofile)) to measure if our changes are actually making our builds faster and smaller. #### Phase 2: External Transpiler Integration & Bundler Improvements **Target Release:** 3.3 ⏳ **Goal:** For this phase we want: - Improve our current bundler performance, via optimizations so that any meteor user can get benefits from it; And an external bundler could get the same benefits. - To have an external transpiler working with Meteor and producing a bundle that is smaller or faster than the current Meteor bundle. #### Phase 3: HMR Improvements **Target Release:** 3.3.x ⏳ **Goal:** Improve the HMR performance, so that it is faster and more reliable on what needs to be changed. #### Phase 4: Build Process Optimization **Target Release:** 3.4 ⏳ **Goal:** Improve the build size and make meteor use less resources for building, decreasing even more build and rebuild time. #### Documentation Strategy We plan to document the changes in the Meteor documentation, including: - How to use the new features - How to integrate with the new bundler - How the meteor bundler pipeline works for future contributors - Examples and guides on how to integrate with the new bundler ## Next releases - Support package.json exports fields ([Discussion](https://github.com/meteor/meteor/discussions/11727)) - Tree-shaking > Tree-shaking and exports fields may be implemented by integrating with more modern build tools. - Capacitor support > Capacitor is a modern alternative to Cordova; we should provide an easy way to build mobile apps using Capacitor. - MongoDB Change Streams support ([Discussion](https://github.com/meteor/meteor/discussions/11842)) > Change Streams is the official way to listen to changes in MongoDB. We should provide a way to use it seamlessly in Meteor. It has been planned for a long time, and now we’re in a position to do it. - Improve TypeScript support for Meteor and packages ([Discussion](https://github.com/meteor/meteor/discussions/12080)) > Should be an ongoing effort to improve the TypeScript support in Meteor and packages. We should provide a better experience for TypeScript users, including better type definitions and support for TypeScript features. - Improve release CI/CD speed and reliability (optimized build times will help) > Our CI/CD takes too long to run, causing long queues and delays in our release process and feedback loop; we need to improve that. ### Candidate items We need to discuss further to decide whether to proceed with these implementations. - Performance improvements (Async Hooks/Async Local Storage optimization) - HTTP/3 Support - Improve DDP Client - Improve Passwordless package ([Discussion](https://github.com/meteor/meteor/discussions/12075)) - Integrate with Tauri, it might replace Cordova and Electron in a single tool - Bring Redis-oplog to core ([Repository](https://github.com/Meteor-Community-Packages/redis-oplog)) - Better file upload support via DDP ([Discussion](https://github.com/meteor/meteor/discussions/11523)) - Improve usage in Windows environments ### Finished items - Change how Meteor executes Async code ([Discussion](https://github.com/meteor/meteor/discussions/11505)) - Provide new async APIs where Fibers are required - Mongo package with Async API ([PR](https://github.com/meteor/meteor/pull/12028)) - Provide async versions for Accounts and core packages - Adapt Meteor Promise implementation - Enable Top-Level Await (TLA) on Meteor server-side ([PR](https://github.com/meteor/meteor/pull/12095)) - Support Top-Level Await (TLA) on Reify - Remove Fibers dependency from Meteor Public APIs - Remove Fibers entirely - Update Cordova integration to Meteor 3.0 - Run Meteor on Node.js v20 - Change web engine from Connect to Express --- For more completed items, refer to our [changelog](https://docs.meteor.com/history.html). --- --- url: /packages/roles.md --- # Roles Authorization package for Meteor - compatible with built-in accounts package. > Available since Meteor 3.1.0 (previously alanning:roles) ## Installation To add roles to your application, run this command in your terminal: ```bash meteor add roles ``` ## Overview The roles package lets you attach roles to users and then check against those roles when deciding whether to grant access to Meteor methods or publish data. The core concept is simple - you create role assignments for users and then verify those roles later. This package provides helper methods to make the process of adding, removing, and verifying roles easier. ## Concepts ### Roles vs Permissions Although named "roles", you can define your **roles**, **scopes** or **permissions** however you like. They are essentially tags assigned to users that you can check later. You can have traditional roles like `admin` or `webmaster`, or more granular permissions like `view-secrets`, `users.view`, or `users.manage`. Often, more granular permissions are better as they handle edge cases without creating many higher-level roles. ### Role Hierarchy Roles can be organized in a hierarchy: - Roles can have multiple parents and children (subroles) - If a parent role is assigned to a user, all its descendant roles also apply - This allows creating "super roles" that aggregate permissions Example hierarchy setup: ```js import { Roles } from "meteor/roles"; // Create base roles await Roles.createRoleAsync("user"); await Roles.createRoleAsync("admin"); // Create permission roles await Roles.createRoleAsync("USERS_VIEW"); await Roles.createRoleAsync("POST_EDIT"); // Set up hierarchy await Roles.addRolesToParentAsync("USERS_VIEW", "admin"); await Roles.addRolesToParentAsync("POST_EDIT", "admin"); await Roles.addRolesToParentAsync("POST_EDIT", "user"); ``` ### Scopes Scopes allow users to have independent sets of roles. Use cases include: - Different communities within your app - Multiple tenants in a multi-tenant application - Different resource groups Users can have both scoped roles and global roles: - Global roles apply across all scopes - Scoped roles only apply within their specific scope - Scopes are independent of each other Example using scopes: ```js // Assign scoped roles await Roles.addUsersToRolesAsync(userId, ["manage-team"], "team-a"); await Roles.addUsersToRolesAsync(userId, ["player"], "team-b"); // Check scoped roles await Roles.userIsInRoleAsync(userId, "manage-team", "team-a"); // true await Roles.userIsInRoleAsync(userId, "manage-team", "team-b"); // false // Assign global role await Roles.addUsersToRolesAsync(userId, "super-admin", null); // Global roles work in all scopes await Roles.userIsInRoleAsync(userId, ["manage-team", "super-admin"], "team-b"); // true ``` ## Role Management Example: ```js import { Roles } from "meteor/roles"; // Create a new role await Roles.createRoleAsync("admin"); // Create if doesn't exist await Roles.createRoleAsync("editor", { unlessExists: true }); ``` ### Modifying Roles Example: ```js // Make 'editor' a child role of 'admin' await Roles.addRolesToParentAsync("editor", "admin"); // Add multiple child roles await Roles.addRolesToParentAsync(["editor", "moderator"], "admin"); ``` Example: ```js // Remove 'editor' as child role of 'admin' await Roles.removeRolesFromParentAsync("editor", "admin"); ``` Example: ```js // Delete role and all its assignments await Roles.deleteRoleAsync("temp-role"); ``` Example: ```js // Rename an existing role await Roles.renameRoleAsync("editor", "content-editor"); ``` ### Assigning Roles Example: ```js // Add global roles await Roles.addUsersToRolesAsync(userId, ["admin", "editor"]); // Add scoped roles await Roles.addUsersToRolesAsync(userId, ["manager"], "department-a"); // Add roles to multiple users await Roles.addUsersToRolesAsync([user1Id, user2Id], ["user"]); ``` Example: ```js // Replace user's global roles await Roles.setUserRolesAsync(userId, ["editor"]); // Replace scoped roles await Roles.setUserRolesAsync(userId, ["viewer"], "project-x"); // Clear all roles in scope await Roles.setUserRolesAsync(userId, [], "project-x"); ``` Example: ```js // Remove global roles await Roles.removeUsersFromRolesAsync(userId, ["admin"]); // Remove scoped roles await Roles.removeUsersFromRolesAsync(userId, ["manager"], "department-a"); // Remove roles from multiple users await Roles.removeUsersFromRolesAsync([user1Id, user2Id], ["temp-role"]); ``` Example: ```js // Rename a scope await Roles.renameScopeAsync("department-1", "marketing"); ``` Example: ```js // Remove a scope and all its role assignments await Roles.removeScopeAsync("old-department"); ``` Example: ```js // Get all roles sorted by name const roles = Roles.getAllRoles({ sort: { _id: 1 } }); // Get roles with custom query const customRoles = Roles.getAllRoles({ fields: { _id: 1, children: 1 }, sort: { _id: -1 }, }); ``` Example: ```js // Find all admin users const adminUsers = await Roles.getUsersInRoleAsync("admin"); // Find users with specific roles in a scope const scopedUsers = await Roles.getUsersInRoleAsync( ["editor", "writer"], "blog" ); // Find users with custom options const users = await Roles.getUsersInRoleAsync("manager", { scope: "department-a", queryOptions: { sort: { createdAt: -1 }, limit: 10, }, }); ``` ## Checking Roles Example: ```js // Check global role const isAdmin = await Roles.userIsInRoleAsync(userId, "admin"); // Check any of multiple roles const canEdit = await Roles.userIsInRoleAsync(userId, ["editor", "admin"]); // Check scoped role const isManager = await Roles.userIsInRoleAsync( userId, "manager", "department-a" ); // Check role in any scope const hasRole = await Roles.userIsInRoleAsync(userId, "viewer", { anyScope: true, }); ``` Example: ```js // Get user's global roles const globalRoles = await Roles.getRolesForUserAsync(userId); // Get scoped roles const deptRoles = await Roles.getRolesForUserAsync(userId, "department-a"); // Get all roles including inherited const allRoles = await Roles.getRolesForUserAsync(userId, { anyScope: true, fullObjects: true, }); ``` Example: ```js // Check if admin is a parent of editor const isParent = await Roles.isParentOfAsync("admin", "editor"); // Can be used to check inheritance chains const hasPermission = await Roles.isParentOfAsync("super-admin", "post-edit"); ``` Example: ```js // Get all scopes for user const allScopes = await Roles.getScopesForUserAsync(userId); // Get scopes where user has specific roles const editorScopes = await Roles.getScopesForUserAsync(userId, ["editor"]); ``` ## Publishing Roles Role assignments need to be published to be available on the client. Example publication: ```js // Publish user's own roles Meteor.publish(null, function () { if (this.userId) { return Meteor.roleAssignment.find({ "user._id": this.userId }); } this.ready(); }); // Publish roles for specific scope Meteor.publish("scopeRoles", function (scope) { if (this.userId) { return Meteor.roleAssignment.find({ scope: scope }); } this.ready(); }); ``` ## Client only APIs On the client alongside the async methods, you can use the `sync` versions of the functions: - `Roles.userIsInRole(userId, roles, scope)` - `Roles.getRolesForUser(userId, scope)` - `Roles.getScopesForUser(userId)` - `Roles.isParentOf(parent, child)` - `Roles.getUsersInRole(role, scope)` - `Roles.getAllRoles(options)` - `Roles.createRole(role, options)` - `Roles.addUsersToRoles(userId, roles, scope)` - `Roles.setUserRoles(userId, roles, scope)` - `Roles.removeUsersFromRoles(userId, roles, scope)` - `Roles.addRolesToParent(child, parent)` - `Roles.removeRolesFromParent(child, parent)` - `Roles.deleteRole(role)` - `Roles.renameRole(oldRole, newRole)` - `Roles.renameScope(oldScope, newScope)` - `Roles.removeScope(scope)` ## Using with Templates The roles package automatically provides an `isInRole` helper for templates: ```handlebars {{#if isInRole "admin"}}
    {{/if}} {{#if isInRole "editor,writer" "blog"}}
    {{/if}} ``` ## Migration to Core Version If you are currently using the `alanning:roles` package, follow these steps to migrate to the core version: 1. Make sure you are on version 3.6 of `alanning:roles` first 2. Run any pending migrations from previous versions 3. Switch all server-side role operations to use the async versions of the functions: - createRoleAsync - deleteRoleAsync - addUsersToRolesAsync - setUserRolesAsync - removeUsersFromRolesAsync - etc. 4. Remove `alanning:roles` package: ```bash meteor remove alanning:roles ``` 5. Add the core roles package: ```bash meteor add roles ``` 6. Update imports to use the new package: ```js import { Roles } from "meteor/roles"; ``` The sync versions of these functions are still available on the client. ## Security Considerations 1. Client-side role checks are for convenience only - always verify permissions on the server 2. Publish only the role data that users need 3. Use scopes to properly isolate role assignments 4. Validate role names and scopes to prevent injection attacks 5. Consider using more granular permissions over broad role assignments ## Example Usage ### Method Security ```js // server/methods.js Meteor.methods({ deletePost: async function (postId) { check(postId, String); const canDelete = await Roles.userIsInRoleAsync( this.userId, ["admin", "moderator"], "posts" ); if (!canDelete) { throw new Meteor.Error("unauthorized", "Not authorized to delete posts"); } Posts.remove(postId); }, }); ``` ### Publication Security ```js // server/publications.js Meteor.publish("secretDocuments", async function (scope) { check(scope, String); const canView = await Roles.userIsInRoleAsync( this.userId, ["view-secrets", "admin"], scope ); if (canView) { return SecretDocs.find({ scope: scope }); } this.ready(); }); ``` ### User Management ```js // server/users.js Meteor.methods({ promoteToEditor: async function (userId, scope) { check(userId, String); check(scope, String); const canPromote = await Roles.userIsInRoleAsync( this.userId, "admin", scope ); if (!canPromote) { throw new Meteor.Error("unauthorized"); } await Roles.addUsersToRolesAsync(userId, ["editor"], scope); }, }); ``` --- --- url: /api-examples.md --- # Runtime API Examples This page demonstrates usage of some of the runtime APIs provided by VitePress. The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files: ```md ## Results ### Theme Data
    {{ theme }}
    ### Page Data
    {{ page }}
    ### Page Frontmatter
    {{ frontmatter }}
    ``` ## Results ### Theme Data
    {{ theme }}
    ### Page Data
    {{ page }}
    ### Page Frontmatter
    {{ frontmatter }}
    ## More Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata). --- --- url: /packages/server-render.md --- # Server Rendering This package implements generic support for server-side rendering in Meteor apps, by providing a mechanism for injecting fragments of HTML into the `` and/or `` of the application's initial HTML response. ## Usage This package exports a function named `onPageLoad` which takes a callback function that will be called at page load (on the client) or whenever a new request happens (on the server). The callback receives a `sink` object, which is an instance of either `ClientSink` or `ServerSink` depending on the environment. Both types of `sink` have the same methods, though the server version accepts only HTML strings as content, whereas the client version also accepts DOM nodes. The current interface of `{Client,Server}Sink` objects is as follows: ```js class Sink { // Appends content to the . appendToHead(content) // Appends content to the . appendToBody(content) // Appends content to the identified element. appendToElementById(id, content) // Replaces the content of the identified element. renderIntoElementById(id, content) // Redirects request to new location. redirect(location, code) // server only methods // sets the status code of the response. setStatusCode(code) // sets a header of the response. setHeader(key, value) // gets request headers getHeaders() // gets request cookies getCookies() } ``` The `sink` object may also expose additional properties depending on the environment. For example, on the server, `sink.request` provides access to the current `request` object, and `sink.arch` identifies the target architecture of the pending HTTP response (e.g. "web.browser"). Here is a basic example of `onPageLoad` usage on the server: ::: code-group ```js [server.js] import from "react"; import { renderToString } from "react-dom/server"; import { onPageLoad } from "meteor/server-render"; import App from "/imports/Server.js"; onPageLoad(sink => { sink.renderIntoElementById("app", renderToString( )); }); ``` Likewise on the client: ```js [client.js] import React from "react"; import ReactDOM from "react-dom"; import { onPageLoad } from "meteor/server-render"; onPageLoad(async (sink) => { const App = (await import("/imports/Client.js")).default; ReactDOM.hydrate(, document.getElementById("app")); }); ``` ::: Note that the `onPageLoad` callback function is allowed to return a `Promise` if it needs to do any asynchronous work, and thus may be implemented by an `async` function (as in the client case above). Note also that the client example does not end up calling any methods of the `sink` object, because `ReactDOM.hydrate` has its own similar API. In fact, you are not even required to use the `onPageLoad` API on the client, if you have your own ideas about how the client should do its rendering. Here is a more complicated example of `onPageLoad` usage on the server, involving the [`styled-components`](https://www.styled-components.com/docs/advanced#server-side-rendering) npm package: ```js import React from "react"; import { onPageLoad } from "meteor/server-render"; import { renderToString } from "react-dom/server"; import { ServerStyleSheet } from "styled-components"; import App from "/imports/Server"; onPageLoad((sink) => { const sheet = new ServerStyleSheet(); const html = renderToString( sheet.collectStyles() ); sink.renderIntoElementById("app", html); sink.appendToHead(sheet.getStyleTags()); }); ``` In this example, the callback not only renders the `` element into the element with `id="app"`, but also appends any `