@tinyhttp/app
Advanced tools
Comparing version 0.1.47 to 0.1.48
@@ -84,2 +84,21 @@ /// <reference types="node" /> | ||
// } | ||
/* export const getFreshOrStale = (req: Request, res: Response) => { | ||
const method = req.method | ||
const status = res.statusCode | ||
// GET or HEAD for weak freshness validation only | ||
if (method !== 'GET' && method !== 'HEAD') return false | ||
// 2xx or 304 as per rfc2616 14.26 | ||
if ((status >= 200 && status < 300) || 304 === status) { | ||
const resHeaders = { | ||
etag: res.get('ETag'), | ||
'last-modified': res.get('Last-Modified'), | ||
} | ||
return fresh(req.headers, resHeaders) | ||
} | ||
return false | ||
} */ | ||
// export const getRequestIs = (types: string | string[], ...args: string[]) => (req: Request) => { | ||
@@ -94,3 +113,21 @@ // let arr = types | ||
// } | ||
declare const getFreshOrStale: (req: Request, res: Response) => boolean; | ||
/* export const getFreshOrStale = (req: Request, res: Response) => { | ||
const method = req.method | ||
const status = res.statusCode | ||
// GET or HEAD for weak freshness validation only | ||
if (method !== 'GET' && method !== 'HEAD') return false | ||
// 2xx or 304 as per rfc2616 14.26 | ||
if ((status >= 200 && status < 300) || 304 === status) { | ||
const resHeaders = { | ||
etag: res.get('ETag'), | ||
'last-modified': res.get('Last-Modified'), | ||
} | ||
return fresh(req.headers, resHeaders) | ||
} | ||
return false | ||
} */ | ||
declare const getAccepts: (req: Request) => (...types: string[]) => string | false | string[]; | ||
@@ -117,7 +154,11 @@ type Connection = IncomingMessage["socket"] & { | ||
secret?: string | string[]; | ||
fresh: boolean; | ||
stale: boolean; | ||
/* fresh: boolean | ||
stale: boolean | ||
*/ | ||
body?: any; | ||
} | ||
declare const applyHandler: (h: Handler) => Handler; | ||
type AppSettings = Partial<{ | ||
networkExtensions: boolean; | ||
}>; | ||
declare class App extends Router { | ||
@@ -128,5 +169,7 @@ middleware: Middleware[]; | ||
onError: ErrorHandler; | ||
settings: AppSettings; | ||
constructor(options?: Partial<{ | ||
noMatchHandler: Handler; | ||
onError: ErrorHandler; | ||
settings: AppSettings; | ||
}>); | ||
@@ -136,3 +179,3 @@ handler(mw: Middleware[], req: Request, res: Response): Promise<void>; | ||
} | ||
declare const extendMiddleware: () => (req: Request, res: Response) => void; | ||
export { applyHandler, App, getQueryParams, URLParams, getURLParams, getRouteFromApp, getProtocol, getRequestHeader, setRequestHeader, getRangeFromHeader, checkIfXMLHttpRequest, getHostname, getIP, getFreshOrStale, getAccepts, Connection, Protocol, Request, json, send, status, setCookie, clearCookie, setHeader, setLocationHeader, getResponseHeader, setLinksHeader, sendStatus, Response, NextFunction, SyncHandler, AsyncHandler, Handler, ErrorHandler, Middleware, Router, extendMiddleware }; | ||
declare const extendMiddleware: ({ networkExtensions }: AppSettings) => (req: Request, res: Response) => void; | ||
export { applyHandler, AppSettings, App, getQueryParams, URLParams, getURLParams, getRouteFromApp, getProtocol, getRequestHeader, setRequestHeader, getRangeFromHeader, checkIfXMLHttpRequest, getHostname, getIP, getAccepts, Connection, Protocol, Request, json, send, status, setCookie, clearCookie, setHeader, setLocationHeader, getResponseHeader, setLinksHeader, sendStatus, Response, NextFunction, SyncHandler, AsyncHandler, Handler, ErrorHandler, Middleware, Router, extendMiddleware }; |
@@ -1,1 +0,534 @@ | ||
import{STATUS_CODES as e,METHODS as t,createServer as r}from"http";import n from"regexparam";import{parse as s}from"url";import o from"range-parser";import a from"proxy-addr";import i from"@foxify/fresh";import d from"es-accepts";import{sign as h}from"@tinyhttp/cookie-signature";import{lookup as l}from"es-mime-types";import{serialize as p}from"@tinyhttp/cookie";import{format as c,parse as u}from"es-content-type";import f from"@tinyhttp/etag";const m=e=>"function"==typeof e?e:!0===e?function(){return!0}:"number"==typeof e?(t,r)=>{if(e)return r<e}:("string"==typeof e&&(e=e.split(/ *, */)),a.compile(e||[])),y=(e="/")=>s(e,!0).query,g=(e="/",t="/")=>((e,t)=>{let r=0,n={},s=t.pattern.exec(e);for(;r<t.keys.length;)n[t.keys[r]]=(null==s?void 0:s[++r])||null;return n})(e,n(t)),w=(e,t)=>e.middleware.find(e=>e.handler.name===t.name),H=e=>{const t=e.connection.encrypted?"https":"http";if(!m(e.connection.remoteAddress))return t;const r=e.headers["X-Forwarded-Proto"]||t,n=r.indexOf(",");return-1!==n?r.substring(0,n).trim():r.trim()},C=e=>t=>{const r=t.toLowerCase();switch(r){case"referer":case"referrer":return e.headers.referrer||e.headers.referer;default:return e.headers[r]}},T=e=>(t,r)=>e.headers[t.toLowerCase()]=r,x=e=>(t,r)=>{const n=e.get("Range");if(n)return o(t,n,r)},E=e=>"XMLHttpRequest"===e.headers["X-Requested-With"],b=e=>{let t=e.get("X-Forwarded-Host");if(t&&m(e.connection.remoteAddress)||(t=e.get("Host")),!t)return;const r="["===t[0]?t.indexOf("]")+1:0,n=t.indexOf(":",r);return-1!==n?t.substring(0,n):t},k=e=>a(e,m),A=(e,t)=>{const r=e.method,n=t.statusCode;if("GET"!==r&&"HEAD"!==r)return!1;if(n>=200&&n<300||304===n){const r={etag:t.get("ETag"),"last-modified":t.get("Last-Modified")};return i(e.headers,r)}return!1},O=e=>(...t)=>new d(e).types(t),S=(t,r,n)=>{const s=n.statusCode=t.code||t.status||500;"string"==typeof t||Buffer.isBuffer(t)?n.end(t):n.end(t.message||e[s])},j=e=>({path:t,handler:r,method:n,handlers:s,type:o})=>{const a=(({path:e,handler:t,method:r})=>({method:r,handler:t||e,path:"string"==typeof e?e:"/"}))({path:t,handler:r,method:n,type:o}),i=s.map(e=>({handler:e}));for(const t of[a,...i])e.push({...t,type:o})};class L{get(e,t,...r){return j(this.middleware)({path:e,handler:t,handlers:r,method:"GET",type:"route"}),this}post(e,t,...r){return j(this.middleware)({path:e,handler:t,handlers:r,method:"POST",type:"route"}),this}put(e,t,...r){return j(this.middleware)({path:e,handler:t,handlers:r,method:"PUT",type:"route"}),this}patch(e,t,...r){return j(this.middleware)({path:e,handler:t,handlers:r,method:"PATCH",type:"route"}),this}head(e,t,...r){return j(this.middleware)({path:e,handler:t,handlers:r,method:"HEAD",type:"route"}),this}delete(e,t,...r){return j(this.middleware)({path:e,handler:t,handlers:r,method:"DELETE",type:"route"}),this}options(e,t,...r){return j(this.middleware)({path:e,handler:t,handlers:r,method:"OPTIONS",type:"route"}),this}all(e,r,...n){for(const s of t)j(this.middleware)({path:e,handler:r,method:s,handlers:n,type:"route"});return this}use(e,t,...r){return j(this.middleware)({path:e,handler:"string"==typeof e?t:e,handlers:r,type:"mw"}),this}}const B=(e,t)=>{const r=Buffer.isBuffer(e)?e:Buffer.from(e,t);return f(r,{weak:!0})};const D=(e,t)=>(e,...r)=>(t.setHeader("Content-Type","application/json"),"object"==typeof e&&null!=e?t.end(JSON.stringify(e,null,2),...r):"string"==typeof e&&t.end(e,...r),t),v=(e,t)=>r=>{let n=r;if("object"==typeof r&&null!==r)n=JSON.stringify(r,null,2);else if("string"==typeof r){const e=t.getHeader("Content-Type");"string"==typeof e&&t.setHeader("Content-Type",function(e,t){const r=u(e);return r.parameters.charset=t,c(r)}(e,"utf-8"))}let s;return!t.getHeader("etag")&&(s=B(n,"utf8"))&&t.setHeader("etag",s),204!==t.statusCode&&304!==t.statusCode||(t.removeHeader("Content-Type"),t.removeHeader("Content-Length"),t.removeHeader("Transfer-Encoding"),n=""),"HEAD"===e.method&&t.end(""),"object"==typeof r?null===r?t.end(""):Buffer.isBuffer(r)?t.getHeader("Content-Type")||t.setHeader("content-type","application/octet-stream"):D(0,t)(n,"utf8"):t.end(n,"utf8"),t},P=(e,t)=>e=>(t.statusCode=e,t),q=(e,t)=>(r,n,s)=>{const o=e.secret,a=s.signed;if(a&&!o)throw new Error('cookieParser("secret") required for signed cookies');let i="object"==typeof n?"j:"+JSON.stringify(n):String(n);return a&&(i="s:"+h(i,o)),s.maxAge&&(s.expires=new Date(Date.now()+s.maxAge),s.maxAge/=1e3),null==s.path&&(s.path="/"),t.setHeader("Set-Cookie",p(r,String(i),s)),t},M=(e,t)=>(r,n)=>{const s=Object.assign({},{expires:new Date(1),path:"/"},n);return q(e,t)(r,"",s)},R=/;\s*charset\s*=/,N=(e,t)=>(e,r)=>{if("string"==typeof e){let n=Array.isArray(r)?r.map(String):String(r);if("content-type"===e.toLowerCase()){if(Array.isArray(n))throw new TypeError("Content-Type cannot be set to an Array");if(!R.test(n)){const e=l(n.split(";")[0]);e&&(n+="; charset="+e.toLowerCase())}}t.setHeader(e,n)}else for(const r in e)t.setHeader(r,e[r]);return t},X=(e,t)=>r=>{let n=r;return"back"===r&&(n=e.get("Referrer")||"/"),t.setHeader("Location",encodeURIComponent(n)),t},F=(e,t)=>e=>t.getHeader(e),J=(e,t)=>e=>{let r=t.get("Link")||"";return r&&(r+=", "),t.set("Link",r+Object.keys(e).map(t=>"<"+e[t]+'>; rel="'+t+'"').join(", "))},G=(t,r)=>t=>{const n=e[t]||String(t);return r.statusCode=t,r.set("Content-Type","text/plain"),r.send(n)},I=()=>(e,t)=>{t.get=F(0,t);const r=H(e),n="https"===r;e.protocol=r,e.secure=n,e.connection=Object.assign(e.socket,{encrypted:n}),e.query=y(e.url),e.fresh=A(e,t),e.stale=!e.fresh,e.get=C(e),e.set=T(e),e.range=x(e),e.accepts=O(e),e.xhr=E(e),e.hostname=b(e),t.header=t.set=N(0,t),t.send=v(e,t),t.json=D(0,t),t.status=P(0,t),t.sendStatus=G(0,t),t.location=X(e,t),t.links=J(0,t),t.cookie=q(e,t),t.clearCookie=M(e,t)},U=e=>async(t,r,n)=>{"AsyncFunction"===e[Symbol.toStringTag]?await e(t,r,n):e(t,r,n)};class W extends L{constructor(e={}){super(),this.locals=Object.create(null),this.middleware=[],this.onError=(null==e?void 0:e.onError)||S,this.noMatchHandler=(null==e?void 0:e.noMatchHandler)||this.onError.bind(null,{code:404})}async handler(e,t,r){I()(t,r);const s={handler:this.noMatchHandler,type:"mw",path:"/"};e.includes(s)||e.push(s);let o=0;const a=e.length-1,i=e=>{e?this.onError(e,t,r):h()},d=e=>async(t,r,s)=>{const{path:o,method:a,handler:i,type:d}=e;if("route"===d){if(t.method===a){const e=t.url.indexOf("?"),a=t.url.slice(0,-1===e?t.url.length:e);n(o).pattern.test(a)?(t.params=g(t.url,o),t.route=w(this,i),r.statusCode=200,U(i)(t,r,s)):h()}}else t.url.startsWith(o)?U(i)(t,r,s):h()};1===e.length&&d(e[0])(t,r);const h=()=>{r.writableEnded||o<a&&d(e[o++])(t,r,i)};h()}listen(e,t,n="localhost",s){return r((e,t)=>{this.handler(this.middleware,e,t)}).listen(e,n,s,t)}}export{W as App,L as Router,U as applyHandler,E as checkIfXMLHttpRequest,M as clearCookie,I as extendMiddleware,O as getAccepts,A as getFreshOrStale,b as getHostname,k as getIP,H as getProtocol,y as getQueryParams,x as getRangeFromHeader,C as getRequestHeader,F as getResponseHeader,w as getRouteFromApp,g as getURLParams,D as json,v as send,G as sendStatus,q as setCookie,N as setHeader,J as setLinksHeader,X as setLocationHeader,T as setRequestHeader,P as status}; | ||
import { STATUS_CODES, METHODS, createServer } from 'http'; | ||
import rg from 'regexparam'; | ||
import { parse } from 'url'; | ||
import parseRange from 'range-parser'; | ||
import proxyAddr from 'proxy-addr'; | ||
import Accepts from 'es-accepts'; | ||
import { sign } from '@tinyhttp/cookie-signature'; | ||
import { lookup } from 'es-mime-types'; | ||
import { serialize } from '@tinyhttp/cookie'; | ||
import { format, parse as parse$1 } from 'es-content-type'; | ||
import etag from '@tinyhttp/etag'; | ||
const compileTrust = (val) => { | ||
if (typeof val === 'function') | ||
return val; | ||
if (val === true) { | ||
// Support plain true/false | ||
return function () { | ||
return true; | ||
}; | ||
} | ||
if (typeof val === 'number') { | ||
// Support trusting hop count | ||
return (_, i) => { | ||
if (val) { | ||
return i < val; | ||
} | ||
}; | ||
} | ||
if (typeof val === 'string') { | ||
// Support comma-separated values | ||
val = val.split(/ *, */); | ||
} | ||
return proxyAddr.compile(val || []); | ||
}; | ||
const rgExec = (path, result) => { | ||
let i = 0, out = {}; | ||
let matches = result.pattern.exec(path); | ||
while (i < result.keys.length) { | ||
out[result.keys[i]] = (matches === null || matches === void 0 ? void 0 : matches[++i]) || null; | ||
} | ||
return out; | ||
}; | ||
const getQueryParams = (url = '/') => { | ||
return parse(url, true).query; | ||
}; | ||
const getURLParams = (reqUrl = '/', url = '/') => { | ||
return rgExec(reqUrl, rg(url)); | ||
}; | ||
const getRouteFromApp = (app, handler) => { | ||
return app.middleware.find((h) => h.handler.name === handler.name); | ||
}; | ||
const getProtocol = (req) => { | ||
const proto = req.connection.encrypted ? 'https' : 'http'; | ||
if (!compileTrust(req.connection.remoteAddress)) { | ||
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 getRequestHeader = (req) => (header) => { | ||
const lc = header.toLowerCase(); | ||
switch (lc) { | ||
case 'referer': | ||
case 'referrer': | ||
return req.headers.referrer || req.headers.referer; | ||
default: | ||
return req.headers[lc]; | ||
} | ||
}; | ||
const setRequestHeader = (req) => (field, value) => { | ||
return (req.headers[field.toLowerCase()] = value); | ||
}; | ||
const getRangeFromHeader = (req) => (size, options) => { | ||
const range = req.get('Range'); | ||
if (!range) | ||
return; | ||
return parseRange(size, range, options); | ||
}; | ||
const checkIfXMLHttpRequest = (req) => { | ||
if (req.headers['X-Requested-With'] === 'XMLHttpRequest') { | ||
return true; | ||
} | ||
else { | ||
return false; | ||
} | ||
}; | ||
const getHostname = (req) => { | ||
let host = req.get('X-Forwarded-Host'); | ||
if (!host || !compileTrust(req.connection.remoteAddress)) { | ||
host = req.get('Host'); | ||
} | ||
if (!host) | ||
return; | ||
// IPv6 literal support | ||
const offset = host[0] === '[' ? host.indexOf(']') + 1 : 0; | ||
const index = host.indexOf(':', offset); | ||
return index !== -1 ? host.substring(0, index) : host; | ||
}; | ||
const getIP = (req) => { | ||
return proxyAddr(req, compileTrust); | ||
}; | ||
// export const getRequestIs = (types: string | string[], ...args: string[]) => (req: Request) => { | ||
// let arr = types | ||
// if (!Array.isArray(types)) { | ||
// arr = new Array(args.length) | ||
// for (let i = 0; i < arr.length; i++) { | ||
// arr[i] = args[i] | ||
// } | ||
// } | ||
// } | ||
/* export const getFreshOrStale = (req: Request, res: Response) => { | ||
const method = req.method | ||
const status = res.statusCode | ||
// GET or HEAD for weak freshness validation only | ||
if (method !== 'GET' && method !== 'HEAD') return false | ||
// 2xx or 304 as per rfc2616 14.26 | ||
if ((status >= 200 && status < 300) || 304 === status) { | ||
const resHeaders = { | ||
etag: res.get('ETag'), | ||
'last-modified': res.get('Last-Modified'), | ||
} | ||
return fresh(req.headers, resHeaders) | ||
} | ||
return false | ||
} */ | ||
const getAccepts = (req) => (...types) => { | ||
return new Accepts(req).types(types); | ||
}; | ||
const onErrorHandler = (err, _req, res) => { | ||
const code = (res.statusCode = err.code || err.status || 500); | ||
if (typeof err === 'string' || Buffer.isBuffer(err)) | ||
res.end(err); | ||
else | ||
res.end(err.message || STATUS_CODES[code]); | ||
}; | ||
const isAsync = (fn) => fn[Symbol.toStringTag] === 'AsyncFunction'; | ||
const createMiddlewareFromRoute = ({ path, handler, method, }) => ({ | ||
method, | ||
handler: handler || path, | ||
path: typeof path === 'string' ? path : '/', | ||
}); | ||
const pushMiddleware = (mw) => ({ path, handler, method, handlers, type, }) => { | ||
const m = createMiddlewareFromRoute({ path, handler, method, type }); | ||
const waresFromHandlers = handlers.map((handler) => ({ | ||
handler, | ||
})); | ||
for (const mdw of [m, ...waresFromHandlers]) { | ||
mw.push({ ...mdw, type }); | ||
} | ||
}; | ||
class Router { | ||
get(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
handlers, | ||
method: 'GET', | ||
type: 'route', | ||
}); | ||
return this; | ||
} | ||
post(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
handlers, | ||
method: 'POST', | ||
type: 'route', | ||
}); | ||
return this; | ||
} | ||
put(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
handlers, | ||
method: 'PUT', | ||
type: 'route', | ||
}); | ||
return this; | ||
} | ||
patch(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
handlers, | ||
method: 'PATCH', | ||
type: 'route', | ||
}); | ||
return this; | ||
} | ||
head(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
handlers, | ||
method: 'HEAD', | ||
type: 'route', | ||
}); | ||
return this; | ||
} | ||
delete(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
handlers, | ||
method: 'DELETE', | ||
type: 'route', | ||
}); | ||
return this; | ||
} | ||
options(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
handlers, | ||
method: 'OPTIONS', | ||
type: 'route', | ||
}); | ||
return this; | ||
} | ||
all(path, handler, ...handlers) { | ||
for (const method of METHODS) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler, | ||
method, | ||
handlers, | ||
type: 'route', | ||
}); | ||
} | ||
return this; | ||
} | ||
use(path, handler, ...handlers) { | ||
pushMiddleware(this.middleware)({ | ||
path, | ||
handler: typeof path === 'string' ? handler : path, | ||
handlers, | ||
type: 'mw', | ||
}); | ||
return this; | ||
} | ||
} | ||
const createETag = (body, encoding) => { | ||
const buf = !Buffer.isBuffer(body) ? Buffer.from(body, encoding) : body; | ||
return etag(buf, { weak: true }); | ||
}; | ||
function setCharset(type, charset) { | ||
const parsed = parse$1(type); | ||
parsed.parameters.charset = charset; | ||
return format(parsed); | ||
} | ||
const json = (_req, res) => (body, ...args) => { | ||
res.setHeader('Content-Type', 'application/json'); | ||
if (typeof body === 'object' && body != null) { | ||
res.end(JSON.stringify(body, null, 2), ...args); | ||
} | ||
else if (typeof body === 'string') { | ||
res.end(body, ...args); | ||
} | ||
return res; | ||
}; | ||
const send = (req, res) => (body) => { | ||
let bodyToSend = body; | ||
// in case of object - turn it to json | ||
if (typeof body === 'object' && body !== null) { | ||
bodyToSend = JSON.stringify(body, null, 2); | ||
} | ||
else { | ||
if (typeof body === 'string') { | ||
// reflect this in content-type | ||
const type = res.getHeader('Content-Type'); | ||
if (typeof type === 'string') { | ||
res.setHeader('Content-Type', setCharset(type, 'utf-8')); | ||
} | ||
} | ||
} | ||
// Set encoding | ||
const encoding = 'utf8'; | ||
// populate ETag | ||
let etag; | ||
if (!res.getHeader('etag') && (etag = createETag(bodyToSend, encoding))) { | ||
res.setHeader('etag', etag); | ||
} | ||
// strip irrelevant headers | ||
if (res.statusCode === 204 || res.statusCode === 304) { | ||
res.removeHeader('Content-Type'); | ||
res.removeHeader('Content-Length'); | ||
res.removeHeader('Transfer-Encoding'); | ||
bodyToSend = ''; | ||
} | ||
if (req.method === 'HEAD') { | ||
res.end(''); | ||
} | ||
if (typeof body === 'object') { | ||
if (body === null) { | ||
res.end(''); | ||
} | ||
else if (Buffer.isBuffer(body)) { | ||
if (!res.getHeader('Content-Type')) { | ||
res.setHeader('content-type', 'application/octet-stream'); | ||
} | ||
} | ||
else { | ||
json(req, res)(bodyToSend, encoding) | ||
; | ||
} | ||
} | ||
else { | ||
{ | ||
// respond with encoding | ||
res.end(bodyToSend, encoding); | ||
} | ||
} | ||
return res; | ||
}; | ||
const status = (_req, res) => (status) => { | ||
res.statusCode = status; | ||
return res; | ||
}; | ||
const setCookie = (req, res) => (name, value, options) => { | ||
const secret = req.secret; | ||
const signed = options.signed; | ||
if (signed && !secret) { | ||
throw new Error('cookieParser("secret") required for signed cookies'); | ||
} | ||
let val = typeof value === 'object' ? 'j:' + JSON.stringify(value) : String(value); | ||
if (signed) { | ||
val = 's:' + sign(val, secret); | ||
} | ||
if (options.maxAge) { | ||
options.expires = new Date(Date.now() + options.maxAge); | ||
options.maxAge /= 1000; | ||
} | ||
if (options.path == null) { | ||
options.path = '/'; | ||
} | ||
res.setHeader('Set-Cookie', serialize(name, String(val), options)); | ||
return res; | ||
}; | ||
const clearCookie = (req, res) => (name, options) => { | ||
const opts = Object.assign({}, { expires: new Date(1), path: '/' }, options); | ||
return setCookie(req, res)(name, '', opts); | ||
}; | ||
const charsetRegExp = /;\s*charset\s*=/; | ||
const setHeader = (_req, res) => (field, val) => { | ||
if (typeof field === 'string') { | ||
let value = Array.isArray(val) ? val.map(String) : String(val); | ||
// add charset to content-type | ||
if (field.toLowerCase() === 'content-type') { | ||
if (Array.isArray(value)) { | ||
throw new TypeError('Content-Type cannot be set to an Array'); | ||
} | ||
if (!charsetRegExp.test(value)) { | ||
const charset = lookup(value.split(';')[0]); | ||
if (charset) | ||
value += '; charset=' + charset.toLowerCase(); | ||
} | ||
} | ||
res.setHeader(field, value); | ||
} | ||
else { | ||
for (const key in field) { | ||
res.setHeader(key, field[key]); | ||
} | ||
} | ||
return res; | ||
}; | ||
const setLocationHeader = (req, res) => (url) => { | ||
let loc = url; | ||
// "back" is an alias for the referrer | ||
if (url === 'back') { | ||
loc = req.get('Referrer') || '/'; | ||
} | ||
// set location | ||
res.setHeader('Location', encodeURIComponent(loc)); | ||
return res; | ||
}; | ||
const getResponseHeader = (_req, res) => (field) => { | ||
return res.getHeader(field); | ||
}; | ||
const setLinksHeader = (_req, res) => (links) => { | ||
let link = res.get('Link') || ''; | ||
if (link) | ||
link += ', '; | ||
return res.set('Link', link + | ||
Object.keys(links) | ||
.map((rel) => '<' + links[rel] + '>; rel="' + rel + '"') | ||
.join(', ')); | ||
}; | ||
const sendStatus = (_req, res) => (statusCode) => { | ||
const body = STATUS_CODES[statusCode] || String(statusCode); | ||
res.statusCode = statusCode; | ||
res.set('Content-Type', 'text/plain'); | ||
return res.send(body); | ||
}; | ||
const extendMiddleware = ({ networkExtensions }) => (req, res) => { | ||
/// Define extensions | ||
res.get = getResponseHeader(req, res); | ||
/* | ||
Request extensions | ||
*/ | ||
if (networkExtensions) { | ||
const proto = getProtocol(req); | ||
const secure = proto === 'https'; | ||
req.protocol = proto; | ||
req.secure = secure; | ||
req.connection = Object.assign(req.socket, { | ||
encrypted: secure, | ||
}); | ||
} | ||
req.query = getQueryParams(req.url); | ||
/* req.fresh = getFreshOrStale(req, res) | ||
req.stale = !req.fresh */ | ||
req.get = getRequestHeader(req); | ||
req.set = setRequestHeader(req); | ||
req.range = getRangeFromHeader(req); | ||
req.accepts = getAccepts(req); | ||
req.xhr = checkIfXMLHttpRequest(req); | ||
req.hostname = getHostname(req); | ||
/* | ||
Response extensions | ||
*/ | ||
res.header = res.set = setHeader(req, res); | ||
res.send = send(req, res); | ||
res.json = json(req, res); | ||
res.status = status(req, res); | ||
res.sendStatus = sendStatus(req, res); | ||
res.location = setLocationHeader(req, res); | ||
res.links = setLinksHeader(req, res); | ||
res.cookie = setCookie(req, res); | ||
res.clearCookie = clearCookie(req, res); | ||
}; | ||
const applyHandler = (h) => async (req, res, next) => { | ||
if (isAsync(h)) { | ||
await h(req, res, next); | ||
} | ||
else { | ||
h(req, res, next); | ||
} | ||
}; | ||
class App extends Router { | ||
constructor(options = {}) { | ||
super(); | ||
this.locals = Object.create(null); | ||
this.middleware = []; | ||
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 || {}; | ||
} | ||
async handler(mw, req, res) { | ||
extendMiddleware(this.settings)(req, res); | ||
const noMatchMW = { | ||
handler: this.noMatchHandler, | ||
type: 'mw', | ||
path: '/', | ||
}; | ||
if (!mw.includes(noMatchMW)) | ||
mw.push(noMatchMW); | ||
let idx = 0; | ||
const len = mw.length - 1; | ||
// skip handling if only one middleware | ||
// TODO: Implement next(err) function properly | ||
const next = (err) => { | ||
if (err) { | ||
this.onError(err, req, res); | ||
} | ||
else { | ||
loop(); | ||
} | ||
}; | ||
const handle = (mw) => async (req, res, next) => { | ||
const { path, method, handler, type } = mw; | ||
if (type === 'route') { | ||
if (req.method === method) { | ||
const queryParamStart = req.url.indexOf('?'); | ||
const reqUrlWithoutParams = req.url.slice(0, queryParamStart === -1 ? req.url.length : queryParamStart); | ||
if (rg(path).pattern.test(reqUrlWithoutParams)) { | ||
req.params = getURLParams(req.url, path); | ||
req.route = getRouteFromApp(this, handler); | ||
// route found, send Success 200 | ||
res.statusCode = 200; | ||
applyHandler(handler)(req, res, next); | ||
} | ||
else { | ||
loop(); | ||
} | ||
} | ||
} | ||
else { | ||
if (req.url.startsWith(path)) { | ||
applyHandler(handler)(req, res, next); | ||
} | ||
else { | ||
loop(); | ||
} | ||
} | ||
}; | ||
if (mw.length === 1) | ||
handle(mw[0])(req, res); | ||
const loop = () => { | ||
if (!res.writableEnded) { | ||
if (idx < len) { | ||
handle(mw[idx++])(req, res, next); | ||
} | ||
} | ||
}; | ||
loop(); | ||
} | ||
listen(port, cb, host = 'localhost', backlog) { | ||
const server = createServer((req, res) => { | ||
this.handler(this.middleware, req, res); | ||
}); | ||
return server.listen(port, host, backlog, cb); | ||
} | ||
} | ||
export { App, Router, applyHandler, checkIfXMLHttpRequest, clearCookie, extendMiddleware, getAccepts, getHostname, getIP, getProtocol, getQueryParams, getRangeFromHeader, getRequestHeader, getResponseHeader, getRouteFromApp, getURLParams, json, send, sendStatus, setCookie, setHeader, setLinksHeader, setLocationHeader, setRequestHeader, status }; |
{ | ||
"name": "@tinyhttp/app", | ||
"version": "0.1.47", | ||
"version": "0.1.48", | ||
"description": "tinyhttp core", | ||
@@ -39,3 +39,2 @@ "type": "module", | ||
"dependencies": { | ||
"@foxify/fresh": "^1.1.0", | ||
"@tinyhttp/cookie": "0.0.13", | ||
@@ -42,0 +41,0 @@ "@tinyhttp/cookie-signature": "0.0.10", |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
45997
9
1235
1
3
- Removed@foxify/fresh@^1.1.0
- Removed@foxify/fresh@1.1.0(transitive)