Update the sh
This commit is contained in:
parent
bed3495be2
commit
9cbf88d3a0
247
REVIEW-AND-GUIDE.md
Normal file
247
REVIEW-AND-GUIDE.md
Normal file
@ -0,0 +1,247 @@
|
||||
# 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`
|
||||
117
scripts/build-to-moh.sh
Normal file
117
scripts/build-to-moh.sh
Normal file
@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# build-to-moh.sh
|
||||
# يبني تطبيق Fchaty وينشئ نسخة باسم "to-moh" على سطح المكتب
|
||||
# هذه النسخة جاهزة للإرسال لرفيقك محمد
|
||||
#
|
||||
# الاستخدام:
|
||||
# chmod +x scripts/build-to-moh.sh
|
||||
# ./scripts/build-to-moh.sh
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPOSITORY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PROJECT_DIR="$REPOSITORY_DIR/FchatyApp"
|
||||
BUILD_DIR="$PROJECT_DIR/.build"
|
||||
PRODUCT_NAME="FchatyApp"
|
||||
ICON_PATH="$PROJECT_DIR/Resources/Fchaty.icns"
|
||||
|
||||
# النسخة على سطح المكتب
|
||||
DESKTOP_DIR="$HOME/Desktop"
|
||||
OUTPUT_APP="$DESKTOP_DIR/to-moh.app"
|
||||
OUTPUT_ZIP="$DESKTOP_DIR/to-moh.zip"
|
||||
|
||||
STAGING_DIR="$(mktemp -d "${TMPDIR:-/tmp}/fchaty-to-moh.XXXXXX")"
|
||||
STAGING_APP="$STAGING_DIR/to-moh.app"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$STAGING_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# ─── التحقق من المتطلبات ─────────────────────────────────────────────────────
|
||||
|
||||
if [[ ! -f "$PROJECT_DIR/Package.swift" ]]; then
|
||||
echo "ERROR: FchatyApp package not found: $PROJECT_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$ICON_PATH" ]]; then
|
||||
echo "ERROR: Application icon not found: $ICON_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ─── البناء ───────────────────────────────────────────────────────────────────
|
||||
|
||||
echo "========================================"
|
||||
echo " بناء نسخة to-moh من Fchaty"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
echo "[1/5] تنظيف الـ build القديم..."
|
||||
rm -rf "$BUILD_DIR"
|
||||
|
||||
echo "[2/5] تشغيل الاختبارات..."
|
||||
swift test --package-path "$PROJECT_DIR"
|
||||
|
||||
echo "[3/5] بناء النسخة النهائية (release)..."
|
||||
swift build --configuration release --package-path "$PROJECT_DIR"
|
||||
|
||||
EXECUTABLE_PATH="$PROJECT_DIR/.build/release/$PRODUCT_NAME"
|
||||
if [[ ! -x "$EXECUTABLE_PATH" ]]; then
|
||||
echo "ERROR: Release executable not found: $EXECUTABLE_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[4/5] إنشاء app bundle باسم to-moh..."
|
||||
mkdir -p "$STAGING_APP/Contents/MacOS" "$STAGING_APP/Contents/Resources"
|
||||
|
||||
# نسخ Info.plist مع تعديل اسم العرض
|
||||
cp "$PROJECT_DIR/Info.plist" "$STAGING_APP/Contents/Info.plist"
|
||||
# تغيير اسم العرض (اختياري — يبقى الاسم الداخلي Fchaty لكن الأيقونة والملف باسم to-moh)
|
||||
|
||||
cp "$EXECUTABLE_PATH" "$STAGING_APP/Contents/MacOS/$PRODUCT_NAME"
|
||||
cp "$ICON_PATH" "$STAGING_APP/Contents/Resources/Fchaty.icns"
|
||||
|
||||
# التوقيع (ad-hoc — كافي للاستخدام الشخصي)
|
||||
codesign --force --sign - --timestamp=none --entitlements "$PROJECT_DIR/FchatyApp.entitlements" "$STAGING_APP"
|
||||
|
||||
# ─── النسخ لسطح المكتب ───────────────────────────────────────────────────────
|
||||
|
||||
echo "[5/5] نقل النسخة لسطح المكتب..."
|
||||
|
||||
# حذف نسخة قديمة إذا موجودة
|
||||
if [[ -e "$OUTPUT_APP" ]]; then
|
||||
rm -rf "$OUTPUT_APP"
|
||||
fi
|
||||
if [[ -e "$OUTPUT_ZIP" ]]; then
|
||||
rm -f "$OUTPUT_ZIP"
|
||||
fi
|
||||
|
||||
mv "$STAGING_APP" "$OUTPUT_APP"
|
||||
|
||||
# إنشاء ملف zip جاهز للإرسال
|
||||
echo "إنشاء ملف zip للإرسال..."
|
||||
cd "$DESKTOP_DIR"
|
||||
zip -r -q "to-moh.zip" "to-moh.app"
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo " تم بنجاح!"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "الملفات على سطح المكتب:"
|
||||
echo " - to-moh.app (التطبيق — يمكنك تجربته)"
|
||||
echo " - to-moh.zip (مضغوط — أرسله لمحمد)"
|
||||
echo ""
|
||||
echo "ملاحظات مهمة:"
|
||||
echo " - محمد لازم يفك الضغط ويحط التطبيق بمجلد Applications"
|
||||
echo " - أول مرة يفتحه رح يطلعله تحذير أمان"
|
||||
echo " - يروح على System Settings → Privacy & Security → يسمح بالتطبيق"
|
||||
echo " - أو يضغط كليك يمين على التطبيق → Open"
|
||||
echo ""
|
||||
echo "النسخة التي تشغلها أنت:"
|
||||
echo " شغّل: ./scripts/run-macos-app.sh"
|
||||
echo " هذا يثبت النسخة بـ /Applications/Fchaty.app"
|
||||
echo ""
|
||||
Loading…
Reference in New Issue
Block a user