Виджет подключен к WebSocket для ответов операторов

Этот коммит содержится в:
Виктор
2026-05-08 20:18:43 +09:00
родитель ec2eeeea66
Коммит 49f502f84c
+60 -9
Просмотреть файл
@@ -2,6 +2,7 @@
const currentScript = document.currentScript; const currentScript = document.currentScript;
const siteId = currentScript?.dataset?.siteId || 'unknown_site'; const siteId = currentScript?.dataset?.siteId || 'unknown_site';
const apiUrl = currentScript?.dataset?.apiUrl || 'http://localhost:3000'; const apiUrl = currentScript?.dataset?.apiUrl || 'http://localhost:3000';
const wsUrl = currentScript?.dataset?.wsUrl || apiUrl.replace(/^http/i, 'ws');
const visitorIdKey = `raspi_chat_visitor_${siteId}`; const visitorIdKey = `raspi_chat_visitor_${siteId}`;
let visitorId = localStorage.getItem(visitorIdKey); let visitorId = localStorage.getItem(visitorIdKey);
@@ -17,7 +18,10 @@
.raspi-chat-panel.open{display:flex} .raspi-chat-panel.open{display:flex}
.raspi-chat-header{padding:16px;background:#172033;color:#fff;font-weight:800} .raspi-chat-header{padding:16px;background:#172033;color:#fff;font-weight:800}
.raspi-chat-messages{flex:1;padding:14px;overflow:auto;background:#f4f6fb;color:#172033;font-size:14px} .raspi-chat-messages{flex:1;padding:14px;overflow:auto;background:#f4f6fb;color:#172033;font-size:14px}
.raspi-chat-message{margin:0 0 10px;padding:10px 12px;border-radius:14px;background:#fff} .raspi-chat-message{margin:0 0 10px;padding:10px 12px;border-radius:14px;background:#fff;line-height:1.45}
.raspi-chat-message.operator{background:#dfe8ff;margin-left:18px}
.raspi-chat-message.visitor{background:#fff;margin-right:18px}
.raspi-chat-system{opacity:.7;font-size:12px;padding:4px 2px}
.raspi-chat-form{display:flex;gap:8px;padding:12px;border-top:1px solid #e7ebf3} .raspi-chat-form{display:flex;gap:8px;padding:12px;border-top:1px solid #e7ebf3}
.raspi-chat-input{flex:1;border:1px solid #d7deea;border-radius:12px;padding:10px;font:14px system-ui} .raspi-chat-input{flex:1;border:1px solid #d7deea;border-radius:12px;padding:10px;font:14px system-ui}
.raspi-chat-send{border:0;border-radius:12px;background:#4f6bff;color:#fff;padding:0 14px;font-weight:800;cursor:pointer} .raspi-chat-send{border:0;border-radius:12px;background:#4f6bff;color:#fff;padding:0 14px;font-weight:800;cursor:pointer}
@@ -31,9 +35,9 @@
const panel = document.createElement('section'); const panel = document.createElement('section');
panel.className = 'raspi-chat-panel'; panel.className = 'raspi-chat-panel';
panel.innerHTML = ` panel.innerHTML = `
<div class="raspi-chat-header">Напишите нам</div> <div class="raspi-chat-header">StackWorks Support</div>
<div class="raspi-chat-messages" data-role="messages"> <div class="raspi-chat-messages" data-role="messages">
<div class="raspi-chat-message">Здравствуйте! Чем можем помочь?</div> <div class="raspi-chat-message operator">Здравствуйте! Чем можем помочь?</div>
</div> </div>
<form class="raspi-chat-form" data-role="form"> <form class="raspi-chat-form" data-role="form">
<input class="raspi-chat-input" data-role="input" placeholder="Ваше сообщение" autocomplete="off" /> <input class="raspi-chat-input" data-role="input" placeholder="Ваше сообщение" autocomplete="off" />
@@ -47,24 +51,68 @@
const form = panel.querySelector('[data-role="form"]'); const form = panel.querySelector('[data-role="form"]');
const input = panel.querySelector('[data-role="input"]'); const input = panel.querySelector('[data-role="input"]');
let socket = null;
let reconnectTimer = null;
button.addEventListener('click', () => { button.addEventListener('click', () => {
panel.classList.toggle('open'); panel.classList.toggle('open');
}); });
function addMessage(text) { function addMessage(text, type = 'visitor') {
const item = document.createElement('div'); const item = document.createElement('div');
item.className = 'raspi-chat-message'; item.className = `raspi-chat-message ${type}`;
item.textContent = text; item.textContent = text;
messages.appendChild(item); messages.appendChild(item);
messages.scrollTop = messages.scrollHeight; messages.scrollTop = messages.scrollHeight;
} }
function addSystem(text) {
const item = document.createElement('div');
item.className = 'raspi-chat-system';
item.textContent = text;
messages.appendChild(item);
messages.scrollTop = messages.scrollHeight;
}
function connectWs() {
if (socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)) {
return;
}
socket = new WebSocket(`${wsUrl}/ws?siteId=${encodeURIComponent(siteId)}&visitorId=${encodeURIComponent(visitorId)}`);
socket.addEventListener('open', () => {
addSystem('Оператор подключен');
});
socket.addEventListener('message', (event) => {
try {
const payload = JSON.parse(event.data);
if (payload.type === 'operator_message') {
addMessage(payload.message?.body || 'Новое сообщение от оператора', 'operator');
}
} catch {
// ignore invalid frames
}
});
socket.addEventListener('close', () => {
addSystem('Соединение потеряно, переподключение...');
clearTimeout(reconnectTimer);
reconnectTimer = setTimeout(connectWs, 3000);
});
}
connectWs();
form.addEventListener('submit', async (event) => { form.addEventListener('submit', async (event) => {
event.preventDefault(); event.preventDefault();
const message = input.value.trim(); const message = input.value.trim();
if (!message) return; if (!message) return;
input.value = ''; input.value = '';
addMessage(message); addMessage(message, 'visitor');
try { try {
const response = await fetch(`${apiUrl}/api/widget/message`, { const response = await fetch(`${apiUrl}/api/widget/message`, {
@@ -72,10 +120,13 @@
headers: { 'content-type': 'application/json' }, headers: { 'content-type': 'application/json' },
body: JSON.stringify({ siteId, visitorId, message }) body: JSON.stringify({ siteId, visitorId, message })
}); });
const result = await response.json(); const result = await response.json();
if (!result.ok) addMessage(`Ошибка: ${result.error || 'message rejected'}`); if (!result.ok) {
} catch (error) { addSystem(`Ошибка: ${result.error || 'message rejected'}`);
addMessage('Сообщение не отправилось. Проверьте соединение.'); }
} catch {
addSystem('Сообщение не отправилось. Проверьте соединение.');
} }
}); });
})(); })();