Untitled
unknown
typescript
a year ago
6.5 kB
8
Indexable
import http from 'node:http';
import path from 'node:path';
import https from 'node:https';
import { readFileSync } from 'node:fs';
import { json, urlencoded, Application, Request, Response, NextFunction, Router } from 'express';
import cookieParser from 'cookie-parser';
import form from 'express-form-data';
import express from 'express';
import morgan from 'morgan';
import actuator from 'express-actuator';
import cors from 'cors';
import rateLimit from 'express-rate-limit';
// Local Services
import { hooks } from './hooks';
import { services } from './services';
import socket, { listen as wsListen } from './controllers/socket';
import SearchCtrl from './controllers/search/search';
import NewMailer from './controllers/email';
import * as operations from './controllers/operations';
// Settings
import settings from './settings';
// Controllers
import { imageUp } from './controllers/imageUp';
import gracefullShutdown from './controllers/gracefullShutdown';
// TypeScript Types
interface Dependency {
method: (...args: any[]) => Promise<any>;
args: any[];
}
interface AppConfig {
origin: string;
port: number;
useHTTP2: boolean;
}
interface WebSocketEvent {
method: Function;
props: {
ws: any;
lyra: SearchCtrl;
db: typeof operations;
mail: ReturnType<typeof NewMailer>;
settings: AppConfig;
};
}
export default class App {
private express: Application;
private router: Router;
private config: AppConfig;
private search: SearchCtrl;
private mail: ReturnType<typeof NewMailer>;
private imageUp: typeof imageUp;
private db: typeof operations;
private events: Record<string, WebSocketEvent>;
private wsMiddlewares: any[];
private depPromises: Promise<any>[];
private server!: http.Server | https.Server;
private ssl?: { key: Buffer; cert: Buffer };
private options?: { key: Buffer; cert: Buffer; allowHTTP1: boolean };
private socket?: any;
private ready: boolean = false;
constructor({ deps }: { deps?: Dependency[] } = {}) {
this.express = express();
this.router = Router();
this.config = settings;
this.search = new SearchCtrl();
this.mail = NewMailer(this.config);
this.imageUp = imageUp;
this.db = operations;
this.events = {};
this.wsMiddlewares = [];
this.depPromises = [];
if (deps) {
this.depPromises = deps.map(
({ method, args }) =>
new Promise((resolve, reject) =>
method(...args)
.then((r) => resolve(r))
.catch((err) => reject(err))
)
);
}
Promise.all(this.depPromises)
.then((res) => res && res.length > 0 && console.log(`=> '${res}'!`))
.then(() => this.init())
.catch((err) => console.log(err));
}
/**
* Boots the actual express application
*/
start(): void {
const readyStatus = setInterval(() => {
if (this.ready) {
this.listen();
clearInterval(readyStatus);
}
}, 300);
}
/**
* Initialize Express app with dependencies
* and then the http(s) server
*/
private init(): void {
const { parse } = form;
// Rate Limiter
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes)
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
});
// Load the middlewares
this.express.use(
cors({
origin: this.config.origin,
credentials: true,
})
);
this.express.use(morgan('common')); // Logger
this.express.use(actuator({ infoGitMode: 'full' })); // Health Checker
this.express.use(json()); // Parse JSON response
this.express.use(urlencoded({ extended: false })); // Legacy URL encoding
this.express.use(cookieParser()); // Parse cookies
this.express.use(parse()); // Parse Form data as JSON
this.express.use('/api', limiter, this.router); // All the API routes
this.express.use(express.static(path.resolve(process.cwd(), '..', 'client'))); // REACT build files (Statics)
if (this.config.useHTTP2) {
// SSL configuration
this.ssl = {
key: readFileSync(path.resolve('ssl', 'privatekey.pem')),
cert: readFileSync(path.resolve('ssl', 'certificate.pem')),
};
this.options = {
...this.ssl,
allowHTTP1: true,
};
// HTTP/2 Server
this.server = https.createServer(this.options, this.express);
// Load the Hooks
hooks(this);
} else {
// HTTP Server
this.server = http.createServer(this.express);
}
// Start Search service
this.search.start();
// Socket Server
this.socket = socket(this.server, { origin: this.config.origin });
// Load the Services
services(this);
// Listen for events
wsListen(this.socket, this.events, ...this.wsMiddlewares);
this.ready = true;
gracefullShutdown.call({ ...this });
}
/**
* Listener for http requests
*/
private listen(): void {
// Serve Front-end
this.express.get('*', (req: Request, res: Response) => {
res.sendFile(path.resolve(process.cwd(), '..', 'client', 'index.html'));
});
// Boot the server
this.server.listen(this.config.port, () => {
console.log(`=> Listening on ${this.config.port}`);
});
}
/**
* Register Hooks
* @param callback function of hook
*/
hook(callback: Function): void {
callback.call({ ...this });
}
/**
* Configure service with api
* @param callback function for services
*/
configure(callback: Function): void {
callback.call({
...this.express,
route: this.router,
ws: this.socket,
imageUp: this.imageUp,
lyra: this.search,
db: this.db,
mail: this.mail,
settings: this.config,
});
}
/**
* Register events for ws with service
* @param event
* @param middlewares
* @param callback
*/
register(
event: string,
middlewares: any[] | Function,
callback?: Function
): void {
let callee;
if (typeof middlewares === 'function') {
callee = middlewares;
} else {
callee = callback;
this.wsMiddlewares = middlewares;
}
this.events[event] = {
method: callee!,
props: {
ws: this.socket,
lyra: this.search,
db: this.db,
mail: this.mail,
settings: this.config,
},
};
}
}
Editor is loading...
Leave a Comment