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.
106 lines
3.3 KiB
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)
|
|
}
|
|
}
|
|
}
|