@enfin/chat 1.2.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.
Files changed (73) hide show
  1. package/README.md +224 -0
  2. package/dist/assets/music/i_phone_message.mp3 +0 -0
  3. package/dist/call/WebRTCManager.d.ts +54 -0
  4. package/dist/call/WebRTCManager.js +274 -0
  5. package/dist/call/signaling.d.ts +3 -0
  6. package/dist/call/signaling.js +24 -0
  7. package/dist/call/types.d.ts +25 -0
  8. package/dist/call/types.js +13 -0
  9. package/dist/components/Chat.d.ts +14 -0
  10. package/dist/components/Chat.js +452 -0
  11. package/dist/components/index.d.ts +3 -0
  12. package/dist/components/index.js +2 -0
  13. package/dist/context/ChatContext.d.ts +59 -0
  14. package/dist/context/ChatContext.js +647 -0
  15. package/dist/esm/call/WebRTCManager.d.ts +54 -0
  16. package/dist/esm/call/WebRTCManager.js +274 -0
  17. package/dist/esm/call/signaling.d.ts +3 -0
  18. package/dist/esm/call/signaling.js +24 -0
  19. package/dist/esm/call/types.d.ts +25 -0
  20. package/dist/esm/call/types.js +13 -0
  21. package/dist/esm/components/Chat.d.ts +14 -0
  22. package/dist/esm/components/Chat.js +452 -0
  23. package/dist/esm/components/index.d.ts +3 -0
  24. package/dist/esm/components/index.js +2 -0
  25. package/dist/esm/context/ChatContext.d.ts +59 -0
  26. package/dist/esm/context/ChatContext.js +647 -0
  27. package/dist/esm/hooks/index.d.ts +6 -0
  28. package/dist/esm/hooks/index.js +6 -0
  29. package/dist/esm/hooks/useCall.d.ts +15 -0
  30. package/dist/esm/hooks/useCall.js +38 -0
  31. package/dist/esm/hooks/useChat.d.ts +23 -0
  32. package/dist/esm/hooks/useChat.js +40 -0
  33. package/dist/esm/hooks/useMessages.d.ts +17 -0
  34. package/dist/esm/hooks/useMessages.js +38 -0
  35. package/dist/esm/hooks/usePresence.d.ts +4 -0
  36. package/dist/esm/hooks/usePresence.js +12 -0
  37. package/dist/esm/hooks/useRooms.d.ts +9 -0
  38. package/dist/esm/hooks/useRooms.js +19 -0
  39. package/dist/esm/hooks/useSocket.d.ts +7 -0
  40. package/dist/esm/hooks/useSocket.js +92 -0
  41. package/dist/esm/index.d.ts +11 -0
  42. package/dist/esm/index.js +15 -0
  43. package/dist/esm/utils/index.d.ts +2 -0
  44. package/dist/esm/utils/index.js +2 -0
  45. package/dist/esm/utils/ringtone.d.ts +2 -0
  46. package/dist/esm/utils/ringtone.js +39 -0
  47. package/dist/esm/utils/testUtils.d.ts +102 -0
  48. package/dist/esm/utils/testUtils.js +153 -0
  49. package/dist/hooks/index.d.ts +6 -0
  50. package/dist/hooks/index.js +6 -0
  51. package/dist/hooks/useCall.d.ts +15 -0
  52. package/dist/hooks/useCall.js +38 -0
  53. package/dist/hooks/useChat.d.ts +23 -0
  54. package/dist/hooks/useChat.js +40 -0
  55. package/dist/hooks/useMessages.d.ts +17 -0
  56. package/dist/hooks/useMessages.js +38 -0
  57. package/dist/hooks/usePresence.d.ts +4 -0
  58. package/dist/hooks/usePresence.js +12 -0
  59. package/dist/hooks/useRooms.d.ts +9 -0
  60. package/dist/hooks/useRooms.js +19 -0
  61. package/dist/hooks/useSocket.d.ts +7 -0
  62. package/dist/hooks/useSocket.js +92 -0
  63. package/dist/index.d.ts +11 -0
  64. package/dist/index.js +15 -0
  65. package/dist/public/music/i_phone_message.mp3 +0 -0
  66. package/dist/style.css +1226 -0
  67. package/dist/utils/index.d.ts +2 -0
  68. package/dist/utils/index.js +2 -0
  69. package/dist/utils/ringtone.d.ts +2 -0
  70. package/dist/utils/ringtone.js +39 -0
  71. package/dist/utils/testUtils.d.ts +102 -0
  72. package/dist/utils/testUtils.js +153 -0
  73. package/package.json +44 -0
@@ -0,0 +1,38 @@
1
+ import { useCallback } from 'react';
2
+ import { useChatContext } from '../context/ChatContext';
3
+ export function useCall() {
4
+ const { activeCall, callState, callError, remoteStream, isMuted, busyBanner, clearBusyBanner, startCall, endCall, acceptCall, rejectCall, mute, unmute, } = useChatContext();
5
+ const handleStartCall = useCallback(async (roomId, participantId) => {
6
+ await startCall(roomId, participantId);
7
+ }, [startCall]);
8
+ const handleEndCall = useCallback(async () => {
9
+ await endCall();
10
+ }, [endCall]);
11
+ const handleAcceptCall = useCallback(async () => {
12
+ await acceptCall();
13
+ }, [acceptCall]);
14
+ const handleRejectCall = useCallback(async () => {
15
+ await rejectCall();
16
+ }, [rejectCall]);
17
+ const handleMute = useCallback(() => {
18
+ mute();
19
+ }, [mute]);
20
+ const handleUnmute = useCallback(() => {
21
+ unmute();
22
+ }, [unmute]);
23
+ return {
24
+ activeCall,
25
+ callState,
26
+ callError,
27
+ remoteStream,
28
+ busyBanner,
29
+ clearBusyBanner,
30
+ startCall: handleStartCall,
31
+ endCall: handleEndCall,
32
+ acceptCall: handleAcceptCall,
33
+ rejectCall: handleRejectCall,
34
+ mute: handleMute,
35
+ unmute: handleUnmute,
36
+ isMuted,
37
+ };
38
+ }
@@ -0,0 +1,23 @@
1
+ export declare function useChat(): {
2
+ userId: string;
3
+ userName: string;
4
+ messages: import("@enfin/chat-shared").Message[];
5
+ rooms: import("@enfin/chat-shared").Room[];
6
+ users: import("../context/ChatContext").ChatUser[];
7
+ currentRoom: import("@enfin/chat-shared").Room;
8
+ typingUsers: import("@enfin/chat-shared").PresenceStatus[];
9
+ connected: boolean;
10
+ loading: boolean;
11
+ sending: boolean;
12
+ error: string;
13
+ sendMessage: (roomId: string, content: string, fileMetadata?: {
14
+ fileUrl?: string;
15
+ fileType?: string;
16
+ fileName?: string;
17
+ }) => Promise<void>;
18
+ selectRoom: (roomId: string) => void;
19
+ refreshUsers: () => Promise<void>;
20
+ emitTyping: (roomId: string, typing: boolean) => void;
21
+ debouncedEmitTyping: (roomId: string, typing: boolean) => void;
22
+ stopTypingSoon: (roomId: string) => void;
23
+ };
@@ -0,0 +1,40 @@
1
+ import { useState, useCallback } from 'react';
2
+ import { useChatContext } from '../context/ChatContext';
3
+ export function useChat() {
4
+ const { userId, userName, messages, rooms, users, currentRoom, typingUsers, connected, loading, error, sendMessage, selectRoom, refreshUsers, emitTyping, debouncedEmitTyping, stopTypingSoon, } = useChatContext();
5
+ const [sending, setSending] = useState(false);
6
+ const handleSendMessage = useCallback(async (roomId, content, fileMetadata) => {
7
+ setSending(true);
8
+ try {
9
+ await sendMessage(roomId, content, fileMetadata);
10
+ }
11
+ finally {
12
+ setSending(false);
13
+ }
14
+ }, [sendMessage]);
15
+ const handleSelectRoom = useCallback((roomId) => {
16
+ selectRoom(roomId);
17
+ }, [selectRoom]);
18
+ const filteredMessages = currentRoom
19
+ ? messages.filter(m => m.roomId === currentRoom._id)
20
+ : [];
21
+ return {
22
+ userId,
23
+ userName,
24
+ messages: filteredMessages,
25
+ rooms,
26
+ users,
27
+ currentRoom,
28
+ typingUsers,
29
+ connected,
30
+ loading,
31
+ sending,
32
+ error,
33
+ sendMessage: handleSendMessage,
34
+ selectRoom: handleSelectRoom,
35
+ refreshUsers,
36
+ emitTyping,
37
+ debouncedEmitTyping,
38
+ stopTypingSoon,
39
+ };
40
+ }
@@ -0,0 +1,17 @@
1
+ import { Message } from '@enfin/chat-shared';
2
+ interface UseMessagesOptions {
3
+ roomId: string;
4
+ limit?: number;
5
+ autoFetch?: boolean;
6
+ }
7
+ export declare function useMessages(options: UseMessagesOptions): {
8
+ messages: Message[];
9
+ loading: boolean;
10
+ currentRoom: import("@enfin/chat-shared").Room;
11
+ sendMessage: (content: string, fileMetadata?: {
12
+ fileUrl?: string;
13
+ fileType?: string;
14
+ fileName?: string;
15
+ }) => Promise<void>;
16
+ };
17
+ export {};
@@ -0,0 +1,38 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ import { useChatContext } from '../context/ChatContext';
3
+ export function useMessages(options) {
4
+ const { messages: allMessages, currentRoom, loading, sendMessage, loadRoomMessages, connected, } = useChatContext();
5
+ const { roomId: targetRoomId, autoFetch = true, } = options;
6
+ // Live updates: filter messages for the target room. Since the SDK keeps
7
+ // a single shared message state in context, any consumer of useMessages
8
+ // automatically receives new messages from socket `message:new` events.
9
+ const roomMessages = allMessages.filter(m => m.roomId === targetRoomId);
10
+ // Track whether we've already fetched the initial history for this room.
11
+ const fetchedForRoomRef = useRef(null);
12
+ useEffect(() => {
13
+ if (!autoFetch || !connected)
14
+ return;
15
+ if (fetchedForRoomRef.current === targetRoomId)
16
+ return;
17
+ fetchedForRoomRef.current = targetRoomId;
18
+ loadRoomMessages(targetRoomId);
19
+ }, [autoFetch, connected, targetRoomId, loadRoomMessages]);
20
+ // Reset the fetched-marker when the roomId changes so refetch happens.
21
+ useEffect(() => {
22
+ return () => {
23
+ fetchedForRoomRef.current = null;
24
+ };
25
+ }, [targetRoomId]);
26
+ const handleSend = useCallback(async (content, fileMetadata) => {
27
+ if (!currentRoom || currentRoom._id !== targetRoomId) {
28
+ throw new Error('Cannot send message to a different room');
29
+ }
30
+ await sendMessage(targetRoomId, content, fileMetadata);
31
+ }, [currentRoom, targetRoomId, sendMessage]);
32
+ return {
33
+ messages: roomMessages,
34
+ loading,
35
+ currentRoom: currentRoom?._id === targetRoomId ? currentRoom : undefined,
36
+ sendMessage: handleSend,
37
+ };
38
+ }
@@ -0,0 +1,4 @@
1
+ export declare function usePresence(): {
2
+ users: import("../context/ChatContext").ChatUser[];
3
+ refreshUsers: () => Promise<void>;
4
+ };
@@ -0,0 +1,12 @@
1
+ import { useCallback } from 'react';
2
+ import { useChatContext } from '../context/ChatContext';
3
+ export function usePresence() {
4
+ const { users, refreshUsers } = useChatContext();
5
+ const handleRefreshUsers = useCallback(async () => {
6
+ await refreshUsers();
7
+ }, [refreshUsers]);
8
+ return {
9
+ users,
10
+ refreshUsers: handleRefreshUsers,
11
+ };
12
+ }
@@ -0,0 +1,9 @@
1
+ import { Room } from '@enfin/chat-shared';
2
+ export declare function useRooms(): {
3
+ rooms: Room[];
4
+ currentRoom: Room;
5
+ loading: boolean;
6
+ connected: boolean;
7
+ createRoom: (name: string, type: "direct" | "group", members: string[]) => Promise<Room>;
8
+ selectRoom: (roomId: string) => void;
9
+ };
@@ -0,0 +1,19 @@
1
+ import { useCallback } from 'react';
2
+ import { useChatContext } from '../context/ChatContext';
3
+ export function useRooms() {
4
+ const { rooms, currentRoom, loading, connected, createRoom, selectRoom, } = useChatContext();
5
+ const handleCreateRoom = useCallback(async (name, type, members) => {
6
+ return createRoom(name, type, members);
7
+ }, [createRoom]);
8
+ const handleSelectRoom = useCallback((roomId) => {
9
+ selectRoom(roomId);
10
+ }, [selectRoom]);
11
+ return {
12
+ rooms,
13
+ currentRoom,
14
+ loading,
15
+ connected,
16
+ createRoom: handleCreateRoom,
17
+ selectRoom: handleSelectRoom,
18
+ };
19
+ }
@@ -0,0 +1,7 @@
1
+ export declare function useSocket(): {
2
+ emit: <T = any>(event: string, data?: T) => Promise<unknown>;
3
+ on: (event: string, callback: (data: any) => void) => () => void;
4
+ off: (event: string, callback?: (data: any) => void) => void;
5
+ onAny: (callback: (event: string, ...args: any[]) => void) => () => void;
6
+ connected: any;
7
+ };
@@ -0,0 +1,92 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { useChatContext } from '../context/ChatContext';
3
+ export function useSocket() {
4
+ const { socket } = useChatContext();
5
+ const [socketState, setSocketState] = useState(socket);
6
+ const cleanupRefs = useRef(new Map());
7
+ const callbackIdRef = useRef(0);
8
+ // Keep the live socket ref in sync
9
+ useEffect(() => {
10
+ setSocketState(socket);
11
+ return () => {
12
+ // Cleanup any listeners when socket changes
13
+ cleanupRefs.current.forEach(cleanup => cleanup());
14
+ cleanupRefs.current.clear();
15
+ };
16
+ }, [socket]);
17
+ const emit = useCallback((event, data) => {
18
+ if (!socketState?.connected) {
19
+ console.warn(`[useSocket] Cannot emit '${event}' — socket not connected`);
20
+ return Promise.resolve();
21
+ }
22
+ return new Promise((resolve, reject) => {
23
+ socketState.emit(event, data, (err, ...args) => {
24
+ if (err)
25
+ reject(err);
26
+ else
27
+ resolve(args[0]);
28
+ });
29
+ });
30
+ }, [socketState]);
31
+ const on = useCallback((event, callback) => {
32
+ if (!socketState?.connected) {
33
+ console.warn(`[useSocket] Cannot listen on '${event}' — socket not connected`);
34
+ return () => { };
35
+ }
36
+ // Generate a unique ID for this listener
37
+ const listenerId = `${event}-${callbackIdRef.current++}`;
38
+ const wrappedCallback = (data) => {
39
+ callback(data);
40
+ };
41
+ // Add the listener
42
+ socketState.on(event, wrappedCallback);
43
+ // Store cleanup function
44
+ const cleanup = () => {
45
+ socketState.off(event, wrappedCallback);
46
+ cleanupRefs.current.delete(listenerId);
47
+ };
48
+ cleanupRefs.current.set(listenerId, cleanup);
49
+ // Return cleanup function
50
+ return cleanup;
51
+ }, [socketState]);
52
+ const off = useCallback((event, callback) => {
53
+ if (!socketState?.connected) {
54
+ console.warn(`[useSocket] Cannot unlisten from '${event}' — socket not connected`);
55
+ return;
56
+ }
57
+ if (callback) {
58
+ // If callback is provided, remove it specifically
59
+ socketState.off(event, callback);
60
+ // Also remove any related cleanup refs
61
+ cleanupRefs.current.forEach((cleanup, key) => {
62
+ if (key.startsWith(`${event}-`))
63
+ cleanup();
64
+ });
65
+ }
66
+ else {
67
+ // Remove all listeners for this event
68
+ socketState.removeAllListeners(event);
69
+ cleanupRefs.current.forEach((cleanup, key) => {
70
+ if (key.startsWith(`${event}-`))
71
+ cleanup();
72
+ });
73
+ }
74
+ }, [socketState]);
75
+ // Helper for listening to all socket events via window events
76
+ const onAny = useCallback((callback) => {
77
+ const handler = (e) => {
78
+ callback(e.detail.event, e.detail.data);
79
+ };
80
+ window.addEventListener('chat:socket-event', handler);
81
+ return () => {
82
+ window.removeEventListener('chat:socket-event', handler);
83
+ };
84
+ }, []);
85
+ return {
86
+ emit,
87
+ on,
88
+ off,
89
+ onAny,
90
+ connected: socketState?.connected || false,
91
+ };
92
+ }
@@ -0,0 +1,11 @@
1
+ export * from '@enfin/chat-shared';
2
+ export { ChatProvider, useChatContext } from './context/ChatContext';
3
+ export type { ChatUser } from './context/ChatContext';
4
+ export { useChat, useRooms, useMessages, useCall, useSocket, usePresence } from './hooks';
5
+ import { Chat } from './components';
6
+ export { Chat };
7
+ export type { ChatProps } from './components';
8
+ export default Chat;
9
+ export * from './utils';
10
+ export type { Message, Room, PresenceStatus, AudioCall, ChatConfig, } from '@enfin/chat-shared';
11
+ export declare const SDK_VERSION = "1.0.0";
@@ -0,0 +1,15 @@
1
+ // @enfin/chat - React Frontend SDK
2
+ // ============================================
3
+ export * from '@enfin/chat-shared';
4
+ // Context & Provider
5
+ export { ChatProvider, useChatContext } from './context/ChatContext';
6
+ // Hooks
7
+ export { useChat, useRooms, useMessages, useCall, useSocket, usePresence } from './hooks';
8
+ // Default UI Components
9
+ import { Chat } from './components';
10
+ export { Chat };
11
+ export default Chat;
12
+ // Utilities (including test utilities)
13
+ export * from './utils';
14
+ // SDK Version
15
+ export const SDK_VERSION = '1.0.0';
@@ -0,0 +1,2 @@
1
+ export * from './testUtils';
2
+ export * from './ringtone';
@@ -0,0 +1,2 @@
1
+ export * from './testUtils';
2
+ export * from './ringtone';
@@ -0,0 +1,2 @@
1
+ export declare function startRingtone(url?: string): void;
2
+ export declare function stopRingtone(): void;
@@ -0,0 +1,39 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
2
+ import defaultRingtoneUrl from '../assets/music/i_phone_message.mp3';
3
+ let audioEl = null;
4
+ let currentUrl = null;
5
+ function getAudio(url) {
6
+ const target = url || defaultRingtoneUrl;
7
+ if (!audioEl || currentUrl !== target) {
8
+ if (audioEl) {
9
+ audioEl.pause();
10
+ audioEl.src = '';
11
+ }
12
+ audioEl = new Audio(target);
13
+ audioEl.loop = true;
14
+ audioEl.preload = 'auto';
15
+ currentUrl = target;
16
+ }
17
+ return audioEl;
18
+ }
19
+ export function startRingtone(url) {
20
+ try {
21
+ const audio = getAudio(url);
22
+ audio.currentTime = 0;
23
+ const playPromise = audio.play();
24
+ if (playPromise && typeof playPromise.catch === 'function') {
25
+ playPromise.catch((err) => {
26
+ console.log('[RINGTONE] play() rejected:', err?.message || err);
27
+ });
28
+ }
29
+ }
30
+ catch (err) {
31
+ console.log('[RINGTONE] start failed:', err?.message || err);
32
+ }
33
+ }
34
+ export function stopRingtone() {
35
+ if (!audioEl)
36
+ return;
37
+ audioEl.pause();
38
+ audioEl.currentTime = 0;
39
+ }
@@ -0,0 +1,102 @@
1
+ import { Socket } from 'socket.io-client';
2
+ /**
3
+ * Test utilities for developers to create test users and simulate interactions
4
+ *
5
+ * This utility allows developers to:
6
+ * 1. Create multiple test users that can chat with each other
7
+ * 2. Send messages between test users
8
+ * 3. Make audio calls between test users
9
+ * 4. Test custom socket events
10
+ */
11
+ export interface TestUser {
12
+ id: string;
13
+ name: string;
14
+ socket: Socket;
15
+ rooms: string[];
16
+ }
17
+ /**
18
+ * Create a test user with automatic socket connection
19
+ *
20
+ * @param serverUrl - Chat server URL
21
+ * @param userId - User ID (optional, generated if not provided)
22
+ * @param userName - User name
23
+ * @returns Test user object with socket connection
24
+ */
25
+ export declare function createTestUser(serverUrl: string, userName: string, userId?: string): TestUser;
26
+ /**
27
+ * Create multiple test users for testing
28
+ *
29
+ * @param serverUrl - Chat server URL
30
+ * @param userNames - Array of user names
31
+ * @returns Array of test users
32
+ */
33
+ export declare function createTestUsers(serverUrl: string, userNames: string[]): TestUser[];
34
+ /**
35
+ * Create a test room between users
36
+ *
37
+ * @param users - Users to add to the room
38
+ * @param roomName - Room name
39
+ * @param isDirect - Whether this is a direct chat
40
+ * @returns Promise resolving to the created room
41
+ */
42
+ export declare function createTestRoom(users: TestUser[], roomName: string, isDirect?: boolean): Promise<any>;
43
+ /**
44
+ * Send a message from one user to another
45
+ *
46
+ * @param from - Sending user
47
+ * @param roomId - Room ID
48
+ * @param content - Message content
49
+ * @returns Promise resolving when message is sent
50
+ */
51
+ export declare function sendMessage(from: TestUser, roomId: string, content: string): Promise<any>;
52
+ /**
53
+ * Start an audio call
54
+ *
55
+ * @param from - Initiating user
56
+ * @param to - Target user
57
+ * @param roomId - Room ID
58
+ * @returns Promise resolving when call is initiated
59
+ */
60
+ export declare function startCall(from: TestUser, to: TestUser, roomId: string): Promise<any>;
61
+ /**
62
+ * Accept an incoming call
63
+ *
64
+ * @param to - User accepting call
65
+ * @param callId - Call ID
66
+ * @returns Promise resolving when call is accepted
67
+ */
68
+ export declare function acceptCall(to: TestUser, callId: string): Promise<any>;
69
+ /**
70
+ * End a call
71
+ *
72
+ * @param by - User ending call
73
+ * @param callId - Call ID
74
+ * @returns Promise resolving when call is ended
75
+ */
76
+ export declare function endCall(by: TestUser, callId: string): Promise<any>;
77
+ /**
78
+ * Listen for socket events (useful for testing)
79
+ *
80
+ * @param socket - Socket to listen on
81
+ * @param event - Event name
82
+ * @param callback - Callback function
83
+ * @returns Cleanup function to remove listener
84
+ */
85
+ export declare function onSocketEvent(socket: Socket, event: string, callback: (data: any) => void): () => void;
86
+ /**
87
+ * Listen to custom socket events (exposed by ChatProvider)
88
+ *
89
+ * @param eventName - Event name to listen to
90
+ * @param callback - Callback function
91
+ * @returns Cleanup function
92
+ */
93
+ export declare function onCustomSocketEvent(eventName: string, callback: (data: any) => void): () => void;
94
+ /**
95
+ * Emit custom event through the socket
96
+ *
97
+ * @param user - User to emit from
98
+ * @param event - Event name
99
+ * @param data - Event data
100
+ * @returns Promise resolving when event is sent
101
+ */
102
+ export declare function emitCustomEvent(user: TestUser, event: string, data?: any): Promise<any>;
@@ -0,0 +1,153 @@
1
+ import io from 'socket.io-client';
2
+ /**
3
+ * Create a test user with automatic socket connection
4
+ *
5
+ * @param serverUrl - Chat server URL
6
+ * @param userId - User ID (optional, generated if not provided)
7
+ * @param userName - User name
8
+ * @returns Test user object with socket connection
9
+ */
10
+ export function createTestUser(serverUrl, userName, userId) {
11
+ const id = userId || `user_${Math.random().toString(36).substr(2, 9)}`;
12
+ const socket = io(`${serverUrl}/chat`, {
13
+ auth: { userId: id, userName, tenantId: id },
14
+ transports: ['websocket'],
15
+ });
16
+ return {
17
+ id,
18
+ name: userName,
19
+ socket,
20
+ rooms: [],
21
+ };
22
+ }
23
+ /**
24
+ * Create multiple test users for testing
25
+ *
26
+ * @param serverUrl - Chat server URL
27
+ * @param userNames - Array of user names
28
+ * @returns Array of test users
29
+ */
30
+ export function createTestUsers(serverUrl, userNames) {
31
+ return userNames.map((name, index) => {
32
+ const userId = `test_user_${String(index + 1).padStart(2, '0')}`;
33
+ return createTestUser(serverUrl, name, userId);
34
+ });
35
+ }
36
+ /**
37
+ * Create a test room between users
38
+ *
39
+ * @param users - Users to add to the room
40
+ * @param roomName - Room name
41
+ * @param isDirect - Whether this is a direct chat
42
+ * @returns Promise resolving to the created room
43
+ */
44
+ export async function createTestRoom(users, roomName, isDirect = false) {
45
+ const roomId = `room_${Date.now()}`;
46
+ if (isDirect && users.length !== 2) {
47
+ throw new Error('Direct rooms require exactly 2 users');
48
+ }
49
+ // Create room (first user creates it)
50
+ await users[0].socket.emitWithAck('room:create', {
51
+ name: roomName,
52
+ type: isDirect ? 'direct' : 'group',
53
+ members: users.map(u => u.id),
54
+ });
55
+ // Wait a bit for room to propagate
56
+ await new Promise(resolve => setTimeout(resolve, 100));
57
+ // Join all users to the room
58
+ for (const user of users) {
59
+ await user.socket.emitWithAck('join', { roomId: roomId });
60
+ user.rooms.push(roomId);
61
+ }
62
+ return { id: roomId, name: roomName, type: isDirect ? 'direct' : 'group' };
63
+ }
64
+ /**
65
+ * Send a message from one user to another
66
+ *
67
+ * @param from - Sending user
68
+ * @param roomId - Room ID
69
+ * @param content - Message content
70
+ * @returns Promise resolving when message is sent
71
+ */
72
+ export async function sendMessage(from, roomId, content) {
73
+ return from.socket.emitWithAck('message:send', {
74
+ roomId,
75
+ content,
76
+ });
77
+ }
78
+ /**
79
+ * Start an audio call
80
+ *
81
+ * @param from - Initiating user
82
+ * @param to - Target user
83
+ * @param roomId - Room ID
84
+ * @returns Promise resolving when call is initiated
85
+ */
86
+ export async function startCall(from, to, roomId) {
87
+ return from.socket.emitWithAck('call:initiate', {
88
+ roomId,
89
+ participantId: to.id,
90
+ });
91
+ }
92
+ /**
93
+ * Accept an incoming call
94
+ *
95
+ * @param to - User accepting call
96
+ * @param callId - Call ID
97
+ * @returns Promise resolving when call is accepted
98
+ */
99
+ export async function acceptCall(to, callId) {
100
+ return to.socket.emitWithAck('call:accept', { callId });
101
+ }
102
+ /**
103
+ * End a call
104
+ *
105
+ * @param by - User ending call
106
+ * @param callId - Call ID
107
+ * @returns Promise resolving when call is ended
108
+ */
109
+ export async function endCall(by, callId) {
110
+ return by.socket.emitWithAck('call:end', { callId });
111
+ }
112
+ /**
113
+ * Listen for socket events (useful for testing)
114
+ *
115
+ * @param socket - Socket to listen on
116
+ * @param event - Event name
117
+ * @param callback - Callback function
118
+ * @returns Cleanup function to remove listener
119
+ */
120
+ export function onSocketEvent(socket, event, callback) {
121
+ socket.on(event, callback);
122
+ return () => socket.off(event, callback);
123
+ }
124
+ /**
125
+ * Listen to custom socket events (exposed by ChatProvider)
126
+ *
127
+ * @param eventName - Event name to listen to
128
+ * @param callback - Callback function
129
+ * @returns Cleanup function
130
+ */
131
+ export function onCustomSocketEvent(eventName, callback) {
132
+ const handler = (event) => {
133
+ if (event.detail.event === eventName) {
134
+ callback(event.detail.data);
135
+ }
136
+ };
137
+ window.addEventListener('chat:socket-event', handler);
138
+ return () => window.removeEventListener('chat:socket-event', handler);
139
+ }
140
+ /**
141
+ * Emit custom event through the socket
142
+ *
143
+ * @param user - User to emit from
144
+ * @param event - Event name
145
+ * @param data - Event data
146
+ * @returns Promise resolving when event is sent
147
+ */
148
+ export function emitCustomEvent(user, event, data) {
149
+ if (user.socket.connected) {
150
+ return user.socket.emitWithAck(event, data);
151
+ }
152
+ return Promise.reject('Socket not connected');
153
+ }
@@ -0,0 +1,6 @@
1
+ export { useChat } from './useChat';
2
+ export { useRooms } from './useRooms';
3
+ export { useMessages } from './useMessages';
4
+ export { useCall } from './useCall';
5
+ export { useSocket } from './useSocket';
6
+ export { usePresence } from './usePresence';
@@ -0,0 +1,6 @@
1
+ export { useChat } from './useChat';
2
+ export { useRooms } from './useRooms';
3
+ export { useMessages } from './useMessages';
4
+ export { useCall } from './useCall';
5
+ export { useSocket } from './useSocket';
6
+ export { usePresence } from './usePresence';
@@ -0,0 +1,15 @@
1
+ export declare function useCall(): {
2
+ activeCall: import("@enfin/chat-shared").AudioCall;
3
+ callState: import("../call/WebRTCManager").WebRTCManagerState;
4
+ callError: string;
5
+ remoteStream: MediaStream;
6
+ busyBanner: string;
7
+ clearBusyBanner: () => void;
8
+ startCall: (roomId: string, participantId: string) => Promise<void>;
9
+ endCall: () => Promise<void>;
10
+ acceptCall: () => Promise<void>;
11
+ rejectCall: () => Promise<void>;
12
+ mute: () => void;
13
+ unmute: () => void;
14
+ isMuted: boolean;
15
+ };