expediate 0.0.2 → 1.0.0
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/.npmignore +5 -4
- package/LICENSE +21 -0
- package/README.md +661 -49
- package/dist/apis.d.ts +166 -0
- package/dist/apis.d.ts.map +1 -0
- package/dist/apis.js +250 -0
- package/dist/apis.js.map +1 -0
- package/dist/git.d.ts +74 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +244 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/jwt-auth.d.ts +280 -0
- package/dist/jwt-auth.d.ts.map +1 -0
- package/dist/jwt-auth.js +575 -0
- package/dist/jwt-auth.js.map +1 -0
- package/dist/misc.d.ts +203 -0
- package/dist/misc.d.ts.map +1 -0
- package/dist/misc.js +549 -0
- package/dist/misc.js.map +1 -0
- package/dist/router.d.ts +224 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +502 -0
- package/dist/router.js.map +1 -0
- package/dist/static.d.ts +164 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/static.js +703 -0
- package/dist/static.js.map +1 -0
- package/package.json +31 -6
- package/.gitignore +0 -14
- package/index.js +0 -266
- package/sample.js +0 -9
- package/static.js +0 -388
package/dist/misc.js
ADDED
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
/* Copyright 2021 Fabien Bavent
|
|
2
|
+
*
|
|
3
|
+
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
4
|
+
* copy of this software and associated documentation files (the "Software"),
|
|
5
|
+
* to deal in the Software without restriction, including without limitation
|
|
6
|
+
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
7
|
+
* and/or sell copies of the Software, and to permit persons to whom the
|
|
8
|
+
* Software is furnished to do so, subject to the following conditions:
|
|
9
|
+
*
|
|
10
|
+
* The above copyright notice and this permission notice shall be included
|
|
11
|
+
* in all copies or substantial portions of the Software.
|
|
12
|
+
*
|
|
13
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
14
|
+
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
16
|
+
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
18
|
+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
19
|
+
* DEALINGS IN THE SOFTWARE.
|
|
20
|
+
*/
|
|
21
|
+
'use strict';
|
|
22
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.json = json;
|
|
27
|
+
exports.formData = formData;
|
|
28
|
+
exports.parseBody = parseBody;
|
|
29
|
+
exports.logger = logger;
|
|
30
|
+
const zlib_1 = __importDefault(require("zlib"));
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Decompression algorithm registry
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
/**
|
|
35
|
+
* Maps a supported `Content-Encoding` value to its corresponding `zlib`
|
|
36
|
+
* decompression function. Only `gzip` and `deflate` are supported.
|
|
37
|
+
*/
|
|
38
|
+
const DECOMPRESS_ALGO = {
|
|
39
|
+
gzip: zlib_1.default.gunzip,
|
|
40
|
+
deflate: zlib_1.default.inflate,
|
|
41
|
+
};
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Internal utilities
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
/**
|
|
46
|
+
* Parse a human-readable byte-size string into a number of bytes.
|
|
47
|
+
*
|
|
48
|
+
* Supported suffixes (case-insensitive): `b`, `kb`, `mb`, `gb`.
|
|
49
|
+
* The suffix is optional; a bare number is treated as bytes.
|
|
50
|
+
*
|
|
51
|
+
* @param value - The size string to parse (e.g. `'100kb'`, `'2.5mb'`).
|
|
52
|
+
* @returns The size in bytes, or `0` if the input cannot be parsed.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* readSize('100kb') // 102400
|
|
57
|
+
* readSize('2mb') // 2097152
|
|
58
|
+
* readSize('1024') // 1024
|
|
59
|
+
* readSize('bad') // 0
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
function readSize(value) {
|
|
63
|
+
if (typeof value === 'number')
|
|
64
|
+
return value;
|
|
65
|
+
const fmt = /^(\d+(\.\d+)?)([kmg]?b?)?$/i.exec(value);
|
|
66
|
+
if (!fmt)
|
|
67
|
+
return 0;
|
|
68
|
+
const num = parseFloat(fmt[1] ?? '0');
|
|
69
|
+
const sfx = (fmt[3] ?? 'b').toLowerCase();
|
|
70
|
+
if (sfx[0] === 'k')
|
|
71
|
+
return num * 1024;
|
|
72
|
+
if (sfx[0] === 'm')
|
|
73
|
+
return num * 1024 * 1024;
|
|
74
|
+
if (sfx[0] === 'g')
|
|
75
|
+
return num * 1024 * 1024 * 1024;
|
|
76
|
+
return num;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Split a `Buffer` on every occurrence of a `delimiter` buffer, returning an
|
|
80
|
+
* array of sub-buffers between delimiters (the delimiters themselves are not
|
|
81
|
+
* included in the output).
|
|
82
|
+
*
|
|
83
|
+
* Behaves like `String.prototype.split` but operates on raw binary data,
|
|
84
|
+
* making it safe for multipart bodies that may contain arbitrary byte
|
|
85
|
+
* sequences.
|
|
86
|
+
*
|
|
87
|
+
* @param buffer - The source buffer to split.
|
|
88
|
+
* @param delimiter - The byte sequence to split on.
|
|
89
|
+
* @returns An array of buffer slices; always contains at least one element.
|
|
90
|
+
*/
|
|
91
|
+
function splitBuffer(buffer, delimiter) {
|
|
92
|
+
const result = [];
|
|
93
|
+
let start = 0;
|
|
94
|
+
let index;
|
|
95
|
+
while ((index = buffer.indexOf(delimiter, start)) !== -1) {
|
|
96
|
+
result.push(buffer.slice(start, index));
|
|
97
|
+
start = index + delimiter.length;
|
|
98
|
+
}
|
|
99
|
+
result.push(buffer.slice(start));
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Extract the `charset` parameter from a `Content-Type` header value.
|
|
104
|
+
*
|
|
105
|
+
* Handles optional whitespace around the semicolon-separated parameters.
|
|
106
|
+
* Falls back to `'utf8'` when no `charset` parameter is found.
|
|
107
|
+
*
|
|
108
|
+
* @param contentType - The raw `Content-Type` header value
|
|
109
|
+
* (e.g. `'text/plain; charset=iso-8859-1'`).
|
|
110
|
+
* @returns A Node.js-compatible encoding name (e.g. `'utf8'`, `'iso-8859-1'`).
|
|
111
|
+
*/
|
|
112
|
+
function extractCharset(contentType) {
|
|
113
|
+
// BUG FIX: the original regex `'s+$` was a literal quote followed by `s+$`
|
|
114
|
+
// instead of `\s+$`. The trim therefore left leading/trailing whitespace on
|
|
115
|
+
// every parameter, breaking charset detection. Corrected to `\s+$`.
|
|
116
|
+
const param = contentType
|
|
117
|
+
.split(';')
|
|
118
|
+
.map((s) => s.replace(/^\s+|\s+$/g, ''))
|
|
119
|
+
.find((s) => s.startsWith('charset='));
|
|
120
|
+
return param ? param.substring('charset='.length) : 'utf8';
|
|
121
|
+
}
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Body collection
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
/**
|
|
126
|
+
* Collect the full request body into a single `Buffer`, enforce size limits,
|
|
127
|
+
* optionally decompress, and validate the `Content-Type` against an expected
|
|
128
|
+
* MIME type.
|
|
129
|
+
*
|
|
130
|
+
* When the body is successfully collected and validated, `callback` is invoked
|
|
131
|
+
* with the raw `Content-Type` header value and the (possibly decompressed)
|
|
132
|
+
* body buffer. In all error cases the appropriate HTTP error response is sent
|
|
133
|
+
* and `callback` is never called.
|
|
134
|
+
*
|
|
135
|
+
* Callers that need to pass control to the next middleware when there is no
|
|
136
|
+
* body should rely on the `next()` call that this function makes when
|
|
137
|
+
* `Content-Length` is `0` or absent.
|
|
138
|
+
*
|
|
139
|
+
* @param req - The incoming request.
|
|
140
|
+
* @param res - The outgoing response.
|
|
141
|
+
* @param opts - Resolved body-parsing options.
|
|
142
|
+
* @param mimetype - Expected MIME type (e.g. `'application/json'`), or `null`
|
|
143
|
+
* to accept any content type.
|
|
144
|
+
* @param next - The next middleware callback; called when the body is
|
|
145
|
+
* empty or absent.
|
|
146
|
+
* @param callback - Invoked with `(contentType, body)` on success.
|
|
147
|
+
*/
|
|
148
|
+
function readBody(req, res, opts, mimetype, next, callback) {
|
|
149
|
+
// BUG FIX: HTTP/1.1 header names are always lowercased by Node.js.
|
|
150
|
+
// The original code used mixed-case keys ('Content-Length', 'Content-Encoding',
|
|
151
|
+
// 'Content-Type') which always evaluated to undefined.
|
|
152
|
+
const length = parseInt(req.headers['content-length'] ?? '0', 10);
|
|
153
|
+
// No body declared — skip to next middleware.
|
|
154
|
+
if (!length || length === 0)
|
|
155
|
+
return next();
|
|
156
|
+
const maxLength = readSize(opts.limit) || 102_400;
|
|
157
|
+
if (length > maxLength)
|
|
158
|
+
return void res.status(413).send('Content Too Large');
|
|
159
|
+
// Compression handling.
|
|
160
|
+
const encoding = req.headers['content-encoding'];
|
|
161
|
+
if (encoding && (opts.inflate === false || !DECOMPRESS_ALGO[encoding]))
|
|
162
|
+
return void res.status(415).send('Unsupported Media Type: Wrong Content-Encoding');
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
164
|
+
const decompress = (encoding ? DECOMPRESS_ALGO[encoding] : undefined) ?? ((d, c) => c(null, d));
|
|
165
|
+
// Content-Type validation.
|
|
166
|
+
const contentType = req.headers['content-type'] ?? '';
|
|
167
|
+
if (mimetype && contentType.split(';')[0].trim() !== mimetype)
|
|
168
|
+
return void res.status(415).send('Unsupported Media Type: Wrong Content-Type');
|
|
169
|
+
// Stream collection.
|
|
170
|
+
let data = Buffer.alloc(0);
|
|
171
|
+
req.on('data', (chunk) => {
|
|
172
|
+
if (data === null)
|
|
173
|
+
return; // already aborted
|
|
174
|
+
// BUG FIX: the size check must happen AFTER concatenating the new chunk,
|
|
175
|
+
// not before. Checking before allowed a stream of (maxLength-1)-byte chunks
|
|
176
|
+
// to bypass the limit entirely.
|
|
177
|
+
// BUG FIX: the original code referenced `chink` (typo) instead of `chunk`.
|
|
178
|
+
const next_ = Buffer.concat([data, chunk]);
|
|
179
|
+
if (next_.length > maxLength) {
|
|
180
|
+
data = null;
|
|
181
|
+
res.status(413).send('Content Too Large');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
data = next_;
|
|
185
|
+
});
|
|
186
|
+
req.on('end', () => {
|
|
187
|
+
if (data === null)
|
|
188
|
+
return; // aborted during streaming
|
|
189
|
+
// zlib types require NonSharedBuffer; Buffer satisfies this at runtime.
|
|
190
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
191
|
+
decompress(data, (err, decompressed) => {
|
|
192
|
+
if (err)
|
|
193
|
+
return void res.status(500).send(err.message);
|
|
194
|
+
callback(contentType, decompressed);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// Body parsers
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
/**
|
|
202
|
+
* Parse a collected body buffer as plain text, decode it using the charset
|
|
203
|
+
* declared in `contentType`, and assign the result to `req.body`.
|
|
204
|
+
*
|
|
205
|
+
* On success, calls `next()`. On failure, sends a 500 Internal Server Error.
|
|
206
|
+
*
|
|
207
|
+
* @param req - The incoming request (mutated: `req.body` is set).
|
|
208
|
+
* @param res - The outgoing response.
|
|
209
|
+
* @param next - Called on successful parsing.
|
|
210
|
+
* @param contentType - The raw `Content-Type` header value.
|
|
211
|
+
* @param data - The raw body buffer.
|
|
212
|
+
*/
|
|
213
|
+
function readBodyAsPlainText(req, res, next, contentType, data) {
|
|
214
|
+
const charset = extractCharset(contentType);
|
|
215
|
+
try {
|
|
216
|
+
req.body = data.toString(charset);
|
|
217
|
+
next();
|
|
218
|
+
}
|
|
219
|
+
catch (ex) {
|
|
220
|
+
res.status(500).send(ex.message);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Parse a collected body buffer as JSON, decode it using the charset declared
|
|
225
|
+
* in `contentType`, apply an optional `reviver`, and assign the result to
|
|
226
|
+
* `req.body`.
|
|
227
|
+
*
|
|
228
|
+
* On success, calls `next()`. On failure (invalid JSON or unsupported charset),
|
|
229
|
+
* sends a 500 Internal Server Error.
|
|
230
|
+
*
|
|
231
|
+
* @param req - The incoming request (mutated: `req.body` is set).
|
|
232
|
+
* @param res - The outgoing response.
|
|
233
|
+
* @param next - Called on successful parsing.
|
|
234
|
+
* @param opts - Resolved options; `opts.reviver` is passed to `JSON.parse`.
|
|
235
|
+
* @param contentType - The raw `Content-Type` header value.
|
|
236
|
+
* @param data - The raw body buffer.
|
|
237
|
+
*/
|
|
238
|
+
function readBodyAsJson(req, res, next, opts, contentType, data) {
|
|
239
|
+
const charset = extractCharset(contentType);
|
|
240
|
+
try {
|
|
241
|
+
req.body = JSON.parse(data.toString(charset), opts.reviver ?? undefined);
|
|
242
|
+
next();
|
|
243
|
+
}
|
|
244
|
+
catch (ex) {
|
|
245
|
+
res.status(500).send(ex.message);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Parse a collected body buffer as `multipart/form-data`, split it on the
|
|
250
|
+
* boundary declared in `contentType`, parse each part's headers, and assign
|
|
251
|
+
* an array of {@link FormPart} objects to `req.body`.
|
|
252
|
+
*
|
|
253
|
+
* On success, calls `next()`. On failure (missing boundary, malformed parts),
|
|
254
|
+
* sends a 500 Internal Server Error.
|
|
255
|
+
*
|
|
256
|
+
* **Multipart wire format recap:**
|
|
257
|
+
* ```
|
|
258
|
+
* --boundary\r\n
|
|
259
|
+
* Header: value\r\n
|
|
260
|
+
* \r\n
|
|
261
|
+
* <binary content>
|
|
262
|
+
* --boundary\r\n
|
|
263
|
+
* ...
|
|
264
|
+
* --boundary--\r\n
|
|
265
|
+
* ```
|
|
266
|
+
* The boundary string in `Content-Type` does **not** include the leading `--`;
|
|
267
|
+
* actual part delimiters on the wire are `\r\n--boundary`.
|
|
268
|
+
*
|
|
269
|
+
* @param req - The incoming request (mutated: `req.body` is set).
|
|
270
|
+
* @param res - The outgoing response.
|
|
271
|
+
* @param next - Called on successful parsing.
|
|
272
|
+
* @param contentType - The raw `Content-Type` header value (must include
|
|
273
|
+
* `boundary=<value>`).
|
|
274
|
+
* @param data - The raw body buffer.
|
|
275
|
+
*/
|
|
276
|
+
function readBodyAsFormData(req, res, next, contentType, data) {
|
|
277
|
+
// BUG FIX: the original regex used `'s+$` (literal quote) instead of `\s+$`.
|
|
278
|
+
const boundary = contentType
|
|
279
|
+
.split(';')
|
|
280
|
+
.map((s) => s.replace(/^\s+|\s+$/g, ''))
|
|
281
|
+
.find((s) => s.startsWith('boundary='))
|
|
282
|
+
?.substring('boundary='.length);
|
|
283
|
+
if (!boundary)
|
|
284
|
+
return void res.status(400).send('Bad Request: missing multipart boundary');
|
|
285
|
+
try {
|
|
286
|
+
// Wire-level delimiter: each part (after the preamble) is preceded by
|
|
287
|
+
// \r\n--boundary. We split on this sequence so every resulting slice is
|
|
288
|
+
// the raw content of one part (headers + blank line + body), without any
|
|
289
|
+
// leading delimiter bytes.
|
|
290
|
+
// BUG FIX: the original code passed `buffer` (undefined) instead of `data`.
|
|
291
|
+
const delimiter = Buffer.from(`\r\n--${boundary}`);
|
|
292
|
+
// Prepend \r\n so the very first part is also cleanly split.
|
|
293
|
+
const normalized = Buffer.concat([Buffer.from('\r\n'), data]);
|
|
294
|
+
const rawParts = splitBuffer(normalized, delimiter);
|
|
295
|
+
const parts = [];
|
|
296
|
+
for (const part of rawParts) {
|
|
297
|
+
// The closing delimiter ends with '--'; skip it.
|
|
298
|
+
// BUG FIX: the original check `buf.length == 2 && buf.toString() == '--'`
|
|
299
|
+
// was incorrect. After splitting on \r\n--boundary, the terminal entry
|
|
300
|
+
// is '--\r\n' (or just '--'), not a 2-byte '--'. Use startsWith.
|
|
301
|
+
if (part.toString('utf8', 0, 2) === '--')
|
|
302
|
+
continue;
|
|
303
|
+
// Each part begins with \r\n (from after the delimiter), then headers,
|
|
304
|
+
// then \r\n\r\n (blank line), then content.
|
|
305
|
+
// Skip the leading \r\n.
|
|
306
|
+
const partContent = part.slice(2);
|
|
307
|
+
const blankLine = Buffer.from('\r\n\r\n');
|
|
308
|
+
const blankIdx = partContent.indexOf(blankLine);
|
|
309
|
+
if (blankIdx === -1)
|
|
310
|
+
continue; // malformed part — skip
|
|
311
|
+
const headerSection = partContent.slice(0, blankIdx).toString('utf8');
|
|
312
|
+
const content = partContent.slice(blankIdx + blankLine.length);
|
|
313
|
+
const headers = {};
|
|
314
|
+
for (const line of headerSection.split('\r\n')) {
|
|
315
|
+
if (!line)
|
|
316
|
+
continue;
|
|
317
|
+
const colonIdx = line.indexOf(':');
|
|
318
|
+
if (colonIdx === -1)
|
|
319
|
+
continue;
|
|
320
|
+
const key = line.substring(0, colonIdx).replace(/^\s+|\s+$/g, '').toLowerCase();
|
|
321
|
+
const value = line.substring(colonIdx + 1).replace(/^\s+|\s+$/g, '');
|
|
322
|
+
headers[key] = value;
|
|
323
|
+
}
|
|
324
|
+
parts.push({ headers, content });
|
|
325
|
+
}
|
|
326
|
+
req.body = parts;
|
|
327
|
+
next();
|
|
328
|
+
}
|
|
329
|
+
catch (ex) {
|
|
330
|
+
res.status(500).send(ex.message);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Dispatch table mapping a MIME type to its body-parser function.
|
|
335
|
+
* Used by {@link parseBody} to select the appropriate parser at runtime.
|
|
336
|
+
*/
|
|
337
|
+
const BODY_READERS = {
|
|
338
|
+
'multipart/form-data': (req, res, next, _opts, ct, data) => readBodyAsFormData(req, res, next, ct, data),
|
|
339
|
+
'application/json': (req, res, next, opts, ct, data) => readBodyAsJson(req, res, next, opts, ct, data),
|
|
340
|
+
'text/plain': (req, res, next, _opts, ct, data) => readBodyAsPlainText(req, res, next, ct, data),
|
|
341
|
+
};
|
|
342
|
+
// ---------------------------------------------------------------------------
|
|
343
|
+
// Public middleware factories
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
345
|
+
/**
|
|
346
|
+
* Middleware factory that parses a `application/json` request body and
|
|
347
|
+
* assigns the parsed value to `req.body`.
|
|
348
|
+
*
|
|
349
|
+
* Also attaches a `res.json(data)` helper to the response object so that
|
|
350
|
+
* handlers can send JSON responses conveniently:
|
|
351
|
+
* ```ts
|
|
352
|
+
* res.json({ ok: true });
|
|
353
|
+
* ```
|
|
354
|
+
*
|
|
355
|
+
* Behaviour:
|
|
356
|
+
* - Requests without a body (`Content-Length: 0` or absent) are passed
|
|
357
|
+
* through to `next()` without touching `req.body`.
|
|
358
|
+
* - Bodies larger than `opts.limit` receive **413 Content Too Large**.
|
|
359
|
+
* - Bodies with an unsupported `Content-Encoding` receive
|
|
360
|
+
* **415 Unsupported Media Type**.
|
|
361
|
+
* - Bodies whose `Content-Type` is not `application/json` receive
|
|
362
|
+
* **415 Unsupported Media Type**.
|
|
363
|
+
* - Parse errors receive **500 Internal Server Error**.
|
|
364
|
+
*
|
|
365
|
+
* @param opts - Optional configuration (see {@link BodyOptions}).
|
|
366
|
+
* @returns An Express-compatible middleware function.
|
|
367
|
+
*/
|
|
368
|
+
function json(opts) {
|
|
369
|
+
const resolved = {
|
|
370
|
+
inflate: true,
|
|
371
|
+
limit: '100kb',
|
|
372
|
+
reviver: null,
|
|
373
|
+
strict: true,
|
|
374
|
+
...opts,
|
|
375
|
+
};
|
|
376
|
+
return (req, res, next) => {
|
|
377
|
+
// Attach a JSON response helper so handlers can call res.json(data).
|
|
378
|
+
res.json = (data) => {
|
|
379
|
+
res.write(JSON.stringify(data));
|
|
380
|
+
res.end();
|
|
381
|
+
};
|
|
382
|
+
readBody(req, res, resolved, 'application/json', next, (contentType, body) => {
|
|
383
|
+
readBodyAsJson(req, res, next, resolved, contentType, body);
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Middleware factory that parses a `multipart/form-data` request body and
|
|
389
|
+
* assigns an array of {@link FormPart} objects to `req.body`.
|
|
390
|
+
*
|
|
391
|
+
* Each element in `req.body` exposes:
|
|
392
|
+
* - `headers` — the part's MIME headers (e.g. `Content-Disposition`).
|
|
393
|
+
* - `content` — the raw binary content of the part as a `Buffer`.
|
|
394
|
+
*
|
|
395
|
+
* Behaviour:
|
|
396
|
+
* - Requests without a body are passed through to `next()`.
|
|
397
|
+
* - Bodies larger than `opts.limit` receive **413 Content Too Large**.
|
|
398
|
+
* - Bodies with a missing or malformed `boundary` parameter receive
|
|
399
|
+
* **400 Bad Request**.
|
|
400
|
+
* - Parse errors receive **500 Internal Server Error**.
|
|
401
|
+
*
|
|
402
|
+
* @param opts - Optional configuration (see {@link BodyOptions}).
|
|
403
|
+
* @returns An Express-compatible middleware function.
|
|
404
|
+
*/
|
|
405
|
+
function formData(opts) {
|
|
406
|
+
const resolved = {
|
|
407
|
+
inflate: true,
|
|
408
|
+
limit: '100kb',
|
|
409
|
+
reviver: null,
|
|
410
|
+
strict: true,
|
|
411
|
+
...opts,
|
|
412
|
+
};
|
|
413
|
+
return (req, res, next) => {
|
|
414
|
+
readBody(req, res, resolved, 'multipart/form-data', next, (contentType, body) => {
|
|
415
|
+
readBodyAsFormData(req, res, next, contentType, body);
|
|
416
|
+
});
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Middleware factory that auto-detects the `Content-Type` of the request body
|
|
421
|
+
* and parses it using the appropriate parser.
|
|
422
|
+
*
|
|
423
|
+
* Supported MIME types:
|
|
424
|
+
* - `application/json` → parsed as JSON; result is a JS value.
|
|
425
|
+
* - `multipart/form-data` → parsed as multipart; result is `FormPart[]`.
|
|
426
|
+
* - `text/plain` → decoded as text; result is a string.
|
|
427
|
+
*
|
|
428
|
+
* Requests with an unsupported MIME type receive **415 Unsupported Media Type**.
|
|
429
|
+
* All other error conditions behave identically to {@link json} and
|
|
430
|
+
* {@link formData}.
|
|
431
|
+
*
|
|
432
|
+
* @param opts - Optional configuration (see {@link BodyOptions}).
|
|
433
|
+
* @returns An Express-compatible middleware function.
|
|
434
|
+
*/
|
|
435
|
+
function parseBody(opts) {
|
|
436
|
+
const resolved = {
|
|
437
|
+
inflate: true,
|
|
438
|
+
limit: '100kb',
|
|
439
|
+
reviver: null,
|
|
440
|
+
strict: true,
|
|
441
|
+
...opts,
|
|
442
|
+
};
|
|
443
|
+
return (req, res, next) => {
|
|
444
|
+
readBody(req, res, resolved, null, next, (contentType, body) => {
|
|
445
|
+
const mimetype = contentType.split(';')[0].trim();
|
|
446
|
+
if (!BODY_READERS[mimetype])
|
|
447
|
+
return res.status(415).send('Unsupported Media Type');
|
|
448
|
+
BODY_READERS[mimetype](req, res, next, resolved, contentType, body);
|
|
449
|
+
});
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
// ---------------------------------------------------------------------------
|
|
453
|
+
// Logger middleware
|
|
454
|
+
// ---------------------------------------------------------------------------
|
|
455
|
+
/**
|
|
456
|
+
* ANSI terminal colour codes indexed by HTTP status class (1xx–5xx).
|
|
457
|
+
*
|
|
458
|
+
* Index 0 is unused (no HTTP 0xx class).
|
|
459
|
+
* - 1xx → yellow (`\x1b[33m`)
|
|
460
|
+
* - 2xx → green (`\x1b[32m`)
|
|
461
|
+
* - 3xx → yellow (`\x1b[33m`)
|
|
462
|
+
* - 4xx → red (`\x1b[31m`)
|
|
463
|
+
* - 5xx → bright red (`\x1b[91m`)
|
|
464
|
+
*/
|
|
465
|
+
const STATUS_COLORS = [
|
|
466
|
+
'\x1b[0m', // 0 — fallback / unknown
|
|
467
|
+
'\x1b[33m', // 1xx — informational (yellow)
|
|
468
|
+
'\x1b[32m', // 2xx — success (green)
|
|
469
|
+
'\x1b[33m', // 3xx — redirection (yellow)
|
|
470
|
+
'\x1b[31m', // 4xx — client error (red)
|
|
471
|
+
'\x1b[91m', // 5xx — server error (bright red)
|
|
472
|
+
];
|
|
473
|
+
/** ANSI reset escape sequence. */
|
|
474
|
+
const ANSI_RESET = '\x1b[0m';
|
|
475
|
+
/**
|
|
476
|
+
* Middleware factory that logs one line per completed HTTP request to the
|
|
477
|
+
* console (or a custom logger function).
|
|
478
|
+
*
|
|
479
|
+
* Each log line contains:
|
|
480
|
+
* - Timestamp (formatted with `Intl.DateTimeFormat`).
|
|
481
|
+
* - HTTP status code (ANSI-coloured by status class).
|
|
482
|
+
* - HTTP method and request path.
|
|
483
|
+
* - Client IP address (honours `X-Forwarded-For`).
|
|
484
|
+
* - User identity (from `opts.user` or `'-'`).
|
|
485
|
+
* - Elapsed time in milliseconds.
|
|
486
|
+
* - Response `Content-Length` (or `'-'` when absent).
|
|
487
|
+
*
|
|
488
|
+
* **Lost-request tracking:** when `opts.track` is `true`, a timer is started
|
|
489
|
+
* for each request. If the response is not finished within `opts.trackTimeout`
|
|
490
|
+
* milliseconds, a `LOST` warning line is emitted. This is useful in
|
|
491
|
+
* development to surface handlers that never call `res.end()`.
|
|
492
|
+
*
|
|
493
|
+
* @param opts - Optional configuration (see {@link LoggerOptions}).
|
|
494
|
+
* @returns An Express-compatible middleware function.
|
|
495
|
+
*
|
|
496
|
+
* @example
|
|
497
|
+
* ```ts
|
|
498
|
+
* app.use('/', logger({
|
|
499
|
+
* track: true,
|
|
500
|
+
* trackTimeout: 30_000,
|
|
501
|
+
* user: (req) => (req as any).authUser ?? '-',
|
|
502
|
+
* locale: 'en-US',
|
|
503
|
+
* logger: (msg) => process.stderr.write(msg + '\n'),
|
|
504
|
+
* }));
|
|
505
|
+
* ```
|
|
506
|
+
*/
|
|
507
|
+
function logger(opts) {
|
|
508
|
+
const options = opts ?? {};
|
|
509
|
+
const log = options.logger ?? console.log;
|
|
510
|
+
const formatter = new Intl.DateTimeFormat(options.locale ?? 'en-GB', options.dateFormat ?? {
|
|
511
|
+
month: 'short',
|
|
512
|
+
day: '2-digit',
|
|
513
|
+
hour: '2-digit',
|
|
514
|
+
minute: '2-digit',
|
|
515
|
+
});
|
|
516
|
+
return (req, res, next) => {
|
|
517
|
+
// Capture timestamp and path at request-arrival time so they are stable
|
|
518
|
+
// even if later middleware mutates req.path (e.g. prefix stripping).
|
|
519
|
+
const timestamp = formatter.format(new Date());
|
|
520
|
+
const requestPath = req.path ?? req.url ?? '/';
|
|
521
|
+
const ip = req.headers['x-forwarded-for']
|
|
522
|
+
?? req.socket?.remoteAddress
|
|
523
|
+
?? '-';
|
|
524
|
+
const user = options.user?.(req) ?? '-';
|
|
525
|
+
const receivedAt = Date.now();
|
|
526
|
+
// Lost-request tracking.
|
|
527
|
+
const tracker = options.track === true
|
|
528
|
+
? setTimeout(() => {
|
|
529
|
+
log(`${timestamp} LOST ${req.method} ${requestPath} ${ip} <${user}>`);
|
|
530
|
+
}, options.trackTimeout ?? 30_000)
|
|
531
|
+
: null;
|
|
532
|
+
res.on('finish', () => {
|
|
533
|
+
if (tracker !== null)
|
|
534
|
+
clearTimeout(tracker);
|
|
535
|
+
const elapsed = Date.now() - receivedAt;
|
|
536
|
+
// BUG FIX: Math.floor is more explicit and correct than parseInt for
|
|
537
|
+
// integer division. Both work, but parseInt(200/100) has a subtle
|
|
538
|
+
// coercion to string first. Math.floor is semantically clearer.
|
|
539
|
+
const statusClass = Math.floor(res.statusCode / 100);
|
|
540
|
+
const colour = STATUS_COLORS[statusClass] ?? STATUS_COLORS[0];
|
|
541
|
+
const statusStr = `${colour}${res.statusCode}${ANSI_RESET}`;
|
|
542
|
+
const contentLen = res.getHeader('content-length') ?? '-';
|
|
543
|
+
log(`${timestamp} ${statusStr} ${req.method} ${requestPath} ${ip} <${user}> ${elapsed}ms (${contentLen})`);
|
|
544
|
+
});
|
|
545
|
+
next();
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
exports.default = { json, formData, parseBody, logger };
|
|
549
|
+
//# sourceMappingURL=misc.js.map
|
package/dist/misc.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"misc.js","sourceRoot":"","sources":["../src/misc.ts"],"names":[],"mappings":"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"}
|