diff --git a/Example/IntegrationTests/Chat/ChatTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift index 4d13b2362..8cafb5a1d 100644 --- a/Example/IntegrationTests/Chat/ChatTests.swift +++ b/Example/IntegrationTests/Chat/ChatTests.swift @@ -58,7 +58,7 @@ final class ChatTests: XCTestCase { }.store(in: &publishers) wait(for: [inviteExpectation], timeout: 4) } -// + // func testAcceptAndCreateNewThread() async { // await waitClientsConnected() // let newThreadInviterExpectation = expectation(description: "new thread on inviting client expectation") @@ -70,7 +70,7 @@ final class ChatTests: XCTestCase { // try! await inviter.invite(publicKey: pubKey, peerAccount: inviteeAccount, openingMessage: "opening message", account: inviterAccount) // // invitee.invitePublisher.sink { [unowned self] invite in -// Task {try! await invitee.accept(inviteId: invite.pubKey)} +// Task {try! await invitee.accept(inviteId: invite.id)} // }.store(in: &publishers) // // invitee.newThreadPublisher.sink { _ in @@ -95,7 +95,7 @@ final class ChatTests: XCTestCase { // try! await inviter.invite(publicKey: pubKey, peerAccount: inviteeAccount, openingMessage: "opening message", account: inviterAccount) // // invitee.invitePublisher.sink { [unowned self] invite in -// Task {try! await invitee.accept(inviteId: invite.pubKey)} +// Task {try! await invitee.accept(inviteId: invite.id)} // }.store(in: &publishers) // // invitee.newThreadPublisher.sink { [unowned self] thread in diff --git a/Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift b/Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift index fbcda14a9..8f312ab56 100644 --- a/Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift +++ b/Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift @@ -49,11 +49,11 @@ final class ChatService { } func accept(invite: Invite) async throws { - try await client.accept(inviteId: invite.pubKey) + try await client.accept(inviteId: invite.id) } func reject(invite: Invite) async throws { - try await client.reject(inviteId: invite.pubKey) + try await client.reject(inviteId: invite.id) } func invite(peerPubkey publicKey: String, peerAccount: Account, message: String, selfAccount: Account) async throws { diff --git a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListPresenter.swift b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListPresenter.swift index 2171f43b5..6969e2067 100644 --- a/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListPresenter.swift +++ b/Example/Showcase/Classes/PresentationLayer/InviteList/InviteListPresenter.swift @@ -56,7 +56,7 @@ private extension InviteListPresenter { func loadInvites() async { let invites = await interactor.getInvites() - self.invites = invites.sorted(by: { $0.pubKey < $1.pubKey }) + self.invites = invites.sorted(by: { $0.publicKey < $1.publicKey }) .map { InviteViewModel(invite: $0) } } } diff --git a/Sources/Chat/ChatClient.swift b/Sources/Chat/ChatClient.swift index 0121d5da1..1585c5cb3 100644 --- a/Sources/Chat/ChatClient.swift +++ b/Sources/Chat/ChatClient.swift @@ -11,6 +11,7 @@ public class ChatClient { private let messagingService: MessagingService private let invitationHandlingService: InvitationHandlingService private let inviteService: InviteService + private let leaveService: LeaveService private let kms: KeyManagementService private let threadStore: Database private let messagesStore: Database @@ -36,7 +37,7 @@ public class ChatClient { public init(registry: Registry, relayClient: RelayClient, kms: KeyManagementService, - logger: ConsoleLogging = ConsoleLogger(loggingLevel: .off), + logger: ConsoleLogging = ConsoleLogger(loggingLevel: .debug), keyValueStorage: KeyValueStorage) { let topicToInvitationPubKeyStore = CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.topicToInvitationPubKey.rawValue) self.registry = registry @@ -63,6 +64,7 @@ public class ChatClient { kms: kms, threadStore: threadStore, logger: logger) + self.leaveService = LeaveService() self.messagesStore = Database() self.messagingService = MessagingService( networkingInteractor: networkingInteractor, @@ -102,7 +104,7 @@ public class ChatClient { } public func reject(inviteId: String) async throws { - + try await invitationHandlingService.reject(inviteId: inviteId) } /// Sends a chat message to an active chat thread @@ -120,7 +122,7 @@ public class ChatClient { } public func leave(topic: String) async throws { - fatalError("not implemented") + try await leaveService.leave(topic: topic) } public func getInvites(account: Account) -> [Invite] { diff --git a/Sources/Chat/Services/RegisterService.swift b/Sources/Chat/HTTPServices/RegisterService.swift similarity index 100% rename from Sources/Chat/Services/RegisterService.swift rename to Sources/Chat/HTTPServices/RegisterService.swift diff --git a/Sources/Chat/Services/ResolveService.swift b/Sources/Chat/HTTPServices/ResolveService.swift similarity index 100% rename from Sources/Chat/Services/ResolveService.swift rename to Sources/Chat/HTTPServices/ResolveService.swift diff --git a/Sources/Chat/ProtocolServices/Common/LeaveService.swift b/Sources/Chat/ProtocolServices/Common/LeaveService.swift new file mode 100644 index 000000000..3b5b0c7ae --- /dev/null +++ b/Sources/Chat/ProtocolServices/Common/LeaveService.swift @@ -0,0 +1,8 @@ + +import Foundation + +class LeaveService { + func leave(topic: String) async throws { + fatalError("not implemented") + } +} diff --git a/Sources/Chat/ProtocolServices/Common/MessagingService.swift b/Sources/Chat/ProtocolServices/Common/MessagingService.swift index 828022a36..0c3031ec5 100644 --- a/Sources/Chat/ProtocolServices/Common/MessagingService.swift +++ b/Sources/Chat/ProtocolServices/Common/MessagingService.swift @@ -29,7 +29,8 @@ class MessagingService { // TODO - manage author account let thread = await threadStore.first {$0.topic == topic} guard let authorAccount = thread?.selfAccount else { throw Errors.threadDoNotExist} - let message = Message(topic: topic, message: messageString, authorAccount: authorAccount, timestamp: JsonRpcID.generate()) + let timestamp = Int64(Date().timeIntervalSince1970 * 1000) + let message = Message(topic: topic, message: messageString, authorAccount: authorAccount, timestamp: timestamp) let request = JSONRPCRequest(params: .message(message)) try await networkingInteractor.request(request, topic: topic, envelopeType: .type0) Task(priority: .background) { @@ -53,7 +54,8 @@ class MessagingService { private func setUpRequestHandling() { networkingInteractor.requestPublisher.sink { [unowned self] subscriptionPayload in switch subscriptionPayload.request.params { - case .message(let message): + case .message(var message): + message.topic = subscriptionPayload.topic handleMessage(message) default: return diff --git a/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift b/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift index 26eef88cb..d903ba298 100644 --- a/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift +++ b/Sources/Chat/ProtocolServices/Invitee/InvitationHandlingService.swift @@ -42,7 +42,7 @@ class InvitationHandlingService { let selfThreadPubKey = try kms.createX25519KeyPair() - let inviteResponse = InviteResponse(pubKey: selfThreadPubKey.hexRepresentation) + let inviteResponse = InviteResponse(publicKey: selfThreadPubKey.hexRepresentation) let response = JsonRpcResult.response(JSONRPCResponse(id: payload.request.id, result: AnyCodable(inviteResponse))) @@ -52,7 +52,7 @@ class InvitationHandlingService { try await networkingInteractor.respond(topic: responseTopic, response: response) - let threadAgreementKeys = try kms.performKeyAgreement(selfPublicKey: selfThreadPubKey, peerPublicKey: invite.pubKey) + let threadAgreementKeys = try kms.performKeyAgreement(selfPublicKey: selfThreadPubKey, peerPublicKey: invite.publicKey) let threadTopic = threadAgreementKeys.derivedTopic() @@ -60,16 +60,35 @@ class InvitationHandlingService { try await networkingInteractor.subscribe(topic: threadTopic) - logger.debug("Accepting an invite") + logger.debug("Accepting an invite on topic: \(threadTopic)") // TODO - derive account let selfAccount = Account("eip155:56:0xe5EeF1368781911d265fDB6946613dA61915a501")! let thread = Thread(topic: threadTopic, selfAccount: selfAccount, peerAccount: invite.account) await threadsStore.add(thread) + invitePayloadStore.delete(forKey: inviteId) + onNewThread?(thread) } + func reject(inviteId: String) async throws { + + guard let payload = try invitePayloadStore.get(key: inviteId) else { throw Error.inviteForIdNotFound } + + guard case .invite(let invite) = payload.request.params else {return} + + let responseTopic = try getInviteResponseTopic(payload, invite) + + //TODO - error not in specs yet + let error = JSONRPCErrorResponse.Error(code: 0, message: "user rejected") + let response = JsonRpcResult.error(JSONRPCErrorResponse(id: payload.request.id, error: error)) + + try await networkingInteractor.respond(topic: responseTopic, response: response) + + invitePayloadStore.delete(forKey: inviteId) + } + private func setUpRequestHandling() { networkingInteractor.requestPublisher.sink { [unowned self] subscriptionPayload in switch subscriptionPayload.request.params { @@ -87,7 +106,7 @@ class InvitationHandlingService { private func handleInvite(_ invite: Invite, _ payload: RequestSubscriptionPayload) throws { logger.debug("did receive an invite") - invitePayloadStore.set(payload, forKey: invite.pubKey) + invitePayloadStore.set(payload, forKey: invite.publicKey) onInvite?(invite) } @@ -101,7 +120,7 @@ class InvitationHandlingService { let selfPubKey = try AgreementPublicKey(hex: selfPubKeyHex) - let agreementKeysI = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: invite.pubKey) + let agreementKeysI = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: invite.publicKey) // agreement keys already stored by serializer let responseTopic = agreementKeysI.derivedTopic() diff --git a/Sources/Chat/ProtocolServices/Inviter/InviteService.swift b/Sources/Chat/ProtocolServices/Inviter/InviteService.swift index 860b50a40..f3a5d32b9 100644 --- a/Sources/Chat/ProtocolServices/Inviter/InviteService.swift +++ b/Sources/Chat/ProtocolServices/Inviter/InviteService.swift @@ -25,13 +25,16 @@ class InviteService { } var peerAccount: Account! + func invite(peerPubKey: String, peerAccount: Account, openingMessage: String, account: Account) async throws { // TODO ad storage self.peerAccount = peerAccount let selfPubKeyY = try kms.createX25519KeyPair() - let invite = Invite(message: openingMessage, account: account, pubKey: selfPubKeyY.hexRepresentation) + let invite = Invite(message: openingMessage, account: account, publicKey: selfPubKeyY.hexRepresentation) let symKeyI = try kms.performKeyAgreement(selfPublicKey: selfPubKeyY, peerPublicKey: peerPubKey) let inviteTopic = try AgreementPublicKey(hex: peerPubKey).rawRepresentation.sha256().toHexString() + + // overrides on invite toipic try kms.setSymmetricKey(symKeyI.sharedKey, for: inviteTopic) let request = JSONRPCRequest(params: .invite(invite)) @@ -42,7 +45,6 @@ class InviteService { try kms.setSymmetricKey(symKeyI.sharedKey, for: responseTopic) try await networkingInteractor.subscribe(topic: responseTopic) - try await networkingInteractor.request(request, topic: inviteTopic, envelopeType: .type1(pubKey: selfPubKeyY.rawRepresentation)) logger.debug("invite sent on topic: \(inviteTopic)") @@ -67,7 +69,9 @@ class InviteService { let inviteResponse = try jsonrpc.result.get(InviteResponse.self) logger.debug("Invite has been accepted") guard case .invite(let inviteParams) = response.requestParams else { return } - Task { try await createThread(selfPubKeyHex: inviteParams.pubKey, peerPubKey: inviteResponse.pubKey, account: inviteParams.account, peerAccount: peerAccount)} + Task(priority: .background) { + try await createThread(selfPubKeyHex: inviteParams.publicKey, peerPubKey: inviteResponse.publicKey, account: inviteParams.account, peerAccount: peerAccount) + } } catch { logger.debug("Handling invite response has failed") } diff --git a/Sources/Chat/Types/ChatRequestParams.swift b/Sources/Chat/Types/ChatRequestParams.swift index ad6cad328..94d6d7ccd 100644 --- a/Sources/Chat/Types/ChatRequestParams.swift +++ b/Sources/Chat/Types/ChatRequestParams.swift @@ -2,8 +2,35 @@ import Foundation import WalletConnectUtils enum ChatRequestParams: Codable, Equatable { + enum Errors: Error { + case decoding + } case invite(Invite) case message(Message) + + private enum CodingKeys: String, CodingKey { + case invite + case message + } + + func encode(to encoder: Encoder) throws { + switch self { + case .invite(let invite): + try invite.encode(to: encoder) + case .message(let message): + try message.encode(to: encoder) + } + } + + init(from decoder: Decoder) throws { + if let invite = try? Invite(from: decoder) { + self = .invite(invite) + } else if let massage = try? Message(from: decoder) { + self = .message(massage) + } else { + throw Errors.decoding + } + } } extension JSONRPCRequest { @@ -11,9 +38,9 @@ extension JSONRPCRequest { var method: String! switch params { case .invite: - method = "invite" + method = "wc_chatInvite" case .message: - method = "message" + method = "wc_chatMessage" } self.init(id: id, method: method, params: params) } diff --git a/Sources/Chat/Types/InviteParams.swift b/Sources/Chat/Types/InviteParams.swift index 2851884a4..f5b0f7180 100644 --- a/Sources/Chat/Types/InviteParams.swift +++ b/Sources/Chat/Types/InviteParams.swift @@ -2,11 +2,14 @@ import WalletConnectUtils import Foundation struct InviteResponse: Codable { - let pubKey: String + let publicKey: String } public struct Invite: Codable, Equatable { + public var id: String { + return publicKey + } public let message: String public let account: Account - public let pubKey: String + public let publicKey: String } diff --git a/Sources/Chat/Types/Message.swift b/Sources/Chat/Types/Message.swift index a846ec92f..f3ffabb5e 100644 --- a/Sources/Chat/Types/Message.swift +++ b/Sources/Chat/Types/Message.swift @@ -2,8 +2,29 @@ import Foundation import WalletConnectUtils public struct Message: Codable, Equatable { - public var topic: String + internal init(topic: String? = nil, message: String, authorAccount: Account, timestamp: Int64) { + self.topic = topic + self.message = message + self.authorAccount = authorAccount + self.timestamp = timestamp + } + + public var topic: String? public let message: String public let authorAccount: Account public let timestamp: Int64 + + enum CodingKeys: String, CodingKey { + case topic + case message + case authorAccount + case timestamp + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(message, forKey: .message) + try container.encode(authorAccount, forKey: .authorAccount) + try container.encode(timestamp, forKey: .timestamp) + } } diff --git a/Sources/WalletConnectUtils/JSONRPC/JSONRPCRequest.swift b/Sources/WalletConnectUtils/JSONRPC/JSONRPCRequest.swift index 4518d0793..2c3f57bb9 100644 --- a/Sources/WalletConnectUtils/JSONRPC/JSONRPCRequest.swift +++ b/Sources/WalletConnectUtils/JSONRPC/JSONRPCRequest.swift @@ -7,7 +7,7 @@ public struct JSONRPCRequest: Codable, Equatable { public let method: String public let params: T - enum CodingKeys: CodingKey { + public enum CodingKeys: CodingKey { case id case jsonrpc case method