ios-dev/comments/Home/node_modules/@tinyhttp/app/dist/index.js

465 lines
14 KiB
JavaScript
Raw Normal View History

2024-03-11 14:47:28 +03:00
import { STATUS_CODES, createServer } from "node:http";
import { proxyaddr, all, compile } from "@tinyhttp/proxy-addr";
import { isIP } from "node:net";
import { getRequestHeader, getQueryParams, getRangeFromHeader, getAccepts, getAcceptsCharsets, getAcceptsEncodings, getAcceptsLanguages, checkIfXMLHttpRequest, getFreshOrStale, getPathname, getURLParams } from "@tinyhttp/req";
import { getURLParams as getURLParams2 } from "@tinyhttp/req";
import { Router, pushMiddleware } from "@tinyhttp/router";
import { getResponseHeader, setHeader, send, json, status, sendStatus, sendFile, setContentType, setLocationHeader, setLinksHeader, setVaryHeader, setCookie, clearCookie, formatResponse, redirect, attachment, download, append } from "@tinyhttp/res";
import { parse } from "regexparam";
import { extname, resolve, dirname, basename, join } from "node:path";
import { statSync } from "node:fs";
const trustRemoteAddress = ({ socket }) => {
const val = socket.remoteAddress;
if (typeof val === "string")
return compile(val.split(",").map((x) => x.trim()));
return compile(val || []);
};
const getProtocol = (req) => {
const proto = `http${req.secure ? "s" : ""}`;
if (!trustRemoteAddress(req))
return proto;
const header = req.headers["X-Forwarded-Proto"] || proto;
const index = header.indexOf(",");
return index !== -1 ? header.substring(0, index).trim() : header.trim();
};
const getHostname = (req) => {
let host = req.get("X-Forwarded-Host");
if (!host || !trustRemoteAddress(req))
host = req.get("Host");
if (!host)
return;
const index = host.indexOf(":", host[0] === "[" ? host.indexOf("]") + 1 : 0);
return index !== -1 ? host.substring(0, index) : host;
};
const getIP = (req) => proxyaddr(req, trustRemoteAddress(req)).replace(/^.*:/, "");
const getIPs = (req) => all(req, trustRemoteAddress(req));
const getSubdomains = (req, subdomainOffset = 2) => {
const hostname = getHostname(req);
if (!hostname)
return [];
const subdomains = isIP(hostname) ? [hostname] : hostname.split(".").reverse();
return subdomains.slice(subdomainOffset);
};
const onErrorHandler = function(err, _req, res) {
if (this.onError === onErrorHandler && this.parent)
return this.parent.onError(err, _req, res);
if (err instanceof Error)
console.error(err);
const code = err.code in STATUS_CODES ? err.code : err.status;
if (typeof err === "string" || Buffer.isBuffer(err))
res.writeHead(500).end(err);
else if (code in STATUS_CODES)
res.writeHead(code).end(STATUS_CODES[code]);
else
res.writeHead(500).end(err.message);
};
const renderTemplate = (_req, res, app) => (file, data, options) => {
app.render(file, data ? { ...res.locals, ...data } : res.locals, options, (err, html) => {
if (err)
throw err;
res.send(html);
});
return res;
};
const extendMiddleware = (app) => (req, res, next) => {
const { settings } = app;
res.get = getResponseHeader(res);
req.get = getRequestHeader(req);
if (settings == null ? void 0 : settings.bindAppToReqRes) {
req.app = app;
res.app = app;
}
if (settings == null ? void 0 : settings.networkExtensions) {
req.protocol = getProtocol(req);
req.secure = req.protocol === "https";
req.hostname = getHostname(req);
req.subdomains = getSubdomains(req, settings.subdomainOffset);
req.ip = getIP(req);
req.ips = getIPs(req);
}
req.query = getQueryParams(req.url);
req.range = getRangeFromHeader(req);
req.accepts = getAccepts(req);
req.acceptsCharsets = getAcceptsCharsets(req);
req.acceptsEncodings = getAcceptsEncodings(req);
req.acceptsLanguages = getAcceptsLanguages(req);
req.xhr = checkIfXMLHttpRequest(req);
res.header = res.set = setHeader(res);
res.send = send(req, res);
res.json = json(res);
res.status = status(res);
res.sendStatus = sendStatus(req, res);
res.sendFile = sendFile(req, res);
res.type = setContentType(res);
res.location = setLocationHeader(req, res);
res.links = setLinksHeader(res);
res.vary = setVaryHeader(res);
res.cookie = setCookie(req, res);
res.clearCookie = clearCookie(req, res);
res.render = renderTemplate(req, res, app);
res.format = formatResponse(req, res, next);
res.redirect = redirect(req, res, next);
res.attachment = attachment(res);
res.download = download(req, res);
res.append = append(res);
res.locals = res.locals || /* @__PURE__ */ Object.create(null);
Object.defineProperty(req, "fresh", { get: getFreshOrStale.bind(null, req, res), configurable: true });
req.stale = !req.fresh;
next();
};
function tryStat(path) {
try {
return statSync(path);
} catch (e) {
return void 0;
}
}
class View {
constructor(name, opts = {}) {
this.ext = extname(name);
this.name = name;
this.root = opts.root;
this.defaultEngine = opts.defaultEngine;
if (!this.ext && !this.defaultEngine)
throw new Error("No default engine was specified and no extension was provided.");
let fileName = name;
if (!this.ext) {
this.ext = this.defaultEngine[0] !== "." ? "." + this.defaultEngine : this.defaultEngine;
fileName += this.ext;
}
if (!opts.engines[this.ext])
throw new Error(`No engine was found for ${this.ext}`);
this.engine = opts.engines[this.ext];
this.path = this.#lookup(fileName);
}
#lookup(name) {
let path;
const roots = [].concat(this.root);
for (let i = 0; i < roots.length && !path; i++) {
const root = roots[i];
const loc = resolve(root, name);
const dir = dirname(loc);
const file = basename(loc);
path = this.#resolve(dir, file);
}
return path;
}
#resolve(dir, file) {
const ext = this.ext;
let path = join(dir, file);
let stat = tryStat(path);
if (stat && stat.isFile()) {
return path;
}
path = join(dir, basename(file, ext), "index" + ext);
stat = tryStat(path);
if (stat && stat.isFile()) {
return path;
}
}
render(options, data, cb) {
this.engine(this.path, data, options, cb);
}
}
const lead = (x) => x.charCodeAt(0) === 47 ? x : "/" + x;
const mount = (fn) => fn instanceof App ? fn.attach : fn;
const applyHandler = (h) => async (req, res, next) => {
try {
if (h[Symbol.toStringTag] === "AsyncFunction") {
await h(req, res, next);
} else
h(req, res, next);
} catch (e) {
next(e);
}
};
class App extends Router {
constructor(options = {}) {
super();
this.middleware = [];
this.locals = {};
this.engines = {};
this.onError = (options == null ? void 0 : options.onError) || onErrorHandler;
this.noMatchHandler = (options == null ? void 0 : options.noMatchHandler) || this.onError.bind(this, { code: 404 });
this.settings = {
view: View,
xPoweredBy: true,
views: `${process.cwd()}/views`,
"view cache": process.env.NODE_ENV === "production",
...options.settings
};
this.applyExtensions = options == null ? void 0 : options.applyExtensions;
this.attach = (req, res) => setImmediate(this.handler.bind(this, req, res, void 0), req, res);
this.cache = {};
}
/**
* Set app setting
* @param setting setting name
* @param value setting value
*/
set(setting, value) {
this.settings[setting] = value;
return this;
}
/**
* Enable app setting
* @param setting Setting name
*/
enable(setting) {
this.settings[setting] = true;
return this;
}
/**
* Check if setting is enabled
* @param setting Setting name
* @returns
*/
enabled(setting) {
return Boolean(this.settings[setting]);
}
/**
* Disable app setting
* @param setting Setting name
*/
disable(setting) {
this.settings[setting] = false;
return this;
}
/**
* Return the app's absolute pathname
* based on the parent(s) that have
* mounted it.
*
* For example if the application was
* mounted as `"/admin"`, which itself
* was mounted as `"/blog"` then the
* return value would be `"/blog/admin"`.
*
*/
path() {
return this.parent ? this.parent.path() + this.mountpath : "";
}
/**
* Register a template engine with extension
*/
engine(ext, fn) {
this.engines[ext[0] === "." ? ext : `.${ext}`] = fn;
return this;
}
/**
* Render a template
* @param file What to render
* @param data data that is passed to a template
* @param options Template engine options
* @param cb Callback that consumes error and html
*/
render(name, data = {}, options = {}, cb) {
let view;
const { _locals, ...opts } = options;
let locals = this.locals;
if (_locals)
locals = { ...locals, ..._locals };
locals = { ...locals, ...data };
if (opts.cache == null)
opts.cache = this.enabled("view cache");
if (opts.cache) {
view = this.cache[name];
}
if (!view) {
const View2 = this.settings["view"];
view = new View2(name, {
defaultEngine: this.settings["view engine"],
root: this.settings.views,
engines: this.engines
});
if (!view.path) {
const dirs = Array.isArray(view.root) && view.root.length > 1 ? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"' : 'directory "' + view.root + '"';
const err = new Error('Failed to lookup view "' + name + '" in views ' + dirs);
return cb(err);
}
if (opts.cache) {
this.cache[name] = view;
}
}
try {
view.render(opts, locals, cb);
} catch (err) {
cb(err);
}
}
use(...args) {
const base = args[0];
const fns = args.slice(1).flat();
let pathArray = [];
if (typeof base === "function" || base instanceof App) {
fns.unshift(base);
} else {
let basePaths = [];
if (Array.isArray(base))
basePaths = [...base];
else if (typeof base === "string")
basePaths = [base];
basePaths = basePaths.filter((element) => {
if (typeof element === "string") {
pathArray.push(element);
return false;
}
return true;
});
fns.unshift(...basePaths);
}
pathArray = pathArray.length ? pathArray.map((path) => lead(path)) : ["/"];
const mountpath = pathArray.join(", ");
let regex;
for (const fn of fns) {
if (fn instanceof App) {
pathArray.forEach((path) => {
regex = parse(path, true);
fn.mountpath = mountpath;
this.apps[path] = fn;
fn.parent = this;
});
}
}
pathArray.forEach((path) => {
var _a;
const handlerPaths = [];
const handlerFunctions = [];
const handlerPathBase = path === "/" ? "" : lead(path);
for (const fn of fns) {
if (fn instanceof App && ((_a = fn.middleware) == null ? void 0 : _a.length)) {
for (const mw of fn.middleware) {
handlerPaths.push(handlerPathBase + lead(mw.path));
handlerFunctions.push(fn);
}
} else {
handlerPaths.push("");
handlerFunctions.push(fn);
}
}
pushMiddleware(this.middleware)({
path,
regex,
type: "mw",
handler: mount(handlerFunctions[0]),
handlers: handlerFunctions.slice(1).map(mount),
fullPaths: handlerPaths
});
});
return this;
}
route(path) {
const app = new App({ settings: this.settings });
this.use(path, app);
return app;
}
#find(url) {
return this.middleware.filter((m) => {
m.regex = m.regex || parse(m.path, m.type === "mw");
let fullPathRegex;
m.fullPath && typeof m.fullPath === "string" ? fullPathRegex = parse(m.fullPath, m.type === "mw") : fullPathRegex = null;
return m.regex.pattern.test(url) && (m.type === "mw" && fullPathRegex ? fullPathRegex.pattern.test(url) : true);
});
}
/**
* Extends Req / Res objects, pushes 404 and 500 handlers, dispatches middleware
* @param req Req object
* @param res Res object
*/
handler(req, res, next) {
const { xPoweredBy } = this.settings;
if (xPoweredBy)
res.setHeader("X-Powered-By", typeof xPoweredBy === "string" ? xPoweredBy : "tinyhttp");
const exts = this.applyExtensions || extendMiddleware(this);
req.originalUrl = req.url || req.originalUrl;
const pathname = getPathname(req.originalUrl);
const matched = this.#find(pathname);
const mw = [
{
handler: exts,
type: "mw",
path: "/"
},
...matched.filter((x) => req.method === "HEAD" || (x.method ? x.method === req.method : true))
];
if (matched[0] != null) {
mw.push({
type: "mw",
handler: (req2, res2, next2) => {
if (req2.method === "HEAD") {
res2.statusCode = 204;
return res2.end("");
}
next2();
},
path: "/"
});
}
mw.push({
handler: this.noMatchHandler,
type: "mw",
path: "/"
});
const handle = (mw2) => async (req2, res2, next2) => {
var _a;
const { path, handler, regex } = mw2;
let params;
try {
params = regex ? getURLParams(regex, pathname) : {};
} catch (e) {
console.error(e);
if (e instanceof URIError)
return res2.sendStatus(400);
else
throw e;
}
let prefix = path;
if (regex) {
for (const key of regex.keys) {
if (key === "wild") {
prefix = prefix.replace("*", params.wild);
} else {
prefix = prefix.replace(`:${key}`, params[key]);
}
}
}
req2.params = { ...req2.params, ...params };
if (mw2.type === "mw") {
req2.url = lead(req2.originalUrl.substring(prefix.length));
}
if (!req2.path)
req2.path = getPathname(req2.url);
if ((_a = this.settings) == null ? void 0 : _a.enableReqRoute)
req2.route = mw2;
await applyHandler(handler)(req2, res2, next2);
};
let idx = 0;
const loop = () => res.writableEnded || idx < mw.length && handle(mw[idx++])(req, res, next);
next = next || ((err) => err ? this.onError(err, req, res) : loop());
loop();
}
/**
* Creates HTTP server and dispatches middleware
* @param port server listening port
* @param Server callback after server starts listening
* @param host server listening host
*/
listen(port, cb, host) {
return createServer().on("request", this.attach).listen(port, host, cb);
}
}
export {
App,
View,
extendMiddleware,
getHostname,
getIP,
getIPs,
getProtocol,
getSubdomains,
getURLParams2 as getURLParams,
onErrorHandler,
renderTemplate
};
//# sourceMappingURL=index.js.map