#12 sync call history between devices via carbons and MAM
- After a call ends, send a plaintext <call-history/> service message to the user's own bare JID with direction, status, duration, start time, peer and video flag plus the call id as origin-id. - Intercept the payload on live, carbon and MAM paths and upsert the same system call card on every device; deduplication reuses the origin-id as clientID, and missed incoming calls bump unread. - Cover payload parsing and round-trip in CallHistorySyncTests, guard the namespace and interception in verify.sh, and document the flow in ARCHITECTURE.md.
This commit is contained in:
1 parent
0d78e02d2b
commit
1a26651daa
10 files changed
+269
-18
No files matched your search
@@ -2090,6 +2090,8 @@ final class AppModel: ObservableObject {
|
||||
activeCall = call
|
||||
case .callHistory(let entry):
|
||||
recordCallHistory(entry)
|
||||
case .callHistorySync(let envelope):
|
||||
recordSyncedCallHistory(envelope)
|
||||
case .callError(let message):
|
||||
errorMessage = message
|
||||
case .recoverableError(let message):
|
||||
@@ -2295,6 +2297,42 @@ final class AppModel: ObservableObject {
|
||||
&& entry.outcome == .missed
|
||||
&& selectedConversationID != entry.peerJID.lowercased()
|
||||
_ = upsertMessage(message, incrementUnread: isUnreadMissedCall)
|
||||
// Sync the card to the user's other devices via Carbons/MAM.
|
||||
xmpp.syncCallHistory(entry)
|
||||
}
|
||||
|
||||
/// Applies a call card received from another of the user's devices. The
|
||||
/// envelope id equals the origin-id of the sync message, which also equals
|
||||
/// the originating device's local clientID, so Carbons and MAM copies
|
||||
/// deduplicate into a single card.
|
||||
private func recordSyncedCallHistory(_ entry: CallHistorySync.Envelope) {
|
||||
guard let account else { return }
|
||||
let direction: ChatMessage.Direction =
|
||||
entry.direction == .outgoing
|
||||
? .outgoing
|
||||
: .incoming
|
||||
let metadata = CallHistoryMetadata(
|
||||
isVideo: entry.isVideo,
|
||||
outcome: entry.outcome
|
||||
)
|
||||
let message = ChatMessage(
|
||||
id: entry.id,
|
||||
conversationID: entry.peerJID,
|
||||
senderJID: direction == .outgoing ? account.normalizedJID : entry.peerJID,
|
||||
body: entry.isVideo ? "Видеозвонок" : "Аудиозвонок",
|
||||
timestamp: entry.startedAt,
|
||||
direction: direction,
|
||||
delivery: direction == .outgoing ? .sent : .delivered,
|
||||
security: .plaintext,
|
||||
kind: .system,
|
||||
duration: entry.duration,
|
||||
callHistory: metadata
|
||||
)
|
||||
let isUnreadMissedCall =
|
||||
direction == .incoming
|
||||
&& entry.outcome == .missed
|
||||
&& selectedConversationID != entry.peerJID.lowercased()
|
||||
_ = upsertMessage(message, incrementUnread: isUnreadMissedCall)
|
||||
}
|
||||
|
||||
private func upsertConversation(jid: String, name: String?) {
|
||||
|
||||
Reference in new issue
Block a user