UNPKG

sterfive-bonjour-service

Version:

A Bonjour/Zeroconf implementation in TypeScript

185 lines 7.41 kB
"use strict"; 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