Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Chat] sync chat sdk with kotlin implementation and add reject method #322

Merged
merged 15 commits into from
Jul 12, 2022
Merged
6 changes: 3 additions & 3 deletions Example/IntegrationTests/Chat/ChatTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Example/Showcase/Classes/DomainLayer/Chat/ChatService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
}
}
8 changes: 5 additions & 3 deletions Sources/Chat/ChatClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Thread>
private let messagesStore: Database<Message>
Expand All @@ -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<String>(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.topicToInvitationPubKey.rawValue)
self.registry = registry
Expand All @@ -63,6 +64,7 @@ public class ChatClient {
kms: kms,
threadStore: threadStore,
logger: logger)
self.leaveService = LeaveService()
self.messagesStore = Database<Message>()
self.messagingService = MessagingService(
networkingInteractor: networkingInteractor,
Expand Down Expand Up @@ -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
Expand All @@ -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] {
Expand Down
8 changes: 8 additions & 0 deletions Sources/Chat/ProtocolServices/Common/LeaveService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

import Foundation

class LeaveService {
func leave(topic: String) async throws {
fatalError("not implemented")
}
}
6 changes: 4 additions & 2 deletions Sources/Chat/ProtocolServices/Common/MessagingService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<ChatRequestParams>(params: .message(message))
try await networkingInteractor.request(request, topic: topic, envelopeType: .type0)
Task(priority: .background) {
Expand All @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need to do that? Is it possible to avoid struct mutation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wc_chatMessage does not contain topic but when we query messages from storage it is convenient to distinguish them by topic.
maybe some storage improvement could allow to avoid this but instant sync with kotlin was a priority
will take this into consideration in next storage PR

handleMessage(message)
default:
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AnyCodable>(id: payload.request.id, result: AnyCodable(inviteResponse)))

Expand All @@ -52,24 +52,43 @@ 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()

try kms.setSymmetricKey(threadAgreementKeys.sharedKey, for: threadTopic)

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gj, need to do the same for approve

}

private func setUpRequestHandling() {
networkingInteractor.requestPublisher.sink { [unowned self] subscriptionPayload in
switch subscriptionPayload.request.params {
Expand All @@ -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)
}

Expand All @@ -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()
Expand Down
10 changes: 7 additions & 3 deletions Sources/Chat/ProtocolServices/Inviter/InviteService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<ChatRequestParams>(params: .invite(invite))
Expand All @@ -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)")
Expand All @@ -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")
}
Expand Down
31 changes: 29 additions & 2 deletions Sources/Chat/Types/ChatRequestParams.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,45 @@ 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 {
init(id: Int64 = JsonRpcID.generate(), params: T) where T == ChatRequestParams {
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)
}
Expand Down
7 changes: 5 additions & 2 deletions Sources/Chat/Types/InviteParams.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
23 changes: 22 additions & 1 deletion Sources/Chat/Types/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
2 changes: 1 addition & 1 deletion Sources/WalletConnectUtils/JSONRPC/JSONRPCRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public struct JSONRPCRequest<T: Codable&Equatable>: Codable, Equatable {
public let method: String
public let params: T

enum CodingKeys: CodingKey {
public enum CodingKeys: CodingKey {
case id
case jsonrpc
case method
Expand Down