# Realtime

Mọi client chat đều nói chuyện với chat gateway tại `https://chat.hatang.dev` qua cùng một giao thức WebSocket. Trang này giải thích một phiên chạy từ đầu tới cuối ra sao, để bạn gỡ lỗi widget, hiểu SDK làm gì, hoặc tự viết client. Chi tiết từng khung nằm ở [tham chiếu giao thức realtime](https://hatang.dev/vi/docs/reference/realtime-protocol.md), các endpoint HTTP ở [tham chiếu chat gateway](https://hatang.dev/vi/docs/reference/chat-gateway.md).

## Chọn đúng giao diện

| Bạn muốn | Dùng |
|---|---|
| Đặt chat trên website | [Widget web](https://hatang.dev/vi/docs/guides/support/web-widget/install.md); widget tự nói giao thức |
| Đặt chat trong ứng dụng di động | SDK native ([Ứng dụng di động](https://hatang.dev/vi/docs/guides/support/mobile-apps.md)) |
| Tự xây giao diện chat cho khách | Giao thức phía khách mô tả bên dưới |
| Đọc và trả lời hội thoại từ server, bot hay CRM | [REST API Hỗ trợ](https://hatang.dev/vi/docs/guides/support/rest-api.md) với API key, kèm [webhook](https://hatang.dev/vi/docs/guides/getting-started/webhooks.md) |

Phía nhân viên của gateway phục vụ dashboard và ứng dụng Hạ Tầng trên iPhone, Android và Mac. Nó cần phiên đăng nhập của một nhân viên, không phải API key, nên tích hợp từ server hãy dùng REST API.

## Phiên của khách

Một khách truy cập (visitor) là một trình duyệt hoặc một bản cài ứng dụng trên một site. Phiên chạy qua năm bước.

### 1. Bootstrap

Client gọi `POST /widget/v1/sites/{siteId}/bootstrap` một lần khi khởi động.

- **Trình duyệt** được kiểm tra bằng `Origin`. Trang phải nằm trên tên miền của site, một tên miền con của nó, hoặc một trong các origin được phép thêm của site. Ngoài ra đều nhận `403 origin_not_allowed`.
- **Ứng dụng native** gửi khoá ứng dụng trong header `x-hatang-app-key` và `app.install_id` (id cố định cho mỗi bản cài) trong body. Không cần origin.

Body có thể mang `token` đã lưu (để tiếp tục đúng khách cũ), `page` hoặc `screen`, và `locale`. Response gồm:

| Trường | Là gì |
|---|---|
| `visitor_token` | Thông tin xác thực của khách. Lưu lại (cookie hoặc local storage trên web, keychain trong ứng dụng) và gửi lại lần sau |
| `visitor` | Hồ sơ khách truy cập |
| `config` | Cài đặt widget của site, chiến dịch và nút liên hệ |
| `availability` | Đội đang trực tuyến hay đang trong giờ làm việc |
| `conversation`, `timeline` | Hội thoại đang mở (nếu có) và 100 phần tử gần nhất khách được thấy |
| `ws_url` | Địa chỉ socket cần mở, ví dụ `wss://chat.hatang.dev/ws?site=site_...` |
| `protocol_version` | Hiện là `1` |

Các lời gọi HTTP khác trong phiên gửi token trong header `x-visitor-token`.

### 2. Kết nối và xác thực

Mở đúng `ws_url` đã nhận và gửi khung `auth` trong vòng 5 giây. Mỗi khung là JSON gồm loại `t`, dữ liệu `d`, phiên bản `v` và `id` tuỳ chọn mà server gửi lại trong câu trả lời.

```js
const ws = new WebSocket(boot.ws_url);
ws.onopen = () => ws.send(JSON.stringify({
  t: "auth", v: 1,
  d: { token: boot.visitor_token, site_id: "site_...", page: { url: location.href, title: document.title } },
}));
ws.onmessage = (e) => {
  const frame = JSON.parse(e.data);
  if (frame.t === "ready") render(frame.d.conversation, frame.d.timeline);
  if (frame.t === "message") append(frame.d);
};
```

Server trả lời bằng `ready`, mang thông tin khách, hội thoại đang mở, dòng thời gian và trạng thái trực của đội. Sau đó cả hai bên gửi `ping` mỗi 25 giây.

### 3. Gửi tin

Mỗi tin gửi đi có một `client_id` do client sinh ra:

```json
{ "t": "message.send", "id": "req_1", "v": 1, "d": { "client_id": "c_9f2", "content": "Shop còn hàng không ạ?" } }
```

1. Hiện tin ở trạng thái đang gửi và chờ tối đa 10 giây để nhận `message.ack`, trong đó có `id` của tin, `seq` và `conversation_id`. Tin đầu tiên của khách sẽ tạo hội thoại.
2. Hết thời gian chờ hoặc gặp lỗi thì giữ bong bóng ở trạng thái lỗi và gửi lại với **cùng** `client_id`. Server nhận ra và xác nhận tin gốc, nên gửi lại không bao giờ tạo hai tin.
3. Bản thân tin nhắn cũng quay về dưới dạng khung `message`. Loại trùng theo `id`.

Khách gửi được 5 tin mỗi 10 giây và 60 tin mỗi phút. Tệp được tải lên trước bằng `POST /widget/v1/sites/{siteId}/uploads` (tối đa 10 MB), rồi gửi theo id trong `attachments`.

### 4. Nhận

Khi đang kết nối, khách nhận:

| Khung | Khi nào |
|---|---|
| `message` | Nhân viên, trợ lý AI hoặc hệ thống nhắn vào một hội thoại của khách |
| `typing` | Nhân viên đang gõ |
| `read` | Phía nhân viên đã đọc tới một `seq` |
| `availability` | Đội chuyển sang trực tuyến hoặc vắng mặt |
| `conversation.update` | Trạng thái, số tin chưa đọc hoặc người đang xử lý của một hội thoại thay đổi |
| `event` | Sự kiện khách nên thấy, như đổi trạng thái hay chuyển sang người thật |
| `session` | Khách vừa được gộp vào một khách khác nhờ `identify` có chữ ký: lưu token mới |

Client báo lại những gì khách làm bằng `typing`, `read`, `page` (trình duyệt) hoặc `screen` (ứng dụng), và `identify` khi khách đăng nhập ([Nhận diện khách](https://hatang.dev/vi/docs/guides/support/web-widget/identity-verification.md)). Ứng dụng còn gửi `app.state` khi chuyển xuống nền, để trả lời tới tay khách bằng thông báo đẩy.

### 5. Kết nối lại mà không mất gì

Mọi tin nhắn và sự kiện trong một hội thoại có `seq` chỉ tăng. Giữ `seq` lớn nhất bạn đã thấy. Sau khi kết nối lại, gửi `resume {conversation_id, after_seq}` và server phát lại từ cơ sở dữ liệu mọi thứ bạn bỏ lỡ. Lịch sử cũ hơn tải qua HTTP với `before_seq`.

Khi socket đóng, xem mã đóng:

| Mã | Ý nghĩa | Làm gì |
|---|---|---|
| 4001 | Không có `auth` trong 5 giây | Kết nối lại và gửi `auth` trước |
| 4003 | Xác thực bị từ chối hoặc bị thu hồi | Dừng; bỏ token và bootstrap một khách mới |
| 4004 | Giao thức của client quá cũ | Trình duyệt tải lại trang; ứng dụng nhắc người dùng cập nhật |
| 4005 | Khung `session` vừa mang token mới | Kết nối lại ngay với token mới |
| Khác | Mạng hoặc gateway khởi động lại | Kết nối lại với backoff `min(30 s, 500 ms · 2^n)` cộng jitter |

## Phía nhân viên

Dashboard và các ứng dụng Hạ Tầng dùng cùng gateway này dưới tư cách nhân viên:

- **Socket.** Kết nối của nhân viên xác thực bằng một agent token ngắn hạn. Dashboard lấy token từ phiên đăng nhập và token này chỉ được chấp nhận từ origin của dashboard; ứng dụng di động và Mac nhận token khi nhân viên đăng nhập. Sau đó nhân viên theo dõi hội thoại, nhận các khung `message`, `conversation.upsert`, `typing` và trạng thái trực tuyến, và gửi tin, ghi chú, giao việc, đổi trạng thái, gắn tag, đặt nhắc việc.
- **HTTP.** Ứng dụng cũng gọi các endpoint `/agent/*`, ví dụ `GET /agent/conversations` để lấy danh sách hộp thư, `POST /agent/uploads` để tải tệp (10 MB, video 25 MB; tệp .mov được lưu thành MP4), `POST/PATCH/DELETE /agent/canned` cho câu trả lời mẫu riêng của nhân viên, và `/agent/conversations/{id}/scheduled` cùng `/agent/scheduled/{id}` cho tin nhắn hẹn giờ (mọi thay đổi tới tất cả nhân viên qua khung `scheduled`). Mỗi người được 300 lượt đọc và 60 lượt ghi mỗi phút.
- **Quyền.** Token mang vai trò của nhân viên. Người chỉ có quyền xem Hỗ trợ vẫn thấy hộp thư cập nhật trực tiếp nhưng không thao tác được ([Nhân viên và phân quyền](https://hatang.dev/vi/help/account/employees-and-roles)).

Vì các token này thuộc về một người và hết hạn trong vòng một giờ, chúng không dành cho tích hợp. Để tự động hoá hộp thư, gọi [REST API Hỗ trợ](https://hatang.dev/vi/docs/guides/support/rest-api.md) bằng API key và đăng ký [webhook](https://hatang.dev/vi/docs/guides/getting-started/webhooks.md).

## Xử lý sự cố

| Hiện tượng | Nguyên nhân thường gặp |
|---|---|
| `origin_not_allowed` khi bootstrap | Tên miền của trang không phải tên miền của site, tên miền con của nó, hay một origin được phép. Thêm vào ô **Origin được phép thêm** trên trang của website trong **Hỗ trợ → Kênh** |
| `invalid_visitor_token` | Token hết hạn, đã reset, hoặc thuộc site khác. Bootstrap lại không kèm token |
| `product_disabled` | Doanh nghiệp đang tắt module Hỗ trợ |
| Socket đóng với mã 4001 | Client gửi khung khác trước `auth`, hoặc chậm quá 5 giây |
| Tin hiện hai lần | Client không loại trùng bản `message` quay về theo `id` |
| Mất tin sau khi rớt mạng | Client không gửi `resume` với `seq` cuối cùng |

## Bước tiếp theo

- [Tham chiếu giao thức realtime](https://hatang.dev/vi/docs/reference/realtime-protocol.md): mọi khung, trường và giới hạn
- [Tham chiếu chat gateway](https://hatang.dev/vi/docs/reference/chat-gateway.md): endpoint HTTP cho widget, SDK và nhân viên
- [JS API](https://hatang.dev/vi/docs/guides/support/web-widget/javascript-api.md): điều khiển widget thay vì tự viết client
- [REST API Hỗ trợ](https://hatang.dev/vi/docs/guides/support/rest-api.md)

Source: https://hatang.dev/vi/docs/guides/support/realtime
