@@ -33,9 +33,11 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
33
33
34
34
// Various types of requests are identified once and cached across all browser.webRequest hooks
35
35
const requestCacheCfg = { max : 128 , maxAge : 1000 * 30 }
36
+ const recoveredTabs = new LRU ( requestCacheCfg )
36
37
const ignoredRequests = new LRU ( requestCacheCfg )
37
38
const ignore = ( id ) => ignoredRequests . set ( id , true )
38
39
const isIgnored = ( id ) => ignoredRequests . get ( id ) !== undefined
40
+ const errorInFlight = new LRU ( { max : 3 , maxAge : 1000 } )
39
41
40
42
const acrhHeaders = new LRU ( requestCacheCfg ) // webui cors fix in Chrome
41
43
const originUrls = new LRU ( requestCacheCfg ) // request.originUrl workaround for Chrome
@@ -365,17 +367,30 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
365
367
// browser.webRequest.onErrorOccurred
366
368
// Fired when a request could not be processed due to an error on network level.
367
369
// 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 ) {
369
372
const state = getState ( )
370
373
if ( ! state . active ) return
371
374
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
+
372
387
// Check if error can be recovered via EthDNS
373
388
if ( isRecoverableViaEthDNS ( request , state ) ) {
374
389
const url = new URL ( request . url )
375
390
url . hostname = `${ url . hostname } .link`
376
391
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 )
379
394
}
380
395
381
396
// Check if error can be recovered via DNSLink
@@ -384,8 +399,8 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
384
399
const dnslink = dnslinkResolver . readAndCacheDnslink ( hostname )
385
400
if ( dnslink ) {
386
401
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 )
389
404
}
390
405
}
391
406
@@ -399,14 +414,15 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
399
414
} else {
400
415
redirectUrl = ipfsPathValidator . resolveToPublicUrl ( request . url , state . pubGwURLString )
401
416
}
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 )
404
419
}
405
420
} ,
406
421
407
422
// browser.webRequest.onCompleted
408
423
// 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 ) {
410
426
const state = getState ( )
411
427
if ( ! state . active ) return
412
428
if ( request . statusCode === 200 ) return // finish if no error to recover from
@@ -418,8 +434,8 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
418
434
} else {
419
435
redirectUrl = ipfsPathValidator . resolveToPublicUrl ( request . url , state . pubGwURLString )
420
436
}
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 )
423
439
}
424
440
}
425
441
}
@@ -560,11 +576,27 @@ function isRecoverableViaEthDNS (request, state) {
560
576
561
577
// We can't redirect in onErrorOccurred/onCompleted
562
578
// 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 ( {
566
597
active : true ,
567
- openerTabId : currentTabId ,
598
+ openerTabId,
568
599
url : redirect . redirectUrl
569
600
} )
601
+ if ( newTab ) recoveredTabs . set ( tabKey , newTab . id )
570
602
}
0 commit comments