# 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
}
```