-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcookie.ts
117 lines (107 loc) · 3.38 KB
/
cookie.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
import {
boolean,
coerce,
date,
defined,
integer,
is,
nonempty,
number,
or,
positive,
string,
trim,
within,
// @ts-ignore tsc non-sense
} from './coerce.ts';
// @ts-ignore tsc non-sense
import { isDefinedTuple } from './misc.ts';
declare global {
interface document {
cookie: string;
}
}
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) => {
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 } = {},
) => {
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(';');
};
// @ts-ignore TODO reference global document in Deno
export const getDomCookies = (cookieString = document.cookie, decoder = decodeURIComponent) =>
parse(cookieString, decoder);
export const setDomCookie = (...args: Parameters<typeof stringify>) => {
// @ts-ignore TODO reference global document in Deno
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) =>
parse(coerce(string, or(''))(request.headers.get('cookie')), decoder);
export const setResponseCookie = (response: Response, ...args: Parameters<typeof stringify>) => {
response.headers.append('set-cookie', stringify(...args));
};