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 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)
async def chat_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
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
+1
View File
@@ -1 +1,2 @@
export const API_BASE_URL: string = '/api';
export const API_FULL_BASE_URL: string = "localhost:8301/api"
+40 -21
View File
@@ -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();
+21
View File
@@ -45,3 +45,24 @@ 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;
}
+2 -1
View File
@@ -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
}
},
},