From 7624e3bb2f854bf8d6bc3d257012f5ff9c15dd12 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 26 Nov 2024 21:48:18 -0800 Subject: [PATCH] [HOS-333] Send a "reload" message to the frontend after state expiry 1. a state instance expires on the backing store 2. frontend attempts to process an event against the expired token and gets a fresh instance of the state without router_data set 3. backend sends a "reload" message on the websocket containing the event and immediately stops processing 4. in response to the "reload" message, frontend sends [hydrate, update client storage, on_load, ] This allows the frontend and backend to re-syncronize on the state of the app before continuing to process regular events. If the event in (2) is a special hydrate event, then it is processed normally by the middleware and the "reload" logic is skipped since this indicates an initial load or a browser refresh. --- reflex/.templates/web/utils/state.js | 4 ++++ reflex/app.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index e14c669f5..f6541c7ae 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -454,6 +454,10 @@ export const connect = async ( queueEvents(update.events, socket); } }); + socket.current.on("reload", async (event) => { + event_processing = false; + queueEvents([...initialEvents(), JSON5.parse(event)], socket); + }) document.addEventListener("visibilitychange", checkVisibility); }; diff --git a/reflex/app.py b/reflex/app.py index fc8efb420..cdf21aa35 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -73,6 +73,7 @@ from reflex.event import ( EventSpec, EventType, IndividualEventType, + get_hydrate_event, window_alert, ) from reflex.model import Model, get_db_status @@ -1259,6 +1260,21 @@ async def process( ) # Get the state for the session exclusively. async with app.state_manager.modify_state(event.substate_token) as state: + # When this is a brand new instance of the state, signal the + # frontend to reload before processing it. + if ( + not state.router_data + and event.name != get_hydrate_event(state) + and app.event_namespace is not None + ): + await asyncio.create_task( + app.event_namespace.emit( + "reload", + data=format.json_dumps(event), + to=sid, + ) + ) + return # re-assign only when the value is different if state.router_data != router_data: # assignment will recurse into substates and force recalculation of