@ionic/discover
Version:
Simple UDP based protocol for service discovery implemented in pure JS.
148 lines (147 loc) • 4.83 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.computeBroadcastAddress = exports.newSilentPublisher = exports.prepareInterfaces = exports.Publisher = void 0;
const tslib_1 = require("tslib");
const debug_1 = require("debug");
const dgram = tslib_1.__importStar(require("dgram"));
const events = tslib_1.__importStar(require("events"));
const netmask_1 = require("netmask");
const os = tslib_1.__importStar(require("os"));
const debug = (0, debug_1.debug)('ionic:discover:publisher');
const PREFIX = 'ION_DP';
const PORT = 41234;
class Publisher extends events.EventEmitter {
constructor(namespace, name, port, commPort) {
super();
this.namespace = namespace;
this.name = name;
this.port = port;
this.commPort = commPort;
this.path = '/';
this.running = false;
this.interval = 2000;
if (name.indexOf(':') >= 0) {
throw new Error('name should not contain ":"');
}
this.id = Math.random().toString(10).substring(2, 8);
}
start() {
return new Promise((resolve, reject) => {
if (this.running) {
return resolve();
}
this.running = true;
if (!this.interfaces) {
this.interfaces = this.getInterfaces();
}
const client = this.client = dgram.createSocket('udp4');
client.on('error', err => {
this.emit('error', err);
});
client.on('listening', () => {
client.setBroadcast(true);
this.timer = setInterval(() => this.sayHello(), this.interval);
this.sayHello();
debug('Publisher starting');
resolve();
});
client.bind();
});
}
stop() {
if (!this.running) {
return;
}
this.running = false;
if (this.timer) {
clearInterval(this.timer);
this.timer = undefined;
}
if (this.client) {
this.client.close();
this.client = undefined;
}
}
buildMessage(ip) {
return {
t: Date.now(),
id: this.id,
nspace: this.namespace,
name: this.name,
host: os.hostname(),
ip,
port: this.port,
commPort: this.commPort,
path: this.path,
};
}
getInterfaces() {
return prepareInterfaces(os.networkInterfaces());
}
sayHello() {
if (!this.interfaces) {
throw new Error('No network interfaces set--was the service started?');
}
if (!this.client) {
throw new Error('Client not initialized--was the service started?');
}
try {
for (const iface of this.interfaces) {
const message = this.buildMessage(iface.address);
const serialized = PREFIX + JSON.stringify(message);
const buf = Buffer.from(serialized);
debug(`Broadcasting %O to ${iface.broadcast}`, serialized);
this.client.send(buf, 0, buf.length, PORT, iface.broadcast, err => {
if (err) {
this.emit('error', err);
}
});
}
}
catch (e) {
this.emit('error', e);
}
}
}
exports.Publisher = Publisher;
function prepareInterfaces(interfaces) {
const set = new Set();
const values = Object.values(interfaces);
const flatValues = values.reduce((prev, current) => prev?.concat(current ? current : []));
if (flatValues) {
return flatValues
.filter((iface) => iface.family === 'IPv4')
.map((iface) => {
return {
address: iface.address,
broadcast: computeBroadcastAddress(iface.address, iface.netmask),
};
})
.filter((iface) => {
if (!set.has(iface.broadcast)) {
set.add(iface.broadcast);
return true;
}
return false;
});
}
}
exports.prepareInterfaces = prepareInterfaces;
function newSilentPublisher(namespace, name, port) {
name = `${name}@${port}`;
const service = new Publisher(namespace, name, port);
service.on('error', () => {
// do not log
});
service.start().catch(() => {
// do not log
});
return service;
}
exports.newSilentPublisher = newSilentPublisher;
function computeBroadcastAddress(address, netmask) {
const ip = address + '/' + netmask;
const block = new netmask_1.Netmask(ip);
return block.broadcast;
}
exports.computeBroadcastAddress = computeBroadcastAddress;