@interopio/bridge 0.0.1-alpha → 0.0.3-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/bin/bridge.js +1 -1
  2. package/dist/main.js +2139 -0
  3. package/dist/main.js.map +7 -0
  4. package/package.json +9 -6
  5. package/gen/instance/GeneratedBuildInfo.ts +0 -4
  6. package/src/cluster/Address.ts +0 -57
  7. package/src/cluster/Cluster.ts +0 -13
  8. package/src/cluster/Endpoint.ts +0 -5
  9. package/src/cluster/Member.ts +0 -9
  10. package/src/cluster/MembershipListener.ts +0 -6
  11. package/src/config/Config.ts +0 -100
  12. package/src/config/DiscoveryConfig.ts +0 -21
  13. package/src/config/Duration.ts +0 -168
  14. package/src/config/KubernetesConfig.ts +0 -7
  15. package/src/config/NamedDiscoveryConfig.ts +0 -17
  16. package/src/config/Properties.ts +0 -49
  17. package/src/config/index.ts +0 -1
  18. package/src/discovery/SimpleDiscoveryNode.ts +0 -14
  19. package/src/discovery/index.ts +0 -207
  20. package/src/discovery/multicast/MulticastDiscoveryStrategy.ts +0 -141
  21. package/src/discovery/multicast/MulticastDiscoveryStrategyFactory.ts +0 -30
  22. package/src/discovery/multicast/MulticastProperties.ts +0 -4
  23. package/src/discovery/settings.ts +0 -37
  24. package/src/error/RequestFailure.ts +0 -48
  25. package/src/gossip/ApplicationState.ts +0 -48
  26. package/src/gossip/EndpointState.ts +0 -141
  27. package/src/gossip/FailureDetector.ts +0 -235
  28. package/src/gossip/Gossiper.ts +0 -1133
  29. package/src/gossip/HeartbeatState.ts +0 -66
  30. package/src/gossip/Messenger.ts +0 -130
  31. package/src/gossip/VersionedValue.ts +0 -59
  32. package/src/index.ts +0 -3
  33. package/src/instance/AddressPicker.ts +0 -245
  34. package/src/instance/BridgeNode.ts +0 -141
  35. package/src/instance/ClusterTopologyIntentTracker.ts +0 -4
  36. package/src/io/VersionedSerializer.ts +0 -230
  37. package/src/io/util.ts +0 -117
  38. package/src/kubernetes/DnsEndpointResolver.ts +0 -70
  39. package/src/kubernetes/KubernetesApiEndpointResolver.ts +0 -111
  40. package/src/kubernetes/KubernetesApiProvider.ts +0 -75
  41. package/src/kubernetes/KubernetesClient.ts +0 -264
  42. package/src/kubernetes/KubernetesConfig.ts +0 -130
  43. package/src/kubernetes/KubernetesDiscoveryStrategy.ts +0 -30
  44. package/src/kubernetes/KubernetesDiscoveryStrategyFactory.ts +0 -71
  45. package/src/kubernetes/KubernetesEndpointResolver.ts +0 -43
  46. package/src/kubernetes/KubernetesProperties.ts +0 -22
  47. package/src/license/BridgeLicenseValidator.ts +0 -19
  48. package/src/license/LicenseValidator.ts +0 -114
  49. package/src/license/types.ts +0 -40
  50. package/src/logging.ts +0 -22
  51. package/src/main.mts +0 -53
  52. package/src/net/Action.ts +0 -143
  53. package/src/net/AddressSerializer.ts +0 -44
  54. package/src/net/ByteBufferAllocator.ts +0 -27
  55. package/src/net/FrameDecoder.ts +0 -314
  56. package/src/net/FrameEncoder.ts +0 -138
  57. package/src/net/HandshakeProtocol.ts +0 -143
  58. package/src/net/InboundConnection.ts +0 -108
  59. package/src/net/InboundConnectionInitiator.ts +0 -150
  60. package/src/net/InboundMessageHandler.ts +0 -377
  61. package/src/net/InboundSink.ts +0 -38
  62. package/src/net/Message.ts +0 -428
  63. package/src/net/OutboundConnection.ts +0 -1141
  64. package/src/net/OutboundConnectionInitiator.ts +0 -76
  65. package/src/net/RequestCallbacks.ts +0 -148
  66. package/src/net/ResponseHandler.ts +0 -30
  67. package/src/net/ShareableBytes.ts +0 -125
  68. package/src/net/internal/AsyncResourceExecutor.ts +0 -464
  69. package/src/net/internal/AsyncSocketPromise.ts +0 -37
  70. package/src/net/internal/channel/ChannelHandlerAdapter.ts +0 -99
  71. package/src/net/internal/channel/types.ts +0 -188
  72. package/src/utils/bigint.ts +0 -23
  73. package/src/utils/buffer.ts +0 -434
  74. package/src/utils/clock.ts +0 -148
  75. package/src/utils/collections.ts +0 -283
  76. package/src/utils/crc.ts +0 -39
  77. package/src/utils/internal/IpAddressUtil.ts +0 -161
  78. package/src/utils/memory/BufferPools.ts +0 -40
  79. package/src/utils/network.ts +0 -130
  80. package/src/utils/promise.ts +0 -38
  81. package/src/utils/uuid.ts +0 -5
  82. package/src/utils/vint.ts +0 -238
  83. package/src/version/MemberVersion.ts +0 -42
  84. package/src/version/Version.ts +0 -12
package/dist/main.js ADDED
@@ -0,0 +1,2139 @@
1
+ // src/utils/uuid.ts
2
+ import { nanoid } from "nanoid";
3
+ function newUUID() {
4
+ return nanoid();
5
+ }
6
+
7
+ // src/utils/network.ts
8
+ import { networkInterfaces } from "node:os";
9
+ import { ADDRCONFIG } from "node:dns";
10
+ import { lookup, lookupService } from "node:dns/promises";
11
+
12
+ // src/utils/internal/IpAddressUtil.ts
13
+ var DOT = ".".charCodeAt(0);
14
+ var COLON = ":".charCodeAt(0);
15
+ function textToNumericFormatV4(address) {
16
+ const result = new Uint8Array(4);
17
+ const len = address.length;
18
+ if (len == 0 || len > 15) {
19
+ return;
20
+ }
21
+ let currentValue = 0;
22
+ let currentByte = 0;
23
+ let newByte = true;
24
+ for (let i = 0; i < len; i++) {
25
+ const ch = address.charCodeAt(i);
26
+ if (ch === DOT) {
27
+ if (newByte || currentValue < 0 || currentValue > 255 || currentByte > 3) {
28
+ return;
29
+ }
30
+ result[currentByte++] = currentValue & 255;
31
+ currentValue = 0;
32
+ newByte = true;
33
+ } else {
34
+ const digit = ch - 48;
35
+ if (digit < 0) {
36
+ return;
37
+ }
38
+ currentValue *= 10;
39
+ currentValue += digit;
40
+ newByte = false;
41
+ }
42
+ }
43
+ if (newByte || currentValue < 0 || currentValue >= 1 << (4 - currentByte) * 8) {
44
+ return;
45
+ }
46
+ switch (currentByte) {
47
+ case 0:
48
+ result[0] = currentValue >> 24 & 255;
49
+ //fall through
50
+ case 1:
51
+ result[1] = currentValue >> 16 & 255;
52
+ //fall through
53
+ case 2:
54
+ result[2] = currentValue >> 8 & 255;
55
+ //fall through
56
+ case 3:
57
+ result[3] = currentValue >> 0 & 255;
58
+ }
59
+ return result;
60
+ }
61
+
62
+ // src/utils/network.ts
63
+ import { readFileSync } from "node:fs";
64
+ var IpAddress = class {
65
+ address;
66
+ family;
67
+ _hostname;
68
+ constructor(host, address, family) {
69
+ this._hostname = host;
70
+ this.address = address;
71
+ this.family = family;
72
+ }
73
+ get addressBytes() {
74
+ if (this.family === 4) {
75
+ return textToNumericFormatV4(this.address);
76
+ }
77
+ }
78
+ async getHost() {
79
+ if (!this._hostname) {
80
+ this._hostname = (await lookupService(this.address, 0)).hostname;
81
+ }
82
+ return this._hostname;
83
+ }
84
+ async getIpAddress(nonLoopback = false) {
85
+ const hints = nonLoopback ? ADDRCONFIG : void 0;
86
+ return await lookup(this.address, { family: this.family, hints });
87
+ }
88
+ };
89
+ function isLoopback(address) {
90
+ return address.family === 4 && address.address.startsWith("127.") || address.family === 6 && address.address.startsWith("::1");
91
+ }
92
+ async function getByName(host) {
93
+ const { address, family } = await lookup(host, { family: 4 });
94
+ if (family !== 4 && family !== 6) {
95
+ throw new Error("unexpected address type");
96
+ }
97
+ return new IpAddress(void 0, address, family);
98
+ }
99
+ function getNetworkInterfaces() {
100
+ return Object.entries(networkInterfaces()).map(([name, addresses]) => {
101
+ return {
102
+ name,
103
+ loopback: addresses.some((info) => info.internal),
104
+ addresses: addresses.map((info) => {
105
+ return new IpAddress(void 0, info.address, info.family === "IPv6" ? 6 : 4);
106
+ })
107
+ };
108
+ });
109
+ }
110
+ function soMaxConn() {
111
+ let somaxconn;
112
+ if (process.platform === "win32") {
113
+ somaxconn = 200;
114
+ } else if (process.platform === "darwin") {
115
+ somaxconn = 128;
116
+ } else {
117
+ somaxconn = 4096;
118
+ }
119
+ try {
120
+ somaxconn = parseInt(readFileSync("/proc/sys/net/core/somaxconn").toString());
121
+ } catch (error) {
122
+ }
123
+ return somaxconn;
124
+ }
125
+ var SOMAXCONN = soMaxConn();
126
+
127
+ // src/cluster/Address.ts
128
+ async function toIpAddress(address) {
129
+ return await getByName(scopedHost(address));
130
+ }
131
+ async function fromHostname(hostname, port) {
132
+ return new AddressImpl(hostname, await getByName(hostname), port);
133
+ }
134
+ function fromIpAddress(address, port) {
135
+ return new AddressImpl(void 0, address, port);
136
+ }
137
+ var AddressImpl = class {
138
+ type;
139
+ host;
140
+ port;
141
+ address;
142
+ constructor(hostname, ipAddress, port) {
143
+ const type = ipAddress.family;
144
+ if (type !== 4 && type !== 6) {
145
+ throw new Error("unexpected address type");
146
+ }
147
+ this.type = type;
148
+ this.host = hostname ?? ipAddress.address;
149
+ this.port = port;
150
+ this.address = ipAddress;
151
+ }
152
+ };
153
+ function scopedHost(address) {
154
+ if (address.type === 6) {
155
+ return `[${address.host}]`;
156
+ }
157
+ return address.host;
158
+ }
159
+
160
+ // src/config/Properties.ts
161
+ var BOOLEAN = new class {
162
+ convert(value) {
163
+ if (typeof value === "boolean") {
164
+ return value;
165
+ } else if (typeof value === "string") {
166
+ return value.toLowerCase() === "true";
167
+ }
168
+ throw new Error("Cannot convert value to boolean: " + value);
169
+ }
170
+ }();
171
+ var STRING = new class {
172
+ convert(value) {
173
+ if (value === void 0) {
174
+ throw new Error("Cannot convert undefined ");
175
+ }
176
+ return String(value);
177
+ }
178
+ }();
179
+ var NUMBER = new class {
180
+ convert(value) {
181
+ if (value === void 0) {
182
+ throw new Error("Cannot convert undefined ");
183
+ }
184
+ if (typeof value === "object") {
185
+ throw new Error("Cannot convert object/array to number: " + value);
186
+ }
187
+ return Number(value);
188
+ }
189
+ }();
190
+ function property(name, converter) {
191
+ return { key: name, optional: true, converter };
192
+ }
193
+
194
+ // src/config/DiscoveryConfig.ts
195
+ var DefaultDiscoveryConfig = class {
196
+ discoveryStrategyConfigs = [];
197
+ discoveryServiceFactory;
198
+ get enabled() {
199
+ return this.discoveryStrategyConfigs.length > 0;
200
+ }
201
+ };
202
+
203
+ // src/logging.ts
204
+ import { IOGateway } from "@interopio/gateway";
205
+ function getLogger(name) {
206
+ return IOGateway.Logging.getLogger(`gateway.bridge.${name}`);
207
+ }
208
+
209
+ // src/kubernetes/KubernetesDiscoveryStrategyFactory.ts
210
+ import { stat } from "node:fs/promises";
211
+ import { lookup as lookup2 } from "node:dns/promises";
212
+
213
+ // src/kubernetes/KubernetesConfig.ts
214
+ import { readFileSync as readFileSync2 } from "node:fs";
215
+
216
+ // src/discovery/settings.ts
217
+ function getProperty(prefix, property2) {
218
+ let s = prefix;
219
+ if (!s.endsWith(".")) {
220
+ s += ".";
221
+ }
222
+ return s + property2.key;
223
+ }
224
+ function readProperty(prefix, property2) {
225
+ if (prefix) {
226
+ const p = getProperty(prefix, property2);
227
+ const v = process.env[p];
228
+ return v;
229
+ }
230
+ }
231
+ function getOrUndefined(properties, prefix, property2) {
232
+ return getOrDefault(properties, prefix, property2);
233
+ }
234
+ function getOrDefault(properties, prefix, property2, defaultValue) {
235
+ if (property2 === void 0) {
236
+ return defaultValue;
237
+ }
238
+ let value = readProperty(prefix, property2);
239
+ if (value === void 0) {
240
+ value = properties[property2.key];
241
+ }
242
+ if (value === void 0) {
243
+ return defaultValue;
244
+ }
245
+ return value;
246
+ }
247
+
248
+ // src/kubernetes/KubernetesProperties.ts
249
+ var KUBERNETES_ENV_PREFIX = "io.bridge.kubernetes";
250
+ var SERVICE_DNS = property("service-dns", STRING);
251
+ var SERVICE_NAME = property("service-name", STRING);
252
+ var SERVICE_LABEL_NAME = property("service-label-name", STRING);
253
+ var SERVICE_LABEL_VALUE = property("service-label-value", STRING);
254
+ var NAMESPACE = property("namespace", STRING);
255
+ var POD_LABEL_NAME = property("pod-label-name", STRING);
256
+ var POD_LABEL_VALUE = property("pod-label-value", STRING);
257
+ var EXPOSE_EXTERNALLY = property("expose-externally", BOOLEAN);
258
+ var SERVICE_PER_POD_LABEL_NAME = property("service-per-pod-label-name", STRING);
259
+ var SERVICE_PER_POD_LABEL_VALUE = property("service-per-pod-label-value", STRING);
260
+ var RESOLVE_NOT_READY_ADDRESSES = property("resolve-not-ready-addresses", BOOLEAN);
261
+ var KUBERNETES_MASTER_URL = property("kubernetes-master", STRING);
262
+ var KUBERNETES_API_TOKEN = property("api-token", STRING);
263
+ var SERVICE_PORT = property("service-port", NUMBER);
264
+
265
+ // src/kubernetes/KubernetesConfig.ts
266
+ function readFileContents(fileName) {
267
+ return readFileSync2(fileName, "utf-8").toString();
268
+ }
269
+ var KubernetesConfig = class {
270
+ serviceDns;
271
+ serviceName;
272
+ serviceLabelName;
273
+ serviceLabelValue;
274
+ namespace;
275
+ podLabelName;
276
+ podLabelValue;
277
+ resolveNotReadyAddresses;
278
+ exposeExternallyMode;
279
+ servicePerPodLabelName;
280
+ servicePerPodLabelValue;
281
+ kubernetesMasterUrl;
282
+ kubernetesApiToken;
283
+ servicePort;
284
+ fileContentsReader;
285
+ tokenProvider;
286
+ constructor(properties, fileContentsReader = readFileContents) {
287
+ this.fileContentsReader = fileContentsReader;
288
+ this.serviceDns = getOrDefault(properties, KUBERNETES_ENV_PREFIX, SERVICE_DNS);
289
+ this.serviceName = getOrDefault(properties, KUBERNETES_ENV_PREFIX, SERVICE_NAME);
290
+ this.serviceLabelName = getOrUndefined(properties, KUBERNETES_ENV_PREFIX, SERVICE_LABEL_NAME);
291
+ this.serviceLabelValue = getOrDefault(properties, KUBERNETES_ENV_PREFIX, SERVICE_LABEL_VALUE, "true");
292
+ this.resolveNotReadyAddresses = getOrDefault(properties, KUBERNETES_ENV_PREFIX, RESOLVE_NOT_READY_ADDRESSES, true);
293
+ this.podLabelName = getOrUndefined(properties, KUBERNETES_ENV_PREFIX, POD_LABEL_NAME);
294
+ this.podLabelValue = getOrUndefined(properties, KUBERNETES_ENV_PREFIX, POD_LABEL_VALUE);
295
+ this.exposeExternallyMode = this.getExposeExternallyMode(properties);
296
+ this.servicePerPodLabelName = getOrUndefined(properties, KUBERNETES_ENV_PREFIX, SERVICE_PER_POD_LABEL_NAME);
297
+ this.servicePerPodLabelValue = getOrUndefined(properties, KUBERNETES_ENV_PREFIX, SERVICE_PER_POD_LABEL_VALUE);
298
+ this.kubernetesMasterUrl = getOrDefault(properties, KUBERNETES_ENV_PREFIX, KUBERNETES_MASTER_URL, "https://kubernetes.default.svc");
299
+ this.tokenProvider = this.buildTokenProvider(properties);
300
+ this.servicePort = getOrDefault(properties, KUBERNETES_ENV_PREFIX, SERVICE_PORT, 0);
301
+ this.namespace = this.getNamespaceWithFallbacks(properties);
302
+ }
303
+ getExposeExternallyMode(properties) {
304
+ const exposeExternally = getOrUndefined(properties, KUBERNETES_ENV_PREFIX, EXPOSE_EXTERNALLY);
305
+ if (exposeExternally === void 0) {
306
+ return "auto";
307
+ } else if (exposeExternally) {
308
+ return "enabled";
309
+ } else {
310
+ return "disabled";
311
+ }
312
+ }
313
+ getNamespaceWithFallbacks(properties) {
314
+ let namespace = getOrDefault(properties, KUBERNETES_ENV_PREFIX, NAMESPACE);
315
+ if (!namespace) {
316
+ namespace = process.env["KUBERNETES_NAMESPACE"];
317
+ }
318
+ if (!namespace && this.mode === "kubernetes-api") {
319
+ namespace = this.readNamespace();
320
+ }
321
+ return namespace;
322
+ }
323
+ buildTokenProvider(properties) {
324
+ const apiToken = getOrUndefined(properties, KUBERNETES_ENV_PREFIX, KUBERNETES_API_TOKEN);
325
+ if (!apiToken && this.mode === "kubernetes-api") {
326
+ return () => readFileContents("/var/run/secrets/kubernetes.io/serviceaccount/token");
327
+ } else {
328
+ return () => apiToken;
329
+ }
330
+ }
331
+ readNamespace() {
332
+ return this.fileContentsReader("/var/run/secrets/kubernetes.io/serviceaccount/namespace");
333
+ }
334
+ get mode() {
335
+ if (this.serviceDns) {
336
+ return "dns-lookup";
337
+ }
338
+ return "kubernetes-api";
339
+ }
340
+ toString() {
341
+ 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 + "}";
342
+ }
343
+ };
344
+
345
+ // src/kubernetes/KubernetesApiProvider.ts
346
+ var KubernetesApiEndpointSlicesProvider = class {
347
+ getEndpointsByServiceLabelUrlString(kubernetesMaster, namespace, param) {
348
+ return `${kubernetesMaster}/apis/discovery.k8s.io/v1/namespaces/${namespace}/endpointslices?${param}`;
349
+ }
350
+ getEndpointsByNameUrlString(kubernetesMaster, namespace, endpointName) {
351
+ return `${kubernetesMaster}/apis/discovery.k8s.io/v1/namespaces/${namespace}/endpointslices?labelSelector=kubernetes.io/service-name=${endpointName}`;
352
+ }
353
+ parseEndpoints(param) {
354
+ const endpoints = new Array();
355
+ for (const item of param.items) {
356
+ endpoints.push(...this.parseEndpointSlices(item));
357
+ }
358
+ return endpoints;
359
+ }
360
+ parseEndpointSlices(item) {
361
+ const addresses = new Array();
362
+ const endpointPort = this.extractPort(item);
363
+ for (const endpoint of item.endpoints) {
364
+ const ready = endpoint.conditions.ready;
365
+ for (const address of endpoint.addresses) {
366
+ addresses.push(new Endpoint(new EndpointAddress({ ip: address, port: endpointPort }), void 0, ready));
367
+ }
368
+ }
369
+ return addresses;
370
+ }
371
+ extractPort(item) {
372
+ for (const port of item.ports) {
373
+ const bridgeServicePort = port.name;
374
+ if (bridgeServicePort && bridgeServicePort === "io-bridge") {
375
+ const servicePort = port.port;
376
+ if (servicePort !== void 0) {
377
+ return servicePort;
378
+ }
379
+ }
380
+ }
381
+ if (item.ports.length === 1) {
382
+ const port = item.ports[0];
383
+ const servicePort = port.port;
384
+ if (servicePort !== void 0) {
385
+ return servicePort;
386
+ }
387
+ }
388
+ }
389
+ };
390
+
391
+ // src/kubernetes/KubernetesClient.ts
392
+ var EndpointAddress = class {
393
+ targetRefName;
394
+ address;
395
+ constructor(address, targetRefName) {
396
+ this.address = address;
397
+ this.targetRefName = targetRefName;
398
+ }
399
+ get ip() {
400
+ return this.address.ip;
401
+ }
402
+ get port() {
403
+ return this.address.port;
404
+ }
405
+ };
406
+ var Endpoint = class {
407
+ privateAddress;
408
+ publicAddress = void 0;
409
+ ready;
410
+ additionalProperties = /* @__PURE__ */ new Map();
411
+ constructor(privateAddress, publicAddress = void 0, ready, additionalProperties = /* @__PURE__ */ new Map()) {
412
+ this.privateAddress = privateAddress;
413
+ this.publicAddress = publicAddress;
414
+ this.ready = ready;
415
+ this.additionalProperties = additionalProperties;
416
+ }
417
+ };
418
+ var READ_TIMEOUT_SECONDS = 10;
419
+ function isReady(podItemStatus) {
420
+ for (const containerStatus of podItemStatus.containerStatuses) {
421
+ if (containerStatus.ready !== true) {
422
+ return false;
423
+ }
424
+ }
425
+ return true;
426
+ }
427
+ function containerPort(container) {
428
+ const ports = container.ports;
429
+ if (ports.length > 0) {
430
+ const port = ports[0];
431
+ return port.containerPort;
432
+ }
433
+ }
434
+ function extractContainerPort(item) {
435
+ const containers = item.spec.containers;
436
+ if (containers.length === 1) {
437
+ const container = containers[0];
438
+ return containerPort(container);
439
+ } else {
440
+ for (const container of containers) {
441
+ if (container.name === "io-bridge") {
442
+ return containerPort(container);
443
+ }
444
+ }
445
+ }
446
+ }
447
+ function parsePodsList(podList) {
448
+ const addresses = new Array();
449
+ for (const item of podList.items) {
450
+ const podName = item.metadata.name;
451
+ const status = item.status;
452
+ const ip = status.podIP;
453
+ if (ip) {
454
+ const port = extractContainerPort(item);
455
+ addresses.push(new Endpoint(new EndpointAddress({ ip, port }, podName), void 0, isReady(status)));
456
+ }
457
+ }
458
+ return addresses;
459
+ }
460
+ function getLabelSelectorParameter(labelNames, labelValues) {
461
+ const labelNameArray = labelNames.split(",");
462
+ const labelValueArray = labelValues.split(",");
463
+ const selectorList = [];
464
+ for (let i = 0; i < labelNameArray.length; i++) {
465
+ selectorList[i] = `${labelNameArray[i]}=${labelValueArray[i]}`;
466
+ }
467
+ return `labelSelector=${selectorList.join(",")}`;
468
+ }
469
+ var KubernetesClient = class {
470
+ namespace;
471
+ kubernetesMaster;
472
+ exposeExternallyMode;
473
+ tokenProvider;
474
+ servicePerPodLabelName;
475
+ servicePerPodLabelValue;
476
+ clientTopologyIntentTracker;
477
+ apiProvider;
478
+ /*testing*/
479
+ constructor(namespace, kubernetesMaster, exposeExternallyMode, tokenProvider, servicePerPodLabelName, servicePerPodLabelValue, clientTopologyIntentTracker, apiProvider) {
480
+ this.namespace = namespace;
481
+ this.kubernetesMaster = kubernetesMaster;
482
+ this.exposeExternallyMode = exposeExternallyMode;
483
+ this.tokenProvider = tokenProvider;
484
+ this.servicePerPodLabelName = servicePerPodLabelName;
485
+ this.servicePerPodLabelValue = servicePerPodLabelValue;
486
+ if (clientTopologyIntentTracker) {
487
+ clientTopologyIntentTracker.init();
488
+ }
489
+ this.apiProvider = apiProvider ?? new KubernetesApiEndpointSlicesProvider();
490
+ }
491
+ start() {
492
+ }
493
+ destroy() {
494
+ if (this.clientTopologyIntentTracker) {
495
+ this.clientTopologyIntentTracker.destroy();
496
+ }
497
+ }
498
+ /**
499
+ * Returns POD addresses in the specified namespace.
500
+ *
501
+ */
502
+ async endpoints() {
503
+ try {
504
+ const url = `${this.kubernetesMaster}/api/v1/namespaces/${this.namespace}/pods`;
505
+ return this.enrichWithPublicAddresses(parsePodsList(await this.callGet(url)));
506
+ } catch (e) {
507
+ return this.handleUnknownError(e);
508
+ }
509
+ }
510
+ /**
511
+ * Retrieves POD addresses for all services in the specified namespace filtered by service labels and values.
512
+ * @param serviceLabels comma separated list of service labels
513
+ * @param serviceLabelValues comma separated list of service label values
514
+ * @return all POD addresses from the specified namespace filtered by labels
515
+ */
516
+ async endpointsByServiceLabel(serviceLabels, serviceLabelValues) {
517
+ try {
518
+ const param = getLabelSelectorParameter(serviceLabels, serviceLabelValues);
519
+ const url = this.apiProvider.getEndpointsByServiceLabelUrlString(this.kubernetesMaster, this.namespace, param);
520
+ return this.enrichWithPublicAddresses(this.apiProvider.parseEndpoints(this.callGet(url)));
521
+ } catch (e) {
522
+ return this.handleUnknownError(e);
523
+ }
524
+ }
525
+ /**
526
+ * Retrieves POD addresses from the specified namespace and given endpointName.
527
+ * @param endpointName endpoint name
528
+ * @return all POD addresses from the specified namespace and the given endpointName
529
+ */
530
+ async endpointsByName(endpointName) {
531
+ try {
532
+ const url = this.apiProvider.getEndpointsByNameUrlString(this.kubernetesMaster, this.namespace, endpointName);
533
+ return this.enrichWithPublicAddresses(this.apiProvider.parseEndpoints(await this.callGet(url)));
534
+ } catch (e) {
535
+ return this.handleUnknownError(e);
536
+ }
537
+ }
538
+ /**
539
+ * Retrieves POD addresses for all services in the specified namespace filtered by pod labels and values.
540
+ * @param podLabels comma separated list of pod labels
541
+ * @param podLabelValues comma separated list of pod label values
542
+ * @return all POD addresses from the specified namespace filtered by the labels
543
+ */
544
+ async endpointsByPodLabel(podLabels, podLabelValues) {
545
+ try {
546
+ const param = getLabelSelectorParameter(podLabels, podLabelValues);
547
+ const url = `${this.kubernetesMaster}/api/v1/namespaces/${this.namespace}/pods?${param}`;
548
+ return this.enrichWithPublicAddresses(parsePodsList(await this.callGet(url)));
549
+ } catch (e) {
550
+ return this.handleUnknownError(e);
551
+ }
552
+ }
553
+ enrichWithPublicAddresses(endpoints) {
554
+ if (this.exposeExternallyMode === "disabled") {
555
+ return endpoints;
556
+ }
557
+ }
558
+ handleUnknownError(e) {
559
+ return [];
560
+ }
561
+ async callGet(url) {
562
+ const controller = new AbortController();
563
+ const timeout = setTimeout(() => {
564
+ controller.abort(`request to ${url} timed out`);
565
+ }, READ_TIMEOUT_SECONDS * 1e3);
566
+ try {
567
+ const response = await fetch(url, {
568
+ signal: controller.signal,
569
+ headers: [["Authorization", `Bearer ${this.tokenProvider()}`]]
570
+ }).catch((err) => {
571
+ throw err;
572
+ });
573
+ return await response.json();
574
+ } finally {
575
+ clearTimeout(timeout);
576
+ }
577
+ }
578
+ };
579
+
580
+ // src/kubernetes/KubernetesEndpointResolver.ts
581
+ var DNS_RETRY = 5;
582
+ var KubernetesEndpointResolver = class {
583
+ logger;
584
+ constructor(logger) {
585
+ this.logger = logger;
586
+ }
587
+ async start() {
588
+ }
589
+ async close() {
590
+ }
591
+ async mapAddress(address) {
592
+ if (!address) {
593
+ return;
594
+ }
595
+ try {
596
+ let retry = 0;
597
+ try {
598
+ return await getByName(address);
599
+ } catch (e) {
600
+ if (retry < DNS_RETRY) {
601
+ retry++;
602
+ } else {
603
+ throw e;
604
+ }
605
+ }
606
+ } catch (e) {
607
+ }
608
+ }
609
+ };
610
+
611
+ // src/config/NamedDiscoveryConfig.ts
612
+ var NamedDiscoveryConfig = class {
613
+ tag;
614
+ _enabled = false;
615
+ constructor(tag) {
616
+ this.tag = tag;
617
+ }
618
+ setEnabled(enabled) {
619
+ this._enabled = enabled;
620
+ return this;
621
+ }
622
+ get enabled() {
623
+ return this._enabled;
624
+ }
625
+ };
626
+
627
+ // src/config/KubernetesConfig.ts
628
+ var KubernetesConfig2 = class extends NamedDiscoveryConfig {
629
+ constructor() {
630
+ super("kubernetes");
631
+ }
632
+ };
633
+
634
+ // src/config/Config.ts
635
+ var AutoDetectionConfig = class {
636
+ enabled = true;
637
+ };
638
+ var DefaultMulticastConfig = class {
639
+ enabled = false;
640
+ group = "239.1.2.3";
641
+ port = 34567;
642
+ };
643
+ var DefaultTcpIpConfig = class {
644
+ enabled = false;
645
+ members = [];
646
+ };
647
+ var JoinConfig = class {
648
+ _multicast = new DefaultMulticastConfig();
649
+ _tcpIp = new DefaultTcpIpConfig();
650
+ _kubernetes = new KubernetesConfig2();
651
+ _discovery = new DefaultDiscoveryConfig();
652
+ _autoDetection = new AutoDetectionConfig();
653
+ get discovery() {
654
+ return this._discovery;
655
+ }
656
+ get multicast() {
657
+ return this._multicast;
658
+ }
659
+ get autoDetectionEnabled() {
660
+ return this._autoDetection.enabled && !this._multicast.enabled && !this._tcpIp.enabled && !this._kubernetes.enabled && !this._discovery.enabled;
661
+ }
662
+ };
663
+ var NetworkConfig = class _NetworkConfig {
664
+ static DEFAULT_PORT = 8383;
665
+ port = _NetworkConfig.DEFAULT_PORT;
666
+ reuseAddress;
667
+ publicAddress;
668
+ join = new JoinConfig();
669
+ constructor() {
670
+ this.reuseAddress = process.platform !== "win32";
671
+ }
672
+ };
673
+ var CorsConfig = class {
674
+ #allowOrigin = void 0;
675
+ set allowOrigin(value) {
676
+ if (value !== void 0) {
677
+ if (value === "*") {
678
+ this.#allowOrigin = value;
679
+ } else if (Array.isArray(value)) {
680
+ this.#allowOrigin = value;
681
+ } else {
682
+ this.#allowOrigin = value.split(",");
683
+ }
684
+ }
685
+ }
686
+ get allowOrigin() {
687
+ return this.#allowOrigin;
688
+ }
689
+ allowCredentials = false;
690
+ };
691
+ var ServerConfig = class {
692
+ port = void 0;
693
+ host = void 0;
694
+ cors = new CorsConfig();
695
+ auth = {
696
+ type: "none",
697
+ basic: {
698
+ realm: "io.Bridge"
699
+ }
700
+ };
701
+ wsPingInterval = 3e4;
702
+ // 30 seconds
703
+ };
704
+ var MeshConfig = class {
705
+ timeout;
706
+ };
707
+ var GatewayConfig = class {
708
+ enabled;
709
+ contexts = { lifetime: "retained" };
710
+ };
711
+ var Config = class {
712
+ properties = {};
713
+ network = new NetworkConfig();
714
+ license;
715
+ server;
716
+ mesh;
717
+ gateway;
718
+ constructor() {
719
+ this.server = new ServerConfig();
720
+ this.mesh = new MeshConfig();
721
+ this.gateway = new GatewayConfig();
722
+ }
723
+ get(name) {
724
+ const value = this.properties[name];
725
+ return value;
726
+ }
727
+ set(name, value) {
728
+ this.properties[name] = value;
729
+ return this;
730
+ }
731
+ };
732
+
733
+ // src/discovery/SimpleDiscoveryNode.ts
734
+ var SimpleDiscoveryNode = class {
735
+ privateAddress;
736
+ publicAddress;
737
+ properties;
738
+ constructor(privateAddress, publicAddress, properties) {
739
+ this.privateAddress = privateAddress;
740
+ this.publicAddress = publicAddress ?? privateAddress;
741
+ this.properties = properties ?? /* @__PURE__ */ new Map();
742
+ }
743
+ };
744
+
745
+ // src/kubernetes/KubernetesApiEndpointResolver.ts
746
+ function buildKubernetesClient(config, clusterTopologyIntentTracker) {
747
+ return new KubernetesClient(config.namespace, config.kubernetesMasterUrl, config.exposeExternallyMode, config.tokenProvider, config.servicePerPodLabelName, config.servicePerPodLabelValue, clusterTopologyIntentTracker);
748
+ }
749
+ var KubernetesApiEndpointResolver = class _KubernetesApiEndpointResolver extends KubernetesEndpointResolver {
750
+ client;
751
+ serviceName;
752
+ port = 0;
753
+ serviceLabel;
754
+ serviceLabelValue;
755
+ podLabel;
756
+ podLabelValue;
757
+ resolveNotReadyAddresses;
758
+ static of(logger, config, tracker) {
759
+ return new _KubernetesApiEndpointResolver(
760
+ logger,
761
+ buildKubernetesClient(config, tracker),
762
+ config.serviceName,
763
+ config.servicePort,
764
+ config.serviceLabelName,
765
+ config.serviceLabelValue,
766
+ config.podLabelName,
767
+ config.podLabelValue,
768
+ config.resolveNotReadyAddresses
769
+ );
770
+ }
771
+ /*testing*/
772
+ constructor(logger, client, serviceName, port = 0, serviceLabel, serviceLabelValue, podLabel, podLabelValue, resolveNotReadyAddresses) {
773
+ super(logger);
774
+ this.client = client;
775
+ this.serviceName = serviceName;
776
+ this.port = port;
777
+ this.serviceLabel = serviceLabel;
778
+ this.serviceLabelValue = serviceLabelValue;
779
+ this.podLabel = podLabel;
780
+ this.podLabelValue = podLabelValue;
781
+ this.resolveNotReadyAddresses = resolveNotReadyAddresses;
782
+ }
783
+ async resolve() {
784
+ if (this.serviceName) {
785
+ return await this.getSimpleDiscoveryNodes(await this.client.endpointsByName(this.serviceName));
786
+ } else if (this.serviceLabel) {
787
+ return await this.getSimpleDiscoveryNodes(await this.client.endpointsByServiceLabel(this.serviceLabel, this.serviceLabelValue));
788
+ } else if (this.podLabel) {
789
+ return await this.getSimpleDiscoveryNodes(await this.client.endpointsByPodLabel(this.podLabel, this.podLabelValue));
790
+ }
791
+ return await this.getSimpleDiscoveryNodes(await this.client.endpoints());
792
+ }
793
+ async getSimpleDiscoveryNodes(endpoints) {
794
+ const discoveredNodes = new Array();
795
+ for (const endpoint of endpoints) {
796
+ await this.addAddress(discoveredNodes, endpoint);
797
+ }
798
+ return discoveredNodes;
799
+ }
800
+ async addAddress(discoveredNodes, endpoint) {
801
+ if (this.resolveNotReadyAddresses === true || endpoint.ready) {
802
+ const privateAddress = await this.createAddress(endpoint.privateAddress, this.resolvePort.bind(this));
803
+ const publicAddress = await this.createAddress(endpoint.publicAddress, this.resolvePortPublic.bind(this));
804
+ discoveredNodes.push(new SimpleDiscoveryNode(privateAddress, publicAddress, /* @__PURE__ */ new Map()));
805
+ }
806
+ }
807
+ async createAddress(address, portResolver) {
808
+ if (address === void 0) {
809
+ return void 0;
810
+ }
811
+ const ip = address.ip;
812
+ const ipAddress = await this.mapAddress(ip);
813
+ const port = portResolver(address);
814
+ return fromIpAddress(ipAddress, port);
815
+ }
816
+ resolvePort(address) {
817
+ if (this.port > 0) {
818
+ return this.port;
819
+ }
820
+ if (address.port) {
821
+ return address.port;
822
+ }
823
+ return NetworkConfig.DEFAULT_PORT;
824
+ }
825
+ resolvePortPublic(address) {
826
+ if (address.port) {
827
+ return address.port;
828
+ }
829
+ if (this.port > 0) {
830
+ return this.port;
831
+ }
832
+ return NetworkConfig.DEFAULT_PORT;
833
+ }
834
+ };
835
+
836
+ // src/kubernetes/KubernetesDiscoveryStrategy.ts
837
+ var KubernetesDiscoveryStrategy = class {
838
+ logger;
839
+ endpointResolver;
840
+ constructor(logger, properties, intentTracker) {
841
+ this.logger = logger;
842
+ const config = new KubernetesConfig(properties);
843
+ this.logger.info(`${config}`);
844
+ this.endpointResolver = KubernetesApiEndpointResolver.of(this.logger, config, intentTracker);
845
+ this.logger.info(`Kubernetes Discovery activated with mode: ${config.mode}`);
846
+ }
847
+ async start() {
848
+ await this.endpointResolver.start();
849
+ }
850
+ async discover() {
851
+ return void 0;
852
+ }
853
+ async close() {
854
+ await this.endpointResolver.close();
855
+ }
856
+ };
857
+
858
+ // src/kubernetes/KubernetesDiscoveryStrategyFactory.ts
859
+ var PROPERTY_DEFINITIONS = [
860
+ SERVICE_DNS,
861
+ SERVICE_NAME,
862
+ SERVICE_LABEL_NAME,
863
+ SERVICE_LABEL_VALUE,
864
+ NAMESPACE,
865
+ POD_LABEL_NAME,
866
+ POD_LABEL_VALUE,
867
+ RESOLVE_NOT_READY_ADDRESSES,
868
+ EXPOSE_EXTERNALLY,
869
+ KUBERNETES_MASTER_URL,
870
+ KUBERNETES_API_TOKEN,
871
+ SERVICE_PORT
872
+ ];
873
+ var KubernetesDiscoveryStrategyFactory = class {
874
+ tokenPath;
875
+ priority = DiscoveryStrategyPriorities.PLATFORM;
876
+ constructor(tokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token") {
877
+ this.tokenPath = tokenPath;
878
+ }
879
+ async defaultKubernetesMasterReachable() {
880
+ try {
881
+ await lookup2("kubernetes.default.svc");
882
+ return true;
883
+ } catch (e) {
884
+ return false;
885
+ }
886
+ }
887
+ async tokenFileExists() {
888
+ try {
889
+ await stat(this.tokenPath);
890
+ return true;
891
+ } catch (e) {
892
+ return false;
893
+ }
894
+ }
895
+ async isAutoDetectionApplicable() {
896
+ return await this.tokenFileExists() && await this.defaultKubernetesMasterReachable();
897
+ }
898
+ newDiscoveryStrategy(node, logger, properties) {
899
+ const tracker = node?.properties.get("internal.discovery.cluster.topology.intent.tracker");
900
+ return new KubernetesDiscoveryStrategy(logger, properties, tracker);
901
+ }
902
+ getConfigurationProperties() {
903
+ return PROPERTY_DEFINITIONS;
904
+ }
905
+ };
906
+
907
+ // src/discovery/multicast/MulticastDiscoveryStrategy.ts
908
+ import { createSocket } from "node:dgram";
909
+
910
+ // src/discovery/multicast/MulticastProperties.ts
911
+ var PORT = property("port", NUMBER);
912
+ var GROUP = property("group", STRING);
913
+
914
+ // src/discovery/multicast/MulticastDiscoveryStrategy.ts
915
+ var DATA_OUTPUT_BUFFER_SIZE = 64 * 1024;
916
+ var DEFAULT_MULTICAST_PORT = 34567;
917
+ var DEFAULT_MULTICAST_GROUP = "239.1.2.3";
918
+ var SOCKET_TIME_TO_LIVE = 255;
919
+ function multicastDiscoverySender(discoveryNode, socket, group, port) {
920
+ let multicastMemberInfo;
921
+ if (discoveryNode) {
922
+ const address = discoveryNode.publicAddress;
923
+ multicastMemberInfo = { host: address.host, port: address.port };
924
+ }
925
+ const msg = Buffer.from(JSON.stringify(multicastMemberInfo));
926
+ return () => {
927
+ socket.send(msg, port, group, (error) => {
928
+ console.error(error);
929
+ });
930
+ };
931
+ }
932
+ function multicastDiscoveryReceiver(socket) {
933
+ return () => {
934
+ return new Promise((resolve, reject) => {
935
+ socket.on("message", (message, remote) => {
936
+ resolve(JSON.parse(message));
937
+ });
938
+ socket.on("error", (err) => {
939
+ reject(err);
940
+ });
941
+ });
942
+ };
943
+ }
944
+ var MulticastDiscoveryStrategy = class {
945
+ logger;
946
+ properties;
947
+ discoveryNode;
948
+ isClient;
949
+ discoverySender;
950
+ discoveryReceiver;
951
+ sendIntervalId;
952
+ socket;
953
+ constructor(discoveryNode, logger, properties) {
954
+ this.logger = logger;
955
+ this.properties = properties;
956
+ this.discoveryNode = discoveryNode;
957
+ }
958
+ async initializeMulticastSocket() {
959
+ const port = getOrDefault(this.properties, "", PORT, DEFAULT_MULTICAST_PORT);
960
+ if (port < 0 || port > 65535) {
961
+ throw new Error("port number must be between 0 and 65535");
962
+ }
963
+ const group = getOrDefault(this.properties, "", GROUP, DEFAULT_MULTICAST_GROUP);
964
+ this.socket = createSocket({
965
+ type: "udp4",
966
+ reuseAddr: true,
967
+ recvBufferSize: DATA_OUTPUT_BUFFER_SIZE,
968
+ sendBufferSize: DATA_OUTPUT_BUFFER_SIZE
969
+ });
970
+ if (this.discoveryNode) {
971
+ const address = await toIpAddress(this.discoveryNode.privateAddress);
972
+ if (!isLoopback(address)) {
973
+ this.socket.setMulticastInterface(address.address);
974
+ }
975
+ }
976
+ this.socket.bind(port, () => {
977
+ this.socket.setMulticastTTL(SOCKET_TIME_TO_LIVE);
978
+ this.socket.addMembership(group);
979
+ });
980
+ this.discoverySender = multicastDiscoverySender(this.discoveryNode, this.socket, group, port);
981
+ this.discoveryReceiver = multicastDiscoveryReceiver(this.socket);
982
+ if (this.discoveryNode == null) {
983
+ this.isClient = true;
984
+ }
985
+ }
986
+ async discover() {
987
+ const multicastMemberInfo = await this.discoveryReceiver();
988
+ if (multicastMemberInfo) {
989
+ const list = [];
990
+ try {
991
+ const discoveryNode = new SimpleDiscoveryNode(await fromHostname(multicastMemberInfo.host, multicastMemberInfo.port));
992
+ list.push(discoveryNode);
993
+ } catch (e) {
994
+ this.logger.trace(e.message);
995
+ }
996
+ return list;
997
+ }
998
+ }
999
+ async start() {
1000
+ await this.initializeMulticastSocket();
1001
+ if (!this.isClient) {
1002
+ this.sendIntervalId = setInterval(() => {
1003
+ this.discoverySender();
1004
+ }, 2e3);
1005
+ }
1006
+ }
1007
+ async close() {
1008
+ if (this.sendIntervalId) {
1009
+ clearInterval(this.sendIntervalId);
1010
+ delete this.sendIntervalId;
1011
+ }
1012
+ return new Promise((resolve, reject) => {
1013
+ if (this.socket) {
1014
+ this.socket.close(() => {
1015
+ resolve();
1016
+ });
1017
+ } else {
1018
+ resolve();
1019
+ }
1020
+ });
1021
+ }
1022
+ };
1023
+
1024
+ // src/discovery/multicast/MulticastDiscoveryStrategyFactory.ts
1025
+ var PROPERTY_DEFINITIONS2 = [
1026
+ GROUP,
1027
+ PORT
1028
+ ];
1029
+ var MulticastDiscoveryStrategyFactory = class {
1030
+ priority = DiscoveryStrategyPriorities.UNKNOWN;
1031
+ newDiscoveryStrategy(discoveryNode, logger, prop) {
1032
+ return new MulticastDiscoveryStrategy(discoveryNode, logger, prop);
1033
+ }
1034
+ async isAutoDetectionApplicable() {
1035
+ return false;
1036
+ }
1037
+ getConfigurationProperties() {
1038
+ return PROPERTY_DEFINITIONS2;
1039
+ }
1040
+ };
1041
+
1042
+ // src/discovery/index.ts
1043
+ var DefaultDiscoveryServiceFactory = class {
1044
+ newDiscoveryService(settings) {
1045
+ return new DefaultDiscoveryService(settings);
1046
+ }
1047
+ };
1048
+ function verifyNoUnknownProperties(mappedProperties, allProperties) {
1049
+ const unknownProperties = Object.keys(allProperties).filter((key) => !mappedProperties.hasOwnProperty(key));
1050
+ if (unknownProperties.length > 0) {
1051
+ throw new Error(`Unknown properties: ${unknownProperties.join(", ")} on discovery strategy`);
1052
+ }
1053
+ }
1054
+ function prepareProperties(properties, propertyDefinitions) {
1055
+ const mappedProperties = {};
1056
+ for (const propertyDefinition of propertyDefinitions) {
1057
+ const propertyKey = propertyDefinition.key;
1058
+ if (properties[propertyKey] == void 0) {
1059
+ if (!propertyDefinition.optional) {
1060
+ throw new Error(`Missing required property: ${propertyKey} on discovery strategy`);
1061
+ }
1062
+ continue;
1063
+ }
1064
+ const value = properties[propertyKey];
1065
+ const converter = propertyDefinition.converter;
1066
+ const mappedValue = converter.convert(value);
1067
+ mappedProperties[propertyKey] = mappedValue;
1068
+ }
1069
+ verifyNoUnknownProperties(mappedProperties, properties);
1070
+ return mappedProperties;
1071
+ }
1072
+ var DefaultDiscoveryService = class {
1073
+ settings;
1074
+ strategies = [];
1075
+ constructor(settings) {
1076
+ this.settings = settings;
1077
+ }
1078
+ async start() {
1079
+ this.strategies.push(...await this.loadDiscoveryStrategies());
1080
+ for (const strategy of this.strategies) {
1081
+ await strategy.start();
1082
+ }
1083
+ }
1084
+ async discover() {
1085
+ const discoveryNodes = /* @__PURE__ */ new Set();
1086
+ for (const strategy of this.strategies) {
1087
+ const candidates = await strategy.discover();
1088
+ if (candidates) {
1089
+ for (const candidate of candidates) {
1090
+ if (this.validateCandidate(candidate)) {
1091
+ discoveryNodes.add(candidate);
1092
+ }
1093
+ }
1094
+ }
1095
+ }
1096
+ return discoveryNodes;
1097
+ }
1098
+ async close() {
1099
+ for (const strategy of this.strategies) {
1100
+ await strategy.close();
1101
+ }
1102
+ }
1103
+ validateCandidate(candidate) {
1104
+ return true;
1105
+ }
1106
+ async loadDiscoveryStrategies() {
1107
+ const discoveryStrategyConfigs = this.settings.conf.discoveryStrategyConfigs;
1108
+ const factories = this.collectFactories(discoveryStrategyConfigs);
1109
+ const discoveryStrategies = new Array();
1110
+ for (const config of discoveryStrategyConfigs) {
1111
+ const discoveryStrategy = await this.buildDiscoveryStrategy(config, factories);
1112
+ discoveryStrategies.push(discoveryStrategy);
1113
+ }
1114
+ const logger = this.settings.logger;
1115
+ if (discoveryStrategies.length == 0 && this.settings.auto) {
1116
+ logger.debug(`Discovery auto-detection enabled, trying to detect discovery strategy`);
1117
+ const autoDetectedFactory = await this.detectDiscoveryStrategyFactory(factories);
1118
+ if (autoDetectedFactory) {
1119
+ logger.debug(`Auto-detected discovery strategy: ${autoDetectedFactory.constructor.name}`);
1120
+ discoveryStrategies.push(autoDetectedFactory.newDiscoveryStrategy(await this.settings.node, logger, {}));
1121
+ } else {
1122
+ logger.debug(`No discovery strategy auto-detected`);
1123
+ }
1124
+ }
1125
+ return discoveryStrategies;
1126
+ }
1127
+ collectFactories(strategyConfigs) {
1128
+ const knownFactories = [new MulticastDiscoveryStrategyFactory(), new KubernetesDiscoveryStrategyFactory()];
1129
+ const factories = new Array();
1130
+ for (const factory of knownFactories) {
1131
+ factories.push(factory);
1132
+ }
1133
+ for (const config of strategyConfigs) {
1134
+ const factory = config.discoveryStrategyFactory;
1135
+ if (factory) {
1136
+ factories.push(factory);
1137
+ }
1138
+ }
1139
+ return factories;
1140
+ }
1141
+ async buildDiscoveryStrategy(config, factories) {
1142
+ const discoveryNode = await this.settings.node;
1143
+ const logger = this.settings.logger;
1144
+ for (const factory of factories) {
1145
+ if (factory.constructor.name == config.discoveryStrategyFactory.constructor.name) {
1146
+ const properties = prepareProperties(config.properties, factory.getConfigurationProperties());
1147
+ return factory.newDiscoveryStrategy(discoveryNode, logger, properties);
1148
+ }
1149
+ }
1150
+ throw new Error("DiscoveryStrategyFactory not found");
1151
+ }
1152
+ async detectDiscoveryStrategyFactory(factories) {
1153
+ let highestPriorityFactory;
1154
+ for (const factory of factories) {
1155
+ try {
1156
+ if (await factory.isAutoDetectionApplicable()) {
1157
+ if (highestPriorityFactory === void 0 || factory.priority > highestPriorityFactory.priority) {
1158
+ highestPriorityFactory = factory;
1159
+ }
1160
+ }
1161
+ } catch (error) {
1162
+ }
1163
+ }
1164
+ return highestPriorityFactory;
1165
+ }
1166
+ };
1167
+ var DiscoveryStrategyPriorities = {
1168
+ UNKNOWN: 0,
1169
+ PLATFORM: 20,
1170
+ CUSTOM: 50
1171
+ };
1172
+
1173
+ // src/version/MemberVersion.ts
1174
+ function parse(version) {
1175
+ const pattern = /(\d+)\.(\d+)(\.(\d+))?(-\w+(?:-\d+)?)?$/g;
1176
+ const x = pattern.exec(version);
1177
+ const major = parseInt(x[1]);
1178
+ const minor = parseInt(x[2]);
1179
+ const patch = x[3] ? parseInt(x[4]) : 0;
1180
+ return [major, minor, patch];
1181
+ }
1182
+ var MemberVersion = class {
1183
+ major;
1184
+ minor;
1185
+ patch;
1186
+ get isUnknown() {
1187
+ return this.major === 0 && this.minor === 0 && this.patch === 0;
1188
+ }
1189
+ toString() {
1190
+ return `${this.major}.${this.minor}.${this.patch}`;
1191
+ }
1192
+ constructor(major, minor, patch) {
1193
+ this.major = major;
1194
+ this.minor = minor;
1195
+ this.patch = patch;
1196
+ }
1197
+ };
1198
+ var UNKNOWN_VERSION = new MemberVersion(0, 0, 0);
1199
+ function parseVersion(version) {
1200
+ if (!version || version.startsWith("0.0.0")) {
1201
+ return UNKNOWN_VERSION;
1202
+ }
1203
+ return new MemberVersion(...parse(version));
1204
+ }
1205
+
1206
+ // package.json
1207
+ var package_default = {
1208
+ name: "@interopio/bridge",
1209
+ version: "0.0.3-beta",
1210
+ license: "see license in license.md",
1211
+ author: "interop.io",
1212
+ homepage: "https://docs.interop.io/bridge",
1213
+ keywords: [
1214
+ "io.bridge",
1215
+ "bridge",
1216
+ "io.connect",
1217
+ "glue42",
1218
+ "interop.io"
1219
+ ],
1220
+ type: "module",
1221
+ exports: {
1222
+ "./package.json": "./package.json",
1223
+ ".": {
1224
+ import: "./dist/index.js"
1225
+ }
1226
+ },
1227
+ bin: {
1228
+ bridge: "./bin/bridge.js"
1229
+ },
1230
+ scripts: {
1231
+ test: "mocha test --recursive",
1232
+ "build:types": "tsc --declaration --emitDeclarationOnly --outDir types",
1233
+ build: "esbuild src/main.mts --outdir=dist --bundle --packages=external --sourcemap --platform=node --format=esm --target=es2024"
1234
+ },
1235
+ dependencies: {
1236
+ "@interopio/gateway-server": "^0.7.0-beta",
1237
+ dotenv: "^17.2.1",
1238
+ jsrsasign: "^11.1.0",
1239
+ nanoid: "^5.0.9"
1240
+ },
1241
+ devDependencies: {
1242
+ "@types/jsrsasign": "^10.5.15",
1243
+ "@types/ws": "^8.18.1",
1244
+ "rand-seed": "^3.0.0"
1245
+ },
1246
+ engines: {
1247
+ node: ">= 20.15 || >=22.2.0 || >=24.0"
1248
+ }
1249
+ };
1250
+
1251
+ // src/instance/AddressPicker.ts
1252
+ import { createServer } from "node:net";
1253
+ async function createAddress(addressDef, port) {
1254
+ if (addressDef.host) {
1255
+ return { type: addressDef.ipAddress.family, host: addressDef.host, port, address: addressDef.ipAddress };
1256
+ }
1257
+ return { type: addressDef.ipAddress.family, host: await addressDef.ipAddress.getHost(), port, address: addressDef.ipAddress };
1258
+ }
1259
+ function createNetServer(logger, host, range) {
1260
+ const ports = portRange(range);
1261
+ return new Promise((resolve, reject) => {
1262
+ let server = createServer({}, () => {
1263
+ });
1264
+ server.on("error", (err) => {
1265
+ if (err["code"] === "EADDRINUSE") {
1266
+ logger.debug(`port ${err["port"]} already in use on address ${err["address"]}`);
1267
+ const { value: port } = ports.next();
1268
+ if (port) {
1269
+ logger.info(`retry starting server on port ${port} and host ${host ?? "<unspecified>"}`);
1270
+ server.close();
1271
+ server.listen(port, host);
1272
+ } else {
1273
+ logger.warn(`all configured port(s) ${range} are in use. closing...`);
1274
+ server.close();
1275
+ reject(err);
1276
+ }
1277
+ } else {
1278
+ logger.error(`server error: ${err.message}`, err);
1279
+ reject(err);
1280
+ }
1281
+ });
1282
+ server.on("listening", () => {
1283
+ const info = server.address();
1284
+ logger.info(`listening on ${info.address}:${info.port}`);
1285
+ resolve(server);
1286
+ });
1287
+ try {
1288
+ const { value: port } = ports.next();
1289
+ server.listen(port, host);
1290
+ } catch (e) {
1291
+ logger.error(`error starting socket server`, e);
1292
+ reject(e instanceof Error ? e : new Error(`listen failed: ${e}`));
1293
+ }
1294
+ });
1295
+ }
1296
+ var InterfaceAddress = class {
1297
+ address;
1298
+ host;
1299
+ constructor(address, host) {
1300
+ this.address = address;
1301
+ this.host = host;
1302
+ }
1303
+ };
1304
+ var AddressDef = class extends InterfaceAddress {
1305
+ ipAddress;
1306
+ port;
1307
+ constructor(ipAddress, host, port) {
1308
+ super(ipAddress.address, host);
1309
+ this.ipAddress = ipAddress;
1310
+ this.port = port ?? 0;
1311
+ }
1312
+ };
1313
+ var DefaultAddressPicker = class {
1314
+ #logger;
1315
+ #config;
1316
+ #publicAddressConfig;
1317
+ #publicAddress;
1318
+ #bindAddress;
1319
+ server;
1320
+ #port;
1321
+ constructor(config, logger) {
1322
+ this.#logger = logger;
1323
+ this.#config = config;
1324
+ this.#port = config.network.port;
1325
+ this.#publicAddressConfig = config.network.publicAddress;
1326
+ }
1327
+ async pickAddress() {
1328
+ if (this.#publicAddress || this.#bindAddress) {
1329
+ return;
1330
+ }
1331
+ const publicAddressDef = await this.getPublicAddressByPortSearch();
1332
+ this.#publicAddress = this.#bindAddress;
1333
+ }
1334
+ getPublicAddressFor(endpoint) {
1335
+ return this.#publicAddress;
1336
+ }
1337
+ getServers() {
1338
+ return /* @__PURE__ */ new Map([["member", this.server]]);
1339
+ }
1340
+ async getPublicAddressByPortSearch() {
1341
+ const bindAddressDef = await this.pickAddressDef();
1342
+ this.server = await createNetServer(this.#logger, "0.0.0.0", bindAddressDef.port === 0 ? this.#port : bindAddressDef.port);
1343
+ const port = this.server.address().port;
1344
+ this.#bindAddress = await createAddress(bindAddressDef, port);
1345
+ return this.getPublicAddress(port);
1346
+ }
1347
+ async getPublicAddress(port) {
1348
+ let address = this.#publicAddressConfig;
1349
+ if (address) {
1350
+ address = address.trim();
1351
+ if (!address) {
1352
+ throw new Error("Public address is empty");
1353
+ } else if ("127.0.0.1" === address) {
1354
+ return this.pickLoopbackAddressDef(address, port);
1355
+ } else {
1356
+ const ipAddress = await getByName(address);
1357
+ return new AddressDef(ipAddress, address, port);
1358
+ }
1359
+ }
1360
+ }
1361
+ async pickAddressDef() {
1362
+ let addressDef = this.pickInterfaceAddressDef();
1363
+ if (addressDef) {
1364
+ }
1365
+ if (addressDef === void 0) {
1366
+ addressDef = await this.pickLoopbackAddressDef();
1367
+ }
1368
+ return addressDef;
1369
+ }
1370
+ pickInterfaceAddressDef() {
1371
+ const interfaces = this.getInterfaces();
1372
+ if (interfaces) {
1373
+ const address = this.pickMatchingAddress(interfaces);
1374
+ if (address) {
1375
+ return address;
1376
+ }
1377
+ }
1378
+ return this.pickMatchingAddress();
1379
+ }
1380
+ getInterfaces() {
1381
+ return [];
1382
+ }
1383
+ async pickLoopbackAddressDef(host, defaultPort) {
1384
+ const address = await getByName("127.0.0.1");
1385
+ return new AddressDef(address, host, defaultPort);
1386
+ }
1387
+ match(ipAddress, interfaces) {
1388
+ for (const iface of interfaces) {
1389
+ if (ipAddress.address === iface.address) {
1390
+ return iface;
1391
+ }
1392
+ }
1393
+ return void 0;
1394
+ }
1395
+ getMatchingAddress(ipAddress, interfaces) {
1396
+ if (interfaces) {
1397
+ return this.match(ipAddress, interfaces);
1398
+ } else {
1399
+ return ipAddress;
1400
+ }
1401
+ }
1402
+ pickMatchingAddress(interfaces) {
1403
+ const preferIPv4 = true;
1404
+ const networkInterfaces2 = getNetworkInterfaces();
1405
+ let matchingAddress;
1406
+ for (const ni of networkInterfaces2) {
1407
+ if (!interfaces && ni.loopback) {
1408
+ continue;
1409
+ }
1410
+ for (const ipAddress of ni.addresses) {
1411
+ if (preferIPv4 && ipAddress.family === 6) {
1412
+ continue;
1413
+ }
1414
+ const address = this.getMatchingAddress(ipAddress, interfaces);
1415
+ break;
1416
+ }
1417
+ }
1418
+ return matchingAddress;
1419
+ }
1420
+ };
1421
+ var PORT_RANGE_MATCHER = /^(\d+|(0x[\da-f]+))(-(\d+|(0x[\da-f]+)))?$/i;
1422
+ function validPort(port) {
1423
+ if (port > 65535) throw new Error(`bad port ${port}`);
1424
+ return port;
1425
+ }
1426
+ function* portRange(port) {
1427
+ if (typeof port === "string") {
1428
+ for (const portRange2 of port.split(",")) {
1429
+ const trimmed = portRange2.trim();
1430
+ const matchResult = PORT_RANGE_MATCHER.exec(trimmed);
1431
+ if (matchResult) {
1432
+ const start = parseInt(matchResult[1]);
1433
+ const end = parseInt(matchResult[4] ?? matchResult[1]);
1434
+ for (let i = validPort(start); i < validPort(end) + 1; i++) {
1435
+ yield i;
1436
+ }
1437
+ } else {
1438
+ throw new Error(`'${portRange2}' is not a valid port or range.`);
1439
+ }
1440
+ }
1441
+ } else {
1442
+ yield validPort(port);
1443
+ }
1444
+ }
1445
+
1446
+ // src/instance/BridgeNode.ts
1447
+ import gatewayServer from "@interopio/gateway-server";
1448
+
1449
+ // src/license/types.ts
1450
+ var licenseValidationResults = {
1451
+ TRIAL_LICENSE_EXPIRED: { licenseType: "trial", expired: true, fatal: true },
1452
+ TRIAL_LICENSE_VALID: { licenseType: "trial", expired: false, fatal: false },
1453
+ PAID_LICENSE_EXPIRED: { licenseType: "paid", expired: true, fatal: false },
1454
+ PAID_LICENSE_VALID: { licenseType: "paid", expired: false, fatal: false },
1455
+ NO_LICENSE: { licenseType: "none", expired: false, fatal: true }
1456
+ };
1457
+
1458
+ // src/license/LicenseValidator.ts
1459
+ import { KJUR } from "jsrsasign";
1460
+ var LicenseError = class extends Error {
1461
+ licensePayload;
1462
+ constructor(message, licensePayload) {
1463
+ super(message);
1464
+ this.licensePayload = licensePayload;
1465
+ }
1466
+ };
1467
+ var LicenseEvaluator = class {
1468
+ tokenResultMap = {
1469
+ "trial-false": licenseValidationResults.TRIAL_LICENSE_VALID,
1470
+ "trial-true": licenseValidationResults.TRIAL_LICENSE_EXPIRED,
1471
+ "paid-false": licenseValidationResults.PAID_LICENSE_VALID,
1472
+ "paid-true": licenseValidationResults.PAID_LICENSE_EXPIRED
1473
+ };
1474
+ validationKey;
1475
+ constructor(validationKey2) {
1476
+ if (!validationKey2 || typeof validationKey2 !== "string") {
1477
+ throw new Error(`Validation key must be a non-empty string`);
1478
+ }
1479
+ this.validationKey = validationKey2;
1480
+ }
1481
+ decrypt(token) {
1482
+ if (KJUR.jws.JWS.verifyJWT(token, this.validationKey, { alg: ["RS256"] })) {
1483
+ return KJUR.jws.JWS.parse(token).payloadObj;
1484
+ } else {
1485
+ throw new LicenseError("invalid jwt token", token);
1486
+ }
1487
+ }
1488
+ validateBasicTokenData(token) {
1489
+ const decoded = this.decrypt(token);
1490
+ if (["paid", "trial"].indexOf(decoded.type) === -1) {
1491
+ throw new LicenseError("Invalid license type", token);
1492
+ }
1493
+ return decoded;
1494
+ }
1495
+ getLicenseStatus(token, now = /* @__PURE__ */ new Date()) {
1496
+ try {
1497
+ const license = this.validateBasicTokenData(token);
1498
+ const isExpired = license.expiration < now.getTime();
1499
+ const licenseValidationResult = this.tokenResultMap[`${license.type}-${isExpired}`];
1500
+ return {
1501
+ ...licenseValidationResult,
1502
+ license
1503
+ };
1504
+ } catch (e) {
1505
+ return {
1506
+ ...licenseValidationResults.NO_LICENSE,
1507
+ error: e.message
1508
+ };
1509
+ }
1510
+ }
1511
+ };
1512
+ var LicenseValidator = class {
1513
+ licenseHelper;
1514
+ logger;
1515
+ constructor({ validationKey: validationKey2, logger }) {
1516
+ this.licenseHelper = new LicenseEvaluator(validationKey2);
1517
+ this.logger = logger ?? getLogger("LicenseValidator");
1518
+ }
1519
+ validate(licenseKeyString, options) {
1520
+ if (typeof licenseKeyString !== "string") {
1521
+ throw new LicenseError("Invalid license key format", licenseKeyString);
1522
+ }
1523
+ const licenseStatus = this.licenseHelper.getLicenseStatus(licenseKeyString);
1524
+ this.printLicenseMessage(licenseStatus, options?.logSuccessMessages ?? true);
1525
+ if (licenseStatus.fatal) {
1526
+ throw new LicenseError("License validation failed", licenseStatus.license);
1527
+ }
1528
+ }
1529
+ printLicenseMessage(licenseStatus, logSuccessMessages) {
1530
+ if (!licenseStatus || licenseStatus.licenseType === "none") {
1531
+ this.logger.error("This license is invalid. Please contact us at sales@interop.io to get a valid license");
1532
+ return;
1533
+ }
1534
+ const message = [
1535
+ licenseStatus.licenseType === "paid" ? `This is a paid license that was issued for client ` : `This is a trial license that was issued for `,
1536
+ licenseStatus.license.organization.displayName,
1537
+ licenseStatus.expired ? `, but it has expired at ` : ` and is valid until `,
1538
+ new Date(licenseStatus.license.expiration).toString(),
1539
+ "."
1540
+ ].join("");
1541
+ if (licenseStatus.fatal) {
1542
+ this.logger.error(message);
1543
+ } else if (licenseStatus.expired) {
1544
+ this.logger.warn(message);
1545
+ } else if (logSuccessMessages) {
1546
+ this.logger.info(message);
1547
+ }
1548
+ }
1549
+ };
1550
+
1551
+ // src/license/BridgeLicenseValidator.ts
1552
+ var validationKey = `-----BEGIN PUBLIC KEY-----
1553
+ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyyZrtwcQCMfyOkZDcjxZ
1554
+ +07BrvgcAvoQETF+ho8VLfaDxgNPZd6Q7jZEfrtv0+wn0pxx8pUDeBh8mCYvkwmb
1555
+ 9tkFH47GJuDhWasNgyp4ugRX4bzoPV3SnHsBR4KJ/F76kBbike6sdIfO2lYGZ36s
1556
+ LJPHV7epOYsfhoc4343/vOxEPvNbwacbXWl4NsCGev4qUmWhe9iMpb/JPd3o4hIU
1557
+ SR31AmR2xoh3BkAJc2q/cSeQK6Kn9nZYocyW367+7FOvYsrOYotsuUASp0OWp+Lh
1558
+ WGfR7F6d016+u6kbvLvcceGztiP1u25QmPNCUmw49cTatthiEwwGHb01CR58mStZ
1559
+ xg5t9+N7X9hPO2K59b8EbOnnFTlwtMMF7MKR56S4YwMamCChr9WGgpOQV+lrqyx2
1560
+ yn9lwn8Yf4gLoLPKBEhHTEy6r/we9qymmlSSe4wr5Fctov2odSE535nvtdRYZkKk
1561
+ t32gOXuwnKg2kRlRSAErpyou1mz7/mWEt1H3sGTRArjNTP2KqZkc14vPToEEJt93
1562
+ ZKjhD1pVEchDiWBOMj9o12pq80tGZ8PhGJasJVVi0JPUiaznG4r12JdyDAjuXMru
1563
+ 6f4Tx0ULkdwn9ia7lchcq7xC2PlTnYz+fGpfU7V0Ci56QDTp6oP567L1EIeddkaI
1564
+ nIsi4KHT7Ctp047FTTelntUCAwEAAQ==
1565
+ -----END PUBLIC KEY-----`;
1566
+ var BridgeLicenseValidator = (logger) => new LicenseValidator({ validationKey, logger });
1567
+
1568
+ // src/instance/BridgeNode.ts
1569
+ import { userInfo } from "node:os";
1570
+
1571
+ // ../bridge-mesh/src/mesh/connections.ts
1572
+ import "@interopio/gateway";
1573
+ var InMemoryNodeConnections = class {
1574
+ #logger;
1575
+ #nodes = /* @__PURE__ */ new Map();
1576
+ #nodesByEndpoint = /* @__PURE__ */ new Map();
1577
+ #memberIds = 0;
1578
+ #timeout;
1579
+ constructor(logger, timeout = 6e4) {
1580
+ this.#logger = logger;
1581
+ this.#timeout = timeout;
1582
+ }
1583
+ announce(nodes) {
1584
+ for (const node of nodes) {
1585
+ const { node: nodeId, users, endpoint } = node;
1586
+ const foundId = this.#nodesByEndpoint.get(endpoint);
1587
+ if (foundId) {
1588
+ if (foundId !== nodeId) {
1589
+ this.#logger.warn(`endpoint ${endpoint} clash. replacing node ${foundId} with ${nodeId}`);
1590
+ this.#nodesByEndpoint.set(endpoint, nodeId);
1591
+ this.#nodes.delete(foundId);
1592
+ }
1593
+ } else {
1594
+ this.#logger.info(`endpoint ${endpoint} announced for ${nodeId}`);
1595
+ this.#nodesByEndpoint.set(endpoint, nodeId);
1596
+ }
1597
+ this.#nodes.set(nodeId, this.updateNode(node, new Set(users ?? []), nodeId, this.#nodes.get(nodeId)));
1598
+ }
1599
+ this.cleanupOldNodes();
1600
+ const sortedNodes = this.sortedNodeValues();
1601
+ return nodes.map((e) => {
1602
+ const { node } = e;
1603
+ const connect = this.findConnections(sortedNodes, this.#nodes.get(node));
1604
+ return { node, connect };
1605
+ });
1606
+ }
1607
+ find(nodeId) {
1608
+ const e = this.#nodes.get(nodeId);
1609
+ if (e !== void 0) {
1610
+ const sortedNodes = this.sortedNodeValues();
1611
+ const { node } = e;
1612
+ return this.findConnections(sortedNodes, this.#nodes.get(node));
1613
+ }
1614
+ return void 0;
1615
+ }
1616
+ remove(nodeId) {
1617
+ const removed = this.#nodes.get(nodeId);
1618
+ if (removed !== void 0) {
1619
+ this.#nodes.delete(nodeId);
1620
+ const endpoint = removed.endpoint;
1621
+ this.#nodesByEndpoint.delete(endpoint);
1622
+ this.#logger.info(`endpoint ${endpoint} removed for ${nodeId}`);
1623
+ return true;
1624
+ }
1625
+ return false;
1626
+ }
1627
+ updateNode(newNode, users, _key, oldNode) {
1628
+ const node = oldNode ?? { ...newNode, memberId: this.#memberIds++ };
1629
+ return { ...node, users, lastAccess: Date.now() };
1630
+ }
1631
+ sortedNodeValues() {
1632
+ return Array.from(this.#nodes.values()).sort((a, b) => a.memberId - b.memberId);
1633
+ }
1634
+ cleanupOldNodes() {
1635
+ const threshold = Date.now() - this.#timeout;
1636
+ for (const [nodeId, v] of this.#nodes) {
1637
+ if (v.lastAccess < threshold) {
1638
+ if (this.#logger.enabledFor("debug")) {
1639
+ this.#logger.debug(`${nodeId} expired - no announcement since ${new Date(v.lastAccess).toISOString()}, timeout is ${this.#timeout} ms.`);
1640
+ }
1641
+ this.#nodes.delete(nodeId);
1642
+ }
1643
+ }
1644
+ }
1645
+ findConnections(sortedNodes, node) {
1646
+ return sortedNodes.reduce((l, c) => {
1647
+ if (node !== void 0 && c.memberId < node.memberId) {
1648
+ const intersection = new Set(c.users);
1649
+ node.users.forEach((user) => {
1650
+ if (!c.users.has(user)) {
1651
+ intersection.delete(user);
1652
+ }
1653
+ });
1654
+ c.users.forEach((user) => {
1655
+ if (!node.users.has(user)) {
1656
+ intersection.delete(user);
1657
+ }
1658
+ });
1659
+ if (intersection.size > 0) {
1660
+ const e = { node: c.node, endpoint: c.endpoint };
1661
+ return l.concat(e);
1662
+ }
1663
+ }
1664
+ return l;
1665
+ }, new Array());
1666
+ }
1667
+ };
1668
+
1669
+ // ../bridge-mesh/src/mesh/relays.ts
1670
+ import { IOGateway as IOGateway3 } from "@interopio/gateway";
1671
+ var codec = IOGateway3.Encoding.transit({
1672
+ keywordize: /* @__PURE__ */ new Map([
1673
+ ["/type", "*"],
1674
+ ["/message/body/type", "*"],
1675
+ ["/message/origin", "*"],
1676
+ ["/message/receiver/type", "*"],
1677
+ ["/message/source/type", "*"],
1678
+ ["/message/body/type", "*"]
1679
+ ])
1680
+ });
1681
+ var InternalRelays = class {
1682
+ #logger;
1683
+ // key -> socket
1684
+ #clients = /* @__PURE__ */ new Map();
1685
+ // node -> key
1686
+ #links = /* @__PURE__ */ new Map();
1687
+ onMsg;
1688
+ onErr;
1689
+ constructor(logger) {
1690
+ this.#logger = logger;
1691
+ }
1692
+ add(key, soc) {
1693
+ this.#clients.set(key, soc);
1694
+ }
1695
+ remove(key) {
1696
+ this.#clients.delete(key);
1697
+ for (const [node, k] of this.#links) {
1698
+ if (k === key) {
1699
+ this.#links.delete(node);
1700
+ }
1701
+ }
1702
+ }
1703
+ receive(key, msg) {
1704
+ const node = this.link(key, msg);
1705
+ if (node && this.onMsg) {
1706
+ this.onMsg(key, node, msg);
1707
+ }
1708
+ }
1709
+ link(key, msg) {
1710
+ try {
1711
+ const decoded = codec.decode(msg);
1712
+ const { type, from, to } = decoded;
1713
+ if (to === "all") {
1714
+ switch (type) {
1715
+ case "hello": {
1716
+ if (this.#logger.enabledFor("debug")) {
1717
+ this.#logger.debug(`${key} registers node ${from}`);
1718
+ }
1719
+ this.#links.set(from, key);
1720
+ break;
1721
+ }
1722
+ case "bye": {
1723
+ if (this.#logger.enabledFor("debug")) {
1724
+ this.#logger.debug(`${key} unregisters node ${from}`);
1725
+ }
1726
+ this.#links.delete(from);
1727
+ break;
1728
+ }
1729
+ }
1730
+ return;
1731
+ }
1732
+ return from;
1733
+ } catch (e) {
1734
+ if (this.onErr) {
1735
+ this.onErr(key, e instanceof Error ? e : new Error(`link failed :${e}`));
1736
+ } else {
1737
+ this.#logger.warn(`${key} unable to process ${msg}`, e);
1738
+ }
1739
+ }
1740
+ }
1741
+ send(key, node, msg, cb) {
1742
+ const decoded = codec.decode(msg);
1743
+ if (this.#logger.enabledFor("debug")) {
1744
+ this.#logger.debug(`${key} sending msg to ${node} ${JSON.stringify(decoded)}`);
1745
+ }
1746
+ const clientKey = this.#links.get(node);
1747
+ if (clientKey) {
1748
+ const client = this.#clients.get(clientKey);
1749
+ if (client) {
1750
+ client.send(msg, { binary: false }, (err) => {
1751
+ cb(clientKey, err);
1752
+ });
1753
+ return;
1754
+ }
1755
+ }
1756
+ throw new Error(`${key} no active link for ${decoded.to}`);
1757
+ }
1758
+ };
1759
+
1760
+ // ../bridge-mesh/src/mesh/rest-directory/routes.ts
1761
+ function routes(config, { handle }) {
1762
+ const { connections, authorize } = config;
1763
+ handle(
1764
+ {
1765
+ request: { method: "POST", path: "/api/nodes" },
1766
+ options: { cors: true, authorize },
1767
+ handler: async ({ request, response }) => {
1768
+ const json = await request.json;
1769
+ if (!Array.isArray(json)) {
1770
+ response.statusCode = 400;
1771
+ await response.end();
1772
+ } else {
1773
+ const nodes = json;
1774
+ const result = connections.announce(nodes);
1775
+ const buffer = Buffer.from(JSON.stringify(result), "utf-8");
1776
+ response.statusCode = 200;
1777
+ response.headers.set("Content-Type", "application/json; charset=utf-8").set("Content-Length", buffer.length);
1778
+ await response.end(buffer);
1779
+ }
1780
+ }
1781
+ },
1782
+ {
1783
+ request: { method: "GET", path: /^\/api\/nodes\/(?<nodeId>.*)$/ },
1784
+ options: { cors: true, authorize },
1785
+ handler: async ({ request, response }, { nodeId }) => {
1786
+ const result = connections.find(nodeId);
1787
+ if (result === void 0) {
1788
+ const buffer = Buffer.from(JSON.stringify(result), "utf-8");
1789
+ response.statusCode = 200;
1790
+ response.headers.set("Content-Type", "application/json; charset=utf-8").set("Content-Length", buffer.length);
1791
+ await response.end(buffer);
1792
+ } else {
1793
+ response.statusCode = 404;
1794
+ await response.end();
1795
+ }
1796
+ }
1797
+ },
1798
+ {
1799
+ request: { method: "DELETE", path: /^\/api\/nodes\/(?<nodeId>.*)$/ },
1800
+ options: { cors: true, authorize },
1801
+ handler: async ({ response }, { nodeId }) => {
1802
+ const removed = connections.remove(nodeId);
1803
+ if (removed) {
1804
+ response.statusCode = 200;
1805
+ } else {
1806
+ response.statusCode = 404;
1807
+ }
1808
+ await response.end();
1809
+ }
1810
+ }
1811
+ );
1812
+ }
1813
+ var routes_default = routes;
1814
+
1815
+ // ../bridge-mesh/src/mesh/ws/relays/core.ts
1816
+ import "@interopio/gateway";
1817
+ async function create(log, internal, env) {
1818
+ log.info(`relays server is listening`);
1819
+ return async (socket, handshake) => {
1820
+ const key = handshake.logPrefix;
1821
+ log.info(`${key} connected on relays`);
1822
+ internal.add(key, socket);
1823
+ socket.on("error", (err) => {
1824
+ log.error(`[${key}] websocket error: ${err}`, err);
1825
+ });
1826
+ socket.on("message", (data, _isBinary) => {
1827
+ if (Array.isArray(data)) {
1828
+ data = Buffer.concat(data);
1829
+ }
1830
+ try {
1831
+ internal.receive(key, data);
1832
+ } catch (e) {
1833
+ log.warn(`[${key}] error processing received data '${data}'`, e);
1834
+ }
1835
+ });
1836
+ socket.on("close", (code, reason) => {
1837
+ internal.remove(key);
1838
+ log.info(`${key} disconnected from relays`);
1839
+ });
1840
+ };
1841
+ }
1842
+ var meshRelays = ({ logger, internal }) => {
1843
+ return async (env) => {
1844
+ return await create(logger, internal, env);
1845
+ };
1846
+ };
1847
+
1848
+ // ../bridge-mesh/src/mesh/ws/cluster/core.ts
1849
+ import "@interopio/gateway";
1850
+ function onMessage(relays, log, key, node, socketsByNodeId, msg) {
1851
+ try {
1852
+ relays.send(key, node, msg, (k, err) => {
1853
+ if (err) {
1854
+ log.warn(`${k} error writing msg ${msg}: ${err}`);
1855
+ return;
1856
+ }
1857
+ if (log.enabledFor("debug")) {
1858
+ log.debug(`${k} sent msg ${msg}`);
1859
+ }
1860
+ });
1861
+ } catch (ex) {
1862
+ log.error(`${key} unable to process message`, ex);
1863
+ if (node) {
1864
+ const socket = socketsByNodeId.get(node)?.get(key);
1865
+ socket?.terminate();
1866
+ }
1867
+ }
1868
+ }
1869
+ async function create2(log, relays, _env) {
1870
+ const socketsByNodeId = /* @__PURE__ */ new Map();
1871
+ relays.onMsg = (k, nodeId, msg) => {
1872
+ try {
1873
+ const sockets = socketsByNodeId.get(nodeId);
1874
+ if (sockets && sockets.size > 0) {
1875
+ for (const [key, socket] of sockets) {
1876
+ socket.send(msg, { binary: false }, (err) => {
1877
+ if (err) {
1878
+ log.warn(`${key} error writing from ${k} msg ${msg}: ${err}`);
1879
+ return;
1880
+ }
1881
+ if (log.enabledFor("debug")) {
1882
+ log.debug(`${key} sent from ${k} msg ${msg}`);
1883
+ }
1884
+ });
1885
+ }
1886
+ } else {
1887
+ log.warn(`${k} dropped msg ${msg}.`);
1888
+ }
1889
+ } catch (ex) {
1890
+ log.error(`${k} unable to process message`, ex);
1891
+ }
1892
+ };
1893
+ return async (socket, handshake) => {
1894
+ const key = handshake.logPrefix;
1895
+ const query = handshake.url.searchParams;
1896
+ log.info(`${key} connected on cluster with ${query}`);
1897
+ const node = query.get("node");
1898
+ if (node) {
1899
+ let sockets = socketsByNodeId.get(node);
1900
+ if (!sockets) {
1901
+ sockets = /* @__PURE__ */ new Map();
1902
+ socketsByNodeId.set(node, sockets);
1903
+ }
1904
+ sockets.set(key, socket);
1905
+ } else {
1906
+ socket.terminate();
1907
+ return;
1908
+ }
1909
+ socket.on("error", (err) => {
1910
+ log.error(`${key} websocket error: ${err}`, err);
1911
+ });
1912
+ socket.on("message", (data, _isBinary) => {
1913
+ if (Array.isArray(data)) {
1914
+ data = Buffer.concat(data);
1915
+ }
1916
+ onMessage(relays, log, key, node, socketsByNodeId, data);
1917
+ });
1918
+ socket.on("close", (_code, _reason) => {
1919
+ log.info(`${key} disconnected from cluster`);
1920
+ const sockets = socketsByNodeId.get(node);
1921
+ if (sockets) {
1922
+ sockets.delete(key);
1923
+ if (sockets.size === 0) {
1924
+ socketsByNodeId.delete(node);
1925
+ }
1926
+ }
1927
+ });
1928
+ };
1929
+ }
1930
+ var meshCluster = ({ logger, relays }) => {
1931
+ return async (environment) => {
1932
+ return await create2(logger, relays, environment);
1933
+ };
1934
+ };
1935
+
1936
+ // ../bridge-mesh/src/index.ts
1937
+ import "@interopio/gateway-server";
1938
+ import "@interopio/gateway";
1939
+ var mesh = async (options, configurer, config) => {
1940
+ const { enabled, logger } = options;
1941
+ if (enabled !== true) {
1942
+ logger.debug(`no mesh`);
1943
+ return;
1944
+ }
1945
+ let { socket, timeout } = options;
1946
+ const authorize = socket.authorize ?? { access: config.auth.type === "none" ? "permitted" : "authenticated" };
1947
+ socket = { ping: 3e4, authorize, ...socket };
1948
+ timeout ??= 6e4;
1949
+ const connections = new InMemoryNodeConnections(logger, timeout);
1950
+ const relays = new InternalRelays(logger);
1951
+ routes_default({ connections, authorize }, configurer);
1952
+ configurer.socket(
1953
+ {
1954
+ path: "/cluster",
1955
+ options: socket,
1956
+ factory: meshCluster({ logger, relays })
1957
+ },
1958
+ {
1959
+ path: "/relays",
1960
+ options: socket,
1961
+ factory: meshRelays({ logger, internal: relays })
1962
+ }
1963
+ );
1964
+ };
1965
+
1966
+ // src/instance/BridgeNode.ts
1967
+ function logStartingInfo(logger) {
1968
+ logger.info(`Starting io.Bridge v${package_default.version} using Node.js ${process.version} with PID ${process.pid} (started by ${userInfo().username} in ${process.cwd()})`);
1969
+ }
1970
+ function createDiscoveryService(logger, conf, isAutoDetectionEnabled, localMember) {
1971
+ const factory = new DefaultDiscoveryServiceFactory();
1972
+ const node = localMember.then((member) => new SimpleDiscoveryNode(member.address));
1973
+ const settings = {
1974
+ mode: "member",
1975
+ node,
1976
+ conf,
1977
+ auto: isAutoDetectionEnabled,
1978
+ logger
1979
+ };
1980
+ return factory.newDiscoveryService(settings);
1981
+ }
1982
+ function createAddressPicker(node) {
1983
+ const config = node.config;
1984
+ const logger = node.getLogger("AddressPicker");
1985
+ return new DefaultAddressPicker(config, logger);
1986
+ }
1987
+ var BridgeNode = class {
1988
+ config;
1989
+ uuid;
1990
+ discoveryService;
1991
+ version;
1992
+ server;
1993
+ licenseValidator;
1994
+ constructor(config) {
1995
+ this.config = config;
1996
+ this.licenseValidator = BridgeLicenseValidator(this.getLogger("license-validator"));
1997
+ this.licenseValidator.validate(config.license);
1998
+ this.uuid = newUUID();
1999
+ this.version = parseVersion(package_default.version);
2000
+ const addressPicker = createAddressPicker(this);
2001
+ const localMemberPromise = addressPicker.pickAddress().then(() => addressPicker.getPublicAddressFor("member")).then((publicAddress) => {
2002
+ return {
2003
+ local: true,
2004
+ address: publicAddress,
2005
+ uuid: this.uuid,
2006
+ version: this.version
2007
+ };
2008
+ });
2009
+ const joinConfig = config.network.join;
2010
+ const isAutoDetectionEnabled = joinConfig.autoDetectionEnabled;
2011
+ const discoveryConfig = joinConfig.discovery;
2012
+ this.discoveryService = createDiscoveryService(getLogger("discovery-service"), discoveryConfig, isAutoDetectionEnabled, localMemberPromise);
2013
+ }
2014
+ getLogger(name) {
2015
+ return getLogger(name);
2016
+ }
2017
+ async scheduleLicenseCheck() {
2018
+ const task = async () => {
2019
+ try {
2020
+ this.licenseValidator.validate(this.config.license, {
2021
+ logSuccessMessages: false
2022
+ });
2023
+ } catch (error) {
2024
+ await this.stop();
2025
+ }
2026
+ };
2027
+ const licenseCheckInterval = 1e3 * 60 * 60 * 24;
2028
+ return setInterval(task, licenseCheckInterval);
2029
+ }
2030
+ async start() {
2031
+ const config = {
2032
+ port: this.config.server.port,
2033
+ host: this.config.server.host,
2034
+ app: async (configurer, config2) => {
2035
+ await mesh({
2036
+ logger: this.getLogger("mesh"),
2037
+ enabled: true,
2038
+ timeout: this.config.mesh.timeout ?? 6e4,
2039
+ // 60 seconds
2040
+ socket: {
2041
+ ping: this.config.server.wsPingInterval ?? 3e4
2042
+ // 30 seconds
2043
+ }
2044
+ }, configurer, config2);
2045
+ },
2046
+ auth: { type: "none", ...this.config.server.auth },
2047
+ cors: this.config.server.cors
2048
+ };
2049
+ if (this.config.gateway.enabled) {
2050
+ config.gateway = {
2051
+ clients: {
2052
+ inactive_seconds: 0
2053
+ },
2054
+ ping: this.config.server.wsPingInterval ?? void 0,
2055
+ mesh: {
2056
+ cluster: {
2057
+ endpoint: `http://localhost:${this.config.server.port}`,
2058
+ directory: { interval: 1e3 * 30 }
2059
+ // 30 seconds
2060
+ }
2061
+ },
2062
+ contexts: {
2063
+ lifetime: this.config.gateway.contexts.lifetime,
2064
+ visibility: [{ restrictions: "cluster" }]
2065
+ },
2066
+ peers: {
2067
+ visibility: [{
2068
+ domain: "interop",
2069
+ restrictions: "local"
2070
+ }]
2071
+ }
2072
+ };
2073
+ }
2074
+ logStartingInfo(this.getLogger("node"));
2075
+ this.server = await gatewayServer(config);
2076
+ if (false) {
2077
+ await this.discoveryService.start();
2078
+ }
2079
+ await this.scheduleLicenseCheck();
2080
+ }
2081
+ async stop() {
2082
+ if (this.server) {
2083
+ await this.server.close();
2084
+ }
2085
+ try {
2086
+ await this.discoveryService.close();
2087
+ } catch (e) {
2088
+ }
2089
+ }
2090
+ };
2091
+
2092
+ // src/main.mts
2093
+ var hadFatalError = false;
2094
+ var reportedErrors = /* @__PURE__ */ new Set();
2095
+ function onFatalError(error) {
2096
+ process.exitCode = 2;
2097
+ hadFatalError = true;
2098
+ const errorMessage = `io.Bridge: ${package_default.version}`;
2099
+ if (!reportedErrors.has(errorMessage)) {
2100
+ console.error(error);
2101
+ reportedErrors.add(errorMessage);
2102
+ }
2103
+ }
2104
+ function propertyNameToEnvKey(name) {
2105
+ return `IO_BRIDGE_${name.toUpperCase().replaceAll("-", "").replaceAll(".", "_")}`;
2106
+ }
2107
+ function loadConfig(name, converter) {
2108
+ const cmdArg = process.argv.find((arg) => {
2109
+ return arg.startsWith(`--${name}=`);
2110
+ })?.split("=")[1];
2111
+ if (cmdArg) {
2112
+ return converter.convert(cmdArg);
2113
+ }
2114
+ const envKey = propertyNameToEnvKey(name);
2115
+ const envVar = process.env[envKey];
2116
+ if (envVar) {
2117
+ return converter.convert(envVar);
2118
+ }
2119
+ }
2120
+ try {
2121
+ process.on("uncaughtException", onFatalError);
2122
+ process.on("unhandledRejection", onFatalError);
2123
+ await import("dotenv/config");
2124
+ const config = new Config();
2125
+ config.license ??= loadConfig("license.key", STRING);
2126
+ config.server.port ??= loadConfig("server.port", NUMBER) ?? 8383;
2127
+ config.server.host ??= loadConfig("server.host", STRING);
2128
+ config.server.wsPingInterval ??= loadConfig("server.ws-ping-interval", NUMBER);
2129
+ config.gateway.enabled ??= loadConfig("gateway.enabled", BOOLEAN);
2130
+ config.gateway.contexts.lifetime ??= loadConfig("gateway.contexts.lifetime", STRING);
2131
+ config.server.auth.type = loadConfig("server.auth.type", STRING) ?? "none";
2132
+ config.server.cors.allowOrigin = loadConfig("server.cors.allow-origin", STRING) ?? "*";
2133
+ config.server.cors.allowCredentials = loadConfig("server.cors.allow-credentials", BOOLEAN) ?? true;
2134
+ const bridge = new BridgeNode(config);
2135
+ await bridge.start();
2136
+ } catch (error) {
2137
+ onFatalError(error);
2138
+ }
2139
+ //# sourceMappingURL=main.js.map