@radatek/microserver 2.3.1 → 2.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/microserver.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * MicroServer
3
- * @version 2.3.1
3
+ * @version 2.3.2
4
4
  * @package @radatek/microserver
5
5
  * @copyright Darius Kisonas 2022
6
6
  * @license MIT
@@ -128,6 +128,7 @@ export interface WebSocketOptions {
128
128
  autoPong?: boolean;
129
129
  permessageDeflate?: boolean;
130
130
  maxWindowBits?: number;
131
+ timeout?: number;
131
132
  deflate?: boolean;
132
133
  }
133
134
  /** WebSocket class */
@@ -213,12 +214,11 @@ export interface Middleware {
213
214
  plugin?: Plugin;
214
215
  }
215
216
  declare class Waiter {
216
- get busy(): boolean;
217
- startJob(): void;
217
+ isBusy(id?: string): boolean;
218
+ startJob(id?: string): void;
218
219
  endJob(id?: string): void;
219
220
  get nextId(): string;
220
- wait(id: string): Promise<void>;
221
- resolve(id: string): void;
221
+ wait(id?: string): Promise<void>;
222
222
  }
223
223
  /** Router */
224
224
  export declare class Router extends EventEmitter {
@@ -373,7 +373,7 @@ export declare class MicroServer extends EventEmitter {
373
373
  once(name: string, cb: Function): this;
374
374
  /** Add listener and call immediatelly for 'ready' */
375
375
  on(name: string, cb: Function): this;
376
- isReady(): boolean;
376
+ get isReady(): boolean;
377
377
  waitReady(): Promise<void>;
378
378
  waitPlugin(id: string): Promise<void>;
379
379
  /** Listen server, should be used only if config.listen is not set */
@@ -538,6 +538,33 @@ export interface AuthOptions {
538
538
  /** Interal next cache cleanup time */
539
539
  cacheCleanup?: number;
540
540
  }
541
+ export interface AuthOptionsInternal extends AuthOptions {
542
+ /** Authentication token */
543
+ token: Buffer;
544
+ /** Users */
545
+ users: (usr: string, psw?: string) => Promise<UserInfo | undefined>;
546
+ /** Default ACL */
547
+ defaultAcl: {
548
+ [key: string]: boolean;
549
+ };
550
+ /** Expire time in seconds */
551
+ expire: number;
552
+ /** Authentication mode */
553
+ mode: 'cookie' | 'token';
554
+ /** Authentication realm for basic authentication */
555
+ realm: string;
556
+ /** Redirect URL */
557
+ redirect: string;
558
+ /** Authentication cache */
559
+ cache: {
560
+ [key: string]: {
561
+ data: UserInfo;
562
+ time: number;
563
+ };
564
+ };
565
+ /** Interal next cache cleanup time */
566
+ cacheCleanup: number;
567
+ }
541
568
  /** Authentication class */
542
569
  export declare class Auth {
543
570
  /** Server request */
@@ -545,10 +572,8 @@ export declare class Auth {
545
572
  /** Server response */
546
573
  res: ServerResponse | undefined;
547
574
  /** Authentication options */
548
- options: AuthOptions;
549
- /** Get user function */
550
- users: ((usr: string, psw?: string, salt?: string) => Promise<UserInfo | undefined>);
551
- constructor(options?: AuthOptions);
575
+ options: AuthOptionsInternal;
576
+ constructor(options: AuthOptionsInternal, req?: ServerRequest, res?: ServerResponse);
552
577
  /** Decode token */
553
578
  decode(data: string): {
554
579
  data: string;
@@ -709,9 +734,10 @@ export declare interface ModelCollections {
709
734
  collection(name: string): Promise<MicroCollection>;
710
735
  }
711
736
  export declare class Model<TSchema extends ModelSchema> {
712
- static collections?: ModelCollections;
737
+ static collections: ModelCollections;
713
738
  static models: Record<string, Model<any>>;
714
- static schema(schema: ModelSchema): ModelSchema;
739
+ static set db(db: any);
740
+ static get db(): any;
715
741
  /** Define model */
716
742
  static define<T extends ModelSchema>(name: string, schema: T, options?: {
717
743
  collection?: MicroCollection | Promise<MicroCollection>;
@@ -778,7 +804,7 @@ export declare interface FindOptions {
778
804
  }
779
805
  /** Collection factory */
780
806
  export declare class MicroCollectionStore {
781
- constructor(dataPath?: string);
807
+ constructor(dataPath?: string, storeTimeDelay?: number);
782
808
  /** Get collection */
783
809
  collection(name: string): Promise<MicroCollection>;
784
810
  }
package/microserver.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * MicroServer
3
- * @version 2.3.1
3
+ * @version 2.3.2
4
4
  * @package @radatek/microserver
5
5
  * @copyright Darius Kisonas 2022
6
6
  * @license MIT
@@ -457,8 +457,10 @@ export class WebSocket extends EventEmitter {
457
457
  maxPayload: 1024 * 1024,
458
458
  permessageDeflate: false,
459
459
  maxWindowBits: 15,
460
+ timeout: 120000,
460
461
  ...options
461
462
  };
463
+ this._socket.setTimeout(this._options.timeout || 120000);
462
464
  const key = req.headers['sec-websocket-key'];
463
465
  const upgrade = req.headers.upgrade;
464
466
  const version = +(req.headers['sec-websocket-version'] || 0);
@@ -922,46 +924,53 @@ export class Controller {
922
924
  return routes;
923
925
  }
924
926
  }
925
- class Waiter {
927
+ class WaiterJob {
926
928
  constructor() {
927
- this._waiters = {};
928
- this._id = 0;
929
+ this._waiters = [];
929
930
  this._busy = 0;
930
931
  }
931
- get busy() {
932
- return this._busy > 0;
933
- }
934
- startJob() {
932
+ start() {
935
933
  this._busy++;
936
934
  }
937
- endJob(id) {
935
+ end() {
938
936
  this._busy--;
937
+ for (const resolve of this._waiters.splice(0))
938
+ resolve();
939
+ }
940
+ async wait() {
939
941
  if (!this._busy)
940
- this.resolve(id || 'ready');
942
+ return;
943
+ return new Promise(resolve => this._waiters.push(resolve));
941
944
  }
942
- get nextId() {
943
- return (++this._id).toString();
945
+ }
946
+ class Waiter {
947
+ constructor() {
948
+ this._id = 0;
949
+ this._waiters = {};
944
950
  }
945
- async wait(id) {
946
- return new Promise(resolve => (this._waiters[id] = this._waiters[id] || []).push(resolve));
951
+ isBusy(id) {
952
+ const waiter = this._waiters[id || 'ready'];
953
+ return !!waiter?._busy;
947
954
  }
948
- resolve(id) {
949
- const resolvers = this._waiters[id];
950
- if (resolvers)
951
- for (const resolve of resolvers)
952
- resolve(undefined);
953
- delete this._waiters[id];
955
+ startJob(id) {
956
+ let waiter = this._waiters[id || 'ready'];
957
+ if (!waiter)
958
+ waiter = this._waiters[id || 'ready'] = new WaiterJob();
959
+ waiter._busy++;
954
960
  }
955
- }
956
- class EmitterWaiter extends Waiter {
957
- constructor(emitter) {
958
- super();
959
- this._emitter = emitter;
961
+ endJob(id) {
962
+ const waiter = this._waiters[id || 'ready'];
963
+ if (waiter)
964
+ waiter.end();
960
965
  }
961
- resolve(id) {
962
- if (id === 'ready')
963
- this._emitter.emit(id);
964
- super.resolve(id);
966
+ get nextId() {
967
+ return (++this._id).toString();
968
+ }
969
+ async wait(id) {
970
+ const waiter = this._waiters[id || 'ready'];
971
+ if (!waiter)
972
+ return;
973
+ return waiter.wait();
965
974
  }
966
975
  }
967
976
  /** Router */
@@ -1361,7 +1370,7 @@ export class Router extends EventEmitter {
1361
1370
  if (plugin.routes)
1362
1371
  await this.use(isFunction(plugin.routes) ? await plugin.routes() : plugin.routes);
1363
1372
  if (added)
1364
- this._waiter.resolve(added);
1373
+ this.emit(added);
1365
1374
  }
1366
1375
  async waitPlugin(id) {
1367
1376
  if (!this.plugins[id])
@@ -1384,12 +1393,9 @@ export class Router extends EventEmitter {
1384
1393
  export class MicroServer extends EventEmitter {
1385
1394
  constructor(config) {
1386
1395
  super();
1387
- this._waiter = new EmitterWaiter(this);
1396
+ this._waiter = new Waiter();
1388
1397
  this._methods = {};
1389
1398
  let promise = Promise.resolve();
1390
- this._init = (f, ...args) => {
1391
- promise = promise.then(() => f.apply(this, args)).catch(e => this.emit('error', e));
1392
- };
1393
1399
  this.config = {
1394
1400
  maxBodySize: defaultMaxBodySize,
1395
1401
  methods: defaultMethods,
@@ -1406,17 +1412,20 @@ export class MicroServer extends EventEmitter {
1406
1412
  if (config[key])
1407
1413
  this.router.use(MicroServer.plugins[key], config[key]);
1408
1414
  }
1409
- if (config.listen)
1410
- this._init(() => {
1411
- this.listen({
1412
- tls: config.tls,
1413
- listen: config.listen || 8080
1414
- });
1415
+ if (config.listen) {
1416
+ this._waiter.startJob();
1417
+ this.listen({
1418
+ tls: config.tls,
1419
+ listen: config.listen || 8080
1420
+ }).then(() => {
1421
+ this._waiter.endJob();
1415
1422
  });
1423
+ }
1424
+ this._waiter.wait().then(() => this.emit('ready'));
1416
1425
  }
1417
1426
  /** Add one time listener or call immediatelly for 'ready' */
1418
1427
  once(name, cb) {
1419
- if (name === 'ready' && this.isReady())
1428
+ if (name === 'ready' && this.isReady)
1420
1429
  cb();
1421
1430
  else
1422
1431
  super.once(name, cb);
@@ -1424,18 +1433,18 @@ export class MicroServer extends EventEmitter {
1424
1433
  }
1425
1434
  /** Add listener and call immediatelly for 'ready' */
1426
1435
  on(name, cb) {
1427
- if (name === 'ready' && this.isReady())
1436
+ if (name === 'ready' && this.isReady)
1428
1437
  cb();
1429
1438
  super.on(name, cb);
1430
1439
  return this;
1431
1440
  }
1432
- isReady() {
1433
- return !this._waiter.busy;
1441
+ get isReady() {
1442
+ return !this._waiter.isBusy();
1434
1443
  }
1435
1444
  async waitReady() {
1436
- if (this.isReady())
1445
+ if (this.isReady)
1437
1446
  return;
1438
- return this._waiter.wait("ready");
1447
+ return this._waiter.wait();
1439
1448
  }
1440
1449
  async waitPlugin(id) {
1441
1450
  await this.router.waitPlugin(id);
@@ -1467,7 +1476,7 @@ export class MicroServer extends EventEmitter {
1467
1476
  }
1468
1477
  const reg = /^((?<proto>\w+):\/\/)?(?<host>(\[[^\]]+\]|[a-z][^:,]+|\d+\.\d+\.\d+\.\d+))?:?(?<port>\d+)?/;
1469
1478
  listen.split(',').forEach(listen => {
1470
- this._waiter.startJob();
1479
+ this._waiter.startJob('listen');
1471
1480
  let { proto, host, port } = reg.exec(listen)?.groups || {};
1472
1481
  let srv;
1473
1482
  switch (proto) {
@@ -1494,20 +1503,20 @@ export class MicroServer extends EventEmitter {
1494
1503
  }
1495
1504
  this.servers.add(srv);
1496
1505
  if (port === '0') // skip listening
1497
- this._waiter.endJob();
1506
+ this._waiter.endJob('listen');
1498
1507
  else {
1499
1508
  srv.listen(parseInt(port), host?.replace(/[\[\]]/g, '') || '0.0.0.0', () => {
1500
1509
  const addr = srv.address();
1501
1510
  this.emit('listen', addr.port, addr.address, srv);
1502
1511
  srv._ready = true;
1503
- this._waiter.endJob();
1512
+ this._waiter.endJob('listen');
1504
1513
  });
1505
1514
  }
1506
1515
  srv.on('error', err => {
1507
1516
  srv.close();
1508
1517
  this.servers.delete(srv);
1509
1518
  if (!srv._ready)
1510
- this._waiter.endJob();
1519
+ this._waiter.endJob('listen');
1511
1520
  this.emit('error', err);
1512
1521
  });
1513
1522
  srv.on('connection', s => {
@@ -1516,7 +1525,7 @@ export class MicroServer extends EventEmitter {
1516
1525
  });
1517
1526
  srv.on('upgrade', this.handlerUpgrade.bind(this));
1518
1527
  });
1519
- return this._waiter.wait('ready');
1528
+ return this._waiter.wait('listen');
1520
1529
  }
1521
1530
  /** Add middleware, routes, etc.. see {router.use} */
1522
1531
  use(...args) {
@@ -1663,7 +1672,6 @@ export class MicroServer extends EventEmitter {
1663
1672
  this.sockets.clear();
1664
1673
  }).then(() => {
1665
1674
  this.emit('close');
1666
- this._waiter.resolve("close");
1667
1675
  });
1668
1676
  }
1669
1677
  /** Add route, alias to `server.router.use(url, ...args)` */
@@ -2006,31 +2014,12 @@ ProxyPlugin.validHeaders = {
2006
2014
  MicroServer.plugins.proxy = ProxyPlugin;
2007
2015
  /** Authentication class */
2008
2016
  export class Auth {
2009
- constructor(options) {
2010
- let token = options?.token || defaultToken;
2011
- if (!token || token.length !== 32)
2012
- token = defaultToken;
2013
- if (token.length !== 32)
2014
- token = crypto.createHash('sha256').update(token).digest();
2015
- if (!(token instanceof Buffer))
2016
- token = Buffer.from(token);
2017
- this.options = {
2018
- token,
2019
- users: options?.users,
2020
- mode: options?.mode || 'cookie',
2021
- defaultAcl: options?.defaultAcl || { '*': false },
2022
- expire: options?.expire || defaultExpire,
2023
- cache: options?.cache || {}
2024
- };
2025
- if (typeof options?.users === 'function')
2026
- this.users = options.users;
2027
- else
2028
- this.users = async (usrid, psw) => {
2029
- const users = this.options.users;
2030
- const usr = users?.[usrid];
2031
- if (usr && (psw === undefined || this.checkPassword(usrid, psw, usr.password || '')))
2032
- return usr;
2033
- };
2017
+ constructor(options, req, res) {
2018
+ this.options = options;
2019
+ this.req = req;
2020
+ this.res = res;
2021
+ if (req)
2022
+ req.auth = this;
2034
2023
  }
2035
2024
  /** Decode token */
2036
2025
  decode(data) {
@@ -2106,7 +2095,7 @@ export class Auth {
2106
2095
  data = JSON.stringify(usr);
2107
2096
  else if (typeof usr === 'string') {
2108
2097
  if (psw !== undefined) {
2109
- const userInfo = await this.users(usr, psw);
2098
+ const userInfo = await this.options.users(usr, psw);
2110
2099
  data = userInfo?.id || userInfo?._id;
2111
2100
  }
2112
2101
  else
@@ -2123,7 +2112,7 @@ export class Auth {
2123
2112
  if (typeof usr === 'object')
2124
2113
  usrInfo = usr;
2125
2114
  if (typeof usr === 'string')
2126
- usrInfo = await this.users(usr, psw, options?.salt);
2115
+ usrInfo = await this.options.users(usr, psw);
2127
2116
  if (usrInfo?.id || usrInfo?._id) {
2128
2117
  const expire = Math.min(34560000, options?.expire || this.options.expire || defaultExpire);
2129
2118
  const expireTime = new Date().getTime() + expire * 1000;
@@ -2289,17 +2278,32 @@ class AuthPlugin extends Plugin {
2289
2278
  ...options,
2290
2279
  cacheCleanup: new Date().getTime()
2291
2280
  };
2292
- if (this.options.token === defaultToken)
2281
+ if (options?.token === defaultToken)
2293
2282
  console.warn('Default token in auth plugin');
2283
+ let token = options?.token || defaultToken;
2284
+ if (!token || token.length !== 32)
2285
+ token = defaultToken;
2286
+ if (token.length !== 32)
2287
+ token = crypto.createHash('sha256').update(token).digest();
2288
+ if (!(token instanceof Buffer))
2289
+ token = Buffer.from(token);
2290
+ this.options.token = token;
2291
+ if (typeof options?.users === 'function')
2292
+ this.options.users = (usr, psw) => options.users(usr, psw);
2293
+ else {
2294
+ this.options.users = async (usrid, psw) => {
2295
+ const users = this.options.users;
2296
+ const usr = users?.[usrid];
2297
+ if (usr && (psw === undefined || router.auth?.checkPassword(usrid, psw, usr.password || '')))
2298
+ return usr;
2299
+ };
2300
+ }
2294
2301
  router.auth = new Auth(this.options);
2295
2302
  }
2296
2303
  /** Authentication middleware */
2297
2304
  async handler(req, res, next) {
2298
2305
  const options = this.options, cache = options.cache;
2299
- const auth = new Auth(options);
2300
- req.auth = auth;
2301
- auth.req = req;
2302
- auth.res = res;
2306
+ const auth = new Auth(this.options, req, res);
2303
2307
  const authorization = req.headers.authorization || '';
2304
2308
  if (authorization.startsWith('Basic ')) {
2305
2309
  let usr = cache?.[authorization];
@@ -2308,7 +2312,7 @@ class AuthPlugin extends Plugin {
2308
2312
  else {
2309
2313
  const usrpsw = Buffer.from(authorization.slice(6), 'base64').toString('utf-8'), pos = usrpsw.indexOf(':'), username = usrpsw.slice(0, pos), psw = usrpsw.slice(pos + 1);
2310
2314
  if (username && psw)
2311
- req.user = await auth.users(username, psw);
2315
+ req.user = await auth.options.users(username, psw);
2312
2316
  if (!req.user)
2313
2317
  return res.error(401);
2314
2318
  if (cache) // 1 hour to expire in cache
@@ -2354,7 +2358,7 @@ class AuthPlugin extends Plugin {
2354
2358
  catch (e) { }
2355
2359
  }
2356
2360
  else {
2357
- req.user = await auth.users(usrData.data);
2361
+ req.user = await auth.options.users(usrData.data);
2358
2362
  if (!req.user) {
2359
2363
  req.auth.logout();
2360
2364
  return new AccessDenied();
@@ -2383,7 +2387,7 @@ export class FileStore {
2383
2387
  this._dir = options?.dir || 'data';
2384
2388
  this._cacheTimeout = options?.cacheTimeout || 2000;
2385
2389
  this._cacheItems = options?.cacheItems || 10;
2386
- this._debounceTimeout = options?.debounceTimeout || 1000;
2390
+ this._debounceTimeout = options?.debounceTimeout ?? 1000;
2387
2391
  this._iter = 0;
2388
2392
  }
2389
2393
  /** cleanup cache */
@@ -2435,16 +2439,16 @@ export class FileStore {
2435
2439
  if (item && new Date().getTime() - item.atime < this._cacheTimeout)
2436
2440
  return item.data;
2437
2441
  try {
2438
- const stat = await fs.promises.lstat(path.join(this._dir, name));
2439
- if (item?.mtime !== stat.mtime.getTime()) {
2440
- let data = JSON.parse(await fs.promises.readFile(path.join(this._dir, name), 'utf8') || '{}');
2442
+ const stat = await fs.promises.lstat(path.join(this._dir, name)).catch(() => null);
2443
+ if (!stat || item?.mtime !== stat.mtime.getTime()) {
2444
+ let data = stat ? JSON.parse(await fs.promises.readFile(path.join(this._dir, name), 'utf8').catch(() => '') || '{}') : {};
2441
2445
  this._iter++;
2442
2446
  this.cleanup();
2443
2447
  if (autosave)
2444
2448
  data = this.observe(data, () => this.save(name, data));
2445
2449
  this._cache[name] = {
2446
2450
  atime: new Date().getTime(),
2447
- mtime: stat.mtime.getTime(),
2451
+ mtime: stat?.mtime.getTime() || new Date().getTime(),
2448
2452
  data: data
2449
2453
  };
2450
2454
  return data;
@@ -2573,14 +2577,32 @@ function newObjectId() {
2573
2577
  break;
2574
2578
  return (new Date().getTime() / 1000 | 0).toString(16) + globalObjectId.toString('hex');
2575
2579
  }
2580
+ class ModelCollectionsInternal {
2581
+ constructor() {
2582
+ this._wait = new Promise(resolve => this._ready = resolve);
2583
+ }
2584
+ set db(db) {
2585
+ Promise.resolve(db).then(db => this._ready(this._db = db));
2586
+ }
2587
+ get db() {
2588
+ return this._db;
2589
+ }
2590
+ async collection(name) {
2591
+ const db = await this._wait;
2592
+ return db.collection(name);
2593
+ }
2594
+ }
2576
2595
  export class Model {
2577
- static schema(schema) {
2578
- return schema;
2596
+ static set db(db) {
2597
+ this.collections.db = db;
2598
+ }
2599
+ static get db() {
2600
+ return this.collections.db;
2579
2601
  }
2580
2602
  /** Define model */
2581
2603
  static define(name, schema, options) {
2582
2604
  options = options || {};
2583
- if (!options.collection && this.collections)
2605
+ if (!options.collection)
2584
2606
  options.collection = this.collections.collection(name);
2585
2607
  const inst = options?.class
2586
2608
  ? new options.class(schema, { name, ...options })
@@ -2963,13 +2985,16 @@ export class Model {
2963
2985
  }
2964
2986
  }
2965
2987
  }
2988
+ Model.collections = new ModelCollectionsInternal();
2966
2989
  Model.models = {};
2967
2990
  /** Collection factory */
2968
2991
  export class MicroCollectionStore {
2969
- constructor(dataPath) {
2992
+ constructor(dataPath, storeTimeDelay) {
2970
2993
  this._collections = new Map();
2971
2994
  if (dataPath)
2972
- this._store = new FileStore({ dir: dataPath.replace(/^\w+:\/\//, '') });
2995
+ this._store = new FileStore({ dir: dataPath.replace(/^\w+:\/\//, ''), debounceTimeout: storeTimeDelay ?? 1000 });
2996
+ if (!Model.db)
2997
+ Model.db = this;
2973
2998
  }
2974
2999
  /** Get collection */
2975
3000
  async collection(name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radatek/microserver",
3
- "version": "2.3.1",
3
+ "version": "2.3.2",
4
4
  "description": "HTTP MicroServer",
5
5
  "author": "Darius Kisonas",
6
6
  "license": "MIT",