Setup eslint and enforce rules
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import {AppClass} from "../lifecycle/AppClass";
|
||||
import {Request} from "./lifecycle/Request";
|
||||
import {AppClass} from '../lifecycle/AppClass'
|
||||
import {Request} from './lifecycle/Request'
|
||||
import {Container} from '../di'
|
||||
|
||||
/**
|
||||
* Base class for controllers that define methods that
|
||||
@@ -7,10 +8,12 @@ import {Request} from "./lifecycle/Request";
|
||||
*/
|
||||
export class Controller extends AppClass {
|
||||
constructor(
|
||||
protected readonly request: Request
|
||||
) { super() }
|
||||
protected readonly request: Request,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
protected container() {
|
||||
protected container(): Container {
|
||||
return this.request
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ErrorWithContext, HTTPStatus, HTTPMessage} from "../util"
|
||||
import {ErrorWithContext, HTTPStatus, HTTPMessage} from '../util'
|
||||
|
||||
/**
|
||||
* An error class that has an associated HTTP status.
|
||||
@@ -9,7 +9,7 @@ import {ErrorWithContext, HTTPStatus, HTTPMessage} from "../util"
|
||||
export class HTTPError extends ErrorWithContext {
|
||||
constructor(
|
||||
public readonly status: HTTPStatus = 500,
|
||||
public readonly message: string = ''
|
||||
public readonly message: string = '',
|
||||
) {
|
||||
super('HTTP ERROR')
|
||||
this.message = message || HTTPMessage[status]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Request} from "../lifecycle/Request";
|
||||
import {uninfer, infer, uuid_v4} from "../../util";
|
||||
import {Request} from '../lifecycle/Request'
|
||||
import {uninfer, infer, uuid4} from '../../util'
|
||||
|
||||
/**
|
||||
* Base type representing a parsed cookie.
|
||||
@@ -61,7 +61,7 @@ export class HTTPCookieJar {
|
||||
* @param value
|
||||
* @param options
|
||||
*/
|
||||
set(name: string, value: any, options?: HTTPCookieOptions) {
|
||||
set(name: string, value: unknown, options?: HTTPCookieOptions): this {
|
||||
this.parsed[name] = {
|
||||
key: name,
|
||||
value,
|
||||
@@ -69,14 +69,16 @@ export class HTTPCookieJar {
|
||||
exists: false,
|
||||
options,
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a cookie exists with the given name.
|
||||
* @param name
|
||||
*/
|
||||
has(name: string) {
|
||||
return !!this.parsed[name]
|
||||
has(name: string): boolean {
|
||||
return Boolean(this.parsed[name])
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,17 +90,21 @@ export class HTTPCookieJar {
|
||||
* @param name
|
||||
* @param options
|
||||
*/
|
||||
clear(name: string, options?: HTTPCookieOptions) {
|
||||
if ( !options ) options = {}
|
||||
clear(name: string, options?: HTTPCookieOptions): this {
|
||||
if ( !options ) {
|
||||
options = {}
|
||||
}
|
||||
options.expires = new Date(0)
|
||||
|
||||
this.parsed[name] = {
|
||||
key: name,
|
||||
value: undefined,
|
||||
originalValue: uuid_v4(),
|
||||
originalValue: uuid4(),
|
||||
exists: false,
|
||||
options,
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,10 +114,14 @@ export class HTTPCookieJar {
|
||||
const headers: string[] = []
|
||||
|
||||
for ( const key in this.parsed ) {
|
||||
if ( !this.parsed.hasOwnProperty(key) ) continue
|
||||
if ( !Object.prototype.hasOwnProperty.call(this.parsed, key) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
const cookie = this.parsed[key]
|
||||
if ( cookie.exists ) continue
|
||||
if ( cookie.exists ) {
|
||||
continue
|
||||
}
|
||||
|
||||
const parts = []
|
||||
parts.push(`${key}=${encodeURIComponent(cookie.originalValue)}`)
|
||||
@@ -144,7 +154,7 @@ export class HTTPCookieJar {
|
||||
const map = {
|
||||
strict: 'Strict',
|
||||
lax: 'Lax',
|
||||
'none-secure': 'None; Secure'
|
||||
'none-secure': 'None; Secure',
|
||||
}
|
||||
|
||||
parts.push(map[cookie.options.sameSite])
|
||||
@@ -163,7 +173,9 @@ export class HTTPCookieJar {
|
||||
const parts = cookie.split('=')
|
||||
|
||||
const key = parts.shift()?.trim()
|
||||
if ( !key ) return;
|
||||
if ( !key ) {
|
||||
return
|
||||
}
|
||||
|
||||
const value = decodeURI(parts.join('='))
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {Inject, Instantiable, Singleton} from "../../di"
|
||||
import {Collection, HTTPStatus} from "../../util"
|
||||
import {HTTPKernelModule} from "./HTTPKernelModule";
|
||||
import {Logging} from "../../service/Logging";
|
||||
import {AppClass} from "../../lifecycle/AppClass";
|
||||
import {Request} from "../lifecycle/Request";
|
||||
import {error} from "../response/ErrorResponseFactory";
|
||||
import {Inject, Instantiable, Singleton} from '../../di'
|
||||
import {Collection, HTTPStatus} from '../../util'
|
||||
import {HTTPKernelModule} from './HTTPKernelModule'
|
||||
import {Logging} from '../../service/Logging'
|
||||
import {AppClass} from '../../lifecycle/AppClass'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
import {error} from '../response/ErrorResponseFactory'
|
||||
|
||||
/**
|
||||
* Interface for fluently registering kernel modules into the kernel.
|
||||
@@ -105,7 +105,8 @@ export class HTTPKernel extends AppClass {
|
||||
}
|
||||
} catch (e: any) {
|
||||
this.logging.error(e)
|
||||
await error(e).status(HTTPStatus.INTERNAL_SERVER_ERROR).write(request)
|
||||
await error(e).status(HTTPStatus.INTERNAL_SERVER_ERROR)
|
||||
.write(request)
|
||||
}
|
||||
|
||||
this.logging.verbose('Finished kernel lifecycle')
|
||||
@@ -127,16 +128,16 @@ export class HTTPKernel extends AppClass {
|
||||
return this
|
||||
}
|
||||
|
||||
let found_index = this.preflight.find((mod: HTTPKernelModule) => mod instanceof other)
|
||||
if ( typeof found_index !== 'undefined' ) {
|
||||
this.preflight = this.preflight.put(found_index, this.app().make(module))
|
||||
let foundIdx = this.preflight.find((mod: HTTPKernelModule) => mod instanceof other)
|
||||
if ( typeof foundIdx !== 'undefined' ) {
|
||||
this.preflight = this.preflight.put(foundIdx, this.app().make(module))
|
||||
return this
|
||||
} else {
|
||||
found_index = this.postflight.find((mod: HTTPKernelModule) => mod instanceof other)
|
||||
foundIdx = this.postflight.find((mod: HTTPKernelModule) => mod instanceof other)
|
||||
}
|
||||
|
||||
if ( typeof found_index !== 'undefined' ) {
|
||||
this.postflight = this.postflight.put(found_index, this.app().make(module))
|
||||
if ( typeof foundIdx !== 'undefined' ) {
|
||||
this.postflight = this.postflight.put(foundIdx, this.app().make(module))
|
||||
} else {
|
||||
throw new KernelModuleNotFoundError(other.name)
|
||||
}
|
||||
@@ -149,16 +150,16 @@ export class HTTPKernel extends AppClass {
|
||||
return this
|
||||
}
|
||||
|
||||
let found_index = this.preflight.find((mod: HTTPKernelModule) => mod instanceof other)
|
||||
if ( typeof found_index !== 'undefined' ) {
|
||||
this.preflight = this.preflight.put(found_index + 1, this.app().make(module))
|
||||
let foundIdx = this.preflight.find((mod: HTTPKernelModule) => mod instanceof other)
|
||||
if ( typeof foundIdx !== 'undefined' ) {
|
||||
this.preflight = this.preflight.put(foundIdx + 1, this.app().make(module))
|
||||
return this
|
||||
} else {
|
||||
found_index = this.postflight.find((mod: HTTPKernelModule) => mod instanceof other)
|
||||
foundIdx = this.postflight.find((mod: HTTPKernelModule) => mod instanceof other)
|
||||
}
|
||||
|
||||
if ( typeof found_index !== 'undefined' ) {
|
||||
this.postflight = this.postflight.put(found_index + 1, this.app().make(module))
|
||||
if ( typeof foundIdx !== 'undefined' ) {
|
||||
this.postflight = this.postflight.put(foundIdx + 1, this.app().make(module))
|
||||
} else {
|
||||
throw new KernelModuleNotFoundError(other.name)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {Injectable} from "../../di";
|
||||
import {AppClass} from "../../lifecycle/AppClass";
|
||||
import {HTTPKernel} from "./HTTPKernel";
|
||||
import {Request} from "../lifecycle/Request";
|
||||
import {Injectable} from '../../di'
|
||||
import {AppClass} from '../../lifecycle/AppClass'
|
||||
import {HTTPKernel} from './HTTPKernel'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
|
||||
/**
|
||||
* Base class for modules that define logic that is applied to requests
|
||||
@@ -23,7 +23,7 @@ export class HTTPKernelModule extends AppClass {
|
||||
* @param {Request} request
|
||||
* @return Promise<boolean>
|
||||
*/
|
||||
public async match(request: Request): Promise<boolean> {
|
||||
public async match(request: Request): Promise<boolean> { // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export class HTTPKernelModule extends AppClass {
|
||||
* Register this module with the given HTTP kernel.
|
||||
* @param {HTTPKernel} kernel
|
||||
*/
|
||||
public static register(kernel: HTTPKernel) {
|
||||
public static register(kernel: HTTPKernel): void {
|
||||
kernel.register(this).before()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {HTTPKernelModule} from "../HTTPKernelModule";
|
||||
import {ResponseObject} from "../../routing/Route";
|
||||
import {Request} from "../../lifecycle/Request";
|
||||
import {plaintext} from "../../response/StringResponseFactory";
|
||||
import {ResponseFactory} from "../../response/ResponseFactory";
|
||||
import {json} from "../../response/JSONResponseFactory";
|
||||
import {HTTPKernelModule} from '../HTTPKernelModule'
|
||||
import {ResponseObject} from '../../routing/Route'
|
||||
import {Request} from '../../lifecycle/Request'
|
||||
import {plaintext} from '../../response/StringResponseFactory'
|
||||
import {ResponseFactory} from '../../response/ResponseFactory'
|
||||
import {json} from '../../response/JSONResponseFactory'
|
||||
|
||||
/**
|
||||
* Base class for HTTP kernel modules that apply some response from a route handler to the request.
|
||||
@@ -15,7 +15,7 @@ export abstract class AbstractResolvedRouteHandlerHTTPModule extends HTTPKernelM
|
||||
* @param request
|
||||
* @protected
|
||||
*/
|
||||
protected async applyResponseObject(object: ResponseObject, request: Request) {
|
||||
protected async applyResponseObject(object: ResponseObject, request: Request): Promise<void> {
|
||||
if ( (typeof object === 'string') || (typeof object === 'number') ) {
|
||||
object = plaintext(String(object))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {HTTPKernel} from "../HTTPKernel";
|
||||
import {Request} from "../../lifecycle/Request";
|
||||
import {ActivatedRoute} from "../../routing/ActivatedRoute";
|
||||
import {ResponseObject} from "../../routing/Route";
|
||||
import {http} from "../../response/HTTPErrorResponseFactory";
|
||||
import {HTTPStatus} from "../../../util";
|
||||
import {AbstractResolvedRouteHandlerHTTPModule} from "./AbstractResolvedRouteHandlerHTTPModule";
|
||||
import {HTTPKernel} from '../HTTPKernel'
|
||||
import {Request} from '../../lifecycle/Request'
|
||||
import {ActivatedRoute} from '../../routing/ActivatedRoute'
|
||||
import {ResponseObject} from '../../routing/Route'
|
||||
import {http} from '../../response/HTTPErrorResponseFactory'
|
||||
import {HTTPStatus} from '../../../util'
|
||||
import {AbstractResolvedRouteHandlerHTTPModule} from './AbstractResolvedRouteHandlerHTTPModule'
|
||||
|
||||
/**
|
||||
* HTTP kernel module that runs the handler for the request's route.
|
||||
@@ -12,14 +12,14 @@ import {AbstractResolvedRouteHandlerHTTPModule} from "./AbstractResolvedRouteHan
|
||||
* In most cases, this is the controller method defined by the route.
|
||||
*/
|
||||
export class ExecuteResolvedRouteHandlerHTTPModule extends AbstractResolvedRouteHandlerHTTPModule {
|
||||
public static register(kernel: HTTPKernel) {
|
||||
public static register(kernel: HTTPKernel): void {
|
||||
kernel.register(this).core()
|
||||
}
|
||||
|
||||
public async apply(request: Request) {
|
||||
public async apply(request: Request): Promise<Request> {
|
||||
if ( request.hasInstance(ActivatedRoute) ) {
|
||||
const route = <ActivatedRoute> request.make(ActivatedRoute)
|
||||
let object: ResponseObject = await route.handler(request)
|
||||
const object: ResponseObject = await route.handler(request)
|
||||
|
||||
await this.applyResponseObject(object, request)
|
||||
} else {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {HTTPKernel} from "../HTTPKernel";
|
||||
import {Request} from "../../lifecycle/Request";
|
||||
import {ActivatedRoute} from "../../routing/ActivatedRoute";
|
||||
import {ResponseObject} from "../../routing/Route";
|
||||
import {AbstractResolvedRouteHandlerHTTPModule} from "./AbstractResolvedRouteHandlerHTTPModule";
|
||||
import {PersistSessionHTTPModule} from "./PersistSessionHTTPModule";
|
||||
import {HTTPKernel} from '../HTTPKernel'
|
||||
import {Request} from '../../lifecycle/Request'
|
||||
import {ActivatedRoute} from '../../routing/ActivatedRoute'
|
||||
import {ResponseObject} from '../../routing/Route'
|
||||
import {AbstractResolvedRouteHandlerHTTPModule} from './AbstractResolvedRouteHandlerHTTPModule'
|
||||
import {PersistSessionHTTPModule} from './PersistSessionHTTPModule'
|
||||
|
||||
/**
|
||||
* HTTP kernel module that executes the postflight handlers for the route.
|
||||
@@ -11,18 +11,18 @@ import {PersistSessionHTTPModule} from "./PersistSessionHTTPModule";
|
||||
* Usually, this is post middleware.
|
||||
*/
|
||||
export class ExecuteResolvedRoutePostflightHTTPModule extends AbstractResolvedRouteHandlerHTTPModule {
|
||||
public static register(kernel: HTTPKernel) {
|
||||
public static register(kernel: HTTPKernel): void {
|
||||
kernel.register(this).before(PersistSessionHTTPModule)
|
||||
}
|
||||
|
||||
public async apply(request: Request) {
|
||||
public async apply(request: Request): Promise<Request> {
|
||||
if ( request.hasInstance(ActivatedRoute) ) {
|
||||
const route = <ActivatedRoute> request.make(ActivatedRoute)
|
||||
const postflight = route.postflight
|
||||
|
||||
for ( const handler of postflight ) {
|
||||
const result: ResponseObject = await handler(request)
|
||||
if ( typeof result !== "undefined" ) {
|
||||
if ( typeof result !== 'undefined' ) {
|
||||
await this.applyResponseObject(result, request)
|
||||
request.response.blockingWriteback(true)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {HTTPKernel} from "../HTTPKernel";
|
||||
import {MountActivatedRouteHTTPModule} from "./MountActivatedRouteHTTPModule";
|
||||
import {Request} from "../../lifecycle/Request";
|
||||
import {ActivatedRoute} from "../../routing/ActivatedRoute";
|
||||
import {ResponseObject} from "../../routing/Route";
|
||||
import {AbstractResolvedRouteHandlerHTTPModule} from "./AbstractResolvedRouteHandlerHTTPModule";
|
||||
import {HTTPKernel} from '../HTTPKernel'
|
||||
import {MountActivatedRouteHTTPModule} from './MountActivatedRouteHTTPModule'
|
||||
import {Request} from '../../lifecycle/Request'
|
||||
import {ActivatedRoute} from '../../routing/ActivatedRoute'
|
||||
import {ResponseObject} from '../../routing/Route'
|
||||
import {AbstractResolvedRouteHandlerHTTPModule} from './AbstractResolvedRouteHandlerHTTPModule'
|
||||
|
||||
/**
|
||||
* HTTP Kernel module that executes the preflight handlers for the route.
|
||||
@@ -11,18 +11,18 @@ import {AbstractResolvedRouteHandlerHTTPModule} from "./AbstractResolvedRouteHan
|
||||
* Usually, this is the pre middleware.
|
||||
*/
|
||||
export class ExecuteResolvedRoutePreflightHTTPModule extends AbstractResolvedRouteHandlerHTTPModule {
|
||||
public static register(kernel: HTTPKernel) {
|
||||
public static register(kernel: HTTPKernel): void {
|
||||
kernel.register(this).after(MountActivatedRouteHTTPModule)
|
||||
}
|
||||
|
||||
public async apply(request: Request) {
|
||||
public async apply(request: Request): Promise<Request> {
|
||||
if ( request.hasInstance(ActivatedRoute) ) {
|
||||
const route = <ActivatedRoute> request.make(ActivatedRoute)
|
||||
const preflight = route.preflight
|
||||
|
||||
for ( const handler of preflight ) {
|
||||
const result: ResponseObject = await handler(request)
|
||||
if ( typeof result !== "undefined" ) {
|
||||
if ( typeof result !== 'undefined' ) {
|
||||
await this.applyResponseObject(result, request)
|
||||
request.response.blockingWriteback(true)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {HTTPKernelModule} from "../HTTPKernelModule";
|
||||
import {Injectable} from "../../../di"
|
||||
import {ErrorWithContext} from "../../../util"
|
||||
import {HTTPKernel} from "../HTTPKernel";
|
||||
import {Request} from "../../lifecycle/Request";
|
||||
import {SetSessionCookieHTTPModule} from "./SetSessionCookieHTTPModule";
|
||||
import {SessionFactory} from "../../session/SessionFactory";
|
||||
import {Session} from "../../session/Session";
|
||||
import {HTTPKernelModule} from '../HTTPKernelModule'
|
||||
import {Injectable} from '../../../di'
|
||||
import {ErrorWithContext} from '../../../util'
|
||||
import {HTTPKernel} from '../HTTPKernel'
|
||||
import {Request} from '../../lifecycle/Request'
|
||||
import {SetSessionCookieHTTPModule} from './SetSessionCookieHTTPModule'
|
||||
import {SessionFactory} from '../../session/SessionFactory'
|
||||
import {Session} from '../../session/Session'
|
||||
|
||||
/**
|
||||
* HTTP kernel middleware that creates the session using the configured driver
|
||||
@@ -15,11 +15,11 @@ import {Session} from "../../session/Session";
|
||||
export class InjectSessionHTTPModule extends HTTPKernelModule {
|
||||
public readonly executeWithBlockingWriteback = true
|
||||
|
||||
public static register(kernel: HTTPKernel) {
|
||||
public static register(kernel: HTTPKernel): void {
|
||||
kernel.register(this).after(SetSessionCookieHTTPModule)
|
||||
}
|
||||
|
||||
public async apply(request: Request) {
|
||||
public async apply(request: Request): Promise<Request> {
|
||||
request.registerFactory(new SessionFactory())
|
||||
|
||||
const session = <Session> request.make(Session)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {Injectable, Inject} from "../../../di"
|
||||
import {HTTPKernelModule} from "../HTTPKernelModule";
|
||||
import {HTTPKernel} from "../HTTPKernel";
|
||||
import {Request} from "../../lifecycle/Request";
|
||||
import {Routing} from "../../../service/Routing";
|
||||
import {ActivatedRoute} from "../../routing/ActivatedRoute";
|
||||
import {Logging} from "../../../service/Logging";
|
||||
import {Injectable, Inject} from '../../../di'
|
||||
import {HTTPKernelModule} from '../HTTPKernelModule'
|
||||
import {HTTPKernel} from '../HTTPKernel'
|
||||
import {Request} from '../../lifecycle/Request'
|
||||
import {Routing} from '../../../service/Routing'
|
||||
import {ActivatedRoute} from '../../routing/ActivatedRoute'
|
||||
import {Logging} from '../../../service/Logging'
|
||||
|
||||
/**
|
||||
* HTTP kernel middleware that tries to find a registered route matching the request's
|
||||
@@ -20,7 +20,7 @@ export class MountActivatedRouteHTTPModule extends HTTPKernelModule {
|
||||
@Inject()
|
||||
protected readonly logging!: Logging
|
||||
|
||||
public static register(kernel: HTTPKernel) {
|
||||
public static register(kernel: HTTPKernel): void {
|
||||
kernel.register(this).before()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import {HTTPKernelModule} from "../HTTPKernelModule"
|
||||
import {HTTPKernel} from "../HTTPKernel"
|
||||
import * as Busboy from "busboy"
|
||||
import {Request} from "../../lifecycle/Request"
|
||||
import {infer, uuid_v4} from "../../../util"
|
||||
import {Files} from "../../../service/Files"
|
||||
import {Config} from "../../../service/Config"
|
||||
import {Logging} from "../../../service/Logging"
|
||||
import {Injectable, Inject, Container} from "../../../di"
|
||||
import {HTTPKernelModule} from '../HTTPKernelModule'
|
||||
import {HTTPKernel} from '../HTTPKernel'
|
||||
import * as Busboy from 'busboy'
|
||||
import {Request} from '../../lifecycle/Request'
|
||||
import {infer, uuid4} from '../../../util'
|
||||
import {Files} from '../../../service/Files'
|
||||
import {Config} from '../../../service/Config'
|
||||
import {Logging} from '../../../service/Logging'
|
||||
import {Injectable, Inject, Container} from '../../../di'
|
||||
|
||||
@Injectable()
|
||||
export class ParseIncomingBodyHTTPModule extends HTTPKernelModule {
|
||||
static register(kernel: HTTPKernel) {
|
||||
static register(kernel: HTTPKernel): void {
|
||||
const files = <Files> Container.getContainer().make(Files)
|
||||
const logging = <Logging> Container.getContainer().make(Logging)
|
||||
if ( !files.hasFilesystem() ) {
|
||||
@@ -32,8 +32,11 @@ export class ParseIncomingBodyHTTPModule extends HTTPKernelModule {
|
||||
public async apply(request: Request): Promise<Request> {
|
||||
const contentType = request.getHeader('content-type')
|
||||
const contentTypes = (Array.isArray(contentType) ? contentType : [contentType])
|
||||
.filter(Boolean).map(x => x!.toLowerCase().split(';')[0])
|
||||
if ( !contentType ) return request
|
||||
.filter(Boolean).map(x => String(x).toLowerCase()
|
||||
.split(';')[0])
|
||||
if ( !contentType ) {
|
||||
return request
|
||||
}
|
||||
|
||||
if (
|
||||
contentTypes.includes('multipart/form-data')
|
||||
@@ -65,7 +68,10 @@ export class ParseIncomingBodyHTTPModule extends HTTPKernelModule {
|
||||
try {
|
||||
const body = JSON.parse(data)
|
||||
for ( const key in body ) {
|
||||
if ( !body.hasOwnProperty(key) ) continue
|
||||
if ( !Object.prototype.hasOwnProperty.call(body, key) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
request.parsedInput[key] = body[key]
|
||||
}
|
||||
res()
|
||||
@@ -94,8 +100,10 @@ export class ParseIncomingBodyHTTPModule extends HTTPKernelModule {
|
||||
request.parsedInput[field] = infer(val)
|
||||
})
|
||||
|
||||
busboy.on('file', async (field, file, filename, encoding, mimetype) => {
|
||||
if ( !this.files.hasFilesystem() ) return
|
||||
busboy.on('file', async (field, file, filename, encoding, mimetype) => { // eslint-disable-line max-params
|
||||
if ( !this.files.hasFilesystem() ) {
|
||||
return
|
||||
}
|
||||
|
||||
if ( !config?.enable ) {
|
||||
this.logging.warn(`Skipping uploaded file '${filename}' because uploading is disabled. Set the server.uploads.enable config to allow uploads.`)
|
||||
@@ -122,7 +130,7 @@ export class ParseIncomingBodyHTTPModule extends HTTPKernelModule {
|
||||
}
|
||||
|
||||
const fs = this.files.getFilesystem()
|
||||
const storePath = `${config.filesystemPrefix ? config.filesystemPrefix : ''}${(config.filesystemPrefix && !config.filesystemPrefix.endsWith('/')) ? '/' : ''}${field}-${uuid_v4()}`
|
||||
const storePath = `${config.filesystemPrefix ? config.filesystemPrefix : ''}${(config.filesystemPrefix && !config.filesystemPrefix.endsWith('/')) ? '/' : ''}${field}-${uuid4()}`
|
||||
this.logging.verbose(`Uploading file in field ${field} to ${fs.getPrefix()}${storePath}`)
|
||||
file.pipe(await fs.putStoreFileAsStream({ storePath })) // FIXME might need to revisit this to ensure we don't res() before pipe finishes
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {HTTPKernelModule} from "../HTTPKernelModule";
|
||||
import {Injectable} from "../../../di"
|
||||
import {HTTPKernel} from "../HTTPKernel";
|
||||
import {Request} from "../../lifecycle/Request";
|
||||
import {Session} from "../../session/Session";
|
||||
import {HTTPKernelModule} from '../HTTPKernelModule'
|
||||
import {Injectable} from '../../../di'
|
||||
import {HTTPKernel} from '../HTTPKernel'
|
||||
import {Request} from '../../lifecycle/Request'
|
||||
import {Session} from '../../session/Session'
|
||||
|
||||
/**
|
||||
* HTTP kernel module that runs after the main logic in the request to persist
|
||||
@@ -12,7 +12,7 @@ import {Session} from "../../session/Session";
|
||||
export class PersistSessionHTTPModule extends HTTPKernelModule {
|
||||
public readonly executeWithBlockingWriteback = true
|
||||
|
||||
public static register(kernel: HTTPKernel) {
|
||||
public static register(kernel: HTTPKernel): void {
|
||||
kernel.register(this).last()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {HTTPKernelModule} from "../HTTPKernelModule";
|
||||
import {Request} from "../../lifecycle/Request";
|
||||
import {Injectable, Inject} from "../../../di"
|
||||
import {HTTPKernel} from "../HTTPKernel";
|
||||
import {Config} from "../../../service/Config";
|
||||
import {HTTPKernelModule} from '../HTTPKernelModule'
|
||||
import {Request} from '../../lifecycle/Request'
|
||||
import {Injectable, Inject} from '../../../di'
|
||||
import {HTTPKernel} from '../HTTPKernel'
|
||||
import {Config} from '../../../service/Config'
|
||||
|
||||
/**
|
||||
* HTTP kernel middleware that sets the `X-Powered-By` header.
|
||||
@@ -14,11 +14,11 @@ export class PoweredByHeaderInjectionHTTPModule extends HTTPKernelModule {
|
||||
@Inject()
|
||||
protected readonly config!: Config;
|
||||
|
||||
public static register(kernel: HTTPKernel) {
|
||||
public static register(kernel: HTTPKernel): void {
|
||||
kernel.register(this).after()
|
||||
}
|
||||
|
||||
public async apply(request: Request) {
|
||||
public async apply(request: Request): Promise<Request> {
|
||||
if ( !this.config.get('server.poweredBy.hide', false) ) {
|
||||
request.response.setHeader('X-Powered-By', this.config.get('server.poweredBy.header', 'Extollo'))
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {HTTPKernelModule} from "../HTTPKernelModule";
|
||||
import {Injectable, Inject} from "../../../di";
|
||||
import {uuid_v4} from "../../../util";
|
||||
import {HTTPKernel} from "../HTTPKernel";
|
||||
import {Request} from "../../lifecycle/Request";
|
||||
import {Logging} from "../../../service/Logging";
|
||||
import {HTTPKernelModule} from '../HTTPKernelModule'
|
||||
import {Injectable, Inject} from '../../../di'
|
||||
import {uuid4} from '../../../util'
|
||||
import {HTTPKernel} from '../HTTPKernel'
|
||||
import {Request} from '../../lifecycle/Request'
|
||||
import {Logging} from '../../../service/Logging'
|
||||
|
||||
/**
|
||||
* HTTP kernel middleware that tries to look up the session ID from the request.
|
||||
@@ -16,13 +16,13 @@ export class SetSessionCookieHTTPModule extends HTTPKernelModule {
|
||||
@Inject()
|
||||
protected readonly logging!: Logging
|
||||
|
||||
public static register(kernel: HTTPKernel) {
|
||||
public static register(kernel: HTTPKernel): void {
|
||||
kernel.register(this).first()
|
||||
}
|
||||
|
||||
public async apply(request: Request) {
|
||||
public async apply(request: Request): Promise<Request> {
|
||||
if ( !request.cookies.has('extollo.session') ) {
|
||||
const session = `${uuid_v4()}-${uuid_v4()}`
|
||||
const session = `${uuid4()}-${uuid4()}`
|
||||
|
||||
this.logging.verbose(`Starting session: ${session}`)
|
||||
request.cookies.set('extollo.session', session)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {Injectable, ScopedContainer, Container} from "../../di"
|
||||
import {infer, UniversalPath} from "../../util"
|
||||
import {IncomingMessage, ServerResponse} from "http"
|
||||
import {HTTPCookieJar} from "../kernel/HTTPCookieJar";
|
||||
import {TLSSocket} from "tls";
|
||||
import * as url from "url";
|
||||
import {Response} from "./Response";
|
||||
import * as Negotiator from "negotiator";
|
||||
import {Injectable, ScopedContainer, Container} from '../../di'
|
||||
import {infer, UniversalPath} from '../../util'
|
||||
import {IncomingMessage, ServerResponse} from 'http'
|
||||
import {HTTPCookieJar} from '../kernel/HTTPCookieJar'
|
||||
import {TLSSocket} from 'tls'
|
||||
import * as url from 'url'
|
||||
import {Response} from './Response'
|
||||
import * as Negotiator from 'negotiator'
|
||||
|
||||
/**
|
||||
* Enumeration of different HTTP verbs.
|
||||
@@ -17,8 +17,8 @@ export type HTTPMethod = 'post' | 'get' | 'patch' | 'put' | 'delete' | 'unknown'
|
||||
* Returns true if the given item is a valid HTTP verb.
|
||||
* @param what
|
||||
*/
|
||||
export function isHTTPMethod(what: any): what is HTTPMethod {
|
||||
return ['post', 'get', 'patch', 'put', 'delete'].includes(what)
|
||||
export function isHTTPMethod(what: unknown): what is HTTPMethod {
|
||||
return ['post', 'get', 'patch', 'put', 'delete'].includes(String(what))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +98,7 @@ export class Request extends ScopedContainer implements DataContainer {
|
||||
public readonly uploadedFiles: {[key: string]: UniversalPath} = {}
|
||||
|
||||
/** If true, the response lifecycle will not time out and send errors. */
|
||||
public bypassTimeout: boolean = false
|
||||
public bypassTimeout = false
|
||||
|
||||
constructor(
|
||||
/** The native Node.js request. */
|
||||
@@ -109,7 +109,7 @@ export class Request extends ScopedContainer implements DataContainer {
|
||||
) {
|
||||
super(Container.getContainer())
|
||||
|
||||
this.secure = !!(clientRequest.connection as TLSSocket).encrypted
|
||||
this.secure = Boolean((clientRequest.connection as TLSSocket).encrypted)
|
||||
|
||||
this.cookies = new HTTPCookieJar(this)
|
||||
this.url = String(clientRequest.url)
|
||||
@@ -137,6 +137,10 @@ export class Request extends ScopedContainer implements DataContainer {
|
||||
|
||||
const query: {[key: string]: any} = {}
|
||||
for ( const key in this.rawQueryData ) {
|
||||
if ( !Object.prototype.hasOwnProperty.call(this.rawQueryData, key) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
const value = this.rawQueryData[key]
|
||||
|
||||
if ( Array.isArray(value) ) {
|
||||
@@ -151,12 +155,11 @@ export class Request extends ScopedContainer implements DataContainer {
|
||||
this.query = query
|
||||
this.isXHR = String(this.clientRequest.headers['x-requested-with']).toLowerCase() === 'xmlhttprequest'
|
||||
|
||||
// @ts-ignore
|
||||
const {address = '0.0.0.0', family = 'IPv4', port = 0} = this.clientRequest.connection.address()
|
||||
const {address = '0.0.0.0', family = 'IPv4', port = 0} = this.clientRequest.connection.address() as any
|
||||
this.address = {
|
||||
address,
|
||||
family,
|
||||
port
|
||||
port,
|
||||
}
|
||||
|
||||
this.mediaTypes = (new Negotiator(clientRequest)).mediaTypes()
|
||||
@@ -164,12 +167,12 @@ export class Request extends ScopedContainer implements DataContainer {
|
||||
}
|
||||
|
||||
/** Get the value of a header, if it exists. */
|
||||
public getHeader(name: string) {
|
||||
public getHeader(name: string): string | string[] | undefined {
|
||||
return this.clientRequest.headers[name.toLowerCase()]
|
||||
}
|
||||
|
||||
/** Get the native Node.js IncomingMessage object. */
|
||||
public toNative() {
|
||||
public toNative(): IncomingMessage {
|
||||
return this.clientRequest
|
||||
}
|
||||
|
||||
@@ -177,7 +180,7 @@ export class Request extends ScopedContainer implements DataContainer {
|
||||
* Get the value of an input field on the request. Spans multiple input sources.
|
||||
* @param key
|
||||
*/
|
||||
public input(key?: string) {
|
||||
public input(key?: string): unknown {
|
||||
if ( !key ) {
|
||||
return {
|
||||
...this.parsedInput,
|
||||
@@ -206,17 +209,21 @@ export class Request extends ScopedContainer implements DataContainer {
|
||||
* Returns true if the request accepts the given media type.
|
||||
* @param type - a mimetype, or the short forms json, xml, or html
|
||||
*/
|
||||
accepts(type: string) {
|
||||
if ( type === 'json' ) type = 'application/json'
|
||||
else if ( type === 'xml' ) type = 'application/xml'
|
||||
else if ( type === 'html' ) type = 'text/html'
|
||||
accepts(type: string): boolean {
|
||||
if ( type === 'json' ) {
|
||||
type = 'application/json'
|
||||
} else if ( type === 'xml' ) {
|
||||
type = 'application/xml'
|
||||
} else if ( type === 'html' ) {
|
||||
type = 'text/html'
|
||||
}
|
||||
|
||||
type = type.toLowerCase()
|
||||
|
||||
const possible = [
|
||||
type,
|
||||
type.split('/')[0] + '/*',
|
||||
'*/*'
|
||||
'*/*',
|
||||
]
|
||||
|
||||
return this.mediaTypes.some(media => possible.includes(media.toLowerCase()))
|
||||
@@ -230,9 +237,15 @@ export class Request extends ScopedContainer implements DataContainer {
|
||||
const xmlIdx = this.mediaTypes.indexOf('application/xml') ?? this.mediaTypes.indexOf('application/*') ?? this.mediaTypes.indexOf('*/*')
|
||||
const htmlIdx = this.mediaTypes.indexOf('text/html') ?? this.mediaTypes.indexOf('text/*') ?? this.mediaTypes.indexOf('*/*')
|
||||
|
||||
if ( htmlIdx >= 0 && htmlIdx <= jsonIdx && htmlIdx <= xmlIdx ) return 'html'
|
||||
if ( jsonIdx >= 0 && jsonIdx <= htmlIdx && jsonIdx <= xmlIdx ) return 'json'
|
||||
if ( xmlIdx >= 0 && xmlIdx <= jsonIdx && xmlIdx <= htmlIdx ) return 'xml'
|
||||
if ( htmlIdx >= 0 && htmlIdx <= jsonIdx && htmlIdx <= xmlIdx ) {
|
||||
return 'html'
|
||||
}
|
||||
if ( jsonIdx >= 0 && jsonIdx <= htmlIdx && jsonIdx <= xmlIdx ) {
|
||||
return 'json'
|
||||
}
|
||||
if ( xmlIdx >= 0 && xmlIdx <= jsonIdx && xmlIdx <= htmlIdx ) {
|
||||
return 'xml'
|
||||
}
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import {Request} from "./Request";
|
||||
import {ErrorWithContext, HTTPStatus, BehaviorSubject} from "../../util"
|
||||
import {ServerResponse} from "http"
|
||||
import {HTTPCookieJar} from "../kernel/HTTPCookieJar";
|
||||
import {Request} from './Request'
|
||||
import {ErrorWithContext, HTTPStatus, BehaviorSubject} from '../../util'
|
||||
import {ServerResponse} from 'http'
|
||||
import {HTTPCookieJar} from '../kernel/HTTPCookieJar'
|
||||
|
||||
/**
|
||||
* Error thrown when the server tries to re-send headers after they have been sent once.
|
||||
*/
|
||||
export class HeadersAlreadySentError extends ErrorWithContext {
|
||||
constructor(response: Response, headerName?: string) {
|
||||
super(`Cannot modify or re-send headers for this request as they have already been sent.`);
|
||||
super(`Cannot modify or re-send headers for this request as they have already been sent.`)
|
||||
this.context = { headerName }
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ export class HeadersAlreadySentError extends ErrorWithContext {
|
||||
* Error thrown when the server tries to re-send a response that has already been sent.
|
||||
*/
|
||||
export class ResponseAlreadySentError extends ErrorWithContext {
|
||||
constructor(response: Response) {
|
||||
super(`Cannot modify or re-send response as it has already ended.`);
|
||||
constructor(public readonly response: Response) {
|
||||
super(`Cannot modify or re-send response as it has already ended.`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ export class Response {
|
||||
private headers: {[key: string]: string | string[]} = {}
|
||||
|
||||
/** True if the headers have been sent. */
|
||||
private _sentHeaders: boolean = false
|
||||
private sentHeaders = false
|
||||
|
||||
/** True if the response has been sent and closed. */
|
||||
private _responseEnded: boolean = false
|
||||
private responseEnded = false
|
||||
|
||||
/** The HTTP status code that should be sent to the client. */
|
||||
private _status: HTTPStatus = HTTPStatus.OK
|
||||
private status: HTTPStatus = HTTPStatus.OK
|
||||
|
||||
/**
|
||||
* If this is true, then some module in the kernel has flagged the response
|
||||
@@ -44,10 +44,10 @@ export class Response {
|
||||
* the response.
|
||||
* @private
|
||||
*/
|
||||
private _blockingWriteback: boolean = false
|
||||
private isBlockingWriteback = false
|
||||
|
||||
/** The body contents that should be written to the response. */
|
||||
public body: string = ''
|
||||
public body = ''
|
||||
|
||||
/**
|
||||
* Behavior subject fired right before the response content is written.
|
||||
@@ -68,14 +68,18 @@ export class Response {
|
||||
) { }
|
||||
|
||||
/** Get the currently set response status. */
|
||||
public getStatus() {
|
||||
return this._status
|
||||
public getStatus(): HTTPStatus {
|
||||
return this.status
|
||||
}
|
||||
|
||||
/** Set a new response status. */
|
||||
public setStatus(status: HTTPStatus) {
|
||||
if ( this._sentHeaders ) throw new HeadersAlreadySentError(this, 'status')
|
||||
this._status = status
|
||||
public setStatus(status: HTTPStatus): this {
|
||||
if ( this.sentHeaders ) {
|
||||
throw new HeadersAlreadySentError(this, 'status')
|
||||
}
|
||||
|
||||
this.status = status
|
||||
return this
|
||||
}
|
||||
|
||||
/** Get the HTTPCookieJar for the client. */
|
||||
@@ -89,8 +93,11 @@ export class Response {
|
||||
}
|
||||
|
||||
/** Set the value of the response header. */
|
||||
public setHeader(name: string, value: string | string[]) {
|
||||
if ( this._sentHeaders ) throw new HeadersAlreadySentError(this, name)
|
||||
public setHeader(name: string, value: string | string[]): this {
|
||||
if ( this.sentHeaders ) {
|
||||
throw new HeadersAlreadySentError(this, name)
|
||||
}
|
||||
|
||||
this.headers[name] = value
|
||||
return this
|
||||
}
|
||||
@@ -99,9 +106,13 @@ export class Response {
|
||||
* Bulk set the specified headers in the response.
|
||||
* @param data
|
||||
*/
|
||||
public setHeaders(data: {[name: string]: string | string[]}) {
|
||||
if ( this._sentHeaders ) throw new HeadersAlreadySentError(this)
|
||||
this.headers = {...this.headers, ...data}
|
||||
public setHeaders(data: {[name: string]: string | string[]}): this {
|
||||
if ( this.sentHeaders ) {
|
||||
throw new HeadersAlreadySentError(this)
|
||||
}
|
||||
|
||||
this.headers = {...this.headers,
|
||||
...data}
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -110,67 +121,88 @@ export class Response {
|
||||
* @param name
|
||||
* @param value
|
||||
*/
|
||||
public appendHeader(name: string, value: string | string[]) {
|
||||
if ( this._sentHeaders ) throw new HeadersAlreadySentError(this, name)
|
||||
if ( !Array.isArray(value) ) value = [value]
|
||||
public appendHeader(name: string, value: string | string[]): this {
|
||||
if ( this.sentHeaders ) {
|
||||
throw new HeadersAlreadySentError(this, name)
|
||||
}
|
||||
if ( !Array.isArray(value) ) {
|
||||
value = [value]
|
||||
}
|
||||
let existing = this.headers[name] ?? []
|
||||
if ( !Array.isArray(existing) ) existing = [existing]
|
||||
if ( !Array.isArray(existing) ) {
|
||||
existing = [existing]
|
||||
}
|
||||
|
||||
existing = [...existing, ...value]
|
||||
if ( existing.length === 1 ) existing = existing[0]
|
||||
if ( existing.length === 1 ) {
|
||||
existing = existing[0]
|
||||
}
|
||||
|
||||
this.headers[name] = existing
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the headers to the client.
|
||||
*/
|
||||
public sendHeaders() {
|
||||
public sendHeaders(): this {
|
||||
const headers = {} as any
|
||||
|
||||
const setCookieHeaders = this.cookies.getSetCookieHeaders()
|
||||
if ( setCookieHeaders.length ) headers['Set-Cookie'] = setCookieHeaders
|
||||
if ( setCookieHeaders.length ) {
|
||||
headers['Set-Cookie'] = setCookieHeaders
|
||||
}
|
||||
|
||||
for ( const key in this.headers ) {
|
||||
if ( !this.headers.hasOwnProperty(key) ) continue
|
||||
if ( !Object.prototype.hasOwnProperty.call(this.headers, key) ) {
|
||||
continue
|
||||
}
|
||||
|
||||
headers[key] = this.headers[key]
|
||||
}
|
||||
|
||||
this.serverResponse.writeHead(this._status, headers)
|
||||
this._sentHeaders = true
|
||||
this.serverResponse.writeHead(this.status, headers)
|
||||
this.sentHeaders = true
|
||||
return this
|
||||
}
|
||||
|
||||
/** Returns true if the headers have been sent. */
|
||||
public hasSentHeaders() {
|
||||
return this._sentHeaders
|
||||
public hasSentHeaders(): boolean {
|
||||
return this.sentHeaders
|
||||
}
|
||||
|
||||
/** Returns true if a body has been set in the response. */
|
||||
public hasBody() {
|
||||
return !!this.body
|
||||
public hasBody(): boolean {
|
||||
return Boolean(this.body)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set the flag for whether the writeback should be blocked.
|
||||
* @param set - if this is specified, the value will be set.
|
||||
*/
|
||||
public blockingWriteback(set?: boolean) {
|
||||
public blockingWriteback(set?: boolean): boolean {
|
||||
if ( typeof set !== 'undefined' ) {
|
||||
this._blockingWriteback = set
|
||||
this.isBlockingWriteback = set
|
||||
}
|
||||
|
||||
return this._blockingWriteback
|
||||
return this.isBlockingWriteback
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the headers and specified data to the client.
|
||||
* @param data
|
||||
*/
|
||||
public async write(data: any) {
|
||||
public async write(data: unknown): Promise<void> {
|
||||
return new Promise<void>((res, rej) => {
|
||||
if ( !this._sentHeaders ) this.sendHeaders()
|
||||
if ( !this.sentHeaders ) {
|
||||
this.sendHeaders()
|
||||
}
|
||||
this.serverResponse.write(data, error => {
|
||||
if ( error ) rej(error)
|
||||
else res()
|
||||
if ( error ) {
|
||||
rej(error)
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -178,7 +210,7 @@ export class Response {
|
||||
/**
|
||||
* Send the response to the client, writing the headers and configured body.
|
||||
*/
|
||||
public async send() {
|
||||
public async send(): Promise<void> {
|
||||
await this.sending$.next(this)
|
||||
this.setHeader('Content-Length', String(this.body?.length ?? 0))
|
||||
await this.write(this.body ?? '')
|
||||
@@ -189,10 +221,14 @@ export class Response {
|
||||
/**
|
||||
* Mark the response as ended and close the socket.
|
||||
*/
|
||||
public end() {
|
||||
if ( this._responseEnded ) throw new ResponseAlreadySentError(this)
|
||||
this._sentHeaders = true
|
||||
public end(): this {
|
||||
if ( this.responseEnded ) {
|
||||
throw new ResponseAlreadySentError(this)
|
||||
}
|
||||
|
||||
this.sentHeaders = true
|
||||
this.serverResponse.end()
|
||||
return this
|
||||
}
|
||||
|
||||
// location?
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {ResponseFactory} from "./ResponseFactory"
|
||||
import {Rehydratable} from "../../util"
|
||||
import {Request} from "../lifecycle/Request";
|
||||
import {ResponseFactory} from './ResponseFactory'
|
||||
import {Rehydratable} from '../../util'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
|
||||
/**
|
||||
* Helper function that creates a DehydratedStateResponseFactory.
|
||||
@@ -15,10 +15,12 @@ export function dehydrate(value: Rehydratable): DehydratedStateResponseFactory {
|
||||
*/
|
||||
export class DehydratedStateResponseFactory extends ResponseFactory {
|
||||
constructor(
|
||||
public readonly rehydratable: Rehydratable
|
||||
) { super() }
|
||||
public readonly rehydratable: Rehydratable,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
public async write(request: Request) {
|
||||
public async write(request: Request): Promise<Request> {
|
||||
request = await super.write(request)
|
||||
request.response.body = JSON.stringify(this.rehydratable.dehydrate())
|
||||
request.response.setHeader('Content-Type', 'application/json')
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import {ResponseFactory} from "./ResponseFactory"
|
||||
import {ErrorWithContext, HTTPStatus} from "../../util"
|
||||
import {Request} from "../lifecycle/Request";
|
||||
import * as api from "./api"
|
||||
import {ResponseFactory} from './ResponseFactory'
|
||||
import {ErrorWithContext, HTTPStatus} from '../../util'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
import * as api from './api'
|
||||
|
||||
/**
|
||||
* Helper to create a new ErrorResponseFactory, with the given HTTP status and output format.
|
||||
* @param error
|
||||
* @param thrownError
|
||||
* @param status
|
||||
* @param output
|
||||
*/
|
||||
export function error(
|
||||
error: Error | string,
|
||||
thrownError: Error | string,
|
||||
status: HTTPStatus = HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
output: 'json' | 'html' | 'auto' = 'auto'
|
||||
output: 'json' | 'html' | 'auto' = 'auto',
|
||||
): ErrorResponseFactory {
|
||||
if ( typeof error === 'string' ) error = new Error(error)
|
||||
return new ErrorResponseFactory(error, status, output)
|
||||
if ( typeof thrownError === 'string' ) {
|
||||
thrownError = new Error(thrownError)
|
||||
}
|
||||
return new ErrorResponseFactory(thrownError, status, output)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,9 +27,9 @@ export class ErrorResponseFactory extends ResponseFactory {
|
||||
protected targetMode: 'json' | 'html' | 'auto' = 'auto'
|
||||
|
||||
constructor(
|
||||
public readonly error: Error,
|
||||
public readonly thrownError: Error,
|
||||
status: HTTPStatus,
|
||||
output: 'json' | 'html' | 'auto' = 'auto'
|
||||
output: 'json' | 'html' | 'auto' = 'auto',
|
||||
) {
|
||||
super()
|
||||
this.status(status)
|
||||
@@ -39,16 +41,16 @@ export class ErrorResponseFactory extends ResponseFactory {
|
||||
return this
|
||||
}
|
||||
|
||||
public async write(request: Request) {
|
||||
public async write(request: Request): Promise<Request> {
|
||||
request = await super.write(request)
|
||||
const wants = request.wants()
|
||||
|
||||
if ( this.targetMode === 'json' || (this.targetMode === 'auto' && wants === 'json') ) {
|
||||
request.response.setHeader('Content-Type', 'application/json')
|
||||
request.response.body = this.buildJSON(this.error)
|
||||
request.response.body = this.buildJSON(this.thrownError)
|
||||
} else if ( this.targetMode === 'html' || (this.targetMode === 'auto' && (wants === 'html' || wants === 'unknown')) ) {
|
||||
request.response.setHeader('Content-Type', 'text/html')
|
||||
request.response.body = this.buildHTML(this.error)
|
||||
request.response.body = this.buildHTML(this.thrownError)
|
||||
}
|
||||
|
||||
// FIXME XML support
|
||||
@@ -61,12 +63,12 @@ export class ErrorResponseFactory extends ResponseFactory {
|
||||
* @param {Error} error
|
||||
* @return string
|
||||
*/
|
||||
protected buildHTML(error: Error) {
|
||||
protected buildHTML(thrownError: Error): string {
|
||||
let context: any
|
||||
if ( error instanceof ErrorWithContext ) {
|
||||
context = error.context
|
||||
if ( error.originalError ) {
|
||||
error = error.originalError
|
||||
if ( thrownError instanceof ErrorWithContext ) {
|
||||
context = thrownError.context
|
||||
if ( thrownError.originalError ) {
|
||||
thrownError = thrownError.originalError
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,10 +76,11 @@ export class ErrorResponseFactory extends ResponseFactory {
|
||||
<b>Sorry, an unexpected error occurred while processing your request.</b>
|
||||
<br>
|
||||
<pre><code>
|
||||
Name: ${error.name}
|
||||
Message: ${error.message}
|
||||
Name: ${thrownError.name}
|
||||
Message: ${thrownError.message}
|
||||
Stack trace:
|
||||
- ${error.stack ? error.stack.split(/\s+at\s+/).slice(1).join('<br> - ') : 'none'}
|
||||
- ${thrownError.stack ? thrownError.stack.split(/\s+at\s+/).slice(1)
|
||||
.join('<br> - ') : 'none'}
|
||||
</code></pre>
|
||||
`
|
||||
|
||||
@@ -85,7 +88,8 @@ Stack trace:
|
||||
str += `
|
||||
<pre><code>
|
||||
Context:
|
||||
${Object.keys(context).map(key => ` - ${key} : ${context[key]}`).join('\n')}
|
||||
${Object.keys(context).map(key => ` - ${key} : ${context[key]}`)
|
||||
.join('\n')}
|
||||
</code></pre>
|
||||
`
|
||||
}
|
||||
@@ -93,7 +97,7 @@ ${Object.keys(context).map(key => ` - ${key} : ${context[key]}`).join('\n')}
|
||||
return str
|
||||
}
|
||||
|
||||
protected buildJSON(error: Error) {
|
||||
return JSON.stringify(api.error(error))
|
||||
protected buildJSON(thrownError: Error): string {
|
||||
return JSON.stringify(api.error(thrownError))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {ResponseFactory} from "./ResponseFactory";
|
||||
import {Request} from "../lifecycle/Request";
|
||||
import {ResponseFactory} from './ResponseFactory'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
|
||||
/**
|
||||
* Helper function that creates a new HTMLResponseFactory.
|
||||
@@ -15,9 +15,11 @@ export function html(value: string): HTMLResponseFactory {
|
||||
export class HTMLResponseFactory extends ResponseFactory {
|
||||
constructor(
|
||||
public readonly value: string,
|
||||
) { super() }
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
public async write(request: Request) {
|
||||
public async write(request: Request): Promise<Request> {
|
||||
request = await super.write(request)
|
||||
request.response.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
request.response.body = this.value
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {ErrorResponseFactory} from "./ErrorResponseFactory";
|
||||
import {HTTPError} from "../HTTPError";
|
||||
import {HTTPStatus} from "../../util"
|
||||
import {ErrorResponseFactory} from './ErrorResponseFactory'
|
||||
import {HTTPError} from '../HTTPError'
|
||||
import {HTTPStatus} from '../../util'
|
||||
|
||||
/**
|
||||
* Helper that generates a new HTTPErrorResponseFactory given the HTTP status and message.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {ResponseFactory} from "./ResponseFactory";
|
||||
import {Request} from "../lifecycle/Request";
|
||||
import {ResponseFactory} from './ResponseFactory'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
|
||||
/**
|
||||
* Helper function to create a new JSONResponseFactory of the given value.
|
||||
* @param value
|
||||
*/
|
||||
export function json(value: any): JSONResponseFactory {
|
||||
export function json(value: unknown): JSONResponseFactory {
|
||||
return new JSONResponseFactory(value)
|
||||
}
|
||||
|
||||
@@ -14,10 +14,12 @@ export function json(value: any): JSONResponseFactory {
|
||||
*/
|
||||
export class JSONResponseFactory extends ResponseFactory {
|
||||
constructor(
|
||||
public readonly value: any
|
||||
) { super() }
|
||||
public readonly value: unknown,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
public async write(request: Request) {
|
||||
public async write(request: Request): Promise<Request> {
|
||||
request = await super.write(request)
|
||||
request.response.setHeader('Content-Type', 'application/json')
|
||||
request.response.body = JSON.stringify(this.value)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {HTTPStatus} from "../../util"
|
||||
import {Request} from "../lifecycle/Request"
|
||||
import {HTTPStatus} from '../../util'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
|
||||
/**
|
||||
* Abstract class that defines "factory" that knows how to write a particular
|
||||
@@ -19,7 +19,7 @@ export abstract class ResponseFactory {
|
||||
}
|
||||
|
||||
/** Set the target status of this factory. */
|
||||
public status(status: HTTPStatus) {
|
||||
public status(status: HTTPStatus): this {
|
||||
this.targetStatus = status
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {ResponseFactory} from "./ResponseFactory";
|
||||
import {Request} from "../lifecycle/Request";
|
||||
import {ResponseFactory} from './ResponseFactory'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
|
||||
/**
|
||||
* Helper function that creates a new StringResponseFactory for the given string value.
|
||||
@@ -16,9 +16,11 @@ export class StringResponseFactory extends ResponseFactory {
|
||||
constructor(
|
||||
/** The string to write as the body. */
|
||||
public readonly value: string,
|
||||
) { super() }
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
public async write(request: Request) {
|
||||
public async write(request: Request): Promise<Request> {
|
||||
request = await super.write(request)
|
||||
request.response.setHeader('Content-Type', 'text/plain')
|
||||
request.response.body = this.value
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {ResponseFactory} from "./ResponseFactory";
|
||||
import {HTTPStatus} from "../../util";
|
||||
import {Request} from "../lifecycle/Request";
|
||||
import {ResponseFactory} from './ResponseFactory'
|
||||
import {HTTPStatus} from '../../util'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
|
||||
/**
|
||||
* Helper function to create a new TemporaryRedirectResponseFactory to the given destination.
|
||||
@@ -18,10 +18,12 @@ export class TemporaryRedirectResponseFactory extends ResponseFactory {
|
||||
|
||||
constructor(
|
||||
/** THe URL where the client should redirect to. */
|
||||
public readonly destination: string
|
||||
) { super() }
|
||||
public readonly destination: string,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
public async write(request: Request) {
|
||||
public async write(request: Request): Promise<Request> {
|
||||
request = await super.write(request)
|
||||
request.response.setHeader('Location', this.destination)
|
||||
return request
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {Container} from "../../di";
|
||||
import {ResponseFactory} from "./ResponseFactory";
|
||||
import {Request} from "../lifecycle/Request";
|
||||
import {ViewEngine} from "../../views/ViewEngine";
|
||||
import {Container} from '../../di'
|
||||
import {ResponseFactory} from './ResponseFactory'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
import {ViewEngine} from '../../views/ViewEngine'
|
||||
|
||||
/**
|
||||
* Helper function that creates a new ViewResponseFactory to render the given view
|
||||
@@ -22,10 +22,12 @@ export class ViewResponseFactory extends ResponseFactory {
|
||||
/** The name of the view to render. */
|
||||
public readonly viewName: string,
|
||||
/** Optional data that should be passed to the view engine as params. */
|
||||
public readonly data?: {[key: string]: any}
|
||||
) { super() }
|
||||
public readonly data?: {[key: string]: any},
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
public async write(request: Request) {
|
||||
public async write(request: Request): Promise<Request> {
|
||||
const viewEngine = <ViewEngine> Container.getContainer().make(ViewEngine)
|
||||
request.response.body = await viewEngine.renderByName(this.viewName, this.data || {})
|
||||
request.response.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
|
||||
@@ -14,13 +14,13 @@ export interface APIResponse {
|
||||
|
||||
/**
|
||||
* Formats a mesage as a successful API response.
|
||||
* @param {string} message
|
||||
* @param {string} displayMessage
|
||||
* @return APIResponse
|
||||
*/
|
||||
export function message(message: string): APIResponse {
|
||||
export function message(displayMessage: string): APIResponse {
|
||||
return {
|
||||
success: true,
|
||||
message,
|
||||
message: displayMessage,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export function message(message: string): APIResponse {
|
||||
* @param record
|
||||
* @return APIResponse
|
||||
*/
|
||||
export function one(record: any): APIResponse {
|
||||
export function one(record: unknown): APIResponse {
|
||||
return {
|
||||
success: true,
|
||||
data: record,
|
||||
@@ -53,23 +53,23 @@ export function many(records: any[]): APIResponse {
|
||||
|
||||
/**
|
||||
* Formats an error message or Error instance as an API response.
|
||||
* @param {string|Error} error
|
||||
* @return APIResponse
|
||||
* @param thrownError
|
||||
*/
|
||||
export function error(error: string | Error): APIResponse {
|
||||
if ( typeof error === 'string' ) {
|
||||
export function error(thrownError: string | Error): APIResponse {
|
||||
if ( typeof thrownError === 'string' ) {
|
||||
return {
|
||||
success: false,
|
||||
message: error,
|
||||
message: thrownError,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
message: error.message,
|
||||
message: thrownError.message,
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack ? error.stack.split(/\s+at\s+/).slice(1) : [],
|
||||
name: thrownError.name,
|
||||
message: thrownError.message,
|
||||
stack: thrownError.stack ? thrownError.stack.split(/\s+at\s+/).slice(1) : [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {ErrorWithContext} from "../../util";
|
||||
import {ResolvedRouteHandler, Route} from "./Route";
|
||||
import {ErrorWithContext} from '../../util'
|
||||
import {ResolvedRouteHandler, Route} from './Route'
|
||||
|
||||
/**
|
||||
* Class representing a resolved route that a request is mounted to.
|
||||
@@ -42,7 +42,7 @@ export class ActivatedRoute {
|
||||
public readonly route: Route,
|
||||
|
||||
/** The request path that activated that route. */
|
||||
public readonly path: string
|
||||
public readonly path: string,
|
||||
) {
|
||||
const params = route.extract(path)
|
||||
if ( !params ) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {AppClass} from "../../lifecycle/AppClass"
|
||||
import {Request} from "../lifecycle/Request"
|
||||
import {ResponseObject} from "./Route"
|
||||
import {AppClass} from '../../lifecycle/AppClass'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
import {ResponseObject} from './Route'
|
||||
import {Container} from '../../di'
|
||||
|
||||
/**
|
||||
* Base class representing a middleware handler that can be applied to routes.
|
||||
@@ -8,10 +9,12 @@ import {ResponseObject} from "./Route"
|
||||
export abstract class Middleware extends AppClass {
|
||||
constructor(
|
||||
/** The request that will be handled by this middleware. */
|
||||
protected readonly request: Request
|
||||
) { super() }
|
||||
protected readonly request: Request,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
protected container() {
|
||||
protected container(): Container {
|
||||
return this.request
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import {AppClass} from "../../lifecycle/AppClass";
|
||||
import {HTTPMethod, Request} from "../lifecycle/Request";
|
||||
import {Application} from "../../lifecycle/Application";
|
||||
import {RouteGroup} from "./RouteGroup";
|
||||
import {ResponseFactory} from "../response/ResponseFactory";
|
||||
import {Response} from "../lifecycle/Response";
|
||||
import {Controllers} from "../../service/Controllers";
|
||||
import {ErrorWithContext, Collection} from "../../util";
|
||||
import {Container} from "../../di";
|
||||
import {Controller} from "../Controller";
|
||||
import {Middlewares} from "../../service/Middlewares";
|
||||
import {Middleware} from "./Middleware";
|
||||
import {Config} from "../../service/Config";
|
||||
import {AppClass} from '../../lifecycle/AppClass'
|
||||
import {HTTPMethod, Request} from '../lifecycle/Request'
|
||||
import {Application} from '../../lifecycle/Application'
|
||||
import {RouteGroup} from './RouteGroup'
|
||||
import {ResponseFactory} from '../response/ResponseFactory'
|
||||
import {Response} from '../lifecycle/Response'
|
||||
import {Controllers} from '../../service/Controllers'
|
||||
import {ErrorWithContext, Collection} from '../../util'
|
||||
import {Container} from '../../di'
|
||||
import {Controller} from '../Controller'
|
||||
import {Middlewares} from '../../service/Middlewares'
|
||||
import {Middleware} from './Middleware'
|
||||
import {Config} from '../../service/Config'
|
||||
|
||||
/**
|
||||
* Type alias for an item that is a valid response object, or lack thereof.
|
||||
@@ -61,7 +61,7 @@ export class Route extends AppClass {
|
||||
private static compiledGroupStack: RouteGroup[] = []
|
||||
|
||||
/** Register a route group handler. */
|
||||
public static registerGroup(group: RouteGroup) {
|
||||
public static registerGroup(group: RouteGroup): void {
|
||||
this.registeredGroups.push(group)
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ export class Route extends AppClass {
|
||||
* @param definition
|
||||
* @param handler
|
||||
*/
|
||||
public static endpoint(method: HTTPMethod | HTTPMethod[], definition: string, handler: RouteHandler) {
|
||||
public static endpoint(method: HTTPMethod | HTTPMethod[], definition: string, handler: RouteHandler): Route {
|
||||
const route = new Route(method, handler, definition)
|
||||
this.registeredRoutes.push(route)
|
||||
return route
|
||||
@@ -163,53 +163,53 @@ export class Route extends AppClass {
|
||||
* @param definition
|
||||
* @param handler
|
||||
*/
|
||||
public static get(definition: string, handler: RouteHandler) {
|
||||
public static get(definition: string, handler: RouteHandler): Route {
|
||||
return this.endpoint('get', definition, handler)
|
||||
}
|
||||
|
||||
/** Create a new POST route on the given endpoint. */
|
||||
public static post(definition: string, handler: RouteHandler) {
|
||||
public static post(definition: string, handler: RouteHandler): Route {
|
||||
return this.endpoint('post', definition, handler)
|
||||
}
|
||||
|
||||
/** Create a new PUT route on the given endpoint. */
|
||||
public static put(definition: string, handler: RouteHandler) {
|
||||
public static put(definition: string, handler: RouteHandler): Route {
|
||||
return this.endpoint('put', definition, handler)
|
||||
}
|
||||
|
||||
/** Create a new PATCH route on the given endpoint. */
|
||||
public static patch(definition: string, handler: RouteHandler) {
|
||||
public static patch(definition: string, handler: RouteHandler): Route {
|
||||
return this.endpoint('patch', definition, handler)
|
||||
}
|
||||
|
||||
/** Create a new DELETE route on the given endpoint. */
|
||||
public static delete(definition: string, handler: RouteHandler) {
|
||||
public static delete(definition: string, handler: RouteHandler): Route {
|
||||
return this.endpoint('delete', definition, handler)
|
||||
}
|
||||
|
||||
/** Create a new route on all HTTP verbs, on the given endpoint. */
|
||||
public static any(definition: string, handler: RouteHandler) {
|
||||
public static any(definition: string, handler: RouteHandler): Route {
|
||||
return this.endpoint(['get', 'put', 'patch', 'post', 'delete'], definition, handler)
|
||||
}
|
||||
|
||||
/** Create a new route group with the given prefix. */
|
||||
public static group(prefix: string, group: () => void | Promise<void>) {
|
||||
public static group(prefix: string, group: () => void | Promise<void>): RouteGroup {
|
||||
const grp = <RouteGroup> Application.getApplication().make(RouteGroup, group, prefix)
|
||||
this.registeredGroups.push(grp)
|
||||
return grp
|
||||
}
|
||||
|
||||
/** Middlewares that should be applied to this route. */
|
||||
protected middlewares: Collection<{ stage: 'pre' | 'post', handler: RouteHandler }> = new Collection<{stage: "pre" | "post"; handler: RouteHandler}>()
|
||||
protected middlewares: Collection<{ stage: 'pre' | 'post', handler: RouteHandler }> = new Collection<{stage: 'pre' | 'post'; handler: RouteHandler}>()
|
||||
|
||||
/** Pre-compiled route handlers for the pre-middleware for this route. */
|
||||
protected _compiledPreflight?: ResolvedRouteHandler[]
|
||||
protected compiledPreflight?: ResolvedRouteHandler[]
|
||||
|
||||
/** Pre-compiled route handlers for the post-middleware for this route. */
|
||||
protected _compiledHandler?: ResolvedRouteHandler
|
||||
protected compiledHandler?: ResolvedRouteHandler
|
||||
|
||||
/** Pre-compiled route handler for the main route handler for this route. */
|
||||
protected _compiledPostflight?: ResolvedRouteHandler[]
|
||||
protected compiledPostflight?: ResolvedRouteHandler[]
|
||||
|
||||
constructor(
|
||||
/** The HTTP method(s) that this route listens on. */
|
||||
@@ -219,8 +219,10 @@ export class Route extends AppClass {
|
||||
protected readonly handler: RouteHandler,
|
||||
|
||||
/** The route path this route listens on. */
|
||||
protected route: string
|
||||
) { super() }
|
||||
protected route: string,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this route matches the given HTTP verb and request path.
|
||||
@@ -228,10 +230,13 @@ export class Route extends AppClass {
|
||||
* @param potential
|
||||
*/
|
||||
public match(method: HTTPMethod, potential: string): boolean {
|
||||
if ( Array.isArray(this.method) && !this.method.includes(method) ) return false
|
||||
else if ( !Array.isArray(this.method) && this.method !== method ) return false
|
||||
if ( Array.isArray(this.method) && !this.method.includes(method) ) {
|
||||
return false
|
||||
} else if ( !Array.isArray(this.method) && this.method !== method ) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !!this.extract(potential)
|
||||
return Boolean(this.extract(potential))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,7 +284,9 @@ export class Route extends AppClass {
|
||||
|
||||
// If we got here, we didn't find a **
|
||||
// So, if the lengths are different, fail
|
||||
if ( routeParts.length !== potentialParts.length ) return
|
||||
if ( routeParts.length !== potentialParts.length ) {
|
||||
return
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
@@ -287,47 +294,47 @@ export class Route extends AppClass {
|
||||
* Try to pre-compile and return the preflight handlers for this route.
|
||||
*/
|
||||
public resolvePreflight(): ResolvedRouteHandler[] {
|
||||
if ( !this._compiledPreflight ) {
|
||||
this._compiledPreflight = this.resolveMiddlewareHandlersForStage('pre')
|
||||
if ( !this.compiledPreflight ) {
|
||||
this.compiledPreflight = this.resolveMiddlewareHandlersForStage('pre')
|
||||
}
|
||||
|
||||
return this._compiledPreflight
|
||||
return this.compiledPreflight
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to pre-compile and return the postflight handlers for this route.
|
||||
*/
|
||||
public resolvePostflight(): ResolvedRouteHandler[] {
|
||||
if ( !this._compiledPostflight ) {
|
||||
this._compiledPostflight = this.resolveMiddlewareHandlersForStage('post')
|
||||
if ( !this.compiledPostflight ) {
|
||||
this.compiledPostflight = this.resolveMiddlewareHandlersForStage('post')
|
||||
}
|
||||
|
||||
return this._compiledPostflight
|
||||
return this.compiledPostflight
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to pre-compile and return the main handler for this route.
|
||||
*/
|
||||
public resolveHandler(): ResolvedRouteHandler {
|
||||
if ( !this._compiledHandler ) {
|
||||
this._compiledHandler = this._resolveHandler()
|
||||
if ( !this.compiledHandler ) {
|
||||
this.compiledHandler = this.compileResolvedHandler()
|
||||
}
|
||||
|
||||
return this._compiledHandler
|
||||
return this.compiledHandler
|
||||
}
|
||||
|
||||
/** Register the given middleware as a preflight handler for this route. */
|
||||
pre(middleware: RouteHandler) {
|
||||
pre(middleware: RouteHandler): this {
|
||||
this.middlewares.push({
|
||||
stage: 'pre',
|
||||
handler: middleware
|
||||
handler: middleware,
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/** Register the given middleware as a postflight handler for this route. */
|
||||
post(middleware: RouteHandler) {
|
||||
post(middleware: RouteHandler): this {
|
||||
this.middlewares.push({
|
||||
stage: 'post',
|
||||
handler: middleware,
|
||||
@@ -337,19 +344,24 @@ export class Route extends AppClass {
|
||||
}
|
||||
|
||||
/** Prefix the route's path with the given prefix, normalizing `/` characters. */
|
||||
private prepend(prefix: string) {
|
||||
if ( !prefix.endsWith('/') ) prefix = `${prefix}/`
|
||||
if ( this.route.startsWith('/') ) this.route = this.route.substring(1)
|
||||
private prepend(prefix: string): this {
|
||||
if ( !prefix.endsWith('/') ) {
|
||||
prefix = `${prefix}/`
|
||||
}
|
||||
if ( this.route.startsWith('/') ) {
|
||||
this.route = this.route.substring(1)
|
||||
}
|
||||
this.route = `${prefix}${this.route}`
|
||||
return this
|
||||
}
|
||||
|
||||
/** Add the given middleware item to the beginning of the preflight handlers. */
|
||||
private prependMiddleware(def: { stage: 'pre' | 'post', handler: RouteHandler }) {
|
||||
private prependMiddleware(def: { stage: 'pre' | 'post', handler: RouteHandler }): void {
|
||||
this.middlewares.prepend(def)
|
||||
}
|
||||
|
||||
/** Add the given middleware item to the end of the postflight handlers. */
|
||||
private appendMiddleware(def: { stage: 'pre' | 'post', handler: RouteHandler }) {
|
||||
private appendMiddleware(def: { stage: 'pre' | 'post', handler: RouteHandler }): void {
|
||||
this.middlewares.push(def)
|
||||
}
|
||||
|
||||
@@ -357,18 +369,18 @@ export class Route extends AppClass {
|
||||
* Resolve and return the route handler for this route.
|
||||
* @private
|
||||
*/
|
||||
private _resolveHandler(): ResolvedRouteHandler {
|
||||
if ( typeof this.handler !== 'string' ) {
|
||||
private compileResolvedHandler(): ResolvedRouteHandler {
|
||||
const handler = this.handler
|
||||
if ( typeof handler !== 'string' ) {
|
||||
return (request: Request) => {
|
||||
// @ts-ignore
|
||||
return this.handler(request, request.response)
|
||||
return handler(request, request.response)
|
||||
}
|
||||
} else {
|
||||
const parts = this.handler.split('.')
|
||||
const parts = handler.split('.')
|
||||
if ( parts.length < 2 ) {
|
||||
const e = new ErrorWithContext('Route handler does not specify a method name.')
|
||||
e.context = {
|
||||
handler: this.handler
|
||||
handler,
|
||||
}
|
||||
throw e
|
||||
}
|
||||
@@ -380,7 +392,7 @@ export class Route extends AppClass {
|
||||
if ( !controllerClass ) {
|
||||
const e = new ErrorWithContext('Controller not found for route handler.')
|
||||
e.context = {
|
||||
handler: this.handler,
|
||||
handler,
|
||||
controllerName,
|
||||
methodName,
|
||||
}
|
||||
@@ -406,13 +418,13 @@ export class Route extends AppClass {
|
||||
private resolveMiddlewareHandlersForStage(stage: 'pre' | 'post'): ResolvedRouteHandler[] {
|
||||
return this.middlewares.where('stage', '=', stage)
|
||||
.map<ResolvedRouteHandler>(def => {
|
||||
if ( typeof def.handler !== 'string' ) {
|
||||
const handler = def.handler
|
||||
if ( typeof handler !== 'string' ) {
|
||||
return (request: Request) => {
|
||||
// @ts-ignore
|
||||
return def.handler(request, request.response)
|
||||
return handler(request, request.response)
|
||||
}
|
||||
} else {
|
||||
const parts = def.handler.split('.')
|
||||
const parts = handler.split('.')
|
||||
if ( parts.length < 2 ) {
|
||||
parts.push('apply') // default middleware method name, if none provided
|
||||
}
|
||||
@@ -424,7 +436,7 @@ export class Route extends AppClass {
|
||||
if ( !middlewareClass ) {
|
||||
const e = new ErrorWithContext('Middleware not found for route handler.')
|
||||
e.context = {
|
||||
handler: def.handler,
|
||||
handler,
|
||||
middlewareName,
|
||||
methodName,
|
||||
}
|
||||
@@ -445,7 +457,7 @@ export class Route extends AppClass {
|
||||
}
|
||||
|
||||
/** Cast the route to an intelligible string. */
|
||||
toString() {
|
||||
toString(): string {
|
||||
const method = Array.isArray(this.method) ? this.method : [this.method]
|
||||
return `${method.join('|')} -> ${this.route}`
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {Collection, ErrorWithContext} from "../../util"
|
||||
import {AppClass} from "../../lifecycle/AppClass"
|
||||
import {RouteHandler} from "./Route"
|
||||
import {Container} from "../../di"
|
||||
import {Logging} from "../../service/Logging";
|
||||
import {Collection, ErrorWithContext} from '../../util'
|
||||
import {AppClass} from '../../lifecycle/AppClass'
|
||||
import {RouteHandler} from './Route'
|
||||
import {Container} from '../../di'
|
||||
import {Logging} from '../../service/Logging'
|
||||
|
||||
/**
|
||||
* Class that defines a group of Routes in the application, with a prefix.
|
||||
@@ -24,7 +24,7 @@ export class RouteGroup extends AppClass {
|
||||
* Array of middlewares that should apply to all routes in this group.
|
||||
* @protected
|
||||
*/
|
||||
protected middlewares: Collection<{ stage: 'pre' | 'post', handler: RouteHandler }> = new Collection<{stage: "pre" | "post"; handler: RouteHandler}>()
|
||||
protected middlewares: Collection<{ stage: 'pre' | 'post', handler: RouteHandler }> = new Collection<{stage: 'pre' | 'post'; handler: RouteHandler}>()
|
||||
|
||||
/**
|
||||
* Get the current group nesting.
|
||||
@@ -48,7 +48,7 @@ export class RouteGroup extends AppClass {
|
||||
* @param name
|
||||
* @param define
|
||||
*/
|
||||
public static named(name: string, define: () => void) {
|
||||
public static named(name: string, define: () => void): void {
|
||||
if ( this.namedGroups[name] ) {
|
||||
Container.getContainer()
|
||||
.make<Logging>(Logging)
|
||||
@@ -69,7 +69,7 @@ export class RouteGroup extends AppClass {
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
public static include(name: string) {
|
||||
public static include(name: string): void {
|
||||
if (!this.namedGroups[name]) {
|
||||
throw new ErrorWithContext(`No route group exists with name: ${name}`, {name})
|
||||
}
|
||||
@@ -83,21 +83,23 @@ export class RouteGroup extends AppClass {
|
||||
public readonly group: () => void | Promise<void>,
|
||||
|
||||
/** The route prefix of this group. */
|
||||
public readonly prefix: string
|
||||
) { super() }
|
||||
public readonly prefix: string,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
/** Register the given middleware to be applied before all routes in this group. */
|
||||
pre(middleware: RouteHandler) {
|
||||
pre(middleware: RouteHandler): this {
|
||||
this.middlewares.push({
|
||||
stage: 'pre',
|
||||
handler: middleware
|
||||
handler: middleware,
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/** Register the given middleware to be applied after all routes in this group. */
|
||||
post(middleware: RouteHandler) {
|
||||
post(middleware: RouteHandler): this {
|
||||
this.middlewares.push({
|
||||
stage: 'post',
|
||||
handler: middleware,
|
||||
@@ -107,7 +109,7 @@ export class RouteGroup extends AppClass {
|
||||
}
|
||||
|
||||
/** Return the middlewares that apply to this group. */
|
||||
getGroupMiddlewareDefinitions() {
|
||||
getGroupMiddlewareDefinitions(): Collection<{ stage: 'pre' | 'post', handler: RouteHandler }> {
|
||||
return this.middlewares
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {NoSessionKeyError, Session, SessionData, SessionNotLoadedError} from "./Session";
|
||||
import {Injectable} from "../../di";
|
||||
import {NoSessionKeyError, Session, SessionData, SessionNotLoadedError} from './Session'
|
||||
import {Injectable} from '../../di'
|
||||
|
||||
/**
|
||||
* Implementation of the session driver that stores session data in memory.
|
||||
@@ -31,44 +31,66 @@ export class MemorySession extends Session {
|
||||
/** The associated data for this session. */
|
||||
protected data?: SessionData
|
||||
|
||||
constructor() { super() }
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
public getKey(): string {
|
||||
if ( !this.sessionID ) throw new NoSessionKeyError()
|
||||
if ( !this.sessionID ) {
|
||||
throw new NoSessionKeyError()
|
||||
}
|
||||
return this.sessionID
|
||||
}
|
||||
|
||||
public setKey(key: string) {
|
||||
public setKey(key: string): void {
|
||||
this.sessionID = key
|
||||
}
|
||||
|
||||
public load() {
|
||||
if ( !this.sessionID ) throw new NoSessionKeyError()
|
||||
public load(): void {
|
||||
if ( !this.sessionID ) {
|
||||
throw new NoSessionKeyError()
|
||||
}
|
||||
|
||||
this.data = MemorySession.getSession(this.sessionID)
|
||||
}
|
||||
|
||||
public persist() {
|
||||
if ( !this.sessionID ) throw new NoSessionKeyError()
|
||||
if ( !this.data ) throw new SessionNotLoadedError()
|
||||
public persist(): void {
|
||||
if ( !this.sessionID ) {
|
||||
throw new NoSessionKeyError()
|
||||
}
|
||||
|
||||
if ( !this.data ) {
|
||||
throw new SessionNotLoadedError()
|
||||
}
|
||||
|
||||
MemorySession.setSession(this.sessionID, this.data)
|
||||
}
|
||||
|
||||
public getData(): SessionData {
|
||||
if ( !this.data ) throw new SessionNotLoadedError()
|
||||
if ( !this.data ) {
|
||||
throw new SessionNotLoadedError()
|
||||
}
|
||||
|
||||
return this.data
|
||||
}
|
||||
|
||||
public setData(data: SessionData) {
|
||||
public setData(data: SessionData): void {
|
||||
this.data = data
|
||||
}
|
||||
|
||||
public get(key: string, fallback?: any): any {
|
||||
if ( !this.data ) throw new SessionNotLoadedError()
|
||||
public get(key: string, fallback?: unknown): any {
|
||||
if ( !this.data ) {
|
||||
throw new SessionNotLoadedError()
|
||||
}
|
||||
|
||||
return this.data?.[key] ?? fallback
|
||||
}
|
||||
|
||||
public set(key: string, value: any) {
|
||||
if ( !this.data ) throw new SessionNotLoadedError()
|
||||
public set(key: string, value: unknown): void {
|
||||
if ( !this.data ) {
|
||||
throw new SessionNotLoadedError()
|
||||
}
|
||||
|
||||
this.data[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Injectable, Inject} from "../../di"
|
||||
import {ErrorWithContext} from "../../util"
|
||||
import {Request} from "../lifecycle/Request"
|
||||
import {Injectable, Inject} from '../../di'
|
||||
import {ErrorWithContext} from '../../util'
|
||||
import {Request} from '../lifecycle/Request'
|
||||
|
||||
/**
|
||||
* Type alias describing some inflated session data.
|
||||
@@ -53,8 +53,8 @@ export abstract class Session {
|
||||
public abstract setData(data: SessionData): void
|
||||
|
||||
/** Get a value from the session by key. */
|
||||
public abstract get(key: string, fallback?: any): any
|
||||
public abstract get(key: string, fallback?: unknown): any
|
||||
|
||||
/** Set a value in the session by key. */
|
||||
public abstract set(key: string, value: any): void
|
||||
public abstract set(key: string, value: unknown): void
|
||||
}
|
||||
|
||||
@@ -5,20 +5,21 @@ import {
|
||||
PropertyDependency,
|
||||
isInstantiable,
|
||||
DEPENDENCY_KEYS_METADATA_KEY,
|
||||
DEPENDENCY_KEYS_PROPERTY_METADATA_KEY
|
||||
} from "../../di"
|
||||
import {Collection, ErrorWithContext} from "../../util"
|
||||
import {MemorySession} from "./MemorySession";
|
||||
import {Session} from "./Session";
|
||||
import {Logging} from "../../service/Logging";
|
||||
import {Config} from "../../service/Config";
|
||||
DEPENDENCY_KEYS_PROPERTY_METADATA_KEY, Instantiable,
|
||||
} from '../../di'
|
||||
import {Collection, ErrorWithContext} from '../../util'
|
||||
import {MemorySession} from './MemorySession'
|
||||
import {Session} from './Session'
|
||||
import {Logging} from '../../service/Logging'
|
||||
import {Config} from '../../service/Config'
|
||||
|
||||
/**
|
||||
* A dependency injection factory that matches the abstract Session class
|
||||
* and produces an instance of the configured session driver implementation.
|
||||
*/
|
||||
export class SessionFactory extends AbstractFactory {
|
||||
export class SessionFactory extends AbstractFactory<Session> {
|
||||
protected readonly logging: Logging
|
||||
|
||||
protected readonly config: Config
|
||||
|
||||
/** True if we have printed the memory session warning at least once. */
|
||||
@@ -30,17 +31,19 @@ export class SessionFactory extends AbstractFactory {
|
||||
this.config = Container.getContainer().make<Config>(Config)
|
||||
}
|
||||
|
||||
produce(dependencies: any[], parameters: any[]): Session {
|
||||
return new (this.getSessionClass())
|
||||
produce(): Session {
|
||||
return new (this.getSessionClass())()
|
||||
}
|
||||
|
||||
match(something: any) {
|
||||
match(something: unknown): boolean {
|
||||
return something === Session
|
||||
}
|
||||
|
||||
getDependencyKeys(): Collection<DependencyRequirement> {
|
||||
const meta = Reflect.getMetadata(DEPENDENCY_KEYS_METADATA_KEY, this.getSessionClass())
|
||||
if ( meta ) return meta
|
||||
if ( meta ) {
|
||||
return meta
|
||||
}
|
||||
return new Collection<DependencyRequirement>()
|
||||
}
|
||||
|
||||
@@ -50,7 +53,9 @@ export class SessionFactory extends AbstractFactory {
|
||||
|
||||
do {
|
||||
const loadedMeta = Reflect.getMetadata(DEPENDENCY_KEYS_PROPERTY_METADATA_KEY, currentToken)
|
||||
if ( loadedMeta ) meta.concat(loadedMeta)
|
||||
if ( loadedMeta ) {
|
||||
meta.concat(loadedMeta)
|
||||
}
|
||||
currentToken = Object.getPrototypeOf(currentToken)
|
||||
} while (Object.getPrototypeOf(currentToken) !== Function.prototype && Object.getPrototypeOf(currentToken) !== Object.prototype)
|
||||
|
||||
@@ -62,7 +67,7 @@ export class SessionFactory extends AbstractFactory {
|
||||
* @protected
|
||||
* @return Instantiable<Session>
|
||||
*/
|
||||
protected getSessionClass() {
|
||||
protected getSessionClass(): Instantiable<Session> {
|
||||
const SessionClass = this.config.get('server.session.driver', MemorySession)
|
||||
if ( SessionClass === MemorySession && !SessionFactory.loggedMemorySessionWarningOnce ) {
|
||||
this.logging.warn(`You are using the default memory-based session driver. It is recommended you configure a persistent session driver instead.`)
|
||||
@@ -70,9 +75,9 @@ export class SessionFactory extends AbstractFactory {
|
||||
}
|
||||
|
||||
if ( !isInstantiable(SessionClass) || !(SessionClass.prototype instanceof Session) ) {
|
||||
const e = new ErrorWithContext('Provided session class does not extend from @extollo/lib.Session');
|
||||
const e = new ErrorWithContext('Provided session class does not extend from @extollo/lib.Session')
|
||||
e.context = {
|
||||
config_key: 'server.session.driver',
|
||||
configKey: 'server.session.driver',
|
||||
class: SessionClass.toString(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user