# 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 ` (DAARION JWT) - query param: `room_slug`, наприклад `energy` **Логіка:** 1. Валідувати JWT → отримати `user_id`. 2. Знайти `city_room` по `slug`: - витягнути `matrix_room_id` / `matrix_room_alias`. 3. Через internal виклик до `matrix-gateway`: - отримати Matrix access token для цього `user_id`. 4. Повернути фронтенду: ```json { "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:** ```json { "user_id": "87838688-d7c4-436c-9466-4ab0947d7730" } ``` **Response:** ```json { "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. Нова схема 1. **При завантаженні сторінки:** ```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]); ``` 2. **Створення 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 }); ``` 3. **Отримання історії:** ```tsx const messages = await matrixClient.getMessages(roomId, { limit: 50 }); ``` 4. **Відправка повідомлень:** ```tsx await matrixClient.sendMessage(roomId, { msgtype: 'm.text', body: text }); ``` 5. **Підписка на нові повідомлення:** ```tsx // Long-polling або sync matrixClient.onMessage((event) => { setMessages(prev => [...prev, mapMatrixEvent(event)]); }); ``` ### 4.3. Matrix Event → Chat Message Mapping ```tsx 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 клієнт: ```typescript // 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: 1. **Presence & Typing:** * слухати `m.presence`, `m.typing` → показувати "online/typing". 2. **Reactions & read receipts.** 3. **Attachments (фото/файли).** 4. **City Map інтеграція** (активність кімнат → візуалізація). --- ## 10. ACCEPTANCE CRITERIA - [ ] `/api/city/chat/bootstrap` повертає Matrix credentials для авторизованого користувача - [ ] Frontend підключається до Matrix і показує історію повідомлень - [ ] Користувач може надсилати повідомлення через DAARION UI - [ ] Повідомлення з'являються в Element Web і навпаки - [ ] Обробляються стани: loading, connecting, online, error