| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 | 
							- 'use strict'
 
- var dnsEqual = require('dns-equal')
 
- var flatten = require('array-flatten')
 
- var Service = require('./service')
 
- var REANNOUNCE_MAX_MS = 60 * 60 * 1000
 
- var REANNOUNCE_FACTOR = 3
 
- module.exports = Registry
 
- function Registry (server) {
 
-   this._server = server
 
-   this._services = []
 
- }
 
- Registry.prototype.publish = function (opts) {
 
-   var service = new Service(opts)
 
-   service.start = start.bind(service, this)
 
-   service.stop = stop.bind(service, this)
 
-   service.start({ probe: opts.probe !== false })
 
-   return service
 
- }
 
- Registry.prototype.unpublishAll = function (cb) {
 
-   teardown(this._server, this._services, cb)
 
-   this._services = []
 
- }
 
- Registry.prototype.destroy = function () {
 
-   this._services.forEach(function (service) {
 
-     service._destroyed = true
 
-   })
 
- }
 
- function start (registry, opts) {
 
-   if (this._activated) return
 
-   this._activated = true
 
-   registry._services.push(this)
 
-   if (opts.probe) {
 
-     var service = this
 
-     probe(registry._server.mdns, this, function (exists) {
 
-       if (exists) {
 
-         service.stop()
 
-         service.emit('error', new Error('Service name is already in use on the network'))
 
-         return
 
-       }
 
-       announce(registry._server, service)
 
-     })
 
-   } else {
 
-     announce(registry._server, this)
 
-   }
 
- }
 
- function stop (registry, cb) {
 
-   if (!this._activated) return // TODO: What about the callback?
 
-   teardown(registry._server, this, cb)
 
-   var index = registry._services.indexOf(this)
 
-   if (index !== -1) registry._services.splice(index, 1)
 
- }
 
- /**
 
-  * 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.
 
-  *
 
-  * TODO: Add support for Simultaneous Probe Tiebreaking:
 
-  * https://tools.ietf.org/html/rfc6762#section-8.2
 
-  */
 
- function probe (mdns, service, cb) {
 
-   var sent = false
 
-   var retries = 0
 
-   var timer
 
-   mdns.on('response', onresponse)
 
-   setTimeout(send, Math.random() * 250)
 
-   function 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 = setTimeout(++retries < 3 ? send : done, 250)
 
-       timer.unref()
 
-     })
 
-   }
 
-   function 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)
 
-   }
 
-   function matchRR (rr) {
 
-     return dnsEqual(rr.name, service.fqdn)
 
-   }
 
-   function done (exists) {
 
-     mdns.removeListener('response', onresponse)
 
-     clearTimeout(timer)
 
-     cb(!!exists)
 
-   }
 
- }
 
- /**
 
-  * 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.
 
-  */
 
- function announce (server, service) {
 
-   var delay = 1000
 
-   var packet = service._records()
 
-   server.register(packet)
 
-   ;(function broadcast () {
 
-     // abort if the service have or is being stopped in the meantime
 
-     if (!service._activated || service._destroyed) return
 
-     server.mdns.respond(packet, function () {
 
-       // 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) {
 
-         setTimeout(broadcast, delay).unref()
 
-       }
 
-     })
 
-   })()
 
- }
 
- /**
 
-  * 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.
 
-  */
 
- function teardown (server, services, cb) {
 
-   if (!Array.isArray(services)) services = [services]
 
-   services = services.filter(function (service) {
 
-     return service._activated // ignore services not currently starting or started
 
-   })
 
-   var records = flatten.depth(services.map(function (service) {
 
-     service._activated = false
 
-     var records = service._records()
 
-     records.forEach(function (record) {
 
-       record.ttl = 0 // prepare goodbye message
 
-     })
 
-     return records
 
-   }), 1)
 
-   if (records.length === 0) return cb && cb()
 
-   server.unregister(records)
 
-   // send goodbye message
 
-   server.mdns.respond(records, function () {
 
-     services.forEach(function (service) {
 
-       service.published = false
 
-     })
 
-     if (cb) cb.apply(null, arguments)
 
-   })
 
- }
 
 
  |