深夜提醒

现在是深夜,建议您注意休息,不要熬夜哦~

🏮 🏮 🏮

新年快乐

祝君万事如意心想事成!

share-image
中
ESC

Debugging and Fixing the AI Chat Session Sharing Problem

English 阅读中文版

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

  1. A user chats with the AI in the bottom-right widget and sends several messages.
  2. They click “Open in new window,” expecting to continue the conversation in the big window.
  3. 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.

Debugging and fixing the AI chat session sharing problem

Analysis

Initial Investigation

First I checked the storage mechanism of the two pages:

Widget:

// Uses localStorage to store session_id
localStorage.setItem('chat_session_id', sessionID);

Full-screen window:

// Uses sessionStorage to store session_id
sessionStorage.setItem('chat_session_id', sessionID);

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:

// The original code
chatNewWindow.addEventListener('click', function() {
var chatWindow = window.open('/chat/', 'chatWindow', ...);
});

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 problematic code
ws.onmessage = function(event) {
var data = JSON.parse(event.data);
if (data.type === 'connected') {
sessionID = data.session_id; // Overwrites directly!
// ...
}
};

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:

fetch('https://api.example.com/chat/history/' + sessionID)
.then(res => res.json())
.then(data => renderMessages(data.messages));

Big window, however, expected WebSocket to push history messages:

ws.onmessage = function(event) {
if (data.messages) {
renderMessages(data.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:

chatNewWindow.addEventListener('click', function() {
var sessionId = localStorage.getItem('chat_session_id') || sessionID;
var chatUrl = '/chat/';
if (sessionId) {
chatUrl += '?session_id=' + encodeURIComponent(sessionId);
}
var chatWindow = window.open(chatUrl, 'chatWindow', ...);
});

Step Two: Read the URL Parameter in the Big Window

When the big window loads, read and save the session_id from the URL:

function getUrlParam(name) {
var urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}

var urlSessionId = getUrlParam('session_id');
if (urlSessionId) {
sessionStorage.setItem('chat_session_id', urlSessionId);
localStorage.setItem('chat_session_id', urlSessionId);
sessionID = urlSessionId;
}

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:

ws.onmessage = function(event) {
var data = JSON.parse(event.data);
if (data.type === 'connected') {
// Only use the server value when there's no sessionID yet
if (!sessionID) {
sessionID = data.session_id;
localStorage.setItem('chat_session_id', sessionID);
} else {
console.log('[Chat] Keeping existing session_id:', sessionID);
}
// ...
}
};

Step Four: Unify the History Loading Approach

The big window also uses the HTTP API to load history, staying consistent with the widget:

function loadChatHistory(sid) {
fetch('https://api.example.com/chat/history/' + encodeURIComponent(sid))
.then(response => response.json())
.then(data => {
// Keep the welcome message
var welcomeMsg = chatBody.querySelector('.chat-message.system');
chatBody.innerHTML = '';
if (welcomeMsg) {
chatBody.appendChild(welcomeMsg);
}

if (data.messages && data.messages.length > 0) {
data.messages.forEach(function(m) {
var isUser = (m.type === 1); // Unified field check
appendMessage(m.content, isUser, m.created_at, m.image_url);
});
}
});
}

// Called after the WebSocket connection succeeds
ws.onmessage = function(event) {
var data = JSON.parse(event.data);
if (data.type === 'connected') {
// ...
if (sessionID) {
loadChatHistory(sessionID);
}
}
};

Step Five: Fix the Field Compatibility Problem

Handle the different field formats the server returns:

// Compatible with both formats: type === 1 or is_user
var isUser = (msg.type === 1) || msg.is_user;

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

// Don't assign directly
sessionID = data.session_id;

// Add a condition check
if (!sessionID) {
sessionID = data.session_id;
}

5. Field Compatibility

When the front-end/back-end protocol is upgraded, keep it backward compatible:

var isUser = (msg.type === 1) || msg.is_user || msg.from === 'user';

Debugging Tips

Throughout the whole investigation, the following debugging methods were very useful:

  1. Console logs: Print the session_id and message content at key points
  2. Network panel: Confirm the data format returned by the HTTP API
  3. WebSocket panel (Chrome DevTools): Inspect the WebSocket message exchanges
  4. 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:

文章作者:阿文
文章链接: https://www.awen.me/post/c3fdbc43.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 阿文的博客!
本文于 2026-03-04 06:41 发布,已超过半年(227天),请注意甄别内容是否已过期。

评论

0 条评论
😀😃😄 😁😅😂 🤣😊😇 🙂🙃😉 😌😍🥰 😘😗😙 😚😋😛 😝😜🤪 🤨🧐🤓 😎🥸🤩 🥳😏😒 😞😔😟 😕🙁☹️ 😣😖😫 😩🥺😢 😭😤😠 😡🤬🤯 😳🥵🥶 😱😨😰 😥😓🤗 🤔🤭🤫 🤥😶😐 😑😬🙄 😯😦😧 😮😲🥱 😴🤤😪 😵🤐🥴 🤢🤮🤧 😷🤒🤕 🤑🤠😈 👿👹👺 🤡💩👻 💀☠️👽 👾🤖🎃 😺😸😹 😻😼😽 🙀😿😾 👍👎👏 🙌👐🤲 🤝🤜🤛 ✌️🤞🤟 🤘👌🤏 👈👉👆 👇☝️✋ 🤚🖐️🖖 👋🤙💪 🦾🖕✍️ 🙏💅🤳 💯💢💥 💫💦💨 🕳️💣💬 👁️‍🗨️🗨️🗯️ 💭💤❤️ 🧡💛💚 💙💜🖤 🤍🤎💔 ❣️💕💞 💓💗💖 💘💝💟 ☮️✝️☪️ 🕉️☸️✡️ 🔯🕎☯️ ☦️🛐⛎ ♈♉♊ ♋♌♍ ♎♏♐ ♑♒♓ 🆔⚛️🉑 ☢️☣️📴 📳🈶🈚 🈸🈺🈷️ ✴️🆚💮 🉐㊙️㊗️ 🈴🈵🈹 🈲🅰️🅱️ 🆎🆑🅾️ 🆘❌⭕ 🛑⛔📛 🚫💯💢 ♨️🚷🚯 🚳🚱🔞 📵🚭❗ ❕❓❔ ‼️⁉️🔅 🔆〽️⚠️ 🚸🔱⚜️ 🔰♻️✅ 🈯💹❇️ ✳️❎🌐 💠Ⓜ️🌀 💤🏧🚾 ♿🅿️🈳 🈂🛂🛃 🛄🛅🛗 🚀🛸🚁 🚉🚆🚅 ✈️🛫🛬 🛩️💺🛰️
加载中...

留言反馈

😀😃😄 😁😅😂 🤣😊😇 🙂🙃😉 😌😍🥰 😘😗😙 😚😋😛 😝😜🤪 🤨🧐🤓 😎🥸🤩 🥳😏😒 😞😔😟 😕🙁☹️ 😣😖😫 😩🥺😢 😭😤😠 😡🤬🤯 😳🥵🥶 😱😨😰 😥😓🤗 🤔🤭🤫 🤥😶😐 😑😬🙄 😯😦😧 😮😲🥱 😴🤤😪 😵🤐🥴 🤢🤮🤧 😷🤒🤕 🤑🤠😈 👿👹👺 🤡💩👻 💀☠️👽 👾🤖🎃 😺😸😹 😻😼😽 🙀😿😾 👍👎👏 🙌👐🤲 🤝🤜🤛 ✌️🤞🤟 🤘👌🤏 👈👉👆 👇☝️✋ 🤚🖐️🖖 👋🤙💪 🦾🖕✍️ 🙏💅🤳 💯💢💥 💫💦💨 🕳️💣💬 👁️‍🗨️🗨️🗯️ 💭💤❤️ 🧡💛💚 💙💜🖤 🤍🤎💔 ❣️💕💞 💓💗💖 💘💝💟 ☮️✝️☪️ 🕉️☸️✡️ 🔯🕎☯️ ☦️🛐⛎ ♈♉♊ ♋♌♍ ♎♏♐ ♑♒♓ 🆔⚛️🉑 ☢️☣️📴 📳🈶🈚 🈸🈺🈷️ ✴️🆚💮 🉐㊙️㊗️ 🈴🈵🈹 🈲🅰️🅱️ 🆎🆑🅾️ 🆘❌⭕ 🛑⛔📛 🚫💯💢 ♨️🚷🚯 🚳🚱🔞 📵🚭❗ ❕❓❔ ‼️⁉️🔅 🔆〽️⚠️ 🚸🔱⚜️ 🔰♻️✅ 🈯💹❇️ ✳️❎🌐 💠Ⓜ️🌀 💤🏧🚾 ♿🅿️🈳 🈂🛂🛃 🛄🛅🛗 🚀🛸🚁 🚉🚆🚅 ✈️🛫🛬 🛩️💺🛰️