Доставка сообщений при потере сети, кэш фото при отправке, FPS клавиатуры, свайп фото, badge tab bar, release notes, sync unread fix
This commit is contained in:
@@ -104,41 +104,23 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify connection health after returning from background.
|
||||
/// Fast path: if already authenticated, send a WebSocket ping first (< 100ms).
|
||||
/// If pong arrives, connection is alive — no reconnect needed.
|
||||
/// If ping fails or times out (500ms), force full reconnect.
|
||||
/// Android parity: `reconnectNowIfNeeded()` — if already in an active state,
|
||||
/// skip reconnect. Otherwise reset backoff and connect immediately.
|
||||
func reconnectIfNeeded() {
|
||||
guard savedPublicKey != nil, savedPrivateHash != nil else { return }
|
||||
|
||||
// Don't interrupt active handshake
|
||||
if connectionState == .handshaking { return }
|
||||
|
||||
// Fast path: if authenticated, try ping first before tearing down.
|
||||
if connectionState == .authenticated, client.isConnected {
|
||||
Self.logger.info("Foreground — ping check")
|
||||
client.sendPing { [weak self] error in
|
||||
guard let self else { return }
|
||||
if error == nil {
|
||||
// Pong received — connection alive, send heartbeat to keep it fresh.
|
||||
Self.logger.info("Foreground ping OK — connection alive")
|
||||
self.client.sendText("heartbeat")
|
||||
return
|
||||
}
|
||||
// Ping failed — connection dead, force reconnect.
|
||||
Self.logger.info("Foreground ping failed — force reconnecting")
|
||||
self.handshakeComplete = false
|
||||
self.heartbeatTask?.cancel()
|
||||
Task { @MainActor in
|
||||
self.connectionState = .connecting
|
||||
}
|
||||
self.client.forceReconnect()
|
||||
}
|
||||
// Android parity: skip if already in any active state.
|
||||
switch connectionState {
|
||||
case .authenticated, .handshaking, .deviceVerificationRequired, .connected:
|
||||
return
|
||||
case .connecting:
|
||||
if client.isConnected { return }
|
||||
case .disconnected:
|
||||
break
|
||||
}
|
||||
|
||||
// Not authenticated — force reconnect immediately.
|
||||
Self.logger.info("Foreground reconnect — force reconnecting")
|
||||
// Reset backoff and connect immediately.
|
||||
Self.logger.info("⚡ Fast reconnect — state=\(self.connectionState.rawValue)")
|
||||
handshakeComplete = false
|
||||
heartbeatTask?.cancel()
|
||||
connectionState = .connecting
|
||||
@@ -402,6 +384,8 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
switch packet.handshakeState {
|
||||
case .completed:
|
||||
handshakeComplete = true
|
||||
// Android parity: reset backoff counter on successful authentication.
|
||||
client.resetReconnectAttempts()
|
||||
Self.logger.info("Handshake completed. Protocol v\(packet.protocolVersion), heartbeat \(packet.heartbeatInterval)s")
|
||||
|
||||
flushPacketQueue()
|
||||
@@ -441,18 +425,41 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
// Android parity: heartbeat at 1/3 the server-specified interval (more aggressive keep-alive).
|
||||
let intervalNs = UInt64(interval) * 1_000_000_000 / 3
|
||||
|
||||
heartbeatTask = Task {
|
||||
heartbeatTask = Task { [weak self] in
|
||||
// Send first heartbeat immediately
|
||||
client.sendText("heartbeat")
|
||||
self?.sendHeartbeat()
|
||||
|
||||
while !Task.isCancelled {
|
||||
try? await Task.sleep(nanoseconds: intervalNs)
|
||||
guard !Task.isCancelled else { break }
|
||||
client.sendText("heartbeat")
|
||||
self?.sendHeartbeat()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Android parity: send heartbeat and trigger disconnect on failure.
|
||||
private func sendHeartbeat() {
|
||||
let state = connectionState
|
||||
guard state == .authenticated || state == .deviceVerificationRequired else { return }
|
||||
guard client.isConnected else {
|
||||
Self.logger.warning("💔 Heartbeat failed: socket not connected — triggering reconnect")
|
||||
handleHeartbeatFailure()
|
||||
return
|
||||
}
|
||||
client.sendText("heartbeat")
|
||||
}
|
||||
|
||||
/// Android parity: failed heartbeat → handleDisconnect.
|
||||
private func handleHeartbeatFailure() {
|
||||
heartbeatTask?.cancel()
|
||||
handshakeComplete = false
|
||||
Task { @MainActor in
|
||||
self.connectionState = .disconnected
|
||||
}
|
||||
// Let WebSocketClient's own handleDisconnect schedule reconnect.
|
||||
client.forceReconnect()
|
||||
}
|
||||
|
||||
// MARK: - Packet Queue
|
||||
|
||||
private func sendPacketDirect(_ packet: any Packet) {
|
||||
|
||||
Reference in New Issue
Block a user