You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lib/src/http/routing/SocketRouteBuilder.ts

106 lines
3.3 KiB

import {StateEvent, WebSocketBus} from '../../support/bus'
import {constructable, Constructable, Instantiable, TypedDependencyKey} from '../../di'
import {Awaitable, Collection, JSONState} from '../../util'
import {Request} from '../lifecycle/Request'
export type SocketEventHandler<TState extends JSONState> = {
eventClass: Instantiable<StateEvent<TState>>,
handler: Constructable<(state: TState) => Awaitable<void>>,
}
/**
* Helper class for building websocket APIs using first-class route syntax.
* This is returned by the `Route.socket(...)` method, so you'll probably want
* to use that.
*
* @see Route#socket
* @example
* ```ts
* Route.socket('/ws/endpoint')
* .connected(MyCtrl, (ctrl: MyCtrl) => ctrl.connect)
* .event(MyEvent, MyCtrl, (ctrl: MyCtrl) => ctrl.myHandler)
* ```
*/
export class SocketRouteBuilder {
public static get(): SocketRouteBuilder {
return new SocketRouteBuilder()
}
/** Handlers that should be registered with any new socket connections. */
protected handlers: Collection<SocketEventHandler<any>> = new Collection()
/** Callback to execute when a new connection is opened. */
protected connectionCallback?: Constructable<(ws: WebSocketBus) => Awaitable<void>>
/**
* Register a callback to execute each time a new socket is opened.
* This can be used to perform basic setup/validation/authentication tasks.
*
* @example
* ```ts
* Route.socket('/ws/endpoint')
* .connected(MyCtrl, (ctrl: MyCtrl) => ctrl.connect)
* ```
*
* @param key
* @param selector
*/
connected<TKey>(
key: TypedDependencyKey<TKey>,
selector: (x: TKey) => (ws: WebSocketBus) => Awaitable<void>,
): this {
this.connectionCallback = constructable<TKey>(key)
.tap(inst => Function.prototype.bind.call(selector(inst), inst as any) as ((ws: WebSocketBus) => Awaitable<void>))
return this
}
/**
* Register a `StateEvent` listener on the socket.
*
* @example
* ```ts
* Route.socket('/ws/endpoint')
* .event(MyEvent, MyCtrl, (ctrl: MyCtrl) => ctrl.myHandler)
* ```
*
* @see StateEvent
* @param eventClass
* @param key
* @param selector
*/
event<TState extends JSONState, TKey>(
eventClass: Instantiable<StateEvent<TState>>,
key: TypedDependencyKey<TKey>,
selector: (x: TKey) => (state: TState) => Awaitable<void>,
): this {
const handler = constructable<TKey>(key)
.tap(inst => Function.prototype.bind.call(selector(inst), inst as any) as ((state: TState) => Awaitable<void>))
this.handlers.push({
eventClass,
handler,
})
return this
}
/**
* Attaches event listeners & initial callback to the given socket. This is used
* by as the handler for new connections.
*
* @see Route#socket
* @param request
* @param ws
*/
async build(request: Request, ws: WebSocketBus): Promise<void> {
await this.handlers.promiseMap(handler => {
ws.subscribe(handler.eventClass, (event: StateEvent<JSONState>) => {
return handler.handler.apply(request)(event.getState())
})
})
if ( this.connectionCallback ) {
await this.connectionCallback.apply(request)(ws)
}
}
}