| 'use strict' | 
|   | 
| var multicastdns = require('multicast-dns') | 
| var dnsEqual = require('dns-equal') | 
| var flatten = require('array-flatten') | 
| var deepEqual = require('deep-equal') | 
|   | 
| module.exports = Server | 
|   | 
| function Server (opts) { | 
|   this.mdns = multicastdns(opts) | 
|   this.mdns.setMaxListeners(0) | 
|   this.registry = {} | 
|   this.mdns.on('query', this._respondToQuery.bind(this)) | 
| } | 
|   | 
| Server.prototype.register = function (records) { | 
|   var self = this | 
|   | 
|   if (Array.isArray(records)) records.forEach(register) | 
|   else register(records) | 
|   | 
|   function register (record) { | 
|     var subRegistry = self.registry[record.type] | 
|     if (!subRegistry) subRegistry = self.registry[record.type] = [] | 
|     else if (subRegistry.some(isDuplicateRecord(record))) return | 
|     subRegistry.push(record) | 
|   } | 
| } | 
|   | 
| Server.prototype.unregister = function (records) { | 
|   var self = this | 
|   | 
|   if (Array.isArray(records)) records.forEach(unregister) | 
|   else unregister(records) | 
|   | 
|   function unregister (record) { | 
|     var type = record.type | 
|     if (!(type in self.registry)) return | 
|     self.registry[type] = self.registry[type].filter(function (r) { | 
|       return r.name !== record.name | 
|     }) | 
|   } | 
| } | 
|   | 
| Server.prototype._respondToQuery = function (query) { | 
|   var self = this | 
|   query.questions.forEach(function (question) { | 
|     var type = question.type | 
|     var name = question.name | 
|   | 
|     // generate the answers section | 
|     var answers = type === 'ANY' | 
|       ? flatten.depth(Object.keys(self.registry).map(self._recordsFor.bind(self, name)), 1) | 
|       : self._recordsFor(name, type) | 
|   | 
|     if (answers.length === 0) return | 
|   | 
|     // generate the additionals section | 
|     var additionals = [] | 
|     if (type !== 'ANY') { | 
|       answers.forEach(function (answer) { | 
|         if (answer.type !== 'PTR') return | 
|         additionals = additionals | 
|           .concat(self._recordsFor(answer.data, 'SRV')) | 
|           .concat(self._recordsFor(answer.data, 'TXT')) | 
|       }) | 
|   | 
|       // to populate the A and AAAA records, we need to get a set of unique | 
|       // targets from the SRV record | 
|       additionals | 
|         .filter(function (record) { | 
|           return record.type === 'SRV' | 
|         }) | 
|         .map(function (record) { | 
|           return record.data.target | 
|         }) | 
|         .filter(unique()) | 
|         .forEach(function (target) { | 
|           additionals = additionals | 
|             .concat(self._recordsFor(target, 'A')) | 
|             .concat(self._recordsFor(target, 'AAAA')) | 
|         }) | 
|     } | 
|   | 
|     self.mdns.respond({ answers: answers, additionals: additionals }, function (err) { | 
|       if (err) throw err // TODO: Handle this (if no callback is given, the error will be ignored) | 
|     }) | 
|   }) | 
| } | 
|   | 
| Server.prototype._recordsFor = function (name, type) { | 
|   if (!(type in this.registry)) return [] | 
|   | 
|   return this.registry[type].filter(function (record) { | 
|     var _name = ~name.indexOf('.') ? record.name : record.name.split('.')[0] | 
|     return dnsEqual(_name, name) | 
|   }) | 
| } | 
|   | 
| function isDuplicateRecord (a) { | 
|   return function (b) { | 
|     return a.type === b.type && | 
|       a.name === b.name && | 
|       deepEqual(a.data, b.data) | 
|   } | 
| } | 
|   | 
| function unique () { | 
|   var set = [] | 
|   return function (obj) { | 
|     if (~set.indexOf(obj)) return false | 
|     set.push(obj) | 
|     return true | 
|   } | 
| } |