| var packet = require('dns-packet') | 
| var dgram = require('dgram') | 
| var thunky = require('thunky') | 
| var events = require('events') | 
| var os = require('os') | 
|   | 
| var noop = function () {} | 
|   | 
| module.exports = function (opts) { | 
|   if (!opts) opts = {} | 
|   | 
|   var that = new events.EventEmitter() | 
|   var port = typeof opts.port === 'number' ? opts.port : 5353 | 
|   var type = opts.type || 'udp4' | 
|   var ip = opts.ip || opts.host || (type === 'udp4' ? '224.0.0.251' : null) | 
|   var me = {address: ip, port: port} | 
|   var memberships = {} | 
|   var destroyed = false | 
|   var interval = null | 
|   | 
|   if (type === 'udp6' && (!ip || !opts.interface)) { | 
|     throw new Error('For IPv6 multicast you must specify `ip` and `interface`') | 
|   } | 
|   | 
|   var socket = opts.socket || dgram.createSocket({ | 
|     type: type, | 
|     reuseAddr: opts.reuseAddr !== false, | 
|     toString: function () { | 
|       return type | 
|     } | 
|   }) | 
|   | 
|   socket.on('error', function (err) { | 
|     if (err.code === 'EACCES' || err.code === 'EADDRINUSE') that.emit('error', err) | 
|     else that.emit('warning', err) | 
|   }) | 
|   | 
|   socket.on('message', function (message, rinfo) { | 
|     try { | 
|       message = packet.decode(message) | 
|     } catch (err) { | 
|       that.emit('warning', err) | 
|       return | 
|     } | 
|   | 
|     that.emit('packet', message, rinfo) | 
|   | 
|     if (message.type === 'query') that.emit('query', message, rinfo) | 
|     if (message.type === 'response') that.emit('response', message, rinfo) | 
|   }) | 
|   | 
|   socket.on('listening', function () { | 
|     if (!port) port = me.port = socket.address().port | 
|     if (opts.multicast !== false) { | 
|       that.update() | 
|       interval = setInterval(that.update, 5000) | 
|       socket.setMulticastTTL(opts.ttl || 255) | 
|       socket.setMulticastLoopback(opts.loopback !== false) | 
|     } | 
|   }) | 
|   | 
|   var bind = thunky(function (cb) { | 
|     if (!port) return cb(null) | 
|     socket.once('error', cb) | 
|     socket.bind(port, opts.interface, function () { | 
|       socket.removeListener('error', cb) | 
|       cb(null) | 
|     }) | 
|   }) | 
|   | 
|   bind(function (err) { | 
|     if (err) return that.emit('error', err) | 
|     that.emit('ready') | 
|   }) | 
|   | 
|   that.send = function (value, rinfo, cb) { | 
|     if (typeof rinfo === 'function') return that.send(value, null, rinfo) | 
|     if (!cb) cb = noop | 
|     if (!rinfo) rinfo = me | 
|   | 
|     bind(onbind) | 
|   | 
|     function onbind (err) { | 
|       if (destroyed) return cb() | 
|       if (err) return cb(err) | 
|       var message = packet.encode(value) | 
|       socket.send(message, 0, message.length, rinfo.port, rinfo.address || rinfo.host, cb) | 
|     } | 
|   } | 
|   | 
|   that.response = | 
|   that.respond = function (res, rinfo, cb) { | 
|     if (Array.isArray(res)) res = {answers: res} | 
|   | 
|     res.type = 'response' | 
|     res.flags = (res.flags || 0) | packet.AUTHORITATIVE_ANSWER | 
|     that.send(res, rinfo, cb) | 
|   } | 
|   | 
|   that.query = function (q, type, rinfo, cb) { | 
|     if (typeof type === 'function') return that.query(q, null, null, type) | 
|     if (typeof type === 'object' && type && type.port) return that.query(q, null, type, rinfo) | 
|     if (typeof rinfo === 'function') return that.query(q, type, null, rinfo) | 
|     if (!cb) cb = noop | 
|   | 
|     if (typeof q === 'string') q = [{name: q, type: type || 'ANY'}] | 
|     if (Array.isArray(q)) q = {type: 'query', questions: q} | 
|   | 
|     q.type = 'query' | 
|     that.send(q, rinfo, cb) | 
|   } | 
|   | 
|   that.destroy = function (cb) { | 
|     if (!cb) cb = noop | 
|     if (destroyed) return process.nextTick(cb) | 
|     destroyed = true | 
|     clearInterval(interval) | 
|     socket.once('close', cb) | 
|     socket.close() | 
|   } | 
|   | 
|   that.update = function () { | 
|     var ifaces = opts.interface ? [].concat(opts.interface) : allInterfaces() | 
|     var updated = false | 
|   | 
|     for (var i = 0; i < ifaces.length; i++) { | 
|       var addr = ifaces[i] | 
|   | 
|       if (memberships[addr]) continue | 
|       memberships[addr] = true | 
|       updated = true | 
|   | 
|       try { | 
|         socket.addMembership(ip, addr) | 
|       } catch (err) { | 
|         that.emit('warning', err) | 
|       } | 
|     } | 
|   | 
|     if (!updated || !socket.setMulticastInterface) return | 
|     socket.setMulticastInterface(opts.interface || defaultInterface()) | 
|   } | 
|   | 
|   return that | 
| } | 
|   | 
| function defaultInterface () { | 
|   var networks = os.networkInterfaces() | 
|   var names = Object.keys(networks) | 
|   | 
|   for (var i = 0; i < names.length; i++) { | 
|     var net = networks[names[i]] | 
|     for (var j = 0; j < net.length; j++) { | 
|       var iface = net[j] | 
|       if (iface.family === 'IPv4' && !iface.internal) return iface.address | 
|     } | 
|   } | 
|   | 
|   return '127.0.0.1' | 
| } | 
|   | 
| function allInterfaces () { | 
|   var networks = os.networkInterfaces() | 
|   var names = Object.keys(networks) | 
|   var res = [] | 
|   | 
|   for (var i = 0; i < names.length; i++) { | 
|     var net = networks[names[i]] | 
|     for (var j = 0; j < net.length; j++) { | 
|       var iface = net[j] | 
|       if (iface.family === 'IPv4') { | 
|         res.push(iface.address) | 
|         // could only addMembership once per interface (https://nodejs.org/api/dgram.html#dgram_socket_addmembership_multicastaddress_multicastinterface) | 
|         break | 
|       } | 
|     } | 
|   } | 
|   | 
|   return res | 
| } |