UNPKG

@dnslink/js

Version:

The reference implementation for DNSLink in JavaScript. Tested in Node.js and in the Browser.

279 lines (235 loc) 6.93 kB
// GENERATED FILE. DO NOT EDIT. var dnslink = (function(exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "AbortError", { enumerable: true, get: function () { return _dnsQuery.AbortError; } }); exports.CODE_MEANING = void 0; Object.defineProperty(exports, "DNSRcodeError", { enumerable: true, get: function () { return _dnsQuery.DNSRcodeError; } }); exports.TXT_PREFIX = exports.LogCode = exports.FQDNReason = exports.EntryReason = exports.DNS_PREFIX = void 0; exports.resolve = resolve; var _dnsQuery = require("dns-query"); const DNS_PREFIX = '_dnslink.'; exports.DNS_PREFIX = DNS_PREFIX; const TXT_PREFIX = 'dnslink='; exports.TXT_PREFIX = TXT_PREFIX; const LogCode = Object.freeze({ fallback: 'FALLBACK', invalidEntry: 'INVALID_ENTRY' }); exports.LogCode = LogCode; const EntryReason = Object.freeze({ wrongStart: 'WRONG_START', namespaceMissing: 'NAMESPACE_MISSING', noIdentifier: 'NO_IDENTIFIER', invalidCharacter: 'INVALID_CHARACTER' }); exports.EntryReason = EntryReason; const FQDNReason = Object.freeze({ emptyPart: 'EMPTY_PART', tooLong: 'TOO_LONG' }); exports.FQDNReason = FQDNReason; const CODE_MEANING = Object.freeze({ [LogCode.fallback]: 'Falling back to domain without _dnslink prefix.', [LogCode.invalidEntry]: 'Entry misformatted, cant be used.', [EntryReason.wrongStart]: 'A DNSLink entry needs to start with a /.', [EntryReason.namespaceMissing]: 'A DNSLink entry needs to have a namespace, like: dnslink=/namespace/identifier.', [EntryReason.noIdentifier]: 'An DNSLink entry needs to have an identifier, like: dnslink=/namespace/identifier.', [EntryReason.invalidCharacter]: 'A DNSLink entry may only contain ascii characters.', [FQDNReason.emptyPart]: 'A FQDN may not contain empty parts.', [FQDNReason.tooLong]: 'A FQDN may be max 253 characters which each subdomain not exceeding 63 characters.' }); exports.CODE_MEANING = CODE_MEANING; function resolve(domain, options = {}) { return _resolve(domain, options); } function bubbleAbort(signal) { if (signal !== undefined && signal !== null && signal.aborted) { throw new _dnsQuery.AbortError(); } } async function _resolve(domain, options) { domain = validateDomain(domain); let fallbackResult = null; let useFallback = false; const defaultResolve = (0, _dnsQuery.lookupTxt)(`${DNS_PREFIX}${domain}`, options); const fallbackResolve = (0, _dnsQuery.lookupTxt)(domain, options).then(result => { fallbackResult = { result }; }, error => { fallbackResult = { error }; }); let data; try { data = await defaultResolve; } catch (err) { if (err.rcode !== 3) { throw err; } } if (data === undefined) { // Could be undefined if an error occured bubbleAbort(options.signal); await fallbackResolve; if (fallbackResult.error) { throw fallbackResult.error; } useFallback = true; data = fallbackResult.result; } const result = processEntries(data.entries); if (useFallback) { result.log.unshift({ code: LogCode.fallback }); } return result; } function validateDomain(domain) { if (domain.endsWith('.')) { domain = domain.substr(0, domain.length - 1); } if (domain.startsWith(DNS_PREFIX)) { domain = domain.substr(DNS_PREFIX.length); } const domainError = testFqdn(domain); if (domainError !== undefined) { throw Object.assign(new Error(`Invalid input domain: ${domain}`), { code: 'INVALID_DOMAIN', reason: domainError, domain }); } return domain; } function testFqdn(domain) { // https://en.wikipedia.org/wiki/Domain_name#Domain_name_syntax if (domain.length > 253 - 9 /* '_dnslink.'.length */ ) { // > The full domain name may not exceed a total length of 253 ASCII characters in its textual representation. return FQDNReason.tooLong; } for (const label of domain.split('.')) { if (label.length === 0) { return FQDNReason.emptyPart; } if (label.length > 63) { return FQDNReason.tooLong; } } } function processEntries(input) { const links = {}; const log = []; for (const entry of input.filter(entry => entry.data.startsWith(TXT_PREFIX))) { const { error, parsed } = validateDNSLinkEntry(entry.data); if (error !== undefined) { log.push({ code: LogCode.invalidEntry, entry: entry.data, reason: error }); continue; } const { namespace, identifier } = parsed; const linksByNS = links[namespace]; const link = { identifier, ttl: entry.ttl }; if (linksByNS === undefined) { links[namespace] = [link]; } else { linksByNS.push(link); } } const txtEntries = []; for (const ns of Object.keys(links).sort()) { const linksByNS = links[ns].sort(sortByID); for (const { identifier, ttl } of linksByNS) { txtEntries.push({ value: `/${ns}/${identifier}`, ttl }); } links[ns] = linksByNS; } return { txtEntries, links, log }; } function sortByID(a, b) { if (a.identifier < b.identifier) return -1; if (a.identifier > b.identifier) return 1; return 0; } function validateDNSLinkEntry(entry) { entry = entry.substr(TXT_PREFIX.length); if (!entry.startsWith('/')) { return { error: EntryReason.wrongStart }; } // https://datatracker.ietf.org/doc/html/rfc4343#section-2.1 if (!/^[\u0020-\u007e]*$/.test(entry)) { return { error: EntryReason.invalidCharacter }; } const parts = entry.split('/'); parts.shift(); let namespace; if (parts.length !== 0) { namespace = parts.shift(); } if (!namespace) { return { error: EntryReason.namespaceMissing }; } let identifier; if (parts.length !== 0) { identifier = parts.join('/'); } if (!identifier) { return { error: EntryReason.noIdentifier }; } return { parsed: { namespace, identifier } }; } return "default" in exports ? exports.default : exports; })({}); if (typeof define === 'function' && define.amd) define([], function() { return dnslink; }); else if (typeof module === 'object' && typeof exports==='object') module.exports = dnslink;