Добавлена доставка ответов Telegram в виджет через WebSocket

Этот коммит содержится в:
Виктор
2026-05-08 20:17:23 +09:00
родитель 8ce489a464
Коммит ec2eeeea66
+66 -5
Просмотреть файл
@@ -2,6 +2,7 @@ import Fastify from 'fastify';
import cors from '@fastify/cors'; import cors from '@fastify/cors';
import rateLimit from '@fastify/rate-limit'; import rateLimit from '@fastify/rate-limit';
import websocket from '@fastify/websocket'; import websocket from '@fastify/websocket';
import { randomUUID } from 'node:crypto';
import { config } from './config.js'; import { config } from './config.js';
import { import {
createVisitorMessage, createVisitorMessage,
@@ -18,6 +19,7 @@ import {
getTelegramBridgeStatus, getTelegramBridgeStatus,
notifyOperatorsAboutVisitorMessage, notifyOperatorsAboutVisitorMessage,
restartTelegramBridge, restartTelegramBridge,
setOperatorMessageNotifier,
startTelegramBridge startTelegramBridge
} from './telegram.js'; } from './telegram.js';
@@ -48,6 +50,57 @@ await app.register(websocket);
const clients = new Map(); const clients = new Map();
function getClientKey(siteId, visitorId) {
return `${siteId || 'unknown_site'}:${visitorId || 'unknown_visitor'}`;
}
function sendJson(connection, payload) {
try {
connection.send(JSON.stringify(payload));
return true;
} catch {
return false;
}
}
function broadcastToVisitor({ siteId, visitorId, payload }) {
const key = getClientKey(siteId, visitorId);
const bucket = clients.get(key);
if (!bucket || bucket.size === 0) return 0;
let sent = 0;
for (const connection of bucket) {
if (sendJson(connection, payload)) sent += 1;
}
return sent;
}
setOperatorMessageNotifier(({ conversation, message, operator }) => {
const sent = broadcastToVisitor({
siteId: conversation.site_id,
visitorId: conversation.visitor_key,
payload: {
type: 'operator_message',
conversationId: conversation.id,
message: {
id: message.id,
direction: 'operator',
body: message.body,
createdAt: message.created_at,
operator: {
id: operator.id,
name: operator.name || 'Оператор'
}
}
}
});
app.log.info(
{ conversationId: conversation.id, visitorId: conversation.visitor_key, sent },
'Operator Telegram answer delivered to widget clients'
);
});
function validateSocks5Settings(settings) { function validateSocks5Settings(settings) {
const proxy = settings.proxy || {}; const proxy = settings.proxy || {};
@@ -95,7 +148,8 @@ app.get('/health', async () => ({
service: 'wschat-backend', service: 'wschat-backend',
env: config.app.env, env: config.app.env,
dbPath: config.db.path, dbPath: config.db.path,
telegram: getTelegramBridgeStatus() telegram: getTelegramBridgeStatus(),
wsClients: Array.from(clients.values()).reduce((sum, bucket) => sum + bucket.size, 0)
})); }));
app.get('/api/config/public', async () => ({ app.get('/api/config/public', async () => ({
@@ -216,14 +270,21 @@ app.post('/api/widget/message', async (request) => {
}); });
app.get('/ws', { websocket: true }, (connection, request) => { app.get('/ws', { websocket: true }, (connection, request) => {
const visitorId = request.query?.visitorId || crypto.randomUUID(); const siteId = String(request.query?.siteId || request.query?.widgetKey || 'unknown_site');
clients.set(visitorId, connection); const visitorId = String(request.query?.visitorId || `visitor_${randomUUID()}`);
const key = getClientKey(siteId, visitorId);
if (!clients.has(key)) clients.set(key, new Set());
clients.get(key).add(connection);
connection.on('close', () => { connection.on('close', () => {
clients.delete(visitorId); const bucket = clients.get(key);
if (!bucket) return;
bucket.delete(connection);
if (bucket.size === 0) clients.delete(key);
}); });
connection.send(JSON.stringify({ type: 'connected', visitorId })); sendJson(connection, { type: 'connected', siteId, visitorId });
}); });
try { try {