Уведомления, Real-time синхронизация, фотки, reply and forward
This commit is contained in:
@@ -105,50 +105,17 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
}
|
||||
|
||||
/// Verify connection health after returning from background.
|
||||
/// If connection appears alive, sends a WebSocket ping to confirm.
|
||||
/// If ping fails or times out (2s), forces immediate reconnection.
|
||||
/// Always force reconnect — after background, the socket is likely dead
|
||||
/// and a 2s ping timeout just delays the inevitable.
|
||||
func reconnectIfNeeded() {
|
||||
guard savedPublicKey != nil, savedPrivateHash != nil else { return }
|
||||
|
||||
// Don't interrupt active handshake
|
||||
if connectionState == .handshaking { return }
|
||||
|
||||
if connectionState == .authenticated && client.isConnected {
|
||||
// Connection looks alive — verify with ping (2s timeout)
|
||||
Self.logger.info("Verifying connection with ping...")
|
||||
|
||||
let pingTimeoutTask = Task { [weak self] in
|
||||
try? await Task.sleep(nanoseconds: 2_000_000_000)
|
||||
guard !Task.isCancelled, let self else { return }
|
||||
Self.logger.info("Ping timeout — connection dead, force reconnecting")
|
||||
self.handshakeComplete = false
|
||||
self.heartbeatTask?.cancel()
|
||||
Task { @MainActor in
|
||||
self.connectionState = .connecting
|
||||
}
|
||||
self.client.forceReconnect()
|
||||
}
|
||||
|
||||
client.sendPing { [weak self] error in
|
||||
pingTimeoutTask.cancel()
|
||||
guard let self else { return }
|
||||
if let error {
|
||||
Self.logger.info("Ping failed: \(error.localizedDescription) — force reconnecting")
|
||||
self.handshakeComplete = false
|
||||
self.heartbeatTask?.cancel()
|
||||
Task { @MainActor in
|
||||
self.connectionState = .connecting
|
||||
}
|
||||
self.client.forceReconnect()
|
||||
} else {
|
||||
Self.logger.info("Ping succeeded — connection alive")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Not authenticated or not connected — force reconnect immediately
|
||||
Self.logger.info("Force reconnect from foreground")
|
||||
Self.logger.info("Foreground reconnect — force reconnecting")
|
||||
handshakeComplete = false
|
||||
heartbeatTask?.cancel()
|
||||
connectionState = .connecting
|
||||
client.forceReconnect()
|
||||
}
|
||||
@@ -234,6 +201,18 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
client.onDataReceived = { [weak self] data in
|
||||
self?.handleIncomingData(data)
|
||||
}
|
||||
|
||||
// Instant reconnect when network is restored (Wi-Fi ↔ cellular, airplane mode off, etc.)
|
||||
client.onNetworkRestored = { [weak self] in
|
||||
guard let self, self.savedPublicKey != nil else { return }
|
||||
Self.logger.info("Network restored — force reconnecting")
|
||||
self.handshakeComplete = false
|
||||
self.heartbeatTask?.cancel()
|
||||
Task { @MainActor in
|
||||
self.connectionState = .connecting
|
||||
}
|
||||
self.client.forceReconnect()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Handshake
|
||||
@@ -399,17 +378,21 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
handshakeComplete = true
|
||||
Self.logger.info("Handshake completed. Protocol v\(packet.protocolVersion), heartbeat \(packet.heartbeatInterval)s")
|
||||
|
||||
Task { @MainActor in
|
||||
self.connectionState = .authenticated
|
||||
}
|
||||
|
||||
flushPacketQueue()
|
||||
startHeartbeat(interval: packet.heartbeatInterval)
|
||||
|
||||
// Desktop parity: request transport server URL after handshake.
|
||||
sendPacketDirect(PacketRequestTransport())
|
||||
|
||||
onHandshakeCompleted?(packet)
|
||||
// CRITICAL: set .authenticated and fire callback in ONE MainActor task.
|
||||
// Previously these were separate tasks — Swift doesn't guarantee FIFO
|
||||
// ordering of unstructured tasks, so requestSynchronize() could race
|
||||
// with the state change and silently drop the sync request.
|
||||
let callback = self.onHandshakeCompleted
|
||||
Task { @MainActor in
|
||||
self.connectionState = .authenticated
|
||||
callback?(packet)
|
||||
}
|
||||
|
||||
case .needDeviceVerification:
|
||||
handshakeComplete = false
|
||||
@@ -429,8 +412,8 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
|
||||
private func startHeartbeat(interval: Int) {
|
||||
heartbeatTask?.cancel()
|
||||
// Desktop parity: heartbeat at half the server-specified interval.
|
||||
let intervalNs = UInt64(interval) * 1_000_000_000 / 2
|
||||
// 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 {
|
||||
// Send first heartbeat immediately
|
||||
|
||||
Reference in New Issue
Block a user