# WebSocket + REST API Reference

## Base URL
- REST API: `https://dayloan.agniplay.com`
- WebSocket: `wss://dayloan.agniplay.com/ws`

## ⚠️ Important: Socket.IO Namespaces

The WebSocket server uses **namespaces** to isolate agent and admin connections. You **must** connect to the correct namespace:

| Client | Namespace | Example Socket.IO URL |
|--------|-----------|----------------------|
| Agent App | `/agent` | `io('https://dayloan.agniplay.com/agent', { path: '/ws' })` |
| Admin App | `/admin` | `io('https://dayloan.agniplay.com/admin', { path: '/ws' })` |

Connecting to the default namespace (`/`) will be **rejected** with error code `4003` (`wrong_namespace`).

---

## REST Endpoints for Location & Presence

### 1. Agent Presence (Online/Offline)

**GET** `/admin/agents/presence`

Get all agents' real-time online/offline presence status tracked via WebSocket.

| Query Param | Type | Description |
|---|---|---|
| `status` | string | Filter: `online` or `offline` (optional) |

Response:
```json
{
  "status": "success",
  "data": {
    "company_id": 1,
    "filter": { "status": null },
    "summary": { "total": 10, "online": 3, "offline": 7 },
    "presence": [
      {
        "agent_id": 5,
        "agent_name": "Ravi Kumar",
        "mobile_no": "9876543210",
        "line": "Line A",
        "agent_status": "Active",
        "presence_status": "online",
        "last_latitude": 12.97159870,
        "last_longitude": 77.59456270,
        "last_activity_type": "traveling",
        "last_speed": 25.50,
        "device_info": "Android 14/Samsung Galaxy M35",
        "app_version": "1.2.3",
        "connected_at": "2026-07-08T09:30:00.000Z",
        "last_seen_at": "2026-07-08T09:35:00.000Z"
      }
    ]
  }
}
```

### 2. Real-Time Agent Locations (Latest Per Agent)

**GET** `/admin/real-time-agent-locations`

Get the latest tracked location for each agent (from `agent_location_tracking` table).

| Query Param | Type | Description |
|---|---|---|
| `line` | string | Filter by agent line (optional) |

Response:
```json
{
  "status": "success",
  "data": {
    "company_id": 1,
    "line": null,
    "total_agents": 10,
    "agents_with_location": 8,
    "real_time_locations": [
      {
        "agent": {
          "id": 5,
          "name": "Ravi Kumar",
          "mobile_no": "9876543210",
          "line": "Line A",
          "status": "Active"
        },
        "location": {
          "latitude": 12.97159870,
          "longitude": 77.59456270,
          "accuracy": 15.5,
          "address": "MG Road, Bangalore",
          "activity_type": "traveling",
          "timestamp": "2026-07-08T09:35:00.000Z"
        },
        "receipt": {
          "id": 123,
          "rec_no": "RCP-001",
          "loan_id": 456,
          "cust_name": "Customer Name"
        }
      }
    ],
    "timestamp": "2026-07-08T09:35:00.000Z"
  }
}
```

### 3. Session-Based Path Tracking (Swiggy/Zomato-style)

**GET** `/admin/agents/:agent_id/live`

Agent's current live location + active session stats.

Response:
```json
{
  "status": "success",
  "data": {
    "agent": { "id": 5, "name": "Ravi", "mobile_no": "...", "line": "A", "status": "Active" },
    "presence": { "online": true, "last_seen_at": "...", "device_info": "...", "app_version": "..." },
    "live_location": { "latitude": 12.9715, "longitude": 77.5945, "accuracy": 12, "speed": 0, "activity_type": "stationary", "recorded_at": "..." },
    "active_session": { "session_id": "uuid", "started_at": "...", "duration_seconds": 5400, "point_count": 320, "distance_meters": 4500 }
  }
}
```

**GET** `/admin/agents/:agent_id/history`

List of all past tracking sessions for an agent (paginated).

| Query Param | Type | Description |
|---|---|---|
| `page` | int | Page number (default 1) |
| `limit` | int | Per page (default 20) |

**GET** `/admin/agents/:agent_id/history/:session_id`

Full GPS path for one session (used to draw Polyline on map).

| Query Param | Type | Description |
|---|---|---|
| `simplify` | bool | Apply Douglas-Peucker simplification (default `true`) |
| `epsilon` | float | Simplification precision in degrees (default `0.00001` ≈ 1m) |

Returns session summary + path points + geofence events + compression stats.

**GET** `/admin/agents/:agent_id/history/:session_id/events`

Geofence events for a specific session (arrived_store, delivery_completed, pause, resume, etc.).

**GET** `/admin/agents/:agent_id/today-path`

All today's GPS points merged across sessions (for daily summary view).

| Query Param | Type | Description |
|---|---|---|
| `simplify` | bool | Apply Douglas-Peucker (default `true`) |
| `epsilon` | float | Precision (default `0.00001`) |

Returns path + session list for the day.

### Session Lifecycle

A **session** represents one duty/trip, NOT a WebSocket connection:

```
Start Duty (tracking.start) / First GPS
        │
        ▼
  [tracking_sessions row created]
        │
        ▼
  All location_update tagged with session_id
  Battery/GPS metadata saved with each point
        │
        ▼
  Network loss / App background / Reconnect
  (SAME session continues - 30-min expiry)
        │
        ▼
  tracking.pause / tracking.resume events
        │
        ▼
  Stop Duty (tracking.stop) / 30-min timeout
        │
        ▼
  [session ended, stats calculated]
```

Sessions auto-end after 30 minutes of inactivity (not just WebSocket disconnect). This prevents fragmented tracks from network switches, app backgrounding, and Android Doze.

### 4. Agent Location Logs (History - Legacy)

**GET** `/admin/agent-location-logs`

Get paginated location logs for all agents with filters.

| Query Param | Type | Description |
|---|---|---|
| `agent_id` | int | Filter by agent (optional) |
| `from_date` | date | Start date (YYYY-MM-DD) |
| `to_date` | date | End date (YYYY-MM-DD) |
| `page` | int | Page number (default 1) |
| `limit` | int | Per page (default 20) |

**GET** `/admin/agent-location-logs/:agent_id`

Get location history for a specific agent.

**GET** `/admin/agent-location-logs-comprehensive`

Comprehensive location logs with receipts, check-in/out.

### 4. Agent Check-In Status

**GET** `/admin/agents/check-in-status`

Get which agents are currently checked in (clocked in for work — separate from WebSocket presence).

### 5. Agents List with Status

**GET** `/admin/agents/status`

Get all agents with their configured active/inactive status.

| Query Param | Type | Description |
|---|---|---|
| `status` | string | `active` or `inactive` |
| `line` | string | Filter by line |

### 6. Agent Self-Service (Use Token from Agent Login)

**GET** `/location_history`

Agent retrieves their own location history (uses agent JWT token).

**POST** `/track_location`

Agent posts their current location directly via REST (fallback if WebSocket unavailable).

---

## WebSocket Events

### Connection
```dart
final socket = io.IOClient('wss://dayloan.agniplay.com/ws');
```

### Agent Namespace (Agent JWT Token)
```js
socket.auth = { token: 'agent_jwt_token' };
socket.connect();
```

| Event | Direction | Payload | Description |
|---|---|---|---|---|
| `tracking.location` | agent → server | `{ request_id, latitude, longitude, accuracy, speed, heading, activity_type, address?, session_id?, recorded_at?, battery_level?, battery_charging?, gps_provider? }` | Send GPS location |
| `tracking.start` | agent → server | `{ request_id, latitude?, longitude?, device_id? }` | Start new tracking session |
| `tracking.stop` | agent → server | `{ request_id, latitude?, longitude? }` | End current tracking session |
| `tracking.pause` | agent → server | `{ request_id, latitude?, longitude?, accuracy? }` | Pause tracking (triggers 30-min session expiry) |
| `tracking.resume` | agent → server | `{ request_id, latitude?, longitude?, accuracy? }` | Resume tracking |
| `tracking.delivery_completed` | agent → server | `{ request_id, receipt_id, loan_id?, latitude?, longitude?, accuracy?, metadata? }` | Mark delivery as completed |
| `tracking.checkin` | agent → server | `{ request_id, latitude, longitude }` | Check-in at location |
| `tracking.checkout` | agent → server | `{ request_id, latitude, longitude }` | Check-out from location |
| `tracking.emergency` | agent → server | `{ request_id, latitude, longitude, message? }` | Emergency alert |
| `heartbeat` | agent → server | `{ request_id }` | Keep-alive every 30s |
| `ack` | server → agent | `{ request_id, status, sequence?, session_id? }` | Confirmation of saved data |
| `error` | server → agent | `{ code, message, details? }` | Error notification |

### Admin Namespace (Admin JWT Token)
```js
socket.auth = { token: 'admin_jwt_token' };
socket.connect();
```

| Event | Direction | Payload | Description |
|---|---|---|---|
| `subscribe:company` | admin → server | `{ company_id }` | Start receiving company events |
| `subscribe:agent` | admin → server | `{ agent_id }` | Subscribe to specific agent |
| `unsubscribe:agent` | admin → server | `{ agent_id }` | Unsubscribe from agent |
| `location:update` | server → admin | `{ agent_id, agent_name, session_id?, latitude, longitude, accuracy, speed, heading, activity_type, recorded_at }` | Live location update |
| `agent.connected` | server → admin | `{ agent_id, agent_name, session_id?, mobile_no, line, timestamp }` | Agent came online |
| `agent.disconnected` | server → admin | `{ agent_id, agent_name, mobile_no, line, timestamp }` | Agent went offline |
| `tracking.emergency` | server → admin | `{ agent_id, agent_name, session_id?, latitude, longitude, message, timestamp }` | Emergency alert |

### Error Codes

| Code | Description |
|---|---|
| 1001 | Invalid/expired token |
| 1002 | Missing token |
| 1003 | Agent not found |
| 1004 | Company not found |
| 1005 | Unauthorized company access |
| 2001 | Invalid location data |
| 2002 | GPS accuracy too low (>100m) |
| 2003 | Duplicate request_id |
| 3001 | Rate limit exceeded |

---

## Flutter Usage Pattern

```dart
// Agent: connect + send location every 10s
AgentWebSocketService().connect(token);
AgentWebSocketService().sendLocationUpdate(
  latitude: 12.9715,
  longitude: 77.5945,
  accuracy: 15.0,
  speed: 0.0,
  heading: 0.0,
  activityType: 'stationary',
);

// Admin: connect + subscribe to company
AdminWebSocketService().connect(token);
AdminWebSocketService().subscribeToCompany(companyId: 1);

// Listen for live locations
AdminWebSocketService().locationStream.listen((loc) {
  // update map marker
});
```

---

## Key Facts
- **Session model**: `tracking_sessions` table tracks each duty/trip lifecycle. One agent can have many sessions per day.
- **GPS points**: `agent_location_tracking` now stores `tracking_session_id`, `recorded_at` (device timestamp), `received_at` (server timestamp), battery/GPS metadata
- **Geofence events**: `tracking_geofence_events` table captures pause/resume/delivery_completed events
- **Session expiry**: Sessions auto-end after 30 minutes of inactivity (configurable via `SESSION_EXPIRE_MINUTES`), independent of WebSocket connection state
- **Path simplification**: Douglas-Peucker algorithm compresses paths by ~95% for efficient Polyline rendering
- WebSocket writes to `agent_presence` (online/offline), `tracking_sessions` (sessions), `agent_location_tracking` (history), and `tracking_geofence_events` (events)
- `agent_presence` has unique key on `(agent_id, com_id)` — one row per agent
- `agent_location_tracking` has unique key on `(agent_id, request_id)` — dedup
- REST endpoints are read-only (WebSocket is the data source)
- All endpoints require JWT authentication
- Admin endpoints use admin JWT; agent endpoints use agent JWT
