version 1.0
This commit is contained in:
parent
833a9bfec2
commit
e973d850ed
@ -32,9 +32,11 @@ struct ChatView: View {
|
||||
}
|
||||
.onAppear {
|
||||
scrollToLatest(using: proxy)
|
||||
Task { await session.markVisibleMessagesRead() }
|
||||
}
|
||||
.onChange(of: session.messages.last?.id) { _, _ in
|
||||
scrollToLatest(using: proxy)
|
||||
Task { await session.markVisibleMessagesRead() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -53,6 +53,12 @@ struct SettingsView: View {
|
||||
Text(appVersion)
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
Button("Quit Fchati", role: .destructive) {
|
||||
NSApplication.shared.terminate(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.padding()
|
||||
|
||||
@ -101,6 +101,17 @@ final class AppSession: ObservableObject {
|
||||
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() {
|
||||
incomingMessagesTask?.cancel()
|
||||
incomingMessagesTask = nil
|
||||
@ -192,6 +203,14 @@ final class AppSession: ObservableObject {
|
||||
await storeIncomingMessage(chatMessage)
|
||||
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":
|
||||
for id in message.messageIDs ?? [] {
|
||||
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
|
||||
|
||||
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 });
|
||||
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
|
||||
flushQueue(peerID, ws);
|
||||
return;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user