-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcookie.ts
More file actions
117 lines (108 loc) · 3.26 KB
/
Copy pathcookie.ts
File metadata and controls
117 lines (108 loc) · 3.26 KB
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
import {
boolean,
coerce,
date,
defined,
integer,
is,
nonempty,
number,
or,
positive,
string,
trim,
within,
} from './coerce.ts';
import { isDefinedTuple } from './misc.ts';
export interface CookieOptions {
expires?: ConstructorParameters<typeof Date>[0];
maxage?: number;
path?: string;
domain?: string;
samesite?: 'none' | 'lax' | 'strict';
secure?: boolean;
httponly?: boolean;
}
export const parse = (
cookieString: string,
decoder = decodeURIComponent,
): Record<string, string> => {
const sanitizer = coerce(decoder, trim, nonempty, or(undefined));
const splitPair = (pair: string) => {
const [key, value] = pair.split('=', 2);
return [sanitizer(key), sanitizer(value)] as const;
};
const pairs = cookieString.split(/; */)
.map(splitPair)
.filter(isDefinedTuple);
return Object.fromEntries(pairs);
};
export const stringify = (
key: string,
value: string | null,
options: CookieOptions & { encoder?: typeof encodeURIComponent } = {},
): string => {
const encoder = options.encoder || encodeURIComponent;
const keyEncoded = coerce(
string,
trim,
nonempty,
encoder,
or(new TypeError(`“${key}” invalid cookie key.`)),
)(key);
const valueEncoded = coerce(string, trim, encoder, or(''))(value);
// If `value` is anything other than a non-empty string, then this is a delete
// operation and we set the `expires` to 0.
let { expires } = options;
if (valueEncoded === '') {
expires = new Date(0).toUTCString();
} else if (is(defined)(expires)) {
expires = coerce(
date,
or(new TypeError(`“${options.expires}” invalid cookie expires.`)),
)(expires)
.toUTCString();
}
return [
[keyEncoded, valueEncoded] as const,
['expires', expires] as const,
['maxage', coerce(number, integer, positive, or(undefined))(options.maxage)] as const,
['domain', coerce(string, trim, nonempty, or(undefined))(options.domain)] as const,
['path', coerce(string, trim, nonempty, or(undefined))(options.path)] as const,
['samesite', coerce(within(['none', 'lax', 'strict']), or('none'))(options.samesite)] as const,
['secure', coerce(boolean(true, false, true, true))(options.secure)] as const,
['httponly', coerce(boolean(true, false, false, false))(options.httponly)] as const,
]
.map(([k, v]) => {
if (typeof v === 'string') {
return `${k}=${v}`;
}
if (v === true) {
return k;
}
return undefined;
})
.filter(is(defined))
.join(';');
};
export const getDomCookies = (
cookieString = document.cookie,
decoder = decodeURIComponent,
): Record<string, string> => parse(cookieString, decoder);
export const setDomCookie = (...args: Parameters<typeof stringify>): void => {
document.cookie = stringify(...args);
};
/**
* Parse request cookies and as an object. Optionally, provide a custom decoder
* (decodeURIComponent is the default).
*/
export const getRequestCookies = (
request: Request,
decoder = decodeURIComponent,
): Record<string, string> => parse(coerce(string, or(''))(request.headers.get('cookie')), decoder);
export const setResponseCookie = (
response: Response,
...args: Parameters<typeof stringify>
): void => {
response.headers.append('set-cookie', stringify(...args));
};