From dcfb37f2771e9421bc1869eb3a1da5ac441d9e07 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 8 Jul 2022 14:31:31 +0700 Subject: [PATCH 1/4] Chat SDK integration --- Example/ExampleApp.xcodeproj/project.pbxproj | 47 ++++++++++++- Example/IntegrationTests/Chat/ChatTests.swift | 8 +-- .../ApplicationLayer/Application.swift | 3 +- .../DomainLayer/Chat/ChatFactory.swift | 22 ++++++ .../DomainLayer/Chat/ChatService.swift | 57 +++++++++++++++ .../Classes/DomainLayer/ChatService.swift | 69 ------------------- .../SocketFactory/SocketFactory.swift | 11 +++ .../Chat/ChatInteractor.swift | 15 ++-- .../PresentationLayer/Chat/ChatModule.swift | 5 +- .../Chat/ChatPresenter.swift | 23 ++++--- .../Chat/Models/MessageViewModel.swift | 19 +++-- .../ChatList/ChatListInteractor.swift | 10 ++- .../ChatList/ChatListPresenter.swift | 16 +++-- .../ChatList/ChatListRouter.swift | 5 +- .../ChatList/Models/ThreadViewModel.swift | 12 +--- .../Invite/InviteInteractor.swift | 4 +- .../Invite/InvitePresenter.swift | 2 +- .../InviteList/InviteListInteractor.swift | 11 ++- .../InviteList/InviteListPresenter.swift | 13 ++-- .../InviteList/Models/InviteViewModel.swift | 9 +-- Sources/Chat/{Chat.swift => ChatClient.swift} | 4 +- Sources/Chat/Registry.swift | 16 ++--- Sources/Chat/Types/InviteParams.swift | 10 +-- Sources/Chat/Types/Message.swift | 10 +-- Sources/Chat/Types/Thread.swift | 8 +-- 25 files changed, 249 insertions(+), 160 deletions(-) create mode 100644 Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift create mode 100644 Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift delete mode 100644 Example/Showcase/Classes/DomainLayer/ChatService.swift create mode 100644 Example/Showcase/Classes/DomainLayer/SocketFactory/SocketFactory.swift rename Sources/Chat/{Chat.swift => ChatClient.swift} (99%) diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index d4fb17fdf..99ef2e6f7 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -64,6 +64,10 @@ A5629AE22876CC6E00094373 /* InviteListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629ADD2876CC6E00094373 /* InviteListView.swift */; }; A5629AE42876E6D200094373 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AE32876E6D200094373 /* ThreadViewModel.swift */; }; A5629AE828772A0100094373 /* InviteViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AE728772A0100094373 /* InviteViewModel.swift */; }; + A5629AEA2877F2D600094373 /* WalletConnectChat in Frameworks */ = {isa = PBXBuildFile; productRef = A5629AE92877F2D600094373 /* WalletConnectChat */; }; + A5629AED2877F6A600094373 /* ChatFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AEC2877F6A600094373 /* ChatFactory.swift */; }; + A5629AF02877F73000094373 /* SocketFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AEF2877F73000094373 /* SocketFactory.swift */; }; + A5629AF22877F75100094373 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = A5629AF12877F75100094373 /* Starscream */; }; A578FA322873036400AA7720 /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A578FA312873036400AA7720 /* InputView.swift */; }; A578FA35287304A300AA7720 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = A578FA34287304A300AA7720 /* Color.swift */; }; A578FA372873D8EE00AA7720 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A578FA362873D8EE00AA7720 /* UIColor.swift */; }; @@ -201,6 +205,8 @@ A5629ADD2876CC6E00094373 /* InviteListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteListView.swift; sourceTree = ""; }; A5629AE32876E6D200094373 /* ThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadViewModel.swift; sourceTree = ""; }; A5629AE728772A0100094373 /* InviteViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteViewModel.swift; sourceTree = ""; }; + A5629AEC2877F6A600094373 /* ChatFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatFactory.swift; sourceTree = ""; }; + A5629AEF2877F73000094373 /* SocketFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocketFactory.swift; sourceTree = ""; }; A578FA312873036400AA7720 /* InputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputView.swift; sourceTree = ""; }; A578FA34287304A300AA7720 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; A578FA362873D8EE00AA7720 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; @@ -283,6 +289,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A5629AEA2877F2D600094373 /* WalletConnectChat in Frameworks */, + A5629AF22877F75100094373 /* Starscream in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -472,7 +480,8 @@ A5629AA42876A19D00094373 /* DomainLayer */ = { isa = PBXGroup; children = ( - A5629AA82876A23100094373 /* ChatService.swift */, + A5629AEE2877F72B00094373 /* SocketFactory */, + A5629AEB2877F69C00094373 /* Chat */, ); path = DomainLayer; sourceTree = ""; @@ -531,6 +540,23 @@ path = Models; sourceTree = ""; }; + A5629AEB2877F69C00094373 /* Chat */ = { + isa = PBXGroup; + children = ( + A5629AA82876A23100094373 /* ChatService.swift */, + A5629AEC2877F6A600094373 /* ChatFactory.swift */, + ); + path = Chat; + sourceTree = ""; + }; + A5629AEE2877F72B00094373 /* SocketFactory */ = { + isa = PBXGroup; + children = ( + A5629AEF2877F73000094373 /* SocketFactory.swift */, + ); + path = SocketFactory; + sourceTree = ""; + }; A578FA332873049400AA7720 /* Style */ = { isa = PBXGroup; children = ( @@ -848,6 +874,10 @@ dependencies = ( ); name = Showcase; + packageProductDependencies = ( + A5629AE92877F2D600094373 /* WalletConnectChat */, + A5629AF12877F75100094373 /* Starscream */, + ); productName = Showcase; productReference = A58E7CE828729F550082D443 /* Showcase.app */; productType = "com.apple.product-type.application"; @@ -1080,6 +1110,8 @@ A5629AE22876CC6E00094373 /* InviteListView.swift in Sources */, A578FA3D2874002400AA7720 /* View.swift in Sources */, A5629AD72876CC5700094373 /* InviteView.swift in Sources */, + A5629AF02877F73000094373 /* SocketFactory.swift in Sources */, + A5629AED2877F6A600094373 /* ChatFactory.swift in Sources */, A58E7D1D2872A57B0082D443 /* Configurator.swift in Sources */, A58E7D482872EF610082D443 /* MessageViewModel.swift in Sources */, A5629AD32876CC5700094373 /* InviteModule.swift in Sources */, @@ -1421,7 +1453,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.Showcase; + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.showcase; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -1450,7 +1482,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.Showcase; + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.showcase; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -1629,6 +1661,15 @@ package = 8449439F278EC49700CC26BB /* XCRemoteSwiftPackageReference "Web3" */; productName = Web3; }; + A5629AE92877F2D600094373 /* WalletConnectChat */ = { + isa = XCSwiftPackageProductDependency; + productName = WalletConnectChat; + }; + A5629AF12877F75100094373 /* Starscream */ = { + isa = XCSwiftPackageProductDependency; + package = A5D85224286333D500DAF5C3 /* XCRemoteSwiftPackageReference "Starscream" */; + productName = Starscream; + }; A5D85225286333D500DAF5C3 /* Starscream */ = { isa = XCSwiftPackageProductDependency; package = A5D85224286333D500DAF5C3 /* XCRemoteSwiftPackageReference "Starscream" */; diff --git a/Example/IntegrationTests/Chat/ChatTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift index cafa0752f..8eb49c1b2 100644 --- a/Example/IntegrationTests/Chat/ChatTests.swift +++ b/Example/IntegrationTests/Chat/ChatTests.swift @@ -7,8 +7,8 @@ import WalletConnectRelay import Combine final class ChatTests: XCTestCase { - var invitee: Chat! - var inviter: Chat! + var invitee: ChatClient! + var inviter: ChatClient! var registry: KeyValueRegistry! private var publishers = [AnyCancellable]() @@ -37,13 +37,13 @@ final class ChatTests: XCTestCase { return } - func makeClient(prefix: String) -> Chat { + func makeClient(prefix: String) -> ChatClient { let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug) let relayHost = "relay.walletconnect.com" let projectId = "8ba9ee138960775e5231b70cc5ef1c3a" let keychain = KeychainStorageMock() let relayClient = RelayClient(relayHost: relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger) - return Chat(registry: registry, relayClient: relayClient, kms: KeyManagementService(keychain: keychain), logger: logger, keyValueStorage: RuntimeKeyValueStorage()) + return ChatClient(registry: registry, relayClient: relayClient, kms: KeyManagementService(keychain: keychain), logger: logger, keyValueStorage: RuntimeKeyValueStorage()) } func testInvite() async { diff --git a/Example/Showcase/Classes/ApplicationLayer/Application.swift b/Example/Showcase/Classes/ApplicationLayer/Application.swift index d8f80727a..efb67378e 100644 --- a/Example/Showcase/Classes/ApplicationLayer/Application.swift +++ b/Example/Showcase/Classes/ApplicationLayer/Application.swift @@ -1,8 +1,9 @@ import Foundation +import Chat final class Application { let chatService: ChatService = { - return ChatService() + return ChatService(client: ChatFactory.create()) }() } diff --git a/Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift b/Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift new file mode 100644 index 000000000..a550b4b03 --- /dev/null +++ b/Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift @@ -0,0 +1,22 @@ +import Foundation +import Chat +import WalletConnectKMS +import WalletConnectRelay + +class ChatFactory { + + static func create() -> ChatClient { + let relayHost = "relay.walletconnect.com" + let projectId = "8ba9ee138960775e5231b70cc5ef1c3a" + let keychain = KeychainStorage(serviceIdentifier: "com.walletconnect.showcase") + let client = HTTPClient(host: relayHost) + let registry = KeyserverRegistryProvider(client: client) + let relayClient = RelayClient(relayHost: relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory()) + return ChatClient( + registry: registry, + relayClient: relayClient, + kms: KeyManagementService(keychain: keychain), + keyValueStorage: UserDefaults.standard + ) + } +} diff --git a/Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift b/Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift new file mode 100644 index 000000000..1b8693e9e --- /dev/null +++ b/Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift @@ -0,0 +1,57 @@ +import Foundation +import Combine +import Chat +import WalletConnectUtils + +typealias Stream = AsyncPublisher> + +final class ChatService { + + static let selfAccount = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")! + + private let client: ChatClient + + init(client: ChatClient) { + self.client = client + } + + var messagePublisher: Stream { + return client.messagePublisher.values + } + + var threadPublisher: Stream { + return client.newThreadPublisher.values + } + + var invitePublisher: Stream { + return client.invitePublisher.values + } + + func getMessages(thread: Chat.Thread) async -> [Chat.Message] { + await client.getMessages(topic: thread.topic) + } + + func getThreads() async -> [Chat.Thread] { + await client.getThreads() + } + + func getInvites(account: Account) async -> [Chat.Invite] { + client.getInvites(account: account) + } + + func sendMessage(topic: String, message: String) async throws { + try await client.message(topic: topic, message: message) + } + + func accept(invite: Invite) async throws { + try await client.accept(inviteId: invite.pubKey) + } + + func reject(invite: Invite) async throws { + try await client.reject(inviteId: invite.pubKey) + } + + func invite(peerPubkey publicKey: String, peerAccount: Account, message: String, selfAccount: Account) async throws { + try await client.invite(publicKey: publicKey, peerAccount: peerAccount, openingMessage: message, account: selfAccount) + } +} diff --git a/Example/Showcase/Classes/DomainLayer/ChatService.swift b/Example/Showcase/Classes/DomainLayer/ChatService.swift deleted file mode 100644 index e3769e83f..000000000 --- a/Example/Showcase/Classes/DomainLayer/ChatService.swift +++ /dev/null @@ -1,69 +0,0 @@ -import Foundation -import Combine - -typealias Stream = AsyncPublisher> - -final class ChatService { - - static let authorAccount: String = "TODO" - - private let messagesSubject: CurrentValueSubject<[Message], Never> = { - return CurrentValueSubject([ - Message(message: "✨gm", authorAccount: "2", timestamp: 0), - Message(message: "how r u man?", authorAccount: "1", timestamp: 0), - Message(message: "good", authorAccount: "2", timestamp: 0), - Message(message: "u?", authorAccount: "2", timestamp: 0), - Message(message: "I’m so happy I have you as my best friend, and I love Lisa so much", authorAccount: "1", timestamp: 0), - Message(message: "Why, Lisa, why, WHY?!", authorAccount: "2", timestamp: 0), - Message(message: "It’s bullshit, I did not hit her. I did nooot. Oh hi, Mark!", authorAccount: "1", timestamp: 0), - Message(message: "Johnny’s my best friend!", authorAccount: "2", timestamp: 0), - Message(message: "Anyway, how’s your sex life?", authorAccount: "1", timestamp: 0) - ]) - }() - - private let threadsSubject: CurrentValueSubject<[Thread], Never> = { - return CurrentValueSubject([ - Thread(topic: "topic1"), - Thread(topic: "topic2") - ]) - }() - - private let invitesSubject: CurrentValueSubject<[Invite], Never> = { - return CurrentValueSubject([ - Invite(message: "In a few minutes, bitch.", pubKey: "slava.eth") - ]) - }() - - func getMessages(topic: String) -> Stream<[Message]> { - return messagesSubject.eraseToAnyPublisher().values - } - - func getThreads() -> Stream<[Thread]> { - return threadsSubject.eraseToAnyPublisher().values - } - - func getInvites() -> Stream<[Invite]> { - return invitesSubject.eraseToAnyPublisher().values - } - - func sendMessage(text: String) async throws { - let message = Message( - message: text, - authorAccount: ChatService.authorAccount, - timestamp: Int64(Date().timeIntervalSince1970) - ) - messagesSubject.send(messagesSubject.value + [message]) - } - - func accept(invite: Invite) async throws { - - } - - func reject(invite: Invite) async throws { - - } - - func invite(account: String) async throws { - - } -} diff --git a/Example/Showcase/Classes/DomainLayer/SocketFactory/SocketFactory.swift b/Example/Showcase/Classes/DomainLayer/SocketFactory/SocketFactory.swift new file mode 100644 index 000000000..d22a5ef48 --- /dev/null +++ b/Example/Showcase/Classes/DomainLayer/SocketFactory/SocketFactory.swift @@ -0,0 +1,11 @@ +import Foundation +import Starscream +import WalletConnectRelay + +extension WebSocket: WebSocketConnecting { } + +struct SocketFactory: WebSocketFactory { + func create(with url: URL) -> WebSocketConnecting { + return WebSocket(url: url) + } +} diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/ChatInteractor.swift b/Example/Showcase/Classes/PresentationLayer/Chat/ChatInteractor.swift index 045a6e2e9..be6326054 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/ChatInteractor.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/ChatInteractor.swift @@ -1,3 +1,6 @@ +import Foundation +import Chat + final class ChatInteractor { private let chatService: ChatService @@ -6,11 +9,15 @@ final class ChatInteractor { self.chatService = chatService } - func getMessages(topic: String) -> Stream<[Message]> { - return chatService.getMessages(topic: topic) + func getMessages(thread: Chat.Thread) async -> [Message] { + return await chatService.getMessages(thread: thread) + } + + func messagesSubscription() -> Stream { + return chatService.messagePublisher } - func sendMessage(text: String) async throws { - try await chatService.sendMessage(text: text) + func sendMessage(topic: String, message: String) async throws { + try await chatService.sendMessage(topic: topic, message: message) } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/ChatModule.swift b/Example/Showcase/Classes/PresentationLayer/Chat/ChatModule.swift index 778c43367..6cb3d854d 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/ChatModule.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/ChatModule.swift @@ -1,12 +1,13 @@ import SwiftUI +import Chat final class ChatModule { @discardableResult - static func create(topic: String, app: Application) -> UIViewController { + static func create(thread: Chat.Thread, app: Application) -> UIViewController { let router = ChatRouter(app: app) let interactor = ChatInteractor(chatService: app.chatService) - let presenter = ChatPresenter(topic: topic, interactor: interactor, router: router) + let presenter = ChatPresenter(thread: thread, interactor: interactor, router: router) let view = ChatView().environmentObject(presenter) let viewController = SceneViewController(viewModel: presenter, content: view) diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/ChatPresenter.swift b/Example/Showcase/Classes/PresentationLayer/Chat/ChatPresenter.swift index dd4f43cd2..302cb9ab7 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/ChatPresenter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/ChatPresenter.swift @@ -1,9 +1,10 @@ import UIKit import Combine +import Chat final class ChatPresenter: ObservableObject { - private let topic: String + private let thread: Chat.Thread private let interactor: ChatInteractor private let router: ChatRouter private var disposeBag = Set() @@ -11,18 +12,18 @@ final class ChatPresenter: ObservableObject { @Published var messages: [MessageViewModel] = [] @Published var input: String = .empty - init(topic: String, interactor: ChatInteractor, router: ChatRouter) { - self.topic = topic + init(thread: Chat.Thread, interactor: ChatInteractor, router: ChatRouter) { + self.thread = thread self.interactor = interactor self.router = router } @MainActor func setupInitialState() async { - for await messages in interactor.getMessages(topic: topic) { - self.messages = messages - .sorted(by: { $0.timestamp < $1.timestamp }) - .map { MessageViewModel(message: $0, currentAccount: "TODO") } + await loadMessages() + + for await _ in interactor.messagesSubscription() { + await loadMessages() } } @@ -44,9 +45,15 @@ extension ChatPresenter: SceneViewModel { private extension ChatPresenter { + func loadMessages() async { + let messages = await interactor.getMessages(thread: thread) + self.messages = messages.sorted(by: { $0.timestamp < $1.timestamp }) + .map { MessageViewModel(message: $0, thread: thread) } + } + func sendMessage() { Task { - try! await interactor.sendMessage(text: input) + try! await interactor.sendMessage(topic: thread.topic, message: input) input = .empty } } diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Models/MessageViewModel.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Models/MessageViewModel.swift index 64166ad49..8f15f706e 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/Models/MessageViewModel.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/Models/MessageViewModel.swift @@ -1,19 +1,18 @@ import Foundation - -// TODO: Delete after Chat SDK integration -struct Message: Codable, Equatable { - let message: String - let authorAccount: String - let timestamp: Int64 -} +import WalletConnectUtils +import Chat struct MessageViewModel { private let message: Message - private let currentAccount: String + private let thread: Chat.Thread - init(message: Message, currentAccount: String) { + init(message: Message, thread: Chat.Thread) { self.message = message - self.currentAccount = currentAccount + self.thread = thread + } + + var currentAccount: Account { + return thread.selfAccount } var isCurrentUser: Bool { diff --git a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListInteractor.swift b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListInteractor.swift index 9cc9bf4c0..ba3fe15c6 100644 --- a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListInteractor.swift +++ b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListInteractor.swift @@ -1,3 +1,5 @@ +import Chat + final class ChatListInteractor { private let chatService: ChatService @@ -6,7 +8,11 @@ final class ChatListInteractor { self.chatService = chatService } - func getThreads() -> Stream<[Thread]> { - return chatService.getThreads() + func getThreads() async -> [Chat.Thread] { + return await chatService.getThreads() + } + + func threadsSubscription() -> Stream { + return chatService.threadPublisher } } diff --git a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListPresenter.swift b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListPresenter.swift index c8f4558ea..c5a91f521 100644 --- a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListPresenter.swift +++ b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListPresenter.swift @@ -16,15 +16,15 @@ final class ChatListPresenter: ObservableObject { @MainActor func setupInitialState() async { - for await threads in interactor.getThreads() { - self.threads = threads - .sorted(by: { $0.topic < $1.topic }) - .map { ThreadViewModel(thread: $0) } + await loadThreads() + + for await _ in interactor.threadsSubscription() { + await loadThreads() } } func didPressThread(_ thread: ThreadViewModel) { - router.presentChat(topic: thread.topic) + router.presentChat(thread: thread.thread) } func didPressChatRequests() { @@ -57,6 +57,12 @@ extension ChatListPresenter: SceneViewModel { private extension ChatListPresenter { + func loadThreads() async { + let threads = await interactor.getThreads() + self.threads = threads.sorted(by: { $0.topic < $1.topic }) + .map { ThreadViewModel(thread: $0) } + } + @objc func presentInvite() { router.presentInvite() } diff --git a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListRouter.swift b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListRouter.swift index ce8ba6cde..9b673f298 100644 --- a/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListRouter.swift +++ b/Example/Showcase/Classes/PresentationLayer/ChatList/ChatListRouter.swift @@ -1,4 +1,5 @@ import UIKit +import Chat final class ChatListRouter { @@ -20,7 +21,7 @@ final class ChatListRouter { InviteListModule.create(app: app).push(from: viewController) } - func presentChat(topic: String) { - ChatModule.create(topic: topic, app: app).push(from: viewController) + func presentChat(thread: Chat.Thread) { + ChatModule.create(thread: thread, app: app).push(from: viewController) } } diff --git a/Example/Showcase/Classes/PresentationLayer/ChatList/Models/ThreadViewModel.swift b/Example/Showcase/Classes/PresentationLayer/ChatList/Models/ThreadViewModel.swift index 0ee86de58..30d66222e 100644 --- a/Example/Showcase/Classes/PresentationLayer/ChatList/Models/ThreadViewModel.swift +++ b/Example/Showcase/Classes/PresentationLayer/ChatList/Models/ThreadViewModel.swift @@ -1,16 +1,8 @@ import Foundation - -// TODO: Delete after Chat SDK integration -struct Thread: Codable { - let topic: String -} +import Chat struct ThreadViewModel: Identifiable { - private let thread: Thread - - init(thread: Thread) { - self.thread = thread - } + let thread: Chat.Thread var topic: String { return thread.topic diff --git a/Example/Showcase/Classes/PresentationLayer/Invite/InviteInteractor.swift b/Example/Showcase/Classes/PresentationLayer/Invite/InviteInteractor.swift index 4b8537296..35be0fb15 100644 --- a/Example/Showcase/Classes/PresentationLayer/Invite/InviteInteractor.swift +++ b/Example/Showcase/Classes/PresentationLayer/Invite/InviteInteractor.swift @@ -5,7 +5,7 @@ final class InviteInteractor { self.chatService = chatService } - func invite(account: String) async { - try! await chatService.invite(account: account) + func invite(account: String, message: String) async { +// try! await chatService.invite(peerPubkey: <#T##String#>, peerAccount: <#T##Account#>, message: message, selfAccount: ChatService.selfAccount) } } diff --git a/Example/Showcase/Classes/PresentationLayer/Invite/InvitePresenter.swift b/Example/Showcase/Classes/PresentationLayer/Invite/InvitePresenter.swift index e9377d27d..ac5889d11 100644 --- a/Example/Showcase/Classes/PresentationLayer/Invite/InvitePresenter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Invite/InvitePresenter.swift @@ -61,7 +61,7 @@ private extension InvitePresenter { @MainActor @objc func invite() { Task(priority: .userInitiated) { - await interactor.invite(account: input) + await interactor.invite(account: input, message: "Hi, welcome!") router.dismiss() } } diff --git a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListInteractor.swift b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListInteractor.swift index 7706577b4..012aa445b 100644 --- a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListInteractor.swift +++ b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListInteractor.swift @@ -1,3 +1,6 @@ +import Chat +import WalletConnectUtils + final class InviteListInteractor { private let chatService: ChatService @@ -5,8 +8,12 @@ final class InviteListInteractor { self.chatService = chatService } - func getInvites() -> Stream<[Invite]> { - return chatService.getInvites() + func getInvites() async -> [Invite] { + return await chatService.getInvites(account: ChatService.selfAccount) + } + + func invitesSubscription() -> Stream { + return chatService.invitePublisher } func accept(invite: Invite) async { diff --git a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListPresenter.swift b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListPresenter.swift index ac88ff5ef..2171f43b5 100644 --- a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListPresenter.swift +++ b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListPresenter.swift @@ -17,10 +17,10 @@ final class InviteListPresenter: ObservableObject { @MainActor func setupInitialState() async { - for await invites in interactor.getInvites() { - self.invites = invites - .sorted(by: { $0.pubKey < $1.pubKey }) - .map { InviteViewModel(invite: $0) } + await loadInvites() + + for await _ in interactor.invitesSubscription() { + await loadInvites() } } @@ -54,4 +54,9 @@ extension InviteListPresenter: SceneViewModel { private extension InviteListPresenter { + func loadInvites() async { + let invites = await interactor.getInvites() + self.invites = invites.sorted(by: { $0.pubKey < $1.pubKey }) + .map { InviteViewModel(invite: $0) } + } } diff --git a/Example/Showcase/Classes/PresentationLayer/InviteList/Models/InviteViewModel.swift b/Example/Showcase/Classes/PresentationLayer/InviteList/Models/InviteViewModel.swift index 0c4784a87..2ad932bca 100644 --- a/Example/Showcase/Classes/PresentationLayer/InviteList/Models/InviteViewModel.swift +++ b/Example/Showcase/Classes/PresentationLayer/InviteList/Models/InviteViewModel.swift @@ -1,10 +1,5 @@ import Foundation - -// TODO: Delete after Chat SDK integration -struct Invite { - let message: String - let pubKey: String -} +import Chat struct InviteViewModel { let invite: Invite @@ -14,7 +9,7 @@ struct InviteViewModel { } var title: String { - return invite.pubKey + return invite.account.absoluteString } var subtitle: String { diff --git a/Sources/Chat/Chat.swift b/Sources/Chat/ChatClient.swift similarity index 99% rename from Sources/Chat/Chat.swift rename to Sources/Chat/ChatClient.swift index 4249b59cb..e53360121 100644 --- a/Sources/Chat/Chat.swift +++ b/Sources/Chat/ChatClient.swift @@ -4,7 +4,7 @@ import WalletConnectKMS import WalletConnectRelay import Combine -class Chat { +public class ChatClient { private var publishers = [AnyCancellable]() private let registry: Registry private let registryService: RegistryService @@ -33,7 +33,7 @@ class Chat { messagePublisherSubject.eraseToAnyPublisher() } - init(registry: Registry, + public init(registry: Registry, relayClient: RelayClient, kms: KeyManagementService, logger: ConsoleLogging = ConsoleLogger(loggingLevel: .off), diff --git a/Sources/Chat/Registry.swift b/Sources/Chat/Registry.swift index 927fa4826..e7f31d357 100644 --- a/Sources/Chat/Registry.swift +++ b/Sources/Chat/Registry.swift @@ -2,25 +2,25 @@ import Foundation import WalletConnectRelay import WalletConnectUtils -protocol Registry { +public protocol Registry { func register(account: Account, pubKey: String) async throws func resolve(account: Account) async throws -> String } -actor KeyserverRegistryProvider: Registry { +public actor KeyserverRegistryProvider: Registry { - var client: HTTPClient + public var client: HTTPClient - init(client: HTTPClient) { - self.client = client - } + public init(client: HTTPClient) { + self.client = client + } - func register(account: Account, pubKey: String) async throws { + public func register(account: Account, pubKey: String) async throws { let service = RegisterService(userAccount: UserAccount(account: account, publicKey: pubKey)) try await client.request(service: service) } - func resolve(account: Account) async throws -> String { + public func resolve(account: Account) async throws -> String { let service = ResolveService(account: account) let resolvedAccount = try await client.request(UserAccount.self, at: service) return resolvedAccount.publicKey diff --git a/Sources/Chat/Types/InviteParams.swift b/Sources/Chat/Types/InviteParams.swift index d4508a54d..0fad27554 100644 --- a/Sources/Chat/Types/InviteParams.swift +++ b/Sources/Chat/Types/InviteParams.swift @@ -6,12 +6,12 @@ struct InviteResponse: Codable { } public struct Invite: Codable, Equatable { - let message: String - let account: Account - let pubKey: String + public let message: String + public let account: Account + public let pubKey: String } public struct InviteEnvelope: Codable { - let pubKey: String - let invite: Invite + public let pubKey: String + public let invite: Invite } diff --git a/Sources/Chat/Types/Message.swift b/Sources/Chat/Types/Message.swift index d6e9450d6..a846ec92f 100644 --- a/Sources/Chat/Types/Message.swift +++ b/Sources/Chat/Types/Message.swift @@ -1,9 +1,9 @@ import Foundation import WalletConnectUtils -struct Message: Codable, Equatable { - var topic: String - let message: String - let authorAccount: Account - let timestamp: Int64 +public struct Message: Codable, Equatable { + public var topic: String + public let message: String + public let authorAccount: Account + public let timestamp: Int64 } diff --git a/Sources/Chat/Types/Thread.swift b/Sources/Chat/Types/Thread.swift index d194d6cfd..f359dd0bf 100644 --- a/Sources/Chat/Types/Thread.swift +++ b/Sources/Chat/Types/Thread.swift @@ -1,8 +1,8 @@ import Foundation import WalletConnectUtils -struct Thread: Codable { - let topic: String - let selfAccount: Account - let peerAccount: Account +public struct Thread: Codable { + public let topic: String + public let selfAccount: Account + public let peerAccount: Account } From 4eeeb84ac21d9f6f389c89546b0cdfd425024a1a Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 8 Jul 2022 09:22:30 +0200 Subject: [PATCH 2/4] remove invite envelope --- Example/IntegrationTests/Chat/ChatTests.swift | 8 ++++---- Sources/Chat/ChatClient.swift | 8 ++++---- .../Invitee/InvitationHandlingService.swift | 4 ++-- Sources/Chat/Types/InviteParams.swift | 5 ----- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/Example/IntegrationTests/Chat/ChatTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift index 8eb49c1b2..4d13b2362 100644 --- a/Example/IntegrationTests/Chat/ChatTests.swift +++ b/Example/IntegrationTests/Chat/ChatTests.swift @@ -69,8 +69,8 @@ final class ChatTests: XCTestCase { // // try! await inviter.invite(publicKey: pubKey, peerAccount: inviteeAccount, openingMessage: "opening message", account: inviterAccount) // -// invitee.invitePublisher.sink { [unowned self] inviteEnvelope in -// Task {try! await invitee.accept(inviteId: inviteEnvelope.pubKey)} +// invitee.invitePublisher.sink { [unowned self] invite in +// Task {try! await invitee.accept(inviteId: invite.pubKey)} // }.store(in: &publishers) // // invitee.newThreadPublisher.sink { _ in @@ -94,8 +94,8 @@ final class ChatTests: XCTestCase { // let pubKey = try! await invitee.register(account: inviteeAccount) // try! await inviter.invite(publicKey: pubKey, peerAccount: inviteeAccount, openingMessage: "opening message", account: inviterAccount) // -// invitee.invitePublisher.sink { [unowned self] inviteEnvelope in -// Task {try! await invitee.accept(inviteId: inviteEnvelope.pubKey)} +// invitee.invitePublisher.sink { [unowned self] invite in +// Task {try! await invitee.accept(inviteId: invite.pubKey)} // }.store(in: &publishers) // // invitee.newThreadPublisher.sink { [unowned self] thread in diff --git a/Sources/Chat/ChatClient.swift b/Sources/Chat/ChatClient.swift index e53360121..63ac5b8a2 100644 --- a/Sources/Chat/ChatClient.swift +++ b/Sources/Chat/ChatClient.swift @@ -23,8 +23,8 @@ public class ChatClient { newThreadPublisherSubject.eraseToAnyPublisher() } - private var invitePublisherSubject = PassthroughSubject() - public var invitePublisher: AnyPublisher { + private var invitePublisherSubject = PassthroughSubject() + public var invitePublisher: AnyPublisher { invitePublisherSubject.eraseToAnyPublisher() } @@ -141,8 +141,8 @@ public class ChatClient { } private func setUpEnginesCallbacks() { - invitationHandlingService.onInvite = { [unowned self] inviteEnvelope in - invitePublisherSubject.send(inviteEnvelope) + invitationHandlingService.onInvite = { [unowned self] invite in + invitePublisherSubject.send(invite) } invitationHandlingService.onNewThread = { [unowned self] newThread in newThreadPublisherSubject.send(newThread) diff --git a/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift b/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift index 70831a222..26eef88cb 100644 --- a/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift +++ b/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift @@ -8,7 +8,7 @@ class InvitationHandlingService { enum Error: Swift.Error { case inviteForIdNotFound } - var onInvite: ((InviteEnvelope) -> Void)? + var onInvite: ((Invite) -> Void)? var onNewThread: ((Thread) -> Void)? private let networkingInteractor: NetworkInteracting private let invitePayloadStore: CodableStore<(RequestSubscriptionPayload)> @@ -88,7 +88,7 @@ class InvitationHandlingService { private func handleInvite(_ invite: Invite, _ payload: RequestSubscriptionPayload) throws { logger.debug("did receive an invite") invitePayloadStore.set(payload, forKey: invite.pubKey) - onInvite?(InviteEnvelope(pubKey: invite.pubKey, invite: invite)) + onInvite?(invite) } private func getInviteResponseTopic(_ payload: RequestSubscriptionPayload, _ invite: Invite) throws -> String { diff --git a/Sources/Chat/Types/InviteParams.swift b/Sources/Chat/Types/InviteParams.swift index 0fad27554..2851884a4 100644 --- a/Sources/Chat/Types/InviteParams.swift +++ b/Sources/Chat/Types/InviteParams.swift @@ -10,8 +10,3 @@ public struct Invite: Codable, Equatable { public let account: Account public let pubKey: String } - -public struct InviteEnvelope: Codable { - public let pubKey: String - public let invite: Invite -} From d62047eea2c8bbaf3c152a3e455e979258a87ac4 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 8 Jul 2022 15:06:21 +0700 Subject: [PATCH 3/4] Register and resolving --- .../ApplicationConfigurator.swift | 23 +++++++++++++++++++ .../DomainLayer/Chat/ChatFactory.swift | 2 +- .../DomainLayer/Chat/ChatService.swift | 13 +++++++++++ .../Invite/InviteInteractor.swift | 6 ++++- Example/Showcase/Other/Info.plist | 5 ++++ Sources/Chat/ChatClient.swift | 2 +- 6 files changed, 48 insertions(+), 3 deletions(-) diff --git a/Example/Showcase/Classes/ApplicationLayer/Configurator/ApplicationConfigurator.swift b/Example/Showcase/Classes/ApplicationLayer/Configurator/ApplicationConfigurator.swift index 28d065d54..87294a1f5 100644 --- a/Example/Showcase/Classes/ApplicationLayer/Configurator/ApplicationConfigurator.swift +++ b/Example/Showcase/Classes/ApplicationLayer/Configurator/ApplicationConfigurator.swift @@ -1,5 +1,9 @@ +import Combine + struct ApplicationConfigurator: Configurator { + private var publishers = Set() + private let app: Application init(app: Application) { @@ -7,8 +11,27 @@ struct ApplicationConfigurator: Configurator { } func configure() { + registerAccount() + ChatListModule.create(app: app) .wrapToNavigationController() .present() } } + +private extension ApplicationConfigurator { + + func registerAccount() { + Task(priority: .high) { + for await status in app.chatService.connectionPublisher { + guard status == .connected else { + fatalError("Not Connected") + } + + print("Socket connected") + + try! await app.chatService.register(account: ChatService.selfAccount) + } + } + } +} diff --git a/Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift b/Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift index a550b4b03..f26f7f311 100644 --- a/Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift +++ b/Example/Showcase/Classes/DomainLayer/Chat/ChatFactory.swift @@ -9,7 +9,7 @@ class ChatFactory { let relayHost = "relay.walletconnect.com" let projectId = "8ba9ee138960775e5231b70cc5ef1c3a" let keychain = KeychainStorage(serviceIdentifier: "com.walletconnect.showcase") - let client = HTTPClient(host: relayHost) + let client = HTTPClient(host: "159.65.123.131") let registry = KeyserverRegistryProvider(client: client) let relayClient = RelayClient(relayHost: relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory()) return ChatClient( diff --git a/Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift b/Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift index 1b8693e9e..fbcda14a9 100644 --- a/Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift +++ b/Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift @@ -2,6 +2,7 @@ import Foundation import Combine import Chat import WalletConnectUtils +import WalletConnectRelay typealias Stream = AsyncPublisher> @@ -15,6 +16,10 @@ final class ChatService { self.client = client } + var connectionPublisher: Stream { + return client.socketConnectionStatusPublisher.values + } + var messagePublisher: Stream { return client.messagePublisher.values } @@ -54,4 +59,12 @@ final class ChatService { func invite(peerPubkey publicKey: String, peerAccount: Account, message: String, selfAccount: Account) async throws { try await client.invite(publicKey: publicKey, peerAccount: peerAccount, openingMessage: message, account: selfAccount) } + + func register(account: Account) async throws { + _ = try await client.register(account: account) + } + + func resolve(account: Account) async throws -> String { + return try await client.resolve(account: account) + } } diff --git a/Example/Showcase/Classes/PresentationLayer/Invite/InviteInteractor.swift b/Example/Showcase/Classes/PresentationLayer/Invite/InviteInteractor.swift index 35be0fb15..5175171ff 100644 --- a/Example/Showcase/Classes/PresentationLayer/Invite/InviteInteractor.swift +++ b/Example/Showcase/Classes/PresentationLayer/Invite/InviteInteractor.swift @@ -1,3 +1,5 @@ +import WalletConnectUtils + final class InviteInteractor { private let chatService: ChatService @@ -6,6 +8,8 @@ final class InviteInteractor { } func invite(account: String, message: String) async { -// try! await chatService.invite(peerPubkey: <#T##String#>, peerAccount: <#T##Account#>, message: message, selfAccount: ChatService.selfAccount) + let peerAccount = Account(account)! + let publicKey = try! await chatService.resolve(account: peerAccount) + try! await chatService.invite(peerPubkey: publicKey, peerAccount: peerAccount, message: message, selfAccount: ChatService.selfAccount) } } diff --git a/Example/Showcase/Other/Info.plist b/Example/Showcase/Other/Info.plist index 0eb786dc1..bc240fdb1 100644 --- a/Example/Showcase/Other/Info.plist +++ b/Example/Showcase/Other/Info.plist @@ -2,6 +2,11 @@ + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/Sources/Chat/ChatClient.swift b/Sources/Chat/ChatClient.swift index 63ac5b8a2..0121d5da1 100644 --- a/Sources/Chat/ChatClient.swift +++ b/Sources/Chat/ChatClient.swift @@ -16,7 +16,7 @@ public class ChatClient { private let messagesStore: Database private let invitePayloadStore: CodableStore<(RequestSubscriptionPayload)> - let socketConnectionStatusPublisher: AnyPublisher + public let socketConnectionStatusPublisher: AnyPublisher private var newThreadPublisherSubject = PassthroughSubject() public var newThreadPublisher: AnyPublisher { From c86ec07f67a8d796451f668867608d5f02881d98 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Sun, 10 Jul 2022 12:10:54 +0700 Subject: [PATCH 4/4] Send message MainActor --- .../PresentationLayer/Chat/ChatPresenter.swift | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/ChatPresenter.swift b/Example/Showcase/Classes/PresentationLayer/Chat/ChatPresenter.swift index 302cb9ab7..25a4fa353 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/ChatPresenter.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/ChatPresenter.swift @@ -28,7 +28,9 @@ final class ChatPresenter: ObservableObject { } func didPressSend() { - sendMessage() + Task(priority: .userInitiated) { + await sendMessage() + } } } @@ -51,10 +53,9 @@ private extension ChatPresenter { .map { MessageViewModel(message: $0, thread: thread) } } - func sendMessage() { - Task { - try! await interactor.sendMessage(topic: thread.topic, message: input) - input = .empty - } + @MainActor + func sendMessage() async { + try! await interactor.sendMessage(topic: thread.topic, message: input) + input = .empty } }