/* eslint-disable class-methods-use-this */
const NativeModule = require('@proceed/native-module');
const bonjour = require('nbonjour').create({
multicast: true, // use udp multicasting
loopback: true, // receive your own packets
reuseAddr: true, // set the reu
});
const { default: exitHook } = require('@darkobits/adeiu');
const PROCEED_SERVICE_TYPE = 'proceed';
/**
* Discovery class that provides the functionality to broadcast this PROCEED
* engine and finding other engines using mdns (bonjour).
* @class
*/
class MDNS extends NativeModule {
constructor() {
super();
this.commands = [
'publish',
'discover',
'unpublish',
'remove_discovered_service',
'reset_discovery',
'on_discovered',
'on_undiscovered',
];
this.published = false;
this.hostname = '';
this.port = 0;
this.txt = '';
// Start building up an internal list right away
this.find();
}
executeCommand(command, args, send) {
if (command === 'publish') {
return this.publish(args);
}
if (command === 'discover') {
return this.discoveredEngines();
}
if (command === 'unpublish') {
return this.unpublish();
}
if (command === 'remove_discovered_service') {
return this.removeDiscoveredService(args);
}
if (command === 'reset_discovery') {
return this.resetDiscovery();
}
if (command === 'on_discovered') {
// register callback for when a machine is discovered
this.onServiceUpEvent(send);
}
if (command === 'on_undiscovered') {
this.onServiceDownEvent(send);
}
return undefined;
}
/**
* Publish this engine as a PROCEED type.
*/
// eslint-disable-next-line class-methods-use-this
async publish(args) {
return new Promise((resolve, reject) => {
const [hostname, port, txt] = args;
this.hostname = hostname;
this.port = port;
this.txt = txt;
const service = bonjour.publish({
name: hostname,
type: PROCEED_SERVICE_TYPE,
port,
host: hostname + '.local',
txt,
});
service.start();
service.on('error', (error) => {
console.log('--> Error publishing bonjour service: ', error);
reject(error);
});
service.on('up', () => {
console.log('--> Published bonjour service: ', hostname);
this.published = true;
resolve();
});
// Unpublish on exit
exitHook(async () => {
await new Promise((resolve2) => {
bonjour.unpublishAll(() => {
resolve2();
});
});
});
});
}
async unpublish() {
return new Promise((resolve) => {
bonjour.unpublishAll(() => {
this.published = false;
resolve();
});
});
}
/**
* Start finding other PROCEED engines in the network.
*/
find() {
// Note: If we want to know when a service is up or down, we can add event
// listeners for the 'up' and 'down' events of the browser. Currently we
// only need the momentarily online services at a specific time, so we do
// not care when exactly they are added / removed.
this.browser = bonjour.find({ type: PROCEED_SERVICE_TYPE });
}
/**
* Set function that is triggered when the browser finds a new service
*
* @param {Function} cb the send callback that pushes the new service to the universal part
*/
onServiceUpEvent(cb) {
this.browser.on(
'up',
function (service) {
cb({
ip: service.referer.address,
port: service.port,
name: service.name,
txt: service.txt,
});
}.bind(this)
);
}
onServiceDownEvent(cb) {
this.browser.on(
'down',
function (service) {
cb({
ip: service.referer.address,
port: service.port,
name: service.name,
txt: service.txt,
});
}.bind(this)
);
}
/**
* Removes a service with the given id and port from the list of found services
*
* Can be used if a service that is supposedly discovered can't be reached using the published ip and port
*
* @param {Array} args contains the ip of the service as its first and the port as its second element
*/
removeDiscoveredService(args) {
const [ip, port] = args;
const service = this.browser.services.find((s) => s.referer.address === ip && s.port === port);
if (service) {
this.browser._removeService(service.fqdn);
}
}
/**
* Resets the discovery of services in the local network and republishes if it is currently publishing
* (Might be used when connecting to a network after a disconnect)
*/
async resetDiscovery() {
this.find();
if (this.published);
{
await this.unpublish();
await this.publish([this.hostname, this.port, this.txt]);
}
}
/**
* Return the current list of online (discovered) proceed engines in the network.
* @param taskID The taskID of the dispatcher task
*/
async discoveredEngines() {
// TODO: Ensure that we searched at least for a certain time before
// returning this list the first time? (Only relevant for instant find()
// call after startup) -> Could be handled with a retry
// console.log(this.browser.services);
return [
this.browser.services.map((service) => ({
ip: service.referer.address,
port: service.port,
name: service.name,
txt: service.txt,
})),
];
}
}
module.exports = MDNS;