diff --git a/Examples/xx-messenger/Sources/AppCore/AppDependecies.swift b/Examples/xx-messenger/Sources/AppCore/AppDependecies.swift index 90077f25563bbc1e91634ee579403863d8f0b4bd..c67559af9d580952c14554465d356e27672b0e72 100644 --- a/Examples/xx-messenger/Sources/AppCore/AppDependecies.swift +++ b/Examples/xx-messenger/Sources/AppCore/AppDependecies.swift @@ -17,6 +17,8 @@ public struct AppDependencies { public var receiveFileHandler: ReceiveFileHandler public var log: Logger public var loadData: URLDataLoader + public var groupRequestHandler: GroupRequestHandler + public var groupMessageHandler: GroupMessageHandler } extension AppDependencies { @@ -59,7 +61,15 @@ extension AppDependencies { now: now ), log: .live(), - loadData: .live + loadData: .live, + groupRequestHandler: .live( + messenger: messenger, + db: dbManager.getDB + ), + groupMessageHandler: .live( + messenger: messenger, + db: dbManager.getDB + ) ) } @@ -79,7 +89,9 @@ extension AppDependencies { messageListener: .unimplemented, receiveFileHandler: .unimplemented, log: .unimplemented, - loadData: .unimplemented + loadData: .unimplemented, + groupRequestHandler: .unimplemented, + groupMessageHandler: .unimplemented ) } diff --git a/Examples/xx-messenger/Sources/AppCore/Groups/GroupMessageHandler.swift b/Examples/xx-messenger/Sources/AppCore/Groups/GroupMessageHandler.swift new file mode 100644 index 0000000000000000000000000000000000000000..c0316957f317d3409c990baaf585f6ba516e8ab2 --- /dev/null +++ b/Examples/xx-messenger/Sources/AppCore/Groups/GroupMessageHandler.swift @@ -0,0 +1,55 @@ +import XXModels +import XXClient +import Foundation +import XXMessengerClient +import XCTestDynamicOverlay + +public struct GroupMessageHandler { + public typealias OnError = (Error) -> Void + + public var run: (@escaping OnError) -> Cancellable + + public func callAsFunction(onError: @escaping OnError) -> Cancellable { + run(onError) + } +} + +extension GroupMessageHandler { + public static func live( + messenger: Messenger, + db: DBManagerGetDB + ) -> GroupMessageHandler { + GroupMessageHandler { onError in + messenger.registerGroupChatProcessor(.init { result in + switch result { + case .success(let callback): + do { + let payload = try MessagePayload.decode(callback.decryptedMessage.payload) + try db().saveMessage(.init( + networkId: callback.decryptedMessage.messageId, + senderId: callback.decryptedMessage.senderId, + recipientId: nil, + groupId: callback.decryptedMessage.groupId, + date: Date(timeIntervalSince1970: TimeInterval(callback.decryptedMessage.timestamp) / 1_000_000_000), + status: .received, + isUnread: true, + text: payload.text, + replyMessageId: payload.replyingTo, + roundURL: callback.roundUrl + )) + } catch { + onError(error) + } + case .failure(let error): + onError(error) + } + }) + } + } +} + +extension GroupMessageHandler { + public static let unimplemented = GroupMessageHandler( + run: XCTestDynamicOverlay.unimplemented("\(Self.self)", placeholder: Cancellable {}) + ) +} diff --git a/Examples/xx-messenger/Sources/AppCore/Groups/GroupRequestHandler.swift b/Examples/xx-messenger/Sources/AppCore/Groups/GroupRequestHandler.swift new file mode 100644 index 0000000000000000000000000000000000000000..7a5be6c5ad9ae943df916c7730b57866709b079b --- /dev/null +++ b/Examples/xx-messenger/Sources/AppCore/Groups/GroupRequestHandler.swift @@ -0,0 +1,99 @@ +import XXModels +import XXClient +import Foundation +import XXMessengerClient +import XCTestDynamicOverlay + +public struct GroupRequestHandler { + public typealias OnError = (Error) -> Void + + public var run: (@escaping OnError) -> Cancellable + + public func callAsFunction(onError: @escaping OnError) -> Cancellable { + run(onError) + } +} + +extension GroupRequestHandler { + public static func live( + messenger: Messenger, + db: DBManagerGetDB + ) -> GroupRequestHandler { + GroupRequestHandler { onError in + messenger.registerGroupRequestHandler(.init { group in + do { + if let _ = try db().fetchGroups(.init(id: [group.getId()])).first { + return + } + guard let leader = try group.getMembership().first else { + return // Failed to get group membership/leader + } + try db().saveGroup(.init( + id: group.getId(), + name: String(data: group.getName(), encoding: .utf8)!, + leaderId: leader.id, + createdAt: Date(timeIntervalSince1970: TimeInterval(group.getCreatedMS()) / 1_000), + authStatus: .pending, + serialized: group.serialize() + )) + if let initialMessageData = group.getInitMessage(), + let initialMessage = String(data: initialMessageData, encoding: .utf8) { + try db().saveMessage(.init( + senderId: leader.id, + recipientId: nil, + groupId: group.getId(), + date: Date(timeIntervalSince1970: TimeInterval(group.getCreatedMS()) / 1_000), + status: .received, + isUnread: true, + text: initialMessage + )) + } + let members = try group.getMembership() + let friends = try db().fetchContacts(.init(id: Set(members.map(\.id)), authStatus: [ + .friend, .hidden, .confirming, + .verified, .requested, .requesting, + .verificationInProgress, .requestFailed, + .verificationFailed, .confirmationFailed + ])) + let strangers = Set(members.map(\.id)).subtracting(Set(friends.map(\.id))) + try strangers.forEach { + if let stranger = try? db().fetchContacts(.init(id: [$0])).first { + print(stranger) + } else { + try db().saveContact(.init( + id: $0, + username: "Fetching...", + authStatus: .stranger, + isRecent: false, + isBlocked: false, + isBanned: false, + createdAt: Date(timeIntervalSince1970: TimeInterval(group.getCreatedMS()) / 1_000) + )) + } + } + try members.map { + XXModels.GroupMember(groupId: group.getId(), contactId: $0.id) + }.forEach { + try db().saveGroupMember($0) + } + let lookupResult = try messenger.lookupContacts(ids: strangers.map { $0 }) + for user in lookupResult.contacts { + if var foo = try? db().fetchContacts(.init(id: [user.getId()])).first, + let username = try? user.getFact(.username)?.value { + foo.username = username + _ = try? db().saveContact(foo) + } + } + } catch { + onError(error) + } + }) + } + } +} + +extension GroupRequestHandler { + public static let unimplemented = GroupRequestHandler( + run: XCTestDynamicOverlay.unimplemented("\(Self.self)", placeholder: Cancellable {}) + ) +} diff --git a/Examples/xx-messenger/Sources/AppCore/Models/MessagePayload.swift b/Examples/xx-messenger/Sources/AppCore/Models/MessagePayload.swift index 67fb94d905b06ad25816ca8c4d11081c72053f19..7ae774337944fdb62b7c5b862cfdd28d7a3a3295 100644 --- a/Examples/xx-messenger/Sources/AppCore/Models/MessagePayload.swift +++ b/Examples/xx-messenger/Sources/AppCore/Models/MessagePayload.swift @@ -1,16 +1,22 @@ import Foundation public struct MessagePayload: Equatable { - public init(text: String) { + public init( + text: String, + replyingTo: Data? = nil + ) { self.text = text + self.replyingTo = replyingTo } public var text: String + public var replyingTo: Data? } extension MessagePayload: Codable { enum CodingKeys: String, CodingKey { case text + case replyingTo } public static func decode(_ data: Data) throws -> Self { diff --git a/Examples/xx-messenger/Sources/AppFeature/AppComponent.swift b/Examples/xx-messenger/Sources/AppFeature/AppComponent.swift index d092a69e57ac781372297df8de4cdcce02a43d36..d0d37d658c208c4694c68caa0fe8be98871c4858 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppComponent.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppComponent.swift @@ -41,6 +41,8 @@ struct AppComponent: ReducerProtocol { @Dependency(\.app.log) var log: Logger @Dependency(\.app.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue> @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue> + @Dependency(\.app.groupRequestHandler) var groupRequestHandler: GroupRequestHandler + @Dependency(\.app.groupMessageHandler) var groupMessageHandler: GroupMessageHandler var body: some ReducerProtocol<State, Action> { BindingReducer() @@ -80,7 +82,12 @@ struct AppComponent: ReducerProtocol { cancellables.append(receiveFileHandler(onError: { error in log(.error(error as NSError)) })) - + cancellables.append(groupRequestHandler(onError: { error in + log(.error(error as NSError)) + })) + cancellables.append(groupMessageHandler(onError: { error in + log(.error(error as NSError)) + })) cancellables.append(messenger.registerBackupCallback(.init { data in try? backupStorage.store(data) })) diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeComponent.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeComponent.swift index 18212bf0ca916cffff86f5a4ee6cb09f4d220359..1f209ba392b3a22210778221499d6bf567e9484e 100644 --- a/Examples/xx-messenger/Sources/HomeFeature/HomeComponent.swift +++ b/Examples/xx-messenger/Sources/HomeFeature/HomeComponent.swift @@ -119,6 +119,10 @@ public struct HomeComponent: ReducerProtocol { try messenger.startFileTransfer() } + if messenger.isGroupChatRunning() == false { + try messenger.startGroupChat() + } + if messenger.isLoggedIn() == false { if try messenger.isRegistered() == false { return .success(.messenger(.didStartUnregistered)) diff --git a/Examples/xx-messenger/Tests/AppFeatureTests/AppComponentTests.swift b/Examples/xx-messenger/Tests/AppFeatureTests/AppComponentTests.swift index 7533666ecc857876698f0478e688257cb2c501e6..b9c76682c487cabde0d702e71a8f1abf81f9c393 100644 --- a/Examples/xx-messenger/Tests/AppFeatureTests/AppComponentTests.swift +++ b/Examples/xx-messenger/Tests/AppFeatureTests/AppComponentTests.swift @@ -64,6 +64,14 @@ final class AppComponentTests: XCTestCase { actions.append(.didRegisterBackupCallback) return Cancellable {} } + store.dependencies.app.groupRequestHandler.run = { _ in + actions.append(.didStartGroupRequestHandler) + return Cancellable {} + } + store.dependencies.app.groupMessageHandler.run = { _ in + actions.append(.didStartGroupMessageHandler) + return Cancellable {} + } actions = [] store.send(.start) @@ -76,6 +84,8 @@ final class AppComponentTests: XCTestCase { .didStartAuthHandler, .didStartMessageListener, .didStartReceiveFileHandler, + .didStartGroupRequestHandler, + .didStartGroupMessageHandler, .didRegisterBackupCallback, ]) @@ -117,6 +127,14 @@ final class AppComponentTests: XCTestCase { actions.append(.didRegisterBackupCallback) return Cancellable {} } + store.dependencies.app.groupRequestHandler.run = { _ in + actions.append(.didStartGroupRequestHandler) + return Cancellable {} + } + store.dependencies.app.groupMessageHandler.run = { _ in + actions.append(.didStartGroupMessageHandler) + return Cancellable {} + } actions = [] store.send(.start) @@ -129,6 +147,8 @@ final class AppComponentTests: XCTestCase { .didStartAuthHandler, .didStartMessageListener, .didStartReceiveFileHandler, + .didStartGroupRequestHandler, + .didStartGroupMessageHandler, .didRegisterBackupCallback, .didLoadMessenger, ]) @@ -170,6 +190,14 @@ final class AppComponentTests: XCTestCase { actions.append(.didRegisterBackupCallback) return Cancellable {} } + store.dependencies.app.groupRequestHandler.run = { _ in + actions.append(.didStartGroupRequestHandler) + return Cancellable {} + } + store.dependencies.app.groupMessageHandler.run = { _ in + actions.append(.didStartGroupMessageHandler) + return Cancellable {} + } actions = [] store.send(.welcome(.finished)) { @@ -183,6 +211,8 @@ final class AppComponentTests: XCTestCase { .didStartAuthHandler, .didStartMessageListener, .didStartReceiveFileHandler, + .didStartGroupRequestHandler, + .didStartGroupMessageHandler, .didRegisterBackupCallback, .didLoadMessenger, ]) @@ -224,6 +254,14 @@ final class AppComponentTests: XCTestCase { actions.append(.didRegisterBackupCallback) return Cancellable {} } + store.dependencies.app.groupRequestHandler.run = { _ in + actions.append(.didStartGroupRequestHandler) + return Cancellable {} + } + store.dependencies.app.groupMessageHandler.run = { _ in + actions.append(.didStartGroupMessageHandler) + return Cancellable {} + } actions = [] store.send(.restore(.finished)) { @@ -237,6 +275,8 @@ final class AppComponentTests: XCTestCase { .didStartAuthHandler, .didStartMessageListener, .didStartReceiveFileHandler, + .didStartGroupRequestHandler, + .didStartGroupMessageHandler, .didRegisterBackupCallback, .didLoadMessenger, ]) @@ -275,6 +315,14 @@ final class AppComponentTests: XCTestCase { actions.append(.didRegisterBackupCallback) return Cancellable {} } + store.dependencies.app.groupRequestHandler.run = { _ in + actions.append(.didStartGroupRequestHandler) + return Cancellable {} + } + store.dependencies.app.groupMessageHandler.run = { _ in + actions.append(.didStartGroupMessageHandler) + return Cancellable {} + } actions = [] store.send(.home(.deleteAccount(.success))) { @@ -288,6 +336,8 @@ final class AppComponentTests: XCTestCase { .didStartAuthHandler, .didStartMessageListener, .didStartReceiveFileHandler, + .didStartGroupRequestHandler, + .didStartGroupMessageHandler, .didRegisterBackupCallback, ]) @@ -378,6 +428,14 @@ final class AppComponentTests: XCTestCase { actions.append(.didRegisterBackupCallback) return Cancellable {} } + store.dependencies.app.groupRequestHandler.run = { _ in + actions.append(.didStartGroupRequestHandler) + return Cancellable {} + } + store.dependencies.app.groupMessageHandler.run = { _ in + actions.append(.didStartGroupMessageHandler) + return Cancellable {} + } actions = [] store.send(.start) @@ -390,6 +448,8 @@ final class AppComponentTests: XCTestCase { .didStartAuthHandler, .didStartMessageListener, .didStartReceiveFileHandler, + .didStartGroupRequestHandler, + .didStartGroupMessageHandler, .didRegisterBackupCallback, ]) @@ -447,6 +507,18 @@ final class AppComponentTests: XCTestCase { store.dependencies.app.backupStorage.store = { data in actions.append(.didStoreBackup(data)) } + store.dependencies.app.groupRequestHandler.run = { _ in + actions.append(.didStartGroupRequestHandler) + return Cancellable { + actions.append(.didCancelGroupRequestHandler) + } + } + store.dependencies.app.groupMessageHandler.run = { _ in + actions.append(.didStartGroupMessageHandler) + return Cancellable { + actions.append(.didCancelGroupMessageHandler) + } + } actions = [] store.send(.start) @@ -458,6 +530,8 @@ final class AppComponentTests: XCTestCase { .didStartAuthHandler, .didStartMessageListener, .didStartReceiveFileHandler, + .didStartGroupRequestHandler, + .didStartGroupMessageHandler, .didRegisterBackupCallback, ]) @@ -473,10 +547,14 @@ final class AppComponentTests: XCTestCase { .didCancelAuthHandler, .didCancelMessageListener, .didCancelReceiveFileHandler, + .didCancelGroupRequestHandler, + .didCancelGroupMessageHandler, .didCancelBackupCallback, .didStartAuthHandler, .didStartMessageListener, .didStartReceiveFileHandler, + .didStartGroupRequestHandler, + .didStartGroupMessageHandler, .didRegisterBackupCallback, ]) @@ -519,6 +597,8 @@ final class AppComponentTests: XCTestCase { .didCancelAuthHandler, .didCancelMessageListener, .didCancelReceiveFileHandler, + .didCancelGroupRequestHandler, + .didCancelGroupMessageHandler, .didCancelBackupCallback, ]) } @@ -539,4 +619,8 @@ private enum Action: Equatable { case didStoreBackup(Data) case didSetLogLevel(LogLevel) case didStartLogging + case didStartGroupRequestHandler + case didCancelGroupRequestHandler + case didStartGroupMessageHandler + case didCancelGroupMessageHandler } diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeComponentTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeComponentTests.swift index 3bd49a4283b9b4e535c40fb8c9279a5482aa3d16..f381d20481b1800fa3ec9f827bc17d6925ee01b8 100644 --- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeComponentTests.swift +++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeComponentTests.swift @@ -22,6 +22,7 @@ final class HomeComponentTests: XCTestCase { var messengerDidConnect = 0 var messengerDidListenForMessages = 0 var messengerDidStartFileTransfer = 0 + var messengerDidStartGroupChat = 0 store.dependencies.app.bgQueue = .immediate store.dependencies.app.mainQueue = .immediate @@ -34,6 +35,8 @@ final class HomeComponentTests: XCTestCase { store.dependencies.app.messenger.startFileTransfer.run = { messengerDidStartFileTransfer += 1 } store.dependencies.app.messenger.isLoggedIn.run = { false } store.dependencies.app.messenger.isRegistered.run = { false } + store.dependencies.app.messenger.isGroupChatRunning.run = { false } + store.dependencies.app.messenger.startGroupChat.run = { messengerDidStartGroupChat += 1 } store.send(.messenger(.start)) @@ -41,6 +44,7 @@ final class HomeComponentTests: XCTestCase { XCTAssertNoDifference(messengerDidConnect, 1) XCTAssertNoDifference(messengerDidListenForMessages, 1) XCTAssertNoDifference(messengerDidStartFileTransfer, 1) + XCTAssertNoDifference(messengerDidStartGroupChat, 1) store.receive(.networkMonitor(.stop)) store.receive(.messenger(.didStartUnregistered)) { @@ -60,6 +64,7 @@ final class HomeComponentTests: XCTestCase { var messengerDidStartFileTransfer = 0 var messengerDidLogIn = 0 var messengerDidResumeBackup = 0 + var messengerDidStartGroupChat = 0 store.dependencies.app.bgQueue = .immediate store.dependencies.app.mainQueue = .immediate @@ -84,6 +89,8 @@ final class HomeComponentTests: XCTestCase { } return cMix } + store.dependencies.app.messenger.isGroupChatRunning.run = { false } + store.dependencies.app.messenger.startGroupChat.run = { messengerDidStartGroupChat += 1 } store.send(.messenger(.start)) @@ -93,6 +100,7 @@ final class HomeComponentTests: XCTestCase { XCTAssertNoDifference(messengerDidStartFileTransfer, 1) XCTAssertNoDifference(messengerDidLogIn, 1) XCTAssertNoDifference(messengerDidResumeBackup, 1) + XCTAssertNoDifference(messengerDidStartGroupChat, 1) store.receive(.networkMonitor(.stop)) store.receive(.messenger(.didStartRegistered)) @@ -131,6 +139,7 @@ final class HomeComponentTests: XCTestCase { } return cMix } + store.dependencies.app.messenger.isGroupChatRunning.run = { true } store.send(.register(.finished)) { $0.register = nil @@ -209,6 +218,7 @@ final class HomeComponentTests: XCTestCase { store.dependencies.app.messenger.isFileTransferRunning.run = { true } store.dependencies.app.messenger.isLoggedIn.run = { false } store.dependencies.app.messenger.isRegistered.run = { throw error } + store.dependencies.app.messenger.isGroupChatRunning.run = { true } store.send(.messenger(.start)) @@ -236,6 +246,7 @@ final class HomeComponentTests: XCTestCase { store.dependencies.app.messenger.isLoggedIn.run = { false } store.dependencies.app.messenger.isRegistered.run = { true } store.dependencies.app.messenger.logIn.run = { throw error } + store.dependencies.app.messenger.isGroupChatRunning.run = { true } store.send(.messenger(.start)) diff --git a/Sources/XXMessengerClient/Messenger/Functions/MessengerIsGroupChatRunning.swift b/Sources/XXMessengerClient/Messenger/Functions/MessengerIsGroupChatRunning.swift new file mode 100644 index 0000000000000000000000000000000000000000..ce177d5d8c2d9858b6218d540131e4bb4c103b27 --- /dev/null +++ b/Sources/XXMessengerClient/Messenger/Functions/MessengerIsGroupChatRunning.swift @@ -0,0 +1,21 @@ +import XCTestDynamicOverlay + +public struct MessengerIsGroupChatRunning { + public var run: () -> Bool + + public func callAsFunction() -> Bool { + run() + } +} + +extension MessengerIsGroupChatRunning { + public static func live(_ env: MessengerEnvironment) -> MessengerIsGroupChatRunning { + MessengerIsGroupChatRunning { env.groupChat.get() != nil } + } +} + +extension MessengerIsGroupChatRunning { + public static let unimplemented = MessengerIsGroupChatRunning( + run: XCTestDynamicOverlay.unimplemented("\(Self.self)", placeholder: false) + ) +} diff --git a/Sources/XXMessengerClient/Messenger/Messenger.swift b/Sources/XXMessengerClient/Messenger/Messenger.swift index 44b213cb05805352ecffd0d7e07ca6f4880a42a7..aefef17da05277b628e9fd7f5f2b5b356566b890 100644 --- a/Sources/XXMessengerClient/Messenger/Messenger.swift +++ b/Sources/XXMessengerClient/Messenger/Messenger.swift @@ -51,6 +51,7 @@ public struct Messenger { public var getNotificationReports: MessengerGetNotificationReports public var registerGroupRequestHandler: MessengerRegisterGroupRequestHandler public var registerGroupChatProcessor: MessengerRegisterGroupChatProcessor + public var isGroupChatRunning: MessengerIsGroupChatRunning public var startGroupChat: MessengerStartGroupChat } @@ -107,6 +108,7 @@ extension Messenger { getNotificationReports: .live(env), registerGroupRequestHandler: .live(env), registerGroupChatProcessor: .live(env), + isGroupChatRunning: .live(env), startGroupChat: .live(env) ) } @@ -164,6 +166,7 @@ extension Messenger { getNotificationReports: .unimplemented, registerGroupRequestHandler: .unimplemented, registerGroupChatProcessor: .unimplemented, + isGroupChatRunning: .unimplemented, startGroupChat: .unimplemented ) } diff --git a/Tests/XXMessengerClientTests/Messenger/Functions/MessengerIsGroupChatRunningTests.swift b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerIsGroupChatRunningTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..364c12039396687de0520ec9465f3671b810e564 --- /dev/null +++ b/Tests/XXMessengerClientTests/Messenger/Functions/MessengerIsGroupChatRunningTests.swift @@ -0,0 +1,20 @@ +import XCTest +@testable import XXMessengerClient + +final class MessengerIsGroupChatRunningTests: XCTestCase { + func testIsRunning() { + var env: MessengerEnvironment = .unimplemented + env.groupChat.get = { .unimplemented } + let isRunning: MessengerIsGroupChatRunning = .live(env) + + XCTAssertTrue(isRunning()) + } + + func testIsNotRunning() { + var env: MessengerEnvironment = .unimplemented + env.groupChat.get = { nil } + let isRunning: MessengerIsGroupChatRunning = .live(env) + + XCTAssertFalse(isRunning()) + } +}