@@ -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,23 @@ 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
+
372
380
// Check if error can be recovered via EthDNS
373
381
if ( isRecoverableViaEthDNS ( request , state ) ) {
374
382
const url = new URL ( request . url )
375
383
url . hostname = `${ url . hostname } .link`
376
384
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 )
385
+ log ( `onErrorOccurred: attempting to recover from DNS error (${ request . error } ) using EthDNS for ${ request . url } → ${ redirect . redirectUrl } ` , request )
386
+ return createTabWithURL ( redirect , browser , recoveredTabs )
379
387
}
380
388
381
389
// Check if error can be recovered via DNSLink
@@ -384,8 +392,8 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
384
392
const dnslink = dnslinkResolver . readAndCacheDnslink ( hostname )
385
393
if ( dnslink ) {
386
394
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 )
395
+ log ( `onErrorOccurred: attempting to recover from network error (${ request . error } ) using dnslink for ${ request . url } → ${ redirect . redirectUrl } ` , request )
396
+ return createTabWithURL ( redirect , browser , recoveredTabs )
389
397
}
390
398
}
391
399
@@ -399,14 +407,15 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
399
407
} else {
400
408
redirectUrl = ipfsPathValidator . resolveToPublicUrl ( request . url , state . pubGwURLString )
401
409
}
402
- log ( `onErrorOccurred: attempting to recover from network error (${ request . error } ) for ${ request . url } ` , redirectUrl )
403
- return createTabWithURL ( { redirectUrl } , browser )
410
+ log ( `onErrorOccurred: attempting to recover from network error (${ request . error } ) for ${ request . url } → ${ redirectUrl } ` , request )
411
+ return createTabWithURL ( { redirectUrl } , browser , recoveredTabs )
404
412
}
405
413
} ,
406
414
407
415
// browser.webRequest.onCompleted
408
416
// Fired when HTTP request is completed (successfully or with an error code)
409
- async onCompleted ( request ) {
417
+ // NOTE: this is executed only if webRequest.ResourceType='main_frame'
418
+ onCompleted ( request ) {
410
419
const state = getState ( )
411
420
if ( ! state . active ) return
412
421
if ( request . statusCode === 200 ) return // finish if no error to recover from
@@ -418,8 +427,8 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
418
427
} else {
419
428
redirectUrl = ipfsPathValidator . resolveToPublicUrl ( request . url , state . pubGwURLString )
420
429
}
421
- log ( `onCompleted: attempting to recover from HTTP Error ${ request . statusCode } for ${ request . url } ` , redirectUrl )
422
- return createTabWithURL ( { redirectUrl } , browser )
430
+ log ( `onCompleted: attempting to recover from HTTP Error ${ request . statusCode } for ${ request . url } → ${ redirectUrl } ` , request )
431
+ return createTabWithURL ( { redirectUrl } , browser , recoveredTabs )
423
432
}
424
433
}
425
434
}
@@ -560,11 +569,27 @@ function isRecoverableViaEthDNS (request, state) {
560
569
561
570
// We can't redirect in onErrorOccurred/onCompleted
562
571
// 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 ( {
572
+ // TODO: display an user-friendly prompt when the very first recovery is done
573
+ async function createTabWithURL ( redirect , browser , recoveredTabs ) {
574
+ const tabKey = redirect . redirectUrl
575
+ // reuse existing tab, if exists
576
+ // (this avoids duplicated tabs - https://github.com/ipfs-shipyard/ipfs-companion/issues/805)
577
+ try {
578
+ const recoveredId = recoveredTabs . get ( tabKey )
579
+ const existingTab = recoveredId ? await browser . tabs . get ( recoveredId ) : undefined
580
+ if ( existingTab ) {
581
+ await browser . tabs . update ( recoveredId , { active : true } )
582
+ return
583
+ }
584
+ } catch ( _ ) {
585
+ // tab no longer exist, let's create a new one
586
+ }
587
+ const failedTab = await browser . tabs . getCurrent ( )
588
+ const openerTabId = failedTab ? failedTab . id : undefined
589
+ const newTab = await browser . tabs . create ( {
566
590
active : true ,
567
- openerTabId : currentTabId ,
591
+ openerTabId,
568
592
url : redirect . redirectUrl
569
593
} )
594
+ if ( newTab ) recoveredTabs . set ( tabKey , newTab . id )
570
595
}
0 commit comments