11import SwiftUI
2- import SwiftyPing
32
43struct 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 += " \n duration: \( String ( format: " %.0f " , duration * 1000 ) ) ms "
60- if ( byteCount != nil ) {
61- displayText += " \n response bytes: \( byteCount ?? 0 ) "
62- }
63- displayText += " \n error: \( 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 \n Error: \( error. localizedDescription) "
116+ let nextScheme = useSecure ? " HTTP " : " failed "
117+ self . displayText = " Testing \( scheme. uppercased ( ) ) failed. Trying \( nextScheme) ... \n Error: \( 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 \n Status: \( status) \n Uptime: \( String ( format: " %.1f " , uptime) ) s \n Timestamp: \( timestamp) "
130+ self . displayText = " ✓ Connection Successful via \( scheme . uppercased ( ) ) \n Status: \( status) \n Uptime: \( String ( format: " %.1f " , uptime) ) s \n Timestamp: \( timestamp) "
130131 } else {
131- displayText = " ✓ Server responded (200) \n But could not parse response "
132+ self . displayText = " ✓ Server responded (200) via \( scheme . uppercased ( ) ) \n But could not parse response "
132133 }
134+ completion ( true )
133135 } else {
134- displayText = " HTTP Error \n Status Code: \( httpResponse. statusCode) "
136+ self . displayText = " Testing \( scheme. uppercased ( ) ) failed \n Status 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) ... \n Error: \( 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