@@ -3,7 +3,11 @@ import { z } from "zod";
33import { StorageManager } from "../shared/storage-manager" ;
44import { safeDeserialize } from "../utils/serialization" ;
55import type { QueryState } from "../types/storage" ;
6- import type { UpdateMessage , QueryActionResult } from "../types/messages" ;
6+ import type {
7+ UpdateMessage ,
8+ QueryActionResult ,
9+ RequestImmediateUpdateMessage ,
10+ } from "../types/messages" ;
711
812// Zod schema for serialized payload validation
913const SerializedPayloadSchema = z . object ( {
@@ -12,18 +16,18 @@ const SerializedPayloadSchema = z.object({
1216 isSerializedPayload : z . literal ( true ) ,
1317} ) ;
1418
19+ // Track preservation flags per tab
20+ const preserveArtificialStatesForTab = new Map < number , boolean > ( ) ;
21+
1522// Define possible background messages
1623type BackgroundMessage =
1724 | UpdateMessage
1825 | QueryActionResult
26+ | ( RequestImmediateUpdateMessage & { inspectedTabId ?: number } )
1927 | {
2028 type : "QUERY_ACTION" ;
2129 inspectedTabId ?: number ;
2230 [ key : string ] : unknown ;
23- }
24- | {
25- type : "REQUEST_IMMEDIATE_UPDATE" ;
26- [ key : string ] : unknown ;
2731 } ;
2832
2933// Handle messages from both content scripts and DevTools
@@ -36,7 +40,18 @@ chrome.runtime.onMessage.addListener(
3640 ! sender . tab ?. id
3741 ) {
3842 // Forward DevTools actions to content script of the specified tab
39- if ( message . type === "QUERY_ACTION" ) {
43+ if (
44+ message . type === "QUERY_ACTION" ||
45+ message . type === "REQUEST_IMMEDIATE_UPDATE"
46+ ) {
47+ // Track preservation flag for REQUEST_IMMEDIATE_UPDATE messages
48+ if (
49+ message . type === "REQUEST_IMMEDIATE_UPDATE" &&
50+ message . preserveArtificialStates
51+ ) {
52+ preserveArtificialStatesForTab . set ( message . inspectedTabId , true ) ;
53+ }
54+
4055 chrome . tabs
4156 . sendMessage ( message . inspectedTabId , message )
4257 . then ( ( ) => {
@@ -124,9 +139,34 @@ chrome.runtime.onMessage.addListener(
124139 processedPayload . tanStackQueryDetected ;
125140
126141 // Clear artificial states when QueryClient is freshly detected
127- // This handles page refreshes and navigations
142+ // Only clear if this tab doesn't have preservation flag set
128143 if ( processedPayload . tanStackQueryDetected === true ) {
129- updateData . artificialStates = { } ;
144+ const shouldPreserve = preserveArtificialStatesForTab . get ( tabId ) ;
145+ if ( ! shouldPreserve ) {
146+ // Clear artificial states for this tab only on fresh page loads/navigations
147+ StorageManager . getState ( )
148+ . then ( ( currentState ) => {
149+ const artificialStates = {
150+ ...( currentState . artificialStates || { } ) ,
151+ } ;
152+ delete artificialStates [ tabId ] ;
153+ return StorageManager . updatePartialState ( {
154+ ...updateData ,
155+ artificialStates,
156+ } ) ;
157+ } )
158+ . then ( ( ) => {
159+ sendResponse ( { received : true } ) ;
160+ } )
161+ . catch ( ( error ) => {
162+ console . error ( "Failed to update storage:" , error ) ;
163+ sendResponse ( { received : false , error : error . message } ) ;
164+ } ) ;
165+ return true ;
166+ } else {
167+ // Clear the preservation flag after using it
168+ preserveArtificialStatesForTab . delete ( tabId ) ;
169+ }
130170 }
131171 }
132172
@@ -157,21 +197,26 @@ chrome.runtime.onMessage.addListener(
157197 } ;
158198 const queryHash = message . queryHash as string ;
159199
200+ // Ensure this tab has an entry in artificial states
201+ if ( ! artificialStates [ tabId ] ) {
202+ artificialStates [ tabId ] = { } ;
203+ }
204+
160205 if ( message . action === "TRIGGER_LOADING" ) {
161- if ( artificialStates [ queryHash ] === "loading" ) {
206+ if ( artificialStates [ tabId ] [ queryHash ] === "loading" ) {
162207 // Cancel loading state
163- delete artificialStates [ queryHash ] ;
208+ delete artificialStates [ tabId ] [ queryHash ] ;
164209 } else {
165210 // Start loading state
166- artificialStates [ queryHash ] = "loading" ;
211+ artificialStates [ tabId ] [ queryHash ] = "loading" ;
167212 }
168213 } else if ( message . action === "TRIGGER_ERROR" ) {
169- if ( artificialStates [ queryHash ] === "error" ) {
214+ if ( artificialStates [ tabId ] [ queryHash ] === "error" ) {
170215 // Cancel error state
171- delete artificialStates [ queryHash ] ;
216+ delete artificialStates [ tabId ] [ queryHash ] ;
172217 } else {
173218 // Start error state
174- artificialStates [ queryHash ] = "error" ;
219+ artificialStates [ tabId ] [ queryHash ] = "error" ;
175220 }
176221 }
177222
@@ -258,7 +303,24 @@ StorageManager.onStateChange(async () => {
258303
259304// Listen to tab activation (when user switches tabs)
260305chrome . tabs . onActivated . addListener ( async ( activeInfo ) => {
306+ // First update icon based on current storage (may be stale)
261307 await updateIconForActiveTab ( activeInfo . tabId ) ;
308+
309+ // Set preservation flag and request immediate update from the newly active tab
310+ preserveArtificialStatesForTab . set ( activeInfo . tabId , true ) ;
311+ try {
312+ await chrome . tabs . sendMessage ( activeInfo . tabId , {
313+ type : "REQUEST_IMMEDIATE_UPDATE" ,
314+ preserveArtificialStates : true ,
315+ } ) ;
316+ } catch ( error ) {
317+ // Tab might not have the content script loaded, that's fine
318+ console . warn (
319+ "Could not request immediate update for tab" ,
320+ activeInfo . tabId ,
321+ error ,
322+ ) ;
323+ }
262324} ) ;
263325
264326// Listen to window focus changes (when user switches between browser windows)
@@ -267,7 +329,24 @@ chrome.windows.onFocusChanged.addListener(async (windowId) => {
267329 try {
268330 const tabs = await chrome . tabs . query ( { active : true , windowId } ) ;
269331 if ( tabs [ 0 ] ?. id ) {
332+ // First update icon based on current storage (may be stale)
270333 await updateIconForActiveTab ( tabs [ 0 ] . id ) ;
334+
335+ // Set preservation flag and request immediate update from the newly focused tab
336+ preserveArtificialStatesForTab . set ( tabs [ 0 ] . id , true ) ;
337+ try {
338+ await chrome . tabs . sendMessage ( tabs [ 0 ] . id , {
339+ type : "REQUEST_IMMEDIATE_UPDATE" ,
340+ preserveArtificialStates : true ,
341+ } ) ;
342+ } catch ( error ) {
343+ // Tab might not have the content script loaded, that's fine
344+ console . warn (
345+ "Could not request immediate update for focused tab" ,
346+ tabs [ 0 ] . id ,
347+ error ,
348+ ) ;
349+ }
271350 }
272351 } catch ( error ) {
273352 console . warn ( "Failed to handle window focus change:" , error ) ;
@@ -286,6 +365,14 @@ updateIconForActiveTab().catch((error) => {
286365chrome . tabs . onRemoved . addListener ( async ( tabId ) => {
287366 try {
288367 const currentState = await StorageManager . getState ( ) ;
368+
369+ // Clean up artificial states for closed tab
370+ if ( currentState . artificialStates && currentState . artificialStates [ tabId ] ) {
371+ const artificialStates = { ...currentState . artificialStates } ;
372+ delete artificialStates [ tabId ] ;
373+ await StorageManager . updatePartialState ( { artificialStates } ) ;
374+ }
375+
289376 if ( currentState . tabId === tabId ) {
290377 // Clear state if it was from the closed tab
291378 await StorageManager . setState ( {
@@ -295,6 +382,9 @@ chrome.tabs.onRemoved.addListener(async (tabId) => {
295382 lastUpdated : Date . now ( ) ,
296383 } ) ;
297384 }
385+
386+ // Clean up preservation flag for closed tab
387+ preserveArtificialStatesForTab . delete ( tabId ) ;
298388 } catch ( error ) {
299389 console . error ( "Failed to clean up state for closed tab:" , error ) ;
300390 }
0 commit comments