diff --git a/ExampleApps/ClientApp/WalletConnect.swift b/ExampleApps/ClientApp/WalletConnect.swift index 009b4e9a3..93a5e47a3 100644 --- a/ExampleApps/ClientApp/WalletConnect.swift +++ b/ExampleApps/ClientApp/WalletConnect.swift @@ -70,6 +70,10 @@ extension WalletConnect: ClientDelegate { delegate.failedToConnect() } + func client(_ client: Client, didConnect url: WCURL) { + // do nothing + } + func client(_ client: Client, didConnect session: Session) { self.session = session let sessionData = try! JSONEncoder().encode(session) @@ -81,4 +85,8 @@ extension WalletConnect: ClientDelegate { UserDefaults.standard.removeObject(forKey: sessionKey) delegate.didDisconnect() } + + func client(_ client: Client, didUpdate session: Session) { + // do nothing + } } diff --git a/Sources/Internal/Communicator.swift b/Sources/Internal/Communicator.swift index b948d3f7a..513a20196 100644 --- a/Sources/Internal/Communicator.swift +++ b/Sources/Internal/Communicator.swift @@ -29,8 +29,8 @@ class Communicator { return sessions.find(url: url) } - func addSession(_ session: Session) { - sessions.add(session) + func addOrUpdateSession(_ session: Session) { + sessions.addOrUpdate(session) } func removeSession(by url: WCURL) { @@ -53,8 +53,8 @@ class Communicator { return pendingDisconnectSessions.find(url: url) } - func addPendingDisconnectSession(_ session: Session) { - pendingDisconnectSessions.add(session) + func addOrUpdatePendingDisconnectSession(_ session: Session) { + pendingDisconnectSessions.addOrUpdate(session) } func removePendingDisconnectSession(by url: WCURL) { @@ -107,7 +107,7 @@ class Communicator { self.queue = queue } - func add(_ session: Session) { + func addOrUpdate(_ session: Session) { dispatchPrecondition(condition: .notOnQueue(queue)) queue.sync { [unowned self] in self.sessions[session.url] = session diff --git a/Sources/Internal/HandshakeHandler.swift b/Sources/Internal/HandshakeHandler.swift index 817c7d5c3..a0db0a0df 100644 --- a/Sources/Internal/HandshakeHandler.swift +++ b/Sources/Internal/HandshakeHandler.swift @@ -4,7 +4,7 @@ import Foundation -protocol HandshakeHandlerDelegate: class { +protocol HandshakeHandlerDelegate: AnyObject { // TODO: instead of IDType, use RequestID func handler(_ handler: HandshakeHandler, didReceiveRequestToCreateSession: Session, requestId: RequestID) } diff --git a/Sources/Internal/UpdateSessionHandler.swift b/Sources/Internal/UpdateSessionHandler.swift index 6d102bab1..57859c356 100644 --- a/Sources/Internal/UpdateSessionHandler.swift +++ b/Sources/Internal/UpdateSessionHandler.swift @@ -4,8 +4,8 @@ import Foundation -protocol UpdateSessionHandlerDelegate: class { - func handler(_ handler: UpdateSessionHandler, didUpdateSessionByURL: WCURL, approved: Bool) +protocol UpdateSessionHandlerDelegate: AnyObject { + func handler(_ handler: UpdateSessionHandler, didUpdateSessionByURL: WCURL, sessionInfo: SessionInfo) } class UpdateSessionHandler: RequestHandler { @@ -22,7 +22,7 @@ class UpdateSessionHandler: RequestHandler { func handle(request: Request) { do { let sessionInfo = try request.parameter(of: SessionInfo.self, at: 0) - delegate?.handler(self, didUpdateSessionByURL: request.url, approved: sessionInfo.approved) + delegate?.handler(self, didUpdateSessionByURL: request.url, sessionInfo: sessionInfo) } catch { LogService.shared.log("WC: wrong format of wc_sessionUpdate request: \(error)") // TODO: send error response @@ -30,6 +30,13 @@ class UpdateSessionHandler: RequestHandler { } } +/// https://docs.walletconnect.org/tech-spec#session-update struct SessionInfo: Decodable { var approved: Bool + var accounts: [String]? + var chainId: Int? +} + +enum ChainID { + static let mainnet = 1 } diff --git a/Sources/PublicInterface/Client.swift b/Sources/PublicInterface/Client.swift index c42b28437..09ad07854 100644 --- a/Sources/PublicInterface/Client.swift +++ b/Sources/PublicInterface/Client.swift @@ -4,10 +4,12 @@ import Foundation -public protocol ClientDelegate: class { +public protocol ClientDelegate: AnyObject { func client(_ client: Client, didFailToConnect url: WCURL) + func client(_ client: Client, didConnect url: WCURL) func client(_ client: Client, didConnect session: Session) func client(_ client: Client, didDisconnect session: Session) + func client(_ client: Client, didUpdate session: Session) } public class Client: WalletConnect { @@ -179,6 +181,7 @@ public class Client: WalletConnect { override func onConnect(to url: WCURL) { LogService.shared.log("WC: client didConnect url: \(url.bridgeURL.absoluteString)") + delegate?.client(self, didConnect: url) if let existingSession = communicator.session(by: url) { communicator.subscribe(on: existingSession.dAppInfo.peerId, url: existingSession.url) delegate?.client(self, didConnect: existingSession) @@ -205,7 +208,7 @@ public class Client: WalletConnect { return } - communicator.addSession(session) + communicator.addOrUpdateSession(session) delegate?.client(self, didConnect: session) } catch { // TODO: handle error @@ -228,16 +231,34 @@ public class Client: WalletConnect { private func expectUpdateSessionRequest(_ request: Request) { if request.method == "wc_sessionUpdate" { - guard let approval = sessionApproval(from: request) else { + guard let info = sessionInfo(from: request) else { // TODO: error handling try! send(Response(request: request, error: .invalidJSON)) return } - guard let session = communicator.session(by: request.url), !approval else { return } - do { - try disconnect(from: session) - } catch { // session already disconnected - delegate?.client(self, didDisconnect: session) + + guard let session = communicator.session(by: request.url) else { return } + + if !info.approved { + do { + try disconnect(from: session) + } catch { // session already disconnected + delegate?.client(self, didDisconnect: session) + } + } else { + // we do not add sessions without walletInfo + let walletInfo = session.walletInfo! + let updatedInfo = Session.WalletInfo( + approved: info.approved, + accounts: info.accounts ?? [], + chainId: info.chainId ?? ChainID.mainnet, + peerId: walletInfo.peerId, + peerMeta: walletInfo.peerMeta + ) + var updatedSesson = session + updatedSesson.walletInfo = updatedInfo + communicator.addOrUpdateSession(updatedSesson) + delegate?.client(self, didUpdate: updatedSesson) } } else { // TODO: error handling @@ -246,10 +267,10 @@ public class Client: WalletConnect { } } - private func sessionApproval(from request: Request) -> Bool? { + private func sessionInfo(from request: Request) -> SessionInfo? { do { let info = try request.parameter(of: SessionInfo.self, at: 0) - return info.approved + return info } catch { LogService.shared.log("WC: incoming approval cannot be parsed: \(error)") return nil @@ -302,7 +323,6 @@ public class Client: WalletConnect { _ = self.responses.removeValue(forKey: requestID) } } - } /// https://docs.walletconnect.org/json-rpc/ethereum#parameters-3 diff --git a/Sources/PublicInterface/Server.swift b/Sources/PublicInterface/Server.swift index a03c3d21b..f35b7d7ed 100644 --- a/Sources/PublicInterface/Server.swift +++ b/Sources/PublicInterface/Server.swift @@ -4,12 +4,12 @@ import Foundation -public protocol RequestHandler: class { +public protocol RequestHandler: AnyObject { func canHandle(request: Request) -> Bool func handle(request: Request) } -public protocol ServerDelegate: class { +public protocol ServerDelegate: AnyObject { /// Websocket connection was dropped during handshake. The connectoin process should be initiated again. func server(_ server: Server, didFailToConnect url: WCURL) @@ -172,7 +172,7 @@ extension Server: HandshakeHandlerDelegate { self.communicator.send(response, topic: session.dAppInfo.peerId) if walletInfo.approved { let updatedSession = Session(url: session.url, dAppInfo: session.dAppInfo, walletInfo: walletInfo) - self.communicator.addSession(updatedSession) + self.communicator.addOrUpdateSession(updatedSession) self.communicator.subscribe(on: walletInfo.peerId, url: updatedSession.url) self.delegate?.server(self, didConnect: updatedSession) } @@ -181,14 +181,27 @@ extension Server: HandshakeHandlerDelegate { } extension Server: UpdateSessionHandlerDelegate { - func handler(_ handler: UpdateSessionHandler, didUpdateSessionByURL url: WCURL, approved: Bool) { + func handler(_ handler: UpdateSessionHandler, didUpdateSessionByURL url: WCURL, sessionInfo: SessionInfo) { guard let session = communicator.session(by: url) else { return } - if !approved { + if !sessionInfo.approved { do { try disconnect(from: session) } catch { // session already disconnected delegate?.server(self, didDisconnect: session) } + } else { + // we do not add sessions without walletInfo + let walletInfo = session.walletInfo! + let updatedInfo = Session.WalletInfo( + approved: sessionInfo.approved, + accounts: sessionInfo.accounts ?? [], + chainId: sessionInfo.chainId ?? ChainID.mainnet, + peerId: walletInfo.peerId, + peerMeta: walletInfo.peerMeta + ) + var updatedSesson = session + updatedSesson.walletInfo = updatedInfo + communicator.addOrUpdateSession(updatedSesson) } } } diff --git a/Sources/PublicInterface/Session.swift b/Sources/PublicInterface/Session.swift index 810e7f882..946353c5b 100644 --- a/Sources/PublicInterface/Session.swift +++ b/Sources/PublicInterface/Session.swift @@ -45,7 +45,7 @@ public struct Session: Codable { public let url: URL public let scheme: String? - public init(name: String, description: String?, icons: [URL], url: URL, scheme: String? = nil ) { + public init(name: String, description: String?, icons: [URL], url: URL, scheme: String? = nil) { self.name = name self.description = description self.icons = icons @@ -69,7 +69,7 @@ public struct Session: Codable { self.peerMeta = peerMeta } - func with(approved: Bool) -> WalletInfo { + public func with(approved: Bool) -> WalletInfo { return WalletInfo(approved: approved, accounts: self.accounts, chainId: self.chainId, diff --git a/Sources/PublicInterface/WalletConnect.swift b/Sources/PublicInterface/WalletConnect.swift index 79719b167..6873047d6 100644 --- a/Sources/PublicInterface/WalletConnect.swift +++ b/Sources/PublicInterface/WalletConnect.swift @@ -33,7 +33,7 @@ open class WalletConnect { guard session.walletInfo != nil else { throw WalletConnectError.missingWalletInfoInSession } - communicator.addSession(session) + communicator.addOrUpdateSession(session) listen(on: session.url) } @@ -46,7 +46,7 @@ open class WalletConnect { throw WalletConnectError.tryingToDisconnectInactiveSession } try sendDisconnectSessionRequest(for: session) - communicator.addPendingDisconnectSession(session) + communicator.addOrUpdatePendingDisconnectSession(session) communicator.disconnect(from: session.url) } diff --git a/Tests/ClientTests.swift b/Tests/ClientTests.swift index 927336964..7f082e67e 100644 --- a/Tests/ClientTests.swift +++ b/Tests/ClientTests.swift @@ -23,7 +23,7 @@ class ClientTests: XCTestCase { } func test_sendRequest_whenNoWalletInfo_thenThrows() { - communicator.addSession(Session.testSessionWithoutWalletInfo) + communicator.addOrUpdateSession(Session.testSessionWithoutWalletInfo) XCTAssertThrowsError(try client.send(Request.testRequest, completion: nil), "missingWalletInfoInSession") { error in XCTAssertEqual(error as? Client.ClientError, .missingWalletInfoInSession) @@ -31,7 +31,7 @@ class ClientTests: XCTestCase { } func test_sendRequest_callsCommunicator() { - communicator.addSession(Session.testSession) + communicator.addOrUpdateSession(Session.testSession) try? client.send(Request.testRequest, completion: nil) XCTAssertNotNil(communicator.sentRequest) } @@ -68,19 +68,25 @@ class ClientTests: XCTestCase { @discardableResult private func prepareAccountWithTestSession() -> String { - communicator.addSession(Session.testSession) + communicator.addOrUpdateSession(Session.testSession) return Session.testSession.walletInfo!.accounts[0] } func test_onConnect_whenSessionExists_thenSubscribesOnDappPeerIdTopic() { - communicator.addSession(Session.testSession) + communicator.addOrUpdateSession(Session.testSession) client.onConnect(to: WCURL.testURL) XCTAssertEqual(communicator.subscribedOn?.topic, Session.testSession.dAppInfo.peerId) XCTAssertEqual(communicator.subscribedOn?.url, Session.testSession.url) } + func test_onConnect_callsDelegate() { + XCTAssertNil(delegate.connectedUrl) + client.onConnect(to: WCURL.testURL) + XCTAssertEqual(delegate.connectedUrl, WCURL.testURL) + } + func test_onConnect_whenSessionExists_thenCallsDelegate() { - communicator.addSession(Session.testSession) + communicator.addOrUpdateSession(Session.testSession) XCTAssertNil(delegate.connectedSession) client.onConnect(to: WCURL.testURL) XCTAssertEqual(delegate.connectedSession, Session.testSession) @@ -104,6 +110,11 @@ class MockClientDelegate: ClientDelegate { didFailToConnect = true } + var connectedUrl: WCURL? + func client(_ client: Client, didConnect url: WCURL) { + connectedUrl = url + } + var connectedSession: Session? func client(_ client: Client, didConnect session: Session) { connectedSession = session @@ -113,4 +124,9 @@ class MockClientDelegate: ClientDelegate { func client(_ client: Client, didDisconnect session: Session) { disconnectedSession = session } + + var didUpdateSession: Session? + func client(_ client: Client, didUpdate session: Session) { + didUpdateSession = session + } } diff --git a/Tests/Helpers.swift b/Tests/Helpers.swift index 5da1f78c5..2ac61e89c 100644 --- a/Tests/Helpers.swift +++ b/Tests/Helpers.swift @@ -16,7 +16,7 @@ class MockCommunicator: Communicator { didListen = true } - override func addSession(_ session: Session) { + override func addOrUpdateSession(_ session: Session) { sessions.append(session) } diff --git a/Tests/WalletConnectTests.swift b/Tests/WalletConnectTests.swift index 7a6f3d50b..88f3f444c 100644 --- a/Tests/WalletConnectTests.swift +++ b/Tests/WalletConnectTests.swift @@ -21,7 +21,7 @@ class WalletConnectTests: XCTestCase { } func test_whenConnectingToExistingURL_thenThrows() { - mockCommunicator.addSession(Session.testSession) + mockCommunicator.addOrUpdateSession(Session.testSession) XCTAssertThrowsError(try wc.connect(to: WCURL.testURL)) } diff --git a/WalletConnectSwift.xcodeproj/project.pbxproj b/WalletConnectSwift.xcodeproj/project.pbxproj index 18ee67918..91b5d649d 100644 --- a/WalletConnectSwift.xcodeproj/project.pbxproj +++ b/WalletConnectSwift.xcodeproj/project.pbxproj @@ -265,7 +265,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1030; - LastUpgradeCheck = 1030; + LastUpgradeCheck = 1250; ORGANIZATIONNAME = "Gnosis Ltd."; TargetAttributes = { 0A92784F230BFB2600FDCC0D = { @@ -487,6 +487,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -548,6 +549,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -615,7 +617,7 @@ repositoryURL = "https://github.com/krzyzanowskim/CryptoSwift.git"; requirement = { kind = upToNextMinorVersion; - minimumVersion = 1.2.0; + minimumVersion = 1.4.0; }; }; 55B7819524A614240036F8E5 /* XCRemoteSwiftPackageReference "Starscream" */ = { diff --git a/WalletConnectSwift.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/WalletConnectSwift.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e162d958e..0583fbb43 100644 --- a/WalletConnectSwift.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/WalletConnectSwift.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git", "state": { "branch": null, - "revision": "79ac632c85df81f2618dd838fc3869633e163370", - "version": "1.2.0" + "revision": "4e31051c63cc0ddf10a25cf5318856c510cf77f4", + "version": "1.4.0" } }, { diff --git a/WalletConnectSwift.xcodeproj/xcshareddata/xcschemes/WalletConnectSwift.xcscheme b/WalletConnectSwift.xcodeproj/xcshareddata/xcschemes/WalletConnectSwift.xcscheme index 163c4f4d0..e8b81a6ad 100644 --- a/WalletConnectSwift.xcodeproj/xcshareddata/xcschemes/WalletConnectSwift.xcscheme +++ b/WalletConnectSwift.xcodeproj/xcshareddata/xcschemes/WalletConnectSwift.xcscheme @@ -1,6 +1,6 @@ + onlyGenerateCoverageForSpecifiedTargets = "YES"> + + + + - - - - - - - -