Онлайн-статусы, исправление навигации и UI чатов
- Реализован PacketOnlineSubscribe (0x04) для подписки на статус собеседника - Онлайн-статус загружается из результатов поиска (PacketSearch) при каждом хэндшейке - Toolbar capsule показывает online/offline/typing вместо @username - Зелёная точка онлайн-индикатора на аватаре в списке чатов (bottom-left, как в Android) - Убрана точка с аватара в toolbar (статус отображается текстом) - Исправлен баг двойного тапа при входе в чат (программная навигация вместо NavigationLink) - DialogRepository.updateUserInfo теперь принимает и сохраняет online-статус - Очистка requestedUserInfoKeys при реконнекте для обновления статусов - Добавлено логирование результатов поиска и отправки пакетов Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ enum ConnectionState: String {
|
||||
case connecting
|
||||
case connected
|
||||
case handshaking
|
||||
case deviceVerificationRequired
|
||||
case authenticated
|
||||
}
|
||||
|
||||
@@ -27,6 +28,14 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
|
||||
private(set) var connectionState: ConnectionState = .disconnected
|
||||
|
||||
// MARK: - Device Verification State
|
||||
|
||||
/// Device waiting for approval from this device (shown as banner on primary device).
|
||||
private(set) var pendingDeviceVerification: DeviceEntry?
|
||||
|
||||
/// All connected devices.
|
||||
private(set) var devices: [DeviceEntry] = []
|
||||
|
||||
// MARK: - Callbacks
|
||||
|
||||
var onMessageReceived: ((PacketMessage) -> Void)?
|
||||
@@ -92,10 +101,13 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
// MARK: - Sending
|
||||
|
||||
func sendPacket(_ packet: any Packet) {
|
||||
let id = String(type(of: packet).packetId, radix: 16)
|
||||
if (!handshakeComplete && !(packet is PacketHandshake)) || !client.isConnected {
|
||||
Self.logger.info("⏳ Queueing packet 0x\(id) — connected=\(self.client.isConnected), handshake=\(self.handshakeComplete)")
|
||||
enqueuePacket(packet)
|
||||
return
|
||||
}
|
||||
Self.logger.info("📤 Sending packet 0x\(id) directly")
|
||||
sendPacketDirect(packet)
|
||||
}
|
||||
|
||||
@@ -172,7 +184,7 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
protocolVersion: 1,
|
||||
heartbeatInterval: 15,
|
||||
device: device,
|
||||
handshakeState: .needDeviceVerification
|
||||
handshakeState: .completed
|
||||
)
|
||||
|
||||
sendPacketDirect(handshake)
|
||||
@@ -254,6 +266,14 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
if let p = packet as? PacketTyping {
|
||||
onTypingReceived?(p)
|
||||
}
|
||||
case 0x17:
|
||||
if let p = packet as? PacketDeviceList {
|
||||
handleDeviceList(p)
|
||||
}
|
||||
case 0x18:
|
||||
if let p = packet as? PacketDeviceResolve {
|
||||
handleDeviceResolve(p)
|
||||
}
|
||||
case 0x19:
|
||||
if let p = packet as? PacketSync {
|
||||
onSyncReceived?(p)
|
||||
@@ -292,8 +312,14 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
|
||||
case .needDeviceVerification:
|
||||
handshakeComplete = false
|
||||
Self.logger.info("Server requires device verification")
|
||||
clearPacketQueue()
|
||||
Self.logger.info("Server requires device verification — approve this device from your other Rosetta app")
|
||||
|
||||
Task { @MainActor in
|
||||
self.connectionState = .deviceVerificationRequired
|
||||
}
|
||||
|
||||
// Keep packet queue: messages will be flushed when the other device
|
||||
// approves this login and the server re-sends handshake with .completed
|
||||
startHeartbeat(interval: packet.heartbeatInterval)
|
||||
}
|
||||
}
|
||||
@@ -370,6 +396,57 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
packetQueueLock.unlock()
|
||||
}
|
||||
|
||||
// MARK: - Device Verification
|
||||
|
||||
private func handleDeviceList(_ packet: PacketDeviceList) {
|
||||
Self.logger.info("📱 Device list received: \(packet.devices.count) devices")
|
||||
for device in packet.devices {
|
||||
Self.logger.info(" - \(device.deviceName) (\(device.deviceOs)) status=\(device.deviceStatus.rawValue) verify=\(device.deviceVerify.rawValue)")
|
||||
}
|
||||
|
||||
Task { @MainActor in
|
||||
self.devices = packet.devices
|
||||
self.pendingDeviceVerification = packet.devices.first { $0.deviceVerify == .notVerified }
|
||||
}
|
||||
}
|
||||
|
||||
private func handleDeviceResolve(_ packet: PacketDeviceResolve) {
|
||||
Self.logger.info("🔐 Device resolve received: deviceId=\(packet.deviceId.prefix(20)), solution=\(packet.solution.rawValue)")
|
||||
|
||||
if packet.solution == .decline {
|
||||
Self.logger.info("🚫 This device was DECLINED — disconnecting")
|
||||
disconnect()
|
||||
}
|
||||
// If accepted, server will re-send handshake with .completed
|
||||
// which is handled by handleHandshakeResponse
|
||||
}
|
||||
|
||||
/// Accept a pending device login from another device.
|
||||
func acceptDevice(_ deviceId: String) {
|
||||
Self.logger.info("✅ Accepting device: \(deviceId.prefix(20))")
|
||||
var packet = PacketDeviceResolve()
|
||||
packet.deviceId = deviceId
|
||||
packet.solution = .accept
|
||||
sendPacketDirect(packet)
|
||||
|
||||
Task { @MainActor in
|
||||
self.pendingDeviceVerification = nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Decline a pending device login from another device.
|
||||
func declineDevice(_ deviceId: String) {
|
||||
Self.logger.info("❌ Declining device: \(deviceId.prefix(20))")
|
||||
var packet = PacketDeviceResolve()
|
||||
packet.deviceId = deviceId
|
||||
packet.solution = .decline
|
||||
sendPacketDirect(packet)
|
||||
|
||||
Task { @MainActor in
|
||||
self.pendingDeviceVerification = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func packetQueueKey(_ packet: any Packet) -> String? {
|
||||
switch packet {
|
||||
case let message as PacketMessage:
|
||||
|
||||
Reference in New Issue
Block a user