Skip to content

Commit a9387be

Browse files
committed
add test ws connections from client
1 parent b0a2b29 commit a9387be

File tree

3 files changed

+225
-58
lines changed

3 files changed

+225
-58
lines changed

AllSpark-ios/CameraViewController.swift

Lines changed: 80 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -622,29 +622,23 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
622622
private func setupWebSocketConnection() {
623623
var hostString = UserDefaults.standard.string(forKey: "serverHost") ?? "localhost:8080"
624624

625-
// Convert to WebSocket protocol
625+
// Strip any existing protocol
626626
if hostString.lowercased().hasPrefix("http://") {
627-
hostString = hostString.replacingOccurrences(of: "http://", with: "ws://")
628-
isSecureProtocol = false
627+
hostString = String(hostString.dropFirst(7))
629628
} else if hostString.lowercased().hasPrefix("https://") {
630-
hostString = hostString.replacingOccurrences(of: "https://", with: "wss://")
631-
isSecureProtocol = true
632-
} else if !hostString.lowercased().hasPrefix("ws://") && !hostString.lowercased().hasPrefix("wss://") {
633-
hostString = "ws://" + hostString
634-
isSecureProtocol = false
635-
} else if hostString.lowercased().hasPrefix("wss://") {
636-
isSecureProtocol = true
629+
hostString = String(hostString.dropFirst(8))
637630
} else if hostString.lowercased().hasPrefix("ws://") {
638-
isSecureProtocol = false
631+
hostString = String(hostString.dropFirst(5))
632+
} else if hostString.lowercased().hasPrefix("wss://") {
633+
hostString = String(hostString.dropFirst(6))
639634
}
640635

641-
guard let wsURL = URL(string: hostString) else {
636+
guard let wsURLSecure = URL(string: "wss://" + hostString) else {
642637
print("Invalid WebSocket URL: \(hostString)")
643638
return
644639
}
645640

646-
self.webSocketURL = wsURL
647-
updateSecurityStatusIcon()
641+
self.webSocketURL = wsURLSecure
648642
connectWebSocket()
649643
}
650644

@@ -662,14 +656,58 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
662656

663657
print("Connecting to WebSocket at \(wsURL)")
664658

659+
// Start receiving messages - this will detect connection errors
660+
receiveWebSocketMessage()
661+
662+
// Set a timeout - if not connected after 5 seconds, try fallback
663+
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { [weak self] in
664+
guard let self = self else { return }
665+
if !self.isConnected {
666+
print("WebSocket connection timeout, attempting fallback...")
667+
self.attemptWsFallback()
668+
}
669+
}
670+
}
671+
672+
private func attemptWsFallback() {
673+
guard let wsURL = webSocketURL else { return }
674+
675+
// Convert wss:// to ws://
676+
var wsURLString = wsURL.absoluteString
677+
wsURLString = wsURLString.replacingOccurrences(of: "wss://", with: "ws://")
678+
679+
guard let wsURLFallback = URL(string: wsURLString) else {
680+
print("Invalid WebSocket fallback URL: \(wsURLString)")
681+
return
682+
}
683+
684+
// Disconnect the current task
685+
if let task = webSocketTask {
686+
task.cancel(with: .goingAway, reason: nil)
687+
}
688+
689+
let verifyCertificate = UserDefaults.standard.bool(forKey: "verifyCertificate")
690+
let config = URLSessionConfiguration.default
691+
let delegate = CertificateVerificationDelegate(verifyCertificate: verifyCertificate)
692+
let urlSession = URLSession(configuration: config, delegate: delegate, delegateQueue: nil)
693+
let task = urlSession.webSocketTask(with: wsURLFallback)
694+
695+
self.webSocketTask = task
696+
self.webSocketURL = wsURLFallback
697+
isSecureProtocol = false
698+
task.resume()
699+
700+
print("Connecting to WebSocket fallback at \(wsURLFallback)")
701+
665702
// Start receiving messages
666703
receiveWebSocketMessage()
667704

668-
// Mark as connected (real connection state would be confirmed by server)
705+
// Mark as connected
669706
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
670707
self?.isConnected = true
671708
self?.updateConnectionStatusIcon()
672-
print("WebSocket connected")
709+
self?.updateSecurityStatusIcon()
710+
print("WebSocket fallback connected")
673711
}
674712
}
675713

@@ -728,6 +766,7 @@ extension CameraViewController {
728766

729767
// Send video metadata first
730768
let metadata: [String: Any] = [
769+
"type": "upload",
731770
"filename": filename,
732771
"filesize": videoData.count,
733772
"mimetype": mimetype
@@ -773,6 +812,22 @@ extension CameraViewController {
773812
DispatchQueue.main.async {
774813
switch result {
775814
case .success(let message):
815+
// Mark as connected on first successful message
816+
if self?.isConnected == false {
817+
self?.isConnected = true
818+
self?.updateConnectionStatusIcon()
819+
820+
// Set secure protocol flag based on actual connection URL
821+
if let webSocketURL = self?.webSocketURL?.absoluteString.lowercased(), webSocketURL.hasPrefix("wss://") {
822+
self?.isSecureProtocol = true
823+
} else {
824+
self?.isSecureProtocol = false
825+
}
826+
self?.updateSecurityStatusIcon()
827+
828+
print("WebSocket connected")
829+
}
830+
776831
switch message {
777832
case .string(let text):
778833
print("Server response: \(text)")
@@ -809,6 +864,15 @@ extension CameraViewController {
809864
}
810865
case .failure(let error):
811866
print("WebSocket receive error: \(error)")
867+
868+
// Check if this is a TLS/connection error and we haven't connected yet
869+
if self?.isConnected == false {
870+
let errorString = error.localizedDescription.lowercased()
871+
if errorString.contains("tls") || errorString.contains("certificate") || errorString.contains("refused") {
872+
print("WSS connection failed with TLS error, attempting WS fallback...")
873+
self?.attemptWsFallback()
874+
}
875+
}
812876
}
813877
}
814878
}

AllSpark-ios/SettingsView.swift

Lines changed: 122 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import SwiftUI
2-
import SwiftyPing
32

43
struct SettingsView: View {
54
@AppStorage("serverHost") private var serverHost: String = "localhost:8080"
@@ -45,35 +44,16 @@ struct SettingsView: View {
4544
.padding()
4645

4746
Button(action: {
48-
displayText = "pinging \(serverHost)..."
49-
// Ping once
50-
let once = try? SwiftyPing(host: serverHost, configuration: PingConfiguration(interval: 0.5, with: 5), queue: DispatchQueue.global())
51-
once?.observer = { (response) in
52-
let duration = response.duration
53-
let byteCount = response.byteCount
54-
// let identifier = response.identifier
55-
// let sequenceNumber = response.sequenceNumber
56-
// let trueSequenceNumber = response.trueSequenceNumber
57-
let error = response.error?.localizedDescription
58-
59-
displayText += "\nduration: \(String(format: "%.0f", duration * 1000))ms"
60-
if (byteCount != nil){
61-
displayText += "\nresponse bytes: \(byteCount ?? 0)"
62-
}
63-
displayText += "\nerror: \(error ?? "None")"
64-
}
65-
once?.targetCount = 1
66-
try? once?.startPinging()
67-
47+
testHTTPConnection()
6848
}) {
69-
Text("Ping Once")
49+
Text("Test HTTP Connection")
7050
}
7151
.padding()
7252

7353
Button(action: {
74-
testHTTPConnection()
54+
testWebSocketConnection()
7555
}) {
76-
Text("Test HTTP Connection")
56+
Text("Test WS Connection")
7757
}
7858
.padding()
7959

@@ -88,21 +68,40 @@ struct SettingsView: View {
8868
}
8969

9070
private func testHTTPConnection() {
91-
displayText = "Testing HTTP connection to \(serverHost)..."
71+
displayText = "Testing connection to \(serverHost)..."
9272

9373
var hostString = serverHost
94-
// Ensure it has http:// prefix
95-
if !hostString.lowercased().hasPrefix("http://") && !hostString.lowercased().hasPrefix("https://") {
96-
hostString = "http://" + hostString
74+
// Strip any existing protocol
75+
if hostString.lowercased().hasPrefix("http://") {
76+
hostString = String(hostString.dropFirst(7))
77+
} else if hostString.lowercased().hasPrefix("https://") {
78+
hostString = String(hostString.dropFirst(8))
79+
}
80+
81+
// Try HTTPS first
82+
testConnectionAttempt(host: hostString, useSecure: true) { success in
83+
if success {
84+
return
85+
}
86+
// If HTTPS failed, try HTTP
87+
self.testConnectionAttempt(host: hostString, useSecure: false) { _ in }
9788
}
89+
}
90+
91+
private func testConnectionAttempt(host: String, useSecure: Bool, completion: @escaping (Bool) -> Void) {
92+
let scheme = useSecure ? "https" : "http"
93+
let hostString = scheme + "://" + host
9894

9995
guard let url = URL(string: hostString + "/api/health") else {
10096
displayText = "Invalid URL: \(hostString)"
97+
completion(false)
10198
return
10299
}
103100

104101
let config = URLSessionConfiguration.default
105102
config.waitsForConnectivity = true
103+
config.timeoutIntervalForRequest = 5.0
104+
config.timeoutIntervalForResource = 10.0
106105

107106
if !verifyCertificate {
108107
config.urlCredentialStorage = nil
@@ -114,7 +113,9 @@ struct SettingsView: View {
114113
let task = session.dataTask(with: url) { data, response, error in
115114
DispatchQueue.main.async {
116115
if let error = error {
117-
displayText = "HTTP Connection Failed\nError: \(error.localizedDescription)"
116+
let nextScheme = useSecure ? "HTTP" : "failed"
117+
self.displayText = "Testing \(scheme.uppercased()) failed. Trying \(nextScheme)...\nError: \(error.localizedDescription)"
118+
completion(false)
118119
return
119120
}
120121

@@ -126,22 +127,107 @@ struct SettingsView: View {
126127
let timestamp = json["timestamp"] as? String ?? "unknown"
127128
let uptime = json["uptime"] as? Double ?? 0
128129

129-
displayText = "HTTP Connection Successful\nStatus: \(status)\nUptime: \(String(format: "%.1f", uptime))s\nTimestamp: \(timestamp)"
130+
self.displayText = "✓ Connection Successful via \(scheme.uppercased())\nStatus: \(status)\nUptime: \(String(format: "%.1f", uptime))s\nTimestamp: \(timestamp)"
130131
} else {
131-
displayText = "✓ Server responded (200)\nBut could not parse response"
132+
self.displayText = "✓ Server responded (200) via \(scheme.uppercased())\nBut could not parse response"
132133
}
134+
completion(true)
133135
} else {
134-
displayText = "HTTP Error\nStatus Code: \(httpResponse.statusCode)"
136+
self.displayText = "Testing \(scheme.uppercased()) failed\nStatus Code: \(httpResponse.statusCode)"
137+
completion(false)
135138
}
136139
} else {
137-
displayText = "Unexpected response type"
140+
self.displayText = "Unexpected response type"
141+
completion(false)
138142
}
139143
}
140144
}
141145
task.resume()
142146
}
143-
}
144147

145-
#Preview {
146-
SettingsView()
148+
private func testWebSocketConnection() {
149+
displayText = "Testing WebSocket connection to \(serverHost)..."
150+
151+
var hostString = serverHost
152+
// Strip any existing protocol
153+
if hostString.lowercased().hasPrefix("http://") {
154+
hostString = String(hostString.dropFirst(7))
155+
} else if hostString.lowercased().hasPrefix("https://") {
156+
hostString = String(hostString.dropFirst(8))
157+
} else if hostString.lowercased().hasPrefix("ws://") {
158+
hostString = String(hostString.dropFirst(5))
159+
} else if hostString.lowercased().hasPrefix("wss://") {
160+
hostString = String(hostString.dropFirst(6))
161+
}
162+
163+
// Try WSS first
164+
testWebSocketAttempt(host: hostString, useSecure: true) { success in
165+
if success {
166+
return
167+
}
168+
// If WSS failed, try WS
169+
self.testWebSocketAttempt(host: hostString, useSecure: false) { _ in }
170+
}
171+
}
172+
173+
private func testWebSocketAttempt(host: String, useSecure: Bool, completion: @escaping (Bool) -> Void) {
174+
let scheme = useSecure ? "wss" : "ws"
175+
let urlString = scheme + "://" + host
176+
177+
guard let url = URL(string: urlString) else {
178+
displayText = "Invalid WebSocket URL: \(urlString)"
179+
completion(false)
180+
return
181+
}
182+
183+
let config = URLSessionConfiguration.default
184+
config.timeoutIntervalForRequest = 5.0
185+
config.timeoutIntervalForResource = 10.0
186+
187+
if !verifyCertificate {
188+
config.urlCredentialStorage = nil
189+
config.requestCachePolicy = .reloadIgnoringLocalCacheData
190+
}
191+
192+
let delegate = CertificateVerificationDelegate(verifyCertificate: verifyCertificate)
193+
let session = URLSession(configuration: config, delegate: delegate, delegateQueue: nil)
194+
let task = session.webSocketTask(with: url)
195+
196+
task.resume()
197+
198+
// Try to send a test message to verify connection works
199+
var connectionVerified = false
200+
let testMessage = URLSessionWebSocketTask.Message.string("{\"type\": \"test\"}")
201+
202+
task.send(testMessage) { [weak task] error in
203+
if let error = error {
204+
DispatchQueue.main.async {
205+
let nextScheme = useSecure ? "WS" : "failed"
206+
self.displayText = "Testing \(scheme.uppercased()) failed. Trying \(nextScheme)...\nError: \(error.localizedDescription)"
207+
}
208+
completion(false)
209+
task?.cancel(with: .goingAway, reason: nil)
210+
} else {
211+
// Message sent successfully, connection is working
212+
connectionVerified = true
213+
DispatchQueue.main.async {
214+
self.displayText = "✓ WebSocket Connection Successful via \(scheme.uppercased())"
215+
}
216+
completion(true)
217+
task?.cancel(with: .goingAway, reason: nil)
218+
}
219+
}
220+
221+
// Timeout after 5 seconds if no response
222+
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
223+
if !connectionVerified {
224+
let nextScheme = useSecure ? "WS" : "failed"
225+
DispatchQueue.main.async {
226+
self.displayText = "Testing \(scheme.uppercased()) timed out. Trying \(nextScheme)..."
227+
}
228+
completion(false)
229+
task.cancel(with: .goingAway, reason: nil)
230+
}
231+
}
232+
}
147233
}

0 commit comments

Comments
 (0)