@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * MicroServer
3
- * @version 3.0.5
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
- private constructor();
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
- private constructor();
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
- private addUpgradeHandler;
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
- export interface ModelCallbackFunc {
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.5
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 || !upgrade || upgrade.toLocaleLowerCase() !== 'websocket' || version !== 13 || req.method !== 'GET') {
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
- req.setReady();
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.addUpgradeHandler(srv));
1483
- server.on('listen', (port, address, srv) => this.addUpgradeHandler(srv));
1479
+ server.servers?.forEach(srv => this._addUpgradeHandler(srv));
1480
+ server.on('listen', (port, address, srv) => this._addUpgradeHandler(srv));
1484
1481
  }
1485
- addUpgradeHandler(srv) {
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
- res.statusCode = code || 403;
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 _ws;
1531
+ let ws;
1535
1532
  Object.defineProperty(req, 'websocket', {
1536
1533
  get: () => {
1537
- if (!_ws)
1538
- _ws = new WebSocket(req, server.config.websocket);
1539
- return _ws;
1534
+ if (!ws)
1535
+ ws = new WebSocket(req, server.config.websocket);
1536
+ return ws;
1540
1537
  },
1541
1538
  enumerable: true
1542
1539
  });
1543
- vserver.handler(req, res);
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
- //TODO: add precompressed .gz support
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.serveFile(req, res, {
1748
- path: filename,
1749
- mimeType,
1750
- stats
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radatek/microserver",
3
- "version": "3.0.5",
3
+ "version": "3.0.7",
4
4
  "description": "HTTP MicroServer",
5
5
  "author": "Darius Kisonas",
6
6
  "license": "MIT",