Socket
Socket
Sign inDemoInstall

@tinyhttp/app

Package Overview
Dependencies
Maintainers
1
Versions
305
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tinyhttp/app - npm Package Compare versions

Comparing version 2.0.22 to 2.0.23

619

dist/index.js

@@ -1,364 +0,303 @@

import { STATUS_CODES, createServer } from 'http';
import path from 'path';
import { proxyaddr, all, compile } from '@tinyhttp/proxy-addr';
import { isIP } from 'net';
import { getRequestHeader, getQueryParams, getRangeFromHeader, getAccepts, getAcceptsCharsets, getAcceptsEncodings, getAcceptsLanguages, checkIfXMLHttpRequest, getFreshOrStale, getPathname, getURLParams } from '@tinyhttp/req';
export { getURLParams } 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 { STATUS_CODES, createServer } from "http";
import path from "path";
import { proxyaddr, all, compile } from "@tinyhttp/proxy-addr";
import { isIP } from "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";
const trustRemoteAddress = ({ socket }) => {
const val = socket.remoteAddress;
if (typeof val === 'function')
return val;
if (typeof val === 'boolean' && val === true)
return () => true;
if (typeof val === 'number')
return (_, i) => (val ? i < val : undefined);
if (typeof val === 'string')
return compile(val.split(',').map((x) => x.trim()));
return compile(val || []);
const val = socket.remoteAddress;
if (typeof val === "function")
return val;
if (typeof val === "boolean" && val === true)
return () => true;
if (typeof val === "number")
return (_, i) => val ? i < val : void 0;
if (typeof val === "string")
return compile(val.split(",").map((x) => x.trim()));
return compile(val || []);
};
const getRouteFromApp = ({ middleware }, h) => middleware.find(({ handler }) => typeof handler === 'function' && handler.name === h.name);
const getRouteFromApp = ({ middleware }, h) => middleware.find(({ handler }) => typeof handler === "function" && handler.name === h.name);
const getProtocol = (req) => {
const proto = req.connection.encrypted ? 'https' : 'http';
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 proto = req.connection.encrypted ? "https" : "http";
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;
// IPv6 literal support
const index = host.indexOf(':', host[0] === '[' ? host.indexOf(']') + 1 : 0);
return index !== -1 ? host.substring(0, index) : host;
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(/^.*:/, ''); // striping the redundant prefix addeded by OS to IPv4 address
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 hostname = getHostname(req);
if (!hostname)
return [];
const subdomains = isIP(hostname) ? [hostname] : hostname.split(".").reverse();
return subdomains.slice(subdomainOffset);
};
const onErrorHandler = (err, _req, res) => {
if (!process.env.TESTING && 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);
if (!process.env.TESTING && 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, (err, html) => {
if (err)
throw err;
res.send(html);
}, options);
return res;
app.render(
file,
data ? { ...data, ...res.locals } : res.locals,
(err, html) => {
if (err)
throw err;
res.send(html);
},
options
);
return res;
};
/**
* Extends Request and Response objects with custom properties and methods
*/
const extendMiddleware = (app) => (req, res, next) => {
const { settings } = app;
res.get = getResponseHeader(res);
req.get = getRequestHeader(req);
if (settings === null || settings === void 0 ? void 0 : settings.bindAppToReqRes) {
req.app = app;
res.app = app;
}
if (settings === null || settings === void 0 ? void 0 : settings.networkExtensions) {
req.protocol = getProtocol(req);
req.secure = req.protocol === 'https';
req.connection = Object.assign(req.socket, { encrypted: req.secure });
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 || Object.create(null);
Object.defineProperty(req, 'fresh', { get: getFreshOrStale.bind(null, req, res), configurable: true });
req.stale = !req.fresh;
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.connection = Object.assign(req.socket, { encrypted: req.secure });
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();
};
/**
* Add leading slash if not present (e.g. path -> /path, /path -> /path)
* @param x
*/
const lead = (x) => (x.charCodeAt(0) === 47 ? x : '/' + x);
const mount = (fn) => (fn instanceof App ? fn.attach : fn);
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);
}
try {
if (h[Symbol.toStringTag] === "AsyncFunction") {
await h(req, res, next);
} else
h(req, res, next);
} catch (e) {
next(e);
}
};
/**
* `App` class - the starting point of tinyhttp app.
*
* With the `App` you can:
* * use routing methods and `.use(...)`
* * set no match (404) and error (500) handlers
* * configure template engines
* * store data in locals
* * listen the http server on a specified port
*
* In case you use TypeScript, you can pass custom types to this class because it is also a generic class.
*
* Example:
*
* ```ts
* interface CoolReq extends Request {
* genericsAreDope: boolean
* }
*
* const app = App<any, CoolReq, Response>()
* ```
*/
class App extends Router {
constructor(options = {}) {
super();
this.middleware = [];
this.locals = {};
this.engines = {};
this.onError = (options === null || options === void 0 ? void 0 : options.onError) || onErrorHandler;
this.noMatchHandler = (options === null || options === void 0 ? void 0 : options.noMatchHandler) || this.onError.bind(null, { code: 404 });
this.settings = options.settings || { xPoweredBy: true, views: process.cwd() };
this.applyExtensions = options === null || options === void 0 ? void 0 : options.applyExtensions;
this.attach = (req, res) => setImmediate(this.handler.bind(this, req, res, undefined), req, res);
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(null, { code: 404 });
this.settings = options.settings || { xPoweredBy: true, views: process.cwd() };
this.applyExtensions = options == null ? void 0 : options.applyExtensions;
this.attach = (req, res) => setImmediate(this.handler.bind(this, req, res, void 0), req, res);
}
set(setting, value) {
this.settings[setting] = value;
return this;
}
enable(setting) {
this.settings[setting] = true;
return this;
}
disable(setting) {
this.settings[setting] = false;
return this;
}
render(file, data = {}, cb, options = {}) {
options.viewsFolder = options.viewsFolder || this.settings.views || `${process.cwd()}/views`;
options.ext = options.ext || file.slice(file.lastIndexOf(".") + 1) || "ejs";
options._locals = options._locals || {};
options.cache = options.cache || process.env.NODE_ENV === "production";
let locals = { ...data, ...this.locals };
if (options._locals)
locals = { ...locals, ...options._locals };
if (!file.endsWith(`.${options.ext}`))
file = `${file}.${options.ext}`;
const dest = options.viewsFolder ? path.join(options.viewsFolder, file) : file;
this.engines[options.ext](dest, locals, options.renderOptions, cb);
return this;
}
use(...args) {
var _a;
const base = args[0];
const fns = args.slice(1).flat();
if (typeof base === "function" || base instanceof App) {
fns.unshift(base);
} else if (Array.isArray(base)) {
fns.unshift(...base);
}
/**
* Set app setting
* @param setting setting name
* @param value setting value
*/
set(setting, value) {
this.settings[setting] = value;
return this;
const path2 = typeof base === "string" ? base : "/";
let regex;
for (const fn of fns) {
if (fn instanceof App) {
regex = parse(path2, true);
fn.mountpath = path2;
this.apps[path2] = fn;
fn.parent = this;
}
}
/**
* Enable app setting
* @param setting Setting name
*/
enable(setting) {
this.settings[setting] = true;
return this;
}
/**
* Disable app setting
* @param setting
*/
disable(setting) {
this.settings[setting] = false;
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(file, data = {}, cb, options = {}) {
options.viewsFolder = options.viewsFolder || this.settings.views || `${process.cwd()}/views`;
options.ext = options.ext || file.slice(file.lastIndexOf('.') + 1) || 'ejs';
options._locals = options._locals || {};
options.cache = options.cache || process.env.NODE_ENV === 'production';
let locals = { ...data, ...this.locals };
if (options._locals)
locals = { ...locals, ...options._locals };
if (!file.endsWith(`.${options.ext}`))
file = `${file}.${options.ext}`;
const dest = options.viewsFolder ? path.join(options.viewsFolder, file) : file;
this.engines[options.ext](dest, locals, options.renderOptions, cb);
return this;
}
use(...args) {
var _a;
const base = args[0];
const fns = args.slice(1).flat();
if (typeof base === 'function' || base instanceof App) {
fns.unshift(base);
const handlerPaths = [];
const handlerFunctions = [];
const handlerPathBase = path2 === "/" ? "" : lead(path2);
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 if (Array.isArray(base)) {
fns.unshift(...base);
}
const path = typeof base === 'string' ? base : '/';
let regex;
for (const fn of fns) {
if (fn instanceof App) {
regex = parse(path, true);
fn.mountpath = path;
this.apps[path] = fn;
fn.parent = this;
}
}
const handlerPaths = [];
const handlerFunctions = [];
const handlerPathBase = path === '/' ? '' : lead(path);
for (const fn of fns) {
if (fn instanceof App && ((_a = fn.middleware) === null || _a === void 0 ? 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;
} else {
handlerPaths.push("");
handlerFunctions.push(fn);
}
}
/**
* Register a template engine with extension
*/
engine(ext, fn) {
this.engines[ext] = fn;
return this;
pushMiddleware(this.middleware)({
path: path2,
regex,
type: "mw",
handler: mount(handlerFunctions[0]),
handlers: handlerFunctions.slice(1).map(mount),
fullPaths: handlerPaths
});
return this;
}
engine(ext, fn) {
this.engines[ext] = fn;
return this;
}
route(path2) {
const app = new App();
this.use(path2, 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);
});
}
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: "/"
});
}
route(path) {
const app = new App();
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) {
/* Set X-Powered-By header */
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: (req, res, next) => {
if (req.method === 'HEAD') {
res.statusCode = 204;
return res.end('');
}
next();
},
path: '/'
});
}
mw.push({
handler: this.noMatchHandler,
type: 'mw',
path: '/'
});
const handle = (mw) => async (req, res, next) => {
var _a;
const { path, handler, regex } = mw;
const params = regex ? getURLParams(regex, pathname) : {};
req.params = { ...req.params, ...params };
if (path.includes(':')) {
const first = Object.values(params)[0];
const url = req.url.slice(req.url.indexOf(first) + (first === null || first === void 0 ? void 0 : first.length));
req.url = lead(url);
}
else {
req.url = lead(req.url.substring(path.length));
}
if (!req.path)
req.path = getPathname(req.url);
if ((_a = this.settings) === null || _a === void 0 ? void 0 : _a.enableReqRoute)
req.route = getRouteFromApp(this, handler);
await applyHandler(handler)(req, res, next);
};
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 = '0.0.0.0') {
return createServer().on('request', this.attach).listen(port, host, cb);
}
mw.push({
handler: this.noMatchHandler,
type: "mw",
path: "/"
});
const handle = (mw2) => async (req2, res2, next2) => {
var _a;
const { path: path2, handler, regex } = mw2;
const params = regex ? getURLParams(regex, pathname) : {};
req2.params = { ...req2.params, ...params };
if (path2.includes(":")) {
const first = Object.values(params)[0];
const url = req2.url.slice(req2.url.indexOf(first) + (first == null ? void 0 : first.length));
req2.url = lead(url);
} else {
req2.url = lead(req2.url.substring(path2.length));
}
if (!req2.path)
req2.path = getPathname(req2.url);
if ((_a = this.settings) == null ? void 0 : _a.enableReqRoute)
req2.route = getRouteFromApp(this, handler);
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();
}
listen(port, cb, host = "0.0.0.0") {
return createServer().on("request", this.attach).listen(port, host, cb);
}
}
export { App, extendMiddleware, getHostname, getIP, getIPs, getProtocol, getRouteFromApp, getSubdomains, onErrorHandler, renderTemplate };
export {
App,
extendMiddleware,
getHostname,
getIP,
getIPs,
getProtocol,
getRouteFromApp,
getSubdomains,
getURLParams2 as getURLParams,
onErrorHandler,
renderTemplate
};
{
"name": "@tinyhttp/app",
"version": "2.0.22",
"version": "2.0.23",
"description": "0-legacy, tiny & fast web framework as a replacement of Express",

@@ -35,13 +35,15 @@ "type": "module",

"dependencies": {
"@tinyhttp/cookie": "2.0.4",
"@tinyhttp/proxy-addr": "2.0.4",
"@tinyhttp/req": "2.0.13",
"@tinyhttp/res": "2.0.17",
"@tinyhttp/router": "2.0.5",
"@tinyhttp/cookie": "2.0.5",
"@tinyhttp/proxy-addr": "2.0.5",
"@tinyhttp/req": "2.0.14",
"@tinyhttp/res": "2.0.18",
"@tinyhttp/router": "2.0.6",
"header-range-parser": "1.1.3",
"regexparam": "^2.0.0"
"regexparam": "^2.0.1"
},
"scripts": {
"build": "rollup -c"
"dev": "vite",
"build": "vite build",
"postbuild": "tsc --emitDeclarationOnly"
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc