248 lines
12 KiB
Markdown
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`
|