|
1 |
| -/* global self */ |
| 1 | +/* eslint-env browser */ |
2 | 2 | 'use strict'
|
3 | 3 |
|
4 |
| -module.exports = (domain, opts, callback) => { |
| 4 | +const TLRU = require('../../utils/tlru') |
| 5 | +const { default: PQueue } = require('p-queue') |
| 6 | +const { default: ky } = require('ky-universal') |
| 7 | +const nodeify = require('promise-nodeify') |
| 8 | + |
| 9 | +// Avoid sending multiple queries for the same hostname by caching results |
| 10 | +const cache = new TLRU(1000) |
| 11 | +// TODO: /api/v0/dns does not return TTL yet: https://github.com/ipfs/go-ipfs/issues/5884 |
| 12 | +// However we know browsers themselves cache DNS records for at least 1 minute, |
| 13 | +// which acts a provisional default ttl: https://stackoverflow.com/a/36917902/11518426 |
| 14 | +const ttl = 60 * 1000 |
| 15 | + |
| 16 | +// browsers limit concurrent connections per host, |
| 17 | +// we don't want preload calls to exhaust the limit (~6) |
| 18 | +const httpQueue = new PQueue({ concurrency: 4 }) |
| 19 | + |
| 20 | +// Delegated HTTP resolver sending DNSLink queries to ipfs.io |
| 21 | +// TODO: replace hardcoded host with configurable DNS over HTTPS: https://github.com/ipfs/js-ipfs/issues/2212 |
| 22 | +const api = ky.create({ |
| 23 | + prefixUrl: 'https://ipfs.io/api/v0/', |
| 24 | + hooks: { |
| 25 | + afterResponse: [ |
| 26 | + async (input, options, response) => { |
| 27 | + const query = new URL(response.url).search.slice(1) |
| 28 | + const json = await response.json() |
| 29 | + cache.set(query, json, ttl) |
| 30 | + } |
| 31 | + ] |
| 32 | + } |
| 33 | +}) |
| 34 | + |
| 35 | +const ipfsPath = (response) => { |
| 36 | + if (response.Path) return response.Path |
| 37 | + throw new Error(response.Message) |
| 38 | +} |
| 39 | + |
| 40 | +module.exports = (fqdn, opts = {}, cb) => { |
5 | 41 | if (typeof opts === 'function') {
|
6 |
| - callback = opts |
| 42 | + cb = opts |
7 | 43 | opts = {}
|
8 | 44 | }
|
| 45 | + const resolveDnslink = async (fqdn, opts = {}) => { |
| 46 | + const searchParams = new URLSearchParams(opts) |
| 47 | + searchParams.set('arg', fqdn) |
9 | 48 |
|
10 |
| - opts = opts || {} |
11 |
| - |
12 |
| - domain = encodeURIComponent(domain) |
13 |
| - let url = `https://ipfs.io/api/v0/dns?arg=${domain}` |
| 49 | + // try cache first |
| 50 | + const query = searchParams.toString() |
| 51 | + if (!opts.nocache && cache.has(query)) { |
| 52 | + const response = cache.get(query) |
| 53 | + return ipfsPath(response) |
| 54 | + } |
14 | 55 |
|
15 |
| - Object.keys(opts).forEach(prop => { |
16 |
| - url += `&${encodeURIComponent(prop)}=${encodeURIComponent(opts[prop])}` |
17 |
| - }) |
| 56 | + // fallback to delegated DNS resolver |
| 57 | + const response = await httpQueue.add(() => api.get('dns', { searchParams }).json()) |
| 58 | + return ipfsPath(response) |
| 59 | + } |
18 | 60 |
|
19 |
| - self.fetch(url, { mode: 'cors' }) |
20 |
| - .then((response) => { |
21 |
| - return response.json() |
22 |
| - }) |
23 |
| - .then((response) => { |
24 |
| - if (response.Path) { |
25 |
| - return callback(null, response.Path) |
26 |
| - } else { |
27 |
| - return callback(new Error(response.Message)) |
28 |
| - } |
29 |
| - }) |
30 |
| - .catch((error) => { |
31 |
| - callback(error) |
32 |
| - }) |
| 61 | + return nodeify(resolveDnslink(fqdn, opts), cb) |
33 | 62 | }
|
0 commit comments