@travetto/web 7.0.0-rc.2 → 7.0.0-rc.3
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 +4 -4
- package/package.json +10 -10
- package/src/decorator/common.ts +4 -4
- package/src/decorator/endpoint.ts +5 -5
- package/src/decorator/param.ts +3 -3
- package/src/interceptor/accept.ts +5 -5
- package/src/interceptor/body.ts +2 -2
- package/src/interceptor/compress.ts +2 -2
- package/src/interceptor/cookie.ts +1 -1
- package/src/interceptor/cors.ts +4 -4
- package/src/interceptor/logging.ts +4 -4
- package/src/interceptor/respond.ts +5 -5
- package/src/registry/registry-adapter.ts +26 -26
- package/src/registry/registry-index.ts +14 -14
- package/src/registry/types.ts +6 -6
- package/src/router/base.ts +17 -17
- package/src/router/standard.ts +6 -6
- package/src/types/core.ts +2 -2
- package/src/types/headers.ts +16 -16
- package/src/types/message.ts +4 -4
- package/src/util/body.ts +19 -19
- package/src/util/common.ts +20 -20
- package/src/util/cookie.ts +9 -9
- package/src/util/endpoint.ts +51 -52
- package/src/util/header.ts +40 -37
- package/src/util/keygrip.ts +1 -1
- package/src/util/net.ts +15 -15
- package/support/test/suite/base.ts +2 -2
package/src/util/header.ts
CHANGED
|
@@ -19,8 +19,8 @@ export class WebHeaderUtil {
|
|
|
19
19
|
* Parse cookie header
|
|
20
20
|
*/
|
|
21
21
|
static parseCookieHeader(header: string): Cookie[] {
|
|
22
|
-
const
|
|
23
|
-
return !
|
|
22
|
+
const text = header.trim();
|
|
23
|
+
return !text ? [] : text.split(SPLIT_SEMI).map(item => {
|
|
24
24
|
const [name, value] = item.split(SPLIT_EQ);
|
|
25
25
|
return { name, value };
|
|
26
26
|
});
|
|
@@ -32,17 +32,17 @@ export class WebHeaderUtil {
|
|
|
32
32
|
static parseSetCookieHeader(header: string): Cookie {
|
|
33
33
|
const parts = header.split(SPLIT_SEMI);
|
|
34
34
|
const [name, value] = parts[0].split(SPLIT_EQ);
|
|
35
|
-
const
|
|
36
|
-
for (const
|
|
37
|
-
const [
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
-
|
|
35
|
+
const result: Cookie = { name, value };
|
|
36
|
+
for (const part of parts.slice(1)) {
|
|
37
|
+
const [key, partValue = ''] = part.toLowerCase().split(SPLIT_EQ);
|
|
38
|
+
const cleanedValue = partValue.charCodeAt(0) === QUOTE ? partValue.slice(1, -1) : partValue;
|
|
39
|
+
if (key === 'expires') {
|
|
40
|
+
result[key] = new Date(cleanedValue);
|
|
41
41
|
} else {
|
|
42
|
-
|
|
42
|
+
result[castKey(key)] = castTo(cleanedValue || true);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
return
|
|
45
|
+
return result;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
@@ -53,20 +53,20 @@ export class WebHeaderUtil {
|
|
|
53
53
|
if (!input) {
|
|
54
54
|
return { value: '', parameters: {} };
|
|
55
55
|
}
|
|
56
|
-
const [
|
|
56
|
+
const [rawValue, ...parts] = input.split(SPLIT_SEMI);
|
|
57
57
|
const item: WebParsedHeader = { value: '', parameters: {} };
|
|
58
|
-
const value =
|
|
58
|
+
const value = rawValue.charCodeAt(0) === QUOTE ? rawValue.slice(1, -1) : rawValue;
|
|
59
59
|
if (value.includes('=')) {
|
|
60
60
|
parts.unshift(value);
|
|
61
61
|
} else {
|
|
62
62
|
item.value = value;
|
|
63
63
|
}
|
|
64
64
|
for (const part of parts) {
|
|
65
|
-
const [
|
|
66
|
-
const
|
|
67
|
-
item.parameters[
|
|
68
|
-
if (
|
|
69
|
-
item.q = parseFloat(
|
|
65
|
+
const [key, partValue = ''] = part.split(SPLIT_EQ);
|
|
66
|
+
const cleanedValue = (partValue.charCodeAt(0) === QUOTE) ? partValue.slice(1, -1) : partValue;
|
|
67
|
+
item.parameters[key] = cleanedValue;
|
|
68
|
+
if (key === 'q') {
|
|
69
|
+
item.q = parseFloat(cleanedValue);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
return item;
|
|
@@ -76,24 +76,24 @@ export class WebHeaderUtil {
|
|
|
76
76
|
* Parse full header
|
|
77
77
|
*/
|
|
78
78
|
static parseHeader(input: string): WebParsedHeader[] {
|
|
79
|
-
const
|
|
79
|
+
const value = input.trim();
|
|
80
80
|
if (!input) { return []; }
|
|
81
|
-
return
|
|
81
|
+
return value.split(SPLIT_COMMA).map(part => this.parseHeaderSegment(part));
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* Build cookie suffix
|
|
86
86
|
*/
|
|
87
|
-
static buildCookieSuffix(
|
|
87
|
+
static buildCookieSuffix(cookie: Cookie): string[] {
|
|
88
88
|
const parts = [];
|
|
89
|
-
if (
|
|
90
|
-
if (
|
|
91
|
-
if (
|
|
92
|
-
if (
|
|
93
|
-
if (
|
|
94
|
-
if (
|
|
95
|
-
if (
|
|
96
|
-
if (
|
|
89
|
+
if (cookie.path) { parts.push(`path=${cookie.path}`); }
|
|
90
|
+
if (cookie.expires) { parts.push(`expires=${cookie.expires.toUTCString()}`); }
|
|
91
|
+
if (cookie.domain) { parts.push(`domain=${cookie.domain}`); }
|
|
92
|
+
if (cookie.priority) { parts.push(`priority=${cookie.priority.toLowerCase()}`); }
|
|
93
|
+
if (cookie.sameSite) { parts.push(`samesite=${cookie.sameSite.toLowerCase()}`); }
|
|
94
|
+
if (cookie.secure) { parts.push('secure'); }
|
|
95
|
+
if (cookie.httponly) { parts.push('httponly'); }
|
|
96
|
+
if (cookie.partitioned) { parts.push('partitioned'); }
|
|
97
97
|
return parts;
|
|
98
98
|
}
|
|
99
99
|
|
|
@@ -104,7 +104,10 @@ export class WebHeaderUtil {
|
|
|
104
104
|
if (header === '*' || header === '*/*') {
|
|
105
105
|
return values[0];
|
|
106
106
|
}
|
|
107
|
-
const sorted = this.parseHeader(header.toLowerCase())
|
|
107
|
+
const sorted = this.parseHeader(header.toLowerCase())
|
|
108
|
+
.filter(item => (item.q ?? 1) > 0)
|
|
109
|
+
.toSorted((a, b) => (b.q ?? 1) - (a.q ?? 1));
|
|
110
|
+
|
|
108
111
|
const set = new Set(values);
|
|
109
112
|
for (const { value } of sorted) {
|
|
110
113
|
const vk: K = castKey(value);
|
|
@@ -127,7 +130,7 @@ export class WebHeaderUtil {
|
|
|
127
130
|
const { parameters } = this.parseHeaderSegment(headers.get('Range'));
|
|
128
131
|
if ('bytes' in parameters) {
|
|
129
132
|
const [start, end] = parameters.bytes.split('-')
|
|
130
|
-
.map(
|
|
133
|
+
.map(value => value ? parseInt(value, 10) : undefined);
|
|
131
134
|
if (start !== undefined) {
|
|
132
135
|
return { start, end: end ?? (start + chunkSize) };
|
|
133
136
|
}
|
|
@@ -137,20 +140,20 @@ export class WebHeaderUtil {
|
|
|
137
140
|
/**
|
|
138
141
|
* Check freshness of the response using request and response headers.
|
|
139
142
|
*/
|
|
140
|
-
static isFresh(
|
|
141
|
-
const cacheControl =
|
|
143
|
+
static isFresh(request: WebHeaders, response: WebHeaders): boolean {
|
|
144
|
+
const cacheControl = request.get('Cache-Control');
|
|
142
145
|
if (cacheControl?.includes('no-cache')) {
|
|
143
146
|
return false;
|
|
144
147
|
}
|
|
145
148
|
|
|
146
|
-
const noneMatch =
|
|
149
|
+
const noneMatch = request.get('If-None-Match');
|
|
147
150
|
if (noneMatch) {
|
|
148
|
-
const etag =
|
|
149
|
-
const validTag = (
|
|
151
|
+
const etag = response.get('ETag');
|
|
152
|
+
const validTag = (value: string): boolean => value === etag || value === `W/${etag}` || `W/${value}` === etag;
|
|
150
153
|
return noneMatch === '*' || (!!etag && noneMatch.split(SPLIT_COMMA).some(validTag));
|
|
151
154
|
} else {
|
|
152
|
-
const modifiedSince =
|
|
153
|
-
const lastModified =
|
|
155
|
+
const modifiedSince = request.get('If-Modified-Since');
|
|
156
|
+
const lastModified = response.get('Last-Modified');
|
|
154
157
|
if (!modifiedSince || !lastModified) {
|
|
155
158
|
return false;
|
|
156
159
|
}
|
package/src/util/keygrip.ts
CHANGED
|
@@ -30,7 +30,7 @@ export class KeyGrip {
|
|
|
30
30
|
.createHmac(this.#algorithm, key ?? this.#keys[0])
|
|
31
31
|
.update(data)
|
|
32
32
|
.digest(this.#encoding)
|
|
33
|
-
.replace(/[/+=]/g,
|
|
33
|
+
.replace(/[/+=]/g, ch => CHAR_MAPPING[castKey(ch)]);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
verify(data: string, digest: string): boolean {
|
package/src/util/net.ts
CHANGED
|
@@ -8,25 +8,25 @@ import { ExecUtil } from '@travetto/runtime';
|
|
|
8
8
|
export class NetUtil {
|
|
9
9
|
|
|
10
10
|
/** Is an error an address in use error */
|
|
11
|
-
static isPortUsedError(
|
|
12
|
-
return !!
|
|
11
|
+
static isPortUsedError(error: unknown): error is Error & { port: number } {
|
|
12
|
+
return !!error && error instanceof Error && error.message.includes('EADDRINUSE');
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/** Get the port process id */
|
|
16
16
|
static async getPortProcessId(port: number): Promise<number | undefined> {
|
|
17
|
-
const
|
|
18
|
-
const result = await ExecUtil.getResult(
|
|
19
|
-
const [
|
|
20
|
-
if (
|
|
21
|
-
return +
|
|
17
|
+
const subProcess = spawn('lsof', ['-t', '-i', `tcp:${port}`]);
|
|
18
|
+
const result = await ExecUtil.getResult(subProcess, { catch: true });
|
|
19
|
+
const [processId] = result.stdout.trim().split(/\n/g);
|
|
20
|
+
if (processId && +processId > 0) {
|
|
21
|
+
return +processId;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/** Free port if in use */
|
|
26
26
|
static async freePort(port: number): Promise<void> {
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
process.kill(
|
|
27
|
+
const processId = await this.getPortProcessId(port);
|
|
28
|
+
if (processId) {
|
|
29
|
+
process.kill(processId);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -54,19 +54,19 @@ export class NetUtil {
|
|
|
54
54
|
*/
|
|
55
55
|
static getLocalAddress(): string {
|
|
56
56
|
const useIPv4 = !![...Object.values(os.networkInterfaces())]
|
|
57
|
-
.find(interfaces => interfaces?.find(
|
|
57
|
+
.find(interfaces => interfaces?.find(item => item.family === 'IPv4'));
|
|
58
58
|
|
|
59
59
|
return useIPv4 ? '0.0.0.0' : '::';
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* Free a port if it is in use, typically used to resolve port conflicts.
|
|
64
|
-
* @param
|
|
64
|
+
* @param error The error that may indicate a port conflict
|
|
65
65
|
* @returns Returns true if the port was freed, false if not handled
|
|
66
66
|
*/
|
|
67
|
-
static async freePortOnConflict(
|
|
68
|
-
if (NetUtil.isPortUsedError(
|
|
69
|
-
await NetUtil.freePort(
|
|
67
|
+
static async freePortOnConflict(error: unknown): Promise<boolean> {
|
|
68
|
+
if (NetUtil.isPortUsedError(error) && typeof error.port === 'number') {
|
|
69
|
+
await NetUtil.freePort(error.port);
|
|
70
70
|
return true;
|
|
71
71
|
} else {
|
|
72
72
|
return false;
|
|
@@ -2,7 +2,7 @@ import { Registry } from '@travetto/registry';
|
|
|
2
2
|
import { castTo, Class } from '@travetto/runtime';
|
|
3
3
|
import { AfterAll, BeforeAll } from '@travetto/test';
|
|
4
4
|
import { DependencyRegistryIndex, Injectable } from '@travetto/di';
|
|
5
|
-
import { ConfigSource,
|
|
5
|
+
import { ConfigSource, ConfigPayload } from '@travetto/config';
|
|
6
6
|
import { Schema } from '@travetto/schema';
|
|
7
7
|
|
|
8
8
|
import { WebDispatcher } from '../../../src/types/dispatch.ts';
|
|
@@ -12,7 +12,7 @@ import { WebMessageInit } from '../../../src/types/message.ts';
|
|
|
12
12
|
|
|
13
13
|
@Injectable()
|
|
14
14
|
export class WebTestConfig implements ConfigSource {
|
|
15
|
-
async get(): Promise<
|
|
15
|
+
async get(): Promise<ConfigPayload> {
|
|
16
16
|
return {
|
|
17
17
|
data: {
|
|
18
18
|
web: {
|