Skip to content

Commit 04a8ca2

Browse files
authored
fix: recovery quirks in Firefox and Chromium (#807)
This fixes small quirks that did not break recovery, but made it bit confusing or inefficient. * fix: duplicated recovery tabs in Chromium Deduplicate `onErrorOccurred` events: sometimes Chrome produces two, possibly due to lack of async functions in webRequest APIs Closes #805 * fix: skip requests from DNS fixup logic in Firefox Firefox automatically adds 'www.' to TLD+1 names if initial DNS lookup failed. This produced bogus request+error which recovery logic is now aware of and ignores. Closes #804 * Other - make `onErrorOccurred` non-async (only Firefox supports those) - listen only for errors when `webRequest.ResourceType`=`main_frame` - reuse existing tab
1 parent 3a959b1 commit 04a8ca2

File tree

3 files changed

+49
-17
lines changed

3 files changed

+49
-17
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ module.exports = async function init () {
105105
browser.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, { urls: ['<all_urls>'] }, onBeforeSendInfoSpec)
106106
browser.webRequest.onBeforeRequest.addListener(onBeforeRequest, { urls: ['<all_urls>'] }, ['blocking'])
107107
browser.webRequest.onHeadersReceived.addListener(onHeadersReceived, { urls: ['<all_urls>'] }, ['blocking', 'responseHeaders'])
108-
browser.webRequest.onErrorOccurred.addListener(onErrorOccurred, { urls: ['<all_urls>'] })
109-
browser.webRequest.onCompleted.addListener(onCompleted, { urls: ['<all_urls>'] })
108+
browser.webRequest.onErrorOccurred.addListener(onErrorOccurred, { urls: ['<all_urls>'], types: ['main_frame'] })
109+
browser.webRequest.onCompleted.addListener(onCompleted, { urls: ['<all_urls>'], types: ['main_frame'] })
110110
browser.storage.onChanged.addListener(onStorageChange)
111111
browser.webNavigation.onCommitted.addListener(onNavigationCommitted)
112112
browser.webNavigation.onDOMContentLoaded.addListener(onDOMContentLoaded)

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

+46-14
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
3333

3434
// Various types of requests are identified once and cached across all browser.webRequest hooks
3535
const requestCacheCfg = { max: 128, maxAge: 1000 * 30 }
36+
const recoveredTabs = new LRU(requestCacheCfg)
3637
const ignoredRequests = new LRU(requestCacheCfg)
3738
const ignore = (id) => ignoredRequests.set(id, true)
3839
const isIgnored = (id) => ignoredRequests.get(id) !== undefined
40+
const errorInFlight = new LRU({ max: 3, maxAge: 1000 })
3941

4042
const acrhHeaders = new LRU(requestCacheCfg) // webui cors fix in Chrome
4143
const originUrls = new LRU(requestCacheCfg) // request.originUrl workaround for Chrome
@@ -365,17 +367,30 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
365367
// browser.webRequest.onErrorOccurred
366368
// Fired when a request could not be processed due to an error on network level.
367369
// For example: TCP timeout, DNS lookup failure
368-
async onErrorOccurred (request) {
370+
// NOTE: this is executed only if webRequest.ResourceType='main_frame'
371+
onErrorOccurred (request) {
369372
const state = getState()
370373
if (!state.active) return
371374

375+
// Avoid duplicates in Chromium, which fires two events instead of one
376+
// https://github.com/ipfs-shipyard/ipfs-companion/issues/805
377+
if (errorInFlight.has(request.url)) return
378+
errorInFlight.set(request.url, request.requestId)
379+
380+
// Skip additional requests produced by DNS fixup logic in Firefox
381+
// https://github.com/ipfs-shipyard/ipfs-companion/issues/804
382+
if (request.error === 'NS_ERROR_UNKNOWN_HOST' && request.url.includes('://www.')) {
383+
const urlBeforeFixup = request.url.replace('://www.', '://')
384+
if (errorInFlight.has(urlBeforeFixup)) return
385+
}
386+
372387
// Check if error can be recovered via EthDNS
373388
if (isRecoverableViaEthDNS(request, state)) {
374389
const url = new URL(request.url)
375390
url.hostname = `${url.hostname}.link`
376391
const redirect = { redirectUrl: url.toString() }
377-
log(`onErrorOccurred: attempting to recover from DNS error (${request.error}) using EthDNS for ${request.url}`, redirect.redirectUrl)
378-
return createTabWithURL(redirect, browser)
392+
log(`onErrorOccurred: attempting to recover from DNS error (${request.error}) using EthDNS for ${request.url}${redirect.redirectUrl}`, request)
393+
return createTabWithURL(redirect, browser, recoveredTabs)
379394
}
380395

381396
// Check if error can be recovered via DNSLink
@@ -384,8 +399,8 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
384399
const dnslink = dnslinkResolver.readAndCacheDnslink(hostname)
385400
if (dnslink) {
386401
const redirect = dnslinkResolver.dnslinkRedirect(request.url, dnslink)
387-
log(`onErrorOccurred: attempting to recover from network error (${request.error}) using dnslink for ${request.url}`, redirect.redirectUrl)
388-
return createTabWithURL(redirect, browser)
402+
log(`onErrorOccurred: attempting to recover from network error (${request.error}) using dnslink for ${request.url}${redirect.redirectUrl}`, request)
403+
return createTabWithURL(redirect, browser, recoveredTabs)
389404
}
390405
}
391406

@@ -399,14 +414,15 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
399414
} else {
400415
redirectUrl = ipfsPathValidator.resolveToPublicUrl(request.url, state.pubGwURLString)
401416
}
402-
log(`onErrorOccurred: attempting to recover from network error (${request.error}) for ${request.url}`, redirectUrl)
403-
return createTabWithURL({ redirectUrl }, browser)
417+
log(`onErrorOccurred: attempting to recover from network error (${request.error}) for ${request.url}${redirectUrl}`, request)
418+
return createTabWithURL({ redirectUrl }, browser, recoveredTabs)
404419
}
405420
},
406421

407422
// browser.webRequest.onCompleted
408423
// Fired when HTTP request is completed (successfully or with an error code)
409-
async onCompleted (request) {
424+
// NOTE: this is executed only if webRequest.ResourceType='main_frame'
425+
onCompleted (request) {
410426
const state = getState()
411427
if (!state.active) return
412428
if (request.statusCode === 200) return // finish if no error to recover from
@@ -418,8 +434,8 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
418434
} else {
419435
redirectUrl = ipfsPathValidator.resolveToPublicUrl(request.url, state.pubGwURLString)
420436
}
421-
log(`onCompleted: attempting to recover from HTTP Error ${request.statusCode} for ${request.url}`, redirectUrl)
422-
return createTabWithURL({ redirectUrl }, browser)
437+
log(`onCompleted: attempting to recover from HTTP Error ${request.statusCode} for ${request.url}${redirectUrl}`, request)
438+
return createTabWithURL({ redirectUrl }, browser, recoveredTabs)
423439
}
424440
}
425441
}
@@ -560,11 +576,27 @@ function isRecoverableViaEthDNS (request, state) {
560576

561577
// We can't redirect in onErrorOccurred/onCompleted
562578
// Indead, we recover by opening URL in a new tab that replaces the failed one
563-
async function createTabWithURL (redirect, browser) {
564-
const currentTabId = await browser.tabs.query({ active: true, currentWindow: true }).then(tabs => tabs[0].id)
565-
return browser.tabs.create({
579+
// TODO: display an user-friendly prompt when the very first recovery is done
580+
async function createTabWithURL (redirect, browser, recoveredTabs) {
581+
const tabKey = redirect.redirectUrl
582+
// reuse existing tab, if exists
583+
// (this avoids duplicated tabs - https://github.com/ipfs-shipyard/ipfs-companion/issues/805)
584+
try {
585+
const recoveredId = recoveredTabs.get(tabKey)
586+
const existingTab = recoveredId ? await browser.tabs.get(recoveredId) : undefined
587+
if (existingTab) {
588+
await browser.tabs.update(recoveredId, { active: true })
589+
return
590+
}
591+
} catch (_) {
592+
// tab no longer exist, let's create a new one
593+
}
594+
const failedTab = await browser.tabs.getCurrent()
595+
const openerTabId = failedTab ? failedTab.id : undefined
596+
const newTab = await browser.tabs.create({
566597
active: true,
567-
openerTabId: currentTabId,
598+
openerTabId,
568599
url: redirect.redirectUrl
569600
})
601+
if (newTab) recoveredTabs.set(tabKey, newTab.id)
570602
}

test/functional/lib/ipfs-request-gateway-recover.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe('requestHandler.onCompleted:', function () { // HTTP-level errors
2727

2828
before(function () {
2929
global.URL = URL
30-
browser.tabs = { ...browser.tabs, query: sinon.stub().resolves([{ id: 20 }]) }
30+
browser.tabs = { ...browser.tabs, getCurrent: sinon.stub().resolves({ id: 20 }) }
3131
global.browser = browser
3232
})
3333

0 commit comments

Comments
 (0)