@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";
package/dist/index.js ADDED
@@ -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';