1
1
import { config as unoConfig } from "$(REMOTE_WEBAPP_PATH)$(REMOTE_BASE_PATH)/uno-config.js" ;
2
2
3
-
4
3
if ( unoConfig . environmentVariables [ "UNO_BOOTSTRAP_DEBUGGER_ENABLED" ] !== "True" ) {
5
4
console . debug ( "[ServiceWorker] Initializing" ) ;
6
5
let uno_enable_tracing = unoConfig . uno_enable_tracing ;
7
6
7
+ // Get the number of fetch retries from environment variables or default to 1
8
+ const fetchRetries = parseInt ( unoConfig . environmentVariables [ "UNO_BOOTSTRAP_FETCH_RETRIES" ] || "1" ) ;
9
+
8
10
self . addEventListener ( 'install' , function ( e ) {
9
11
console . debug ( '[ServiceWorker] Installing offline worker' ) ;
10
12
e . waitUntil (
@@ -15,65 +17,146 @@ if (unoConfig.environmentVariables["UNO_BOOTSTRAP_DEBUGGER_ENABLED"] !== "True")
15
17
// worker to fail installing.
16
18
for ( var i = 0 ; i < unoConfig . offline_files . length ; i ++ ) {
17
19
try {
20
+ const currentFile = unoConfig . offline_files [ i ] ;
18
21
if ( uno_enable_tracing ) {
19
- console . debug ( `[ServiceWorker] cache ${ key } ` ) ;
22
+ console . debug ( `[ServiceWorker] caching ${ currentFile } ` ) ;
20
23
}
21
24
22
- await cache . add ( unoConfig . offline_files [ i ] ) ;
25
+ await cache . add ( currentFile ) ;
23
26
}
24
27
catch ( e ) {
25
- console . debug ( `[ServiceWorker] Failed to fetch ${ unoConfig . offline_files [ i ] } ` ) ;
28
+ console . debug ( `[ServiceWorker] Failed to fetch ${ unoConfig . offline_files [ i ] } : ${ e . message } ` ) ;
26
29
}
27
30
}
28
31
29
32
// Add the runtime's own files to the cache. We cannot use the
30
33
// existing cached content from the runtime as the keys contain a
31
34
// hash we cannot reliably compute.
32
- var c = await fetch ( "$(REMOTE_WEBAPP_PATH)_framework/blazor.boot.json" ) ;
33
- const monoConfigResources = ( await c . json ( ) ) . resources ;
34
-
35
- var entries = {
36
- ...( monoConfigResources . coreAssembly || { } )
37
- , ...( monoConfigResources . assembly || { } )
38
- , ...( monoConfigResources . lazyAssembly || { } )
39
- , ...( monoConfigResources . jsModuleWorker || { } )
40
- , ...( monoConfigResources . jsModuleGlobalization || { } )
41
- , ...( monoConfigResources . jsModuleNative || { } )
42
- , ...( monoConfigResources . jsModuleRuntime || { } )
43
- , ...( monoConfigResources . wasmNative || { } )
44
- , ...( monoConfigResources . icu || { } )
45
- , ...( monoConfigResources . coreAssembly || { } )
46
- } ;
47
-
48
- for ( var key in entries ) {
49
- var uri = `$(REMOTE_WEBAPP_PATH)_framework/${ key } ` ;
50
-
51
- if ( uno_enable_tracing ) {
52
- console . debug ( `[ServiceWorker] cache ${ uri } ` ) ;
35
+ try {
36
+ var c = await fetch ( "$(REMOTE_WEBAPP_PATH)_framework/blazor.boot.json" ) ;
37
+ // Response validation to catch HTTP errors early
38
+ // This prevents trying to parse invalid JSON from error responses
39
+ if ( ! c . ok ) {
40
+ throw new Error ( `Failed to fetch blazor.boot.json: ${ c . status } ${ c . statusText } ` ) ;
53
41
}
54
42
55
- await cache . add ( uri ) ;
43
+ const bootJson = await c . json ( ) ;
44
+ const monoConfigResources = bootJson . resources || { } ;
45
+
46
+ var entries = {
47
+ ...( monoConfigResources . coreAssembly || { } ) ,
48
+ ...( monoConfigResources . assembly || { } ) ,
49
+ ...( monoConfigResources . lazyAssembly || { } ) ,
50
+ ...( monoConfigResources . jsModuleWorker || { } ) ,
51
+ ...( monoConfigResources . jsModuleGlobalization || { } ) ,
52
+ ...( monoConfigResources . jsModuleNative || { } ) ,
53
+ ...( monoConfigResources . jsModuleRuntime || { } ) ,
54
+ ...( monoConfigResources . wasmNative || { } ) ,
55
+ ...( monoConfigResources . icu || { } )
56
+ } ;
57
+
58
+ for ( var key in entries ) {
59
+ var uri = `$(REMOTE_WEBAPP_PATH)_framework/${ key } ` ;
60
+
61
+ try {
62
+ if ( uno_enable_tracing ) {
63
+ console . debug ( `[ServiceWorker] cache ${ uri } ` ) ;
64
+ }
65
+
66
+ await cache . add ( uri ) ;
67
+ } catch ( e ) {
68
+ console . error ( `[ServiceWorker] Failed to cache ${ uri } :` , e . message ) ;
69
+ }
70
+ }
71
+ } catch ( e ) {
72
+ // Centralized error handling for the entire boot.json processing
73
+ console . error ( '[ServiceWorker] Error processing blazor.boot.json:' , e . message ) ;
56
74
}
57
75
} )
58
76
) ;
59
77
} ) ;
60
78
79
+ // Cache cleanup logic to prevent storage bloat
80
+ // This removes any old caches that might have been created by previous
81
+ // versions of the service worker, helping prevent storage quota issues
61
82
self . addEventListener ( 'activate' , event => {
62
- event . waitUntil ( self . clients . claim ( ) ) ;
83
+ event . waitUntil (
84
+ caches . keys ( ) . then ( function ( cacheNames ) {
85
+ return Promise . all (
86
+ cacheNames . filter ( function ( cacheName ) {
87
+ return cacheName !== '$(CACHE_KEY)' ;
88
+ } ) . map ( function ( cacheName ) {
89
+ console . debug ( '[ServiceWorker] Deleting old cache:' , cacheName ) ;
90
+ return caches . delete ( cacheName ) ;
91
+ } )
92
+ ) ;
93
+ } ) . then ( function ( ) {
94
+ return self . clients . claim ( ) ;
95
+ } )
96
+ ) ;
63
97
} ) ;
64
98
65
99
self . addEventListener ( 'fetch' , event => {
66
- event . respondWith ( async function ( ) {
67
- try {
68
- // Network first mode to get fresh content every time, then fallback to
69
- // cache content if needed.
70
- return await fetch ( event . request ) ;
71
- } catch ( err ) {
72
- return caches . match ( event . request ) . then ( response => {
73
- return response || fetch ( event . request ) ;
74
- } ) ;
75
- }
76
- } ( ) ) ;
100
+ event . respondWith (
101
+ ( async function ( ) {
102
+ // FIXED: Critical fix for "already used" Request objects #956
103
+ // Request objects can only be used once in a fetch operation
104
+ // Cloning the request allows for reuse in fallback scenarios
105
+ const requestClone = event . request . clone ( ) ;
106
+
107
+ try {
108
+ // Network first mode to get fresh content every time, then fallback to
109
+ // cache content if needed.
110
+ return await fetch ( requestClone ) ;
111
+ } catch ( err ) {
112
+ // Logging to track network failures
113
+ console . debug ( `[ServiceWorker] Network fetch failed, falling back to cache for: ${ requestClone . url } ` ) ;
114
+
115
+ const cachedResponse = await caches . match ( event . request ) ;
116
+ if ( cachedResponse ) {
117
+ return cachedResponse ;
118
+ }
119
+
120
+ // Add retry mechanism - attempt to fetch again if retries are configured
121
+ if ( fetchRetries > 0 ) {
122
+ console . debug ( `[ServiceWorker] Resource not in cache, attempting ${ fetchRetries } network retries for: ${ requestClone . url } ` ) ;
123
+
124
+ // Try multiple fetch attempts with exponential backoff
125
+ for ( let retryCount = 0 ; retryCount < fetchRetries ; retryCount ++ ) {
126
+ try {
127
+ // Exponential backoff between retries (500ms, 1s, 2s, etc.)
128
+ const retryDelay = Math . pow ( 2 , retryCount ) * 500 ;
129
+ await new Promise ( resolve => setTimeout ( resolve , retryDelay ) ) ;
130
+
131
+ if ( uno_enable_tracing ) {
132
+ console . debug ( `[ServiceWorker] Retry attempt ${ retryCount + 1 } /${ fetchRetries } for: ${ requestClone . url } ` ) ;
133
+ }
134
+
135
+ // Need a fresh request clone for each retry
136
+ return await fetch ( event . request . clone ( ) ) ;
137
+ } catch ( retryErr ) {
138
+ if ( uno_enable_tracing ) {
139
+ console . debug ( `[ServiceWorker] Retry ${ retryCount + 1 } failed: ${ retryErr . message } ` ) ;
140
+ }
141
+ // Continue to next retry attempt
142
+ }
143
+ }
144
+ }
145
+
146
+ // Graceful error handling with a proper HTTP response
147
+ // Rather than letting the fetch fail with a generic error,
148
+ // we return a controlled 503 Service Unavailable response
149
+ console . error ( `[ServiceWorker] Resource not available in cache or network after ${ fetchRetries } retries: ${ requestClone . url } ` ) ;
150
+ return new Response ( 'Network error occurred, and resource was not found in cache.' , {
151
+ status : 503 ,
152
+ statusText : 'Service Unavailable' ,
153
+ headers : new Headers ( {
154
+ 'Content-Type' : 'text/plain'
155
+ } )
156
+ } ) ;
157
+ }
158
+ } ) ( )
159
+ ) ;
77
160
} ) ;
78
161
}
79
162
else {
0 commit comments