# MeeChat Mobile API - Pusher Realtime Documentation

## Overview

Dokumentasi ini menjelaskan integrasi Pusher untuk realtime messaging pada Mobile API MeeChat. Mobile app harus subscribe ke channels yang sama dengan web agar pesan, status update, dan assignment dapat tersinkronisasi secara realtime.

**Note:** Sistem mendukung **Dedicated** dan **Shared** packages. Logika subscription channel identik, namun logic filtering event berbeda (lihat section Channels).

---

## Authentication

### Pusher Auth Endpoint

```
POST /api/mobile/broadcasting/auth
Headers:
  Authorization: Bearer {token}
  Content-Type: application/json

Body:
{
  "socket_id": "123456.789012",
  "channel_name": "private-waba.1"
}

Response:
{
  "auth": "APP_KEY:SIGNATURE"
}
```

---

## Channels

### 1. WABA Channel (Primary)
**Channel:** `private-waba.{waba_pool_id}`

Events yang diterima:
| Event | Description | Payload |
|-------|-------------|---------|
| `incoming.message` | Pesan masuk dari customer | [MessageData](#messagedata-payload) |
| `outgoing.message` | Pesan keluar (dari agent/client) | [MessageData](#messagedata-payload) |
| `message.status.update` | Status update pesan | [StatusData](#statusdata-payload) |
| `chat.assigned` | Chat di-assign ke agent | [AssignmentData](#assignmentdata-payload) |


> **⚠️ PENTING UNTUK USER SHARED PACKAGE:**
> Channel ini bersifat **Shared (Broadcasting)** untuk semua user dalam satu WABA Pool.
> Artinya, client/agent dengan package Shared akan menerima event `incoming.message` dari customer milik client lain di pool yang sama.
> 
> **Mobile App WAJIB melakukan validasi Client-Side:**
> Saat menerima event `incoming.message` atau `chat.assigned`:
> 1. Cek apakah `chatAssignmentId` ada di database lokal app / list conversation yang didapat dari API.
> 2. ATAU abaikan event jika `customer_number` tidak dikenali.
> 3. API List Conversation (`GET /api/mobile/conversations`) sudah memfilter data dengan benar. Gunakan data tersebut sebagai whitelist.
> **JANGAN menampilkan notifikasi/chat untuk pesan yang tidak valid.**

---

### 2. Inbox Agent Channel (Agent Only)
**Channel:** `private-inbox.agent.{agent_id}`

Events yang diterima:
| Event | Description |
|-------|-------------|
| `incoming.message` | Pesan masuk untuk chat yang di-assign ke agent ini |
| `chat.updated` | Chat update (assigned/closed/reopened) |
| `chat.assigned` | Chat baru di-assign |

---

### 3. Inbox WABA Channel
**Channel:** `private-inbox.waba.{waba_pool_id}`

Events yang diterima:
| Event | Description |
|-------|-------------|
| `chat.updated` | Update status chat |
| `chat.assigned` | Assignment chat ke agent |
| `chat.closed` | Chat ditutup |
| `chat.reopened` | Chat dibuka kembali |

---

## Event Payloads

### MessageData Payload
```json
{
  "messageData": {
    "id": 123,
    "wa_message_id": "wamid.xxx",
    "from_number": "628123456789",
    "to_number": "628987654321",
    "type": "text",
    "body": "Hello, ini pesan",
    "media_id": null,
    "media_url": "https://...",
    "media_filename": "image.jpg",
    "timestamp": "2024-12-29T10:00:00+07:00",
    "direction": "incoming"
  },
  "wabaPoolId": 1,
  "chatAssignmentId": 42,
  "sessionId": 42,
  "agentId": 5
}
```

### StatusData Payload
```json
{
  "statusData": {
    "wa_message_id": "wamid.xxx",
    "status": "delivered",
    "timestamp": "2024-12-29T10:01:00+07:00"
  },
  "wabaPoolId": 1
}
```

**Status values:** `sent`, `delivered`, `read`, `failed`

### AssignmentData Payload
```json
{
  "sessionData": {
    "id": 42,
    "customer_number": "628123456789",
    "customer_name": "John Doe",
    "agent_id": 5,
    "agent_name": "Agent Smith",
    "status": "open",
    "assigned_at": "2024-12-29T10:00:00+07:00"
  },
  "wabaPoolId": 1
}
```

### ChatUpdated Payload
```json
{
  "chatData": {
    "id": 42,
    "customer_number": "628123456789",
    "customer_name": "John Doe",
    "agent_id": 5,
    "status": "open",
    "last_message_at": "2024-12-29T10:05:00+07:00"
  },
  "wabaPoolId": 1,
  "agentId": 5,
  "action": "updated"
}
```

---

## Flutter Implementation Example

### 1. Setup Pusher Client

```dart
import 'package:pusher_channels_flutter/pusher_channels_flutter.dart';

class PusherService {
  late PusherChannelsFlutter pusher;
  
  Future<void> init(String authToken) async {
    pusher = PusherChannelsFlutter.getInstance();
    
    await pusher.init(
      apiKey: 'YOUR_PUSHER_KEY',
      cluster: 'ap1',
      onAuthorizer: (channelName, socketId, options) async {
        final response = await http.post(
          Uri.parse('$baseUrl/api/mobile/broadcasting/auth'),
          headers: {
            'Authorization': 'Bearer $authToken',
            'Content-Type': 'application/json',
          },
          body: jsonEncode({
            'socket_id': socketId,
            'channel_name': channelName,
          }),
        );
        return jsonDecode(response.body);
      },
    );
    
    await pusher.connect();
  }
}
```

### 2. Subscribe to Channels

```dart
Future<void> subscribeToChannels(int wabaPoolId, int? agentId) async {
  // Main WABA channel - for all messages
  final wabaChannel = await pusher.subscribe(
    channelName: 'private-waba.$wabaPoolId',
  );
  
  wabaChannel.onEvent = (event) {
    switch (event.eventName) {
      case 'incoming.message':
        _handleIncomingMessage(jsonDecode(event.data));
        break;
      case 'outgoing.message':
        _handleOutgoingMessage(jsonDecode(event.data));
        break;
      case 'message.status.update':
        _handleStatusUpdate(jsonDecode(event.data));
        break;
      case 'chat.assigned':
        _handleChatAssigned(jsonDecode(event.data));
        break;
    }
  };
  
  // Agent-specific channel (if agent role)
  if (agentId != null) {
    final agentChannel = await pusher.subscribe(
      channelName: 'private-inbox.agent.$agentId',
    );
    
    agentChannel.onEvent = (event) {
      switch (event.eventName) {
        case 'incoming.message':
          _handleAssignedMessage(jsonDecode(event.data));
          break;
        case 'chat.updated':
          _handleChatUpdate(jsonDecode(event.data));
          break;
      }
    };
  }
}
```

### 3. Event Handlers

```dart
void _handleIncomingMessage(Map<String, dynamic> data) {
  final messageData = data['messageData'];
  final conversationId = data['chatAssignmentId'];
  
  // Add to local message list
  final message = ChatMessage(
    id: messageData['id'].toString(),
    waMessageId: messageData['wa_message_id'],
    body: messageData['body'],
    type: messageData['type'],
    direction: MessageDirection.incoming,
    timestamp: DateTime.parse(messageData['timestamp']),
    mediaUrl: messageData['media_url'],
    mediaFilename: messageData['media_filename'],
  );
  
  // Notify UI
  conversationNotifier.addMessage(conversationId, message);
}

void _handleStatusUpdate(Map<String, dynamic> data) {
  final statusData = data['statusData'];
  final waMessageId = statusData['wa_message_id'];
  final status = statusData['status'];
  
  // Update message status in local state
  messageNotifier.updateStatus(waMessageId, status);
}

void _handleChatAssigned(Map<String, dynamic> data) {
  final sessionData = data['sessionData'];
  
  // Add new conversation to list
  conversationListNotifier.addConversation(Conversation(
    id: sessionData['id'],
    customerNumber: sessionData['customer_number'],
    customerName: sessionData['customer_name'],
    agentId: sessionData['agent_id'],
    status: sessionData['status'],
  ));
}
```

---

## Channel Subscription Matrix

| Role | Channels to Subscribe |
|------|----------------------|
| **Client** | `private-waba.{waba_pool_id}`, `private-inbox.waba.{waba_pool_id}` |
| **Agent** | `private-waba.{waba_pool_id}`, `private-inbox.agent.{agent_id}`, `private-inbox.waba.{waba_pool_id}` |

---

## Best Practices

1. **Subscribe on Login** - Subscribe ke channels setelah user berhasil login
2. **Unsubscribe on Logout** - Unsubscribe semua channels saat logout
3. **Handle Reconnection** - Pusher akan auto-reconnect, pastikan state tetap sinkron
4. **Deduplicate Messages** - Simpan `wa_message_id` untuk menghindari duplikat
5. **Optimistic UI** - Update UI segera saat kirim pesan, konfirmasi dengan event

---

## Pusher Configuration

```
PUSHER_APP_ID=your_app_id
PUSHER_APP_KEY=your_app_key
PUSHER_APP_SECRET=your_app_secret
PUSHER_APP_CLUSTER=ap1
```

Mobile app hanya memerlukan `APP_KEY` dan `CLUSTER`.
