@@ -35,6 +35,8 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
3535 private var webSocketURL : URL ?
3636 private var isConnected = false
3737 private var isSecureProtocol = false
38+ private var isAttemptingConnection = false
39+ private var connectionAttemptTimer : Timer ?
3840
3941 // Display layer
4042 private var imageView : UIImageView !
@@ -68,8 +70,8 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
6870 self ? . captureSession. startRunning ( )
6971 }
7072
71- // Reconnect WebSocket if needed
72- if !isConnected || webSocketTask == nil {
73+ // Reconnect WebSocket if not connected
74+ if !isConnected && !isAttemptingConnection {
7375 setupWebSocketConnection ( )
7476 }
7577 }
@@ -216,11 +218,19 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
216218 private func updateConnectionStatusIcon( ) {
217219 DispatchQueue . main. async { [ weak self] in
218220 if self ? . isConnected ?? false {
221+ // Connected - green
219222 if let image = UIImage ( systemName: " wifi " ) {
220223 self ? . connectionStatusIcon. setImage ( image, for: . normal)
221224 self ? . connectionStatusIcon. tintColor = . systemGreen
222225 }
226+ } else if self ? . isAttemptingConnection ?? false {
227+ // Attempting connection - amber/orange
228+ if let image = UIImage ( systemName: " wifi " ) {
229+ self ? . connectionStatusIcon. setImage ( image, for: . normal)
230+ self ? . connectionStatusIcon. tintColor = . systemOrange
231+ }
223232 } else {
233+ // Disconnected - red
224234 if let image = UIImage ( systemName: " wifi.slash " ) {
225235 self ? . connectionStatusIcon. setImage ( image, for: . normal)
226236 self ? . connectionStatusIcon. tintColor = . systemRed
@@ -610,6 +620,10 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
610620
611621 private func connectWebSocket( ) {
612622 guard let wsURL = webSocketURL else { return }
623+ guard !isAttemptingConnection else { return } // Prevent multiple simultaneous attempts
624+
625+ isAttemptingConnection = true
626+ updateConnectionStatusIcon ( )
613627
614628 // Set secure protocol flag based on connection URL
615629 if wsURL. absoluteString. lowercased ( ) . hasPrefix ( " wss:// " ) {
@@ -629,21 +643,13 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
629643
630644 print ( " Connecting to WebSocket at \( wsURL) " )
631645
632- // Start receiving messages - this will detect connection errors
646+ // Start receiving messages - this will detect connection errors and trigger fallback if needed
633647 receiveWebSocketMessage ( )
634-
635- // Set a timeout - if not connected after 5 seconds, try fallback
636- DispatchQueue . main. asyncAfter ( deadline: . now( ) + 5.0 ) { [ weak self] in
637- guard let self = self else { return }
638- if !self . isConnected {
639- print ( " WebSocket connection timeout, attempting fallback... " )
640- self . attemptWsFallback ( )
641- }
642- }
643648 }
644649
645650 private func attemptWsFallback( ) {
646651 guard let wsURL = webSocketURL else { return }
652+ guard isSecureProtocol else { return } // Only fallback if we were trying WSS
647653
648654 // Convert wss:// to ws://
649655 var wsURLString = wsURL. absoluteString
@@ -654,33 +660,21 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
654660 return
655661 }
656662
657- // Disconnect the current task
663+ // Disconnect the current failed WSS task
658664 if let task = webSocketTask {
659665 task. cancel ( with: . goingAway, reason: nil )
660666 }
667+ webSocketTask = nil
668+ isAttemptingConnection = false
661669
662- let verifyCertificate = UserDefaults . standard. bool ( forKey: " verifyCertificate " )
663- let config = URLSessionConfiguration . default
664- let delegate = CertificateVerificationDelegate ( verifyCertificate: verifyCertificate)
665- let urlSession = URLSession ( configuration: config, delegate: delegate, delegateQueue: nil )
666- let task = urlSession. webSocketTask ( with: wsURLFallback)
667-
668- self . webSocketTask = task
669- self . webSocketURL = wsURLFallback
670+ // Update the URL to the fallback WS URL
671+ webSocketURL = wsURLFallback
670672 isSecureProtocol = false
671- task. resume ( )
672-
673- print ( " Connecting to WebSocket fallback at \( wsURLFallback) " )
674673
675- // Start receiving messages
676- receiveWebSocketMessage ( )
674+ print ( " WSS connection failed, attempting WS fallback at \( wsURLFallback) " )
677675
678- // Mark as connected
679- DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.5 ) { [ weak self] in
680- self ? . isConnected = true
681- self ? . updateConnectionStatusIcon ( )
682- print ( " WebSocket fallback connected " )
683- }
676+ // Now connect using WS
677+ connectWebSocket ( )
684678 }
685679
686680 private func disconnectWebSocket( ) {
@@ -689,6 +683,9 @@ class CameraViewController: UIViewController, UIDocumentPickerDelegate, UINaviga
689683 }
690684 webSocketTask = nil
691685 isConnected = false
686+ isAttemptingConnection = false
687+ connectionAttemptTimer? . invalidate ( )
688+ connectionAttemptTimer = nil
692689 updateConnectionStatusIcon ( )
693690 print ( " WebSocket disconnected " )
694691 }
@@ -780,16 +777,22 @@ extension CameraViewController {
780777 private func receiveWebSocketMessage( ) {
781778 guard let webSocketTask = webSocketTask else { return }
782779
780+ // Set a flag to track if this is the first receive attempt for this connection
781+ let isFirstReceive = !isConnected && isAttemptingConnection
782+
783783 webSocketTask. receive { [ weak self] result in
784784 DispatchQueue . main. async {
785785 switch result {
786786 case . success( let message) :
787- // Mark as connected on first successful message
788- if self ? . isConnected == false {
787+ // Mark as connected on first successful receive (connection is established)
788+ if self ? . isConnected == false && self ? . isAttemptingConnection == true {
789789 self ? . isConnected = true
790+ self ? . isAttemptingConnection = false
791+ self ? . connectionAttemptTimer? . invalidate ( )
792+ self ? . connectionAttemptTimer = nil
790793 self ? . updateConnectionStatusIcon ( )
791794
792- print ( " WebSocket connected " )
795+ print ( " WebSocket connection established " )
793796 }
794797
795798 switch message {
@@ -829,13 +832,40 @@ extension CameraViewController {
829832 case . failure( let error) :
830833 print ( " WebSocket receive error: \( error) " )
831834
832- // Check if this is a TLS/connection error and we haven't connected yet
833- if self ? . isConnected == false {
835+ // If we haven't connected yet and were using WSS, try the WS fallback
836+ if self ? . isConnected == false && self ? . isSecureProtocol == true {
834837 let errorString = error. localizedDescription. lowercased ( )
835- if errorString. contains ( " tls " ) || errorString. contains ( " certificate " ) || errorString. contains ( " refused " ) {
836- print ( " WSS connection failed with TLS error, attempting WS fallback... " )
838+ // Check for common secure connection errors
839+ if errorString. contains ( " tls " ) || errorString. contains ( " certificate " ) || errorString. contains ( " refused " ) || errorString. contains ( " connection " ) {
840+ print ( " WSS connection failed, attempting WS fallback... " )
841+ self ? . connectionAttemptTimer? . invalidate ( )
842+ self ? . connectionAttemptTimer = nil
837843 self ? . attemptWsFallback ( )
838844 }
845+ } else if self ? . isConnected == false {
846+ // WS fallback also failed
847+ print ( " WS fallback connection failed " )
848+ self ? . connectionAttemptTimer? . invalidate ( )
849+ self ? . connectionAttemptTimer = nil
850+ self ? . isAttemptingConnection = false
851+ self ? . updateConnectionStatusIcon ( )
852+ }
853+ }
854+ }
855+ }
856+
857+ // On first receive, set a short timeout to mark connection as established if no early error
858+ if isFirstReceive {
859+ connectionAttemptTimer? . invalidate ( )
860+ connectionAttemptTimer = Timer . scheduledTimer ( withTimeInterval: 0.5 , repeats: false ) { [ weak self] _ in
861+ // If we still haven't connected after 0.5s but no error occurred, mark as connected
862+ if self ? . isConnected == false && self ? . isAttemptingConnection == true {
863+ DispatchQueue . main. async {
864+ self ? . isConnected = true
865+ self ? . isAttemptingConnection = false
866+ self ? . connectionAttemptTimer = nil
867+ self ? . updateConnectionStatusIcon ( )
868+ print ( " WebSocket connection established (timeout) " )
839869 }
840870 }
841871 }
0 commit comments