UNPKG

@furystack/websocket-api

Version:
101 lines 4.69 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; import { AggregatedError, IdentityContext } from '@furystack/core'; import { Injectable, Injected } from '@furystack/inject'; import { HttpUserContext, ServerManager } from '@furystack/rest-service'; import { using } from '@furystack/utils'; import { URL } from 'url'; import ws, { WebSocketServer } from 'ws'; import { WebSocketApiSettings } from './websocket-api-settings.js'; /** * A WebSocket API implementation for FuryStack */ let WebSocketApi = class WebSocketApi { socket = new WebSocketServer({ noServer: true }); clients = new Map(); isInitialized = false; async init() { if (!this.isInitialized) { this.socket.on('connection', (websocket, msg) => { const connectionInjector = this.injector.createChild({ owner: msg }); const httpUserContext = connectionInjector.getInstance(HttpUserContext); connectionInjector.setExplicitInstance({ getCurrentUser: () => httpUserContext.getCurrentUser(msg), isAuthorized: (...roles) => httpUserContext.isAuthorized(msg, ...roles), isAuthenticated: () => httpUserContext.isAuthenticated(msg), }, IdentityContext); this.clients.set(websocket, { injector: connectionInjector, message: msg, ws: websocket }); websocket.on('message', (message) => { this.execute(message, msg, connectionInjector, websocket); }); websocket.on('close', () => { this.clients.delete(websocket); }); }); await this.serverManager .getOrCreate({ port: this.settings.port, hostName: this.settings.host }) .then((server) => { server.server.on('upgrade', (request, socket, head) => { const { pathname } = new URL(request.url, `http://${request.headers.host}`); if (pathname === this.settings.path) { this.socket.handleUpgrade(request, socket, head, (websocket) => { this.socket.emit('connection', websocket, request); }); } }); }); } else { throw Error('WebSocket API is already initialized'); } } async [Symbol.asyncDispose]() { this.socket.clients.forEach((client) => client.close()); this.socket.clients.forEach((client) => client.terminate()); await new Promise((resolve, reject) => this.socket.close((err) => (err ? reject(err) : resolve()))); } async broadcast(callback) { const errors = []; await Promise.all([...this.clients.values()] .filter((client) => client.ws.readyState === ws.OPEN) .map(async (client) => { try { await callback(client); } catch (error) { errors.push(error); } })); if (errors.length) { throw new AggregatedError('The Broadcast operation encountered some errors', errors); } } execute(data, request, injector, socket) { const Action = this.settings.actions.find((a) => a.canExecute({ data, request, socket })); if (Action) { using(injector.getInstance(Action), (action) => { void action.execute({ data, request, socket }); }); } } }; __decorate([ Injected(WebSocketApiSettings), __metadata("design:type", WebSocketApiSettings) ], WebSocketApi.prototype, "settings", void 0); __decorate([ Injected(ServerManager), __metadata("design:type", ServerManager) ], WebSocketApi.prototype, "serverManager", void 0); WebSocketApi = __decorate([ Injectable({ lifetime: 'scoped' }) ], WebSocketApi); export { WebSocketApi }; //# sourceMappingURL=websocket-api.js.map