# API tích hợp trò chơi tương tác NBTiktok v2

API này cho phép máy khách đăng nhập bằng email và mật khẩu, duy trì Session, tùy chọn xác thực với backend trò chơi triển khai độc lập, nhận sự kiện WebSocket và kiểm thử trong chế độ Debug.

Chỉ sử dụng HTTPS Origin công khai do NBTiktok cung cấp. Không kết nối trực tiếp tới cổng container, cổng chẩn đoán, `/internal/*` hoặc tuyến quản trị.

## Quy trình tích hợp

1. Gọi `POST /api/game-client/v2/login` với email, mật khẩu, `gameId`, `mode` và `clientBuild`; không dùng CAPTCHA.
2. Với tích hợp trực tiếp bảng xếp hạng hoặc thuộc tính người chơi, không gửi `backend` và dùng `credentials.gameApi`.
3. Khi backend độc lập được bật, đăng nhập trả về `backendAuth` và `credentials.gameBackend`; URL do cấu hình đáng tin cậy của máy khách chọn.
4. Chỉ gửi `credentials.gameBackend` tới URL đáng tin cậy do build máy khách chọn.
5. Kết nối bằng WebSocket Ticket dùng một lần và kiểm tra `ready`; Production lưu bền vững trước ACK, Debug phản hồi `debug_command`.
6. Luân chuyển toàn bộ credential và gia hạn WSS backend độc lập tại `refreshAt`; xin Ticket nền tảng mới khi ngắt kết nối và kết thúc bằng `DELETE /api/game-client/v2/session`.

## Các tuyến công khai

| Khả năng | Method / Path | Xác thực |
| --- | --- | --- |
| Đăng nhập và tạo Session | `POST /api/game-client/v2/login` | Chỉ email, mật khẩu, trò chơi, mode và build |
| Ủy quyền thiết bị đã xóa | `POST /api/game-client/v2/device-authorizations` và `/token` | Trả `410 DEVICE_AUTHORIZATION_REMOVED` |
| CAPTCHA đã xóa | `GET /api/game-client/v2/captcha` | Trả `410 CAPTCHA_LOGIN_REMOVED` |
| Xem Session | `GET /api/game-client/v2/session` | `Bearer <sessionAccessToken>` |
| Luân chuyển thông tin xác thực | `POST /api/game-client/v2/session/refresh` | `Bearer <refreshToken>`; body rỗng |
| Cấp WebSocket Ticket | `POST /api/game-client/v2/session/websocket-ticket` | `Bearer <sessionAccessToken>`; body rỗng |
| Đăng xuất | `DELETE /api/game-client/v2/session` | `Bearer <sessionAccessToken>` |
| Kết nối sự kiện | `GET /ws/game-client/v2?ticket=...` | Ticket dùng một lần, hiệu lực 60 giây |
| Khám phá game công khai | `GET /api/games/v2` | Không |
| Hướng dẫn game theo locale | `GET /api/games/v2/:gameId/guide?locale=...` | Không |
| Danh mục quà tặng | `GET /api/games/v2/:gameId/gifts` | Không |
| Ảnh chụp trò chơi | `GET /api/games/v2/:gameId/screenshots/:position` | Không; `position` từ 1–5 |
| Bảng xếp hạng và thuộc tính người chơi | `/openapi/v2/*` | `Bearer <credentials.gameApi.token>` |
| HTTP/WSS backend độc lập | URL đáng tin cậy của máy khách | HTTP Bearer; frame đầu WSS `nbt.auth` |
| Khóa xác minh backend | `GET /.well-known/game-backend-jwks.json` | Không; cache theo `Cache-Control` |

Ngoại trừ `204` và ảnh nhị phân, body HTTP là JSON. Đăng nhập dùng envelope phẳng `{ "error": "INVALID_CREDENTIALS" }`. Session, WebSocket Ticket, `/openapi/v2/*` và giới hạn gateway dùng `{ "error": { "code": "ERROR_CODE", "requestId": "018f1f6d-7b2a-7e34-a6cc-5e5f3fb70420" } }`.

## Đăng nhập bằng email và mật khẩu

```http
POST /api/game-client/v2/login
Content-Type: application/json
```

```json
{
  "email": "streamer@example.com",
  "password": "account-password",
  "gameId": "game_one",
  "mode": "production",
  "clientBuild": 120
}
```

Mật khẩu chỉ được gửi tới nền tảng NBTiktok và phải xóa khỏi bộ nhớ sau request. Không lưu trong PlayerPrefs, cấu hình, log, URL, báo cáo lỗi hay telemetry. Nền tảng không bao giờ đặt email, mật khẩu, Token đăng nhập hoặc Cookie vào JWT backend hay gửi chúng cho backend độc lập. Email và mật khẩu chọn tài khoản cùng Sandbox riêng của tài khoản.

## Phản hồi đăng nhập thành công

```json
{
  "session": {
    "sessionId": "6fe3890b-bb7c-4e45-b2b2-1db2d77751ce",
    "streamerId": "9f833c5b-ec62-4530-9ae4-0af8130a26b6",
    "authorizationId": "20637113-6d4e-490c-ac6d-74242cd1de85",
    "gameId": "game_one",
    "mode": "production",
    "credentialSource": "user_session",
    "credentialId": null,
    "roomId": "creator-room",
    "clientBuild": 120,
    "minimumClientBuild": 100,
    "expiresAt": "2026-08-20T12:00:00.000Z",
    "absoluteExpiresAt": "2026-08-26T12:00:00.000Z"
  },
  "credentials": {
    "sessionAccess": {
      "token": "opaque-session-access-token",
      "expiresAt": "2026-08-19T12:30:00.000Z"
    },
    "gameApi": {
      "token": "signed-game-api-token",
      "expiresAt": "2026-08-19T12:10:00.000Z"
    },
    "gameBackend": {
      "token": "signed-game-backend-token",
      "expiresAt": "2026-08-19T12:30:00.000Z"
    },
    "refresh": {
      "token": "opaque-single-use-refresh-token",
      "expiresAt": "2026-08-20T12:00:00.000Z",
      "refreshAt": "2026-08-19T12:08:00.000Z"
    },
    "websocket": {
      "url": "/ws/game-client/v2",
      "ticket": "opaque-single-use-ticket",
      "expiresAt": "2026-08-19T12:01:00.000Z"
    }
  },
  "backendAuth": {
    "audience": "urn:nbtiktok:game-backend:game_one",
    "operatorName": "Example Studio",
    "privacyPolicyUrl": "https://game.example.com/privacy",
    "configVersion": 3
  }
}
```

Các ID, phòng, chế độ, chính sách bản dựng, thời hạn và đường dẫn trong phản hồi là dữ liệu có thẩm quyền. Không ghi mật khẩu, Token hay Ticket vào PlayerPrefs, tệp cấu hình, log, URL, báo cáo lỗi hoặc telemetry.

Session access Token tối đa 30 phút, Game API Token tối đa 10 phút và JWT backend độc lập tối đa 30 phút. Vì `refreshAt` là điểm sớm nhất của mọi credential ngắn hạn, cả bộ thường vẫn refresh khoảng tám phút sau khi phát hành. Refresh Token chỉ dùng một lần, Session trượt tối đa 24 giờ; Ticket tối đa 60 giây và chỉ tiêu thụ một lần.

Mỗi tài khoản chỉ có một Production Session; đăng nhập Production mới thu hồi Session cũ kể cả khi là trò chơi khác. Debug giữ một Session theo `authorizationId`, có thể chạy cùng Production; đăng nhập Debug mới cho cùng quyền thay Session Debug cũ.

### Lỗi đăng nhập và tạo Session

| HTTP | `error` | Xử lý |
| --- | --- | --- |
| 400 | `MODE_INVALID` | Sửa `mode` |
| 400 | `CLIENT_BUILD_INVALID` | Gửi số nguyên an toàn từ 1 |
| 401 | `INVALID_CREDENTIALS` | Email không tồn tại, mật khẩu sai hoặc tài khoản tạm khóa; hiển thị cùng một thông báo |
| 403 | `DEALER_UNAVAILABLE` | Kiểm tra đại lý và tư cách thành viên |
| 403 | `GAME_DISABLED` | Dừng đăng nhập trò chơi bị tắt |
| 403 | `GAME_NOT_AUTHORIZED` | Cấp quyền trò chơi hợp lệ |
| 404 | `GAME_NOT_FOUND` | Sửa `gameId` |
| 409 | `ROOM_ID_REQUIRED` | Thêm ID phòng livestream vào hồ sơ |
| 409 | `PROFILE_INCOMPLETE` | Hoàn thiện vùng trong hồ sơ |
| 409 | `GIFT_CREDIT_EXHAUSTED` | Khôi phục hạn mức quà rồi đăng nhập |
| 409 | `BILLING_CONFIG_REQUIRED` | Chờ cấu hình thanh toán Production |
| 426 | `CLIENT_BUILD_UNSUPPORTED` | Nâng lên `minimumClientBuild` trả về |
| 429 | `LOGIN_RATE_LIMITED` / `RATE_LIMITED` | Tuân theo `Retry-After` và backoff |
| 503 | `AUTH_UNAVAILABLE` / `BILLING_BACKLOG_PAUSED` | Backoff lũy thừa và báo người dùng |

Phản hồi `426` dùng envelope V2: `error.code` là `CLIENT_BUILD_UNSUPPORTED`, `error.requestId` là ID tương quan, và `error.details` chứa `clientBuild` đã gửi cùng `minimumClientBuild` hiện tại.

## Định tuyến backend độc lập

NBTiktok không lưu hoặc trả về URL API nghiệp vụ. Bản cục bộ, kiểm thử và chính thức dùng cùng JWT `credentials.gameBackend`, audience chính xác theo trò chơi và cách xác minh JWKS. Máy khách đọc URL từ cấu hình build đáng tin cậy; build phát triển có thể ghi đè cục bộ. Request đăng nhập không gửi URL hay Secret bổ sung.

Phản hồi chỉ có `backendAuth` gồm audience, nhà vận hành, chính sách riêng tư và phiên bản cấu hình, không có route. Chỉ gửi JWT tới Origin đáng tin cậy do máy khách chọn và từ chối redirect khác Origin.

HTTP gửi JWT bằng `Authorization: Bearer`. WebSocket của backend độc lập dùng subprotocol cố định `nbt.game-backend.v1` và chỉ gửi cùng JWT trong text frame đầu tiên `{"type":"nbt.auth","token":"..."}`, không bao giờ trong URL. Sau refresh Session, gửi lại frame đó với JWT mới trên kết nối hiện tại. Đây không phải `/ws/game-client/v2` của nền tảng; endpoint nền tảng tiếp tục dùng `credentials.websocket.ticket` một lần.

Build đáng tin cậy nên cấu hình riêng `NBT_GAME_BACKEND_HTTP_URL` và `NBT_GAME_BACKEND_WS_URL`. Chỉ localhost, `127.0.0.0/8` và `[::1]` được dùng `http/ws` rõ; địa chỉ từ xa phải dùng `https/wss`.

Portal dùng xác thực lại email/mật khẩu NBTiktok cùng Authorization Code và PKCE. Callback chính thức khớp chính xác URI HTTPS đã đăng ký; callback cục bộ chỉ dùng `127.0.0.1` hoặc `[::1]` với đường dẫn `/__nbt/callback`.

## Endpoint đã ngừng hoạt động


## Session và luân chuyển thông tin xác thực

```http
GET /api/game-client/v2/session
Authorization: Bearer <sessionAccessToken>
```

Mọi Session giữ các trường tương thích `credentialSource="user_session"` và `credentialId=null`. Kiểm tra danh tính, mode, room, build và thời hạn; cấu hình định tuyến backend vẫn thuộc máy khách. `roomId` của Sandbox có thể là `null`.

```http
POST /api/game-client/v2/session/refresh
Authorization: Bearer <refreshToken>
```

Phản hồi body rỗng trả Session, `backendAuth` có thẩm quyền, access, Game API, game backend tùy chọn và refresh Token mới; không trả `websocket`. Cài đặt cả bộ mới theo cách nguyên tử, ngừng dùng Token HTTP cũ, gia hạn WebSocket backend độc lập bằng Backend JWT mới và bỏ Ticket nền tảng chưa dùng. Nếu không còn `gameBackend`, đóng kết nối backend độc lập và ngừng gọi nó.

Refresh Token đã dùng trả `REFRESH_TOKEN_REUSED`, thu hồi Session và đóng socket với `4014`. Token không biết hoặc hết hạn trả `401 REFRESH_TOKEN_INVALID`; Session hết hạn tuyệt đối hoặc sai ngữ cảnh trả `401 SESSION_REAUTH_REQUIRED`. Nếu lỗi mạng làm trạng thái tiêu thụ không rõ ràng, không gửi lại Token đó; hãy xóa thông tin trong bộ nhớ và đăng nhập lại. `refreshAt` luôn là thời điểm ISO 8601; nếu đã đến hạn khi client hoạt động lại, hãy thực hiện ngay một refresh tuần tự. Khi nhận `SESSION_REAUTH_REQUIRED`, xóa credential và đăng nhập lại.

## Kết nối WebSocket và hàng rào

Đổi HTTPS Origin công khai thành `wss://`, rồi nối đường dẫn tương đối:

```text
wss://<platform-origin>/ws/game-client/v2?ticket=<single-use-ticket>
```

URL chỉ được chứa Ticket. Ticket sai, đã dùng hoặc hết hạn nhận `401`; trạng thái thanh toán có thể nhận `409`; thiếu dung lượng hoặc nguồn trực tiếp có thể nhận `503`. Sau khi ngắt kết nối, gọi `POST /api/game-client/v2/session/websocket-ticket` bằng access Token còn hạn.

Thông điệp nghiệp vụ đầu tiên phải là:

```json
{
  "type": "ready",
  "sessionId": "6fe3890b-bb7c-4e45-b2b2-1db2d77751ce",
  "userId": "account-user-id",
  "streamerId": "9f833c5b-ec62-4530-9ae4-0af8130a26b6",
  "authorizationId": "20637113-6d4e-490c-ac6d-74242cd1de85",
  "gameId": "game_one",
  "mode": "production",
  "credentialSource": "user_session",
  "credentialId": null,
  "roomId": "creator-room",
  "clientBuild": 120,
  "minimumClientBuild": 100,
  "leaseGeneration": 4
}
```

So sánh `sessionId`, `streamerId`, `authorizationId`, `gameId`, `mode`, `roomId`, `clientBuild`, `minimumClientBuild`; yêu cầu `userId` là UUID và `leaseGeneration` là số nguyên dương. Từ chối nếu khác thẩm quyền cục bộ. Gắn hàng đợi sự kiện và ACK với `gameId` cùng `leaseGeneration`; không tái sử dụng trạng thái kết nối cũ. Nhịp tim dùng Ping/Pong của giao thức WebSocket, không gửi JSON `{ "type": "pong" }` tùy chỉnh.

## Sự kiện Production và ACK tin cậy

Sau `ready`, kéo sự kiện tin cậy chưa xác nhận. `limit` mặc định 100 và tối đa 200.

```json
{
  "type": "pull",
  "gameId": "game_one",
  "leaseGeneration": 4,
  "limit": 100
}
```

```json
{
  "type": "event",
  "gameId": "game_one",
  "leaseGeneration": 4,
  "event": {
    "eventId": "2d2a0ef5-2388-45de-a255-2dcc73543d3b",
    "eventType": "gift",
    "occurredAt": "2026-08-19T12:00:00.000Z",
    "payload": {
      "player": { "sourcePlayerId": "viewer-id", "displayName": "Viewer" },
      "gift": {
        "providerGiftId": "5655",
        "displayName": "Rose",
        "totalCount": 1,
        "deltaCount": 1,
        "unitDiamondCount": 5,
        "giftCoins": 5,
        "missingPrice": false,
        "groupId": "gift-group",
        "repeatEnd": true
      }
    }
  }
}
```

`eventId` là UUID, `occurredAt` là ISO 8601; `gameId` và `leaseGeneration` chỉ ở envelope ngoài. `eventType` gồm `comment`, `like`, `gift`, `stream_end`. Bình luận dùng `payload.player` cùng `content`; lượt thích dùng `payload.player` cùng `count`; quà dùng `payload.player` và đối tượng `payload.gift` ở trên.

```json
[
  {
    "eventId": "b59fc69c-4c6a-4c09-990b-e0d6514b03fd",
    "eventType": "comment",
    "occurredAt": "2026-08-19T12:00:01.123Z",
    "payload": { "player": { "sourcePlayerId": "viewer-id", "displayName": "Viewer" }, "content": "join" }
  },
  {
    "eventId": "3a83d2f9-2095-4821-926d-ff8a80a0e771",
    "eventType": "like",
    "occurredAt": "2026-08-19T12:00:02.000Z",
    "payload": { "player": { "sourcePlayerId": "viewer-id", "displayName": "Viewer" }, "count": 12 }
  }
]
```

Trong chuỗi quà, `totalCount` là tổng tích lũy còn `deltaCount` là phần tăng của sự kiện; cấp hiệu ứng theo `deltaCount`. `groupId` có thể null, `repeatEnd` báo kết thúc chuỗi. `unitDiamondCount` có thể bằng 0 với `missingPrice: true`; trò chơi không được suy ra thanh toán nền tảng và không coi `ack_result.consumed` là số kim cương.

Chỉ `comment` và `gift` được lưu để phát lại và cần ACK. `like` chỉ thời gian thực, không ACK. `stream_end` không phát lại hoặc ACK và sau đó đóng bằng `4007`. Bỏ qua trường mới, nhưng luôn dùng `eventId` chuẩn để chống lặp.

ACK có nghĩa là hiệu ứng và bản ghi xử lý đã được lưu bền vững, không chỉ là đã nhận WebSocket. Không ACK sự kiện thất bại để Core phát lại. Nếu sự kiện phát lại đã hoàn tất cục bộ, bỏ qua hiệu ứng và ACK lại cùng `eventId`.

```json
{
  "type": "ack",
  "gameId": "game_one",
  "leaseGeneration": 4,
  "eventIds": ["2d2a0ef5-2388-45de-a255-2dcc73543d3b"]
}
```

Mỗi lô tối đa 500 UUID không trùng. Khuyến nghị gửi sau 25ms hoặc đủ 100 sự kiện, điều kiện nào đến trước; mỗi kết nối chỉ có một lô đang chờ.

```json
{
  "type": "ack_result",
  "gameId": "game_one",
  "leaseGeneration": 4,
  "acknowledged": 1,
  "consumed": 1,
  "duplicate": 0
}
```

`acknowledged` là số xác nhận lần đầu, `consumed` là số sự kiện lần đầu vào kiểm toán tiêu thụ quà (không phải kim cương), `duplicate` là số đã xác nhận trước. Phải thỏa `acknowledged + duplicate === số eventIds đã gửi` và `consumed <= acknowledged`; lô phát lại toàn bộ có thể trả `acknowledged = 0, duplicate = N`. Kết quả sai là lệch giao thức: đóng kết nối, xóa hàng đợi ACK, xin Ticket mới và khôi phục từ phát lại.

## Giao thức Debug

Debug dùng cùng đăng nhập, refresh, Ticket, `ready` và hàng rào. `debug_command` chứa `commandId`, `requiresAck: true`, `transient: true` và `event` đầy đủ với `eventId`, `eventType`, `occurredAt`, `payload`. Lệnh không lưu, không phát lại, không vào sự kiện Production, kiểm toán hay thanh toán; thường phải trả trong 15 giây.

```json
{
  "type": "debug_command",
  "commandId": "da6c668b-9e00-48d0-8113-97fbd52dc43f",
  "gameId": "game_one",
  "leaseGeneration": 4,
  "requiresAck": true,
  "transient": true,
  "event": {
    "eventId": "75c93fa7-3e1e-4fbc-b736-37b21c49e3d6",
    "eventType": "gift",
    "occurredAt": "2026-08-19T12:00:03.000Z",
    "payload": {}
  }
}
```

Luôn phản hồi kết quả:

```json
{
  "type": "debug_command_ack",
  "gameId": "game_one",
  "leaseGeneration": 4,
  "commandId": "79ac82bb-a842-4817-aeb8-cf345db37639",
  "outcome": "processed"
}
```

Khi thất bại, dùng `"outcome": "failed"` và `errorCode` dài 1–128 ký tự, ví dụ `EFFECT_REJECTED`; khi thành công phải bỏ `errorCode`. Core xác nhận bằng `type: "debug_command_ack_received"`, `status` là `processed` hoặc `failed`. Máy khách Debug không được gửi Production `pull` hay `ack`.

## Lỗi, danh mục quà và đăng xuất

Lỗi giao thức có dạng `{ "type": "error", "error": "ERROR_CODE" }`. Với `SESSION_FENCE_STALE`, `ACK_EVENT_INVALID`, `ACK_FAILED` hoặc lỗi hàng rào Debug, hãy ngắt kết nối và khôi phục từ trạng thái bền vững.

| Mã đóng | Lý do | Tiếp tục Session | Hành động |
| --- | --- | --- | --- |
| `4001` | `SESSION_REPLACED` | Không | Dừng máy khách cũ, xóa thông tin |
| `4002` | `CLIENT_LOGOUT` | Không | Đăng xuất bình thường |
| `4003` | `SESSION_INVALID` / `SESSION_EXPIRED` / `SESSION_REAUTH_REQUIRED` | Không | Đăng nhập lại |
| `4004` | `AUTHORIZATION_INVALID` / `AUTHORIZATION_EXPIRED` | Không | Sửa quyền rồi đăng nhập |
| `4005` | `GAME_DELETED` / `GAME_DISABLED` | Không | Dừng trò chơi |
| `4006` | `CLIENT_BUILD_UNSUPPORTED` | Không | Nâng cấp máy khách |
| `4007` | `STREAM_ENDED` | Không | Kết thúc lượt Production |
| `4008` | `SOURCE_BINDING_CHANGED` | Không | Cập nhật hồ sơ rồi đăng nhập |
| `4009` | Quản trị hoặc phát hành dừng | Không | Hiện reason và xử lý phù hợp |
| `4010` | `SLOW_CONSUMER` | Có | Giảm nghẽn, backoff rồi kết nối |
| `4011` | `SOURCE_RELEASED` | Có | Backoff rồi xin Ticket mới |
| `4012` | `BALANCE_EXHAUSTED` | Không | Xóa thông tin và bổ sung hạn mức |
| `4013` | `HEARTBEAT_TIMEOUT` | Có | Kiểm tra mạng rồi backoff |
| `4014` | `REFRESH_TOKEN_REUSED` | Không | Xóa toàn bộ và đăng nhập lại |

Chỉ `4010`, `4011`, `4013` được kết nối lại bằng backoff lũy thừa có jitter và giới hạn; các mã khác kết thúc Session.

Danh mục công khai `GET /api/games/v2/game_one/gifts` không cần xác thực. Phản hồi có `game.gameId`, `game.displayName`; mỗi quà hợp lệ có `key` bắt buộc, `catalogGiftId`, `providerGiftId`, `displayName`, `diamondCount`, và `iconUrl` có thể null. `key` xác định hiệu ứng và chỉ duy nhất trong một game; các game khác nhau có thể dùng lại cùng key. Khi tải hoặc làm mới danh mục, hãy tạo ánh xạ `providerGiftId → key`. Sự kiện quà WebSocket vẫn chỉ chứa `providerGiftId`, không chứa `key`; nếu ID sự kiện không có trong danh mục, không được đoán hay tự ghép key, mà phải bỏ qua hiệu ứng an toàn hoặc dùng hiệu ứng dự phòng chung do game định nghĩa. Chỉ trả quà đang bật, đủ danh tính và giá; canonical gift event vẫn là sự thật khi chạy.

Đăng xuất bằng:

```http
DELETE /api/game-client/v2/session
Authorization: Bearer <sessionAccessToken>
```

Thành công trả `204 No Content`; WebSocket đóng với `4002 CLIENT_LOGOUT`. Sau đó xóa mã thiết bị, Token và Ticket khỏi bộ nhớ. Bảng xếp hạng phải dùng `credentials.gameApi.token` riêng. Xem [API bảng xếp hạng](./leaderboard.vi.md).

## Danh sách kiểm tra phát hành

- Chỉ kết nối HTTPS/WSS công khai của nền tảng và backend đáng tin cậy trong cấu hình build; không truy cập `/internal/*`.
- Kiểm tra cấu trúc đăng nhập mật khẩu, refresh, Ticket, `ready` và sự kiện.
- Kiểm tra HTTP Bearer, `nbt.auth`, gia hạn tại chỗ, hết hạn và URL đáng tin cậy của backend độc lập.
- Chỉ giữ thông tin trong bộ nhớ, che log và telemetry.
- Refresh tuần tự và xử lý kết quả mạng không chắc chắn.
- Mỗi lần kết nối lại xin Ticket mới và cập nhật `leaseGeneration`.
- `comment`, `gift` chống lặp bền vững bằng `eventId`; hiệu ứng lỗi không ACK.
- Gom ACK 25ms / 100 sự kiện, một lô đang chờ trên mỗi kết nối.
- Phân biệt `debug_command` với Production `event`.
- Phân biệt ba mã có thể kết nối lại với mã kết thúc.
- Đăng xuất bằng DELETE và xóa toàn bộ thông tin trong bộ nhớ.
