cookie-muncher
Advanced tools
Comparing version
@@ -12,3 +12,3 @@ type Cookie = { | ||
* @property {boolean} httpOnly - Whether the cookie should only be accessible via HTTP | ||
* @property {"Strict" | "Lax" | "None"} sameSite - The same-site policy for the cookie | ||
* @property {"strict" | "lax" | "none"} sameSite - The same-site policy for the cookie | ||
*/ | ||
@@ -22,6 +22,7 @@ type CookieOptions = { | ||
httpOnly?: boolean; | ||
sameSite?: "Strict" | "Lax" | "None"; | ||
sameSite?: "strict" | "lax" | "none"; | ||
}; | ||
type DomCookieOptions = Omit<CookieOptions, "httpOnly">; | ||
type HttpCookieOptions = CookieOptions; | ||
type RemoveDomCookieOptions = Pick<CookieOptions, "domain" | "path" | "sameSite">; | ||
@@ -38,16 +39,2 @@ /** | ||
declare enum CookieMaxAge { | ||
Now = -1, | ||
OneHour = 3600, | ||
SixHours = 21600, | ||
TwlveHours = 43200, | ||
OneDay = 86400, | ||
OneWeek = 604800, | ||
TwoWeeks = 1209600, | ||
OneMonth = 2592000, | ||
ThreeMonths = 7776000, | ||
SixMonths = 15552000, | ||
OneYear = 31536000 | ||
} | ||
declare const httpCookie: { | ||
@@ -64,3 +51,3 @@ parse: typeof parseCookies; | ||
declare function removeCookie(name: string): void; | ||
declare function removeCookie(name: string, options?: RemoveDomCookieOptions): void; | ||
@@ -74,2 +61,16 @@ declare const domCookie: { | ||
export { Cookie, CookieMaxAge, DomCookieOptions, HttpCookieOptions, domCookie, httpCookie }; | ||
declare enum CookieMaxAge { | ||
Now = -1, | ||
OneHour = 3600, | ||
SixHours = 21600, | ||
TwlveHours = 43200, | ||
OneDay = 86400, | ||
OneWeek = 604800, | ||
TwoWeeks = 1209600, | ||
OneMonth = 2592000, | ||
ThreeMonths = 7776000, | ||
SixMonths = 15552000, | ||
OneYear = 31536000 | ||
} | ||
export { Cookie, CookieMaxAge, DomCookieOptions, HttpCookieOptions, RemoveDomCookieOptions, domCookie, httpCookie }; |
@@ -1,1 +0,1 @@ | ||
"use strict";var f=Object.defineProperty;var a=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var d=Object.prototype.hasOwnProperty;var y=(r,o)=>{for(var t in o)f(r,t,{get:o[t],enumerable:!0})},O=(r,o,t,s)=>{if(o&&typeof o=="object"||typeof o=="function")for(let i of h(o))!d.call(r,i)&&i!==t&&f(r,i,{get:()=>o[i],enumerable:!(s=a(o,i))||s.enumerable});return r};var v=r=>O(f({},"__esModule",{value:!0}),r);var $={};y($,{CookieMaxAge:()=>n,domCookie:()=>S,httpCookie:()=>x});module.exports=v($);function m(r){return r?r.split(";").map(o=>{let[t,s]=o.split("="),i=decodeURIComponent(t.trim()),C=decodeURIComponent(s.trim());return{name:i,value:C}}):[]}function p(r,o={}){let t=[`${r.name}=${r.value}`];return o.maxAge&&t.push(`Max-Age=${o.maxAge}`),o.expires&&t.push(`Expires=${o.expires.toUTCString()}`),o.domain&&t.push(`Domain=${o.domain}`),t.push(`Path=${o.path||"/"}`),o.secure&&t.push("Secure"),o.httpOnly&&t.push("HttpOnly"),o.sameSite&&t.push(`SameSite=${o.sameSite}`),t.join("; ")}var n=(e=>(e[e.Now=-1]="Now",e[e.OneHour=3600]="OneHour",e[e.SixHours=21600]="SixHours",e[e.TwlveHours=43200]="TwlveHours",e[e.OneDay=86400]="OneDay",e[e.OneWeek=604800]="OneWeek",e[e.TwoWeeks=1209600]="TwoWeeks",e[e.OneMonth=2592e3]="OneMonth",e[e.ThreeMonths=7776e3]="ThreeMonths",e[e.SixMonths=15552e3]="SixMonths",e[e.OneYear=31536e3]="OneYear",e))(n||{});var x={parse:m,serialize:p};function u(){return m(document.cookie)}function k(r){return m(document.cookie).find(o=>o.name===r)||null}function c(r,o={}){document.cookie=p(r,o)}function l(r){document.cookie=p({name:r,value:""},{maxAge:-1})}var S={getAll:u,get:k,set:c,remove:l};0&&(module.exports={CookieMaxAge,domCookie,httpCookie}); | ||
"use strict";var f=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var x=(e,o)=>{for(var r in o)f(e,r,{get:o[r],enumerable:!0})},z=(e,o,r,s)=>{if(o&&typeof o=="object"||typeof o=="function")for(let m of v(o))!w.call(e,m)&&m!==r&&f(e,m,{get:()=>o[m],enumerable:!(s=b(o,m))||s.enumerable});return e};var O=e=>z(f({},"__esModule",{value:!0}),e);var E={};x(E,{CookieMaxAge:()=>l,domCookie:()=>S,httpCookie:()=>g});module.exports=O(E);function p(e){return e?e.split(";").map(o=>{let[r,s]=o.split("="),m=r.trim(),y=decodeURIComponent(s.trim());return{name:m,value:y}}):[]}function C(e){return`${e.charAt(0).toUpperCase()}${e.slice(1)}`}function h(e){return new TextEncoder().encode(e).byteLength}function i(e,o={}){let r=[`${e.name}=${encodeURIComponent(e.value)}`];return o.maxAge&&r.push(`Max-Age=${o.maxAge}`),o.expires&&r.push(`Expires=${o.expires.toUTCString()}`),o.domain&&r.push(`Domain=${encodeURIComponent(o.domain)}`),r.push(`Path=${encodeURIComponent(o.path||"/")}`),o.secure&&r.push("Secure"),o.httpOnly&&r.push("HttpOnly"),o.sameSite&&r.push(`SameSite=${C(o.sameSite)}`),r.join("; ")}var g={parse:p,serialize:i};function n(){if(!navigator.cookieEnabled)throw Error("Cookies are disabled by the browser")}function c(){return n(),p(document.cookie)}function u(e){return n(),p(document.cookie).find(o=>o.name===e)||null}function k(e,o={}){if(n(),document.cookie.split(";").length>50)throw Error("You have more than 50 cookies, most browsers limit the number of cookies to 50");let s=i(e,o);if(h(s)>4096)throw Error(`The size of this cookie is greater than ${4096} bytes, most browsers limit the number of cookies to this size.`);document.cookie=i(e,o)}var l=(t=>(t[t.Now=-1]="Now",t[t.OneHour=3600]="OneHour",t[t.SixHours=21600]="SixHours",t[t.TwlveHours=43200]="TwlveHours",t[t.OneDay=86400]="OneDay",t[t.OneWeek=604800]="OneWeek",t[t.TwoWeeks=1209600]="TwoWeeks",t[t.OneMonth=2592e3]="OneMonth",t[t.ThreeMonths=7776e3]="ThreeMonths",t[t.SixMonths=15552e3]="SixMonths",t[t.OneYear=31536e3]="OneYear",t))(l||{});function a(e,o){n(),document.cookie=i({name:e,value:""},{...o,maxAge:-1})}var S={getAll:c,get:u,set:k,remove:a};0&&(module.exports={CookieMaxAge,domCookie,httpCookie}); |
{ | ||
"name": "cookie-muncher", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "Effortless cookie management", | ||
@@ -30,3 +30,2 @@ "author": "Bluzzi", | ||
"eslint": "^8.39.0", | ||
"happy-dom": "^9.9.2", | ||
"tsup": "^6.7.0", | ||
@@ -33,0 +32,0 @@ "typescript": "^5.0.4", |
# Cookie Muncher | ||
A lightweight and typesafe package for manipulating cookies in NodeJS and the browser. | ||
✅ Lightweight | ||
✅ Easy to use | ||
✅ Unit tested | ||
✅ Typesafe | ||
✅ Compatible with NodeJS & browsers | ||
✅ ES module & CommonJS compatible | ||
- 🚀 Lightweight | ||
- 🌏 Works in all browsers | ||
- 🧪 Unit tested | ||
- 🔷 Typesafe | ||
- 📦 Support ESM & CJS | ||
- ✅ [RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265) compliant | ||
- 📖 Well documented | ||
@@ -59,24 +60,35 @@ ## Installation | ||
const cookies = "foo=bar; bar=foo"; | ||
const cookies = "foo=bar; equation=E%3Dmc%5E2"; | ||
console.log(httpCookie.parse(cookies)); | ||
// Output: [{ name: "foo", value: "bar" }, { name: "bar", value: "foo" }] | ||
// Output: [{ name: "foo", value: "bar" }, { name: "equation", value: "E=mc^2" }] | ||
``` | ||
### `domCookie.set(cookie: Cookie, options?: DomCookieOptions): void` | ||
### `domCookie.get(name: string): Cookie | null` | ||
### `domCookie.getAll(): Cookie[]` | ||
### `domCookie.remove(name: string): void` | ||
Create, update, get and remove cookies from the browser (only work on DOM context). | ||
Create an edit cookie. | ||
To be able to edit a cookie, you must define the same `Domain` and `Path` as the cookie. | ||
You cannot create or edit `HttpOnly` cookies. | ||
You may not create more than 50 cookies for a single `Domain` and each cookie must not exceed 4096 bytes. If it does, an error will be thrown by this function. | ||
```ts | ||
import { domCookie } from "cookie-muncher"; | ||
``` | ||
```ts | ||
domCookie.set({ name: "foo", value: "bar" }); | ||
domCookie.set({ name: "bar", value: "foo" }, { path: "/bar" }); | ||
``` | ||
### `domCookie.get(name: string): Cookie | null` | ||
Get the value of a cookie. | ||
You cannot get the value of an `HttpOnly` cookie. | ||
Make sure the `Path` of the cookie is accessible in the current context. | ||
```ts | ||
import { domCookie } from "cookie-muncher"; | ||
console.log(domCookie.get("foo")); | ||
@@ -86,3 +98,8 @@ // Ouput: { name: "foo", value: "bar" } | ||
### `domCookie.getAll(): Cookie[]` | ||
Get all existing cookies. With the same limitations as the `domCookie.get(name: string)` function. | ||
```ts | ||
import { domCookie } from "cookie-muncher"; | ||
console.log(domCookie.getAll()); | ||
@@ -92,7 +109,59 @@ // Output: [{ name: "foo", value: "bar" }, { name: "bar", value: "foo" }] | ||
### `domCookie.remove(name: string, options?: DomCookieOptions): void` | ||
Delete a cookie, make sure to set the same `Domain` and `Path` of the cookie you wish to delete. | ||
```ts | ||
domCookie.remove("foo"); | ||
import { domCookie } from "cookie-muncher"; | ||
domCookie.remove("foo", { path: "/bar" }); | ||
``` | ||
### `HttpCookieOptions` & `DomCookieOptions` | ||
```ts | ||
import type { HttpCookieOptions, DomCookieOptions } from "cookie-muncher"; | ||
``` | ||
#### `domain` | ||
Indicates the input for the [Domain Set-Cookie attribute](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3). The cookie is typically applied to only the current domain when no domain is set by default, and this is recognized by most clients. | ||
#### `expires` | ||
Specifies the Date object to be used as the value for the [Expires Set-Cookie attribute](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.1). By default, the cookie has no expiration, which is recognized by most clients as a "non-persistent cookie" that gets deleted upon a certain condition, such as closing the web browser application. | ||
#### `maxAge` | ||
Specifies the `number` (in seconds) to be used as the value for the [Max-Age Set-Cookie attribute](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.2). The given number will be rounded down to an integer. By default, the cookie has no maximum age. | ||
According to the [cookie storage model specification](https://datatracker.ietf.org/doc/html/rfc6265#section-5.3), if both `expires` and `maxAge` are set, then `maxAge` takes precedence. However, it is possible that not all clients will obey this rule, so if both are set, they should point to the same date and time to ensure proper functionality. | ||
#### `httpOnly` | ||
Specifies the `boolean` value to be used for the [HttpOnly Set-Cookie attribute](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.6). The `HttpOnly` attribute is set if the value is truthy; otherwise, it is not set. By default, the `HttpOnly` attribute is not set. | ||
It's important to exercise caution when setting this attribute to `true` because compliant clients will restrict client-side JavaScript from accessing the cookie via `document.cookie`. | ||
It's worth noting that `HttpOnly` cookies are inaccessible to client-side JavaScript, which also means that you cannot create an `HttpOnly` cookie using client-side JavaScript. | ||
> **Info** | ||
> This parameter is disabled on the `DomCookieOptions` type. | ||
#### `path` | ||
Indicates the input for the [Path Set-Cookie attribute](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.4). By default, the path is set to the ["default-path"](https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4). | ||
#### `sameSite` | ||
Indicates the input for the [SameSite Set-Cookie attribute](tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7) : | ||
- lax: sets the `SameSite` attribute to `Lax` for lax same-site enforcement | ||
- none: sets the `SameSite` attribute to `None` for explicit cross-site cookies | ||
- strict: sets the `SameSite` attribute to `Strict` for strict same-site enforcement | ||
For more information about the different enforcement levels, refer to [the specification](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7). | ||
It's important to note that the SameSite attribute is not yet fully standardized and may change in the future. As a result, many clients may ignore this attribute until they understand it. | ||
#### `secure` | ||
Specifies the `boolean` value for the [Secure Set-Cookie attribute](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.5). When truthy, the `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set. | ||
Be careful when setting this to `true`, as compliant clients will not send the cookie back to the server in the future if the browser does not have an HTTPS connection. | ||
This method specifies the `boolean` value for the [Secure Set-Cookie attribute](https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.5). Setting it to `true` will enable the `Secure` attribute, which is not set by default. It's important to note that if you set this to `true`, the cookie will only be sent back to the server in the future if the browser has an HTTPS connection. Therefore, you should be careful when using this attribute to ensure that your application works as intended in all scenarios. | ||
## License | ||
This package is MIT licensed. |
import type { Cookie } from "#/typing/cookie"; | ||
import { parseCookies } from "#/http/parser"; | ||
import { isCookieEnabled } from "#/utils/cookie"; | ||
export function getAllCookies(): Cookie[] { | ||
isCookieEnabled(); | ||
return parseCookies(document.cookie); | ||
} |
import type { Cookie } from "#/typing/cookie"; | ||
import { parseCookies } from "#/http/parser"; | ||
import { isCookieEnabled } from "#/utils/cookie"; | ||
export function getCookie(name: string): Cookie | null { | ||
isCookieEnabled(); | ||
return parseCookies(document.cookie).find(cookie => cookie.name === name) || null; | ||
} |
@@ -1,8 +0,16 @@ | ||
import { CookieMaxAge, serializeCookie } from "#/http/serializer"; | ||
import type { RemoveDomCookieOptions } from "#/typing/cookie"; | ||
import { serializeCookie } from "#/http/serializer"; | ||
import { CookieMaxAge } from "#/utils/duration"; | ||
import { isCookieEnabled } from "#/utils/cookie"; | ||
export function removeCookie(name: string): void { | ||
export function removeCookie(name: string, options?: RemoveDomCookieOptions): void { | ||
isCookieEnabled(); | ||
document.cookie = serializeCookie( | ||
{ name, value: "" }, | ||
{ maxAge: CookieMaxAge.Now } | ||
{ | ||
...options, | ||
maxAge: CookieMaxAge.Now | ||
} | ||
); | ||
} |
import type { Cookie, DomCookieOptions } from "#/typing/cookie"; | ||
import { serializeCookie } from "#/http/serializer"; | ||
import { getByteSize } from "#/utils/string"; | ||
import { cookieMaxByteSize, isCookieEnabled } from "#/utils/cookie"; | ||
export function setCookie(cookie: Cookie, options: DomCookieOptions = {}): void { | ||
isCookieEnabled(); | ||
const cookiesCount = document.cookie.split(";").length; | ||
if (cookiesCount > 50) { | ||
throw Error("You have more than 50 cookies, most browsers limit the number of cookies to 50"); | ||
} | ||
const newCookie = serializeCookie(cookie, options); | ||
if (getByteSize(newCookie) > cookieMaxByteSize) { | ||
throw Error(`The size of this cookie is greater than ${cookieMaxByteSize} bytes, most browsers limit the number of cookies to this size.`); | ||
} | ||
document.cookie = serializeCookie(cookie, options); | ||
} |
@@ -7,4 +7,2 @@ import { parseCookies } from "./parser"; | ||
serialize: serializeCookie | ||
}; | ||
export { CookieMaxAge } from "./serializer"; | ||
}; |
@@ -12,3 +12,3 @@ import type { Cookie } from "#/typing/cookie"; | ||
const name = decodeURIComponent(rawName.trim()); | ||
const name = rawName.trim(); | ||
const value = decodeURIComponent(rawValue.trim()); | ||
@@ -15,0 +15,0 @@ |
@@ -7,3 +7,3 @@ import { describe, expect, it } from "vitest"; | ||
it("should return a string with only the cookie name and value when no options are provided", () => { | ||
expect(serializeCookie(cookie)).toBe(`${cookieString}; Path=/`); | ||
expect(serializeCookie(cookie)).toBe(`${cookieString}; Path=%2F`); | ||
}); | ||
@@ -16,3 +16,3 @@ | ||
expect(result).toBe(`${cookieString}; Max-Age=3600; Path=/`); | ||
expect(result).toBe(`${cookieString}; Max-Age=3600; Path=%2F`); | ||
}); | ||
@@ -25,3 +25,3 @@ | ||
expect(result).toBe(`${cookieString}; Expires=Sun, 23 Apr 2023 23:59:59 GMT; Path=/`); | ||
expect(result).toBe(`${cookieString}; Expires=Sun, 23 Apr 2023 23:59:59 GMT; Path=%2F`); | ||
}); | ||
@@ -34,3 +34,3 @@ | ||
expect(result).toBe(`${cookieString}; Domain=example.com; Path=/`); | ||
expect(result).toBe(`${cookieString}; Domain=example.com; Path=%2F`); | ||
}); | ||
@@ -43,3 +43,3 @@ | ||
expect(result).toBe(`${cookieString}; Path=/path`); | ||
expect(result).toBe(`${cookieString}; Path=%2Fpath`); | ||
}); | ||
@@ -52,3 +52,3 @@ | ||
expect(result).toBe(`${cookieString}; Path=/; Secure`); | ||
expect(result).toBe(`${cookieString}; Path=%2F; Secure`); | ||
}); | ||
@@ -61,3 +61,3 @@ | ||
expect(result).toBe(`${cookieString}; Path=/; HttpOnly`); | ||
expect(result).toBe(`${cookieString}; Path=%2F; HttpOnly`); | ||
}); | ||
@@ -67,6 +67,6 @@ | ||
const result = serializeCookie(cookie, { | ||
sameSite: "Lax" | ||
sameSite: "lax" | ||
}); | ||
expect(result).toBe(`${cookieString}; Path=/; SameSite=Lax`); | ||
expect(result).toBe(`${cookieString}; Path=%2F; SameSite=Lax`); | ||
}); | ||
@@ -82,3 +82,3 @@ | ||
httpOnly: true, | ||
sameSite: "Lax" | ||
sameSite: "lax" | ||
}); | ||
@@ -88,3 +88,3 @@ | ||
`${cookieString}; Max-Age=3600; Expires=Sun, 23 Apr 2023 23:59:59 GMT;`, | ||
"Domain=example.com; Path=/path; Secure; HttpOnly; SameSite=Lax" | ||
"Domain=example.com; Path=%2Fpath; Secure; HttpOnly; SameSite=Lax" | ||
].join(" "); | ||
@@ -91,0 +91,0 @@ |
@@ -1,2 +0,1 @@ | ||
export { serializeCookie } from "./serializer"; | ||
export { CookieMaxAge } from "./serializer.enum"; | ||
export { serializeCookie } from "./serializer"; |
import type { Cookie, HttpCookieOptions } from "#/typing/cookie"; | ||
import { capitalizeFirstLetter } from "#/utils/string"; | ||
@@ -7,13 +8,13 @@ /** | ||
export function serializeCookie(cookie: Cookie, options: HttpCookieOptions = {}): string { | ||
const parts: string[] = [`${cookie.name}=${cookie.value}`]; | ||
const parts: string[] = [`${cookie.name}=${encodeURIComponent(cookie.value)}`]; | ||
if (options.maxAge) parts.push(`Max-Age=${options.maxAge}`); | ||
if (options.expires) parts.push(`Expires=${options.expires.toUTCString()}`); | ||
if (options.domain) parts.push(`Domain=${options.domain}`); | ||
parts.push(`Path=${options.path || "/"}`); | ||
if (options.domain) parts.push(`Domain=${encodeURIComponent(options.domain)}`); | ||
parts.push(`Path=${encodeURIComponent(options.path || "/")}`); | ||
if (options.secure) parts.push("Secure"); | ||
if (options.httpOnly) parts.push("HttpOnly"); | ||
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`); | ||
if (options.sameSite) parts.push(`SameSite=${capitalizeFirstLetter(options.sameSite)}`); | ||
return parts.join("; "); | ||
} |
@@ -0,3 +1,9 @@ | ||
// HTTP & DOM modules: | ||
export * from "#/http"; | ||
export * from "#/dom"; | ||
// Utils: | ||
export * from "#/utils/duration"; | ||
// Types: | ||
export * from "#/typing/cookie"; |
@@ -13,3 +13,3 @@ export type Cookie = { | ||
* @property {boolean} httpOnly - Whether the cookie should only be accessible via HTTP | ||
* @property {"Strict" | "Lax" | "None"} sameSite - The same-site policy for the cookie | ||
* @property {"strict" | "lax" | "none"} sameSite - The same-site policy for the cookie | ||
*/ | ||
@@ -23,6 +23,8 @@ type CookieOptions = { | ||
httpOnly?: boolean; | ||
sameSite?: "Strict" | "Lax" | "None"; | ||
sameSite?: "strict" | "lax" | "none"; | ||
} | ||
export type DomCookieOptions = Omit<CookieOptions, "httpOnly">; | ||
export type HttpCookieOptions = CookieOptions; | ||
export type HttpCookieOptions = CookieOptions; | ||
export type RemoveDomCookieOptions = Pick<CookieOptions, "domain" | "path" | "sameSite">; |
import { defineConfig } from "vitest/config"; | ||
export default defineConfig({ | ||
test: { | ||
environment: "happy-dom" | ||
}, | ||
resolve: { | ||
@@ -8,0 +5,0 @@ alias: { |
Sorry, the diff of this file is not supported yet
28184
41.03%7
-12.5%35
9.38%350
11.82%164
72.63%