Skip to content

Commit da53877

Browse files
authored
Send a "reconnect" hydrate whenever the socket reconnects (#5969)
* Send a "reconnect" hydrate whenever the socket reconnects * "reconnect" hydrate doesn't reset client storage or trigger on_load, it just returns the latest complete state dict * this event is driven by the frontend after the socket connects * update `link_token_to_sid` to NOT emit a delta when the sid changes for a token; the client will be informed of this during the initial hydrate or "reconnect" hydrate. Fix #5963 * fix lost+found test case * make sure there is a hydrate event before we assume if the app is stateless, then presumably we don't end up connecting a websocket at all, so we wouldn't hit this code. also if `initialEvents` was empty for whatever reason, then the app probably wouldn't work... but lets make it look a bit safer. * oops mocked app isn't based on real app
1 parent d3c4273 commit da53877

File tree

4 files changed

+27
-7
lines changed

4 files changed

+27
-7
lines changed

reflex/.templates/web/utils/state.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ export const connect = async (
568568
!socket.current.wait_connect
569569
) {
570570
socket.current.wait_connect = true;
571+
socket.current.rehydrate = true;
571572
socket.current.io.opts.query = { token: getToken() }; // Update token for reconnect.
572573
socket.current.connect();
573574
}
@@ -615,6 +616,22 @@ export const connect = async (
615616
window.addEventListener("pagehide", pagehideHandler);
616617
window.addEventListener("beforeunload", disconnectTrigger);
617618
window.addEventListener("unload", disconnectTrigger);
619+
if (socket.current.rehydrate) {
620+
socket.current.rehydrate = false;
621+
const events = initialEvents();
622+
if (events.length > 0) {
623+
// On reconnect, we only hydrate, do not re-run on_load events.
624+
const hydrate_event = initialEvents()[0];
625+
hydrate_event.payload.is_reconnect = true;
626+
queueEvents(
627+
[hydrate_event],
628+
socket,
629+
true,
630+
navigate,
631+
() => params.current,
632+
);
633+
}
634+
}
618635
// Drain any initial events from the queue.
619636
while (event_queue.length > 0 && !event_processing) {
620637
await processEvent(socket.current, navigate, () => params.current);

reflex/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2263,7 +2263,7 @@ async def link_token_to_sid(self, sid: str, token: str):
22632263
await self.emit("new_token", new_token, to=sid)
22642264

22652265
# Update client state to apply new sid/token for running background tasks.
2266-
async with self.app.modify_state(
2266+
async with self.app.state_manager.modify_state(
22672267
_substate_key(new_token or token, self.app.state_manager.state)
22682268
) as state:
22692269
state.router_data[constants.RouteVar.SESSION_ID] = sid

reflex/middleware/hydrate_middleware.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,14 @@ async def preprocess(
3535
if event.name != get_hydrate_event(state):
3636
return None
3737

38-
# Clear client storage, to respect clearing cookies
39-
state._reset_client_storage()
40-
41-
# Mark state as not hydrated (until on_loads are complete)
42-
setattr(state, constants.CompileVars.IS_HYDRATED, False)
38+
# In reconnect mode, don't reset client storage or call on_load.
39+
is_reconnect = event.payload.get("is_reconnect", False)
40+
if not is_reconnect:
41+
# Clear client storage, to respect clearing cookies
42+
state._reset_client_storage()
43+
44+
# Mark state as not hydrated (until on_loads are complete)
45+
setattr(state, constants.CompileVars.IS_HYDRATED, False)
4346

4447
# Get the initial state.
4548
delta = await _resolve_delta(state.dict())

tests/units/utils/test_token_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,7 @@ def new_event_namespace() -> EventNamespace:
519519
state.router_data = {}
520520

521521
mock_app = Mock()
522-
mock_app.modify_state = Mock(
522+
mock_app.state_manager.modify_state = Mock(
523523
return_value=AsyncMock(__aenter__=AsyncMock(return_value=state))
524524
)
525525

0 commit comments

Comments
 (0)