diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/GroupFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/GroupFeature.xcscheme new file mode 100644 index 0000000000000000000000000000000000000000..a2c612b798714cdc30572dad61ce6b5615e7f374 --- /dev/null +++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/GroupFeature.xcscheme @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1410" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "GroupFeature" + BuildableName = "GroupFeature" + BlueprintName = "GroupFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "GroupFeatureTests" + BuildableName = "GroupFeatureTests" + BlueprintName = "GroupFeatureTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "GroupFeature" + BuildableName = "GroupFeature" + BlueprintName = "GroupFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/GroupsFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/GroupsFeature.xcscheme new file mode 100644 index 0000000000000000000000000000000000000000..65b9cc334358a6bcfd2a06176d7d9da743bfea75 --- /dev/null +++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/GroupsFeature.xcscheme @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1410" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "GroupsFeature" + BuildableName = "GroupsFeature" + BlueprintName = "GroupsFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "GroupsFeatureTests" + BuildableName = "GroupsFeatureTests" + BlueprintName = "GroupsFeatureTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "GroupsFeature" + BuildableName = "GroupsFeature" + BlueprintName = "GroupsFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/NewGroupFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/NewGroupFeature.xcscheme new file mode 100644 index 0000000000000000000000000000000000000000..8a4e4405667c2a1dd517c6d8a3468e2c21a5dc3b --- /dev/null +++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/NewGroupFeature.xcscheme @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1410" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "NewGroupFeature" + BuildableName = "NewGroupFeature" + BlueprintName = "NewGroupFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "NewGroupFeatureTests" + BuildableName = "NewGroupFeatureTests" + BlueprintName = "NewGroupFeatureTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "NewGroupFeature" + BuildableName = "NewGroupFeature" + BlueprintName = "NewGroupFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index e49750ca16702861be964a5991472e4ff186b9fb..be183c099b55f8840c04f0470ab00cf3fd37ffd4 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -22,8 +22,11 @@ let package = Package( .library(name: "ContactFeature", targets: ["ContactFeature"]), .library(name: "ContactLookupFeature", targets: ["ContactLookupFeature"]), .library(name: "ContactsFeature", targets: ["ContactsFeature"]), + .library(name: "GroupFeature", targets: ["GroupFeature"]), + .library(name: "GroupsFeature", targets: ["GroupsFeature"]), .library(name: "HomeFeature", targets: ["HomeFeature"]), .library(name: "MyContactFeature", targets: ["MyContactFeature"]), + .library(name: "NewGroupFeature", targets: ["NewGroupFeature"]), .library(name: "RegisterFeature", targets: ["RegisterFeature"]), .library(name: "ResetAuthFeature", targets: ["ResetAuthFeature"]), .library(name: "RestoreFeature", targets: ["RestoreFeature"]), @@ -38,7 +41,7 @@ let package = Package( ), .package( url: "https://github.com/pointfreeco/swift-composable-architecture.git", - .upToNextMajor(from: "0.43.0") + .upToNextMajor(from: "0.47.2") ), .package( url: "https://git.xx.network/elixxir/client-ios-db.git", @@ -46,15 +49,15 @@ let package = Package( ), .package( url: "https://github.com/darrarski/swift-composable-presentation.git", - .upToNextMajor(from: "0.6.0") + .upToNextMajor(from: "0.6.1") ), .package( url: "https://github.com/pointfreeco/xctest-dynamic-overlay.git", - .upToNextMajor(from: "0.5.0") + .upToNextMajor(from: "0.6.0") ), .package( url: "https://github.com/pointfreeco/swift-custom-dump.git", - .upToNextMajor(from: "0.6.0") + .upToNextMajor(from: "0.6.1") ), .package( url: "https://github.com/apple/swift-log.git", @@ -260,12 +263,56 @@ let package = Package( ], swiftSettings: swiftSettings ), + .target( + name: "GroupFeature", + dependencies: [ + .target(name: "AppCore"), + .target(name: "ChatFeature"), + .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), + .product(name: "ComposablePresentation", package: "swift-composable-presentation"), + .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "XXModels", package: "client-ios-db"), + ], + swiftSettings: swiftSettings + ), + .testTarget( + name: "GroupFeatureTests", + dependencies: [ + .target(name: "GroupFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), + ], + swiftSettings: swiftSettings + ), + .target( + name: "GroupsFeature", + dependencies: [ + .target(name: "AppCore"), + .target(name: "GroupFeature"), + .target(name: "NewGroupFeature"), + .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), + .product(name: "ComposablePresentation", package: "swift-composable-presentation"), + .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "XXModels", package: "client-ios-db"), + ], + swiftSettings: swiftSettings + ), + .testTarget( + name: "GroupsFeatureTests", + dependencies: [ + .target(name: "GroupsFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), + ], + swiftSettings: swiftSettings + ), .target( name: "HomeFeature", dependencies: [ .target(name: "AppCore"), .target(name: "BackupFeature"), .target(name: "ContactsFeature"), + .target(name: "GroupsFeature"), .target(name: "RegisterFeature"), .target(name: "UserSearchFeature"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), @@ -302,6 +349,25 @@ let package = Package( ], swiftSettings: swiftSettings ), + .target( + name: "NewGroupFeature", + dependencies: [ + .target(name: "AppCore"), + .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), + .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "XXModels", package: "client-ios-db"), + ], + swiftSettings: swiftSettings + ), + .testTarget( + name: "NewGroupFeatureTests", + dependencies: [ + .target(name: "NewGroupFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), + ], + swiftSettings: swiftSettings + ), .target( name: "RegisterFeature", dependencies: [ diff --git a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/project.pbxproj b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/project.pbxproj index 3aa853ea0de719e4ec0c0602968dd637f7214a95..79aef5cb6f3b4768082dd1f6d64ad3131d79e024 100644 --- a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/project.pbxproj +++ b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/project.pbxproj @@ -165,7 +165,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 7; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -228,7 +228,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 6; + CURRENT_PROJECT_VERSION = 7; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; diff --git a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme index aef30c316ef7aa40b1b1c36e37057d4284222bf4..5cd339fe12651b68629b2b533f8add948a6211a6 100644 --- a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme +++ b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme @@ -119,6 +119,26 @@ ReferencedContainer = "container:.."> </BuildableReference> </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "GroupFeatureTests" + BuildableName = "GroupFeatureTests" + BlueprintName = "GroupFeatureTests" + ReferencedContainer = "container:.."> + </BuildableReference> + </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "GroupsFeatureTests" + BuildableName = "GroupsFeatureTests" + BlueprintName = "GroupsFeatureTests" + ReferencedContainer = "container:.."> + </BuildableReference> + </TestableReference> <TestableReference skipped = "NO"> <BuildableReference @@ -139,6 +159,16 @@ ReferencedContainer = "container:.."> </BuildableReference> </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "NewGroupFeatureTests" + BuildableName = "NewGroupFeatureTests" + BlueprintName = "NewGroupFeatureTests" + ReferencedContainer = "container:.."> + </BuildableReference> + </TestableReference> <TestableReference skipped = "NO"> <BuildableReference diff --git a/Examples/xx-messenger/Sources/AppCore/AppDependecies.swift b/Examples/xx-messenger/Sources/AppCore/AppDependecies.swift index 90077f25563bbc1e91634ee579403863d8f0b4bd..9e55ac6da53b79b6c43352a78f58c78e4020551d 100644 --- a/Examples/xx-messenger/Sources/AppCore/AppDependecies.swift +++ b/Examples/xx-messenger/Sources/AppCore/AppDependecies.swift @@ -12,11 +12,14 @@ public struct AppDependencies { public var bgQueue: AnySchedulerOf<DispatchQueue> public var now: () -> Date public var sendMessage: SendMessage + public var sendGroupMessage: SendGroupMessage public var sendImage: SendImage public var messageListener: MessageListenerHandler public var receiveFileHandler: ReceiveFileHandler public var log: Logger public var loadData: URLDataLoader + public var groupRequestHandler: GroupRequestHandler + public var groupMessageHandler: GroupMessageHandler } extension AppDependencies { @@ -44,6 +47,11 @@ extension AppDependencies { db: dbManager.getDB, now: now ), + sendGroupMessage: .live( + messenger: messenger, + db: dbManager.getDB, + now: now + ), sendImage: .live( messenger: messenger, db: dbManager.getDB, @@ -59,7 +67,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 + ) ) } @@ -75,11 +91,14 @@ extension AppDependencies { placeholder: Date(timeIntervalSince1970: 0) ), sendMessage: .unimplemented, + sendGroupMessage: .unimplemented, sendImage: .unimplemented, 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/AppCore/SendMessage/SendGroupMessage.swift b/Examples/xx-messenger/Sources/AppCore/SendMessage/SendGroupMessage.swift new file mode 100644 index 0000000000000000000000000000000000000000..9427053788da3f869e9dd6b0845d581c7e8905c2 --- /dev/null +++ b/Examples/xx-messenger/Sources/AppCore/SendMessage/SendGroupMessage.swift @@ -0,0 +1,84 @@ +import Foundation +import XCTestDynamicOverlay +import XXClient +import XXMessengerClient +import XXModels + +public struct SendGroupMessage { + public typealias OnError = (Error) -> Void + public typealias Completion = () -> Void + + public var run: (String, Data, @escaping OnError, @escaping Completion) -> Void + + public func callAsFunction( + text: String, + to groupId: Data, + onError: @escaping OnError, + completion: @escaping Completion + ) { + run(text, groupId, onError, completion) + } +} + +extension SendGroupMessage { + public static func live( + messenger: Messenger, + db: DBManagerGetDB, + now: @escaping () -> Date + ) -> SendGroupMessage { + SendGroupMessage { text, groupId, onError, completion in + do { + let chat = try messenger.groupChat.tryGet() + let myContactId = try messenger.e2e.tryGet().getContact().getId() + var message = try db().saveMessage(.init( + senderId: myContactId, + recipientId: nil, + groupId: groupId, + date: now(), + status: .sending, + isUnread: false, + text: text + )) + let payload = MessagePayload(text: message.text) + let report = try chat.send( + groupId: groupId, + message: try payload.encode() + ) + message.networkId = report.messageId + message.roundURL = report.roundURL + message = try db().saveMessage(message) + try messenger.cMix.tryGet().waitForRoundResult( + roundList: try report.encode(), + timeoutMS: 30_000, + callback: .init { result in + let status: XXModels.Message.Status + switch result { + case .delivered(_): + status = .sent + case .notDelivered(let timedOut): + status = timedOut ? .sendingTimedOut : .sendingFailed + } + do { + try db().bulkUpdateMessages( + .init(id: [message.id]), + .init(status: status) + ) + } catch { + onError(error) + } + completion() + } + ) + } catch { + onError(error) + completion() + } + } + } +} + +extension SendGroupMessage { + public static let unimplemented = SendGroupMessage( + run: XCTUnimplemented("\(Self.self)") + ) +} diff --git a/Examples/xx-messenger/Sources/AppCore/SharedUI/GroupAuthStatusView.swift b/Examples/xx-messenger/Sources/AppCore/SharedUI/GroupAuthStatusView.swift new file mode 100644 index 0000000000000000000000000000000000000000..bddb9314099396b612fae595309affc0d8840297 --- /dev/null +++ b/Examples/xx-messenger/Sources/AppCore/SharedUI/GroupAuthStatusView.swift @@ -0,0 +1,57 @@ +import SwiftUI +import XXModels + +public struct GroupAuthStatusView: View { + public init(_ authStatus: XXModels.Group.AuthStatus) { + self.authStatus = authStatus + } + + public var authStatus: XXModels.Group.AuthStatus + + public var body: some View { + switch authStatus { + case .pending: + HStack { + Text("Pending") + Spacer() + Image(systemName: "envelope.badge") + } + + case .deleting: + HStack { + Text("Deleting") + Spacer() + ProgressView() + } + + case .participating: + HStack { + Text("Participating") + Spacer() + Image(systemName: "checkmark") + } + + case .hidden: + HStack { + Text("Hidden") + Spacer() + Image(systemName: "eye.slash") + } + } + } +} + +#if DEBUG +struct GroupAuthStatusView_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + Form { + Section { GroupAuthStatusView(.pending) } + Section { GroupAuthStatusView(.deleting) } + Section { GroupAuthStatusView(.participating) } + Section { GroupAuthStatusView(.hidden) } + } + } + } +} +#endif 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/ChatFeature/ChatComponent.swift b/Examples/xx-messenger/Sources/ChatFeature/ChatComponent.swift index 6c71789dbbb201122cfda709999ac36984363b55..def3f14cdbae265ed27077c7e88a96416b924a49 100644 --- a/Examples/xx-messenger/Sources/ChatFeature/ChatComponent.swift +++ b/Examples/xx-messenger/Sources/ChatFeature/ChatComponent.swift @@ -10,7 +10,8 @@ import XXModels public struct ChatComponent: ReducerProtocol { public struct State: Equatable, Identifiable { public enum ID: Equatable, Hashable { - case contact(Data) + case contact(XXModels.Contact.ID) + case group(XXModels.Group.ID) } public struct Message: Equatable, Identifiable { @@ -18,6 +19,7 @@ public struct ChatComponent: ReducerProtocol { id: Int64, date: Date, senderId: Data, + senderName: String?, text: String, status: XXModels.Message.Status, fileTransfer: XXModels.FileTransfer? = nil @@ -25,6 +27,7 @@ public struct ChatComponent: ReducerProtocol { self.id = id self.date = date self.senderId = senderId + self.senderName = senderName self.text = text self.status = status self.fileTransfer = fileTransfer @@ -33,6 +36,7 @@ public struct ChatComponent: ReducerProtocol { public var id: Int64 public var date: Date public var senderId: Data + public var senderName: String? public var text: String public var status: XXModels.Message.Status public var fileTransfer: XXModels.FileTransfer? @@ -77,6 +81,7 @@ public struct ChatComponent: ReducerProtocol { @Dependency(\.app.messenger) var messenger: Messenger @Dependency(\.app.dbManager.getDB) var db: DBManagerGetDB @Dependency(\.app.sendMessage) var sendMessage: SendMessage + @Dependency(\.app.sendGroupMessage) var sendGroupMessage: SendGroupMessage @Dependency(\.app.sendImage) var sendImage: SendImage @Dependency(\.app.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue> @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue> @@ -93,37 +98,46 @@ public struct ChatComponent: ReducerProtocol { let myContactId = try messenger.e2e.tryGet().getContact().getId() state.myContactId = myContactId let queryChat: XXModels.Message.Query.Chat - let receivedFileTransfersQuery: XXModels.FileTransfer.Query - let sentFileTransfersQuery: XXModels.FileTransfer.Query + let receivedFileTransfersPublisher: AnyPublisher<[XXModels.FileTransfer], Error> + let sentFileTransfersPublisher: AnyPublisher<[XXModels.FileTransfer], Error> switch state.id { case .contact(let contactId): queryChat = .direct(myContactId, contactId) - receivedFileTransfersQuery = .init( + receivedFileTransfersPublisher = try db().fetchFileTransfersPublisher(.init( contactId: contactId, isIncoming: true - ) - sentFileTransfersQuery = .init( + )) + sentFileTransfersPublisher = try db().fetchFileTransfersPublisher(.init( contactId: myContactId, isIncoming: false - ) + )) + case .group(let groupId): + queryChat = .group(groupId) + receivedFileTransfersPublisher = Just([]) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + sentFileTransfersPublisher = Just([]) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() } let messagesQuery = XXModels.Message.Query(chat: queryChat) return Publishers.CombineLatest3( try db().fetchMessagesPublisher(messagesQuery), - try db().fetchFileTransfersPublisher(receivedFileTransfersQuery), - try db().fetchFileTransfersPublisher(sentFileTransfersQuery) + try db().fetchContactsPublisher(.init()), + Publishers.CombineLatest( + receivedFileTransfersPublisher, + sentFileTransfersPublisher + ).map(+) ) - .map { messages, receivedFileTransfers, sentFileTransfers in - (messages, receivedFileTransfers + sentFileTransfers) - } .assertNoFailure() - .map { messages, fileTransfers in - messages.compactMap { message in + .map { messages, contacts, fileTransfers -> [State.Message] in + messages.compactMap { message -> State.Message? in guard let id = message.id else { return nil } return State.Message( id: id, date: message.date, senderId: message.senderId, + senderName: contacts.first { $0.id == message.senderId }?.username, text: message.text, status: message.status, fileTransfer: fileTransfers.first { $0.id == message.fileTransferId } @@ -163,6 +177,17 @@ public struct ChatComponent: ReducerProtocol { subscriber.send(completion: .finished) } ) + case .group(let groupId): + sendGroupMessage( + text: text, + to: groupId, + onError: { error in + subscriber.send(.sendFailed(error.localizedDescription)) + }, + completion: { + subscriber.send(completion: .finished) + } + ) } return AnyCancellable {} } @@ -175,21 +200,18 @@ public struct ChatComponent: ReducerProtocol { return .none case .imagePicked(let data): - let chatId = state.id + guard case .contact(let recipientId) = state.id else { return .none } return Effect.run { subscriber in - switch chatId { - case .contact(let recipientId): - sendImage( - data, - to: recipientId, - onError: { error in - subscriber.send(.sendFailed(error.localizedDescription)) - }, - completion: { - subscriber.send(completion: .finished) - } - ) - } + sendImage( + data, + to: recipientId, + onError: { error in + subscriber.send(.sendFailed(error.localizedDescription)) + }, + completion: { + subscriber.send(completion: .finished) + } + ) return AnyCancellable {} } .subscribe(on: bgQueue) diff --git a/Examples/xx-messenger/Sources/ChatFeature/ChatView.swift b/Examples/xx-messenger/Sources/ChatFeature/ChatView.swift index 1611815c087786954fd730fe1768760801a7ea73..a7272385a3241f5d46890a88039edefeb7b41d80 100644 --- a/Examples/xx-messenger/Sources/ChatFeature/ChatView.swift +++ b/Examples/xx-messenger/Sources/ChatFeature/ChatView.swift @@ -16,6 +16,7 @@ public struct ChatView: View { var failure: String? var sendFailure: String? var text: String + var disableImagePicker: Bool init(state: ChatComponent.State) { myContactId = state.myContactId @@ -23,6 +24,12 @@ public struct ChatView: View { failure = state.failure sendFailure = state.sendFailure text = state.text + switch state.id { + case .contact(_): + disableImagePicker = false + case .group(_): + disableImagePicker = true + } } } @@ -109,6 +116,7 @@ public struct ChatView: View { } } } + .disabled(viewStore.disableImagePicker) } } .padding() @@ -139,6 +147,13 @@ public struct ChatView: View { var body: some View { VStack { + if let sender = message.senderName { + Text(sender) + .foregroundColor(.secondary) + .font(.footnote) + .frame(maxWidth: .infinity, alignment: alignment) + } + Text("\(message.date.formatted()), \(statusText)") .foregroundColor(.secondary) .font(.footnote) @@ -208,6 +223,7 @@ public struct ChatView_Previews: PreviewProvider { id: 1, date: Date(), senderId: "contact-id".data(using: .utf8)!, + senderName: "Contact", text: "Hello!", status: .received ), @@ -215,6 +231,7 @@ public struct ChatView_Previews: PreviewProvider { id: 2, date: Date(), senderId: "my-contact-id".data(using: .utf8)!, + senderName: "Me", text: "Hi!", status: .sent ), @@ -222,6 +239,7 @@ public struct ChatView_Previews: PreviewProvider { id: 3, date: Date(), senderId: "contact-id".data(using: .utf8)!, + senderName: "Contact", text: "", status: .received, fileTransfer: .init( @@ -237,6 +255,7 @@ public struct ChatView_Previews: PreviewProvider { id: 4, date: Date(), senderId: "my-contact-id".data(using: .utf8)!, + senderName: "Me", text: "", status: .sent, fileTransfer: .init( diff --git a/Examples/xx-messenger/Sources/GroupFeature/GroupComponent.swift b/Examples/xx-messenger/Sources/GroupFeature/GroupComponent.swift new file mode 100644 index 0000000000000000000000000000000000000000..359ee8991596e56cac72c39626955366588033b0 --- /dev/null +++ b/Examples/xx-messenger/Sources/GroupFeature/GroupComponent.swift @@ -0,0 +1,119 @@ +import AppCore +import ChatFeature +import ComposableArchitecture +import ComposablePresentation +import Foundation +import XXMessengerClient +import XXModels + +public struct GroupComponent: ReducerProtocol { + public struct State: Equatable { + public init( + groupId: XXModels.Group.ID, + groupInfo: XXModels.GroupInfo? = nil, + isJoining: Bool = false, + joinFailure: String? = nil, + chat: ChatComponent.State? = nil + ) { + self.groupId = groupId + self.groupInfo = groupInfo + self.isJoining = isJoining + self.joinFailure = joinFailure + self.chat = chat + } + + public var groupId: XXModels.Group.ID + public var groupInfo: XXModels.GroupInfo? + public var isJoining: Bool + public var joinFailure: String? + public var chat: ChatComponent.State? + } + + public enum Action: Equatable { + case start + case didFetchGroupInfo(XXModels.GroupInfo?) + case joinButtonTapped + case didJoin + case didFailToJoin(String) + case chatButtonTapped + case didDismissChat + case chat(ChatComponent.Action) + } + + public init() {} + + @Dependency(\.app.messenger) var messenger: Messenger + @Dependency(\.app.dbManager.getDB) var db: DBManagerGetDB + @Dependency(\.app.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue> + @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue> + + public var body: some ReducerProtocol<State, Action> { + Reduce { state, action in + switch action { + case .start: + return Effect + .catching { try db() } + .flatMap { [state] in + let query = GroupInfo.Query(groupId: state.groupId) + return $0.fetchGroupInfosPublisher(query).map(\.first) + } + .assertNoFailure() + .map(Action.didFetchGroupInfo) + .subscribe(on: bgQueue) + .receive(on: mainQueue) + .eraseToEffect() + + case .didFetchGroupInfo(let groupInfo): + state.groupInfo = groupInfo + return .none + + case .joinButtonTapped: + guard let info = state.groupInfo else { return .none } + state.isJoining = true + state.joinFailure = nil + return Effect.result { + do { + let groupChat = try messenger.groupChat.tryGet() + try groupChat.joinGroup(serializedGroupData: info.group.serialized) + var group = info.group + group.authStatus = .participating + try db().saveGroup(group) + return .success(.didJoin) + } catch { + return .success(.didFailToJoin(error.localizedDescription)) + } + } + .subscribe(on: bgQueue) + .receive(on: mainQueue) + .eraseToEffect() + + case .didJoin: + state.isJoining = false + state.joinFailure = nil + return .none + + case .didFailToJoin(let failure): + state.isJoining = false + state.joinFailure = failure + return .none + + case .chatButtonTapped: + state.chat = ChatComponent.State(id: .group(state.groupId)) + return .none + + case .didDismissChat: + state.chat = nil + return .none + + case .chat(_): + return .none + } + } + .presenting( + state: .keyPath(\.chat), + id: .notNil(), + action: /Action.chat, + presented: { ChatComponent() } + ) + } +} diff --git a/Examples/xx-messenger/Sources/GroupFeature/GroupView.swift b/Examples/xx-messenger/Sources/GroupFeature/GroupView.swift new file mode 100644 index 0000000000000000000000000000000000000000..a7520d53802b8fc8a31a306a4305804b94c1cb61 --- /dev/null +++ b/Examples/xx-messenger/Sources/GroupFeature/GroupView.swift @@ -0,0 +1,137 @@ +import AppCore +import ChatFeature +import ComposableArchitecture +import ComposablePresentation +import SwiftUI +import XXModels + +public struct GroupView: View { + public typealias Component = GroupComponent + typealias ViewStore = ComposableArchitecture.ViewStore<ViewState, Component.Action> + + public init(store: StoreOf<Component>) { + self.store = store + } + + let store: StoreOf<Component> + + struct ViewState: Equatable { + init(state: Component.State) { + info = state.groupInfo + isJoining = state.isJoining + joinFailure = state.joinFailure + } + + var info: XXModels.GroupInfo? + var isJoining: Bool + var joinFailure: String? + } + + public var body: some View { + WithViewStore(store, observe: ViewState.init) { viewStore in + Form { + if let info = viewStore.info { + Section("Name") { + Text(info.group.name) + } + + Section("Leader") { + Label(info.leader.username ?? "", systemImage: "person.badge.shield.checkmark") + } + + Section("Members") { + ForEach(info.members.filter { $0 != info.leader }) { contact in + Label(contact.username ?? "", systemImage: "person") + } + } + + Section("Status") { + GroupAuthStatusView(info.group.authStatus) + + if case .pending = info.group.authStatus { + Button { + viewStore.send(.joinButtonTapped) + } label: { + HStack { + Text("Join") + Spacer() + if viewStore.isJoining { + ProgressView() + } else { + Image(systemName: "play.fill") + } + } + } + .disabled(viewStore.isJoining) + } + + if let failure = viewStore.joinFailure { + Text(failure) + } + } + } + + Section { + Button { + viewStore.send(.chatButtonTapped) + } label: { + HStack { + Text("Chat") + Spacer() + Image(systemName: "chevron.forward") + } + } + } + } + .navigationTitle("Group") + .background(NavigationLinkWithStore( + store.scope( + state: \.chat, + action: Component.Action.chat + ), + onDeactivate: { viewStore.send(.didDismissChat) }, + destination: ChatView.init(store:) + )) + .task { viewStore.send(.start) } + } + } +} + +#if DEBUG +public struct GroupView_Previews: PreviewProvider { + public static var previews: some View { + NavigationView { + GroupView(store: Store( + initialState: GroupComponent.State( + groupId: "group-id".data(using: .utf8)!, + groupInfo: .init( + group: .init( + id: "group-id".data(using: .utf8)!, + name: "Preview group", + leaderId: "group-leader-id".data(using: .utf8)!, + createdAt: Date(timeIntervalSince1970: TimeInterval(86_400)), + authStatus: .participating, + serialized: "group-serialized".data(using: .utf8)! + ), + leader: .init( + id: "group-leader-id".data(using: .utf8)!, + username: "Group leader" + ), + members: [ + .init( + id: "member-1-id".data(using: .utf8)!, + username: "Member 1" + ), + .init( + id: "member-2-id".data(using: .utf8)!, + username: "Member 2" + ), + ] + ) + ), + reducer: EmptyReducer() + )) + } + } +} +#endif diff --git a/Examples/xx-messenger/Sources/GroupsFeature/GroupsComponent.swift b/Examples/xx-messenger/Sources/GroupsFeature/GroupsComponent.swift new file mode 100644 index 0000000000000000000000000000000000000000..91e6dc55a8aeacfe8f811fffeabe092c391b6fd0 --- /dev/null +++ b/Examples/xx-messenger/Sources/GroupsFeature/GroupsComponent.swift @@ -0,0 +1,97 @@ +import AppCore +import ComposableArchitecture +import ComposablePresentation +import Foundation +import GroupFeature +import NewGroupFeature +import XXModels + +public struct GroupsComponent: ReducerProtocol { + public struct State: Equatable { + public init( + groups: IdentifiedArrayOf<Group> = [], + newGroup: NewGroupComponent.State? = nil, + group: GroupComponent.State? = nil + ) { + self.groups = groups + self.newGroup = newGroup + self.group = group + } + + public var groups: IdentifiedArrayOf<XXModels.Group> = [] + public var newGroup: NewGroupComponent.State? + public var group: GroupComponent.State? + } + + public enum Action: Equatable { + case start + case didFetchGroups([XXModels.Group]) + case didSelectGroup(XXModels.Group) + case didDismissGroup + case newGroupButtonTapped + case newGroupDismissed + case newGroup(NewGroupComponent.Action) + case group(GroupComponent.Action) + } + + public init() {} + + @Dependency(\.app.dbManager.getDB) var db: DBManagerGetDB + @Dependency(\.app.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue> + @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue> + + public var body: some ReducerProtocol<State, Action> { + Reduce { state, action in + switch action { + case .start: + return Effect + .catching { try db() } + .flatMap { $0.fetchGroupsPublisher(.init()) } + .assertNoFailure() + .map(Action.didFetchGroups) + .subscribe(on: bgQueue) + .receive(on: mainQueue) + .eraseToEffect() + + case .didFetchGroups(let groups): + state.groups = IdentifiedArray(uniqueElements: groups) + return .none + + case .didSelectGroup(let group): + state.group = GroupComponent.State(groupId: group.id) + return .none + + case .didDismissGroup: + state.group = nil + return .none + + case .newGroupButtonTapped: + state.newGroup = NewGroupComponent.State() + return .none + + case .newGroupDismissed: + state.newGroup = nil + return .none + + case .newGroup(.didFinish): + state.newGroup = nil + return .none + + case .newGroup(_), .group(_): + return .none + } + } + .presenting( + state: .keyPath(\.newGroup), + id: .notNil(), + action: /Action.newGroup, + presented: { NewGroupComponent() } + ) + .presenting( + state: .keyPath(\.group), + id: .notNil(), + action: /Action.group, + presented: { GroupComponent() } + ) + } +} diff --git a/Examples/xx-messenger/Sources/GroupsFeature/GroupsView.swift b/Examples/xx-messenger/Sources/GroupsFeature/GroupsView.swift new file mode 100644 index 0000000000000000000000000000000000000000..1391f6d91d2b23b62c0da579c410cd9213e6af54 --- /dev/null +++ b/Examples/xx-messenger/Sources/GroupsFeature/GroupsView.swift @@ -0,0 +1,100 @@ +import AppCore +import ComposableArchitecture +import ComposablePresentation +import GroupFeature +import NewGroupFeature +import SwiftUI +import XXModels + +public struct GroupsView: View { + public typealias Component = GroupsComponent + typealias ViewStore = ComposableArchitecture.ViewStore<ViewState, Component.Action> + + public init(store: StoreOf<Component>) { + self.store = store + } + + let store: StoreOf<Component> + + struct ViewState: Equatable { + init(state: Component.State) { + groups = state.groups + } + + var groups: IdentifiedArrayOf<XXModels.Group> + } + + public var body: some View { + WithViewStore(store, observe: ViewState.init) { viewStore in + Form { + newGroupButton(viewStore) + + ForEach(viewStore.groups) { group in + groupView(group, viewStore) + } + } + .navigationTitle("Groups") + .background(NavigationLinkWithStore( + store.scope( + state: \.newGroup, + action: Component.Action.newGroup + ), + onDeactivate: { viewStore.send(.newGroupDismissed) }, + destination: NewGroupView.init + )) + .background(NavigationLinkWithStore( + store.scope( + state: \.group, + action: Component.Action.group + ), + onDeactivate: { viewStore.send(.didDismissGroup) }, + destination: GroupView.init + )) + .task { viewStore.send(.start) } + } + } + + func newGroupButton(_ viewStore: ViewStore) -> some View { + Section { + Button { + viewStore.send(.newGroupButtonTapped) + } label: { + HStack { + Text("New Group") + Spacer() + Image(systemName: "chevron.forward") + } + } + } + } + + func groupView(_ group: XXModels.Group, _ viewStore: ViewStore) -> some View { + Section { + Button { + viewStore.send(.didSelectGroup(group)) + } label: { + HStack { + Label(group.name, systemImage: "person.3") + .font(.callout) + .tint(Color.primary) + Spacer() + Image(systemName: "chevron.forward") + } + } + GroupAuthStatusView(group.authStatus) + } + } +} + +#if DEBUG +public struct GroupsView_Previews: PreviewProvider { + public static var previews: some View { + NavigationView { + GroupsView(store: Store( + initialState: GroupsComponent.State(), + reducer: EmptyReducer() + )) + } + } +} +#endif diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeComponent.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeComponent.swift index 18212bf0ca916cffff86f5a4ee6cb09f4d220359..91b0acffa31647524cc91e1d9f2eec028768e064 100644 --- a/Examples/xx-messenger/Sources/HomeFeature/HomeComponent.swift +++ b/Examples/xx-messenger/Sources/HomeFeature/HomeComponent.swift @@ -5,6 +5,7 @@ import ComposableArchitecture import ComposablePresentation import ContactsFeature import Foundation +import GroupsFeature import RegisterFeature import UserSearchFeature import XCTestDynamicOverlay @@ -23,7 +24,8 @@ public struct HomeComponent: ReducerProtocol { register: RegisterComponent.State? = nil, contacts: ContactsComponent.State? = nil, userSearch: UserSearchComponent.State? = nil, - backup: BackupComponent.State? = nil + backup: BackupComponent.State? = nil, + groups: GroupsComponent.State? = nil ) { self.failure = failure self.isNetworkHealthy = isNetworkHealthy @@ -33,6 +35,7 @@ public struct HomeComponent: ReducerProtocol { self.contacts = contacts self.userSearch = userSearch self.backup = backup + self.groups = groups } public var failure: String? @@ -44,6 +47,7 @@ public struct HomeComponent: ReducerProtocol { public var contacts: ContactsComponent.State? public var userSearch: UserSearchComponent.State? public var backup: BackupComponent.State? + public var groups: GroupsComponent.State? } public enum Action: Equatable { @@ -79,10 +83,13 @@ public struct HomeComponent: ReducerProtocol { case didDismissContacts case backupButtonTapped case didDismissBackup + case groupsButtonTapped + case didDismissGroups case register(RegisterComponent.Action) case contacts(ContactsComponent.Action) case userSearch(UserSearchComponent.Action) case backup(BackupComponent.Action) + case groups(GroupsComponent.Action) } public init() {} @@ -119,6 +126,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)) @@ -260,7 +271,15 @@ public struct HomeComponent: ReducerProtocol { state.backup = nil return .none - case .register(_), .contacts(_), .userSearch(_), .backup(_): + case .groupsButtonTapped: + state.groups = GroupsComponent.State() + return .none + + case .didDismissGroups: + state.groups = nil + return .none + + case .register(_), .contacts(_), .userSearch(_), .backup(_), .groups(_): return .none } } @@ -288,5 +307,11 @@ public struct HomeComponent: ReducerProtocol { action: /Action.backup, presented: { BackupComponent() } ) + .presenting( + state: .keyPath(\.groups), + id: .notNil(), + action: /Action.groups, + presented: { GroupsComponent() } + ) } } diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift index f2fb823ce3772601b24e65bf66063bad2304846f..24465f6e6f3b39344d070660b21fdbaef0310a76 100644 --- a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift +++ b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift @@ -3,6 +3,7 @@ import BackupFeature import ComposableArchitecture import ComposablePresentation import ContactsFeature +import GroupsFeature import RegisterFeature import SwiftUI import UserSearchFeature @@ -109,6 +110,16 @@ public struct HomeView: View { Image(systemName: "chevron.forward") } } + + Button { + viewStore.send(.groupsButtonTapped) + } label: { + HStack { + Text("Groups") + Spacer() + Image(systemName: "chevron.forward") + } + } } header: { Text("Contacts") } @@ -181,6 +192,16 @@ public struct HomeView: View { }, destination: BackupView.init(store:) )) + .background(NavigationLinkWithStore( + store.scope( + state: \.groups, + action: HomeComponent.Action.groups + ), + onDeactivate: { + viewStore.send(.didDismissGroups) + }, + destination: GroupsView.init(store:) + )) } .navigationViewStyle(.stack) .task { viewStore.send(.messenger(.start)) } diff --git a/Examples/xx-messenger/Sources/NewGroupFeature/NewGroupComponent.swift b/Examples/xx-messenger/Sources/NewGroupFeature/NewGroupComponent.swift new file mode 100644 index 0000000000000000000000000000000000000000..fd5e36c6b25aa5760ecec4214b3630454c6de1c4 --- /dev/null +++ b/Examples/xx-messenger/Sources/NewGroupFeature/NewGroupComponent.swift @@ -0,0 +1,149 @@ +import AppCore +import ComposableArchitecture +import Foundation +import XXMessengerClient +import XXModels + +public struct NewGroupComponent: ReducerProtocol { + public struct State: Equatable { + public enum Field: String, Hashable { + case name + case message + } + + public init( + contacts: IdentifiedArrayOf<XXModels.Contact> = [], + members: IdentifiedArrayOf<XXModels.Contact> = [], + name: String = "", + message: String = "", + focusedField: Field? = nil, + isCreating: Bool = false, + failure: String? = nil + ) { + self.contacts = contacts + self.members = members + self.name = name + self.message = message + self.focusedField = focusedField + self.isCreating = isCreating + self.failure = failure + } + + public var contacts: IdentifiedArrayOf<XXModels.Contact> + public var members: IdentifiedArrayOf<XXModels.Contact> + @BindableState public var name: String + @BindableState public var message: String + @BindableState public var focusedField: Field? + public var isCreating: Bool + public var failure: String? + } + + public enum Action: Equatable, BindableAction { + case start + case didFetchContacts([XXModels.Contact]) + case didSelectContact(XXModels.Contact) + case createButtonTapped + case didFinish + case didFail(String) + case binding(BindingAction<State>) + } + + public init() {} + + @Dependency(\.app.messenger) var messenger: Messenger + @Dependency(\.app.dbManager.getDB) var db: DBManagerGetDB + @Dependency(\.app.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue> + @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue> + @Dependency(\.date) var date: DateGenerator + + public var body: some ReducerProtocol<State, Action> { + BindingReducer() + Reduce { state, action in + switch action { + case .start: + let myId = try? messenger.e2e.tryGet().getContact().getId() + return Effect + .catching { try db() } + .flatMap { $0.fetchContactsPublisher(.init()) } + .assertNoFailure() + .map { $0.filter { $0.id != myId } } + .map(Action.didFetchContacts) + .subscribe(on: bgQueue) + .receive(on: mainQueue) + .eraseToEffect() + + case .didFetchContacts(let contacts): + state.contacts = IdentifiedArray(uniqueElements: contacts) + return .none + + case .didSelectContact(let contact): + if state.members.contains(contact) { + state.members.remove(contact) + } else { + state.members.append(contact) + } + return .none + + case .createButtonTapped: + state.focusedField = nil + state.isCreating = true + state.failure = nil + return Effect.result { [state] in + do { + let groupChat = try messenger.groupChat.tryGet() + let report = try groupChat.makeGroup( + membership: state.members.map(\.id), + message: state.message.data(using: .utf8)!, + name: state.name.data(using: .utf8)! + ) + let myContactId = try messenger.e2e.tryGet().getContact().getId() + let group = XXModels.Group( + id: report.id, + name: state.name, + leaderId: myContactId, + createdAt: date(), + authStatus: .participating, + serialized: try report.encode() + ) + try db().saveGroup(group) + if state.message.isEmpty == false { + try db().saveMessage(.init( + senderId: myContactId, + recipientId: nil, + groupId: group.id, + date: group.createdAt, + status: .sent, + isUnread: false, + text: state.message + )) + } + try state.members.map { + GroupMember(groupId: group.id, contactId: $0.id) + }.forEach { + try db().saveGroupMember($0) + } + return .success(.didFinish) + } catch { + return .success(.didFail(error.localizedDescription)) + } + } + .subscribe(on: bgQueue) + .receive(on: mainQueue) + .eraseToEffect() + + case .didFinish: + state.isCreating = false + state.failure = nil + return .none + + case .didFail(let failure): + state.isCreating = false + state.failure = failure + return .none + + case .binding(_): + return .none + } + } + } +} diff --git a/Examples/xx-messenger/Sources/NewGroupFeature/NewGroupView.swift b/Examples/xx-messenger/Sources/NewGroupFeature/NewGroupView.swift new file mode 100644 index 0000000000000000000000000000000000000000..2536dc120bfd4ed462fead26661e06c40f5c790f --- /dev/null +++ b/Examples/xx-messenger/Sources/NewGroupFeature/NewGroupView.swift @@ -0,0 +1,127 @@ +import AppCore +import ComposableArchitecture +import SwiftUI +import XXModels + +public struct NewGroupView: View { + public typealias Component = NewGroupComponent + typealias ViewStore = ComposableArchitecture.ViewStore<ViewState, Component.Action> + + public init(store: StoreOf<Component>) { + self.store = store + } + + let store: StoreOf<Component> + @FocusState var focusedField: Component.State.Field? + + struct ViewState: Equatable { + init(state: Component.State) { + contacts = state.contacts + members = state.members + name = state.name + message = state.message + focusedField = state.focusedField + isCreating = state.isCreating + failure = state.failure + } + + var contacts: IdentifiedArrayOf<XXModels.Contact> + var members: IdentifiedArrayOf<XXModels.Contact> + var name: String + var message: String + var focusedField: Component.State.Field? + var isCreating: Bool + var failure: String? + } + + public var body: some View { + WithViewStore(store, observe: ViewState.init) { viewStore in + Form { + Section { + membersView(viewStore) + nameView(viewStore) + messageView(viewStore) + } + Section { + createButton(viewStore) + if let failure = viewStore.failure { + Text(failure) + } + } + } + .navigationTitle("New Group") + .task { viewStore.send(.start) } + .onChange(of: viewStore.focusedField) { focusedField = $0 } + .onChange(of: focusedField) { viewStore.send(.set(\.$focusedField, $0)) } + } + } + + func membersView(_ viewStore: ViewStore) -> some View { + NavigationLink("Members (\(viewStore.members.count))") { + Form { + ForEach(viewStore.contacts) { contact in + Button { + viewStore.send(.didSelectContact(contact)) + } label: { + HStack { + Text(contact.username ?? "") + Spacer() + if viewStore.members.contains(contact) { + Image(systemName: "checkmark") + } + } + } + } + } + } + .disabled(viewStore.isCreating) + } + + func nameView(_ viewStore: ViewStore) -> some View { + TextField("Group name", text: viewStore.binding( + get: \.name, + send: { .set(\.$name, $0) } + )) + .focused($focusedField, equals: .name) + .disabled(viewStore.isCreating) + } + + func messageView(_ viewStore: ViewStore) -> some View { + TextField("Initial message", text: viewStore.binding( + get: \.message, + send: { .set(\.$message, $0) } + )) + .focused($focusedField, equals: .message) + .disabled(viewStore.isCreating) + } + + func createButton(_ viewStore: ViewStore) -> some View { + Button { + viewStore.send(.createButtonTapped) + } label: { + HStack { + Text("Create group") + Spacer() + if viewStore.isCreating { + ProgressView() + } else { + Image(systemName: "play.fill") + } + } + } + .disabled(viewStore.isCreating) + } +} + +#if DEBUG +public struct NewGroupView_Previews: PreviewProvider { + public static var previews: some View { + NavigationView { + NewGroupView(store: Store( + initialState: NewGroupComponent.State(), + reducer: EmptyReducer() + )) + } + } +} +#endif diff --git a/Examples/xx-messenger/Tests/AppCoreTests/SendMessage/SendGroupMessageTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/SendMessage/SendGroupMessageTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..1df2eaa3717344509c590a462036218c0ff416b0 --- /dev/null +++ b/Examples/xx-messenger/Tests/AppCoreTests/SendMessage/SendGroupMessageTests.swift @@ -0,0 +1,279 @@ +import CustomDump +import XCTest +import XCTestDynamicOverlay +import XXClient +import XXMessengerClient +import XXModels +@testable import AppCore + +final class SendGroupMessageTests: XCTestCase { + enum Action: Equatable { + case didReceiveError(String) + case didComplete + case didSaveMessage(XXModels.Message) + case didSend(groupId: Data, message: Data, tag: String?) + case didWaitForRoundResults(roundList: Data, timeoutMS: Int) + case didUpdateMessage( + query: XXModels.Message.Query, + assignments: XXModels.Message.Assignments + ) + } + + var actions: [Action]! + + override func setUp() { + actions = [] + } + + override func tearDown() { + actions = nil + } + + func testSend() { + let text = "Hello!" + let groupId = "group-id".data(using: .utf8)! + let myContactId = "my-contact-id".data(using: .utf8)! + let messageId: Int64 = 321 + let sendReport = GroupSendReport( + rounds: [], + roundURL: "round-url", + timestamp: 1234, + messageId: "message-id".data(using: .utf8)! + ) + + var messageDeliveryCallback: MessageDeliveryCallback? + + var messenger: Messenger = .unimplemented + messenger.groupChat.get = { + var groupChat: GroupChat = .unimplemented + groupChat.send.run = { groupId, message, tag in + self.actions.append(.didSend(groupId: groupId, message: message, tag: tag)) + return sendReport + } + return groupChat + } + messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact: XXClient.Contact = .unimplemented(Data()) + contact.getIdFromContact.run = { _ in myContactId } + return contact + } + return e2e + } + messenger.cMix.get = { + var cMix: CMix = .unimplemented + cMix.waitForRoundResult.run = { roundList, timeoutMS, callback in + self.actions.append(.didWaitForRoundResults(roundList: roundList, timeoutMS: timeoutMS)) + messageDeliveryCallback = callback + } + return cMix + } + var db: DBManagerGetDB = .unimplemented + db.run = { + var db: Database = .unimplemented + db.saveMessage.run = { message in + self.actions.append(.didSaveMessage(message)) + var message = message + message.id = messageId + return message + } + db.bulkUpdateMessages.run = { query, assignments in + self.actions.append(.didUpdateMessage(query: query, assignments: assignments)) + return 1 + } + return db + } + let now = Date() + let send: SendGroupMessage = .live( + messenger: messenger, + db: db, + now: { now } + ) + + send( + text: text, + to: groupId, + onError: { error in + self.actions.append(.didReceiveError(error.localizedDescription)) + }, + completion: { + self.actions.append(.didComplete) + } + ) + + XCTAssertNoDifference(actions, [ + .didSaveMessage(.init( + senderId: myContactId, + recipientId: nil, + groupId: groupId, + date: now, + status: .sending, + isUnread: false, + text: text + )), + .didSend( + groupId: groupId, + message: try! MessagePayload(text: text).encode(), + tag: nil + ), + .didSaveMessage(.init( + id: messageId, + networkId: sendReport.messageId, + senderId: myContactId, + recipientId: nil, + groupId: groupId, + date: now, + status: .sending, + isUnread: false, + text: text, + roundURL: sendReport.roundURL + )), + .didWaitForRoundResults( + roundList: try! sendReport.encode(), + timeoutMS: 30_000 + ), + ]) + + actions = [] + messageDeliveryCallback?.handle(.delivered(roundResults: [])) + + XCTAssertNoDifference(actions, [ + .didUpdateMessage( + query: .init(id: [messageId]), + assignments: .init(status: .sent) + ), + .didComplete, + ]) + + actions = [] + messageDeliveryCallback?.handle(.notDelivered(timedOut: true)) + + XCTAssertNoDifference(actions, [ + .didUpdateMessage( + query: .init(id: [messageId]), + assignments: .init(status: .sendingTimedOut) + ), + .didComplete, + ]) + + actions = [] + messageDeliveryCallback?.handle(.notDelivered(timedOut: false)) + + XCTAssertNoDifference(actions, [ + .didUpdateMessage( + query: .init(id: [messageId]), + assignments: .init(status: .sendingFailed) + ), + .didComplete, + ]) + } + + func testSendDatabaseFailure() { + struct Failure: Error, Equatable {} + let failure = Failure() + + var messenger: Messenger = .unimplemented + messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact: XXClient.Contact = .unimplemented(Data()) + contact.getIdFromContact.run = { _ in Data() } + return contact + } + return e2e + } + messenger.groupChat.get = { .unimplemented } + var db: DBManagerGetDB = .unimplemented + db.run = { throw failure } + let send: SendGroupMessage = .live( + messenger: messenger, + db: db, + now: XCTestDynamicOverlay.unimplemented("now", placeholder: Date()) + ) + + send( + text: "Hello", + to: "group-id".data(using: .utf8)!, + onError: { error in + self.actions.append(.didReceiveError(error.localizedDescription)) + }, + completion: { + self.actions.append(.didComplete) + } + ) + + XCTAssertNoDifference(actions, [ + .didReceiveError(failure.localizedDescription), + .didComplete + ]) + } + + func testBulkUpdateOnDeliveryFailure() { + struct Failure: Error, Equatable {} + let failure = Failure() + + var messageDeliveryCallback: MessageDeliveryCallback? + + var messenger: Messenger = .unimplemented + messenger.groupChat.get = { + var groupChat: GroupChat = .unimplemented + groupChat.send.run = { _, _, _ in + GroupSendReport( + rounds: [], + roundURL: "", + timestamp: 0, + messageId: Data() + ) + } + return groupChat + } + messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact: XXClient.Contact = .unimplemented(Data()) + contact.getIdFromContact.run = { _ in Data() } + return contact + } + return e2e + } + messenger.cMix.get = { + var cMix: CMix = .unimplemented + cMix.waitForRoundResult.run = { _, _, callback in + messageDeliveryCallback = callback + } + return cMix + } + var db: DBManagerGetDB = .unimplemented + db.run = { + var db: Database = .unimplemented + db.saveMessage.run = { message in message } + db.bulkUpdateMessages.run = { _, _ in throw failure } + return db + } + let now = Date() + let send: SendGroupMessage = .live( + messenger: messenger, + db: db, + now: { now } + ) + + send( + text: "Hello", + to: Data(), + onError: { error in + self.actions.append(.didReceiveError(error.localizedDescription)) + }, + completion: { + self.actions.append(.didComplete) + } + ) + + messageDeliveryCallback?.handle(.delivered(roundResults: [])) + + XCTAssertNoDifference(actions, [ + .didReceiveError(failure.localizedDescription), + .didComplete, + ]) + } +} 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/ChatFeatureTests/ChatComponentTests.swift b/Examples/xx-messenger/Tests/ChatFeatureTests/ChatComponentTests.swift index c8f5952864c6f77f04ec42b53b5875a317ee37a4..ba7ed23792f71c169b42daffc10fec988caef3a5 100644 --- a/Examples/xx-messenger/Tests/ChatFeatureTests/ChatComponentTests.swift +++ b/Examples/xx-messenger/Tests/ChatFeatureTests/ChatComponentTests.swift @@ -9,7 +9,7 @@ import XXModels @testable import ChatFeature final class ChatComponentTests: XCTestCase { - func testStart() { + func testStartDirectChat() { let contactId = "contact-id".data(using: .utf8)! let myContactId = "my-contact-id".data(using: .utf8)! @@ -22,6 +22,8 @@ final class ChatComponentTests: XCTestCase { let messagesPublisher = PassthroughSubject<[XXModels.Message], Error>() var didFetchFileTransfersWithQuery: [XXModels.FileTransfer.Query] = [] let fileTransfersPublisher = PassthroughSubject<[XXModels.FileTransfer], Error>() + var didFetchContactsWithQuery: [XXModels.Contact.Query] = [] + let contactsPublisher = PassthroughSubject<[XXModels.Contact], Error>() store.dependencies.app.mainQueue = .immediate store.dependencies.app.bgQueue = .immediate @@ -40,6 +42,10 @@ final class ChatComponentTests: XCTestCase { didFetchMessagesWithQuery.append(query) return messagesPublisher.eraseToAnyPublisher() } + db.fetchContactsPublisher.run = { query in + didFetchContactsWithQuery.append(query) + return contactsPublisher.eraseToAnyPublisher() + } db.fetchFileTransfersPublisher.run = { query in didFetchFileTransfersWithQuery.append(query) return fileTransfersPublisher.eraseToAnyPublisher() @@ -58,6 +64,9 @@ final class ChatComponentTests: XCTestCase { .init(contactId: contactId, isIncoming: true), .init(contactId: myContactId, isIncoming: false), ]) + XCTAssertNoDifference(didFetchContactsWithQuery, [ + .init(), + ]) let receivedFileTransfer = FileTransfer( id: "file-transfer-1-id".data(using: .utf8)!, @@ -111,12 +120,17 @@ final class ChatComponentTests: XCTestCase { receivedFileTransfer, sentFileTransfer, ]) + contactsPublisher.send([ + .init(id: myContactId, username: "My username"), + .init(id: contactId, username: "Contact username"), + ]) let expectedMessages = IdentifiedArrayOf<ChatComponent.State.Message>(uniqueElements: [ .init( id: 1, date: Date(timeIntervalSince1970: 1), senderId: contactId, + senderName: "Contact username", text: "Message 1", status: .received, fileTransfer: receivedFileTransfer @@ -125,6 +139,7 @@ final class ChatComponentTests: XCTestCase { id: 2, date: Date(timeIntervalSince1970: 2), senderId: myContactId, + senderName: "My username", text: "Message 2", status: .sent, fileTransfer: sentFileTransfer @@ -137,6 +152,131 @@ final class ChatComponentTests: XCTestCase { messagesPublisher.send(completion: .finished) fileTransfersPublisher.send(completion: .finished) + contactsPublisher.send(completion: .finished) + } + + func testStartGroupChat() { + let groupId = "group-id".data(using: .utf8)! + let myContactId = "my-contact-id".data(using: .utf8)! + let firstMemberId = "member-1-id".data(using: .utf8)! + let secondMemberId = "member-2-id".data(using: .utf8)! + + let store = TestStore( + initialState: ChatComponent.State(id: .group(groupId)), + reducer: ChatComponent() + ) + + var didFetchMessagesWithQuery: [XXModels.Message.Query] = [] + let messagesPublisher = PassthroughSubject<[XXModels.Message], Error>() + var didFetchContactsWithQuery: [XXModels.Contact.Query] = [] + let contactsPublisher = PassthroughSubject<[XXModels.Contact], Error>() + + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact: XXClient.Contact = .unimplemented(Data()) + contact.getIdFromContact.run = { _ in myContactId } + return contact + } + return e2e + } + store.dependencies.app.dbManager.getDB.run = { + var db: Database = .unimplemented + db.fetchMessagesPublisher.run = { query in + didFetchMessagesWithQuery.append(query) + return messagesPublisher.eraseToAnyPublisher() + } + db.fetchContactsPublisher.run = { query in + didFetchContactsWithQuery.append(query) + return contactsPublisher.eraseToAnyPublisher() + } + return db + } + + store.send(.start) { + $0.myContactId = myContactId + } + + XCTAssertNoDifference(didFetchMessagesWithQuery, [ + .init(chat: .group(groupId)) + ]) + XCTAssertNoDifference(didFetchContactsWithQuery, [ + .init(), + ]) + + messagesPublisher.send([ + .init( + id: 0, + senderId: myContactId, + recipientId: nil, + groupId: groupId, + date: Date(timeIntervalSince1970: 0), + status: .sent, + isUnread: false, + text: "Message 0" + ), + .init( + id: 1, + senderId: firstMemberId, + recipientId: nil, + groupId: groupId, + date: Date(timeIntervalSince1970: 1), + status: .received, + isUnread: false, + text: "Message 1" + ), + .init( + id: 2, + senderId: secondMemberId, + recipientId: nil, + groupId: groupId, + date: Date(timeIntervalSince1970: 2), + status: .received, + isUnread: false, + text: "Message 2" + ), + ]) + contactsPublisher.send([ + .init(id: myContactId, username: "My username"), + .init(id: firstMemberId, username: "First username"), + .init(id: secondMemberId, username: "Second username"), + ]) + + let expectedMessages = IdentifiedArrayOf<ChatComponent.State.Message>(uniqueElements: [ + .init( + id: 0, + date: Date(timeIntervalSince1970: 0), + senderId: myContactId, + senderName: "My username", + text: "Message 0", + status: .sent + ), + .init( + id: 1, + date: Date(timeIntervalSince1970: 1), + senderId: firstMemberId, + senderName: "First username", + text: "Message 1", + status: .received + ), + .init( + id: 2, + date: Date(timeIntervalSince1970: 2), + senderId: secondMemberId, + senderName: "Second username", + text: "Message 2", + status: .received + ), + ]) + + store.receive(.didFetchMessages(expectedMessages)) { + $0.messages = expectedMessages + } + + messagesPublisher.send(completion: .finished) + contactsPublisher.send(completion: .finished) } func testStartFailure() { @@ -165,7 +305,7 @@ final class ChatComponentTests: XCTestCase { } } - func testSend() { + func testSendDirectMessage() { struct SendMessageParams: Equatable { var text: String var recipientId: Data @@ -200,7 +340,7 @@ final class ChatComponentTests: XCTestCase { sendMessageCompletion?() } - func testSendFailure() { + func testSendDirectMessageFailure() { var sendMessageOnError: SendMessage.OnError? var sendMessageCompletion: SendMessage.Completion? @@ -237,6 +377,80 @@ final class ChatComponentTests: XCTestCase { } } + func testSendGroupMessage() { + let groupId = "group-id".data(using: .utf8)! + let text = "Hello" + struct SendGroupMessageParams: Equatable { + var text: String + var groupId: Data + } + var didSendGroupMessageWithParams: [SendGroupMessageParams] = [] + var sendGroupMessageCompletion: SendGroupMessage.Completion? + + let store = TestStore( + initialState: ChatComponent.State(id: .group(groupId)), + reducer: ChatComponent() + ) + + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.sendGroupMessage.run = { text, groupId, _, completion in + didSendGroupMessageWithParams.append(.init(text: text, groupId: groupId)) + sendGroupMessageCompletion = completion + } + + store.send(.set(\.$text, text)) { + $0.text = text + } + + store.send(.sendTapped) { + $0.text = "" + } + + XCTAssertNoDifference(didSendGroupMessageWithParams, [ + .init(text: text, groupId: groupId) + ]) + + sendGroupMessageCompletion?() + } + + func testSendGroupMessageFailure() { + var sendGroupMessageOnError: SendGroupMessage.OnError? + var sendGroupMessageCompletion: SendGroupMessage.Completion? + + let store = TestStore( + initialState: ChatComponent.State( + id: .group("group-id".data(using: .utf8)!), + text: "Hello" + ), + reducer: ChatComponent() + ) + + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.sendGroupMessage.run = { _, _, onError, completion in + sendGroupMessageOnError = onError + sendGroupMessageCompletion = completion + } + + store.send(.sendTapped) { + $0.text = "" + } + + let error = NSError(domain: "test", code: 123) + sendGroupMessageOnError?(error) + + store.receive(.sendFailed(error.localizedDescription)) { + $0.sendFailure = error.localizedDescription + } + + sendGroupMessageCompletion?() + + store.send(.dismissSendFailureTapped) { + $0.sendFailure = nil + } + } + func testSendImage() { struct SendImageParams: Equatable { var image: Data diff --git a/Examples/xx-messenger/Tests/GroupFeatureTests/GroupComponentTests.swift b/Examples/xx-messenger/Tests/GroupFeatureTests/GroupComponentTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..b3791b1f107106d9935e6576e664f9ad40cf80f7 --- /dev/null +++ b/Examples/xx-messenger/Tests/GroupFeatureTests/GroupComponentTests.swift @@ -0,0 +1,193 @@ +import ChatFeature +import Combine +import ComposableArchitecture +import CustomDump +import XCTest +import XXClient +import XXMessengerClient +import XXModels +@testable import GroupFeature + +final class GroupComponentTests: XCTestCase { + enum Action: Equatable { + case didFetchGroupInfos(GroupInfo.Query) + case didJoinGroup(Data) + case didSaveGroup(XXModels.Group) + } + + var actions: [Action]! + + override func setUp() { + actions = [] + } + + override func tearDown() { + actions = nil + } + + func testStart() { + let groupId = "group-id".data(using: .utf8)! + let groupInfosSubject = PassthroughSubject<[GroupInfo], Error>() + + let store = TestStore( + initialState: GroupComponent.State( + groupId: groupId + ), + reducer: GroupComponent() + ) + + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.dbManager.getDB.run = { + var db: Database = .unimplemented + db.fetchGroupInfosPublisher.run = { query in + self.actions.append(.didFetchGroupInfos(query)) + return groupInfosSubject.eraseToAnyPublisher() + } + return db + } + + store.send(.start) + + XCTAssertNoDifference(actions, [ + .didFetchGroupInfos(.init(groupId: groupId)), + ]) + + let groupInfo = GroupInfo.stub() + groupInfosSubject.send([groupInfo]) + + store.receive(.didFetchGroupInfo(groupInfo)) { + $0.groupInfo = groupInfo + } + + groupInfosSubject.send(completion: .finished) + } + + func testJoinGroup() { + var groupInfo = GroupInfo.stub() + groupInfo.group.authStatus = .pending + + let store = TestStore( + initialState: GroupComponent.State( + groupId: groupInfo.group.id, + groupInfo: groupInfo + ), + reducer: GroupComponent() + ) + + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.groupChat.get = { + var groupChat: GroupChat = .unimplemented + groupChat.joinGroup.run = { serializedGroupData in + self.actions.append(.didJoinGroup(serializedGroupData)) + } + return groupChat + } + store.dependencies.app.dbManager.getDB.run = { + var db: Database = .unimplemented + db.saveGroup.run = { group in + self.actions.append(.didSaveGroup(group)) + return group + } + return db + } + + store.send(.joinButtonTapped) { + $0.isJoining = true + } + + XCTAssertNoDifference(actions, [ + .didJoinGroup(groupInfo.group.serialized), + .didSaveGroup({ + var group = groupInfo.group + group.authStatus = .participating + return group + }()) + ]) + + store.receive(.didJoin) { + $0.isJoining = false + } + } + + func testJoinGroupFailure() { + let groupInfo = GroupInfo.stub() + struct Failure: Error {} + let failure = Failure() + + let store = TestStore( + initialState: GroupComponent.State( + groupId: groupInfo.group.id, + groupInfo: groupInfo + ), + reducer: GroupComponent() + ) + + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.groupChat.get = { + var groupChat: GroupChat = .unimplemented + groupChat.joinGroup.run = { _ in throw failure } + return groupChat + } + + store.send(.joinButtonTapped) { + $0.isJoining = true + } + + store.receive(.didFailToJoin(failure.localizedDescription)) { + $0.isJoining = false + $0.joinFailure = failure.localizedDescription + } + } + + func testPresentChat() { + let groupInfo = GroupInfo.stub() + + let store = TestStore( + initialState: GroupComponent.State( + groupId: groupInfo.group.id, + groupInfo: groupInfo + ), + reducer: GroupComponent() + ) + + store.send(.chatButtonTapped) { + $0.chat = ChatComponent.State(id: .group(groupInfo.id)) + } + + store.send(.didDismissChat) { + $0.chat = nil + } + } +} + +private extension XXModels.GroupInfo { + static func stub() -> XXModels.GroupInfo { + XXModels.GroupInfo( + group: .init( + id: "group-id".data(using: .utf8)!, + name: "Group Name", + leaderId: "group-leader-id".data(using: .utf8)!, + createdAt: Date(timeIntervalSince1970: TimeInterval(86_400)), + authStatus: .participating, + serialized: "group-serialized".data(using: .utf8)! + ), + leader: .init( + id: "group-leader-id".data(using: .utf8)!, + username: "Group leader" + ), + members: [ + .init( + id: "member-1-id".data(using: .utf8)!, + username: "Member 1" + ), + .init( + id: "member-2-id".data(using: .utf8)!, + username: "Member 2" + ), + ] + ) + } +} diff --git a/Examples/xx-messenger/Tests/GroupsFeatureTests/GroupsComponentTests.swift b/Examples/xx-messenger/Tests/GroupsFeatureTests/GroupsComponentTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..5742aaf73119e9b3687191f587de4b2203f4f944 --- /dev/null +++ b/Examples/xx-messenger/Tests/GroupsFeatureTests/GroupsComponentTests.swift @@ -0,0 +1,158 @@ +import Combine +import ComposableArchitecture +import CustomDump +import GroupFeature +import NewGroupFeature +import XCTest +import XXModels +@testable import GroupsFeature + +final class GroupsComponentTests: XCTestCase { + enum Action: Equatable { + case didFetchGroups(XXModels.Group.Query) + } + + var actions: [Action]! + + override func setUp() { + actions = [] + } + + override func tearDown() { + actions = nil + } + + func testStart() { + let groupsSubject = PassthroughSubject<[XXModels.Group], Error>() + + let store = TestStore( + initialState: GroupsComponent.State(), + reducer: GroupsComponent() + ) + + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.dbManager.getDB.run = { + var db: Database = .unimplemented + db.fetchGroupsPublisher.run = { query in + self.actions.append(.didFetchGroups(query)) + return groupsSubject.eraseToAnyPublisher() + } + return db + } + + store.send(.start) + + XCTAssertNoDifference(actions, [ + .didFetchGroups(.init()) + ]) + + let groups: [XXModels.Group] = [ + .stub(1), + .stub(2), + .stub(3), + ] + groupsSubject.send(groups) + + store.receive(.didFetchGroups(groups)) { + $0.groups = IdentifiedArray(uniqueElements: groups) + } + + groupsSubject.send(completion: .finished) + } + + func testSelectGroup() { + let groups: [XXModels.Group] = [ + .stub(1), + .stub(2), + .stub(3), + ] + + let store = TestStore( + initialState: GroupsComponent.State( + groups: IdentifiedArray(uniqueElements: groups) + ), + reducer: GroupsComponent() + ) + + store.send(.didSelectGroup(groups[1])) { + $0.group = GroupComponent.State(groupId: groups[1].id) + } + } + + func testDismissGroup() { + let groups: [XXModels.Group] = [ + .stub(1), + .stub(2), + .stub(3), + ] + + let store = TestStore( + initialState: GroupsComponent.State( + groups: IdentifiedArray(uniqueElements: groups), + group: GroupComponent.State( + groupId: groups[1].id + ) + ), + reducer: GroupsComponent() + ) + + store.send(.didDismissGroup) { + $0.group = nil + } + } + + func testPresentNewGroup() { + let store = TestStore( + initialState: GroupsComponent.State(), + reducer: GroupsComponent() + ) + + store.send(.newGroupButtonTapped) { + $0.newGroup = NewGroupComponent.State() + } + + store.send(.newGroupDismissed) { + $0.newGroup = nil + } + } + + func testDismissNewGroup() { + let store = TestStore( + initialState: GroupsComponent.State( + newGroup: NewGroupComponent.State() + ), + reducer: GroupsComponent() + ) + + store.send(.newGroupDismissed) { + $0.newGroup = nil + } + } + + func testNewGroupDidFinish() { + let store = TestStore( + initialState: GroupsComponent.State( + newGroup: NewGroupComponent.State() + ), + reducer: GroupsComponent() + ) + + store.send(.newGroup(.didFinish)) { + $0.newGroup = nil + } + } +} + +private extension XXModels.Group { + static func stub(_ id: Int) -> XXModels.Group { + XXModels.Group( + id: "group-\(id)-id".data(using: .utf8)!, + name: "Group \(id)", + leaderId: "group-\(id)-leader-id".data(using: .utf8)!, + createdAt: Date(timeIntervalSince1970: TimeInterval(id * 86_400)), + authStatus: .participating, + serialized: "group-\(id)-serialized".data(using: .utf8)! + ) + } +} diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeComponentTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeComponentTests.swift index 3bd49a4283b9b4e535c40fb8c9279a5482aa3d16..813a4534da468e32fc31fdde76c21ab4f8f00f33 100644 --- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeComponentTests.swift +++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeComponentTests.swift @@ -3,6 +3,7 @@ import BackupFeature import ComposableArchitecture import ContactsFeature import CustomDump +import GroupsFeature import RegisterFeature import UserSearchFeature import XCTest @@ -22,6 +23,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 +36,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 +45,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 +65,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 +90,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 +101,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 +140,7 @@ final class HomeComponentTests: XCTestCase { } return cMix } + store.dependencies.app.messenger.isGroupChatRunning.run = { true } store.send(.register(.finished)) { $0.register = nil @@ -209,6 +219,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 +247,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)) @@ -529,4 +541,28 @@ final class HomeComponentTests: XCTestCase { $0.backup = nil } } + + func testGroupsButtonTapped() { + let store = TestStore( + initialState: HomeComponent.State(), + reducer: HomeComponent() + ) + + store.send(.groupsButtonTapped) { + $0.groups = GroupsComponent.State() + } + } + + func testDidDismissGroups() { + let store = TestStore( + initialState: HomeComponent.State( + groups: GroupsComponent.State() + ), + reducer: HomeComponent() + ) + + store.send(.didDismissGroups) { + $0.groups = nil + } + } } diff --git a/Examples/xx-messenger/Tests/NewGroupFeatureTests/NewGroupComponentTests.swift b/Examples/xx-messenger/Tests/NewGroupFeatureTests/NewGroupComponentTests.swift new file mode 100644 index 0000000000000000000000000000000000000000..3207ed8cf24c110fafb019dcb9280a6b0b5e496e --- /dev/null +++ b/Examples/xx-messenger/Tests/NewGroupFeatureTests/NewGroupComponentTests.swift @@ -0,0 +1,290 @@ +import Combine +import ComposableArchitecture +import CustomDump +import XCTest +import XXClient +import XXMessengerClient +import XXModels +@testable import NewGroupFeature + +final class NewGroupComponentTests: XCTestCase { + enum Action: Equatable { + case didFetchContacts(XXModels.Contact.Query) + case didMakeGroup(membership: [Data], message: Data?, name: Data?) + case didSaveGroup(XXModels.Group) + case didSaveMessage(XXModels.Message) + case didSaveGroupMember(XXModels.GroupMember) + } + + var actions: [Action]! + + override func setUp() { + actions = [] + } + + override func tearDown() { + actions = nil + } + + func testStart() { + let contactsSubject = PassthroughSubject<[XXModels.Contact], Error>() + + let store = TestStore( + initialState: NewGroupComponent.State(), + reducer: NewGroupComponent() + ) + + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact = XXClient.Contact.unimplemented("my-contact-data".data(using: .utf8)!) + contact.getIdFromContact.run = { _ in "my-contact-id".data(using: .utf8)! } + return contact + } + return e2e + } + store.dependencies.app.dbManager.getDB.run = { + var db: Database = .unimplemented + db.fetchContactsPublisher.run = { query in + self.actions.append(.didFetchContacts(query)) + return contactsSubject.eraseToAnyPublisher() + } + return db + } + + store.send(.start) + + XCTAssertNoDifference(actions, [ + .didFetchContacts(.init()) + ]) + + let contacts: [XXModels.Contact] = [ + .init(id: "contact-1-id".data(using: .utf8)!), + .init(id: "contact-2-id".data(using: .utf8)!), + .init(id: "contact-3-id".data(using: .utf8)!), + ] + contactsSubject.send(contacts) + + store.receive(.didFetchContacts(contacts)) { + $0.contacts = IdentifiedArray(uniqueElements: contacts) + } + + contactsSubject.send(completion: .finished) + } + + func testSelectMembers() { + let contacts: [XXModels.Contact] = [ + .init(id: "contact-1-id".data(using: .utf8)!), + .init(id: "contact-2-id".data(using: .utf8)!), + .init(id: "contact-3-id".data(using: .utf8)!), + ] + + let store = TestStore( + initialState: NewGroupComponent.State( + contacts: IdentifiedArray(uniqueElements: contacts) + ), + reducer: NewGroupComponent() + ) + + store.send(.didSelectContact(contacts[0])) { + $0.members = IdentifiedArray(uniqueElements: [contacts[0]]) + } + + store.send(.didSelectContact(contacts[1])) { + $0.members = IdentifiedArray(uniqueElements: [contacts[0], contacts[1]]) + } + + store.send(.didSelectContact(contacts[0])) { + $0.members = IdentifiedArray(uniqueElements: [contacts[1]]) + } + } + + func testEnterGroupName() { + let store = TestStore( + initialState: NewGroupComponent.State(), + reducer: NewGroupComponent() + ) + + store.send(.binding(.set(\.$focusedField, .name))) { + $0.focusedField = .name + } + + store.send(.binding(.set(\.$name, "My New Group"))) { + $0.name = "My New Group" + } + + store.send(.binding(.set(\.$focusedField, nil))) { + $0.focusedField = nil + } + } + + func testEnterInitialMessage() { + let store = TestStore( + initialState: NewGroupComponent.State(), + reducer: NewGroupComponent() + ) + + store.send(.binding(.set(\.$focusedField, .message))) { + $0.focusedField = .message + } + + store.send(.binding(.set(\.$message, "Welcome message"))) { + $0.message = "Welcome message" + } + + store.send(.binding(.set(\.$focusedField, nil))) { + $0.focusedField = nil + } + } + + func testCreateGroup() { + let members: [XXModels.Contact] = [ + .init(id: "member-contact-1".data(using: .utf8)!), + .init(id: "member-contact-2".data(using: .utf8)!), + .init(id: "member-contact-3".data(using: .utf8)!), + ] + let name = "New group" + let message = "Welcome message" + let groupReport = GroupReport( + id: "new-group-id".data(using: .utf8)!, + rounds: [], + roundURL: "", + status: 0 + ) + let myContactId = "my-contact-id".data(using: .utf8)! + let currentDate = Date(timeIntervalSince1970: 123) + + let store = TestStore( + initialState: NewGroupComponent.State( + members: IdentifiedArray(uniqueElements: members), + name: name, + message: message + ), + reducer: NewGroupComponent() + ) + + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.groupChat.get = { + var groupChat: GroupChat = .unimplemented + groupChat.makeGroup.run = { membership, message, name in + self.actions.append(.didMakeGroup( + membership: membership, + message: message, + name: name + )) + return groupReport + } + return groupChat + } + store.dependencies.app.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact = XXClient.Contact.unimplemented("my-contact-data".data(using: .utf8)!) + contact.getIdFromContact.run = { _ in myContactId } + return contact + } + return e2e + } + store.dependencies.date = .constant(currentDate) + store.dependencies.app.dbManager.getDB.run = { + var db: Database = .unimplemented + db.saveGroup.run = { group in + self.actions.append(.didSaveGroup(group)) + return group + } + db.saveMessage.run = { message in + self.actions.append(.didSaveMessage(message)) + return message + } + db.saveGroupMember.run = { groupMember in + self.actions.append(.didSaveGroupMember(groupMember)) + return groupMember + } + return db + } + + store.send(.createButtonTapped) { + $0.isCreating = true + } + + XCTAssertNoDifference(actions, [ + .didMakeGroup( + membership: members.map(\.id), + message: message.data(using: .utf8)!, + name: name.data(using: .utf8)! + ), + .didSaveGroup(.init( + id: groupReport.id, + name: name, + leaderId: myContactId, + createdAt: currentDate, + authStatus: .participating, + serialized: try! groupReport.encode() + )), + .didSaveMessage(.init( + senderId: myContactId, + recipientId: nil, + groupId: groupReport.id, + date: currentDate, + status: .sent, + isUnread: false, + text: message + )), + .didSaveGroupMember(.init( + groupId: groupReport.id, + contactId: members[0].id + )), + .didSaveGroupMember(.init( + groupId: groupReport.id, + contactId: members[1].id + )), + .didSaveGroupMember(.init( + groupId: groupReport.id, + contactId: members[2].id + )), + ]) + + store.receive(.didFinish) { + $0.isCreating = false + } + } + + func testCreateGroupFailure() { + struct Failure: Error, Equatable {} + let failure = Failure() + + let store = TestStore( + initialState: NewGroupComponent.State(), + reducer: NewGroupComponent() + ) + + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.groupChat.get = { + var groupChat: GroupChat = .unimplemented + groupChat.makeGroup.run = { _, _, _ in throw failure } + return groupChat + } + + store.send(.createButtonTapped) { + $0.isCreating = true + } + + store.receive(.didFail(failure.localizedDescription)) { + $0.isCreating = false + $0.failure = failure.localizedDescription + } + } + + func testFinish() { + let store = TestStore( + initialState: NewGroupComponent.State(), + reducer: NewGroupComponent() + ) + + store.send(.didFinish) + } +} diff --git a/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4626e31d8eb62969ebd67d9f70afd2f184787bd0..6a90e7fb3973c153ce2fa363a6ed46ea8f2fe03f 100644 --- a/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/combine-schedulers", "state" : { - "revision" : "aa3e575929f2bcc5bad012bd2575eae716cbcdf7", - "version" : "0.8.0" + "revision" : "882ac01eb7ef9e36d4467eb4b1151e74fcef85ab", + "version" : "0.9.1" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/groue/GRDB.swift", "state" : { - "revision" : "13e1f4d7c2896a6a9293102f664e5311e017ffb2", - "version" : "6.1.0" + "revision" : "8330469ac3cbbf0ee52b7e8a4e2b1f4c44bb13ea", + "version" : "6.4.0" } }, { @@ -50,8 +50,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-case-paths", "state" : { - "revision" : "15bba50ebf3a2065388c8d12210debe4f6ada202", - "version" : "0.10.0" + "revision" : "bb436421f57269fbcfe7360735985321585a86e5", + "version" : "0.10.1" + } + }, + { + "identity" : "swift-clocks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-clocks", + "state" : { + "revision" : "20b25ca0dd88ebfb9111ec937814ddc5a8880172", + "version" : "0.2.0" } }, { @@ -68,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-composable-architecture.git", "state" : { - "revision" : "5bd450a8ac6a802f82d485bac219cbfacffa69fb", - "version" : "0.43.0" + "revision" : "c9259b5f74892690cb04a9a8088b4a1789b05a7d", + "version" : "0.47.2" } }, { @@ -77,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/darrarski/swift-composable-presentation.git", "state" : { - "revision" : "f69eb0c9a82832f67dfd5dace98e6d0e8d748b0f", - "version" : "0.6.0" + "revision" : "dec8f4fa46d5e07848aa6fc763a6c9fd0007b37a", + "version" : "0.6.1" } }, { @@ -86,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump.git", "state" : { - "revision" : "819d9d370cd721c9d87671e29d947279292e4541", - "version" : "0.6.0" + "revision" : "ead7d30cc224c3642c150b546f4f1080d1c411a8", + "version" : "0.6.1" } }, { @@ -95,8 +104,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-identified-collections", "state" : { - "revision" : "bfb0d43e75a15b6dfac770bf33479e8393884a36", - "version" : "0.4.1" + "revision" : "a08887de589e3829d488e0b4b707b2ca804b1060", + "version" : "0.5.0" } }, { @@ -108,13 +117,22 @@ "version" : "1.4.4" } }, + { + "identity" : "swiftui-navigation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swiftui-navigation", + "state" : { + "revision" : "4b666bcc59ba1711a7543ecb37e1d181963b180c", + "version" : "0.4.2" + } + }, { "identity" : "xctest-dynamic-overlay", "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay.git", "state" : { - "revision" : "16e6409ee82e1b81390bdffbf217b9c08ab32784", - "version" : "0.5.0" + "revision" : "5a5457a744239896e9b0b03a8e1a5069c3e7b91f", + "version" : "0.6.0" } } ], diff --git a/Frameworks/Bindings.txt b/Frameworks/Bindings.txt index d38c6191e6b7efaeaefb18d4107cfdb300a18274..3e5d533ac45dc9ce1e158953db4c5e6a45bebfb0 100644 --- a/Frameworks/Bindings.txt +++ b/Frameworks/Bindings.txt @@ -1,4 +1,4 @@ -https://git.xx.network/elixxir/client/-/commit/7a48e9fb32831c90532d059148da03453a9093b7 +https://git.xx.network/elixxir/client/-/commit/118de443434478c16f16220858c54fb1237fc342 go version go1.19.3 darwin/arm64 Xcode 14.1 Build version 14B47b gomobile bind target: ios,iossimulator,macos diff --git a/Frameworks/Bindings.xcframework/Info.plist b/Frameworks/Bindings.xcframework/Info.plist index e66824e5243985b4ac053ef62aeaaf571fe03970..43fe32dad94767ae305b61db56f5604f8612617e 100644 --- a/Frameworks/Bindings.xcframework/Info.plist +++ b/Frameworks/Bindings.xcframework/Info.plist @@ -6,7 +6,7 @@ <array> <dict> <key>LibraryIdentifier</key> - <string>ios-arm64_x86_64-simulator</string> + <string>macos-arm64_x86_64</string> <key>LibraryPath</key> <string>Bindings.framework</string> <key>SupportedArchitectures</key> @@ -15,34 +15,34 @@ <string>x86_64</string> </array> <key>SupportedPlatform</key> - <string>ios</string> - <key>SupportedPlatformVariant</key> - <string>simulator</string> + <string>macos</string> </dict> <dict> <key>LibraryIdentifier</key> - <string>ios-arm64</string> + <string>ios-arm64_x86_64-simulator</string> <key>LibraryPath</key> <string>Bindings.framework</string> <key>SupportedArchitectures</key> <array> <string>arm64</string> + <string>x86_64</string> </array> <key>SupportedPlatform</key> <string>ios</string> + <key>SupportedPlatformVariant</key> + <string>simulator</string> </dict> <dict> <key>LibraryIdentifier</key> - <string>macos-arm64_x86_64</string> + <string>ios-arm64</string> <key>LibraryPath</key> <string>Bindings.framework</string> <key>SupportedArchitectures</key> <array> <string>arm64</string> - <string>x86_64</string> </array> <key>SupportedPlatform</key> - <string>macos</string> + <string>ios</string> </dict> </array> <key>CFBundlePackageType</key> diff --git a/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Bindings b/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Bindings index 9523b4aac9e1c29073e98d8728546d947098e37a..4b0ee2902564f493694ba1b4e2b7441f4ff8b61a 100644 Binary files a/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Bindings and b/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Bindings differ diff --git a/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Bindings.objc.h b/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Bindings.objc.h index dcf46cd4439785f4f904ea1f41277a66ed3e7db9..29b692cb622e84ca73a5d29b1f3954765ffb29db 100644 --- a/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Bindings.objc.h +++ b/Frameworks/Bindings.xcframework/ios-arm64/Bindings.framework/Versions/A/Headers/Bindings.objc.h @@ -470,7 +470,7 @@ be returned by this function. Any padding will be discarded within this function. Parameters: - - ciphertext - the encrypted data returned by [ChannelDbCipher.Encrypt]. + - ciphertext - the encrypted data returned by [ChannelDbCipher.Encrypt]. */ - (NSData* _Nullable)decrypt:(NSData* _Nullable)ciphertext error:(NSError* _Nullable* _Nullable)error; /** @@ -478,15 +478,28 @@ Parameters: done on the plaintext so all encrypted data looks uniform at rest. Parameters: - - plaintext - The data to be encrypted. This must be smaller than the block - size passed into [NewChannelsDatabaseCipher]. If it is larger, this will - return an error. + - plaintext - The data to be encrypted. This must be smaller than the block + size passed into [NewChannelsDatabaseCipher]. If it is larger, this will + return an error. */ - (NSData* _Nullable)encrypt:(NSData* _Nullable)plaintext error:(NSError* _Nullable* _Nullable)error; /** * GetID returns the ID for this ChannelDbCipher in the channelDbCipherTracker. */ - (long)getID; +/** + * MarshalJSON marshals the cipher into valid JSON. This function adheres to the +json.Marshaler interface. + */ +- (NSData* _Nullable)marshalJSON:(NSError* _Nullable* _Nullable)error; +/** + * UnmarshalJSON unmarshalls JSON into the cipher. This function adheres to the +json.Unmarshaler interface. + +Note that this function does not transfer the internal RNG. Use +NewCipherFromJSON to properly reconstruct a cipher from JSON. + */ +- (BOOL)unmarshalJSON:(NSData* _Nullable)data error:(NSError* _Nullable* _Nullable)error; @end /** @@ -495,10 +508,11 @@ contains the public channel info formatted in pretty print and the private key for the channel in PEM format. Example JSON: - { - "Channel": "\u003cSpeakeasy-v3:name|description:desc|level:Public|created:1665489600000000000|secrets:zjHmrPPMDQ0tNSANjAmQfKhRpJIdJMU+Hz5hsZ+fVpk=|qozRNkADprqb38lsnU7WxCtGCq9OChlySCEgl4NHjI4=|2|328|7aZQAtuVjE84q4Z09iGytTSXfZj9NyTa6qBp0ueKjCI=\u003e", - "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMCYCAQACAwDVywIDAQABAgMAlVECAgDvAgIA5QICAJECAgCVAgIA1w==\n-----END RSA PRIVATE KEY-----" - } + + { + "Channel": "\u003cSpeakeasy-v3:name|description:desc|level:Public|created:1665489600000000000|secrets:zjHmrPPMDQ0tNSANjAmQfKhRpJIdJMU+Hz5hsZ+fVpk=|qozRNkADprqb38lsnU7WxCtGCq9OChlySCEgl4NHjI4=|2|328|7aZQAtuVjE84q4Z09iGytTSXfZj9NyTa6qBp0ueKjCI=\u003e", + "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMCYCAQACAwDVywIDAQABAgMAlVECAgDvAgIA5QICAJECAgCVAgIA1w==\n-----END RSA PRIVATE KEY-----" + } */ @interface BindingsChannelGeneration : NSObject <goSeqRefInterface> { } @@ -514,11 +528,12 @@ Example JSON: * ChannelInfo contains information about a channel. Example of ChannelInfo JSON: - { - "Name": "Test Channel", - "Description": "This is a test channel", - "ChannelID": "RRnpRhmvXtW9ugS1nILJ3WfttdctDvC2jeuH43E0g/0D", - } + + { + "Name": "Test Channel", + "Description": "This is a test channel", + "ChannelID": "RRnpRhmvXtW9ugS1nILJ3WfttdctDvC2jeuH43E0g/0D", + } */ @interface BindingsChannelInfo : NSObject <goSeqRefInterface> { } @@ -536,11 +551,12 @@ Example of ChannelInfo JSON: ChannelsManager's Send operations. JSON Example: - { - "MessageId": "0kitNxoFdsF4q1VMSI/xPzfCnGB2l+ln2+7CTHjHbJw=", - "Rounds":[1,5,9], - "EphId": 0 - } + + { + "MessageId": "0kitNxoFdsF4q1VMSI/xPzfCnGB2l+ln2+7CTHjHbJw=", + "Rounds":[1,5,9], + "EphId": 0 + } */ @interface BindingsChannelSendReport : NSObject <goSeqRefInterface> { } @@ -574,12 +590,12 @@ reload this channel manager, use [LoadChannelsManager], passing in the storage tag retrieved by [ChannelsManager.GetStorageTag]. Parameters: - - cmixID - The tracked Cmix object ID. This can be retrieved using - [Cmix.GetID]. - - privateIdentity - Bytes of a private identity ([channel.PrivateIdentity]) - that is generated by [GenerateChannelIdentity]. - - event - An interface that contains a function that initialises and returns - the event model that is bindings-compatible. + - cmixID - The tracked Cmix object ID. This can be retrieved using + [Cmix.GetID]. + - privateIdentity - Bytes of a private identity ([channel.PrivateIdentity]) + that is generated by [GenerateChannelIdentity]. + - event - An interface that contains a function that initialises and returns + the event model that is bindings-compatible. */ - (nullable instancetype)init:(long)cmixID privateIdentity:(NSData* _Nullable)privateIdentity eventBuilder:(id<BindingsEventModelBuilder> _Nullable)eventBuilder; // skipped constructor ChannelsManager.NewChannelsManagerGoEventModel with unsupported parameter or return types @@ -597,13 +613,14 @@ string. * GetChannels returns the IDs of all channels that have been joined. Returns: - - []byte - A JSON marshalled list of IDs. + - []byte - A JSON marshalled list of IDs. JSON Example: - { - "U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID", - "15tNdkKbYXoMn58NO6VbDMDWFEyIhTWEGsvgcJsHWAgD" - } + + { + "U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID", + "15tNdkKbYXoMn58NO6VbDMDWFEyIhTWEGsvgcJsHWAgD" + } */ - (NSData* _Nullable)getChannels:(NSError* _Nullable* _Nullable)error; /** @@ -639,14 +656,14 @@ calling [ChannelsManager.JoinChannelFromURL]. There is no enforcement for public URLs. Parameters: - - cmixID - The tracked Cmix object ID. - - host - The URL to append the channel info to. - - maxUses - The maximum number of uses the link can be used (0 for - unlimited). - - marshalledChanId - A marshalled channel ID ([id.ID]). + - cmixID - The tracked Cmix object ID. + - host - The URL to append the channel info to. + - maxUses - The maximum number of uses the link can be used (0 for + unlimited). + - marshalledChanId - A marshalled channel ID ([id.ID]). Returns: - - JSON of ShareURL. + - JSON of ShareURL. */ - (NSData* _Nullable)getShareURL:(long)cmixID host:(NSString* _Nullable)host maxUses:(long)maxUses marshalledChanId:(NSData* _Nullable)marshalledChanId error:(NSError* _Nullable* _Nullable)error; /** @@ -658,14 +675,15 @@ Returns: been joined. Parameters: - - channelPretty - A portable channel string. Should be received from - another user or generated via GenerateChannel. + - channelPretty - A portable channel string. Should be received from + another user or generated via GenerateChannel. The pretty print will be of the format: - <Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=> + + <Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=> Returns: - - []byte - JSON of [ChannelInfo], which describes all relevant channel info. + - []byte - JSON of [ChannelInfo], which describes all relevant channel info. */ - (NSData* _Nullable)joinChannel:(NSString* _Nullable)channelPretty error:(NSError* _Nullable* _Nullable)error; /** @@ -673,7 +691,7 @@ Returns: channel was not previously joined. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). */ - (BOOL)leaveChannel:(NSData* _Nullable)marshalledChanId error:(NSError* _Nullable* _Nullable)error; /** @@ -685,10 +703,10 @@ There can only be one handler per [channels.MessageType], and this will return an error on any re-registration. Parameters: - - messageType - represents the [channels.MessageType] which will have a - registered listener. - - listenerCb - the callback which will be executed when a channel message - of messageType is received. + - messageType - represents the [channels.MessageType] which will have a + registered listener. + - listenerCb - the callback which will be executed when a channel message + of messageType is received. */ - (BOOL)registerReceiveHandler:(long)messageType listenerCb:(id<BindingsChannelMessageReceptionCallback> _Nullable)listenerCb error:(NSError* _Nullable* _Nullable)error; /** @@ -696,7 +714,7 @@ Parameters: memory (~3 weeks) over the event model. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). */ - (BOOL)replayChannel:(NSData* _Nullable)marshalledChanId error:(NSError* _Nullable* _Nullable)error; /** @@ -707,23 +725,23 @@ before being sent over the wire, is too long, this will return an error. The message must be at most 510 bytes long. Parameters: - - adminPrivateKey - The PEM-encoded admin RSA private key. - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). - - messageType - The message type of the message. This will be a valid - [channels.MessageType]. - - message - The contents of the message. The message should be at most 510 - bytes. This need not be of data type string, as the message could be a - specified format that the channel may recognize. - - leaseTimeMS - The lease of the message. This will be how long the message - is valid until, in milliseconds. As per the channels.Manager - documentation, this has different meanings depending on the use case. - These use cases may be generic enough that they will not be enumerated - here. - - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, - and GetDefaultCMixParams will be used internally. + - adminPrivateKey - The PEM-encoded admin RSA private key. + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - messageType - The message type of the message. This will be a valid + [channels.MessageType]. + - message - The contents of the message. The message should be at most 510 + bytes. This need not be of data type string, as the message could be a + specified format that the channel may recognize. + - leaseTimeMS - The lease of the message. This will be how long the message + is valid until, in milliseconds. As per the channels.Manager + documentation, this has different meanings depending on the use case. + These use cases may be generic enough that they will not be enumerated + here. + - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, + and GetDefaultCMixParams will be used internally. Returns: - - []byte - A JSON marshalled ChannelSendReport. + - []byte - A JSON marshalled ChannelSendReport. */ - (NSData* _Nullable)sendAdminGeneric:(NSData* _Nullable)adminPrivateKey marshalledChanId:(NSData* _Nullable)marshalledChanId messageType:(long)messageType message:(NSData* _Nullable)message leaseTimeMS:(int64_t)leaseTimeMS cmixParamsJSON:(NSData* _Nullable)cmixParamsJSON error:(NSError* _Nullable* _Nullable)error; /** @@ -736,22 +754,22 @@ to send a payload of 802 bytes at minimum. The meaning of validUntil depends on the use case. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). - - messageType - The message type of the message. This will be a valid - [channels.MessageType]. - - message - The contents of the message. This need not be of data type - string, as the message could be a specified format that the channel may - recognize. - - leaseTimeMS - The lease of the message. This will be how long the message - is valid until, in milliseconds. As per the channels.Manager - documentation, this has different meanings depending on the use case. - These use cases may be generic enough that they will not be enumerated - here. - - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, - and GetDefaultCMixParams will be used internally. + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - messageType - The message type of the message. This will be a valid + [channels.MessageType]. + - message - The contents of the message. This need not be of data type + string, as the message could be a specified format that the channel may + recognize. + - leaseTimeMS - The lease of the message. This will be how long the message + is valid until, in milliseconds. As per the channels.Manager + documentation, this has different meanings depending on the use case. + These use cases may be generic enough that they will not be enumerated + here. + - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, + and GetDefaultCMixParams will be used internally. Returns: - - []byte - A JSON marshalled ChannelSendReport. + - []byte - A JSON marshalled ChannelSendReport. */ - (NSData* _Nullable)sendGeneric:(NSData* _Nullable)marshalledChanId messageType:(long)messageType message:(NSData* _Nullable)message leaseTimeMS:(int64_t)leaseTimeMS cmixParamsJSON:(NSData* _Nullable)cmixParamsJSON error:(NSError* _Nullable* _Nullable)error; /** @@ -764,20 +782,20 @@ The message will auto delete validUntil after the round it is sent in, lasting forever if [channels.ValidForever] is used. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). - - message - The contents of the message. The message should be at most 510 - bytes. This is expected to be Unicode, and thus a string data type is - expected - - leaseTimeMS - The lease of the message. This will be how long the message - is valid until, in milliseconds. As per the channels.Manager - documentation, this has different meanings depending on the use case. - These use cases may be generic enough that they will not be enumerated - here. - - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be - empty, and GetDefaultCMixParams will be used internally. + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - message - The contents of the message. The message should be at most 510 + bytes. This is expected to be Unicode, and thus a string data type is + expected + - leaseTimeMS - The lease of the message. This will be how long the message + is valid until, in milliseconds. As per the channels.Manager + documentation, this has different meanings depending on the use case. + These use cases may be generic enough that they will not be enumerated + here. + - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be + empty, and GetDefaultCMixParams will be used internally. Returns: - - []byte - A JSON marshalled ChannelSendReport + - []byte - A JSON marshalled ChannelSendReport */ - (NSData* _Nullable)sendMessage:(NSData* _Nullable)marshalledChanId message:(NSString* _Nullable)message leaseTimeMS:(int64_t)leaseTimeMS cmixParamsJSON:(NSData* _Nullable)cmixParamsJSON error:(NSError* _Nullable* _Nullable)error; /** @@ -787,19 +805,19 @@ be rejected otherwise. Users will drop the reaction if they do not recognize the reactTo message. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). - - reaction - The user's reaction. This should be a single emoji with no - other characters. As such, a Unicode string is expected. - - messageToReactTo - The marshalled [channel.MessageID] of the message you - wish to reply to. This may be found in the ChannelSendReport if replying - to your own. Alternatively, if reacting to another user's message, you may - retrieve it via the ChannelMessageReceptionCallback registered using - RegisterReceiveHandler. - - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, - and GetDefaultCMixParams will be used internally. + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - reaction - The user's reaction. This should be a single emoji with no + other characters. As such, a Unicode string is expected. + - messageToReactTo - The marshalled [channel.MessageID] of the message you + wish to reply to. This may be found in the ChannelSendReport if replying + to your own. Alternatively, if reacting to another user's message, you may + retrieve it via the ChannelMessageReceptionCallback registered using + RegisterReceiveHandler. + - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, + and GetDefaultCMixParams will be used internally. Returns: - - []byte - A JSON marshalled ChannelSendReport. + - []byte - A JSON marshalled ChannelSendReport. */ - (NSData* _Nullable)sendReaction:(NSData* _Nullable)marshalledChanId reaction:(NSString* _Nullable)reaction messageToReactTo:(NSData* _Nullable)messageToReactTo cmixParamsJSON:(NSData* _Nullable)cmixParamsJSON error:(NSError* _Nullable* _Nullable)error; /** @@ -814,25 +832,25 @@ delete validUntil after the round it is sent in, lasting forever if [channels.ValidForever] is used. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). - - message - The contents of the message. The message should be at most 510 - bytes. This is expected to be Unicode, and thus a string data type is - expected. - - messageToReactTo - The marshalled [channel.MessageID] of the message you - wish to reply to. This may be found in the ChannelSendReport if replying - to your own. Alternatively, if reacting to another user's message, you may - retrieve it via the ChannelMessageReceptionCallback registered using - RegisterReceiveHandler. - - leaseTimeMS - The lease of the message. This will be how long the message - is valid until, in milliseconds. As per the channels.Manager - documentation, this has different meanings depending on the use case. - These use cases may be generic enough that they will not be enumerated - here. - - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, - and GetDefaultCMixParams will be used internally. + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - message - The contents of the message. The message should be at most 510 + bytes. This is expected to be Unicode, and thus a string data type is + expected. + - messageToReactTo - The marshalled [channel.MessageID] of the message you + wish to reply to. This may be found in the ChannelSendReport if replying + to your own. Alternatively, if reacting to another user's message, you may + retrieve it via the ChannelMessageReceptionCallback registered using + RegisterReceiveHandler. + - leaseTimeMS - The lease of the message. This will be how long the message + is valid until, in milliseconds. As per the channels.Manager + documentation, this has different meanings depending on the use case. + These use cases may be generic enough that they will not be enumerated + here. + - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, + and GetDefaultCMixParams will be used internally. Returns: - - []byte - A JSON marshalled ChannelSendReport + - []byte - A JSON marshalled ChannelSendReport */ - (NSData* _Nullable)sendReply:(NSData* _Nullable)marshalledChanId message:(NSString* _Nullable)message messageToReactTo:(NSData* _Nullable)messageToReactTo leaseTimeMS:(int64_t)leaseTimeMS cmixParamsJSON:(NSData* _Nullable)cmixParamsJSON error:(NSError* _Nullable* _Nullable)error; /** @@ -2130,17 +2148,18 @@ channel the message was sent to and the message itself. This is returned via the callback as JSON marshalled bytes. JSON Example: - { - "ChannelId": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "MessageId": "3S6DiVjWH9mLmjy1oaam/3x45bJQzOW6u2KgeUn59wA=", - "ReplyTo":"cxMyGUFJ+Ff1Xp2X+XkIpOnNAQEZmv8SNP5eYH4tCik=", - "MessageType": 42, - "SenderUsername": "hunter2", - "Content": "YmFuX2JhZFVTZXI=", - "Timestamp": 1662502150335283000, - "Lease": 25, - "Rounds": [ 1, 4, 9], - } + + { + "ChannelId": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "MessageId": "3S6DiVjWH9mLmjy1oaam/3x45bJQzOW6u2KgeUn59wA=", + "ReplyTo":"cxMyGUFJ+Ff1Xp2X+XkIpOnNAQEZmv8SNP5eYH4tCik=", + "MessageType": 42, + "SenderUsername": "hunter2", + "Content": "YmFuX2JhZFVTZXI=", + "Timestamp": 1662502150335283000, + "Lease": 25, + "Rounds": [ 1, 4, 9], + } */ @interface BindingsReceivedChannelMessageReport : NSObject <goSeqRefInterface> { } @@ -2271,22 +2290,25 @@ JSON Example: channel's share URL and password, if it needs one. JSON example for a public channel: - { - "url": "https://internet.speakeasy.tech/?0Name=name&1Description=desc&2Level=Public&3Created=1665489600000000000&e=%2FWNZvuHPuv%2Bx23XbZXVNzCi7y8rUSxkh75MpR9UrsCo%3D&k=ddX1CH52xH%2F%2Fb6lKrbvDghdSmCQr90ktsOAZ%2FrhEonI%3D&l=2&m=0&p=328&s=%2FD%2FoQP2mio3XAWfhmWF0xmZrpj4nAsb9JLXj%2B0Mzq9Y%3D&v=1", - "password": "" - } + + { + "url": "https://internet.speakeasy.tech/?0Name=name&1Description=desc&2Level=Public&3Created=1665489600000000000&e=%2FWNZvuHPuv%2Bx23XbZXVNzCi7y8rUSxkh75MpR9UrsCo%3D&k=ddX1CH52xH%2F%2Fb6lKrbvDghdSmCQr90ktsOAZ%2FrhEonI%3D&l=2&m=0&p=328&s=%2FD%2FoQP2mio3XAWfhmWF0xmZrpj4nAsb9JLXj%2B0Mzq9Y%3D&v=1", + "password": "" + } JSON example for a private channel: - { - "url": "https://internet.speakeasy.tech/?0Name=name&1Description=desc&3Created=1665489600000000000&d=5AZQirb%2FYrmUITLn%2FFzCaGek1APfJnd2q0KwORGj%2BnbGg26kTShG6cfD3w6c%2BA3RDzxuKDSDN0zS4n1LbjiGe0KYdb8eJVeyRZtld516hfojNDXNAwZq8zbeZy4jjbF627fcLHRNS%2FaII4uJ5UB3gLUeBeZGraaybCCu3FIj1N4RbcJ5cQgT7hBf93bHmJc%3D&m=0&v=1", - "password": "tribune gangrene labrador italics nutmeg process exhume legal" - } + + { + "url": "https://internet.speakeasy.tech/?0Name=name&1Description=desc&3Created=1665489600000000000&d=5AZQirb%2FYrmUITLn%2FFzCaGek1APfJnd2q0KwORGj%2BnbGg26kTShG6cfD3w6c%2BA3RDzxuKDSDN0zS4n1LbjiGe0KYdb8eJVeyRZtld516hfojNDXNAwZq8zbeZy4jjbF627fcLHRNS%2FaII4uJ5UB3gLUeBeZGraaybCCu3FIj1N4RbcJ5cQgT7hBf93bHmJc%3D&m=0&v=1", + "password": "tribune gangrene labrador italics nutmeg process exhume legal" + } JSON example for a secret channel: - { - "url": "https://internet.speakeasy.tech/?d=w5evLthm%2Fq2j11g6PPtV0QoLaAqNCIER0OqxhxL%2FhpGVJI0057ZPgGBrKoJNE1%2FdoVuU35%2FhohuW%2BWvGlx6IuHoN6mDj0HfNj6Lo%2B8GwIaD6jOEwUcH%2FMKGsKnoqFsMaMPd5gXYgdHvA8l5SRe0gSCVqGKUaG6JgL%2FDu4iyjY7v4ykwZdQ7soWOcBLHDixGEkVLpwsCrPVHkT2K0W6gV74GIrQ%3D%3D&m=0&v=1", - "password": "frenzy contort staple thicket consuming affiliate scion demeanor" - } + + { + "url": "https://internet.speakeasy.tech/?d=w5evLthm%2Fq2j11g6PPtV0QoLaAqNCIER0OqxhxL%2FhpGVJI0057ZPgGBrKoJNE1%2FdoVuU35%2FhohuW%2BWvGlx6IuHoN6mDj0HfNj6Lo%2B8GwIaD6jOEwUcH%2FMKGsKnoqFsMaMPd5gXYgdHvA8l5SRe0gSCVqGKUaG6JgL%2FDu4iyjY7v4ykwZdQ7soWOcBLHDixGEkVLpwsCrPVHkT2K0W6gV74GIrQ%3D%3D&m=0&v=1", + "password": "frenzy contort staple thicket consuming affiliate scion demeanor" + } */ @interface BindingsShareURL : NSObject <goSeqRefInterface> { } @@ -2481,11 +2503,11 @@ FOUNDATION_EXPORT BOOL BindingsAsyncRequestRestLike(long e2eID, NSData* _Nullabl and codeset version. Parameters: - - pubKey - The Ed25519 public key. - - codesetVersion - The version of the codeset used to generate the identity. + - pubKey - The Ed25519 public key. + - codesetVersion - The version of the codeset used to generate the identity. Returns: - - JSON of [channel.Identity]. + - JSON of [channel.Identity]. */ FOUNDATION_EXPORT NSData* _Nullable BindingsConstructIdentity(NSData* _Nullable pubKey, long codesetVersion, NSError* _Nullable* _Nullable error); @@ -2512,12 +2534,12 @@ pretty print. This function can only be used for private or secret channel URLs. To get the privacy level of a channel URL, use [GetShareUrlType]. Parameters: - - url - The channel's share URL. Should be received from another user or - generated via [GetShareURL]. - - password - The password needed to decrypt the secret data in the URL. + - url - The channel's share URL. Should be received from another user or + generated via [GetShareURL]. + - password - The password needed to decrypt the secret data in the URL. Returns: - - The channel pretty print. + - The channel pretty print. */ FOUNDATION_EXPORT NSString* _Nonnull BindingsDecodePrivateURL(NSString* _Nullable url, NSString* _Nullable password, NSError* _Nullable* _Nullable error); @@ -2527,11 +2549,11 @@ function can only be used for public channel URLs. To get the privacy level of a channel URL, use [GetShareUrlType]. Parameters: - - url - The channel's share URL. Should be received from another user or - generated via [GetShareURL]. + - url - The channel's share URL. Should be received from another user or + generated via [GetShareURL]. Returns: - - The channel pretty print. + - The channel pretty print. */ FOUNDATION_EXPORT NSString* _Nonnull BindingsDecodePublicURL(NSString* _Nullable url, NSError* _Nullable* _Nullable error); @@ -2561,33 +2583,33 @@ the admin. It is only for making new channels, not joining existing ones. It returns a pretty print of the channel and the private key. Parameters: - - cmixID - The tracked cmix object ID. This can be retrieved using - [Cmix.GetID]. - - name - The name of the new channel. The name must be between 3 and 24 - characters inclusive. It can only include upper and lowercase unicode - letters, digits 0 through 9, and underscores (_). It cannot be changed - once a channel is created. - - description - The description of a channel. The description is optional - but cannot be longer than 144 characters and can include all unicode - characters. It cannot be changed once a channel is created. - - privacyLevel - The broadcast.PrivacyLevel of the channel. 0 = public, - 1 = private, and 2 = secret. Refer to the comment below for more - information. + - cmixID - The tracked cmix object ID. This can be retrieved using + [Cmix.GetID]. + - name - The name of the new channel. The name must be between 3 and 24 + characters inclusive. It can only include upper and lowercase unicode + letters, digits 0 through 9, and underscores (_). It cannot be changed + once a channel is created. + - description - The description of a channel. The description is optional + but cannot be longer than 144 characters and can include all unicode + characters. It cannot be changed once a channel is created. + - privacyLevel - The broadcast.PrivacyLevel of the channel. 0 = public, + 1 = private, and 2 = secret. Refer to the comment below for more + information. Returns: - - []byte - [ChannelGeneration] describes a generated channel. It contains - both the public channel info and the private key for the channel in PEM - format. + - []byte - [ChannelGeneration] describes a generated channel. It contains + both the public channel info and the private key for the channel in PEM + format. The [broadcast.PrivacyLevel] of a channel indicates the level of channel information revealed when sharing it via URL. For any channel besides public channels, the secret information is encrypted and a password is required to share and join a channel. - - A privacy level of [broadcast.Public] reveals all the information - including the name, description, privacy level, public key and salt. - - A privacy level of [broadcast.Private] reveals only the name and - description. - - A privacy level of [broadcast.Secret] reveals nothing. + - A privacy level of [broadcast.Public] reveals all the information + including the name, description, privacy level, public key and salt. + - A privacy level of [broadcast.Private] reveals only the name and + description. + - A privacy level of [broadcast.Secret] reveals nothing. */ FOUNDATION_EXPORT NSData* _Nullable BindingsGenerateChannel(long cmixID, NSString* _Nullable name, NSString* _Nullable description, long privacyLevel, NSError* _Nullable* _Nullable error); @@ -2597,11 +2619,11 @@ FOUNDATION_EXPORT NSData* _Nullable BindingsGenerateChannel(long cmixID, NSStrin via [GetPublicChannelIdentityFromPrivate]. Parameters: - - cmixID - The tracked cmix object ID. This can be retrieved using - [Cmix.GetID]. + - cmixID - The tracked cmix object ID. This can be retrieved using + [Cmix.GetID]. Returns: - - Marshalled bytes of [channel.PrivateIdentity]. + - Marshalled bytes of [channel.PrivateIdentity]. */ FOUNDATION_EXPORT NSData* _Nullable BindingsGenerateChannelIdentity(long cmixID, NSError* _Nullable* _Nullable error); @@ -2625,13 +2647,14 @@ FOUNDATION_EXPORT BindingsChannelDbCipher* _Nullable BindingsGetChannelDbCipherT * GetChannelInfo returns the info about a channel from its public description. Parameters: - - prettyPrint - The pretty print of the channel. + - prettyPrint - The pretty print of the channel. The pretty print will be of the format: - <Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=> + + <Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=> Returns: - - []byte - JSON of [ChannelInfo], which describes all relevant channel info. + - []byte - JSON of [ChannelInfo], which describes all relevant channel info. */ FOUNDATION_EXPORT NSData* _Nullable BindingsGetChannelInfo(NSString* _Nullable prettyPrint, NSError* _Nullable* _Nullable error); @@ -2639,23 +2662,24 @@ FOUNDATION_EXPORT NSData* _Nullable BindingsGetChannelInfo(NSString* _Nullable p * GetChannelJSON returns the JSON of the channel for the given pretty print. Parameters: - - prettyPrint - The pretty print of the channel. + - prettyPrint - The pretty print of the channel. Returns: - - JSON of the [broadcast.Channel] object. + - JSON of the [broadcast.Channel] object. Example JSON of [broadcast.Channel]: - { - "ReceptionID": "Ja/+Jh+1IXZYUOn+IzE3Fw/VqHOscomD0Q35p4Ai//kD", - "Name": "My_Channel", - "Description": "Here is information about my channel.", - "Salt": "+tlrU/htO6rrV3UFDfpQALUiuelFZ+Cw9eZCwqRHk+g=", - "RsaPubKeyHash": "PViT1mYkGBj6AYmE803O2RpA7BX24EjgBdldu3pIm4o=", - "RsaPubKeyLength": 5, - "RSASubPayloads": 1, - "Secret": "JxZt/wPx2luoPdHY6jwbXqNlKnixVU/oa9DgypZOuyI=", - "Level": 0 - } + + { + "ReceptionID": "Ja/+Jh+1IXZYUOn+IzE3Fw/VqHOscomD0Q35p4Ai//kD", + "Name": "My_Channel", + "Description": "Here is information about my channel.", + "Salt": "+tlrU/htO6rrV3UFDfpQALUiuelFZ+Cw9eZCwqRHk+g=", + "RsaPubKeyHash": "PViT1mYkGBj6AYmE803O2RpA7BX24EjgBdldu3pIm4o=", + "RsaPubKeyLength": 5, + "RSASubPayloads": 1, + "Secret": "JxZt/wPx2luoPdHY6jwbXqNlKnixVU/oa9DgypZOuyI=", + "Level": 0 + } */ FOUNDATION_EXPORT NSData* _Nullable BindingsGetChannelJSON(NSString* _Nullable prettyPrint, NSError* _Nullable* _Nullable error); @@ -2762,10 +2786,10 @@ FOUNDATION_EXPORT NSData* _Nullable BindingsGetPubkeyFromContact(NSData* _Nullab from a bytes version and returns it JSON marshaled. Parameters: - - marshaledPublic - Bytes of the public identity ([channel.Identity]). + - marshaledPublic - Bytes of the public identity ([channel.Identity]). Returns: - - JSON of the constructed [channel.Identity]. + - JSON of the constructed [channel.Identity]. */ FOUNDATION_EXPORT NSData* _Nullable BindingsGetPublicChannelIdentity(NSData* _Nullable marshaledPublic, NSError* _Nullable* _Nullable error); @@ -2775,11 +2799,11 @@ FOUNDATION_EXPORT NSData* _Nullable BindingsGetPublicChannelIdentity(NSData* _Nu ([channel.PrivateIdentity]). Parameters: - - marshaledPrivate - Bytes of the private identity - (channel.PrivateIdentity]). + - marshaledPrivate - Bytes of the private identity + (channel.PrivateIdentity]). Returns: - - JSON of the public [channel.Identity]. + - JSON of the public [channel.Identity]. */ FOUNDATION_EXPORT NSData* _Nullable BindingsGetPublicChannelIdentityFromPrivate(NSData* _Nullable marshaledPrivate, NSError* _Nullable* _Nullable error); @@ -2790,11 +2814,11 @@ given channel ID. NOTE: This function is unsafe and only for debugging purposes only. Parameters: - - cmixID - ID of [Cmix] object in tracker. - - channelIdBase64 - The [id.ID] of the channel in base 64 encoding. + - cmixID - ID of [Cmix] object in tracker. + - channelIdBase64 - The [id.ID] of the channel in base 64 encoding. Returns: - - The PEM file of the private key. + - The PEM file of the private key. */ FOUNDATION_EXPORT NSString* _Nonnull BindingsGetSavedChannelPrivateKeyUNSAFE(long cmixID, NSString* _Nullable channelIdBase64, NSError* _Nullable* _Nullable error); @@ -2803,15 +2827,16 @@ FOUNDATION_EXPORT NSString* _Nonnull BindingsGetSavedChannelPrivateKeyUNSAFE(lon If the URL is an invalid channel URL, an error is returned. Parameters: - - url - The channel share URL. + - url - The channel share URL. Returns: - - An int that corresponds to the [broadcast.PrivacyLevel] as outlined below. + - An int that corresponds to the [broadcast.PrivacyLevel] as outlined below. Possible returns: - 0 = public channel - 1 = private channel - 2 = secret channel + + 0 = public channel + 1 = private channel + 2 = secret channel */ FOUNDATION_EXPORT BOOL BindingsGetShareUrlType(NSString* _Nullable url, long* _Nullable ret0_, NSError* _Nullable* _Nullable error); @@ -2825,11 +2850,11 @@ FOUNDATION_EXPORT NSString* _Nonnull BindingsGetVersion(void); data. Parameters: - - password - The password used to encrypt the identity. - - data - The encrypted data. + - password - The password used to encrypt the identity. + - data - The encrypted data. Returns: - - JSON of [channel.PrivateIdentity]. + - JSON of [channel.PrivateIdentity]. */ FOUNDATION_EXPORT NSData* _Nullable BindingsImportPrivateIdentity(NSString* _Nullable password, NSData* _Nullable data, NSError* _Nullable* _Nullable error); @@ -2909,12 +2934,12 @@ The channel manager should have previously been created with [ChannelsManager.GetStorageTag]. Parameters: - - cmixID - The tracked cmix object ID. This can be retrieved using - [Cmix.GetID]. - - storageTag - The storage tag associated with the previously created - channel manager and retrieved with [ChannelsManager.GetStorageTag]. - - event - An interface that contains a function that initialises and returns - the event model that is bindings-compatible. + - cmixID - The tracked cmix object ID. This can be retrieved using + [Cmix.GetID]. + - storageTag - The storage tag associated with the previously created + channel manager and retrieved with [ChannelsManager.GetStorageTag]. + - event - An interface that contains a function that initialises and returns + the event model that is bindings-compatible. */ FOUNDATION_EXPORT BindingsChannelsManager* _Nullable BindingsLoadChannelsManager(long cmixID, NSString* _Nullable storageTag, id<BindingsEventModelBuilder> _Nullable eventBuilder, NSError* _Nullable* _Nullable error); @@ -3014,12 +3039,12 @@ FOUNDATION_EXPORT BOOL BindingsMultiLookupUD(long e2eID, NSData* _Nullable udCon * NewChannelsDatabaseCipher constructs a ChannelDbCipher object. Parameters: - - cmixID - The tracked [Cmix] object ID. - - password - The password for storage. This should be the same password - passed into [NewCmix]. - - plaintTextBlockSize - The maximum size of a payload to be encrypted. - A payload passed into [ChannelDbCipher.Encrypt] that is larger than - plaintTextBlockSize will result in an error. + - cmixID - The tracked [Cmix] object ID. + - password - The password for storage. This should be the same password + passed into [NewCmix]. + - plaintTextBlockSize - The maximum size of a payload to be encrypted. + A payload passed into [ChannelDbCipher.Encrypt] that is larger than + plaintTextBlockSize will result in an error. */ FOUNDATION_EXPORT BindingsChannelDbCipher* _Nullable BindingsNewChannelsDatabaseCipher(long cmixID, NSData* _Nullable password, long plaintTextBlockSize, NSError* _Nullable* _Nullable error); @@ -3033,12 +3058,12 @@ reload this channel manager, use [LoadChannelsManager], passing in the storage tag retrieved by [ChannelsManager.GetStorageTag]. Parameters: - - cmixID - The tracked Cmix object ID. This can be retrieved using - [Cmix.GetID]. - - privateIdentity - Bytes of a private identity ([channel.PrivateIdentity]) - that is generated by [GenerateChannelIdentity]. - - event - An interface that contains a function that initialises and returns - the event model that is bindings-compatible. + - cmixID - The tracked Cmix object ID. This can be retrieved using + [Cmix.GetID]. + - privateIdentity - Bytes of a private identity ([channel.PrivateIdentity]) + that is generated by [GenerateChannelIdentity]. + - event - An interface that contains a function that initialises and returns + the event model that is bindings-compatible. */ FOUNDATION_EXPORT BindingsChannelsManager* _Nullable BindingsNewChannelsManager(long cmixID, NSData* _Nullable privateIdentity, id<BindingsEventModelBuilder> _Nullable eventBuilder, NSError* _Nullable* _Nullable error); diff --git a/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Bindings b/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Bindings index 91e00787b70d81643696ce73d3baf18e406901ed..a616579df5bfd7ae5d3cd41ec961af17d8ed9e79 100644 Binary files a/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Bindings and b/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Bindings differ diff --git a/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Bindings.objc.h b/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Bindings.objc.h index dcf46cd4439785f4f904ea1f41277a66ed3e7db9..29b692cb622e84ca73a5d29b1f3954765ffb29db 100644 --- a/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Bindings.objc.h +++ b/Frameworks/Bindings.xcframework/ios-arm64_x86_64-simulator/Bindings.framework/Versions/A/Headers/Bindings.objc.h @@ -470,7 +470,7 @@ be returned by this function. Any padding will be discarded within this function. Parameters: - - ciphertext - the encrypted data returned by [ChannelDbCipher.Encrypt]. + - ciphertext - the encrypted data returned by [ChannelDbCipher.Encrypt]. */ - (NSData* _Nullable)decrypt:(NSData* _Nullable)ciphertext error:(NSError* _Nullable* _Nullable)error; /** @@ -478,15 +478,28 @@ Parameters: done on the plaintext so all encrypted data looks uniform at rest. Parameters: - - plaintext - The data to be encrypted. This must be smaller than the block - size passed into [NewChannelsDatabaseCipher]. If it is larger, this will - return an error. + - plaintext - The data to be encrypted. This must be smaller than the block + size passed into [NewChannelsDatabaseCipher]. If it is larger, this will + return an error. */ - (NSData* _Nullable)encrypt:(NSData* _Nullable)plaintext error:(NSError* _Nullable* _Nullable)error; /** * GetID returns the ID for this ChannelDbCipher in the channelDbCipherTracker. */ - (long)getID; +/** + * MarshalJSON marshals the cipher into valid JSON. This function adheres to the +json.Marshaler interface. + */ +- (NSData* _Nullable)marshalJSON:(NSError* _Nullable* _Nullable)error; +/** + * UnmarshalJSON unmarshalls JSON into the cipher. This function adheres to the +json.Unmarshaler interface. + +Note that this function does not transfer the internal RNG. Use +NewCipherFromJSON to properly reconstruct a cipher from JSON. + */ +- (BOOL)unmarshalJSON:(NSData* _Nullable)data error:(NSError* _Nullable* _Nullable)error; @end /** @@ -495,10 +508,11 @@ contains the public channel info formatted in pretty print and the private key for the channel in PEM format. Example JSON: - { - "Channel": "\u003cSpeakeasy-v3:name|description:desc|level:Public|created:1665489600000000000|secrets:zjHmrPPMDQ0tNSANjAmQfKhRpJIdJMU+Hz5hsZ+fVpk=|qozRNkADprqb38lsnU7WxCtGCq9OChlySCEgl4NHjI4=|2|328|7aZQAtuVjE84q4Z09iGytTSXfZj9NyTa6qBp0ueKjCI=\u003e", - "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMCYCAQACAwDVywIDAQABAgMAlVECAgDvAgIA5QICAJECAgCVAgIA1w==\n-----END RSA PRIVATE KEY-----" - } + + { + "Channel": "\u003cSpeakeasy-v3:name|description:desc|level:Public|created:1665489600000000000|secrets:zjHmrPPMDQ0tNSANjAmQfKhRpJIdJMU+Hz5hsZ+fVpk=|qozRNkADprqb38lsnU7WxCtGCq9OChlySCEgl4NHjI4=|2|328|7aZQAtuVjE84q4Z09iGytTSXfZj9NyTa6qBp0ueKjCI=\u003e", + "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMCYCAQACAwDVywIDAQABAgMAlVECAgDvAgIA5QICAJECAgCVAgIA1w==\n-----END RSA PRIVATE KEY-----" + } */ @interface BindingsChannelGeneration : NSObject <goSeqRefInterface> { } @@ -514,11 +528,12 @@ Example JSON: * ChannelInfo contains information about a channel. Example of ChannelInfo JSON: - { - "Name": "Test Channel", - "Description": "This is a test channel", - "ChannelID": "RRnpRhmvXtW9ugS1nILJ3WfttdctDvC2jeuH43E0g/0D", - } + + { + "Name": "Test Channel", + "Description": "This is a test channel", + "ChannelID": "RRnpRhmvXtW9ugS1nILJ3WfttdctDvC2jeuH43E0g/0D", + } */ @interface BindingsChannelInfo : NSObject <goSeqRefInterface> { } @@ -536,11 +551,12 @@ Example of ChannelInfo JSON: ChannelsManager's Send operations. JSON Example: - { - "MessageId": "0kitNxoFdsF4q1VMSI/xPzfCnGB2l+ln2+7CTHjHbJw=", - "Rounds":[1,5,9], - "EphId": 0 - } + + { + "MessageId": "0kitNxoFdsF4q1VMSI/xPzfCnGB2l+ln2+7CTHjHbJw=", + "Rounds":[1,5,9], + "EphId": 0 + } */ @interface BindingsChannelSendReport : NSObject <goSeqRefInterface> { } @@ -574,12 +590,12 @@ reload this channel manager, use [LoadChannelsManager], passing in the storage tag retrieved by [ChannelsManager.GetStorageTag]. Parameters: - - cmixID - The tracked Cmix object ID. This can be retrieved using - [Cmix.GetID]. - - privateIdentity - Bytes of a private identity ([channel.PrivateIdentity]) - that is generated by [GenerateChannelIdentity]. - - event - An interface that contains a function that initialises and returns - the event model that is bindings-compatible. + - cmixID - The tracked Cmix object ID. This can be retrieved using + [Cmix.GetID]. + - privateIdentity - Bytes of a private identity ([channel.PrivateIdentity]) + that is generated by [GenerateChannelIdentity]. + - event - An interface that contains a function that initialises and returns + the event model that is bindings-compatible. */ - (nullable instancetype)init:(long)cmixID privateIdentity:(NSData* _Nullable)privateIdentity eventBuilder:(id<BindingsEventModelBuilder> _Nullable)eventBuilder; // skipped constructor ChannelsManager.NewChannelsManagerGoEventModel with unsupported parameter or return types @@ -597,13 +613,14 @@ string. * GetChannels returns the IDs of all channels that have been joined. Returns: - - []byte - A JSON marshalled list of IDs. + - []byte - A JSON marshalled list of IDs. JSON Example: - { - "U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID", - "15tNdkKbYXoMn58NO6VbDMDWFEyIhTWEGsvgcJsHWAgD" - } + + { + "U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID", + "15tNdkKbYXoMn58NO6VbDMDWFEyIhTWEGsvgcJsHWAgD" + } */ - (NSData* _Nullable)getChannels:(NSError* _Nullable* _Nullable)error; /** @@ -639,14 +656,14 @@ calling [ChannelsManager.JoinChannelFromURL]. There is no enforcement for public URLs. Parameters: - - cmixID - The tracked Cmix object ID. - - host - The URL to append the channel info to. - - maxUses - The maximum number of uses the link can be used (0 for - unlimited). - - marshalledChanId - A marshalled channel ID ([id.ID]). + - cmixID - The tracked Cmix object ID. + - host - The URL to append the channel info to. + - maxUses - The maximum number of uses the link can be used (0 for + unlimited). + - marshalledChanId - A marshalled channel ID ([id.ID]). Returns: - - JSON of ShareURL. + - JSON of ShareURL. */ - (NSData* _Nullable)getShareURL:(long)cmixID host:(NSString* _Nullable)host maxUses:(long)maxUses marshalledChanId:(NSData* _Nullable)marshalledChanId error:(NSError* _Nullable* _Nullable)error; /** @@ -658,14 +675,15 @@ Returns: been joined. Parameters: - - channelPretty - A portable channel string. Should be received from - another user or generated via GenerateChannel. + - channelPretty - A portable channel string. Should be received from + another user or generated via GenerateChannel. The pretty print will be of the format: - <Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=> + + <Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=> Returns: - - []byte - JSON of [ChannelInfo], which describes all relevant channel info. + - []byte - JSON of [ChannelInfo], which describes all relevant channel info. */ - (NSData* _Nullable)joinChannel:(NSString* _Nullable)channelPretty error:(NSError* _Nullable* _Nullable)error; /** @@ -673,7 +691,7 @@ Returns: channel was not previously joined. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). */ - (BOOL)leaveChannel:(NSData* _Nullable)marshalledChanId error:(NSError* _Nullable* _Nullable)error; /** @@ -685,10 +703,10 @@ There can only be one handler per [channels.MessageType], and this will return an error on any re-registration. Parameters: - - messageType - represents the [channels.MessageType] which will have a - registered listener. - - listenerCb - the callback which will be executed when a channel message - of messageType is received. + - messageType - represents the [channels.MessageType] which will have a + registered listener. + - listenerCb - the callback which will be executed when a channel message + of messageType is received. */ - (BOOL)registerReceiveHandler:(long)messageType listenerCb:(id<BindingsChannelMessageReceptionCallback> _Nullable)listenerCb error:(NSError* _Nullable* _Nullable)error; /** @@ -696,7 +714,7 @@ Parameters: memory (~3 weeks) over the event model. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). */ - (BOOL)replayChannel:(NSData* _Nullable)marshalledChanId error:(NSError* _Nullable* _Nullable)error; /** @@ -707,23 +725,23 @@ before being sent over the wire, is too long, this will return an error. The message must be at most 510 bytes long. Parameters: - - adminPrivateKey - The PEM-encoded admin RSA private key. - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). - - messageType - The message type of the message. This will be a valid - [channels.MessageType]. - - message - The contents of the message. The message should be at most 510 - bytes. This need not be of data type string, as the message could be a - specified format that the channel may recognize. - - leaseTimeMS - The lease of the message. This will be how long the message - is valid until, in milliseconds. As per the channels.Manager - documentation, this has different meanings depending on the use case. - These use cases may be generic enough that they will not be enumerated - here. - - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, - and GetDefaultCMixParams will be used internally. + - adminPrivateKey - The PEM-encoded admin RSA private key. + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - messageType - The message type of the message. This will be a valid + [channels.MessageType]. + - message - The contents of the message. The message should be at most 510 + bytes. This need not be of data type string, as the message could be a + specified format that the channel may recognize. + - leaseTimeMS - The lease of the message. This will be how long the message + is valid until, in milliseconds. As per the channels.Manager + documentation, this has different meanings depending on the use case. + These use cases may be generic enough that they will not be enumerated + here. + - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, + and GetDefaultCMixParams will be used internally. Returns: - - []byte - A JSON marshalled ChannelSendReport. + - []byte - A JSON marshalled ChannelSendReport. */ - (NSData* _Nullable)sendAdminGeneric:(NSData* _Nullable)adminPrivateKey marshalledChanId:(NSData* _Nullable)marshalledChanId messageType:(long)messageType message:(NSData* _Nullable)message leaseTimeMS:(int64_t)leaseTimeMS cmixParamsJSON:(NSData* _Nullable)cmixParamsJSON error:(NSError* _Nullable* _Nullable)error; /** @@ -736,22 +754,22 @@ to send a payload of 802 bytes at minimum. The meaning of validUntil depends on the use case. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). - - messageType - The message type of the message. This will be a valid - [channels.MessageType]. - - message - The contents of the message. This need not be of data type - string, as the message could be a specified format that the channel may - recognize. - - leaseTimeMS - The lease of the message. This will be how long the message - is valid until, in milliseconds. As per the channels.Manager - documentation, this has different meanings depending on the use case. - These use cases may be generic enough that they will not be enumerated - here. - - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, - and GetDefaultCMixParams will be used internally. + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - messageType - The message type of the message. This will be a valid + [channels.MessageType]. + - message - The contents of the message. This need not be of data type + string, as the message could be a specified format that the channel may + recognize. + - leaseTimeMS - The lease of the message. This will be how long the message + is valid until, in milliseconds. As per the channels.Manager + documentation, this has different meanings depending on the use case. + These use cases may be generic enough that they will not be enumerated + here. + - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, + and GetDefaultCMixParams will be used internally. Returns: - - []byte - A JSON marshalled ChannelSendReport. + - []byte - A JSON marshalled ChannelSendReport. */ - (NSData* _Nullable)sendGeneric:(NSData* _Nullable)marshalledChanId messageType:(long)messageType message:(NSData* _Nullable)message leaseTimeMS:(int64_t)leaseTimeMS cmixParamsJSON:(NSData* _Nullable)cmixParamsJSON error:(NSError* _Nullable* _Nullable)error; /** @@ -764,20 +782,20 @@ The message will auto delete validUntil after the round it is sent in, lasting forever if [channels.ValidForever] is used. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). - - message - The contents of the message. The message should be at most 510 - bytes. This is expected to be Unicode, and thus a string data type is - expected - - leaseTimeMS - The lease of the message. This will be how long the message - is valid until, in milliseconds. As per the channels.Manager - documentation, this has different meanings depending on the use case. - These use cases may be generic enough that they will not be enumerated - here. - - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be - empty, and GetDefaultCMixParams will be used internally. + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - message - The contents of the message. The message should be at most 510 + bytes. This is expected to be Unicode, and thus a string data type is + expected + - leaseTimeMS - The lease of the message. This will be how long the message + is valid until, in milliseconds. As per the channels.Manager + documentation, this has different meanings depending on the use case. + These use cases may be generic enough that they will not be enumerated + here. + - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be + empty, and GetDefaultCMixParams will be used internally. Returns: - - []byte - A JSON marshalled ChannelSendReport + - []byte - A JSON marshalled ChannelSendReport */ - (NSData* _Nullable)sendMessage:(NSData* _Nullable)marshalledChanId message:(NSString* _Nullable)message leaseTimeMS:(int64_t)leaseTimeMS cmixParamsJSON:(NSData* _Nullable)cmixParamsJSON error:(NSError* _Nullable* _Nullable)error; /** @@ -787,19 +805,19 @@ be rejected otherwise. Users will drop the reaction if they do not recognize the reactTo message. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). - - reaction - The user's reaction. This should be a single emoji with no - other characters. As such, a Unicode string is expected. - - messageToReactTo - The marshalled [channel.MessageID] of the message you - wish to reply to. This may be found in the ChannelSendReport if replying - to your own. Alternatively, if reacting to another user's message, you may - retrieve it via the ChannelMessageReceptionCallback registered using - RegisterReceiveHandler. - - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, - and GetDefaultCMixParams will be used internally. + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - reaction - The user's reaction. This should be a single emoji with no + other characters. As such, a Unicode string is expected. + - messageToReactTo - The marshalled [channel.MessageID] of the message you + wish to reply to. This may be found in the ChannelSendReport if replying + to your own. Alternatively, if reacting to another user's message, you may + retrieve it via the ChannelMessageReceptionCallback registered using + RegisterReceiveHandler. + - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, + and GetDefaultCMixParams will be used internally. Returns: - - []byte - A JSON marshalled ChannelSendReport. + - []byte - A JSON marshalled ChannelSendReport. */ - (NSData* _Nullable)sendReaction:(NSData* _Nullable)marshalledChanId reaction:(NSString* _Nullable)reaction messageToReactTo:(NSData* _Nullable)messageToReactTo cmixParamsJSON:(NSData* _Nullable)cmixParamsJSON error:(NSError* _Nullable* _Nullable)error; /** @@ -814,25 +832,25 @@ delete validUntil after the round it is sent in, lasting forever if [channels.ValidForever] is used. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). - - message - The contents of the message. The message should be at most 510 - bytes. This is expected to be Unicode, and thus a string data type is - expected. - - messageToReactTo - The marshalled [channel.MessageID] of the message you - wish to reply to. This may be found in the ChannelSendReport if replying - to your own. Alternatively, if reacting to another user's message, you may - retrieve it via the ChannelMessageReceptionCallback registered using - RegisterReceiveHandler. - - leaseTimeMS - The lease of the message. This will be how long the message - is valid until, in milliseconds. As per the channels.Manager - documentation, this has different meanings depending on the use case. - These use cases may be generic enough that they will not be enumerated - here. - - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, - and GetDefaultCMixParams will be used internally. + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - message - The contents of the message. The message should be at most 510 + bytes. This is expected to be Unicode, and thus a string data type is + expected. + - messageToReactTo - The marshalled [channel.MessageID] of the message you + wish to reply to. This may be found in the ChannelSendReport if replying + to your own. Alternatively, if reacting to another user's message, you may + retrieve it via the ChannelMessageReceptionCallback registered using + RegisterReceiveHandler. + - leaseTimeMS - The lease of the message. This will be how long the message + is valid until, in milliseconds. As per the channels.Manager + documentation, this has different meanings depending on the use case. + These use cases may be generic enough that they will not be enumerated + here. + - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, + and GetDefaultCMixParams will be used internally. Returns: - - []byte - A JSON marshalled ChannelSendReport + - []byte - A JSON marshalled ChannelSendReport */ - (NSData* _Nullable)sendReply:(NSData* _Nullable)marshalledChanId message:(NSString* _Nullable)message messageToReactTo:(NSData* _Nullable)messageToReactTo leaseTimeMS:(int64_t)leaseTimeMS cmixParamsJSON:(NSData* _Nullable)cmixParamsJSON error:(NSError* _Nullable* _Nullable)error; /** @@ -2130,17 +2148,18 @@ channel the message was sent to and the message itself. This is returned via the callback as JSON marshalled bytes. JSON Example: - { - "ChannelId": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "MessageId": "3S6DiVjWH9mLmjy1oaam/3x45bJQzOW6u2KgeUn59wA=", - "ReplyTo":"cxMyGUFJ+Ff1Xp2X+XkIpOnNAQEZmv8SNP5eYH4tCik=", - "MessageType": 42, - "SenderUsername": "hunter2", - "Content": "YmFuX2JhZFVTZXI=", - "Timestamp": 1662502150335283000, - "Lease": 25, - "Rounds": [ 1, 4, 9], - } + + { + "ChannelId": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "MessageId": "3S6DiVjWH9mLmjy1oaam/3x45bJQzOW6u2KgeUn59wA=", + "ReplyTo":"cxMyGUFJ+Ff1Xp2X+XkIpOnNAQEZmv8SNP5eYH4tCik=", + "MessageType": 42, + "SenderUsername": "hunter2", + "Content": "YmFuX2JhZFVTZXI=", + "Timestamp": 1662502150335283000, + "Lease": 25, + "Rounds": [ 1, 4, 9], + } */ @interface BindingsReceivedChannelMessageReport : NSObject <goSeqRefInterface> { } @@ -2271,22 +2290,25 @@ JSON Example: channel's share URL and password, if it needs one. JSON example for a public channel: - { - "url": "https://internet.speakeasy.tech/?0Name=name&1Description=desc&2Level=Public&3Created=1665489600000000000&e=%2FWNZvuHPuv%2Bx23XbZXVNzCi7y8rUSxkh75MpR9UrsCo%3D&k=ddX1CH52xH%2F%2Fb6lKrbvDghdSmCQr90ktsOAZ%2FrhEonI%3D&l=2&m=0&p=328&s=%2FD%2FoQP2mio3XAWfhmWF0xmZrpj4nAsb9JLXj%2B0Mzq9Y%3D&v=1", - "password": "" - } + + { + "url": "https://internet.speakeasy.tech/?0Name=name&1Description=desc&2Level=Public&3Created=1665489600000000000&e=%2FWNZvuHPuv%2Bx23XbZXVNzCi7y8rUSxkh75MpR9UrsCo%3D&k=ddX1CH52xH%2F%2Fb6lKrbvDghdSmCQr90ktsOAZ%2FrhEonI%3D&l=2&m=0&p=328&s=%2FD%2FoQP2mio3XAWfhmWF0xmZrpj4nAsb9JLXj%2B0Mzq9Y%3D&v=1", + "password": "" + } JSON example for a private channel: - { - "url": "https://internet.speakeasy.tech/?0Name=name&1Description=desc&3Created=1665489600000000000&d=5AZQirb%2FYrmUITLn%2FFzCaGek1APfJnd2q0KwORGj%2BnbGg26kTShG6cfD3w6c%2BA3RDzxuKDSDN0zS4n1LbjiGe0KYdb8eJVeyRZtld516hfojNDXNAwZq8zbeZy4jjbF627fcLHRNS%2FaII4uJ5UB3gLUeBeZGraaybCCu3FIj1N4RbcJ5cQgT7hBf93bHmJc%3D&m=0&v=1", - "password": "tribune gangrene labrador italics nutmeg process exhume legal" - } + + { + "url": "https://internet.speakeasy.tech/?0Name=name&1Description=desc&3Created=1665489600000000000&d=5AZQirb%2FYrmUITLn%2FFzCaGek1APfJnd2q0KwORGj%2BnbGg26kTShG6cfD3w6c%2BA3RDzxuKDSDN0zS4n1LbjiGe0KYdb8eJVeyRZtld516hfojNDXNAwZq8zbeZy4jjbF627fcLHRNS%2FaII4uJ5UB3gLUeBeZGraaybCCu3FIj1N4RbcJ5cQgT7hBf93bHmJc%3D&m=0&v=1", + "password": "tribune gangrene labrador italics nutmeg process exhume legal" + } JSON example for a secret channel: - { - "url": "https://internet.speakeasy.tech/?d=w5evLthm%2Fq2j11g6PPtV0QoLaAqNCIER0OqxhxL%2FhpGVJI0057ZPgGBrKoJNE1%2FdoVuU35%2FhohuW%2BWvGlx6IuHoN6mDj0HfNj6Lo%2B8GwIaD6jOEwUcH%2FMKGsKnoqFsMaMPd5gXYgdHvA8l5SRe0gSCVqGKUaG6JgL%2FDu4iyjY7v4ykwZdQ7soWOcBLHDixGEkVLpwsCrPVHkT2K0W6gV74GIrQ%3D%3D&m=0&v=1", - "password": "frenzy contort staple thicket consuming affiliate scion demeanor" - } + + { + "url": "https://internet.speakeasy.tech/?d=w5evLthm%2Fq2j11g6PPtV0QoLaAqNCIER0OqxhxL%2FhpGVJI0057ZPgGBrKoJNE1%2FdoVuU35%2FhohuW%2BWvGlx6IuHoN6mDj0HfNj6Lo%2B8GwIaD6jOEwUcH%2FMKGsKnoqFsMaMPd5gXYgdHvA8l5SRe0gSCVqGKUaG6JgL%2FDu4iyjY7v4ykwZdQ7soWOcBLHDixGEkVLpwsCrPVHkT2K0W6gV74GIrQ%3D%3D&m=0&v=1", + "password": "frenzy contort staple thicket consuming affiliate scion demeanor" + } */ @interface BindingsShareURL : NSObject <goSeqRefInterface> { } @@ -2481,11 +2503,11 @@ FOUNDATION_EXPORT BOOL BindingsAsyncRequestRestLike(long e2eID, NSData* _Nullabl and codeset version. Parameters: - - pubKey - The Ed25519 public key. - - codesetVersion - The version of the codeset used to generate the identity. + - pubKey - The Ed25519 public key. + - codesetVersion - The version of the codeset used to generate the identity. Returns: - - JSON of [channel.Identity]. + - JSON of [channel.Identity]. */ FOUNDATION_EXPORT NSData* _Nullable BindingsConstructIdentity(NSData* _Nullable pubKey, long codesetVersion, NSError* _Nullable* _Nullable error); @@ -2512,12 +2534,12 @@ pretty print. This function can only be used for private or secret channel URLs. To get the privacy level of a channel URL, use [GetShareUrlType]. Parameters: - - url - The channel's share URL. Should be received from another user or - generated via [GetShareURL]. - - password - The password needed to decrypt the secret data in the URL. + - url - The channel's share URL. Should be received from another user or + generated via [GetShareURL]. + - password - The password needed to decrypt the secret data in the URL. Returns: - - The channel pretty print. + - The channel pretty print. */ FOUNDATION_EXPORT NSString* _Nonnull BindingsDecodePrivateURL(NSString* _Nullable url, NSString* _Nullable password, NSError* _Nullable* _Nullable error); @@ -2527,11 +2549,11 @@ function can only be used for public channel URLs. To get the privacy level of a channel URL, use [GetShareUrlType]. Parameters: - - url - The channel's share URL. Should be received from another user or - generated via [GetShareURL]. + - url - The channel's share URL. Should be received from another user or + generated via [GetShareURL]. Returns: - - The channel pretty print. + - The channel pretty print. */ FOUNDATION_EXPORT NSString* _Nonnull BindingsDecodePublicURL(NSString* _Nullable url, NSError* _Nullable* _Nullable error); @@ -2561,33 +2583,33 @@ the admin. It is only for making new channels, not joining existing ones. It returns a pretty print of the channel and the private key. Parameters: - - cmixID - The tracked cmix object ID. This can be retrieved using - [Cmix.GetID]. - - name - The name of the new channel. The name must be between 3 and 24 - characters inclusive. It can only include upper and lowercase unicode - letters, digits 0 through 9, and underscores (_). It cannot be changed - once a channel is created. - - description - The description of a channel. The description is optional - but cannot be longer than 144 characters and can include all unicode - characters. It cannot be changed once a channel is created. - - privacyLevel - The broadcast.PrivacyLevel of the channel. 0 = public, - 1 = private, and 2 = secret. Refer to the comment below for more - information. + - cmixID - The tracked cmix object ID. This can be retrieved using + [Cmix.GetID]. + - name - The name of the new channel. The name must be between 3 and 24 + characters inclusive. It can only include upper and lowercase unicode + letters, digits 0 through 9, and underscores (_). It cannot be changed + once a channel is created. + - description - The description of a channel. The description is optional + but cannot be longer than 144 characters and can include all unicode + characters. It cannot be changed once a channel is created. + - privacyLevel - The broadcast.PrivacyLevel of the channel. 0 = public, + 1 = private, and 2 = secret. Refer to the comment below for more + information. Returns: - - []byte - [ChannelGeneration] describes a generated channel. It contains - both the public channel info and the private key for the channel in PEM - format. + - []byte - [ChannelGeneration] describes a generated channel. It contains + both the public channel info and the private key for the channel in PEM + format. The [broadcast.PrivacyLevel] of a channel indicates the level of channel information revealed when sharing it via URL. For any channel besides public channels, the secret information is encrypted and a password is required to share and join a channel. - - A privacy level of [broadcast.Public] reveals all the information - including the name, description, privacy level, public key and salt. - - A privacy level of [broadcast.Private] reveals only the name and - description. - - A privacy level of [broadcast.Secret] reveals nothing. + - A privacy level of [broadcast.Public] reveals all the information + including the name, description, privacy level, public key and salt. + - A privacy level of [broadcast.Private] reveals only the name and + description. + - A privacy level of [broadcast.Secret] reveals nothing. */ FOUNDATION_EXPORT NSData* _Nullable BindingsGenerateChannel(long cmixID, NSString* _Nullable name, NSString* _Nullable description, long privacyLevel, NSError* _Nullable* _Nullable error); @@ -2597,11 +2619,11 @@ FOUNDATION_EXPORT NSData* _Nullable BindingsGenerateChannel(long cmixID, NSStrin via [GetPublicChannelIdentityFromPrivate]. Parameters: - - cmixID - The tracked cmix object ID. This can be retrieved using - [Cmix.GetID]. + - cmixID - The tracked cmix object ID. This can be retrieved using + [Cmix.GetID]. Returns: - - Marshalled bytes of [channel.PrivateIdentity]. + - Marshalled bytes of [channel.PrivateIdentity]. */ FOUNDATION_EXPORT NSData* _Nullable BindingsGenerateChannelIdentity(long cmixID, NSError* _Nullable* _Nullable error); @@ -2625,13 +2647,14 @@ FOUNDATION_EXPORT BindingsChannelDbCipher* _Nullable BindingsGetChannelDbCipherT * GetChannelInfo returns the info about a channel from its public description. Parameters: - - prettyPrint - The pretty print of the channel. + - prettyPrint - The pretty print of the channel. The pretty print will be of the format: - <Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=> + + <Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=> Returns: - - []byte - JSON of [ChannelInfo], which describes all relevant channel info. + - []byte - JSON of [ChannelInfo], which describes all relevant channel info. */ FOUNDATION_EXPORT NSData* _Nullable BindingsGetChannelInfo(NSString* _Nullable prettyPrint, NSError* _Nullable* _Nullable error); @@ -2639,23 +2662,24 @@ FOUNDATION_EXPORT NSData* _Nullable BindingsGetChannelInfo(NSString* _Nullable p * GetChannelJSON returns the JSON of the channel for the given pretty print. Parameters: - - prettyPrint - The pretty print of the channel. + - prettyPrint - The pretty print of the channel. Returns: - - JSON of the [broadcast.Channel] object. + - JSON of the [broadcast.Channel] object. Example JSON of [broadcast.Channel]: - { - "ReceptionID": "Ja/+Jh+1IXZYUOn+IzE3Fw/VqHOscomD0Q35p4Ai//kD", - "Name": "My_Channel", - "Description": "Here is information about my channel.", - "Salt": "+tlrU/htO6rrV3UFDfpQALUiuelFZ+Cw9eZCwqRHk+g=", - "RsaPubKeyHash": "PViT1mYkGBj6AYmE803O2RpA7BX24EjgBdldu3pIm4o=", - "RsaPubKeyLength": 5, - "RSASubPayloads": 1, - "Secret": "JxZt/wPx2luoPdHY6jwbXqNlKnixVU/oa9DgypZOuyI=", - "Level": 0 - } + + { + "ReceptionID": "Ja/+Jh+1IXZYUOn+IzE3Fw/VqHOscomD0Q35p4Ai//kD", + "Name": "My_Channel", + "Description": "Here is information about my channel.", + "Salt": "+tlrU/htO6rrV3UFDfpQALUiuelFZ+Cw9eZCwqRHk+g=", + "RsaPubKeyHash": "PViT1mYkGBj6AYmE803O2RpA7BX24EjgBdldu3pIm4o=", + "RsaPubKeyLength": 5, + "RSASubPayloads": 1, + "Secret": "JxZt/wPx2luoPdHY6jwbXqNlKnixVU/oa9DgypZOuyI=", + "Level": 0 + } */ FOUNDATION_EXPORT NSData* _Nullable BindingsGetChannelJSON(NSString* _Nullable prettyPrint, NSError* _Nullable* _Nullable error); @@ -2762,10 +2786,10 @@ FOUNDATION_EXPORT NSData* _Nullable BindingsGetPubkeyFromContact(NSData* _Nullab from a bytes version and returns it JSON marshaled. Parameters: - - marshaledPublic - Bytes of the public identity ([channel.Identity]). + - marshaledPublic - Bytes of the public identity ([channel.Identity]). Returns: - - JSON of the constructed [channel.Identity]. + - JSON of the constructed [channel.Identity]. */ FOUNDATION_EXPORT NSData* _Nullable BindingsGetPublicChannelIdentity(NSData* _Nullable marshaledPublic, NSError* _Nullable* _Nullable error); @@ -2775,11 +2799,11 @@ FOUNDATION_EXPORT NSData* _Nullable BindingsGetPublicChannelIdentity(NSData* _Nu ([channel.PrivateIdentity]). Parameters: - - marshaledPrivate - Bytes of the private identity - (channel.PrivateIdentity]). + - marshaledPrivate - Bytes of the private identity + (channel.PrivateIdentity]). Returns: - - JSON of the public [channel.Identity]. + - JSON of the public [channel.Identity]. */ FOUNDATION_EXPORT NSData* _Nullable BindingsGetPublicChannelIdentityFromPrivate(NSData* _Nullable marshaledPrivate, NSError* _Nullable* _Nullable error); @@ -2790,11 +2814,11 @@ given channel ID. NOTE: This function is unsafe and only for debugging purposes only. Parameters: - - cmixID - ID of [Cmix] object in tracker. - - channelIdBase64 - The [id.ID] of the channel in base 64 encoding. + - cmixID - ID of [Cmix] object in tracker. + - channelIdBase64 - The [id.ID] of the channel in base 64 encoding. Returns: - - The PEM file of the private key. + - The PEM file of the private key. */ FOUNDATION_EXPORT NSString* _Nonnull BindingsGetSavedChannelPrivateKeyUNSAFE(long cmixID, NSString* _Nullable channelIdBase64, NSError* _Nullable* _Nullable error); @@ -2803,15 +2827,16 @@ FOUNDATION_EXPORT NSString* _Nonnull BindingsGetSavedChannelPrivateKeyUNSAFE(lon If the URL is an invalid channel URL, an error is returned. Parameters: - - url - The channel share URL. + - url - The channel share URL. Returns: - - An int that corresponds to the [broadcast.PrivacyLevel] as outlined below. + - An int that corresponds to the [broadcast.PrivacyLevel] as outlined below. Possible returns: - 0 = public channel - 1 = private channel - 2 = secret channel + + 0 = public channel + 1 = private channel + 2 = secret channel */ FOUNDATION_EXPORT BOOL BindingsGetShareUrlType(NSString* _Nullable url, long* _Nullable ret0_, NSError* _Nullable* _Nullable error); @@ -2825,11 +2850,11 @@ FOUNDATION_EXPORT NSString* _Nonnull BindingsGetVersion(void); data. Parameters: - - password - The password used to encrypt the identity. - - data - The encrypted data. + - password - The password used to encrypt the identity. + - data - The encrypted data. Returns: - - JSON of [channel.PrivateIdentity]. + - JSON of [channel.PrivateIdentity]. */ FOUNDATION_EXPORT NSData* _Nullable BindingsImportPrivateIdentity(NSString* _Nullable password, NSData* _Nullable data, NSError* _Nullable* _Nullable error); @@ -2909,12 +2934,12 @@ The channel manager should have previously been created with [ChannelsManager.GetStorageTag]. Parameters: - - cmixID - The tracked cmix object ID. This can be retrieved using - [Cmix.GetID]. - - storageTag - The storage tag associated with the previously created - channel manager and retrieved with [ChannelsManager.GetStorageTag]. - - event - An interface that contains a function that initialises and returns - the event model that is bindings-compatible. + - cmixID - The tracked cmix object ID. This can be retrieved using + [Cmix.GetID]. + - storageTag - The storage tag associated with the previously created + channel manager and retrieved with [ChannelsManager.GetStorageTag]. + - event - An interface that contains a function that initialises and returns + the event model that is bindings-compatible. */ FOUNDATION_EXPORT BindingsChannelsManager* _Nullable BindingsLoadChannelsManager(long cmixID, NSString* _Nullable storageTag, id<BindingsEventModelBuilder> _Nullable eventBuilder, NSError* _Nullable* _Nullable error); @@ -3014,12 +3039,12 @@ FOUNDATION_EXPORT BOOL BindingsMultiLookupUD(long e2eID, NSData* _Nullable udCon * NewChannelsDatabaseCipher constructs a ChannelDbCipher object. Parameters: - - cmixID - The tracked [Cmix] object ID. - - password - The password for storage. This should be the same password - passed into [NewCmix]. - - plaintTextBlockSize - The maximum size of a payload to be encrypted. - A payload passed into [ChannelDbCipher.Encrypt] that is larger than - plaintTextBlockSize will result in an error. + - cmixID - The tracked [Cmix] object ID. + - password - The password for storage. This should be the same password + passed into [NewCmix]. + - plaintTextBlockSize - The maximum size of a payload to be encrypted. + A payload passed into [ChannelDbCipher.Encrypt] that is larger than + plaintTextBlockSize will result in an error. */ FOUNDATION_EXPORT BindingsChannelDbCipher* _Nullable BindingsNewChannelsDatabaseCipher(long cmixID, NSData* _Nullable password, long plaintTextBlockSize, NSError* _Nullable* _Nullable error); @@ -3033,12 +3058,12 @@ reload this channel manager, use [LoadChannelsManager], passing in the storage tag retrieved by [ChannelsManager.GetStorageTag]. Parameters: - - cmixID - The tracked Cmix object ID. This can be retrieved using - [Cmix.GetID]. - - privateIdentity - Bytes of a private identity ([channel.PrivateIdentity]) - that is generated by [GenerateChannelIdentity]. - - event - An interface that contains a function that initialises and returns - the event model that is bindings-compatible. + - cmixID - The tracked Cmix object ID. This can be retrieved using + [Cmix.GetID]. + - privateIdentity - Bytes of a private identity ([channel.PrivateIdentity]) + that is generated by [GenerateChannelIdentity]. + - event - An interface that contains a function that initialises and returns + the event model that is bindings-compatible. */ FOUNDATION_EXPORT BindingsChannelsManager* _Nullable BindingsNewChannelsManager(long cmixID, NSData* _Nullable privateIdentity, id<BindingsEventModelBuilder> _Nullable eventBuilder, NSError* _Nullable* _Nullable error); diff --git a/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Bindings b/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Bindings index ecd3142a1f4d3259c940950b488d71d38794a272..d2b36722d13be6fb98007d8fa0d902553ba0a5fe 100644 Binary files a/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Bindings and b/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Bindings differ diff --git a/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Headers/Bindings.objc.h b/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Headers/Bindings.objc.h index dcf46cd4439785f4f904ea1f41277a66ed3e7db9..29b692cb622e84ca73a5d29b1f3954765ffb29db 100644 --- a/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Headers/Bindings.objc.h +++ b/Frameworks/Bindings.xcframework/macos-arm64_x86_64/Bindings.framework/Versions/A/Headers/Bindings.objc.h @@ -470,7 +470,7 @@ be returned by this function. Any padding will be discarded within this function. Parameters: - - ciphertext - the encrypted data returned by [ChannelDbCipher.Encrypt]. + - ciphertext - the encrypted data returned by [ChannelDbCipher.Encrypt]. */ - (NSData* _Nullable)decrypt:(NSData* _Nullable)ciphertext error:(NSError* _Nullable* _Nullable)error; /** @@ -478,15 +478,28 @@ Parameters: done on the plaintext so all encrypted data looks uniform at rest. Parameters: - - plaintext - The data to be encrypted. This must be smaller than the block - size passed into [NewChannelsDatabaseCipher]. If it is larger, this will - return an error. + - plaintext - The data to be encrypted. This must be smaller than the block + size passed into [NewChannelsDatabaseCipher]. If it is larger, this will + return an error. */ - (NSData* _Nullable)encrypt:(NSData* _Nullable)plaintext error:(NSError* _Nullable* _Nullable)error; /** * GetID returns the ID for this ChannelDbCipher in the channelDbCipherTracker. */ - (long)getID; +/** + * MarshalJSON marshals the cipher into valid JSON. This function adheres to the +json.Marshaler interface. + */ +- (NSData* _Nullable)marshalJSON:(NSError* _Nullable* _Nullable)error; +/** + * UnmarshalJSON unmarshalls JSON into the cipher. This function adheres to the +json.Unmarshaler interface. + +Note that this function does not transfer the internal RNG. Use +NewCipherFromJSON to properly reconstruct a cipher from JSON. + */ +- (BOOL)unmarshalJSON:(NSData* _Nullable)data error:(NSError* _Nullable* _Nullable)error; @end /** @@ -495,10 +508,11 @@ contains the public channel info formatted in pretty print and the private key for the channel in PEM format. Example JSON: - { - "Channel": "\u003cSpeakeasy-v3:name|description:desc|level:Public|created:1665489600000000000|secrets:zjHmrPPMDQ0tNSANjAmQfKhRpJIdJMU+Hz5hsZ+fVpk=|qozRNkADprqb38lsnU7WxCtGCq9OChlySCEgl4NHjI4=|2|328|7aZQAtuVjE84q4Z09iGytTSXfZj9NyTa6qBp0ueKjCI=\u003e", - "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMCYCAQACAwDVywIDAQABAgMAlVECAgDvAgIA5QICAJECAgCVAgIA1w==\n-----END RSA PRIVATE KEY-----" - } + + { + "Channel": "\u003cSpeakeasy-v3:name|description:desc|level:Public|created:1665489600000000000|secrets:zjHmrPPMDQ0tNSANjAmQfKhRpJIdJMU+Hz5hsZ+fVpk=|qozRNkADprqb38lsnU7WxCtGCq9OChlySCEgl4NHjI4=|2|328|7aZQAtuVjE84q4Z09iGytTSXfZj9NyTa6qBp0ueKjCI=\u003e", + "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMCYCAQACAwDVywIDAQABAgMAlVECAgDvAgIA5QICAJECAgCVAgIA1w==\n-----END RSA PRIVATE KEY-----" + } */ @interface BindingsChannelGeneration : NSObject <goSeqRefInterface> { } @@ -514,11 +528,12 @@ Example JSON: * ChannelInfo contains information about a channel. Example of ChannelInfo JSON: - { - "Name": "Test Channel", - "Description": "This is a test channel", - "ChannelID": "RRnpRhmvXtW9ugS1nILJ3WfttdctDvC2jeuH43E0g/0D", - } + + { + "Name": "Test Channel", + "Description": "This is a test channel", + "ChannelID": "RRnpRhmvXtW9ugS1nILJ3WfttdctDvC2jeuH43E0g/0D", + } */ @interface BindingsChannelInfo : NSObject <goSeqRefInterface> { } @@ -536,11 +551,12 @@ Example of ChannelInfo JSON: ChannelsManager's Send operations. JSON Example: - { - "MessageId": "0kitNxoFdsF4q1VMSI/xPzfCnGB2l+ln2+7CTHjHbJw=", - "Rounds":[1,5,9], - "EphId": 0 - } + + { + "MessageId": "0kitNxoFdsF4q1VMSI/xPzfCnGB2l+ln2+7CTHjHbJw=", + "Rounds":[1,5,9], + "EphId": 0 + } */ @interface BindingsChannelSendReport : NSObject <goSeqRefInterface> { } @@ -574,12 +590,12 @@ reload this channel manager, use [LoadChannelsManager], passing in the storage tag retrieved by [ChannelsManager.GetStorageTag]. Parameters: - - cmixID - The tracked Cmix object ID. This can be retrieved using - [Cmix.GetID]. - - privateIdentity - Bytes of a private identity ([channel.PrivateIdentity]) - that is generated by [GenerateChannelIdentity]. - - event - An interface that contains a function that initialises and returns - the event model that is bindings-compatible. + - cmixID - The tracked Cmix object ID. This can be retrieved using + [Cmix.GetID]. + - privateIdentity - Bytes of a private identity ([channel.PrivateIdentity]) + that is generated by [GenerateChannelIdentity]. + - event - An interface that contains a function that initialises and returns + the event model that is bindings-compatible. */ - (nullable instancetype)init:(long)cmixID privateIdentity:(NSData* _Nullable)privateIdentity eventBuilder:(id<BindingsEventModelBuilder> _Nullable)eventBuilder; // skipped constructor ChannelsManager.NewChannelsManagerGoEventModel with unsupported parameter or return types @@ -597,13 +613,14 @@ string. * GetChannels returns the IDs of all channels that have been joined. Returns: - - []byte - A JSON marshalled list of IDs. + - []byte - A JSON marshalled list of IDs. JSON Example: - { - "U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID", - "15tNdkKbYXoMn58NO6VbDMDWFEyIhTWEGsvgcJsHWAgD" - } + + { + "U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID", + "15tNdkKbYXoMn58NO6VbDMDWFEyIhTWEGsvgcJsHWAgD" + } */ - (NSData* _Nullable)getChannels:(NSError* _Nullable* _Nullable)error; /** @@ -639,14 +656,14 @@ calling [ChannelsManager.JoinChannelFromURL]. There is no enforcement for public URLs. Parameters: - - cmixID - The tracked Cmix object ID. - - host - The URL to append the channel info to. - - maxUses - The maximum number of uses the link can be used (0 for - unlimited). - - marshalledChanId - A marshalled channel ID ([id.ID]). + - cmixID - The tracked Cmix object ID. + - host - The URL to append the channel info to. + - maxUses - The maximum number of uses the link can be used (0 for + unlimited). + - marshalledChanId - A marshalled channel ID ([id.ID]). Returns: - - JSON of ShareURL. + - JSON of ShareURL. */ - (NSData* _Nullable)getShareURL:(long)cmixID host:(NSString* _Nullable)host maxUses:(long)maxUses marshalledChanId:(NSData* _Nullable)marshalledChanId error:(NSError* _Nullable* _Nullable)error; /** @@ -658,14 +675,15 @@ Returns: been joined. Parameters: - - channelPretty - A portable channel string. Should be received from - another user or generated via GenerateChannel. + - channelPretty - A portable channel string. Should be received from + another user or generated via GenerateChannel. The pretty print will be of the format: - <Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=> + + <Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=> Returns: - - []byte - JSON of [ChannelInfo], which describes all relevant channel info. + - []byte - JSON of [ChannelInfo], which describes all relevant channel info. */ - (NSData* _Nullable)joinChannel:(NSString* _Nullable)channelPretty error:(NSError* _Nullable* _Nullable)error; /** @@ -673,7 +691,7 @@ Returns: channel was not previously joined. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). */ - (BOOL)leaveChannel:(NSData* _Nullable)marshalledChanId error:(NSError* _Nullable* _Nullable)error; /** @@ -685,10 +703,10 @@ There can only be one handler per [channels.MessageType], and this will return an error on any re-registration. Parameters: - - messageType - represents the [channels.MessageType] which will have a - registered listener. - - listenerCb - the callback which will be executed when a channel message - of messageType is received. + - messageType - represents the [channels.MessageType] which will have a + registered listener. + - listenerCb - the callback which will be executed when a channel message + of messageType is received. */ - (BOOL)registerReceiveHandler:(long)messageType listenerCb:(id<BindingsChannelMessageReceptionCallback> _Nullable)listenerCb error:(NSError* _Nullable* _Nullable)error; /** @@ -696,7 +714,7 @@ Parameters: memory (~3 weeks) over the event model. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). */ - (BOOL)replayChannel:(NSData* _Nullable)marshalledChanId error:(NSError* _Nullable* _Nullable)error; /** @@ -707,23 +725,23 @@ before being sent over the wire, is too long, this will return an error. The message must be at most 510 bytes long. Parameters: - - adminPrivateKey - The PEM-encoded admin RSA private key. - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). - - messageType - The message type of the message. This will be a valid - [channels.MessageType]. - - message - The contents of the message. The message should be at most 510 - bytes. This need not be of data type string, as the message could be a - specified format that the channel may recognize. - - leaseTimeMS - The lease of the message. This will be how long the message - is valid until, in milliseconds. As per the channels.Manager - documentation, this has different meanings depending on the use case. - These use cases may be generic enough that they will not be enumerated - here. - - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, - and GetDefaultCMixParams will be used internally. + - adminPrivateKey - The PEM-encoded admin RSA private key. + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - messageType - The message type of the message. This will be a valid + [channels.MessageType]. + - message - The contents of the message. The message should be at most 510 + bytes. This need not be of data type string, as the message could be a + specified format that the channel may recognize. + - leaseTimeMS - The lease of the message. This will be how long the message + is valid until, in milliseconds. As per the channels.Manager + documentation, this has different meanings depending on the use case. + These use cases may be generic enough that they will not be enumerated + here. + - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, + and GetDefaultCMixParams will be used internally. Returns: - - []byte - A JSON marshalled ChannelSendReport. + - []byte - A JSON marshalled ChannelSendReport. */ - (NSData* _Nullable)sendAdminGeneric:(NSData* _Nullable)adminPrivateKey marshalledChanId:(NSData* _Nullable)marshalledChanId messageType:(long)messageType message:(NSData* _Nullable)message leaseTimeMS:(int64_t)leaseTimeMS cmixParamsJSON:(NSData* _Nullable)cmixParamsJSON error:(NSError* _Nullable* _Nullable)error; /** @@ -736,22 +754,22 @@ to send a payload of 802 bytes at minimum. The meaning of validUntil depends on the use case. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). - - messageType - The message type of the message. This will be a valid - [channels.MessageType]. - - message - The contents of the message. This need not be of data type - string, as the message could be a specified format that the channel may - recognize. - - leaseTimeMS - The lease of the message. This will be how long the message - is valid until, in milliseconds. As per the channels.Manager - documentation, this has different meanings depending on the use case. - These use cases may be generic enough that they will not be enumerated - here. - - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, - and GetDefaultCMixParams will be used internally. + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - messageType - The message type of the message. This will be a valid + [channels.MessageType]. + - message - The contents of the message. This need not be of data type + string, as the message could be a specified format that the channel may + recognize. + - leaseTimeMS - The lease of the message. This will be how long the message + is valid until, in milliseconds. As per the channels.Manager + documentation, this has different meanings depending on the use case. + These use cases may be generic enough that they will not be enumerated + here. + - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, + and GetDefaultCMixParams will be used internally. Returns: - - []byte - A JSON marshalled ChannelSendReport. + - []byte - A JSON marshalled ChannelSendReport. */ - (NSData* _Nullable)sendGeneric:(NSData* _Nullable)marshalledChanId messageType:(long)messageType message:(NSData* _Nullable)message leaseTimeMS:(int64_t)leaseTimeMS cmixParamsJSON:(NSData* _Nullable)cmixParamsJSON error:(NSError* _Nullable* _Nullable)error; /** @@ -764,20 +782,20 @@ The message will auto delete validUntil after the round it is sent in, lasting forever if [channels.ValidForever] is used. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). - - message - The contents of the message. The message should be at most 510 - bytes. This is expected to be Unicode, and thus a string data type is - expected - - leaseTimeMS - The lease of the message. This will be how long the message - is valid until, in milliseconds. As per the channels.Manager - documentation, this has different meanings depending on the use case. - These use cases may be generic enough that they will not be enumerated - here. - - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be - empty, and GetDefaultCMixParams will be used internally. + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - message - The contents of the message. The message should be at most 510 + bytes. This is expected to be Unicode, and thus a string data type is + expected + - leaseTimeMS - The lease of the message. This will be how long the message + is valid until, in milliseconds. As per the channels.Manager + documentation, this has different meanings depending on the use case. + These use cases may be generic enough that they will not be enumerated + here. + - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be + empty, and GetDefaultCMixParams will be used internally. Returns: - - []byte - A JSON marshalled ChannelSendReport + - []byte - A JSON marshalled ChannelSendReport */ - (NSData* _Nullable)sendMessage:(NSData* _Nullable)marshalledChanId message:(NSString* _Nullable)message leaseTimeMS:(int64_t)leaseTimeMS cmixParamsJSON:(NSData* _Nullable)cmixParamsJSON error:(NSError* _Nullable* _Nullable)error; /** @@ -787,19 +805,19 @@ be rejected otherwise. Users will drop the reaction if they do not recognize the reactTo message. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). - - reaction - The user's reaction. This should be a single emoji with no - other characters. As such, a Unicode string is expected. - - messageToReactTo - The marshalled [channel.MessageID] of the message you - wish to reply to. This may be found in the ChannelSendReport if replying - to your own. Alternatively, if reacting to another user's message, you may - retrieve it via the ChannelMessageReceptionCallback registered using - RegisterReceiveHandler. - - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, - and GetDefaultCMixParams will be used internally. + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - reaction - The user's reaction. This should be a single emoji with no + other characters. As such, a Unicode string is expected. + - messageToReactTo - The marshalled [channel.MessageID] of the message you + wish to reply to. This may be found in the ChannelSendReport if replying + to your own. Alternatively, if reacting to another user's message, you may + retrieve it via the ChannelMessageReceptionCallback registered using + RegisterReceiveHandler. + - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, + and GetDefaultCMixParams will be used internally. Returns: - - []byte - A JSON marshalled ChannelSendReport. + - []byte - A JSON marshalled ChannelSendReport. */ - (NSData* _Nullable)sendReaction:(NSData* _Nullable)marshalledChanId reaction:(NSString* _Nullable)reaction messageToReactTo:(NSData* _Nullable)messageToReactTo cmixParamsJSON:(NSData* _Nullable)cmixParamsJSON error:(NSError* _Nullable* _Nullable)error; /** @@ -814,25 +832,25 @@ delete validUntil after the round it is sent in, lasting forever if [channels.ValidForever] is used. Parameters: - - marshalledChanId - A JSON marshalled channel ID ([id.ID]). - - message - The contents of the message. The message should be at most 510 - bytes. This is expected to be Unicode, and thus a string data type is - expected. - - messageToReactTo - The marshalled [channel.MessageID] of the message you - wish to reply to. This may be found in the ChannelSendReport if replying - to your own. Alternatively, if reacting to another user's message, you may - retrieve it via the ChannelMessageReceptionCallback registered using - RegisterReceiveHandler. - - leaseTimeMS - The lease of the message. This will be how long the message - is valid until, in milliseconds. As per the channels.Manager - documentation, this has different meanings depending on the use case. - These use cases may be generic enough that they will not be enumerated - here. - - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, - and GetDefaultCMixParams will be used internally. + - marshalledChanId - A JSON marshalled channel ID ([id.ID]). + - message - The contents of the message. The message should be at most 510 + bytes. This is expected to be Unicode, and thus a string data type is + expected. + - messageToReactTo - The marshalled [channel.MessageID] of the message you + wish to reply to. This may be found in the ChannelSendReport if replying + to your own. Alternatively, if reacting to another user's message, you may + retrieve it via the ChannelMessageReceptionCallback registered using + RegisterReceiveHandler. + - leaseTimeMS - The lease of the message. This will be how long the message + is valid until, in milliseconds. As per the channels.Manager + documentation, this has different meanings depending on the use case. + These use cases may be generic enough that they will not be enumerated + here. + - cmixParamsJSON - A JSON marshalled [xxdk.CMIXParams]. This may be empty, + and GetDefaultCMixParams will be used internally. Returns: - - []byte - A JSON marshalled ChannelSendReport + - []byte - A JSON marshalled ChannelSendReport */ - (NSData* _Nullable)sendReply:(NSData* _Nullable)marshalledChanId message:(NSString* _Nullable)message messageToReactTo:(NSData* _Nullable)messageToReactTo leaseTimeMS:(int64_t)leaseTimeMS cmixParamsJSON:(NSData* _Nullable)cmixParamsJSON error:(NSError* _Nullable* _Nullable)error; /** @@ -2130,17 +2148,18 @@ channel the message was sent to and the message itself. This is returned via the callback as JSON marshalled bytes. JSON Example: - { - "ChannelId": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "MessageId": "3S6DiVjWH9mLmjy1oaam/3x45bJQzOW6u2KgeUn59wA=", - "ReplyTo":"cxMyGUFJ+Ff1Xp2X+XkIpOnNAQEZmv8SNP5eYH4tCik=", - "MessageType": 42, - "SenderUsername": "hunter2", - "Content": "YmFuX2JhZFVTZXI=", - "Timestamp": 1662502150335283000, - "Lease": 25, - "Rounds": [ 1, 4, 9], - } + + { + "ChannelId": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "MessageId": "3S6DiVjWH9mLmjy1oaam/3x45bJQzOW6u2KgeUn59wA=", + "ReplyTo":"cxMyGUFJ+Ff1Xp2X+XkIpOnNAQEZmv8SNP5eYH4tCik=", + "MessageType": 42, + "SenderUsername": "hunter2", + "Content": "YmFuX2JhZFVTZXI=", + "Timestamp": 1662502150335283000, + "Lease": 25, + "Rounds": [ 1, 4, 9], + } */ @interface BindingsReceivedChannelMessageReport : NSObject <goSeqRefInterface> { } @@ -2271,22 +2290,25 @@ JSON Example: channel's share URL and password, if it needs one. JSON example for a public channel: - { - "url": "https://internet.speakeasy.tech/?0Name=name&1Description=desc&2Level=Public&3Created=1665489600000000000&e=%2FWNZvuHPuv%2Bx23XbZXVNzCi7y8rUSxkh75MpR9UrsCo%3D&k=ddX1CH52xH%2F%2Fb6lKrbvDghdSmCQr90ktsOAZ%2FrhEonI%3D&l=2&m=0&p=328&s=%2FD%2FoQP2mio3XAWfhmWF0xmZrpj4nAsb9JLXj%2B0Mzq9Y%3D&v=1", - "password": "" - } + + { + "url": "https://internet.speakeasy.tech/?0Name=name&1Description=desc&2Level=Public&3Created=1665489600000000000&e=%2FWNZvuHPuv%2Bx23XbZXVNzCi7y8rUSxkh75MpR9UrsCo%3D&k=ddX1CH52xH%2F%2Fb6lKrbvDghdSmCQr90ktsOAZ%2FrhEonI%3D&l=2&m=0&p=328&s=%2FD%2FoQP2mio3XAWfhmWF0xmZrpj4nAsb9JLXj%2B0Mzq9Y%3D&v=1", + "password": "" + } JSON example for a private channel: - { - "url": "https://internet.speakeasy.tech/?0Name=name&1Description=desc&3Created=1665489600000000000&d=5AZQirb%2FYrmUITLn%2FFzCaGek1APfJnd2q0KwORGj%2BnbGg26kTShG6cfD3w6c%2BA3RDzxuKDSDN0zS4n1LbjiGe0KYdb8eJVeyRZtld516hfojNDXNAwZq8zbeZy4jjbF627fcLHRNS%2FaII4uJ5UB3gLUeBeZGraaybCCu3FIj1N4RbcJ5cQgT7hBf93bHmJc%3D&m=0&v=1", - "password": "tribune gangrene labrador italics nutmeg process exhume legal" - } + + { + "url": "https://internet.speakeasy.tech/?0Name=name&1Description=desc&3Created=1665489600000000000&d=5AZQirb%2FYrmUITLn%2FFzCaGek1APfJnd2q0KwORGj%2BnbGg26kTShG6cfD3w6c%2BA3RDzxuKDSDN0zS4n1LbjiGe0KYdb8eJVeyRZtld516hfojNDXNAwZq8zbeZy4jjbF627fcLHRNS%2FaII4uJ5UB3gLUeBeZGraaybCCu3FIj1N4RbcJ5cQgT7hBf93bHmJc%3D&m=0&v=1", + "password": "tribune gangrene labrador italics nutmeg process exhume legal" + } JSON example for a secret channel: - { - "url": "https://internet.speakeasy.tech/?d=w5evLthm%2Fq2j11g6PPtV0QoLaAqNCIER0OqxhxL%2FhpGVJI0057ZPgGBrKoJNE1%2FdoVuU35%2FhohuW%2BWvGlx6IuHoN6mDj0HfNj6Lo%2B8GwIaD6jOEwUcH%2FMKGsKnoqFsMaMPd5gXYgdHvA8l5SRe0gSCVqGKUaG6JgL%2FDu4iyjY7v4ykwZdQ7soWOcBLHDixGEkVLpwsCrPVHkT2K0W6gV74GIrQ%3D%3D&m=0&v=1", - "password": "frenzy contort staple thicket consuming affiliate scion demeanor" - } + + { + "url": "https://internet.speakeasy.tech/?d=w5evLthm%2Fq2j11g6PPtV0QoLaAqNCIER0OqxhxL%2FhpGVJI0057ZPgGBrKoJNE1%2FdoVuU35%2FhohuW%2BWvGlx6IuHoN6mDj0HfNj6Lo%2B8GwIaD6jOEwUcH%2FMKGsKnoqFsMaMPd5gXYgdHvA8l5SRe0gSCVqGKUaG6JgL%2FDu4iyjY7v4ykwZdQ7soWOcBLHDixGEkVLpwsCrPVHkT2K0W6gV74GIrQ%3D%3D&m=0&v=1", + "password": "frenzy contort staple thicket consuming affiliate scion demeanor" + } */ @interface BindingsShareURL : NSObject <goSeqRefInterface> { } @@ -2481,11 +2503,11 @@ FOUNDATION_EXPORT BOOL BindingsAsyncRequestRestLike(long e2eID, NSData* _Nullabl and codeset version. Parameters: - - pubKey - The Ed25519 public key. - - codesetVersion - The version of the codeset used to generate the identity. + - pubKey - The Ed25519 public key. + - codesetVersion - The version of the codeset used to generate the identity. Returns: - - JSON of [channel.Identity]. + - JSON of [channel.Identity]. */ FOUNDATION_EXPORT NSData* _Nullable BindingsConstructIdentity(NSData* _Nullable pubKey, long codesetVersion, NSError* _Nullable* _Nullable error); @@ -2512,12 +2534,12 @@ pretty print. This function can only be used for private or secret channel URLs. To get the privacy level of a channel URL, use [GetShareUrlType]. Parameters: - - url - The channel's share URL. Should be received from another user or - generated via [GetShareURL]. - - password - The password needed to decrypt the secret data in the URL. + - url - The channel's share URL. Should be received from another user or + generated via [GetShareURL]. + - password - The password needed to decrypt the secret data in the URL. Returns: - - The channel pretty print. + - The channel pretty print. */ FOUNDATION_EXPORT NSString* _Nonnull BindingsDecodePrivateURL(NSString* _Nullable url, NSString* _Nullable password, NSError* _Nullable* _Nullable error); @@ -2527,11 +2549,11 @@ function can only be used for public channel URLs. To get the privacy level of a channel URL, use [GetShareUrlType]. Parameters: - - url - The channel's share URL. Should be received from another user or - generated via [GetShareURL]. + - url - The channel's share URL. Should be received from another user or + generated via [GetShareURL]. Returns: - - The channel pretty print. + - The channel pretty print. */ FOUNDATION_EXPORT NSString* _Nonnull BindingsDecodePublicURL(NSString* _Nullable url, NSError* _Nullable* _Nullable error); @@ -2561,33 +2583,33 @@ the admin. It is only for making new channels, not joining existing ones. It returns a pretty print of the channel and the private key. Parameters: - - cmixID - The tracked cmix object ID. This can be retrieved using - [Cmix.GetID]. - - name - The name of the new channel. The name must be between 3 and 24 - characters inclusive. It can only include upper and lowercase unicode - letters, digits 0 through 9, and underscores (_). It cannot be changed - once a channel is created. - - description - The description of a channel. The description is optional - but cannot be longer than 144 characters and can include all unicode - characters. It cannot be changed once a channel is created. - - privacyLevel - The broadcast.PrivacyLevel of the channel. 0 = public, - 1 = private, and 2 = secret. Refer to the comment below for more - information. + - cmixID - The tracked cmix object ID. This can be retrieved using + [Cmix.GetID]. + - name - The name of the new channel. The name must be between 3 and 24 + characters inclusive. It can only include upper and lowercase unicode + letters, digits 0 through 9, and underscores (_). It cannot be changed + once a channel is created. + - description - The description of a channel. The description is optional + but cannot be longer than 144 characters and can include all unicode + characters. It cannot be changed once a channel is created. + - privacyLevel - The broadcast.PrivacyLevel of the channel. 0 = public, + 1 = private, and 2 = secret. Refer to the comment below for more + information. Returns: - - []byte - [ChannelGeneration] describes a generated channel. It contains - both the public channel info and the private key for the channel in PEM - format. + - []byte - [ChannelGeneration] describes a generated channel. It contains + both the public channel info and the private key for the channel in PEM + format. The [broadcast.PrivacyLevel] of a channel indicates the level of channel information revealed when sharing it via URL. For any channel besides public channels, the secret information is encrypted and a password is required to share and join a channel. - - A privacy level of [broadcast.Public] reveals all the information - including the name, description, privacy level, public key and salt. - - A privacy level of [broadcast.Private] reveals only the name and - description. - - A privacy level of [broadcast.Secret] reveals nothing. + - A privacy level of [broadcast.Public] reveals all the information + including the name, description, privacy level, public key and salt. + - A privacy level of [broadcast.Private] reveals only the name and + description. + - A privacy level of [broadcast.Secret] reveals nothing. */ FOUNDATION_EXPORT NSData* _Nullable BindingsGenerateChannel(long cmixID, NSString* _Nullable name, NSString* _Nullable description, long privacyLevel, NSError* _Nullable* _Nullable error); @@ -2597,11 +2619,11 @@ FOUNDATION_EXPORT NSData* _Nullable BindingsGenerateChannel(long cmixID, NSStrin via [GetPublicChannelIdentityFromPrivate]. Parameters: - - cmixID - The tracked cmix object ID. This can be retrieved using - [Cmix.GetID]. + - cmixID - The tracked cmix object ID. This can be retrieved using + [Cmix.GetID]. Returns: - - Marshalled bytes of [channel.PrivateIdentity]. + - Marshalled bytes of [channel.PrivateIdentity]. */ FOUNDATION_EXPORT NSData* _Nullable BindingsGenerateChannelIdentity(long cmixID, NSError* _Nullable* _Nullable error); @@ -2625,13 +2647,14 @@ FOUNDATION_EXPORT BindingsChannelDbCipher* _Nullable BindingsGetChannelDbCipherT * GetChannelInfo returns the info about a channel from its public description. Parameters: - - prettyPrint - The pretty print of the channel. + - prettyPrint - The pretty print of the channel. The pretty print will be of the format: - <Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=> + + <Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=> Returns: - - []byte - JSON of [ChannelInfo], which describes all relevant channel info. + - []byte - JSON of [ChannelInfo], which describes all relevant channel info. */ FOUNDATION_EXPORT NSData* _Nullable BindingsGetChannelInfo(NSString* _Nullable prettyPrint, NSError* _Nullable* _Nullable error); @@ -2639,23 +2662,24 @@ FOUNDATION_EXPORT NSData* _Nullable BindingsGetChannelInfo(NSString* _Nullable p * GetChannelJSON returns the JSON of the channel for the given pretty print. Parameters: - - prettyPrint - The pretty print of the channel. + - prettyPrint - The pretty print of the channel. Returns: - - JSON of the [broadcast.Channel] object. + - JSON of the [broadcast.Channel] object. Example JSON of [broadcast.Channel]: - { - "ReceptionID": "Ja/+Jh+1IXZYUOn+IzE3Fw/VqHOscomD0Q35p4Ai//kD", - "Name": "My_Channel", - "Description": "Here is information about my channel.", - "Salt": "+tlrU/htO6rrV3UFDfpQALUiuelFZ+Cw9eZCwqRHk+g=", - "RsaPubKeyHash": "PViT1mYkGBj6AYmE803O2RpA7BX24EjgBdldu3pIm4o=", - "RsaPubKeyLength": 5, - "RSASubPayloads": 1, - "Secret": "JxZt/wPx2luoPdHY6jwbXqNlKnixVU/oa9DgypZOuyI=", - "Level": 0 - } + + { + "ReceptionID": "Ja/+Jh+1IXZYUOn+IzE3Fw/VqHOscomD0Q35p4Ai//kD", + "Name": "My_Channel", + "Description": "Here is information about my channel.", + "Salt": "+tlrU/htO6rrV3UFDfpQALUiuelFZ+Cw9eZCwqRHk+g=", + "RsaPubKeyHash": "PViT1mYkGBj6AYmE803O2RpA7BX24EjgBdldu3pIm4o=", + "RsaPubKeyLength": 5, + "RSASubPayloads": 1, + "Secret": "JxZt/wPx2luoPdHY6jwbXqNlKnixVU/oa9DgypZOuyI=", + "Level": 0 + } */ FOUNDATION_EXPORT NSData* _Nullable BindingsGetChannelJSON(NSString* _Nullable prettyPrint, NSError* _Nullable* _Nullable error); @@ -2762,10 +2786,10 @@ FOUNDATION_EXPORT NSData* _Nullable BindingsGetPubkeyFromContact(NSData* _Nullab from a bytes version and returns it JSON marshaled. Parameters: - - marshaledPublic - Bytes of the public identity ([channel.Identity]). + - marshaledPublic - Bytes of the public identity ([channel.Identity]). Returns: - - JSON of the constructed [channel.Identity]. + - JSON of the constructed [channel.Identity]. */ FOUNDATION_EXPORT NSData* _Nullable BindingsGetPublicChannelIdentity(NSData* _Nullable marshaledPublic, NSError* _Nullable* _Nullable error); @@ -2775,11 +2799,11 @@ FOUNDATION_EXPORT NSData* _Nullable BindingsGetPublicChannelIdentity(NSData* _Nu ([channel.PrivateIdentity]). Parameters: - - marshaledPrivate - Bytes of the private identity - (channel.PrivateIdentity]). + - marshaledPrivate - Bytes of the private identity + (channel.PrivateIdentity]). Returns: - - JSON of the public [channel.Identity]. + - JSON of the public [channel.Identity]. */ FOUNDATION_EXPORT NSData* _Nullable BindingsGetPublicChannelIdentityFromPrivate(NSData* _Nullable marshaledPrivate, NSError* _Nullable* _Nullable error); @@ -2790,11 +2814,11 @@ given channel ID. NOTE: This function is unsafe and only for debugging purposes only. Parameters: - - cmixID - ID of [Cmix] object in tracker. - - channelIdBase64 - The [id.ID] of the channel in base 64 encoding. + - cmixID - ID of [Cmix] object in tracker. + - channelIdBase64 - The [id.ID] of the channel in base 64 encoding. Returns: - - The PEM file of the private key. + - The PEM file of the private key. */ FOUNDATION_EXPORT NSString* _Nonnull BindingsGetSavedChannelPrivateKeyUNSAFE(long cmixID, NSString* _Nullable channelIdBase64, NSError* _Nullable* _Nullable error); @@ -2803,15 +2827,16 @@ FOUNDATION_EXPORT NSString* _Nonnull BindingsGetSavedChannelPrivateKeyUNSAFE(lon If the URL is an invalid channel URL, an error is returned. Parameters: - - url - The channel share URL. + - url - The channel share URL. Returns: - - An int that corresponds to the [broadcast.PrivacyLevel] as outlined below. + - An int that corresponds to the [broadcast.PrivacyLevel] as outlined below. Possible returns: - 0 = public channel - 1 = private channel - 2 = secret channel + + 0 = public channel + 1 = private channel + 2 = secret channel */ FOUNDATION_EXPORT BOOL BindingsGetShareUrlType(NSString* _Nullable url, long* _Nullable ret0_, NSError* _Nullable* _Nullable error); @@ -2825,11 +2850,11 @@ FOUNDATION_EXPORT NSString* _Nonnull BindingsGetVersion(void); data. Parameters: - - password - The password used to encrypt the identity. - - data - The encrypted data. + - password - The password used to encrypt the identity. + - data - The encrypted data. Returns: - - JSON of [channel.PrivateIdentity]. + - JSON of [channel.PrivateIdentity]. */ FOUNDATION_EXPORT NSData* _Nullable BindingsImportPrivateIdentity(NSString* _Nullable password, NSData* _Nullable data, NSError* _Nullable* _Nullable error); @@ -2909,12 +2934,12 @@ The channel manager should have previously been created with [ChannelsManager.GetStorageTag]. Parameters: - - cmixID - The tracked cmix object ID. This can be retrieved using - [Cmix.GetID]. - - storageTag - The storage tag associated with the previously created - channel manager and retrieved with [ChannelsManager.GetStorageTag]. - - event - An interface that contains a function that initialises and returns - the event model that is bindings-compatible. + - cmixID - The tracked cmix object ID. This can be retrieved using + [Cmix.GetID]. + - storageTag - The storage tag associated with the previously created + channel manager and retrieved with [ChannelsManager.GetStorageTag]. + - event - An interface that contains a function that initialises and returns + the event model that is bindings-compatible. */ FOUNDATION_EXPORT BindingsChannelsManager* _Nullable BindingsLoadChannelsManager(long cmixID, NSString* _Nullable storageTag, id<BindingsEventModelBuilder> _Nullable eventBuilder, NSError* _Nullable* _Nullable error); @@ -3014,12 +3039,12 @@ FOUNDATION_EXPORT BOOL BindingsMultiLookupUD(long e2eID, NSData* _Nullable udCon * NewChannelsDatabaseCipher constructs a ChannelDbCipher object. Parameters: - - cmixID - The tracked [Cmix] object ID. - - password - The password for storage. This should be the same password - passed into [NewCmix]. - - plaintTextBlockSize - The maximum size of a payload to be encrypted. - A payload passed into [ChannelDbCipher.Encrypt] that is larger than - plaintTextBlockSize will result in an error. + - cmixID - The tracked [Cmix] object ID. + - password - The password for storage. This should be the same password + passed into [NewCmix]. + - plaintTextBlockSize - The maximum size of a payload to be encrypted. + A payload passed into [ChannelDbCipher.Encrypt] that is larger than + plaintTextBlockSize will result in an error. */ FOUNDATION_EXPORT BindingsChannelDbCipher* _Nullable BindingsNewChannelsDatabaseCipher(long cmixID, NSData* _Nullable password, long plaintTextBlockSize, NSError* _Nullable* _Nullable error); @@ -3033,12 +3058,12 @@ reload this channel manager, use [LoadChannelsManager], passing in the storage tag retrieved by [ChannelsManager.GetStorageTag]. Parameters: - - cmixID - The tracked Cmix object ID. This can be retrieved using - [Cmix.GetID]. - - privateIdentity - Bytes of a private identity ([channel.PrivateIdentity]) - that is generated by [GenerateChannelIdentity]. - - event - An interface that contains a function that initialises and returns - the event model that is bindings-compatible. + - cmixID - The tracked Cmix object ID. This can be retrieved using + [Cmix.GetID]. + - privateIdentity - Bytes of a private identity ([channel.PrivateIdentity]) + that is generated by [GenerateChannelIdentity]. + - event - An interface that contains a function that initialises and returns + the event model that is bindings-compatible. */ FOUNDATION_EXPORT BindingsChannelsManager* _Nullable BindingsNewChannelsManager(long cmixID, NSData* _Nullable privateIdentity, id<BindingsEventModelBuilder> _Nullable eventBuilder, NSError* _Nullable* _Nullable error); diff --git a/Package.resolved b/Package.resolved index d7760fa278975208f6d8594eac3dbd5c29784cbe..ec74561fcbec29a3917ef111bf0afe9fbcd9c674 100644 --- a/Package.resolved +++ b/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump.git", "state" : { - "revision" : "819d9d370cd721c9d87671e29d947279292e4541", - "version" : "0.6.0" + "revision" : "ead7d30cc224c3642c150b546f4f1080d1c411a8", + "version" : "0.6.1" } }, { @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay.git", "state" : { - "revision" : "16e6409ee82e1b81390bdffbf217b9c08ab32784", - "version" : "0.5.0" + "revision" : "5a5457a744239896e9b0b03a8e1a5069c3e7b91f", + "version" : "0.6.0" } } ], diff --git a/Package.swift b/Package.swift index b1d78d2adb57ef25fa0056fae812cc04b853cf7e..d5844ed406969435ec67a1b268e042f87fa9e146 100644 --- a/Package.swift +++ b/Package.swift @@ -21,11 +21,11 @@ let package = Package( dependencies: [ .package( url: "https://github.com/pointfreeco/swift-custom-dump.git", - .upToNextMajor(from: "0.6.0") + .upToNextMajor(from: "0.6.1") ), .package( url: "https://github.com/pointfreeco/xctest-dynamic-overlay.git", - .upToNextMajor(from: "0.5.0") + .upToNextMajor(from: "0.6.0") ), .package( url: "https://github.com/kishikawakatsumi/KeychainAccess.git", 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()) + } +}