-
Notifications
You must be signed in to change notification settings - Fork 78
/
response-cookies.ts
126 lines (108 loc) · 3.51 KB
/
response-cookies.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import type { ResponseCookie } from './types'
import {
parseSetCookie,
splitCookiesString,
stringifyCookie,
} from './serialize'
/**
* A class for manipulating {@link Response} cookies (`Set-Cookie` header).
* Loose implementation of the experimental [Cookie Store API](https://wicg.github.io/cookie-store/#dictdef-cookie)
* The main difference is `ResponseCookies` methods do not return a Promise.
*/
export class ResponseCookies {
/** @internal */
readonly _headers: Headers
/** @internal */
_parsed: Map<string, ResponseCookie> = new Map()
constructor(responseHeaders: Headers) {
this._headers = responseHeaders
const setCookie =
responseHeaders.getSetCookie?.() ??
responseHeaders.get('set-cookie') ??
[]
const cookieStrings = Array.isArray(setCookie)
? setCookie
: splitCookiesString(setCookie)
for (const cookieString of cookieStrings) {
const parsed = parseSetCookie(cookieString)
if (parsed) this._parsed.set(parsed.name, parsed)
}
}
/**
* {@link https://wicg.github.io/cookie-store/#CookieStore-get CookieStore#get} without the Promise.
*/
get(
...args: [key: string] | [options: ResponseCookie]
): ResponseCookie | undefined {
const key = typeof args[0] === 'string' ? args[0] : args[0].name
return this._parsed.get(key)
}
/**
* {@link https://wicg.github.io/cookie-store/#CookieStore-getAll CookieStore#getAll} without the Promise.
*/
getAll(
...args: [key: string] | [options: ResponseCookie] | []
): ResponseCookie[] {
const all = Array.from(this._parsed.values())
if (!args.length) {
return all
}
const key = typeof args[0] === 'string' ? args[0] : args[0]?.name
return all.filter((c) => c.name === key)
}
has(name: string) {
return this._parsed.has(name)
}
/**
* {@link https://wicg.github.io/cookie-store/#CookieStore-set CookieStore#set} without the Promise.
*/
set(
...args:
| [key: string, value: string, cookie?: Partial<ResponseCookie>]
| [options: ResponseCookie]
): this {
const [name, value, cookie] =
args.length === 1 ? [args[0].name, args[0].value, args[0]] : args
const map = this._parsed
map.set(name, normalizeCookie({ name, value, ...cookie }))
replace(map, this._headers)
return this
}
/**
* {@link https://wicg.github.io/cookie-store/#CookieStore-delete CookieStore#delete} without the Promise.
*/
delete(
...args:
| [key: string]
| [options: Omit<ResponseCookie, 'value' | 'expires'>]
): this {
const [name, options] =
typeof args[0] === 'string' ? [args[0]] : [args[0].name, args[0]]
return this.set({ ...options, name, value: '', expires: new Date(0) })
}
[Symbol.for('edge-runtime.inspect.custom')]() {
return `ResponseCookies ${JSON.stringify(Object.fromEntries(this._parsed))}`
}
toString() {
return [...this._parsed.values()].map(stringifyCookie).join('; ')
}
}
function replace(bag: Map<string, ResponseCookie>, headers: Headers) {
headers.delete('set-cookie')
for (const [, value] of bag) {
const serialized = stringifyCookie(value)
headers.append('set-cookie', serialized)
}
}
function normalizeCookie(cookie: ResponseCookie = { name: '', value: '' }) {
if (typeof cookie.expires === 'number') {
cookie.expires = new Date(cookie.expires)
}
if (cookie.maxAge) {
cookie.expires = new Date(Date.now() + cookie.maxAge * 1000)
}
if (cookie.path === null || cookie.path === undefined) {
cookie.path = '/'
}
return cookie
}