{
  "info": {
    "name": "Zafirra — Call flow (REST + Socket.IO)",
    "description": "## Zafirra — voice/video call (Socket.IO)\n\n### Handshake query (required on every connection)\n| Key | Example |\n|-----|---------|\n| userId | Mongo ObjectId of the user |\n| lang | `ar` or `en` |\n| userType | `client`, `provider`, etc. (must match enum) |\n| deviceType | e.g. `android`, `ios`, `web` |\n| deviceId | unique string per device |\n\n### Client → server (emit)\n| Event | Who | Payload |\n|-------|-----|---------|\n| **call:start** | Caller | `{ \"callCredentialId\": \"<24-char hex>\" }` |\n| **call:answer** | Receiver | `{ \"callCredentialId\": \"...\", \"action\": \"answer\" | \"reject\" }` |\n| **call:finish** | Caller or receiver | `{ \"callCredentialId\": \"...\" }` |\n\n### Server → client (listen)\n| Event | When |\n|-------|------|\n| **call:waiting** | After **call:start** — to caller; includes Agora fields |\n| **call:answered** | Receiver accepted — to caller |\n| **call:rejected** | Receiver rejected — to caller |\n| **call:answer** | Timeout auto-reject — to **both** (action `reject`, `auto`, `reason`) |\n| **call:finished** | Call ended normally — to **both** |\n| **call:finish** | Auto end at planned `endedAt` — to **both** (if enabled on server) |\n| **call:error** | Validation / business error — to emitting socket |\n\n### REST first\nRun **POST /api/call/credential** with caller Bearer token; copy `data.callCredentialId` into collection variable `callCredentialId` (or use the Tests script).\n\n### Tokens\nAPI routes need a JWT that exists in **UserToken** (same as app login). Set `callerToken` / tokens from sign-in.\n",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "_postman_id": "zafirra-call-flow-001"
  },
  "item": [
    {
      "name": "REST",
      "item": [
        {
          "name": "POST /api/call/credential",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{callerToken}}",
                "type": "text"
              },
              {
                "key": "secretKey",
                "value": "{{secretKey}}",
                "type": "text"
              },
              {
                "key": "lang",
                "value": "ar",
                "type": "text"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/call/credential?receiverId={{receiverId}}&receiverType=client",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "call",
                "credential"
              ],
              "query": [
                {
                  "key": "receiverId",
                  "value": "{{receiverId}}",
                  "description": "Callee Client/Provider id"
                },
                {
                  "key": "receiverType",
                  "value": "client",
                  "description": "client | provider"
                }
              ]
            },
            "description": "Creates **CallCredential** and returns `data.callCredentialId`, Agora token, channel, etc.\n\n**Required:** `callerToken` (Bearer), `secretKey`, `receiverId`."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "try {",
                  "  const res = pm.response.json();",
                  "  if (res.data && res.data.callCredentialId) {",
                  "    pm.collectionVariables.set('callCredentialId', res.data.callCredentialId);",
                  "    console.log('callCredentialId →', res.data.callCredentialId);",
                  "  }",
                  "} catch (e) { console.warn(e); }"
                ],
                "type": "text/javascript"
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Socket.IO",
      "description": "Connect URL pattern: `{{baseUrl}}?userId=...&lang=ar&userType=client&deviceType=android&deviceId=...`\n\nAfter import, if a request opens as unknown protocol, use **New → Socket.IO**, paste URL + Params from the item description, then **Messages** → emit with JSON from **Body** on this item.\n\n### Copy-paste emit bodies (Messages tab)\n**call:start**\n```json\n{\n  \"callCredentialId\": \"{{callCredentialId}}\"\n}\n```\n**call:answer** (answer)\n```json\n{\n  \"callCredentialId\": \"{{callCredentialId}}\",\n  \"action\": \"answer\"\n}\n```\n**call:answer** (reject)\n```json\n{\n  \"callCredentialId\": \"{{callCredentialId}}\",\n  \"action\": \"reject\"\n}\n```\n**call:finish**\n```json\n{\n  \"callCredentialId\": \"{{callCredentialId}}\"\n}\n```\n",
      "item": [
        {
          "name": "1. Caller — emit call:start",
          "request": {
            "method": "SOCKET.IO",
            "header": [],
            "url": "{{baseUrl}}?userId={{callerId}}&lang=ar&userType=client&deviceType=android&deviceId=postman-caller",
            "description": "### Emit\n```json\n{\n  \"callCredentialId\": \"{{callCredentialId}}\"\n}\n```\n\n### Listen (subscribe in Postman Events tab)\n- **call:waiting** — ring-out payload (credentials, receiver info)\n- **call:answered** — receiver picked up\n- **call:answer** — server may emit timeout reject (`action: reject`, `auto: true`)\n- **call:finished** / **call:finish** — call ended\n- **call:error**",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"callCredentialId\": \"{{callCredentialId}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "protocolProfileBehavior": {
            "disableBodyPruning": true,
            "socketio": {
              "version": "4",
              "path": "/socket.io/",
              "transports": [
                "websocket"
              ],
              "handshakeQuery": {
                "userId": "{{callerId}}",
                "lang": "ar",
                "userType": "client",
                "deviceType": "android",
                "deviceId": "postman-caller"
              },
              "listen": [
                {
                  "name": "call:waiting",
                  "enabled": true
                },
                {
                  "name": "call:answered",
                  "enabled": true
                },
                {
                  "name": "call:answer",
                  "enabled": true
                },
                {
                  "name": "call:finished",
                  "enabled": true
                },
                {
                  "name": "call:finish",
                  "enabled": true
                },
                {
                  "name": "call:error",
                  "enabled": true
                }
              ],
              "emit": {
                "event": "call:start",
                "payload": {
                  "callCredentialId": "{{callCredentialId}}"
                }
              }
            }
          }
        },
        {
          "name": "2. Receiver — emit call:answer (pick up)",
          "request": {
            "method": "SOCKET.IO",
            "header": [],
            "url": "{{baseUrl}}?userId={{receiverId}}&lang=ar&userType=client&deviceType=android&deviceId=postman-receiver",
            "description": "### Emit\n```json\n{\n  \"callCredentialId\": \"{{callCredentialId}}\",\n  \"action\": \"answer\"\n}\n```\n\n### Listen\n- **call:error** — unauthorized / validation\n- **call:finished** / **call:finish** — when caller ends or server auto-ends",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"callCredentialId\": \"{{callCredentialId}}\",\n  \"action\": \"answer\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "protocolProfileBehavior": {
            "disableBodyPruning": true,
            "socketio": {
              "version": "4",
              "path": "/socket.io/",
              "transports": [
                "websocket"
              ],
              "handshakeQuery": {
                "userId": "{{receiverId}}",
                "lang": "ar",
                "userType": "client",
                "deviceType": "android",
                "deviceId": "postman-receiver"
              },
              "listen": [
                {
                  "name": "call:error",
                  "enabled": true
                },
                {
                  "name": "call:finished",
                  "enabled": true
                },
                {
                  "name": "call:finish",
                  "enabled": true
                },
                {
                  "name": "call:answer",
                  "enabled": true
                }
              ],
              "emit": {
                "event": "call:answer",
                "payload": {
                  "callCredentialId": "{{callCredentialId}}",
                  "action": "answer"
                }
              }
            }
          }
        },
        {
          "name": "3. Receiver — emit call:answer (reject)",
          "request": {
            "method": "SOCKET.IO",
            "header": [],
            "url": "{{baseUrl}}?userId={{receiverId}}&lang=ar&userType=client&deviceType=android&deviceId=postman-receiver",
            "description": "### Emit\n```json\n{\n  \"callCredentialId\": \"{{callCredentialId}}\",\n  \"action\": \"reject\"\n}\n```\n\n### Listen (caller side mainly)\n- Caller receives **call:rejected** (separate event in app)\n- **call:error** on this socket if invalid",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"callCredentialId\": \"{{callCredentialId}}\",\n  \"action\": \"reject\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "protocolProfileBehavior": {
            "disableBodyPruning": true,
            "socketio": {
              "version": "4",
              "path": "/socket.io/",
              "transports": [
                "websocket"
              ],
              "handshakeQuery": {
                "userId": "{{receiverId}}",
                "lang": "ar",
                "userType": "client",
                "deviceType": "android",
                "deviceId": "postman-receiver"
              },
              "listen": [
                {
                  "name": "call:error",
                  "enabled": true
                },
                {
                  "name": "call:rejected",
                  "enabled": true
                },
                {
                  "name": "call:finished",
                  "enabled": true
                },
                {
                  "name": "call:finish",
                  "enabled": true
                }
              ],
              "emit": {
                "event": "call:answer",
                "payload": {
                  "callCredentialId": "{{callCredentialId}}",
                  "action": "reject"
                }
              }
            }
          }
        },
        {
          "name": "4. Caller — emit call:finish (hang up)",
          "request": {
            "method": "SOCKET.IO",
            "header": [],
            "url": "{{baseUrl}}?userId={{callerId}}&lang=ar&userType=client&deviceType=android&deviceId=postman-caller",
            "description": "### Emit\n```json\n{\n  \"callCredentialId\": \"{{callCredentialId}}\"\n}\n```\n\n### Listen\n- **call:finished** — to both parties (payload: `finishedAt`, `durationInSeconds`, `finishedBy`)\n- **call:error**",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"callCredentialId\": \"{{callCredentialId}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "protocolProfileBehavior": {
            "disableBodyPruning": true,
            "socketio": {
              "version": "4",
              "path": "/socket.io/",
              "transports": [
                "websocket"
              ],
              "handshakeQuery": {
                "userId": "{{callerId}}",
                "lang": "ar",
                "userType": "client",
                "deviceType": "android",
                "deviceId": "postman-caller"
              },
              "listen": [
                {
                  "name": "call:finished",
                  "enabled": true
                },
                {
                  "name": "call:finish",
                  "enabled": true
                },
                {
                  "name": "call:error",
                  "enabled": true
                }
              ],
              "emit": {
                "event": "call:finish",
                "payload": {
                  "callCredentialId": "{{callCredentialId}}"
                }
              }
            }
          }
        }
      ]
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "http://127.0.0.1:30114",
      "type": "string"
    },
    {
      "key": "secretKey",
      "value": "6a72f384a1dce4d7b9488d6a1e5c67ae83d7aaf208b3ecfd03bcb40c0129e810",
      "type": "string"
    },
    {
      "key": "callerToken",
      "value": "",
      "type": "string",
      "description": "Bearer token for caller (must exist in UserToken)"
    },
    {
      "key": "callerId",
      "value": "",
      "type": "string",
      "description": "Caller Mongo _id (must match JWT)"
    },
    {
      "key": "receiverId",
      "value": "",
      "type": "string",
      "description": "Callee Mongo _id"
    },
    {
      "key": "callCredentialId",
      "value": "",
      "type": "string",
      "description": "Filled by REST Tests script"
    }
  ]
}