ostrio:cookies

v2.9.2β€’Published 3 weeks ago

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
  • πŸš€ 400,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, Number, Array, Object, Boolean, and null 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 subsequent 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 a CookiesCore 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: /)
  • 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 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) Send all current cookies to the server via fetch and callback

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) Send all current cookies to the server via fetch and Promise

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 CookiesCore)
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 derives from 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 { Meteor } from 'meteor/meteor';
2import { WebApp } from 'meteor/webapp';
3import { CookiesCore } from 'meteor/ostrio:cookies';
4
5if (Meteor.isServer) {
6  // EXAMPLE SERVER USAGE
7  WebApp.connectHandlers.use((request, response, next) => {
8    const cookies = new CookiesCore({
9      _cookies: request.headers.cookie || '',
10      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    next();
25  });
26}
27
28if (Meteor.isClient) {
29  const cookies = new CookiesCore({
30    // {runOnServer: true} Enables syncing cookies between client and server
31    // Requires `new Cookies({auto: true})` on server
32    runOnServer: true,
33    _cookies: { // <- Set default cookies
34      key: 'name',
35      theme: 'dark',
36      isNew: true,
37      'agreed-with-gdpr': false,
38    }
39  });
40
41  // SET OR CHANGE COOKIES IN RUNTIME
42  cookies.set('ab-test', 42);
43  cookies.set('isNew', false);
44  cookies.set('agreed-with-gdpr', true);
45
46  // SYNC COOKIES
47  await cookies.sendAsync();
48}

Examples

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

Example: Client Usage

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

Example: Server Usage

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

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 { Cookies } from 'meteor/ostrio:cookies';
2import { WebApp } from 'meteor/webapp';
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 (isCheckoutSessionValid(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 on URL it was called from. Access request details on handler callback using cookies.response.req.url {IncomingMessage} object:

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

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, headless Tinytest runner
npm test

# Direct command
mtest --package ./ --port=8888 --once

# Type definitions
npm run test:types

# Browser fallback
meteor test-packages ./ --once --driver-package test-in-console

Support Our Open Source Contributions