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