UNPKG

4.72 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.computeBroadcastAddress = exports.newSilentPublisher = exports.prepareInterfaces = exports.Publisher = void 0;
4const Debug = require("debug");
5const dgram = require("dgram");
6const events = require("events");
7const netmask_1 = require("netmask");
8const os = require("os");
9const debug = Debug('ionic:discover:publisher');
10const PREFIX = 'ION_DP';
11const PORT = 41234;
12class Publisher extends events.EventEmitter {
13 constructor(namespace, name, port, commPort) {
14 super();
15 this.namespace = namespace;
16 this.name = name;
17 this.port = port;
18 this.commPort = commPort;
19 this.path = '/';
20 this.running = false;
21 this.interval = 2000;
22 if (name.indexOf(':') >= 0) {
23 throw new Error('name should not contain ":"');
24 }
25 this.id = Math.random().toString(10).substring(2, 8);
26 }
27 start() {
28 return new Promise((resolve, reject) => {
29 if (this.running) {
30 return resolve();
31 }
32 this.running = true;
33 if (!this.interfaces) {
34 this.interfaces = this.getInterfaces();
35 }
36 const client = this.client = dgram.createSocket('udp4');
37 client.on('error', err => {
38 this.emit('error', err);
39 });
40 client.on('listening', () => {
41 client.setBroadcast(true);
42 this.timer = setInterval(() => this.sayHello(), this.interval);
43 this.sayHello();
44 debug('Publisher starting');
45 resolve();
46 });
47 client.bind();
48 });
49 }
50 stop() {
51 if (!this.running) {
52 return;
53 }
54 this.running = false;
55 if (this.timer) {
56 clearInterval(this.timer);
57 this.timer = undefined;
58 }
59 if (this.client) {
60 this.client.close();
61 this.client = undefined;
62 }
63 }
64 buildMessage(ip) {
65 return {
66 t: Date.now(),
67 id: this.id,
68 nspace: this.namespace,
69 name: this.name,
70 host: os.hostname(),
71 ip,
72 port: this.port,
73 commPort: this.commPort,
74 path: this.path,
75 };
76 }
77 getInterfaces() {
78 return prepareInterfaces(os.networkInterfaces());
79 }
80 sayHello() {
81 if (!this.interfaces) {
82 throw new Error('No network interfaces set--was the service started?');
83 }
84 if (!this.client) {
85 throw new Error('Client not initialized--was the service started?');
86 }
87 try {
88 for (const iface of this.interfaces) {
89 const message = this.buildMessage(iface.address);
90 const serialized = PREFIX + JSON.stringify(message);
91 const buf = Buffer.from(serialized);
92 debug(`Broadcasting %O to ${iface.broadcast}`, serialized);
93 this.client.send(buf, 0, buf.length, PORT, iface.broadcast, err => {
94 if (err) {
95 this.emit('error', err);
96 }
97 });
98 }
99 }
100 catch (e) {
101 this.emit('error', e);
102 }
103 }
104}
105exports.Publisher = Publisher;
106function prepareInterfaces(interfaces) {
107 const set = new Set();
108 const values = Object.values(interfaces);
109 const flatValues = values.reduce((prev, current) => prev?.concat(current ? current : []));
110 if (flatValues) {
111 return flatValues
112 .filter((iface) => iface.family === 'IPv4')
113 .map((iface) => {
114 return {
115 address: iface.address,
116 broadcast: computeBroadcastAddress(iface.address, iface.netmask),
117 };
118 })
119 .filter((iface) => {
120 if (!set.has(iface.broadcast)) {
121 set.add(iface.broadcast);
122 return true;
123 }
124 return false;
125 });
126 }
127}
128exports.prepareInterfaces = prepareInterfaces;
129function newSilentPublisher(namespace, name, port) {
130 name = `${name}@${port}`;
131 const service = new Publisher(namespace, name, port);
132 service.on('error', () => {
133 // do not log
134 });
135 service.start().catch(() => {
136 // do not log
137 });
138 return service;
139}
140exports.newSilentPublisher = newSilentPublisher;
141function computeBroadcastAddress(address, netmask) {
142 const ip = address + '/' + netmask;
143 const block = new netmask_1.Netmask(ip);
144 return block.broadcast;
145}
146exports.computeBroadcastAddress = computeBroadcastAddress;