@travetto/web 6.0.0-rc.2 → 6.0.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -12
- package/__index__.ts +1 -1
- package/package.json +2 -2
- package/src/interceptor/{cookies.ts → cookie.ts} +11 -6
- package/src/types/cookie.ts +1 -1
- package/src/types/request.ts +3 -1
- package/src/util/cookie.ts +106 -88
package/README.md
CHANGED
|
@@ -20,7 +20,6 @@ The module provides a declarative API for creating and describing a Web applicat
|
|
|
20
20
|
* Using [WebInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/types/interceptor.ts#L15)s
|
|
21
21
|
* Creating a Custom [WebInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/types/interceptor.ts#L15)
|
|
22
22
|
* Cookies
|
|
23
|
-
* SSL Support
|
|
24
23
|
* Error Handling
|
|
25
24
|
|
|
26
25
|
## Request/Response Pattern
|
|
@@ -38,18 +37,53 @@ export class BaseWebMessage<B = unknown, C = unknown> implements WebMessage<B, C
|
|
|
38
37
|
|
|
39
38
|
**Code: Request Shape**
|
|
40
39
|
```typescript
|
|
40
|
+
import { HttpMethod, HttpProtocol } from './core.ts';
|
|
41
|
+
import { BaseWebMessage } from './message.ts';
|
|
42
|
+
|
|
43
|
+
export interface WebConnection {
|
|
44
|
+
host?: string;
|
|
45
|
+
port?: number;
|
|
46
|
+
ip?: string;
|
|
47
|
+
httpProtocol?: HttpProtocol;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface WebRequestContext {
|
|
51
|
+
path: string;
|
|
52
|
+
pathParams?: Record<string, unknown>;
|
|
53
|
+
httpQuery?: Record<string, unknown>;
|
|
54
|
+
httpMethod?: HttpMethod;
|
|
55
|
+
connection?: WebConnection;
|
|
56
|
+
}
|
|
41
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Web Request object
|
|
60
|
+
*/
|
|
61
|
+
export class WebRequest<B = unknown> extends BaseWebMessage<B, Readonly<WebRequestContext>> {
|
|
62
|
+
|
|
63
|
+
}
|
|
42
64
|
```
|
|
43
65
|
|
|
44
66
|
**Code: Response Shape**
|
|
45
67
|
```typescript
|
|
68
|
+
import { BaseWebMessage } from './message.ts';
|
|
69
|
+
|
|
70
|
+
export interface WebResponseContext {
|
|
71
|
+
httpStatusCode?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Web Response as a simple object
|
|
76
|
+
*/
|
|
46
77
|
export class WebResponse<B = unknown> extends BaseWebMessage<B, WebResponseContext> {
|
|
78
|
+
|
|
47
79
|
/**
|
|
48
80
|
* Build the redirect
|
|
49
81
|
* @param location Location to redirect to
|
|
50
82
|
* @param statusCode Status code
|
|
51
83
|
*/
|
|
52
|
-
static redirect(location: string, statusCode = 302): WebResponse<undefined
|
|
84
|
+
static redirect(location: string, statusCode = 302): WebResponse<undefined> {
|
|
85
|
+
return new WebResponse({ context: { httpStatusCode: statusCode }, headers: { Location: location } });
|
|
86
|
+
}
|
|
53
87
|
}
|
|
54
88
|
```
|
|
55
89
|
|
|
@@ -60,6 +94,7 @@ To start, we must define a [@Controller](https://github.com/travetto/travetto/tr
|
|
|
60
94
|
* `path` - The required context path the controller will operate atop
|
|
61
95
|
* `title` - The definition of the controller
|
|
62
96
|
* `description` - High level description fo the controller
|
|
97
|
+
|
|
63
98
|
Additionally, the module is predicated upon [Dependency Injection](https://github.com/travetto/travetto/tree/main/module/di#readme "Dependency registration/management and injection support."), and so all standard injection techniques (constructor, fields) work for registering dependencies.
|
|
64
99
|
|
|
65
100
|
[JSDoc](http://usejsdoc.org/about-getting-started.html) comments can also be used to define the `title` attribute.
|
|
@@ -85,9 +120,11 @@ The most common pattern is to register HTTP-driven endpoints. The HTTP methods
|
|
|
85
120
|
* [@Patch](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L64)
|
|
86
121
|
* [@Head](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L76)
|
|
87
122
|
* [@Options](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/endpoint.ts#L82)
|
|
123
|
+
|
|
88
124
|
Similar to the Controller, each endpoint decorator handles the following config:
|
|
89
125
|
* `title` - The definition of the endpoint
|
|
90
126
|
* `description` - High level description fo the endpoint
|
|
127
|
+
|
|
91
128
|
[JSDoc](http://usejsdoc.org/about-getting-started.html) comments can also be used to define the `title` attribute, as well as describing the parameters using `@param` tags in the comment.
|
|
92
129
|
|
|
93
130
|
The return type of the method will also be used to describe the `responseType` if not specified manually.
|
|
@@ -121,11 +158,13 @@ Endpoints can be configured to describe and enforce parameter behavior. Request
|
|
|
121
158
|
* [@QueryParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L43) - Query params - can be either a single value or bind to a whole object
|
|
122
159
|
* [@Body](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L55) - Request body
|
|
123
160
|
* [@HeaderParam](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L49) - Header values
|
|
161
|
+
|
|
124
162
|
Each [@Param](https://github.com/travetto/travetto/tree/main/module/web/src/decorator/param.ts#L24) can be configured to indicate:
|
|
125
163
|
* `name` - Name of param, field name, defaults to handler parameter name if necessary
|
|
126
164
|
* `description` - Description of param, pulled from [JSDoc](http://usejsdoc.org/about-getting-started.html), or defaults to name if empty
|
|
127
165
|
* `required?` - Is the field required?, defaults to whether or not the parameter itself is optional
|
|
128
166
|
* `type` - The class of the type to be enforced, pulled from parameter type
|
|
167
|
+
|
|
129
168
|
[JSDoc](http://usejsdoc.org/about-getting-started.html) comments can also be used to describe parameters using `@param` tags in the comment.
|
|
130
169
|
|
|
131
170
|
**Code: Full-fledged Controller with Endpoints**
|
|
@@ -720,15 +759,5 @@ export class SimpleEndpoints {
|
|
|
720
759
|
}
|
|
721
760
|
```
|
|
722
761
|
|
|
723
|
-
## SSL Support
|
|
724
|
-
Additionally the framework supports SSL out of the box, by allowing you to specify your public and private keys for the cert. In dev mode, the framework will also automatically generate a self-signed cert if:
|
|
725
|
-
* SSL support is configured
|
|
726
|
-
* [node-forge](https://www.npmjs.com/package/node-forge) is installed
|
|
727
|
-
* Not running in prod
|
|
728
|
-
* No keys provided
|
|
729
|
-
This is useful for local development where you implicitly trust the cert.
|
|
730
|
-
|
|
731
|
-
SSL support can be enabled by setting `web.ssl.active: true` in your config. The key/cert can be specified as string directly in the config file/environment variables. The key/cert can also be specified as a path to be picked up by [RuntimeResources](https://github.com/travetto/travetto/tree/main/module/runtime/src/resources.ts#L8).
|
|
732
|
-
|
|
733
762
|
## Full Config
|
|
734
763
|
The entire [WebConfig](https://github.com/travetto/travetto/tree/main/module/web/src/config.ts#L7) which will show the full set of valid configuration parameters for the web module.
|
package/__index__.ts
CHANGED
|
@@ -26,7 +26,7 @@ export * from './src/registry/types.ts';
|
|
|
26
26
|
export * from './src/interceptor/accept.ts';
|
|
27
27
|
export * from './src/interceptor/body-parse.ts';
|
|
28
28
|
export * from './src/interceptor/cors.ts';
|
|
29
|
-
export * from './src/interceptor/
|
|
29
|
+
export * from './src/interceptor/cookie.ts';
|
|
30
30
|
export * from './src/interceptor/compress.ts';
|
|
31
31
|
export * from './src/interceptor/context.ts';
|
|
32
32
|
export * from './src/interceptor/decompress.ts';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/web",
|
|
3
|
-
"version": "6.0.0-rc.
|
|
3
|
+
"version": "6.0.0-rc.4",
|
|
4
4
|
"description": "Declarative api for Web Applications with support for the dependency injection.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"negotiator": "^1.0.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@travetto/cli": "^6.0.0-rc.
|
|
45
|
+
"@travetto/cli": "^6.0.0-rc.3",
|
|
46
46
|
"@travetto/test": "^6.0.0-rc.2",
|
|
47
47
|
"@travetto/transformer": "^6.0.0-rc.3"
|
|
48
48
|
},
|
|
@@ -25,7 +25,7 @@ export class CookieConfig implements CookieSetOptions {
|
|
|
25
25
|
/**
|
|
26
26
|
* Are they signed
|
|
27
27
|
*/
|
|
28
|
-
signed
|
|
28
|
+
signed?: boolean;
|
|
29
29
|
/**
|
|
30
30
|
* Supported only via http (not in JS)
|
|
31
31
|
*/
|
|
@@ -42,18 +42,22 @@ export class CookieConfig implements CookieSetOptions {
|
|
|
42
42
|
/**
|
|
43
43
|
* Is the cookie only valid for https
|
|
44
44
|
*/
|
|
45
|
-
secure?: boolean
|
|
45
|
+
secure?: boolean;
|
|
46
46
|
/**
|
|
47
47
|
* The domain of the cookie
|
|
48
48
|
*/
|
|
49
49
|
domain?: string;
|
|
50
|
+
/**
|
|
51
|
+
* The default path of the cookie
|
|
52
|
+
*/
|
|
53
|
+
path: string = '/';
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
/**
|
|
53
57
|
* Loads cookies from the request, verifies, exposes, and then signs and sets
|
|
54
58
|
*/
|
|
55
59
|
@Injectable()
|
|
56
|
-
export class
|
|
60
|
+
export class CookieInterceptor implements WebInterceptor<CookieConfig> {
|
|
57
61
|
|
|
58
62
|
#cookieJar = new AsyncContextValue<CookieJar>(this);
|
|
59
63
|
|
|
@@ -77,8 +81,9 @@ export class CookiesInterceptor implements WebInterceptor<CookieConfig> {
|
|
|
77
81
|
|
|
78
82
|
finalizeConfig({ config }: WebInterceptorContext<CookieConfig>): CookieConfig {
|
|
79
83
|
const url = new URL(this.webConfig.baseUrl ?? 'x://localhost');
|
|
80
|
-
config.secure ??= url.protocol === 'https';
|
|
84
|
+
config.secure ??= url.protocol === 'https:';
|
|
81
85
|
config.domain ??= url.hostname;
|
|
86
|
+
config.signed ??= !!config.keys?.length;
|
|
82
87
|
return config;
|
|
83
88
|
}
|
|
84
89
|
|
|
@@ -87,11 +92,11 @@ export class CookiesInterceptor implements WebInterceptor<CookieConfig> {
|
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
async filter({ request, config, next }: WebChainedContext<CookieConfig>): Promise<WebResponse> {
|
|
90
|
-
const jar = new CookieJar(request.headers.get('Cookie')
|
|
95
|
+
const jar = new CookieJar(config).importCookieHeader(request.headers.get('Cookie'));
|
|
91
96
|
this.#cookieJar.set(jar);
|
|
92
97
|
|
|
93
98
|
const response = await next();
|
|
94
|
-
for (const c of jar.
|
|
99
|
+
for (const c of jar.exportSetCookieHeader()) { response.headers.append('Set-Cookie', c); }
|
|
95
100
|
return response;
|
|
96
101
|
}
|
|
97
102
|
}
|
package/src/types/cookie.ts
CHANGED
package/src/types/request.ts
CHANGED
|
@@ -19,4 +19,6 @@ export interface WebRequestContext {
|
|
|
19
19
|
/**
|
|
20
20
|
* Web Request object
|
|
21
21
|
*/
|
|
22
|
-
export class WebRequest<B = unknown> extends BaseWebMessage<B, Readonly<WebRequestContext>> {
|
|
22
|
+
export class WebRequest<B = unknown> extends BaseWebMessage<B, Readonly<WebRequestContext>> {
|
|
23
|
+
|
|
24
|
+
}
|
package/src/util/cookie.ts
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
import keygrip from 'keygrip';
|
|
2
2
|
import { AppError, castKey, castTo } from '@travetto/runtime';
|
|
3
3
|
|
|
4
|
-
import { Cookie, CookieGetOptions } from '../types/cookie.ts';
|
|
4
|
+
import { Cookie, CookieGetOptions, CookieSetOptions } from '../types/cookie.ts';
|
|
5
5
|
|
|
6
6
|
const pairText = (c: Cookie): string => `${c.name}=${c.value}`;
|
|
7
7
|
const pair = (k: string, v: unknown): string => `${k}=${v}`;
|
|
8
8
|
|
|
9
|
+
type CookieJarOptions = { keys?: string[] } & CookieSetOptions;
|
|
10
|
+
|
|
9
11
|
export class CookieJar {
|
|
10
12
|
|
|
11
|
-
static
|
|
13
|
+
static parseCookieHeader(header: string): Cookie[] {
|
|
14
|
+
return header.split(/\s{0,4};\s{0,4}/g)
|
|
15
|
+
.map(x => x.trim())
|
|
16
|
+
.filter(x => !!x)
|
|
17
|
+
.map(item => {
|
|
18
|
+
const kv = item.split(/\s{0,4}=\s{0,4}/);
|
|
19
|
+
return { name: kv[0], value: kv[1] };
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static parseSetCookieHeader(header: string): Cookie {
|
|
12
24
|
const parts = header.split(/\s{0,4};\s{0,4}/g);
|
|
13
25
|
const [name, value] = parts[0].split(/\s{0,4}=\s{0,4}/);
|
|
14
26
|
const c: Cookie = { name, value };
|
|
@@ -27,119 +39,125 @@ export class CookieJar {
|
|
|
27
39
|
return c;
|
|
28
40
|
}
|
|
29
41
|
|
|
30
|
-
static
|
|
31
|
-
const
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (c.path) { header.push(pair('path', c.path)); }
|
|
42
|
-
if (c.expires) { header.push(pair('expires', c.expires.toUTCString())); }
|
|
43
|
-
if (c.domain) { header.push(pair('domain', c.domain)); }
|
|
44
|
-
if (c.priority) { header.push(pair('priority', c.priority.toLowerCase())); }
|
|
45
|
-
if (c.sameSite) { header.push(pair('samesite', c.sameSite.toLowerCase())); }
|
|
46
|
-
if (c.secure) { header.push('secure'); }
|
|
47
|
-
if (c.httpOnly) { header.push('httponly'); }
|
|
48
|
-
if (c.partitioned) { header.push('partitioned'); }
|
|
49
|
-
}
|
|
50
|
-
return header.join(';');
|
|
42
|
+
static responseSuffix(c: Cookie): string[] {
|
|
43
|
+
const parts = [];
|
|
44
|
+
if (c.path) { parts.push(pair('path', c.path)); }
|
|
45
|
+
if (c.expires) { parts.push(pair('expires', c.expires.toUTCString())); }
|
|
46
|
+
if (c.domain) { parts.push(pair('domain', c.domain)); }
|
|
47
|
+
if (c.priority) { parts.push(pair('priority', c.priority.toLowerCase())); }
|
|
48
|
+
if (c.sameSite) { parts.push(pair('samesite', c.sameSite.toLowerCase())); }
|
|
49
|
+
if (c.secure) { parts.push('secure'); }
|
|
50
|
+
if (c.httpOnly) { parts.push('httponly'); }
|
|
51
|
+
if (c.partitioned) { parts.push('partitioned'); }
|
|
52
|
+
return parts;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
#secure?: boolean;
|
|
54
55
|
#grip?: keygrip;
|
|
55
56
|
#cookies: Record<string, Cookie> = {};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
57
|
+
#setOptions: CookieSetOptions = {};
|
|
58
|
+
#deleteOptions: CookieSetOptions = { maxAge: 0, expires: undefined };
|
|
59
|
+
|
|
60
|
+
constructor({ keys, ...options }: CookieJarOptions = {}) {
|
|
61
|
+
this.#grip = keys?.length ? new keygrip(keys) : undefined;
|
|
62
|
+
this.#setOptions = {
|
|
63
|
+
secure: false,
|
|
64
|
+
path: '/',
|
|
65
|
+
signed: !!keys?.length,
|
|
66
|
+
...options,
|
|
67
|
+
};
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
#
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
c.signed = index >= 0;
|
|
77
|
-
sc.signed = false;
|
|
78
|
-
sc.secure = c.secure;
|
|
79
|
-
|
|
80
|
-
if (index >= 1) {
|
|
81
|
-
sc.value = this.#grip.sign(key);
|
|
82
|
-
sc.response = true;
|
|
83
|
-
return sc;
|
|
70
|
+
#exportCookie(cookie: Cookie, response?: boolean): string[] {
|
|
71
|
+
const suffix = response ? CookieJar.responseSuffix(cookie) : null;
|
|
72
|
+
const payload = pairText(cookie);
|
|
73
|
+
const out = suffix ? [[payload, ...suffix].join(';')] : [payload];
|
|
74
|
+
if (cookie.signed) {
|
|
75
|
+
const sigPair = pair(`${cookie.name}.sig`, this.#grip!.sign(payload));
|
|
76
|
+
out.push(suffix ? [sigPair, ...suffix].join(';') : sigPair);
|
|
84
77
|
}
|
|
78
|
+
return out;
|
|
85
79
|
}
|
|
86
80
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
81
|
+
import(cookies: Cookie[]): this {
|
|
82
|
+
const signatures: Record<string, string> = {};
|
|
83
|
+
for (const cookie of cookies) {
|
|
84
|
+
if (this.#setOptions.signed && cookie.name.endsWith('.sig')) {
|
|
85
|
+
signatures[cookie.name.replace(/[.]sig$/, '')] = cookie.value!;
|
|
86
|
+
} else {
|
|
87
|
+
this.#cookies[cookie.name] = { signed: false, ...cookie };
|
|
88
|
+
}
|
|
92
89
|
}
|
|
93
|
-
return { ...c, name: `${c.name}.sig`, value: this.#grip.sign(pairText(c)) };
|
|
94
|
-
}
|
|
95
90
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.#cookies[c.name] = c;
|
|
101
|
-
if (this.#grip && !c.name.endsWith('.sig')) {
|
|
102
|
-
toCheck.push(c);
|
|
91
|
+
for (const [name, value] of Object.entries(signatures)) {
|
|
92
|
+
const cookie = this.#cookies[name];
|
|
93
|
+
if (!cookie) {
|
|
94
|
+
continue;
|
|
103
95
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
96
|
+
cookie.signed = true;
|
|
97
|
+
|
|
98
|
+
const computed = pairText(cookie);
|
|
99
|
+
const index = this.#grip!.index(computed, value);
|
|
100
|
+
|
|
101
|
+
if (index < 0) {
|
|
102
|
+
delete this.#cookies[name];
|
|
103
|
+
} else if (index >= 1) {
|
|
104
|
+
cookie.response = true;
|
|
109
105
|
}
|
|
110
106
|
}
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
has(name: string, opts: CookieGetOptions = {}): boolean {
|
|
111
|
+
const needSigned = opts.signed ?? this.#setOptions.signed;
|
|
112
|
+
return name in this.#cookies && this.#cookies[name].signed === needSigned;
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
get(name: string, opts: CookieGetOptions = {}): string | undefined {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
if (this.has(name, opts)) {
|
|
117
|
+
return this.#cookies[name]?.value;
|
|
118
|
+
}
|
|
116
119
|
}
|
|
117
120
|
|
|
118
|
-
set(
|
|
119
|
-
this.#cookies[
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
set(cookie: Cookie): void {
|
|
122
|
+
const alias = this.#cookies[cookie.name] = {
|
|
123
|
+
...this.#setOptions,
|
|
124
|
+
...cookie,
|
|
125
|
+
response: true,
|
|
126
|
+
...(cookie.value === null || cookie.value === undefined) ? this.#deleteOptions : {},
|
|
127
|
+
};
|
|
123
128
|
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
c.expires = undefined;
|
|
129
|
+
if (!this.#setOptions.secure && alias.secure) {
|
|
130
|
+
throw new AppError('Cannot send secure cookie over unencrypted connection');
|
|
127
131
|
}
|
|
128
132
|
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
this.#cookies[sc.name] = sc;
|
|
132
|
-
sc.response = true;
|
|
133
|
+
if (alias.signed && !this.#grip) {
|
|
134
|
+
throw new AppError('Signing keys required for signed cookies');
|
|
133
135
|
}
|
|
134
|
-
}
|
|
135
136
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
.map(c => CookieJar.toHeaderValue(c, response));
|
|
137
|
+
if (alias.maxAge !== undefined && !alias.expires) {
|
|
138
|
+
alias.expires = new Date(Date.now() + alias.maxAge);
|
|
139
|
+
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
getAll(): Cookie[] {
|
|
143
143
|
return Object.values(this.#cookies);
|
|
144
144
|
}
|
|
145
|
+
|
|
146
|
+
importCookieHeader(header: string | null | undefined): this {
|
|
147
|
+
return this.import(CookieJar.parseCookieHeader(header ?? ''));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
importSetCookieHeader(headers: string[] | null | undefined): this {
|
|
151
|
+
return this.import(headers?.map(CookieJar.parseSetCookieHeader) ?? []);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
exportCookieHeader(): string {
|
|
155
|
+
return this.getAll().flatMap(c => this.#exportCookie(c)).join('; ');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
exportSetCookieHeader(): string[] {
|
|
159
|
+
return this.getAll()
|
|
160
|
+
.filter(x => x.response)
|
|
161
|
+
.flatMap(c => this.#exportCookie(c, true));
|
|
162
|
+
}
|
|
145
163
|
}
|