Setup eslint and enforce rules
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2021-06-02 22:36:25 -05:00
parent 82e7a1f299
commit 1d5056b753
149 changed files with 6104 additions and 3114 deletions

View File

@@ -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'
}

View File

@@ -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?