@lenne.tech/nest-server 11.42.0 → 11.42.2
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/.claude/rules/configurable-features.md +1 -1
- package/.claude/rules/testing.md +3 -3
- package/FRAMEWORK-API.md +2 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +1 -0
- package/dist/core/modules/tus/core-tus.controller.js +2 -2
- package/dist/core/modules/tus/core-tus.controller.js.map +1 -1
- package/dist/core/modules/tus/core-tus.service.d.ts +2 -0
- package/dist/core/modules/tus/core-tus.service.js +68 -0
- package/dist/core/modules/tus/core-tus.service.js.map +1 -1
- package/dist/core/modules/tus/interfaces/tus-config.interface.d.ts +2 -1
- package/dist/core/modules/tus/interfaces/tus-config.interface.js +1 -0
- package/dist/core/modules/tus/interfaces/tus-config.interface.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/migration-guides/11.42.0-to-11.42.1.md +66 -0
- package/migration-guides/11.42.1-to-11.42.2.md +53 -0
- package/package.json +7 -7
- package/src/core/common/interfaces/server-options.interface.ts +17 -0
- package/src/core/modules/tus/README.md +26 -0
- package/src/core/modules/tus/core-tus.controller.ts +4 -2
- package/src/core/modules/tus/core-tus.service.ts +105 -0
- package/src/core/modules/tus/interfaces/tus-config.interface.ts +4 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Migration Guide: 11.42.0 → 11.42.1
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
| -------------------- | --------------------------------------------------------------------------------------------- |
|
|
7
|
+
| **Bugfix** | Browser uploads through TUS work with cookie authentication and separate app/API origins (§1) |
|
|
8
|
+
| **New option** | `tus.allowedOrigins` overrides the derived origins (§1) |
|
|
9
|
+
| **Migration Effort** | None — `pnpm update` |
|
|
10
|
+
|
|
11
|
+
## Quick Migration
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm update @lenne.tech/nest-server
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Vendor-mode projects pick up the core update; nothing else to change.
|
|
18
|
+
|
|
19
|
+
## 1. TUS answered a credentialed request with `Access-Control-Allow-Origin: *`
|
|
20
|
+
|
|
21
|
+
The tus handler writes its responses itself, past the API's CORS layer, and `@tus/server` answers
|
|
22
|
+
`Access-Control-Allow-Origin: *` unless it is given `allowedOrigins`. The core gave it none. With cookie
|
|
23
|
+
authentication and separate app and API origins (`lt dev up`, or `app.example.com` /
|
|
24
|
+
`api.example.com`), the request is credentialed and the API adds
|
|
25
|
+
`Access-Control-Allow-Credentials: true` — a combination every browser refuses:
|
|
26
|
+
|
|
27
|
+
> The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*'
|
|
28
|
+
> when the request's credentials mode is 'include'.
|
|
29
|
+
|
|
30
|
+
So every browser upload failed, while the server answered `201`. Preflights and unauthenticated
|
|
31
|
+
requests were fine, which is why no test noticed — only an **authenticated** request with an `Origin`
|
|
32
|
+
reaches the header.
|
|
33
|
+
|
|
34
|
+
Since 11.42.1 the tus server takes its origins from `buildCorsConfig()`, the decision REST, GraphQL and
|
|
35
|
+
Better-Auth already share (`appUrl`, `baseUrl`, `cors.allowedOrigins`, `cors.allowAll`), and mirrors an
|
|
36
|
+
allowed `Origin`. A foreign origin gets no `Access-Control-Allow-Origin` at all. To use different
|
|
37
|
+
origins for uploads:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
tus: {
|
|
41
|
+
allowedOrigins: ['https://uploads.example.com'];
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Without credentialed CORS configured — cookies off, or no origin resolvable — nothing changes; the tus
|
|
46
|
+
default `*` stays.
|
|
47
|
+
|
|
48
|
+
## 2. Dependencies
|
|
49
|
+
|
|
50
|
+
Updated within their majors: `@modelcontextprotocol/sdk` 1.32.0, `mongoose` 9.10.4, `supertest` 7.3.1, and
|
|
51
|
+
the `@aws-sdk/*` S3 packages 3.1146.0 (dev). NestJS stays on 11.
|
|
52
|
+
|
|
53
|
+
`@modelcontextprotocol/sdk` 1.32.0 lets its CLIENT transports follow redirects only within the same
|
|
54
|
+
origin. The framework uses the server side only, so this matters only to a project that builds its own MCP
|
|
55
|
+
client transport and relied on a cross-origin redirect.
|
|
56
|
+
|
|
57
|
+
## Troubleshooting
|
|
58
|
+
|
|
59
|
+
| Symptom | Cause | Fix |
|
|
60
|
+
| -------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------ |
|
|
61
|
+
| Browser: "must not be the wildcard '*' when the request's credentials mode is 'include'" on `/tus` | 11.42.0 or older | Update to 11.42.1 |
|
|
62
|
+
| Upload from an origin that is not the app refused by CORS | Origin not in `appUrl` / `cors.allowedOrigins` | Add it to `cors.allowedOrigins`, or set `tus.allowedOrigins` |
|
|
63
|
+
|
|
64
|
+
## Module Documentation
|
|
65
|
+
|
|
66
|
+
- [TUS README](../src/core/modules/tus/README.md) — CORS Headers
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Migration Guide: 11.42.1 → 11.42.2
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
| -------------------- | ------------------------------------------------------------------------ |
|
|
7
|
+
| **Bugfix** | Finished TUS uploads answer 204 behind the `compression` middleware (§1) |
|
|
8
|
+
| **Migration Effort** | None — `pnpm update` |
|
|
9
|
+
|
|
10
|
+
## Quick Migration
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm update @lenne.tech/nest-server
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Vendor-mode projects pick up the core update; nothing else to change.
|
|
17
|
+
|
|
18
|
+
## 1. A finished TUS upload never got an answer although the file was stored
|
|
19
|
+
|
|
20
|
+
`@tus/server` finishes its responses with `res.end(callback)`, a documented Node signature. The
|
|
21
|
+
`compression` middleware patches `res.end(chunk, encoding)` and treats the first argument as a chunk,
|
|
22
|
+
so the callback reached `Buffer.byteLength()` and threw:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer … Received function
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The starter's `main.ts` — and therefore every generated project — registers `compression` with
|
|
29
|
+
`filter: () => true` and `threshold: 0`, which removes the two checks that would otherwise skip a
|
|
30
|
+
bodyless 204. The failure hit the final `PATCH`, AFTER the upload had been migrated into the file
|
|
31
|
+
store: the request hung — the headers were already sent, so no error response could follow — the
|
|
32
|
+
client never saw a 2xx, `tus-js-client` never fired `onSuccess`, and no log named `compression`.
|
|
33
|
+
|
|
34
|
+
Since 11.42.2 `CoreTusService` normalises `res.end(callback)` before the tus server writes
|
|
35
|
+
(`normalizeEndCallback()`, wrapped around `getServer().handle()`), so it holds for the core controller
|
|
36
|
+
and for any project controller that calls the tus server. No change to `main.ts` is needed; a project
|
|
37
|
+
that worked around it by excluding `/tus` from the compression filter can keep or drop that filter.
|
|
38
|
+
|
|
39
|
+
## 2. TUS request errors are logged with their stack
|
|
40
|
+
|
|
41
|
+
`CoreTusController` logged only `error.message` for a failed TUS request. A `TypeError` thrown by a
|
|
42
|
+
middleware's patched `res.end` then read like a tus failure, with nothing pointing at the middleware —
|
|
43
|
+
the defect in §1 took several rounds to find for exactly that reason. The stack is now logged too.
|
|
44
|
+
|
|
45
|
+
## Troubleshooting
|
|
46
|
+
|
|
47
|
+
| Symptom | Cause | Fix |
|
|
48
|
+
| -------------------------------------------------------------------------------------- | ------------------------------------- | ----------------- |
|
|
49
|
+
| File stored, final PATCH never answers (request hangs), `Received function` in the log | 11.42.1 or older behind `compression` | Update to 11.42.2 |
|
|
50
|
+
|
|
51
|
+
## Module Documentation
|
|
52
|
+
|
|
53
|
+
- [TUS README](../src/core/modules/tus/README.md) — Troubleshooting
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.42.
|
|
3
|
+
"version": "11.42.2",
|
|
4
4
|
"description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"@apollo/server": "5.5.1",
|
|
93
93
|
"@as-integrations/express5": "1.1.2",
|
|
94
94
|
"@getbrevo/brevo": "6.0.3",
|
|
95
|
-
"@modelcontextprotocol/sdk": "1.
|
|
95
|
+
"@modelcontextprotocol/sdk": "1.32.0",
|
|
96
96
|
"@nestjs/apollo": "13.4.5",
|
|
97
97
|
"@nestjs/common": "11.2.7",
|
|
98
98
|
"@nestjs/core": "11.2.7",
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
"json-to-graphql-query": "2.3.0",
|
|
127
127
|
"lodash": "4.18.1",
|
|
128
128
|
"mongodb": "7.6.0",
|
|
129
|
-
"mongoose": "9.10.
|
|
129
|
+
"mongoose": "9.10.4",
|
|
130
130
|
"multer": "2.4.0",
|
|
131
131
|
"node-mailjet": "6.0.11",
|
|
132
132
|
"nodemailer": "10.0.13",
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
"reflect-metadata": "0.2.2",
|
|
136
136
|
"rfdc": "1.4.1",
|
|
137
137
|
"rxjs": "7.8.2",
|
|
138
|
-
"supertest": "7.3.
|
|
138
|
+
"supertest": "7.3.1",
|
|
139
139
|
"ts-morph": "28.0.0",
|
|
140
140
|
"undici": "7.30.0",
|
|
141
141
|
"ws": "8.21.3",
|
|
@@ -182,9 +182,9 @@
|
|
|
182
182
|
}
|
|
183
183
|
},
|
|
184
184
|
"devDependencies": {
|
|
185
|
-
"@aws-sdk/client-s3": "3.
|
|
186
|
-
"@aws-sdk/lib-storage": "3.
|
|
187
|
-
"@aws-sdk/s3-request-presigner": "3.
|
|
185
|
+
"@aws-sdk/client-s3": "3.1146.0",
|
|
186
|
+
"@aws-sdk/lib-storage": "3.1146.0",
|
|
187
|
+
"@aws-sdk/s3-request-presigner": "3.1146.0",
|
|
188
188
|
"@better-auth/core": "1.7.7",
|
|
189
189
|
"@better-auth/passkey": "1.7.7",
|
|
190
190
|
"@compodoc/compodoc": "2.0.0",
|
|
@@ -3606,6 +3606,23 @@ export interface ITusConfig {
|
|
|
3606
3606
|
*/
|
|
3607
3607
|
allowedHeaders?: string[];
|
|
3608
3608
|
|
|
3609
|
+
/**
|
|
3610
|
+
* Origins the tus handler may answer a cross-origin request with (`Access-Control-Allow-Origin`).
|
|
3611
|
+
*
|
|
3612
|
+
* Unset, they come from the API's own CORS configuration — `buildCorsConfig()`, the function REST,
|
|
3613
|
+
* GraphQL and Better-Auth share (`appUrl`, `baseUrl`, `cors.allowedOrigins`, `cors.allowAll`).
|
|
3614
|
+
* Without credentialed CORS configured (cookies off, or no origin resolvable) the tus server keeps
|
|
3615
|
+
* its own default, `*`.
|
|
3616
|
+
*
|
|
3617
|
+
* The wildcard is what broke browser uploads with cookie authentication and separate app/API
|
|
3618
|
+
* origins before 11.42.1: a credentialed response with `Access-Control-Allow-Origin: *` is refused
|
|
3619
|
+
* by every browser, although the server answered 201.
|
|
3620
|
+
*
|
|
3621
|
+
* @default undefined (derived from the API CORS configuration)
|
|
3622
|
+
* @since 11.42.1
|
|
3623
|
+
*/
|
|
3624
|
+
allowedOrigins?: string[];
|
|
3625
|
+
|
|
3609
3626
|
/**
|
|
3610
3627
|
* Allowed MIME types for uploads.
|
|
3611
3628
|
* If undefined, all types are allowed.
|
|
@@ -107,6 +107,17 @@ The TUS server automatically handles CORS headers for browser-based clients:
|
|
|
107
107
|
- `Upload-Offset`
|
|
108
108
|
- `Upload-Metadata`
|
|
109
109
|
|
|
110
|
+
**`Access-Control-Allow-Origin` follows the API's CORS configuration (11.42.1+).** The tus handler
|
|
111
|
+
writes its responses itself, and without `allowedOrigins` `@tus/server` answers `*`. With cookie
|
|
112
|
+
authentication and separate app and API origins the request is credentialed, and every browser
|
|
113
|
+
refuses `*` there — uploads failed in the browser although the server answered 201. The origins now
|
|
114
|
+
come from `buildCorsConfig()` (`appUrl`, `baseUrl`, `cors.allowedOrigins`, `cors.allowAll`), the same
|
|
115
|
+
decision REST, GraphQL and Better-Auth make; `tus.allowedOrigins` overrides. Without credentialed CORS
|
|
116
|
+
(cookies off, or no origin resolvable) the tus default `*` stays.
|
|
117
|
+
|
|
118
|
+
The preflight (`OPTIONS`) is answered by Nest's CORS layer and was never affected — test an
|
|
119
|
+
**authenticated** request with an `Origin` header when checking this.
|
|
120
|
+
|
|
110
121
|
---
|
|
111
122
|
|
|
112
123
|
## Configuration
|
|
@@ -515,6 +526,21 @@ from elsewhere (an API key, a signed form token), or to let a support role resum
|
|
|
515
526
|
2. Verify MongoDB connection is established
|
|
516
527
|
3. Check server logs for initialization errors
|
|
517
528
|
|
|
529
|
+
### Upload is stored but the client never sees success (fixed in 11.42.2)
|
|
530
|
+
|
|
531
|
+
**Symptom:** the file appears in the file store (`Upload … migrated to …` in the log), but the final
|
|
532
|
+
`PATCH` never answers (the request hangs: the headers are already sent, so no error response can
|
|
533
|
+
follow), `tus-js-client` never fires `onSuccess`, and the log shows
|
|
534
|
+
`ERR_INVALID_ARG_TYPE … Received function`.
|
|
535
|
+
|
|
536
|
+
**Cause:** `@tus/server` finishes its responses with `res.end(callback)`, and the `compression`
|
|
537
|
+
middleware patches `res.end(chunk, encoding)` and reads the callback as a chunk. The starter registers
|
|
538
|
+
`compression` with `filter: () => true` and `threshold: 0`, so it also handles the bodyless 204.
|
|
539
|
+
|
|
540
|
+
**Solution:** update to 11.42.2 — `CoreTusService` normalises `res.end(callback)` before the tus
|
|
541
|
+
server writes (`normalizeEndCallback()`), for the core controller and any project controller that
|
|
542
|
+
calls `getServer().handle()`. No change to `main.ts` is needed.
|
|
543
|
+
|
|
518
544
|
### Upload stalls or fails to resume
|
|
519
545
|
|
|
520
546
|
**Cause:** Upload expired or server restarted
|
|
@@ -105,7 +105,9 @@ export class CoreTusController {
|
|
|
105
105
|
try {
|
|
106
106
|
await server.handle(req, res);
|
|
107
107
|
} catch (error) {
|
|
108
|
-
|
|
108
|
+
// The stack, not just the message: a TypeError from a middleware's patched `res.end` reads
|
|
109
|
+
// like a tus failure without it, and that cost a project several rounds of diagnosis (11.42.2).
|
|
110
|
+
this.logger.error(`TUS request error: ${error.message}`, error?.stack);
|
|
109
111
|
if (!res.headersSent) {
|
|
110
112
|
res.status(500).json({ message: 'Upload error' });
|
|
111
113
|
}
|
|
@@ -131,7 +133,7 @@ export class CoreTusController {
|
|
|
131
133
|
try {
|
|
132
134
|
await server.handle(req, res);
|
|
133
135
|
} catch (error) {
|
|
134
|
-
this.logger.error(`TUS request error for upload ${req.params.id}: ${error.message}
|
|
136
|
+
this.logger.error(`TUS request error for upload ${req.params.id}: ${error.message}`, error?.stack);
|
|
135
137
|
if (!res.headersSent) {
|
|
136
138
|
res.status(500).json({ message: 'Upload error' });
|
|
137
139
|
}
|
|
@@ -9,6 +9,7 @@ import { Readable } from 'stream';
|
|
|
9
9
|
|
|
10
10
|
import { GridFSHelper } from '../../common/helpers/gridfs.helper';
|
|
11
11
|
import { ITusConfig } from '../../common/interfaces/server-options.interface';
|
|
12
|
+
import { buildCorsConfig } from '../../common/helpers/cookies.helper';
|
|
12
13
|
import { ConfigService } from '../../common/services/config.service';
|
|
13
14
|
import { CoreRedisService } from '../../common/services/core-redis.service';
|
|
14
15
|
import { CoreS3Service } from '../../common/services/core-s3.service';
|
|
@@ -53,6 +54,9 @@ export interface CoreTusServiceOptions {
|
|
|
53
54
|
s3Service?: CoreS3Service;
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
/** Marks a response whose `end()` was already normalised (see `normalizeEndCallback()`). */
|
|
58
|
+
const END_CALLBACK_NORMALIZED = Symbol('tusEndCallbackNormalized');
|
|
59
|
+
|
|
56
60
|
/**
|
|
57
61
|
* Core TUS Service
|
|
58
62
|
*
|
|
@@ -638,6 +642,46 @@ export class CoreTusService implements OnModuleDestroy, OnModuleInit {
|
|
|
638
642
|
return { metadata };
|
|
639
643
|
}
|
|
640
644
|
|
|
645
|
+
/**
|
|
646
|
+
* The origins the tus handler may answer a cross-origin request with — or `undefined` to keep the
|
|
647
|
+
* tus server's own default (`*`).
|
|
648
|
+
*
|
|
649
|
+
* The tus handler writes its responses itself, past the API's CORS layer, and without
|
|
650
|
+
* `allowedOrigins` @tus/server sets `Access-Control-Allow-Origin: *`. On a credentialed request —
|
|
651
|
+
* cookie authentication with separate app and API origins — every browser refuses that, so no
|
|
652
|
+
* browser upload could succeed although the server answered 201. Preflights and 401s were fine
|
|
653
|
+
* (Nest answers those), which is why nothing noticed: only an AUTHENTICATED request reaches it.
|
|
654
|
+
*
|
|
655
|
+
* Taken from `tus.allowedOrigins` when set, else from `buildCorsConfig()` — the same decision REST,
|
|
656
|
+
* GraphQL and Better-Auth make. Without credentialed CORS configured the previous default stays.
|
|
657
|
+
*/
|
|
658
|
+
protected resolveCorsOrigins(): ((origin: string) => boolean) | undefined {
|
|
659
|
+
const toOrigin = (value: string): string => {
|
|
660
|
+
try {
|
|
661
|
+
return new URL(value).origin;
|
|
662
|
+
} catch {
|
|
663
|
+
return value;
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
const fromList = (list: string[]) => {
|
|
667
|
+
const allowed = new Set(list.filter((entry) => typeof entry === 'string').map(toOrigin));
|
|
668
|
+
return (origin: string) => allowed.has(toOrigin(origin));
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
if (Array.isArray(this.config.allowedOrigins) && this.config.allowedOrigins.length) {
|
|
672
|
+
return fromList(this.config.allowedOrigins);
|
|
673
|
+
}
|
|
674
|
+
const options = this.options?.configService?.configFastButReadOnly ?? ConfigService.configFastButReadOnly ?? {};
|
|
675
|
+
const cors = buildCorsConfig(options);
|
|
676
|
+
if (cors.credentials !== true) {
|
|
677
|
+
return undefined;
|
|
678
|
+
}
|
|
679
|
+
if (cors.origin === true) {
|
|
680
|
+
return () => true;
|
|
681
|
+
}
|
|
682
|
+
return Array.isArray(cors.origin) ? fromList(cors.origin as string[]) : undefined;
|
|
683
|
+
}
|
|
684
|
+
|
|
641
685
|
/**
|
|
642
686
|
* The VALIDATED tenant a new upload belongs to, or `undefined`.
|
|
643
687
|
*
|
|
@@ -716,8 +760,12 @@ export class CoreTusService implements OnModuleDestroy, OnModuleInit {
|
|
|
716
760
|
// correct for the single-replica deployment that configuration describes.
|
|
717
761
|
const locker = this.createLocker();
|
|
718
762
|
|
|
763
|
+
const cors = this.resolveCorsOrigins();
|
|
719
764
|
const server = new Server({
|
|
720
765
|
allowedHeaders: this.config.allowedHeaders || DEFAULT_TUS_ALLOWED_HEADERS,
|
|
766
|
+
// Without these, @tus/server answers every request with `Access-Control-Allow-Origin: *`,
|
|
767
|
+
// which a browser refuses on a credentialed (cookie) request — see resolveCorsOrigins().
|
|
768
|
+
...(cors ? { allowedCredentials: true, allowedOrigins: cors } : {}),
|
|
721
769
|
datastore,
|
|
722
770
|
...(locker ? { locker } : {}),
|
|
723
771
|
maxSize: this.config.maxSize,
|
|
@@ -749,9 +797,66 @@ export class CoreTusService implements OnModuleDestroy, OnModuleInit {
|
|
|
749
797
|
respectForwardedHeaders: true,
|
|
750
798
|
});
|
|
751
799
|
|
|
800
|
+
// Every caller of getServer().handle() — the core controller and any project controller — gets
|
|
801
|
+
// the response normalised before @tus/server writes to it. See normalizeEndCallback().
|
|
802
|
+
const handle = server.handle.bind(server);
|
|
803
|
+
server.handle = (req, res) => {
|
|
804
|
+
this.normalizeEndCallback(res);
|
|
805
|
+
return handle(req, res);
|
|
806
|
+
};
|
|
807
|
+
|
|
752
808
|
return server;
|
|
753
809
|
}
|
|
754
810
|
|
|
811
|
+
/**
|
|
812
|
+
* Makes `res.end(callback)` safe for middlewares that patch `res.end` with the old two-argument form.
|
|
813
|
+
*
|
|
814
|
+
* @tus/server finishes its responses through srvx with `res.end(callback)` — a documented Node
|
|
815
|
+
* signature. `compression` (and any middleware written like it) patches `res.end(chunk, encoding)`
|
|
816
|
+
* and treats the first argument as a chunk unconditionally, so the callback reaches
|
|
817
|
+
* `Buffer.byteLength()` and throws `ERR_INVALID_ARG_TYPE ... Received function`. The starter
|
|
818
|
+
* registers `compression` with `filter: () => true, threshold: 0`, which removes the checks that
|
|
819
|
+
* would otherwise skip a bodyless 204, so every finished upload failed AFTER the file had been
|
|
820
|
+
* stored: the client never saw a 2xx and retried an upload that was already complete.
|
|
821
|
+
*
|
|
822
|
+
* The wrapper moves a function argument into the callback slot and runs it on `finish`, which is
|
|
823
|
+
* what Node itself does with it — or on `close`, so a connection dropped after `end()` resolves srvx
|
|
824
|
+
* too. A response destroyed before `end()` stays unresolved, exactly as with plain Node.
|
|
825
|
+
* Idempotent per response. Protected so a project can widen it.
|
|
826
|
+
*/
|
|
827
|
+
protected normalizeEndCallback(res: any): void {
|
|
828
|
+
if (!res || typeof res.end !== 'function' || res[END_CALLBACK_NORMALIZED]) {
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const end = res.end;
|
|
832
|
+
res.end = function normalizedEnd(this: any, chunk?: unknown, encoding?: unknown, callback?: unknown) {
|
|
833
|
+
if (typeof chunk === 'function') {
|
|
834
|
+
callback = chunk;
|
|
835
|
+
chunk = undefined;
|
|
836
|
+
encoding = undefined;
|
|
837
|
+
} else if (typeof encoding === 'function') {
|
|
838
|
+
callback = encoding;
|
|
839
|
+
encoding = undefined;
|
|
840
|
+
}
|
|
841
|
+
if (typeof callback === 'function') {
|
|
842
|
+
let called = false;
|
|
843
|
+
const done = () => {
|
|
844
|
+
if (!called) {
|
|
845
|
+
called = true;
|
|
846
|
+
(callback as () => void)();
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
this.once('finish', done);
|
|
850
|
+
this.once('close', done);
|
|
851
|
+
}
|
|
852
|
+
if (chunk === undefined) {
|
|
853
|
+
return end.call(this);
|
|
854
|
+
}
|
|
855
|
+
return encoding === undefined ? end.call(this, chunk) : end.call(this, chunk, encoding);
|
|
856
|
+
};
|
|
857
|
+
res[END_CALLBACK_NORMALIZED] = true;
|
|
858
|
+
}
|
|
859
|
+
|
|
755
860
|
/**
|
|
756
861
|
* Parse TUS metadata into object
|
|
757
862
|
* @tus/server v2 already parses metadata into an object
|
|
@@ -31,13 +31,16 @@ export const DEFAULT_TUS_ALLOWED_HEADERS: string[] = [];
|
|
|
31
31
|
* Default TUS configuration
|
|
32
32
|
*/
|
|
33
33
|
export const DEFAULT_TUS_CONFIG: Required<
|
|
34
|
-
Omit<ITusConfig, 'allowedTypes' | 'creation' | 'expiration'> & {
|
|
34
|
+
Omit<ITusConfig, 'allowedOrigins' | 'allowedTypes' | 'creation' | 'expiration'> & {
|
|
35
|
+
allowedOrigins: undefined;
|
|
35
36
|
allowedTypes: undefined;
|
|
36
37
|
creation: boolean;
|
|
37
38
|
expiration: ITusExpirationConfig;
|
|
38
39
|
}
|
|
39
40
|
> = {
|
|
40
41
|
allowedHeaders: DEFAULT_TUS_ALLOWED_HEADERS,
|
|
42
|
+
// Derived from the API CORS configuration (CoreTusService.resolveCorsOrigins())
|
|
43
|
+
allowedOrigins: undefined,
|
|
41
44
|
allowedTypes: undefined,
|
|
42
45
|
checksum: true,
|
|
43
46
|
concatenation: true,
|