@@ -31,48 +31,32 @@ class LockStore {
3131 return this . dbPromise ;
3232 }
3333
34- async getOwner ( ) {
34+ async _runTransaction ( mode , callback ) {
3535 try {
3636 const db = await this . _openDB ( ) ;
37- const data = await new Promise ( ( resolve , reject ) => {
38- const tx = db . transaction ( STORE_NAME , "readonly" ) ;
39- const req = tx . objectStore ( STORE_NAME ) . get ( "stateData" ) ;
37+ return await new Promise ( ( resolve , reject ) => {
38+ const tx = db . transaction ( STORE_NAME , mode ) ;
39+ const req = callback ( tx . objectStore ( STORE_NAME ) ) ;
4040 req . onsuccess = ( ) => resolve ( req . result ) ;
4141 req . onerror = ( ) => reject ( req . error ) ;
4242 } ) ;
43- return data ?. clientId || null ;
4443 } catch ( e ) {
45- console . warn ( " IndexedDB read failed" , e ) ;
44+ console . warn ( ` IndexedDB ${ mode } failed` , e ) ;
4645 return null ;
4746 }
4847 }
4948
49+ async getOwner ( ) {
50+ const data = await this . _runTransaction ( "readonly" , ( store ) => store . get ( "stateData" ) ) ;
51+ return data ?. clientId || null ;
52+ }
53+
5054 async setOwner ( clientId ) {
51- try {
52- const db = await this . _openDB ( ) ;
53- await new Promise ( ( resolve , reject ) => {
54- const tx = db . transaction ( STORE_NAME , "readwrite" ) ;
55- const req = tx . objectStore ( STORE_NAME ) . put ( { clientId } , "stateData" ) ;
56- req . onsuccess = ( ) => resolve ( ) ;
57- req . onerror = ( ) => reject ( req . error ) ;
58- } ) ;
59- } catch ( e ) {
60- console . warn ( "IndexedDB write failed" , e ) ;
61- }
55+ await this . _runTransaction ( "readwrite" , ( store ) => store . put ( { clientId } , "stateData" ) ) ;
6256 }
6357
6458 async clear ( ) {
65- try {
66- const db = await this . _openDB ( ) ;
67- await new Promise ( ( resolve , reject ) => {
68- const tx = db . transaction ( STORE_NAME , "readwrite" ) ;
69- const req = tx . objectStore ( STORE_NAME ) . delete ( "stateData" ) ;
70- req . onsuccess = ( ) => resolve ( ) ;
71- req . onerror = ( ) => reject ( req . error ) ;
72- } ) ;
73- } catch ( e ) {
74- console . warn ( "IndexedDB clear failed" , e ) ;
75- }
59+ await this . _runTransaction ( "readwrite" , ( store ) => store . delete ( "stateData" ) ) ;
7660 }
7761
7862 async hasLock ( clientId ) {
@@ -86,7 +70,10 @@ class LockStore {
8670const STORE = new LockStore ( ) ;
8771
8872function replyToClient ( event , type , msg = { } ) {
89- event . ports ?. [ 0 ] ?. postMessage ( { type, ...msg } ) ;
73+ if ( event . ports ?. [ 0 ] ) {
74+ event . ports [ 0 ] . start ?. ( ) ;
75+ event . ports [ 0 ] . postMessage ( { type, ...msg } ) ;
76+ }
9077}
9178
9279function replyError ( event , message ) {
@@ -114,7 +101,7 @@ class RequestLimiter {
114101 } catch ( e ) {
115102 // Individual task errors are handled by their respective promises
116103 }
117- await new Promise ( ( r ) => setTimeout ( r , 1 ) ) ;
104+ await delayAsync ( 1 ) ; // Yield to event loop
118105 }
119106 this . active -- ;
120107 }
@@ -127,18 +114,16 @@ class RequestLimiter {
127114
128115 if ( this . active < this . limit ) {
129116 this . active ++ ;
130- this . _processQueue ( ) ;
117+ this . _processQueue ( ) ; // Intentionally not awaited
131118 }
132119 } ) ;
133120 }
134121
135122 clear ( ) {
136- for ( const task of this . queue ) {
137- if ( task . resolve )
138- task . resolve ( 0 ) ;
139- }
123+ for ( const task of this . queue )
124+ task . resolve ?. ( 0 ) ;
140125
141- this . queue = [ ] ;
126+ this . queue . length = 0 ;
142127 }
143128}
144129
@@ -159,56 +144,64 @@ function handleResetPreloadingMessage(event) {
159144}
160145
161146async function handlePreloadSuitesMessage ( event , clientId , { suites = [ ] , delay = 0 , clearCache = true } ) {
162- await STORE . getOwner ( ) ;
163- await updateActiveClient ( clientId ) ;
164-
165- handleResetPreloadingMessage ( ) ; // Call it without event to avoid replying to the PRELOAD_SUITES channel!
166-
167- currentPreloadEvent = event ;
168- const preloadId = currentPreloadId ;
169-
170- failedRequests . clear ( ) ;
171-
172- if ( clearCache )
173- await caches . delete ( CACHE_NAME ) ;
174-
175- const cache = await caches . open ( CACHE_NAME ) ;
176- let loaded = 0 ;
177- let transferredSize = 0 ;
178- const urlsToCache = [ ] ;
147+ try {
148+ await STORE . getOwner ( ) ;
149+ await updateActiveClient ( clientId ) ;
150+
151+ handleResetPreloadingMessage ( ) ; // Call it without event to avoid replying to the PRELOAD_SUITES channel!
152+
153+ currentPreloadEvent = event ;
154+ const preloadId = currentPreloadId ;
155+
156+ failedRequests . clear ( ) ;
157+ if ( clearCache )
158+ await caches . delete ( CACHE_NAME ) ;
159+
160+ const cache = await caches . open ( CACHE_NAME ) ;
161+ const urlsToCache = await getUrlsToCache ( suites ) ;
162+ const total = urlsToCache . length ;
163+
164+ let loaded = 0 ;
165+ let transferredSize = 0 ;
166+
167+ const promises = urlsToCache . map ( async ( item , index ) => {
168+ if ( preloadId !== currentPreloadId )
169+ return ;
170+ const size = await fetchAndCache ( cache , item . url , delay * index ) ;
171+ if ( preloadId !== currentPreloadId )
172+ return ;
173+ transferredSize += size ;
174+ loaded ++ ;
175+ replyToClient ( event , SW_MESSAGES . PRELOAD_PROGRESS , { loaded, total, url : item . url , suiteName : item . suiteName } ) ;
176+ } ) ;
179177
180- for ( const suite of suites ) {
181- if ( ! suite . resources )
182- continue ;
183- urlsToCache . push ( ...await parseSuiteResources ( suite ) ) ;
184- }
178+ await Promise . all ( promises ) ;
185179
186- const total = urlsToCache . length ;
187- const promises = urlsToCache . map ( async ( item , index ) => {
188- if ( preloadId !== currentPreloadId )
189- return ;
190- const size = await fetchAndCache ( cache , item . url , delay * index ) ;
191180 if ( preloadId !== currentPreloadId )
192181 return ;
193- transferredSize += size ;
194- loaded ++ ;
195- replyToClient ( event , SW_MESSAGES . PRELOAD_PROGRESS , { loaded, total, url : item . url , suiteName : item . suiteName } ) ;
196- } ) ;
197-
198- await Promise . all ( promises ) ;
199182
200- if ( preloadId !== currentPreloadId )
201- return ;
202-
203- if ( ! await STORE . hasLock ( clientId ) ) {
204- replyError ( event , "Speedometer aborted: Another tab took over." ) ;
183+ if ( ! await STORE . hasLock ( clientId ) ) {
184+ replyError ( event , "Speedometer aborted: Another tab took over." ) ;
185+ return ;
186+ }
187+ replyToClient ( event , SW_MESSAGES . PRELOAD_DONE , { transferredSize, count : urlsToCache . length } ) ;
188+ } catch ( error ) {
189+ console . error ( "Error during preload:" , error ) ;
190+ replyError ( event , error . message || "Failed to preload resources." ) ;
191+ } finally {
205192 if ( currentPreloadEvent === event )
206193 currentPreloadEvent = null ;
207- return ;
208194 }
209- replyToClient ( event , SW_MESSAGES . PRELOAD_DONE , { transferredSize, count : urlsToCache . length } ) ;
210- if ( currentPreloadEvent === event )
211- currentPreloadEvent = null ;
195+ }
196+
197+ async function getUrlsToCache ( suites ) {
198+ const urlsToCache = [ ] ;
199+ for ( const suite of suites ) {
200+ if ( suite . resources )
201+ urlsToCache . push ( ...await parseSuiteResources ( suite ) ) ;
202+ }
203+
204+ return urlsToCache ;
212205}
213206
214207async function parseSuiteResources ( suite ) {
@@ -237,7 +230,7 @@ async function fetchAndCache(cache, url, delayMs) {
237230 await delayAsync ( delayMs ) ;
238231 return requestLimiter . schedule ( async ( ) => {
239232 const request = new Request ( url , { cache : "no-cache" } ) ;
240- const existing = await cache . match ( request , { ignoreSearch : true , ignoreVary : true } ) ;
233+ const existing = await cache . match ( request , { ignoreSearch : true } ) ;
241234 if ( existing )
242235 return getResponseSize ( existing ) ;
243236
@@ -278,12 +271,16 @@ async function updateActiveClient(newClientId) {
278271}
279272
280273async function handleClearCacheMessage ( event , clientId ) {
281- if ( ! await STORE . hasLock ( clientId ) ) {
282- replyError ( event , "Cannot clear SW: You do not own the lock." ) ;
283- return ;
274+ try {
275+ if ( ! await STORE . hasLock ( clientId ) ) {
276+ replyError ( event , "Cannot clear SW: You do not own the lock." ) ;
277+ return ;
278+ }
279+ await STORE . clear ( ) ;
280+ replyToClient ( event , "SUCCESS" ) ;
281+ } catch ( e ) {
282+ replyError ( event , e . message || "Failed to clear cache" ) ;
284283 }
285- await STORE . clear ( ) ;
286- replyToClient ( event , "SUCCESS" ) ;
287284}
288285
289286async function handleGetFailedRequestsMessage ( event , clientId ) {
@@ -301,9 +298,10 @@ self.addEventListener("message", (event) => {
301298 const { data } = event ;
302299 if ( ! data )
303300 return ;
301+ const clientId = data . clientId || event . source ?. id ;
304302 const handler = MESSAGE_HANDLERS [ data . type ] ;
305303 if ( handler )
306- event . waitUntil ( handler ( event , event . source ?. id , data ) ) ;
304+ event . waitUntil ( handler ( event , clientId , data ) ) ;
307305 else
308306 console . error ( "Unknown service worker message type:" , data . type ) ;
309307} ) ;
@@ -321,7 +319,7 @@ self.addEventListener("fetch", (event) => {
321319 requestToMatch = urlObj . href ;
322320 }
323321
324- const cachedResponse = await caches . match ( requestToMatch , { cacheName : CACHE_NAME , ignoreSearch : true , ignoreVary : true } ) ;
322+ const cachedResponse = await caches . match ( requestToMatch , { cacheName : CACHE_NAME , ignoreSearch : true } ) ;
325323 if ( cachedResponse )
326324 return cachedResponse ;
327325
0 commit comments