UNPKG

rjweb-server

Version:

Easy and Robust Way to create a Web Server with Many Easy-to-use Features in NodeJS

283 lines (282 loc) 11.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultOptions = void 0; const global_1 = require("../types/global"); const utils_1 = require("@rjweb/utils"); const GlobalContext_1 = __importDefault(require("../types/internal/classes/GlobalContext")); const ContentTypes_1 = __importDefault(require("./router/ContentTypes")); const File_1 = __importDefault(require("./router/File")); const Middleware_1 = require("./Middleware"); const Validator_1 = __importDefault(require("./Validator")); const Path_1 = __importDefault(require("./router/Path")); const mergeClasses_1 = __importDefault(require("../functions/mergeClasses")); const openapi3_ts_1 = require("openapi3-ts"); const http_1 = __importDefault(require("../handlers/http")); const ws_1 = __importDefault(require("../handlers/ws")); const RequestContext_1 = __importDefault(require("../types/internal/classes/RequestContext")); const HttpRequestContext_1 = __importDefault(require("./request/HttpRequestContext")); const WsOpenContext_1 = __importDefault(require("./request/WsOpenContext")); const WsMessageContext_1 = __importDefault(require("./request/WsMessageContext")); const WsCloseContext_1 = __importDefault(require("./request/WsCloseContext")); exports.defaultOptions = { port: 0, bind: '0.0.0.0', version: true, compression: { http: { enabled: true, preferOrder: ['brotli', 'gzip', 'deflate'], maxSize: (0, utils_1.size)(10).mb(), minSize: (0, utils_1.size)(1).kb() }, ws: { enabled: true, maxSize: (0, utils_1.size)(1).mb() } }, performance: { eTag: true, lastModified: true }, logging: { debug: false, error: true, warn: true }, proxy: { enabled: false, force: false, compress: false, header: 'x-forwarded-for', ips: { validate: false, mode: 'whitelist', list: [...global_1.ReverseProxyIps.LOCAL, ...global_1.ReverseProxyIps.CLOUDFLARE] }, credentials: { authenticate: false, username: 'proxy', password: 'proxy' } } }; class Server { /** * Construct a new Server Instance * @example * ``` * import { Server } from "rjweb-server" * import { Runtime } from "@rjweb/runtime-bun" * * const server = new Server(Runtime, { * port: 3000 * }) * ``` * @since 3.0.0 */ constructor(implementation, options, middlewares, context = {}) { this.context = context; this._status = 'stopped'; this.promises = []; this.openAPISchemas = {}; this.Validator = Validator_1.default; this.middlewares = middlewares ?? []; this.options = utils_1.object.deepParse(exports.defaultOptions, options); this.implementation = new implementation(this.options); this.global = new GlobalContext_1.default(this.options, this.implementation, { HttpRequest: (0, mergeClasses_1.default)(HttpRequestContext_1.default, ...this.middlewares.filter((middleware) => middleware.rjwebVersion === Middleware_1.currentVersion).map((middleware) => middleware.classContexts.HttpRequest(middleware.config, HttpRequestContext_1.default))), WsOpen: (0, mergeClasses_1.default)(WsOpenContext_1.default, ...this.middlewares.filter((middleware) => middleware.rjwebVersion === Middleware_1.currentVersion).map((middleware) => middleware.classContexts.WsOpen(middleware.config, WsOpenContext_1.default))), WsMessage: (0, mergeClasses_1.default)(WsMessageContext_1.default, ...this.middlewares.filter((middleware) => middleware.rjwebVersion === Middleware_1.currentVersion).map((middleware) => middleware.classContexts.WsMessage(middleware.config, WsMessageContext_1.default))), WsClose: (0, mergeClasses_1.default)(WsCloseContext_1.default, ...this.middlewares.filter((middleware) => middleware.rjwebVersion === Middleware_1.currentVersion).map((middleware) => middleware.classContexts.WsClose(middleware.config, WsCloseContext_1.default))) }); const outdated = this.middlewares.find((middleware) => middleware.rjwebVersion !== Middleware_1.currentVersion); if (outdated) { throw new Error(`Middleware Version Mismatch\n ${outdated.infos.name}: ${outdated.infos.version} (Recieved Middleware v${outdated.rjwebVersion}, expected v${Middleware_1.currentVersion})`); } this.global.logger.debug('Server Created!'); this.global.logger.debug('Implementation:'); this.global.logger.debug(` ${this.implementation.name()}: ${this.implementation.version()}`); this.global.logger.debug('Middlewares:'); if (this.middlewares.length) { for (const middleware of this.middlewares) { this.global.logger.debug(` ${middleware.infos.name}: ${middleware.infos.version}`); } } else { this.global.logger.debug(' (None)'); } } /** * Add a Content Type Mapping to override (or expand) content types * @since 5.3.0 */ contentTypes(callback) { const contentTypes = new ContentTypes_1.default(); callback(contentTypes); this.global.contentTypes.import(contentTypes['data']); return this; } get FileLoader() { const global = this.global, promises = this.promises; return class FakeFileLoader extends File_1.default { constructor(prefix) { super(prefix, global, undefined, undefined, promises); } }; } /** * Listen to Error Callbacks * @since 9.0.0 */ error(key, callback) { this.global.errorHandlers[key] = callback; return this; } /** * Listen to Ratelimit Callbacks * @since 9.0.0 */ rateLimit(key, callback) { this.global.rateLimitHandlers[key] = callback; return this; } /** * Listen to Not Found Callbacks * @since 9.0.0 */ notFound(callback) { this.global.notFoundHandler = callback; return this; } /** * Create a new Path * @since 6.0.0 */ path(prefix, callback) { const path = new Path_1.default(prefix, this.global, undefined, undefined, this.promises); callback(path); this.global.routes.http.push(...path['routesHttp']); this.global.routes.static.push(...path['routesStatic']); this.global.routes.ws.push(...path['routesWS']); return this; } /** * Listen to all HTTP Requests (does not override routed ones) * @since 9.0.0 */ http(callback) { this.global.httpHandler = callback; return this; } /** * Generate an OpenAPI Specification for the Server * @since 9.0.0 */ openAPI(name, version, server, contact) { const builder = new openapi3_ts_1.oas31.OpenApiBuilder() .addTitle(name) .addVersion(version) .addServer(server); if (contact) builder.addContact(contact); const routes = {}; for (const route of this.global.routes.http) { if (route.urlData.type === 'regexp') continue; if (!routes[route.urlData.value]) routes[route.urlData.value] = {}; if (route['urlMethod'] === 'CONNECT') continue; routes[route.urlData.value][route['urlMethod']] = route; } for (const path in routes) { const pathItem = {}; for (const [method, route] of Object.entries(routes[path])) { pathItem[method.toLowerCase()] = route['openApi']; } builder.addPath(path, pathItem); } for (const schema in this.openAPISchemas) { builder.addSchema(schema, this.openAPISchemas[schema]); } return builder.rootDoc; } /** * Add an OpenAPI Schema to the Server * @since 9.0.0 */ schema(name, schema) { this.openAPISchemas[name] = schema; return this; } /** * Get the Server's Port that its listening on * @since 9.0.0 */ port() { if (this._status === 'stopped') return null; return (0, utils_1.as)(this.implementation.port()); } /** * Get the Server's Status * @since 9.0.0 */ status() { return this._status; } /** * Start the Server Instance * @example * ``` * import { Server } from "rjweb-server" * * const server = new Server({}) * * server.start().then((port) => { * console.log(`Server Started on Port ${port}`) * }) * ``` * @since 3.0.0 */ start() { if (this._status === 'listening') throw new Error('Server is already listening'); this.implementation.handle({ http: (context) => (0, http_1.default)(new RequestContext_1.default(context, this.middlewares, this, this.global), context, this, this.middlewares, this.context), ws: (ws) => (0, ws_1.default)(ws.data(), ws, this, this.middlewares) }); return new Promise(async (resolve, reject) => { try { await this.implementation.start(); this._status = 'listening'; this.global.logger.debug('Running Router Promises ...'); const promisesStartTime = performance.now(); await Promise.all(this.promises); this.global.logger.debug(`Running Router Promises ... Done ${(performance.now() - promisesStartTime).toFixed(2)}ms`); this.global.logger.debug('Running Middleware Promises ...'); const middlewareStartTime = performance.now(); await Promise.all(this.middlewares.map((middleware) => middleware.callbacks.load?.(middleware.config, this, this.global))); this.global.logger.debug(`Running Middleware Promises ... Done ${(performance.now() - middlewareStartTime).toFixed(2)}ms`); resolve(this.implementation.port()); } catch (err) { this.implementation.stop(); this._status = 'stopped'; reject(err); } }); } /** * Stop the Server Instance * @example * ``` * import { Server } from "rjweb-server" * * const server = new Server({}) * * server.start().then((port) => { * console.log(`Server Started on Port ${port}`) * * setTimeout(() => { * server.stop() * console.log('Server Stopped after 5 seconds') * }, 5000) * }) * ``` * @since 3.0.0 */ stop() { if (this._status === 'stopped') throw new Error('Server is already stopped'); this.implementation.stop(); this._status = 'stopped'; return this; } } exports.default = Server;