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_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"
}
Логіка:
1. Побудувати Matrix username: daarion_{user_id[:8]}
2. Спробувати логін з відомим паролем
3. Якщо користувач не існує — створити через admin API
4. Повернути 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. Нова схема¶
- При завантаженні сторінки: ```tsx // /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 клієнта:
tsx // Використовуємо 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 }); -
Отримання історії:
tsx const messages = await matrixClient.getMessages(roomId, { limit: 50 }); -
Відправка повідомлень:
tsx await matrixClient.sendMessage(roomId, { msgtype: 'm.text', body: text }); -
Підписка на нові повідомлення:
tsx // 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