fix(embed): isolate browser chat sessions (#2335)

* fix(embed): isolate browser chat sessions

* fix(embed): harden reset and reconnect recovery
This commit is contained in:
RockChinQ
2026-07-13 15:08:45 +08:00
committed by GitHub
parent 2c3e52c16c
commit bd35b793e1
6 changed files with 588 additions and 74 deletions
+87 -5
View File
@@ -303,11 +303,60 @@
'<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8l-6-6zM6 20V4h7v5h5v11H6z"/><path d="M8 17l2.5-3.5L13 17l2-2.5L18 17H8z"/></svg>';
// ========== State ==========
function createSessionId() {
if (window.crypto && typeof window.crypto.randomUUID === "function") {
return window.crypto.randomUUID();
}
var bytes = new Uint8Array(16);
if (!window.crypto || typeof window.crypto.getRandomValues !== "function") {
throw new Error("Secure random number generation is unavailable");
}
window.crypto.getRandomValues(bytes);
bytes[6] = (bytes[6] & 15) | 64;
bytes[8] = (bytes[8] & 63) | 128;
var hex = Array.prototype.map
.call(bytes, function (value) {
return value.toString(16).padStart(2, "0");
})
.join("");
return (
hex.slice(0, 8) +
"-" +
hex.slice(8, 12) +
"-" +
hex.slice(12, 16) +
"-" +
hex.slice(16, 20) +
"-" +
hex.slice(20)
);
}
function getOrCreateSessionId() {
var storageKey = "langbot_embed_session_" + CONFIG.botUuid;
try {
var stored = window.sessionStorage.getItem(storageKey);
if (
/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/.test(
stored || "",
)
)
return stored;
var created = createSessionId();
window.sessionStorage.setItem(storageKey, created);
return created;
} catch (e) {
return createSessionId();
}
}
var state = {
isOpen: false,
isConnected: false,
ws: null,
connectionId: null,
sessionId: getOrCreateSessionId(),
reconnectAttempts: 0,
heartbeatTimer: null,
messages: [],
@@ -315,6 +364,9 @@
isStreaming: false,
streamingMsgId: null,
historyLoaded: false,
hasConnected: false,
messageVersion: 0,
historyReloadTimer: null,
pendingImage: null,
feedbackState: {},
};
@@ -473,7 +525,9 @@
"/api/v1/embed/" +
CONFIG.botUuid +
"/ws/connect?session_type=" +
CONFIG.sessionType;
CONFIG.sessionType +
"&session_id=" +
encodeURIComponent(state.sessionId);
try {
state.ws = new WebSocket(url);
@@ -520,6 +574,8 @@
case "connected":
state.isConnected = true;
state.connectionId = data.connection_id;
if (state.hasConnected) loadHistory(true);
state.hasConnected = true;
updateStatusDot();
updateSendBtn();
break;
@@ -587,6 +643,7 @@
if (existingIdx >= 0) {
state.messages[existingIdx] = msg;
state.messageVersion++;
updateMessageEl(existingIdx, msg);
} else {
addMessage(msg);
@@ -656,16 +713,27 @@
}
// ========== Message History ==========
function loadHistory() {
if (state.historyLoaded) return;
function scheduleHistoryReload() {
if (state.historyReloadTimer) clearTimeout(state.historyReloadTimer);
state.historyReloadTimer = setTimeout(function () {
state.historyReloadTimer = null;
loadHistory(true);
}, 100);
}
function loadHistory(force) {
if (state.historyLoaded && !force) return;
state.historyLoaded = true;
var messageVersion = state.messageVersion;
var url =
CONFIG.baseUrl +
"/api/v1/embed/" +
CONFIG.botUuid +
"/messages/" +
CONFIG.sessionType;
CONFIG.sessionType +
"?session_id=" +
encodeURIComponent(state.sessionId);
var headers = {};
if (state.sessionToken)
headers["Authorization"] = "Bearer " + state.sessionToken;
@@ -675,6 +743,16 @@
})
.then(function (json) {
if (json.code === 0 && json.data && json.data.messages) {
if (force && messageVersion !== state.messageVersion) {
scheduleHistoryReload();
return;
}
if (force) {
state.messages = [];
state.isStreaming = false;
state.streamingMsgId = null;
renderMessages();
}
var msgs = json.data.messages;
for (var i = 0; i < msgs.length; i++) {
addMessage(msgs[i], true);
@@ -693,13 +771,16 @@
"/api/v1/embed/" +
CONFIG.botUuid +
"/reset/" +
CONFIG.sessionType;
CONFIG.sessionType +
"?session_id=" +
encodeURIComponent(state.sessionId);
var headers = {};
if (state.sessionToken)
headers["Authorization"] = "Bearer " + state.sessionToken;
fetch(url, { method: "POST", headers: headers })
.then(function () {
state.messages = [];
state.messageVersion++;
state.isStreaming = false;
state.streamingMsgId = null;
state.historyLoaded = true;
@@ -713,6 +794,7 @@
// ========== UI Rendering ==========
function addMessage(msg, silent) {
state.messages.push(msg);
if (!silent) state.messageVersion++;
var el = createMessageEl(msg);
if (els.welcome) {
els.welcome.style.display = "none";