# Client exports

Client exports are called from **client** scripts:

```lua title="client.lua"
local mdt = exports['koja-mdt']
```

The client never gets more power than the player already has in the tablet. Exports that change something go through the same server actions as the tablet, so the player's permissions, duty rules and rate limits apply.

:::hint{type="info" title="Blocking exports"}
Exports marked **yields** wait for the server (up to 10 seconds). Call them from a thread (`CreateThread`), a command or an event handler. They return `nil, 'timeout'` when the server does not answer.
:::

## Tablet

| Export | Returns | Notes |
|---|---|---|
| `OpenMdt()` | | Asks the server to open the tablet. Access, duty (`Config.Open.allowOffDuty`), tablet item and the `mdt:open` hook apply. |
| `OpenMdtPage(page, id?, params?)` | | Opens straight to a page, or navigates there when the tablet is already open. |
| `CloseMdt()` | | |
| `ToggleMdt()` | `boolean` | Same as the open key: closes when open, otherwise checks `ClientIntegrations.CanOpen()` (pause menu, death, vehicle-only) and asks the server. Returns `true` when an open was requested. |
| `IsOpen()` | `boolean` | |
| `GetStreet(coords?)` | `string` | Street, crossing and zone at `coords` (default: the player). |

`page` is a tablet route: `dashboard`, `dispatch`, `map`, `citizens`, `vehicles`, `alpr`, `weapons`, `incidents`, `citations`, `warrants`, `custody`, `bolos`, `evidence`, `charges`, `court`, `roster`, `logs`, `settings`, `mail`, `divisions`, `certifications`, `forms`, `reviews`, `profile`, `cameras`, `bodycams`, `calendar`, `patients`, `treatments`, `hospital`, `bills`, `docket`, `judicial`, `expungements` or `bar`. `id` opens a record on that page, `params` are passed to the page.

```lua title="client.lua"
RegisterCommand('lookup', function(_, args)
    exports['koja-mdt']:OpenMdtPage('citizens', args[1])
end)
```

## Local officer

The client caches the local unit and keeps it updated from live pushes. Values are `nil` when the player is not an MDT member.

| Export | Returns | Notes |
|---|---|---|
| `IsOnDuty()` | `boolean` | |
| `GetMyDepartment()` | `departmentId, { id, label, short, type, color }` | |
| `GetMyOfficer()` | `officer` | Copy of the cached unit: `source`, `identifier`, `name`, `callsign`, `badge`, `department`, `rank`, `gradeLabel`, `status`, `onDuty`, `call`, `image`. |
| `GetMyCallsign()` | `string` | |
| `GetMyStatus()` | `status, callId` | |
| `SetMyStatus(status)` | `true` or `nil, errorCode` | **Yields.** `status` must be one of the department's statuses. Errors `invalid`, `not_in_game`, hook reason. |
| `SetDuty(state)` | `onDuty` or `nil, errorCode` | **Yields.** Clocks in (`true`) or out (`false`), with the same rules as the tablet toggle and the `officer:duty` hook. |
| `ToggleDuty()` | `onDuty` or `nil, errorCode` | **Yields.** |

```lua title="client.lua"
CreateThread(function()
    local onDuty, err = exports['koja-mdt']:SetDuty(true)
    if onDuty then exports['koja-mdt']:SetMyStatus('available') end
end)
```

## Dispatch

| Export | Returns | Notes |
|---|---|---|
| `SendDispatch(data, cb?)` | `boolean` | See below. |
| `SendPanic()` | | Same as the panic key (law enforcement only, server cooldown). |
| `GetActiveCalls()` | `call[]` or `nil, errorCode` | **Yields.** Active calls the player's department can see. Needs `dispatch.view`. |
| `AttachToCall(callId)` | `call` or `nil, errorCode` | **Yields.** Errors `not_found`, `not_in_game`, `cancelled`. |
| `DetachFromCall(callId)` | `call` or `nil, errorCode` | **Yields.** |
| `SetCallWaypoint(callId)` | `true` or `nil, errorCode` | **Yields.** Sets the GPS to the call. |
| `GetQuickDispatchCalls()` | `call[]` | The calls currently on the quick-dispatch cards. |
| `ShowQuickDispatch(visible)` | `boolean` | Shows or hides the quick-dispatch cards. `false` when quick dispatch is disabled. |

### `SendDispatch(data, cb?)`

The client never creates calls freely. Two validated server paths are used:

- **Citizen path** (default): `{ code, message, anonymous }`. `code` must be a public command (`911`, `311`) or a code registered with `public = true` (`RegisterDispatchCode`). The server takes the player's real position, applies the rate limit and `Config.Dispatch.publicCooldown`, and notifies the player. The phone number comes from `ClientIntegrations.GetPhoneNumber()`.
- **Officer path**: `{ asOfficer = true, code, title, message, priority }`. Runs the `dispatch:create` tablet action at the player's position, so it needs `dispatch.manage`. `cb(ok, callOrError)` runs when the server answers.

```lua title="client.lua"
exports['koja-mdt']:SendDispatch({ code = '911', message = 'Shots fired near the pier' })

exports['koja-mdt']:SendDispatch({ asOfficer = true, code = '10-80', title = 'Pursuit', message = 'Northbound on Route 1', priority = 1 }, function(ok, result)
    if not ok then print('dispatch failed: ' .. tostring(result)) end
end)
```

## Bodycam

| Export | Returns | Notes |
|---|---|---|
| `GetBodycamState()` | `state, data` | `state`: `off`, `standby` or `recording`. `data` is the last state the server sent (`serial`, `reason`, `since`, …) or `nil`. |
| `IsBodycamRecording()` | `boolean` | |
| `SetBodycam(mode)` | `boolean` | Sends a request to the server: `on`, `off`, `record` (also `rec`, `start`), `stop`, `toggle`, `power`. The server decides (duty, department settings, item). |

```lua title="client.lua"
AddEventHandler('my-weapons:drawn', function()
    if not exports['koja-mdt']:IsBodycamRecording() then exports['koja-mdt']:SetBodycam('record') end
end)
```

## Client events

Local events, listen with `AddEventHandler`. They are listed with their arguments on the [events](/new/mdt/api/events) page: `koja-mdt:client:opened`, `koja-mdt:client:closed`, `koja-mdt:client:dutyChanged`, `koja-mdt:client:statusChanged`, `koja-mdt:client:newCall`, `koja-mdt:client:callUpdated`, `koja-mdt:client:notification` and `koja-mdt:client:bodycamChanged`.

```lua title="client.lua"
AddEventHandler('koja-mdt:client:dutyChanged', function(onDuty, department)
    exports['my-hud']:SetBadgeVisible(onDuty)
end)
```
