fchaty/REVIEW-AND-GUIDE.md
2026-07-27 21:32:40 +02:00

248 lines
12 KiB
Markdown

# Fchaty - مراجعة شاملة ودليل النشر والصيانة
## 1. البنية الحالية للمشروع
المشروع مؤلف من قسمين رئيسيين:
### 1.1 السيرفر (relay-server/)
سيرفر Node.js يعمل كـ relay بين المستخدمين. لا يخزّن الرسائل بشكل دائم (فقط كطابور مؤقت للرسائل أثناء انقطاع الاتصال).
**الملفات:**
- `src/index.js` — كل كود السيرفر (Express + WebSocket)
- `package.json` — التبعيات (express, ws, multer)
- `Dockerfile` — بناء Docker image
- `docker-compose.yml` — تشغيل مع Traefik كـ reverse proxy
- `.env` — متغيرات البيئة (غير موجود بالـ git، لازم تنشئه يدوي)
**كيف يشتغل السيرفر:**
1. مستخدم A ينشئ كود اقتران عبر `POST /pairing/create` → يحصل على token + code
2. مستخدم B يدخل الكود عبر `POST /pairing/join` → يحصل على token
3. كلاهما يتصلون بالـ WebSocket على `wss://fchaty.diyaa.de/ws`
4. يرسلون `{type:"auth", token:"..."}` للمصادقة
5. الرسائل تمر عبر السيرفر من peer لـ peer
6. إذا الطرف الآخر offline، الرسالة تتخزّن بالـ queue على الدسك
**البيانات المخزنة على السيرفر:**
- `/data/files/` — الملفات المرفوعة (تنظّف تلقائياً بعد 30 يوم)
- `/data/queue/` — الرسائل المعلقة (تنتهي بعد 7 أيام)
- `/data/pairing/` — جلسات الاقتران (الأزواج الدائمة تبقى للأبد)
### 1.2 تطبيق macOS (FchatyApp/)
تطبيق Swift/SwiftUI يعمل على macOS 14+.
**الملفات الأساسية:**
- `Sources/FchatyApp/FchatyApp.swift` — نقطة الدخول + التابات (Chat, Pairing, Settings)
- `Sources/FchatyApp/Session/AppSession.swift` — إدارة الجلسة والاتصال
- `Sources/FchatyApp/Networking/WSClient.swift` — اتصال WebSocket
- `Sources/FchatyApp/Networking/RelayAPI.swift` — HTTP API calls
- `Sources/FchatyApp/Storage/MessageStore.swift` — تخزين الرسائل محلياً (JSON file)
- `Sources/FchatyApp/Storage/KeychainStore.swift` — تخزين tokens بالـ Keychain
- `Sources/FchatyApp/Features/Chat/ChatView.swift` — واجهة المحادثة
- `Sources/FchatyApp/Features/Chat/ChatViewModel.swift` — منطق إرسال الرسائل
- `Sources/FchatyApp/Features/Pairing/PairingView.swift` — واجهة الاقتران
- `Sources/FchatyApp/Features/Pairing/PairingViewModel.swift` — منطق الاقتران
- `Sources/FchatyApp/Features/Settings/SettingsView.swift` — الإعدادات
- `Sources/FchatyApp/Notifications/NotificationManager.swift` — الإشعارات
**كيف يعمل التطبيق:**
1. عند الفتح، يحاول يسترجع الجلسة القديمة من الـ Keychain
2. إذا في token محفوظ، يتصل بالـ WebSocket مباشرة
3. الرسائل تتحفظ محلياً بملف `~/Library/Application Support/de.diyaa.fchaty/messages.json`
4. الـ Keychain يحفظ: authToken, peerID, peerName, installationID
---
## 2. نقاط الضعف والمشاكل المكتشفة
### مشكلة 1: المحادثات القديمة تظهر أحياناً (خطيرة)
**السبب:** ملف `messages.json` يحفظ كل الرسائل بدون ربطها بجلسة معينة. لما تعمل unpair ثم pair جديد، الرسائل القديمة تبقى بالملف. التطبيق يحملها كلها عند الفتح بدون تصفية.
**الملف:** `AppSession.swift` سطر 146-151 — `loadStoredMessages()` يحمّل كل الرسائل بدون فلتر.
**الملف:** `AppSession.swift` سطر 115-124 — `unpair()` ما بيحذف الرسائل المحفوظة!
**الحل المطلوب:** عند `unpair()`، يجب حذف ملف `messages.json` أو إضافة `sessionID` لكل رسالة لتصفيتها.
### مشكلة 2: الاقتران (Pairing) يضل loading (خطيرة)
**الأسباب المحتملة:**
**أ) WebSocket reconnect محدود — بعد 5 محاولات بيوقف:**
`WSClient.swift` سطر 244 — `let delays: [UInt64] = [2, 4, 8, 16, 32]`
بعد ~62 ثانية من المحاولات، بيوقف نهائياً بدون ما يبلّغ المستخدم!
**ب) ما في مؤشر للمستخدم إنو الاتصال فشل نهائياً:**
الـ `connectionDidFail` بيحاول يعمل reconnect بس ما بيرجع error للـ UI إذا الـ reconnect فشل.
**ج) Auth timeout 10 ثواني:**
إذا السيرفر بطيء أو في مشكلة بالشبكة، الاتصال بينقطع بعد 10 ثواني.
**د) الـ Creator بيضل "Waiting for the other person..." حتى لو انقطع الـ WebSocket:**
`PairingView.swift` — مافي آلية لكشف إنو الاتصال فقد وإعادة عرض حالة الخطأ.
### مشكلة 3: fileRegistry بالذاكرة فقط (متوسطة)
**السبب:** `index.js` سطر 42 — `const fileRegistry = new Map()`
بعد إعادة تشغيل السيرفر، كل الملفات المرفوعة تصبح غير قابلة للتنزيل (404) رغم إنها لسا موجودة على الدسك.
**الحل المطلوب:** تحميل metadata الملفات من الدسك عند بدء السيرفر (مشابه لـ `loadQueueFromDisk`).
### مشكلة 4: ملف .env غير موجود (متوسطة)
ملف `.env` مطلوب لتشغيل Docker compose لكنه مستبعد من git. لما تعمل deploy جديد لازم تنشئه يدوي.
### مشكلة 5: أداء MessageStore (منخفضة حالياً)
كل عملية save تحمّل كل الرسائل من الدسك، تضيف الرسالة الجديدة، وتكتب الملف كامل من جديد. مع تراكم الرسائل، الأداء رح يسوء.
### مشكلة 6: لا يوجد آلية لمعرفة حالة الطرف الآخر (online/offline) (منخفضة)
التطبيق ما بيعرض إذا الشخص الآخر متصل أو لا، إلا لحظة الاتصال الأولى.
### مشكلة 7: الـ installationID vs peerID (مربكة)
المرسل يُعرّف بـ `KeychainStore.installationID` (UUID ثابت محلي) لكن السيرفر يستخدم `peerID` (يتغير مع كل pairing جديد). هذا يعني أن `message.from == KeychainStore.installationID` لن يتطابق أبداً مع الرسائل المرسلة لأن السيرفر يعيد كتابة `from` بالـ `peerID`. لكن لأن الرسائل الصادرة تُحفظ محلياً قبل ما تمر بالسيرفر، التطابق يعمل فقط للرسائل المحلية.
**ملاحظة مهمة:** هذا يعني إنو إذا الرسالة جاية من الـ queue (رسائل offline)، قد تظهر كرسالة واردة حتى لو أنت المرسل — لأن `from` فيها هو `peerID` مش `installationID`.
---
## 3. سيناريو الديبلوي الكامل (خطوة بخطوة لشخص مبتدئ)
### 3.1 متطلبات السيرفر
- سيرفر Linux مع Docker و Docker Compose
- Traefik كـ reverse proxy (مع شبكة `traefik-net` جاهزة)
- DNS: نطاق `fchaty.diyaa.de` يشير للسيرفر
### 3.2 خطوات نشر السيرفر
```bash
# 1. نسخ الملفات للسيرفر
scp -r relay-server/ user@server:/path/to/fchaty/
# 2. الدخول للسيرفر
ssh user@server
cd /path/to/fchaty/relay-server
# 3. إنشاء ملف .env
cat > .env << 'EOF'
PORT=3000
MAX_FILE_SIZE_MB=25
FILE_TTL_DAYS=30
ADMIN_TOKEN=your-secret-admin-token-here
TRAEFIK_CERT_RESOLVER=myresolver
EOF
# 4. بناء وتشغيل
docker compose build
docker compose up -d
# 5. التأكد من التشغيل
docker compose logs -f
# لازم تشوف: "Fchaty Relay listening on port 3000"
# 6. فحص الصحة
curl https://fchaty.diyaa.de/health
# لازم يرجع: {"ok":true,"connections":0,"queued":0}
```
### 3.3 تحديث السيرفر بعد تعديلات على الكود
```bash
# 1. نقل الملفات المعدلة
scp relay-server/src/index.js user@server:/path/to/fchaty/relay-server/src/
# 2. على السيرفر
ssh user@server
cd /path/to/fchaty/relay-server
# 3. إعادة بناء وتشغيل
docker compose build
docker compose up -d
# ملاحظة مهمة: إعادة التشغيل تقطع كل اتصالات WebSocket الحالية.
# التطبيقات رح تحاول تعيد الاتصال تلقائياً (5 محاولات).
# الرسائل المعلقة والـ pairing sessions تنحفظ على الدسك ولا تضيع.
# لكن fileRegistry (الملفات المرفوعة) رح تضيع من الذاكرة!
```
### 3.4 بناء تطبيق macOS
```bash
# على جهاز Mac (يتطلب Xcode Command Line Tools)
cd /path/to/f-chaty-native-new
# تشغيل السكربت الجاهز (يبني + يثبت + يفتح)
chmod +x scripts/run-macos-app.sh
./scripts/run-macos-app.sh
# هذا السكربت:
# 1. يحذف build artifacts القديمة
# 2. يشغل الـ tests
# 3. يبني release executable
# 4. ينشئ .app bundle مع الأيقونة والـ entitlements
# 5. يوقّع التطبيق (ad-hoc signing)
# 6. يثبته بـ /Applications/Fchaty.app
# 7. يفتحه
```
---
## 4. هل لازم نعمل بلد بعد كل تعديل؟
### تعديلات على السيرفر (relay-server/src/index.js):
**نعم، لازم نعمل rebuild + restart للـ Docker container.**
```bash
docker compose build && docker compose up -d
```
السبب: السيرفر يشتغل داخل Docker container، والكود يُنسخ أثناء الـ build. أي تعديل على `index.js` ما بيتأثر إلا بعد إعادة البناء.
### تعديلات على تطبيق macOS (FchatyApp/):
**نعم، لازم نعمل build جديد.**
```bash
./scripts/run-macos-app.sh
```
السبب: التطبيق Swift compiled، أي تعديل بالكود يحتاج إعادة compile. النسخة القديمة لازم تُستبدل.
### تعديلات على الـ .env فقط:
**لا حاجة لـ rebuild، فقط restart:**
```bash
docker compose down && docker compose up -d
```
### ملخص:
| نوع التعديل | لازم build جديد؟ | الأمر |
|---|---|---|
| كود السيرفر (index.js) | نعم | `docker compose build && docker compose up -d` |
| تطبيق macOS (Swift) | نعم | `./scripts/run-macos-app.sh` |
| .env فقط | لا، بس restart | `docker compose down && docker compose up -d` |
| docker-compose.yml | لا، بس restart | `docker compose down && docker compose up -d` |
---
## 5. قائمة المهام المطلوبة لاستقرار التطبيق
### أولوية عالية (لازم تنعمل فوراً):
- [ ] **إصلاح مشكلة المحادثات القديمة:** حذف `messages.json` عند `unpair()` في `AppSession.swift`
- [ ] **إصلاح مشكلة الاتصال اللامحدود:** جعل WebSocket reconnect لانهائي (مع backoff cap عند 60 ثانية) بدل 5 محاولات فقط، وإبلاغ الـ UI عند فقدان الاتصال
- [ ] **إصلاح fileRegistry:** تحميل metadata الملفات من الدسك عند بدء السيرفر بدل الاعتماد على الذاكرة فقط
- [ ] **إنشاء ملف `.env.example`:** لتسهيل الديبلوي على أي شخص
### أولوية متوسطة:
- [ ] **إضافة مؤشر حالة الاتصال بالـ UI:** يظهر بوضوح إذا كان الاتصال مفقود أو يحاول إعادة الاتصال
- [ ] **إضافة زر retry يدوي:** لما الاتصال يفشل نهائياً، المستخدم يقدر يحاول يدوياً
- [ ] **تحسين أداء MessageStore:** استخدام SQLite بدل JSON file
- [ ] **إضافة حد أقصى للرسائل المحلية:** لتجنب استهلاك الذاكرة
### أولوية منخفضة:
- [ ] **إضافة مؤشر online/offline للطرف الآخر**
- [ ] **إضافة typing indicator**
- [ ] **تشفير الرسائل end-to-end**
- [ ] **إضافة logging أفضل للسيرفر**
- [ ] **إضافة health check أكثر تفصيلاً**
---
## 6. سكربت إنشاء نسخة to-moh
السكربت `scripts/build-to-moh.sh` ينشئ نسخة من التطبيق باسم `to-moh` على سطح المكتب.
انظر الملف المنفصل: `scripts/build-to-moh.sh`