helene
Version: 
Real-time Web Apps for Node.js
145 lines • 5.42 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebSocketTransport = exports.WebSocketTransportEvents = void 0;
const utils_1 = require("../../utils");
const socket_io_1 = require("socket.io");
const client_node_1 = require("../client-node");
var WebSocketTransportEvents;
(function (WebSocketTransportEvents) {
    WebSocketTransportEvents["WEBSOCKET_SERVER_ERROR"] = "websocket:server:error";
})(WebSocketTransportEvents || (exports.WebSocketTransportEvents = WebSocketTransportEvents = {}));
class WebSocketTransport {
    server;
    wss;
    options = {
        path: utils_1.HELENE_WS_PATH,
    };
    constructor(server, origins, opts) {
        this.server = server;
        Object.assign(this.options, opts ?? {});
        this.wss = new socket_io_1.Server(this.server.httpTransport.http, {
            ...this.options,
            connectionStateRecovery: {
                maxDisconnectionDuration: 2 * 60 * 1000,
                skipMiddlewares: true,
                ...this.options?.connectionStateRecovery,
            },
            cors: {
                credentials: true,
                origin: origins ?? '*',
                ...this.options?.cors,
            },
        });
        this.wss.use((socket, next) => {
            if (!this.server.acceptConnections) {
                console.log('Helene: Connection Refused');
                return next(new Error('Helene: Connection Refused'));
            }
            next();
        });
        this.wss.on(utils_1.WebSocketEvents.CONNECTION, this.handleConnection);
        this.wss.on(utils_1.WebSocketEvents.ERROR, (error) => server.emit(WebSocketTransportEvents.WEBSOCKET_SERVER_ERROR, error));
    }
    handleConnection = (socket) => {
        const node = new client_node_1.ClientNode(this.server, socket, undefined, undefined, this.server.rateLimit);
        node.setId(socket.handshake.query.uuid);
        this.server.addClient(node);
        this.server.emit(utils_1.ServerEvents.CONNECTION, node);
        node.setTrackingProperties(socket);
        socket.on('disconnect', this.handleClose(node));
        socket.on('error', (error) => this.server.emit(utils_1.ServerEvents.SOCKET_ERROR, socket, error));
        socket.on('message', this.handleMessage(node));
    };
    handleClose = (node) => () => {
        node.close();
        this.server.deleteClient(node);
    };
    handleMessage = (node) => async (data) => {
        try {
            node.heartbeat.messageReceived();
            const parsedData = utils_1.Presentation.decode(data);
            if (parsedData.type === utils_1.PayloadType.SETUP) {
                node.setId(parsedData.uuid);
                this.server.addClient(node);
                this.server.emit(utils_1.ServerEvents.CONNECTION, node);
            }
            if (parsedData.type !== utils_1.PayloadType.METHOD)
                return;
            await this.execute(parsedData, node);
        }
        catch (error) {
            return node.error({
                message: utils_1.Errors.PARSE_ERROR,
            });
        }
    };
    async execute(payload, node) {
        if (node.limiter && !node.limiter.tryRemoveTokens(1)) {
            return node.error({
                uuid: payload.uuid,
                message: utils_1.Errors.RATE_LIMIT_EXCEEDED,
                method: payload.method,
            });
        }
        const uuid = payload?.uuid ? { uuid: payload.uuid } : null;
        const method = this.server.methods.get(payload.method);
        if (!method)
            return node.error({
                uuid: payload.uuid,
                message: utils_1.Errors.METHOD_NOT_FOUND,
                method: payload.method,
            });
        if (method.isProtected && !node.authenticated)
            return node.error({
                uuid: payload.uuid,
                message: utils_1.Errors.METHOD_FORBIDDEN,
                method: payload.method,
            });
        try {
            const methodPromise = method.exec(payload.params, node);
            if (payload.void)
                return;
            const response = await methodPromise;
            return node.result({
                uuid: payload.uuid,
                method: payload.method,
                result: response,
            });
        }
        catch (error) {
            console.error(error);
            if (payload.void)
                return;
            if (error instanceof utils_1.PublicError) {
                return node.error({
                    message: error.message,
                    ...uuid,
                });
            }
            if (error instanceof utils_1.SchemaValidationError) {
                return node.error({
                    message: error.message,
                    errors: error.errors,
                    ...uuid,
                });
            }
            return node.error({
                message: utils_1.Errors.INTERNAL_ERROR,
                ...uuid,
            });
        }
    }
    close() {
        return new Promise(resolve => {
            if (!this.wss)
                return resolve();
            this.server.allClients.forEach(node => {
                if (node.socket)
                    node.close();
            });
            resolve();
        });
    }
}
exports.WebSocketTransport = WebSocketTransport;
//# sourceMappingURL=websocket-transport.js.map