@kwaeri/router
Version:
The @kwaeri/router component of the @kwaer/node-kit application platform.
229 lines • 9.64 kB
JavaScript
/**
* SPDX-PackageName: kwaeri/router
* SPDX-PackageVersion: 0.3.5
* SPDX-FileCopyrightText: © 2014 - 2022 Richard Winters <kirvedx@gmail.com> and contributors
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception OR MIT
*/
;
// INCLUDES
import * as events from 'events';
import * as fs from 'fs';
import debug from 'debug';
// DEFINES
const DEBUG = debug('nodekit:router');
export class Router {
/**
* @var { events.EventEmitter } promoter
*/
promoter;
/**
* @var { Controller }
*/
controller;
/**
* @var { any }
*/
errorController;
/**
* @var { Session }
*/
session;
/**
* @var { string }
*/
baseAssetPath;
/**
* @var { string }
*/
derivedAssetPath;
/**
* Class constructor
*/
constructor(configuration) {
DEBUG(`Loading the promoter`);
// Create an instance of an event emitter and store it:
this.promoter = new events.EventEmitter();
// Get the session object to derive from:
const derivedSession = configuration.session.provider;
// Store the base controller:
this.controller = configuration.controller;
// Store the error controller:
this.errorController = configuration.errorController || null;
// Set the base and derived asset path(s):
const { base, root, asset_path, asset_provider } = configuration[configuration.appType];
this.baseAssetPath = base + root + asset_path;
this.derivedAssetPath = this.baseAssetPath + '/' + asset_provider;
// Remove references we do not want to pass forward with the configuration:
delete configuration.session.provider;
delete configuration.controller;
// Store the promoter in the configuration to be passed down the dependency chain:
configuration.promoter = this.promoter;
// Instantiate the session provider:
this.session = new derivedSession(configuration);
DEBUG(`Starting the session service`);
// Start the session service:
this.session.start();
// Set the base controller's configuration:
this.controller.configuration = configuration;
}
/**
* Routes the client request
*
* @param { http.IncomingMessage } request
* @param { http.ServerResponse } response
*
* @returns { void }
*/
async route(request, response) {
// Save a reference of this instance (route didn't bind to router in
// server so "this" is Server [post rewrite]):
const router = this.router, // this
{ session, controller } = router, path = request.path, referrer = request.headers.referrer; // For why was it named referer? (missing second r)
// Assets needs to be modifiable
let assets = router.derivedAssetPath;
// Check the type of content the request is for, and set the proper headers.
//
// In the below regexp, we ensure that a period comes before the media identifier at the
// END of the string so that we do not get accidental matches from a path-naming coincidence
//
// We also suggest that there could be another period at the end of the string followed by
// either gz, bz2, or map (css.map). This covers most media types
let mediaType = path.match(/\.(css|less|js|eot|ttf|woff?2|ico|bmp|jpe?g|gif|png|svg|json|xml|text|zip|tar|mp3|mp4|mov|ogg|webm)\.?(gz|bz2|map)?$/);
// JS files are sometimes fetched as media (e.g. from a script element, defined in the src
// attribute). When this happens we will ALWAYS have a referer (that is a string value of
// referer, this means that somebody is directly requesting a .JS file (Or perhaps their
// browser is in privacy mode?).
//
// Depending on the case we handle it correctly, directly called JS file requests are sent
// to the controller, while the router handles feeding any media JS files needed by the
// application directly to the browser.
if (mediaType !== null) {
// Personally, I don't think we need to throw an exception just because a css file or
// zip file wasn't found - that's annoying, especially in node.js environments.
let doThrow = false;
// Since we forced that check for the period, the proper media type is DEFINITELY at
// array position [1], as array position [0] would be the original string matched
// that the additional matches are drawn from:
//
// We'll potentially work with tar.gz|bz2 files - we've added a match group for them:
if (mediaType[1] === 'tar')
mediaType = mediaType[2].toString();
else
mediaType = mediaType[1].toString();
// Now we check the value of mediaType and set the proper content-type for the response:
switch (mediaType) {
// This case covers .css.map as well, 'map' is in mediaType[2] until mediaType is
// redefined above
case 'css':
case 'less':
mediaType = 'text/css';
break;
case 'js':
mediaType = 'application/javascript';
break;
case 'eot':
case 'ttf':
case 'woff':
case 'woff2':
mediaType = 'application/x-font-' + mediaType;
break;
case 'ico':
case 'bmp':
case 'gif':
case 'jpg':
case 'jpeg':
case 'png':
case 'svg':
if (mediaType === 'ico') {
mediaType = 'image/x-icon';
// If this is a favicon we'll alter the source path a little bit to keep it
// separate of any third-party assets
var favicon = path.search("favicon");
if (favicon > -1)
assets = router.baseAssetPath;
}
else if (mediaType === 'jpg')
mediaType = 'image/jpeg';
else
mediaType = 'image/' + mediaType;
break;
case 'xml':
case 'json':
mediaType = 'application/' + mediaType;
break;
case 'text':
mediaType = 'text/plain';
break;
case 'zip':
mediaType = 'application/octet-stream';
break;
case 'gz':
mediaType = 'application/x-gzip';
break;
case 'bz2':
mediaType = 'application/x-bzip2';
break;
case 'mp3':
mediaType = 'audio/mpeg';
break;
case 'mp4':
mediaType = 'video/mp4';
break;
case 'ogg':
mediaType = 'video/ogg';
break;
case 'webm':
mediaType = 'video/webm';
break;
}
// Send a success header:
response.statusCode = 200;
response.setHeader('Content-Type', mediaType);
DEBUG(`Set 'Content-Type' to '${mediaType}'`);
// If we find a reference to extensions in our path, modify the assets path:
if (path.indexOf("extensions") > -1) {
// Modify the asset path
assets = router.baseAssetPath;
DEBUG(`Set 'assets' [path] to '${router.baseAssetPath}'`);
}
// Read in the asset or resource
fs.readFile(// __dirname is always the location of the file it is used
assets + path, // in without a trailing slash
'binary', // <-- Yep, always binary
(error, data) => {
// If there is an error:
if (error) {
// Log it:
console.error(`[ROUTER] Error reading asset or resource: ${assets}${path}.`);
// If we are flagged to throw an exception:
if (doThrow)
throw error;
// Otherwise redirect the user to our nifty 404 controller:
controller.handle404(request, response);
}
else {
// If there are no errors, respond with the asset/resource
response.setHeader('Content-Length', data.length);
DEBUG(`Set 'Content-Length' to '${data.length}'`);
response.write(data, 'binary');
response.end();
}
});
}
else {
// Attempt to load the requested controller
//session.find(
// request,
// response,
// ( req: any, res: any ) => {
// DEBUG( `Call 'approach' for '${path}'` );
// controller.approach( req, res );
// }
//);
DEBUG(`Call 'approach' for '${path}'`);
controller.approach(...await session.find(request, response));
}
}
;
}
//# sourceMappingURL=router.mjs.map