sterfive-bonjour-service
Version:
A Bonjour/Zeroconf implementation in TypeScript
185 lines • 7.41 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Registry = void 0;
const timers_1 = require("timers");
const array_flatten_1 = __importDefault(require("array-flatten"));
const dns_equal_1 = __importDefault(require("dns-equal"));
const service_1 = __importDefault(require("./service"));
const REANNOUNCE_MAX_MS = 60 * 60 * 1000;
const REANNOUNCE_FACTOR = 3;
class Registry {
constructor(server) {
this.services = [];
this.server = server;
}
publish(config) {
function start(service, registry, opts) {
if (service.activated)
return;
service.activated = true;
registry.services.push(service);
if (!(service instanceof service_1.default))
return;
if (opts.probe) {
registry.probe(registry.server.mdns, service, (exists) => {
if (exists) {
service.stop();
console.log(new Error('Service name is already in use on the network'));
return;
}
registry.announce(registry.server, service);
});
}
else {
registry.announce(registry.server, service);
}
}
function stop(service, registry, callback) {
if (!service.activated)
return;
if (!(service instanceof service_1.default))
return;
registry.teardown(registry.server, service, callback);
const index = registry.services.indexOf(service);
if (index !== -1)
registry.services.splice(index, 1);
}
let service = new service_1.default(config);
service.start = start.bind(null, service, this);
service.stop = stop.bind(null, service, this);
service.start({ probe: config.probe !== false });
return service;
}
unpublishAll(callback) {
this.teardown(this.server, this.services, callback);
this.services = [];
}
destroy() {
this.services.map((service) => {
if (service._broadCastTimeout) {
clearTimeout(service._broadCastTimeout);
service._broadCastTimeout = undefined;
}
service.destroyed = true;
});
}
/**
* Check if a service name is already in use on the network.
*
* Used before announcing the new service.
*
* To guard against race conditions where multiple services are started
* simultaneously on the network, wait a random amount of time (between
* 0 and 250 ms) before probing.
*
*/
probe(mdns, service, callback) {
let sent = false;
let retries = 0;
let timer;
const send = () => {
// abort if the service have or is being stopped in the meantime
if (!service.activated || service.destroyed)
return;
mdns.query(service.fqdn, 'ANY', function () {
// This function will optionally be called with an error object. We'll
// just silently ignore it and retry as we normally would
sent = true;
timer = (0, timers_1.setTimeout)(++retries < 3 ? send : done, 250);
timer.unref();
});
};
const matchRR = (rr) => {
return (0, dns_equal_1.default)(rr.name, service.fqdn);
};
const onresponse = (packet) => {
// Apparently conflicting Multicast DNS responses received *before*
// the first probe packet is sent MUST be silently ignored (see
// discussion of stale probe packets in RFC 6762 Section 8.2,
// "Simultaneous Probe Tiebreaking" at
// https://tools.ietf.org/html/rfc6762#section-8.2
if (!sent)
return;
if (packet.answers.some(matchRR) || packet.additionals.some(matchRR))
done(true);
};
const done = (exists) => {
mdns.removeListener('response', onresponse);
clearTimeout(timer);
callback(!!exists);
};
mdns.on('response', onresponse);
(0, timers_1.setTimeout)(send, Math.random() * 250);
}
/**
* Initial service announcement
*
* Used to announce new services when they are first registered.
*
* Broadcasts right away, then after 3 seconds, 9 seconds, 27 seconds,
* and so on, up to a maximum interval of one hour.
*/
announce(server, service) {
let delay = 1000;
const packet = service.records();
// Register the records
server.register(packet);
const broadcast = () => {
if (!service.activated || service.destroyed)
return;
service._broadCastTimeout = undefined;
server.mdns.respond({ answers: packet }, () => {
// This function will optionally be called with an error object. We'll
// just silently ignore it and retry as we normally would
if (!service.published) {
service.activated = true;
service.published = true;
service.emit('up');
}
delay = delay * REANNOUNCE_FACTOR;
if (delay < REANNOUNCE_MAX_MS && !service.destroyed) {
service._broadCastTimeout = (0, timers_1.setTimeout)(broadcast, delay);
}
});
};
service._broadCastTimeout = (0, timers_1.setTimeout)(broadcast, 1);
}
/**
* Stop the given services
*
* Besides removing a service from the mDNS registry, a "goodbye"
* message is sent for each service to let the network know about the
* shutdown.
*/
teardown(server, serviceOrServices, callback) {
let services = Array.isArray(serviceOrServices) ? serviceOrServices : [serviceOrServices];
services = services.filter((service) => service.activated); // ignore services not currently starting or started
const records = array_flatten_1.default.depth(services.map((service) => {
service.activated = false;
const records = service.records();
records.forEach((record) => {
record.ttl = 0; // prepare goodbye message
});
return records;
}), 1);
if (records.length === 0)
return callback && callback();
server.unregister(records);
// send goodbye message
server.mdns.respond(records, function () {
;
services.forEach(function (service) {
service.published = false;
});
if (typeof callback === 'function') {
callback.apply(null, arguments);
}
});
}
}
exports.Registry = Registry;
exports.default = Registry;
//# sourceMappingURL=registry.js.map