Replace Starscream with URLSessionWebSocketTask

Changelog-Fixed: Fix slow reconnection issues
This commit is contained in:
Bryan Montz
2023-04-13 09:12:16 -05:00
committed by William Casarin
parent 6ac68b5a73
commit 0e94c48e26
11 changed files with 175 additions and 126 deletions

View File

@@ -1,4 +1,3 @@
dependencies: [ dependencies: [
.Package(url: "https://github.com/daltoniam/Starscream.git", majorVersion: 4),
.Package(url: "https://github.com/jb55/secp256k1.swift.git", branch: "main") .Package(url: "https://github.com/jb55/secp256k1.swift.git", branch: "main")
] ]

View File

@@ -216,7 +216,6 @@
4CE6DEF827F7A08200C66700 /* damusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DEF727F7A08200C66700 /* damusTests.swift */; }; 4CE6DEF827F7A08200C66700 /* damusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DEF727F7A08200C66700 /* damusTests.swift */; };
4CE6DF0227F7A08200C66700 /* damusUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DF0127F7A08200C66700 /* damusUITests.swift */; }; 4CE6DF0227F7A08200C66700 /* damusUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DF0127F7A08200C66700 /* damusUITests.swift */; };
4CE6DF0427F7A08200C66700 /* damusUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DF0327F7A08200C66700 /* damusUITestsLaunchTests.swift */; }; 4CE6DF0427F7A08200C66700 /* damusUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DF0327F7A08200C66700 /* damusUITestsLaunchTests.swift */; };
4CE6DF1227F7A2B300C66700 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 4CE6DF1127F7A2B300C66700 /* Starscream */; };
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */; }; 4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */; };
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794729941DA700F758CC /* RelayFilters.swift */; }; 4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794729941DA700F758CC /* RelayFilters.swift */; };
4CE8794C2995B59E00F758CC /* RelayMetadatas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794B2995B59E00F758CC /* RelayMetadatas.swift */; }; 4CE8794C2995B59E00F758CC /* RelayMetadatas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794B2995B59E00F758CC /* RelayMetadatas.swift */; };
@@ -253,6 +252,7 @@
4CFF8F6B29CD0079008DB934 /* RepostedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */; }; 4CFF8F6B29CD0079008DB934 /* RepostedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */; };
4CFF8F6D29CD022E008DB934 /* WideEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */; }; 4CFF8F6D29CD022E008DB934 /* WideEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */; };
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; }; 4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; };
50088DA129E8271A008A1FDF /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50088DA029E8271A008A1FDF /* WebSocket.swift */; };
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; }; 50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; };
5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; }; 5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; };
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; }; 5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; };
@@ -669,6 +669,7 @@
4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostedEvent.swift; sourceTree = "<group>"; }; 4CFF8F6A29CD0079008DB934 /* RepostedEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostedEvent.swift; sourceTree = "<group>"; };
4CFF8F6C29CD022E008DB934 /* WideEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WideEventView.swift; sourceTree = "<group>"; }; 4CFF8F6C29CD022E008DB934 /* WideEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WideEventView.swift; sourceTree = "<group>"; };
4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; }; 4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; };
50088DA029E8271A008A1FDF /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = "<group>"; };
50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; }; 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; };
5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; }; 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; };
5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; }; 5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; };
@@ -708,7 +709,6 @@
files = ( files = (
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */, 4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */,
6C7DE41F2955169800E66263 /* Vault in Frameworks */, 6C7DE41F2955169800E66263 /* Vault in Frameworks */,
4CE6DF1227F7A2B300C66700 /* Starscream in Frameworks */,
4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */, 4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -973,6 +973,7 @@
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */, 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */,
4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */, 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */,
4C363A8F28247A1D006E126D /* NostrLink.swift */, 4C363A8F28247A1D006E126D /* NostrLink.swift */,
50088DA029E8271A008A1FDF /* WebSocket.swift */,
); );
path = Nostr; path = Nostr;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1348,7 +1349,6 @@
); );
name = damus; name = damus;
packageProductDependencies = ( packageProductDependencies = (
4CE6DF1127F7A2B300C66700 /* Starscream */,
4C649880286E0EE300EAE2B3 /* secp256k1 */, 4C649880286E0EE300EAE2B3 /* secp256k1 */,
4C06670328FC7EC500038D2A /* Kingfisher */, 4C06670328FC7EC500038D2A /* Kingfisher */,
6C7DE41E2955169800E66263 /* Vault */, 6C7DE41E2955169800E66263 /* Vault */,
@@ -1454,7 +1454,6 @@
); );
mainGroup = 4CE6DEDA27F7A08100C66700; mainGroup = 4CE6DEDA27F7A08100C66700;
packageReferences = ( packageReferences = (
4CE6DF1027F7A2B300C66700 /* XCRemoteSwiftPackageReference "Starscream" */,
4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */, 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */,
4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */, 4C06670228FC7EC500038D2A /* XCRemoteSwiftPackageReference "Kingfisher" */,
6C7DE41D2955169800E66263 /* XCRemoteSwiftPackageReference "Vault" */, 6C7DE41D2955169800E66263 /* XCRemoteSwiftPackageReference "Vault" */,
@@ -1661,6 +1660,7 @@
4CF0ABE52981EE0C00D66079 /* EULAView.swift in Sources */, 4CF0ABE52981EE0C00D66079 /* EULAView.swift in Sources */,
4CBCA930297DB57F00EC6B2F /* WebsiteLink.swift in Sources */, 4CBCA930297DB57F00EC6B2F /* WebsiteLink.swift in Sources */,
4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */, 4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */,
50088DA129E8271A008A1FDF /* WebSocket.swift in Sources */,
4C3EA64128FF553900C48A62 /* hash_u5.c in Sources */, 4C3EA64128FF553900C48A62 /* hash_u5.c in Sources */,
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */, 5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */,
4C3EA64F28FF59F200C48A62 /* tal.c in Sources */, 4C3EA64F28FF59F200C48A62 /* tal.c in Sources */,
@@ -2259,14 +2259,6 @@
revision = 40b4b38b3b1c83f7088c76189a742870e0ca06a9; revision = 40b4b38b3b1c83f7088c76189a742870e0ca06a9;
}; };
}; };
4CE6DF1027F7A2B300C66700 /* XCRemoteSwiftPackageReference "Starscream" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/daltoniam/Starscream";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 4.0.0;
};
};
6C7DE41D2955169800E66263 /* XCRemoteSwiftPackageReference "Vault" */ = { 6C7DE41D2955169800E66263 /* XCRemoteSwiftPackageReference "Vault" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/SparrowTek/Vault"; repositoryURL = "https://github.com/SparrowTek/Vault";
@@ -2288,11 +2280,6 @@
package = 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */; package = 4C64987F286E0EE300EAE2B3 /* XCRemoteSwiftPackageReference "secp256k1" */;
productName = secp256k1; productName = secp256k1;
}; };
4CE6DF1127F7A2B300C66700 /* Starscream */ = {
isa = XCSwiftPackageProductDependency;
package = 4CE6DF1027F7A2B300C66700 /* XCRemoteSwiftPackageReference "Starscream" */;
productName = Starscream;
};
6C7DE41E2955169800E66263 /* Vault */ = { 6C7DE41E2955169800E66263 /* Vault */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 6C7DE41D2955169800E66263 /* XCRemoteSwiftPackageReference "Vault" */; package = 6C7DE41D2955169800E66263 /* XCRemoteSwiftPackageReference "Vault" */;

View File

@@ -17,15 +17,6 @@
"revision" : "40b4b38b3b1c83f7088c76189a742870e0ca06a9" "revision" : "40b4b38b3b1c83f7088c76189a742870e0ca06a9"
} }
}, },
{
"identity" : "starscream",
"kind" : "remoteSourceControl",
"location" : "https://github.com/daltoniam/Starscream",
"state" : {
"revision" : "df8d82047f6654d8e4b655d1b1525c64e1059d21",
"version" : "4.0.4"
}
},
{ {
"identity" : "vault", "identity" : "vault",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",

View File

@@ -6,7 +6,6 @@
// //
import SwiftUI import SwiftUI
import Starscream
struct TimestampedProfile { struct TimestampedProfile {
let profile: Profile let profile: Profile
@@ -686,31 +685,6 @@ func get_since_time(last_event: NostrEvent?) -> Int64? {
return nil return nil
} }
func ws_nostr_event(relay: String, ev: WebSocketEvent) -> NostrEvent? {
switch ev {
case .binary(let dat):
return NostrEvent(content: "binary data? \(dat.count) bytes", pubkey: relay)
case .cancelled:
return NostrEvent(content: "cancelled", pubkey: relay)
case .connected:
return NostrEvent(content: "connected", pubkey: relay)
case .disconnected:
return NostrEvent(content: "disconnected", pubkey: relay)
case .error(let err):
return NostrEvent(content: "error \(err.debugDescription)", pubkey: relay)
case .text(let txt):
return NostrEvent(content: "text \(txt)", pubkey: relay)
case .pong:
return NostrEvent(content: "pong", pubkey: relay)
case .ping:
return NostrEvent(content: "ping", pubkey: relay)
case .viabilityChanged(let b):
return NostrEvent(content: "viabilityChanged \(b)", pubkey: relay)
case .reconnectSuggested(let b):
return NostrEvent(content: "reconnectSuggested \(b)", pubkey: relay)
}
}
func is_notification(ev: NostrEvent, pubkey: String) -> Bool { func is_notification(ev: NostrEvent, pubkey: String) -> Bool {
if ev.pubkey == pubkey { if ev.pubkey == pubkey {
return false return false

View File

@@ -290,17 +290,12 @@ class HomeModel: ObservableObject {
send_home_filters(relay_id: relay_id) send_home_filters(relay_id: relay_id)
} }
case .error(let merr): case .error(let merr):
let desc = merr.debugDescription let desc = String(describing: merr)
if desc.contains("Software caused connection abort") { if desc.contains("Software caused connection abort") {
pool.reconnect(to: [relay_id]) pool.reconnect(to: [relay_id])
} }
case .disconnected: fallthrough case .disconnected:
case .cancelled:
pool.reconnect(to: [relay_id]) pool.reconnect(to: [relay_id])
case .reconnectSuggested(let t):
if t {
pool.reconnect(to: [relay_id])
}
default: default:
break break
} }

View File

@@ -52,14 +52,12 @@ class Relay: Identifiable {
let descriptor: RelayDescriptor let descriptor: RelayDescriptor
let connection: RelayConnection let connection: RelayConnection
var last_pong: UInt32
var flags: Int var flags: Int
init(descriptor: RelayDescriptor, connection: RelayConnection) { init(descriptor: RelayDescriptor, connection: RelayConnection) {
self.flags = 0 self.flags = 0
self.descriptor = descriptor self.descriptor = descriptor
self.connection = connection self.connection = connection
self.last_pong = 0
} }
func mark_broken() { func mark_broken() {

View File

@@ -5,26 +5,22 @@
// Created by William Casarin on 2022-04-02. // Created by William Casarin on 2022-04-02.
// //
import Combine
import Foundation import Foundation
import Starscream
enum NostrConnectionEvent { enum NostrConnectionEvent {
case ws_event(WebSocketEvent) case ws_event(WebSocketEvent)
case nostr_event(NostrResponse) case nostr_event(NostrResponse)
} }
final class RelayConnection: WebSocketDelegate { final class RelayConnection {
private(set) var isConnected = false private(set) var isConnected = false
private(set) var isConnecting = false private(set) var isConnecting = false
private(set) var isReconnecting = false
private(set) var last_connection_attempt: TimeInterval = 0 private(set) var last_connection_attempt: TimeInterval = 0
private lazy var socket = { private lazy var socket = WebSocket(url)
let req = URLRequest(url: url) private var subscriptionToken: AnyCancellable?
let socket = WebSocket(request: req, compressionHandler: .none)
socket.delegate = self
return socket
}()
private var handleEvent: (NostrConnectionEvent) -> () private var handleEvent: (NostrConnectionEvent) -> ()
private let url: URL private let url: URL
@@ -33,16 +29,6 @@ final class RelayConnection: WebSocketDelegate {
self.handleEvent = handleEvent self.handleEvent = handleEvent
} }
func reconnect() {
if isConnected {
isReconnecting = true
disconnect()
} else {
// we're already disconnected, so just connect
connect(force: true)
}
}
func connect(force: Bool = false) { func connect(force: Bool = false) {
if !force && (isConnected || isConnecting) { if !force && (isConnected || isConnecting) {
return return
@@ -50,11 +36,27 @@ final class RelayConnection: WebSocketDelegate {
isConnecting = true isConnecting = true
last_connection_attempt = Date().timeIntervalSince1970 last_connection_attempt = Date().timeIntervalSince1970
subscriptionToken = socket.subject
.receive(on: DispatchQueue.main)
.sink { [weak self] completion in
switch completion {
case .failure(let error):
self?.receive(event: .error(error))
case .finished:
self?.receive(event: .disconnected(.normalClosure, nil))
}
} receiveValue: { [weak self] event in
self?.receive(event: event)
}
socket.connect() socket.connect()
} }
func disconnect() { func disconnect() {
socket.disconnect() socket.disconnect()
subscriptionToken = nil
isConnected = false isConnected = false
isConnecting = false isConnecting = false
} }
@@ -64,34 +66,46 @@ final class RelayConnection: WebSocketDelegate {
print("failed to encode nostr req: \(req)") print("failed to encode nostr req: \(req)")
return return
} }
socket.send(.string(req))
socket.write(string: req)
} }
// MARK: - WebSocketDelegate private func receive(event: WebSocketEvent) {
func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event { switch event {
case .connected: case .connected:
self.isConnected = true self.isConnected = true
self.isConnecting = false self.isConnecting = false
case .message(let message):
case .disconnected: self.receive(message: message)
self.isConnecting = false case .disconnected(let closeCode, let reason):
self.isConnected = false if closeCode != .normalClosure {
if self.isReconnecting { print("⚠️ Warning: RelayConnection (\(self.url)) closed with code \(closeCode), reason: \(String(describing: reason))")
self.isReconnecting = false }
self.connect() isConnected = false
isConnecting = false
reconnect()
case .error(let error):
print("⚠️ Warning: RelayConnection (\(self.url)) error: \(error)")
isConnected = false
isConnecting = false
reconnect()
}
self.handleEvent(.ws_event(event))
} }
case .cancelled, .error: func reconnect() {
self.isConnecting = false guard !isConnecting else {
self.isConnected = false return // we're already trying to connect
}
disconnect()
connect()
}
case .text(let txt): private func receive(message: URLSessionWebSocketTask.Message) {
if txt.utf8.count > 2000 { switch message {
case .string(let messageString):
if messageString.utf8.count > 2000 {
DispatchQueue.global(qos: .default).async { DispatchQueue.global(qos: .default).async {
if let ev = decode_nostr_event(txt: txt) { if let ev = decode_nostr_event(txt: messageString) {
DispatchQueue.main.async { DispatchQueue.main.async {
self.handleEvent(.nostr_event(ev)) self.handleEvent(.nostr_event(ev))
} }
@@ -99,18 +113,18 @@ final class RelayConnection: WebSocketDelegate {
} }
} }
} else { } else {
if let ev = decode_nostr_event(txt: txt) { if let ev = decode_nostr_event(txt: messageString) {
handleEvent(.nostr_event(ev)) handleEvent(.nostr_event(ev))
return return
} }
} }
case .data(let messageData):
if let messageString = String(data: messageData, encoding: .utf8) {
default: receive(message: .string(messageString))
break }
@unknown default:
print("An unexpected URLSessionWebSocketTask.Message was received.")
} }
handleEvent(.ws_event(event))
} }
} }

View File

@@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import Network
struct SubscriptionId: Identifiable, CustomStringConvertible { struct SubscriptionId: Identifiable, CustomStringConvertible {
let id: String let id: String
@@ -45,6 +46,23 @@ class RelayPool {
var seen: Set<String> = Set() var seen: Set<String> = Set()
var counts: [String: UInt64] = [:] var counts: [String: UInt64] = [:]
private let network_monitor = NWPathMonitor()
private let network_monitor_queue = DispatchQueue(label: "io.damus.network_monitor")
private var last_network_status: NWPath.Status = .unsatisfied
init() {
network_monitor.pathUpdateHandler = { [weak self] path in
if (path.status == .satisfied || path.status == .requiresConnection) && self?.last_network_status != path.status {
DispatchQueue.main.async {
self?.connect_to_disconnected()
}
}
self?.last_network_status = path.status
}
network_monitor.start(queue: network_monitor_queue)
}
var descriptors: [RelayDescriptor] { var descriptors: [RelayDescriptor] {
relays.map { $0.descriptor } relays.map { $0.descriptor }
} }
@@ -106,11 +124,11 @@ class RelayPool {
for relay in relays { for relay in relays {
let c = relay.connection let c = relay.connection
let is_connecting = c.isReconnecting || c.isConnecting let is_connecting = c.isConnecting
if is_connecting && (Date.now.timeIntervalSince1970 - c.last_connection_attempt) > 5 { if is_connecting && (Date.now.timeIntervalSince1970 - c.last_connection_attempt) > 5 {
print("stale connection detected (\(relay.descriptor.url.absoluteString)). retrying...") print("stale connection detected (\(relay.descriptor.url.absoluteString)). retrying...")
relay.connection.connect(force: true) relay.connection.reconnect()
} else if relay.is_broken || is_connecting || c.isConnected { } else if relay.is_broken || is_connecting || c.isConnected {
continue continue
} else { } else {
@@ -208,19 +226,6 @@ class RelayPool {
relays.first(where: { $0.id == id }) relays.first(where: { $0.id == id })
} }
func record_last_pong(relay_id: String, event: NostrConnectionEvent) {
if case .ws_event(let ws_event) = event {
if case .pong = ws_event {
for relay in relays {
if relay.id == relay_id {
relay.last_pong = UInt32(Date.now.timeIntervalSince1970)
return
}
}
}
}
}
func run_queue(_ relay_id: String) { func run_queue(_ relay_id: String) {
self.request_queue = request_queue.reduce(into: Array<QueuedRequest>()) { (q, req) in self.request_queue = request_queue.reduce(into: Array<QueuedRequest>()) { (q, req) in
guard req.relay == relay_id else { guard req.relay == relay_id else {
@@ -250,7 +255,6 @@ class RelayPool {
} }
func handle_event(relay_id: String, event: NostrConnectionEvent) { func handle_event(relay_id: String, event: NostrConnectionEvent) {
record_last_pong(relay_id: relay_id, event: event)
record_seen(relay_id: relay_id, event: event) record_seen(relay_id: relay_id, event: event)
// run req queue when we reconnect // run req queue when we reconnect

View File

@@ -0,0 +1,87 @@
//
// WebSocket.swift
// damus
//
// Created by Bryan Montz on 4/13/23.
//
import Combine
import Foundation
enum WebSocketEvent {
case connected
case message(URLSessionWebSocketTask.Message)
case disconnected(URLSessionWebSocketTask.CloseCode, String?)
case error(Error)
}
final class WebSocket: NSObject, URLSessionWebSocketDelegate {
private let url: URL
private let session: URLSession
private lazy var webSocketTask: URLSessionWebSocketTask = {
let task = session.webSocketTask(with: url)
task.delegate = self
return task
}()
let subject = PassthroughSubject<WebSocketEvent, Never>()
init(_ url: URL, session: URLSession = .shared) {
self.url = url
self.session = session
}
func connect() {
resume()
}
func disconnect(closeCode: URLSessionWebSocketTask.CloseCode = .normalClosure, reason: Data? = nil) {
webSocketTask.cancel(with: closeCode, reason: reason)
// reset after disconnecting to be ready for reconnecting
let task = session.webSocketTask(with: url)
task.delegate = self
webSocketTask = task
let reason_str: String?
if let reason {
reason_str = String(data: reason, encoding: .utf8)
} else {
reason_str = nil
}
subject.send(.disconnected(closeCode, reason_str))
}
func send(_ message: URLSessionWebSocketTask.Message) {
webSocketTask.send(message) { [weak self] error in
if let error {
self?.subject.send(.error(error))
}
}
}
private func resume() {
webSocketTask.receive { [weak self] result in
switch result {
case .success(let message):
self?.subject.send(.message(message))
self?.resume()
case .failure(let error):
self?.subject.send(.error(error))
}
}
webSocketTask.resume()
}
// MARK: - URLSessionWebSocketDelegate
func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol theProtocol: String?) {
subject.send(.connected)
}
func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
disconnect(closeCode: closeCode, reason: reason)
}
}

View File

@@ -24,7 +24,7 @@ struct RelayStatus: View {
if c.isConnected { if c.isConnected {
conn_image = "network" conn_image = "network"
conn_color = .green conn_color = .green
} else if c.isConnecting || c.isReconnecting { } else if c.isConnecting {
connecting = true connecting = true
} else { } else {
conn_image = "exclamationmark.circle.fill" conn_image = "exclamationmark.circle.fill"

View File

@@ -127,7 +127,7 @@ struct SaveKeysView: View {
case .error(let err): case .error(let err):
self.loading = false self.loading = false
self.error = "\(err.debugDescription)" self.error = String(describing: err)
default: default:
break break
} }