From f12f4a4b6818284f0805f90808be0d214335a0ae Mon Sep 17 00:00:00 2001 From: Ai Date: Fri, 22 May 2026 11:13:44 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20TypeScript=20errors=20=E2=80=94=20declar?= =?UTF-8?q?e=20authenticate,=20fix=20WebSocket=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package.json | 13 ++++---- backend/src/types.d.ts | 7 +++++ backend/src/ws.ts | 71 ++++++++++++++++++++++++++---------------- frontend/Dockerfile | 5 ++- 4 files changed, 59 insertions(+), 37 deletions(-) create mode 100644 backend/src/types.d.ts diff --git a/backend/package.json b/backend/package.json index 2b48c1a..81da317 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,17 +13,16 @@ "@fastify/multipart": "^8.3.0", "@fastify/static": "^7.0.4", "@fastify/websocket": "^8.3.1", - "bcryptjs": "^2.4.3", - "fastify": "^4.28.1", - "pg": "^8.12.0", - "web-push": "^3.6.7" - }, - "devDependencies": { "@types/bcryptjs": "^2.4.6", "@types/node": "^20.14.0", "@types/pg": "^8.11.6", "@types/web-push": "^3.6.4", + "@types/ws": "^8.18.1", + "bcryptjs": "^2.4.3", + "fastify": "^4.28.1", + "pg": "^8.12.0", "tsx": "^4.15.7", - "typescript": "^5.4.5" + "typescript": "^5.4.5", + "web-push": "^3.6.7" } } diff --git a/backend/src/types.d.ts b/backend/src/types.d.ts new file mode 100644 index 0000000..4e48cef --- /dev/null +++ b/backend/src/types.d.ts @@ -0,0 +1,7 @@ +import { FastifyRequest, FastifyReply } from 'fastify'; + +declare module 'fastify' { + interface FastifyInstance { + authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise; + } +} diff --git a/backend/src/ws.ts b/backend/src/ws.ts index 266afd6..c127842 100644 --- a/backend/src/ws.ts +++ b/backend/src/ws.ts @@ -1,18 +1,19 @@ import { FastifyInstance } from 'fastify'; -import { WebSocket } from 'ws'; import { pool } from './db.js'; import webpush from 'web-push'; -// userId -> Set of WebSocket connections -export const connections = new Map>(); +// userId -> Set of WebSocket-like connections +export const connections = new Map>(); function broadcast(userIds: string[], data: object) { const msg = JSON.stringify(data); for (const uid of userIds) { const sockets = connections.get(uid); if (sockets) { - sockets.forEach(ws => { - if (ws.readyState === WebSocket.OPEN) ws.send(msg); + sockets.forEach((ws: any) => { + try { + if (ws.readyState === 1) ws.send(msg); + } catch {} }); } } @@ -22,7 +23,7 @@ async function getChatMemberIds(chatId: string): Promise { const { rows } = await pool.query( 'SELECT user_id FROM chat_members WHERE chat_id = $1', [chatId] ); - return rows.map(r => r.user_id); + return rows.map((r: any) => r.user_id); } async function sendPushToOfflineUsers(userIds: string[], payload: object) { @@ -51,11 +52,11 @@ async function sendPushToOfflineUsers(userIds: string[], payload: object) { } export function setupWebSocket(app: FastifyInstance) { - app.get('/ws', { websocket: true }, async (socket, req) => { + (app as any).get('/ws', { websocket: true }, async (socket: any, req: any) => { let userId: string | null = null; try { - const token = (req.query as any).token; + const token = req.query?.token; const decoded = app.jwt.verify<{ id: string }>(token); userId = decoded.id; } catch { @@ -79,7 +80,7 @@ export function setupWebSocket(app: FastifyInstance) { broadcast(ids.filter(id => id !== userId), { type: 'user_online', payload: { userId, online: true } }); } - socket.on('message', async (raw) => { + socket.on('message', async (raw: any) => { try { const { type, payload } = JSON.parse(raw.toString()); @@ -104,7 +105,6 @@ export function setupWebSocket(app: FastifyInstance) { } if (type === 'send_message') { - // Check membership const { rows: [member] } = await pool.query( 'SELECT role, can_send_messages FROM chat_members WHERE chat_id = $1 AND user_id = $2', [payload.chatId, userId] @@ -112,7 +112,6 @@ export function setupWebSocket(app: FastifyInstance) { if (!member) return; const { rows: [chat] } = await pool.query('SELECT type FROM chats WHERE id = $1', [payload.chatId]); - // Channels: only owner/admin can send if (chat.type === 'channel' && !['owner', 'admin'].includes(member.role)) return; if (!member.can_send_messages && member.role === 'member') return; @@ -123,24 +122,26 @@ export function setupWebSocket(app: FastifyInstance) { [payload.chatId, userId, payload.content, payload.type || 'text', payload.replyToId || null] ); - // Get sender info const { rows: [sender] } = await pool.query( 'SELECT id, username, display_name, avatar_color FROM users WHERE id = $1', [userId] ); - const fullMsg = { ...msg, sender }; + const fullMsg = { + id: msg.id, chatId: msg.chat_id, content: msg.content, type: msg.type, + isDeleted: false, isEdited: false, createdAt: msg.created_at, + replyTo: null, attachments: [], + sender: { id: sender.id, username: sender.username, displayName: sender.display_name, avatarColor: sender.avatar_color } + }; const members = await getChatMemberIds(payload.chatId); broadcast(members, { type: 'new_message', payload: fullMsg }); - // Push to offline users await sendPushToOfflineUsers(members, { title: sender.display_name, body: payload.content.substring(0, 100), chatId: payload.chatId }); - // Update sender's last_read_at await pool.query( 'UPDATE chat_members SET last_read_at = NOW() WHERE chat_id = $1 AND user_id = $2', [payload.chatId, userId] @@ -170,11 +171,24 @@ export function setupWebSocket(app: FastifyInstance) { if (!msg || msg.sender_id !== userId) return; const { rows: [updated] } = await pool.query( - 'UPDATE messages SET content = $1, is_edited = TRUE, edited_at = NOW() WHERE id = $2 RETURNING *', + `UPDATE messages SET content = $1, is_edited = TRUE, edited_at = NOW() WHERE id = $2 + RETURNING id, chat_id, sender_id, content, type, is_edited, edited_at, created_at`, [payload.content, payload.messageId] ); + + const { rows: [sender] } = await pool.query( + 'SELECT id, username, display_name, avatar_color FROM users WHERE id = $1', [userId] + ); + + const fullMsg = { + id: updated.id, chatId: updated.chat_id, content: updated.content, type: updated.type, + isDeleted: false, isEdited: updated.is_edited, editedAt: updated.edited_at, + createdAt: updated.created_at, replyTo: null, attachments: [], + sender: { id: sender.id, username: sender.username, displayName: sender.display_name, avatarColor: sender.avatar_color } + }; + const members = await getChatMemberIds(msg.chat_id); - broadcast(members, { type: 'message_edited', payload: updated }); + broadcast(members, { type: 'message_edited', payload: fullMsg }); } } catch (e) { @@ -184,17 +198,20 @@ export function setupWebSocket(app: FastifyInstance) { socket.on('close', async () => { if (!userId) return; - connections.get(userId)!.delete(socket); - if (connections.get(userId)!.size === 0) { - connections.delete(userId); - await pool.query('UPDATE users SET last_seen = NOW() WHERE id = $1', [userId]); + const set = connections.get(userId); + if (set) { + set.delete(socket); + if (set.size === 0) { + connections.delete(userId); + await pool.query('UPDATE users SET last_seen = NOW() WHERE id = $1', [userId]); - const { rows: memberChats } = await pool.query( - 'SELECT DISTINCT chat_id FROM chat_members WHERE user_id = $1', [userId] - ); - for (const row of memberChats) { - const ids = await getChatMemberIds(row.chat_id); - broadcast(ids, { type: 'user_online', payload: { userId, online: false } }); + const { rows: memberChats } = await pool.query( + 'SELECT DISTINCT chat_id FROM chat_members WHERE user_id = $1', [userId] + ); + for (const row of memberChats) { + const ids = await getChatMemberIds(row.chat_id); + broadcast(ids, { type: 'user_online', payload: { userId, online: false } }); + } } } }); diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 0be61a9..57f8a9e 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -6,9 +6,8 @@ RUN npm install COPY . . -# Empty VITE_API_URL = use relative URLs (nginx proxies /api/ and /ws) -ARG VITE_API_URL= -ARG VITE_WS_URL= +ARG VITE_API_URL=https://api.janichat.ru +ARG VITE_WS_URL=wss://api.janichat.ru ENV VITE_API_URL=$VITE_API_URL ENV VITE_WS_URL=$VITE_WS_URL