@interopio/gateway-server
Version:
[](https://www.npmjs.com/package/@interopio/gateway-server)
508 lines (500 loc) • 15.9 kB
JavaScript
;
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