14 KiB
MATRIX CHAT CLIENT — DAARION.city
Version: 1.0.0
0. PURPOSE
Зробити так, щоб сторінка /city/[slug] у DAARION UI була повноцінним Matrix-чатом:
- використовує реальні Matrix rooms (
matrix_room_id,matrix_room_alias), - працює від імені поточного користувача DAARION,
- показує історію, нові повідомлення, статус підключення,
- використовує існуючий Chat Layout (UI), але замість тимчасового WebSocket — Matrix.
Це базовий крок для подальшого:
- Presence / Typing / Read receipts,
- агентів як ботів,
- 2D/2.5D City Map з live-активністю.
1. ARCHITECTURE OVERVIEW
┌─────────────────────────────────────────────────────────────────┐
│ DAARION Frontend │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ /city/[slug] Page │ │
│ │ ┌─────────────────┐ ┌────────────────────────────────┐ │ │
│ │ │ Room Info │ │ Matrix Chat Client │ │ │
│ │ │ (from API) │ │ - Connect to Synapse │ │ │
│ │ └─────────────────┘ │ - Send/receive messages │ │ │
│ │ │ - Show history │ │ │
│ │ └────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Backend │
│ ┌─────────────┐ ┌──────────────────┐ ┌───────────────────┐ │
│ │ auth-service│ │ city-service │ │ matrix-gateway │ │
│ │ (7020) │ │ (7001) │ │ (7025) │ │
│ │ │ │ │ │ │ │
│ │ JWT tokens │ │ /chat/bootstrap │ │ /user/token │ │
│ │ User→Matrix │ │ matrix_room_id │ │ Create rooms │ │
│ └─────────────┘ └──────────────────┘ └───────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Matrix Synapse (8018) │
│ - Rooms: !xxx:daarion.space │
│ - Users: @daarion_xxx:daarion.space │
│ - Messages, history, sync │
└─────────────────────────────────────────────────────────────────┘
Компоненти:
-
auth-service (7020)
- знає
user_id, email, Matrix user mapping.
- знає
-
matrix-gateway (7025)
- вміє створювати кімнати (вже реалізовано),
- буде видавати Matrix access tokens для користувачів.
-
city-service (7001)
- надає
matrix_room_id/matrix_room_alias, - новий endpoint
/chat/bootstrap.
- надає
-
web (Next.js UI)
- сторінка
/city/[slug], - компонент
ChatRoom, - Matrix chat client.
- сторінка
2. AUTH MODEL
Допущення (MVP):
- Користувач уже залогінений у DAARION (JWT).
- Для кожного
user_idвже існує Matrix-акаунт (авто-provisioning реалізовано раніше). - Потрібен bootstrap endpoint, який:
- по JWT → знаходить Matrix user,
- видає Matrix access token,
- повертає
matrix_room_idдля кімнати.
Matrix User Mapping
| DAARION user_id | Matrix user_id |
|---|---|
87838688-d7c4-436c-... |
@daarion_87838688:daarion.space |
3. BACKEND: CHAT BOOTSTRAP API
3.1. Endpoint: GET /api/city/chat/bootstrap
Розташування: city-service (логічно відповідає за City+Matrix інтеграцію)
Вхід:
- HTTP заголовок
Authorization: Bearer <access_token>(DAARION JWT) - query param:
room_slug, наприкладenergy
Логіка:
- Валідувати JWT → отримати
user_id. - Знайти
city_roomпоslug:- витягнути
matrix_room_id/matrix_room_alias.
- витягнути
- Через internal виклик до
matrix-gateway:- отримати Matrix access token для цього
user_id.
- отримати Matrix access token для цього
- Повернути фронтенду:
{
"matrix_hs_url": "https://app.daarion.space",
"matrix_user_id": "@daarion_87838688:daarion.space",
"matrix_access_token": "syt_...",
"matrix_room_id": "!gykdLyazhkcSZGHmbG:daarion.space",
"matrix_room_alias": "#city_energy:daarion.space",
"room": {
"id": "room_city_energy",
"slug": "energy",
"name": "Energy"
}
}
3.2. Matrix Gateway: User Token Endpoint
Endpoint: POST /internal/matrix/users/token
Request:
{
"user_id": "87838688-d7c4-436c-9466-4ab0947d7730"
}
Response:
{
"matrix_user_id": "@daarion_87838688:daarion.space",
"access_token": "syt_...",
"device_id": "DEVICE_ID"
}
Логіка:
- Побудувати Matrix username:
daarion_{user_id[:8]} - Спробувати логін з відомим паролем
- Якщо користувач не існує — створити через admin API
- Повернути access token
3.3. Security
- Endpoint вимагає валідний DAARION JWT.
matrix_access_token— короткоживучий (30 хв) або session-based.- Internal endpoints (
/internal/*) доступні тільки з Docker network.
4. FRONTEND: MATRIX CHAT CLIENT
4.1. Поточний Chat Layout
Вже існує:
- сторінка
/city/[slug], - компонент
ChatRoom:messages[],onSend(message),- індикатор підключення.
Зараз він працює через свій WebSocket/stub.
4.2. Нова схема
-
При завантаженні сторінки:
// /city/[slug]/page.tsx const [bootstrap, setBootstrap] = useState(null); const [status, setStatus] = useState<'loading' | 'connecting' | 'online' | 'error'>('loading'); useEffect(() => { async function init() { // 1. Отримати bootstrap дані const res = await fetch(`/api/city/chat/bootstrap?room_slug=${slug}`, { headers: { Authorization: `Bearer ${token}` } }); const data = await res.json(); setBootstrap(data); // 2. Ініціалізувати Matrix client setStatus('connecting'); } init(); }, [slug]); -
Створення Matrix клієнта:
// Використовуємо REST API напряму (без matrix-js-sdk для простоти MVP) const matrixClient = new MatrixRestClient({ baseUrl: bootstrap.matrix_hs_url, accessToken: bootstrap.matrix_access_token, userId: bootstrap.matrix_user_id, roomId: bootstrap.matrix_room_id }); -
Отримання історії:
const messages = await matrixClient.getMessages(roomId, { limit: 50 }); -
Відправка повідомлень:
await matrixClient.sendMessage(roomId, { msgtype: 'm.text', body: text }); -
Підписка на нові повідомлення:
// Long-polling або sync matrixClient.onMessage((event) => { setMessages(prev => [...prev, mapMatrixEvent(event)]); });
4.3. Matrix Event → Chat Message Mapping
function mapMatrixEvent(event: MatrixEvent): ChatMessage {
return {
id: event.event_id,
senderId: event.sender,
senderName: event.sender.split(':')[0].replace('@daarion_', 'User '),
text: event.content.body,
timestamp: new Date(event.origin_server_ts),
isUser: event.sender === bootstrap.matrix_user_id,
};
}
5. MATRIX REST CLIENT (Lightweight)
Замість важкого matrix-js-sdk, створимо легкий REST клієнт:
// lib/matrix-client.ts
export class MatrixRestClient {
private baseUrl: string;
private accessToken: string;
private userId: string;
constructor(config: MatrixClientConfig) {
this.baseUrl = config.baseUrl;
this.accessToken = config.accessToken;
this.userId = config.userId;
}
// Get room messages
async getMessages(roomId: string, options?: { limit?: number; from?: string }) {
const params = new URLSearchParams({
dir: 'b',
limit: String(options?.limit || 50)
});
if (options?.from) params.set('from', options.from);
const res = await fetch(
`${this.baseUrl}/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/messages?${params}`,
{ headers: this.authHeaders() }
);
return res.json();
}
// Send text message
async sendMessage(roomId: string, body: string) {
const txnId = `m${Date.now()}`;
const res = await fetch(
`${this.baseUrl}/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${txnId}`,
{
method: 'PUT',
headers: this.authHeaders(),
body: JSON.stringify({
msgtype: 'm.text',
body: body
})
}
);
return res.json();
}
// Join room
async joinRoom(roomId: string) {
const res = await fetch(
`${this.baseUrl}/_matrix/client/v3/join/${encodeURIComponent(roomId)}`,
{
method: 'POST',
headers: this.authHeaders()
}
);
return res.json();
}
// Sync (for real-time updates)
async sync(since?: string) {
const params = new URLSearchParams({ timeout: '30000' });
if (since) params.set('since', since);
const res = await fetch(
`${this.baseUrl}/_matrix/client/v3/sync?${params}`,
{ headers: this.authHeaders() }
);
return res.json();
}
private authHeaders() {
return {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json'
};
}
}
6. UI / UX REQUIREMENTS
6.1. Стан підключення
| Status | UI |
|---|---|
loading |
Skeleton loader |
connecting |
"Підключення до Matrix…" + spinner |
online |
Зелений індикатор "Онлайн" |
error |
Червоний індикатор + "Помилка підключення" + кнопка "Повторити" |
6.2. Відображення історії
- При завантаженні показувати останні 50 повідомлень
- Infinite scroll для старіших повідомлень
- Показувати дату-роздільник між днями
6.3. Надсилання повідомлень
- Enter — відправити
- Shift+Enter — новий рядок
- Показувати "sending..." стан
- При помилці — показати "Не вдалося відправити" + retry
7. LIMITATIONS / MVP
Поки що:
- ✅ Тільки текстові повідомлення (
m.text) - ❌ Без файлів/зображень
- ❌ Без threads/reactions
- ❌ Без read receipts
- ❌ Без typing indicators
Це все буде додано у наступних фазах.
8. API SUMMARY
City Service (7001)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/city/chat/bootstrap?room_slug=X |
Bootstrap Matrix chat |
Matrix Gateway (7025)
| Method | Endpoint | Description |
|---|---|---|
| POST | /internal/matrix/users/token |
Get/create user token |
9. ROADMAP AFTER THIS
Після Matrix Chat Client:
-
Presence & Typing:
- слухати
m.presence,m.typing→ показувати "online/typing".
- слухати
-
Reactions & read receipts.
-
Attachments (фото/файли).
-
City Map інтеграція (активність кімнат → візуалізація).
10. ACCEPTANCE CRITERIA
/api/city/chat/bootstrapповертає Matrix credentials для авторизованого користувача- Frontend підключається до Matrix і показує історію повідомлень
- Користувач може надсилати повідомлення через DAARION UI
- Повідомлення з'являються в Element Web і навпаки
- Обробляються стани: loading, connecting, online, error