dnssd
Version:
Bonjour/Avahi-like service discovery in pure JavaScript
406 lines (331 loc) • 10.7 kB
JavaScript
;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var os = require('os');
var util = require('util');
var remove_colors_re = /\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]/g;
/**
* Makes a fully qualified domain name from dns labels
*
* @param {...string}
* @return {string}
*/
module.exports.fqdn = function () {
for (var _len = arguments.length, labels = Array(_len), _key = 0; _key < _len; _key++) {
labels[_key] = arguments[_key];
}
var name = labels.join('.');
return name.substr(-1) === '.' ? name : name + '.';
};
/**
* Get hostname. Strips .local if os.hostname includes it
* @return {string}
*/
module.exports.hostname = function () {
return os.hostname().replace(/.local\.?$/, '');
};
/**
* Parses a resource record name into instance, service type, etc
*
* Deals with these name formats:
* - Instance . _service . _protocol . domain .
* - Subtype . _sub . _service . _protocol . domain .
* - _service . _protocol . domain .
* - Single_Label_Host . local .
*
* If name fails to parse as expected, it returns an empty obj.
*
* @param {string}
* @return {object}
*/
module.exports.parse = function (fullname) {
var obj = {};
// a full registration name, eg:
// - '_http._tcp.local.'
// - 'Instance No. 1._http._tcp.local.'
// - 'SubTypeName._sub._http._tcp.local.'
if (!!~fullname.indexOf('._tcp.') || !!~fullname.indexOf('._udp.')) {
obj.protocol = !!~fullname.indexOf('._tcp.') ? '_tcp' : '_udp';
// [['Instance No', ' 1', '_http'], [local]]
var parts = fullname.split(obj.protocol).map(function (part) {
return part.split('.').filter(function (p) {
return !!p;
});
});
obj.domain = parts[1].join('.'); // 'local'
obj.service = parts[0].pop(); // '_http'
if (parts[0].slice(-1)[0] === '_sub') {
obj.subtype = parts[0].slice(0, -1).join('.'); // 'SubTypeName'
} else {
obj.instance = parts[0].join('.'); // 'Instance No. 1'
}
// a 2 label domain name, eg: 'Machine.Name.local.'
} else if (fullname.match(/local$|local\.$/)) {
obj.instance = fullname.split('.local').shift(); // Machine.Name
obj.domain = 'local';
}
return obj;
};
module.exports.pad = function (value, len) {
var fill = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ' ';
var str = String(value);
var needed = len - str.length;
return needed > 0 ? str + fill.repeat(needed) : str;
};
module.exports.padStart = function (value, len) {
var fill = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ' ';
var str = String(value);
var needed = len - str.length;
return needed > 0 ? fill.repeat(needed) + str : str;
};
/**
* Visually padEnd. Adding colors to strings adds escape sequences that
* make it a color but also adds characters to str.length that aren't
* displayed.
*
* @param {string} str
* @param {number} num
* @return {string}
*/
function visualPad(str, num) {
var needed = num - str.replace(remove_colors_re, '').length;
return needed > 0 ? str + ' '.repeat(needed) : str;
}
/**
* Make a table of records strings that have equal column lengths.
*
* Ex, turn groups of records:
* [
* [
* Host.local. * QU,
* ]
* [
* Host.local. A 10 169.254.132.42,
* Host.local. AAAA 10 fe80::c17c:ec1c:530d:842a,
* ]
* ]
*
* into a more readable form that can be printed:
* [
* [
* 'Host.local. * QU'
* ]
* [
* 'Host.local. A 10 169.254.132.42'
* 'Host.local. AAAA 10 fe80::c17c:ec1c:530d:842a'
* ]
* ]
*
* @param {...ResourceRecords[]} groups
* @return {string[][]}
*/
function alignRecords() {
var colWidths = [];
var result = void 0;
// Get max size for each column (have to look at all records)
for (var _len2 = arguments.length, groups = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
groups[_key2] = arguments[_key2];
}
result = groups.map(function (records) {
return records.map(function (record) {
// break record into parts
var parts = record.toParts();
parts.forEach(function (part, i) {
var len = part.replace(remove_colors_re, '').length;
if (!colWidths[i]) colWidths[i] = 0;
if (len > colWidths[i]) colWidths[i] = len;
});
return parts;
});
});
// Add padding:
result = result.map(function (records) {
return records.map(function (recordParts) {
return recordParts.map(function (part, i) {
return visualPad(part, colWidths[i]);
}).join(' ');
});
});
return result;
}
module.exports.alignRecords = alignRecords;
/**
* Makes a "raw" txt obj for TXT records. A "raw" obj will have string values
* converted to buffers since TXT key values are just opaque binary data. False
* values are removed since they aren't sent (missing key = implied false).
*
* {key: 'value'} => {'key': <Buffer 76 61 6c 75 65>}
* {key: true} => {key: true}
* {key: null} => {key: null}
* {key: false} => {}
*
* @param {object} obj
* @return {object} - a new object, original not modified
*/
module.exports.makeRawTXT = function (obj) {
var result = {};
Object.keys(obj).filter(function (key) {
return obj[key] !== false;
}).forEach(function (key) {
var value = obj[key];
result[key] = typeof value === 'string' ? Buffer.alloc(value.length, value) : value;
});
return result;
};
/**
* Makes a more readable txt obj for TXT records. Buffers are converted to
* utf8 strings, which is likely what you want anyway.
*
* @param {object} obj
* @return {object} - a new object, original not modified
*/
module.exports.makeReadableTXT = function (obj) {
var result = {};
Object.keys(obj).filter(function (key) {
return obj[key] !== false;
}).forEach(function (key) {
var value = obj[key];
result[key] = Buffer.isBuffer(value) ? value.toString() : value;
});
return result;
};
module.exports.defaults = function (obj, defaults) {
Object.keys(defaults).forEach(function (key) {
if (!obj.hasOwnProperty(key)) obj[key] = defaults[key];
});
};
module.exports.random = function (min, max) {
return Math.random() * (max - min) + min;
};
module.exports.color = function (str) {
var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'white';
var bright = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var colors = {
black: 30,
red: 31,
green: 32,
yellow: 33,
blue: 34,
magenta: 35,
cyan: 36,
white: 37,
grey: 90 // bright black
};
var code = (colors[color] || 37) + (bright ? 60 : 0);
return '\x1B[' + code + 'm' + str + '\x1B[0m';
};
module.exports.bg = function (str) {
var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'white';
var bright = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var colors = {
black: 40,
red: 41,
green: 42,
yellow: 43,
blue: 44,
magenta: 45,
cyan: 46,
white: 47,
grey: 100 // bright black
};
var code = (colors[color] || 40) + (bright ? 60 : 0);
return '\x1B[' + code + 'm' + str + '\x1B[0m';
};
module.exports.truncate = function (str, len) {
var end = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '…';
return str.length < len ? str : str.slice(0, len) + end;
};
function stringify() {
var arg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
if (type === '%s' || type === '%d') {
return String(arg);
}
// check that each item has the .toParts() method that misc.alignRecords uses
// or else it will throw
if (type === '%r') {
if (Array.isArray(arg) && arg.every(function (record) {
return 'toParts' in record;
})) {
return '\n' + alignRecords(arg).map(function (group) {
return group.join('\n');
}).join('\n');
}
return String(arg);
}
// util.inspect has pretty colors for objects
if ((typeof arg === 'undefined' ? 'undefined' : _typeof(arg)) === 'object') {
var str = util.inspect(arg, { colors: true });
return str.match('\n') ? '\n' + str + '\n' : str;
}
return String(arg);
}
module.exports.format = function (msg) {
for (var _len3 = arguments.length, args = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
args[_key3 - 1] = arguments[_key3];
}
var hasFormatters = typeof msg === 'string' && msg.match(/%[a-z]/);
// replace each format marker in message string with the formatted arg
// (or just add formatted message to output if no args)
var output = hasFormatters && args.length ? msg.replace(/%([a-z])/g, function (type) {
return stringify(args.shift(), type);
}) : stringify(msg);
// add padding for printing surplus args left over
if (args.length) output += ' ';
// print args that didn't have a formatter
output += args.map(function (arg) {
return stringify(arg);
}).join(' ');
// remove hanging newline at end and add indentation
output = output.replace(/\n$/, '');
output = output.replace(/\n/g, '\n ');
return output;
};
/**
* Map fn() n times
*/
module.exports.map_n = function (fn, n) {
var results = [];
for (var i = 0; i < n; i++) {
results.push(fn());
}
return results;
};
/**
* Call fn after n calls
*/
module.exports.after_n = function (fn, n) {
var count = n;
return function () {
count--;
if (count <= 0) return fn.apply(undefined, arguments);
};
};
/**
* Deep equality check
*/
module.exports.equals = function equals(a, b) {
if (a === b) return true;
if (typeof a !== 'undefined' && typeof b === 'undefined') return false;
if (typeof a === 'undefined' && typeof b !== 'undefined') return false;
if (Array.isArray(a) !== Array.isArray(b)) {
return false;
}
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;
for (var i = 0; i < a.length; i++) {
if (!equals(a[i], b[i])) return false;
}
return true;
}
if (a instanceof Object && b instanceof Object) {
var a_keys = Object.keys(a);
var b_keys = Object.keys(b);
if (a_keys.length !== b_keys.length) {
return false;
}
return a_keys.every(function (key) {
return equals(a[key], b[key]);
});
}
return false;
};