# Pusher Events Documentation for Frontend This document describes all Pusher real-time events that the frontend should subscribe to and handle. ## Pusher setup ``` const pusherInstance = new Pusher(key, { cluster, forceTLS: true, channelAuthorization: { endpoint: `apiUrl/pusher/auth`, transport: "ajax", headers: { Authorization: `Bearer ${Cookies.get('token') || ''}`, }, } }); ``` ## Table of Contents - [Channel Types](#channel-types) - [Event Types](#event-types) - [Authentication](#authentication) - [Event Details](#event-details) - [Typing Indicator](#typing-indicator) --- ## Channel Types ### 1. Conversation Channel **Channel Name:** `conversation-{conversationId}` Subscribe to this channel to receive real-time updates for a specific conversation. **Example:** ```javascript const conversationChannel = pusher.subscribe('conversation-123'); ``` ### 2. Channel-Wide Channel **Channel Name:** `channel-{channelId}` Subscribe to this channel to receive updates for all conversations within a channel. **Example:** ```javascript const channelChannel = pusher.subscribe('channel-456'); ``` ### 3. User-Specific Channel **Channel Name:** `user-{userId}-conversations` Subscribe to this channel to receive personal notifications and conversation updates for a specific user. **Example:** ```javascript const userChannel = pusher.subscribe('user-789-conversations'); ``` --- ## Event Types | Event Name | Channels | Description | |------------|----------|-------------| | `new-message` | conversation, channel, user | Triggered when a new message is sent | | `message-updated` | conversation | Triggered when a message is edited | | `message-deleted` | channel | Triggered when a message is deleted | | `conversation-updated` | user | Triggered when conversation metadata changes | --- --- ## Event Details ### 1. new-message Triggered when a new message is sent to a conversation. **Channels:** - `conversation-{conversationId}` - `channel-{channelId}` - `user-{userId}-conversations` **Event Data:** ```typescript { conversationId: number; channelId: number; message: { id: number; conversationId: number; senderId: number; message: string; messageType: 'TEXT' | 'IMG' | 'VID' | 'AUDIO' | 'FILE' | 'STICKER' | 'GIF' | 'CATALOG_ITEM'; isRead: 0 | 1; // 0 = unread, 1 = read isEdited: 0 | 1; // 0 = not edited, 1 = edited mediaUrl?: string; catalogItem?: { id: number; name: string; description: string; price: number; photoUrls: string[]; discountPercentage: number | null; }; repliedMessage?: { id: number; senderId: number; message: string; messageType: string; sender: { id: number; firstName: string; lastName: string; email: string; }; createdAt: string; }; sender: { id: number; firstName: string; lastName: string; email: string; }; createdAt: string; updatedAt: string; }; } ``` --- ### 2. message-updated Triggered when a message is edited. **Channels:** - `conversation-{conversationId}` **Event Data:** ```typescript { conversationId: number; channelId: number; message: { id: number; conversationId: number; senderId: number; message: string; messageType: string; isRead: 0 | 1; isEdited: 1; // Always 1 for edited messages mediaUrl?: string; catalogItem?: { id: number; name: string; description: string; price: number; photoUrls: string[]; discountPercentage: number | null; }; sender: { id: number; firstName: string; lastName: string; email: string; }; createdAt: string; updatedAt: string; }; } ``` ### 3. message-deleted Triggered when a message is deleted. **Channels:** - `conversation-{conversationId}` **Event Data:** ```typescript { messageId: number; conversationId: number; deletedBy: number; // User ID who deleted the message } ``` --- ### 4. conversation-updated Triggered when conversation metadata changes (sent to user-specific channels). **Channels:** - `user-{userId}-conversations` **Event Data:** ```typescript { conversationId: number; id: number; channelId: number; channel: { id: number; name: string; // ... other channel properties }; isRead: 0 | 1; // 0 = has unread messages, 1 = all read messages: Array<{ id: number; conversationId: number; senderId: number; message: string; messageType: string; isRead: 0 | 1; isEdited: 0 | 1; mediaUrl?: string; catalogItem?: object; sender: { id: number; firstName: string; lastName: string; email: string; }; createdAt: string; updatedAt: string; }>; unreadCount: number; createdAt: string; updatedAt: string; } ``` # useActiveChannel Hook Documentation ## Overview The `useActiveChannel` hook manages real-time presence tracking for users in the application. It subscribes to a Pusher presence channel to track which users are currently online and updates the Redux store accordingly. ## Location ``` hooks/use-active-channel.ts ``` ## Dependencies - **Pusher.js**: Real-time WebSocket communication - **Redux**: State management for active users list - **PusherContext**: Custom context for Pusher instance ## How It Works ### 1. Channel Subscription The hook subscribes to the `presence-messenger` Pusher channel, which is a special presence channel that tracks connected members. ### 2. Event Listeners The hook listens to three Pusher events: - **`pusher:subscription_succeeded`**: Fired when successfully subscribed to the channel - Receives all currently active members - Dispatches `set()` action to initialize the active members list in Redux - **`pusher:member_added`**: Fired when a new user comes online - Receives the new member's information - Dispatches `add()` action to add the member to the active list - **`pusher:member_removed`**: Fired when a user goes offline - Receives the member's information who left - Dispatches `remove()` action to remove the member from the active list ### 3. Redux Store Updates All active member IDs are stored in Redux at: ```typescript state.activeList.members // string[] ``` ## Usage ### Basic Implementation ```typescript import useActiveChannel from '@/hooks/use-active-channel'; const ActiveStatus = () => { useActiveChannel(); return null; }; export default ActiveStatus; ``` ### In Layout (Recommended) Add to your dashboard layout to track presence across the entire app: ```typescript // app/(dashboard)/layout.tsx import ActiveStatus from '@/components/active-status'; import { PusherProvider } from '@/context/pusher-context'; export default function DashboardLayout({ children }) { return ( <PusherProvider> <ActiveStatus /> {children} </PusherProvider> ); } ``` ### Accessing Active Members Use Redux selector to check if a user is online: ```typescript import { useSelector } from 'react-redux'; import { RootState } from '@/src/redux/store'; function UserStatus({ userId }) { const activeMembers = useSelector((state: RootState) => state.activeList.members); const isOnline = activeMembers.includes(userId.toString()); return ( <div> {isOnline ? 'Online' : 'Offline'} </div> ); } ``` ## Redux Actions The hook dispatches three actions from `activeListSlice`: ### `set(members: string[])` Initializes the active members list with all currently online users. ```typescript dispatch(set(['user1', 'user2', 'user3'])); ``` ### `add(memberId: string)` Adds a single member to the active list when they come online. ```typescript dispatch(add('user4')); ``` ### `remove(memberId: string)` Removes a member from the active list when they go offline. ```typescript dispatch(remove('user2')); ``` ## State Structure ```typescript interface ActiveListState { members: string[]; // Array of user IDs who are currently online } ``` ## Cleanup The hook automatically handles cleanup: - Unsubscribes from the Pusher channel when the component unmounts - Unbinds all event listeners - Resets the active channel state ## Requirements ### 1. Pusher Configuration Ensure Pusher is properly configured in your environment: ```env NEXT_PUBLIC_PUSHER_KEY=your_pusher_key NEXT_PUBLIC_PUSHER_CLUSTER=your_cluster ``` ### 2. Backend Authorization Your backend must have a `/pusher/auth` endpoint that: - Verifies the user's authentication token - Authorizes the presence channel subscription - Returns user information for presence tracking Example backend response: ```json { "auth": "pusher_auth_signature", "channel_data": { "user_id": "123" } } ``` ### 3. Redux Store Setup Ensure `activeListReducer` is added to your Redux store: ```typescript // src/redux/store.ts import activeListReducer from './slice/chat/activeListSlice'; export const store = configureStore({ reducer: { activeList: activeListReducer, // ... other reducers }, }); ``` ## Example Use Cases ### 1. Show Online Status Badge ```typescript function UserAvatar({ userId, name }) { const activeMembers = useSelector((state: RootState) => state.activeList.members); const isOnline = activeMembers.includes(userId.toString()); return ( <div className="relative"> <Avatar> <AvatarFallback>{name}</AvatarFallback> </Avatar> {isOnline && ( <span className="absolute bottom-0 right-0 h-3 w-3 rounded-full bg-green-500 ring-2 ring-white" /> )} </div> ); } ``` ### 2. Filter Online Users ```typescript function OnlineUsersList({ users }) { const activeMembers = useSelector((state: RootState) => state.activeList.members); const onlineUsers = users.filter(user => activeMembers.includes(user.id.toString()) ); return ( <div> <h3>Online Users ({onlineUsers.length})</h3> {onlineUsers.map(user => ( <UserItem key={user.id} user={user} /> ))} </div> ); } ``` ### 3. Show Active Status in Chat Header ```typescript function ChatHeader({ chatUser }) { const activeMembers = useSelector((state: RootState) => state.activeList.members); const isActive = activeMembers.includes(chatUser.id.toString()); return ( <div> <h2>{chatUser.name}</h2> <p>{isActive ? 'Active now' : 'Offline'}</p> </div> ); } ``` --- ## Typing Indicator Documentation ### Send Typing Indicator ``` POST /chat/messages/typing/:conversationId ``` **Authentication:** Required (JWT Bearer Token) **URL Parameters:** - `conversationId` (number, required): The ID of the conversation **Request Body:** ```json { "isTyping": true } ``` **Parameters:** - `isTyping` (boolean, required): - `true` - User started typing - `false` - User stopped typing **Success Response (200 OK):** ```json { "success": true, "statusCode": 200, "message": "Typing indicator sent", } ``` **Error Responses:** **404 - Conversation Not Found** ```json { "statusCode": 404, "message": "Conversation not found" } ``` **403 - No Access** ```json { "statusCode": 403, "message": "You do not have access to this conversation" } ``` **401 - Unauthorized** ```json { "statusCode": 401, "message": "Unauthorized" } ``` --- ## Pusher Event ### Event Details **Channel:** `conversation-{conversationId}` **Event Name:** `typing` **Event Data:** ```typescript { userId: number; // ID of the user who is typing isTyping: boolean; // true = started typing, false = stopped typing userName : "John Client" // user name } ```