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,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?
|
||||
|
||||
Reference in New Issue
Block a user