Complete WebSocket

This commit is contained in:
2025-08-18 19:15:40 +03:00
Unverified
parent 6ecd885e7a
commit 06d183d6e4
6 changed files with 109 additions and 46 deletions
+41 -21
View File
@@ -1,6 +1,7 @@
from datetime import datetime from datetime import datetime
from email.policy import HTTP 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 sqlalchemy.orm import Session
from dependencies import get_current_user, get_db from dependencies import get_current_user, get_db
from models import Message, SendMessageRequest from models import Message, SendMessageRequest
@@ -70,41 +71,56 @@ class MessaggingSocketManager:
def __init__(self) -> None: def __init__(self) -> None:
self.connections: list[WebSocket] = [] 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): 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}}) 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: while True:
data = await websocket.receive_json() 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"}}) await websocket.send_json({"type": "ping", "data": {"status": "success"}})
elif data.type == "getMessages": elif type == "getMessages":
try: 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: except HTTPException as e:
await self.send_error(websocket, data.type, e) await self.send_error(websocket, type, e)
elif data.type == "sendMessage": elif type == "sendMessage":
try: try:
current_user, db = self.get_dependencies() current_user = get_current_user_inner()
request: SendMessageRequest = SendMessageRequest.model_validate(data.data) if not current_user:
raise HTTPException(401)
request: SendMessageRequest = SendMessageRequest.model_validate(data["data"])
response = await send_message_inner(request, current_user, db) response = await send_message_inner(request, current_user, db)
await self.broadcast({ await self.broadcast({
"type": "newMessage", "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: except HTTPException as e:
await self.send_error(websocket, data.type, e) await self.send_error(websocket, type, e)
else: 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): async def disconnect(self, websocket: WebSocket, code: int = 1000, message: str | None = None):
try: try:
@@ -112,11 +128,11 @@ class MessaggingSocketManager:
finally: finally:
self.connections.remove(websocket) self.connections.remove(websocket)
async def connect(self, websocket: WebSocket): async def connect(self, websocket: WebSocket, db: Session):
await websocket.accept() await websocket.accept()
self.connections.append(websocket) self.connections.append(websocket)
try: try:
await self.handle_connection(websocket) await self.handle_connection(websocket, db)
finally: finally:
self.connections.remove(websocket) self.connections.remove(websocket)
@@ -127,5 +143,9 @@ class MessaggingSocketManager:
messagingManager = MessaggingSocketManager() messagingManager = MessaggingSocketManager()
@router.websocket("/chat/ws") @router.websocket("/chat/ws")
async def messaging(websocket: WebSocket): async def chat_websocket(
await messagingManager.connect(websocket) websocket: WebSocket,
db: Session = Depends(get_db)
):
logger.logger.log(1, f"WebSocket connected: {websocket}")
await messagingManager.connect(websocket, db)
+2 -1
View File
@@ -4,7 +4,7 @@ import { API_BASE_URL } from "./config";
// Authentication and navigation handling // Authentication and navigation handling
export let currentUser: User | null = null; export let currentUser: User | null = null;
let authToken: string | null = null; export let authToken: string | null = null;
// Helper function to get auth headers // Helper function to get auth headers
@@ -39,6 +39,7 @@ export function showChat() {
document.getElementById('login-form')!.style.display = 'none'; document.getElementById('login-form')!.style.display = 'none';
document.getElementById('register-form')!.style.display = 'none'; document.getElementById('register-form')!.style.display = 'none';
document.getElementById('chat-interface')!.style.display = 'block'; document.getElementById('chat-interface')!.style.display = 'block';
loadMessages();
} }
// Clear all alerts // Clear all alerts
+2 -1
View File
@@ -1 +1,2 @@
export const API_BASE_URL: string = '/api'; export const API_BASE_URL: string = '/api';
export const API_FULL_BASE_URL: string = "localhost:8301/api"
+41 -22
View File
@@ -1,9 +1,11 @@
import './css/style.scss'; import './css/style.scss';
import { showLogin, getAuthHeaders } from './auth'; import { showLogin, getAuthHeaders, authToken } from './auth';
import { API_BASE_URL } from './config'; import { API_BASE_URL, API_FULL_BASE_URL } from './config';
import type { Message, Messages } from './types'; import type { Message, Messages, WebSocketMessage } from './types';
import "./links"; import "./links";
const websocket = new WebSocket(`ws://${API_FULL_BASE_URL}/chat/ws`);
// Функция для форматирования времени // Функция для форматирования времени
function formatTime(dateString: string) { function formatTime(dateString: string) {
@@ -91,30 +93,47 @@ export function sendMessage() {
const message = input.value.trim(); const message = input.value.trim();
if (message) { if (message) {
fetch(`${API_BASE_URL}/send_message`, { const payload: WebSocketMessage = {
method: 'POST', data: {
headers: getAuthHeaders(), content: message
body: JSON.stringify({ content: message }) },
}).then(response => { credentials: {
if (response.ok) { scheme: "Bearer",
input.value = ''; 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) => { document.getElementById('message-form')!.addEventListener('submit', (e) => {
e.preventDefault(); e.preventDefault();
sendMessage(); sendMessage();
}); });
// Проверка новых сообщений каждые 2 секунды (only when chat is visible)
setInterval(() => {
if (document.getElementById('chat-interface')!.style.display !== 'none') {
loadMessages();
}
}, 2000);
showLogin();
+21
View File
@@ -44,4 +44,25 @@ export interface RegisterRequest {
export interface LoginResponse { export interface LoginResponse {
username: string; username: string;
token: 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;
} }
+2 -1
View File
@@ -25,7 +25,8 @@ export default defineConfig({
"/api": { "/api": {
target: "http://127.0.0.1:8300/", target: "http://127.0.0.1:8300/",
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, "") rewrite: (path) => path.replace(/^\/api/, ""),
ws: true
} }
}, },
}, },