version 1.0
This commit is contained in:
parent
833a9bfec2
commit
e973d850ed
@ -32,9 +32,11 @@ struct ChatView: View {
|
|||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
scrollToLatest(using: proxy)
|
scrollToLatest(using: proxy)
|
||||||
|
Task { await session.markVisibleMessagesRead() }
|
||||||
}
|
}
|
||||||
.onChange(of: session.messages.last?.id) { _, _ in
|
.onChange(of: session.messages.last?.id) { _, _ in
|
||||||
scrollToLatest(using: proxy)
|
scrollToLatest(using: proxy)
|
||||||
|
Task { await session.markVisibleMessagesRead() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -53,6 +53,12 @@ struct SettingsView: View {
|
|||||||
Text(appVersion)
|
Text(appVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
Button("Quit Fchati", role: .destructive) {
|
||||||
|
NSApplication.shared.terminate(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.formStyle(.grouped)
|
.formStyle(.grouped)
|
||||||
.padding()
|
.padding()
|
||||||
|
|||||||
@ -101,6 +101,17 @@ final class AppSession: ObservableObject {
|
|||||||
try await sendAndStore(message)
|
try await sendAndStore(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func markVisibleMessagesRead() async {
|
||||||
|
let unread = messages.filter { !$0.isRead && $0.from != KeychainStore.installationID }
|
||||||
|
guard !unread.isEmpty else { return }
|
||||||
|
|
||||||
|
let ids = unread.map { $0.id }
|
||||||
|
ids.forEach { markMessageAsRead(id: $0) }
|
||||||
|
|
||||||
|
let receipt = WSMessage(type: "read", messageIDs: ids)
|
||||||
|
try? await webSocket.send(receipt)
|
||||||
|
}
|
||||||
|
|
||||||
func unpair() {
|
func unpair() {
|
||||||
incomingMessagesTask?.cancel()
|
incomingMessagesTask?.cancel()
|
||||||
incomingMessagesTask = nil
|
incomingMessagesTask = nil
|
||||||
@ -192,6 +203,14 @@ final class AppSession: ObservableObject {
|
|||||||
await storeIncomingMessage(chatMessage)
|
await storeIncomingMessage(chatMessage)
|
||||||
NotificationManager.shared.notify(from: chatMessage.fromName, body: chatMessage.body)
|
NotificationManager.shared.notify(from: chatMessage.fromName, body: chatMessage.body)
|
||||||
|
|
||||||
|
case "peer.joined":
|
||||||
|
// The partner just came online — save their identity and transition to connected.
|
||||||
|
if let peerID = message.peerID, let peerName = message.fromName {
|
||||||
|
KeychainStore.peerID = peerID
|
||||||
|
KeychainStore.peerName = peerName
|
||||||
|
state = .connected(peerID: peerID, peerName: peerName)
|
||||||
|
}
|
||||||
|
|
||||||
case "read":
|
case "read":
|
||||||
for id in message.messageIDs ?? [] {
|
for id in message.messageIDs ?? [] {
|
||||||
markMessageAsRead(id: id)
|
markMessageAsRead(id: id)
|
||||||
|
|||||||
@ -225,6 +225,30 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Bug Fixes — Full Code Audit ✅
|
||||||
|
|
||||||
|
**Applied by:** Claude
|
||||||
|
|
||||||
|
### Fix 3 — Creator never reaches `.connected` state (critical)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- `relay-server/src/index.js` — after WebSocket auth, notify the partner with `{ type: "peer.joined", peerID, peerName }`
|
||||||
|
- `FchatiApp/Sources/FchatiApp/Session/AppSession.swift` — handle `peer.joined`: save peerID/peerName to Keychain, transition state to `.connected`
|
||||||
|
|
||||||
|
**Problem:** When the creator generates a pairing code and connects via WebSocket, their state gets stuck at `.connecting` forever. The server never told the creator that the joiner joined — it only responded to the joiner's HTTP request. The creator had no way to learn the joiner's identity or transition to `.connected`.
|
||||||
|
|
||||||
|
**Fix:** Server now sends `{ type: "peer.joined", peerID, peerName }` to the online partner whenever any peer authenticates on WebSocket. Client handles this event and saves credentials + transitions state.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Fix 4 — Read receipts were never sent (important)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- `FchatiApp/Sources/FchatiApp/Session/AppSession.swift` — added `markVisibleMessagesRead()`: marks all incoming unread messages as read and sends `{ type: "read", messageIDs: [...] }` via WebSocket
|
||||||
|
- `FchatiApp/Sources/FchatiApp/Features/Chat/ChatView.swift` — calls `markVisibleMessagesRead()` on appear and when new messages arrive
|
||||||
|
|
||||||
|
**Problem:** The code handled *incoming* read receipts correctly but never *sent* them. The partner's messages were never acknowledged as read, so the partner never saw "read" status on their messages.
|
||||||
|
|
||||||
## Pending
|
## Pending
|
||||||
|
|
||||||
Nothing remaining in the codebase. Next step: deploy relay server to production.
|
Nothing remaining.
|
||||||
|
|||||||
@ -417,6 +417,13 @@ wss.on('connection', (ws) => {
|
|||||||
send(ws, { type: 'auth.ok', peerID });
|
send(ws, { type: 'auth.ok', peerID });
|
||||||
console.log(`[ws] connected: ${peerName} (${peerID})`);
|
console.log(`[ws] connected: ${peerName} (${peerID})`);
|
||||||
|
|
||||||
|
// Notify the partner that this peer is now online
|
||||||
|
const partnerIDOnAuth = peerPartnerID(session, peerID);
|
||||||
|
const partnerConnOnAuth = connections.get(partnerIDOnAuth);
|
||||||
|
if (partnerConnOnAuth?.ws.readyState === 1) {
|
||||||
|
send(partnerConnOnAuth.ws, { type: 'peer.joined', peerID, peerName });
|
||||||
|
}
|
||||||
|
|
||||||
// Deliver messages that arrived while this peer was offline
|
// Deliver messages that arrived while this peer was offline
|
||||||
flushQueue(peerID, ws);
|
flushQueue(peerID, ws);
|
||||||
return;
|
return;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user