@veroai/chat 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,86 @@
1
+ import { C as ChatEvents, a as CallAction, b as CallType } from './client-C63XKtNw.js';
2
+ export { A as AgentConfig, c as ApiError, d as ApiResponse, e as CallEvent, f as ChatApi, g as ChatClient, h as ChatClientConfig, i as Conversation, j as ConversationType, k as CreateConversationParams, G as GetMessagesParams, M as Message, l as MessageType, N as NewMessageEvent, P as PaginatedMessages, m as Participant, n as PresenceEvent, o as PresenceStatus, R as ReadReceipt, p as ReadReceiptEvent, q as RoomInfo, S as SendMessageParams, T as TypingEvent, U as User, r as UserPresence, W as WebSocketEventType, s as WebSocketMessage } from './client-C63XKtNw.js';
3
+ import EventEmitter from 'eventemitter3';
4
+
5
+ /**
6
+ * VeroAI Chat WebSocket Manager
7
+ *
8
+ * Handles WebSocket connection, reconnection, and message handling
9
+ */
10
+
11
+ interface WebSocketConfig {
12
+ url: string;
13
+ getToken: () => string | null | Promise<string | null>;
14
+ autoReconnect?: boolean;
15
+ reconnectInterval?: number;
16
+ maxReconnectAttempts?: number;
17
+ heartbeatInterval?: number;
18
+ }
19
+ type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
20
+ /**
21
+ * WebSocket connection manager with auto-reconnect
22
+ */
23
+ declare class WebSocketManager extends EventEmitter<ChatEvents> {
24
+ private config;
25
+ private ws;
26
+ private state;
27
+ private reconnectAttempts;
28
+ private reconnectTimer;
29
+ private heartbeatTimer;
30
+ private pendingMessages;
31
+ constructor(config: WebSocketConfig);
32
+ /**
33
+ * Get current connection state
34
+ */
35
+ getState(): ConnectionState;
36
+ /**
37
+ * Check if connected
38
+ */
39
+ isConnected(): boolean;
40
+ /**
41
+ * Connect to the WebSocket server
42
+ */
43
+ connect(): Promise<void>;
44
+ /**
45
+ * Disconnect from the WebSocket server
46
+ */
47
+ disconnect(): void;
48
+ /**
49
+ * Send a message through the WebSocket
50
+ */
51
+ send(type: string, payload: unknown): void;
52
+ /**
53
+ * Send typing indicator
54
+ */
55
+ sendTypingStart(conversationId: string): void;
56
+ /**
57
+ * Stop typing indicator
58
+ */
59
+ sendTypingStop(conversationId: string): void;
60
+ /**
61
+ * Subscribe to a conversation for real-time updates
62
+ */
63
+ subscribeToConversation(conversationId: string): void;
64
+ /**
65
+ * Unsubscribe from a conversation
66
+ */
67
+ unsubscribeFromConversation(conversationId: string): void;
68
+ /**
69
+ * Update presence status
70
+ */
71
+ updatePresence(status: string, statusMessage?: string): void;
72
+ /**
73
+ * Send call notification (ring, accept, reject, end)
74
+ * Note: Actual WebRTC signaling is handled by LiveKit
75
+ */
76
+ sendCallNotification(conversationId: string, action: CallAction, callType?: CallType, roomName?: string): void;
77
+ private handleClose;
78
+ private scheduleReconnect;
79
+ private handleMessage;
80
+ private flushPendingMessages;
81
+ private startHeartbeat;
82
+ private stopHeartbeat;
83
+ private clearTimers;
84
+ }
85
+
86
+ export { CallAction, CallType, ChatEvents, WebSocketManager };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { ChatApi, ChatClient, WebSocketManager } from './chunk-JI6KXOLF.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,187 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+ import { g as ChatClient, U as User, i as Conversation, o as PresenceStatus, h as ChatClientConfig, M as Message, S as SendMessageParams } from '../client-C63XKtNw.js';
4
+ import 'eventemitter3';
5
+
6
+ interface ChatContextValue {
7
+ /** The ChatClient instance */
8
+ client: ChatClient | null;
9
+ /** Whether the WebSocket is connected */
10
+ isConnected: boolean;
11
+ /** Current user profile */
12
+ currentUser: User | null;
13
+ /** List of conversations */
14
+ conversations: Conversation[];
15
+ /** Loading state for conversations */
16
+ isLoadingConversations: boolean;
17
+ /** Refresh conversations from server */
18
+ refreshConversations: () => Promise<void>;
19
+ /** Connect to WebSocket */
20
+ connect: () => Promise<void>;
21
+ /** Disconnect from WebSocket */
22
+ disconnect: () => void;
23
+ /** Update current user's status */
24
+ updateStatus: (status: PresenceStatus, statusMessage?: string) => Promise<void>;
25
+ }
26
+ interface ChatProviderProps {
27
+ children: React.ReactNode;
28
+ /** ChatClient configuration */
29
+ config: ChatClientConfig;
30
+ /** Auto-fetch conversations on mount */
31
+ autoFetchConversations?: boolean;
32
+ /** Auto-fetch current user on mount */
33
+ autoFetchCurrentUser?: boolean;
34
+ }
35
+ /**
36
+ * ChatProvider - Provides chat functionality to React components
37
+ *
38
+ * @example
39
+ * ```tsx
40
+ * function App() {
41
+ * return (
42
+ * <ChatProvider
43
+ * config={{
44
+ * apiUrl: 'https://api.veroai.dev',
45
+ * wsUrl: 'wss://ws.veroai.dev',
46
+ * token: authToken,
47
+ * }}
48
+ * >
49
+ * <ChatApp />
50
+ * </ChatProvider>
51
+ * );
52
+ * }
53
+ * ```
54
+ */
55
+ declare function ChatProvider({ children, config, autoFetchConversations, autoFetchCurrentUser, }: ChatProviderProps): react_jsx_runtime.JSX.Element;
56
+ /**
57
+ * useChat - Access chat context
58
+ *
59
+ * @example
60
+ * ```tsx
61
+ * function ChatList() {
62
+ * const { conversations, isConnected } = useChat();
63
+ * return <div>{conversations.map(c => <div key={c.id}>{c.name}</div>)}</div>;
64
+ * }
65
+ * ```
66
+ */
67
+ declare function useChat(): ChatContextValue;
68
+ /**
69
+ * useChatClient - Access the ChatClient instance directly
70
+ */
71
+ declare function useChatClient(): ChatClient | null;
72
+
73
+ /**
74
+ * useConversation Hook
75
+ *
76
+ * Manage a single conversation with real-time updates
77
+ */
78
+
79
+ interface UseConversationOptions {
80
+ /** Auto-fetch messages on mount */
81
+ autoFetchMessages?: boolean;
82
+ /** Number of messages to fetch initially */
83
+ initialMessageLimit?: number;
84
+ /** Auto-subscribe to real-time updates */
85
+ autoSubscribe?: boolean;
86
+ }
87
+ interface UseConversationReturn {
88
+ /** The conversation object */
89
+ conversation: Conversation | null;
90
+ /** Messages in the conversation */
91
+ messages: Message[];
92
+ /** Whether messages are loading */
93
+ isLoading: boolean;
94
+ /** Whether more messages are available */
95
+ hasMore: boolean;
96
+ /** Users currently typing */
97
+ typingUsers: string[];
98
+ /** Send a message */
99
+ sendMessage: (params: SendMessageParams) => Promise<Message>;
100
+ /** Send a text message (convenience) */
101
+ send: (content: string) => Promise<Message>;
102
+ /** Load more (older) messages */
103
+ loadMore: () => Promise<void>;
104
+ /** Refresh messages from server */
105
+ refresh: () => Promise<void>;
106
+ /** Mark conversation as read */
107
+ markAsRead: () => Promise<void>;
108
+ /** Start typing indicator */
109
+ startTyping: () => void;
110
+ /** Stop typing indicator */
111
+ stopTyping: () => void;
112
+ /** Error if any */
113
+ error: Error | null;
114
+ }
115
+ /**
116
+ * useConversation - Manage a single conversation
117
+ *
118
+ * @example
119
+ * ```tsx
120
+ * function ChatRoom({ conversationId }) {
121
+ * const {
122
+ * messages,
123
+ * send,
124
+ * isLoading,
125
+ * typingUsers,
126
+ * } = useConversation(conversationId);
127
+ *
128
+ * return (
129
+ * <div>
130
+ * {messages.map(m => <Message key={m.id} message={m} />)}
131
+ * {typingUsers.length > 0 && <div>Someone is typing...</div>}
132
+ * <input onKeyDown={(e) => e.key === 'Enter' && send(e.target.value)} />
133
+ * </div>
134
+ * );
135
+ * }
136
+ * ```
137
+ */
138
+ declare function useConversation(conversationId: string | undefined, options?: UseConversationOptions): UseConversationReturn;
139
+
140
+ /**
141
+ * usePresence Hook
142
+ *
143
+ * Track and manage user presence/online status
144
+ */
145
+
146
+ interface UsePresenceReturn {
147
+ /** Online users */
148
+ onlineUsers: User[];
149
+ /** Whether loading online users */
150
+ isLoading: boolean;
151
+ /** Get presence status for a specific user */
152
+ getUserStatus: (userId: string) => PresenceStatus;
153
+ /** Refresh online users list */
154
+ refresh: () => Promise<void>;
155
+ /** Error if any */
156
+ error: Error | null;
157
+ }
158
+ /**
159
+ * usePresence - Track online users and presence
160
+ *
161
+ * @example
162
+ * ```tsx
163
+ * function OnlineUsers() {
164
+ * const { onlineUsers, getUserStatus } = usePresence();
165
+ *
166
+ * return (
167
+ * <div>
168
+ * {onlineUsers.map(user => (
169
+ * <div key={user.id}>
170
+ * {user.firstName} - {getUserStatus(user.id)}
171
+ * </div>
172
+ * ))}
173
+ * </div>
174
+ * );
175
+ * }
176
+ * ```
177
+ */
178
+ declare function usePresence(): UsePresenceReturn;
179
+ /**
180
+ * useUserPresence - Track presence for a specific user
181
+ */
182
+ declare function useUserPresence(userId: string | undefined): {
183
+ status: PresenceStatus;
184
+ isOnline: boolean;
185
+ };
186
+
187
+ export { type ChatContextValue, ChatProvider, type ChatProviderProps, type UseConversationOptions, type UseConversationReturn, type UsePresenceReturn, useChat, useChatClient, useConversation, usePresence, useUserPresence };
@@ -0,0 +1,371 @@
1
+ import { ChatClient } from '../chunk-JI6KXOLF.js';
2
+ import { createContext, useState, useRef, useEffect, useCallback, useContext } from 'react';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ var ChatContext = createContext(null);
6
+ function ChatProvider({
7
+ children,
8
+ config,
9
+ autoFetchConversations = true,
10
+ autoFetchCurrentUser = true
11
+ }) {
12
+ const [client, setClient] = useState(null);
13
+ const [isConnected, setIsConnected] = useState(false);
14
+ const [currentUser, setCurrentUser] = useState(null);
15
+ const [conversations, setConversations] = useState([]);
16
+ const [isLoadingConversations, setIsLoadingConversations] = useState(false);
17
+ const clientRef = useRef(null);
18
+ useEffect(() => {
19
+ const chatClient = new ChatClient({
20
+ ...config,
21
+ autoConnect: false
22
+ // We'll connect manually to handle state
23
+ });
24
+ clientRef.current = chatClient;
25
+ setClient(chatClient);
26
+ chatClient.on("connected", () => setIsConnected(true));
27
+ chatClient.on("disconnected", () => setIsConnected(false));
28
+ chatClient.on("conversation:created", (conv) => {
29
+ setConversations((prev) => [conv, ...prev]);
30
+ });
31
+ chatClient.on("conversation:updated", (conv) => {
32
+ setConversations(
33
+ (prev) => prev.map((c) => c.id === conv.id ? conv : c)
34
+ );
35
+ });
36
+ chatClient.on("message:new", ({ conversationId }) => {
37
+ setConversations(
38
+ (prev) => prev.map(
39
+ (c) => c.id === conversationId ? { ...c, lastMessageAt: (/* @__PURE__ */ new Date()).toISOString() } : c
40
+ )
41
+ );
42
+ });
43
+ if (config.wsUrl && config.autoConnect !== false) {
44
+ chatClient.connect().catch(console.error);
45
+ }
46
+ return () => {
47
+ chatClient.disconnect();
48
+ chatClient.removeAllListeners();
49
+ };
50
+ }, [config.apiUrl, config.wsUrl, config.token]);
51
+ useEffect(() => {
52
+ if (!client || !autoFetchCurrentUser) return;
53
+ client.getCurrentUser().then(setCurrentUser).catch(console.error);
54
+ }, [client, autoFetchCurrentUser]);
55
+ const refreshConversations = useCallback(async () => {
56
+ if (!client) return;
57
+ setIsLoadingConversations(true);
58
+ try {
59
+ const convs = await client.listConversations();
60
+ setConversations(convs);
61
+ } catch (error) {
62
+ console.error("[ChatProvider] Failed to fetch conversations:", error);
63
+ } finally {
64
+ setIsLoadingConversations(false);
65
+ }
66
+ }, [client]);
67
+ useEffect(() => {
68
+ if (autoFetchConversations && client) {
69
+ refreshConversations();
70
+ }
71
+ }, [autoFetchConversations, client, refreshConversations]);
72
+ const connect = useCallback(async () => {
73
+ if (client) {
74
+ await client.connect();
75
+ }
76
+ }, [client]);
77
+ const disconnect = useCallback(() => {
78
+ client?.disconnect();
79
+ }, [client]);
80
+ const updateStatus = useCallback(
81
+ async (status, statusMessage) => {
82
+ if (client) {
83
+ await client.updateStatus(status, statusMessage);
84
+ setCurrentUser(
85
+ (prev) => prev ? { ...prev, status, statusMessage } : prev
86
+ );
87
+ }
88
+ },
89
+ [client]
90
+ );
91
+ const value = {
92
+ client,
93
+ isConnected,
94
+ currentUser,
95
+ conversations,
96
+ isLoadingConversations,
97
+ refreshConversations,
98
+ connect,
99
+ disconnect,
100
+ updateStatus
101
+ };
102
+ return /* @__PURE__ */ jsx(ChatContext.Provider, { value, children });
103
+ }
104
+ function useChat() {
105
+ const context = useContext(ChatContext);
106
+ if (!context) {
107
+ throw new Error("useChat must be used within a ChatProvider");
108
+ }
109
+ return context;
110
+ }
111
+ function useChatClient() {
112
+ const { client } = useChat();
113
+ return client;
114
+ }
115
+ function useConversation(conversationId, options = {}) {
116
+ const {
117
+ autoFetchMessages = true,
118
+ initialMessageLimit = 50,
119
+ autoSubscribe = true
120
+ } = options;
121
+ const { client, conversations } = useChat();
122
+ const [conversation, setConversation] = useState(null);
123
+ const [messages, setMessages] = useState([]);
124
+ const [isLoading, setIsLoading] = useState(false);
125
+ const [hasMore, setHasMore] = useState(true);
126
+ const [typingUsers, setTypingUsers] = useState([]);
127
+ const [error, setError] = useState(null);
128
+ useEffect(() => {
129
+ if (!conversationId) {
130
+ setConversation(null);
131
+ setMessages([]);
132
+ return;
133
+ }
134
+ const existing = conversations.find((c) => c.id === conversationId);
135
+ if (existing) {
136
+ setConversation(existing);
137
+ } else if (client) {
138
+ client.getConversation(conversationId).then(setConversation).catch((err) => {
139
+ setError(err);
140
+ console.error("[useConversation] Failed to fetch conversation:", err);
141
+ });
142
+ }
143
+ }, [conversationId, conversations, client]);
144
+ const fetchMessages = useCallback(async (before) => {
145
+ if (!client || !conversationId) return;
146
+ setIsLoading(true);
147
+ setError(null);
148
+ try {
149
+ const result = await client.getMessages(conversationId, {
150
+ limit: initialMessageLimit,
151
+ before
152
+ });
153
+ if (before) {
154
+ setMessages((prev) => [...prev, ...result.messages]);
155
+ } else {
156
+ setMessages(result.messages);
157
+ }
158
+ setHasMore(result.hasMore);
159
+ } catch (err) {
160
+ setError(err instanceof Error ? err : new Error(String(err)));
161
+ console.error("[useConversation] Failed to fetch messages:", err);
162
+ } finally {
163
+ setIsLoading(false);
164
+ }
165
+ }, [client, conversationId, initialMessageLimit]);
166
+ useEffect(() => {
167
+ if (autoFetchMessages && conversationId && client) {
168
+ fetchMessages();
169
+ }
170
+ }, [autoFetchMessages, conversationId, client, fetchMessages]);
171
+ useEffect(() => {
172
+ if (!client || !conversationId || !autoSubscribe) return;
173
+ client.subscribeToConversation(conversationId);
174
+ const handleNewMessage = ({ message, conversationId: convId }) => {
175
+ if (convId === conversationId) {
176
+ setMessages((prev) => [message, ...prev]);
177
+ }
178
+ };
179
+ const handleMessageUpdated = (message) => {
180
+ if (message.conversationId === conversationId) {
181
+ setMessages(
182
+ (prev) => prev.map((m) => m.id === message.id ? message : m)
183
+ );
184
+ }
185
+ };
186
+ const handleMessageDeleted = (messageId, convId) => {
187
+ if (convId === conversationId) {
188
+ setMessages((prev) => prev.filter((m) => m.id !== messageId));
189
+ }
190
+ };
191
+ const handleTypingStart = ({ conversationId: convId, userId }) => {
192
+ if (convId === conversationId) {
193
+ setTypingUsers(
194
+ (prev) => prev.includes(userId) ? prev : [...prev, userId]
195
+ );
196
+ }
197
+ };
198
+ const handleTypingStop = ({ conversationId: convId, userId }) => {
199
+ if (convId === conversationId) {
200
+ setTypingUsers((prev) => prev.filter((id) => id !== userId));
201
+ }
202
+ };
203
+ client.on("message:new", handleNewMessage);
204
+ client.on("message:updated", handleMessageUpdated);
205
+ client.on("message:deleted", handleMessageDeleted);
206
+ client.on("typing:start", handleTypingStart);
207
+ client.on("typing:stop", handleTypingStop);
208
+ return () => {
209
+ client.unsubscribeFromConversation(conversationId);
210
+ client.off("message:new", handleNewMessage);
211
+ client.off("message:updated", handleMessageUpdated);
212
+ client.off("message:deleted", handleMessageDeleted);
213
+ client.off("typing:start", handleTypingStart);
214
+ client.off("typing:stop", handleTypingStop);
215
+ };
216
+ }, [client, conversationId, autoSubscribe]);
217
+ const sendMessage = useCallback(
218
+ async (params) => {
219
+ if (!client || !conversationId) {
220
+ throw new Error("No conversation selected");
221
+ }
222
+ return client.sendMessage(conversationId, params);
223
+ },
224
+ [client, conversationId]
225
+ );
226
+ const send = useCallback(
227
+ async (content) => {
228
+ return sendMessage({ content });
229
+ },
230
+ [sendMessage]
231
+ );
232
+ const loadMore = useCallback(async () => {
233
+ if (!hasMore || isLoading || messages.length === 0) return;
234
+ const oldestMessage = messages[messages.length - 1];
235
+ await fetchMessages(oldestMessage.createdAt);
236
+ }, [hasMore, isLoading, messages, fetchMessages]);
237
+ const refresh = useCallback(async () => {
238
+ setMessages([]);
239
+ setHasMore(true);
240
+ await fetchMessages();
241
+ }, [fetchMessages]);
242
+ const markAsRead = useCallback(async () => {
243
+ if (!client || !conversationId) return;
244
+ await client.markConversationRead(conversationId);
245
+ }, [client, conversationId]);
246
+ const startTyping = useCallback(() => {
247
+ if (conversationId) {
248
+ client?.sendTypingStart(conversationId);
249
+ }
250
+ }, [client, conversationId]);
251
+ const stopTyping = useCallback(() => {
252
+ if (conversationId) {
253
+ client?.sendTypingStop(conversationId);
254
+ }
255
+ }, [client, conversationId]);
256
+ return {
257
+ conversation,
258
+ messages,
259
+ isLoading,
260
+ hasMore,
261
+ typingUsers,
262
+ sendMessage,
263
+ send,
264
+ loadMore,
265
+ refresh,
266
+ markAsRead,
267
+ startTyping,
268
+ stopTyping,
269
+ error
270
+ };
271
+ }
272
+ function usePresence() {
273
+ const { client } = useChat();
274
+ const [onlineUsers, setOnlineUsers] = useState([]);
275
+ const [isLoading, setIsLoading] = useState(false);
276
+ const [error, setError] = useState(null);
277
+ const [presenceMap, setPresenceMap] = useState(/* @__PURE__ */ new Map());
278
+ const refresh = useCallback(async () => {
279
+ if (!client) return;
280
+ setIsLoading(true);
281
+ setError(null);
282
+ try {
283
+ const users = await client.getOnlineUsers();
284
+ setOnlineUsers(users);
285
+ const newMap = /* @__PURE__ */ new Map();
286
+ users.forEach((user) => {
287
+ if (user.status) {
288
+ newMap.set(user.id, user.status);
289
+ }
290
+ });
291
+ setPresenceMap(newMap);
292
+ } catch (err) {
293
+ setError(err instanceof Error ? err : new Error(String(err)));
294
+ console.error("[usePresence] Failed to fetch online users:", err);
295
+ } finally {
296
+ setIsLoading(false);
297
+ }
298
+ }, [client]);
299
+ useEffect(() => {
300
+ if (client) {
301
+ refresh();
302
+ }
303
+ }, [client, refresh]);
304
+ useEffect(() => {
305
+ if (!client) return;
306
+ const handlePresenceUpdate = ({ userId, status }) => {
307
+ setPresenceMap((prev) => {
308
+ const newMap = new Map(prev);
309
+ newMap.set(userId, status);
310
+ return newMap;
311
+ });
312
+ if (status === "offline") {
313
+ setOnlineUsers((prev) => prev.filter((u) => u.id !== userId));
314
+ } else {
315
+ setOnlineUsers((prev) => {
316
+ const exists = prev.some((u) => u.id === userId);
317
+ if (!exists) {
318
+ refresh();
319
+ }
320
+ return prev.map(
321
+ (u) => u.id === userId ? { ...u, status } : u
322
+ );
323
+ });
324
+ }
325
+ };
326
+ client.on("presence:updated", handlePresenceUpdate);
327
+ return () => {
328
+ client.off("presence:updated", handlePresenceUpdate);
329
+ };
330
+ }, [client, refresh]);
331
+ const getUserStatus = useCallback(
332
+ (userId) => {
333
+ return presenceMap.get(userId) || "offline";
334
+ },
335
+ [presenceMap]
336
+ );
337
+ return {
338
+ onlineUsers,
339
+ isLoading,
340
+ getUserStatus,
341
+ refresh,
342
+ error
343
+ };
344
+ }
345
+ function useUserPresence(userId) {
346
+ const { client } = useChat();
347
+ const [status, setStatus] = useState("offline");
348
+ useEffect(() => {
349
+ if (!client || !userId) return;
350
+ client.getUser(userId).then((user) => {
351
+ setStatus(user.status || "offline");
352
+ }).catch(console.error);
353
+ const handlePresenceUpdate = ({ userId: eventUserId, status: newStatus }) => {
354
+ if (eventUserId === userId) {
355
+ setStatus(newStatus);
356
+ }
357
+ };
358
+ client.on("presence:updated", handlePresenceUpdate);
359
+ return () => {
360
+ client.off("presence:updated", handlePresenceUpdate);
361
+ };
362
+ }, [client, userId]);
363
+ return {
364
+ status,
365
+ isOnline: status !== "offline"
366
+ };
367
+ }
368
+
369
+ export { ChatProvider, useChat, useChatClient, useConversation, usePresence, useUserPresence };
370
+ //# sourceMappingURL=index.js.map
371
+ //# sourceMappingURL=index.js.map