Skip to content
Snippets Groups Projects
Commit f267e537 authored by Dariusz Rybicki's avatar Dariusz Rybicki
Browse files

Merge branch 'feature/group-chats' into 'development'

Group Chats

See merge request elixxir/elixxir-dapps-sdk-swift!147
parents a8c2da54 d167e687
No related branches found
No related tags found
2 merge requests!147Group Chats,!102Release 1.0.0
Showing
with 477 additions and 8 deletions
......@@ -98,6 +98,9 @@ let ud = messenger.ud()
// get Backup:
let backup = messenger.backup()
// get GroupChat
let groupChat = messenger.groupChat()
```
## 💾 Backup
......@@ -217,3 +220,26 @@ let transferId = try messenger.sendFile(.init(file: file, recipientId: ...)) { i
}
}
```
## 💬 Group Chat
### Setup
```swift
// register callbacks:
let groupRequestsCancellable = messenger.registerGroupRequestHandler(.init { group in
// handle group request...
})
let groupChatProcessorCancellable = messenger.registerGroupChatProcessor(.init { result in
switch result {
case .success(let callback):
// handle group chat processor callback...
case .failure(let error):
// handle error...
}
})
// start group chat manager:
try messenger.startGroupChat()
```
\ No newline at end of file
......@@ -2,6 +2,8 @@ import Bindings
import XCTestDynamicOverlay
public struct GroupChatProcessor {
public typealias Result = Swift.Result<Callback, NSError>
public struct Callback: Equatable {
public init(
decryptedMessage: GroupChatMessage,
......@@ -29,14 +31,14 @@ public struct GroupChatProcessor {
public init(
name: String = "GroupChatProcessor",
handle: @escaping (Result<Callback, NSError>) -> Void
handle: @escaping (Result) -> Void
) {
self.name = name
self.handle = handle
}
public var name: String
public var handle: (Result<Callback, NSError>) -> Void
public var handle: (Result) -> Void
}
extension GroupChatProcessor {
......
......@@ -19,6 +19,7 @@ extension MessengerDestroy {
env.sleep(1)
}
}
env.groupChat.set(nil)
env.fileTransfer.set(nil)
env.backup.set(nil)
env.ud.set(nil)
......
import XCTestDynamicOverlay
import XXClient
public struct MessengerRegisterGroupChatProcessor {
public var run: (GroupChatProcessor) -> Cancellable
public func callAsFunction(_ processor: GroupChatProcessor) -> Cancellable {
run(processor)
}
}
extension MessengerRegisterGroupChatProcessor {
public static func live(_ env: MessengerEnvironment) -> MessengerRegisterGroupChatProcessor {
MessengerRegisterGroupChatProcessor { processor in
env.groupChatProcessors.register(processor)
}
}
}
extension MessengerRegisterGroupChatProcessor {
public static let unimplemented = MessengerRegisterGroupChatProcessor(
run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
)
}
import XCTestDynamicOverlay
import XXClient
public struct MessengerRegisterGroupRequestHandler {
public var run: (GroupRequest) -> Cancellable
public func callAsFunction(_ handler: GroupRequest) -> Cancellable {
run(handler)
}
}
extension MessengerRegisterGroupRequestHandler {
public static func live(_ env: MessengerEnvironment) -> MessengerRegisterGroupRequestHandler {
MessengerRegisterGroupRequestHandler { handler in
env.groupRequests.register(handler)
}
}
}
extension MessengerRegisterGroupRequestHandler {
public static let unimplemented = MessengerRegisterGroupRequestHandler(
run: XCTUnimplemented("\(Self.self)", placeholder: Cancellable {})
)
}
import XXClient
import XCTestDynamicOverlay
public struct MessengerStartGroupChat {
public enum Error: Swift.Error, Equatable {
case notConnected
}
public var run: () throws -> Void
public func callAsFunction() throws {
try run()
}
}
extension MessengerStartGroupChat {
public static func live(_ env: MessengerEnvironment) -> MessengerStartGroupChat {
MessengerStartGroupChat {
guard let e2e = env.e2e.get() else {
throw Error.notConnected
}
let groupChat = try env.newGroupChat(
e2eId: e2e.getId(),
groupRequest: env.groupRequests.registered(),
groupChatProcessor: env.groupChatProcessors.registered()
)
env.groupChat.set(groupChat)
}
}
}
extension MessengerStartGroupChat {
public static let unimplemented = MessengerStartGroupChat(
run: XCTestDynamicOverlay.unimplemented("\(Self.self)")
)
}
......@@ -6,6 +6,7 @@ public struct Messenger {
public var ud: Stored<UserDiscovery?>
public var backup: Stored<Backup?>
public var fileTransfer: Stored<FileTransfer?>
public var groupChat: Stored<GroupChat?>
public var isCreated: MessengerIsCreated
public var create: MessengerCreate
public var restoreBackup: MessengerRestoreBackup
......@@ -48,6 +49,9 @@ public struct Messenger {
public var receiveFile: MessengerReceiveFile
public var trackServices: MessengerTrackServices
public var getNotificationReports: MessengerGetNotificationReports
public var registerGroupRequestHandler: MessengerRegisterGroupRequestHandler
public var registerGroupChatProcessor: MessengerRegisterGroupChatProcessor
public var startGroupChat: MessengerStartGroupChat
}
extension Messenger {
......@@ -58,6 +62,7 @@ extension Messenger {
ud: env.ud,
backup: env.backup,
fileTransfer: env.fileTransfer,
groupChat: env.groupChat,
isCreated: .live(env),
create: .live(env),
restoreBackup: .live(env),
......@@ -99,7 +104,10 @@ extension Messenger {
sendFile: .live(env),
receiveFile: .live(env),
trackServices: .live(env),
getNotificationReports: .live(env)
getNotificationReports: .live(env),
registerGroupRequestHandler: .live(env),
registerGroupChatProcessor: .live(env),
startGroupChat: .live(env)
)
}
}
......@@ -111,6 +119,7 @@ extension Messenger {
ud: .unimplemented(),
backup: .unimplemented(),
fileTransfer: .unimplemented(),
groupChat: .unimplemented(),
isCreated: .unimplemented,
create: .unimplemented,
restoreBackup: .unimplemented,
......@@ -152,6 +161,9 @@ extension Messenger {
sendFile: .unimplemented,
receiveFile: .unimplemented,
trackServices: .unimplemented,
getNotificationReports: .unimplemented
getNotificationReports: .unimplemented,
registerGroupRequestHandler: .unimplemented,
registerGroupChatProcessor: .unimplemented,
startGroupChat: .unimplemented
)
}
......@@ -18,6 +18,9 @@ public struct MessengerEnvironment {
public var getFileTransferParams: GetFileTransferParams
public var getNotificationsReport: GetNotificationsReport
public var getSingleUseParams: GetSingleUseParams
public var groupChat: Stored<GroupChat?>
public var groupChatProcessors: GroupChatProcessorRegistry
public var groupRequests: GroupRequestCallbacksRegistry
public var initFileTransfer: InitFileTransfer
public var initializeBackup: InitializeBackup
public var isListeningForMessages: Stored<Bool>
......@@ -31,6 +34,7 @@ public struct MessengerEnvironment {
public var ndfEnvironment: NDFEnvironment
public var newCMix: NewCMix
public var newCMixFromBackup: NewCMixFromBackup
public var newGroupChat: NewGroupChat
public var newOrLoadUd: NewOrLoadUd
public var newUdManagerFromBackup: NewUdManagerFromBackup
public var passwordStorage: PasswordStorage
......@@ -71,6 +75,9 @@ extension MessengerEnvironment {
getFileTransferParams: .liveDefault,
getNotificationsReport: .live,
getSingleUseParams: .liveDefault,
groupChat: .inMemory(),
groupChatProcessors: .live(),
groupRequests: .live(),
initFileTransfer: .live,
initializeBackup: .live,
isListeningForMessages: .inMemory(false),
......@@ -84,6 +91,7 @@ extension MessengerEnvironment {
ndfEnvironment: .mainnet,
newCMix: .live,
newCMixFromBackup: .live,
newGroupChat: .live,
newOrLoadUd: .live,
newUdManagerFromBackup: .live,
passwordStorage: .keychain,
......@@ -119,6 +127,9 @@ extension MessengerEnvironment {
getFileTransferParams: .unimplemented,
getNotificationsReport: .unimplemented,
getSingleUseParams: .unimplemented,
groupChat: .unimplemented(),
groupChatProcessors: .unimplemented,
groupRequests: .unimplemented,
initFileTransfer: .unimplemented,
initializeBackup: .unimplemented,
isListeningForMessages: .unimplemented(placeholder: false),
......@@ -132,6 +143,7 @@ extension MessengerEnvironment {
ndfEnvironment: .unimplemented,
newCMix: .unimplemented,
newCMixFromBackup: .unimplemented,
newGroupChat: .unimplemented,
newOrLoadUd: .unimplemented,
newUdManagerFromBackup: .unimplemented,
passwordStorage: .unimplemented,
......
import Foundation
import XCTestDynamicOverlay
import XXClient
public struct GroupChatProcessorRegistry {
public var register: (GroupChatProcessor) -> Cancellable
public var registered: () -> GroupChatProcessor
}
extension GroupChatProcessorRegistry {
public static func live() -> GroupChatProcessorRegistry {
class Registry {
var items: [UUID: GroupChatProcessor] = [:]
}
let registry = Registry()
return GroupChatProcessorRegistry(
register: { processor in
let id = UUID()
registry.items[id] = processor
return Cancellable { registry.items[id] = nil }
},
registered: {
GroupChatProcessor(
name: "GroupChatProcessorRegistry.registered",
handle: { result in
registry.items.values.forEach { $0.handle(result) }
}
)
}
)
}
}
extension GroupChatProcessorRegistry {
public static let unimplemented = GroupChatProcessorRegistry(
register: XCTestDynamicOverlay.unimplemented(
"\(Self.self).register",
placeholder: Cancellable {}
),
registered: XCTestDynamicOverlay.unimplemented(
"\(Self.self).registered",
placeholder: .unimplemented
)
)
}
import Foundation
import XCTestDynamicOverlay
import XXClient
public struct GroupRequestCallbacksRegistry {
public var register: (GroupRequest) -> Cancellable
public var registered: () -> GroupRequest
}
extension GroupRequestCallbacksRegistry {
public static func live() -> GroupRequestCallbacksRegistry {
class Registry {
var items: [UUID: GroupRequest] = [:]
}
let registry = Registry()
return GroupRequestCallbacksRegistry(
register: { groupRequest in
let id = UUID()
registry.items[id] = groupRequest
return Cancellable { registry.items[id] = nil }
},
registered: {
GroupRequest { group in
registry.items.values.forEach { $0.handle(group) }
}
}
)
}
}
extension GroupRequestCallbacksRegistry {
public static let unimplemented = GroupRequestCallbacksRegistry(
register: XCTestDynamicOverlay.unimplemented(
"\(Self.self).register",
placeholder: Cancellable {}
),
registered: XCTestDynamicOverlay.unimplemented(
"\(Self.self).registered",
placeholder: .unimplemented
)
)
}
......@@ -31,9 +31,6 @@ extension ListenersRegistry {
extension ListenersRegistry {
public static let unimplemented = ListenersRegistry(
register: XCTUnimplemented("\(Self.self).register", placeholder: Cancellable {}),
registered: XCTUnimplemented("\(Self.self).registered", placeholder: Listener(
name: "unimplemented",
handle: { _ in }
))
registered: XCTUnimplemented("\(Self.self).registered", placeholder: .unimplemented)
)
}
......@@ -10,6 +10,7 @@ final class MessengerDestroyTests: XCTestCase {
var didStopNetworkFollower = 0
var didSleep: [TimeInterval] = []
var didRemoveItem: [String] = []
var didSetGroupChat: [GroupChat?] = []
var didSetFileTransfer: [FileTransfer?] = []
var didSetBackup: [Backup?] = []
var didSetUD: [UserDiscovery?] = []
......@@ -29,6 +30,7 @@ final class MessengerDestroyTests: XCTestCase {
}
env.sleep = { didSleep.append($0) }
env.storageDir = storageDir
env.groupChat.set = { didSetGroupChat.append($0) }
env.fileTransfer.set = { didSetFileTransfer.append($0) }
env.backup.set = { didSetBackup.append($0) }
env.ud.set = { didSetUD.append($0) }
......@@ -44,6 +46,7 @@ final class MessengerDestroyTests: XCTestCase {
XCTAssertNoDifference(didStopNetworkFollower, 1)
XCTAssertNoDifference(didSleep, [1, 1])
XCTAssertNoDifference(didSetGroupChat.map { $0 == nil }, [true])
XCTAssertNoDifference(didSetFileTransfer.map { $0 == nil }, [true])
XCTAssertNoDifference(didSetBackup.map { $0 == nil }, [true])
XCTAssertNoDifference(didSetUD.map { $0 == nil }, [true])
......@@ -76,6 +79,7 @@ final class MessengerDestroyTests: XCTestCase {
func testRemoveDirectoryFailure() {
struct Error: Swift.Error, Equatable {}
let error = Error()
var didSetGroupChat: [GroupChat?] = []
var didSetFileTransfer: [FileTransfer?] = []
var didSetBackup: [Backup?] = []
var didSetUD: [UserDiscovery?] = []
......@@ -86,6 +90,7 @@ final class MessengerDestroyTests: XCTestCase {
var env: MessengerEnvironment = .unimplemented
env.cMix.get = { nil }
env.groupChat.set = { didSetGroupChat.append($0) }
env.fileTransfer.set = { didSetFileTransfer.append($0) }
env.backup.set = { didSetBackup.append($0) }
env.ud.set = { didSetUD.append($0) }
......@@ -99,6 +104,7 @@ final class MessengerDestroyTests: XCTestCase {
XCTAssertThrowsError(try destroy()) { err in
XCTAssertEqual(err as? Error, error)
}
XCTAssertNoDifference(didSetGroupChat.map { $0 == nil }, [true])
XCTAssertNoDifference(didSetFileTransfer.map { $0 == nil }, [true])
XCTAssertNoDifference(didSetBackup.map { $0 == nil }, [true])
XCTAssertNoDifference(didSetUD.map { $0 == nil }, [true])
......@@ -113,6 +119,7 @@ final class MessengerDestroyTests: XCTestCase {
let error = Error()
let storageDir = "test-storage-dir"
var didRemoveItem: [String] = []
var didSetGroupChat: [GroupChat?] = []
var didSetFileTransfer: [FileTransfer?] = []
var didSetBackup: [Backup?] = []
var didSetUD: [UserDiscovery?] = []
......@@ -123,6 +130,7 @@ final class MessengerDestroyTests: XCTestCase {
var env: MessengerEnvironment = .unimplemented
env.cMix.get = { nil }
env.groupChat.set = { didSetGroupChat.append($0) }
env.fileTransfer.set = { didSetFileTransfer.append($0) }
env.backup.set = { didSetBackup.append($0) }
env.ud.set = { didSetUD.append($0) }
......@@ -138,6 +146,7 @@ final class MessengerDestroyTests: XCTestCase {
XCTAssertThrowsError(try destroy()) { err in
XCTAssertEqual(err as? Error, error)
}
XCTAssertNoDifference(didSetGroupChat.map { $0 == nil }, [true])
XCTAssertNoDifference(didSetFileTransfer.map { $0 == nil }, [true])
XCTAssertNoDifference(didSetBackup.map { $0 == nil }, [true])
XCTAssertNoDifference(didSetUD.map { $0 == nil }, [true])
......
import XCTest
import XXClient
@testable import XXMessengerClient
final class MessengerRegisterGroupChatProcessorTests: XCTestCase {
func testRegister() {
var registered: [GroupChatProcessor] = []
var didHandle: [GroupChatProcessor.Result] = []
var didCancel = 0
var env: MessengerEnvironment = .unimplemented
env.groupChatProcessors.register = { processor in
registered.append(processor)
return Cancellable { didCancel += 1 }
}
let register: MessengerRegisterGroupChatProcessor = .live(env)
let cancellable = register(.init { result in
didHandle.append(result)
})
XCTAssertEqual(registered.count, 1)
let result = GroupChatProcessor.Result.success(.stub())
registered.forEach { processor in
processor.handle(result)
}
XCTAssertEqual(didHandle, [result])
cancellable.cancel()
XCTAssertEqual(didCancel, 1)
}
}
import XCTest
import XXClient
@testable import XXMessengerClient
final class MessengerRegisterGroupRequestHandlerTests: XCTestCase {
func testRegister() {
var registered: [GroupRequest] = []
var didHandle: [Group] = []
var didCancel = 0
var env: MessengerEnvironment = .unimplemented
env.groupRequests.register = { handler in
registered.append(handler)
return Cancellable { didCancel += 1 }
}
let register: MessengerRegisterGroupRequestHandler = .live(env)
let cancellable = register(.init { group in
didHandle.append(group)
})
XCTAssertEqual(registered.count, 1)
let group = Group.stub(1)
registered.forEach { handler in
handler.handle(group)
}
XCTAssertEqual(didHandle.map { $0.getId() }, [group.getId()])
cancellable.cancel()
XCTAssertEqual(didCancel, 1)
}
}
import CustomDump
import XXClient
import XCTest
@testable import XXMessengerClient
final class MessengerStartGroupChatTests: XCTestCase {
func testStart() throws {
var didCreateNewGroupChatWithE2eId: [Int] = []
var didSetGroupChat: [GroupChat?] = []
let e2eId = 123
var env: MessengerEnvironment = .unimplemented
env.e2e.get = {
var e2e: E2E = .unimplemented
e2e.getId.run = { e2eId }
return e2e
}
env.groupRequests.registered = { .unimplemented }
env.groupChatProcessors.registered = { .unimplemented }
env.newGroupChat.run = { e2eId, _, _ in
didCreateNewGroupChatWithE2eId.append(e2eId)
return .unimplemented
}
env.groupChat.set = { groupChat in
didSetGroupChat.append(groupChat)
}
let start: MessengerStartGroupChat = .live(env)
try start()
XCTAssertEqual(didCreateNewGroupChatWithE2eId, [e2eId])
XCTAssertEqual(didSetGroupChat.map { $0 != nil }, [true])
}
func testStartWithoutE2E() throws {
var env: MessengerEnvironment = .unimplemented
env.e2e.get = { nil }
let start: MessengerStartGroupChat = .live(env)
XCTAssertThrowsError(try start()) { error in
XCTAssertEqual(error as? MessengerStartGroupChat.Error, .notConnected)
}
}
}
......@@ -55,3 +55,36 @@ extension MessageService {
)
}
}
extension Group {
static func stub(_ id: Int) -> Group {
var group = Group.unimplemented
group.getId.run = { "group-\(id)".data(using: .utf8)! }
return group
}
}
extension GroupChatMessage {
static func stub() -> GroupChatMessage {
GroupChatMessage(
groupId: "\(Int.random(in: 100...999))".data(using: .utf8)!,
senderId: "\(Int.random(in: 100...999))".data(using: .utf8)!,
messageId: "\(Int.random(in: 100...999))".data(using: .utf8)!,
payload: "\(Int.random(in: 100...999))".data(using: .utf8)!,
timestamp: Int64.random(in: 100...999)
)
}
}
extension GroupChatProcessor.Callback {
static func stub() -> GroupChatProcessor.Callback {
GroupChatProcessor.Callback(
decryptedMessage: .stub(),
msg: "\(Int.random(in: 100...999))".data(using: .utf8)!,
receptionId: "\(Int.random(in: 100...999))".data(using: .utf8)!,
ephemeralId: Int64.random(in: 100...999),
roundId: Int64.random(in: 100...999),
roundUrl: "\(Int.random(in: 100...999))"
)
}
}
import CustomDump
import XCTest
import XXClient
@testable import XXMessengerClient
final class GroupChatProcessorRegistryTests: XCTestCase {
func testRegistry() {
var firstProcessorDidHandle: [GroupChatProcessor.Result] = []
var secondProcessorDidHandle: [GroupChatProcessor.Result] = []
let firstProcessor = GroupChatProcessor(
name: "first",
handle: { firstProcessorDidHandle.append($0) }
)
let secondProcessor = GroupChatProcessor(
name: "second",
handle: { secondProcessorDidHandle.append($0) }
)
let registry: GroupChatProcessorRegistry = .live()
let registeredProcessors = registry.registered()
let firstProcessorCancellable = registry.register(firstProcessor)
let secondProcessorCancellable = registry.register(secondProcessor)
let firstResult = GroupChatProcessor.Result.success(.stub())
registeredProcessors.handle(firstResult)
XCTAssertNoDifference(firstProcessorDidHandle, [firstResult])
XCTAssertNoDifference(secondProcessorDidHandle, [firstResult])
firstProcessorDidHandle = []
secondProcessorDidHandle = []
firstProcessorCancellable.cancel()
let secondResult = GroupChatProcessor.Result.success(.stub())
registeredProcessors.handle(secondResult)
XCTAssertNoDifference(firstProcessorDidHandle, [])
XCTAssertNoDifference(secondProcessorDidHandle, [secondResult])
firstProcessorDidHandle = []
secondProcessorDidHandle = []
secondProcessorCancellable.cancel()
let thirdResult = GroupChatProcessor.Result.success(.stub())
registeredProcessors.handle(thirdResult)
XCTAssertNoDifference(firstProcessorDidHandle, [])
XCTAssertNoDifference(secondProcessorDidHandle, [])
}
}
import CustomDump
import XCTest
import XXClient
@testable import XXMessengerClient
final class GroupRequestCallbacksRegistryTests: XCTestCase {
func testRegistry() {
var firstCallbackDidHandle: [Group] = []
var secondCallbackDidHandle: [Group] = []
let firstCallback = GroupRequest { group in
firstCallbackDidHandle.append(group)
}
let secondCallback = GroupRequest { group in
secondCallbackDidHandle.append(group)
}
let registry: GroupRequestCallbacksRegistry = .live()
let registeredCallbacks = registry.registered()
let firstCallbackCancellable = registry.register(firstCallback)
let secondCallbackCancellable = registry.register(secondCallback)
let firstGroup = Group.stub(1)
registeredCallbacks.handle(firstGroup)
XCTAssertNoDifference(firstCallbackDidHandle.map { $0.getId() }, [firstGroup.getId()])
XCTAssertNoDifference(secondCallbackDidHandle.map { $0.getId() }, [firstGroup.getId()])
firstCallbackDidHandle = []
secondCallbackDidHandle = []
firstCallbackCancellable.cancel()
let secondGroup = Group.stub(2)
registeredCallbacks.handle(secondGroup)
XCTAssertNoDifference(firstCallbackDidHandle.map { $0.getId() }, [])
XCTAssertNoDifference(secondCallbackDidHandle.map { $0.getId() }, [secondGroup.getId()])
firstCallbackDidHandle = []
secondCallbackDidHandle = []
secondCallbackCancellable.cancel()
let thirdGroup = Group.stub(3)
registeredCallbacks.handle(thirdGroup)
XCTAssertNoDifference(firstCallbackDidHandle.map { $0.getId() }, [])
XCTAssertNoDifference(secondCallbackDidHandle.map { $0.getId() }, [])
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment