Skip to content

Commit 6c37c6a

Browse files
authored
feat: preload visited DNSLink URLs to local node (#827)
With #826 data was no longer getting into the cache of a local node if DNSLink redirect is disabled by the user. This adds background queue that ensures every visited DNSLink asset is preloaded to the cache of a local IPFS node, if the user chooses to do so.
1 parent 3c9e72d commit 6c37c6a

File tree

6 files changed

+65
-24
lines changed

6 files changed

+65
-24
lines changed

add-on/_locales/en/messages.json

+8
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,14 @@
287287
"message": "If global redirect is enabled, this will include DNSLink websites and redirect them to respective /ipns/{fqdn} paths at Custom Gateway",
288288
"description": "An option description on the Preferences screen (option_dnslinkRedirect_description)"
289289
},
290+
"option_dnslinkDataPreload_title": {
291+
"message": "Preload visited pages",
292+
"description": "An option title on the Preferences screen (option_dnslinkDataPreload_title)"
293+
},
294+
"option_dnslinkDataPreload_description": {
295+
"message": "DNSLink websites will be preloaded to the local IPFS node to enable offline access and improve resiliency against network failures",
296+
"description": "An option description on the Preferences screen (option_dnslinkDataPreload_description)"
297+
},
290298
"option_dnslinkRedirect_warning": {
291299
"message": "Redirecting to a path-based gateway breaks Origin-based security isolation of DNSLink website! Please leave this disabled unless you are aware of (and ok with) related risks.",
292300
"description": "A warning on the Preferences screen, displayed when URL does not belong to Secure Context (option_customGatewayUrl_warning)"

add-on/src/lib/dnslink.js

+33-16
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ module.exports = function createDnslinkResolver (getState) {
1717
// DNSLink lookup result cache
1818
const cacheOptions = { max: 1000, maxAge: 1000 * 60 * 60 * 12 }
1919
const cache = new LRU(cacheOptions)
20-
// upper bound for concurrent background lookups done by preloadDnslink(url)
21-
const lookupQueue = new PQueue({ concurrency: 8 })
20+
// upper bound for concurrent background lookups done by resolve(url)
21+
const lookupQueue = new PQueue({ concurrency: 4 })
22+
// preload of DNSLink data
23+
const preloadUrlCache = new LRU(cacheOptions)
24+
const preloadQueue = new PQueue({ concurrency: 4 })
2225

2326
const dnslinkResolver = {
2427

@@ -89,20 +92,34 @@ module.exports = function createDnslinkResolver (getState) {
8992
return dnslink
9093
},
9194

92-
// does not return anything, runs async lookup in the background
93-
// and saves result into cache with an optional callback
94-
preloadDnslink (url, cb) {
95-
if (dnslinkResolver.canLookupURL(url)) {
96-
lookupQueue.add(async () => {
97-
const fqdn = new URL(url).hostname
98-
const result = dnslinkResolver.readAndCacheDnslink(fqdn)
99-
if (cb) {
100-
cb(result)
101-
}
102-
})
103-
} else if (cb) {
104-
cb(null)
105-
}
95+
// runs async lookup in a queue in the background and returns the record
96+
async resolve (url) {
97+
if (!dnslinkResolver.canLookupURL(url)) return
98+
const fqdn = new URL(url).hostname
99+
const cachedResult = dnslinkResolver.cachedDnslink(fqdn)
100+
if (cachedResult) return cachedResult
101+
return lookupQueue.add(() => {
102+
return dnslinkResolver.readAndCacheDnslink(fqdn)
103+
})
104+
},
105+
106+
// preloads data behind the url to local node
107+
async preloadData (url) {
108+
const state = getState()
109+
if (!state.dnslinkDataPreload || state.dnslinkRedirect) return
110+
if (preloadUrlCache.get(url)) return
111+
preloadUrlCache.set(url, true)
112+
const dnslink = await dnslinkResolver.resolve(url)
113+
if (!dnslink) return
114+
if (state.ipfsNodeType === 'embedded') return
115+
if (state.peerCount < 1) return
116+
return preloadQueue.add(async () => {
117+
const { pathname } = new URL(url)
118+
const preloadUrl = new URL(state.gwURLString)
119+
preloadUrl.pathname = `${dnslink}${pathname}`
120+
await fetch(preloadUrl.toString(), { method: 'HEAD' })
121+
return preloadUrl
122+
})
106123
},
107124

108125
// low level lookup without cache

add-on/src/lib/ipfs-request.js

+11-8
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
8989
if (request.type === 'main_frame') {
9090
// lazily trigger DNSLink lookup (will do anything only if status for root domain is not in cache)
9191
if (state.dnslinkPolicy && dnslinkResolver.canLookupURL(request.url)) {
92-
dnslinkResolver.preloadDnslink(request.url)
92+
dnslinkResolver.resolve(request.url) // no await: preload record in background
9393
}
9494
}
9595
return isIgnored(request.requestId)
@@ -142,15 +142,18 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
142142
return redirectToGateway(request.url, state, ipfsPathValidator)
143143
}
144144
// Detect dnslink using heuristics enabled in Preferences
145-
if (state.dnslinkRedirect && state.dnslinkPolicy && dnslinkResolver.canLookupURL(request.url)) {
146-
const dnslinkRedirect = dnslinkResolver.dnslinkRedirect(request.url)
147-
if (dnslinkRedirect && isSafeToRedirect(request, runtime)) {
148-
// console.log('onBeforeRequest.dnslinkRedirect', dnslinkRedirect)
149-
return dnslinkRedirect
145+
if (state.dnslinkPolicy && dnslinkResolver.canLookupURL(request.url)) {
146+
if (state.dnslinkRedirect) {
147+
const dnslinkRedirect = dnslinkResolver.dnslinkRedirect(request.url)
148+
if (dnslinkRedirect && isSafeToRedirect(request, runtime)) {
149+
// console.log('onBeforeRequest.dnslinkRedirect', dnslinkRedirect)
150+
return dnslinkRedirect
151+
}
152+
} else if (state.dnslinkDataPreload) {
153+
dnslinkResolver.preloadData(request.url)
150154
}
151155
if (state.dnslinkPolicy === 'best-effort') {
152-
// dnslinkResolver.preloadDnslink(request.url, (dnslink) => console.log(`---> preloadDnslink(${new URL(request.url).hostname})=${dnslink}`))
153-
dnslinkResolver.preloadDnslink(request.url)
156+
dnslinkResolver.resolve(request.url)
154157
}
155158
}
156159
}

add-on/src/lib/options.js

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ exports.optionDefaults = Object.freeze({
1717
automaticMode: true,
1818
linkify: false,
1919
dnslinkPolicy: 'best-effort',
20+
dnslinkDataPreload: true,
2021
dnslinkRedirect: false,
2122
recoverFailedHttpRequests: true,
2223
detectIpfsPathHeader: true,

add-on/src/options/forms/dnslink-form.js

+11
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ const switchToggle = require('../../pages/components/switch-toggle')
77

88
function dnslinkForm ({
99
dnslinkPolicy,
10+
dnslinkDataPreload,
1011
dnslinkRedirect,
1112
onOptionChange
1213
}) {
1314
const onDnslinkPolicyChange = onOptionChange('dnslinkPolicy')
1415
const onDnslinkRedirectChange = onOptionChange('dnslinkRedirect')
16+
const onDnslinkDataPreloadChange = onOptionChange('dnslinkDataPreload')
1517

1618
return html`
1719
<form>
@@ -47,6 +49,15 @@ function dnslinkForm ({
4749
</option>
4850
</select>
4951
</div>
52+
<div>
53+
<label for="dnslinkDataPreload">
54+
<dl>
55+
<dt>${browser.i18n.getMessage('option_dnslinkDataPreload_title')}</dt>
56+
<dd>${browser.i18n.getMessage('option_dnslinkDataPreload_description')}</dd>
57+
</dl>
58+
</label>
59+
<div>${switchToggle({ id: 'dnslinkDataPreload', checked: dnslinkDataPreload, disabled: dnslinkRedirect, onchange: onDnslinkDataPreloadChange })}</div>
60+
</div>
5061
<div>
5162
<label for="dnslinkRedirect">
5263
<dl>

add-on/src/options/page.js

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ module.exports = function optionsPage (state, emit) {
8080
})}
8181
${dnslinkForm({
8282
dnslinkPolicy: state.options.dnslinkPolicy,
83+
dnslinkDataPreload: state.options.dnslinkDataPreload,
8384
dnslinkRedirect: state.options.dnslinkRedirect,
8485
onOptionChange
8586
})}

0 commit comments

Comments
 (0)