Skip to content

Commit 7968822

Browse files
committed
Group Preferences Actions
1 parent 1eab635 commit 7968822

File tree

10 files changed

+140
-50
lines changed

10 files changed

+140
-50
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
}

Tests/XMTPTests/ContactsTests.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ class ContactsTests: XCTestCase {
1515
let fixtures = await fixtures()
1616
try await fixtures.bobClient.ensureUserContactPublished()
1717

18-
let bobAddressLowercased = fixtures.bobClient.address.lowercased()
19-
let bobContact = try await fixtures.aliceClient.getUserContact(peerAddress: bobAddressLowercased)
18+
let bobAddressLowerCased = fixtures.bobClient.address.lowercased()
19+
let bobContact = try await fixtures.aliceClient.getUserContact(peerAddress: bobAddressLowerCased)
2020

2121
XCTAssertNotNil(bobContact)
2222
}
@@ -68,7 +68,7 @@ class ContactsTests: XCTestCase {
6868
XCTAssertTrue(result)
6969
}
7070

71-
func testBlockAddress() async throws {
71+
func testDenyAddress() async throws {
7272
let fixtures = await fixtures()
7373

7474
let contacts = fixtures.bobClient.contacts

Tests/XMTPTests/GroupTests.swift

+34-17
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ func assertThrowsAsyncError<T>(
3232
}
3333
}
3434

35-
3635
@available(iOS 16, *)
3736
class GroupTests: XCTestCase {
3837
// Use these fixtures to talk to the local node
@@ -92,7 +91,6 @@ class GroupTests: XCTestCase {
9291
XCTAssert(!bobGroup.id.isEmpty)
9392
XCTAssert(!aliceGroup.id.isEmpty)
9493

95-
9694
try await aliceGroup.addMembers(addresses: [fixtures.fred.address])
9795
try await bobGroup.sync()
9896
XCTAssertEqual(aliceGroup.memberAddresses.count, 3)
@@ -115,16 +113,21 @@ class GroupTests: XCTestCase {
115113
XCTAssert(try bobGroup.isAdmin())
116114
XCTAssert(try !aliceGroup.isAdmin())
117115
}
118-
116+
119117
func testCanCreateAGroupWithAdminPermissions() async throws {
120118
let fixtures = try await localFixtures()
121119
let bobGroup = try await fixtures.bobClient.conversations.newGroup(with: [fixtures.alice.address], permissions: GroupPermissions.groupCreatorIsAdmin)
122120
try await fixtures.aliceClient.conversations.sync()
123121
let aliceGroup = try await fixtures.aliceClient.conversations.groups().first!
124122
XCTAssert(!bobGroup.id.isEmpty)
125123
XCTAssert(!aliceGroup.id.isEmpty)
126-
127-
124+
125+
let bobConsentResult = await fixtures.bobClient.contacts.consentList.groupState(groupId: bobGroup.id)
126+
XCTAssertEqual(bobConsentResult, ConsentState.allowed)
127+
128+
let aliceConsentResult = await fixtures.aliceClient.contacts.consentList.groupState(groupId: aliceGroup.id)
129+
XCTAssertEqual(aliceConsentResult, ConsentState.unknown)
130+
128131
try await bobGroup.addMembers(addresses: [fixtures.fred.address])
129132
try await aliceGroup.sync()
130133
XCTAssertEqual(aliceGroup.memberAddresses.count, 3)
@@ -192,7 +195,6 @@ class GroupTests: XCTestCase {
192195
let members = group.memberAddresses.map(\.localizedLowercase).sorted()
193196
let peerMembers = Conversation.group(group).peerAddresses.map(\.localizedLowercase).sorted()
194197

195-
196198
XCTAssertEqual([fixtures.bob.address.localizedLowercase, fixtures.alice.address.localizedLowercase].sorted(), members)
197199
XCTAssertEqual([fixtures.bob.address.localizedLowercase].sorted(), peerMembers)
198200
}
@@ -329,15 +331,30 @@ class GroupTests: XCTestCase {
329331
}
330332
}
331333

334+
func testGroupStartsWithAllowedState() async throws {
335+
let fixtures = try await localFixtures()
336+
let bobGroup = try await fixtures.bobClient.conversations.newGroup(with: [fixtures.alice.walletAddress])
337+
338+
_ = try await bobGroup.send(content: "howdy")
339+
_ = try await bobGroup.send(content: "gm")
340+
try await bobGroup.sync()
341+
342+
let isGroupAllowedResult = await fixtures.bobClient.contacts.isGroupAllowed(groupId: bobGroup.id)
343+
XCTAssertTrue(isGroupAllowedResult)
344+
345+
let groupStateResult = await fixtures.bobClient.contacts.consentList.groupState(groupId: bobGroup.id)
346+
XCTAssertEqual(groupStateResult, ConsentState.allowed)
347+
}
348+
332349
func testCanSendMessagesToGroup() async throws {
333350
let fixtures = try await localFixtures()
334351
let aliceGroup = try await fixtures.aliceClient.conversations.newGroup(with: [fixtures.bob.address])
335352

336353
try await fixtures.bobClient.conversations.sync()
337354
let bobGroup = try await fixtures.bobClient.conversations.groups()[0]
338355

339-
try await aliceGroup.send(content: "sup gang original")
340-
try await aliceGroup.send(content: "sup gang")
356+
_ = try await aliceGroup.send(content: "sup gang original")
357+
_ = try await aliceGroup.send(content: "sup gang")
341358

342359
try await aliceGroup.sync()
343360
let aliceGroupsCount = try await aliceGroup.messages().count
@@ -360,8 +377,8 @@ class GroupTests: XCTestCase {
360377
try await fixtures.bobClient.conversations.sync()
361378
let bobGroup = try await fixtures.bobClient.conversations.groups()[0]
362379

363-
try await aliceGroup.send(content: "sup gang original")
364-
try await aliceGroup.send(content: "sup gang")
380+
_ = try await aliceGroup.send(content: "sup gang original")
381+
_ = try await aliceGroup.send(content: "sup gang")
365382

366383
try await aliceGroup.sync()
367384
let aliceGroupsCount = try await aliceGroup.decryptedMessages().count
@@ -388,7 +405,7 @@ class GroupTests: XCTestCase {
388405
}
389406
}
390407

391-
try await fixtures.bobClient.conversations.newGroup(with: [fixtures.alice.address])
408+
_ = try await fixtures.bobClient.conversations.newGroup(with: [fixtures.alice.address])
392409

393410
await waitForExpectations(timeout: 3)
394411
}
@@ -425,8 +442,8 @@ class GroupTests: XCTestCase {
425442
}
426443
}
427444

428-
try await group.send(content: "hi")
429-
try await convo.send(content: "hi")
445+
_ = try await group.send(content: "hi")
446+
_ = try await convo.send(content: "hi")
430447

431448
await waitForExpectations(timeout: 3)
432449
}
@@ -445,8 +462,8 @@ class GroupTests: XCTestCase {
445462
}
446463
}
447464

448-
try await group.send(content: "hi")
449-
try await convo.send(content: "hi")
465+
_ = try await group.send(content: "hi")
466+
_ = try await convo.send(content: "hi")
450467

451468
await waitForExpectations(timeout: 3)
452469
}
@@ -464,7 +481,7 @@ class GroupTests: XCTestCase {
464481
}
465482
}
466483

467-
try await group.send(content: "hi")
484+
_ = try await group.send(content: "hi")
468485

469486
await waitForExpectations(timeout: 3)
470487
}
@@ -481,7 +498,7 @@ class GroupTests: XCTestCase {
481498
}
482499
}
483500

484-
try await group.send(content: "hi")
501+
_ = try await group.send(content: "hi")
485502

486503
await waitForExpectations(timeout: 3)
487504
}

XMTPiOSExample/.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

0 commit comments

Comments
 (0)