196 lines
6.5 KiB
Markdown
196 lines
6.5 KiB
Markdown
# Fchaty — الإصلاحات المطلوبة
|
|
|
|
ثلاث مشاكل أساسية تحتاج إصلاح. لكل مشكلة: الملف المعني، السطر، وصف المشكلة، والحل المطلوب.
|
|
|
|
---
|
|
|
|
## Fix 1: حذف الرسائل المحلية عند فك الاقتران (unpair)
|
|
|
|
**المشكلة:** عند `unpair()` الرسائل القديمة تبقى محفوظة بملف `messages.json` المحلي. عند إعادة الاقتران مع شخص جديد، المحادثة القديمة تظهر.
|
|
|
|
**الملف:** `FchatyApp/Sources/FchatyApp/Session/AppSession.swift`
|
|
|
|
**السطر:** 115-124 — دالة `unpair()`
|
|
|
|
**الكود الحالي:**
|
|
```swift
|
|
func unpair() {
|
|
incomingMessagesTask?.cancel()
|
|
incomingMessagesTask = nil
|
|
KeychainStore.clearAll()
|
|
let webSocket = webSocket
|
|
Task {
|
|
await webSocket.disconnect()
|
|
}
|
|
state = .unpaired
|
|
}
|
|
```
|
|
|
|
**المطلوب:**
|
|
1. إضافة دالة `deleteAll()` في `MessageStore` تحذف ملف `messages.json` من الدسك.
|
|
2. استدعاء `deleteAll()` ومسح `messages` array في `unpair()`.
|
|
|
|
**الملف الإضافي:** `FchatyApp/Sources/FchatyApp/Storage/MessageStore.swift`
|
|
|
|
أضف هذه الدالة في `MessageStore`:
|
|
```swift
|
|
func deleteAll() throws {
|
|
let url = try messagesURL()
|
|
if fileManager.fileExists(atPath: url.path) {
|
|
try fileManager.removeItem(at: url)
|
|
}
|
|
}
|
|
```
|
|
|
|
وعدّل `unpair()` في `AppSession.swift`:
|
|
```swift
|
|
func unpair() {
|
|
incomingMessagesTask?.cancel()
|
|
incomingMessagesTask = nil
|
|
messages = []
|
|
Task {
|
|
try? await messageStore.deleteAll()
|
|
}
|
|
KeychainStore.clearAll()
|
|
let webSocket = webSocket
|
|
Task {
|
|
await webSocket.disconnect()
|
|
}
|
|
state = .unpaired
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Fix 2: جعل WebSocket reconnect لانهائي
|
|
|
|
**المشكلة:** الـ reconnect يحاول 5 مرات فقط (2, 4, 8, 16, 32 ثانية) ثم يتوقف نهائياً بدون إبلاغ المستخدم. رفيقك يشوف loading لانهائي لأن التطبيق توقف عن المحاولة.
|
|
|
|
**الملف:** `FchatyApp/Sources/FchatyApp/Networking/WSClient.swift`
|
|
|
|
**السطر:** 243-273 — دالة `reconnect()`
|
|
|
|
**الكود الحالي:**
|
|
```swift
|
|
private func reconnect(using token: String, id: UUID) async {
|
|
let delays: [UInt64] = [2, 4, 8, 16, 32]
|
|
|
|
for delay in delays {
|
|
do {
|
|
try await Task.sleep(nanoseconds: delay * 1_000_000_000)
|
|
} catch {
|
|
return
|
|
}
|
|
|
|
guard reconnectID == id, !isDisconnecting else {
|
|
return
|
|
}
|
|
|
|
do {
|
|
try await establishConnection(token: token)
|
|
if reconnectID == id {
|
|
reconnectID = nil
|
|
reconnectTask = nil
|
|
}
|
|
return
|
|
} catch {
|
|
continue
|
|
}
|
|
}
|
|
|
|
if reconnectID == id {
|
|
reconnectID = nil
|
|
reconnectTask = nil
|
|
}
|
|
}
|
|
```
|
|
|
|
**المطلوب:** تغيير الدالة لتحاول بشكل لانهائي مع exponential backoff بحد أقصى 60 ثانية:
|
|
|
|
```swift
|
|
private func reconnect(using token: String, id: UUID) async {
|
|
var delay: UInt64 = 2
|
|
|
|
while !Task.isCancelled {
|
|
do {
|
|
try await Task.sleep(nanoseconds: delay * 1_000_000_000)
|
|
} catch {
|
|
return
|
|
}
|
|
|
|
guard reconnectID == id, !isDisconnecting else {
|
|
return
|
|
}
|
|
|
|
do {
|
|
try await establishConnection(token: token)
|
|
if reconnectID == id {
|
|
reconnectID = nil
|
|
reconnectTask = nil
|
|
}
|
|
return
|
|
} catch {
|
|
delay = min(delay * 2, 60)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Fix 3: تحميل fileRegistry من الدسك عند بدء السيرفر
|
|
|
|
**المشكلة:** `fileRegistry` هو `Map` في الذاكرة فقط. بعد إعادة تشغيل السيرفر (deploy جديد مثلاً)، كل الملفات المرفوعة تصبح 404 رغم إنها موجودة على الدسك.
|
|
|
|
**الملف:** `relay-server/src/index.js`
|
|
|
|
**السطر:** 42 — تعريف `fileRegistry`
|
|
|
|
**المطلوب:** إضافة دالة تقرأ الملفات الموجودة بمجلد `/data/files/` وتعبّي الـ `fileRegistry` عند بدء السيرفر.
|
|
|
|
أضف هذه الدالة بعد سطر 42 (`const fileRegistry = new Map();`):
|
|
|
|
```javascript
|
|
function loadFileRegistryFromDisk() {
|
|
if (!fs.existsSync(UPLOADS_DIR)) return;
|
|
|
|
for (const entry of fs.readdirSync(UPLOADS_DIR, { withFileTypes: true })) {
|
|
if (!entry.isFile()) continue;
|
|
|
|
const diskPath = path.join(UPLOADS_DIR, entry.name);
|
|
try {
|
|
const stats = fs.statSync(diskPath);
|
|
fileRegistry.set(entry.name, {
|
|
diskPath,
|
|
originalName: entry.name, // الاسم الأصلي ضايع — نستخدم الـ UUID كفولباك
|
|
size: stats.size,
|
|
});
|
|
} catch (_) {}
|
|
}
|
|
}
|
|
|
|
loadFileRegistryFromDisk();
|
|
console.log(`[files] loaded ${fileRegistry.size} files from disk`);
|
|
```
|
|
|
|
**ملاحظة مهمة:** الاسم الأصلي للملف (`originalName`) لا يُحفظ على الدسك حالياً — multer يحفظ الملف باسم UUID فقط. هذا الإصلاح يسترجع الملفات لكن بدون أسمائها الأصلية. لحل كامل، يجب حفظ metadata الملف (اسمه الأصلي وحجمه) بملف `.meta.json` بجانب كل ملف عند الرفع. هذا تحسين إضافي وليس ضرورياً الآن.
|
|
|
|
---
|
|
|
|
## ملخص سريع
|
|
|
|
| # | الملف | المشكلة | الأولوية |
|
|
|---|---|---|---|
|
|
| 1 | `AppSession.swift` + `MessageStore.swift` | الرسائل القديمة تظهر بعد unpair | عالية |
|
|
| 2 | `WSClient.swift` | الاتصال يفشل نهائياً بعد 5 محاولات | عالية |
|
|
| 3 | `relay-server/src/index.js` | الملفات 404 بعد restart السيرفر | عالية |
|
|
|
|
## الاختبارات
|
|
|
|
بعد تنفيذ الإصلاحات:
|
|
1. `swift test --package-path FchatyApp` — لازم كل الاختبارات تنجح
|
|
2. تأكد إنو `unpair()` يمسح الرسائل — عمل pairing، أرسل رسائل، عمل unpair، تأكد إنو المحادثة فاضية
|
|
3. تأكد إنو الـ reconnect ما بيوقف — شغّل التطبيق، أوقف السيرفر، شغله بعد دقيقة، لازم يتصل تلقائياً
|
|
4. تأكد إنو الملفات تتحمل — أرسل ملف، أعد تشغيل السيرفر، حاول حمّل الملف
|