UNPKG

@interopio/gateway-server

Version:

[![npm version](https://img.shields.io/npm/v/@interopio/gateway-server.svg)](https://www.npmjs.com/package/@interopio/gateway-server)

508 lines (500 loc) 15.9 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/logger.ts function getLogger2(name) { return GatewayLogging.getLogger(`gateway.server.${name}`); } var GatewayLogging; var init_logger = __esm({ "src/logger.ts"() { "use strict"; GatewayLogging = __toESM(require("@interopio/gateway/logging/core"), 1); } }); // src/mesh/ws/broker/core.ts var core_exports = {}; __export(core_exports, { default: () => core_default }); function broadcastNodeAdded(nodes, newSocket, newNodeId) { Object.entries(nodes.nodes).forEach(([nodeId, socket]) => { if (nodeId !== newNodeId) { newSocket.send(codec.encode({ type: "node-added", "node-id": newNodeId, "new-node": nodeId })); socket.send(codec.encode({ type: "node-added", "node-id": nodeId, "new-node": newNodeId })); } }); } function broadcastNodeRemoved(nodes, removedNodeId) { Object.entries(nodes.nodes).forEach(([nodeId, socket]) => { if (nodeId !== removedNodeId) { socket.send(codec.encode({ type: "node-removed", "node-id": nodeId, "removed-node": removedNodeId })); } }); } function onOpen(connectedNodes, logPrefix) { logger.info(`${logPrefix}connection accepted`); } function onClose(connectedNodes, logPrefix, code, reason) { logger.info(`${logPrefix}connection closed [${code}](${reason})`); const nodeIds = connectedNodes.sockets[logPrefix]; if (nodeIds) { delete connectedNodes.sockets[logPrefix]; for (const nodeId of nodeIds) { delete connectedNodes.nodes[nodeId]; } for (const nodeId of nodeIds) { broadcastNodeRemoved(connectedNodes, nodeId); } } } function processMessage(connectedNodes, socket, key, msg) { switch (msg.type) { case "hello": { const nodeId = msg["node-id"]; connectedNodes.nodes[nodeId] = socket; connectedNodes.sockets[key] = connectedNodes.sockets[key] ?? []; connectedNodes.sockets[key].push(nodeId); logger.info(`[${key}] node ${nodeId} added.`); broadcastNodeAdded(connectedNodes, socket, nodeId); break; } case "bye": { const nodeId = msg["node-id"]; delete connectedNodes[nodeId]; logger.info(`[${key}] node ${nodeId} removed.`); broadcastNodeRemoved(connectedNodes, nodeId); break; } case "data": { const sourceNodeId = msg.from; const targetNodeId = msg.to; if ("all" === targetNodeId) { Object.entries(connectedNodes.nodes).forEach(([nodeId, socket2]) => { if (nodeId !== sourceNodeId) { socket2.send(codec.encode(msg)); } }); } else { const socket2 = connectedNodes.nodes[targetNodeId]; if (socket2) { socket2.send(codec.encode(msg)); } else { logger.warn(`unable to send to node ${targetNodeId} message ${JSON.stringify(msg)}`); } } break; } default: { logger.warn(`[${key}] ignoring unknown message ${JSON.stringify(msg)}`); break; } } } function onMessage(connectedNodes, socket, logPrefix, msg) { try { const decoded = codec.decode(msg); if (logger.enabledFor("debug")) { logger.debug(`${logPrefix}processing msg ${JSON.stringify(decoded)}`); } processMessage(connectedNodes, socket, logPrefix, decoded); } catch (ex) { logger.error(`${logPrefix}unable to process message`, ex); } } async function create(environment) { const connectedNodes = { nodes: {}, sockets: {} }; logger.info(`mesh server is listening`); return async ({ socket, handshake }) => { const logPrefix = handshake.logPrefix; onOpen(connectedNodes, logPrefix); socket.on("error", (err) => { logger.error(`${logPrefix}websocket error: ${err}`, err); }); socket.on("message", (data, _isBinary) => { if (Array.isArray(data)) { data = Buffer.concat(data); } onMessage(connectedNodes, socket, logPrefix, data); }); socket.on("close", (code, reason) => { onClose(connectedNodes, logPrefix, code, reason); }); }; } var import_gateway, GatewayEncoders, logger, codec, core_default; var init_core = __esm({ "src/mesh/ws/broker/core.ts"() { "use strict"; init_logger(); import_gateway = require("@interopio/gateway"); GatewayEncoders = import_gateway.IOGateway.Encoding; logger = getLogger2("mesh.ws.broker"); codec = GatewayEncoders.transit({ keywordize: /* @__PURE__ */ new Map([ ["/type", "*"], ["/message/body/type", "*"], ["/message/origin", "*"], ["/message/receiver/type", "*"], ["/message/source/type", "*"], ["/message/body/type", "*"] ]) }); core_default = create; } }); // src/gateway/ent/index.ts var index_exports = {}; __export(index_exports, { configure_logging: () => configure_logging, create: () => create2 }); module.exports = __toCommonJS(index_exports); // src/gateway/ent/logging.ts var import_node_util = require("node:util"); var LogInfoAdapter = class { #event; #parsed; #timestamp; #output; constructor(event) { this.#event = event; } parsed() { if (this.#parsed === void 0) { let err = void 0; let vargs = this.#event.data; if (this.#event.data[0] instanceof Error) { err = this.#event.data[0]; vargs = vargs.slice(1); } const msg = (0, import_node_util.format)(this.#event.message, ...vargs); this.#parsed = { err, msg }; } return this.#parsed; } get time() { return this.#event.time; } get level() { return this.#event.level; } get namespace() { return this.#event.name; } get file() { return void 0; } get line() { return void 0; } get message() { return this.parsed().msg; } get stacktrace() { return this.parsed().err; } get timestamp() { if (this.#timestamp === void 0) { this.#timestamp = this.time.toISOString(); } return this.#timestamp; } get output() { if (this.#output === void 0) { const err = this.parsed().err; const stacktrace = err ? ` ${err.stack ?? err}` : ""; this.#output = `${this.timestamp} ${this.level.toUpperCase()} [${this.namespace}] - ${this.message}${stacktrace}`; } return this.#output; } }; function toLogConfig(config) { let level = "info"; if (config?.level) { if (config.level === "fatal") { level = "error"; } else if (config.level !== "report") { level = config.level; } } const result = { level }; const appenderFn = config?.appender; if (appenderFn) { result.appender = (event) => { appenderFn(new LogInfoAdapter(event)); }; } return result; } // src/gateway/ent/server.ts var import_gateway_server = require("@interopio/gateway-server"); var ServerDelegate = class { constructor(config) { this.config = config; } server; async connect(cb) { if (!this.server) { throw new Error(`not started`); } const client = await this.server.gateway.connect((c, m) => cb(c, m)); return client; } info() { return this.server?.gateway.info(); } async start() { if (!this.server) { this.server = await import_gateway_server.GatewayServer.Factory(this.config); } return this; } async stop() { await this.server?.close(); delete this.server; return this; } }; // src/gateway/ent/config.ts function toMetricsFilters(legacy) { if (legacy) { const publishers = legacy.publishers.map((publisher) => { return { identity: publisher.publisher, metrics: publisher.metrics }; }); const non_matched = legacy["non-matched"]; return { publishers, non_matched }; } } function toMetricsPublisherConfig(legacy) { const filters = toMetricsFilters(legacy?.filters); const conflation = toConflation(legacy?.conflation); return { ...legacy, filters, conflation }; } function toConflation(legacy) { if (legacy) { return { interval: legacy.interval }; } } function toMetricsConfig(legacy) { const metrics = { publishers: [] }; legacy?.publishers?.forEach((publisher) => { if (typeof publisher === "string") { if (publisher === "rest") { metrics.publishers.push("rest"); if (legacy.rest) { const conf = { ...legacy.rest }; const userAgent = conf["user-agent"]; delete conf["user-agent"]; const headers = { ...conf.headers, ...userAgent ? { "user-agent": userAgent } : {} }; delete conf.headers; metrics.rest = { endpoint: conf.endpoint, headers, ...toMetricsPublisherConfig(conf) }; metrics.rest["publishFn"] ??= "@interopio/gateway-server/metrics/publisher/rest"; } } else if (publisher === "file") { metrics.publishers.push("file"); if (legacy.file) { const conf = { ...legacy.file }; const status = conf["skip-status"] === void 0 ? true : !conf["skip-status"]; delete conf["skip-status"]; metrics.file = { location: conf.location, status, ...toMetricsPublisherConfig(conf) }; metrics.file["publishFn"] ??= "@interopio/gateway/metrics/publisher/file"; } } else { } } else { const configuration = { ...publisher.configuration }; const splitSize = configuration["split-size"]; delete configuration["split-size"]; const file = publisher["file"]; const custom = { split_size: splitSize, publisher: { file, configuration }, ...toMetricsPublisherConfig(configuration) }; metrics.publishers.push(custom); } }); if (legacy?.filters) { metrics.filters = toMetricsFilters(legacy?.filters); } return metrics; } function trimTrailingSlash(url) { return url?.endsWith("/") ? url.slice(0, -1) : url; } function toCluster(legacy) { if (legacy?.directory) { const legacyDirectory = legacy.directory; const config = { endpoint: legacy?.["endpoint"] }; if (legacyDirectory.type === "rest") { let directory = void 0; if (config.endpoint === void 0) { config.endpoint = trimTrailingSlash(legacyDirectory.config?.directory_uri); } else { directory = { uri: trimTrailingSlash(legacyDirectory.config?.directory_uri) }; } if (legacyDirectory.config?.announce_interval) { directory ??= {}; directory.interval = Number(legacyDirectory.config.announce_interval); } if (directory !== void 0) { config.directory = directory; } return config; } else if (legacyDirectory.type === "static") { config.directory = { members: legacyDirectory.members ?? [] }; return config; } } } function toMeshConfig(legacy) { const mesh = {}; if (legacy?.configuration?.node_id) mesh.node = legacy.configuration.node_id; if (legacy?.type === "broker") mesh.broker = { endpoint: legacy.broker?.endpoint ?? "<unresolved>" }; if (legacy?.type === "p2p") { const cluster = toCluster(legacy.p2p); if (cluster) { mesh.cluster = cluster; } } return mesh; } function toAuthenticationConfig(legacy) { const authentication = { available: [] }; if (legacy?.default) { authentication.default = legacy.default; } const as = legacy?.available; as.forEach((a) => { if (a === "basic" || a === "oauth2" || legacy?.[a]?.["authenticator"] !== void 0) { authentication.available.push(a); if (legacy?.[a] !== void 0) { authentication[a] = legacy[a]; } } }); return authentication; } function toServerConfig(config) { const gateway = { route: config.route, maxConnections: config.limits?.max_connections, origins: config.security?.origin_filters, authorize: config["authorize"] ?? { access: "permitted" }, clients: config["clients"] ?? { inactive_seconds: 0, buffer_size: 100 }, contexts: { lifetime: "retained", visibility: [ { context: /___channel___.+/, restrictions: "cluster" }, { context: /T42\..+/, restrictions: "local" } ] }, methods: { visibility: [ { method: /T42\..+/, restrictions: "local" } ] }, peers: { visibility: [ { domain: "context", restrictions: "cluster" }, { domain: "agm", restrictions: "local" } ] }, metrics: { publishers: [] } }; if (config.authentication !== void 0) { if (config.authentication.token_ttl) gateway.token = { ttl: config.authentication?.token_ttl }; if (config.authentication.available || config.authentication.default) gateway.authentication = toAuthenticationConfig(config.authentication); } if (config["globals"]) { gateway.globals = config["globals"]; } if (config["contexts"]) { gateway.contexts = config["contexts"]; } if (config["methods"]) { gateway.methods = config["methods"]; } if (config["peers"]) { gateway.peers = config["peers"]; } if (config.cluster?.enabled) { gateway.mesh = toMeshConfig(config.cluster); } if (config.metrics?.publishers) gateway.metrics = toMetricsConfig(config.metrics); return { port: config.port ?? 3434, host: config.ip ?? config["host"], memory: config["memory"], app: async (configurer) => { if (config.cluster?.embedded_broker?.enabled === true) { configurer.socket({ path: config.cluster.embedded_broker.route ?? "/mesh-broker", options: { authorize: config.cluster.embedded_broker["authorize"] ?? { access: "permitted" } }, factory: async (env) => { if (gateway.mesh?.broker?.endpoint === "<unresolved>") { gateway.mesh.broker.endpoint = env.endpoint; } const delegate = (await Promise.resolve().then(() => (init_core(), core_exports))).default; return await delegate(env); } }); } }, gateway }; } // src/gateway/ent/index.ts var import_gateway2 = require("@interopio/gateway"); function create2(config) { return new ServerDelegate(toServerConfig(config)); } function configure_logging(config) { import_gateway2.IOGateway.Logging.configure(toLogConfig(config)); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { configure_logging, create }); //# sourceMappingURL=gateway-ent.cjs.map