Batch send ICE candidates

This commit is contained in:
Niels Andriesse 2021-08-17 16:02:20 +10:00
parent 525eb40d8d
commit 1ad42547b2
10 changed files with 151 additions and 149 deletions

View File

@ -37,6 +37,7 @@ final class CallVCV2 : UIViewController, WebRTCWrapperDelegate {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
WebRTCWrapper.current = webRTCWrapper
setUpViewHierarchy()
cameraManager.prepare()

View File

@ -7,7 +7,7 @@ extension AppDelegate {
func setUpCallHandling() {
MessageReceiver.handleOfferCallMessage = { message in
DispatchQueue.main.async {
let sdp = RTCSessionDescription(type: .offer, sdp: message.sdp!)
let sdp = RTCSessionDescription(type: .offer, sdp: message.sdps![0])
guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully
let alert = UIAlertController(title: "Incoming Call", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Accept", style: .default, handler: { _ in

View File

@ -2,9 +2,9 @@ import WebRTC
extension WebRTCWrapper {
public func handleICECandidate(_ candidate: RTCIceCandidate) {
public func handleICECandidates(_ candidate: [RTCIceCandidate]) {
print("[Calls] Received ICE candidate message.")
peerConnection.add(candidate)
candidate.forEach { peerConnection.add($0) }
}
public func handleRemoteSDP(_ sdp: RTCSessionDescription, from sessionID: String) {
@ -15,7 +15,6 @@ extension WebRTCWrapper {
} else {
guard let self = self,
sdp.type == .offer, self.peerConnection.localDescription == nil else { return }
// Automatically answer the call
Storage.write { transaction in
self.sendAnswer(to: sessionID, using: transaction).retainUntilComplete()
}

View File

@ -9,6 +9,8 @@ public protocol WebRTCWrapperDelegate : AnyObject {
public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate {
public weak var delegate: WebRTCWrapperDelegate?
private let contactSessionID: String
private var queuedICECandidates: [RTCIceCandidate] = []
private var iceCandidateSendTimer: Timer?
private let defaultICEServers = [
"stun:stun.l.google.com:19302",
@ -101,7 +103,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate {
audioSession.unlockForConfiguration()
}
// MARK: Call Management
// MARK: Signaling
public func sendOffer(to sessionID: String, using transaction: YapDatabaseReadWriteTransaction) -> Promise<Void> {
print("[Calls] Initiating call.")
guard let thread = TSContactThread.fetch(for: sessionID, using: transaction) else { return Promise(error: Error.noThread) }
@ -120,7 +122,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate {
DispatchQueue.main.async {
let message = CallMessage()
message.kind = .offer
message.sdp = sdp.sdp
message.sdps = [ sdp.sdp ]
MessageSender.send(message, in: thread, using: transaction)
seal.fulfill(())
}
@ -147,7 +149,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate {
DispatchQueue.main.async {
let message = CallMessage()
message.kind = .answer
message.sdp = sdp.sdp
message.sdps = [ sdp.sdp ]
MessageSender.send(message, in: thread, using: transaction)
seal.fulfill(())
}
@ -156,6 +158,29 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate {
return promise
}
private func queueICECandidateForSending(_ candidate: RTCIceCandidate) {
queuedICECandidates.append(candidate)
iceCandidateSendTimer?.invalidate()
iceCandidateSendTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in
self.sendICECandidates()
}
}
private func sendICECandidates() {
Storage.write { transaction in
let candidates = self.queuedICECandidates
guard let thread = TSContactThread.fetch(for: self.contactSessionID, using: transaction) else { return }
let message = CallMessage()
let sdps = candidates.map { $0.sdp }
let sdpMLineIndexes = candidates.map { UInt32($0.sdpMLineIndex) }
let sdpMids = candidates.map { $0.sdpMid! }
message.kind = .iceCandidates(sdpMLineIndexes: sdpMLineIndexes, sdpMids: sdpMids)
message.sdps = sdps
self.queuedICECandidates.removeAll()
MessageSender.send(message, in: thread, using: transaction)
}
}
public func dropConnection() {
peerConnection.close()
}
@ -187,13 +212,7 @@ public final class WebRTCWrapper : NSObject, RTCPeerConnectionDelegate {
public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) {
print("[Calls] ICE candidate generated.")
Storage.write { transaction in
guard let thread = TSContactThread.fetch(for: self.contactSessionID, using: transaction) else { return }
let message = CallMessage()
message.kind = .iceCandidate(sdpMLineIndex: UInt32(candidate.sdpMLineIndex), sdpMid: candidate.sdpMid!)
message.sdp = candidate.sdp
MessageSender.send(message, in: thread, using: transaction)
}
queueICECandidateForSending(candidate)
}
public func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {

View File

@ -1,25 +1,27 @@
import WebRTC
// NOTE: Multiple ICE candidates may be batched together for performance
/// See https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription for more information.
@objc(SNCallMessage)
public final class CallMessage : ControlMessage {
public var kind: Kind?
/// See https://developer.mozilla.org/en-US/docs/Glossary/SDP for more information.
public var sdp: String?
public var sdps: [String]?
// MARK: Kind
public enum Kind : Codable, CustomStringConvertible {
case offer
case answer
case provisionalAnswer
case iceCandidate(sdpMLineIndex: UInt32, sdpMid: String)
case iceCandidates(sdpMLineIndexes: [UInt32], sdpMids: [String])
public var description: String {
switch self {
case .offer: return "offer"
case .answer: return "answer"
case .provisionalAnswer: return "provisionalAnswer"
case .iceCandidate(_, _): return "iceCandidate"
case .iceCandidates(_, _): return "iceCandidates"
}
}
}
@ -27,16 +29,17 @@ public final class CallMessage : ControlMessage {
// MARK: Initialization
public override init() { super.init() }
internal init(kind: Kind, sdp: String) {
internal init(kind: Kind, sdps: [String]) {
super.init()
self.kind = kind
self.sdp = sdp
self.sdps = sdps
}
// MARK: Validation
public override var isValid: Bool {
guard super.isValid else { return false }
return kind != nil && sdp != nil
guard let sdps = sdps, !sdps.isEmpty else { return false }
return kind != nil
}
// MARK: Coding
@ -47,13 +50,13 @@ public final class CallMessage : ControlMessage {
case "offer": kind = .offer
case "answer": kind = .answer
case "provisionalAnswer": kind = .provisionalAnswer
case "iceCandidate":
guard let sdpMLineIndex = coder.decodeObject(forKey: "sdpMLineIndex") as? UInt32,
let sdpMid = coder.decodeObject(forKey: "sdpMid") as? String else { return nil }
kind = .iceCandidate(sdpMLineIndex: sdpMLineIndex, sdpMid: sdpMid)
case "iceCandidates":
guard let sdpMLineIndexes = coder.decodeObject(forKey: "sdpMLineIndexes") as? [UInt32],
let sdpMids = coder.decodeObject(forKey: "sdpMids") as? [String] else { return nil }
kind = .iceCandidates(sdpMLineIndexes: sdpMLineIndexes, sdpMids: sdpMids)
default: preconditionFailure()
}
if let sdp = coder.decodeObject(forKey: "sdp") as! String? { self.sdp = sdp }
if let sdps = coder.decodeObject(forKey: "sdps") as! [String]? { self.sdps = sdps }
}
public override func encode(with coder: NSCoder) {
@ -62,13 +65,13 @@ public final class CallMessage : ControlMessage {
case .offer: coder.encode("offer", forKey: "kind")
case .answer: coder.encode("answer", forKey: "kind")
case .provisionalAnswer: coder.encode("provisionalAnswer", forKey: "kind")
case let .iceCandidate(sdpMLineIndex, sdpMid):
coder.encode("iceCandidate", forKey: "kind")
coder.encode(sdpMLineIndex, forKey: "sdpMLineIndex")
coder.encode(sdpMid, forKey: "sdpMid")
case let .iceCandidates(sdpMLineIndexes, sdpMids):
coder.encode("iceCandidates", forKey: "kind")
coder.encode(sdpMLineIndexes, forKey: "sdpMLineIndexes")
coder.encode(sdpMids, forKey: "sdpMids")
default: preconditionFailure()
}
coder.encode(sdp, forKey: "sdp")
coder.encode(sdps, forKey: "sdps")
}
// MARK: Proto Conversion
@ -79,17 +82,17 @@ public final class CallMessage : ControlMessage {
case .offer: kind = .offer
case .answer: kind = .answer
case .provisionalAnswer: kind = .provisionalAnswer
case .iceCandidate:
let sdpMLineIndex = callMessageProto.sdpMlineIndex
guard let sdpMid = callMessageProto.sdpMid else { return nil }
kind = .iceCandidate(sdpMLineIndex: sdpMLineIndex, sdpMid: sdpMid)
case .iceCandidates:
let sdpMLineIndexes = callMessageProto.sdpMlineIndexes
let sdpMids = callMessageProto.sdpMids
kind = .iceCandidates(sdpMLineIndexes: sdpMLineIndexes, sdpMids: sdpMids)
}
let sdp = callMessageProto.sdp
return CallMessage(kind: kind, sdp: sdp)
let sdps = callMessageProto.sdps
return CallMessage(kind: kind, sdps: sdps)
}
public override func toProto(using transaction: YapDatabaseReadWriteTransaction) -> SNProtoContent? {
guard let kind = kind, let sdp = sdp else {
guard let kind = kind, let sdps = sdps, !sdps.isEmpty else {
SNLog("Couldn't construct call message proto from: \(self).")
return nil
}
@ -101,12 +104,13 @@ public final class CallMessage : ControlMessage {
case .offer: type = .offer
case .answer: type = .answer
case .provisionalAnswer: type = .provisionalAnswer
case .iceCandidate(_, _): type = .iceCandidate
case .iceCandidates(_, _): type = .iceCandidates
}
let callMessageProto = SNProtoCallMessage.builder(type: type, sdp: sdp)
if case let .iceCandidate(sdpMLineIndex, sdpMid) = kind {
callMessageProto.setSdpMlineIndex(sdpMLineIndex)
callMessageProto.setSdpMid(sdpMid)
let callMessageProto = SNProtoCallMessage.builder(type: type)
callMessageProto.setSdps(sdps)
if case let .iceCandidates(sdpMLineIndexes, sdpMids) = kind {
callMessageProto.setSdpMlineIndexes(sdpMLineIndexes)
callMessageProto.setSdpMids(sdpMids)
}
let contentProto = SNProtoContent.builder()
do {
@ -123,7 +127,7 @@ public final class CallMessage : ControlMessage {
"""
CallMessage(
kind: \(kind?.description ?? "null"),
sdp: \(sdp ?? "null")
sdps: \(sdps?.description ?? "null")
)
"""
}

View File

@ -656,7 +656,7 @@ extension SNProtoContent.SNProtoContentBuilder {
case offer = 1
case answer = 2
case provisionalAnswer = 3
case iceCandidate = 4
case iceCandidates = 4
}
private class func SNProtoCallMessageTypeWrap(_ value: SessionProtos_CallMessage.TypeEnum) -> SNProtoCallMessageType {
@ -664,7 +664,7 @@ extension SNProtoContent.SNProtoContentBuilder {
case .offer: return .offer
case .answer: return .answer
case .provisionalAnswer: return .provisionalAnswer
case .iceCandidate: return .iceCandidate
case .iceCandidates: return .iceCandidates
}
}
@ -673,25 +673,22 @@ extension SNProtoContent.SNProtoContentBuilder {
case .offer: return .offer
case .answer: return .answer
case .provisionalAnswer: return .provisionalAnswer
case .iceCandidate: return .iceCandidate
case .iceCandidates: return .iceCandidates
}
}
// MARK: - SNProtoCallMessageBuilder
@objc public class func builder(type: SNProtoCallMessageType, sdp: String) -> SNProtoCallMessageBuilder {
return SNProtoCallMessageBuilder(type: type, sdp: sdp)
@objc public class func builder(type: SNProtoCallMessageType) -> SNProtoCallMessageBuilder {
return SNProtoCallMessageBuilder(type: type)
}
// asBuilder() constructs a builder that reflects the proto's contents.
@objc public func asBuilder() -> SNProtoCallMessageBuilder {
let builder = SNProtoCallMessageBuilder(type: type, sdp: sdp)
if hasSdpMlineIndex {
builder.setSdpMlineIndex(sdpMlineIndex)
}
if let _value = sdpMid {
builder.setSdpMid(_value)
}
let builder = SNProtoCallMessageBuilder(type: type)
builder.setSdps(sdps)
builder.setSdpMlineIndexes(sdpMlineIndexes)
builder.setSdpMids(sdpMids)
return builder
}
@ -701,27 +698,44 @@ extension SNProtoContent.SNProtoContentBuilder {
@objc fileprivate override init() {}
@objc fileprivate init(type: SNProtoCallMessageType, sdp: String) {
@objc fileprivate init(type: SNProtoCallMessageType) {
super.init()
setType(type)
setSdp(sdp)
}
@objc public func setType(_ valueParam: SNProtoCallMessageType) {
proto.type = SNProtoCallMessageTypeUnwrap(valueParam)
}
@objc public func setSdp(_ valueParam: String) {
proto.sdp = valueParam
@objc public func addSdps(_ valueParam: String) {
var items = proto.sdps
items.append(valueParam)
proto.sdps = items
}
@objc public func setSdpMlineIndex(_ valueParam: UInt32) {
proto.sdpMlineIndex = valueParam
@objc public func setSdps(_ wrappedItems: [String]) {
proto.sdps = wrappedItems
}
@objc public func setSdpMid(_ valueParam: String) {
proto.sdpMid = valueParam
@objc public func addSdpMlineIndexes(_ valueParam: UInt32) {
var items = proto.sdpMlineIndexes
items.append(valueParam)
proto.sdpMlineIndexes = items
}
@objc public func setSdpMlineIndexes(_ wrappedItems: [UInt32]) {
proto.sdpMlineIndexes = wrappedItems
}
@objc public func addSdpMids(_ valueParam: String) {
var items = proto.sdpMids
items.append(valueParam)
proto.sdpMids = items
}
@objc public func setSdpMids(_ wrappedItems: [String]) {
proto.sdpMids = wrappedItems
}
@objc public func build() throws -> SNProtoCallMessage {
@ -737,31 +751,22 @@ extension SNProtoContent.SNProtoContentBuilder {
@objc public let type: SNProtoCallMessageType
@objc public let sdp: String
@objc public var sdpMlineIndex: UInt32 {
return proto.sdpMlineIndex
}
@objc public var hasSdpMlineIndex: Bool {
return proto.hasSdpMlineIndex
@objc public var sdps: [String] {
return proto.sdps
}
@objc public var sdpMid: String? {
guard proto.hasSdpMid else {
return nil
}
return proto.sdpMid
@objc public var sdpMlineIndexes: [UInt32] {
return proto.sdpMlineIndexes
}
@objc public var hasSdpMid: Bool {
return proto.hasSdpMid
@objc public var sdpMids: [String] {
return proto.sdpMids
}
private init(proto: SessionProtos_CallMessage,
type: SNProtoCallMessageType,
sdp: String) {
type: SNProtoCallMessageType) {
self.proto = proto
self.type = type
self.sdp = sdp
}
@objc
@ -780,18 +785,12 @@ extension SNProtoContent.SNProtoContentBuilder {
}
let type = SNProtoCallMessageTypeWrap(proto.type)
guard proto.hasSdp else {
throw SNProtoError.invalidProtobuf(description: "\(logTag) missing required field: sdp")
}
let sdp = proto.sdp
// MARK: - Begin Validation Logic for SNProtoCallMessage -
// MARK: - End Validation Logic for SNProtoCallMessage -
let result = SNProtoCallMessage(proto: proto,
type: type,
sdp: sdp)
type: type)
return result
}

View File

@ -319,33 +319,11 @@ struct SessionProtos_CallMessage {
/// Clears the value of `type`. Subsequent reads from it will return its default value.
mutating func clearType() {self._type = nil}
/// @required
var sdp: String {
get {return _sdp ?? String()}
set {_sdp = newValue}
}
/// Returns true if `sdp` has been explicitly set.
var hasSdp: Bool {return self._sdp != nil}
/// Clears the value of `sdp`. Subsequent reads from it will return its default value.
mutating func clearSdp() {self._sdp = nil}
var sdps: [String] = []
var sdpMlineIndex: UInt32 {
get {return _sdpMlineIndex ?? 0}
set {_sdpMlineIndex = newValue}
}
/// Returns true if `sdpMlineIndex` has been explicitly set.
var hasSdpMlineIndex: Bool {return self._sdpMlineIndex != nil}
/// Clears the value of `sdpMlineIndex`. Subsequent reads from it will return its default value.
mutating func clearSdpMlineIndex() {self._sdpMlineIndex = nil}
var sdpMlineIndexes: [UInt32] = []
var sdpMid: String {
get {return _sdpMid ?? String()}
set {_sdpMid = newValue}
}
/// Returns true if `sdpMid` has been explicitly set.
var hasSdpMid: Bool {return self._sdpMid != nil}
/// Clears the value of `sdpMid`. Subsequent reads from it will return its default value.
mutating func clearSdpMid() {self._sdpMid = nil}
var sdpMids: [String] = []
var unknownFields = SwiftProtobuf.UnknownStorage()
@ -354,7 +332,7 @@ struct SessionProtos_CallMessage {
case offer // = 1
case answer // = 2
case provisionalAnswer // = 3
case iceCandidate // = 4
case iceCandidates // = 4
init() {
self = .offer
@ -365,7 +343,7 @@ struct SessionProtos_CallMessage {
case 1: self = .offer
case 2: self = .answer
case 3: self = .provisionalAnswer
case 4: self = .iceCandidate
case 4: self = .iceCandidates
default: return nil
}
}
@ -375,7 +353,7 @@ struct SessionProtos_CallMessage {
case .offer: return 1
case .answer: return 2
case .provisionalAnswer: return 3
case .iceCandidate: return 4
case .iceCandidates: return 4
}
}
@ -384,9 +362,6 @@ struct SessionProtos_CallMessage {
init() {}
fileprivate var _type: SessionProtos_CallMessage.TypeEnum? = nil
fileprivate var _sdp: String? = nil
fileprivate var _sdpMlineIndex: UInt32? = nil
fileprivate var _sdpMid: String? = nil
}
#if swift(>=4.2)
@ -1817,14 +1792,13 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
static let protoMessageName: String = _protobuf_package + ".CallMessage"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "type"),
2: .same(proto: "sdp"),
3: .same(proto: "sdpMLineIndex"),
4: .same(proto: "sdpMid"),
2: .same(proto: "sdps"),
3: .same(proto: "sdpMLineIndexes"),
4: .same(proto: "sdpMids"),
]
public var isInitialized: Bool {
if self._type == nil {return false}
if self._sdp == nil {return false}
return true
}
@ -1835,9 +1809,9 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
// enabled. https://github.com/apple/swift-protobuf/issues/1034
switch fieldNumber {
case 1: try { try decoder.decodeSingularEnumField(value: &self._type) }()
case 2: try { try decoder.decodeSingularStringField(value: &self._sdp) }()
case 3: try { try decoder.decodeSingularUInt32Field(value: &self._sdpMlineIndex) }()
case 4: try { try decoder.decodeSingularStringField(value: &self._sdpMid) }()
case 2: try { try decoder.decodeRepeatedStringField(value: &self.sdps) }()
case 3: try { try decoder.decodeRepeatedUInt32Field(value: &self.sdpMlineIndexes) }()
case 4: try { try decoder.decodeRepeatedStringField(value: &self.sdpMids) }()
default: break
}
}
@ -1847,23 +1821,23 @@ extension SessionProtos_CallMessage: SwiftProtobuf.Message, SwiftProtobuf._Messa
if let v = self._type {
try visitor.visitSingularEnumField(value: v, fieldNumber: 1)
}
if let v = self._sdp {
try visitor.visitSingularStringField(value: v, fieldNumber: 2)
if !self.sdps.isEmpty {
try visitor.visitRepeatedStringField(value: self.sdps, fieldNumber: 2)
}
if let v = self._sdpMlineIndex {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3)
if !self.sdpMlineIndexes.isEmpty {
try visitor.visitRepeatedUInt32Field(value: self.sdpMlineIndexes, fieldNumber: 3)
}
if let v = self._sdpMid {
try visitor.visitSingularStringField(value: v, fieldNumber: 4)
if !self.sdpMids.isEmpty {
try visitor.visitRepeatedStringField(value: self.sdpMids, fieldNumber: 4)
}
try unknownFields.traverse(visitor: &visitor)
}
static func ==(lhs: SessionProtos_CallMessage, rhs: SessionProtos_CallMessage) -> Bool {
if lhs._type != rhs._type {return false}
if lhs._sdp != rhs._sdp {return false}
if lhs._sdpMlineIndex != rhs._sdpMlineIndex {return false}
if lhs._sdpMid != rhs._sdpMid {return false}
if lhs.sdps != rhs.sdps {return false}
if lhs.sdpMlineIndexes != rhs.sdpMlineIndexes {return false}
if lhs.sdpMids != rhs.sdpMids {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
@ -1874,7 +1848,7 @@ extension SessionProtos_CallMessage.TypeEnum: SwiftProtobuf._ProtoNameProviding
1: .same(proto: "OFFER"),
2: .same(proto: "ANSWER"),
3: .same(proto: "PROVISIONAL_ANSWER"),
4: .same(proto: "ICE_CANDIDATE"),
4: .same(proto: "ICE_CANDIDATES"),
]
}

View File

@ -57,15 +57,16 @@ message CallMessage {
OFFER = 1;
ANSWER = 2;
PROVISIONAL_ANSWER = 3;
ICE_CANDIDATE = 4;
ICE_CANDIDATES = 4;
}
// Multiple ICE candidates may be batched together for performance
// @required
required Type type = 1;
// @required
required string sdp = 2;
optional uint32 sdpMLineIndex = 3;
optional string sdpMid = 4;
required Type type = 1;
repeated string sdps = 2;
repeated uint32 sdpMLineIndexes = 3;
repeated string sdpMids = 4;
}
message KeyPair {

View File

@ -269,12 +269,20 @@ extension MessageReceiver {
handleOfferCallMessage?(message)
case .answer:
print("[Calls] Received answer message.")
let sdp = RTCSessionDescription(type: .answer, sdp: message.sdp!)
let sdp = RTCSessionDescription(type: .answer, sdp: message.sdps![0])
webRTCWrapper.handleRemoteSDP(sdp, from: message.sender!)
case .provisionalAnswer: break // TODO: Implement
case let .iceCandidate(sdpMLineIndex, sdpMid):
let candidate = RTCIceCandidate(sdp: message.sdp!, sdpMLineIndex: Int32(sdpMLineIndex), sdpMid: sdpMid)
webRTCWrapper.handleICECandidate(candidate)
case let .iceCandidates(sdpMLineIndexes, sdpMids):
var candidates: [RTCIceCandidate] = []
let sdps = message.sdps!
for i in 0..<sdps.count {
let sdp = sdps[i]
let sdpMLineIndex = sdpMLineIndexes[i]
let sdpMid = sdpMids[i]
let candidate = RTCIceCandidate(sdp: sdp, sdpMLineIndex: Int32(sdpMLineIndex), sdpMid: sdpMid)
candidates.append(candidate)
}
webRTCWrapper.handleICECandidates(candidates)
}
}

View File

@ -109,7 +109,7 @@ public enum HTTP {
}
}
public static func execute(_ verb: Verb, _ url: String, body: Data?, headers: [String:String] = [:], timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise<JSON> {
public static func execute(_ verb: Verb, _ url: String, body: Data?, timeout: TimeInterval = HTTP.timeout, useSeedNodeURLSession: Bool = false) -> Promise<JSON> {
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = verb.rawValue
request.httpBody = body
@ -117,9 +117,6 @@ public enum HTTP {
request.allHTTPHeaderFields?.removeValue(forKey: "User-Agent")
request.setValue("WhatsApp", forHTTPHeaderField: "User-Agent") // Set a fake value
request.setValue("en-us", forHTTPHeaderField: "Accept-Language") // Set a fake value
headers.forEach { (key, value) in
request.setValue(value, forHTTPHeaderField: key)
}
let (promise, seal) = Promise<JSON>.pending()
let urlSession = useSeedNodeURLSession ? seedNodeURLSession : snodeURLSession
let task = urlSession.dataTask(with: request) { data, response, error in