Skip to content

Commit

Permalink
Start on v2 allow state stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
nakajima committed Oct 11, 2023
1 parent 8fac49c commit 0d00b17
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 93 deletions.
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/xmtp/xmtp-rust-swift",
"state" : {
"revision" : "4a76e5401fa780c40610e2f0d248f695261d08dd",
"version" : "0.3.1-beta0"
"revision" : "d1aaac47fc7c57645a6fe9e06972b957b3efa33c",
"version" : "0.3.5-beta0"
}
}
],
Expand Down
69 changes: 69 additions & 0 deletions Sources/XMTP/Contacts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,35 @@

import Foundation

public enum AllowState: String, Codable {
case allowed, blocked, unknown
}

struct AllowListEntry: Codable, Hashable {
enum EntryType: String, Codable {
case address
}

static func address(_ address: String, type: AllowState) -> AllowListEntry {
AllowListEntry(value: address, entryType: .address, permissionType: type)
}

var value: String
var entryType: EntryType
var permissionType: AllowState
}

struct AllowList {
var allowedAddresses: Set<AllowListEntry> = []
var blockedAddresses: Set<AllowListEntry> = []

var entries: Set<AllowListEntry> = []

func state(address: String) -> AllowState {
entries.first(where: { $0.entryType == .address && $0.value == address })?.permissionType ?? AllowState.unknown
}
}

/// Provides access to contact bundles.
public actor Contacts {
var client: Client
Expand All @@ -17,10 +46,50 @@ public actor Contacts {
// Whether or not we have sent invite/intro to this contact
var hasIntroduced: [String: Bool] = [:]

var allowList = AllowList()

init(client: Client) {
self.client = client
}

public func isAllowed(_ address: String) -> Bool {
for entry in allowList.entries {
switch entry.entryType {
case .address:
if address == entry.value {
return entry.permissionType == .allowed
}
}
}

return false
}

public func isBlocked(_ address: String) -> Bool {
for entry in allowList.entries {
switch entry.entryType {
case .address:
if address == entry.value {
return entry.permissionType == .blocked
}
}
}

return false
}

public func allow(addresses: [String]) {
for address in addresses {
allowList.entries.insert(.address(address, type: .allowed))
}
}

public func block(addresses: [String]) {
for address in addresses {
allowList.entries.insert(.address(address, type: .blocked))
}
}

func markIntroduced(_ peerAddress: String, _ isIntroduced: Bool) {
hasIntroduced[peerAddress] = isIntroduced
}
Expand Down
39 changes: 26 additions & 13 deletions Sources/XMTP/Conversation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ public enum Conversation: Sendable {
case v1, v2
}

public func allowState() async -> AllowState {
let client: Client

switch self {
case .v1(let conversationV1):
client = conversationV1.client
case .v2(let conversationV2):
client = conversationV2.client
}

return await client.contacts.allowList.state(address: peerAddress)
}

public var version: Version {
switch self {
case .v1:
Expand Down Expand Up @@ -82,7 +95,7 @@ public enum Conversation: Sendable {
/// See Conversations.importTopicData()
public func toTopicData() -> Xmtp_KeystoreApi_V1_TopicMap.TopicData {
Xmtp_KeystoreApi_V1_TopicMap.TopicData.with {
$0.createdNs = UInt64(createdAt.timeIntervalSince1970 * 1_000) * 1_000_000
$0.createdNs = UInt64(createdAt.timeIntervalSince1970 * 1000) * 1_000_000
$0.peerAddress = peerAddress
if case let .v2(cv2) = self {
$0.invitation = Xmtp_MessageContents_InvitationV1.with {
Expand Down Expand Up @@ -123,16 +136,16 @@ public enum Conversation: Sendable {
}
}

// This is a convenience for invoking the underlying `client.publish(prepared.envelopes)`
// If a caller has a `Client` handy, they may opt to do that directly instead.
@discardableResult public func send(prepared: PreparedMessage) async throws -> String {
switch self {
case let .v1(conversationV1):
return try await conversationV1.send(prepared: prepared)
case let .v2(conversationV2):
return try await conversationV2.send(prepared: prepared)
}
}
// This is a convenience for invoking the underlying `client.publish(prepared.envelopes)`
// If a caller has a `Client` handy, they may opt to do that directly instead.
@discardableResult public func send(prepared: PreparedMessage) async throws -> String {
switch self {
case let .v1(conversationV1):
return try await conversationV1.send(prepared: prepared)
case let .v2(conversationV2):
return try await conversationV2.send(prepared: prepared)
}
}

@discardableResult public func send<T>(content: T, options: SendOptions? = nil, fallback _: String? = nil) async throws -> String {
switch self {
Expand Down Expand Up @@ -199,10 +212,10 @@ public enum Conversation: Sendable {
}

/// List messages in the conversation
public func messages(limit: Int? = nil, before: Date? = nil, after: Date? = nil, direction: PagingInfoSortDirection? = .descending) async throws -> [DecodedMessage] {
public func messages(limit: Int? = nil, before: Date? = nil, after: Date? = nil, direction: PagingInfoSortDirection? = .descending) async throws -> [DecodedMessage] {
switch self {
case let .v1(conversationV1):
return try await conversationV1.messages(limit: limit, before: before, after: after, direction: direction)
return try await conversationV1.messages(limit: limit, before: before, after: after, direction: direction)
case let .v2(conversationV2):
return try await conversationV2.messages(limit: limit, before: before, after: after, direction: direction)
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/XMTP/Conversations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ public actor Conversations {
let sealedInvitation = try await sendInvitation(recipient: recipient, invitation: invitation, created: Date())
let conversationV2 = try ConversationV2.create(client: client, invitation: invitation, header: sealedInvitation.v1.header)

await client.contacts.allow(addresses: [peerAddress])

let conversation: Conversation = .v2(conversationV2)
conversationsByTopic[conversation.topic] = conversation
return conversation
Expand Down
28 changes: 28 additions & 0 deletions Tests/XMTPTests/ContactsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,32 @@ class ContactsTests: XCTestCase {
let hasContact = await fixtures.aliceClient.contacts.has(fixtures.bob.walletAddress)
XCTAssert(hasContact)
}

func testAllowAddress() async throws {
let fixtures = await fixtures()

let contacts = fixtures.bobClient.contacts
var result = await contacts.isAllowed(fixtures.alice.address)

XCTAssertFalse(result)

await contacts.allow(addresses: [fixtures.alice.address])

result = await contacts.isAllowed(fixtures.alice.address)
XCTAssertTrue(result)
}

func testBlockAddress() async throws {
let fixtures = await fixtures()

let contacts = fixtures.bobClient.contacts
var result = await contacts.isAllowed(fixtures.alice.address)

XCTAssertFalse(result)

await contacts.block(addresses: [fixtures.alice.address])

result = await contacts.isBlocked(fixtures.alice.address)
XCTAssertTrue(result)
}
}
Loading

0 comments on commit 0d00b17

Please sign in to comment.