# Socket.IO Integration Guide (Web, Android, iOS)

المستند ده مبني على الإعدادات الفعلية في `.env` وعلى كود السيرفر داخل `www/nodeServer`.

## 1) Socket URL المعتمد (Production)

- `NODE_MODE=live`
- `NODE_HOST=samtam.net`
- `NODE_PORT=4987`
- عنوان الاتصال الرسمي:

`https://samtam.net:4987`

### قاعدة مهمة جدا

- استخدم `https://` فقط في كل العملاء (Web / Android / iOS).
- لا تستخدم `wss://` أو `ws://` يدويًا.
- مع Socket.IO العميل يتولى الترقية للـ WebSocket تلقائيا عند الحاجة، لكن عنوان البداية يظل `https://`.

## 2) CORS / Origin

من `app.js`:

- السيرفر يسمح بالـ `origin` القادم من `APP_URL`.
- حاليا `APP_URL=https://samtam.net`.
- `credentials: true` مفعلة على السيرفر، لذلك في تطبيق الويب فعّل `withCredentials: true` إذا كنت تعتمد على كوكيز/جلسات.

## 3) Query Params إلزامية عند `connect`

السيرفر يتحقق من وجود هذه القيم كلها داخل `socket.handshake.query`:

- `userId` (string رقمية وغير صفر)
- `userType` (string)
- `name` (string)
- `lang` (string مثل `ar` أو `en`)
- `deviceType` (string مثل `web` أو `android` أو `ios`)
- `deviceId` (string: معرف جهاز أو FCM token)

لو أي قيمة ناقصة:

- السيرفر يرسل `error_message`
- وقد يغلق الاتصال (`disconnect`) خصوصا عند نقص Query أساسي.

## 4) Events من العميل إلى السيرفر

| Event | Payload |
|---|---|
| `enter-chat` | `{ "room_id": 123 }` |
| `send-message` | `{ "room_id": 123, "type": "text" \| "image", "body": "..." }` |
| `exit-chat` | `{ "room_id": 123 }` |
| `start-call` | `{ "room_id": 123, "shareLink": "..." }` |
| `answer-call` | `{ "room_id": 123 }` |
| `reject-call` | `{ "room_id": 123 }` |
| `return-from-call` | `{ "room_id": 123 }` |

### Validation فعلي في السيرفر

- `enter-chat` و `exit-chat`: لازم `room_id` رقم صالح.
- `send-message`:
  - `room_id` رقم صالح
  - `type` فقط `text` أو `image`
  - `body` نص إجباري
- أحداث المكالمات (`start-call` / `answer-call` / `reject-call` / `return-from-call`) لا يوجد لها validation صارم حاليا في الكود، فالأفضل إرسال Payload صحيح دائما.

## 5) Events من السيرفر إلى العميل

### `message-received`

يصل في حالتين:

1) رسالة دردشة (`type: text` أو `image`)
2) حدث مكالمة (`type: call` أو `answer-call` أو `call-rejected` أو `return-from-call`)

> ملاحظة: في أحداث المكالمة فقط، `room_id` يخرج كسلسلة string أحيانا (مثال `"123"`). اعمل parsing مرن في العميل.

### `error_message`

الشكل العام:

```json
{
  "key": "fail",
  "message": "....",
  "status": 400
}
```

## 6) قيم `userType` الموصى بها

المستخدم فعليا في الـ maps والـ repos داخل `socket/helper.js`:

- `user`
- `provider`
- `delegate`
- `admin`

والقيم المستخدمة مع الـ morph للرسائل تشمل بشكل أساسي: `user`, `provider`, `admin`.

## 7) جاهز للويب (JavaScript/TypeScript)

```javascript
import { io } from "socket.io-client";

const socket = io("https://samtam.net:4987", {
  transports: ["websocket", "polling"],
  withCredentials: true,
  query: {
    userId: String(userId),
    userType: "user",
    name: displayName,
    lang: "ar",
    deviceType: "web",
    deviceId: deviceId,
  },
});

socket.on("connect", () => {
  console.log("connected", socket.id);
});

socket.on("message-received", (payload) => {
  // handle message/call payload
});

socket.on("error_message", (err) => {
  // err.key, err.message, err.status
});
```

## 8) جاهز للأندرويد (Kotlin - socket.io client)

```kotlin
import io.socket.client.IO
import io.socket.client.Socket

val opts = IO.Options().apply {
    transports = arrayOf("websocket", "polling")
    query = "userId=$userId" +
            "&userType=user" +
            "&name=${java.net.URLEncoder.encode(displayName, "UTF-8")}" +
            "&lang=ar" +
            "&deviceType=android" +
            "&deviceId=$deviceId"
}

val socket: Socket = IO.socket("https://samtam.net:4987", opts)

socket.on(Socket.EVENT_CONNECT) {
    // connected
}

socket.on("message-received") { args ->
    val payload = args.firstOrNull()
}

socket.on("error_message") { args ->
    val err = args.firstOrNull()
}

socket.connect()
```

## 9) جاهز للـ iOS (Swift - Socket.IO-Client-Swift)

```swift
import SocketIO

let manager = SocketManager(
    socketURL: URL(string: "https://samtam.net:4987")!,
    config: [
        .compress,
        .forceWebsockets(false),
        .connectParams([
            "userId": "\(userId)",
            "userType": "user",
            "name": displayName,
            "lang": "ar",
            "deviceType": "ios",
            "deviceId": deviceId
        ])
    ]
)

let socket = manager.defaultSocket

socket.on(clientEvent: .connect) { _, _ in
    // connected
}

socket.on("message-received") { data, _ in
    // parse payload
}

socket.on("error_message") { data, _ in
    // parse error
}

socket.connect()
```

## 10) تسلسل تشغيل الدردشة والمكالمة

1. `connect`
2. `enter-chat`
3. `send-message` عند الإرسال
4. مكالمة:
   - المرسل: `start-call`
   - المستقبل: `answer-call` أو `reject-call`
   - عند الانتهاء: `return-from-call`
5. `exit-chat` عند مغادرة الشاشة

## 11) ملاحظات تنفيذية مهمة

- في `type=image`: أرسل اسم الملف/القيمة المتوقعة من API حسب تدفق الرفع عندكم، والسيرفر هو الذي يعيد الشكل النهائي في `message-received`.
- لا تعتمد على نوع ثابت لـ `room_id` في كل الأحداث (رقم/نص)؛ طبّق تحويل موحد قبل الاستخدام.
- عند ظهور `error_message` بسبب Query ناقصة، أصلح القيم ثم أعد الاتصال من جديد.
