Skip to content

Commit

Permalink
#chat/registry (#262)
Browse files Browse the repository at this point in the history
* Add registry manager

* Update api methods

* update resolve

* Add description to api methods

* update invite method

* remove teardown

* make registryStore private
  • Loading branch information
llbartekll committed Jul 5, 2022
1 parent 9e94672 commit 147e100
Show file tree
Hide file tree
Showing 17 changed files with 170 additions and 62 deletions.
46 changes: 36 additions & 10 deletions Sources/Chat/Chat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import Combine
class Chat {
private var publishers = [AnyCancellable]()
let registry: Registry
let registryManager: RegistryManager
let engine: Engine
let kms: KeyManagementService
var onConnected: (()->())?

let socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never>

var newThreadPublisherSubject = PassthroughSubject<Thread, Never>()
Expand All @@ -29,13 +30,13 @@ class Chat {
kms: KeyManagementService,
logger: ConsoleLogging = ConsoleLogger(loggingLevel: .off),
keyValueStorage: KeyValueStorage) {
let topicToInvitationPubKeyStore = CodableStore<String>(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.topicToInvitationPubKey.rawValue)
self.registry = registry

self.kms = kms
let serialiser = Serializer(kms: kms)
let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serialiser)
let topicToInvitationPubKeyStore = CodableStore<String>(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.topicToInvitationPubKey.rawValue)
let inviteStore = CodableStore<Invite>(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.invite.rawValue)
self.registryManager = RegistryManager(registry: registry, networkingInteractor: networkingInteractor, kms: kms, logger: logger, topicToInvitationPubKeyStore: topicToInvitationPubKeyStore)
self.engine = Engine(registry: registry,
networkingInteractor: networkingInteractor,
kms: kms,
Expand All @@ -47,19 +48,44 @@ class Chat {
setUpEnginesCallbacks()
}

func register(account: Account) {
engine.register(account: account)
/// Registers a new record on Chat keyserver,
/// record is a blockchain account with a client generated public key
/// - Parameter account: CAIP10 blockchain account
/// - Returns: public key
func register(account: Account) async throws -> String {
try await registryManager.register(account: account)
}

func invite(account: Account) throws {
try engine.invite(account: account)
/// Queries the default keyserver with a blockchain account
/// - Parameter account: CAIP10 blockachain account
/// - Returns: public key associated with an account in chat's keyserver
func resolve(account: Account) async throws -> String {
try await registry.resolve(account: account)
}

func accept(inviteId: String) throws {
try engine.accept(inviteId: inviteId)
/// Sends a chat invite with opening message
/// - Parameters:
/// - publicKey: publicKey associated with a peer
/// - openingMessage: oppening message for a chat invite
func invite(publicKey: String, openingMessage: String) async throws {
try await engine.invite(peerPubKey: publicKey, openingMessage: openingMessage)
}

func accept(inviteId: String) async throws {
try await engine.accept(inviteId: inviteId)
}

/// Sends a chat message to an active chat thread
/// - Parameters:
/// - topic: thread topic
/// - message: chat message
func message(topic: String, message: String) {

}

func message(threadTopic: String, message: String) {
/// To Ping peer client
/// - Parameter topic: chat thread topic
func ping(topic: String) {

}

Expand Down
29 changes: 8 additions & 21 deletions Sources/Chat/Engine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,20 @@ class Engine {
setUpResponseHandling()
}

func invite(account: Account) throws {
let peerPubKeyHex = registry.resolve(account: account)!
print("resolved pub key: \(peerPubKeyHex)")
func invite(peerPubKey: String, openingMessage: String) async throws {
let pubKey = try kms.createX25519KeyPair()
let invite = Invite(pubKey: pubKey.hexRepresentation, message: "hello")
let topic = try AgreementPublicKey(hex: peerPubKeyHex).rawRepresentation.sha256().toHexString()
let request = ChatRequest(method: .invite, params: .invite(invite))
let invite = Invite(pubKey: pubKey.hexRepresentation, openingMessage: openingMessage)
let topic = try AgreementPublicKey(hex: peerPubKey).rawRepresentation.sha256().toHexString()
let request = ChatRequest(params: .invite(invite))
networkingInteractor.requestUnencrypted(request, topic: topic)
let agreementKeys = try kms.performKeyAgreement(selfPublicKey: pubKey, peerPublicKey: peerPubKeyHex)
let agreementKeys = try kms.performKeyAgreement(selfPublicKey: pubKey, peerPublicKey: peerPubKey)
let threadTopic = agreementKeys.derivedTopic()
networkingInteractor.subscribe(topic: threadTopic)
try await networkingInteractor.subscribe(topic: threadTopic)
logger.debug("invite sent on topic: \(topic)")
}


func accept(inviteId: String) throws {
func accept(inviteId: String) async throws {
guard let hexPubKey = try topicToInvitationPubKeyStore.get(key: "todo-topic") else {
throw ChatError.noPublicKeyForInviteId
}
Expand All @@ -61,21 +59,10 @@ class Engine {
logger.debug("accepting an invitation")
let agreementKeys = try! kms.performKeyAgreement(selfPublicKey: pubKey, peerPublicKey: invite.pubKey)
let topic = agreementKeys.derivedTopic()
networkingInteractor.subscribe(topic: topic)
try await networkingInteractor.subscribe(topic: topic)
fatalError("not implemented")
}

func register(account: Account) {
let pubKey = try! kms.createX25519KeyPair()
let pubKeyHex = pubKey.hexRepresentation
print("registered pubKey: \(pubKeyHex)")
registry.register(account: account, pubKey: pubKeyHex)
let topic = pubKey.rawRepresentation.sha256().toHexString()
try! topicToInvitationPubKeyStore.set(pubKeyHex, forKey: topic)
networkingInteractor.subscribe(topic: topic)
print("did register and is subscribing on topic: \(topic)")
}

private func handleInvite(_ invite: Invite) {
onInvite?(invite)
logger.debug("did receive an invite")
Expand Down
14 changes: 7 additions & 7 deletions Sources/Chat/NetworkingInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import Combine
import WalletConnectRelay
import WalletConnectUtils

class NetworkingInteractor {
protocol NetworkInteracting {
func subscribe(topic: String) async throws
}

class NetworkingInteractor: NetworkInteracting {
let relayClient: RelayClient
private let serializer: Serializing
var requestPublisher: AnyPublisher<RequestSubscriptionPayload, Never> {
Expand Down Expand Up @@ -41,12 +45,8 @@ class NetworkingInteractor {
}
}

func subscribe(topic: String) {
relayClient.subscribe(topic: topic) { [weak self] error in
// if let error = error {
// print(error)
// }
}
func subscribe(topic: String) async throws {
try await relayClient.subscribe(topic: topic)
}

private func manageSubscription(_ topic: String, _ message: String) {
Expand Down
16 changes: 9 additions & 7 deletions Sources/Chat/Registry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@ import Foundation
import WalletConnectUtils

protocol Registry {
func register(account: Account, pubKey: String)
func resolve(account: Account) -> String?
func register(account: Account, pubKey: String) async throws
func resolve(account: Account) async throws -> String
}

class KeyValueRegistry: Registry {
actor KeyValueRegistry: Registry {

var registryStore: [Account: String] = [:]
private var registryStore: [Account: String] = [:]

func register(account address: Account, pubKey: String) {
func register(account address: Account, pubKey: String) async throws {
registryStore[address] = pubKey
}

func resolve(account: Account) -> String? {
return registryStore[account]
func resolve(account: Account) async throws -> String {
guard let record = registryStore[account] else { throw ChatError.recordNotFound}
return record
}
}

35 changes: 35 additions & 0 deletions Sources/Chat/RegistryManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

import Foundation
import WalletConnectUtils
import WalletConnectKMS

actor RegistryManager {
let networkingInteractor: NetworkInteracting
let topicToInvitationPubKeyStore: CodableStore<String>
let registry: Registry
let logger: ConsoleLogging
let kms: KeyManagementServiceProtocol

init(registry: Registry,
networkingInteractor: NetworkInteracting,
kms: KeyManagementServiceProtocol,
logger: ConsoleLogging,
topicToInvitationPubKeyStore: CodableStore<String>) {
self.registry = registry
self.kms = kms
self.networkingInteractor = networkingInteractor
self.logger = logger
self.topicToInvitationPubKeyStore = topicToInvitationPubKeyStore
}

func register(account: Account) async throws -> String {
let pubKey = try kms.createX25519KeyPair()
let pubKeyHex = pubKey.hexRepresentation
try await registry.register(account: account, pubKey: pubKeyHex)
let topic = pubKey.rawRepresentation.sha256().toHexString()
topicToInvitationPubKeyStore.set(pubKeyHex, forKey: topic)
try await networkingInteractor.subscribe(topic: topic)
logger.debug("Did register an account: \(account) and is subscribing on topic: \(topic)")
return pubKeyHex
}
}
1 change: 1 addition & 0 deletions Sources/Chat/Types/ChatError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import Foundation
enum ChatError: Error {
case noPublicKeyForInviteId
case noInviteForId
case recordNotFound
}
9 changes: 7 additions & 2 deletions Sources/Chat/Types/ChatRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ struct ChatRequest: Codable {
case params
}

internal init(id: Int64 = generateId(), jsonrpc: String = "2.0", method: Method, params: Params) {
internal init(id: Int64 = generateId(), jsonrpc: String = "2.0", params: Params) {
self.id = id
self.jsonrpc = jsonrpc
self.method = method
self.params = params
switch params {
case .invite(_):
self.method = Method.invite
case .message(_):
self.method = Method.message
}
}


Expand Down
2 changes: 1 addition & 1 deletion Sources/Chat/Types/Invite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Foundation

struct Invite: Codable {
let pubKey: String
let message: String
let openingMessage: String

var id: String {
return pubKey
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ final class ChatTests: XCTestCase {
await waitClientsConnected()
let inviteExpectation = expectation(description: "invitation expectation")
let account = Account(chainIdentifier: "eip155:1", address: "0x3627523167367216556273151")!
client1.register(account: account)
try! client2.invite(account: account)
client1.invitePublisher.sink { invite in
let pubKey = try! await client1.register(account: account)
try! await client2.invite(publicKey: pubKey, openingMessage: "")
client1.invitePublisher.sink { _ in
inviteExpectation.fulfill()
}.store(in: &publishers)
wait(for: [inviteExpectation], timeout: 4)
Expand Down
15 changes: 15 additions & 0 deletions Tests/ChatTests/Mocks/NetworkingInteractorMock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

import Foundation
@testable import Chat

class NetworkingInteractorMock: NetworkInteracting {
private(set) var subscriptions: [String] = []

func subscribe(topic: String) async throws {
subscriptions.append(topic)
}

func didSubscribe(to topic: String) -> Bool {
subscriptions.contains { $0 == topic }
}
}
37 changes: 37 additions & 0 deletions Tests/ChatTests/RegistryManagerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Foundation
import XCTest
@testable import Chat
import WalletConnectUtils
@testable import WalletConnectKMS
@testable import TestingUtils

final class RegistryManagerTests: XCTestCase {
var registryManager: RegistryManager!
var networkingInteractor: NetworkingInteractorMock!
var topicToInvitationPubKeyStore: CodableStore<String>!
var registry: Registry!
var kms: KeyManagementServiceMock!

override func setUp() {
registry = KeyValueRegistry()
networkingInteractor = NetworkingInteractorMock()
kms = KeyManagementServiceMock()
topicToInvitationPubKeyStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "")
registryManager = RegistryManager(
registry: registry,
networkingInteractor: networkingInteractor,
kms: kms,
logger: ConsoleLoggerMock(),
topicToInvitationPubKeyStore: topicToInvitationPubKeyStore)
}

func testRegister() async {
let account = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")!
try! await registryManager.register(account: account)
XCTAssert(!networkingInteractor.subscriptions.isEmpty, "networkingInteractors subscribes to new topic")
let resolved = try! await registry.resolve(account: account)
XCTAssertNotNil(resolved, "register account is resolvable")
XCTAssertFalse(topicToInvitationPubKeyStore.getAll().isEmpty, "stores topic to invitation")
}
}

4 changes: 2 additions & 2 deletions Tests/WalletConnectSignTests/ApproveEngineTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ final class ApproveEngineTests: XCTestCase {

var engine: ApproveEngine!
var metadata: AppMetadata!
var networkingInteractor: MockedWCRelay!
var networkingInteractor: NetworkingInteractorMock!
var cryptoMock: KeyManagementServiceMock!
var pairingStorageMock: WCPairingStorageMock!
var sessionStorageMock: WCSessionStorageMock!
Expand All @@ -19,7 +19,7 @@ final class ApproveEngineTests: XCTestCase {

override func setUp() {
metadata = AppMetadata.stub()
networkingInteractor = MockedWCRelay()
networkingInteractor = NetworkingInteractorMock()
cryptoMock = KeyManagementServiceMock()
pairingStorageMock = WCPairingStorageMock()
sessionStorageMock = WCSessionStorageMock()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import WalletConnectKMS

class ControllerSessionStateMachineTests: XCTestCase {
var sut: ControllerSessionStateMachine!
var networkingInteractor: MockedWCRelay!
var networkingInteractor: NetworkingInteractorMock!
var storageMock: WCSessionStorageMock!
var cryptoMock: KeyManagementServiceMock!

override func setUp() {
networkingInteractor = MockedWCRelay()
networkingInteractor = NetworkingInteractorMock()
storageMock = WCSessionStorageMock()
cryptoMock = KeyManagementServiceMock()
sut = ControllerSessionStateMachine(networkingInteractor: networkingInteractor, kms: cryptoMock, sessionStore: storageMock, logger: ConsoleLoggerMock())
Expand Down
2 changes: 1 addition & 1 deletion Tests/WalletConnectSignTests/Mocks/MockedRelay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import WalletConnectUtils
@testable import WalletConnectSign
@testable import TestingUtils

class MockedWCRelay: NetworkInteracting {
class NetworkingInteractorMock: NetworkInteracting {

private(set) var subscriptions: [String] = []
private(set) var unsubscriptions: [String] = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import WalletConnectKMS

class NonControllerSessionStateMachineTests: XCTestCase {
var sut: NonControllerSessionStateMachine!
var networkingInteractor: MockedWCRelay!
var networkingInteractor: NetworkingInteractorMock!
var storageMock: WCSessionStorageMock!
var cryptoMock: KeyManagementServiceMock!

override func setUp() {
networkingInteractor = MockedWCRelay()
networkingInteractor = NetworkingInteractorMock()
storageMock = WCSessionStorageMock()
cryptoMock = KeyManagementServiceMock()
sut = NonControllerSessionStateMachine(networkingInteractor: networkingInteractor, kms: cryptoMock, sessionStore: storageMock, logger: ConsoleLoggerMock())
Expand Down
Loading

0 comments on commit 147e100

Please sign in to comment.