@radatek/microserver 3.0.5 → 3.0.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/microserver.d.ts +25 -35
- package/microserver.js +116 -92
- package/package.json +1 -1
package/microserver.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MicroServer
|
|
3
|
-
* @version 3.0.
|
|
3
|
+
* @version 3.0.7
|
|
4
4
|
* @package @radatek/microserver
|
|
5
5
|
* @copyright Darius Kisonas 2022
|
|
6
6
|
* @license MIT
|
|
@@ -44,7 +44,6 @@ export declare abstract class Plugin {
|
|
|
44
44
|
priority?: number;
|
|
45
45
|
handler?(req: ServerRequest, res: ServerResponse, next: Function): Promise<string | object | void> | string | object | void;
|
|
46
46
|
routes?(): Promise<RoutesSet | void> | RoutesSet | void;
|
|
47
|
-
constructor();
|
|
48
47
|
}
|
|
49
48
|
interface PluginClass {
|
|
50
49
|
new (options: any, server: MicroServer): Plugin;
|
|
@@ -86,7 +85,7 @@ export declare class ServerRequest<T = any> extends http.IncomingMessage {
|
|
|
86
85
|
rawBody: Buffer[];
|
|
87
86
|
/** Request raw body size */
|
|
88
87
|
rawBodySize: number;
|
|
89
|
-
|
|
88
|
+
protected constructor(res: http.ServerResponse, server: MicroServer);
|
|
90
89
|
/** Extend http.IncomingMessage */
|
|
91
90
|
static extend(req: http.IncomingMessage, res: http.ServerResponse, server: MicroServer): ServerRequest;
|
|
92
91
|
/** Check if request is ready */
|
|
@@ -115,7 +114,7 @@ export declare class ServerResponse<T = any> extends http.ServerResponse {
|
|
|
115
114
|
readonly req: ServerRequest<T>;
|
|
116
115
|
/** Should response be json */
|
|
117
116
|
isJson: boolean;
|
|
118
|
-
|
|
117
|
+
protected constructor(server: MicroServer);
|
|
119
118
|
/** Extends http.ServerResponse */
|
|
120
119
|
static extend(res: http.ServerResponse): void;
|
|
121
120
|
/** Send error reponse */
|
|
@@ -164,7 +163,7 @@ export interface MicroServerConfig extends ListenConfig {
|
|
|
164
163
|
/** extra options for plugins */
|
|
165
164
|
[key: string]: any;
|
|
166
165
|
}
|
|
167
|
-
interface MicroServerEvents {
|
|
166
|
+
export interface MicroServerEvents {
|
|
168
167
|
ready: () => void;
|
|
169
168
|
close: () => void;
|
|
170
169
|
listen: (port: number, address: string, server: http.Server) => void;
|
|
@@ -260,6 +259,7 @@ export interface CorsOptions {
|
|
|
260
259
|
/** Max age */
|
|
261
260
|
maxAge?: number;
|
|
262
261
|
}
|
|
262
|
+
/** CORS plugin. Config may be: true - allow all, string - allow specific origin, or CorsOptions */
|
|
263
263
|
export declare class CorsPlugin extends Plugin {
|
|
264
264
|
priority: number;
|
|
265
265
|
name: string;
|
|
@@ -267,6 +267,7 @@ export declare class CorsPlugin extends Plugin {
|
|
|
267
267
|
constructor(options?: CorsOptions | string | true);
|
|
268
268
|
handler?(req: ServerRequest, res: ServerResponse, next: Function): Promise<string | object | void> | string | object | void;
|
|
269
269
|
}
|
|
270
|
+
/** Methods plugin to support OPTIONS method, and restrict allowed methods. Configuration is comma separated string */
|
|
270
271
|
export declare class MethodsPlugin extends Plugin {
|
|
271
272
|
priority: number;
|
|
272
273
|
name: string;
|
|
@@ -276,6 +277,7 @@ export declare class MethodsPlugin extends Plugin {
|
|
|
276
277
|
export interface BodyOptions {
|
|
277
278
|
maxBodySize?: number;
|
|
278
279
|
}
|
|
280
|
+
/** Body parser plugin */
|
|
279
281
|
export declare class BodyPlugin extends Plugin {
|
|
280
282
|
priority: number;
|
|
281
283
|
name: string;
|
|
@@ -293,6 +295,7 @@ export interface UploadFile {
|
|
|
293
295
|
size: number;
|
|
294
296
|
filePath?: string;
|
|
295
297
|
}
|
|
298
|
+
/** Upload plugin, At least uploadDir option is required */
|
|
296
299
|
export declare class UploadPlugin extends Plugin {
|
|
297
300
|
priority: number;
|
|
298
301
|
name: string;
|
|
@@ -323,14 +326,12 @@ interface WebSocketEvents {
|
|
|
323
326
|
message: (data: string | Buffer | ArrayBuffer | Blob) => void;
|
|
324
327
|
open: () => void;
|
|
325
328
|
}
|
|
326
|
-
/** WebSocket class */
|
|
329
|
+
/** WebSocket class used in upgrade handler */
|
|
327
330
|
export declare class WebSocket extends EventEmitter {
|
|
328
331
|
ready: boolean;
|
|
329
332
|
constructor(req: ServerRequest, options?: WebSocketOptions);
|
|
330
333
|
/** Close connection */
|
|
331
334
|
close(reason?: number, data?: Buffer): void;
|
|
332
|
-
/** Generate WebSocket frame from data */
|
|
333
|
-
static getFrame(data: number | string | Buffer | undefined, options?: any): Buffer;
|
|
334
335
|
/** Send data */
|
|
335
336
|
send(data: string | Buffer): void;
|
|
336
337
|
/** Send ping frame */
|
|
@@ -343,11 +344,11 @@ export declare class WebSocket extends EventEmitter {
|
|
|
343
344
|
off<K extends keyof WebSocketEvents>(event: K, listener: WebSocketEvents[K]): this;
|
|
344
345
|
removeListener<K extends keyof WebSocketEvents>(event: K, listener: WebSocketEvents[K]): this;
|
|
345
346
|
}
|
|
347
|
+
/** WebSocket plugin to support `WEBSOCKET /url` routes */
|
|
346
348
|
export declare class WebSocketPlugin extends Plugin {
|
|
347
349
|
name: string;
|
|
348
350
|
constructor(options?: any, server?: MicroServer);
|
|
349
|
-
|
|
350
|
-
upgradeHandler(server: MicroServer, req: ServerRequest, socket: net.Socket, head: any): void;
|
|
351
|
+
upgradeHandler(server: MicroServer, req: ServerRequest, socket: net.Socket, head: any): any;
|
|
351
352
|
}
|
|
352
353
|
/** Trust proxy plugin, adds `req.ip` and `req.localip` */
|
|
353
354
|
export declare class TrustProxyPlugin extends Plugin {
|
|
@@ -390,6 +391,8 @@ export interface StaticFilesOptions {
|
|
|
390
391
|
maxAge?: number;
|
|
391
392
|
/** static errors file for status code, '*' - default */
|
|
392
393
|
errors?: Record<string, string>;
|
|
394
|
+
/** check precompressed file */
|
|
395
|
+
precompressedGzip?: boolean;
|
|
393
396
|
}
|
|
394
397
|
export interface ServeFileOptions {
|
|
395
398
|
/** path */
|
|
@@ -411,11 +414,7 @@ export interface ServeFileOptions {
|
|
|
411
414
|
/** stat */
|
|
412
415
|
stats?: fs.Stats;
|
|
413
416
|
}
|
|
414
|
-
/**
|
|
415
|
-
* Static files middleware plugin
|
|
416
|
-
* Usage: server.use('static', '/public')
|
|
417
|
-
* Usage: server.use('static', { root: 'public', path: '/static' })
|
|
418
|
-
*/
|
|
417
|
+
/** Static files plugin. At least must be path to public folder as string or StaticFilesOptions */
|
|
419
418
|
export declare class StaticFilesPlugin extends Plugin {
|
|
420
419
|
priority: number;
|
|
421
420
|
/** Default mime types */
|
|
@@ -444,6 +443,7 @@ export declare class StaticFilesPlugin extends Plugin {
|
|
|
444
443
|
maxAge?: number;
|
|
445
444
|
prefix: string;
|
|
446
445
|
errors?: Record<string, string>;
|
|
446
|
+
checkPrecompressedGzip?: boolean;
|
|
447
447
|
constructor(options?: StaticFilesOptions | string, server?: MicroServer);
|
|
448
448
|
/** Default static files handler */
|
|
449
449
|
handler(req: ServerRequest, res: ServerResponse, next: Function): any;
|
|
@@ -467,9 +467,9 @@ export interface ProxyOptions {
|
|
|
467
467
|
[key: string]: boolean;
|
|
468
468
|
};
|
|
469
469
|
}
|
|
470
|
+
/** Reverse proxy plugin, At least remote url as string or in ProxyOptions is required */
|
|
470
471
|
export declare class ProxyPlugin extends Plugin {
|
|
471
472
|
priority: number;
|
|
472
|
-
name: string;
|
|
473
473
|
/** Default valid headers */
|
|
474
474
|
static validHeaders: {
|
|
475
475
|
[key: string]: boolean;
|
|
@@ -584,22 +584,11 @@ export declare class Auth {
|
|
|
584
584
|
};
|
|
585
585
|
/** Encode token */
|
|
586
586
|
encode(data: string, expire?: number): string;
|
|
587
|
-
/**
|
|
588
|
-
* Check acl over authenticated user with: `id`, `group/*`, `*`
|
|
589
|
-
* @param {string} id - to authenticate: `id`, `group/id`, `model/action`, comma separated best: true => false => def
|
|
590
|
-
* @param {boolean} [def=false] - default access
|
|
591
|
-
*/
|
|
587
|
+
/** Check acl over authenticated user with: `id`, `group/*`, `*` */
|
|
592
588
|
acl(id: string, def?: boolean): boolean;
|
|
593
|
-
/**
|
|
594
|
-
* Authenticate user and setup cookie
|
|
595
|
-
* @param {string|UserInfo} usr - user id used with options.users to retrieve user object. User object must contain `id` and `acl` object (Ex. usr = {id:'usr', acl:{'users/*':true}})
|
|
596
|
-
* @param {string} [psw] - user password (if used for user authentication with options.users)
|
|
597
|
-
* @param {number} [expire] - expire time in seconds (default: options.expire)
|
|
598
|
-
*/
|
|
589
|
+
/** Generate token from user info */
|
|
599
590
|
token(usr: string | UserInfo | undefined, psw: string | undefined, expire?: number): Promise<string | undefined>;
|
|
600
|
-
/**
|
|
601
|
-
* Authenticate user and setup cookie
|
|
602
|
-
*/
|
|
591
|
+
/** Authenticate user and setup cookie */
|
|
603
592
|
login(usr: string | UserInfo | undefined, psw?: string, options?: {
|
|
604
593
|
expire?: number;
|
|
605
594
|
salt?: string;
|
|
@@ -626,6 +615,7 @@ export declare class AuthPlugin extends Plugin {
|
|
|
626
615
|
/** Authentication middleware */
|
|
627
616
|
handler(req: ServerRequest, res: ServerResponse, next: Function): Promise<any>;
|
|
628
617
|
}
|
|
618
|
+
/** Load standard plugins with predefined configs: MethodsPlugin, CorsPlugin, TrustProxyPlugin, BodyPlugin, AuthPlugin, StaticFilesPlugin */
|
|
629
619
|
export declare class StandardPlugins extends Plugin {
|
|
630
620
|
constructor(options?: any, server?: MicroServer);
|
|
631
621
|
}
|
|
@@ -722,7 +712,7 @@ export declare class FileStore {
|
|
|
722
712
|
observe(data: object, cb: (data: object, key: string, value: any) => void): object;
|
|
723
713
|
}
|
|
724
714
|
/** Model validation options */
|
|
725
|
-
interface ModelContextOptions {
|
|
715
|
+
export interface ModelContextOptions {
|
|
726
716
|
/** User info */
|
|
727
717
|
user?: UserInfo;
|
|
728
718
|
/** Request params */
|
|
@@ -741,12 +731,12 @@ interface ModelContextOptions {
|
|
|
741
731
|
projection?: Record<string, 0 | 1 | true | false>;
|
|
742
732
|
}
|
|
743
733
|
/** Model field validation options */
|
|
744
|
-
interface ModelValidateFieldOptions extends ModelContextOptions {
|
|
734
|
+
export interface ModelValidateFieldOptions extends ModelContextOptions {
|
|
745
735
|
name: string;
|
|
746
736
|
field: ResolvedFieldSchema;
|
|
747
737
|
model: Model<any>;
|
|
748
738
|
}
|
|
749
|
-
|
|
739
|
+
interface ModelCallbackFunc {
|
|
750
740
|
(options: any): any;
|
|
751
741
|
}
|
|
752
742
|
type ModelBasicCtorType = typeof String | typeof Number | typeof Boolean | typeof Date;
|
|
@@ -780,7 +770,7 @@ export interface ModelFieldSchema {
|
|
|
780
770
|
/** Regex validation or 'email', 'url', 'date', 'time', 'date-time' */
|
|
781
771
|
format?: string;
|
|
782
772
|
}
|
|
783
|
-
interface ResolvedFieldSchema {
|
|
773
|
+
export interface ResolvedFieldSchema {
|
|
784
774
|
type: string;
|
|
785
775
|
model?: Model<any>;
|
|
786
776
|
primaryKey?: boolean;
|
|
@@ -937,4 +927,4 @@ export declare class MicroCollection<TSchema extends ModelSchema = any> {
|
|
|
937
927
|
modifiedCount: number;
|
|
938
928
|
}>;
|
|
939
929
|
}
|
|
940
|
-
|
|
930
|
+
export {};
|
package/microserver.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MicroServer
|
|
3
|
-
* @version 3.0.
|
|
3
|
+
* @version 3.0.7
|
|
4
4
|
* @package @radatek/microserver
|
|
5
5
|
* @copyright Darius Kisonas 2022
|
|
6
6
|
* @license MIT
|
|
@@ -77,7 +77,6 @@ function deferPromise(cb) {
|
|
|
77
77
|
export class Plugin {
|
|
78
78
|
name;
|
|
79
79
|
priority;
|
|
80
|
-
constructor() { }
|
|
81
80
|
}
|
|
82
81
|
// #region ServerRequest/ServerResponse
|
|
83
82
|
/** Extended http.IncomingMessage */
|
|
@@ -116,7 +115,9 @@ export class ServerRequest extends http.IncomingMessage {
|
|
|
116
115
|
rawBody;
|
|
117
116
|
/** Request raw body size */
|
|
118
117
|
rawBodySize;
|
|
118
|
+
// @internal
|
|
119
119
|
_body;
|
|
120
|
+
// @internal
|
|
120
121
|
_isReady;
|
|
121
122
|
constructor(res, server) {
|
|
122
123
|
super(new net.Socket());
|
|
@@ -365,9 +366,13 @@ export class MicroServer extends EventEmitter {
|
|
|
365
366
|
config;
|
|
366
367
|
/** Authorization object */
|
|
367
368
|
auth;
|
|
369
|
+
// @internal
|
|
368
370
|
_plugins = {};
|
|
371
|
+
// @internal
|
|
369
372
|
_stack = [];
|
|
373
|
+
// @internal
|
|
370
374
|
_router = new RouterPlugin();
|
|
375
|
+
// @internal
|
|
371
376
|
_worker = new Worker();
|
|
372
377
|
/** All sockets */
|
|
373
378
|
sockets;
|
|
@@ -499,6 +504,7 @@ export class MicroServer extends EventEmitter {
|
|
|
499
504
|
return this._worker.wait('listen');
|
|
500
505
|
}
|
|
501
506
|
/* bind middleware or create one from string like: 'redirect:302,https://redirect.to', 'error:422', 'param:name=value', 'acl:users/get', 'model:User', 'group:Users', 'user:admin' */
|
|
507
|
+
// @internal
|
|
502
508
|
_bind(fn) {
|
|
503
509
|
if (typeof fn === 'string') {
|
|
504
510
|
let name = fn;
|
|
@@ -683,6 +689,7 @@ export class MicroServer extends EventEmitter {
|
|
|
683
689
|
this._router.add(method, url, args.filter(o => o).map((o) => this._bind(o)), true);
|
|
684
690
|
return this._worker.endJob();
|
|
685
691
|
}
|
|
692
|
+
// @internal
|
|
686
693
|
async _plugin(plugin) {
|
|
687
694
|
if (plugin.handler) {
|
|
688
695
|
const middleware = plugin.handler.bind(plugin);
|
|
@@ -793,16 +800,24 @@ export class MicroServer extends EventEmitter {
|
|
|
793
800
|
}
|
|
794
801
|
// #region RouterPlugin
|
|
795
802
|
class RouterItem {
|
|
803
|
+
// @internal
|
|
796
804
|
_stack; // add if middlewares added if not last
|
|
805
|
+
// @internal
|
|
797
806
|
_next; // next middlewares
|
|
807
|
+
// @internal
|
|
798
808
|
_paramName; // param name if param is used
|
|
809
|
+
// @internal
|
|
799
810
|
_paramWild;
|
|
811
|
+
// @internal
|
|
800
812
|
_withParam; // next middlewares if param is used
|
|
813
|
+
// @internal
|
|
801
814
|
_last;
|
|
802
815
|
}
|
|
816
|
+
/** Router plugin */
|
|
803
817
|
class RouterPlugin extends Plugin {
|
|
804
818
|
priority = 100;
|
|
805
819
|
name = 'router';
|
|
820
|
+
// @internal
|
|
806
821
|
_tree = {};
|
|
807
822
|
constructor() {
|
|
808
823
|
super();
|
|
@@ -852,6 +867,7 @@ class RouterPlugin extends Plugin {
|
|
|
852
867
|
node._stack.push(...middlewares);
|
|
853
868
|
}
|
|
854
869
|
}
|
|
870
|
+
// @internal
|
|
855
871
|
_getStack(path, treeItems) {
|
|
856
872
|
const out = [];
|
|
857
873
|
const segments = path.split('/').filter(s => s);
|
|
@@ -929,6 +945,7 @@ class RouterPlugin extends Plugin {
|
|
|
929
945
|
this.walk(this._getStack(req.pathname, ['hook', method, '*']), req, res, next);
|
|
930
946
|
}
|
|
931
947
|
}
|
|
948
|
+
/** CORS plugin. Config may be: true - allow all, string - allow specific origin, or CorsOptions */
|
|
932
949
|
export class CorsPlugin extends Plugin {
|
|
933
950
|
priority = -100;
|
|
934
951
|
name = 'cors';
|
|
@@ -962,10 +979,13 @@ export class CorsPlugin extends Plugin {
|
|
|
962
979
|
}
|
|
963
980
|
// #enregion CorsPlugin
|
|
964
981
|
// #region MethodsPlugin
|
|
982
|
+
/** Methods plugin to support OPTIONS method, and restrict allowed methods. Configuration is comma separated string */
|
|
965
983
|
export class MethodsPlugin extends Plugin {
|
|
966
984
|
priority = -90;
|
|
967
985
|
name = 'methods';
|
|
986
|
+
// @internal
|
|
968
987
|
_methods;
|
|
988
|
+
// @internal
|
|
969
989
|
_methodsIdx;
|
|
970
990
|
constructor(methods) {
|
|
971
991
|
super();
|
|
@@ -986,9 +1006,11 @@ export class MethodsPlugin extends Plugin {
|
|
|
986
1006
|
return next();
|
|
987
1007
|
}
|
|
988
1008
|
}
|
|
1009
|
+
/** Body parser plugin */
|
|
989
1010
|
export class BodyPlugin extends Plugin {
|
|
990
1011
|
priority = -80;
|
|
991
1012
|
name = 'body';
|
|
1013
|
+
// @internal
|
|
992
1014
|
_maxBodySize;
|
|
993
1015
|
constructor(options) {
|
|
994
1016
|
super();
|
|
@@ -1040,10 +1062,13 @@ export class BodyPlugin extends Plugin {
|
|
|
1040
1062
|
});
|
|
1041
1063
|
}
|
|
1042
1064
|
}
|
|
1065
|
+
/** Upload plugin, At least uploadDir option is required */
|
|
1043
1066
|
export class UploadPlugin extends Plugin {
|
|
1044
1067
|
priority = -70;
|
|
1045
1068
|
name = 'upload';
|
|
1069
|
+
// @internal
|
|
1046
1070
|
_maxFileSize;
|
|
1071
|
+
// @internal
|
|
1047
1072
|
_uploadDir;
|
|
1048
1073
|
constructor(options) {
|
|
1049
1074
|
super();
|
|
@@ -1133,6 +1158,10 @@ export class UploadPlugin extends Plugin {
|
|
|
1133
1158
|
const safeWriteLength = buffer.length - lookahead;
|
|
1134
1159
|
if (safeWriteLength > 0) {
|
|
1135
1160
|
lastFile.size += safeWriteLength;
|
|
1161
|
+
if (lastFile.size > this._maxFileSize) {
|
|
1162
|
+
req.setReady(new ResponseError("file too big", 413));
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1136
1165
|
fileStream.write(buffer.subarray(0, safeWriteLength));
|
|
1137
1166
|
buffer = buffer.subarray(safeWriteLength);
|
|
1138
1167
|
}
|
|
@@ -1167,12 +1196,17 @@ export class UploadPlugin extends Plugin {
|
|
|
1167
1196
|
}
|
|
1168
1197
|
const EMPTY_BUFFER = Buffer.alloc(0);
|
|
1169
1198
|
const DEFLATE_TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
|
|
1170
|
-
/** WebSocket class */
|
|
1199
|
+
/** WebSocket class used in upgrade handler */
|
|
1171
1200
|
export class WebSocket extends EventEmitter {
|
|
1201
|
+
// @internal
|
|
1172
1202
|
_socket;
|
|
1203
|
+
// @internal
|
|
1173
1204
|
_frame;
|
|
1205
|
+
// @internal
|
|
1174
1206
|
_buffers = [EMPTY_BUFFER];
|
|
1207
|
+
// @internal
|
|
1175
1208
|
_buffersLength = 0;
|
|
1209
|
+
// @internal
|
|
1176
1210
|
_options;
|
|
1177
1211
|
ready = false;
|
|
1178
1212
|
constructor(req, options) {
|
|
@@ -1191,7 +1225,7 @@ export class WebSocket extends EventEmitter {
|
|
|
1191
1225
|
const version = +(req.headers['sec-websocket-version'] || 0);
|
|
1192
1226
|
const extensions = req.headers['sec-websocket-extensions'];
|
|
1193
1227
|
const headers = [];
|
|
1194
|
-
if (!key ||
|
|
1228
|
+
if (!key || upgrade?.toLocaleLowerCase() !== 'websocket' || version !== 13) {
|
|
1195
1229
|
this._abort('Invalid WebSocket request', 400);
|
|
1196
1230
|
return;
|
|
1197
1231
|
}
|
|
@@ -1202,12 +1236,12 @@ export class WebSocket extends EventEmitter {
|
|
|
1202
1236
|
headers.push(header);
|
|
1203
1237
|
this._options.deflate = true;
|
|
1204
1238
|
}
|
|
1205
|
-
this.ready = true;
|
|
1206
1239
|
this._upgrade(key, headers, () => {
|
|
1207
|
-
|
|
1240
|
+
this.ready = true;
|
|
1208
1241
|
this.emit('open');
|
|
1209
1242
|
});
|
|
1210
1243
|
}
|
|
1244
|
+
// @internal
|
|
1211
1245
|
_upgrade(key, headers = [], cb) {
|
|
1212
1246
|
const digest = crypto.createHash('sha1')
|
|
1213
1247
|
.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
|
@@ -1238,45 +1272,6 @@ export class WebSocket extends EventEmitter {
|
|
|
1238
1272
|
}
|
|
1239
1273
|
return this._sendFrame(0x88, data || EMPTY_BUFFER, () => this._socket.destroy());
|
|
1240
1274
|
}
|
|
1241
|
-
/** Generate WebSocket frame from data */
|
|
1242
|
-
static getFrame(data, options) {
|
|
1243
|
-
let msgType = 8;
|
|
1244
|
-
let dataLength = 0;
|
|
1245
|
-
if (typeof data === 'string') {
|
|
1246
|
-
msgType = 1;
|
|
1247
|
-
dataLength = Buffer.byteLength(data, 'utf8');
|
|
1248
|
-
}
|
|
1249
|
-
else if (data instanceof Buffer) {
|
|
1250
|
-
msgType = 2;
|
|
1251
|
-
dataLength = data.length;
|
|
1252
|
-
}
|
|
1253
|
-
else if (typeof data === 'number') {
|
|
1254
|
-
msgType = data;
|
|
1255
|
-
}
|
|
1256
|
-
const headerSize = 2 + (dataLength < 126 ? 0 : dataLength < 65536 ? 2 : 8) + (dataLength && options?.mask ? 4 : 0);
|
|
1257
|
-
const frame = Buffer.allocUnsafe(headerSize + dataLength);
|
|
1258
|
-
frame[0] = 0x80 | msgType;
|
|
1259
|
-
frame[1] = dataLength > 65535 ? 127 : dataLength > 125 ? 126 : dataLength;
|
|
1260
|
-
if (dataLength > 65535)
|
|
1261
|
-
frame.writeBigUInt64BE(dataLength, 2);
|
|
1262
|
-
else if (dataLength > 125)
|
|
1263
|
-
frame.writeUInt16BE(dataLength, 2);
|
|
1264
|
-
if (dataLength && frame.length > dataLength) {
|
|
1265
|
-
if (typeof data === 'string')
|
|
1266
|
-
frame.write(data, headerSize, 'utf8');
|
|
1267
|
-
else
|
|
1268
|
-
data.copy(frame, headerSize);
|
|
1269
|
-
}
|
|
1270
|
-
if (dataLength && options?.mask) {
|
|
1271
|
-
let i = headerSize, h = headerSize - 4;
|
|
1272
|
-
for (let i = 0; i < 4; i++)
|
|
1273
|
-
frame[h + i] = Math.floor(Math.random() * 256);
|
|
1274
|
-
for (let j = 0; j < dataLength; j++, i++) {
|
|
1275
|
-
frame[i] ^= frame[h + (j & 3)];
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
return frame;
|
|
1279
|
-
}
|
|
1280
1275
|
/** Send data */
|
|
1281
1276
|
send(data) {
|
|
1282
1277
|
let msgType = typeof data === 'string' ? 1 : 2;
|
|
@@ -1298,6 +1293,7 @@ export class WebSocket extends EventEmitter {
|
|
|
1298
1293
|
else
|
|
1299
1294
|
return this._sendFrame(0x80 | msgType, data);
|
|
1300
1295
|
}
|
|
1296
|
+
// @internal
|
|
1301
1297
|
_errorHandler(error) {
|
|
1302
1298
|
this.emit('error', error);
|
|
1303
1299
|
if (this.ready)
|
|
@@ -1306,6 +1302,7 @@ export class WebSocket extends EventEmitter {
|
|
|
1306
1302
|
this._socket.destroy();
|
|
1307
1303
|
this.ready = false;
|
|
1308
1304
|
}
|
|
1305
|
+
// @internal
|
|
1309
1306
|
_headerLength(buffer) {
|
|
1310
1307
|
if (this._frame)
|
|
1311
1308
|
return 0;
|
|
@@ -1314,6 +1311,7 @@ export class WebSocket extends EventEmitter {
|
|
|
1314
1311
|
let hederInfo = buffer[1];
|
|
1315
1312
|
return 2 + (hederInfo & 0x80 ? 4 : 0) + ((hederInfo & 0x7F) === 126 ? 2 : 0) + ((hederInfo & 0x7F) === 127 ? 8 : 0);
|
|
1316
1313
|
}
|
|
1314
|
+
// @internal
|
|
1317
1315
|
_dataHandler(data) {
|
|
1318
1316
|
while (data.length) {
|
|
1319
1317
|
let frame = this._frame;
|
|
@@ -1420,6 +1418,7 @@ export class WebSocket extends EventEmitter {
|
|
|
1420
1418
|
this._buffers.push(EMPTY_BUFFER);
|
|
1421
1419
|
}
|
|
1422
1420
|
}
|
|
1421
|
+
// @internal
|
|
1423
1422
|
_abort(message, code, headers) {
|
|
1424
1423
|
code = code || 400;
|
|
1425
1424
|
message = message || http.STATUS_CODES[code] || 'Closed';
|
|
@@ -1446,6 +1445,7 @@ export class WebSocket extends EventEmitter {
|
|
|
1446
1445
|
pong(buffer) {
|
|
1447
1446
|
this._sendFrame(0x8A, buffer || EMPTY_BUFFER);
|
|
1448
1447
|
}
|
|
1448
|
+
// @internal
|
|
1449
1449
|
_sendFrame(opcode, data, cb) {
|
|
1450
1450
|
if (!this.ready)
|
|
1451
1451
|
return;
|
|
@@ -1465,32 +1465,27 @@ export class WebSocket extends EventEmitter {
|
|
|
1465
1465
|
else
|
|
1466
1466
|
this._socket.write(frame, () => this._socket.write(data, cb));
|
|
1467
1467
|
}
|
|
1468
|
-
on(event, listener) { return super.on(event, listener); }
|
|
1469
|
-
addListener(event, listener) { return super.addListener(event, listener); }
|
|
1470
|
-
once(event, listener) { return super.once(event, listener); }
|
|
1471
|
-
off(event, listener) { return super.off(event, listener); }
|
|
1472
|
-
removeListener(event, listener) { return super.removeListener(event, listener); }
|
|
1473
1468
|
}
|
|
1469
|
+
/** WebSocket plugin to support `WEBSOCKET /url` routes */
|
|
1474
1470
|
export class WebSocketPlugin extends Plugin {
|
|
1475
1471
|
name = 'websocket';
|
|
1472
|
+
// @internal
|
|
1476
1473
|
_handler;
|
|
1477
1474
|
constructor(options, server) {
|
|
1478
1475
|
super();
|
|
1479
1476
|
if (!server)
|
|
1480
1477
|
throw new Error('Server instance is required');
|
|
1481
1478
|
this._handler = this.upgradeHandler.bind(this, server);
|
|
1482
|
-
server.servers?.forEach(srv => this.
|
|
1483
|
-
server.on('listen', (port, address, srv) => this.
|
|
1479
|
+
server.servers?.forEach(srv => this._addUpgradeHandler(srv));
|
|
1480
|
+
server.on('listen', (port, address, srv) => this._addUpgradeHandler(srv));
|
|
1484
1481
|
}
|
|
1485
|
-
|
|
1482
|
+
// @internal
|
|
1483
|
+
_addUpgradeHandler(srv) {
|
|
1486
1484
|
if (!srv.listeners('upgrade').includes(this._handler))
|
|
1487
1485
|
srv.on('upgrade', this._handler);
|
|
1488
1486
|
}
|
|
1489
1487
|
upgradeHandler(server, req, socket, head) {
|
|
1490
1488
|
const host = req.headers.host || '';
|
|
1491
|
-
const vhostPlugin = server.getPlugin('vhost');
|
|
1492
|
-
const vserver = vhostPlugin?.vhosts?.[host] || server;
|
|
1493
|
-
req.method = 'WEBSOCKET';
|
|
1494
1489
|
const res = {
|
|
1495
1490
|
req,
|
|
1496
1491
|
get headersSent() {
|
|
@@ -1521,7 +1516,9 @@ export class WebSocketPlugin extends Plugin {
|
|
|
1521
1516
|
socket.write(headers.join('\r\n'), () => { socket.destroy(); });
|
|
1522
1517
|
},
|
|
1523
1518
|
error(code) {
|
|
1524
|
-
|
|
1519
|
+
if (typeof code !== 'number')
|
|
1520
|
+
code = 405;
|
|
1521
|
+
res.statusCode = code || 405;
|
|
1525
1522
|
res.end();
|
|
1526
1523
|
},
|
|
1527
1524
|
send(data) {
|
|
@@ -1531,16 +1528,19 @@ export class WebSocketPlugin extends Plugin {
|
|
|
1531
1528
|
setHeader() { }
|
|
1532
1529
|
};
|
|
1533
1530
|
ServerRequest.extend(req, res, server);
|
|
1534
|
-
let
|
|
1531
|
+
let ws;
|
|
1535
1532
|
Object.defineProperty(req, 'websocket', {
|
|
1536
1533
|
get: () => {
|
|
1537
|
-
if (!
|
|
1538
|
-
|
|
1539
|
-
return
|
|
1534
|
+
if (!ws)
|
|
1535
|
+
ws = new WebSocket(req, server.config.websocket);
|
|
1536
|
+
return ws;
|
|
1540
1537
|
},
|
|
1541
1538
|
enumerable: true
|
|
1542
1539
|
});
|
|
1543
|
-
|
|
1540
|
+
if (req.method !== 'GET' || req.headers.upgrade?.toLowerCase() !== 'websocket')
|
|
1541
|
+
return res.error(400);
|
|
1542
|
+
req.method = 'WEBSOCKET';
|
|
1543
|
+
server.handler(req, res);
|
|
1544
1544
|
}
|
|
1545
1545
|
}
|
|
1546
1546
|
// #endregion WebSocket
|
|
@@ -1549,6 +1549,7 @@ export class WebSocketPlugin extends Plugin {
|
|
|
1549
1549
|
export class TrustProxyPlugin extends Plugin {
|
|
1550
1550
|
priority = -60;
|
|
1551
1551
|
name = 'trustproxy';
|
|
1552
|
+
// @internal
|
|
1552
1553
|
_trustProxy = [];
|
|
1553
1554
|
constructor(options) {
|
|
1554
1555
|
super();
|
|
@@ -1618,13 +1619,7 @@ export class VHostPlugin extends Plugin {
|
|
|
1618
1619
|
}
|
|
1619
1620
|
}
|
|
1620
1621
|
const etagPrefix = crypto.randomBytes(4).toString('hex');
|
|
1621
|
-
|
|
1622
|
-
//TODO: add unknown file extension handler
|
|
1623
|
-
/**
|
|
1624
|
-
* Static files middleware plugin
|
|
1625
|
-
* Usage: server.use('static', '/public')
|
|
1626
|
-
* Usage: server.use('static', { root: 'public', path: '/static' })
|
|
1627
|
-
*/
|
|
1622
|
+
/** Static files plugin. At least must be path to public folder as string or StaticFilesOptions */
|
|
1628
1623
|
export class StaticFilesPlugin extends Plugin {
|
|
1629
1624
|
priority = 110;
|
|
1630
1625
|
/** Default mime types */
|
|
@@ -1668,6 +1663,7 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1668
1663
|
maxAge;
|
|
1669
1664
|
prefix;
|
|
1670
1665
|
errors;
|
|
1666
|
+
checkPrecompressedGzip;
|
|
1671
1667
|
constructor(options, server) {
|
|
1672
1668
|
super();
|
|
1673
1669
|
if (!options)
|
|
@@ -1683,8 +1679,8 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1683
1679
|
this.etag = options.etag !== false;
|
|
1684
1680
|
this.maxAge = options.maxAge;
|
|
1685
1681
|
this.errors = options.errors;
|
|
1682
|
+
this.checkPrecompressedGzip = options.precompressedGzip;
|
|
1686
1683
|
this.prefix = ('/' + (options.path?.replace(/^[.\/]*/, '') || '').replace(/\/$/, '')).replace(/\/$/, '');
|
|
1687
|
-
const defSend = ServerResponse.prototype.send;
|
|
1688
1684
|
if (server && !server.getPlugin('static')) {
|
|
1689
1685
|
this.name = 'static'; // only first plugin instance is registered as
|
|
1690
1686
|
const defSend = ServerResponse.prototype.send;
|
|
@@ -1744,11 +1740,27 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1744
1740
|
req.filename = filename;
|
|
1745
1741
|
return handler.call(this, req, res, next);
|
|
1746
1742
|
}
|
|
1747
|
-
this.
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1743
|
+
if (this.checkPrecompressedGzip && (req.headers['accept-encoding'] || '').includes('gzip')) {
|
|
1744
|
+
const gzipped = filename + '.gz';
|
|
1745
|
+
fs.stat(gzipped, (err, statsGz) => {
|
|
1746
|
+
if (!err && statsGz.isFile()) {
|
|
1747
|
+
res.setHeader('Content-Encoding', 'gzip');
|
|
1748
|
+
filename = gzipped;
|
|
1749
|
+
stats = statsGz;
|
|
1750
|
+
}
|
|
1751
|
+
this.serveFile(req, res, {
|
|
1752
|
+
path: filename,
|
|
1753
|
+
mimeType,
|
|
1754
|
+
stats
|
|
1755
|
+
});
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
else
|
|
1759
|
+
this.serveFile(req, res, {
|
|
1760
|
+
path: filename,
|
|
1761
|
+
mimeType,
|
|
1762
|
+
stats
|
|
1763
|
+
});
|
|
1752
1764
|
});
|
|
1753
1765
|
}
|
|
1754
1766
|
/** Send static file */
|
|
@@ -1802,9 +1814,9 @@ export class StaticFilesPlugin extends Plugin {
|
|
|
1802
1814
|
statRes(null, options.stats);
|
|
1803
1815
|
}
|
|
1804
1816
|
}
|
|
1817
|
+
/** Reverse proxy plugin, At least remote url as string or in ProxyOptions is required */
|
|
1805
1818
|
export class ProxyPlugin extends Plugin {
|
|
1806
1819
|
priority = 120;
|
|
1807
|
-
name = 'proxy';
|
|
1808
1820
|
/** Default valid headers */
|
|
1809
1821
|
static validHeaders = {
|
|
1810
1822
|
authorization: true,
|
|
@@ -1838,6 +1850,8 @@ export class ProxyPlugin extends Plugin {
|
|
|
1838
1850
|
options = { remote: options };
|
|
1839
1851
|
if (!options.remote)
|
|
1840
1852
|
throw new Error('Invalid param');
|
|
1853
|
+
if (server && !server.getPlugin('proxy'))
|
|
1854
|
+
this.name = 'proxy';
|
|
1841
1855
|
this.remoteUrl = new URL(options.remote);
|
|
1842
1856
|
this.regex = options.match ? new RegExp(options.match) : undefined;
|
|
1843
1857
|
this.headers = options.headers;
|
|
@@ -1965,11 +1979,7 @@ export class Auth {
|
|
|
1965
1979
|
encrypted = iv.toString('base64').slice(0, 22) + cipher.getAuthTag().toString('base64').slice(0, 22) + encrypted;
|
|
1966
1980
|
return encrypted.replace(/==?/, '').replace(/\//g, '.').replace(/\+/g, '-');
|
|
1967
1981
|
}
|
|
1968
|
-
/**
|
|
1969
|
-
* Check acl over authenticated user with: `id`, `group/*`, `*`
|
|
1970
|
-
* @param {string} id - to authenticate: `id`, `group/id`, `model/action`, comma separated best: true => false => def
|
|
1971
|
-
* @param {boolean} [def=false] - default access
|
|
1972
|
-
*/
|
|
1982
|
+
/** Check acl over authenticated user with: `id`, `group/*`, `*` */
|
|
1973
1983
|
acl(id, def = false) {
|
|
1974
1984
|
if (!this.req?.user)
|
|
1975
1985
|
return false;
|
|
@@ -1991,12 +2001,7 @@ export class Auth {
|
|
|
1991
2001
|
access = reqAcl['*'];
|
|
1992
2002
|
return access ?? def;
|
|
1993
2003
|
}
|
|
1994
|
-
/**
|
|
1995
|
-
* Authenticate user and setup cookie
|
|
1996
|
-
* @param {string|UserInfo} usr - user id used with options.users to retrieve user object. User object must contain `id` and `acl` object (Ex. usr = {id:'usr', acl:{'users/*':true}})
|
|
1997
|
-
* @param {string} [psw] - user password (if used for user authentication with options.users)
|
|
1998
|
-
* @param {number} [expire] - expire time in seconds (default: options.expire)
|
|
1999
|
-
*/
|
|
2004
|
+
/** Generate token from user info */
|
|
2000
2005
|
async token(usr, psw, expire) {
|
|
2001
2006
|
let data;
|
|
2002
2007
|
if (typeof usr === 'object' && usr && (usr.id || usr._id))
|
|
@@ -2012,9 +2017,7 @@ export class Auth {
|
|
|
2012
2017
|
if (data)
|
|
2013
2018
|
return this.encode(data, expire);
|
|
2014
2019
|
}
|
|
2015
|
-
/**
|
|
2016
|
-
* Authenticate user and setup cookie
|
|
2017
|
-
*/
|
|
2020
|
+
/** Authenticate user and setup cookie */
|
|
2018
2021
|
async login(usr, psw, options) {
|
|
2019
2022
|
let usrInfo;
|
|
2020
2023
|
if (typeof usr === 'object')
|
|
@@ -2289,6 +2292,7 @@ export class AuthPlugin extends Plugin {
|
|
|
2289
2292
|
}
|
|
2290
2293
|
}
|
|
2291
2294
|
// #endregion AuthPlugin
|
|
2295
|
+
/** Load standard plugins with predefined configs: MethodsPlugin, CorsPlugin, TrustProxyPlugin, BodyPlugin, AuthPlugin, StaticFilesPlugin */
|
|
2292
2296
|
export class StandardPlugins extends Plugin {
|
|
2293
2297
|
constructor(options, server) {
|
|
2294
2298
|
super();
|
|
@@ -2304,9 +2308,9 @@ export class StandardPlugins extends Plugin {
|
|
|
2304
2308
|
use(MethodsPlugin);
|
|
2305
2309
|
use(CorsPlugin, 'cors');
|
|
2306
2310
|
use(TrustProxyPlugin, 'trustproxy');
|
|
2311
|
+
use(AuthPlugin, 'auth');
|
|
2307
2312
|
use(BodyPlugin);
|
|
2308
2313
|
use(StaticFilesPlugin, 'static');
|
|
2309
|
-
use(AuthPlugin, 'auth');
|
|
2310
2314
|
}
|
|
2311
2315
|
}
|
|
2312
2316
|
/**
|
|
@@ -2487,7 +2491,9 @@ export class Controller {
|
|
|
2487
2491
|
// #endregion Controller
|
|
2488
2492
|
// #region Worker
|
|
2489
2493
|
class WorkerJob {
|
|
2494
|
+
// @internal
|
|
2490
2495
|
_promises = [];
|
|
2496
|
+
// @internal
|
|
2491
2497
|
_busy = 0;
|
|
2492
2498
|
start() {
|
|
2493
2499
|
this._busy++;
|
|
@@ -2505,7 +2511,9 @@ class WorkerJob {
|
|
|
2505
2511
|
}
|
|
2506
2512
|
}
|
|
2507
2513
|
class Worker {
|
|
2514
|
+
// @internal
|
|
2508
2515
|
_id = 0;
|
|
2516
|
+
// @internal
|
|
2509
2517
|
_jobs = {};
|
|
2510
2518
|
isBusy(id) {
|
|
2511
2519
|
const job = this._jobs[id || 'ready'];
|
|
@@ -2534,11 +2542,17 @@ class Worker {
|
|
|
2534
2542
|
}
|
|
2535
2543
|
/** JSON File store */
|
|
2536
2544
|
export class FileStore {
|
|
2545
|
+
// @internal
|
|
2537
2546
|
_cache;
|
|
2547
|
+
// @internal
|
|
2538
2548
|
_dir;
|
|
2549
|
+
// @internal
|
|
2539
2550
|
_cacheTimeout;
|
|
2551
|
+
// @internal
|
|
2540
2552
|
_cacheItems;
|
|
2553
|
+
// @internal
|
|
2541
2554
|
_debounceTimeout;
|
|
2555
|
+
// @internal
|
|
2542
2556
|
_iter;
|
|
2543
2557
|
constructor(options) {
|
|
2544
2558
|
this._cache = {};
|
|
@@ -2562,7 +2576,9 @@ export class FileStore {
|
|
|
2562
2576
|
}
|
|
2563
2577
|
}
|
|
2564
2578
|
}
|
|
2579
|
+
// @internal
|
|
2565
2580
|
_queue = Promise.resolve();
|
|
2581
|
+
// @internal
|
|
2566
2582
|
async _sync(cb) {
|
|
2567
2583
|
let r;
|
|
2568
2584
|
let p = new Promise(resolve => r = resolve);
|
|
@@ -2741,8 +2757,11 @@ function newObjectId() {
|
|
|
2741
2757
|
return (new Date().getTime() / 1000 | 0).toString(16) + globalObjectId.toString('hex');
|
|
2742
2758
|
}
|
|
2743
2759
|
class ModelCollectionsInternal {
|
|
2760
|
+
// @internal
|
|
2744
2761
|
_ready;
|
|
2762
|
+
// @internal
|
|
2745
2763
|
_wait = new Promise(resolve => this._ready = resolve);
|
|
2764
|
+
// @internal
|
|
2746
2765
|
_db;
|
|
2747
2766
|
set db(db) {
|
|
2748
2767
|
Promise.resolve(db).then(db => this._ready(this._db = db));
|
|
@@ -2759,7 +2778,7 @@ export class Models {
|
|
|
2759
2778
|
}
|
|
2760
2779
|
export class Model {
|
|
2761
2780
|
static collections = new ModelCollectionsInternal();
|
|
2762
|
-
static models =
|
|
2781
|
+
static models = new Models();
|
|
2763
2782
|
static set db(db) {
|
|
2764
2783
|
this.collections.db = db;
|
|
2765
2784
|
}
|
|
@@ -3045,6 +3064,7 @@ export class Model {
|
|
|
3045
3064
|
}
|
|
3046
3065
|
return res;
|
|
3047
3066
|
}
|
|
3067
|
+
// @internal
|
|
3048
3068
|
_fieldFunction(value, def) {
|
|
3049
3069
|
if (typeof value === 'string' && value.startsWith('${') && value.endsWith('}')) {
|
|
3050
3070
|
const names = value.slice(2, -1).split('.');
|
|
@@ -3062,6 +3082,7 @@ export class Model {
|
|
|
3062
3082
|
return () => def;
|
|
3063
3083
|
return () => value;
|
|
3064
3084
|
}
|
|
3085
|
+
// @internal
|
|
3065
3086
|
_validateField(value, options) {
|
|
3066
3087
|
const field = options.field;
|
|
3067
3088
|
if (value == null) {
|
|
@@ -3189,7 +3210,9 @@ export class Model {
|
|
|
3189
3210
|
}
|
|
3190
3211
|
/** Collection factory */
|
|
3191
3212
|
export class MicroCollectionStore {
|
|
3213
|
+
// @internal
|
|
3192
3214
|
_collections = new Map();
|
|
3215
|
+
// @internal
|
|
3193
3216
|
_store;
|
|
3194
3217
|
constructor(dataPath, storeTimeDelay) {
|
|
3195
3218
|
if (dataPath)
|
|
@@ -3212,6 +3235,7 @@ export class MicroCollection {
|
|
|
3212
3235
|
name;
|
|
3213
3236
|
/** Collection data */
|
|
3214
3237
|
data;
|
|
3238
|
+
// @internal
|
|
3215
3239
|
_save;
|
|
3216
3240
|
constructor(options = {}) {
|
|
3217
3241
|
this.name = options.name || this.constructor.name;
|