Skip to content

Commit f948443

Browse files
committed
tests: updates related to subdomain gateways
1 parent e9a34a7 commit f948443

8 files changed

+100
-53
lines changed

add-on/src/lib/dnslink.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ module.exports = function createDnslinkResolver (getState) {
6161
// to load the correct path from IPFS
6262
// - https://github.com/ipfs/ipfs-companion/issues/298
6363
const ipnsPath = dnslinkResolver.convertToIpnsPath(url)
64-
const gateway = state.ipfsNodeType === 'embedded' ? state.pubGwURLString : state.gwURLString
64+
const gateway = state.localGwAvailable ? state.gwURLString : state.pubGwURLString
6565
return pathAtHttpGateway(ipnsPath, gateway)
6666
}
6767
},
@@ -110,7 +110,7 @@ module.exports = function createDnslinkResolver (getState) {
110110
preloadUrlCache.set(url, true)
111111
const dnslink = await dnslinkResolver.resolve(url)
112112
if (!dnslink) return
113-
if (state.ipfsNodeType === 'embedded') return
113+
if (!state.localGwAvailable) return
114114
if (state.peerCount < 1) return
115115
return preloadQueue.add(async () => {
116116
const { pathname } = new URL(url)

add-on/src/lib/http-proxy.js

+22-11
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,31 @@ log.error = debug('ipfs-companion:http-proxy:error')
2323
// registerSubdomainProxy is necessary wourkaround for supporting subdomains
2424
// under 'localhost' (*.ipfs.localhost) because some operating systems do not
2525
// resolve them to local IP and return NX error not found instead
26-
async function registerSubdomainProxy (getState, runtime) {
27-
const { useSubdomainProxy: enable, gwURLString } = getState()
26+
async function registerSubdomainProxy (getState, runtime, notify) {
27+
try {
28+
const { useSubdomainProxy: enable, gwURLString } = getState()
2829

29-
// HTTP Proxy feature is exposed on the gateway port
30-
// Just ensure we use localhost IP to remove any dependency on DNS
31-
const proxy = safeURL(gwURLString, { useLocalhostName: false })
30+
// HTTP Proxy feature is exposed on the gateway port
31+
// Just ensure we use localhost IP to remove any dependency on DNS
32+
const proxy = safeURL(gwURLString, { useLocalhostName: false })
3233

33-
// Firefox uses own APIs for selective proxying
34-
if (runtime.isFirefox) {
35-
return registerSubdomainProxyFirefox(enable, proxy.hostname, proxy.port)
36-
}
34+
// Firefox uses own APIs for selective proxying
35+
if (runtime.isFirefox) {
36+
return await registerSubdomainProxyFirefox(enable, proxy.hostname, proxy.port)
37+
}
3738

38-
// at this point we asume Chromium
39-
return registerSubdomainProxyChromium(enable, proxy.host)
39+
// at this point we asume Chromium
40+
return await registerSubdomainProxyChromium(enable, proxy.host)
41+
} catch (err) {
42+
// registerSubdomainProxy is just a failsafe, not necessary in most cases,
43+
// so we should not break init when it fails.
44+
// For now we just log error and exit as NOOP
45+
log.error('registerSubdomainProxy failed', err)
46+
// Show pop-up only the first time, during init() when notify is passed
47+
try {
48+
if (notify) notify('notify_addonIssueTitle', 'notify_addonIssueMsg')
49+
} catch (_) {}
50+
}
4051
}
4152

4253
// storing listener for later

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ module.exports = async function init () {
8484
ipfsProxyContentScript = await registerIpfsProxyContentScript()
8585
log('register all listeners')
8686
registerListeners()
87-
await registerSubdomainProxy(getState, runtime)
8887
await setApiStatusUpdateInterval(options.ipfsApiPollMs)
88+
await registerSubdomainProxy(getState, runtime, notify)
8989
log('init done')
9090
await showPendingLandingPages()
9191
} catch (error) {
@@ -324,7 +324,7 @@ module.exports = async function init () {
324324
}
325325
ipfsImportHandler.copyShareLink(result)
326326
ipfsImportHandler.preloadFilesAtPublicGateway(result)
327-
if (state.ipfsNodeType === 'embedded' || !state.openViaWebUI) {
327+
if (!state.localGwAvailable || !state.openViaWebUI) {
328328
return ipfsImportHandler.openFilesAtGateway({ result, openRootInNewTab: true })
329329
} else {
330330
return ipfsImportHandler.openFilesAtWebUI(importDir)
@@ -557,7 +557,7 @@ module.exports = async function init () {
557557
// enable/disable gw redirect based on API going online or offline
558558
// newPeerCount === -1 currently implies node is offline.
559559
// TODO: use `node.isOnline()` if available (js-ipfs)
560-
if (state.automaticMode && state.ipfsNodeType !== 'embedded') {
560+
if (state.automaticMode && state.localGwAvailable) {
561561
if (oldPeerCount === offlinePeerCount && newPeerCount > offlinePeerCount && !state.redirect) {
562562
browser.storage.local.set({ useCustomGateway: true })
563563
.then(() => notify('notify_apiOnlineTitle', 'notify_apiOnlineAutomaticModeMsg'))

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

+32-22
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,9 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
9696
}
9797

9898
const postNormalizationSkip = (state, request) => {
99-
// skip requests to the public gateway if embedded node is running (otherwise we have too much recursion)
100-
if (state.ipfsNodeType === 'embedded' && sameGateway(request.url, state.pubGwURL)) {
99+
// skip requests to the public gateway if we can't reedirect them to local
100+
// node is running (otherwise we have too much recursion)
101+
if (!state.localGwAvailable && sameGateway(request.url, state.pubGwURL)) {
101102
ignore(request.requestId)
102103
// TODO: do not skip and redirect to `ipfs://` and `ipns://` if hasNativeProtocolHandler === true
103104
}
@@ -351,7 +352,9 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
351352
// All the following requests will be upgraded to IPNS
352353
const cachedDnslink = dnslinkResolver.readAndCacheDnslink(new URL(request.url).hostname)
353354
const redirectUrl = dnslinkResolver.dnslinkAtGateway(request.url, cachedDnslink)
354-
if (redirectUrl) {
355+
// redirect only if local node is around, as we can't guarantee DNSLink support
356+
// at a public subdomain gateway (requires more than 1 level of wildcard TLS certs)
357+
if (redirectUrl && state.localGwAvailable) {
355358
log(`onHeadersReceived: dnslinkRedirect from ${request.url} to ${redirectUrl}`)
356359
return { redirectUrl }
357360
}
@@ -371,8 +374,8 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
371374
const url = new URL(request.url)
372375
const pathWithArgs = `${xIpfsPath}${url.search}${url.hash}`
373376
const newUrl = pathAtHttpGateway(pathWithArgs, state.pubGwURLString)
374-
// redirect only if anything changed
375-
if (newUrl !== request.url) {
377+
// redirect only if local node is around
378+
if (newUrl && state.localGwAvailable) {
376379
log(`onHeadersReceived: normalized ${request.url} to ${newUrl}`)
377380
return redirectToGateway(request, newUrl, state, ipfsPathValidator)
378381
}
@@ -407,9 +410,10 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
407410
if (isRecoverableViaEthDNS(request, state)) {
408411
const url = new URL(request.url)
409412
url.hostname = `${url.hostname}.link`
410-
const redirect = { redirectUrl: url.toString() }
411-
log(`onErrorOccurred: attempting to recover from DNS error (${request.error}) using EthDNS for ${request.url}${redirect.redirectUrl}`, request)
412-
return createTabWithURL(redirect, browser, recoveredTabs)
413+
const redirectUrl = url.toString()
414+
log(`onErrorOccurred: attempting to recover from DNS error (${request.error}) using EthDNS for ${request.url}${redirectUrl}`, request)
415+
// TODO: update existing tab
416+
return createTabWithURL(request, redirectUrl, browser, recoveredTabs)
413417
}
414418

415419
// Check if error can be recovered via DNSLink
@@ -419,7 +423,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
419423
if (dnslink) {
420424
const redirectUrl = dnslinkResolver.dnslinkAtGateway(request.url, dnslink)
421425
log(`onErrorOccurred: attempting to recover from network error (${request.error}) using dnslink for ${request.url}${redirectUrl}`, request)
422-
return createTabWithURL({ redirectUrl }, browser, recoveredTabs)
426+
return createTabWithURL(request, redirectUrl, browser, recoveredTabs)
423427
}
424428
}
425429

@@ -433,7 +437,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
433437
if (isRecoverable(request, state, ipfsPathValidator)) {
434438
const redirectUrl = ipfsPathValidator.resolveToPublicUrl(request.url)
435439
log(`onErrorOccurred: attempting to recover from network error (${request.error}) for ${request.url}${redirectUrl}`, request)
436-
return createTabWithURL({ redirectUrl }, browser, recoveredTabs)
440+
return createTabWithURL(request, redirectUrl, browser, recoveredTabs)
437441
}
438442
},
439443

@@ -463,7 +467,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
463467
if (isRecoverable(request, state, ipfsPathValidator)) {
464468
const redirectUrl = ipfsPathValidator.resolveToPublicUrl(request.url)
465469
log(`onCompleted: attempting to recover from HTTP Error ${request.statusCode} for ${request.url}${redirectUrl}`, request)
466-
return createTabWithURL({ redirectUrl }, browser, recoveredTabs)
470+
return createTabWithURL(request, redirectUrl, browser, recoveredTabs)
467471
}
468472
}
469473
}
@@ -474,11 +478,8 @@ exports.createRequestModifier = createRequestModifier
474478

475479
function redirectToGateway (request, url, state, ipfsPathValidator) {
476480
const { resolveToPublicUrl, resolveToLocalUrl } = ipfsPathValidator
477-
const useLocal = state.ipfsNodeType !== 'embedded'
478-
const redirectUrl = useLocal ? resolveToLocalUrl(url) : resolveToPublicUrl(url)
481+
const redirectUrl = state.localGwAvailable ? resolveToLocalUrl(url) : resolveToPublicUrl(url)
479482
// redirect only if we actually change anything
480-
console.log('request.url', request.url)
481-
console.log('redirectUrl', redirectUrl)
482483
if (redirectUrl && request.url !== redirectUrl) return { redirectUrl }
483484
}
484485

@@ -588,11 +589,17 @@ function findHeaderIndex (name, headers) {
588589

589590
// Recovery check for onErrorOccurred (request.error) and onCompleted (request.statusCode)
590591
function isRecoverable (request, state, ipfsPathValidator) {
591-
return state.recoverFailedHttpRequests &&
592+
// Note: we are unable to recover default public gateways without a local one
593+
const { error, statusCode, url } = request
594+
const { redirect, localGwAvailable, pubGwURL, pubSubdomainGwURL } = state
595+
return (state.recoverFailedHttpRequests &&
592596
request.type === 'main_frame' &&
593-
(recoverableNetworkErrors.has(request.error) || recoverableHttpError(request.statusCode)) &&
594-
(ipfsPathValidator.publicIpfsOrIpnsResource(request.url) || isIPFS.subdomain(request.url)) &&
595-
!sameGateway(request.url, state.pubGwURL) && !sameGateway(request.url, state.pubSubdomainGwURL)
597+
(recoverableNetworkErrors.has(error) ||
598+
recoverableHttpError(statusCode)) &&
599+
ipfsPathValidator.publicIpfsOrIpnsResource(url) &&
600+
((redirect && localGwAvailable) ||
601+
(!sameGateway(url, pubGwURL) &&
602+
!sameGateway(url, pubSubdomainGwURL))))
596603
}
597604

598605
// Recovery check for onErrorOccurred (request.error)
@@ -616,8 +623,11 @@ function isRecoverableViaEthDNS (request, state) {
616623
// We can't redirect in onErrorOccurred/onCompleted
617624
// Indead, we recover by opening URL in a new tab that replaces the failed one
618625
// TODO: display an user-friendly prompt when the very first recovery is done
619-
async function createTabWithURL (redirect, browser, recoveredTabs) {
620-
const tabKey = redirect.redirectUrl
626+
async function createTabWithURL (request, redirectUrl, browser, recoveredTabs) {
627+
// Do nothing if the URL remains the same
628+
if (request.url === redirectUrl) return
629+
630+
const tabKey = redirectUrl
621631
// reuse existing tab, if exists
622632
// (this avoids duplicated tabs - https://github.com/ipfs-shipyard/ipfs-companion/issues/805)
623633
try {
@@ -635,7 +645,7 @@ async function createTabWithURL (redirect, browser, recoveredTabs) {
635645
const newTab = await browser.tabs.create({
636646
active: true,
637647
openerTabId,
638-
url: redirect.redirectUrl
648+
url: redirectUrl
639649
})
640650
if (newTab) recoveredTabs.set(tabKey, newTab.id)
641651
}

add-on/src/lib/state.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,12 @@ function initState (options, overrides) {
5656
return false
5757
}
5858
}
59-
state.isNodeConnected = () => state.peerCount > 0
60-
state.isNodeActive = () => state.peerCount > offlinePeerCount
59+
// TODO state.connected ~= state.peerCount > 0
60+
// TODO state.nodeActive ~= API is online,eg. state.peerCount > offlinePeerCount
61+
Object.defineProperty(state, 'localGwAvailable', {
62+
// TODO: make quick fetch to confirm it works?
63+
get: function () { return this.ipfsNodeType !== 'embedded' }
64+
})
6165
// apply optional overrides
6266
if (overrides) Object.assign(state, overrides)
6367
return state

test/functional/lib/dnslink.test.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ describe('dnslinkResolver (dnslinkPolicy=detectIpfsPathHeader)', function () {
6363
dnslinkPolicy: 'detectIpfsPathHeader',
6464
peerCount: 1
6565
})
66-
const getExternalNodeState = () => Object.assign({}, getState(), { ipfsNodeType: 'external' })
67-
const getEmbeddedNodeState = () => Object.assign({}, getState(), { ipfsNodeType: 'embedded' })
66+
const getExternalNodeState = () => Object.assign(getState(), { ipfsNodeType: 'external' })
67+
const getEmbeddedNodeState = () => Object.assign(getState(), { ipfsNodeType: 'embedded' })
6868

6969
describe('dnslinkAtGateway(url)', function () {
7070
['/api/v0/foo', '/ipfs/foo', '/ipns/foo'].forEach(path => {
@@ -149,8 +149,8 @@ describe('dnslinkResolver (dnslinkPolicy=enabled)', function () {
149149
dnslinkPolicy: 'enabled',
150150
peerCount: 1
151151
})
152-
const getExternalNodeState = () => Object.assign({}, getState(), { ipfsNodeType: 'external' })
153-
const getEmbeddedNodeState = () => Object.assign({}, getState(), { ipfsNodeType: 'embedded' })
152+
const getExternalNodeState = () => Object.assign(getState(), { ipfsNodeType: 'external' })
153+
const getEmbeddedNodeState = () => Object.assign(getState(), { ipfsNodeType: 'embedded' })
154154

155155
describe('dnslinkAtGateway(url)', function () {
156156
['/api/v0/foo', '/ipfs/foo', '/ipns/foo'].forEach(path => {

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

+11-5
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,21 @@ describe('requestHandler.onCompleted:', function () { // HTTP-level errors
6161
await requestHandler.onCompleted(request)
6262
assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called')
6363
})
64-
it('should do nothing if broken request is a non-public IPFS request to 127.0.0.1', async function () {
64+
it('should do nothing if broken request is a local request to 127.0.0.1/ipfs', async function () {
6565
const request = urlRequestWithStatus('http://127.0.0.1:8080/ipfs/QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500)
6666
await requestHandler.onCompleted(request)
6767
assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called')
6868
})
69-
it('should do nothing if broken request is a non-public IPFS request to localhost', async function () {
69+
it('should do nothing if broken request is a local request to localhost/ipfs', async function () {
7070
const request = urlRequestWithStatus('http://localhost:8080/ipfs/QmYzZgeWE7r8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500)
7171
await requestHandler.onCompleted(request)
7272
assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called')
7373
})
74+
it('should do nothing if broken request is a local request to *.ipfs.localhost', async function () {
75+
const request = urlRequestWithStatus('http://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhw.ipfs.localhost:8080/', 500)
76+
await requestHandler.onCompleted(request)
77+
assert.ok(browser.tabs.create.notCalled, 'tabs.create should not be called')
78+
})
7479
it('should do nothing if broken request is to the default public gateway', async function () {
7580
const request = urlRequestWithStatus('https://ipfs.io/ipfs/QmYbZgeWE7y8HXkH8zbb8J9ddHQvp8LTqm6isL791eo14h', 500)
7681
await requestHandler.onCompleted(request)
@@ -176,13 +181,14 @@ describe('requestHandler.onErrorOccurred:', function () { // network errors
176181
await requestHandler.onErrorOccurred(request)
177182
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')
178183
})
179-
it('should recover from unreachable HTTP server by reopening DNSLink on the public gateway', async function () {
184+
it('should recover from unreachable HTTP server by reopening DNSLink on the active gateway', async function () {
180185
state.dnslinkPolicy = 'best-effort'
181186
dnslinkResolver.setDnslink('en.wikipedia-on-ipfs.org', '/ipfs/QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco')
182-
const expectedUrl = 'http://127.0.0.1:8080/ipns/en.wikipedia-on-ipfs.org/'
187+
// avoid DNS failures when recovering to local gateweay (if available)
188+
const expectedUrl = 'http://localhost:8080/ipns/en.wikipedia-on-ipfs.org/'
183189
const request = urlRequestWithNetworkError('https://en.wikipedia-on-ipfs.org/')
184190
await requestHandler.onErrorOccurred(request)
185-
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')
191+
assert.ok(browser.tabs.create.withArgs({ url: expectedUrl, active: true, openerTabId: 20 }).calledOnce, 'tabs.create should be called with DNSLink on local gateway URL')
186192
dnslinkResolver.clearCache()
187193
})
188194
it('should recover from failed DNS for .eth opening it on EthDNS gateway at .eth.link', async function () {

0 commit comments

Comments
 (0)