From 06d183d6e453944d72563ab83f61643ff410151a Mon Sep 17 00:00:00 2001 From: denis0001-dev Date: Mon, 18 Aug 2025 19:15:40 +0300 Subject: [PATCH] Complete WebSocket --- backend/routes/messaging.py | 62 +++++++++++++++++++++++------------- frontend/src/auth.ts | 3 +- frontend/src/config.ts | 3 +- frontend/src/main.ts | 63 ++++++++++++++++++++++++------------- frontend/src/types.ts | 21 +++++++++++++ frontend/vite.config.ts | 3 +- 6 files changed, 109 insertions(+), 46 deletions(-) diff --git a/backend/routes/messaging.py b/backend/routes/messaging.py index c994104..13d63f3 100644 --- a/backend/routes/messaging.py +++ b/backend/routes/messaging.py @@ -1,6 +1,7 @@ from datetime import datetime from email.policy import HTTP -from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisconnect +from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisconnect, WebSocketException, logger, status +from fastapi.security import HTTPAuthorizationCredentials from sqlalchemy.orm import Session from dependencies import get_current_user, get_db from models import Message, SendMessageRequest @@ -70,41 +71,56 @@ class MessaggingSocketManager: def __init__(self) -> None: self.connections: list[WebSocket] = [] - def get_dependencies(self): - return get_current_user(), next(get_db()) - async def send_error(self, websocket: WebSocket, type: str, e: HTTPException): await websocket.send_json({"type": type, "error": {"code": e.status_code, "detail": e.detail}}) - async def handle_connection(self, websocket: WebSocket): + async def handle_connection(self, websocket: WebSocket, db: Session): while True: data = await websocket.receive_json() + type = data["type"] - if data.type == "ping": + def get_current_user_inner() -> dict | None: + if data["credentials"]: + return get_current_user( + HTTPAuthorizationCredentials( + scheme=data["credentials"]["scheme"], + credentials=data["credentials"]["credentials"] + ), + db + ) + else: + return None + + if type == "ping": await websocket.send_json({"type": "ping", "data": {"status": "success"}}) - elif data.type == "getMessages": + elif type == "getMessages": try: - current_user, db = self.get_dependencies() + current_user = get_current_user_inner() + if not current_user: + raise HTTPException(401) - await websocket.send_json({"type": data.type, "data": await get_messages_inner(current_user, db)}) + await websocket.send_json({"type": type, "data": await get_messages_inner(current_user, db)}) except HTTPException as e: - await self.send_error(websocket, data.type, e) - elif data.type == "sendMessage": + await self.send_error(websocket, type, e) + elif type == "sendMessage": try: - current_user, db = self.get_dependencies() - request: SendMessageRequest = SendMessageRequest.model_validate(data.data) + current_user = get_current_user_inner() + if not current_user: + raise HTTPException(401) + + request: SendMessageRequest = SendMessageRequest.model_validate(data["data"]) response = await send_message_inner(request, current_user, db) await self.broadcast({ "type": "newMessage", - "data": response.message + "data": response["message"] }) - await websocket.send_json({"type": data.type, "data": response}) + await websocket.send_json({"type": type, "data": response}) except HTTPException as e: - await self.send_error(websocket, data.type, e) + await self.send_error(websocket, type, e) else: - await websocket.send_json({"type": data.type, "error": {"code": 400, "detail": "Invalid type"}}) + await websocket.send_json({"type": type, "error": {"code": 400, "detail": "Invalid type"}}) async def disconnect(self, websocket: WebSocket, code: int = 1000, message: str | None = None): try: @@ -112,11 +128,11 @@ class MessaggingSocketManager: finally: self.connections.remove(websocket) - async def connect(self, websocket: WebSocket): + async def connect(self, websocket: WebSocket, db: Session): await websocket.accept() self.connections.append(websocket) try: - await self.handle_connection(websocket) + await self.handle_connection(websocket, db) finally: self.connections.remove(websocket) @@ -127,5 +143,9 @@ class MessaggingSocketManager: messagingManager = MessaggingSocketManager() @router.websocket("/chat/ws") -async def messaging(websocket: WebSocket): - await messagingManager.connect(websocket) \ No newline at end of file +async def chat_websocket( + websocket: WebSocket, + db: Session = Depends(get_db) +): + logger.logger.log(1, f"WebSocket connected: {websocket}") + await messagingManager.connect(websocket, db) \ No newline at end of file diff --git a/frontend/src/auth.ts b/frontend/src/auth.ts index b1dd8ea..054b3e3 100644 --- a/frontend/src/auth.ts +++ b/frontend/src/auth.ts @@ -4,7 +4,7 @@ import { API_BASE_URL } from "./config"; // Authentication and navigation handling export let currentUser: User | null = null; -let authToken: string | null = null; +export let authToken: string | null = null; // Helper function to get auth headers @@ -39,6 +39,7 @@ export function showChat() { document.getElementById('login-form')!.style.display = 'none'; document.getElementById('register-form')!.style.display = 'none'; document.getElementById('chat-interface')!.style.display = 'block'; + loadMessages(); } // Clear all alerts diff --git a/frontend/src/config.ts b/frontend/src/config.ts index b894285..00f665f 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -1 +1,2 @@ -export const API_BASE_URL: string = '/api'; \ No newline at end of file +export const API_BASE_URL: string = '/api'; +export const API_FULL_BASE_URL: string = "localhost:8301/api" \ No newline at end of file diff --git a/frontend/src/main.ts b/frontend/src/main.ts index d68b171..b0ee549 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -1,9 +1,11 @@ import './css/style.scss'; -import { showLogin, getAuthHeaders } from './auth'; -import { API_BASE_URL } from './config'; -import type { Message, Messages } from './types'; +import { showLogin, getAuthHeaders, authToken } from './auth'; +import { API_BASE_URL, API_FULL_BASE_URL } from './config'; +import type { Message, Messages, WebSocketMessage } from './types'; import "./links"; +const websocket = new WebSocket(`ws://${API_FULL_BASE_URL}/chat/ws`); + // Функция для форматирования времени function formatTime(dateString: string) { @@ -91,30 +93,47 @@ export function sendMessage() { const message = input.value.trim(); if (message) { - fetch(`${API_BASE_URL}/send_message`, { - method: 'POST', - headers: getAuthHeaders(), - body: JSON.stringify({ content: message }) - }).then(response => { - if (response.ok) { - input.value = ''; + const payload: WebSocketMessage = { + data: { + content: message + }, + credentials: { + scheme: "Bearer", + credentials: authToken! + }, + type: "sendMessage" + } + + let callback: ((e: MessageEvent) => void) | null = null + callback = (e) => { + websocket.removeEventListener("message", callback!); + const response: WebSocketMessage = JSON.parse(e.data) + console.log(response) + if (!response.error) { + input.value = ""; } - }); + } + websocket.addEventListener("message", callback); + + websocket.send(JSON.stringify(payload)); } } -// Initialization +websocket.addEventListener("message", (e) => { + const message: WebSocketMessage = JSON.parse(e.data); + switch (message.type) { + case "newMessage": { + const newMessage: Message = message.data; + addMessage(newMessage, newMessage.is_author); + break; + } + } +}); + +showLogin(); + document.getElementById('message-form')!.addEventListener('submit', (e) => { e.preventDefault(); sendMessage(); -}); - -// Проверка новых сообщений каждые 2 секунды (only when chat is visible) -setInterval(() => { - if (document.getElementById('chat-interface')!.style.display !== 'none') { - loadMessages(); - } -}, 2000); - -showLogin(); \ No newline at end of file +}); \ No newline at end of file diff --git a/frontend/src/types.ts b/frontend/src/types.ts index ac3eeca..defaf34 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -44,4 +44,25 @@ export interface RegisterRequest { export interface LoginResponse { username: string; token: string; +} + +// --------------- +// WebSocket types +// --------------- + +export interface WebSocketMessage { + type: string; + credentials?: WebSocketCredentials; + data?: any; + error?: WebSocketError; +} + +export interface WebSocketError { + code: number; + detail: string; +} + +export interface WebSocketCredentials { + scheme: string; + credentials: string; } \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 715d584..2ce05f6 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -25,7 +25,8 @@ export default defineConfig({ "/api": { target: "http://127.0.0.1:8300/", changeOrigin: true, - rewrite: (path) => path.replace(/^\/api/, "") + rewrite: (path) => path.replace(/^\/api/, ""), + ws: true } }, },