@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.
- package/README.md +224 -0
- package/dist/assets/music/i_phone_message.mp3 +0 -0
- package/dist/call/WebRTCManager.d.ts +54 -0
- package/dist/call/WebRTCManager.js +274 -0
- package/dist/call/signaling.d.ts +3 -0
- package/dist/call/signaling.js +24 -0
- package/dist/call/types.d.ts +25 -0
- package/dist/call/types.js +13 -0
- package/dist/components/Chat.d.ts +14 -0
- package/dist/components/Chat.js +452 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.js +2 -0
- package/dist/context/ChatContext.d.ts +59 -0
- package/dist/context/ChatContext.js +647 -0
- package/dist/esm/call/WebRTCManager.d.ts +54 -0
- package/dist/esm/call/WebRTCManager.js +274 -0
- package/dist/esm/call/signaling.d.ts +3 -0
- package/dist/esm/call/signaling.js +24 -0
- package/dist/esm/call/types.d.ts +25 -0
- package/dist/esm/call/types.js +13 -0
- package/dist/esm/components/Chat.d.ts +14 -0
- package/dist/esm/components/Chat.js +452 -0
- package/dist/esm/components/index.d.ts +3 -0
- package/dist/esm/components/index.js +2 -0
- package/dist/esm/context/ChatContext.d.ts +59 -0
- package/dist/esm/context/ChatContext.js +647 -0
- package/dist/esm/hooks/index.d.ts +6 -0
- package/dist/esm/hooks/index.js +6 -0
- package/dist/esm/hooks/useCall.d.ts +15 -0
- package/dist/esm/hooks/useCall.js +38 -0
- package/dist/esm/hooks/useChat.d.ts +23 -0
- package/dist/esm/hooks/useChat.js +40 -0
- package/dist/esm/hooks/useMessages.d.ts +17 -0
- package/dist/esm/hooks/useMessages.js +38 -0
- package/dist/esm/hooks/usePresence.d.ts +4 -0
- package/dist/esm/hooks/usePresence.js +12 -0
- package/dist/esm/hooks/useRooms.d.ts +9 -0
- package/dist/esm/hooks/useRooms.js +19 -0
- package/dist/esm/hooks/useSocket.d.ts +7 -0
- package/dist/esm/hooks/useSocket.js +92 -0
- package/dist/esm/index.d.ts +11 -0
- package/dist/esm/index.js +15 -0
- package/dist/esm/utils/index.d.ts +2 -0
- package/dist/esm/utils/index.js +2 -0
- package/dist/esm/utils/ringtone.d.ts +2 -0
- package/dist/esm/utils/ringtone.js +39 -0
- package/dist/esm/utils/testUtils.d.ts +102 -0
- package/dist/esm/utils/testUtils.js +153 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/useCall.d.ts +15 -0
- package/dist/hooks/useCall.js +38 -0
- package/dist/hooks/useChat.d.ts +23 -0
- package/dist/hooks/useChat.js +40 -0
- package/dist/hooks/useMessages.d.ts +17 -0
- package/dist/hooks/useMessages.js +38 -0
- package/dist/hooks/usePresence.d.ts +4 -0
- package/dist/hooks/usePresence.js +12 -0
- package/dist/hooks/useRooms.d.ts +9 -0
- package/dist/hooks/useRooms.js +19 -0
- package/dist/hooks/useSocket.d.ts +7 -0
- package/dist/hooks/useSocket.js +92 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +15 -0
- package/dist/public/music/i_phone_message.mp3 +0 -0
- package/dist/style.css +1226 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/ringtone.d.ts +2 -0
- package/dist/utils/ringtone.js +39 -0
- package/dist/utils/testUtils.d.ts +102 -0
- package/dist/utils/testUtils.js +153 -0
- 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,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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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';
|
|
Binary file
|