sockress 0.2.6 → 0.2.7
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/dist/index.d.ts +269 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1267 -0
- package/dist/index.js.map +1 -0
- package/package.json +6 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,1267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Router = exports.createSockress = exports.SockressApp = exports.SockressAppRoute = exports.SockressRoute = exports.SockressRouter = exports.SockressResponse = exports.SockressRequestImpl = void 0;
|
|
7
|
+
exports.sockress = sockress;
|
|
8
|
+
exports.createUploader = createUploader;
|
|
9
|
+
exports.serveStatic = serveStatic;
|
|
10
|
+
const http_1 = __importDefault(require("http"));
|
|
11
|
+
const tls_1 = require("tls");
|
|
12
|
+
const ws_1 = require("ws");
|
|
13
|
+
const cookie_1 = require("cookie");
|
|
14
|
+
const nanoid_1 = require("nanoid");
|
|
15
|
+
const multer_1 = __importDefault(require("multer"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
const fs_1 = require("fs");
|
|
18
|
+
class SockressRequestImpl {
|
|
19
|
+
constructor(id, method, path, query, headers, body, cookies, files, file, type, ip, protocol, secure, raw, originalUrl, baseUrl) {
|
|
20
|
+
this.id = id;
|
|
21
|
+
this.method = method;
|
|
22
|
+
this.path = path;
|
|
23
|
+
this.query = query;
|
|
24
|
+
this.headers = headers;
|
|
25
|
+
this.body = body;
|
|
26
|
+
this.cookies = cookies;
|
|
27
|
+
this.files = files;
|
|
28
|
+
this.file = file;
|
|
29
|
+
this.type = type;
|
|
30
|
+
this.ip = ip;
|
|
31
|
+
this.protocol = protocol;
|
|
32
|
+
this.secure = secure;
|
|
33
|
+
this.raw = raw;
|
|
34
|
+
this.params = {};
|
|
35
|
+
this.context = {};
|
|
36
|
+
const host = this.get('host') || '';
|
|
37
|
+
this.hostname = host.split(':')[0] || undefined;
|
|
38
|
+
this.originalUrl = originalUrl;
|
|
39
|
+
this.baseUrl = baseUrl || '';
|
|
40
|
+
this.subdomains = this.hostname ? extractSubdomains(this.hostname) : [];
|
|
41
|
+
}
|
|
42
|
+
get(field) {
|
|
43
|
+
const key = field.toLowerCase();
|
|
44
|
+
const value = this.headers[key];
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
return value[0];
|
|
47
|
+
}
|
|
48
|
+
if (typeof value === 'number') {
|
|
49
|
+
return String(value);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
accepts(types) {
|
|
54
|
+
const acceptHeader = this.get('accept') || '*/*';
|
|
55
|
+
const acceptTypes = acceptHeader.split(',').map((t) => t.trim().split(';')[0].toLowerCase());
|
|
56
|
+
const requestedTypes = Array.isArray(types) ? types : [types];
|
|
57
|
+
for (const requested of requestedTypes) {
|
|
58
|
+
const normalized = requested.toLowerCase();
|
|
59
|
+
if (acceptTypes.includes(normalized) || acceptTypes.includes('*/*')) {
|
|
60
|
+
return requested;
|
|
61
|
+
}
|
|
62
|
+
const mimeType = normalized.includes('/') ? normalized : `application/${normalized}`;
|
|
63
|
+
if (acceptTypes.some((at) => at === mimeType || at.startsWith(mimeType.split('/')[0] + '/*'))) {
|
|
64
|
+
return requested;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
is(type) {
|
|
70
|
+
const contentType = (this.get('content-type') || '').toLowerCase().split(';')[0].trim();
|
|
71
|
+
const types = Array.isArray(type) ? type : [type];
|
|
72
|
+
for (const t of types) {
|
|
73
|
+
const normalized = t.toLowerCase();
|
|
74
|
+
if (contentType === normalized || contentType.startsWith(normalized + '/')) {
|
|
75
|
+
return t;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return contentType ? false : null;
|
|
79
|
+
}
|
|
80
|
+
param(name, defaultValue) {
|
|
81
|
+
var _a, _b;
|
|
82
|
+
return (_b = (_a = this.params[name]) !== null && _a !== void 0 ? _a : defaultValue) !== null && _b !== void 0 ? _b : '';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.SockressRequestImpl = SockressRequestImpl;
|
|
86
|
+
class SockressResponse {
|
|
87
|
+
constructor(mode, cors, allowedOrigin) {
|
|
88
|
+
this.mode = mode;
|
|
89
|
+
this.cors = cors;
|
|
90
|
+
this.allowedOrigin = allowedOrigin;
|
|
91
|
+
this.statusCode = 200;
|
|
92
|
+
this.sent = false;
|
|
93
|
+
this.headers = {};
|
|
94
|
+
this.cookies = [];
|
|
95
|
+
if (mode.kind === 'http') {
|
|
96
|
+
this.raw = mode.res;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
status(code) {
|
|
100
|
+
this.statusCode = code;
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
set(field, value) {
|
|
104
|
+
this.headers[field.toLowerCase()] = value;
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
append(field, value) {
|
|
108
|
+
const current = this.headers[field.toLowerCase()];
|
|
109
|
+
if (current) {
|
|
110
|
+
this.headers[field.toLowerCase()] = `${current}, ${value}`;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.headers[field.toLowerCase()] = value;
|
|
114
|
+
}
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
cookie(name, value, options = {}) {
|
|
118
|
+
this.cookies.push((0, cookie_1.serialize)(name, value, options));
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
clearCookie(name, options = {}) {
|
|
122
|
+
return this.cookie(name, '', { ...options, maxAge: 0 });
|
|
123
|
+
}
|
|
124
|
+
json(payload) {
|
|
125
|
+
this.set('content-type', 'application/json; charset=utf-8');
|
|
126
|
+
return this.send(payload);
|
|
127
|
+
}
|
|
128
|
+
send(payload) {
|
|
129
|
+
if (this.sent) {
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
this.sent = true;
|
|
133
|
+
if (!this.headers['content-type'] && typeof payload === 'string') {
|
|
134
|
+
this.set('content-type', 'text/plain; charset=utf-8');
|
|
135
|
+
}
|
|
136
|
+
const headersWithCors = this.buildHeaders();
|
|
137
|
+
if (this.mode.kind === 'http') {
|
|
138
|
+
const res = this.mode.res;
|
|
139
|
+
res.statusCode = this.statusCode;
|
|
140
|
+
Object.entries(headersWithCors).forEach(([key, value]) => {
|
|
141
|
+
res.setHeader(key, value);
|
|
142
|
+
});
|
|
143
|
+
if (this.cookies.length) {
|
|
144
|
+
res.setHeader('Set-Cookie', this.cookies);
|
|
145
|
+
}
|
|
146
|
+
if (Buffer.isBuffer(payload)) {
|
|
147
|
+
res.end(payload);
|
|
148
|
+
}
|
|
149
|
+
else if (typeof payload === 'string') {
|
|
150
|
+
res.end(payload);
|
|
151
|
+
}
|
|
152
|
+
else if (payload === undefined || payload === null) {
|
|
153
|
+
res.end();
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const buffer = Buffer.from(JSON.stringify(payload));
|
|
157
|
+
if (!this.headers['content-type']) {
|
|
158
|
+
res.setHeader('content-type', 'application/json; charset=utf-8');
|
|
159
|
+
}
|
|
160
|
+
res.end(buffer);
|
|
161
|
+
}
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
const message = {
|
|
165
|
+
type: 'response',
|
|
166
|
+
id: this.mode.requestId,
|
|
167
|
+
status: this.statusCode,
|
|
168
|
+
headers: headersWithCors,
|
|
169
|
+
body: payload,
|
|
170
|
+
cookies: this.cookies.length ? [...this.cookies] : undefined
|
|
171
|
+
};
|
|
172
|
+
this.mode.socket.send(JSON.stringify(message));
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
end() {
|
|
176
|
+
return this.send();
|
|
177
|
+
}
|
|
178
|
+
isSent() {
|
|
179
|
+
return this.sent;
|
|
180
|
+
}
|
|
181
|
+
redirect(url, statusOrUrl) {
|
|
182
|
+
if (typeof url === 'number') {
|
|
183
|
+
this.statusCode = url;
|
|
184
|
+
const target = typeof statusOrUrl === 'string' ? statusOrUrl : '/';
|
|
185
|
+
this.set('Location', target);
|
|
186
|
+
return this.send();
|
|
187
|
+
}
|
|
188
|
+
const status = typeof statusOrUrl === 'number' ? statusOrUrl : 302;
|
|
189
|
+
this.status(status);
|
|
190
|
+
this.set('Location', url);
|
|
191
|
+
return this.send();
|
|
192
|
+
}
|
|
193
|
+
sendFile(filePath, options) {
|
|
194
|
+
return new Promise(async (resolve, reject) => {
|
|
195
|
+
try {
|
|
196
|
+
const resolvedPath = (options === null || options === void 0 ? void 0 : options.root) ? path_1.default.join(options.root, filePath) : path_1.default.resolve(filePath);
|
|
197
|
+
const stats = await fs_1.promises.stat(resolvedPath);
|
|
198
|
+
if (!stats.isFile()) {
|
|
199
|
+
reject(new Error('Path is not a file'));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const buffer = await fs_1.promises.readFile(resolvedPath);
|
|
203
|
+
if (options === null || options === void 0 ? void 0 : options.headers) {
|
|
204
|
+
Object.entries(options.headers).forEach(([key, value]) => {
|
|
205
|
+
this.set(key, value);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
this.set('content-type', mimeFromExtension(path_1.default.extname(resolvedPath)));
|
|
209
|
+
this.set('content-length', stats.size.toString());
|
|
210
|
+
this.send(buffer);
|
|
211
|
+
resolve(this);
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
reject(error);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
download(filePath, filename, options) {
|
|
219
|
+
return new Promise(async (resolve, reject) => {
|
|
220
|
+
try {
|
|
221
|
+
const resolvedPath = (options === null || options === void 0 ? void 0 : options.root) ? path_1.default.join(options.root, filePath) : path_1.default.resolve(filePath);
|
|
222
|
+
const stats = await fs_1.promises.stat(resolvedPath);
|
|
223
|
+
if (!stats.isFile()) {
|
|
224
|
+
reject(new Error('Path is not a file'));
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const buffer = await fs_1.promises.readFile(resolvedPath);
|
|
228
|
+
const downloadName = filename || path_1.default.basename(resolvedPath);
|
|
229
|
+
if (options === null || options === void 0 ? void 0 : options.headers) {
|
|
230
|
+
Object.entries(options.headers).forEach(([key, value]) => {
|
|
231
|
+
this.set(key, value);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
this.set('content-disposition', `attachment; filename="${downloadName}"`);
|
|
235
|
+
this.set('content-type', mimeFromExtension(path_1.default.extname(resolvedPath)));
|
|
236
|
+
this.set('content-length', stats.size.toString());
|
|
237
|
+
this.send(buffer);
|
|
238
|
+
resolve(this);
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
reject(error);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
sendStatus(code) {
|
|
246
|
+
this.status(code);
|
|
247
|
+
const statusText = getStatusText(code);
|
|
248
|
+
return this.send(statusText);
|
|
249
|
+
}
|
|
250
|
+
format(obj, req) {
|
|
251
|
+
if (this.sent)
|
|
252
|
+
return this;
|
|
253
|
+
const accept = req.get('accept') || '*/*';
|
|
254
|
+
const keys = Object.keys(obj);
|
|
255
|
+
for (const key of keys) {
|
|
256
|
+
if (accept.includes(key) || key === 'default') {
|
|
257
|
+
const handler = obj[key];
|
|
258
|
+
if (handler) {
|
|
259
|
+
handler(req, this);
|
|
260
|
+
return this;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (obj.default) {
|
|
265
|
+
obj.default(req, this);
|
|
266
|
+
}
|
|
267
|
+
return this;
|
|
268
|
+
}
|
|
269
|
+
location(url) {
|
|
270
|
+
return this.set('Location', url);
|
|
271
|
+
}
|
|
272
|
+
vary(field) {
|
|
273
|
+
const current = this.headers['vary'];
|
|
274
|
+
if (current) {
|
|
275
|
+
this.set('Vary', `${current}, ${field}`);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
this.set('Vary', field);
|
|
279
|
+
}
|
|
280
|
+
return this;
|
|
281
|
+
}
|
|
282
|
+
buildHeaders() {
|
|
283
|
+
const headers = { ...this.headers };
|
|
284
|
+
headers['access-control-allow-origin'] = this.allowedOrigin;
|
|
285
|
+
headers['access-control-allow-credentials'] = String(this.cors.credentials);
|
|
286
|
+
headers['access-control-allow-methods'] = this.cors.methods.join(', ');
|
|
287
|
+
headers['access-control-allow-headers'] = this.cors.allowedHeaders.join(', ');
|
|
288
|
+
headers['access-control-expose-headers'] = this.cors.exposedHeaders.join(', ');
|
|
289
|
+
headers['access-control-max-age'] = String(this.cors.maxAge);
|
|
290
|
+
return headers;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
exports.SockressResponse = SockressResponse;
|
|
294
|
+
class SockressRouter {
|
|
295
|
+
constructor() {
|
|
296
|
+
this.middlewares = [];
|
|
297
|
+
this.routes = [];
|
|
298
|
+
}
|
|
299
|
+
use(pathOrHandler, ...rest) {
|
|
300
|
+
let path = '/';
|
|
301
|
+
let stack = [];
|
|
302
|
+
if (typeof pathOrHandler === 'string') {
|
|
303
|
+
path = pathOrHandler;
|
|
304
|
+
stack = rest;
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
stack = [pathOrHandler, ...rest];
|
|
308
|
+
}
|
|
309
|
+
if (!stack.length) {
|
|
310
|
+
throw new Error('use() requires at least one handler');
|
|
311
|
+
}
|
|
312
|
+
for (const handler of stack) {
|
|
313
|
+
if (!handler)
|
|
314
|
+
continue;
|
|
315
|
+
this.middlewares.push({
|
|
316
|
+
path,
|
|
317
|
+
handler,
|
|
318
|
+
isErrorHandler: handler.length === 4
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return this;
|
|
322
|
+
}
|
|
323
|
+
register(method, path, handlers) {
|
|
324
|
+
if (!handlers.length) {
|
|
325
|
+
throw new Error(`Route ${method} ${path} requires at least one handler`);
|
|
326
|
+
}
|
|
327
|
+
this.routes.push({
|
|
328
|
+
method,
|
|
329
|
+
matcher: buildMatcher(path),
|
|
330
|
+
handlers
|
|
331
|
+
});
|
|
332
|
+
return this;
|
|
333
|
+
}
|
|
334
|
+
get(path, ...handlers) {
|
|
335
|
+
return this.register('GET', path, handlers);
|
|
336
|
+
}
|
|
337
|
+
post(path, ...handlers) {
|
|
338
|
+
return this.register('POST', path, handlers);
|
|
339
|
+
}
|
|
340
|
+
put(path, ...handlers) {
|
|
341
|
+
return this.register('PUT', path, handlers);
|
|
342
|
+
}
|
|
343
|
+
patch(path, ...handlers) {
|
|
344
|
+
return this.register('PATCH', path, handlers);
|
|
345
|
+
}
|
|
346
|
+
delete(path, ...handlers) {
|
|
347
|
+
return this.register('DELETE', path, handlers);
|
|
348
|
+
}
|
|
349
|
+
head(path, ...handlers) {
|
|
350
|
+
return this.register('HEAD', path, handlers);
|
|
351
|
+
}
|
|
352
|
+
options(path, ...handlers) {
|
|
353
|
+
return this.register('OPTIONS', path, handlers);
|
|
354
|
+
}
|
|
355
|
+
all(path, ...handlers) {
|
|
356
|
+
return this.register('ALL', path, handlers);
|
|
357
|
+
}
|
|
358
|
+
route(path) {
|
|
359
|
+
return new SockressRoute(this, path);
|
|
360
|
+
}
|
|
361
|
+
getStack() {
|
|
362
|
+
return { middlewares: this.middlewares, routes: this.routes };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
exports.SockressRouter = SockressRouter;
|
|
366
|
+
class SockressRoute {
|
|
367
|
+
constructor(router, path) {
|
|
368
|
+
this.router = router;
|
|
369
|
+
this.path = path;
|
|
370
|
+
}
|
|
371
|
+
get(...handlers) {
|
|
372
|
+
this.router.get(this.path, ...handlers);
|
|
373
|
+
return this;
|
|
374
|
+
}
|
|
375
|
+
post(...handlers) {
|
|
376
|
+
this.router.post(this.path, ...handlers);
|
|
377
|
+
return this;
|
|
378
|
+
}
|
|
379
|
+
put(...handlers) {
|
|
380
|
+
this.router.put(this.path, ...handlers);
|
|
381
|
+
return this;
|
|
382
|
+
}
|
|
383
|
+
patch(...handlers) {
|
|
384
|
+
this.router.patch(this.path, ...handlers);
|
|
385
|
+
return this;
|
|
386
|
+
}
|
|
387
|
+
delete(...handlers) {
|
|
388
|
+
this.router.delete(this.path, ...handlers);
|
|
389
|
+
return this;
|
|
390
|
+
}
|
|
391
|
+
head(...handlers) {
|
|
392
|
+
this.router.head(this.path, ...handlers);
|
|
393
|
+
return this;
|
|
394
|
+
}
|
|
395
|
+
options(...handlers) {
|
|
396
|
+
this.router.options(this.path, ...handlers);
|
|
397
|
+
return this;
|
|
398
|
+
}
|
|
399
|
+
all(...handlers) {
|
|
400
|
+
this.router.all(this.path, ...handlers);
|
|
401
|
+
return this;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
exports.SockressRoute = SockressRoute;
|
|
405
|
+
class SockressAppRoute {
|
|
406
|
+
constructor(app, path) {
|
|
407
|
+
this.app = app;
|
|
408
|
+
this.path = path;
|
|
409
|
+
}
|
|
410
|
+
get(...handlers) {
|
|
411
|
+
this.app.get(this.path, ...handlers);
|
|
412
|
+
return this;
|
|
413
|
+
}
|
|
414
|
+
post(...handlers) {
|
|
415
|
+
this.app.post(this.path, ...handlers);
|
|
416
|
+
return this;
|
|
417
|
+
}
|
|
418
|
+
put(...handlers) {
|
|
419
|
+
this.app.put(this.path, ...handlers);
|
|
420
|
+
return this;
|
|
421
|
+
}
|
|
422
|
+
patch(...handlers) {
|
|
423
|
+
this.app.patch(this.path, ...handlers);
|
|
424
|
+
return this;
|
|
425
|
+
}
|
|
426
|
+
delete(...handlers) {
|
|
427
|
+
this.app.delete(this.path, ...handlers);
|
|
428
|
+
return this;
|
|
429
|
+
}
|
|
430
|
+
head(...handlers) {
|
|
431
|
+
this.app.head(this.path, ...handlers);
|
|
432
|
+
return this;
|
|
433
|
+
}
|
|
434
|
+
options(...handlers) {
|
|
435
|
+
this.app.options(this.path, ...handlers);
|
|
436
|
+
return this;
|
|
437
|
+
}
|
|
438
|
+
all(...handlers) {
|
|
439
|
+
this.app.all(this.path, ...handlers);
|
|
440
|
+
return this;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
exports.SockressAppRoute = SockressAppRoute;
|
|
444
|
+
class SockressApp {
|
|
445
|
+
constructor(config) {
|
|
446
|
+
this.config = config;
|
|
447
|
+
this.middlewares = [];
|
|
448
|
+
this.routes = [];
|
|
449
|
+
this.paramHandlers = new Map();
|
|
450
|
+
this.shutdownRegistered = false;
|
|
451
|
+
this.shuttingDown = false;
|
|
452
|
+
}
|
|
453
|
+
static Router() {
|
|
454
|
+
return new SockressRouter();
|
|
455
|
+
}
|
|
456
|
+
static create(options) {
|
|
457
|
+
return new SockressApp(normalizeOptions(options));
|
|
458
|
+
}
|
|
459
|
+
use(pathOrHandler, ...rest) {
|
|
460
|
+
let path = '/';
|
|
461
|
+
let stack = [];
|
|
462
|
+
if (typeof pathOrHandler === 'string') {
|
|
463
|
+
path = pathOrHandler;
|
|
464
|
+
stack = rest;
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
stack = [pathOrHandler, ...rest];
|
|
468
|
+
}
|
|
469
|
+
if (!stack.length) {
|
|
470
|
+
throw new Error('use() requires at least one handler');
|
|
471
|
+
}
|
|
472
|
+
for (const item of stack) {
|
|
473
|
+
if (!item)
|
|
474
|
+
continue;
|
|
475
|
+
if (item instanceof SockressRouter) {
|
|
476
|
+
const routerStack = item.getStack();
|
|
477
|
+
for (const layer of routerStack.middlewares) {
|
|
478
|
+
this.middlewares.push({
|
|
479
|
+
path: path === '/' ? layer.path : `${path}${layer.path === '/' ? '' : layer.path}`,
|
|
480
|
+
handler: layer.handler,
|
|
481
|
+
isErrorHandler: layer.isErrorHandler
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
for (const route of routerStack.routes) {
|
|
485
|
+
this.routes.push({
|
|
486
|
+
method: route.method,
|
|
487
|
+
matcher: buildMatcher(path === '/' ? route.matcher.raw : `${path}${route.matcher.raw}`),
|
|
488
|
+
handlers: route.handlers
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
this.middlewares.push({
|
|
494
|
+
path,
|
|
495
|
+
handler: item,
|
|
496
|
+
isErrorHandler: item.length === 4
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return this;
|
|
501
|
+
}
|
|
502
|
+
useStatic(route, directory, options) {
|
|
503
|
+
var _a;
|
|
504
|
+
const handler = serveStatic(directory, { ...options, stripPrefix: (_a = options === null || options === void 0 ? void 0 : options.stripPrefix) !== null && _a !== void 0 ? _a : route });
|
|
505
|
+
return this.use(route, handler);
|
|
506
|
+
}
|
|
507
|
+
register(method, path, handlers) {
|
|
508
|
+
if (!handlers.length) {
|
|
509
|
+
throw new Error(`Route ${method} ${path} requires at least one handler`);
|
|
510
|
+
}
|
|
511
|
+
this.routes.push({
|
|
512
|
+
method,
|
|
513
|
+
matcher: buildMatcher(path),
|
|
514
|
+
handlers
|
|
515
|
+
});
|
|
516
|
+
return this;
|
|
517
|
+
}
|
|
518
|
+
get(path, ...handlers) {
|
|
519
|
+
return this.register('GET', path, handlers);
|
|
520
|
+
}
|
|
521
|
+
post(path, ...handlers) {
|
|
522
|
+
return this.register('POST', path, handlers);
|
|
523
|
+
}
|
|
524
|
+
put(path, ...handlers) {
|
|
525
|
+
return this.register('PUT', path, handlers);
|
|
526
|
+
}
|
|
527
|
+
patch(path, ...handlers) {
|
|
528
|
+
return this.register('PATCH', path, handlers);
|
|
529
|
+
}
|
|
530
|
+
delete(path, ...handlers) {
|
|
531
|
+
return this.register('DELETE', path, handlers);
|
|
532
|
+
}
|
|
533
|
+
head(path, ...handlers) {
|
|
534
|
+
return this.register('HEAD', path, handlers);
|
|
535
|
+
}
|
|
536
|
+
options(path, ...handlers) {
|
|
537
|
+
return this.register('OPTIONS', path, handlers);
|
|
538
|
+
}
|
|
539
|
+
all(path, ...handlers) {
|
|
540
|
+
return this.register('ALL', path, handlers);
|
|
541
|
+
}
|
|
542
|
+
param(name, handler) {
|
|
543
|
+
this.paramHandlers.set(name, handler);
|
|
544
|
+
return this;
|
|
545
|
+
}
|
|
546
|
+
route(path) {
|
|
547
|
+
return new SockressAppRoute(this, path);
|
|
548
|
+
}
|
|
549
|
+
listen(port, hostOrCallback, maybeCallback) {
|
|
550
|
+
let host;
|
|
551
|
+
let callback;
|
|
552
|
+
if (typeof hostOrCallback === 'function') {
|
|
553
|
+
callback = hostOrCallback;
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
host = hostOrCallback;
|
|
557
|
+
callback = maybeCallback;
|
|
558
|
+
}
|
|
559
|
+
if (this.server) {
|
|
560
|
+
throw new Error('Sockress server is already running');
|
|
561
|
+
}
|
|
562
|
+
const httpServer = http_1.default.createServer((req, res) => this.handleHttp(req, res));
|
|
563
|
+
const wss = new ws_1.WebSocketServer({ noServer: true });
|
|
564
|
+
httpServer.on('upgrade', (req, socket, head) => {
|
|
565
|
+
var _a;
|
|
566
|
+
const { pathname } = new URL((_a = req.url) !== null && _a !== void 0 ? _a : '/', `http://${req.headers.host || 'localhost'}`);
|
|
567
|
+
if (pathname !== this.config.socket.path) {
|
|
568
|
+
socket.destroy();
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (!isOriginAllowed(req.headers.origin, this.config.cors.origin)) {
|
|
572
|
+
socket.destroy();
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
576
|
+
wss.emit('connection', ws, req);
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
wss.on('connection', (socket, req) => this.handleSocket(socket, req));
|
|
580
|
+
this.server = httpServer;
|
|
581
|
+
this.wss = wss;
|
|
582
|
+
const listener = httpServer.listen(port, host, () => {
|
|
583
|
+
const addressInfo = httpServer.address();
|
|
584
|
+
if (!addressInfo || typeof addressInfo === 'string') {
|
|
585
|
+
callback === null || callback === void 0 ? void 0 : callback(null, undefined);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
callback === null || callback === void 0 ? void 0 : callback(null, enhanceAddressInfo(addressInfo, host));
|
|
589
|
+
});
|
|
590
|
+
httpServer.on('error', (err) => callback === null || callback === void 0 ? void 0 : callback(err));
|
|
591
|
+
this.startHeartbeat();
|
|
592
|
+
this.registerShutdownHooks();
|
|
593
|
+
return listener;
|
|
594
|
+
}
|
|
595
|
+
async close() {
|
|
596
|
+
await Promise.all([
|
|
597
|
+
this.server
|
|
598
|
+
? new Promise((resolve, reject) => this.server.close((err) => (err ? reject(err) : resolve())))
|
|
599
|
+
: Promise.resolve(),
|
|
600
|
+
this.wss
|
|
601
|
+
? new Promise((resolve, reject) => this.wss.close((err) => (err ? reject(err) : resolve())))
|
|
602
|
+
: Promise.resolve()
|
|
603
|
+
]);
|
|
604
|
+
if (this.heartbeatInterval) {
|
|
605
|
+
clearInterval(this.heartbeatInterval);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
startHeartbeat() {
|
|
609
|
+
if (!this.wss)
|
|
610
|
+
return;
|
|
611
|
+
this.heartbeatInterval = setInterval(() => {
|
|
612
|
+
var _a;
|
|
613
|
+
(_a = this.wss) === null || _a === void 0 ? void 0 : _a.clients.forEach((socket) => {
|
|
614
|
+
if (socket.isAlive === false) {
|
|
615
|
+
return socket.terminate();
|
|
616
|
+
}
|
|
617
|
+
socket.isAlive = false;
|
|
618
|
+
socket.ping();
|
|
619
|
+
});
|
|
620
|
+
}, this.config.socket.heartbeatInterval);
|
|
621
|
+
}
|
|
622
|
+
registerShutdownHooks() {
|
|
623
|
+
if (this.shutdownRegistered)
|
|
624
|
+
return;
|
|
625
|
+
this.shutdownRegistered = true;
|
|
626
|
+
if (typeof process === 'undefined' || !process.on) {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const finalize = () => {
|
|
630
|
+
if (this.shuttingDown)
|
|
631
|
+
return;
|
|
632
|
+
this.shuttingDown = true;
|
|
633
|
+
this.close().catch(() => undefined);
|
|
634
|
+
};
|
|
635
|
+
process.once('beforeExit', finalize);
|
|
636
|
+
process.once('SIGINT', finalize);
|
|
637
|
+
process.once('SIGTERM', finalize);
|
|
638
|
+
}
|
|
639
|
+
async handleHttp(req, res) {
|
|
640
|
+
var _a;
|
|
641
|
+
try {
|
|
642
|
+
const { method = 'GET' } = req;
|
|
643
|
+
const url = new URL((_a = req.url) !== null && _a !== void 0 ? _a : '/', `http://${req.headers.host || 'localhost'}`);
|
|
644
|
+
const path = url.pathname || '/';
|
|
645
|
+
const query = parseQuery(url.searchParams);
|
|
646
|
+
const cookies = req.headers.cookie ? (0, cookie_1.parse)(req.headers.cookie) : {};
|
|
647
|
+
const contentType = (req.headers['content-type'] || '').toLowerCase();
|
|
648
|
+
const skipBodyParsing = contentType.startsWith('multipart/form-data');
|
|
649
|
+
let parsedBody;
|
|
650
|
+
if (!skipBodyParsing) {
|
|
651
|
+
const body = await readBody(req, this.config.bodyLimit);
|
|
652
|
+
parsedBody = parseBody(body, req.headers['content-type']);
|
|
653
|
+
}
|
|
654
|
+
const normalizedPayload = normalizeBodyPayload(parsedBody);
|
|
655
|
+
const primaryFile = pickPrimaryFile(normalizedPayload.files);
|
|
656
|
+
const secure = isSocketEncrypted(req.socket);
|
|
657
|
+
const originalUrl = req.url || '/';
|
|
658
|
+
const sockressReq = new SockressRequestImpl((0, nanoid_1.nanoid)(), method.toUpperCase(), path, query, req.headers, normalizedPayload.body, cookies, normalizedPayload.files, primaryFile, 'http', getIp(req), secure ? 'https' : 'http', secure, req, originalUrl, '');
|
|
659
|
+
const origin = pickOrigin(req.headers.origin, this.config.cors.origin);
|
|
660
|
+
const sockressRes = new SockressResponse({ kind: 'http', req, res }, this.config.cors, origin);
|
|
661
|
+
if (sockressReq.method === 'OPTIONS') {
|
|
662
|
+
sockressRes.status(204).end();
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
await this.runPipeline(sockressReq, sockressRes);
|
|
666
|
+
}
|
|
667
|
+
catch (error) {
|
|
668
|
+
res.statusCode = 500;
|
|
669
|
+
res.setHeader('content-type', 'application/json; charset=utf-8');
|
|
670
|
+
res.end(JSON.stringify({ error: 'Internal Server Error', details: error instanceof Error ? error.message : error }));
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
handleSocket(socket, req) {
|
|
674
|
+
socket.isAlive = true;
|
|
675
|
+
socket.on('pong', () => {
|
|
676
|
+
socket.isAlive = true;
|
|
677
|
+
});
|
|
678
|
+
socket.on('message', async (raw) => {
|
|
679
|
+
var _a, _b, _c;
|
|
680
|
+
try {
|
|
681
|
+
const payload = JSON.parse(raw.toString());
|
|
682
|
+
if (payload.type !== 'request') {
|
|
683
|
+
return socket.send(JSON.stringify({ type: 'error', message: 'Unsupported message type' }));
|
|
684
|
+
}
|
|
685
|
+
const path = payload.path || '/';
|
|
686
|
+
const method = (payload.method || 'GET').toUpperCase();
|
|
687
|
+
const query = (_a = payload.query) !== null && _a !== void 0 ? _a : {};
|
|
688
|
+
const headers = normalizeHeaders((_b = payload.headers) !== null && _b !== void 0 ? _b : {});
|
|
689
|
+
const cookieHeader = headers.cookie;
|
|
690
|
+
const cookieString = Array.isArray(cookieHeader) ? cookieHeader[0] : cookieHeader;
|
|
691
|
+
const cookies = typeof cookieString === 'string' ? (0, cookie_1.parse)(cookieString) : {};
|
|
692
|
+
const secure = isSocketEncrypted(req.socket);
|
|
693
|
+
const normalizedPayload = normalizeBodyPayload(payload.body);
|
|
694
|
+
const primaryFile = pickPrimaryFile(normalizedPayload.files);
|
|
695
|
+
const originalUrl = payload.path || '/';
|
|
696
|
+
const sockressReq = new SockressRequestImpl((_c = payload.id) !== null && _c !== void 0 ? _c : (0, nanoid_1.nanoid)(), method, path, query, headers, normalizedPayload.body, cookies, normalizedPayload.files, primaryFile, 'socket', getIp(req), secure ? 'wss' : 'ws', secure, undefined, originalUrl, '');
|
|
697
|
+
const origin = pickOrigin(req.headers.origin, this.config.cors.origin);
|
|
698
|
+
const sockressRes = new SockressResponse({ kind: 'socket', socket, requestId: sockressReq.id }, this.config.cors, origin);
|
|
699
|
+
await this.runPipeline(sockressReq, sockressRes);
|
|
700
|
+
}
|
|
701
|
+
catch (error) {
|
|
702
|
+
const outgoing = {
|
|
703
|
+
type: 'error',
|
|
704
|
+
message: error instanceof Error ? error.message : 'Unexpected socket payload'
|
|
705
|
+
};
|
|
706
|
+
socket.send(JSON.stringify(outgoing));
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
async runPipeline(req, res) {
|
|
711
|
+
const stack = this.composeStack(req, req.method);
|
|
712
|
+
let idx = 0;
|
|
713
|
+
const next = async (err) => {
|
|
714
|
+
const layer = stack[idx++];
|
|
715
|
+
if (!layer) {
|
|
716
|
+
if (err) {
|
|
717
|
+
this.renderError(err, req, res);
|
|
718
|
+
}
|
|
719
|
+
else if (!res.isSent()) {
|
|
720
|
+
res.status(404).json({ error: 'Not Found' });
|
|
721
|
+
}
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const handler = layer.handler;
|
|
725
|
+
const isErrorHandler = layer.isErrorHandler;
|
|
726
|
+
try {
|
|
727
|
+
if (err) {
|
|
728
|
+
if (isErrorHandler) {
|
|
729
|
+
await handler(err, req, res, next);
|
|
730
|
+
}
|
|
731
|
+
else {
|
|
732
|
+
await next(err);
|
|
733
|
+
}
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (isErrorHandler) {
|
|
737
|
+
await next();
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
await handler(req, res, next);
|
|
741
|
+
}
|
|
742
|
+
catch (error) {
|
|
743
|
+
await next(error);
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
await next();
|
|
747
|
+
}
|
|
748
|
+
composeStack(req, method) {
|
|
749
|
+
const { path } = req;
|
|
750
|
+
const stack = [];
|
|
751
|
+
for (const layer of this.middlewares) {
|
|
752
|
+
if (matchesPrefix(layer.path, path)) {
|
|
753
|
+
stack.push({
|
|
754
|
+
handler: layer.handler,
|
|
755
|
+
isErrorHandler: layer.isErrorHandler
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
for (const route of this.routes) {
|
|
760
|
+
if (route.method !== method && route.method !== 'ALL') {
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
const match = route.matcher.match(path);
|
|
764
|
+
if (!match)
|
|
765
|
+
continue;
|
|
766
|
+
req.params = { ...match.params };
|
|
767
|
+
for (const [paramName, paramValue] of Object.entries(match.params)) {
|
|
768
|
+
const paramHandler = this.paramHandlers.get(paramName);
|
|
769
|
+
if (paramHandler) {
|
|
770
|
+
const wrapped = (request, res, next) => {
|
|
771
|
+
request.params = { ...match.params };
|
|
772
|
+
return paramHandler(request, res, next);
|
|
773
|
+
};
|
|
774
|
+
stack.push({ handler: wrapped, isErrorHandler: false });
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
for (const handler of route.handlers) {
|
|
778
|
+
const isErrorHandler = handler.length === 4;
|
|
779
|
+
if (isErrorHandler) {
|
|
780
|
+
const wrapped = (err, request, res, next) => {
|
|
781
|
+
request.params = { ...match.params };
|
|
782
|
+
return handler(err, request, res, next);
|
|
783
|
+
};
|
|
784
|
+
stack.push({ handler: wrapped, isErrorHandler: true });
|
|
785
|
+
}
|
|
786
|
+
else {
|
|
787
|
+
const wrapped = (request, res, next) => {
|
|
788
|
+
request.params = { ...match.params };
|
|
789
|
+
return handler(request, res, next);
|
|
790
|
+
};
|
|
791
|
+
stack.push({ handler: wrapped, isErrorHandler: false });
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return stack;
|
|
796
|
+
}
|
|
797
|
+
renderError(err, req, res) {
|
|
798
|
+
if (res.isSent()) {
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
res.status(500).json({
|
|
802
|
+
error: 'Internal Server Error',
|
|
803
|
+
details: err instanceof Error ? err.message : err
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
exports.SockressApp = SockressApp;
|
|
808
|
+
function sockress(options) {
|
|
809
|
+
return SockressApp.create(options);
|
|
810
|
+
}
|
|
811
|
+
exports.createSockress = sockress;
|
|
812
|
+
exports.Router = SockressApp.Router;
|
|
813
|
+
function normalizeOptions(options) {
|
|
814
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
|
|
815
|
+
const cors = {
|
|
816
|
+
origin: (_b = (_a = options === null || options === void 0 ? void 0 : options.cors) === null || _a === void 0 ? void 0 : _a.origin) !== null && _b !== void 0 ? _b : '*',
|
|
817
|
+
credentials: (_d = (_c = options === null || options === void 0 ? void 0 : options.cors) === null || _c === void 0 ? void 0 : _c.credentials) !== null && _d !== void 0 ? _d : true,
|
|
818
|
+
methods: (_f = (_e = options === null || options === void 0 ? void 0 : options.cors) === null || _e === void 0 ? void 0 : _e.methods) !== null && _f !== void 0 ? _f : ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
819
|
+
allowedHeaders: (_h = (_g = options === null || options === void 0 ? void 0 : options.cors) === null || _g === void 0 ? void 0 : _g.allowedHeaders) !== null && _h !== void 0 ? _h : ['Content-Type', 'Authorization', 'X-Requested-With'],
|
|
820
|
+
exposedHeaders: (_k = (_j = options === null || options === void 0 ? void 0 : options.cors) === null || _j === void 0 ? void 0 : _j.exposedHeaders) !== null && _k !== void 0 ? _k : [],
|
|
821
|
+
maxAge: (_m = (_l = options === null || options === void 0 ? void 0 : options.cors) === null || _l === void 0 ? void 0 : _l.maxAge) !== null && _m !== void 0 ? _m : 600
|
|
822
|
+
};
|
|
823
|
+
const socket = {
|
|
824
|
+
path: (_p = (_o = options === null || options === void 0 ? void 0 : options.socket) === null || _o === void 0 ? void 0 : _o.path) !== null && _p !== void 0 ? _p : '/sockress',
|
|
825
|
+
heartbeatInterval: (_r = (_q = options === null || options === void 0 ? void 0 : options.socket) === null || _q === void 0 ? void 0 : _q.heartbeatInterval) !== null && _r !== void 0 ? _r : 30000,
|
|
826
|
+
idleTimeout: (_t = (_s = options === null || options === void 0 ? void 0 : options.socket) === null || _s === void 0 ? void 0 : _s.idleTimeout) !== null && _t !== void 0 ? _t : 120000
|
|
827
|
+
};
|
|
828
|
+
const bodyLimit = (_u = options === null || options === void 0 ? void 0 : options.bodyLimit) !== null && _u !== void 0 ? _u : 1000000;
|
|
829
|
+
return { cors, socket, bodyLimit };
|
|
830
|
+
}
|
|
831
|
+
function buildMatcher(path) {
|
|
832
|
+
if (path === '*' || path === '/*') {
|
|
833
|
+
return {
|
|
834
|
+
raw: path,
|
|
835
|
+
match: (incoming) => ({ params: { wild: incoming.replace(/^\//, '') } })
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
const keys = [];
|
|
839
|
+
const pattern = path
|
|
840
|
+
.split('/')
|
|
841
|
+
.map((segment) => {
|
|
842
|
+
if (!segment)
|
|
843
|
+
return '';
|
|
844
|
+
if (segment.startsWith(':')) {
|
|
845
|
+
const key = segment.replace(/^:/, '').replace(/\?$/, '');
|
|
846
|
+
keys.push(key);
|
|
847
|
+
return segment.endsWith('?') ? '(?:\\/([^/]+))?' : '\\/([^/]+)';
|
|
848
|
+
}
|
|
849
|
+
if (segment === '*') {
|
|
850
|
+
keys.push('wild');
|
|
851
|
+
return '\\/(.*)';
|
|
852
|
+
}
|
|
853
|
+
return `\\/${segment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`;
|
|
854
|
+
})
|
|
855
|
+
.join('');
|
|
856
|
+
const regex = new RegExp(`^${pattern || '\\/'}\\/?$`);
|
|
857
|
+
return {
|
|
858
|
+
raw: path,
|
|
859
|
+
match: (incoming) => {
|
|
860
|
+
const exec = regex.exec(incoming === '' ? '/' : incoming);
|
|
861
|
+
if (!exec) {
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
const params = {};
|
|
865
|
+
keys.forEach((key, index) => {
|
|
866
|
+
const value = exec[index + 1];
|
|
867
|
+
if (value !== undefined) {
|
|
868
|
+
params[key] = decodeURIComponent(value);
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
return { params };
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
async function readBody(req, limit) {
|
|
876
|
+
return new Promise((resolve, reject) => {
|
|
877
|
+
const chunks = [];
|
|
878
|
+
let total = 0;
|
|
879
|
+
req.on('data', (chunk) => {
|
|
880
|
+
total += chunk.length;
|
|
881
|
+
if (total > limit) {
|
|
882
|
+
reject(new Error('Payload too large'));
|
|
883
|
+
req.destroy();
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
chunks.push(chunk);
|
|
887
|
+
});
|
|
888
|
+
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
889
|
+
req.on('error', (err) => reject(err));
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
function parseBody(buffer, contentType) {
|
|
893
|
+
if (!buffer.length)
|
|
894
|
+
return undefined;
|
|
895
|
+
const type = contentType === null || contentType === void 0 ? void 0 : contentType.split(';')[0].trim().toLowerCase();
|
|
896
|
+
if (type === 'application/json') {
|
|
897
|
+
const text = buffer.toString('utf8');
|
|
898
|
+
return text ? JSON.parse(text) : undefined;
|
|
899
|
+
}
|
|
900
|
+
if (type === 'application/x-www-form-urlencoded') {
|
|
901
|
+
const params = new URLSearchParams(buffer.toString('utf8'));
|
|
902
|
+
const result = {};
|
|
903
|
+
for (const [key, value] of params.entries()) {
|
|
904
|
+
if (result[key]) {
|
|
905
|
+
const existing = result[key];
|
|
906
|
+
result[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
result[key] = value;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return result;
|
|
913
|
+
}
|
|
914
|
+
return buffer;
|
|
915
|
+
}
|
|
916
|
+
function parseQuery(searchParams) {
|
|
917
|
+
const result = {};
|
|
918
|
+
for (const [key, value] of searchParams.entries()) {
|
|
919
|
+
if (result[key]) {
|
|
920
|
+
const existing = result[key];
|
|
921
|
+
result[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
result[key] = value;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return result;
|
|
928
|
+
}
|
|
929
|
+
function matchesPrefix(base, path) {
|
|
930
|
+
if (base === '/' || base === '')
|
|
931
|
+
return true;
|
|
932
|
+
if (!base.startsWith('/')) {
|
|
933
|
+
base = `/${base}`;
|
|
934
|
+
}
|
|
935
|
+
return path === base || path.startsWith(`${base}/`);
|
|
936
|
+
}
|
|
937
|
+
function getIp(req) {
|
|
938
|
+
var _a;
|
|
939
|
+
const forwarded = req.headers['x-forwarded-for'];
|
|
940
|
+
if (Array.isArray(forwarded)) {
|
|
941
|
+
return forwarded[0];
|
|
942
|
+
}
|
|
943
|
+
if (typeof forwarded === 'string') {
|
|
944
|
+
return forwarded.split(',')[0].trim();
|
|
945
|
+
}
|
|
946
|
+
return (_a = req.socket.remoteAddress) !== null && _a !== void 0 ? _a : undefined;
|
|
947
|
+
}
|
|
948
|
+
function isOriginAllowed(originHeader, allowed) {
|
|
949
|
+
if (!originHeader || allowed === '*')
|
|
950
|
+
return true;
|
|
951
|
+
if (Array.isArray(allowed)) {
|
|
952
|
+
return allowed.includes(originHeader);
|
|
953
|
+
}
|
|
954
|
+
return allowed === originHeader;
|
|
955
|
+
}
|
|
956
|
+
function pickOrigin(requestOrigin, allowed) {
|
|
957
|
+
var _a;
|
|
958
|
+
if (allowed === '*')
|
|
959
|
+
return '*';
|
|
960
|
+
if (Array.isArray(allowed)) {
|
|
961
|
+
if (requestOrigin && allowed.includes(requestOrigin)) {
|
|
962
|
+
return requestOrigin;
|
|
963
|
+
}
|
|
964
|
+
return (_a = allowed[0]) !== null && _a !== void 0 ? _a : '*';
|
|
965
|
+
}
|
|
966
|
+
return allowed;
|
|
967
|
+
}
|
|
968
|
+
function isSocketEncrypted(socket) {
|
|
969
|
+
return socket instanceof tls_1.TLSSocket && Boolean(socket.encrypted);
|
|
970
|
+
}
|
|
971
|
+
function normalizeBodyPayload(value) {
|
|
972
|
+
var _a, _b;
|
|
973
|
+
if (value &&
|
|
974
|
+
typeof value === 'object' &&
|
|
975
|
+
'__formData' in value &&
|
|
976
|
+
typeof value.__formData === 'object') {
|
|
977
|
+
const form = (value.__formData || {});
|
|
978
|
+
const files = convertSerializedFiles((_a = form.files) !== null && _a !== void 0 ? _a : {});
|
|
979
|
+
const fields = (_b = form.fields) !== null && _b !== void 0 ? _b : {};
|
|
980
|
+
return {
|
|
981
|
+
body: fields,
|
|
982
|
+
files: Object.keys(files).length ? files : undefined
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
return { body: value === undefined ? {} : value };
|
|
986
|
+
}
|
|
987
|
+
function convertSerializedFiles(serialized) {
|
|
988
|
+
const files = {};
|
|
989
|
+
for (const [field, entries] of Object.entries(serialized)) {
|
|
990
|
+
files[field] = entries
|
|
991
|
+
.filter((entry) => typeof entry.data === 'string')
|
|
992
|
+
.map((entry) => {
|
|
993
|
+
var _a, _b, _c;
|
|
994
|
+
return ({
|
|
995
|
+
fieldName: field,
|
|
996
|
+
name: (_a = entry.name) !== null && _a !== void 0 ? _a : 'file',
|
|
997
|
+
type: (_b = entry.type) !== null && _b !== void 0 ? _b : 'application/octet-stream',
|
|
998
|
+
size: (_c = entry.size) !== null && _c !== void 0 ? _c : Buffer.from(entry.data, 'base64').length,
|
|
999
|
+
buffer: Buffer.from(entry.data, 'base64'),
|
|
1000
|
+
lastModified: entry.lastModified
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
return files;
|
|
1005
|
+
}
|
|
1006
|
+
function pickPrimaryFile(files) {
|
|
1007
|
+
if (!files) {
|
|
1008
|
+
return undefined;
|
|
1009
|
+
}
|
|
1010
|
+
const firstKey = Object.keys(files)[0];
|
|
1011
|
+
if (!firstKey)
|
|
1012
|
+
return undefined;
|
|
1013
|
+
const list = files[firstKey];
|
|
1014
|
+
if (!Array.isArray(list) || !list.length)
|
|
1015
|
+
return undefined;
|
|
1016
|
+
return list[0];
|
|
1017
|
+
}
|
|
1018
|
+
function createUploader(options) {
|
|
1019
|
+
const storage = multer_1.default.memoryStorage();
|
|
1020
|
+
const multerInstance = (0, multer_1.default)({
|
|
1021
|
+
storage,
|
|
1022
|
+
limits: options === null || options === void 0 ? void 0 : options.limits
|
|
1023
|
+
});
|
|
1024
|
+
const resolvedDest = (options === null || options === void 0 ? void 0 : options.dest) ? path_1.default.resolve(options.dest) : undefined;
|
|
1025
|
+
const wrap = (factory) => (...args) => {
|
|
1026
|
+
const middleware = factory(...args);
|
|
1027
|
+
return (req, res, next) => {
|
|
1028
|
+
if (req.type === 'socket') {
|
|
1029
|
+
if (!resolvedDest || !req.files) {
|
|
1030
|
+
if (!req.file) {
|
|
1031
|
+
req.file = pickPrimaryFile(req.files);
|
|
1032
|
+
}
|
|
1033
|
+
next();
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
persistFilesToDisk(req.files, resolvedDest, options === null || options === void 0 ? void 0 : options.preserveFilename)
|
|
1037
|
+
.then(() => {
|
|
1038
|
+
if (!req.file) {
|
|
1039
|
+
req.file = pickPrimaryFile(req.files);
|
|
1040
|
+
}
|
|
1041
|
+
next();
|
|
1042
|
+
})
|
|
1043
|
+
.catch(next);
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
if (!req.raw || !res.raw) {
|
|
1047
|
+
next(new Error('Uploads require an HTTP request'));
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
middleware(req.raw, res.raw, (err) => {
|
|
1051
|
+
if (err) {
|
|
1052
|
+
next(err);
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
const normalized = normalizeMulterOutput(req.raw);
|
|
1056
|
+
req.body = mergeBodies(req.body, normalized.fields);
|
|
1057
|
+
req.files = normalized.files;
|
|
1058
|
+
req.file = normalized.file;
|
|
1059
|
+
if (!resolvedDest || !req.files) {
|
|
1060
|
+
next();
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
persistFilesToDisk(req.files, resolvedDest, options === null || options === void 0 ? void 0 : options.preserveFilename)
|
|
1064
|
+
.then(() => next())
|
|
1065
|
+
.catch(next);
|
|
1066
|
+
});
|
|
1067
|
+
};
|
|
1068
|
+
};
|
|
1069
|
+
return {
|
|
1070
|
+
single: (field) => wrap(multerInstance.single.bind(multerInstance))(field),
|
|
1071
|
+
array: (field, maxCount) => wrap(multerInstance.array.bind(multerInstance))(field, maxCount),
|
|
1072
|
+
fields: (defs) => wrap(multerInstance.fields.bind(multerInstance))(defs),
|
|
1073
|
+
any: () => wrap(multerInstance.any.bind(multerInstance))()
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
function serveStatic(root, options) {
|
|
1077
|
+
var _a, _b;
|
|
1078
|
+
const resolvedRoot = path_1.default.resolve(root);
|
|
1079
|
+
const stripPrefix = (options === null || options === void 0 ? void 0 : options.stripPrefix) ? ensureLeadingSlash(options.stripPrefix) : '';
|
|
1080
|
+
const indexFile = (_a = options === null || options === void 0 ? void 0 : options.index) !== null && _a !== void 0 ? _a : 'index.html';
|
|
1081
|
+
const maxAge = (_b = options === null || options === void 0 ? void 0 : options.maxAge) !== null && _b !== void 0 ? _b : 0;
|
|
1082
|
+
return async (req, res, next) => {
|
|
1083
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
1084
|
+
next();
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
let relativePath = req.path || '/';
|
|
1088
|
+
if (stripPrefix && relativePath.startsWith(stripPrefix)) {
|
|
1089
|
+
relativePath = relativePath.slice(stripPrefix.length) || '/';
|
|
1090
|
+
}
|
|
1091
|
+
const sanitized = sanitizeRelativePath(relativePath);
|
|
1092
|
+
let target = path_1.default.join(resolvedRoot, sanitized);
|
|
1093
|
+
try {
|
|
1094
|
+
let stats = await fs_1.promises.stat(target);
|
|
1095
|
+
if (stats.isDirectory()) {
|
|
1096
|
+
target = path_1.default.join(target, indexFile);
|
|
1097
|
+
stats = await fs_1.promises.stat(target);
|
|
1098
|
+
}
|
|
1099
|
+
const buffer = await fs_1.promises.readFile(target);
|
|
1100
|
+
res.set('cache-control', `public, max-age=${Math.floor(maxAge / 1000)}`);
|
|
1101
|
+
res.set('content-length', stats.size.toString());
|
|
1102
|
+
res.set('content-type', mimeFromExtension(path_1.default.extname(target)));
|
|
1103
|
+
if (req.method === 'HEAD') {
|
|
1104
|
+
res.end();
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
res.send(buffer);
|
|
1108
|
+
}
|
|
1109
|
+
catch {
|
|
1110
|
+
next();
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
function mergeBodies(body, nextBody) {
|
|
1115
|
+
const current = typeof body === 'object' && body !== null ? body : {};
|
|
1116
|
+
return { ...current, ...nextBody };
|
|
1117
|
+
}
|
|
1118
|
+
function normalizeMulterOutput(req) {
|
|
1119
|
+
var _a;
|
|
1120
|
+
const files = {};
|
|
1121
|
+
const pushFile = (file) => {
|
|
1122
|
+
var _a, _b;
|
|
1123
|
+
if (!file)
|
|
1124
|
+
return;
|
|
1125
|
+
const normalized = {
|
|
1126
|
+
fieldName: file.fieldname || file.name || 'file',
|
|
1127
|
+
name: file.originalname || file.filename || file.fieldname || 'file',
|
|
1128
|
+
type: file.mimetype || 'application/octet-stream',
|
|
1129
|
+
size: (_a = file.size) !== null && _a !== void 0 ? _a : (file.buffer ? file.buffer.length : 0),
|
|
1130
|
+
buffer: (_b = file.buffer) !== null && _b !== void 0 ? _b : Buffer.alloc(0),
|
|
1131
|
+
lastModified: file.lastModified
|
|
1132
|
+
};
|
|
1133
|
+
if (!files[normalized.fieldName]) {
|
|
1134
|
+
files[normalized.fieldName] = [];
|
|
1135
|
+
}
|
|
1136
|
+
files[normalized.fieldName].push(normalized);
|
|
1137
|
+
};
|
|
1138
|
+
if (req.file) {
|
|
1139
|
+
pushFile(req.file);
|
|
1140
|
+
}
|
|
1141
|
+
if (Array.isArray(req.files)) {
|
|
1142
|
+
req.files.forEach(pushFile);
|
|
1143
|
+
}
|
|
1144
|
+
else if (req.files && typeof req.files === 'object') {
|
|
1145
|
+
Object.values(req.files).forEach((entry) => {
|
|
1146
|
+
if (Array.isArray(entry)) {
|
|
1147
|
+
entry.forEach(pushFile);
|
|
1148
|
+
}
|
|
1149
|
+
else {
|
|
1150
|
+
pushFile(entry);
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
return {
|
|
1155
|
+
file: pickPrimaryFile(files),
|
|
1156
|
+
files: Object.keys(files).length ? files : undefined,
|
|
1157
|
+
fields: (_a = req.body) !== null && _a !== void 0 ? _a : {}
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
async function persistFilesToDisk(files, dest, preserveFilename) {
|
|
1161
|
+
if (!Object.keys(files).length)
|
|
1162
|
+
return;
|
|
1163
|
+
await fs_1.promises.mkdir(dest, { recursive: true });
|
|
1164
|
+
for (const list of Object.values(files)) {
|
|
1165
|
+
for (const file of list) {
|
|
1166
|
+
const filename = preserveFilename ? sanitizeFilename(file.name) : `${Date.now()}-${(0, nanoid_1.nanoid)(8)}${path_1.default.extname(file.name || '')}`;
|
|
1167
|
+
const target = path_1.default.join(dest, filename);
|
|
1168
|
+
await fs_1.promises.writeFile(target, file.buffer);
|
|
1169
|
+
file.path = target;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
function sanitizeFilename(name) {
|
|
1174
|
+
return name.replace(/[^a-zA-Z0-9.\-_]/g, '_');
|
|
1175
|
+
}
|
|
1176
|
+
function sanitizeRelativePath(requestPath) {
|
|
1177
|
+
const normalized = path_1.default.normalize(requestPath);
|
|
1178
|
+
if (normalized.startsWith('..')) {
|
|
1179
|
+
return normalized.replace(/^(\.\.(\/|\\|$))+/, '');
|
|
1180
|
+
}
|
|
1181
|
+
return normalized;
|
|
1182
|
+
}
|
|
1183
|
+
function ensureLeadingSlash(value) {
|
|
1184
|
+
if (!value.startsWith('/')) {
|
|
1185
|
+
return `/${value}`;
|
|
1186
|
+
}
|
|
1187
|
+
return value;
|
|
1188
|
+
}
|
|
1189
|
+
function mimeFromExtension(ext) {
|
|
1190
|
+
switch (ext.toLowerCase()) {
|
|
1191
|
+
case '.html':
|
|
1192
|
+
case '.htm':
|
|
1193
|
+
return 'text/html; charset=utf-8';
|
|
1194
|
+
case '.css':
|
|
1195
|
+
return 'text/css; charset=utf-8';
|
|
1196
|
+
case '.js':
|
|
1197
|
+
case '.mjs':
|
|
1198
|
+
return 'application/javascript; charset=utf-8';
|
|
1199
|
+
case '.json':
|
|
1200
|
+
return 'application/json; charset=utf-8';
|
|
1201
|
+
case '.png':
|
|
1202
|
+
return 'image/png';
|
|
1203
|
+
case '.jpg':
|
|
1204
|
+
case '.jpeg':
|
|
1205
|
+
return 'image/jpeg';
|
|
1206
|
+
case '.gif':
|
|
1207
|
+
return 'image/gif';
|
|
1208
|
+
case '.svg':
|
|
1209
|
+
return 'image/svg+xml';
|
|
1210
|
+
case '.ico':
|
|
1211
|
+
return 'image/x-icon';
|
|
1212
|
+
default:
|
|
1213
|
+
return 'application/octet-stream';
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
function normalizeHeaders(headers) {
|
|
1217
|
+
const normalized = {};
|
|
1218
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1219
|
+
if (value === undefined || value === null)
|
|
1220
|
+
continue;
|
|
1221
|
+
const headerKey = key.toLowerCase();
|
|
1222
|
+
normalized[headerKey] = Array.isArray(value) ? value.map((entry) => String(entry)) : String(value);
|
|
1223
|
+
}
|
|
1224
|
+
return normalized;
|
|
1225
|
+
}
|
|
1226
|
+
function enhanceAddressInfo(info, preferredHost) {
|
|
1227
|
+
const hostname = normalizeHostname(preferredHost !== null && preferredHost !== void 0 ? preferredHost : info.address);
|
|
1228
|
+
return {
|
|
1229
|
+
...info,
|
|
1230
|
+
hostname,
|
|
1231
|
+
url: `http://${hostname}:${info.port}`
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
function normalizeHostname(host) {
|
|
1235
|
+
if (!host)
|
|
1236
|
+
return 'localhost';
|
|
1237
|
+
const lowered = host.toLowerCase();
|
|
1238
|
+
if (lowered === '::' || lowered === '::1' || lowered === '0.0.0.0') {
|
|
1239
|
+
return 'localhost';
|
|
1240
|
+
}
|
|
1241
|
+
return host;
|
|
1242
|
+
}
|
|
1243
|
+
function extractSubdomains(hostname) {
|
|
1244
|
+
const parts = hostname.split('.');
|
|
1245
|
+
if (parts.length <= 2)
|
|
1246
|
+
return [];
|
|
1247
|
+
return parts.slice(0, -2);
|
|
1248
|
+
}
|
|
1249
|
+
function getStatusText(code) {
|
|
1250
|
+
const statusTexts = {
|
|
1251
|
+
200: 'OK',
|
|
1252
|
+
201: 'Created',
|
|
1253
|
+
204: 'No Content',
|
|
1254
|
+
301: 'Moved Permanently',
|
|
1255
|
+
302: 'Found',
|
|
1256
|
+
304: 'Not Modified',
|
|
1257
|
+
400: 'Bad Request',
|
|
1258
|
+
401: 'Unauthorized',
|
|
1259
|
+
403: 'Forbidden',
|
|
1260
|
+
404: 'Not Found',
|
|
1261
|
+
500: 'Internal Server Error',
|
|
1262
|
+
502: 'Bad Gateway',
|
|
1263
|
+
503: 'Service Unavailable'
|
|
1264
|
+
};
|
|
1265
|
+
return statusTexts[code] || 'Unknown';
|
|
1266
|
+
}
|
|
1267
|
+
//# sourceMappingURL=index.js.map
|