| '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 |
| } |
| } |