@@ -26,8 +26,9 @@ interface AggregateError extends Error {
26
26
interface FetchHandlerArg {
27
27
path : string
28
28
request : Request
29
-
29
+ event : FetchEvent
30
30
}
31
+
31
32
interface GetVerifiedFetchUrlOptions {
32
33
protocol ?: string | null
33
34
id ?: string | null
@@ -38,6 +39,7 @@ interface StoreReponseInCacheOptions {
38
39
response : Response
39
40
cacheKey : string
40
41
isMutable : boolean
42
+ event : FetchEvent
41
43
}
42
44
43
45
/**
@@ -80,6 +82,7 @@ const CURRENT_CACHES = Object.freeze({
80
82
} )
81
83
let verifiedFetch : VerifiedFetch
82
84
const channel = new HeliaServiceWorkerCommsChannel ( 'SW' )
85
+ const timeoutAbortEventType = 'verified-fetch-timeout'
83
86
const ONE_HOUR_IN_SECONDS = 3600
84
87
const urlInterceptRegex = [ new RegExp ( `${ self . location . origin } /ip(n|f)s/` ) ]
85
88
const updateVerifiedFetch = async ( ) : Promise < void > => {
@@ -309,19 +312,17 @@ function getCacheKey (event: FetchEvent): string {
309
312
}
310
313
311
314
async function fetchAndUpdateCache ( event : FetchEvent , url : URL , cacheKey : string ) : Promise < Response > {
312
- const response = await fetchHandler ( { path : url . pathname , request : event . request } )
315
+ const response = await fetchHandler ( { path : url . pathname , request : event . request , event } )
313
316
314
317
// log all of the headers:
315
318
response . headers . forEach ( ( value , key ) => {
316
319
log . trace ( 'helia-sw: response headers: %s: %s' , key , value )
317
320
} )
318
321
319
- log ( 'helia-sw: response range header value: "%s"' , response . headers . get ( 'content-range' ) )
320
-
321
322
log ( 'helia-sw: response status: %s' , response . status )
322
323
323
324
try {
324
- await storeReponseInCache ( { response, isMutable : true , cacheKey } )
325
+ await storeReponseInCache ( { response, isMutable : true , cacheKey, event } )
325
326
trace ( 'helia-ws: updated cache for %s' , cacheKey )
326
327
} catch ( err ) {
327
328
error ( 'helia-ws: failed updating response in cache for %s' , cacheKey , err )
@@ -356,10 +357,25 @@ async function getResponseFromCacheOrFetch (event: FetchEvent): Promise<Response
356
357
return fetchAndUpdateCache ( event , url , cacheKey )
357
358
}
358
359
359
- const invalidOkResponseCodesForCache = [ 206 ]
360
- async function storeReponseInCache ( { response, isMutable, cacheKey } : StoreReponseInCacheOptions ) : Promise < void > {
361
- // 👇 only cache successful responses
362
- if ( ! response . ok || invalidOkResponseCodesForCache . some ( code => code === response . status ) ) {
360
+ function shouldCacheResponse ( { event, response } : { event : FetchEvent , response : Response } ) : boolean {
361
+ if ( ! response . ok ) {
362
+ return false
363
+ }
364
+ const statusCodesToNotCache = [ 206 ]
365
+ if ( statusCodesToNotCache . some ( code => code === response . status ) ) {
366
+ log ( 'helia-sw: not caching response with status %s' , response . status )
367
+ return false
368
+ }
369
+ if ( event . request . headers . get ( 'pragma' ) === 'no-cache' || event . request . headers . get ( 'cache-control' ) === 'no-cache' ) {
370
+ log ( 'helia-sw: request indicated no-cache, not caching' )
371
+ return false
372
+ }
373
+
374
+ return true
375
+ }
376
+
377
+ async function storeReponseInCache ( { response, isMutable, cacheKey, event } : StoreReponseInCacheOptions ) : Promise < void > {
378
+ if ( ! shouldCacheResponse ( { event, response } ) ) {
363
379
return
364
380
}
365
381
trace ( 'helia-ws: updating cache for %s in the background' , cacheKey )
@@ -378,10 +394,11 @@ async function storeReponseInCache ({ response, isMutable, cacheKey }: StoreRepo
378
394
}
379
395
380
396
log ( 'helia-ws: storing response for key %s in cache' , cacheKey )
381
- await cache . put ( cacheKey , respToCache )
397
+ // do not await this.. large responses will delay [TTFB](https://web.dev/articles/ttfb) and [TTI](https://web.dev/articles/tti)
398
+ void cache . put ( cacheKey , respToCache )
382
399
}
383
400
384
- async function fetchHandler ( { path, request } : FetchHandlerArg ) : Promise < Response > {
401
+ async function fetchHandler ( { path, request, event } : FetchHandlerArg ) : Promise < Response > {
385
402
// test and enforce origin isolation before anything else is executed
386
403
const originLocation = await findOriginIsolationRedirect ( new URL ( request . url ) )
387
404
if ( originLocation !== null ) {
@@ -407,8 +424,29 @@ async function fetchHandler ({ path, request }: FetchHandlerArg): Promise<Respon
407
424
* * https://bugs.chromium.org/p/chromium/issues/detail?id=823697
408
425
* * https://bugzilla.mozilla.org/show_bug.cgi?id=1394102
409
426
*/
410
- // 5 minute timeout
411
- const signal = AbortSignal . timeout ( 5 * 60 * 1000 )
427
+ const abortController = new AbortController ( )
428
+ const signal = abortController . signal
429
+ const abortFn = ( event : Pick < AbortSignalEventMap [ 'abort' ] , 'type' > ) : void => {
430
+ clearTimeout ( signalAbortTimeout )
431
+ if ( event ?. type === timeoutAbortEventType ) {
432
+ log . trace ( 'helia-sw: timeout waiting for response from @helia/verified-fetch' )
433
+ abortController . abort ( 'timeout' )
434
+ } else {
435
+ log . trace ( 'helia-sw: request signal aborted' )
436
+ abortController . abort ( 'request signal aborted' )
437
+ }
438
+ }
439
+ /**
440
+ * five minute delay to get the initial response.
441
+ *
442
+ * @todo reduce to 2 minutes?
443
+ */
444
+ const signalAbortTimeout = setTimeout ( ( ) => {
445
+ abortFn ( { type : timeoutAbortEventType } )
446
+ } , 5 * 60 * 1000 )
447
+ // if the fetch event is aborted, we need to abort the signal we give to @helia/verified-fetch
448
+ event . request . signal . addEventListener ( 'abort' , abortFn )
449
+
412
450
try {
413
451
const { id, protocol } = getSubdomainParts ( request . url )
414
452
const verifiedFetchUrl = getVerifiedFetchUrl ( { id, protocol, path } )
@@ -419,14 +457,25 @@ async function fetchHandler ({ path, request }: FetchHandlerArg): Promise<Respon
419
457
log . trace ( 'fetchHandler: request headers: %s: %s' , key , value )
420
458
} )
421
459
422
- return await verifiedFetch ( verifiedFetchUrl , {
460
+ const response = await verifiedFetch ( verifiedFetchUrl , {
423
461
signal,
424
462
headers,
425
463
// TODO redirect: 'manual', // enable when http urls are supported by verified-fetch: https://github.com/ipfs-shipyard/helia-service-worker-gateway/issues/62#issuecomment-1977661456
426
464
onProgress : ( e ) => {
427
465
trace ( `${ e . type } : ` , e . detail )
428
466
}
429
467
} )
468
+ /**
469
+ * Now that we've got a response back from Helia, don't abort the promise since any additional networking calls
470
+ * that may performed by Helia would be dropped.
471
+ *
472
+ * If `event.request.signal` is aborted, that would cancel any underlying network requests.
473
+ *
474
+ * Note: we haven't awaited the arrayBuffer, blob, json, etc. `await verifiedFetch` only awaits the construction of
475
+ * the response object, regardless of it's inner content
476
+ */
477
+ clearTimeout ( signalAbortTimeout )
478
+ return response
430
479
} catch ( err : unknown ) {
431
480
const errorMessages : string [ ] = [ ]
432
481
if ( isAggregateError ( err ) ) {
0 commit comments