@@ -9,36 +9,20 @@ const LRU = require('lru-cache')
9
9
const IsIpfs = require ( 'is-ipfs' )
10
10
const isFQDN = require ( 'is-fqdn' )
11
11
const { pathAtHttpGateway } = require ( './ipfs-path' )
12
+
12
13
const redirectOptOutHint = 'x-ipfs-companion-no-redirect'
13
- const recoverableErrors = new Set ( [
14
+ const recoverableNetworkErrors = new Set ( [
14
15
// Firefox
16
+ 'NS_ERROR_UNKNOWN_HOST' , // dns failure
15
17
'NS_ERROR_NET_TIMEOUT' , // eg. httpd is offline
16
18
'NS_ERROR_NET_RESET' , // failed to load because the server kept reseting the connection
17
19
'NS_ERROR_NET_ON_RESOLVED' , // no network
18
20
// Chrome
21
+ 'net::ERR_NAME_NOT_RESOLVED' , // dns failure
19
22
'net::ERR_CONNECTION_TIMED_OUT' , // eg. httpd is offline
20
23
'net::ERR_INTERNET_DISCONNECTED' // no network
21
24
] )
22
-
23
- const recoverableErrorCodes = new Set ( [
24
- 404 ,
25
- 408 ,
26
- 410 ,
27
- 415 ,
28
- 451 ,
29
- 500 ,
30
- 502 ,
31
- 503 ,
32
- 504 ,
33
- 509 ,
34
- 520 ,
35
- 521 ,
36
- 522 ,
37
- 523 ,
38
- 524 ,
39
- 525 ,
40
- 526
41
- ] )
25
+ const recoverableHttpError = ( code ) => code && code >= 400
42
26
43
27
// Request modifier provides event listeners for the various stages of making an HTTP request
44
28
// API Details: https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/webRequest
@@ -171,11 +155,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
171
155
// This is a good place to listen if you want to modify HTTP request headers.
172
156
onBeforeSendHeaders ( request ) {
173
157
const state = getState ( )
174
-
175
- // Skip if IPFS integrations are inactive
176
- if ( ! state . active ) {
177
- return
178
- }
158
+ if ( ! state . active ) return
179
159
180
160
// Special handling of requests made to API
181
161
if ( request . url . startsWith ( state . apiURLString ) ) {
@@ -286,11 +266,7 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
286
266
// You can use this event to modify HTTP response headers or do a very late redirect.
287
267
onHeadersReceived ( request ) {
288
268
const state = getState ( )
289
-
290
- // Skip if IPFS integrations are inactive
291
- if ( ! state . active ) {
292
- return
293
- }
269
+ if ( ! state . active ) return
294
270
295
271
// Special handling of requests made to API
296
272
if ( request . url . startsWith ( state . apiURLString ) ) {
@@ -387,58 +363,48 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
387
363
} ,
388
364
389
365
// browser.webRequest.onErrorOccurred
390
- // Fired when a request could not be processed due to an error:
391
- // for example, a lack of Internet connectivity.
366
+ // Fired when a request could not be processed due to an error on network level.
367
+ // For example: TCP timeout, DNS lookup failure
392
368
async onErrorOccurred ( request ) {
393
369
const state = getState ( )
394
-
395
- // Skip if IPFS integrations are inactive or request is marked as ignored
396
- if ( ! state . active || isIgnored ( request . requestId ) ) {
397
- return
370
+ if ( ! state . active ) return
371
+
372
+ // Check if error can be recovered via DNSLink
373
+ if ( isRecoverableViaDNSLink ( request , state , dnslinkResolver ) ) {
374
+ const url = new URL ( request . url )
375
+ let dnslink = dnslinkResolver . readAndCacheDnslink ( url . hostname )
376
+ if ( ! dnslink && url . hostname . endsWith ( '.eth' ) ) {
377
+ // negative for .eth usually means the lack of support for the tld
378
+ // we retry via EthDNS at eth.link as a fallback
379
+ url . hostname = `${ url . hostname } .link` // TODO this is needed until go-ipfs v0.5.0 ships with https://github.com/ipfs/go-ipfs/pull/6448
380
+ dnslink = dnslinkResolver . readAndCacheDnslink ( url . hostname )
381
+ }
382
+ const redirect = dnslinkResolver . dnslinkRedirect ( url . toString ( ) , dnslink )
383
+ log ( `onErrorOccurred: attempting to recover from network error (${ request . error } ) using dnslink for ${ url . toString ( ) } ` , redirect . redirectUrl )
384
+ return createTabWithURL ( redirect , browser )
398
385
}
399
386
400
- // console.log('onErrorOccurred:' + request.error)
401
- // console.log('onErrorOccurred', request)
402
- // Check if error is final and can be recovered via DNSLink
403
- let redirect
404
- const recoverableViaDnslink =
405
- state . dnslinkPolicy &&
406
- request . type === 'main_frame' &&
407
- recoverableErrors . has ( request . error )
408
- if ( recoverableViaDnslink && dnslinkResolver . canLookupURL ( request . url ) ) {
409
- // Explicit call to ignore global DNSLink policy and force DNS TXT lookup
410
- const cachedDnslink = dnslinkResolver . readAndCacheDnslink ( new URL ( request . url ) . hostname )
411
- redirect = dnslinkResolver . dnslinkRedirect ( request . url , cachedDnslink )
412
- log ( `onErrorOccurred: attempting to recover using dnslink for ${ request . url } ` , redirect )
413
- }
414
- // if error cannot be recovered via DNSLink
415
- // direct the request to the public gateway
416
- const recoverable = isRecoverable ( request , state , ipfsPathValidator )
417
- if ( ! redirect && recoverable ) {
387
+ // Check if error can be recovered by opening same content-addresed path
388
+ // using active gateway (public or local, depending on redirect state)
389
+ if ( isRecoverable ( request , state , ipfsPathValidator ) ) {
418
390
const redirectUrl = ipfsPathValidator . resolveToPublicUrl ( request . url , state . pubGwURLString )
419
- redirect = { redirectUrl }
420
- log ( `onErrorOccurred: attempting to recover failed request for ${ request . url } ` , redirect )
421
- }
422
- // We can't redirect in onErrorOccurred, so if DNSLink is present
423
- // recover by opening IPNS version in a new tab
424
- // TODO: add tests and demo
425
- if ( redirect ) {
426
- createTabWithURL ( redirect , browser )
391
+ log ( `onErrorOccurred: attempting to recover from network error (${ request . error } ) for ${ request . url } ` , redirectUrl )
392
+ return createTabWithURL ( { redirectUrl } , browser )
427
393
}
428
394
} ,
429
395
396
+ // browser.webRequest.onCompleted
397
+ // Fired when HTTP request is completed (successfully or with an error code)
430
398
async onCompleted ( request ) {
431
399
const state = getState ( )
432
-
433
- const recoverable =
434
- isRecoverable ( request , state , ipfsPathValidator ) &&
435
- recoverableErrorCodes . has ( request . statusCode )
436
- if ( recoverable ) {
400
+ if ( ! state . active ) return
401
+ if ( request . statusCode === 200 ) return // finish if no error to recover from
402
+ if ( isRecoverable ( request , state , ipfsPathValidator ) ) {
437
403
const redirectUrl = ipfsPathValidator . resolveToPublicUrl ( request . url , state . pubGwURLString )
438
404
const redirect = { redirectUrl }
439
405
if ( redirect ) {
440
- log ( `onCompleted: attempting to recover failed request for ${ request . url } ` , redirect )
441
- createTabWithURL ( redirect , browser )
406
+ log ( `onCompleted: attempting to recover from HTTP Error ${ request . statusCode } for ${ request . url } ` , redirect )
407
+ return createTabWithURL ( redirect , browser )
442
408
}
443
409
}
444
410
}
@@ -548,18 +514,32 @@ function findHeaderIndex (name, headers) {
548
514
return headers . findIndex ( x => x . name && x . name . toLowerCase ( ) === name . toLowerCase ( ) )
549
515
}
550
516
551
- // utility functions for handling redirects
552
- // from onErrorOccurred and onCompleted
517
+ // RECOVERY OF FAILED REQUESTS
518
+ // ===================================================================
519
+
520
+ // Recovery check for onErrorOccurred (request.error) and onCompleted (request.statusCode)
553
521
function isRecoverable ( request , state , ipfsPathValidator ) {
554
522
return state . recoverFailedHttpRequests &&
523
+ request . type === 'main_frame' &&
524
+ ( recoverableNetworkErrors . has ( request . error ) || recoverableHttpError ( request . statusCode ) ) &&
555
525
ipfsPathValidator . publicIpfsOrIpnsResource ( request . url ) &&
556
- ! request . url . startsWith ( state . pubGwURLString ) &&
557
- request . type === 'main_frame'
526
+ ! request . url . startsWith ( state . pubGwURLString )
527
+ }
528
+
529
+ // Recovery check for onErrorOccurred (request.error)
530
+ function isRecoverableViaDNSLink ( request , state , dnslinkResolver ) {
531
+ const recoverableViaDnslink =
532
+ request . type === 'main_frame' &&
533
+ state . dnslinkPolicy &&
534
+ recoverableNetworkErrors . has ( request . error )
535
+ return recoverableViaDnslink && dnslinkResolver . canLookupURL ( request . url )
558
536
}
559
537
538
+ // We can't redirect in onErrorOccurred/onCompleted
539
+ // Indead, we recover by opening URL in a new tab that replaces the failed one
560
540
async function createTabWithURL ( redirect , browser ) {
561
541
const currentTabId = await browser . tabs . query ( { active : true , currentWindow : true } ) . then ( tabs => tabs [ 0 ] . id )
562
- await browser . tabs . create ( {
542
+ return browser . tabs . create ( {
563
543
active : true ,
564
544
openerTabId : currentTabId ,
565
545
url : redirect . redirectUrl
0 commit comments