From 9cbf88d3a007a9c458d4c8888acafe4495c9ca1c Mon Sep 17 00:00:00 2001 From: diyaa Date: Mon, 27 Jul 2026 21:32:40 +0200 Subject: [PATCH] Update the sh --- REVIEW-AND-GUIDE.md | 247 ++++++++++++++++++++++++++++++++++++++++ scripts/build-to-moh.sh | 117 +++++++++++++++++++ 2 files changed, 364 insertions(+) create mode 100644 REVIEW-AND-GUIDE.md create mode 100644 scripts/build-to-moh.sh diff --git a/REVIEW-AND-GUIDE.md b/REVIEW-AND-GUIDE.md new file mode 100644 index 0000000..ba83f65 --- /dev/null +++ b/REVIEW-AND-GUIDE.md @@ -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` diff --git a/scripts/build-to-moh.sh b/scripts/build-to-moh.sh new file mode 100644 index 0000000..d24974b --- /dev/null +++ b/scripts/build-to-moh.sh @@ -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 ""