From ca76d648a795fd4057f2b22cc7976abd1b59f25b Mon Sep 17 00:00:00 2001 From: Ethan Mateja Date: Fri, 1 Mar 2024 11:12:11 -0800 Subject: [PATCH] Group Preferences Actions (#272) * bump the protos to the latest * Group Preferences Actions --------- Co-authored-by: Naomi Plasterer --- .swiftlint.yml | 1 + Sources/XMTPiOS/Contacts.swift | 111 ++++++-- Sources/XMTPiOS/Conversations.swift | 6 +- Sources/XMTPiOS/Group.swift | 6 + .../Proto/message_contents/message.pb.swift | 40 ++- .../private_preferences.pb.swift | 243 ++++++++++++++---- Tests/XMTPTests/ContactsTests.swift | 6 +- Tests/XMTPTests/GroupTests.swift | 51 ++-- XMTPiOSExample/.swiftlint.yml | 1 + .../NotificationService.swift | 2 - .../xcshareddata/swiftpm/Package.resolved | 4 +- .../Views/GroupSettingsView.swift | 2 - .../XMTPiOSExample/Views/LoginView.swift | 4 - 13 files changed, 370 insertions(+), 107 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index d5856a6e..c581ecba 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -21,6 +21,7 @@ disabled_rules: # rule identifiers turned on by default to exclude from running - redundant_optional_initialization - operator_whitespace - comma + - no_optional_try opt_in_rules: - force_unwrapping diff --git a/Sources/XMTPiOS/Contacts.swift b/Sources/XMTPiOS/Contacts.swift index 2252eb2f..7f910b8f 100644 --- a/Sources/XMTPiOS/Contacts.swift +++ b/Sources/XMTPiOS/Contacts.swift @@ -16,12 +16,16 @@ public enum ConsentState: String, Codable { public struct ConsentListEntry: Codable, Hashable { public enum EntryType: String, Codable { - case address + case address, groupId } static func address(_ address: String, type: ConsentState = .unknown) -> ConsentListEntry { ConsentListEntry(value: address, entryType: .address, consentType: type) } + + static func groupId(groupId: String, type: ConsentState = ConsentState.unknown) -> ConsentListEntry { + ConsentListEntry(value: groupId, entryType: .groupId, consentType: type) + } public var value: String public var entryType: EntryType @@ -48,9 +52,7 @@ public class ConsentList { self.client = client privateKey = client.privateKeyBundleV1.identityKey.secp256K1.bytes publicKey = client.privateKeyBundleV1.identityKey.publicKey.secp256K1Uncompressed.bytes - // swiftlint:disable no_optional_try identifier = try? LibXMTP.generatePrivatePreferencesTopicIdentifier(privateKey: privateKey) - // swiftlint:enable no_optional_try } func load() async throws -> ConsentList { @@ -58,7 +60,6 @@ public class ConsentList { throw ContactError.invalidIdentifier } - let envelopes = try await client.apiClient.envelopes(topic: Topic.preferenceList(identifier).description, pagination: Pagination(direction: .ascending)) let consentList = ConsentList(client: client) @@ -71,13 +72,21 @@ public class ConsentList { } for preference in preferences { - for address in preference.allow.walletAddresses { + for address in preference.allowAddress.walletAddresses { _ = consentList.allow(address: address) } - for address in preference.block.walletAddresses { + for address in preference.denyAddress.walletAddresses { _ = consentList.deny(address: address) } + + for groupId in preference.allowGroup.groupIds { + _ = consentList.allowGroup(groupId: groupId) + } + + for groupId in preference.denyGroup.groupIds { + _ = consentList.denyGroup(groupId: groupId) + } } return consentList @@ -89,13 +98,31 @@ public class ConsentList { } var payload = PrivatePreferencesAction() - switch entry.consentType { - case .allowed: - payload.allow.walletAddresses = [entry.value] - case .denied: - payload.block.walletAddresses = [entry.value] - case .unknown: - payload.messageType = nil + switch entry.entryType { + + case .address: + switch entry.consentType { + case .allowed: + payload.allowAddress.walletAddresses = [entry.value] + case .denied: + payload.denyAddress.walletAddresses = [entry.value] + case .unknown: + payload.messageType = nil + } + + case .groupId: + switch entry.consentType { + case .allowed: + if let valueData = entry.value.data(using: .utf8) { + payload.allowGroup.groupIds = [valueData] + } + case .denied: + if let valueData = entry.value.data(using: .utf8) { + payload.denyGroup.groupIds = [valueData] + } + case .unknown: + payload.messageType = nil + } } let message = try LibXMTP.userPreferencesEncrypt( @@ -127,12 +154,36 @@ public class ConsentList { return entry } + func allowGroup(groupId: Data) -> ConsentListEntry { + let groupIdString = groupId.toHex + let entry = ConsentListEntry.groupId(groupId: groupIdString, type: ConsentState.allowed) + entries[ConsentListEntry.groupId(groupId: groupIdString).key] = entry + + return entry + } + + func denyGroup(groupId: Data) -> ConsentListEntry { + let groupIdString = groupId.toHex + let entry = ConsentListEntry.groupId(groupId: groupIdString, type: ConsentState.denied) + entries[ConsentListEntry.groupId(groupId: groupIdString).key] = entry + + return entry + } + func state(address: String) -> ConsentState { - let entry = entries[ConsentListEntry.address(address).key] + guard let entry = entries[ConsentListEntry.address(address).key] else { + return .unknown + } + + return entry.consentType + } + + func groupState(groupId: Data) -> ConsentState { + guard let entry = entries[ConsentListEntry.groupId(groupId: groupId.toHex).key] else { + return .unknown + } - // swiftlint:disable no_optional_try - return entry?.consentType ?? .unknown - // swiftlint:enable no_optional_try + return entry.consentType } } @@ -166,6 +217,14 @@ public actor Contacts { return consentList.state(address: address) == .denied } + public func isGroupAllowed(groupId: Data) -> Bool { + return consentList.groupState(groupId: groupId) == .allowed + } + + public func isGroupDenied(groupId: Data) -> Bool { + return consentList.groupState(groupId: groupId) == .denied + } + public func allow(addresses: [String]) async throws { for address in addresses { try await ConsentList(client: client).publish(entry: consentList.allow(address: address)) @@ -178,6 +237,20 @@ public actor Contacts { } } + public func allowGroup(groupIds: [Data]) async throws { + for groupId in groupIds { + let entry = consentList.allowGroup(groupId: groupId) + try await ConsentList(client: client).publish(entry: entry) + } + } + + public func denyGroup(groupIds: [Data]) async throws { + for groupId in groupIds { + let entry = consentList.denyGroup(groupId: groupId) + try await ConsentList(client: client).publish(entry: entry) + } + } + func markIntroduced(_ peerAddress: String, _ isIntroduced: Bool) { hasIntroduced[peerAddress] = isIntroduced } @@ -198,15 +271,11 @@ public actor Contacts { let response = try await client.query(topic: .contact(peerAddress)) for envelope in response.envelopes { - // swiftlint:disable no_optional_try if let contactBundle = try? ContactBundle.from(envelope: envelope) { knownBundles[peerAddress] = contactBundle - return contactBundle } - // swiftlint:enable no_optional_try } - return nil } } diff --git a/Sources/XMTPiOS/Conversations.swift b/Sources/XMTPiOS/Conversations.swift index 2771ecdb..7184ef6a 100644 --- a/Sources/XMTPiOS/Conversations.swift +++ b/Sources/XMTPiOS/Conversations.swift @@ -153,7 +153,11 @@ public actor Conversations { throw GroupError.memberNotRegistered(erroredAddresses) } - return try await v3Client.conversations().createGroup(accountAddresses: addresses, permissions: permissions).fromFFI(client: client) + let group = try await v3Client.conversations().createGroup(accountAddresses: addresses, permissions: permissions).fromFFI(client: client) + + try await client.contacts.allowGroup(groupIds: [group.id]) + + return group } /// Import a previously seen conversation. diff --git a/Sources/XMTPiOS/Group.swift b/Sources/XMTPiOS/Group.swift index 786ae38a..d077bc7f 100644 --- a/Sources/XMTPiOS/Group.swift +++ b/Sources/XMTPiOS/Group.swift @@ -109,6 +109,12 @@ public struct Group: Identifiable, Equatable, Hashable { } public func send(encodedContent: EncodedContent) async throws -> String { + let groupState = await client.contacts.consentList.groupState(groupId: id) + + if groupState == ConsentState.unknown { + try await client.contacts.allowGroup(groupIds: [id]) + } + try await ffiGroup.send(contentBytes: encodedContent.serializedData()) return id.toHex } diff --git a/Sources/XMTPiOS/Proto/message_contents/message.pb.swift b/Sources/XMTPiOS/Proto/message_contents/message.pb.swift index 8cbf8114..22e935fe 100644 --- a/Sources/XMTPiOS/Proto/message_contents/message.pb.swift +++ b/Sources/XMTPiOS/Proto/message_contents/message.pb.swift @@ -125,16 +125,32 @@ public struct Xmtp_MessageContents_MessageV2 { public mutating func clearCiphertext() {self._ciphertext = nil} /// HMAC of the message ciphertext, with the HMAC key derived from the topic key - public var senderHmac: Data = Data() + public var senderHmac: Data { + get {return _senderHmac ?? Data()} + set {_senderHmac = newValue} + } + /// Returns true if `senderHmac` has been explicitly set. + public var hasSenderHmac: Bool {return self._senderHmac != nil} + /// Clears the value of `senderHmac`. Subsequent reads from it will return its default value. + public mutating func clearSenderHmac() {self._senderHmac = nil} /// Flag indicating whether the message should be pushed from a notification server - public var shouldPush: Bool = false + public var shouldPush: Bool { + get {return _shouldPush ?? false} + set {_shouldPush = newValue} + } + /// Returns true if `shouldPush` has been explicitly set. + public var hasShouldPush: Bool {return self._shouldPush != nil} + /// Clears the value of `shouldPush`. Subsequent reads from it will return its default value. + public mutating func clearShouldPush() {self._shouldPush = nil} public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} fileprivate var _ciphertext: Xmtp_MessageContents_Ciphertext? = nil + fileprivate var _senderHmac: Data? = nil + fileprivate var _shouldPush: Bool? = nil } /// Versioned Message @@ -432,8 +448,8 @@ extension Xmtp_MessageContents_MessageV2: SwiftProtobuf.Message, SwiftProtobuf._ switch fieldNumber { case 1: try { try decoder.decodeSingularBytesField(value: &self.headerBytes) }() case 2: try { try decoder.decodeSingularMessageField(value: &self._ciphertext) }() - case 3: try { try decoder.decodeSingularBytesField(value: &self.senderHmac) }() - case 4: try { try decoder.decodeSingularBoolField(value: &self.shouldPush) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self._senderHmac) }() + case 4: try { try decoder.decodeSingularBoolField(value: &self._shouldPush) }() default: break } } @@ -450,20 +466,20 @@ extension Xmtp_MessageContents_MessageV2: SwiftProtobuf.Message, SwiftProtobuf._ try { if let v = self._ciphertext { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() - if !self.senderHmac.isEmpty { - try visitor.visitSingularBytesField(value: self.senderHmac, fieldNumber: 3) - } - if self.shouldPush != false { - try visitor.visitSingularBoolField(value: self.shouldPush, fieldNumber: 4) - } + try { if let v = self._senderHmac { + try visitor.visitSingularBytesField(value: v, fieldNumber: 3) + } }() + try { if let v = self._shouldPush { + try visitor.visitSingularBoolField(value: v, fieldNumber: 4) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: Xmtp_MessageContents_MessageV2, rhs: Xmtp_MessageContents_MessageV2) -> Bool { if lhs.headerBytes != rhs.headerBytes {return false} if lhs._ciphertext != rhs._ciphertext {return false} - if lhs.senderHmac != rhs.senderHmac {return false} - if lhs.shouldPush != rhs.shouldPush {return false} + if lhs._senderHmac != rhs._senderHmac {return false} + if lhs._shouldPush != rhs._shouldPush {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/Sources/XMTPiOS/Proto/message_contents/private_preferences.pb.swift b/Sources/XMTPiOS/Proto/message_contents/private_preferences.pb.swift index 185242d9..575bb037 100644 --- a/Sources/XMTPiOS/Proto/message_contents/private_preferences.pb.swift +++ b/Sources/XMTPiOS/Proto/message_contents/private_preferences.pb.swift @@ -9,8 +9,8 @@ /// Private Key Storage /// -/// Following definitions are not used in the protocol, instead -/// they provide a way for encoding private keys for storage. +/// Following definitions are not used in the protocol, instead they provide a +/// way for encoding private keys for storage. import Foundation import SwiftProtobuf @@ -25,9 +25,8 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -/// PrivatePreferencesAction is a message used to update the client's -/// preference store. The only current actions are allow and block. -/// Other actions may be added later +/// PrivatePreferencesAction is a message used to update the client's preference +/// store. public struct Xmtp_MessageContents_PrivatePreferencesAction { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -35,27 +34,45 @@ public struct Xmtp_MessageContents_PrivatePreferencesAction { public var messageType: Xmtp_MessageContents_PrivatePreferencesAction.OneOf_MessageType? = nil - public var allow: Xmtp_MessageContents_PrivatePreferencesAction.Allow { + public var allowAddress: Xmtp_MessageContents_PrivatePreferencesAction.AllowAddress { get { - if case .allow(let v)? = messageType {return v} - return Xmtp_MessageContents_PrivatePreferencesAction.Allow() + if case .allowAddress(let v)? = messageType {return v} + return Xmtp_MessageContents_PrivatePreferencesAction.AllowAddress() } - set {messageType = .allow(newValue)} + set {messageType = .allowAddress(newValue)} } - public var block: Xmtp_MessageContents_PrivatePreferencesAction.Block { + public var denyAddress: Xmtp_MessageContents_PrivatePreferencesAction.DenyAddress { get { - if case .block(let v)? = messageType {return v} - return Xmtp_MessageContents_PrivatePreferencesAction.Block() + if case .denyAddress(let v)? = messageType {return v} + return Xmtp_MessageContents_PrivatePreferencesAction.DenyAddress() } - set {messageType = .block(newValue)} + set {messageType = .denyAddress(newValue)} + } + + public var allowGroup: Xmtp_MessageContents_PrivatePreferencesAction.AllowGroup { + get { + if case .allowGroup(let v)? = messageType {return v} + return Xmtp_MessageContents_PrivatePreferencesAction.AllowGroup() + } + set {messageType = .allowGroup(newValue)} + } + + public var denyGroup: Xmtp_MessageContents_PrivatePreferencesAction.DenyGroup { + get { + if case .denyGroup(let v)? = messageType {return v} + return Xmtp_MessageContents_PrivatePreferencesAction.DenyGroup() + } + set {messageType = .denyGroup(newValue)} } public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_MessageType: Equatable { - case allow(Xmtp_MessageContents_PrivatePreferencesAction.Allow) - case block(Xmtp_MessageContents_PrivatePreferencesAction.Block) + case allowAddress(Xmtp_MessageContents_PrivatePreferencesAction.AllowAddress) + case denyAddress(Xmtp_MessageContents_PrivatePreferencesAction.DenyAddress) + case allowGroup(Xmtp_MessageContents_PrivatePreferencesAction.AllowGroup) + case denyGroup(Xmtp_MessageContents_PrivatePreferencesAction.DenyGroup) #if !swift(>=4.1) public static func ==(lhs: Xmtp_MessageContents_PrivatePreferencesAction.OneOf_MessageType, rhs: Xmtp_MessageContents_PrivatePreferencesAction.OneOf_MessageType) -> Bool { @@ -63,12 +80,20 @@ public struct Xmtp_MessageContents_PrivatePreferencesAction { // allocates stack space for every case branch when no optimizations are // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch (lhs, rhs) { - case (.allow, .allow): return { - guard case .allow(let l) = lhs, case .allow(let r) = rhs else { preconditionFailure() } + case (.allowAddress, .allowAddress): return { + guard case .allowAddress(let l) = lhs, case .allowAddress(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.denyAddress, .denyAddress): return { + guard case .denyAddress(let l) = lhs, case .denyAddress(let r) = rhs else { preconditionFailure() } return l == r }() - case (.block, .block): return { - guard case .block(let l) = lhs, case .block(let r) = rhs else { preconditionFailure() } + case (.allowGroup, .allowGroup): return { + guard case .allowGroup(let l) = lhs, case .allowGroup(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.denyGroup, .denyGroup): return { + guard case .denyGroup(let l) = lhs, case .denyGroup(let r) = rhs else { preconditionFailure() } return l == r }() default: return false @@ -77,12 +102,13 @@ public struct Xmtp_MessageContents_PrivatePreferencesAction { #endif } - /// Add the given wallet addresses to the allow list - public struct Allow { + /// Allow 1:1 direct message (DM) access + public struct AllowAddress { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. + /// Add the given wallet addresses to the allow list public var walletAddresses: [String] = [] public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -90,12 +116,13 @@ public struct Xmtp_MessageContents_PrivatePreferencesAction { public init() {} } - /// Add the given wallet addresses to the block list - public struct Block { + /// Deny (block) 1:1 direct message (DM) access + public struct DenyAddress { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. + /// Add the given wallet addresses to the deny list public var walletAddresses: [String] = [] public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -103,6 +130,34 @@ public struct Xmtp_MessageContents_PrivatePreferencesAction { public init() {} } + /// Allow Group access + public struct AllowGroup { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// Add the given group_ids to the allow list + public var groupIds: [Data] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + } + + /// Deny (deny) Group access + public struct DenyGroup { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// Add the given group_ids to the deny list + public var groupIds: [Data] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + } + public init() {} } @@ -148,8 +203,10 @@ public struct Xmtp_MessageContents_PrivatePreferencesPayload { #if swift(>=5.5) && canImport(_Concurrency) extension Xmtp_MessageContents_PrivatePreferencesAction: @unchecked Sendable {} extension Xmtp_MessageContents_PrivatePreferencesAction.OneOf_MessageType: @unchecked Sendable {} -extension Xmtp_MessageContents_PrivatePreferencesAction.Allow: @unchecked Sendable {} -extension Xmtp_MessageContents_PrivatePreferencesAction.Block: @unchecked Sendable {} +extension Xmtp_MessageContents_PrivatePreferencesAction.AllowAddress: @unchecked Sendable {} +extension Xmtp_MessageContents_PrivatePreferencesAction.DenyAddress: @unchecked Sendable {} +extension Xmtp_MessageContents_PrivatePreferencesAction.AllowGroup: @unchecked Sendable {} +extension Xmtp_MessageContents_PrivatePreferencesAction.DenyGroup: @unchecked Sendable {} extension Xmtp_MessageContents_PrivatePreferencesPayload: @unchecked Sendable {} extension Xmtp_MessageContents_PrivatePreferencesPayload.OneOf_Version: @unchecked Sendable {} #endif // swift(>=5.5) && canImport(_Concurrency) @@ -161,8 +218,10 @@ fileprivate let _protobuf_package = "xmtp.message_contents" extension Xmtp_MessageContents_PrivatePreferencesAction: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".PrivatePreferencesAction" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "allow"), - 2: .same(proto: "block"), + 1: .standard(proto: "allow_address"), + 2: .standard(proto: "deny_address"), + 3: .standard(proto: "allow_group"), + 4: .standard(proto: "deny_group"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -172,29 +231,55 @@ extension Xmtp_MessageContents_PrivatePreferencesAction: SwiftProtobuf.Message, // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { - var v: Xmtp_MessageContents_PrivatePreferencesAction.Allow? + var v: Xmtp_MessageContents_PrivatePreferencesAction.AllowAddress? var hadOneofValue = false if let current = self.messageType { hadOneofValue = true - if case .allow(let m) = current {v = m} + if case .allowAddress(let m) = current {v = m} } try decoder.decodeSingularMessageField(value: &v) if let v = v { if hadOneofValue {try decoder.handleConflictingOneOf()} - self.messageType = .allow(v) + self.messageType = .allowAddress(v) } }() case 2: try { - var v: Xmtp_MessageContents_PrivatePreferencesAction.Block? + var v: Xmtp_MessageContents_PrivatePreferencesAction.DenyAddress? + var hadOneofValue = false + if let current = self.messageType { + hadOneofValue = true + if case .denyAddress(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.messageType = .denyAddress(v) + } + }() + case 3: try { + var v: Xmtp_MessageContents_PrivatePreferencesAction.AllowGroup? + var hadOneofValue = false + if let current = self.messageType { + hadOneofValue = true + if case .allowGroup(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.messageType = .allowGroup(v) + } + }() + case 4: try { + var v: Xmtp_MessageContents_PrivatePreferencesAction.DenyGroup? var hadOneofValue = false if let current = self.messageType { hadOneofValue = true - if case .block(let m) = current {v = m} + if case .denyGroup(let m) = current {v = m} } try decoder.decodeSingularMessageField(value: &v) if let v = v { if hadOneofValue {try decoder.handleConflictingOneOf()} - self.messageType = .block(v) + self.messageType = .denyGroup(v) } }() default: break @@ -208,14 +293,22 @@ extension Xmtp_MessageContents_PrivatePreferencesAction: SwiftProtobuf.Message, // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and // https://github.com/apple/swift-protobuf/issues/1182 switch self.messageType { - case .allow?: try { - guard case .allow(let v)? = self.messageType else { preconditionFailure() } + case .allowAddress?: try { + guard case .allowAddress(let v)? = self.messageType else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 1) }() - case .block?: try { - guard case .block(let v)? = self.messageType else { preconditionFailure() } + case .denyAddress?: try { + guard case .denyAddress(let v)? = self.messageType else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 2) }() + case .allowGroup?: try { + guard case .allowGroup(let v)? = self.messageType else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + }() + case .denyGroup?: try { + guard case .denyGroup(let v)? = self.messageType else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -228,8 +321,8 @@ extension Xmtp_MessageContents_PrivatePreferencesAction: SwiftProtobuf.Message, } } -extension Xmtp_MessageContents_PrivatePreferencesAction.Allow: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = Xmtp_MessageContents_PrivatePreferencesAction.protoMessageName + ".Allow" +extension Xmtp_MessageContents_PrivatePreferencesAction.AllowAddress: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Xmtp_MessageContents_PrivatePreferencesAction.protoMessageName + ".AllowAddress" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "wallet_addresses"), ] @@ -253,15 +346,15 @@ extension Xmtp_MessageContents_PrivatePreferencesAction.Allow: SwiftProtobuf.Mes try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Xmtp_MessageContents_PrivatePreferencesAction.Allow, rhs: Xmtp_MessageContents_PrivatePreferencesAction.Allow) -> Bool { + public static func ==(lhs: Xmtp_MessageContents_PrivatePreferencesAction.AllowAddress, rhs: Xmtp_MessageContents_PrivatePreferencesAction.AllowAddress) -> Bool { if lhs.walletAddresses != rhs.walletAddresses {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Xmtp_MessageContents_PrivatePreferencesAction.Block: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = Xmtp_MessageContents_PrivatePreferencesAction.protoMessageName + ".Block" +extension Xmtp_MessageContents_PrivatePreferencesAction.DenyAddress: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Xmtp_MessageContents_PrivatePreferencesAction.protoMessageName + ".DenyAddress" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "wallet_addresses"), ] @@ -285,13 +378,77 @@ extension Xmtp_MessageContents_PrivatePreferencesAction.Block: SwiftProtobuf.Mes try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: Xmtp_MessageContents_PrivatePreferencesAction.Block, rhs: Xmtp_MessageContents_PrivatePreferencesAction.Block) -> Bool { + public static func ==(lhs: Xmtp_MessageContents_PrivatePreferencesAction.DenyAddress, rhs: Xmtp_MessageContents_PrivatePreferencesAction.DenyAddress) -> Bool { if lhs.walletAddresses != rhs.walletAddresses {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } +extension Xmtp_MessageContents_PrivatePreferencesAction.AllowGroup: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Xmtp_MessageContents_PrivatePreferencesAction.protoMessageName + ".AllowGroup" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "group_ids"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedBytesField(value: &self.groupIds) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.groupIds.isEmpty { + try visitor.visitRepeatedBytesField(value: self.groupIds, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Xmtp_MessageContents_PrivatePreferencesAction.AllowGroup, rhs: Xmtp_MessageContents_PrivatePreferencesAction.AllowGroup) -> Bool { + if lhs.groupIds != rhs.groupIds {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Xmtp_MessageContents_PrivatePreferencesAction.DenyGroup: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Xmtp_MessageContents_PrivatePreferencesAction.protoMessageName + ".DenyGroup" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "group_ids"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedBytesField(value: &self.groupIds) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.groupIds.isEmpty { + try visitor.visitRepeatedBytesField(value: self.groupIds, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Xmtp_MessageContents_PrivatePreferencesAction.DenyGroup, rhs: Xmtp_MessageContents_PrivatePreferencesAction.DenyGroup) -> Bool { + if lhs.groupIds != rhs.groupIds {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Xmtp_MessageContents_PrivatePreferencesPayload: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".PrivatePreferencesPayload" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ diff --git a/Tests/XMTPTests/ContactsTests.swift b/Tests/XMTPTests/ContactsTests.swift index da5087cf..f3d4796b 100644 --- a/Tests/XMTPTests/ContactsTests.swift +++ b/Tests/XMTPTests/ContactsTests.swift @@ -15,8 +15,8 @@ class ContactsTests: XCTestCase { let fixtures = await fixtures() try await fixtures.bobClient.ensureUserContactPublished() - let bobAddressLowercased = fixtures.bobClient.address.lowercased() - let bobContact = try await fixtures.aliceClient.getUserContact(peerAddress: bobAddressLowercased) + let bobAddressLowerCased = fixtures.bobClient.address.lowercased() + let bobContact = try await fixtures.aliceClient.getUserContact(peerAddress: bobAddressLowerCased) XCTAssertNotNil(bobContact) } @@ -68,7 +68,7 @@ class ContactsTests: XCTestCase { XCTAssertTrue(result) } - func testBlockAddress() async throws { + func testDenyAddress() async throws { let fixtures = await fixtures() let contacts = fixtures.bobClient.contacts diff --git a/Tests/XMTPTests/GroupTests.swift b/Tests/XMTPTests/GroupTests.swift index 7d916186..8299075c 100644 --- a/Tests/XMTPTests/GroupTests.swift +++ b/Tests/XMTPTests/GroupTests.swift @@ -32,7 +32,6 @@ func assertThrowsAsyncError( } } - @available(iOS 16, *) class GroupTests: XCTestCase { // Use these fixtures to talk to the local node @@ -92,7 +91,6 @@ class GroupTests: XCTestCase { XCTAssert(!bobGroup.id.isEmpty) XCTAssert(!aliceGroup.id.isEmpty) - try await aliceGroup.addMembers(addresses: [fixtures.fred.address]) try await bobGroup.sync() XCTAssertEqual(aliceGroup.memberAddresses.count, 3) @@ -115,7 +113,7 @@ class GroupTests: XCTestCase { XCTAssert(try bobGroup.isAdmin()) XCTAssert(try !aliceGroup.isAdmin()) } - + func testCanCreateAGroupWithAdminPermissions() async throws { let fixtures = try await localFixtures() let bobGroup = try await fixtures.bobClient.conversations.newGroup(with: [fixtures.alice.address], permissions: GroupPermissions.groupCreatorIsAdmin) @@ -123,8 +121,13 @@ class GroupTests: XCTestCase { let aliceGroup = try await fixtures.aliceClient.conversations.groups().first! XCTAssert(!bobGroup.id.isEmpty) XCTAssert(!aliceGroup.id.isEmpty) - - + + let bobConsentResult = await fixtures.bobClient.contacts.consentList.groupState(groupId: bobGroup.id) + XCTAssertEqual(bobConsentResult, ConsentState.allowed) + + let aliceConsentResult = await fixtures.aliceClient.contacts.consentList.groupState(groupId: aliceGroup.id) + XCTAssertEqual(aliceConsentResult, ConsentState.unknown) + try await bobGroup.addMembers(addresses: [fixtures.fred.address]) try await aliceGroup.sync() XCTAssertEqual(aliceGroup.memberAddresses.count, 3) @@ -192,7 +195,6 @@ class GroupTests: XCTestCase { let members = group.memberAddresses.map(\.localizedLowercase).sorted() let peerMembers = Conversation.group(group).peerAddresses.map(\.localizedLowercase).sorted() - XCTAssertEqual([fixtures.bob.address.localizedLowercase, fixtures.alice.address.localizedLowercase].sorted(), members) XCTAssertEqual([fixtures.bob.address.localizedLowercase].sorted(), peerMembers) } @@ -329,6 +331,21 @@ class GroupTests: XCTestCase { } } + func testGroupStartsWithAllowedState() async throws { + let fixtures = try await localFixtures() + let bobGroup = try await fixtures.bobClient.conversations.newGroup(with: [fixtures.alice.walletAddress]) + + _ = try await bobGroup.send(content: "howdy") + _ = try await bobGroup.send(content: "gm") + try await bobGroup.sync() + + let isGroupAllowedResult = await fixtures.bobClient.contacts.isGroupAllowed(groupId: bobGroup.id) + XCTAssertTrue(isGroupAllowedResult) + + let groupStateResult = await fixtures.bobClient.contacts.consentList.groupState(groupId: bobGroup.id) + XCTAssertEqual(groupStateResult, ConsentState.allowed) + } + func testCanSendMessagesToGroup() async throws { let fixtures = try await localFixtures() let aliceGroup = try await fixtures.aliceClient.conversations.newGroup(with: [fixtures.bob.address]) @@ -336,8 +353,8 @@ class GroupTests: XCTestCase { try await fixtures.bobClient.conversations.sync() let bobGroup = try await fixtures.bobClient.conversations.groups()[0] - try await aliceGroup.send(content: "sup gang original") - try await aliceGroup.send(content: "sup gang") + _ = try await aliceGroup.send(content: "sup gang original") + _ = try await aliceGroup.send(content: "sup gang") try await aliceGroup.sync() let aliceGroupsCount = try await aliceGroup.messages().count @@ -360,8 +377,8 @@ class GroupTests: XCTestCase { try await fixtures.bobClient.conversations.sync() let bobGroup = try await fixtures.bobClient.conversations.groups()[0] - try await aliceGroup.send(content: "sup gang original") - try await aliceGroup.send(content: "sup gang") + _ = try await aliceGroup.send(content: "sup gang original") + _ = try await aliceGroup.send(content: "sup gang") try await aliceGroup.sync() let aliceGroupsCount = try await aliceGroup.decryptedMessages().count @@ -388,7 +405,7 @@ class GroupTests: XCTestCase { } } - try await fixtures.bobClient.conversations.newGroup(with: [fixtures.alice.address]) + _ = try await fixtures.bobClient.conversations.newGroup(with: [fixtures.alice.address]) await waitForExpectations(timeout: 3) } @@ -425,8 +442,8 @@ class GroupTests: XCTestCase { } } - try await group.send(content: "hi") - try await convo.send(content: "hi") + _ = try await group.send(content: "hi") + _ = try await convo.send(content: "hi") await waitForExpectations(timeout: 3) } @@ -445,8 +462,8 @@ class GroupTests: XCTestCase { } } - try await group.send(content: "hi") - try await convo.send(content: "hi") + _ = try await group.send(content: "hi") + _ = try await convo.send(content: "hi") await waitForExpectations(timeout: 3) } @@ -464,7 +481,7 @@ class GroupTests: XCTestCase { } } - try await group.send(content: "hi") + _ = try await group.send(content: "hi") await waitForExpectations(timeout: 3) } @@ -481,7 +498,7 @@ class GroupTests: XCTestCase { } } - try await group.send(content: "hi") + _ = try await group.send(content: "hi") await waitForExpectations(timeout: 3) } diff --git a/XMTPiOSExample/.swiftlint.yml b/XMTPiOSExample/.swiftlint.yml index d5856a6e..c581ecba 100644 --- a/XMTPiOSExample/.swiftlint.yml +++ b/XMTPiOSExample/.swiftlint.yml @@ -21,6 +21,7 @@ disabled_rules: # rule identifiers turned on by default to exclude from running - redundant_optional_initialization - operator_whitespace - comma + - no_optional_try opt_in_rules: - force_unwrapping diff --git a/XMTPiOSExample/NotificationService/NotificationService.swift b/XMTPiOSExample/NotificationService/NotificationService.swift index 9485c065..32c4cc1d 100644 --- a/XMTPiOSExample/NotificationService/NotificationService.swift +++ b/XMTPiOSExample/NotificationService/NotificationService.swift @@ -27,7 +27,6 @@ class NotificationService: UNNotificationServiceExtension { let persistence = Persistence() - // swiftlint:disable no_optional_try guard let keysData = persistence.loadKeys(), let keys = try? PrivateKeyBundle(serializedData: keysData), let conversationContainer = try persistence.load(conversationTopic: topic) @@ -53,7 +52,6 @@ class NotificationService: UNNotificationServiceExtension { contentHandler(bestAttemptContent) } } - // swiftlint:enable no_optional_try } catch { print("Error receiving notification: \(error)") } diff --git a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3612b1e6..42929c12 100644 --- a/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/XMTPiOSExample/XMTPiOSExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/xmtp/libxmtp-swift", "state" : { - "revision" : "f6571b6b771b4c0a2a9469bd407ce124a9d91fed", - "version" : "0.4.2-beta3" + "revision" : "28ee27a4ded8b996a74850e366247e9fe51d782a", + "version" : "0.4.2-beta4" } }, { diff --git a/XMTPiOSExample/XMTPiOSExample/Views/GroupSettingsView.swift b/XMTPiOSExample/XMTPiOSExample/Views/GroupSettingsView.swift index 87a66376..ae152465 100644 --- a/XMTPiOSExample/XMTPiOSExample/Views/GroupSettingsView.swift +++ b/XMTPiOSExample/XMTPiOSExample/Views/GroupSettingsView.swift @@ -114,9 +114,7 @@ struct GroupSettingsView: View { } private func syncGroupMembers() async { - // swiftlint:disable no_optional_try try? await group.sync() - // swiftlint:enable no_optional_try await MainActor.run { self.groupMembers = group.memberAddresses } diff --git a/XMTPiOSExample/XMTPiOSExample/Views/LoginView.swift b/XMTPiOSExample/XMTPiOSExample/Views/LoginView.swift index 81b41fd8..df3ae873 100644 --- a/XMTPiOSExample/XMTPiOSExample/Views/LoginView.swift +++ b/XMTPiOSExample/XMTPiOSExample/Views/LoginView.swift @@ -28,9 +28,7 @@ struct ModalWrapper: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController { let controller = UIViewController() Task { - // swiftlint:disable no_optional_try try? await Task.sleep(for: .seconds(0.4)) - // swiftlint:enable no_optional_try await MainActor.run { WalletConnectModal.present(from: controller) } @@ -110,7 +108,6 @@ struct LoginView: View { var publishers: [AnyCancellable] = [] @State private var isShowingWebview = true - // swiftlint:disable function_body_length init( onConnected: @escaping (Client) -> Void ) { @@ -177,7 +174,6 @@ struct LoginView: View { } .store(in: &publishers) } - // swiftlint:enable function_body_length var body: some View { ModalWrapper()