mastercontroller 1.2.14 → 1.3.1

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.
@@ -4,7 +4,8 @@
4
4
  "Bash(rm:*)",
5
5
  "Bash(mv:*)",
6
6
  "Bash(node -e:*)",
7
- "Bash(find:*)"
7
+ "Bash(find:*)",
8
+ "Bash(node -c:*)"
8
9
  ],
9
10
  "deny": [],
10
11
  "ask": []
package/MasterControl.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // MasterControl - by Alexander rich
2
- // version 1.0.251
2
+ // version 1.0.252
3
3
 
4
4
  var url = require('url');
5
5
  var fileserver = require('fs');
@@ -192,7 +192,16 @@ class MasterControl {
192
192
 
193
193
  component(folderLocation, innerFolder){
194
194
 
195
- var rootFolderLocation = path.join(this.root, folderLocation, innerFolder);
195
+ // Enhanced: Support both relative (to master.root) and absolute paths
196
+ // If folderLocation is absolute, use it directly; otherwise join with master.root
197
+ var rootFolderLocation;
198
+ if (path.isAbsolute(folderLocation)) {
199
+ // Absolute path provided - use it directly
200
+ rootFolderLocation = path.join(folderLocation, innerFolder);
201
+ } else {
202
+ // Relative path - join with master.root (original behavior)
203
+ rootFolderLocation = path.join(this.root, folderLocation, innerFolder);
204
+ }
196
205
 
197
206
  // Structure is always: {rootFolderLocation}/config/initializers/config.js
198
207
  var configPath = path.join(rootFolderLocation, 'config', 'initializers', 'config.js');
@@ -251,6 +260,9 @@ class MasterControl {
251
260
  // before user config initializes them.
252
261
  try {
253
262
  $that.addInternalTools([
263
+ 'MasterPipeline',
264
+ 'MasterTimeout',
265
+ 'MasterErrorRenderer',
254
266
  'MasterAction',
255
267
  'MasterActionFilters',
256
268
  'MasterRouter',
@@ -258,6 +270,7 @@ class MasterControl {
258
270
  'MasterError',
259
271
  'MasterCors',
260
272
  'MasterSession',
273
+ 'SessionSecurity',
261
274
  'MasterSocket',
262
275
  'MasterHtml',
263
276
  'MasterTemplate',
@@ -267,6 +280,10 @@ class MasterControl {
267
280
  } catch (e) {
268
281
  console.error('[MasterControl] Failed to load internal tools:', e && e.message);
269
282
  }
283
+
284
+ // Register core middleware that must run for framework to function
285
+ $that._registerCoreMiddleware();
286
+
270
287
  if(type === "http"){
271
288
  $that.serverProtocol = "http";
272
289
  return http.createServer(async function(req, res) {
@@ -423,120 +440,148 @@ class MasterControl {
423
440
  });
424
441
  }
425
442
 
426
- async serverRun(req, res){
443
+ /**
444
+ * Register core middleware that must run for the framework to function
445
+ * This includes: static files, body parsing, scoped services, routing, error handling
446
+ */
447
+ _registerCoreMiddleware(){
427
448
  var $that = this;
428
- console.log("path", `${req.method} ${req.url}`);
429
449
 
430
- // Handle CORS preflight (OPTIONS) requests early and positively
431
- if (req.method === 'OPTIONS') {
432
- try {
433
- if (this.cors && typeof this.cors.load === 'function') {
434
- if (!this.cors.options) {
435
- this.cors.init({
436
- origin: true,
437
- methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
438
- allowedHeaders: true,
439
- credentials: false,
440
- maxAge: 86400
441
- });
450
+ // 1. Static File Serving
451
+ $that.pipeline.use(async (ctx, next) => {
452
+ if (ctx.isStatic) {
453
+ // Serve static files
454
+ let pathname = `.${ctx.request.url}`;
455
+
456
+ fs.exists(pathname, function (exist) {
457
+ if (!exist) {
458
+ ctx.response.statusCode = 404;
459
+ ctx.response.end(`File ${pathname} not found!`);
460
+ return;
442
461
  }
443
- this.cors.load({ request: req, response: res });
444
- } else {
445
- res.setHeader('access-control-allow-origin', '*');
446
- res.setHeader('access-control-allow-methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
447
- if (req.headers['access-control-request-headers']) {
448
- res.setHeader('access-control-allow-headers', req.headers['access-control-request-headers']);
462
+
463
+ if (fs.statSync(pathname).isDirectory()) {
464
+ pathname += '/index' + path.parse(pathname).ext;
449
465
  }
450
- res.setHeader('access-control-max-age', '86400');
451
- }
452
- } catch (e) {
453
- res.setHeader('access-control-allow-origin', '*');
454
- res.setHeader('access-control-allow-methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
455
- if (req.headers['access-control-request-headers']) {
456
- res.setHeader('access-control-allow-headers', req.headers['access-control-request-headers']);
457
- }
458
- res.setHeader('access-control-max-age', '86400');
459
- }
460
- res.statusCode = 204;
461
- res.setHeader('content-length', '0');
462
- res.end();
463
- return;
464
- }
465
466
 
466
- // Apply CORS headers to ALL non-OPTIONS requests
467
- try {
468
- if (this.cors && typeof this.cors.load === 'function') {
469
- this.cors.load({ request: req, response: res });
470
- }
471
- } catch (e) {
472
- console.warn('CORS load failed for non-OPTIONS request:', e.message);
473
- }
467
+ fs.readFile(pathname, function(err, data) {
468
+ if (err) {
469
+ ctx.response.statusCode = 500;
470
+ ctx.response.end(`Error getting the file: ${err}.`);
471
+ } else {
472
+ const mimeType = $that.router.findMimeType(path.parse(pathname).ext);
473
+ ctx.response.setHeader('Content-type', mimeType || 'text/plain');
474
+ ctx.response.end(data);
475
+ }
476
+ });
477
+ });
474
478
 
475
- // parse URL
476
- const parsedUrl = url.parse(req.url);
477
- // extract URL path
478
- let pathname = `.${parsedUrl.pathname}`;
479
+ return; // Terminal - don't call next()
480
+ }
479
481
 
480
- // based on the URL path, extract the file extension. e.g. .js, .doc, ...
481
- const ext = path.parse(pathname).ext;
482
+ await next(); // Not static, continue pipeline
483
+ });
482
484
 
483
- // handle simple preflight configuration - might need a complex approch for all scenarios
485
+ // 2. Timeout Tracking (optional - disabled by default until init)
486
+ // Will be configured by user in config.js with master.timeout.init()
487
+ // This is just a placeholder registration - actual timeout is set in user config
484
488
 
489
+ // 3. Request Body Parsing (always needed)
490
+ $that.pipeline.use(async (ctx, next) => {
491
+ // Parse body using MasterRequest
492
+ const params = await $that.request.getRequestParam(ctx.request, ctx.response);
485
493
 
486
- // if extension exist then its a file.
487
- if(ext === ""){
488
- var requestObject = await this.middleware(req, res);
489
- if(requestObject !== -1){
490
- // HSTS header if enabled
491
- if(this.serverProtocol === 'https' && this._hstsEnabled){
492
- res.setHeader('strict-transport-security', `max-age=${this._hstsMaxAge}; includeSubDomains`);
494
+ // Merge parsed params into context
495
+ if (params && params.query) {
496
+ ctx.params.query = params.query;
493
497
  }
494
- var loadedDone = false;
495
- if (typeof $that._loadedFunc === 'function') {
496
- loadedDone = $that._loadedFunc(requestObject);
497
- if (loadedDone){
498
- require(`${this.root}/config/load`)(requestObject);
499
- }
498
+ if (params && params.formData) {
499
+ ctx.params.formData = params.formData;
500
500
  }
501
- else{
502
- require(`${this.root}/config/load`)(requestObject);
501
+
502
+ await next();
503
+ });
504
+
505
+ // 4. Load Scoped Services (per request - always needed)
506
+ $that.pipeline.use(async (ctx, next) => {
507
+ for (var key in $that._scopedList) {
508
+ var className = $that._scopedList[key];
509
+ $that.requestList[key] = new className();
503
510
  }
504
-
505
-
506
- }
507
- }
508
- else{
509
-
510
- fs.exists(pathname, function (exist) {
511
-
512
- if(!exist) {
513
- // if the file is not found, return 404
514
- res.statusCode = 404;
515
- res.end(`File ${pathname} not found!`);
516
- return;
517
- }
518
-
519
- // if is a directory search for index file matching the extension
520
- if (fs.statSync(pathname).isDirectory()) pathname += '/index' + ext;
521
-
522
- // read file from file system
523
- fs.readFile(pathname, function(err, data){
524
- if(err){
525
- res.statusCode = 500;
526
- res.end(`Error getting the file: ${err}.`);
527
- } else {
528
- const mimeType = $that.router.findMimeType(ext);
529
-
530
- // if the file is found, set Content-type and send data
531
- res.setHeader('Content-type', mimeType || 'text/plain' );
532
- res.end(data);
533
- }
534
- });
535
-
511
+ await next();
512
+ });
513
+
514
+ // 4. HSTS Header (if enabled for HTTPS)
515
+ $that.pipeline.use(async (ctx, next) => {
516
+ if ($that.serverProtocol === 'https' && $that._hstsEnabled) {
517
+ ctx.response.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
518
+ }
519
+ await next();
520
+ });
521
+
522
+ // 5. Routing (TERMINAL - always needed)
523
+ $that.pipeline.run(async (ctx) => {
524
+ // Load config/load which triggers routing
525
+ require(`${$that.root}/config/load`)(ctx);
526
+ });
527
+
528
+ // 6. Global Error Handler
529
+ $that.pipeline.useError(async (error, ctx, next) => {
530
+ logger.error({
531
+ code: 'MC_ERR_PIPELINE',
532
+ message: 'Error in middleware pipeline',
533
+ error: error.message,
534
+ stack: error.stack,
535
+ path: ctx.request.url,
536
+ method: ctx.type
536
537
  });
538
+
539
+ if (!ctx.response.headersSent) {
540
+ ctx.response.statusCode = 500;
541
+ ctx.response.setHeader('Content-Type', 'application/json');
542
+ ctx.response.end(JSON.stringify({
543
+ error: 'Internal Server Error',
544
+ message: process.env.NODE_ENV === 'production'
545
+ ? 'An error occurred'
546
+ : error.message
547
+ }));
548
+ }
549
+ });
550
+ }
551
+
552
+ async serverRun(req, res){
553
+ var $that = this;
554
+ console.log("path", `${req.method} ${req.url}`);
555
+
556
+ // Create request context for middleware pipeline
557
+ const parsedUrl = url.parse(req.url);
558
+ const pathname = parsedUrl.pathname;
559
+ const ext = path.parse(pathname).ext;
560
+
561
+ const context = {
562
+ request: req,
563
+ response: res,
564
+ requrl: url.parse(req.url, true),
565
+ pathName: pathname.replace(/^\/|\/$/g, '').toLowerCase(),
566
+ type: req.method.toLowerCase(),
567
+ params: {},
568
+ state: {}, // User-defined state shared across middleware
569
+ master: $that, // Access to framework instance
570
+ isStatic: ext !== '' // Is this a static file request?
571
+ };
572
+
573
+ // Execute middleware pipeline
574
+ try {
575
+ await $that.pipeline.execute(context);
576
+ } catch (error) {
577
+ console.error('Pipeline execution failed:', error);
578
+ if (!res.headersSent) {
579
+ res.statusCode = 500;
580
+ res.end('Internal Server Error');
581
+ }
537
582
  }
538
-
539
- } // end server()
583
+
584
+ } // end serverRun()
540
585
 
541
586
  start(server){
542
587
  this.server = server;
@@ -565,6 +610,9 @@ class MasterControl {
565
610
  if(requiredList.constructor === Array){
566
611
  // Map module names to their new organized paths
567
612
  const modulePathMap = {
613
+ 'MasterPipeline': './MasterPipeline',
614
+ 'MasterTimeout': './MasterTimeout',
615
+ 'MasterErrorRenderer': './error/MasterErrorRenderer',
568
616
  'MasterError': './error/MasterError',
569
617
  'MasterAction': './MasterAction',
570
618
  'MasterActionFilters': './MasterActionFilters',
@@ -572,6 +620,7 @@ class MasterControl {
572
620
  'MasterRequest': './MasterRequest',
573
621
  'MasterCors': './MasterCors',
574
622
  'MasterSession': './MasterSession',
623
+ 'SessionSecurity': './security/SessionSecurity',
575
624
  'MasterSocket': './MasterSocket',
576
625
  'MasterHtml': './MasterHtml',
577
626
  'MasterTemplate': './MasterTemplate',
package/MasterCors.js CHANGED
@@ -11,6 +11,13 @@ class MasterCors{
11
11
  else{
12
12
  master.error.log("cors options missing", "warn");
13
13
  }
14
+
15
+ // Auto-register with pipeline if available
16
+ if (master.pipeline) {
17
+ master.pipeline.use(this.middleware());
18
+ }
19
+
20
+ return this; // Chainable
14
21
  }
15
22
 
16
23
  load(params){
@@ -167,6 +174,28 @@ class MasterCors{
167
174
  }
168
175
  }
169
176
  }
177
+
178
+ /**
179
+ * Get CORS middleware for the pipeline
180
+ * Handles both preflight OPTIONS requests and regular requests
181
+ */
182
+ middleware() {
183
+ var $that = this;
184
+
185
+ return async (ctx, next) => {
186
+ // Handle preflight OPTIONS request
187
+ if (ctx.type === 'options') {
188
+ $that.load({ request: ctx.request, response: ctx.response });
189
+ ctx.response.statusCode = 204;
190
+ ctx.response.end();
191
+ return; // Terminal - don't call next()
192
+ }
193
+
194
+ // Regular request - apply CORS headers
195
+ $that.load({ request: ctx.request, response: ctx.response });
196
+ await next();
197
+ };
198
+ }
170
199
  }
171
200
 
172
201
  master.extend("cors", MasterCors);