diff --git a/rust/bridge/shared/types/src/ffi/chat.rs b/rust/bridge/shared/types/src/ffi/chat.rs index d93d1fe1..b258c430 100644 --- a/rust/bridge/shared/types/src/ffi/chat.rs +++ b/rust/bridge/shared/types/src/ffi/chat.rs @@ -17,7 +17,7 @@ type ReceivedIncomingMessage = extern "C" fn( cleanup: *mut ServerMessageAck, ); type ReceivedQueueEmpty = extern "C" fn(ctx: *mut c_void); -type ConnectionInterrupted = extern "C" fn(ctx: *mut c_void); +type ConnectionInterrupted = extern "C" fn(ctx: *mut c_void, error: *mut SignalFfiError); type DestroyChatListener = extern "C" fn(ctx: *mut c_void); /// Callbacks for [`ChatListener`]. @@ -77,8 +77,14 @@ impl ChatListener for ChatListenerStruct { (self.0.received_queue_empty)(self.0.ctx) } - // TODO: pass `_disconnect_cause` to `connection_interrupted` - fn connection_interrupted(&mut self, _disconnect_cause: ChatServiceError) { - (self.0.connection_interrupted)(self.0.ctx) + fn connection_interrupted(&mut self, disconnect_cause: ChatServiceError) { + let error = match disconnect_cause { + ChatServiceError::ServiceIntentionallyDisconnected => None, + c => Some(Box::new(SignalFfiError::from(c))), + }; + (self.0.connection_interrupted)( + self.0.ctx, + error.map_or(std::ptr::null_mut(), Box::into_raw), + ) } } diff --git a/swift/Sources/LibSignalClient/ChatListener.swift b/swift/Sources/LibSignalClient/ChatListener.swift index a7a7f895..e3ae2415 100644 --- a/swift/Sources/LibSignalClient/ChatListener.swift +++ b/swift/Sources/LibSignalClient/ChatListener.swift @@ -12,13 +12,7 @@ public protocol ConnectionEventsListener: AnyObject { /// Called when the client gets disconnected from the server. /// /// This includes both deliberate disconnects as well as unexpected socket closures. - /// - /// The default implementation of this method does nothing. - func connectionWasInterrupted(_ service: Service) -} - -extension ConnectionEventsListener { - func connectionWasInterrupted(_: Service) {} + func connectionWasInterrupted(_ service: Service, error: Error?) } public protocol ChatListener: ConnectionEventsListener { @@ -89,13 +83,15 @@ internal class ChatListenerBridge { bridge.chatListener.chatServiceDidReceiveQueueEmpty(chatService) } - let connectionInterrupted: SignalConnectionInterrupted = { rawCtx in + let connectionInterrupted: SignalConnectionInterrupted = { rawCtx, maybeError in let bridge = Unmanaged.fromOpaque(rawCtx!).takeUnretainedValue() guard let chatService = bridge.chatService else { return } - bridge.chatListener.connectionWasInterrupted(chatService) + let error = convertError(maybeError) + + bridge.chatListener.connectionWasInterrupted(chatService, error: error) } return .init( @@ -130,13 +126,15 @@ internal final class UnauthConnectionEventsListenerBridge { let receivedQueueEmpty: SignalReceivedQueueEmpty = { _ in fatalError("not used for the unauth listener") } - let connectionInterrupted: SignalConnectionInterrupted = { rawCtx in + let connectionInterrupted: SignalConnectionInterrupted = { rawCtx, maybeError in let bridge = Unmanaged.fromOpaque(rawCtx!).takeUnretainedValue() guard let chatService = bridge.chatService else { return } - bridge.listener.connectionWasInterrupted(chatService) + let error = convertError(maybeError) + + bridge.listener.connectionWasInterrupted(chatService, error: error) } return .init( diff --git a/swift/Sources/LibSignalClient/Error.swift b/swift/Sources/LibSignalClient/Error.swift index 34411505..2da4d014 100644 --- a/swift/Sources/LibSignalClient/Error.swift +++ b/swift/Sources/LibSignalClient/Error.swift @@ -70,6 +70,19 @@ public enum SignalError: Error { internal typealias SignalFfiErrorRef = OpaquePointer +internal func convertError(_ error: SignalFfiErrorRef?) -> Error? { + // It would be *slightly* more efficient for checkError to call convertError, + // instead of the other way around. However, then it would be harder to implement + // checkError, since some of the conversion operations can themselves throw. + // So this is more maintainable. + do { + try checkError(error) + return nil + } catch let thrownError { + return thrownError + } +} + internal func checkError(_ error: SignalFfiErrorRef?) throws { guard let error = error else { return } diff --git a/swift/Sources/SignalFfi/signal_ffi.h b/swift/Sources/SignalFfi/signal_ffi.h index 06f97e41..c86ef1bd 100644 --- a/swift/Sources/SignalFfi/signal_ffi.h +++ b/swift/Sources/SignalFfi/signal_ffi.h @@ -634,7 +634,7 @@ typedef void (*SignalReceivedIncomingMessage)(void *ctx, SignalOwnedBuffer envel typedef void (*SignalReceivedQueueEmpty)(void *ctx); -typedef void (*SignalConnectionInterrupted)(void *ctx); +typedef void (*SignalConnectionInterrupted)(void *ctx, SignalFfiError *error); typedef void (*SignalDestroyChatListener)(void *ctx); diff --git a/swift/Tests/LibSignalClientTests/ChatServiceTests.swift b/swift/Tests/LibSignalClientTests/ChatServiceTests.swift index 4e0dfac7..e2e17588 100644 --- a/swift/Tests/LibSignalClientTests/ChatServiceTests.swift +++ b/swift/Tests/LibSignalClientTests/ChatServiceTests.swift @@ -197,8 +197,9 @@ final class ChatServiceTests: TestCaseBase { self.queueEmpty.fulfill() } - func connectionWasInterrupted(_: AuthenticatedChatService) { + func connectionWasInterrupted(_: AuthenticatedChatService, error: Error?) { XCTAssertEqual(self.stage, 3) + XCTAssertNotNil(error) self.stage += 1 self.connectionInterrupted.fulfill() } @@ -263,6 +264,7 @@ final class ChatServiceTests: TestCaseBase { func chatServiceDidReceiveQueueEmpty(_: AuthenticatedChatService) {} func chatService(_ chat: AuthenticatedChatService, didReceiveIncomingMessage envelope: Data, serverDeliveryTimestamp: UInt64, sendAck: () async throws -> Void) {} + func connectionWasInterrupted(_ service: AuthenticatedChatService, error: Error?) {} } let net = Net(env: .staging, userAgent: Self.userAgent) @@ -298,7 +300,8 @@ final class ChatServiceTests: TestCaseBase { self.expectation = expectation } - func connectionWasInterrupted(_: UnauthenticatedChatService) { + func connectionWasInterrupted(_: UnauthenticatedChatService, error: Error?) { + XCTAssertNil(error) self.expectation.fulfill() } }