UNPKG

rjweb-server

Version:

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

271 lines (270 loc) 9.98 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const Route_1 = __importDefault(require("../Route")); const Http_1 = __importDefault(require("./Http")); const parseURL_1 = __importDefault(require("../../functions/parseURL")); const RateLimit_1 = __importDefault(require("./RateLimit")); const path_1 = __importDefault(require("path")); const utils_1 = require("@rjweb/utils"); const Ws_1 = __importDefault(require("./Ws")); class Path { computePath(path) { if (path instanceof RegExp) return path; return (0, parseURL_1.default)(this.prefix.concat('/', path)).path; } /** * Create a new Path * @since 6.0.0 */ constructor(prefix, global, validators = [], ratelimits, promises, openApi) { this.validators = validators; this.routesHttp = []; this.routesStatic = []; this.routesWS = []; this.prefix = prefix; this._global = global; if (ratelimits) { this._httpRatelimit = ratelimits[0]; this._wsRatelimit = ratelimits[1]; } else { this._httpRatelimit = new RateLimit_1.default()['data']; this._wsRatelimit = new RateLimit_1.default()['data']; } this.promises = promises ?? []; this.openApi = openApi ?? {}; } /** * Add OpenAPI Documentation to all HTTP Endpoints in this Path (and all children) * @since 9.0.0 */ document(item) { this.openApi = utils_1.object.deepMerge(this.openApi, item); return this; } /** * Add a Ratelimit to all HTTP Endpoints in this Path (and all children) * * When a User requests an Endpoint, that will count against their hit count, if * the hits exceeds the `<maxHits>` in `<timeWindow>ms`, they wont be able to access * the route for `<penalty>ms`. * @example * ``` * import { time } from "@rjweb/utils" * * server.path('/', (path) => path * .httpRateLimit((limit) => limit * .hits(5) * .window(time(20).s()) * .penalty(0) * ) // This will allow 5 requests every 20 seconds * .http('GET', '/hello', (ws) => ws * .onRequest(async(ctr) => { * ctr.print('Hello bro!') * }) * ) * ) * * server.rateLimit('httpRequest', (ctr) => { * ctr.print(`Please wait ${ctr.getRateLimit()?.resetIn}ms!!!!!`) * }) * ``` * @since 8.6.0 */ httpRatelimit(callback) { const limit = new RateLimit_1.default(); limit['data'] = Object.assign({}, this._httpRatelimit); callback(limit); this._httpRatelimit = limit['data']; return this; } /** * Add a Ratelimit to all WebSocket Endpoints in this Path (and all children) * * When a User sends a message to a Socket, that will count against their hit count, if * the hits exceeds the `<maxHits>` in `<timeWindow>ms`, they wont be able to access * the route for `<penalty>ms`. * @example * ``` * import { time } from "@rjweb/utils" * * server.path('/', (path) => path * .httpRateLimit((limit) => limit * .hits(5) * .window(time(20).s()) * .penalty(0) * ) // This will allow 5 messages every 20 seconds * .ws('/echo', (ws) => ws * .onMessage(async(ctr) => { * ctr.print(await ctr.rawMessageBytes()) * }) * ) * ) * * server.rateLimit('wsMessage', (ctr) => { * ctr.print(`Please wait ${ctr.getRateLimit()?.resetIn}ms!!!!!`) * }) * ``` * @since 8.6.0 */ wsRatelimit(callback) { const limit = new RateLimit_1.default(); limit['data'] = Object.assign({}, this._wsRatelimit); callback(limit); this._wsRatelimit = limit['data']; return this; } /** * Create a redirection route * @example * ``` * const server = new Server(...) * * server.path('/', (path) => path * .redirect('/google', 'https://google.com') * ) * ``` * @since 3.10.0 */ redirect(path, redirect, type) { for (const p of Array.isArray(path) ? path : [path]) { const route = new Route_1.default('http', 'GET', this.computePath(p), { onRequest(ctr) { return ctr.redirect(redirect, type); } }); route.validators.unshift(...this.validators); this.routesHttp.push(route); } return this; } /** * Create a subpath of this Path * @since 6.0.0 */ path(prefix, callback) { const path = new Path(this.prefix.concat('/', prefix), this._global, [...this.validators], [this._httpRatelimit, this._wsRatelimit], this.promises, this.openApi); callback(path); this.routesHttp.push(...path.routesHttp); this.routesStatic.push(...path.routesStatic); this.routesWS.push(...path.routesWS); return this; } /** * Use a Validator on all Endpoints in this Path * * This will attach a Validator to all Endpoints in this Path * @since 9.0.0 */ validate(validator) { this.validators.push(validator); this.openApi = utils_1.object.deepMerge(this.openApi, validator.openApi); return this; } /** * Serve Static Files * @example * ``` * // All Files in "./public" will be served dynamically so they wont be loaded as routes by default * // Due to the stripHtmlEnding Option being on files will be served differently, /index.html -> /; /about.html -> /about; /contributors/index.html -> /contributors * const server = new Server(...) * * server.path('/', (path) => path * .static('./public', { * stripHtmlEnding: true // If enabled will remove .html ending from files * }) * ) * ``` * @since 3.1.0 */ static(folder, options = {}) { const route = new Route_1.default('static', 'GET', this.computePath('/'), { folder: path_1.default.posix.resolve(folder), stripHtmlEnding: options.stripHtmlEnding ?? false }); route.validators.unshift(...this.validators); this.routesStatic.push(route); if (options.preProcessFiles) { this.promises.push(new Promise(async (resolve) => { for await (const file of utils_1.filesystem.walk(route.data.folder, { async: true, recursive: true })) { if (file.isDirectory() || !file.path) continue; const $path = path_1.default.posix.relative(route.data.folder, file.path).replace('./', ''); if (!route.data.stripHtmlEnding) { this._global.cache.staticFiles.set(this.computePath($path).toString(), [path_1.default.posix.resolve(file.path), route]); } else { { // path/index.html if (file.name.endsWith('index.html')) { this._global.cache.staticFiles.set(this.computePath($path.replace('index.html', '')).toString(), [path_1.default.posix.resolve(file.path), route]); } } { // path.html if (file.name.endsWith('.html')) { this._global.cache.staticFiles.set(this.computePath($path.replace('.html', '')).toString(), [path_1.default.posix.resolve(file.path), route]); } } { // path this._global.cache.staticFiles.set(this.computePath($path).toString(), [path_1.default.posix.resolve(file.path), route]); } } } resolve(); })); } return this; } /** * Add a new HTTP Route * @example * ``` * const server = new Server(...) * * server.path('/', (path) => path * .http('GET', '/hello', (ws) => ws * .onRequest((ctr) => { * ctr.print('Hello!') * }) * ) * ) * ``` * @since 6.0.0 */ http(method, path, callback) { for (const p of Array.isArray(path) ? path : [path]) { const http = new Http_1.default(method, this.computePath(p), this._httpRatelimit); http['route'].openApi = Object.assign({}, this.openApi); callback(http); const url = http['route'].urlData; if (url.type === 'regexp') url.prefix = this.computePath('/').toString(); http['route'].validators.unshift(...this.validators); this.routesHttp.push(http['route']); } return this; } /** * Add a new WebSocket Route * @example * ``` * const server = new Server(...) * * server.path('/', (path) => path * .ws('/echo', (ws) => ws * .onMessage((ctr) => { * ctr.print(ctr.rawMessageBytes()) * }) * ) * ) * ``` * @since 6.0.0 */ ws(path, callback) { for (const p of Array.isArray(path) ? path : [path]) { const ws = new Ws_1.default(this.computePath(p), this._wsRatelimit); ws['route'].openApi = Object.assign({}, this.openApi); callback(ws); const url = ws['route'].urlData; if (url.type === 'regexp') url.prefix = this.computePath('/').toString(); ws['route'].validators.unshift(...this.validators); this.routesWS.push(ws['route']); } return this; } } exports.default = Path;