Skip to content

# 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`

Логіка:

1. Валідувати JWT → отримати `user_id`.
2. Знайти `city_room` по `slug`:
3. витягнути `matrix_room_id` / `matrix_room_alias`.
4. Через internal виклик до `matrix-gateway`:
5. отримати Matrix access token для цього `user_id`.
6. Повернути фронтенду:

    {
      "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. Нова схема

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]); ```

1. Створення 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 });`

2. Отримання історії: `tsx const messages = await matrixClient.getMessages(roomId, { limit: 50 });`

3. Відправка повідомлень: `tsx await matrixClient.sendMessage(roomId, { msgtype: 'm.text', body: text });`

4. Підписка на нові повідомлення: `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:

1. Presence & Typing:

2. слухати `m.presence`, `m.typing` → показувати "online/typing".

3. Reactions & read receipts.

4. Attachments (фото/файли).

5. City Map інтеграція (активність кімнат → візуалізація).

## 10. ACCEPTANCE CRITERIA

- [ ] `/api/city/chat/bootstrap` повертає Matrix credentials для авторизованого користувача
- [ ] Frontend підключається до Matrix і показує історію повідомлень
- [ ] Користувач може надсилати повідомлення через DAARION UI
- [ ] Повідомлення з'являються в Element Web і навпаки
- [ ] Обробляються стани: loading, connecting, online, error