Skip to content

Commit

Permalink
feat: bidirectional provider support and subscriptions for websockets (
Browse files Browse the repository at this point in the history
  • Loading branch information
koraykoska authored Oct 29, 2022
1 parent a668020 commit 6a6394b
Show file tree
Hide file tree
Showing 11 changed files with 677 additions and 21 deletions.
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ let package = Package(
name: "Web3PromiseKit",
dependencies: [
.target(name: "Web3"),
.target(name: "Web3ContractABI"),
.product(name: "PromiseKit", package: "PromiseKit"),
],
path: "Sources",
Expand Down
8 changes: 4 additions & 4 deletions Sources/Core/Json/EthereumBlockObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ public struct EthereumBlockObject: Codable {
public let difficulty: EthereumQuantity

/// Integer of the total difficulty of the chain until this block.
public let totalDifficulty: EthereumQuantity
public let totalDifficulty: EthereumQuantity?

/// The "extra data" field of this block.
public let extraData: EthereumData

/// Integer the size of this block in bytes.
public let size: EthereumQuantity
public let size: EthereumQuantity?

/// The maximum gas allowed in this block.
public let gasLimit: EthereumQuantity
Expand All @@ -64,10 +64,10 @@ public struct EthereumBlockObject: Codable {
public let timestamp: EthereumQuantity

/// Array of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter.
public let transactions: [Transaction]
public let transactions: [Transaction]?

/// Array of uncle hashes.
public let uncles: [EthereumData]
public let uncles: [EthereumData]?

/**
* Represents a transaction as either a hash or an object.
Expand Down
42 changes: 42 additions & 0 deletions Sources/Core/Json/RPCResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import Foundation

// MARK: - Normal RPC Response

public struct RPCResponse<Result: Codable>: Codable {

/// The rpc id
Expand Down Expand Up @@ -37,3 +39,43 @@ public struct RPCResponse<Result: Codable>: Codable {
}

public typealias BasicRPCResponse = RPCResponse<EthereumValue>

// MARK: - RPC Event Notification Response

public struct RPCEventResponse<Result: Codable>: Codable {

/// The jsonrpc version. Typically 2.0
public let jsonrpc: String

/// The method. Typically eth_subscription
public let method: String

/// The params of the notification
public let params: Params?

public struct Params: Codable {

/// The subscription id
public let subscription: String

/// The actual expected result
public let result: Result
}

/// The error
public let error: Error?

public struct Error: Swift.Error, Codable {

/// The error code
public let code: Int

/// The error message
public let message: String

/// Description
public var localizedDescription: String {
return "RPC Error (\(code)) \(message)"
}
}
}
26 changes: 26 additions & 0 deletions Sources/Core/Providers/Web3Provider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,29 @@ public protocol Web3Provider {
func send<Params, Result>(request: RPCRequest<Params>, response: @escaping Web3ResponseCompletion<Result>)
}

public protocol Web3BidirectionalProvider: Web3Provider {

/// Subscribes to the given event (full request needs to be included) and responds with the subscription id. `onEvent` fires every time a response is received for the event.
func subscribe<Params, Result>(request: RPCRequest<Params>, response: @escaping Web3ResponseCompletion<String>, onEvent: @escaping Web3ResponseCompletion<Result>)

/// Unsubscribes the given subscription id
func unsubscribe(subscriptionId: String, completion: @escaping (_ success: Bool) -> Void)
}

public struct Web3Response<Result: Codable> {

public enum Error: Swift.Error {
// Standard

case emptyResponse
case requestFailed(Swift.Error?)
case connectionFailed(Swift.Error?)
case serverError(Swift.Error?)
case decodingError(Swift.Error?)

// Events

case subscriptionCancelled(Swift.Error?)
}

public enum Status<Result> {
Expand Down Expand Up @@ -61,6 +76,17 @@ public struct Web3Response<Result: Codable> {
}
}

/// Initialize with a notification response
public init(rpcEventResponse: RPCEventResponse<Result>) {
if let params = rpcEventResponse.params {
self.status = .success(params.result)
} else if let error = rpcEventResponse.error {
self.status = .failure(error)
} else {
self.status = .failure(Error.emptyResponse)
}
}

/// For convenience, initialize with one of the common errors
public init(error: Error) {
self.status = .failure(error)
Expand Down
170 changes: 170 additions & 0 deletions Sources/Core/Toolbox/SynchronizedArray.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import Foundation
#if canImport(Dispatch)
import Dispatch
#endif

public final class SynchronizedArray<Element>: Sequence {

private var internalArray: [Element] = []
private let accessQueue = DispatchQueue(label: "SynchronizedArrayAccess", attributes: .concurrent)

public var array: [Element] {
get {
var arrayCopy: [Element]?

accessQueue.sync {
arrayCopy = self.internalArray
}

return arrayCopy!
}

set {
let arrayCopy = newValue

accessQueue.async(flags: .barrier) {
self.internalArray = arrayCopy
}
}
}

public init() {
}

public init(array: [Element]) {
self.internalArray = array
}

public func append(_ newElement: Element) {
self.accessQueue.async(flags: .barrier) {
self.internalArray.append(newElement)
}
}

public func remove(atIndex index: Int) {
self.accessQueue.async(flags: .barrier) {
self.internalArray.remove(at: index)
}
}

// MARK: - In Place stuff
public func filterInPlace(_ isIncluded: (Element) throws -> Bool) throws {
var thrownError: Error? = nil

accessQueue.sync(flags: .barrier) {
do {
try self.internalArray = self.internalArray.filter(isIncluded)
} catch {
thrownError = error
}
}

if let error = thrownError {
throw error
}
}

public func filterInPlace(_ isIncluded: @escaping (Element) -> Bool) {
accessQueue.async(flags: .barrier) {
self.internalArray = self.internalArray.filter(isIncluded)
}
}

// MARK: - Accessors
public var count: Int {
var count = 0

self.accessQueue.sync {
count = self.internalArray.count
}

return count
}

public var isEmpty: Bool {
var e: Bool!

self.accessQueue.sync {
e = self.internalArray.isEmpty
}

return e
}

public func first() -> Element? {
var element: Element?

self.accessQueue.sync {
if !self.internalArray.isEmpty {
element = self.internalArray[0]
}
}

return element
}

public func popLast() -> Element? {
var element: Element?

accessQueue.sync(flags: .barrier) {
element = self.internalArray.popLast()
}

return element
}

public func removeFirst() -> Element? {
var element: Element?

accessQueue.sync(flags: .barrier) {
if self.internalArray.count > 0 {
element = self.internalArray.removeFirst()
} else {
element = nil
}
}

return element
}

public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T] {
// Get array copy
let arrayCopy = array

return try arrayCopy.map(transform)
}

public func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> [Element] {
// Get array copy
let arrayCopy = array

return try arrayCopy.filter(isIncluded)
}

public subscript(index: Int) -> Element {
set {
self.accessQueue.async(flags: .barrier) {
self.internalArray[index] = newValue
}
}
get {
var element: Element!

self.accessQueue.sync {
element = self.internalArray[index]
}

return element
}
}

public func makeIterator() -> Array<Element>.Iterator {
var iterator: Array<Element>.Iterator!

accessQueue.sync {
iterator = self.internalArray.makeIterator()
}

return iterator
}
}
Loading

0 comments on commit 6a6394b

Please sign in to comment.