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

12 KiB

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 خطوات نشر السيرفر

# 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 تحديث السيرفر بعد تعديلات على الكود

# 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

# على جهاز 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.

docker compose build && docker compose up -d

السبب: السيرفر يشتغل داخل Docker container، والكود يُنسخ أثناء الـ build. أي تعديل على index.js ما بيتأثر إلا بعد إعادة البناء.

تعديلات على تطبيق macOS (FchatyApp/):

نعم، لازم نعمل build جديد.

./scripts/run-macos-app.sh

السبب: التطبيق Swift compiled، أي تعديل بالكود يحتاج إعادة compile. النسخة القديمة لازم تُستبدل.

تعديلات على الـ .env فقط:

لا حاجة لـ rebuild، فقط restart:

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