mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Complete WebSocket
This commit is contained in:
+41
-21
@@ -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)
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
// Initialization
|
|
||||||
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();
|
|
||||||
@@ -45,3 +45,24 @@ 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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user