Add foreground service, some cleanup, and start websocket server

This commit is contained in:
2022-07-13 21:35:18 -05:00
parent 4d7769de56
commit 6476416c67
12 changed files with 158 additions and 43 deletions

View File

@@ -107,7 +107,7 @@ export class Request extends ScopedContainer implements DataContainer {
protected clientRequest: IncomingMessage,
/** The native Node.js response. */
protected serverResponse: ServerResponse,
protected serverResponse?: ServerResponse,
) {
super(Container.getContainer())
this.registerSingletonInstance(Request, this)

View File

@@ -66,7 +66,7 @@ export class Response {
public readonly request: Request,
/** The native Node.js ServerResponse. */
protected readonly serverResponse: ServerResponse,
protected readonly serverResponse?: ServerResponse,
) { }
protected get logging(): Logging {
@@ -173,6 +173,11 @@ export class Response {
*/
public sendHeaders(): this {
this.logging.verbose(`Sending headers...`)
if ( !this.serverResponse ) {
throw new ErrorWithContext('Unable to send headers: Response has no underlying connection.', {
suggestion: 'This usually means the Request was created by an alternative server, like WebsocketServer. You should use that server to handle the request.',
})
}
const headers = {} as any
const setCookieHeaders = this.cookies.getSetCookieHeaders()
@@ -220,8 +225,14 @@ export class Response {
* @param data
*/
public async write(data: string | Buffer | Uint8Array | Readable): Promise<void> {
this.logging.verbose(`Writing headers & data to response... (destroyed? ${this.serverResponse.destroyed})`)
this.logging.verbose(`Writing headers & data to response... (destroyed? ${!this.serverResponse || this.serverResponse.destroyed})`)
return new Promise<void>((res, rej) => {
if ( !this.serverResponse ) {
throw new ErrorWithContext('Unable to write response: Response has no underlying connection.', {
suggestion: 'This usually means the Request was created by an alternative server, like WebsocketServer. You should use that server to handle the request.',
})
}
if ( this.responseEnded || this.serverResponse.destroyed ) {
throw new ErrorWithContext('Tried to write to Response after lifecycle ended.')
}
@@ -274,7 +285,7 @@ export class Response {
* or the connection has been destroyed.
*/
public canSend(): boolean {
return !(this.responseEnded || this.serverResponse.destroyed)
return !(this.responseEnded || !this.serverResponse || this.serverResponse.destroyed)
}
/**
@@ -286,7 +297,7 @@ export class Response {
}
this.sentHeaders = true
this.serverResponse.end()
this.serverResponse?.end()
return this
}

View File

@@ -109,6 +109,10 @@ ${Object.keys(context).map(key => ` - ${key} : ${JSON.stringify(context[key])
return 'It looks like this route relies on the security framework. Is the route you are accessing inside a middleware (e.g. SessionAuthMiddleware)?'
} else if ( this.thrownError.message.startsWith('Unable to resolve schema for validator') ) {
return 'Make sure the directory in which the interface file is located is listed in extollo.cc.zodify in package.json, and that it ends with the proper .type.ts suffix.'
} else if ( this.thrownError instanceof ErrorWithContext ) {
if ( typeof this.thrownError.context.suggestion === 'string' ) {
return this.thrownError.context.suggestion
}
}
return ''

View File

@@ -136,6 +136,14 @@ export class Route<TReturn extends ResponseObject, THandlerParams extends unknow
return new Route(method, endpoint)
}
/**
* Create a new WebSocket route on the given endpoint.
* @param endpoint
*/
public static socket(endpoint: string): Route<void, [void]> {
return new Route<void, [void]>('ws', endpoint)
}
/**
* Create a new GET route on the given endpoint.
*/
@@ -188,10 +196,14 @@ export class Route<TReturn extends ResponseObject, THandlerParams extends unknow
protected displays: Collection<{stage: 'pre'|'post'|'handler', display: string}> = new Collection()
constructor(
protected method: HTTPMethod | HTTPMethod[],
protected method: 'ws' | HTTPMethod | HTTPMethod[],
protected route: string,
) {}
public isForWebSocket(): boolean {
return this.method === 'ws'
}
/**
* Set a programmatic name for this route.
* @param name
@@ -212,6 +224,10 @@ export class Route<TReturn extends ResponseObject, THandlerParams extends unknow
* Get the string-form methods supported by the route.
*/
public getMethods(): HTTPMethod[] {
if ( this.method === 'ws' ) {
return []
}
if ( !Array.isArray(this.method) ) {
return [this.method]
}
@@ -250,10 +266,14 @@ export class Route<TReturn extends ResponseObject, THandlerParams extends unknow
* @param method
* @param potential
*/
public match(method: HTTPMethod, potential: string): boolean {
if ( Array.isArray(this.method) && !this.method.includes(method) ) {
public match(method: 'ws' | HTTPMethod, potential: string): boolean {
if ( method === 'ws' && !this.isForWebSocket() ) {
return false
} else if ( !Array.isArray(this.method) && this.method !== method ) {
} else if ( method !== 'ws' && this.isForWebSocket() ) {
return false
} else if ( method !== 'ws' && Array.isArray(this.method) && !this.method.includes(method) ) {
return false
} else if ( method !== 'ws' && !Array.isArray(this.method) && this.method !== method ) {
return false
}