ostrio:cookies

v2.9.0β€’Published last month

support support

Cookies for Meteor

Isomorphic and bulletproof πŸͺ cookie management for Meteor applications with support for Client, Server, Browser, Cordova, Meteor-Desktop, and other Meteor environments.

  • πŸ‘¨β€πŸ’» Stable codebase
  • πŸš€ 320.000+ downloads
  • πŸ‘¨β€πŸ”¬ 99.9% tests coverage / TDD
  • πŸ“¦ No external dependencies (no underscore, jQuery, or Blaze)
  • πŸ–₯ Consistent API across Server and Client environments
  • πŸ“± Compatible with Cordova, Browser, Meteor-Desktop, and other client platforms
  • γŠ—οΈ Full Unicode support for cookie values
  • πŸ‘¨β€πŸ’» Supports String, Array, Object, and Boolean as cookie value types
  • β™Ώ IE support, thanks to @derwok
  • πŸ“¦ Shipped with TypeScript types
  • πŸ“¦ Looking for persistent Client (Browser) storage? Try the ClientStorage package.

ToC:

Installation

meteor add ostrio:cookies

ES6 Import

1import { Cookies } from 'meteor/ostrio:cookies';

FAQ

  • Cordova Usage: This recommendation applies only to outgoing cookies from Client β†’ Server. Cookies set by the server work out-of-the-box on the client:
    • Enable withCredentials
    • Set { allowQueryStringCookies: true } and { allowedCordovaOrigins: true } on both Client and Server
    • When allowQueryStringCookies is enabled, cookies are transferred to the server via a query string (GET parameters)
    • For security, this is allowed only when the Origin header matches the regular expression ^http://localhost:12[0-9]{3}$ (Meteor/Cordova connects through localhost:12XXX)
  • Cookies Missing on Server? In most cases, this is due to Meteor's HTTP callback-chain ordering. Ensure that new Cookies() is called before routes are registered:
  • Meteor-Desktop Compatibility: ostrio:cookies can be used in meteor-desktop projects. Since Meteor-Desktop works similarly to Cordova, all Cordova recommendations from above apply

API

[!NOTE] On the server, cookies are set only after headers are sent (i.e. on the next route or page reload)

To sync cookies from Client to Server without a page reload, use sendAsync() or send()

[!TIP] On the Server: cookies are implemented as middleware that attaches a CookiesCore instance to the incoming request (accessible as req.Cookies). Ensure that the Cookies middleware is registered before other middleware and routes

In .meteor/packages: Place the ostrio:cookies package above all community packages, order of packages does matter in this file

See FAQ for more tips

[!IMPORTANT] On the Server: it's possible to create many new Cookies() instances with handler callbacks and onCookies hooks, then later each instance can get destroyed calling .destroy() method.

Note: Only one middleware will be registered and passed into WebApp.connectHandlers.use() at the time! All consequent handler and onCookies callbacks and hooks will be added to shared Map and called as expected within the first registered middleware. Invoking .middleware() method manually will result in warning and will return "blank" middleware handler which will instantly call NextFunc()

new Cookies() Constructor

Create a new instance of Cookies (available on both Client and Server).

Arguments:

  • opts {CookiesOptions} - Config object

Available CookiesOptions:

  • opts.auto {boolean} – [Server] Auto-bind as req.Cookies (default: true)
  • opts.handler {function} – [Server] Custom middleware handler; receives the Cookies instance
  • opts.onCookies {function} – [Server] Callback triggered after .send() or .sendAsync() is called and the cookies are received by the server. (Note: available only if auto is true.)
  • opts.TTL {number | boolean} – Default expiration time (max-age) in milliseconds. Set to false for session cookies
  • opts.runOnServer {boolean} – Set to false to disable server usage (default: true)
  • opts.allowQueryStringCookies {boolean} – Allow passing cookies via query string (primarily for Cordova)
  • opts.allowedCordovaOrigins {RegExp | boolean} – [Server] Allow setting cookies from specific origins (defaults to ^http:\/\/localhost:12[0-9]{3}$ if true)
  • opts.name {string} - Sets .NAME property of Cookies & CookiesCore instances, use it for instance identification, default COOKIES

Example:

1import { Cookies } from 'meteor/ostrio:cookies';
2
3const cookies = new Cookies({
4  TTL: 31557600000 // One year TTL
5});

.get()

(Anywhere) Read a cookie. Returns undefined if the cookie is not found

Arguments:

  • key {string} – The name of the cookie.
1cookies.get('age'); // undefined if not found
2cookies.set('age', 25); // returns true
3cookies.get('age'); // returns 25

.set()

(Anywhere) Create or update a cookie

Arguments:

  • key {string} – The cookie name
  • value {string | number | boolean | object | array} – The cookie value
  • opts {CookieOptions} – Optional settings

Supported CookieOptions:

  • opts.expires {number | Date | Infinity}: Cookie expiration
  • opts.maxAge {number}: Maximum age in seconds
  • opts.path {string}: Cookie path (default: current path)
  • opts.domain {string}: Cookie domain
  • opts.secure {boolean}: Transmit only over HTTPS
  • opts.httpOnly {boolean}: Inaccessible to client-side JavaScript
  • opts.sameSite {boolean | 'None' | 'Strict' | 'Lax'}: Cross-site cookie policy
  • opts.partitioned {boolean}: Specifies Partitioned attribute in Set-Cookie header. When enabled, clients will only send the cookie back when the current domain and top-level domain matches
  • opts.priority {'Low' | 'Medium' | 'High'}: Specifies the value for the Priority attribute in `Set-Cookie`` header
  • opts.firstPartyOnly {boolean}: Deprecated (use sameSite instead)
1cookies.set('age', 25, {
2  path: '/',
3  secure: true
4});

.remove()

(Anywhere) Remove cookie(s)

  • remove() – Removes all cookies on the current domain
  • remove(key) – Removes the specified cookie
  • remove(key, path, domain) – Removes a cookie with the given key, path, and domain

Arguments:

  • key {string} - The name of the cookie to create/overwrite
  • path {string} - [Optional] The path from where the cookie was readable. E.g., "/", "/mydir"; if not specified, defaults to the current path of the current document location (string or null). The path must be absolute (see RFC 2965). For more information on how to use relative paths in this argument, read more
  • domain {string} - [Optional] The domain from where the cookie was readable. E.g., "example.com", ".example.com" (includes all subdomains) or "subdomain.example.com"; if not specified, defaults to the host portion of the current document location (string or null)
1const isRemoved = cookies.remove(key, path, domain); // boolean
2const isRemoved = cookies.remove('age', '/'); // boolean
3const isRemoved = cookies.remove(key, '/', 'example.com'); // boolean

.has()

(Anywhere) Check if a cookie exists

Arguments:

  • key {string} – The name of the cookie
1const hasKey = cookies.has(key); // boolean
2const hasKey = cookies.has('age'); // boolean

.keys()

(Anywhere) Returns an array of all cookie names

1const cookieKeys = cookies.keys(); // string[] (e.g., ['locale', 'country', 'gender'])

.send()

(Client only) Synchronously send all current cookies to the server via XHR

Arguments:

  • callback {function} – Callback with signature (error, response).
1cookies.send((error, response) => {
2  if (error) {
3    console.error(error);
4  } else {
5    console.log('Cookies synced:', response);
6  }
7});

.sendAsync()

(Client only) Asynchronously send all current cookies to the server via XHR

1const response = await cookies.sendAsync();
2console.log('Cookies synced:', response);

.middleware()

(Server only) Returns a middleware function to integrate cookies into your server’s request pipeline. Usage: Register this middleware with your Meteor server (e.g., via WebApp.connectHandlers.use).

1import { WebApp } from 'meteor/webapp';
2import { Cookies } from 'meteor/ostrio:cookies';
3
4const cookies = new Cookies({
5  auto: false,
6  handler(cookiesInstance) {
7    // Custom processing with cookiesInstance (of type Cookies)
8  }
9});
10
11WebApp.connectHandlers.use(cookies.middleware());

.destroy()

(Server only) Unregisters hooks, callbacks, and middleware

1cookies.isDestroyed // false
2cookies.destroy(); // true
3cookies.isDestroyed // true
4cookies.destroy(); // false β€” returns `false` as instance was already destroyed

new CookiesCore() constructor

CookiesCore is low-level constructor that can be used to directly parse and manage cookies

Arguments:

  • opts {CookiesCoreOptions} – Optional settings

Supported CookiesCoreOptions:

  • _cookies {string | CookieDict} - Cookies string from document.cookie, Set-Cookie header, or { [key: string]: unknown } Object
  • setCookie {boolean} - Set to true when _cookies option derivative of Set-Cookie header
  • response {ServerResponse} - HTTP server response object
  • TTL {number | false} - Default cookies expiration time (max-age) in milliseconds. If false, the cookie lasts for the session
  • runOnServer {boolean} - Client only. If true β€” enables send and sendAsync from client
  • allowQueryStringCookies {boolean} - If true, allow passing cookies via query string (used primarily in Cordova)
  • allowedCordovaOrigins {RegExp | boolean} - A regular expression or boolean to allow cookies from specific origins
  • opts.name {string} - Sets .NAME property of CookiesCore instances, use it for instance identification, default COOKIES_CORE

[!NOTE] CookiesCore instance has the same methods as Cookies class except .destroy() and .middleware()

1import { CookiesCore } from 'meteor/ostrio:cookies';
2
3if (Meteor.isServer) {
4  // EXAMPLE SERVER USAGE
5  WebApp.connectHandlers.use((request, response, next) => {
6    const headerCookies = response.headers.get('set-cookie');
7    const cookies = new CookiesCore({
8      _cookies: headerCookies,
9      setCookie: true, // <- Switch cookie-parser to header mode
10      response: response,
11    });
12
13    // FOR EXAMPLE: CHECK SESSION EXPIRATION
14    if (cookies.has('session-exp')) {
15      if (cookies.get('session-exp') < Date.now()) {
16        // .remove() WILL ADD `Set-Cookie` HEADER WITH expires=0 OPTION
17        cookies.remove('session-id');
18        cookies.remove('session-exp');
19      }
20    } else {
21      // MARK USER AS NEW
22      cookies.set('session-type', 'new-user');
23    }
24  });
25}
26
27if (Meteor.isClient) {
28  const cookies = new CookiesCore({
29    // {runOnServer: true} Enables syncing cookies between client and server
30    // Requires `new Cookies({auto: true})` on server
31    runOnServer: true,
32    _cookies: { // <- Set default cookies
33      key: 'name',
34      theme: 'dark',
35      isNew: true,
36      'agreed-with-gdpr': false,
37    }
38  });
39
40  // SET OR CHANGE COOKIES IN RUNTIME
41  cookies.set('ab-test', 42);
42  cookies.set('isNew', false);
43  cookies.set('agreed-with-gdpr', true);
44
45  // SYNC COOKIES
46  await cookies.sendAsync();
47}

Examples

Use new Cookies() on Client and Server separately or in the same file

Example: Client Usage

1import { Meteor } from 'meteor/meteor';
2import { Cookies } from 'meteor/ostrio:cookies';
3const cookies = new Cookies();
4
5cookies.set('locale', 'en');
6cookies.set('country', 'usa');
7cookies.set('gender', 'male');
8
9console.log(cookies.get('gender')); // "male"
10console.log(cookies.has('locale')); // true
11console.log(cookies.keys()); // ['locale', 'country', 'gender']
12
13cookies.remove('locale');
14console.log(cookies.get('locale')); // undefined

Example: Server Usage

1import { Meteor } from 'meteor/meteor';
2import { Cookies } from 'meteor/ostrio:cookies';
3import { WebApp } from 'meteor/webapp';
4
5new Cookies();
6WebApp.connectHandlers.use((req, res, next) => {
7  const cookiesInstance = req.Cookies;
8
9  cookiesInstance.set('locale', 'en');
10  cookiesInstance.set('country', 'usa');
11  cookiesInstance.set('gender', 'male');
12
13  console.log(cookiesInstance.get('gender')); // "male"
14  next();
15});

Sometimes it is required to build temporary or separate logic based on Client's cookies. And to split logic between different modules and files

1import { Meteor } from 'meteor/meteor';
2import { Cookies } from 'meteor/ostrio:cookies';
3
4// register default middleware that will handle requests and req.Cookies extension
5const globalCookies = new Cookies();
6
7// In checkout module/file
8WebApp.connectHandlers.use((req, res, next) => {
9  if (req.Cookies.has('checkout-session')) {
10    const sessionId = req.Cookies.get('checkout-session');
11    // CHECK IF CHECKOUT SESSION IS VALID
12    if (isCheoutSessionValid(sessionId)) {
13      // FORCE-REDIRECT USER TO CHECKOUT IF SESSION IS VALID
14      res.statusCode = 302;
15      res.setHeader('Location', `https://example.com?chsessid=${sessionId}`);
16      res.end();
17      return;
18    }
19
20    // REMOVE CHECKOUT COOKIE IF NOT VALID OR EXPIRED
21    req.Cookies.remove('checkout-session');
22  }
23
24  next();
25});
26
27// In session module/file
28const sessionCookies = new Cookies({
29  auto: false,
30  async handler(cookies) {
31    // FOR EXAMPLE: CHECK SESSION EXPIRATION
32    if (cookies.has('session-exp')) {
33      if (cookies.get('session-exp') < Date.now()) {
34        // .remove() WILL ADD `Set-Cookie` HEADER WITH expires=0 OPTION
35        cookies.remove('session-id');
36        cookies.remove('session-exp');
37      }
38    } else {
39      // MARK USER AS NEW
40      cookies.set('session-type', 'new-user');
41    }
42  }
43});
44// unregister handler when it isn't needed
45sessionCookies.destroy();

Example: Set and read cookies based on URL

Often cookies logic depends from URL it was called from. Access request details on handler callback using cookies.response.req.url {IncomingMessage} object:

1import { Cookies } from 'meteor/ostrio:cookies';
2
3new Cookies({
4  auto: false,
5  async handler(cookies) {
6    const url = new URL(cookies.response.req.url);
7    switch (url.pathname) {
8      case '/signup/create':
9        // GET USER'S SELECTED PLAN ON SIGNUP
10        const plan = url.searchParams.get('plan') || 'default-plan';
11        cookies.set('selected-tariff', plan);
12        break;
13      case '/shopping-cart/new':
14        // CREATE NEW CHECKOUT SESSION ID
15        cookies.set('checkout-session', Random.id());
16        break;
17    }
18  }
19});

Example: Alternative Usage

1import { Meteor } from 'meteor/meteor';
2import { Cookies } from 'meteor/ostrio:cookies';
3
4if (Meteor.isClient) {
5  const cookies = new Cookies();
6  cookies.set('gender', 'male');
7  console.log(cookies.get('gender')); // "male"
8  console.log(cookies.keys()); // ['gender']
9}
10
11if (Meteor.isServer) {
12  const { WebApp } = require('meteor/webapp');
13  const cookiesInstance = new Cookies({
14    auto: false, // Disable auto-binding (optional)
15    handler(cookies) {
16      console.log(cookies.get('gender')); // "male"
17    }
18  });
19  WebApp.connectHandlers.use(cookiesInstance.middleware());
20}

Running Tests

  1. Clone the package repository.
  2. Open a terminal in the cloned directory.
  3. Run tests using:

Meteor/Tinytest

# Default
meteor test-packages ./

# With a custom port
meteor test-packages ./ --port 8888

Support Our Open Source Contributions