Skip to content

Commit ca76d64

Browse files
Group Preferences Actions (#272)
* bump the protos to the latest * Group Preferences Actions --------- Co-authored-by: Naomi Plasterer <naomi@xmtp.com>
1 parent d6a719d commit ca76d64

File tree

13 files changed

+370
-107
lines changed

13 files changed

+370
-107
lines changed

.swiftlint.yml

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ disabled_rules: # rule identifiers turned on by default to exclude from running
2121
- redundant_optional_initialization
2222
- operator_whitespace
2323
- comma
24+
- no_optional_try
2425

2526
opt_in_rules:
2627
- force_unwrapping

Sources/XMTPiOS/Contacts.swift

+90-21
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@ public enum ConsentState: String, Codable {
1616

1717
public struct ConsentListEntry: Codable, Hashable {
1818
public enum EntryType: String, Codable {
19-
case address
19+
case address, groupId
2020
}
2121

2222
static func address(_ address: String, type: ConsentState = .unknown) -> ConsentListEntry {
2323
ConsentListEntry(value: address, entryType: .address, consentType: type)
2424
}
25+
26+
static func groupId(groupId: String, type: ConsentState = ConsentState.unknown) -> ConsentListEntry {
27+
ConsentListEntry(value: groupId, entryType: .groupId, consentType: type)
28+
}
2529

2630
public var value: String
2731
public var entryType: EntryType
@@ -48,17 +52,14 @@ public class ConsentList {
4852
self.client = client
4953
privateKey = client.privateKeyBundleV1.identityKey.secp256K1.bytes
5054
publicKey = client.privateKeyBundleV1.identityKey.publicKey.secp256K1Uncompressed.bytes
51-
// swiftlint:disable no_optional_try
5255
identifier = try? LibXMTP.generatePrivatePreferencesTopicIdentifier(privateKey: privateKey)
53-
// swiftlint:enable no_optional_try
5456
}
5557

5658
func load() async throws -> ConsentList {
5759
guard let identifier = identifier else {
5860
throw ContactError.invalidIdentifier
5961
}
6062

61-
6263
let envelopes = try await client.apiClient.envelopes(topic: Topic.preferenceList(identifier).description, pagination: Pagination(direction: .ascending))
6364
let consentList = ConsentList(client: client)
6465

@@ -71,13 +72,21 @@ public class ConsentList {
7172
}
7273

7374
for preference in preferences {
74-
for address in preference.allow.walletAddresses {
75+
for address in preference.allowAddress.walletAddresses {
7576
_ = consentList.allow(address: address)
7677
}
7778

78-
for address in preference.block.walletAddresses {
79+
for address in preference.denyAddress.walletAddresses {
7980
_ = consentList.deny(address: address)
8081
}
82+
83+
for groupId in preference.allowGroup.groupIds {
84+
_ = consentList.allowGroup(groupId: groupId)
85+
}
86+
87+
for groupId in preference.denyGroup.groupIds {
88+
_ = consentList.denyGroup(groupId: groupId)
89+
}
8190
}
8291

8392
return consentList
@@ -89,13 +98,31 @@ public class ConsentList {
8998
}
9099

91100
var payload = PrivatePreferencesAction()
92-
switch entry.consentType {
93-
case .allowed:
94-
payload.allow.walletAddresses = [entry.value]
95-
case .denied:
96-
payload.block.walletAddresses = [entry.value]
97-
case .unknown:
98-
payload.messageType = nil
101+
switch entry.entryType {
102+
103+
case .address:
104+
switch entry.consentType {
105+
case .allowed:
106+
payload.allowAddress.walletAddresses = [entry.value]
107+
case .denied:
108+
payload.denyAddress.walletAddresses = [entry.value]
109+
case .unknown:
110+
payload.messageType = nil
111+
}
112+
113+
case .groupId:
114+
switch entry.consentType {
115+
case .allowed:
116+
if let valueData = entry.value.data(using: .utf8) {
117+
payload.allowGroup.groupIds = [valueData]
118+
}
119+
case .denied:
120+
if let valueData = entry.value.data(using: .utf8) {
121+
payload.denyGroup.groupIds = [valueData]
122+
}
123+
case .unknown:
124+
payload.messageType = nil
125+
}
99126
}
100127

101128
let message = try LibXMTP.userPreferencesEncrypt(
@@ -127,12 +154,36 @@ public class ConsentList {
127154
return entry
128155
}
129156

157+
func allowGroup(groupId: Data) -> ConsentListEntry {
158+
let groupIdString = groupId.toHex
159+
let entry = ConsentListEntry.groupId(groupId: groupIdString, type: ConsentState.allowed)
160+
entries[ConsentListEntry.groupId(groupId: groupIdString).key] = entry
161+
162+
return entry
163+
}
164+
165+
func denyGroup(groupId: Data) -> ConsentListEntry {
166+
let groupIdString = groupId.toHex
167+
let entry = ConsentListEntry.groupId(groupId: groupIdString, type: ConsentState.denied)
168+
entries[ConsentListEntry.groupId(groupId: groupIdString).key] = entry
169+
170+
return entry
171+
}
172+
130173
func state(address: String) -> ConsentState {
131-
let entry = entries[ConsentListEntry.address(address).key]
174+
guard let entry = entries[ConsentListEntry.address(address).key] else {
175+
return .unknown
176+
}
177+
178+
return entry.consentType
179+
}
180+
181+
func groupState(groupId: Data) -> ConsentState {
182+
guard let entry = entries[ConsentListEntry.groupId(groupId: groupId.toHex).key] else {
183+
return .unknown
184+
}
132185

133-
// swiftlint:disable no_optional_try
134-
return entry?.consentType ?? .unknown
135-
// swiftlint:enable no_optional_try
186+
return entry.consentType
136187
}
137188
}
138189

@@ -166,6 +217,14 @@ public actor Contacts {
166217
return consentList.state(address: address) == .denied
167218
}
168219

220+
public func isGroupAllowed(groupId: Data) -> Bool {
221+
return consentList.groupState(groupId: groupId) == .allowed
222+
}
223+
224+
public func isGroupDenied(groupId: Data) -> Bool {
225+
return consentList.groupState(groupId: groupId) == .denied
226+
}
227+
169228
public func allow(addresses: [String]) async throws {
170229
for address in addresses {
171230
try await ConsentList(client: client).publish(entry: consentList.allow(address: address))
@@ -178,6 +237,20 @@ public actor Contacts {
178237
}
179238
}
180239

240+
public func allowGroup(groupIds: [Data]) async throws {
241+
for groupId in groupIds {
242+
let entry = consentList.allowGroup(groupId: groupId)
243+
try await ConsentList(client: client).publish(entry: entry)
244+
}
245+
}
246+
247+
public func denyGroup(groupIds: [Data]) async throws {
248+
for groupId in groupIds {
249+
let entry = consentList.denyGroup(groupId: groupId)
250+
try await ConsentList(client: client).publish(entry: entry)
251+
}
252+
}
253+
181254
func markIntroduced(_ peerAddress: String, _ isIntroduced: Bool) {
182255
hasIntroduced[peerAddress] = isIntroduced
183256
}
@@ -198,15 +271,11 @@ public actor Contacts {
198271
let response = try await client.query(topic: .contact(peerAddress))
199272

200273
for envelope in response.envelopes {
201-
// swiftlint:disable no_optional_try
202274
if let contactBundle = try? ContactBundle.from(envelope: envelope) {
203275
knownBundles[peerAddress] = contactBundle
204-
205276
return contactBundle
206277
}
207-
// swiftlint:enable no_optional_try
208278
}
209-
210279
return nil
211280
}
212281
}

Sources/XMTPiOS/Conversations.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,11 @@ public actor Conversations {
153153
throw GroupError.memberNotRegistered(erroredAddresses)
154154
}
155155

156-
return try await v3Client.conversations().createGroup(accountAddresses: addresses, permissions: permissions).fromFFI(client: client)
156+
let group = try await v3Client.conversations().createGroup(accountAddresses: addresses, permissions: permissions).fromFFI(client: client)
157+
158+
try await client.contacts.allowGroup(groupIds: [group.id])
159+
160+
return group
157161
}
158162

159163
/// Import a previously seen conversation.

Sources/XMTPiOS/Group.swift

+6
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ public struct Group: Identifiable, Equatable, Hashable {
109109
}
110110

111111
public func send(encodedContent: EncodedContent) async throws -> String {
112+
let groupState = await client.contacts.consentList.groupState(groupId: id)
113+
114+
if groupState == ConsentState.unknown {
115+
try await client.contacts.allowGroup(groupIds: [id])
116+
}
117+
112118
try await ffiGroup.send(contentBytes: encodedContent.serializedData())
113119
return id.toHex
114120
}

Sources/XMTPiOS/Proto/message_contents/message.pb.swift

+28-12
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,32 @@ public struct Xmtp_MessageContents_MessageV2 {
125125
public mutating func clearCiphertext() {self._ciphertext = nil}
126126

127127
/// HMAC of the message ciphertext, with the HMAC key derived from the topic key
128-
public var senderHmac: Data = Data()
128+
public var senderHmac: Data {
129+
get {return _senderHmac ?? Data()}
130+
set {_senderHmac = newValue}
131+
}
132+
/// Returns true if `senderHmac` has been explicitly set.
133+
public var hasSenderHmac: Bool {return self._senderHmac != nil}
134+
/// Clears the value of `senderHmac`. Subsequent reads from it will return its default value.
135+
public mutating func clearSenderHmac() {self._senderHmac = nil}
129136

130137
/// Flag indicating whether the message should be pushed from a notification server
131-
public var shouldPush: Bool = false
138+
public var shouldPush: Bool {
139+
get {return _shouldPush ?? false}
140+
set {_shouldPush = newValue}
141+
}
142+
/// Returns true if `shouldPush` has been explicitly set.
143+
public var hasShouldPush: Bool {return self._shouldPush != nil}
144+
/// Clears the value of `shouldPush`. Subsequent reads from it will return its default value.
145+
public mutating func clearShouldPush() {self._shouldPush = nil}
132146

133147
public var unknownFields = SwiftProtobuf.UnknownStorage()
134148

135149
public init() {}
136150

137151
fileprivate var _ciphertext: Xmtp_MessageContents_Ciphertext? = nil
152+
fileprivate var _senderHmac: Data? = nil
153+
fileprivate var _shouldPush: Bool? = nil
138154
}
139155

140156
/// Versioned Message
@@ -432,8 +448,8 @@ extension Xmtp_MessageContents_MessageV2: SwiftProtobuf.Message, SwiftProtobuf._
432448
switch fieldNumber {
433449
case 1: try { try decoder.decodeSingularBytesField(value: &self.headerBytes) }()
434450
case 2: try { try decoder.decodeSingularMessageField(value: &self._ciphertext) }()
435-
case 3: try { try decoder.decodeSingularBytesField(value: &self.senderHmac) }()
436-
case 4: try { try decoder.decodeSingularBoolField(value: &self.shouldPush) }()
451+
case 3: try { try decoder.decodeSingularBytesField(value: &self._senderHmac) }()
452+
case 4: try { try decoder.decodeSingularBoolField(value: &self._shouldPush) }()
437453
default: break
438454
}
439455
}
@@ -450,20 +466,20 @@ extension Xmtp_MessageContents_MessageV2: SwiftProtobuf.Message, SwiftProtobuf._
450466
try { if let v = self._ciphertext {
451467
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
452468
} }()
453-
if !self.senderHmac.isEmpty {
454-
try visitor.visitSingularBytesField(value: self.senderHmac, fieldNumber: 3)
455-
}
456-
if self.shouldPush != false {
457-
try visitor.visitSingularBoolField(value: self.shouldPush, fieldNumber: 4)
458-
}
469+
try { if let v = self._senderHmac {
470+
try visitor.visitSingularBytesField(value: v, fieldNumber: 3)
471+
} }()
472+
try { if let v = self._shouldPush {
473+
try visitor.visitSingularBoolField(value: v, fieldNumber: 4)
474+
} }()
459475
try unknownFields.traverse(visitor: &visitor)
460476
}
461477

462478
public static func ==(lhs: Xmtp_MessageContents_MessageV2, rhs: Xmtp_MessageContents_MessageV2) -> Bool {
463479
if lhs.headerBytes != rhs.headerBytes {return false}
464480
if lhs._ciphertext != rhs._ciphertext {return false}
465-
if lhs.senderHmac != rhs.senderHmac {return false}
466-
if lhs.shouldPush != rhs.shouldPush {return false}
481+
if lhs._senderHmac != rhs._senderHmac {return false}
482+
if lhs._shouldPush != rhs._shouldPush {return false}
467483
if lhs.unknownFields != rhs.unknownFields {return false}
468484
return true
469485
}

0 commit comments

Comments
 (0)