@remix-run/headers
Version:
A toolkit for working with HTTP headers in JavaScript
207 lines (196 loc) • 5.97 kB
text/typescript
import { type HeaderValue } from './header-value.ts'
import { parseParams, quote } from './param-values.ts'
import { capitalize, isValidDate } from './utils.ts'
type SameSiteValue = 'Strict' | 'Lax' | 'None'
/**
* Properties for a `Set-Cookie` header value.
*/
export interface CookieProperties {
/**
* The domain of the cookie. For example, `example.com`.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#domaindomain-value)
*/
domain?: string
/**
* The expiration date of the cookie. If not specified, the cookie is a session cookie that is
* removed when the browser is closed.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#expiresdate)
*/
expires?: Date
/**
* Indicates this cookie should not be accessible via JavaScript.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#httponly)
*/
httpOnly?: boolean
/**
* The maximum age of the cookie in seconds.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#max-age)
*/
maxAge?: number
/**
* Indicates this cookie is a partitioned cookie.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#partitioned)
*/
partitioned?: boolean
/**
* The path of the cookie. For example, `/` or `/admin`.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value)
*/
path?: string
/**
* The `SameSite` attribute of the cookie. This attribute lets servers require that a cookie shouldn't be sent with
* cross-site requests, which provides some protection against cross-site request forgery attacks.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value)
*/
sameSite?: SameSiteValue
/**
* Indicates the cookie should only be sent over HTTPS.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#secure)
*/
secure?: boolean
}
/**
* Initializer for a `Set-Cookie` header value.
*/
export interface SetCookieInit extends CookieProperties {
/**
* The name of the cookie.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie-namecookie-value)
*/
name?: string
/**
* The value of the cookie.
*
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie-namecookie-value)
*/
value?: string
}
/**
* The value of a `Set-Cookie` HTTP header.
*
* [MDN `Set-Cookie` Reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie)
*
* [HTTP/1.1 Specification](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1)
*/
export class SetCookie implements HeaderValue, SetCookieInit {
domain?: string
expires?: Date
httpOnly?: boolean
maxAge?: number
name?: string
partitioned?: boolean
path?: string
sameSite?: SameSiteValue
secure?: boolean
value?: string
/**
* @param init A string or object to initialize the header
*/
constructor(init?: string | SetCookieInit) {
if (init) {
if (typeof init === 'string') {
let params = parseParams(init)
if (params.length > 0) {
this.name = params[0][0]
this.value = params[0][1]
for (let [key, value] of params.slice(1)) {
switch (key.toLowerCase()) {
case 'domain':
this.domain = value
break
case 'expires': {
if (typeof value === 'string') {
let date = new Date(value)
if (isValidDate(date)) {
this.expires = date
}
}
break
}
case 'httponly':
this.httpOnly = true
break
case 'max-age': {
if (typeof value === 'string') {
let v = parseInt(value, 10)
if (!isNaN(v)) this.maxAge = v
}
break
}
case 'partitioned':
this.partitioned = true
break
case 'path':
this.path = value
break
case 'samesite':
if (typeof value === 'string' && /strict|lax|none/i.test(value)) {
this.sameSite = capitalize(value) as SameSiteValue
}
break
case 'secure':
this.secure = true
break
}
}
}
} else {
this.domain = init.domain
this.expires = init.expires
this.httpOnly = init.httpOnly
this.maxAge = init.maxAge
this.name = init.name
this.partitioned = init.partitioned
this.path = init.path
this.sameSite = init.sameSite
this.secure = init.secure
this.value = init.value
}
}
}
/**
* Returns the string representation of the header value.
*
* @return The header value as a string
*/
toString(): string {
if (!this.name) {
return ''
}
let parts = [`${this.name}=${quote(this.value || '')}`]
if (this.domain) {
parts.push(`Domain=${this.domain}`)
}
if (this.expires) {
parts.push(`Expires=${this.expires.toUTCString()}`)
}
if (this.httpOnly) {
parts.push('HttpOnly')
}
if (this.maxAge != null) {
parts.push(`Max-Age=${this.maxAge}`)
}
if (this.partitioned) {
parts.push('Partitioned')
}
if (this.path) {
parts.push(`Path=${this.path}`)
}
if (this.sameSite) {
parts.push(`SameSite=${this.sameSite}`)
}
if (this.secure) {
parts.push('Secure')
}
return parts.join('; ')
}
}