sen-ether-client 0.1.5 → 0.1.7

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.
package/API.md CHANGED
@@ -67,7 +67,12 @@ variable as its multicast default:
67
67
  - `SEN_ETHER_DISCOVERY_PORT`
68
68
 
69
69
  Multicast group, bind address and interface selection are explicit `sen-ether-client`
70
- options, not SEN environment variables.
70
+ options, not SEN environment variables. When no `interfaceAddress` is provided,
71
+ multicast discovery joins every local IPv4 interface visible to Node.js. If a
72
+ SEN producer on the same host sends discovery through a physical interface that
73
+ does not loop multicast packets back locally, discovery can still return no
74
+ processes; in that case run the producer discovery on `lo`, pass the matching
75
+ `interfaceAddress`, or use SEN TCP discovery.
71
76
 
72
77
  Preferred multi-session usage:
73
78
 
package/lib/discovery.js CHANGED
@@ -50,6 +50,26 @@ function resolveInterfaceAddress(value) {
50
50
  return ipv4.address;
51
51
  }
52
52
 
53
+ function multicastInterfaceCandidates(interfaceAddress) {
54
+ if (interfaceAddress) {
55
+ return [interfaceAddress];
56
+ }
57
+
58
+ try {
59
+ const addresses = [];
60
+ for (const candidates of Object.values(os.networkInterfaces())) {
61
+ for (const item of candidates ?? []) {
62
+ if ((item.family === 'IPv4' || item.family === 4) && item.address) {
63
+ addresses.push(item.address);
64
+ }
65
+ }
66
+ }
67
+ return [...new Set(addresses)];
68
+ } catch {
69
+ return [];
70
+ }
71
+ }
72
+
53
73
  function normalizeTcpRemote(socket) {
54
74
  return {
55
75
  address: socket.remoteAddress,
@@ -149,8 +169,29 @@ export class EtherDiscoveryScanner extends EventEmitter {
149
169
  const onListening = () => {
150
170
  socket.off('error', onError);
151
171
  try {
152
- socket.addMembership(this.group, this.interfaceAddress);
172
+ const interfaces = multicastInterfaceCandidates(this.interfaceAddress);
173
+ if (interfaces.length) {
174
+ let joined = 0;
175
+ /** @type {Error | undefined} */
176
+ let firstError;
177
+ for (const interfaceAddress of interfaces) {
178
+ try {
179
+ socket.addMembership(this.group, interfaceAddress);
180
+ joined += 1;
181
+ } catch (error) {
182
+ firstError ??= /** @type {Error} */ (error);
183
+ }
184
+ }
185
+ if (!joined) {
186
+ throw firstError ?? new Error(`could not join multicast group ${this.group}`);
187
+ }
188
+ } else {
189
+ socket.addMembership(this.group);
190
+ }
153
191
  socket.setMulticastLoopback(true);
192
+ if (this.interfaceAddress) {
193
+ socket.setMulticastInterface(this.interfaceAddress);
194
+ }
154
195
  } catch (error) {
155
196
  reject(error);
156
197
  return;
package/lib/sen.js CHANGED
@@ -132,6 +132,13 @@ function busSummary(sessionName, busName) {
132
132
  };
133
133
  }
134
134
 
135
+ function knownBusNames(sen) {
136
+ return [...new Set([
137
+ ...sen.remoteBuses,
138
+ ...sen.buses.keys()
139
+ ])].sort();
140
+ }
141
+
135
142
  function selectorDescription(selector) {
136
143
  return typeof selector === 'function' ? '<predicate>' : String(selector);
137
144
  }
@@ -364,12 +371,7 @@ export class Sen extends EventEmitter {
364
371
  throw new Error('no SEN ether processes discovered');
365
372
  }
366
373
  this.targets = targets;
367
- for (const target of targets) {
368
- const sessionName = targetSessionName(target);
369
- if (sessionName && !this.targetsBySession.has(sessionName)) {
370
- this.targetsBySession.set(sessionName, target);
371
- }
372
- }
374
+ this.#rememberTargets(targets, { replace: false });
373
375
  this.emit('connect', {
374
376
  sessions: [...this.targetsBySession.keys()],
375
377
  targets
@@ -602,8 +604,7 @@ export class Sen extends EventEmitter {
602
604
  }
603
605
 
604
606
  const sessionName = this.target?.session?.name ?? this.client.processInfo.sessionName;
605
- return [...this.remoteBuses]
606
- .sort()
607
+ return knownBusNames(this)
607
608
  .map(busName => options.qualified ? queryBusName(sessionName, busName) : busName);
608
609
  }
609
610
 
@@ -621,24 +622,68 @@ export class Sen extends EventEmitter {
621
622
 
622
623
  if (this.client) {
623
624
  const sessionName = this.target?.session?.name ?? this.client.processInfo.sessionName;
624
- return (await waitForSessionBuses(this, settleMs))
625
- .map(busName => busSummary(sessionName, busName))
626
- .filter(Boolean)
627
- .sort((a, b) => a.qualified.localeCompare(b.qualified));
628
- }
625
+ const summaries = new Map();
626
+ const addBus = busName => {
627
+ const summary = busSummary(sessionName, busName);
628
+ if (summary) {
629
+ summaries.set(summary.qualified, summary);
630
+ }
631
+ };
629
632
 
630
- if (!this.targets.length && !this.sessions.size) {
631
- const targets = await this.#discoverTargets(config);
632
- if (!targets.length) {
633
- throw new Error('no SEN ether processes discovered');
633
+ for (const busName of await waitForSessionBuses(this, settleMs)) {
634
+ addBus(busName);
634
635
  }
635
- this.targets = targets;
636
- for (const target of targets) {
637
- const sessionName = targetSessionName(target);
638
- if (sessionName && !this.targetsBySession.has(sessionName)) {
639
- this.targetsBySession.set(sessionName, target);
636
+
637
+ if (!summaries.size || config.refreshTargets === true) {
638
+ try {
639
+ const target = await this.#discoverTarget({ ...config, session: sessionName });
640
+ if (target) {
641
+ const session = new Sen({
642
+ ...config,
643
+ session: sessionName,
644
+ reconnect: false
645
+ });
646
+ session.on('warning', error => this.emit('warning', error));
647
+ session.on('error', error => this.emit('warning', error));
648
+ try {
649
+ await session.connect({
650
+ ...config,
651
+ session: sessionName,
652
+ target,
653
+ reconnect: false
654
+ });
655
+ for (const busName of await waitForSessionBuses(session, settleMs)) {
656
+ addBus(busName);
657
+ }
658
+ } finally {
659
+ await session.close().catch(error => this.emit('warning', error));
660
+ }
661
+ }
662
+ } catch (error) {
663
+ this.emit('warning', error);
640
664
  }
641
665
  }
666
+
667
+ return [...summaries.values()].sort((a, b) => a.qualified.localeCompare(b.qualified));
668
+ }
669
+
670
+ let discoveredTargets = [];
671
+ if (!this.targets.length || this.sessions.size || config.refreshTargets === true) {
672
+ try {
673
+ discoveredTargets = await this.#discoverTargets(config);
674
+ } catch (error) {
675
+ if (!this.targets.length && !this.sessions.size) {
676
+ throw error;
677
+ }
678
+ this.emit('warning', error);
679
+ }
680
+ }
681
+
682
+ if (discoveredTargets.length) {
683
+ this.targets = discoveredTargets;
684
+ this.#rememberTargets(discoveredTargets, { replace: true });
685
+ } else if (!this.targets.length && !this.sessions.size) {
686
+ throw new Error('no SEN ether processes discovered');
642
687
  }
643
688
 
644
689
  const summaries = new Map();
@@ -782,6 +827,16 @@ export class Sen extends EventEmitter {
782
827
  return target;
783
828
  }
784
829
 
830
+ #rememberTargets(targets, options = {}) {
831
+ const replace = options.replace !== false;
832
+ for (const target of targets) {
833
+ const sessionName = targetSessionName(target);
834
+ if (sessionName && (replace || !this.targetsBySession.has(sessionName))) {
835
+ this.targetsBySession.set(sessionName, target);
836
+ }
837
+ }
838
+ }
839
+
785
840
  async #reconnectTarget(options) {
786
841
  if (options.tcpHub || !options.target) {
787
842
  return await this.#discoverTarget(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sen-ether-client",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Pure JavaScript SEN client for existing kernels over ether",
5
5
  "senCompatibility": {
6
6
  "kernelProtocolVersion": 9,