diff --git a/add-on/src/lib/ipfs-request.js b/add-on/src/lib/ipfs-request.js index ac999633b..2cf7285e9 100644 --- a/add-on/src/lib/ipfs-request.js +++ b/add-on/src/lib/ipfs-request.js @@ -37,7 +37,6 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru // Various types of requests are identified once and cached across all browser.webRequest hooks const requestCacheCfg = { max: 128, maxAge: 1000 * 30 } - const recoveredTabs = new LRU(requestCacheCfg) const ignoredRequests = new LRU(requestCacheCfg) const ignore = (id) => ignoredRequests.set(id, true) const isIgnored = (id) => ignoredRequests.get(id) !== undefined @@ -369,8 +368,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru url.hostname = `${url.hostname}.link` const redirectUrl = url.toString() log(`onErrorOccurred: attempting to recover from DNS error (${request.error}) using EthDNS for ${request.url} → ${redirectUrl}`, request) - // TODO: update existing tab - return createTabWithURL(request, redirectUrl, browser, recoveredTabs) + return updateTabWithURL(request, redirectUrl, browser) } // Check if error can be recovered via DNSLink @@ -380,7 +378,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru if (dnslink) { const redirectUrl = dnslinkResolver.dnslinkAtGateway(request.url, dnslink) log(`onErrorOccurred: attempting to recover from network error (${request.error}) using dnslink for ${request.url} → ${redirectUrl}`, request) - return createTabWithURL(request, redirectUrl, browser, recoveredTabs) + return updateTabWithURL(request, redirectUrl, browser) } } @@ -394,7 +392,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru if (isRecoverable(request, state, ipfsPathValidator)) { const redirectUrl = ipfsPathValidator.resolveToPublicUrl(request.url) log(`onErrorOccurred: attempting to recover from network error (${request.error}) for ${request.url} → ${redirectUrl}`, request) - return createTabWithURL(request, redirectUrl, browser, recoveredTabs) + return updateTabWithURL(request, redirectUrl, browser) } }, @@ -424,7 +422,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru if (isRecoverable(request, state, ipfsPathValidator)) { const redirectUrl = ipfsPathValidator.resolveToPublicUrl(request.url) log(`onCompleted: attempting to recover from HTTP Error ${request.statusCode} for ${request.url} → ${redirectUrl}`, request) - return createTabWithURL(request, redirectUrl, browser, recoveredTabs) + return updateTabWithURL(request, redirectUrl, browser) } } } @@ -608,29 +606,12 @@ function isRecoverableViaEthDNS (request, state) { // We can't redirect in onErrorOccurred/onCompleted // Indead, we recover by opening URL in a new tab that replaces the failed one // TODO: display an user-friendly prompt when the very first recovery is done -async function createTabWithURL (request, redirectUrl, browser, recoveredTabs) { +async function updateTabWithURL (request, redirectUrl, browser) { // Do nothing if the URL remains the same if (request.url === redirectUrl) return - const tabKey = redirectUrl - // reuse existing tab, if exists - // (this avoids duplicated tabs - https://github.com/ipfs-shipyard/ipfs-companion/issues/805) - try { - const recoveredId = recoveredTabs.get(tabKey) - const existingTab = recoveredId ? await browser.tabs.get(recoveredId) : undefined - if (existingTab) { - await browser.tabs.update(recoveredId, { active: true }) - return - } - } catch (_) { - // tab no longer exist, let's create a new one - } - const failedTab = await browser.tabs.getCurrent() - const openerTabId = failedTab ? failedTab.id : undefined - const newTab = await browser.tabs.create({ + return browser.tabs.update({ active: true, - openerTabId, url: redirectUrl }) - if (newTab) recoveredTabs.set(tabKey, newTab.id) } diff --git a/test/functional/lib/ipfs-request-gateway-recover.test.js b/test/functional/lib/ipfs-request-gateway-recover.test.js index 5f680dd7a..83faaa090 100644 --- a/test/functional/lib/ipfs-request-gateway-recover.test.js +++ b/test/functional/lib/ipfs-request-gateway-recover.test.js @@ -49,47 +49,47 @@ describe('requestHandler.onCompleted:', function () { // HTTP-level errors it('should do nothing if broken request is for the default subdomain gateway', async function () { const request = urlRequestWithStatus('https://QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h.ipfs.dweb.link/wiki/', 500) await requestHandler.onCompleted(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) it('should redirect to default subdomain gateway on broken subdomain gateway request', async function () { const request = urlRequestWithStatus('http://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.brokenexample.com/wiki/', 500) await requestHandler.onCompleted(request) - assert.ok(browser.tabs.create.withArgs({ url: 'https://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.dweb.link/wiki/', active: true, openerTabId: 20 }).calledOnce, 'tabs.create should be called with default subdomain gateway URL') + assert.ok(browser.tabs.update.withArgs({ url: 'https://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.dweb.link/wiki/', active: true }).calledOnce, 'tabs.update should be called with default subdomain gateway URL') }) it('should do nothing if broken request is a non-IPFS request', async function () { const request = urlRequestWithStatus('https://wikipedia.org', 500) await requestHandler.onCompleted(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) it('should do nothing if broken request is a local request to 127.0.0.1/ipfs', async function () { const request = urlRequestWithStatus('http://127.0.0.1:8080/ipfs/QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500) await requestHandler.onCompleted(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) it('should do nothing if broken request is a local request to localhost/ipfs', async function () { const request = urlRequestWithStatus('http://localhost:8080/ipfs/QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500) await requestHandler.onCompleted(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) it('should do nothing if broken request is a local request to *.ipfs.localhost', async function () { const request = urlRequestWithStatus('http://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhw.ipfs.localhost:8080/', 500) await requestHandler.onCompleted(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) it('should do nothing if broken request is to the default public gateway', async function () { const request = urlRequestWithStatus('https://ipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500) await requestHandler.onCompleted(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) it('should do nothing if broken request is not a \'main_frame\' request', async function () { const request = urlRequestWithStatus('https://nondefaultipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500, 'stylesheet') await requestHandler.onCompleted(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) it('should recover from unreachable third party public gateway by reopening on the public gateway', async function () { const request = urlRequestWithStatus('https://nondefaultipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500) await requestHandler.onCompleted(request) - assert.ok(browser.tabs.create.withArgs({ url: 'https://ipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', active: true, openerTabId: 20 }).calledOnce, 'tabs.create should be called with IPFS default public gateway URL') + assert.ok(browser.tabs.update.withArgs({ url: 'https://ipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', active: true }).calledOnce, 'tabs.update should be called with IPFS default public gateway URL') }) }) @@ -101,17 +101,17 @@ describe('requestHandler.onCompleted:', function () { // HTTP-level errors it('should do nothing on failed subdomain gateway request', async function () { const request = urlRequestWithStatus('https://QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h.ipfs.brokendomain.com/wiki/', 500) await requestHandler.onCompleted(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) it('should do nothing on broken non-default public gateway IPFS request', async function () { const request = urlRequestWithStatus('https://nondefaultipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500) await requestHandler.onCompleted(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) }) afterEach(function () { - browser.tabs.create.reset() + browser.tabs.update.reset() }) after(function () { @@ -148,38 +148,38 @@ describe('requestHandler.onErrorOccurred:', function () { // network errors it('should do nothing if failed request is for the default subdomain gateway', async function () { const request = urlRequestWithStatus('https://QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h.ipfs.dweb.link/wiki/', 500) await requestHandler.onErrorOccurred(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) it('should redirect to default subdomain gateway on failed subdomain gateway request', async function () { const request = urlRequestWithStatus('http://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.brokenexample.com/wiki/', 500) await requestHandler.onErrorOccurred(request) - assert.ok(browser.tabs.create.withArgs({ url: 'https://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.dweb.link/wiki/', active: true, openerTabId: 20 }).calledOnce, 'tabs.create should be called with default subdomain gateway URL') + assert.ok(browser.tabs.update.withArgs({ url: 'https://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq.ipfs.dweb.link/wiki/', active: true }).calledOnce, 'tabs.update should be called with default subdomain gateway URL') }) it('should do nothing if failed request is a non-IPFS request', async function () { const request = urlRequestWithNetworkError('https://wikipedia.org') await requestHandler.onErrorOccurred(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) it('should do nothing if failed request is a non-public IPFS request', async function () { const request = urlRequestWithNetworkError('http://127.0.0.1:8080/ipfs/QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h') await requestHandler.onErrorOccurred(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) it('should do nothing if failed request is to the default public gateway', async function () { const request = urlRequestWithNetworkError('https://ipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h') await requestHandler.onErrorOccurred(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) it('should do nothing if failed request is not a \'main_frame\' request', async function () { const requestType = 'stylesheet' const request = urlRequestWithNetworkError('https://nondefaultipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 'net::ERR_NAME_NOT_RESOLVED', requestType) await requestHandler.onErrorOccurred(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) it('should recover from unreachable third party public gateway by reopening on the public gateway', async function () { const request = urlRequestWithNetworkError('https://nondefaultipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h') await requestHandler.onErrorOccurred(request) - assert.ok(browser.tabs.create.withArgs({ url: 'https://ipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', active: true, openerTabId: 20 }).calledOnce, 'tabs.create should be called with IPFS default public gateway URL') + assert.ok(browser.tabs.update.withArgs({ url: 'https://ipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', active: true }).calledOnce, 'tabs.update should be called with IPFS default public gateway URL') }) it('should recover from unreachable HTTP server by reopening DNSLink on the active gateway', async function () { state.dnslinkPolicy = 'best-effort' @@ -188,7 +188,7 @@ describe('requestHandler.onErrorOccurred:', function () { // network errors const expectedUrl = 'http://localhost:8080/ipns/en.wikipedia-on-ipfs.org/' const request = urlRequestWithNetworkError('https://en.wikipedia-on-ipfs.org/') await requestHandler.onErrorOccurred(request) - assert.ok(browser.tabs.create.withArgs({ url: expectedUrl, active: true, openerTabId: 20 }).calledOnce, 'tabs.create should be called with DNSLink on local gateway URL') + assert.ok(browser.tabs.update.withArgs({ url: expectedUrl, active: true }).calledOnce, 'tabs.update should be called with DNSLink on local gateway URL') dnslinkResolver.clearCache() }) it('should recover from failed DNS for .eth opening it on EthDNS gateway at .eth.link', async function () { @@ -199,7 +199,7 @@ describe('requestHandler.onErrorOccurred:', function () { // network errors const expectedUrl = 'https://almonit.eth.link/' const request = urlRequestWithNetworkError('https://almonit.eth', dnsFailure) await requestHandler.onErrorOccurred(request) - assert.ok(browser.tabs.create.withArgs({ url: expectedUrl, active: true, openerTabId: 20 }).calledOnce, 'tabs.create should be called with ENS resource on local gateway URL') + assert.ok(browser.tabs.update.withArgs({ url: expectedUrl, active: true }).calledOnce, 'tabs.update should be called with ENS resource on local gateway URL') dnslinkResolver.clearCache() }) }) @@ -212,19 +212,19 @@ describe('requestHandler.onErrorOccurred:', function () { // network errors it('should do nothing on unreachable third party public gateway', async function () { const request = urlRequestWithNetworkError('https://nondefaultipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h') await requestHandler.onErrorOccurred(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) it('should do nothing on failed subdomain gateway request', async function () { const request = urlRequestWithStatus('https://QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h.ipfs.brokendomain.com/wiki/', 500) await requestHandler.onErrorOccurred(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') }) it('should do nothing on unreachable HTTP server with DNSLink', async function () { state.dnslinkPolicy = 'best-effort' dnslinkResolver.setDnslink('en.wikipedia-on-ipfs.org', '/ipfs/QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco') const request = urlRequestWithNetworkError('https://en.wikipedia-on-ipfs.org') await requestHandler.onErrorOccurred(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') dnslinkResolver.clearCache() }) it('should do nothing on failed non-default public gateway IPFS request', async function () { @@ -234,13 +234,13 @@ describe('requestHandler.onErrorOccurred:', function () { // network errors const dnsFailure = 'net::ERR_NAME_NOT_RESOLVED' // chrome code const request = urlRequestWithNetworkError('https://almonit.eth', dnsFailure) await requestHandler.onErrorOccurred(request) - assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called') + assert.ok(browser.tabs.update.notCalled, 'tabs.update should not be called') dnslinkResolver.clearCache() }) }) afterEach(function () { - browser.tabs.create.reset() + browser.tabs.update.reset() }) after(function () {