I recently integrated an AI chat feature into my blog, with two interaction modes: a small widget in the bottom-right corner, and a full-screen page you get by clicking “Open in new window.” But while testing I hit a nasty problem: the chat history in the widget wouldn’t sync to the full-screen window.
The Symptoms
- A user chats with the AI in the bottom-right widget and sends several messages.
- They click “Open in new window,” expecting to continue the conversation in the big window.
- When the big window opens, it only shows the default welcome message — the entire previous conversation is gone.
This is obviously a UX disaster — the user thinks they can “seamlessly switch” to the big window to keep chatting, but instead it’s like starting a brand-new conversation.

Analysis
Initial Investigation
First I checked the storage mechanism of the two pages:
Widget:
|
Full-screen window:
|
Found the problem: the storage locations don’t match!
Deeper Analysis
Even after unifying the storage location, the problem persisted. Further analysis revealed several overlapping issues:
1. Missing Cross-Window Communication
When the widget opened the big window, it didn’t pass the session_id:
|
It opened /chat/ directly with no parameters, so the big window had no way to know which session it should use.
2. Session ID Being Overwritten
After the WebSocket connection succeeded, the server returned a session_id, and the code used that value unconditionally:
|
The problem is that the server might assign a new session_id for a “new connection,” which would lose the original session.
3. Different History Loading Approaches
Widget actively pulled history via an HTTP API:
|
Big window, however, expected WebSocket to push history messages:
|
In reality, the WebSocket welcome message didn’t include history messages, so the big window could never load the history.
4. Inconsistent Field Naming
In the messages returned by the server, the user-identifier field was sometimes type: 1 and sometimes is_user: true, and the code didn’t handle this compatibility correctly.
The Solution
Step One: Pass the Session ID via URL
When the widget opens the big window, pass the session_id through a URL parameter:
|
Step Two: Read the URL Parameter in the Big Window
When the big window loads, read and save the session_id from the URL:
|
Step Three: Prevent the Session ID from Being Overwritten
On the WebSocket connection, if a session_id already exists, don’t use the new ID returned by the server:
|
Step Four: Unify the History Loading Approach
The big window also uses the HTTP API to load history, staying consistent with the widget:
|
Step Five: Fix the Field Compatibility Problem
Handle the different field formats the server returns:
|
Lessons Learned
1. Cross-Window State Sharing
When you need to share state across multiple windows/tabs, you can’t rely only on client-side storage (localStorage/sessionStorage). URL parameters are the most reliable way to communicate across windows.
2. Server-Side Session Management
If the server assigns a new session_id for each WebSocket connection, the client must explicitly tell the server “I want to join an existing session,” rather than passively accepting the new session.
3. Data Loading Strategy
WebSocket is good for real-time message pushes, but loading historical data is better done via an HTTP API:
- HTTP supports caching, pagination, and resumable transfers
- It avoids large WebSocket messages causing a blockage
- It’s easy to debug (you can hit the API directly from the browser address bar)
4. Defensive Programming
|
5. Field Compatibility
When the front-end/back-end protocol is upgraded, keep it backward compatible:
|
Debugging Tips
Throughout the whole investigation, the following debugging methods were very useful:
- Console logs: Print the
session_idand message content at key points - Network panel: Confirm the data format returned by the HTTP API
- WebSocket panel (Chrome DevTools): Inspect the WebSocket message exchanges
- Application panel: Check the actual stored contents of localStorage/sessionStorage
The Final Result
After the fix, a user can start a conversation in the widget, click “Open in new window,” and seamlessly switch to the big window to continue — with all history preserved. Closing the big window leaves the widget still connected, able to keep chatting.
This experience is really valuable for scenarios that need long, multi-turn conversations (like code debugging or complex consulting).
Related code changes:
评论
0 条评论