helene
Version:
Real-time Web Apps for Node.js
182 lines • 7.48 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpTransport = exports.HttpTransportEvents = void 0;
const utils_1 = require("../../utils");
const cors_1 = __importDefault(require("cors"));
const express_1 = __importDefault(require("express"));
const http_1 = __importDefault(require("http"));
const client_node_1 = require("../client-node");
const ejson_1 = require("../../ejson");
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
const isString_1 = __importDefault(require("lodash/isString"));
var HttpTransportEvents;
(function (HttpTransportEvents) {
HttpTransportEvents["HTTP_LISTENING"] = "http:listening";
HttpTransportEvents["HTTP_SERVER_ERROR"] = "http:server:error";
HttpTransportEvents["HTTP_SERVER_CLOSED"] = "http:server:closed";
})(HttpTransportEvents || (exports.HttpTransportEvents = HttpTransportEvents = {}));
class HttpTransport {
server;
http;
express;
constructor(server, origins, limit) {
this.server = server;
this.express = (0, express_1.default)();
this.http = http_1.default.createServer(this.express);
this.express.use('/__h', express_1.default.urlencoded({ extended: true }));
this.express.use('/__h', express_1.default.text({ type: 'text/plain' }));
if (limit) {
const limiter = (0, express_rate_limit_1.default)({
...(limit === true
? { windowMs: 60 * 1000, max: 120 }
: { windowMs: limit.interval, max: limit.max }),
standardHeaders: true,
legacyHeaders: false,
});
this.express.use('/__h', limiter);
}
if (origins)
this.setCORS(origins);
if (this.server.requestListener) {
this.http.on(utils_1.ServerEvents.REQUEST, this.server.requestListener);
}
this.express.post('/__h', this.requestHandler);
this.authMiddleware = this.authMiddleware.bind(this);
this.contextMiddleware = this.contextMiddleware.bind(this);
}
setCORS(origins) {
this.express.use((0, cors_1.default)({
credentials: true,
origin: function (origin, callback) {
if (!origin || origins.includes(origin))
return callback(null, true);
callback(new Error('Not allowed by CORS'));
},
}));
}
async getServerContext(clientNode, context = {}) {
const token = clientNode.req.headers[utils_1.TOKEN_HEADER_KEY];
if ((0, isString_1.default)(token) && token.length && token !== 'undefined') {
context.token = token.replace('Bearer ', '');
}
if (this.server.auth instanceof Function) {
let result = this.server.auth.call(clientNode, context ?? {});
result = result instanceof Promise ? await result : result;
return result;
}
return false;
}
requestHandler = async (req, res) => {
let uuid;
let payload;
try {
const transport = req.body && (0, isString_1.default)(req.body) ? ejson_1.EJSON.parse(req.body) : {};
if (!transport.payload) {
return res.send(utils_1.Presentation.encode({
type: utils_1.PayloadType.ERROR,
message: utils_1.Errors.INVALID_REQUEST,
}));
}
payload = transport.payload;
const method = this.server.getMethod(payload.method);
const clientId = req.headers[utils_1.CLIENT_ID_HEADER_KEY];
const clientNode = new client_node_1.ClientNode(this.server, null, req, res);
clientNode.uuid = clientId;
if (!method) {
return res.send(utils_1.Presentation.encode({
type: utils_1.PayloadType.ERROR,
message: utils_1.Errors.METHOD_NOT_FOUND,
method: payload.method,
}));
}
const serverContext = await this.getServerContext(clientNode, transport.context);
clientNode.authenticated = Boolean(serverContext);
clientNode.setContext(serverContext);
if (method.isProtected && !clientNode.authenticated) {
return res.send(utils_1.Presentation.encode({
type: utils_1.PayloadType.ERROR,
message: utils_1.Errors.METHOD_FORBIDDEN,
method: payload.method,
}));
}
uuid = payload?.uuid ? { uuid: payload.uuid } : null;
const result = await method.exec(payload.params, clientNode);
res.send(utils_1.Presentation.encode({
type: utils_1.PayloadType.RESULT,
uuid: payload.uuid,
method: payload.method,
result,
}));
}
catch (error) {
console.error(error);
if (payload?.void)
return;
if (error instanceof utils_1.PublicError) {
return res.send(utils_1.Presentation.encode({
type: utils_1.PayloadType.ERROR,
message: error.message,
...uuid,
}));
}
if (error instanceof utils_1.SchemaValidationError) {
return res.send(utils_1.Presentation.encode({
type: utils_1.PayloadType.ERROR,
message: error.message,
errors: error.errors,
...uuid,
}));
}
return res.send(utils_1.Presentation.encode({
type: utils_1.PayloadType.ERROR,
message: utils_1.Errors.INTERNAL_ERROR,
...uuid,
}));
}
};
async contextMiddleware(req, res, next) {
const clientNode = new client_node_1.ClientNode(this.server, null, req, res);
req.context = await this.getServerContext(clientNode);
next();
}
async authMiddleware(req, res, next) {
const clientNode = new client_node_1.ClientNode(this.server, null, req, res);
const serverContext = await this.getServerContext(clientNode);
if (serverContext === false) {
res.status(403);
return res.end('403 Forbidden');
}
req.context = serverContext;
next();
}
static(path, catchAll) {
const middleware = express_1.default.static(path);
this.express.use('/', middleware);
if (catchAll) {
this.express.use(/(.*)/, middleware);
}
}
/**
* Need to close WebSocket server first.
*/
close() {
return new Promise(resolve => {
if (!this.http) {
this.server.emit(HttpTransportEvents.HTTP_SERVER_CLOSED);
return resolve();
}
this.http.closeAllConnections();
this.http.close(() => {
this.http.unref();
this.http = undefined;
this.server.emit(HttpTransportEvents.HTTP_SERVER_CLOSED);
resolve();
});
});
}
}
exports.HttpTransport = HttpTransport;
//# sourceMappingURL=http-transport.js.map