@interopio/bridge
Version:
`io.Bridge` is a distributed scalable application that provides connectivity between `io.Connect` platforms.
1,597 lines (1,561 loc) • 70.6 kB
JavaScript
// src/utils/uuid.ts
import { nanoid } from "nanoid";
function newUUID() {
return nanoid();
}
// src/utils/network.ts
import { networkInterfaces } from "node:os";
import { ADDRCONFIG } from "node:dns";
import { lookup, lookupService } from "node:dns/promises";
// src/utils/internal/IpAddressUtil.ts
var DOT = ".".charCodeAt(0);
var COLON = ":".charCodeAt(0);
function textToNumericFormatV4(address) {
const result = new Uint8Array(4);
const len = address.length;
if (len == 0 || len > 15) {
return;
}
let currentValue = 0;
let currentByte = 0;
let newByte = true;
for (let i = 0; i < len; i++) {
const ch = address.charCodeAt(i);
if (ch === DOT) {
if (newByte || currentValue < 0 || currentValue > 255 || currentByte > 3) {
return;
}
result[currentByte++] = currentValue & 255;
currentValue = 0;
newByte = true;
} else {
const digit = ch - 48;
if (digit < 0) {
return;
}
currentValue *= 10;
currentValue += digit;
newByte = false;
}
}
if (newByte || currentValue < 0 || currentValue >= 1 << (4 - currentByte) * 8) {
return;
}
switch (currentByte) {
case 0:
result[0] = currentValue >> 24 & 255;
//fall through
case 1:
result[1] = currentValue >> 16 & 255;
//fall through
case 2:
result[2] = currentValue >> 8 & 255;
//fall through
case 3:
result[3] = currentValue >> 0 & 255;
}
return result;
}
// src/utils/network.ts
import { readFileSync } from "node:fs";
var IpAddress = class {
address;
family;
_hostname;
constructor(host, address, family) {
this._hostname = host;
this.address = address;
this.family = family;
}
get addressBytes() {
if (this.family === 4) {
return textToNumericFormatV4(this.address);
}
}
async getHost() {
if (!this._hostname) {
this._hostname = (await lookupService(this.address, 0)).hostname;
}
return this._hostname;
}
async getIpAddress(nonLoopback = false) {
const hints = nonLoopback ? ADDRCONFIG : void 0;
return await lookup(this.address, { family: this.family, hints });
}
};
function isLoopback(address) {
return address.family === 4 && address.address.startsWith("127.") || address.family === 6 && address.address.startsWith("::1");
}
async function getByName(host) {
const { address, family } = await lookup(host, { family: 4 });
if (family !== 4 && family !== 6) {
throw new Error("unexpected address type");
}
return new IpAddress(void 0, address, family);
}
function getNetworkInterfaces() {
return Object.entries(networkInterfaces()).map(([name, addresses]) => {
return {
name,
loopback: addresses.some((info) => info.internal),
addresses: addresses.map((info) => {
return new IpAddress(void 0, info.address, info.family === "IPv6" ? 6 : 4);
})
};
});
}
function soMaxConn() {
let somaxconn;
if (process.platform === "win32") {
somaxconn = 200;
} else if (process.platform === "darwin") {
somaxconn = 128;
} else {
somaxconn = 4096;
}
try {
somaxconn = parseInt(readFileSync("/proc/sys/net/core/somaxconn").toString());
} catch (error) {
}
return somaxconn;
}
var SOMAXCONN = soMaxConn();
// src/cluster/Address.ts
async function toIpAddress(address) {
return await getByName(scopedHost(address));
}
async function fromHostname(hostname, port) {
return new AddressImpl(hostname, await getByName(hostname), port);
}
function fromIpAddress(address, port) {
return new AddressImpl(void 0, address, port);
}
var AddressImpl = class {
type;
host;
port;
address;
constructor(hostname, ipAddress, port) {
const type = ipAddress.family;
if (type !== 4 && type !== 6) {
throw new Error("unexpected address type");
}
this.type = type;
this.host = hostname ?? ipAddress.address;
this.port = port;
this.address = ipAddress;
}
};
function scopedHost(address) {
if (address.type === 6) {
return `[${address.host}]`;
}
return address.host;
}
// src/config/Properties.ts
var BOOLEAN = new class {
convert(value) {
if (typeof value === "boolean") {
return value;
} else if (typeof value === "string") {
return value.toLowerCase() === "true";
}
throw new Error("Cannot convert value to boolean: " + value);
}
}();
var STRING = new class {
convert(value) {
if (value === void 0) {
throw new Error("Cannot convert undefined ");
}
return String(value);
}
}();
var NUMBER = new class {
convert(value) {
if (value === void 0) {
throw new Error("Cannot convert undefined ");
}
if (typeof value === "object") {
throw new Error("Cannot convert object/array to number: " + value);
}
return Number(value);
}
}();
function property(name, converter) {
return { key: name, optional: true, converter };
}
// src/config/DiscoveryConfig.ts
var DefaultDiscoveryConfig = class {
discoveryStrategyConfigs = [];
discoveryServiceFactory;
get enabled() {
return this.discoveryStrategyConfigs.length > 0;
}
};
// src/logging.ts
import { IOGateway } from "@interopio/gateway";
function getLogger(name) {
return IOGateway.Logging.getLogger(`gateway.bridge.${name}`);
}
// src/kubernetes/KubernetesDiscoveryStrategyFactory.ts
import { stat } from "node:fs/promises";
import { lookup as lookup2 } from "node:dns/promises";
// src/kubernetes/KubernetesConfig.ts
import { readFileSync as readFileSync2 } from "node:fs";
// src/discovery/settings.ts
function getProperty(prefix, property2) {
let s = prefix;
if (!s.endsWith(".")) {
s += ".";
}
return s + property2.key;
}
function readProperty(prefix, property2) {
if (prefix) {
const p = getProperty(prefix, property2);
const v = process.env[p];
return v;
}
}
function getOrUndefined(properties, prefix, property2) {
return getOrDefault(properties, prefix, property2);
}
function getOrDefault(properties, prefix, property2, defaultValue) {
if (property2 === void 0) {
return defaultValue;
}
let value = readProperty(prefix, property2);
if (value === void 0) {
value = properties[property2.key];
}
if (value === void 0) {
return defaultValue;
}
return value;
}
// src/kubernetes/KubernetesProperties.ts
var KUBERNETES_ENV_PREFIX = "io.bridge.kubernetes";
var SERVICE_DNS = property("service-dns", STRING);
var SERVICE_NAME = property("service-name", STRING);
var SERVICE_LABEL_NAME = property("service-label-name", STRING);
var SERVICE_LABEL_VALUE = property("service-label-value", STRING);
var NAMESPACE = property("namespace", STRING);
var POD_LABEL_NAME = property("pod-label-name", STRING);
var POD_LABEL_VALUE = property("pod-label-value", STRING);
var EXPOSE_EXTERNALLY = property("expose-externally", BOOLEAN);
var SERVICE_PER_POD_LABEL_NAME = property("service-per-pod-label-name", STRING);
var SERVICE_PER_POD_LABEL_VALUE = property("service-per-pod-label-value", STRING);
var RESOLVE_NOT_READY_ADDRESSES = property("resolve-not-ready-addresses", BOOLEAN);
var KUBERNETES_MASTER_URL = property("kubernetes-master", STRING);
var KUBERNETES_API_TOKEN = property("api-token", STRING);
var SERVICE_PORT = property("service-port", NUMBER);
// src/kubernetes/KubernetesConfig.ts
function readFileContents(fileName) {
return readFileSync2(fileName, "utf-8").toString();
}
var KubernetesConfig = class {
serviceDns;
serviceName;
serviceLabelName;
serviceLabelValue;
namespace;
podLabelName;
podLabelValue;
resolveNotReadyAddresses;
exposeExternallyMode;
servicePerPodLabelName;
servicePerPodLabelValue;
kubernetesMasterUrl;
kubernetesApiToken;
servicePort;
fileContentsReader;
tokenProvider;
constructor(properties, fileContentsReader = readFileContents) {
this.fileContentsReader = fileContentsReader;
this.serviceDns = getOrDefault(properties, KUBERNETES_ENV_PREFIX, SERVICE_DNS);
this.serviceName = getOrDefault(properties, KUBERNETES_ENV_PREFIX, SERVICE_NAME);
this.serviceLabelName = getOrUndefined(properties, KUBERNETES_ENV_PREFIX, SERVICE_LABEL_NAME);
this.serviceLabelValue = getOrDefault(properties, KUBERNETES_ENV_PREFIX, SERVICE_LABEL_VALUE, "true");
this.resolveNotReadyAddresses = getOrDefault(properties, KUBERNETES_ENV_PREFIX, RESOLVE_NOT_READY_ADDRESSES, true);
this.podLabelName = getOrUndefined(properties, KUBERNETES_ENV_PREFIX, POD_LABEL_NAME);
this.podLabelValue = getOrUndefined(properties, KUBERNETES_ENV_PREFIX, POD_LABEL_VALUE);
this.exposeExternallyMode = this.getExposeExternallyMode(properties);
this.servicePerPodLabelName = getOrUndefined(properties, KUBERNETES_ENV_PREFIX, SERVICE_PER_POD_LABEL_NAME);
this.servicePerPodLabelValue = getOrUndefined(properties, KUBERNETES_ENV_PREFIX, SERVICE_PER_POD_LABEL_VALUE);
this.kubernetesMasterUrl = getOrDefault(properties, KUBERNETES_ENV_PREFIX, KUBERNETES_MASTER_URL, "https://kubernetes.default.svc");
this.tokenProvider = this.buildTokenProvider(properties);
this.servicePort = getOrDefault(properties, KUBERNETES_ENV_PREFIX, SERVICE_PORT, 0);
this.namespace = this.getNamespaceWithFallbacks(properties);
}
getExposeExternallyMode(properties) {
const exposeExternally = getOrUndefined(properties, KUBERNETES_ENV_PREFIX, EXPOSE_EXTERNALLY);
if (exposeExternally === void 0) {
return "auto";
} else if (exposeExternally) {
return "enabled";
} else {
return "disabled";
}
}
getNamespaceWithFallbacks(properties) {
let namespace = getOrDefault(properties, KUBERNETES_ENV_PREFIX, NAMESPACE);
if (!namespace) {
namespace = process.env["KUBERNETES_NAMESPACE"];
}
if (!namespace && this.mode === "kubernetes-api") {
namespace = this.readNamespace();
}
return namespace;
}
buildTokenProvider(properties) {
const apiToken = getOrUndefined(properties, KUBERNETES_ENV_PREFIX, KUBERNETES_API_TOKEN);
if (!apiToken && this.mode === "kubernetes-api") {
return () => readFileContents("/var/run/secrets/kubernetes.io/serviceaccount/token");
} else {
return () => apiToken;
}
}
readNamespace() {
return this.fileContentsReader("/var/run/secrets/kubernetes.io/serviceaccount/namespace");
}
get mode() {
if (this.serviceDns) {
return "dns-lookup";
}
return "kubernetes-api";
}
toString() {
return "KubernetesConfig: {service-dns: " + this.serviceDns + ", service-name: " + this.serviceName + ", service-port: " + this.servicePort + ", service-label-name: " + this.serviceLabelName + ", service-label-value: " + this.serviceLabelValue + ", namespace: " + this.namespace + ", pod-label-name: " + this.podLabelName + ", pod-label-value: " + this.podLabelValue + ", resolve-not-ready-addresses: " + this.resolveNotReadyAddresses + ", expose-externally-mode: " + this.exposeExternallyMode + ", service-per-pod-label-name: " + this.servicePerPodLabelName + ", service-per-pod-label-value: " + this.servicePerPodLabelValue + ", kubernetes-master-url: " + this.kubernetesMasterUrl + "}";
}
};
// src/kubernetes/KubernetesApiProvider.ts
var KubernetesApiEndpointSlicesProvider = class {
getEndpointsByServiceLabelUrlString(kubernetesMaster, namespace, param) {
return `${kubernetesMaster}/apis/discovery.k8s.io/v1/namespaces/${namespace}/endpointslices?${param}`;
}
getEndpointsByNameUrlString(kubernetesMaster, namespace, endpointName) {
return `${kubernetesMaster}/apis/discovery.k8s.io/v1/namespaces/${namespace}/endpointslices?labelSelector=kubernetes.io/service-name=${endpointName}`;
}
parseEndpoints(param) {
const endpoints = new Array();
for (const item of param.items) {
endpoints.push(...this.parseEndpointSlices(item));
}
return endpoints;
}
parseEndpointSlices(item) {
const addresses = new Array();
const endpointPort = this.extractPort(item);
for (const endpoint of item.endpoints) {
const ready = endpoint.conditions.ready;
for (const address of endpoint.addresses) {
addresses.push(new Endpoint(new EndpointAddress({ ip: address, port: endpointPort }), void 0, ready));
}
}
return addresses;
}
extractPort(item) {
for (const port of item.ports) {
const bridgeServicePort = port.name;
if (bridgeServicePort && bridgeServicePort === "io-bridge") {
const servicePort = port.port;
if (servicePort !== void 0) {
return servicePort;
}
}
}
if (item.ports.length === 1) {
const port = item.ports[0];
const servicePort = port.port;
if (servicePort !== void 0) {
return servicePort;
}
}
}
};
// src/kubernetes/KubernetesClient.ts
var EndpointAddress = class {
targetRefName;
address;
constructor(address, targetRefName) {
this.address = address;
this.targetRefName = targetRefName;
}
get ip() {
return this.address.ip;
}
get port() {
return this.address.port;
}
};
var Endpoint = class {
privateAddress;
publicAddress = void 0;
ready;
additionalProperties = /* @__PURE__ */ new Map();
constructor(privateAddress, publicAddress = void 0, ready, additionalProperties = /* @__PURE__ */ new Map()) {
this.privateAddress = privateAddress;
this.publicAddress = publicAddress;
this.ready = ready;
this.additionalProperties = additionalProperties;
}
};
var READ_TIMEOUT_SECONDS = 10;
function isReady(podItemStatus) {
for (const containerStatus of podItemStatus.containerStatuses) {
if (containerStatus.ready !== true) {
return false;
}
}
return true;
}
function containerPort(container) {
const ports = container.ports;
if (ports.length > 0) {
const port = ports[0];
return port.containerPort;
}
}
function extractContainerPort(item) {
const containers = item.spec.containers;
if (containers.length === 1) {
const container = containers[0];
return containerPort(container);
} else {
for (const container of containers) {
if (container.name === "io-bridge") {
return containerPort(container);
}
}
}
}
function parsePodsList(podList) {
const addresses = new Array();
for (const item of podList.items) {
const podName = item.metadata.name;
const status = item.status;
const ip = status.podIP;
if (ip) {
const port = extractContainerPort(item);
addresses.push(new Endpoint(new EndpointAddress({ ip, port }, podName), void 0, isReady(status)));
}
}
return addresses;
}
function getLabelSelectorParameter(labelNames, labelValues) {
const labelNameArray = labelNames.split(",");
const labelValueArray = labelValues.split(",");
const selectorList = [];
for (let i = 0; i < labelNameArray.length; i++) {
selectorList[i] = `${labelNameArray[i]}=${labelValueArray[i]}`;
}
return `labelSelector=${selectorList.join(",")}`;
}
var KubernetesClient = class {
namespace;
kubernetesMaster;
exposeExternallyMode;
tokenProvider;
servicePerPodLabelName;
servicePerPodLabelValue;
clientTopologyIntentTracker;
apiProvider;
/*testing*/
constructor(namespace, kubernetesMaster, exposeExternallyMode, tokenProvider, servicePerPodLabelName, servicePerPodLabelValue, clientTopologyIntentTracker, apiProvider) {
this.namespace = namespace;
this.kubernetesMaster = kubernetesMaster;
this.exposeExternallyMode = exposeExternallyMode;
this.tokenProvider = tokenProvider;
this.servicePerPodLabelName = servicePerPodLabelName;
this.servicePerPodLabelValue = servicePerPodLabelValue;
if (clientTopologyIntentTracker) {
clientTopologyIntentTracker.init();
}
this.apiProvider = apiProvider ?? new KubernetesApiEndpointSlicesProvider();
}
start() {
}
destroy() {
if (this.clientTopologyIntentTracker) {
this.clientTopologyIntentTracker.destroy();
}
}
/**
* Returns POD addresses in the specified namespace.
*
*/
async endpoints() {
try {
const url = `${this.kubernetesMaster}/api/v1/namespaces/${this.namespace}/pods`;
return this.enrichWithPublicAddresses(parsePodsList(await this.callGet(url)));
} catch (e) {
return this.handleUnknownError(e);
}
}
/**
* Retrieves POD addresses for all services in the specified namespace filtered by service labels and values.
* @param serviceLabels comma separated list of service labels
* @param serviceLabelValues comma separated list of service label values
* @return all POD addresses from the specified namespace filtered by labels
*/
async endpointsByServiceLabel(serviceLabels, serviceLabelValues) {
try {
const param = getLabelSelectorParameter(serviceLabels, serviceLabelValues);
const url = this.apiProvider.getEndpointsByServiceLabelUrlString(this.kubernetesMaster, this.namespace, param);
return this.enrichWithPublicAddresses(this.apiProvider.parseEndpoints(this.callGet(url)));
} catch (e) {
return this.handleUnknownError(e);
}
}
/**
* Retrieves POD addresses from the specified namespace and given endpointName.
* @param endpointName endpoint name
* @return all POD addresses from the specified namespace and the given endpointName
*/
async endpointsByName(endpointName) {
try {
const url = this.apiProvider.getEndpointsByNameUrlString(this.kubernetesMaster, this.namespace, endpointName);
return this.enrichWithPublicAddresses(this.apiProvider.parseEndpoints(await this.callGet(url)));
} catch (e) {
return this.handleUnknownError(e);
}
}
/**
* Retrieves POD addresses for all services in the specified namespace filtered by pod labels and values.
* @param podLabels comma separated list of pod labels
* @param podLabelValues comma separated list of pod label values
* @return all POD addresses from the specified namespace filtered by the labels
*/
async endpointsByPodLabel(podLabels, podLabelValues) {
try {
const param = getLabelSelectorParameter(podLabels, podLabelValues);
const url = `${this.kubernetesMaster}/api/v1/namespaces/${this.namespace}/pods?${param}`;
return this.enrichWithPublicAddresses(parsePodsList(await this.callGet(url)));
} catch (e) {
return this.handleUnknownError(e);
}
}
enrichWithPublicAddresses(endpoints) {
if (this.exposeExternallyMode === "disabled") {
return endpoints;
}
}
handleUnknownError(e) {
return [];
}
async callGet(url) {
const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort(`request to ${url} timed out`);
}, READ_TIMEOUT_SECONDS * 1e3);
try {
const response = await fetch(url, {
signal: controller.signal,
headers: [["Authorization", `Bearer ${this.tokenProvider()}`]]
}).catch((err) => {
throw err;
});
return await response.json();
} finally {
clearTimeout(timeout);
}
}
};
// src/kubernetes/KubernetesEndpointResolver.ts
var DNS_RETRY = 5;
var KubernetesEndpointResolver = class {
logger;
constructor(logger) {
this.logger = logger;
}
async start() {
}
async close() {
}
async mapAddress(address) {
if (!address) {
return;
}
try {
let retry = 0;
try {
return await getByName(address);
} catch (e) {
if (retry < DNS_RETRY) {
retry++;
} else {
throw e;
}
}
} catch (e) {
}
}
};
// src/config/NamedDiscoveryConfig.ts
var NamedDiscoveryConfig = class {
tag;
_enabled = false;
constructor(tag) {
this.tag = tag;
}
setEnabled(enabled) {
this._enabled = enabled;
return this;
}
get enabled() {
return this._enabled;
}
};
// src/config/KubernetesConfig.ts
var KubernetesConfig2 = class extends NamedDiscoveryConfig {
constructor() {
super("kubernetes");
}
};
// src/config/Config.ts
var AutoDetectionConfig = class {
enabled = true;
};
var DefaultMulticastConfig = class {
enabled = false;
group = "239.1.2.3";
port = 34567;
};
var DefaultTcpIpConfig = class {
enabled = false;
members = [];
};
var JoinConfig = class {
_multicast = new DefaultMulticastConfig();
_tcpIp = new DefaultTcpIpConfig();
_kubernetes = new KubernetesConfig2();
_discovery = new DefaultDiscoveryConfig();
_autoDetection = new AutoDetectionConfig();
get discovery() {
return this._discovery;
}
get multicast() {
return this._multicast;
}
get autoDetectionEnabled() {
return this._autoDetection.enabled && !this._multicast.enabled && !this._tcpIp.enabled && !this._kubernetes.enabled && !this._discovery.enabled;
}
};
var NetworkConfig = class _NetworkConfig {
static DEFAULT_PORT = 8383;
port = _NetworkConfig.DEFAULT_PORT;
reuseAddress;
publicAddress;
join = new JoinConfig();
constructor() {
this.reuseAddress = process.platform !== "win32";
}
};
var CorsConfig = class {
#allowOrigin = void 0;
set allowOrigin(value) {
if (value !== void 0) {
if (value === "*") {
this.#allowOrigin = value;
} else if (Array.isArray(value)) {
this.#allowOrigin = value;
} else {
this.#allowOrigin = value.split(",");
}
}
}
get allowOrigin() {
return this.#allowOrigin;
}
disabled;
allowCredentials = false;
};
var ServerConfig = class {
port = void 0;
host = void 0;
cors = new CorsConfig();
auth = {
type: "none",
basic: {
realm: "io.Bridge"
}
};
wsPingInterval = 3e4;
// 30 seconds
};
var MeshConfig = class {
timeout;
};
var GatewayConfig = class {
enabled;
contexts = { lifetime: "retained" };
};
var Config = class {
properties = {};
network = new NetworkConfig();
license;
server;
mesh;
gateway;
constructor() {
this.server = new ServerConfig();
this.mesh = new MeshConfig();
this.gateway = new GatewayConfig();
}
get(name) {
const value = this.properties[name];
return value;
}
set(name, value) {
this.properties[name] = value;
return this;
}
};
// src/discovery/SimpleDiscoveryNode.ts
var SimpleDiscoveryNode = class {
privateAddress;
publicAddress;
properties;
constructor(privateAddress, publicAddress, properties) {
this.privateAddress = privateAddress;
this.publicAddress = publicAddress ?? privateAddress;
this.properties = properties ?? /* @__PURE__ */ new Map();
}
};
// src/kubernetes/KubernetesApiEndpointResolver.ts
function buildKubernetesClient(config, clusterTopologyIntentTracker) {
return new KubernetesClient(config.namespace, config.kubernetesMasterUrl, config.exposeExternallyMode, config.tokenProvider, config.servicePerPodLabelName, config.servicePerPodLabelValue, clusterTopologyIntentTracker);
}
var KubernetesApiEndpointResolver = class _KubernetesApiEndpointResolver extends KubernetesEndpointResolver {
client;
serviceName;
port = 0;
serviceLabel;
serviceLabelValue;
podLabel;
podLabelValue;
resolveNotReadyAddresses;
static of(logger, config, tracker) {
return new _KubernetesApiEndpointResolver(
logger,
buildKubernetesClient(config, tracker),
config.serviceName,
config.servicePort,
config.serviceLabelName,
config.serviceLabelValue,
config.podLabelName,
config.podLabelValue,
config.resolveNotReadyAddresses
);
}
/*testing*/
constructor(logger, client, serviceName, port = 0, serviceLabel, serviceLabelValue, podLabel, podLabelValue, resolveNotReadyAddresses) {
super(logger);
this.client = client;
this.serviceName = serviceName;
this.port = port;
this.serviceLabel = serviceLabel;
this.serviceLabelValue = serviceLabelValue;
this.podLabel = podLabel;
this.podLabelValue = podLabelValue;
this.resolveNotReadyAddresses = resolveNotReadyAddresses;
}
async resolve() {
if (this.serviceName) {
return await this.getSimpleDiscoveryNodes(await this.client.endpointsByName(this.serviceName));
} else if (this.serviceLabel) {
return await this.getSimpleDiscoveryNodes(await this.client.endpointsByServiceLabel(this.serviceLabel, this.serviceLabelValue));
} else if (this.podLabel) {
return await this.getSimpleDiscoveryNodes(await this.client.endpointsByPodLabel(this.podLabel, this.podLabelValue));
}
return await this.getSimpleDiscoveryNodes(await this.client.endpoints());
}
async getSimpleDiscoveryNodes(endpoints) {
const discoveredNodes = new Array();
for (const endpoint of endpoints) {
await this.addAddress(discoveredNodes, endpoint);
}
return discoveredNodes;
}
async addAddress(discoveredNodes, endpoint) {
if (this.resolveNotReadyAddresses === true || endpoint.ready) {
const privateAddress = await this.createAddress(endpoint.privateAddress, this.resolvePort.bind(this));
const publicAddress = await this.createAddress(endpoint.publicAddress, this.resolvePortPublic.bind(this));
discoveredNodes.push(new SimpleDiscoveryNode(privateAddress, publicAddress, /* @__PURE__ */ new Map()));
}
}
async createAddress(address, portResolver) {
if (address === void 0) {
return void 0;
}
const ip = address.ip;
const ipAddress = await this.mapAddress(ip);
const port = portResolver(address);
return fromIpAddress(ipAddress, port);
}
resolvePort(address) {
if (this.port > 0) {
return this.port;
}
if (address.port) {
return address.port;
}
return NetworkConfig.DEFAULT_PORT;
}
resolvePortPublic(address) {
if (address.port) {
return address.port;
}
if (this.port > 0) {
return this.port;
}
return NetworkConfig.DEFAULT_PORT;
}
};
// src/kubernetes/KubernetesDiscoveryStrategy.ts
var KubernetesDiscoveryStrategy = class {
logger;
endpointResolver;
constructor(logger, properties, intentTracker) {
this.logger = logger;
const config = new KubernetesConfig(properties);
this.logger.info(`${config}`);
this.endpointResolver = KubernetesApiEndpointResolver.of(this.logger, config, intentTracker);
this.logger.info(`Kubernetes Discovery activated with mode: ${config.mode}`);
}
async start() {
await this.endpointResolver.start();
}
async discover() {
return void 0;
}
async close() {
await this.endpointResolver.close();
}
};
// src/kubernetes/KubernetesDiscoveryStrategyFactory.ts
var PROPERTY_DEFINITIONS = [
SERVICE_DNS,
SERVICE_NAME,
SERVICE_LABEL_NAME,
SERVICE_LABEL_VALUE,
NAMESPACE,
POD_LABEL_NAME,
POD_LABEL_VALUE,
RESOLVE_NOT_READY_ADDRESSES,
EXPOSE_EXTERNALLY,
KUBERNETES_MASTER_URL,
KUBERNETES_API_TOKEN,
SERVICE_PORT
];
var KubernetesDiscoveryStrategyFactory = class {
tokenPath;
priority = DiscoveryStrategyPriorities.PLATFORM;
constructor(tokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token") {
this.tokenPath = tokenPath;
}
async defaultKubernetesMasterReachable() {
try {
await lookup2("kubernetes.default.svc");
return true;
} catch (e) {
return false;
}
}
async tokenFileExists() {
try {
await stat(this.tokenPath);
return true;
} catch (e) {
return false;
}
}
async isAutoDetectionApplicable() {
return await this.tokenFileExists() && await this.defaultKubernetesMasterReachable();
}
newDiscoveryStrategy(node, logger, properties) {
const tracker = node?.properties.get("internal.discovery.cluster.topology.intent.tracker");
return new KubernetesDiscoveryStrategy(logger, properties, tracker);
}
getConfigurationProperties() {
return PROPERTY_DEFINITIONS;
}
};
// src/discovery/multicast/MulticastDiscoveryStrategy.ts
import { createSocket } from "node:dgram";
// src/discovery/multicast/MulticastProperties.ts
var PORT = property("port", NUMBER);
var GROUP = property("group", STRING);
// src/discovery/multicast/MulticastDiscoveryStrategy.ts
var DATA_OUTPUT_BUFFER_SIZE = 64 * 1024;
var DEFAULT_MULTICAST_PORT = 34567;
var DEFAULT_MULTICAST_GROUP = "239.1.2.3";
var SOCKET_TIME_TO_LIVE = 255;
function multicastDiscoverySender(discoveryNode, socket, group, port) {
let multicastMemberInfo;
if (discoveryNode) {
const address = discoveryNode.publicAddress;
multicastMemberInfo = { host: address.host, port: address.port };
}
const msg = Buffer.from(JSON.stringify(multicastMemberInfo));
return () => {
socket.send(msg, port, group, (error) => {
console.error(error);
});
};
}
function multicastDiscoveryReceiver(socket) {
return () => {
return new Promise((resolve, reject) => {
socket.on("message", (message, remote) => {
resolve(JSON.parse(message));
});
socket.on("error", (err) => {
reject(err);
});
});
};
}
var MulticastDiscoveryStrategy = class {
logger;
properties;
discoveryNode;
isClient;
discoverySender;
discoveryReceiver;
sendIntervalId;
socket;
constructor(discoveryNode, logger, properties) {
this.logger = logger;
this.properties = properties;
this.discoveryNode = discoveryNode;
}
async initializeMulticastSocket() {
const port = getOrDefault(this.properties, "", PORT, DEFAULT_MULTICAST_PORT);
if (port < 0 || port > 65535) {
throw new Error("port number must be between 0 and 65535");
}
const group = getOrDefault(this.properties, "", GROUP, DEFAULT_MULTICAST_GROUP);
this.socket = createSocket({
type: "udp4",
reuseAddr: true,
recvBufferSize: DATA_OUTPUT_BUFFER_SIZE,
sendBufferSize: DATA_OUTPUT_BUFFER_SIZE
});
if (this.discoveryNode) {
const address = await toIpAddress(this.discoveryNode.privateAddress);
if (!isLoopback(address)) {
this.socket.setMulticastInterface(address.address);
}
}
this.socket.bind(port, () => {
this.socket.setMulticastTTL(SOCKET_TIME_TO_LIVE);
this.socket.addMembership(group);
});
this.discoverySender = multicastDiscoverySender(this.discoveryNode, this.socket, group, port);
this.discoveryReceiver = multicastDiscoveryReceiver(this.socket);
if (this.discoveryNode == null) {
this.isClient = true;
}
}
async discover() {
const multicastMemberInfo = await this.discoveryReceiver();
if (multicastMemberInfo) {
const list = [];
try {
const discoveryNode = new SimpleDiscoveryNode(await fromHostname(multicastMemberInfo.host, multicastMemberInfo.port));
list.push(discoveryNode);
} catch (e) {
if (this.logger.enabledFor("trace")) {
this.logger.debug(e.message);
}
}
return list;
}
}
async start() {
await this.initializeMulticastSocket();
if (!this.isClient) {
this.sendIntervalId = setInterval(() => {
this.discoverySender();
}, 2e3);
}
}
async close() {
if (this.sendIntervalId) {
clearInterval(this.sendIntervalId);
delete this.sendIntervalId;
}
return new Promise((resolve, reject) => {
if (this.socket) {
this.socket.close(() => {
resolve();
});
} else {
resolve();
}
});
}
};
// src/discovery/multicast/MulticastDiscoveryStrategyFactory.ts
var PROPERTY_DEFINITIONS2 = [
GROUP,
PORT
];
var MulticastDiscoveryStrategyFactory = class {
priority = DiscoveryStrategyPriorities.UNKNOWN;
newDiscoveryStrategy(discoveryNode, logger, prop) {
return new MulticastDiscoveryStrategy(discoveryNode, logger, prop);
}
async isAutoDetectionApplicable() {
return false;
}
getConfigurationProperties() {
return PROPERTY_DEFINITIONS2;
}
};
// src/discovery/index.ts
var DefaultDiscoveryServiceFactory = class {
newDiscoveryService(settings) {
return new DefaultDiscoveryService(settings);
}
};
function verifyNoUnknownProperties(mappedProperties, allProperties) {
const unknownProperties = Object.keys(allProperties).filter((key) => !mappedProperties.hasOwnProperty(key));
if (unknownProperties.length > 0) {
throw new Error(`Unknown properties: ${unknownProperties.join(", ")} on discovery strategy`);
}
}
function prepareProperties(properties, propertyDefinitions) {
const mappedProperties = {};
for (const propertyDefinition of propertyDefinitions) {
const propertyKey = propertyDefinition.key;
if (properties[propertyKey] == void 0) {
if (!propertyDefinition.optional) {
throw new Error(`Missing required property: ${propertyKey} on discovery strategy`);
}
continue;
}
const value = properties[propertyKey];
const converter = propertyDefinition.converter;
const mappedValue = converter.convert(value);
mappedProperties[propertyKey] = mappedValue;
}
verifyNoUnknownProperties(mappedProperties, properties);
return mappedProperties;
}
var DefaultDiscoveryService = class {
settings;
strategies = [];
constructor(settings) {
this.settings = settings;
}
async start() {
this.strategies.push(...await this.loadDiscoveryStrategies());
for (const strategy of this.strategies) {
await strategy.start();
}
}
async discover() {
const discoveryNodes = /* @__PURE__ */ new Set();
for (const strategy of this.strategies) {
const candidates = await strategy.discover();
if (candidates) {
for (const candidate of candidates) {
if (this.validateCandidate(candidate)) {
discoveryNodes.add(candidate);
}
}
}
}
return discoveryNodes;
}
async close() {
for (const strategy of this.strategies) {
await strategy.close();
}
}
validateCandidate(candidate) {
return true;
}
async loadDiscoveryStrategies() {
const discoveryStrategyConfigs = this.settings.conf.discoveryStrategyConfigs;
const factories = this.collectFactories(discoveryStrategyConfigs);
const discoveryStrategies = new Array();
for (const config of discoveryStrategyConfigs) {
const discoveryStrategy = await this.buildDiscoveryStrategy(config, factories);
discoveryStrategies.push(discoveryStrategy);
}
const logger = this.settings.logger;
if (discoveryStrategies.length == 0 && this.settings.auto) {
logger.debug(`Discovery auto-detection enabled, trying to detect discovery strategy`);
const autoDetectedFactory = await this.detectDiscoveryStrategyFactory(factories);
if (autoDetectedFactory) {
logger.debug(`Auto-detected discovery strategy: ${autoDetectedFactory.constructor.name}`);
discoveryStrategies.push(autoDetectedFactory.newDiscoveryStrategy(await this.settings.node, logger, {}));
} else {
logger.debug(`No discovery strategy auto-detected`);
}
}
return discoveryStrategies;
}
collectFactories(strategyConfigs) {
const knownFactories = [new MulticastDiscoveryStrategyFactory(), new KubernetesDiscoveryStrategyFactory()];
const factories = new Array();
for (const factory of knownFactories) {
factories.push(factory);
}
for (const config of strategyConfigs) {
const factory = config.discoveryStrategyFactory;
if (factory) {
factories.push(factory);
}
}
return factories;
}
async buildDiscoveryStrategy(config, factories) {
const discoveryNode = await this.settings.node;
const logger = this.settings.logger;
for (const factory of factories) {
if (factory.constructor.name == config.discoveryStrategyFactory.constructor.name) {
const properties = prepareProperties(config.properties, factory.getConfigurationProperties());
return factory.newDiscoveryStrategy(discoveryNode, logger, properties);
}
}
throw new Error("DiscoveryStrategyFactory not found");
}
async detectDiscoveryStrategyFactory(factories) {
let highestPriorityFactory;
for (const factory of factories) {
try {
if (await factory.isAutoDetectionApplicable()) {
if (highestPriorityFactory === void 0 || factory.priority > highestPriorityFactory.priority) {
highestPriorityFactory = factory;
}
}
} catch (error) {
}
}
return highestPriorityFactory;
}
};
var DiscoveryStrategyPriorities = {
UNKNOWN: 0,
PLATFORM: 20,
CUSTOM: 50
};
// src/version/MemberVersion.ts
function parse(version) {
const pattern = /^(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?<patch>0|[1-9]\d*)(?:-(?<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/g;
const { groups } = pattern.exec(version);
const major = parseInt(groups["major"]);
const minor = parseInt(groups["minor"]);
const patch = parseInt(groups["patch"]);
return [major, minor, patch];
}
var MemberVersion = class {
major;
minor;
patch;
get isUnknown() {
return this.major === 0 && this.minor === 0 && this.patch === 0;
}
toString() {
return `${this.major}.${this.minor}.${this.patch}`;
}
constructor(major, minor, patch) {
this.major = major;
this.minor = minor;
this.patch = patch;
}
};
var UNKNOWN_VERSION = new MemberVersion(0, 0, 0);
function parseVersion(version) {
if (!version || version.startsWith("0.0.0")) {
return UNKNOWN_VERSION;
}
return new MemberVersion(...parse(version));
}
// package.json
var package_default = {
name: "@interopio/bridge",
version: "0.0.6-beta.0",
license: "see license in license.md",
author: "interop.io",
homepage: "https://docs.interop.io/bridge",
keywords: [
"io.bridge",
"bridge",
"io.connect",
"glue42",
"interop.io"
],
type: "module",
exports: {
"./package.json": "./package.json",
".": {
default: "./dist/index.js"
},
"./main": {
import: {
types: "./types/main.d.ts",
default: "./dist/main.js"
}
}
},
bin: {
bridge: "./bin/bridge.js"
},
scripts: {
test: "mocha test --recursive",
"build:types": "tsc --declaration --emitDeclarationOnly --outDir types",
"build:main": "esbuild src/main.mts --outdir=dist --bundle --packages=external --sourcemap --platform=node --format=esm --target=node20.15",
"build:index": "esbuild src/index.ts --outdir=dist --target=node20.15",
build: "npm run build:main && npm run build:index"
},
dependencies: {
"@interopio/gateway-server": "^0.10.0-beta.0",
dotenv: "^17.2.2",
jsrsasign: "^11.1.0",
nanoid: "^5.0.9"
},
devDependencies: {
"@types/jsrsasign": "^10.5.15",
"@types/ws": "^8.18.1",
"rand-seed": "^3.0.0"
},
engines: {
node: ">= 20.15 || >=22.2.0 || >=24.0"
}
};
// src/instance/AddressPicker.ts
import { createServer } from "node:net";
async function createAddress(addressDef, port) {
if (addressDef.host) {
return { type: addressDef.ipAddress.family, host: addressDef.host, port, address: addressDef.ipAddress };
}
return { type: addressDef.ipAddress.family, host: await addressDef.ipAddress.getHost(), port, address: addressDef.ipAddress };
}
function createNetServer(logger, host, range) {
const ports = portRange(range);
return new Promise((resolve, reject) => {
let server = createServer({}, () => {
});
server.on("error", (err) => {
if (err["code"] === "EADDRINUSE") {
logger.debug(`port ${err["port"]} already in use on address ${err["address"]}`);
const { value: port } = ports.next();
if (port) {
logger.info(`retry starting server on port ${port} and host ${host ?? "<unspecified>"}`);
server.close();
server.listen(port, host);
} else {
logger.warn(`all configured port(s) ${range} are in use. closing...`);
server.close();
reject(err);
}
} else {
logger.error(`server error: ${err.message}`, err);
reject(err);
}
});
server.on("listening", () => {
const info = server.address();
logger.info(`listening on ${info.address}:${info.port}`);
resolve(server);
});
try {
const { value: port } = ports.next();
server.listen(port, host);
} catch (e) {
logger.error(`error starting socket server`, e);
reject(e instanceof Error ? e : new Error(`listen failed: ${e}`));
}
});
}
var InterfaceAddress = class {
address;
host;
constructor(address, host) {
this.address = address;
this.host = host;
}
};
var AddressDef = class extends InterfaceAddress {
ipAddress;
port;
constructor(ipAddress, host, port) {
super(ipAddress.address, host);
this.ipAddress = ipAddress;
this.port = port ?? 0;
}
};
var DefaultAddressPicker = class {
#logger;
#config;
#publicAddressConfig;
#publicAddress;
#bindAddress;
server;
#port;
constructor(config, logger) {
this.#logger = logger;
this.#config = config;
this.#port = config.network.port;
this.#publicAddressConfig = config.network.publicAddress;
}
async pickAddress() {
if (this.#publicAddress || this.#bindAddress) {
return;
}
const publicAddressDef = await this.getPublicAddressByPortSearch();
this.#publicAddress = this.#bindAddress;
}
getPublicAddressFor(endpoint) {
return this.#publicAddress;
}
getServers() {
return /* @__PURE__ */ new Map([["member", this.server]]);
}
async getPublicAddressByPortSearch() {
const bindAddressDef = await this.pickAddressDef();
this.server = await createNetServer(this.#logger, "0.0.0.0", bindAddressDef.port === 0 ? this.#port : bindAddressDef.port);
const port = this.server.address().port;
this.#bindAddress = await createAddress(bindAddressDef, port);
return this.getPublicAddress(port);
}
async getPublicAddress(port) {
let address = this.#publicAddressConfig;
if (address) {
address = address.trim();
if (!address) {
throw new Error("Public address is empty");
} else if ("127.0.0.1" === address) {
return this.pickLoopbackAddressDef(address, port);
} else {
const ipAddress = await getByName(address);
return new AddressDef(ipAddress, address, port);
}
}
}
async pickAddressDef() {
let addressDef = this.pickInterfaceAddressDef();
if (addressDef) {
}
if (addressDef === void 0) {
addressDef = await this.pickLoopbackAddressDef();
}
return addressDef;
}
pickInterfaceAddressDef() {
const interfaces = this.getInterfaces();
if (interfaces) {
const address = this.pickMatchingAddress(interfaces);
if (address) {
return address;
}
}
return this.pickMatchingAddress();
}
getInterfaces() {
return [];
}
async pickLoopbackAddressDef(host, defaultPort) {
const address = await getByName("127.0.0.1");
return new AddressDef(address, host, defaultPort);
}
match(ipAddress, interfaces) {
for (const iface of interfaces) {
if (ipAddress.address === iface.address) {
return iface;
}
}
return void 0;
}
getMatchingAddress(ipAddress, interfaces) {
if (interfaces) {
return this.match(ipAddress, interfaces);
} else {
return ipAddress;
}
}
pickMatchingAddress(interfaces) {
const preferIPv4 = true;
const networkInterfaces2 = getNetworkInterfaces();
let matchingAddress;
for (const ni of networkInterfaces2) {
if (!interfaces && ni.loopback) {
continue;
}
for (const ipAddress of ni.addresses) {
if (preferIPv4 && ipAddress.family === 6) {
continue;
}
const address = this.getMatchingAddress(ipAddress, interfaces);
break;
}
}
return matchingAddress;
}
};
var PORT_RANGE_MATCHER = /^(\d+|(0x[\da-f]+))(-(\d+|(0x[\da-f]+)))?$/i;
function validPort(port) {
if (port > 65535) throw new Error(`bad port ${port}`);
return port;
}
function* portRange(port) {
if (typeof port === "string") {
for (const portRange2 of port.split(",")) {
const trimmed = portRange2.trim();
const matchResult = PORT_RANGE_MATCHER.exec(trimmed);
if (matchResult) {
const start = parseInt(matchResult[1]);
const end = parseInt(matchResult[4] ?? matchResult[1]);
for (let i = validPort(start); i < validPort(end) + 1; i++) {
yield i;
}
} else {
throw new Error(`'${portRange2}' is not a valid port or range.`);
}
}
} else {
yield validPort(port);
}
}
// src/instance/BridgeNode.ts
import gatewayServer from "@interopio/gateway-server";
// src/license/types.ts
var licenseValidationResults = {
TRIAL_LICENSE_EXPIRED: { licenseType: "trial", expired: true, fatal: true },
TRIAL_LICENSE_VALID: { licenseType: "trial", expired: false, fatal: false },
PAID_LICENSE_EXPIRED: { licenseType: "paid", expired: true, fatal: false },
PAID_LICENSE_VALID: { licenseType: "paid", expired: false, fatal: false },
NO_LICENSE: { licenseType: "none", expired: false, fatal: true }
};
// src/license/LicenseValidator.ts
import { KJUR } from "jsrsasign";
var LicenseError = class extends Error {
licensePayload;
constructor(message, licensePayload) {
super(message);
this.licensePayload = licensePayload;
}
};
var LicenseEvaluator = class {
tokenResultMap = {
"trial-false": licenseValidationResults.TRIAL_LICENSE_VALID,
"trial-true": licenseValidationResults.TRIAL_LICENSE_EXPIRED,
"paid-false": licenseValidationResults.PAID_LICENSE_VALID,
"paid-true": licenseValidationResults.PAID_LICENSE_EXPIRED
};
validationKey;
constructor(validationKey2) {
if (!validationKey2 || typeof validationKey2 !== "string") {
throw new Error(`Validation key must be a non-empty string`);
}
this.validationKey = validationKey2;
}
decrypt(token) {
if (KJUR.jws.JWS.verifyJWT(token, this.validationKey, { alg: ["RS256"] })) {
return KJUR.jws.JWS.parse(token).payloadObj;
} else {
throw new LicenseError("invalid jwt token", token);
}
}
validateBasicTokenData(token) {
const decoded = this.decrypt(token);
if (["paid", "trial"].indexOf(decoded.type) === -1) {
throw new LicenseError("Invalid license type", token);
}
return decoded;
}
getLicenseStatus(token, now = /* @__PURE__ */ new Date()) {
try {
const license = this.validateBasicTokenData(token);
const isExpired = license.expiration < now.getTime();
const licenseValidationResult = this.tokenResultMap[`${license.type}-${isExpired}`];
return {
...licenseValidationResult,
license
};
} catch (e) {
return {
...licenseValidationResults.NO_LICENSE,
error: e.message
};
}
}
};
var LicenseValidator = class {
licenseHelper;
logger;
constructor({ validationKey: validationKey2, logger }) {
this.licenseHelper = new LicenseEvaluator(validationKey2);
this.logger = logger ?? getLogger("LicenseValidator");
}
validate(licenseKeyString, options) {
if (typeof licenseKeyString !== "string") {
throw new LicenseError("Invalid license key format", licenseKeyString);
}
const licenseStatus = this.licenseHelper.getLicenseStatus(licenseKeyString);
this.printLicenseMessage(licenseStatus, options?.logSuccessMessages ?? true);
if (licenseStatus.fatal) {
throw new LicenseError("License validation failed", licenseStatus.license);
}
}
printLicenseMessage(licenseStatus, logSuccessMessages) {
if (!licenseStatus || licenseStatus.licenseType === "none") {
this.logger.error("This license is invalid. Please contact us at sales@interop.io to get a valid license");
return;
}
const message = [
licenseStatus.licenseType === "paid" ? `This is a paid license that was issued for client ` : `This is a trial license that was issued for `,
licenseStatus.license.organization.displayName,
licenseStatus.expired ? `, but it has expired at ` : ` and is valid until `,
new Date(licenseStatus.license.expiration).toString(),
"."
].join("");
if (licenseStatus.fatal) {
this.logger.error(message);
} else if (licenseStatus.expired) {
this.logger.warn(message);
} else if (logSuccessMessages) {
this.logger.info(message);
}
}
};
// src/license/BridgeLicenseValidator.ts
var validationKey = `-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyyZrtwcQCMfyOkZDcjxZ
+07BrvgcAvoQETF+ho8VLfaDxgNPZd6Q7jZEfrtv0+wn0pxx8pUDeBh8mCYvkwmb
9tkFH47GJuDhWasNgyp4ugRX4bzoPV3SnHsBR4KJ/F76kBbike6sdIfO2lYGZ36s
LJPHV7epOYsfhoc4343/vOxEPvNbwacbXWl4NsCGev4qUmWhe9iMpb/JPd3o4hIU
SR31AmR2xoh3BkAJc2q/cSeQK6Kn9nZYocyW367+7FOvYsrOYotsuUASp0OWp+Lh
WGfR7F6d016+u6kbvLvcceGztiP1u25QmPNCUmw49cTatthiEwwGHb01CR58mStZ
xg5t9+N7X9hPO2K59b8EbOnnFTlwtMMF7MKR56S4YwMamCChr9WGgpOQV+lrqyx2
yn9lwn8Yf4gLoLPKBEhHTEy6r/we9qymmlSSe4wr5Fctov2odSE535nvtdRYZkKk
t32gOXuwnKg2kRlRSAErpyou1mz7/mWEt1H3sGTRArjNTP2KqZkc14vPToEEJt93
ZKjhD1pVEchDiWBOMj9o12pq80tGZ8PhGJasJVVi0JPUiaznG4r12JdyDAjuXMru
6f4Tx0ULkdwn9ia7lchcq7xC2PlTnYz+fGpfU7V0Ci56QDTp6oP567L1EIeddkaI
nIsi4KHT7Ctp047FTTelntUCAwEAAQ==
-----END PUBLIC KEY-----`;
var BridgeLicenseValidator = (logger) => new LicenseValidator({ validationKey, logger });
// src/instance/BridgeNode.ts
import { userInfo } from "node:os";
// ../bridge-mesh/src/mesh/connections.ts
import "@interopio/gateway";
var InMemoryNodeConnections = class {
#logger;
#nodes = /* @__PURE__ */ new Map();
#nodesByEndpoint = /* @__PURE__ */ new Map();
#memberIds = 0;
#timeout;
constructor(logger, timeout = 6e4) {
this.#logger = logger;
this.#timeout = timeout;
}
announce(nodes) {
for (const node of nodes) {
const { node: nodeId, users, endpoint } = node;
const foundId = this.#nodesByEndpoint.get(endpoin