@furystack/websocket-api
Version:
HTTP Api FuryStack package
101 lines • 4.69 kB
JavaScript
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