mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-23 02:27:14 +00:00
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:
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user