aboutsummaryrefslogtreecommitdiff
path: root/node_modules/cacheable-lookup/source
diff options
context:
space:
mode:
authorMinteck <nekostarfan@gmail.com>2021-08-24 14:41:48 +0200
committerMinteck <nekostarfan@gmail.com>2021-08-24 14:41:48 +0200
commitd25e11bee6ca5ca523884da132d18e1400e077b9 (patch)
tree8af39fde19f7ed640a60fb397c7edd647dff1c4c /node_modules/cacheable-lookup/source
downloadkartik-iridium-d25e11bee6ca5ca523884da132d18e1400e077b9.tar.gz
kartik-iridium-d25e11bee6ca5ca523884da132d18e1400e077b9.tar.bz2
kartik-iridium-d25e11bee6ca5ca523884da132d18e1400e077b9.zip
Initial commit
Diffstat (limited to 'node_modules/cacheable-lookup/source')
-rw-r--r--node_modules/cacheable-lookup/source/index.js436
1 files changed, 436 insertions, 0 deletions
diff --git a/node_modules/cacheable-lookup/source/index.js b/node_modules/cacheable-lookup/source/index.js
new file mode 100644
index 0000000..21f731e
--- /dev/null
+++ b/node_modules/cacheable-lookup/source/index.js
@@ -0,0 +1,436 @@
+'use strict';
+const {
+ V4MAPPED,
+ ADDRCONFIG,
+ ALL,
+ promises: {
+ Resolver: AsyncResolver
+ },
+ lookup: dnsLookup
+} = require('dns');
+const {promisify} = require('util');
+const os = require('os');
+
+const kCacheableLookupCreateConnection = Symbol('cacheableLookupCreateConnection');
+const kCacheableLookupInstance = Symbol('cacheableLookupInstance');
+const kExpires = Symbol('expires');
+
+const supportsALL = typeof ALL === 'number';
+
+const verifyAgent = agent => {
+ if (!(agent && typeof agent.createConnection === 'function')) {
+ throw new Error('Expected an Agent instance as the first argument');
+ }
+};
+
+const map4to6 = entries => {
+ for (const entry of entries) {
+ if (entry.family === 6) {
+ continue;
+ }
+
+ entry.address = `::ffff:${entry.address}`;
+ entry.family = 6;
+ }
+};
+
+const getIfaceInfo = () => {
+ let has4 = false;
+ let has6 = false;
+
+ for (const device of Object.values(os.networkInterfaces())) {
+ for (const iface of device) {
+ if (iface.internal) {
+ continue;
+ }
+
+ if (iface.family === 'IPv6') {
+ has6 = true;
+ } else {
+ has4 = true;
+ }
+
+ if (has4 && has6) {
+ return {has4, has6};
+ }
+ }
+ }
+
+ return {has4, has6};
+};
+
+const isIterable = map => {
+ return Symbol.iterator in map;
+};
+
+const ttl = {ttl: true};
+const all = {all: true};
+
+class CacheableLookup {
+ constructor({
+ cache = new Map(),
+ maxTtl = Infinity,
+ fallbackDuration = 3600,
+ errorTtl = 0.15,
+ resolver = new AsyncResolver(),
+ lookup = dnsLookup
+ } = {}) {
+ this.maxTtl = maxTtl;
+ this.errorTtl = errorTtl;
+
+ this._cache = cache;
+ this._resolver = resolver;
+ this._dnsLookup = promisify(lookup);
+
+ if (this._resolver instanceof AsyncResolver) {
+ this._resolve4 = this._resolver.resolve4.bind(this._resolver);
+ this._resolve6 = this._resolver.resolve6.bind(this._resolver);
+ } else {
+ this._resolve4 = promisify(this._resolver.resolve4.bind(this._resolver));
+ this._resolve6 = promisify(this._resolver.resolve6.bind(this._resolver));
+ }
+
+ this._iface = getIfaceInfo();
+
+ this._pending = {};
+ this._nextRemovalTime = false;
+ this._hostnamesToFallback = new Set();
+
+ if (fallbackDuration < 1) {
+ this._fallback = false;
+ } else {
+ this._fallback = true;
+
+ const interval = setInterval(() => {
+ this._hostnamesToFallback.clear();
+ }, fallbackDuration * 1000);
+
+ /* istanbul ignore next: There is no `interval.unref()` when running inside an Electron renderer */
+ if (interval.unref) {
+ interval.unref();
+ }
+ }
+
+ this.lookup = this.lookup.bind(this);
+ this.lookupAsync = this.lookupAsync.bind(this);
+ }
+
+ set servers(servers) {
+ this.clear();
+
+ this._resolver.setServers(servers);
+ }
+
+ get servers() {
+ return this._resolver.getServers();
+ }
+
+ lookup(hostname, options, callback) {
+ if (typeof options === 'function') {
+ callback = options;
+ options = {};
+ } else if (typeof options === 'number') {
+ options = {
+ family: options
+ };
+ }
+
+ if (!callback) {
+ throw new Error('Callback must be a function.');
+ }
+
+ // eslint-disable-next-line promise/prefer-await-to-then
+ this.lookupAsync(hostname, options).then(result => {
+ if (options.all) {
+ callback(null, result);
+ } else {
+ callback(null, result.address, result.family, result.expires, result.ttl);
+ }
+ }, callback);
+ }
+
+ async lookupAsync(hostname, options = {}) {
+ if (typeof options === 'number') {
+ options = {
+ family: options
+ };
+ }
+
+ let cached = await this.query(hostname);
+
+ if (options.family === 6) {
+ const filtered = cached.filter(entry => entry.family === 6);
+
+ if (options.hints & V4MAPPED) {
+ if ((supportsALL && options.hints & ALL) || filtered.length === 0) {
+ map4to6(cached);
+ } else {
+ cached = filtered;
+ }
+ } else {
+ cached = filtered;
+ }
+ } else if (options.family === 4) {
+ cached = cached.filter(entry => entry.family === 4);
+ }
+
+ if (options.hints & ADDRCONFIG) {
+ const {_iface} = this;
+ cached = cached.filter(entry => entry.family === 6 ? _iface.has6 : _iface.has4);
+ }
+
+ if (cached.length === 0) {
+ const error = new Error(`cacheableLookup ENOTFOUND ${hostname}`);
+ error.code = 'ENOTFOUND';
+ error.hostname = hostname;
+
+ throw error;
+ }
+
+ if (options.all) {
+ return cached;
+ }
+
+ return cached[0];
+ }
+
+ async query(hostname) {
+ let cached = await this._cache.get(hostname);
+
+ if (!cached) {
+ const pending = this._pending[hostname];
+
+ if (pending) {
+ cached = await pending;
+ } else {
+ const newPromise = this.queryAndCache(hostname);
+ this._pending[hostname] = newPromise;
+
+ try {
+ cached = await newPromise;
+ } finally {
+ delete this._pending[hostname];
+ }
+ }
+ }
+
+ cached = cached.map(entry => {
+ return {...entry};
+ });
+
+ return cached;
+ }
+
+ async _resolve(hostname) {
+ const wrap = async promise => {
+ try {
+ return await promise;
+ } catch (error) {
+ if (error.code === 'ENODATA' || error.code === 'ENOTFOUND') {
+ return [];
+ }
+
+ throw error;
+ }
+ };
+
+ // ANY is unsafe as it doesn't trigger new queries in the underlying server.
+ const [A, AAAA] = await Promise.all([
+ this._resolve4(hostname, ttl),
+ this._resolve6(hostname, ttl)
+ ].map(promise => wrap(promise)));
+
+ let aTtl = 0;
+ let aaaaTtl = 0;
+ let cacheTtl = 0;
+
+ const now = Date.now();
+
+ for (const entry of A) {
+ entry.family = 4;
+ entry.expires = now + (entry.ttl * 1000);
+
+ aTtl = Math.max(aTtl, entry.ttl);
+ }
+
+ for (const entry of AAAA) {
+ entry.family = 6;
+ entry.expires = now + (entry.ttl * 1000);
+
+ aaaaTtl = Math.max(aaaaTtl, entry.ttl);
+ }
+
+ if (A.length > 0) {
+ if (AAAA.length > 0) {
+ cacheTtl = Math.min(aTtl, aaaaTtl);
+ } else {
+ cacheTtl = aTtl;
+ }
+ } else {
+ cacheTtl = aaaaTtl;
+ }
+
+ return {
+ entries: [
+ ...A,
+ ...AAAA
+ ],
+ cacheTtl
+ };
+ }
+
+ async _lookup(hostname) {
+ try {
+ const entries = await this._dnsLookup(hostname, {
+ all: true
+ });
+
+ return {
+ entries,
+ cacheTtl: 0
+ };
+ } catch (_) {
+ return {
+ entries: [],
+ cacheTtl: 0
+ };
+ }
+ }
+
+ async _set(hostname, data, cacheTtl) {
+ if (this.maxTtl > 0 && cacheTtl > 0) {
+ cacheTtl = Math.min(cacheTtl, this.maxTtl) * 1000;
+ data[kExpires] = Date.now() + cacheTtl;
+
+ try {
+ await this._cache.set(hostname, data, cacheTtl);
+ } catch (error) {
+ this.lookupAsync = async () => {
+ const cacheError = new Error('Cache Error. Please recreate the CacheableLookup instance.');
+ cacheError.cause = error;
+
+ throw cacheError;
+ };
+ }
+
+ if (isIterable(this._cache)) {
+ this._tick(cacheTtl);
+ }
+ }
+ }
+
+ async queryAndCache(hostname) {
+ if (this._hostnamesToFallback.has(hostname)) {
+ return this._dnsLookup(hostname, all);
+ }
+
+ let query = await this._resolve(hostname);
+
+ if (query.entries.length === 0 && this._fallback) {
+ query = await this._lookup(hostname);
+
+ if (query.entries.length !== 0) {
+ // Use `dns.lookup(...)` for that particular hostname
+ this._hostnamesToFallback.add(hostname);
+ }
+ }
+
+ const cacheTtl = query.entries.length === 0 ? this.errorTtl : query.cacheTtl;
+ await this._set(hostname, query.entries, cacheTtl);
+
+ return query.entries;
+ }
+
+ _tick(ms) {
+ const nextRemovalTime = this._nextRemovalTime;
+
+ if (!nextRemovalTime || ms < nextRemovalTime) {
+ clearTimeout(this._removalTimeout);
+
+ this._nextRemovalTime = ms;
+
+ this._removalTimeout = setTimeout(() => {
+ this._nextRemovalTime = false;
+
+ let nextExpiry = Infinity;
+
+ const now = Date.now();
+
+ for (const [hostname, entries] of this._cache) {
+ const expires = entries[kExpires];
+
+ if (now >= expires) {
+ this._cache.delete(hostname);
+ } else if (expires < nextExpiry) {
+ nextExpiry = expires;
+ }
+ }
+
+ if (nextExpiry !== Infinity) {
+ this._tick(nextExpiry - now);
+ }
+ }, ms);
+
+ /* istanbul ignore next: There is no `timeout.unref()` when running inside an Electron renderer */
+ if (this._removalTimeout.unref) {
+ this._removalTimeout.unref();
+ }
+ }
+ }
+
+ install(agent) {
+ verifyAgent(agent);
+
+ if (kCacheableLookupCreateConnection in agent) {
+ throw new Error('CacheableLookup has been already installed');
+ }
+
+ agent[kCacheableLookupCreateConnection] = agent.createConnection;
+ agent[kCacheableLookupInstance] = this;
+
+ agent.createConnection = (options, callback) => {
+ if (!('lookup' in options)) {
+ options.lookup = this.lookup;
+ }
+
+ return agent[kCacheableLookupCreateConnection](options, callback);
+ };
+ }
+
+ uninstall(agent) {
+ verifyAgent(agent);
+
+ if (agent[kCacheableLookupCreateConnection]) {
+ if (agent[kCacheableLookupInstance] !== this) {
+ throw new Error('The agent is not owned by this CacheableLookup instance');
+ }
+
+ agent.createConnection = agent[kCacheableLookupCreateConnection];
+
+ delete agent[kCacheableLookupCreateConnection];
+ delete agent[kCacheableLookupInstance];
+ }
+ }
+
+ updateInterfaceInfo() {
+ const {_iface} = this;
+
+ this._iface = getIfaceInfo();
+
+ if ((_iface.has4 && !this._iface.has4) || (_iface.has6 && !this._iface.has6)) {
+ this._cache.clear();
+ }
+ }
+
+ clear(hostname) {
+ if (hostname) {
+ this._cache.delete(hostname);
+ return;
+ }
+
+ this._cache.clear();
+ }
+}
+
+module.exports = CacheableLookup;
+module.exports.default = CacheableLookup;