@travetto/web 6.0.0-rc.4 → 6.0.0-rc.6
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 +13 -13
- package/__index__.ts +1 -1
- package/package.json +2 -3
- package/src/context.ts +2 -2
- package/src/interceptor/{body-parse.ts → body.ts} +9 -13
- package/src/util/body.ts +14 -9
- package/support/transformer.web.ts +10 -5
package/README.md
CHANGED
|
@@ -377,7 +377,7 @@ Out of the box, the web framework comes with a few interceptors, and more are co
|
|
|
377
377
|
1. global - Intended to run outside of the request flow - [AsyncContextInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/context.ts#L13)
|
|
378
378
|
1. terminal - Handles once request and response are finished building - [LoggingInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/logging.ts#L28), [RespondInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/respond.ts#L12)
|
|
379
379
|
1. pre-request - Prepares the request for running - [TrustProxyInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/trust-proxy.ts#L23)
|
|
380
|
-
1. request - Handles inbound request, validation, and body preparation - [DecompressInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/decompress.ts#L53), [AcceptInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/accept.ts#L34), [
|
|
380
|
+
1. request - Handles inbound request, validation, and body preparation - [DecompressInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/decompress.ts#L53), [AcceptInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/accept.ts#L34), [BodyInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/body.ts#L57), [CookieInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/cookie.ts#L60)
|
|
381
381
|
1. response - Prepares outbound response - [CompressInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/compress.ts#L50), [CorsInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/cors.ts#L51), [EtagInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/etag.ts#L34), [ResponseCacheInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/response-cache.ts#L30)
|
|
382
382
|
1. application - Lives outside of the general request/response behavior, [Web Auth](https://github.com/travetto/travetto/tree/main/module/auth-web#readme "Web authentication integration support for the Travetto framework") uses this for login and logout flows.
|
|
383
383
|
|
|
@@ -460,8 +460,8 @@ export class DecompressConfig {
|
|
|
460
460
|
}
|
|
461
461
|
```
|
|
462
462
|
|
|
463
|
-
####
|
|
464
|
-
[
|
|
463
|
+
#### CookieInterceptor
|
|
464
|
+
[CookieInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/cookie.ts#L60) is responsible for processing inbound cookie headers and populating the appropriate data on the request, as well as sending the appropriate response data
|
|
465
465
|
|
|
466
466
|
**Code: Cookies Config**
|
|
467
467
|
```typescript
|
|
@@ -473,7 +473,7 @@ export class CookieConfig implements CookieSetOptions {
|
|
|
473
473
|
/**
|
|
474
474
|
* Are they signed
|
|
475
475
|
*/
|
|
476
|
-
signed
|
|
476
|
+
signed?: boolean;
|
|
477
477
|
/**
|
|
478
478
|
* Supported only via http (not in JS)
|
|
479
479
|
*/
|
|
@@ -490,20 +490,24 @@ export class CookieConfig implements CookieSetOptions {
|
|
|
490
490
|
/**
|
|
491
491
|
* Is the cookie only valid for https
|
|
492
492
|
*/
|
|
493
|
-
secure?: boolean
|
|
493
|
+
secure?: boolean;
|
|
494
494
|
/**
|
|
495
495
|
* The domain of the cookie
|
|
496
496
|
*/
|
|
497
497
|
domain?: string;
|
|
498
|
+
/**
|
|
499
|
+
* The default path of the cookie
|
|
500
|
+
*/
|
|
501
|
+
path: string = '/';
|
|
498
502
|
}
|
|
499
503
|
```
|
|
500
504
|
|
|
501
|
-
####
|
|
502
|
-
[
|
|
505
|
+
#### BodyInterceptor
|
|
506
|
+
[BodyInterceptor](https://github.com/travetto/travetto/tree/main/module/web/src/interceptor/body.ts#L57) handles the inbound request, and converting the body payload into an appropriate format.
|
|
503
507
|
|
|
504
|
-
**Code: Body
|
|
508
|
+
**Code: Body Config**
|
|
505
509
|
```typescript
|
|
506
|
-
export class
|
|
510
|
+
export class WebBodyConfig {
|
|
507
511
|
/**
|
|
508
512
|
* Parse request body
|
|
509
513
|
*/
|
|
@@ -523,10 +527,6 @@ export class BodyParseConfig {
|
|
|
523
527
|
|
|
524
528
|
@Ignore()
|
|
525
529
|
_limit: number | undefined;
|
|
526
|
-
|
|
527
|
-
postConstruct(): void {
|
|
528
|
-
this._limit = WebCommonUtil.parseByteSize(this.limit);
|
|
529
|
-
}
|
|
530
530
|
}
|
|
531
531
|
```
|
|
532
532
|
|
package/__index__.ts
CHANGED
|
@@ -24,7 +24,7 @@ export * from './src/registry/visitor.ts';
|
|
|
24
24
|
export * from './src/registry/types.ts';
|
|
25
25
|
|
|
26
26
|
export * from './src/interceptor/accept.ts';
|
|
27
|
-
export * from './src/interceptor/body
|
|
27
|
+
export * from './src/interceptor/body.ts';
|
|
28
28
|
export * from './src/interceptor/cors.ts';
|
|
29
29
|
export * from './src/interceptor/cookie.ts';
|
|
30
30
|
export * from './src/interceptor/compress.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.6",
|
|
4
4
|
"description": "Declarative api for Web Applications with support for the dependency injection.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web",
|
|
@@ -37,13 +37,12 @@
|
|
|
37
37
|
"@types/negotiator": "^0.6.3",
|
|
38
38
|
"find-my-way": "^9.3.0",
|
|
39
39
|
"fresh": "^0.5.2",
|
|
40
|
-
"iconv-lite": "^0.6.3",
|
|
41
40
|
"keygrip": "^1.1.0",
|
|
42
41
|
"negotiator": "^1.0.0"
|
|
43
42
|
},
|
|
44
43
|
"peerDependencies": {
|
|
45
44
|
"@travetto/cli": "^6.0.0-rc.3",
|
|
46
|
-
"@travetto/test": "^6.0.0-rc.
|
|
45
|
+
"@travetto/test": "^6.0.0-rc.3",
|
|
47
46
|
"@travetto/transformer": "^6.0.0-rc.3"
|
|
48
47
|
},
|
|
49
48
|
"peerDependenciesMeta": {
|
package/src/context.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AsyncContextValue, AsyncContext } from '@travetto/context';
|
|
2
2
|
import { Inject, Injectable } from '@travetto/di';
|
|
3
|
-
import { AppError, castTo, Class
|
|
3
|
+
import { AppError, castTo, Class } from '@travetto/runtime';
|
|
4
4
|
|
|
5
5
|
import { WebRequest } from './types/request.ts';
|
|
6
6
|
|
|
@@ -21,7 +21,7 @@ export class WebAsyncContext {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
postConstruct(): void {
|
|
24
|
-
this.registerSource(
|
|
24
|
+
this.registerSource(WebRequest, () => this.#request.get());
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
withContext<T>(request: WebRequest, next: () => Promise<T>): Promise<T> {
|
|
@@ -24,10 +24,10 @@ export interface BodyContentParser {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* Web body
|
|
27
|
+
* Web body configuration
|
|
28
28
|
*/
|
|
29
|
-
@Config('web.
|
|
30
|
-
export class
|
|
29
|
+
@Config('web.body')
|
|
30
|
+
export class WebBodyConfig {
|
|
31
31
|
/**
|
|
32
32
|
* Parse request body
|
|
33
33
|
*/
|
|
@@ -47,25 +47,21 @@ export class BodyParseConfig {
|
|
|
47
47
|
|
|
48
48
|
@Ignore()
|
|
49
49
|
_limit: number | undefined;
|
|
50
|
-
|
|
51
|
-
postConstruct(): void {
|
|
52
|
-
this._limit = WebCommonUtil.parseByteSize(this.limit);
|
|
53
|
-
}
|
|
54
50
|
}
|
|
55
51
|
|
|
56
52
|
|
|
57
53
|
/**
|
|
58
|
-
*
|
|
54
|
+
* Verifies content length, decodes character encodings, and parses body input string via the content type
|
|
59
55
|
*/
|
|
60
56
|
@Injectable()
|
|
61
|
-
export class
|
|
57
|
+
export class BodyInterceptor implements WebInterceptor<WebBodyConfig> {
|
|
62
58
|
|
|
63
59
|
dependsOn = [AcceptInterceptor, DecompressInterceptor];
|
|
64
60
|
category: WebInterceptorCategory = 'request';
|
|
65
61
|
parsers: Record<string, BodyContentParser> = {};
|
|
66
62
|
|
|
67
63
|
@Inject()
|
|
68
|
-
config:
|
|
64
|
+
config: WebBodyConfig;
|
|
69
65
|
|
|
70
66
|
async postConstruct(): Promise<void> {
|
|
71
67
|
// Load all the parser types
|
|
@@ -75,11 +71,11 @@ export class BodyParseInterceptor implements WebInterceptor<BodyParseConfig> {
|
|
|
75
71
|
}
|
|
76
72
|
}
|
|
77
73
|
|
|
78
|
-
applies({ endpoint, config }: WebInterceptorContext<
|
|
74
|
+
applies({ endpoint, config }: WebInterceptorContext<WebBodyConfig>): boolean {
|
|
79
75
|
return config.applies && endpoint.allowsBody;
|
|
80
76
|
}
|
|
81
77
|
|
|
82
|
-
async filter({ request, config, next }: WebChainedContext<
|
|
78
|
+
async filter({ request, config, next }: WebChainedContext<WebBodyConfig>): Promise<WebResponse> {
|
|
83
79
|
const input = request.body;
|
|
84
80
|
|
|
85
81
|
if (!WebBodyUtil.isRaw(input)) {
|
|
@@ -88,8 +84,8 @@ export class BodyParseInterceptor implements WebInterceptor<BodyParseConfig> {
|
|
|
88
84
|
|
|
89
85
|
const lengthRead = +(request.headers.get('Content-Length') || '');
|
|
90
86
|
const length = Number.isNaN(lengthRead) ? undefined : lengthRead;
|
|
87
|
+
const limit = config._limit ??= WebCommonUtil.parseByteSize(config.limit);
|
|
91
88
|
|
|
92
|
-
const limit = config._limit ?? Number.MAX_SAFE_INTEGER;
|
|
93
89
|
if (length && length > limit) {
|
|
94
90
|
throw WebError.for('Request entity too large', 413, { length, limit });
|
|
95
91
|
}
|
package/src/util/body.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { TextDecoder } from 'node:util';
|
|
3
2
|
import { Readable } from 'node:stream';
|
|
4
3
|
import { buffer as toBuffer } from 'node:stream/consumers';
|
|
5
4
|
|
|
@@ -187,27 +186,33 @@ export class WebBodyUtil {
|
|
|
187
186
|
static async readText(input: Readable | Buffer, limit: number, encoding?: string): Promise<{ text: string, read: number }> {
|
|
188
187
|
encoding ??= (Buffer.isBuffer(input) ? undefined : input.readableEncoding) ?? 'utf-8';
|
|
189
188
|
|
|
190
|
-
|
|
189
|
+
let decoder: TextDecoder;
|
|
190
|
+
try {
|
|
191
|
+
decoder = new TextDecoder(encoding);
|
|
192
|
+
} catch {
|
|
191
193
|
throw WebError.for('Specified Encoding Not Supported', 415, { encoding });
|
|
192
194
|
}
|
|
193
195
|
|
|
194
196
|
if (Buffer.isBuffer(input)) {
|
|
195
|
-
|
|
197
|
+
if (input.byteLength > limit) {
|
|
198
|
+
throw WebError.for('Request Entity Too Large', 413, { received: input.byteLength, limit });
|
|
199
|
+
}
|
|
200
|
+
return { text: decoder.decode(input), read: input.byteLength };
|
|
196
201
|
}
|
|
197
202
|
|
|
198
203
|
let received = Buffer.isBuffer(input) ? input.byteOffset : 0;
|
|
199
|
-
const decoder = iconv.getDecoder(encoding);
|
|
200
204
|
const all: string[] = [];
|
|
201
205
|
|
|
202
206
|
try {
|
|
203
|
-
for await (const chunk of input.iterator({ destroyOnReturn: false })) {
|
|
204
|
-
|
|
207
|
+
for await (const chunk of castTo<AsyncIterable<string | Buffer>>(input.iterator({ destroyOnReturn: false }))) {
|
|
208
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, 'utf8');
|
|
209
|
+
received += buffer.byteLength;
|
|
205
210
|
if (received > limit) {
|
|
206
211
|
throw WebError.for('Request Entity Too Large', 413, { received, limit });
|
|
207
212
|
}
|
|
208
|
-
all.push(decoder.
|
|
213
|
+
all.push(decoder.decode(buffer, { stream: true }));
|
|
209
214
|
}
|
|
210
|
-
all.push(decoder.
|
|
215
|
+
all.push(decoder.decode(Buffer.alloc(0), { stream: false }));
|
|
211
216
|
return { text: all.join(''), read: received };
|
|
212
217
|
} catch (err) {
|
|
213
218
|
if (err instanceof Error && err.name === 'AbortError') {
|
|
@@ -44,21 +44,26 @@ export class WebTransformer {
|
|
|
44
44
|
// If non-regex
|
|
45
45
|
if (arg && ts.isStringLiteral(arg)) {
|
|
46
46
|
const literal = LiteralUtil.toLiteral(arg);
|
|
47
|
-
|
|
48
|
-
throw new Error(`Unexpected literal type: ${literal}`);
|
|
49
|
-
}
|
|
50
|
-
// If param name matches path param, default to @Path
|
|
47
|
+
// If param name matches path param, default to @PathParam
|
|
51
48
|
detectedParamType = new RegExp(`:${name}\\b`).test(literal) ? 'PathParam' : 'QueryParam';
|
|
52
49
|
} else {
|
|
53
50
|
// Default to query for empty or regex endpoints
|
|
54
51
|
detectedParamType = 'QueryParam';
|
|
55
52
|
}
|
|
56
|
-
} else
|
|
53
|
+
} else {
|
|
57
54
|
// Treat as schema, and see if endpoint supports a body for default behavior on untyped
|
|
58
55
|
detectedParamType = epDec.targets?.includes('@travetto/web:HttpRequestBody') ? 'Body' : 'QueryParam';
|
|
59
56
|
config.name = '';
|
|
60
57
|
}
|
|
61
58
|
|
|
59
|
+
if (paramType.key === 'managed') {
|
|
60
|
+
if (paramType.name === 'WebResponse') {
|
|
61
|
+
throw new Error(`${paramType.name} must be registered using @ContextParam`);
|
|
62
|
+
} else if (paramType.name === 'WebRequest') {
|
|
63
|
+
throw new Error(`${paramType.name} is an invalid endpoint parameter`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
62
67
|
node = SchemaTransformUtil.computeField(state, node, config);
|
|
63
68
|
|
|
64
69
|
const modifiers = (node.modifiers ?? []).filter(x => x !== pDec);
|