import * as plugins from './dnsly.plugins.js'; export type TDnsProvider = 'google' | 'cloudflare'; export const makeNodeProcessUseDnsProvider = (providerArg: TDnsProvider) => { switch (providerArg) { case 'cloudflare': plugins.dns.setServers([ '1.1.1.1', '1.0.0.1', '[2606:4700:4700::1111]', '[2606:4700:4700::1001]', ]); break; case 'google': plugins.dns.setServers([ '8.8.8.8', '8.8.4.4', '[2001:4860:4860::8888]', '[2606:4700:4700::1001]', ]); } }; export interface ISmartDnsConstructorOptions {} export interface IGoogleDNSHTTPSResponse { Status: number; TC: boolean; RD: boolean; RA: boolean; AD: boolean; CD: boolean; Question: Array<{ name: string; type: number }>; Answer: Array<{ name: string; type: number; TTL: number; data: string }>; Additional: []; Comment: string; } /** * class dnsly offers methods for working with dns from a dns provider like Google DNS */ export class Smartdns { public dnsServerIp: string; public dnsServerPort: number; public dnsTypeMap: { [key: string]: number } = { A: 1, AAAA: 28, CNAME: 5, MX: 15, TXT: 16, }; /** * constructor for class dnsly */ constructor(optionsArg: ISmartDnsConstructorOptions) {} /** * check a dns record until it has propagated to Google DNS * should be considerably fast * @param recordNameArg * @param recordTypeArg * @param expectedValue */ public async checkUntilAvailable( recordNameArg: string, recordTypeArg: plugins.tsclass.network.TDnsRecordType, expectedValue: string, cyclesArg: number = 50, intervalArg: number = 500 ) { let runCycles = 0; const doCheck = async () => { if (runCycles < cyclesArg) { runCycles++; try { let myRecordArray: plugins.tsclass.network.IDnsRecord[]; if (runCycles % 2 === 0 || !plugins.dns) { myRecordArray = await this.getRecords(recordNameArg, recordTypeArg, 0); } else { myRecordArray = await this.getRecordWithNodeDNS(recordNameArg, recordTypeArg); } const myRecord = myRecordArray[0].value; if (myRecord === expectedValue) { console.log( `smartdns: .checkUntilAvailable() verified that wanted >>>${recordTypeArg}<<< record exists for >>>${recordNameArg}<<< with value >>>${expectedValue}<<<` ); return true; } else { await plugins.smartdelay.delayFor(intervalArg); return await doCheck(); } } catch (err) { // console.log(err); await plugins.smartdelay.delayFor(intervalArg); return await doCheck(); } } else { console.log( `smartdns: .checkUntilAvailable() failed permanently for ${recordNameArg} with value ${recordTypeArg} - ${expectedValue}...` ); return false; } }; return await doCheck(); } /** * get A Dns Record */ public async getRecordsA(recordNameArg: string): Promise { return await this.getRecords(recordNameArg, 'A'); } /** * get AAAA Record */ public async getRecordsAAAA(recordNameArg: string) { return await this.getRecords(recordNameArg, 'AAAA'); } /** * gets a txt record */ public async getRecordsTxt(recordNameArg: string): Promise { return await this.getRecords(recordNameArg, 'TXT'); } public async getRecords( recordNameArg: string, recordTypeArg: plugins.tsclass.network.TDnsRecordType, retriesCounterArg = 20 ): Promise { const requestUrl = `https://cloudflare-dns.com/dns-query?name=${recordNameArg}&type=${recordTypeArg}&do=1`; const returnArray: plugins.tsclass.network.IDnsRecord[] = []; const getResponseBody = async (counterArg = 0): Promise => { const response = await plugins.smartrequest.request(requestUrl, { method: 'GET', headers: { accept: 'application/dns-json', }, }); const responseBody: IGoogleDNSHTTPSResponse = response.body; if (responseBody?.Status !== 0 && counterArg < retriesCounterArg) { await plugins.smartdelay.delayFor(500); return getResponseBody(counterArg++); } else { return responseBody; } }; const responseBody = await getResponseBody(); if (!responseBody.Answer || !typeof responseBody.Answer[Symbol.iterator]) { return returnArray; } for (const dnsEntry of responseBody.Answer) { if (dnsEntry.data.startsWith('"') && dnsEntry.data.endsWith('"')) { dnsEntry.data = dnsEntry.data.replace(/^"(.*)"$/, '$1'); } if (dnsEntry.name.endsWith('.')) { dnsEntry.name = dnsEntry.name.substring(0, dnsEntry.name.length - 1); } returnArray.push({ name: dnsEntry.name, type: this.convertDnsTypeNumberToTypeName(dnsEntry.type), dnsSecEnabled: responseBody.AD, value: dnsEntry.data, }); } // console.log(responseBody); return returnArray; } /** * gets a record using nodejs dns resolver */ public async getRecordWithNodeDNS( recordNameArg: string, recordTypeArg: plugins.tsclass.network.TDnsRecordType ): Promise { const done = plugins.smartpromise.defer(); plugins.dns.resolve(recordNameArg, recordTypeArg, (err, recordsArg) => { if (err) { done.reject(err); return; } const returnArray: plugins.tsclass.network.IDnsRecord[] = []; for (const recordKey in recordsArg) { returnArray.push({ name: recordNameArg, value: recordsArg[recordKey][0], type: recordTypeArg, dnsSecEnabled: false, }); } done.resolve(returnArray); }); return done.promise; } public async getNameServers(domainNameArg: string): Promise { const done = plugins.smartpromise.defer(); plugins.dns.resolveNs(domainNameArg, (err, result) => { if (!err) { done.resolve(result); } else { console.log(err); done.reject(err); } }); return await done.promise; } public convertDnsTypeNameToTypeNumber(dnsTypeNameArg: string): number { return this.dnsTypeMap[dnsTypeNameArg]; } public convertDnsTypeNumberToTypeName( dnsTypeNumberArg: number ): plugins.tsclass.network.TDnsRecordType { for (const key in this.dnsTypeMap) { if (this.dnsTypeMap[key] === dnsTypeNumberArg) { return key as plugins.tsclass.network.TDnsRecordType; } } return null; } }