Skip to content

Commit 57db4c3

Browse files
almost done
1 parent dfd43fd commit 57db4c3

File tree

2 files changed

+204
-73
lines changed

2 files changed

+204
-73
lines changed

web3swift/Web3/Classes/Web3+WebsocketProvider.swift

Lines changed: 160 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import Starscream
1010
import PromiseKit
1111
import BigInt
1212
import Foundation
13+
import EthereumAddress
1314

1415
extension web3.Eth {
15-
public func getLatestPendingTransactions(forDelegate delegate: Web3SocketDelegate) throws {
16+
17+
public func getWebsocketProvider(forDelegate delegate: Web3SocketDelegate) throws -> InfuraWebsocketProvider {
1618
var infuraWSProvider: InfuraWebsocketProvider
1719
if !(provider is InfuraWebsocketProvider) {
1820
guard let infuraNetwork = provider.network else {
@@ -26,7 +28,17 @@ extension web3.Eth {
2628
infuraWSProvider = provider as! InfuraWebsocketProvider
2729
}
2830
infuraWSProvider.connectSocket()
29-
try infuraWSProvider.subscribeOn(method: .newPendingTransactionFilter)
31+
return infuraWSProvider
32+
}
33+
34+
public func getLatestPendingTransactions(forDelegate delegate: Web3SocketDelegate) throws {
35+
let provider = try getWebsocketProvider(forDelegate: delegate)
36+
try provider.filter(method: .newPendingTransactionFilter)
37+
}
38+
39+
public func subscribeOnPendingTransactions(forDelegate delegate: Web3SocketDelegate) throws {
40+
let provider = try getWebsocketProvider(forDelegate: delegate)
41+
try provider.subscribeOnNewPendingTransactions()
3042
}
3143
}
3244

@@ -35,20 +47,40 @@ public protocol IWebsocketProvider {
3547
var delegate: Web3SocketDelegate {get set}
3648
func connectSocket() throws
3749
func disconnectSocket() throws
50+
func writeMessage(string: String)
51+
func writeMessage(data: Data)
3852
}
3953

4054
public enum InfuraWebsocketMethod: String, Encodable {
4155

4256
case newPendingTransactionFilter = "eth_newPendingTransactionFilter"
4357
case getFilterChanges = "eth_getFilterChanges"
58+
case newFilter = "eth_newFilter"
59+
case newBlockFilter = "eth_newBlockFilter"
60+
case getFilterLogs = "eth_getFilterLogs"
61+
case uninstallFilter = "eth_uninstallFilter"
62+
case subscribe = "eth_subscribe"
63+
case unsubscribe = "eth_unsubscribe"
4464

45-
public var requiredNumOfParameters: Int {
65+
public var requiredNumOfParameters: Int? {
4666
get {
4767
switch self {
4868
case .newPendingTransactionFilter:
4969
return 0
5070
case .getFilterChanges:
5171
return 1
72+
case .newFilter:
73+
return nil
74+
case .newBlockFilter:
75+
return 0
76+
case .getFilterLogs:
77+
return nil
78+
case .uninstallFilter:
79+
return 1
80+
case .subscribe:
81+
return nil
82+
case .unsubscribe:
83+
return 1
5284
}
5385
}
5486
}
@@ -88,11 +120,14 @@ public struct InfuraWebsocketRequest: Encodable {
88120

89121
public protocol Web3SocketDelegate {
90122
func received(message: Any)
123+
func gotError(error: Error)
91124
}
92125

93126
public final class InfuraWebsocketProvider: WebsocketProvider {
94-
public var subscriptionKey: String?
95-
private var subscriptionTimer: Timer?
127+
public var filterID: String?
128+
public var subscriptionIDs = Set<String>()
129+
private var subscriptionIDforUnsubscribing: String? = nil
130+
private var filterTimer: Timer?
96131

97132
public init?(_ network: Networks,
98133
delegate: Web3SocketDelegate,
@@ -115,52 +150,110 @@ public final class InfuraWebsocketProvider: WebsocketProvider {
115150
guard let socketProvider = InfuraWebsocketProvider(network,
116151
delegate: delegate,
117152
keystoreManager: manager) else {return nil}
118-
socketProvider.socket.connect()
153+
socketProvider.connectSocket()
119154
return socketProvider
120155
}
121156

122-
public func subscribeOn(method: InfuraWebsocketMethod, params: [Encodable]? = nil) throws {
123-
do {
124-
subscriptionTimer?.invalidate()
125-
subscriptionKey = nil
126-
let params = params ?? []
127-
let paramsCount = params.count
128-
guard method.requiredNumOfParameters == paramsCount else {
129-
throw Web3Error.inputError(desc: "Wrong number of params: need - \(method.requiredNumOfParameters), got - \(paramsCount)")
130-
}
131-
let request = JSONRPCRequestFabric.prepareRequest(method, parameters: params)
132-
let encoder = JSONEncoder()
133-
let requestData = try encoder.encode(request)
134-
socket.write(data: requestData)
135-
subscriptionTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.getSubscriptionChanges), userInfo: nil, repeats: true)
136-
} catch {
137-
throw Web3Error.connectionError
157+
public func writeMessage(method: InfuraWebsocketMethod, params: [Encodable]) throws {
158+
let request = JSONRPCRequestFabric.prepareRequest(method, parameters: params)
159+
let encoder = JSONEncoder()
160+
let requestData = try encoder.encode(request)
161+
writeMessage(data: requestData)
162+
}
163+
164+
public func filter(method: InfuraWebsocketMethod, params: [Encodable]? = nil) throws {
165+
filterTimer?.invalidate()
166+
filterID = nil
167+
let params = params ?? []
168+
let paramsCount = params.count
169+
guard method.requiredNumOfParameters == paramsCount || method.requiredNumOfParameters == nil else {
170+
throw Web3Error.inputError(desc: "Wrong number of params: need - \(method.requiredNumOfParameters!), got - \(paramsCount)")
138171
}
172+
try writeMessage(method: method, params: params)
173+
filterTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(getFilterChanges), userInfo: nil, repeats: true)
139174
}
140175

141-
@objc public func getSubscriptionChanges() {
142-
DispatchQueue.global().async { [unowned self] in
143-
if let key = self.subscriptionKey {
144-
self.subscriptionTimer?.invalidate()
145-
let method = InfuraWebsocketMethod.getFilterChanges
146-
let request = JSONRPCRequestFabric.prepareRequest(method, parameters: [key])
147-
let encoder = JSONEncoder()
148-
if let requestData = try? encoder.encode(request) {
149-
self.socket.write(data: requestData)
150-
}
151-
}
176+
@objc public func getFilterChanges() throws {
177+
if let id = self.filterID {
178+
filterTimer?.invalidate()
179+
let method = InfuraWebsocketMethod.getFilterChanges
180+
try writeMessage(method: method, params: [id])
181+
}
182+
}
183+
184+
public func getFilterLogs() throws {
185+
if let id = self.filterID {
186+
let method = InfuraWebsocketMethod.getFilterLogs
187+
try writeMessage(method: method, params: [id])
188+
}
189+
}
190+
191+
public func unistallFilter() throws {
192+
if let id = self.filterID {
193+
let method = InfuraWebsocketMethod.uninstallFilter
194+
try writeMessage(method: method, params: [id])
152195
}
153196
}
154197

198+
public func subscribe(params: [Encodable]) throws {
199+
let method = InfuraWebsocketMethod.subscribe
200+
try writeMessage(method: method, params: params)
201+
}
202+
203+
public func unsubscribe(subscriptionID: String) throws {
204+
let method = InfuraWebsocketMethod.unsubscribe
205+
subscriptionIDforUnsubscribing = subscriptionID
206+
try writeMessage(method: method, params: [subscriptionID])
207+
}
208+
209+
public func subscribeOnNewHeads() throws {
210+
let method = InfuraWebsocketMethod.subscribe
211+
let params = ["newHeads"]
212+
try writeMessage(method: method, params: params)
213+
}
214+
215+
public func subscribeOnNewPendingTransactions() throws {
216+
let method = InfuraWebsocketMethod.subscribe
217+
let params = ["newPendingTransactions"]
218+
try writeMessage(method: method, params: params)
219+
}
220+
221+
public func subscribeOnSyncing() throws {
222+
guard network != Networks.Kovan else {
223+
throw Web3Error.inputError(desc: "Can't sync on Kovan")
224+
}
225+
let method = InfuraWebsocketMethod.subscribe
226+
let params = ["syncing"]
227+
try writeMessage(method: method, params: params)
228+
}
229+
155230
override public func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
156231
if let data = text.data(using: String.Encoding.utf8),
157-
let dictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:AnyObject] {
158-
if subscriptionKey == nil,
232+
let dictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
233+
if filterID == nil,
159234
let result = dictionary["result"] as? String {
160-
subscriptionKey = result
161-
} else {
162-
let result = dictionary["result"] as Any
235+
// setting filter id
236+
filterID = result
237+
} else if let params = dictionary["params"] as? [String: Any],
238+
let subscription = params["subscription"] as? String,
239+
let result = params["result"] {
240+
// subscription result
241+
subscriptionIDs.insert(subscription)
163242
delegate.received(message: result)
243+
} else if let unsubscribed = dictionary["result"] as? Bool {
244+
// unsubsribe result
245+
if unsubscribed == true, let id = subscriptionIDforUnsubscribing {
246+
subscriptionIDs.remove(id)
247+
} else if let id = subscriptionIDforUnsubscribing {
248+
delegate.gotError(error: Web3Error.processingError(desc: "Can\'t unsubscribe \(id)"))
249+
} else {
250+
delegate.received(message: unsubscribed)
251+
}
252+
} else if let message = dictionary["result"] {
253+
// filter result
254+
delegate.received(message: message)
255+
} else {
256+
delegate.gotError(error: Web3Error.processingError(desc: "Can\'t get known result. Message is: \(text)"))
164257
}
165258
}
166259
}
@@ -169,15 +262,11 @@ public final class InfuraWebsocketProvider: WebsocketProvider {
169262
/// The default websocket provider.
170263
public class WebsocketProvider: Web3Provider, IWebsocketProvider, WebSocketDelegate {
171264
public func sendAsync(_ request: JSONRPCrequest, queue: DispatchQueue) -> Promise<JSONRPCresponse> {
172-
if request.method == nil {
173-
return Promise(error: Web3Error.nodeError(desc: "RPC method is nil"))
174-
}
175-
176-
return Web3HttpProvider.post(request, providerURL: self.url, queue: queue, session: self.session)
265+
return Promise(error: Web3Error.inputError(desc: "Sending is unsupported for Websocket provider. Please, use \'sendMessage\'"))
177266
}
178267

179268
public func sendAsync(_ requests: JSONRPCrequestBatch, queue: DispatchQueue) -> Promise<JSONRPCresponseBatch> {
180-
return Web3HttpProvider.post(requests, providerURL: self.url, queue: queue, session: self.session)
269+
return Promise(error: Web3Error.inputError(desc: "Sending is unsupported for Websocket provider. Please, use \'sendMessage\'"))
181270
}
182271

183272
public var network: Networks?
@@ -202,16 +291,22 @@ public class WebsocketProvider: Web3Provider, IWebsocketProvider, WebSocketDeleg
202291
socket = WebSocket(url: endpoint)
203292
socket.delegate = self
204293
if net == nil {
205-
let request = JSONRPCRequestFabric.prepareRequest(.getNetwork, parameters: [])
206-
207-
if let response = try? Web3HttpProvider.post(request,
208-
providerURL: endpoint,
209-
queue: DispatchQueue.global(qos: .userInteractive),
210-
session: session).wait(),
211-
response.error == nil,
212-
let result: String = response.getValue(),
213-
let intNetworkNumber = Int(result) {
214-
network = Networks.fromInt(intNetworkNumber)
294+
let endpointString = endpoint.absoluteString
295+
if endpointString.hasPrefix("wss://") && endpointString.hasSuffix(".infura.io/ws") {
296+
let networkString = endpointString.replacingOccurrences(of: "wss://", with: "")
297+
.replacingOccurrences(of: ".infura.io/ws", with: "")
298+
switch networkString {
299+
case "mainnet":
300+
network = Networks.Mainnet
301+
case "rinkeby":
302+
network = Networks.Rinkeby
303+
case "ropsten":
304+
network = Networks.Ropsten
305+
case "kovan":
306+
network = Networks.Kovan
307+
default:
308+
break
309+
}
215310
}
216311
} else {
217312
network = net
@@ -231,13 +326,21 @@ public class WebsocketProvider: Web3Provider, IWebsocketProvider, WebSocketDeleg
231326
keystoreManager manager: KeystoreManager? = nil,
232327
network net: Networks? = nil) -> WebsocketProvider {
233328
let socketProvider = WebsocketProvider(endpoint: endpoint,
234-
delegate: delegate,
235-
keystoreManager: manager,
236-
network: net)
329+
delegate: delegate,
330+
keystoreManager: manager,
331+
network: net)
237332
socketProvider.connectSocket()
238333
return socketProvider
239334
}
240335

336+
public func writeMessage(string: String) {
337+
socket.write(string: string)
338+
}
339+
340+
public func writeMessage(data: Data) {
341+
socket.write(data: data)
342+
}
343+
241344
public func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {
242345
print("got some text: \(text)")
243346
delegate.received(message: text)

web3swiftTests/web3swift_websocket_Tests.swift

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,55 @@ import Starscream
1111

1212
@testable import web3swift_iOS
1313

14+
class SpyDelegate: Web3SocketDelegate {
15+
16+
// Setting .None is unnecessary, but helps with clarity imho
17+
var somethingWithDelegateResult: String? = nil
18+
19+
// Async test code needs to fulfill the XCTestExpecation used for the test
20+
// when all the async operations have been completed. For this reason we need
21+
// to store a reference to the expectation
22+
var asyncExpectation: XCTestExpectation?
23+
24+
func received(message: Any) {
25+
guard let expectation = asyncExpectation else {
26+
XCTFail("SpyDelegate was not setup correctly. Missing XCTExpectation reference")
27+
return
28+
}
29+
30+
print(message as? String)
31+
expectation.fulfill()
32+
}
33+
34+
func gotError(error: Error) {
35+
XCTFail(error.localizedDescription)
36+
}
37+
}
38+
1439
class web3swift_websocket_Tests: XCTestCase {
1540

16-
var socketProvider: Web3SocketProvider?
41+
let spyDelegate = SpyDelegate()
42+
var socketProvider: InfuraWebsocketProvider?
1743

18-
func test() {
19-
let provider = Web3.InfuraMainnetWeb3().provider
20-
guard let socketProvider = Web3SocketProvider.connectToSocket(provider, delegate: self) else {
44+
func testSubscribeOnPendingTXs() {
45+
guard let socketProvider = InfuraWebsocketProvider.connectToSocket(.Mainnet, delegate: spyDelegate) else {
2146
return XCTFail()
2247
}
23-
self.socketProvider = socketProvider
24-
do {
25-
try self.socketProvider!.subscribeOn(method: .pendingTransactions)
26-
sleep(1000000000)
27-
} catch {
28-
XCTFail()
48+
spyDelegate.asyncExpectation = expectation(description: "Delegate called")
49+
try! socketProvider.filter(method: .newPendingTransactionFilter)
50+
51+
waitForExpectations(timeout: 100000) { error in
52+
if let error = error {
53+
XCTFail("waitForExpectationsWithTimeout errored: \(error)")
54+
}
55+
56+
guard let result = self.spyDelegate.somethingWithDelegateResult else {
57+
XCTFail("Expected delegate to be called")
58+
return
59+
}
60+
print(result)
61+
62+
XCTAssert(true)
2963
}
3064
}
3165
}
32-
33-
extension web3swift_websocket_Tests: Web3SocketDelegate {
34-
func received(message: Any) {
35-
print("received: \(message)")
36-
}
37-
}

0 commit comments

Comments
 (0)