chat.svelte.ts RawHistoryFolder
1/**
2 * Chat State Management
3 *
4 * Minimal state manager for user session.
5 * Real-time data (messages, users) comes from Convex subscriptions in components.
6 */
7
8import type { User, SafeUser } from '../types/chat';
9import { createSafeUser } from '../types/chat';
10
11class ChatState {
12 // Current user state (from session)
13 private _currentUser = $state<SafeUser | null>(null);
14 private _currentRoomId = $state<string | null>(null);
15 // SHA-256 hash of the session token: used as `tokenHash` for Convex authQuery/authMutation
16 private _sessionTokenHash = $state<string | null>(null);
17
18 // Connection state for UI feedback
19 private _connectionStatus = $state<'connected' | 'disconnected' | 'reconnecting'>('disconnected');
20
21 // Mention notifications per room
22 private _roomMentionCounts = $state<Record<string, number>>({});
23
24 // Derived public state
25 public connectionStatus = $derived(this._connectionStatus);
26 public isConnected = $derived(this._connectionStatus === 'connected');
27 public mentionTotal = $derived(
28 Object.values(this._roomMentionCounts).reduce((sum, count) => sum + count, 0)
29 );
30
31 getMentionTotal() {
32 return this.mentionTotal;
33 }
34
35 /**
36 * Get the current user
37 */
38 getCurrentUser() {
39 return this._currentUser;
40 }
41
42 /**
43 * Set the current user (called from layout when session changes)
44 */
45 setCurrentUser(user: User | SafeUser | null) {
46 if (!user) {
47 this._currentUser = null;
48 this._sessionTokenHash = null;
49 this._connectionStatus = 'disconnected';
50 this._roomMentionCounts = {};
51 return;
52 }
53
54 const safeUser = 'password' in user ? createSafeUser(user) : user;
55 this._currentUser = safeUser;
56 this._connectionStatus = 'connected';
57 }
58
59 /**
60 * Session token hash for client-side authenticated Convex calls
61 */
62 getSessionTokenHash() {
63 return this._sessionTokenHash;
64 }
65
66 setSessionTokenHash(hash: string | null) {
67 this._sessionTokenHash = hash;
68 }
69
70 /**
71 * Get the current room ID
72 */
73 getCurrentRoomId() {
74 return this._currentRoomId;
75 }
76
77 /**
78 * Set the current room ID
79 */
80 setCurrentRoomId(roomId: string | null) {
81 this._currentRoomId = roomId;
82 }
83
84 /**
85 * Set connection status
86 */
87 setConnectionStatus(status: 'connected' | 'disconnected' | 'reconnecting') {
88 this._connectionStatus = status;
89 }
90
91 /**
92 * Update mention count for a room
93 */
94 setRoomMentionCount(roomId: string | null, count: number) {
95 if (!roomId) return;
96 const next = Math.max(0, count);
97 if (this._roomMentionCounts[roomId] === next) return;
98 this._roomMentionCounts = { ...this._roomMentionCounts, [roomId]: next };
99 }
100
101 /**
102 * Get mention count for a room
103 */
104 getRoomMentionCount(roomId: string | null) {
105 if (!roomId) return 0;
106 return this._roomMentionCounts[roomId] ?? 0;
107 }
108}
109
110export const chatState = new ChatState();