dnssd
Version:
Bonjour/Avahi-like service discovery in pure JavaScript
261 lines (193 loc) • 6.19 kB
JavaScript
const dnssd = require('../index.js');
//
//
main(process.argv);
//
//
function showHelp(reason) {
if (reason) console.log(`\n${reason}`);
console.log('\nUsage: dnssd-js <command>\n');
console.log('dnssd-js browse [type] [domain]');
console.log('dnssd-js advertise <type> <port> [name] [txt]');
console.log('dnssd-js query <record name> <record type>');
console.log('dnssd-js address <hostname> [v4/v6]');
process.exit();
}
function exit(err) {
if (err) console.log(`\n${err}\n`);
process.exit();
}
function padStart(value, len, filler = ' ') {
const str = String(value);
const fill = String(filler);
const needed = len - str.length;
return (needed > 0) ? fill.repeat(needed) + str : str;
}
function padEnd(value, len, filler = ' ') {
const str = String(value);
const fill = String(filler);
const needed = len - str.length;
return (needed > 0) ? str + fill.repeat(needed) : str;
}
function timestamp() {
const now = new Date();
const time = [
padStart(now.getHours(), 2, 0),
padStart(now.getMinutes(), 2, 0),
padStart(now.getSeconds(), 2, 0),
padStart(now.getMilliseconds(), 3, 0),
];
return `[${time.join(':')}]`;
}
function print(time, status, type, instance) {
console.log('%s %s %s %s',
padEnd(time, 14),
padEnd(status, 4),
padEnd(type, 20),
instance);
}
function browse(serviceType, domain) {
const browser = new dnssd.Browser(serviceType, {domain});
// format to:
// [23:18:53:805] Up _http._tcp Test @ box.local. (169.254.22.58:4576)
browser.on('serviceUp', (service) => {
print(timestamp(), 'Up',
`_${service.type.name}._${service.type.protocol}`,
`${service.name} @ ${service.host} (${service.addresses[0]}:${service.port})`);
});
browser.on('serviceDown', (service) => {
print(timestamp(), 'Down',
`_${service.type.name}._${service.type.protocol}`,
`${service.name} @ ${service.host} (${service.addresses[0]}:${service.port})`);
});
browser.on('error', exit);
browser.start();
}
function browseAll(domain) {
console.log('Browsing for all available services:\n');
print('Time', 'U/D', 'Service Type', 'Instance');
print('----', '---', '------------', '--------');
const browser = new dnssd.Browser(dnssd.all(), { domain });
const active = {};
browser.on('serviceUp', (type) => {
const serviceType = `_${type.name}._${type.protocol}`;
// only start one browser for each new service type
if (!active[serviceType]) {
active[serviceType] = true;
browse(serviceType, domain);
}
});
browser.on('error', exit);
browser.start();
}
function browseType(type, domain) {
if (!type || type === 'all') return browseAll(domain);
let serviceType;
// wrap in case type validation fails
try {
serviceType = new dnssd.ServiceType(type);
} catch (err) {
exit(`Bad service type: ${err.message}`);
}
console.log(`Browsing for "${serviceType}":\n`);
print('Time', 'U/D', 'Service Type', 'Instance');
print('----', '---', '------------', '--------');
browse(serviceType, domain);
}
function advertise(type, portString, name, obj) {
const port = parseInt(portString, 10);
let txt, advertisement;
if (!port) exit('Missing port number');
if (!Number.isInteger(port)) exit('Bad port number');
// parse txt string
if (obj) {
try {
txt = JSON.parse(obj);
} catch (err) {
exit(`Problem parsing txt object: ${txt.message}`);
}
}
// wrap in case advert args fail validation check
try {
advertisement = new dnssd.Advertisement(type, port, {name, txt});
} catch (err) {
exit(`Problem creating advertisement: ${err.messsage}`);
}
console.log(`Creating advertisement for "${type}"...`);
advertisement.on('active', () => {
console.log('%s Advertisement now active', timestamp());
});
advertisement.on('instanceRenamed', (instance) => {
console.log('%s Advertisement instance renamed "%s" due to a conflict.',
timestamp(), instance);
});
advertisement.on('hostRenamed', (host) => {
console.log('%s Advertisement hostname renamed "%s" due to a conflict.',
timestamp(), host);
});
advertisement.on('stopped', () => {
console.log('%s Advertisement stopped.', timestamp());
exit();
});
advertisement.on('error', exit);
advertisement.start();
}
function query(name, type) {
if (!name) exit('Missing record name');
if (!type) exit('Missing record type');
// rrtype could be string (like TXT) or and int (like 16)
const rrtype = (Number.isInteger(parseInt(type, 10))) ? parseInt(type, 10) : type;
let getRecord;
// wrap to check for validation errors
try {
getRecord = dnssd.resolve(name, rrtype);
} catch (err) {
exit(err);
}
getRecord
.then((result) => {
console.log(`Found record: ${result.answer}`);
})
.catch((err) => {
exit(`Record not found. \nReason: ${err.message}`);
});
}
function address(hostname, IPv = 'v4/v6') {
if (!hostname) exit('Missing hostname');
let getIPv4, getIPv6;
// wrap to check for validation errors
try {
if (!!~IPv.indexOf('v4')) getIPv4 = dnssd.resolveA(hostname);
if (!!~IPv.indexOf('v6')) getIPv6 = dnssd.resolveAAAA(hostname);
} catch (err) {
exit(err);
}
if (!getIPv4 && !getIPv6) exit('Invalid address type given: ' + IPv);
Promise.all([getIPv4, getIPv6])
.then((results) => {
const IPv4 = results[0];
const IPv6 = results[1];
if (IPv4) console.log(`Found IPv4 address: ${IPv4}`);
if (IPv6) console.log(`Found IPv6 address: ${IPv6}`);
})
.catch((err) => {
exit(`Address not found. \nReason: ${err.message}`);
});
}
function main(args) {
const commands = {
browse: browseType,
register: advertise,
advertise,
query,
address,
};
const command = args[2];
if (!command || command === '-h' || command === '--help') showHelp();
if (!(command in commands)) showHelp('Unknown command given');
commands[command].apply(null, args.slice(3));
process.on('SIGINT', exit);
process.on('SIGTERM', exit);
process.on('SIGQUIT', exit);
}