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

fix: multithreading handling #189

Merged
merged 2 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Example/Tests/MockInfuraProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ class MockReadOnlyRPCProvider: ReadOnlyRPCProvider {
var response: Any? = "{}"
var expectation: XCTestExpectation?

override func sendRequest(_ request: any RPCRequest, chainId: String, appMetadata: AppMetadata) async -> Any? {
override func sendRequest(_ request: any RPCRequest,
params: Any = "",
chainId: String,
appMetadata: AppMetadata) async -> Any? {
sendRequestCalled = true
expectation?.fulfill()
return response
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Alternatively, you can add the URL directly in your project's package file:
dependencies: [
.package(
url: "https://github.com/MetaMask/metamask-ios-sdk",
from: "0.8.7"
from: "0.8.8"
)
]
```
Expand Down
3 changes: 2 additions & 1 deletion Sources/metamask-ios-sdk/Classes/API/InfuraProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ public class ReadOnlyRPCProvider {
params: Any = "",
chainId: String,
appMetadata: AppMetadata) async -> Any? {
Logging.log("ReadOnlyRPCProvider:: Sending request \(request.method) on chain \(chainId) via Infura API")

let params: [String: Any] = [
"method": request.method,
Expand All @@ -122,6 +121,8 @@ public class ReadOnlyRPCProvider {
Logging.error("ReadOnlyRPCProvider:: Infura endpoint for chainId \(chainId) is not available")
return nil
}

Logging.log("ReadOnlyRPCProvider:: Sending request \(request.method) on chain \(chainId) using endpoint \(endpoint) via Infura API")

let devicePlatformInfo = DeviceInfo.platformDescription
network.addHeaders([
Expand Down
18 changes: 16 additions & 2 deletions Sources/metamask-ios-sdk/Classes/API/Network.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,25 @@ public protocol Networking: ObservableObject {

public class Network: Networking {
public init() {}

private let queue = DispatchQueue(label: "headers.queue")

private var additionalHeaders: [String: String] = [
"Accept": "application/json",
"Content-Type": "application/json"
]

func getAdditionalHeaders() -> [String: String] {
return queue.sync { [weak self] in
return self?.additionalHeaders ?? [:]
}
}

func mergeHeaders(_ headers: [String: String]) {
queue.sync {
additionalHeaders.merge(headers) { (_, new) in new }
}
}

public func fetch<T: Decodable>(_ Type: T.Type, endpoint: Endpoint) async throws -> T {
guard let url = URL(string: endpoint.url) else {
Expand Down Expand Up @@ -52,12 +66,12 @@ public class Network: Networking {
}

public func addHeaders(_ headers: [String: String]) {
additionalHeaders.merge(headers) { (_, new) in new }
mergeHeaders(headers)
}

private func request(for url: URL) -> URLRequest {
var request = URLRequest(url: url)
for (key, value) in additionalHeaders {
for (key, value) in getAdditionalHeaders() {
request.addValue(value, forHTTPHeaderField: key)
}

Expand Down
1 change: 1 addition & 0 deletions Sources/metamask-ios-sdk/Classes/Analytics/Event.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public enum Event: String {
case connectionAuthorised = "sdk_connection_authorized"
case connectionRejected = "sdk_connection_rejected"
case disconnected = "sdk_disconnected"
case connectionTerminated = "sdk_connection_terminated"

var name: String {
rawValue
Expand Down
89 changes: 67 additions & 22 deletions Sources/metamask-ios-sdk/Classes/Ethereum/Ethereum.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ protocol EthereumEventsDelegate: AnyObject {
public class Ethereum {
static let CONNECTION_ID = TimestampGenerator.timestamp()
static let BATCH_CONNECTION_ID = TimestampGenerator.timestamp()

var submittedRequests: [String: SubmittedRequest] = [:]
private let queue = DispatchQueue(label: "submittedRequests.queue")

private var cancellables: Set<AnyCancellable> = []
private let cancellablesLock = NSRecursiveLock()

let readOnlyRPCProvider: ReadOnlyRPCProvider

Expand Down Expand Up @@ -85,7 +89,7 @@ public class Ethereum {
}

private func fetchCachedSession() {
if
if
let account = store.string(for: ACCOUNT_KEY),
let chainId = store.string(for: CHAINID_KEY)
{
Expand Down Expand Up @@ -126,6 +130,42 @@ public class Ethereum {
appMetadata = metadata
commClient.appMetadata = metadata
}

func addRequest(_ submittedRequest: SubmittedRequest, id: String) {
queue.async { [weak self] in
self?.submittedRequests[id] = submittedRequest
}
}

func getAllRequests() -> [String: SubmittedRequest] {
return queue.sync { [weak self] in
return self?.submittedRequests ?? [:]
}
}

func getRequest(id: String) -> SubmittedRequest? {
return queue.sync { [weak self] in
return self?.submittedRequests[id]
}
}

func removeRequest(id: String) {
queue.async { [weak self] in
self?.submittedRequests.removeValue(forKey: id)
}
}

func removeAllRequests() {
queue.async { [weak self] in
self?.submittedRequests.removeAll()
}
}

private func syncCancellables() -> Set<AnyCancellable> {
cancellablesLock.sync {
return cancellables
}
}

// MARK: Session Management

Expand All @@ -141,8 +181,8 @@ public class Ethereum {
}

let submittedRequest = SubmittedRequest(method: "")
submittedRequests[Ethereum.CONNECTION_ID] = submittedRequest
let publisher = submittedRequests[Ethereum.CONNECTION_ID]?.publisher
addRequest(submittedRequest, id: Ethereum.CONNECTION_ID)
let publisher = getRequest(id: Ethereum.CONNECTION_ID)?.publisher

return publisher
}
Expand All @@ -153,7 +193,7 @@ public class Ethereum {
}

return await withCheckedContinuation { continuation in
publisher
let cancellable = publisher
.tryMap { output in
// remove nil and NSNUll values in result
if let resultArray = output as? [Any?] {
Expand Down Expand Up @@ -182,7 +222,11 @@ public class Ethereum {
}
}, receiveValue: { result in
continuation.resume(returning: .success(result))
}).store(in: &cancellables)
})

cancellablesLock.sync {
cancellables.insert(cancellable)
}
}
}

Expand Down Expand Up @@ -216,8 +260,8 @@ public class Ethereum {
}

let submittedRequest = SubmittedRequest(method: connectSignRequest.method)
submittedRequests[connectSignRequest.id] = submittedRequest
let publisher = submittedRequests[connectSignRequest.id]?.publisher
addRequest(submittedRequest, id: connectSignRequest.id)
let publisher = getRequest(id: connectSignRequest.id)?.publisher

commClient.connect(with: requestJson)

Expand Down Expand Up @@ -262,8 +306,8 @@ public class Ethereum {
}
case .deeplinking:
let submittedRequest = SubmittedRequest(method: connectWithRequest.method)
submittedRequests[connectWithRequest.id] = submittedRequest
let publisher = submittedRequests[connectWithRequest.id]?.publisher
addRequest(submittedRequest, id: connectWithRequest.id)
let publisher = getRequest(id: connectWithRequest.id)?.publisher

// React Native SDK has request params as Data
if let paramsData = req.params as? Data {
Expand Down Expand Up @@ -421,14 +465,14 @@ public class Ethereum {

func terminateConnection() {
if connected {
track?(.connectionRejected, [:])
track?(.connectionTerminated, [:])
}

let error = RequestError(from: ["message": "The connection request has been rejected"])
submittedRequests.forEach { key, _ in
submittedRequests[key]?.error(error)
getAllRequests().forEach { key, _ in
getRequest(id: key)?.error(error)
}
submittedRequests.removeAll()
removeAllRequests()
clearSession()
}

Expand Down Expand Up @@ -534,8 +578,8 @@ public class Ethereum {
)

let submittedRequest = SubmittedRequest(method: requestAccountsRequest.method)
submittedRequests[requestAccountsRequest.id] = submittedRequest
let publisher = submittedRequests[requestAccountsRequest.id]?.publisher
addRequest(submittedRequest, id: requestAccountsRequest.id)
let publisher = getRequest(id: requestAccountsRequest.id)?.publisher

commClient.addRequest { [weak self] in
self?.sendRequest(requestAccountsRequest)
Expand All @@ -562,8 +606,9 @@ public class Ethereum {
} else {
let id = request.id
let submittedRequest = SubmittedRequest(method: request.method)
submittedRequests[id] = submittedRequest
let publisher = submittedRequests[id]?.publisher
addRequest(submittedRequest, id: id)

let publisher = getRequest(id: id)?.publisher

if connected || !account.isEmpty {
connected = true
Expand Down Expand Up @@ -649,13 +694,13 @@ public class Ethereum {
}

func sendResult(_ result: Any, id: String) {
submittedRequests[id]?.send(result)
submittedRequests.removeValue(forKey: id)
getRequest(id: id)?.send(result)
removeRequest(id: id)
}

func sendError(_ error: RequestError, id: String) {
submittedRequests[id]?.error(error)
submittedRequests.removeValue(forKey: id)
getRequest(id: id)?.error(error)
removeRequest(id: id)

if error.codeType == .unauthorisedRequest {
clearSession()
Expand All @@ -676,7 +721,7 @@ public class Ethereum {
}

func receiveResponse(_ data: [String: Any], id: String) {
guard let request = submittedRequests[id] else { return }
guard let request = getRequest(id: id) else { return }

track?(.sdkRpcRequestDone, [
"from": "mobile",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// NSRecursiveLock.swift
//

import Foundation

extension NSRecursiveLock {
@inlinable @discardableResult
func sync<Value>(_ work: () -> Value) -> Value {
lock()
defer { unlock() }
return work()
}
}
2 changes: 1 addition & 1 deletion metamask-ios-sdk.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'metamask-ios-sdk'
s.version = '0.8.7'
s.version = '0.8.8'
s.summary = 'Enable users to easily connect with their MetaMask Mobile wallet.'
s.swift_version = '5.5'

Expand Down