New Update to Pair
This commit is contained in:
parent
9cbf88d3a0
commit
4e0dcbe873
195
FIXES-FOR-CODEX.md
Normal file
195
FIXES-FOR-CODEX.md
Normal file
@ -0,0 +1,195 @@
|
||||
# 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. تأكد إنو الملفات تتحمل — أرسل ملف، أعد تشغيل السيرفر، حاول حمّل الملف
|
||||
@ -241,9 +241,9 @@ actor WSClient {
|
||||
}
|
||||
|
||||
private func reconnect(using token: String, id: UUID) async {
|
||||
let delays: [UInt64] = [2, 4, 8, 16, 32]
|
||||
var delay: UInt64 = 2
|
||||
|
||||
for delay in delays {
|
||||
while !Task.isCancelled {
|
||||
do {
|
||||
try await Task.sleep(nanoseconds: delay * 1_000_000_000)
|
||||
} catch {
|
||||
@ -262,14 +262,10 @@ actor WSClient {
|
||||
}
|
||||
return
|
||||
} catch {
|
||||
delay = min(delay * 2, 60)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if reconnectID == id {
|
||||
reconnectID = nil
|
||||
reconnectTask = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func closeFailedConnection(_ task: URLSessionWebSocketTask) {
|
||||
|
||||
@ -115,6 +115,10 @@ final class AppSession: ObservableObject {
|
||||
func unpair() {
|
||||
incomingMessagesTask?.cancel()
|
||||
incomingMessagesTask = nil
|
||||
messages = []
|
||||
Task {
|
||||
try? await messageStore.deleteAll()
|
||||
}
|
||||
KeychainStore.clearAll()
|
||||
let webSocket = webSocket
|
||||
Task {
|
||||
|
||||
@ -55,6 +55,13 @@ actor MessageStore {
|
||||
try write(messages)
|
||||
}
|
||||
|
||||
func deleteAll() throws {
|
||||
let url = try messagesURL()
|
||||
if fileManager.fileExists(atPath: url.path) {
|
||||
try fileManager.removeItem(at: url)
|
||||
}
|
||||
}
|
||||
|
||||
var unreadCount: Int {
|
||||
get async {
|
||||
(try? loadAll().filter { !$0.isRead }.count) ?? 0
|
||||
|
||||
@ -41,6 +41,27 @@ const pairingSessions = new Map(); // code -> session
|
||||
const connections = new Map(); // peerID -> { ws, name }
|
||||
const fileRegistry = new Map(); // fileID -> { diskPath, originalName, size }
|
||||
|
||||
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,
|
||||
size: stats.size,
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
loadFileRegistryFromDisk();
|
||||
console.log(`[files] loaded ${fileRegistry.size} files from disk`);
|
||||
|
||||
function positiveInteger(value, fallback) {
|
||||
const parsed = Number.parseInt(value ?? '', 10);
|
||||
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user