diff --git a/reflex/app.py b/reflex/app.py index dc91180c075..8eaa40770d1 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -2082,17 +2082,42 @@ async def on_connect(self, sid: str, environ: dict): if isinstance(self._token_manager, RedisTokenManager): # Make sure this instance is watching for updates from other instances. self._token_manager.ensure_lost_and_found_task(self.emit_update) + query_params = urllib.parse.parse_qs(environ.get("QUERY_STRING", "")) token_list = query_params.get("token", []) - if token_list: - await self.link_token_to_sid(sid, token_list[0]) - else: + token = token_list[0] if token_list else None + + if not token: console.warn(f"No token provided in connection for session {sid}") + return + + try: + # Detect reconnect (token seen before) + is_reconnect = token in self._token_manager.token_to_sid + + # Always link first to ensure mappings are valid + await self.link_token_to_sid(sid, token) + + # If this is a reconnect and backend has lost state, trigger reload + if is_reconnect: + substate_key = _substate_key(token, self.app.state_manager.state) + state = await self.app.state_manager.get_state(substate_key) + + if not getattr(state, "router_data", None): + console.debug( + f"[Reflex] Reconnect detected for expired state (token={token}), emitting reload" + ) + await self.emit("reload", {"reason": "state_expired"}, to=sid) + return + + except Exception as e: + console.warn(f"[Reflex] on_connect error for token {token}: {e}") subprotocol = environ.get("HTTP_SEC_WEBSOCKET_PROTOCOL") if subprotocol and subprotocol != constants.Reflex.VERSION: console.warn( - f"Frontend version {subprotocol} for session {sid} does not match the backend version {constants.Reflex.VERSION}." + f"Frontend version {subprotocol} for session {sid} " + f"does not match backend version {constants.Reflex.VERSION}." ) def on_disconnect(self, sid: str) -> asyncio.Task | None: