UNPKG

rjweb-server

Version:

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

306 lines (305 loc) 9.18 kB
import RPath from "../path"; import { isRegExp } from "util/types"; import parsePath from "../../functions/parsePath"; import path from "path"; import fs from "fs"; import RouteWS from "./ws"; import RouteHTTP from "./http"; import RouteDefaultHeaders from "./defaultHeaders"; class RoutePath { /** Generate Route Block */ constructor(path2, validations, headers, contexts) { this.hasCalledGet = false; this.httpPath = parsePath(path2); this.routes = []; this.statics = []; this.webSockets = []; this.loadPaths = []; this.parsedHeaders = {}; this.validations = validations || []; this.headers = headers || {}; this.externals = []; if (Object.keys(this.headers).length > 0) { const routeDefaultHeaders = new RouteDefaultHeaders(); this.externals.push({ object: routeDefaultHeaders }); for (const [key, value] of Object.entries(this.headers)) { routeDefaultHeaders.add(key, value); } } } /** * Add a Validation * @example * ``` * // The /api route will automatically check for correct credentials * // Obviously still putting the prefix (in this case / from the RoutePath in front) * const controller = new Server({ }) * * controller.path('/api', (path) => path * .validate(async(ctr, end) => { * if (!ctr.headers.has('Authorization')) return end(ctr.status(401).print('Unauthorized')) * if (ctr.headers.get('Authorization') !== 'key123 or db request ig') return end(ctr.status(401).print('Unauthorized')) * }) * .redirect('/pics', 'https://google.com/search?q=devil') * ) * ``` * @since 3.2.1 */ validate(code) { this.validations.push(code); return this; } /** * Add a HTTP Route * @example * ``` * controller.path('/', (path) => path * .http('GET', '/hello', (ws) => ws * .onRequest(async(ctr) => { * ctr.print('Hello bro!') * }) * ) * ) * ``` * @since 6.0.0 */ http(method, path2, callback) { if (this.routes.some((obj) => isRegExp(obj.path) ? false : obj.path.path === parsePath(path2))) return this; const routeHTTP = new RouteHTTP(path2, method, this.validations, this.parsedHeaders); this.externals.push({ object: routeHTTP, addPrefix: this.httpPath }); callback(routeHTTP); return this; } /** * Add a Websocket Route * @example * ``` * controller.path('/', (path) => path * .ws('/uptime', (ws) => ws * .onConnect(async(ctr) => { * console.log('Connected to ws!') * }) * .onMessage((ctr) => { * console.log('Received message', ctr.message) * }) * .onClose((ctr) => { * console.log('Disconnected from ws!') * }) * ) * ) * ``` * @since 5.4.0 */ ws(path2, callback) { if (this.webSockets.some((obj) => isRegExp(obj.path) ? false : obj.path.path === parsePath(path2))) return this; const routeWS = new RouteWS(path2, this.validations, this.parsedHeaders); this.externals.push({ object: routeWS, addPrefix: this.httpPath }); callback(routeWS); return this; } /** * Add Default Headers * @example * ``` * controller.path('/', (path) => path * .defaultHeaders((dH) => dH * .add('X-Api-Version', '1.0.0') * ) * ) * ``` * @since 6.0.0 */ defaultHeaders(callback) { const routeDefaultHeaders = new RouteDefaultHeaders(); this.externals.push({ object: routeDefaultHeaders }); callback(routeDefaultHeaders); this.headers = Object.assign(this.headers, routeDefaultHeaders["defaultHeaders"]); return this; } /** * Add a Redirect * @example * ``` * // The /devil route will automatically redirect to google.com * // Obviously still putting the prefix (in this case / from the RoutePath in front) * const controller = new Server({ }) * * controller.path('/', (path) => path * .redirect('/devil', 'https://google.com') * .redirect('/devilpics', 'https://google.com/search?q=devil') * ) * ``` * @since 3.1.0 */ redirect(request, redirect) { this.routes.push({ type: "http", method: "GET", path: new RPath("GET", parsePath([this.httpPath, request])), onRequest: (ctr) => ctr.redirect(redirect), data: { validations: this.validations, headers: this.parsedHeaders }, context: { data: {}, keep: true } }); return this; } /** * Serve Static Files * @example * ``` * // All Files in "./static" will be served dynamically so they wont be loaded as routes by default * // Due to the hideHTML Option being on files will be served differently, /index.html -> /; /about.html -> /about; /contributors/index.html -> /contributors * const controller = new Server({ }) * * controller.path('/', (path) => path * .static('./static', { * hideHTML: true, // If enabled will remove .html ending from files * addTypes: true, // If enabled will automatically add content-types to some file endings (including the custom ones defined in the main config) * }) * ) * ``` * @since 3.1.0 */ static(folder, options = {}) { const addTypes = options?.addTypes ?? true; const compress = options?.compress ?? true; const hideHTML = options?.hideHTML ?? false; this.statics.push({ type: "static", path: new RPath("GET", parsePath(this.httpPath)), location: folder, data: { doCompress: compress, addTypes, hideHTML, validations: this.validations, headers: this.parsedHeaders } }); return this; } /** * Load CJS Route Files * @example * ``` * // All Files in "./routes" ending with .js will be loaded as routes * const controller = new Server({ }) * * controller.path('/', (path) => path * .loadCJS('./routes') * ) * ``` * @since 3.1.0 */ loadCJS(folder, options = {}) { const fileBasedRouting = options?.fileBasedRouting ?? false; if (!fs.existsSync(path.resolve(folder))) throw Error("The CJS Function folder wasnt found!"); this.loadPaths.push({ path: path.resolve(folder), prefix: this.httpPath, type: "cjs", validations: this.validations, fileBasedRouting, headers: this.parsedHeaders }); return this; } /** * Load ESM Route Files * @example * ``` * // All Files in "./routes" ending with .js will be loaded as routes * const controller = new Server({ }) * * controller.path('/', (path) => path * .loadESM('./routes') * ) * ``` * @since 4.0.0 */ loadESM(folder, options = {}) { const fileBasedRouting = options?.fileBasedRouting ?? false; if (!fs.existsSync(path.resolve(folder))) throw Error("The ESM Function folder wasnt found!"); this.loadPaths.push({ path: path.resolve(folder), prefix: this.httpPath, type: "esm", validations: this.validations, fileBasedRouting, headers: this.parsedHeaders }); return this; } /** * Add a new Block of Routes with a Prefix * @example * ``` * const controller = new Server({ }) * * controller.path('/', (path) => path * .http('GET', '/cool', (http) => http * .onRequest((ctr) => { * ctr.print('cool!') * }) * ) * .path('/api', (path) => path * .context({ * version: '1.0.0' * }) * .http('GET', '/', (http) => http * .onRequest((ctr) => { * ctr.print(`Welcome to the API!, Version ${ctr["@"].version}`) * }) * ) * ) * ) * ``` * @since 5.0.0 */ path(prefix, router) { if ("getData" in router) { this.externals.push({ object: router, addPrefix: parsePath([this.httpPath, prefix]) }); } else { const routePath = new RoutePath(parsePath([this.httpPath, prefix]), [...this.validations], Object.assign({}, this.headers)); this.externals.push({ object: routePath }); router(routePath); } return this; } /** * Internal Method for Generating Routes Object * @since 6.0.0 */ async getData() { if (!this.hasCalledGet) for (const external of this.externals) { const result = await external.object.getData(external.addPrefix ?? "/"); if ("routes" in result && result.routes.length > 0) this.routes.push(...result.routes); if ("webSockets" in result && result.webSockets.length > 0) this.webSockets.push(...result.webSockets); if ("statics" in result && result.statics.length > 0) this.statics.push(...result.statics); if ("loadPaths" in result && result.loadPaths.length > 0) this.loadPaths.push(...result.loadPaths); if ("defaultHeaders" in result) this.parsedHeaders = Object.assign(this.parsedHeaders, result.defaultHeaders); } this.hasCalledGet = true; return { routes: this.routes, webSockets: this.webSockets, statics: this.statics, loadPaths: this.loadPaths }; } } export { RoutePath as default };