Skip to content

Commit d0d93d3

Browse files
authored
fix: multithreading handling (#189)
* fix: multithreading handling * bump version number
1 parent 0399a53 commit d0d93d3

File tree

8 files changed

+106
-28
lines changed

8 files changed

+106
-28
lines changed

Example/Tests/MockInfuraProvider.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ class MockReadOnlyRPCProvider: ReadOnlyRPCProvider {
1111
var response: Any? = "{}"
1212
var expectation: XCTestExpectation?
1313

14-
override func sendRequest(_ request: any RPCRequest, chainId: String, appMetadata: AppMetadata) async -> Any? {
14+
override func sendRequest(_ request: any RPCRequest,
15+
params: Any = "",
16+
chainId: String,
17+
appMetadata: AppMetadata) async -> Any? {
1518
sendRequestCalled = true
1619
expectation?.fulfill()
1720
return response

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Alternatively, you can add the URL directly in your project's package file:
5454
dependencies: [
5555
.package(
5656
url: "https://github.com/MetaMask/metamask-ios-sdk",
57-
from: "0.8.7"
57+
from: "0.8.8"
5858
)
5959
]
6060
```

Sources/metamask-ios-sdk/Classes/API/InfuraProvider.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ public class ReadOnlyRPCProvider {
109109
params: Any = "",
110110
chainId: String,
111111
appMetadata: AppMetadata) async -> Any? {
112-
Logging.log("ReadOnlyRPCProvider:: Sending request \(request.method) on chain \(chainId) via Infura API")
113112

114113
let params: [String: Any] = [
115114
"method": request.method,
@@ -122,6 +121,8 @@ public class ReadOnlyRPCProvider {
122121
Logging.error("ReadOnlyRPCProvider:: Infura endpoint for chainId \(chainId) is not available")
123122
return nil
124123
}
124+
125+
Logging.log("ReadOnlyRPCProvider:: Sending request \(request.method) on chain \(chainId) using endpoint \(endpoint) via Infura API")
125126

126127
let devicePlatformInfo = DeviceInfo.platformDescription
127128
network.addHeaders([

Sources/metamask-ios-sdk/Classes/API/Network.swift

+16-2
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,25 @@ public protocol Networking: ObservableObject {
1414

1515
public class Network: Networking {
1616
public init() {}
17+
18+
private let queue = DispatchQueue(label: "headers.queue")
1719

1820
private var additionalHeaders: [String: String] = [
1921
"Accept": "application/json",
2022
"Content-Type": "application/json"
2123
]
24+
25+
func getAdditionalHeaders() -> [String: String] {
26+
return queue.sync { [weak self] in
27+
return self?.additionalHeaders ?? [:]
28+
}
29+
}
30+
31+
func mergeHeaders(_ headers: [String: String]) {
32+
queue.sync {
33+
additionalHeaders.merge(headers) { (_, new) in new }
34+
}
35+
}
2236

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

5468
public func addHeaders(_ headers: [String: String]) {
55-
additionalHeaders.merge(headers) { (_, new) in new }
69+
mergeHeaders(headers)
5670
}
5771

5872
private func request(for url: URL) -> URLRequest {
5973
var request = URLRequest(url: url)
60-
for (key, value) in additionalHeaders {
74+
for (key, value) in getAdditionalHeaders() {
6175
request.addValue(value, forHTTPHeaderField: key)
6276
}
6377

Sources/metamask-ios-sdk/Classes/Analytics/Event.swift

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public enum Event: String {
1111
case connectionAuthorised = "sdk_connection_authorized"
1212
case connectionRejected = "sdk_connection_rejected"
1313
case disconnected = "sdk_disconnected"
14+
case connectionTerminated = "sdk_connection_terminated"
1415

1516
var name: String {
1617
rawValue

Sources/metamask-ios-sdk/Classes/Ethereum/Ethereum.swift

+67-22
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ protocol EthereumEventsDelegate: AnyObject {
1616
public class Ethereum {
1717
static let CONNECTION_ID = TimestampGenerator.timestamp()
1818
static let BATCH_CONNECTION_ID = TimestampGenerator.timestamp()
19+
1920
var submittedRequests: [String: SubmittedRequest] = [:]
21+
private let queue = DispatchQueue(label: "submittedRequests.queue")
22+
2023
private var cancellables: Set<AnyCancellable> = []
24+
private let cancellablesLock = NSRecursiveLock()
2125

2226
let readOnlyRPCProvider: ReadOnlyRPCProvider
2327

@@ -85,7 +89,7 @@ public class Ethereum {
8589
}
8690

8791
private func fetchCachedSession() {
88-
if
92+
if
8993
let account = store.string(for: ACCOUNT_KEY),
9094
let chainId = store.string(for: CHAINID_KEY)
9195
{
@@ -126,6 +130,42 @@ public class Ethereum {
126130
appMetadata = metadata
127131
commClient.appMetadata = metadata
128132
}
133+
134+
func addRequest(_ submittedRequest: SubmittedRequest, id: String) {
135+
queue.async { [weak self] in
136+
self?.submittedRequests[id] = submittedRequest
137+
}
138+
}
139+
140+
func getAllRequests() -> [String: SubmittedRequest] {
141+
return queue.sync { [weak self] in
142+
return self?.submittedRequests ?? [:]
143+
}
144+
}
145+
146+
func getRequest(id: String) -> SubmittedRequest? {
147+
return queue.sync { [weak self] in
148+
return self?.submittedRequests[id]
149+
}
150+
}
151+
152+
func removeRequest(id: String) {
153+
queue.async { [weak self] in
154+
self?.submittedRequests.removeValue(forKey: id)
155+
}
156+
}
157+
158+
func removeAllRequests() {
159+
queue.async { [weak self] in
160+
self?.submittedRequests.removeAll()
161+
}
162+
}
163+
164+
private func syncCancellables() -> Set<AnyCancellable> {
165+
cancellablesLock.sync {
166+
return cancellables
167+
}
168+
}
129169

130170
// MARK: Session Management
131171

@@ -141,8 +181,8 @@ public class Ethereum {
141181
}
142182

143183
let submittedRequest = SubmittedRequest(method: "")
144-
submittedRequests[Ethereum.CONNECTION_ID] = submittedRequest
145-
let publisher = submittedRequests[Ethereum.CONNECTION_ID]?.publisher
184+
addRequest(submittedRequest, id: Ethereum.CONNECTION_ID)
185+
let publisher = getRequest(id: Ethereum.CONNECTION_ID)?.publisher
146186

147187
return publisher
148188
}
@@ -153,7 +193,7 @@ public class Ethereum {
153193
}
154194

155195
return await withCheckedContinuation { continuation in
156-
publisher
196+
let cancellable = publisher
157197
.tryMap { output in
158198
// remove nil and NSNUll values in result
159199
if let resultArray = output as? [Any?] {
@@ -182,7 +222,11 @@ public class Ethereum {
182222
}
183223
}, receiveValue: { result in
184224
continuation.resume(returning: .success(result))
185-
}).store(in: &cancellables)
225+
})
226+
227+
cancellablesLock.sync {
228+
cancellables.insert(cancellable)
229+
}
186230
}
187231
}
188232

@@ -216,8 +260,8 @@ public class Ethereum {
216260
}
217261

218262
let submittedRequest = SubmittedRequest(method: connectSignRequest.method)
219-
submittedRequests[connectSignRequest.id] = submittedRequest
220-
let publisher = submittedRequests[connectSignRequest.id]?.publisher
263+
addRequest(submittedRequest, id: connectSignRequest.id)
264+
let publisher = getRequest(id: connectSignRequest.id)?.publisher
221265

222266
commClient.connect(with: requestJson)
223267

@@ -262,8 +306,8 @@ public class Ethereum {
262306
}
263307
case .deeplinking:
264308
let submittedRequest = SubmittedRequest(method: connectWithRequest.method)
265-
submittedRequests[connectWithRequest.id] = submittedRequest
266-
let publisher = submittedRequests[connectWithRequest.id]?.publisher
309+
addRequest(submittedRequest, id: connectWithRequest.id)
310+
let publisher = getRequest(id: connectWithRequest.id)?.publisher
267311

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

422466
func terminateConnection() {
423467
if connected {
424-
track?(.connectionRejected, [:])
468+
track?(.connectionTerminated, [:])
425469
}
426470

427471
let error = RequestError(from: ["message": "The connection request has been rejected"])
428-
submittedRequests.forEach { key, _ in
429-
submittedRequests[key]?.error(error)
472+
getAllRequests().forEach { key, _ in
473+
getRequest(id: key)?.error(error)
430474
}
431-
submittedRequests.removeAll()
475+
removeAllRequests()
432476
clearSession()
433477
}
434478

@@ -534,8 +578,8 @@ public class Ethereum {
534578
)
535579

536580
let submittedRequest = SubmittedRequest(method: requestAccountsRequest.method)
537-
submittedRequests[requestAccountsRequest.id] = submittedRequest
538-
let publisher = submittedRequests[requestAccountsRequest.id]?.publisher
581+
addRequest(submittedRequest, id: requestAccountsRequest.id)
582+
let publisher = getRequest(id: requestAccountsRequest.id)?.publisher
539583

540584
commClient.addRequest { [weak self] in
541585
self?.sendRequest(requestAccountsRequest)
@@ -562,8 +606,9 @@ public class Ethereum {
562606
} else {
563607
let id = request.id
564608
let submittedRequest = SubmittedRequest(method: request.method)
565-
submittedRequests[id] = submittedRequest
566-
let publisher = submittedRequests[id]?.publisher
609+
addRequest(submittedRequest, id: id)
610+
611+
let publisher = getRequest(id: id)?.publisher
567612

568613
if connected || !account.isEmpty {
569614
connected = true
@@ -649,13 +694,13 @@ public class Ethereum {
649694
}
650695

651696
func sendResult(_ result: Any, id: String) {
652-
submittedRequests[id]?.send(result)
653-
submittedRequests.removeValue(forKey: id)
697+
getRequest(id: id)?.send(result)
698+
removeRequest(id: id)
654699
}
655700

656701
func sendError(_ error: RequestError, id: String) {
657-
submittedRequests[id]?.error(error)
658-
submittedRequests.removeValue(forKey: id)
702+
getRequest(id: id)?.error(error)
703+
removeRequest(id: id)
659704

660705
if error.codeType == .unauthorisedRequest {
661706
clearSession()
@@ -676,7 +721,7 @@ public class Ethereum {
676721
}
677722

678723
func receiveResponse(_ data: [String: Any], id: String) {
679-
guard let request = submittedRequests[id] else { return }
724+
guard let request = getRequest(id: id) else { return }
680725

681726
track?(.sdkRpcRequestDone, [
682727
"from": "mobile",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// NSRecursiveLock.swift
3+
//
4+
5+
import Foundation
6+
7+
extension NSRecursiveLock {
8+
@inlinable @discardableResult
9+
func sync<Value>(_ work: () -> Value) -> Value {
10+
lock()
11+
defer { unlock() }
12+
return work()
13+
}
14+
}

metamask-ios-sdk.podspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'metamask-ios-sdk'
3-
s.version = '0.8.7'
3+
s.version = '0.8.8'
44
s.summary = 'Enable users to easily connect with their MetaMask Mobile wallet.'
55
s.swift_version = '5.5'
66

0 commit comments

Comments
 (0)