Chat alkalmazás
A Chat alkalmazás egy valós idejű üzenetküldő rendszer, amely lehetővé teszi a felhasználók közötti közvetlen kommunikációt. Socket.IO alapú real-time kapcsolattal és REST API fallback-kel rendelkezik.
Áttekintés
Szekció neve “Áttekintés”A Chat alkalmazás három fő részből áll:
- Felhasználói lista (jobb oldali sidebar) - online/offline státusszal
- Beszélgetések listája - olvasatlan üzenetek számával
- Chat ablak - üzenetek megjelenítése és küldése
Főbb funkciók
Szekció neve “Főbb funkciók”- Valós idejű üzenetküldés Socket.IO-val
- Online/offline státusz követés
- Gépelés jelzés (typing indicator)
- Olvasatlan üzenetek számlálása
- Automatikus fallback REST API-ra (dev mode)
- Toast értesítések új üzenetekről
- Beszélgetések automatikus rendezése (legfrissebb felül)
Fájl struktúra
Szekció neve “Fájl struktúra”apps/chat/├── index.svelte # Fő layout (sidebar + beszélgetések + chat ablak)├── chat.remote.ts # Server actions (üzenetek, beszélgetések)├── components/│ ├── UserList.svelte # Felhasználói lista online/offline csoportosítással│ ├── ConversationList.svelte # Beszélgetések listája│ └── ChatWindow.svelte # Üzenetek megjelenítése és küldés└── stores/ └── chatStore.svelte.ts # Chat állapotkezelés és Socket.IO kapcsolatServer Actions
Szekció neve “Server Actions”chat.remote.ts
Szekció neve “chat.remote.ts”A Chat alkalmazás 8 server action-t definiál:
1. getChatUsers (query)
Szekció neve “1. getChatUsers (query)”Visszaadja az összes felhasználót (kivéve a jelenlegi felhasználót) chat indításához.
const result = await getChatUsers();// { success: true, users: ChatUser[] }2. getConversations (query)
Szekció neve “2. getConversations (query)”Lekéri a felhasználó összes beszélgetését az utolsó üzenettel és olvasatlan számmal.
const result = await getConversations();// { success: true, conversations: ConversationWithLastMessage[] }3. getMessages (command)
Szekció neve “3. getMessages (command)”Lekéri egy beszélgetés üzeneteit lapozással.
const result = await getMessages({ conversationId: 1, limit: 50, // opcionális, alapértelmezett: 50 offset: 0 // opcionális, alapértelmezett: 0});// { success: true, messages: MessageWithSender[] }Validáció:
conversationId: minimum 1limit: 1-100 közöttoffset: minimum 0- Ellenőrzi, hogy a felhasználó tagja-e a beszélgetésnek
4. sendMessage (command)
Szekció neve “4. sendMessage (command)”Új üzenet küldése egy felhasználónak.
const result = await sendMessage({ recipientId: 2, content: "Helló!"});// { success: true, message: MessageWithSender, conversationId: number }Validáció:
recipientId: minimum 1content: 1-5000 karakter között
Működés:
- Lekéri vagy létrehozza a beszélgetést
- Elmenti az üzenetet az adatbázisba
- Visszaadja az üzenetet a küldő adataival
5. markMessagesAsRead (command)
Szekció neve “5. markMessagesAsRead (command)”Beszélgetés üzeneteinek olvasottá jelölése.
const result = await markMessagesAsRead({ conversationId: 1});// { success: true }6. getUnreadCount (query)
Szekció neve “6. getUnreadCount (query)”Összes olvasatlan üzenet számának lekérése.
const result = await getUnreadCount();// { success: true, count: number }7. getCurrentUserId (query)
Szekció neve “7. getCurrentUserId (query)”Jelenlegi felhasználó ID-jának lekérése.
const result = await getCurrentUserId();// { success: true, userId: number }8. getOrCreateConversation (command)
Szekció neve “8. getOrCreateConversation (command)”Beszélgetés lekérése vagy létrehozása egy felhasználóval.
const result = await getOrCreateConversation({ otherUserId: 2});// { success: true, conversationId: number }ChatStore
Szekció neve “ChatStore”A chatStore.svelte.ts kezeli a chat állapotot és a Socket.IO kapcsolatot.
Állapot
Szekció neve “Állapot”interface ChatState { conversations: ConversationWithLastMessage[]; activeConversationId: number | null; messages: MessageWithSender[]; unreadCount: number; isConnected: boolean; // Socket.IO kapcsolat állapota onlineUsers: Set<number>; // Online felhasználók ID-i typingUsers: Map<number, boolean>; // Gépelő felhasználók beszélgetésenként}Főbb metódusok
Szekció neve “Főbb metódusok”connect(userId: number)
Szekció neve “connect(userId: number)”Socket.IO kapcsolat inicializálása és event listener-ek beállítása.
const chatStore = getChatStore();await chatStore.connect(userId);Socket.IO események:
chat:new-message- Új üzenet érkezettchat:user-online- Felhasználó online lettchat:user-offline- Felhasználó offline lettchat:online-users- Online felhasználók listájachat:user-typing- Gépelés jelzés
Fallback működés:
- Ha Socket.IO nem elérhető, 10 másodpercenként poll-oz
- Dev mode-ban automatikusan polling-ot használ
disconnect()
Szekció neve “disconnect()”Socket.IO kapcsolat bontása és polling leállítása.
chatStore.disconnect();loadConversations()
Szekció neve “loadConversations()”Beszélgetések újratöltése az API-ból.
await chatStore.loadConversations();loadMessages(conversationId: number)
Szekció neve “loadMessages(conversationId: number)”Beszélgetés üzeneteinek betöltése és aktívvá tétele.
await chatStore.loadMessages(1);Mellékhatások:
- Beállítja az
activeConversationId-t - Automatikusan olvasottá jelöli az üzeneteket
- Frissíti az olvasatlan számot
sendMessage(recipientId: number, content: string)
Szekció neve “sendMessage(recipientId: number, content: string)”Üzenet küldése Socket.IO-n keresztül.
const result = await chatStore.sendMessage(2, "Helló!");Működés:
- Meghívja a
sendMessageserver action-t - Elküldi Socket.IO-n keresztül (
chat:send-messageevent) - Hozzáadja az üzenetet a helyi állapothoz (ha aktív beszélgetés)
- Frissíti a beszélgetések listáját
markAsRead(conversationId: number)
Szekció neve “markAsRead(conversationId: number)”Üzenetek olvasottá jelölése.
await chatStore.markAsRead(1);sendTypingIndicator(recipientId, conversationId, isTyping)
Szekció neve “sendTypingIndicator(recipientId, conversationId, isTyping)”Gépelés jelzés küldése Socket.IO-n keresztül.
chatStore.sendTypingIndicator(2, 1, true); // Gépelés kezdésechatStore.sendTypingIndicator(2, 1, false); // Gépelés végeisUserOnline(userId: number): boolean
Szekció neve “isUserOnline(userId: number): boolean”Ellenőrzi, hogy egy felhasználó online-e.
if (chatStore.isUserOnline(2)) { console.log('User is online');}isUserTyping(conversationId: number): boolean
Szekció neve “isUserTyping(conversationId: number): boolean”Ellenőrzi, hogy valaki gépel-e egy beszélgetésben.
if (chatStore.isUserTyping(1)) { console.log('Other user is typing...');}Komponensek
Szekció neve “Komponensek”UserList.svelte
Szekció neve “UserList.svelte”Felhasználói lista online/offline csoportosítással és keresővel.
Funkciók:
- Keresés név és felhasználónév alapján
- Online/offline csoportok összecsukhatók
- Státusz indikátor (zöld/szürke)
- Kattintásra beszélgetés indítása
Használat:
<UserList />ConversationList.svelte
Szekció neve “ConversationList.svelte”Beszélgetések listája az utolsó üzenettel és olvasatlan számmal.
Funkciók:
- Automatikus rendezés (legfrissebb felül)
- Olvasatlan üzenetek badge-e
- Aktív beszélgetés kiemelése
- Frissítés gomb
- Relatív időbélyeg (pl. “2 perce”)
Használat:
<ConversationList />ChatWindow.svelte
Szekció neve “ChatWindow.svelte”Üzenetek megjelenítése és küldése.
Funkciók:
- Üzenetek csoportosítása küldő szerint
- Avatar megjelenítés
- Gépelés jelzés animációval
- Enter billentyűvel küldés
- Automatikus görgetés új üzenethez
- Üres állapot kezelés
Használat:
<ChatWindow currentUserId={userId} />Props:
currentUserId: Jelenlegi felhasználó ID-ja (üzenetek megkülönböztetéséhez)
Socket.IO integráció
Szekció neve “Socket.IO integráció”Szerver oldal
Szekció neve “Szerver oldal”A Socket.IO szerver a server.js fájlban van konfigurálva (Express + Socket.IO).
Események (szerver → kliens):
chat:new-message- Új üzenet érkezettchat:user-online- Felhasználó online lettchat:user-offline- Felhasználó offline lettchat:online-users- Online felhasználók listájachat:user-typing- Gépelés jelzés
Események (kliens → szerver):
register- Felhasználó regisztrálása (userId)chat:send-message- Üzenet küldésechat:mark-read- Üzenetek olvasottá jelölésechat:typing- Gépelés jelzés küldése
Kliens oldal
Szekció neve “Kliens oldal”A ChatStore automatikusan kezeli a Socket.IO kapcsolatot:
// Kapcsolódásconst chatStore = getChatStore();await chatStore.connect(userId);
// Automatikus újracsatlakozás// - Végtelen újrapróbálkozás// - 1-5 másodperc késleltetéssel// - WebSocket + polling fallbackToast értesítések
Szekció neve “Toast értesítések”Új üzenet érkezésekor (ha nem az aktív beszélgetésben):
toast.info(senderName, { description: messagePreview, duration: 5000, action: { label: 'Megnyitás', onClick: () => openMessageInChat(conversationId) }});Működés:
- Dinamikusan importálja a
svelte-sonnertoast-ot - Megjeleníti a küldő nevét és az üzenet előnézetét
- “Megnyitás” gombbal megnyitja a beszélgetést
Adatbázis séma
Szekció neve “Adatbázis séma”conversations tábla
Szekció neve “conversations tábla”{ id: number; participant1Id: number; participant2Id: number; lastMessageAt: Date | null; createdAt: Date;}messages tábla
Szekció neve “messages tábla”{ id: number; conversationId: number; senderId: number; content: string; isRead: boolean; readAt: Date | null; sentAt: Date;}ChatRepository
Szekció neve “ChatRepository”A chatRepository.ts kezeli az adatbázis műveleteket.
Főbb metódusok
Szekció neve “Főbb metódusok”getOrCreateConversation(userId1, userId2)
Szekció neve “getOrCreateConversation(userId1, userId2)”Lekéri vagy létrehozza a beszélgetést két felhasználó között.
const conversation = await chatRepository.getOrCreateConversation(1, 2);Működés:
- Ellenőrzi mindkét irányban (participant1 ↔ participant2)
- Ha nem létezik, létrehozza
getUserConversations(userId)
Szekció neve “getUserConversations(userId)”Lekéri a felhasználó összes beszélgetését.
const conversations = await chatRepository.getUserConversations(1);Visszaadja:
- Beszélgetés adatok
- Másik felhasználó neve és képe
- Utolsó üzenet
- Olvasatlan üzenetek száma
getConversationMessages(conversationId, limit, offset)
Szekció neve “getConversationMessages(conversationId, limit, offset)”Lekéri egy beszélgetés üzeneteit.
const messages = await chatRepository.getConversationMessages(1, 50, 0);Működés:
- Lapozható (limit + offset)
- Időrendi sorrendben (legrégebbi → legújabb)
- Küldő neve és képe csatolva
sendMessage(conversationId, senderId, content)
Szekció neve “sendMessage(conversationId, senderId, content)”Új üzenet mentése.
const message = await chatRepository.sendMessage(1, 2, "Helló!");Mellékhatások:
- Frissíti a beszélgetés
lastMessageAtmezőjét
markMessagesAsRead(conversationId, userId)
Szekció neve “markMessagesAsRead(conversationId, userId)”Beszélgetés üzeneteinek olvasottá jelölése.
await chatRepository.markMessagesAsRead(1, 2);Működés:
- Csak a másik felhasználó üzeneteit jelöli olvasottá
- Beállítja az
isReadésreadAtmezőket
getUserUnreadCount(userId)
Szekció neve “getUserUnreadCount(userId)”Összes olvasatlan üzenet számának lekérése.
const count = await chatRepository.getUserUnreadCount(1);Használati példák
Szekció neve “Használati példák”Chat alkalmazás indítása
Szekció neve “Chat alkalmazás indítása”import { getChatStore } from '$apps/chat/stores/chatStore.svelte';import { getCurrentUserId } from '$apps/chat/chat.remote';
// Felhasználó ID lekéréseconst result = await getCurrentUserId();if (result.success && result.userId) { // ChatStore inicializálása const chatStore = getChatStore(); await chatStore.connect(result.userId);}Új beszélgetés indítása
Szekció neve “Új beszélgetés indítása”import { getChatStore } from '$apps/chat/stores/chatStore.svelte';import { getOrCreateConversation } from '$apps/chat/chat.remote';
const chatStore = getChatStore();
// Beszélgetés létrehozásaconst result = await getOrCreateConversation({ otherUserId: 2 });
if (result.success && result.conversationId) { // Beszélgetések frissítése await chatStore.loadConversations();
// Beszélgetés megnyitása await chatStore.loadMessages(result.conversationId);}Üzenet küldése
Szekció neve “Üzenet küldése”import { getChatStore } from '$apps/chat/stores/chatStore.svelte';
const chatStore = getChatStore();
// Üzenet küldéseconst result = await chatStore.sendMessage(2, "Helló, hogy vagy?");
if (result.success) { console.log('Üzenet elküldve');}Gépelés jelzés
Szekció neve “Gépelés jelzés”import { getChatStore } from '$apps/chat/stores/chatStore.svelte';
const chatStore = getChatStore();let typingTimeout: ReturnType<typeof setTimeout> | null = null;
function handleInput(recipientId: number, conversationId: number) { // Gépelés kezdése chatStore.sendTypingIndicator(recipientId, conversationId, true);
// Timeout törlése if (typingTimeout) clearTimeout(typingTimeout);
// 3 másodperc után gépelés vége typingTimeout = setTimeout(() => { chatStore.sendTypingIndicator(recipientId, conversationId, false); }, 3000);}Fordítások
Szekció neve “Fordítások”A Chat alkalmazás fordításai a translations.chat namespace-ben találhatók:
-- packages/database/src/seeds/translations/chat.tsINSERT INTO translations (namespace, key, locale, value) VALUES ('chat', 'title', 'hu', 'Chat'), ('chat', 'title', 'en', 'Chat'), ('chat', 'users', 'hu', 'Felhasználók'), ('chat', 'users', 'en', 'Users'), ('chat', 'conversations', 'hu', 'Beszélgetések'), ('chat', 'conversations', 'en', 'Conversations'), -- ...Használat komponensben:
<script> import { I18nProvider } from '$lib/i18n/components';</script>
<I18nProvider namespaces={['chat', 'common']}> <!-- Chat komponensek --></I18nProvider>Best practice-ek
Szekció neve “Best practice-ek”- Socket.IO kapcsolat kezelése: Mindig hívd meg a
disconnect()metódust, amikor az alkalmazás bezárul - Gépelés jelzés: Használj timeout-ot, hogy ne küldjön folyamatosan event-eket
- Üzenetek lapozása: Implementálj “load more” funkciót nagy beszélgetéseknél
- Offline működés: A fallback polling biztosítja, hogy dev mode-ban is működjön
- Toast értesítések: Csak akkor jelenítsd meg, ha nem az aktív beszélgetésben érkezik az üzenet
- Olvasatlan számlálás: Automatikusan frissül Socket.IO event-ekkel
- Avatar képek: Használj
referrerpolicy="no-referrer"éscrossorigin="anonymous"attribútumokat - Üzenetek csoportosítása: Csak akkor jelenítsd meg az avatart, ha új küldő van
Hibaelhárítás
Szekció neve “Hibaelhárítás”Socket.IO nem csatlakozik
Szekció neve “Socket.IO nem csatlakozik”Probléma: isConnected mindig false marad.
Megoldás:
- Ellenőrizd, hogy a Socket.IO szerver fut-e (
server.js) - Nézd meg a böngésző konzolt Socket.IO hibákért
- Dev mode-ban a polling fallback automatikusan aktiválódik
Üzenetek nem jelennek meg
Szekció neve “Üzenetek nem jelennek meg”Probléma: Új üzenet nem jelenik meg a chat ablakban.
Megoldás:
- Ellenőrizd, hogy az aktív beszélgetés ID helyes-e
- Nézd meg a
chat:new-messageevent érkezik-e (DevTools Network tab) - Ellenőrizd a
currentUserIdprop-ot aChatWindowkomponensben
Gépelés jelzés nem működik
Szekció neve “Gépelés jelzés nem működik”Probléma: A typing indicator nem jelenik meg.
Megoldás:
- Ellenőrizd, hogy Socket.IO csatlakozva van-e
- Nézd meg a
chat:typingevent küldését és fogadását - Ellenőrizd a
typingUsersMap-et a store-ban
Online státusz nem frissül
Szekció neve “Online státusz nem frissül”Probléma: Felhasználók mindig offline-nak látszanak.
Megoldás:
- Ellenőrizd a
chat:online-usersevent fogadását - Nézd meg az
onlineUsersSet-et a store-ban - Dev mode-ban a polling frissíti az online státuszt