stefane9
stefane9

Reputation: 11

Swift iOS Client Successfully Connects to Java Backend via WebSocket but Fails to Receive Messages

I’m working on an iOS client using Starscream to communicate with a Java backend over WebSocket. The connection is established successfully, and I can send messages. However, the Swift client does not receive any messages – nothing arrives despite all debugging efforts.

Interestingly, when I use a JavaScript client, I can receive the messages without issues (code provided below). This confirms that the backend is sending messages correctly. I’d appreciate any insights into what might be causing this issue.

iOS WebSocket Manager (Starscream):

import Foundation
import Starscream
import SwiftUI

class WebSocketManager: WebSocketDelegate {
    static let shared = WebSocketManager()
    
    @AppStorage("userId") private var userId: String = ""
    var socket: WebSocket!
    var isConnected: Bool = false
    var subscribedTopics: Set<String> = []
    var onMessageReceived: ((ChatMessage) -> Void)?

    private init() {
        connectWebSocket()
    }

    func connectWebSocket() {
        var request = URLRequest(url: URL(string: "ws://192.168.0.80:9001/ws")!)
        request.timeoutInterval = 5

        if let jwtToken = TokenManager.shared.getAccessToken() {
            request.setValue("Bearer \(jwtToken)", forHTTPHeaderField: "Authorization")
        }

        socket = WebSocket(request: request)
        socket.delegate = self
        socket.connect()

        let friends = CoreDataFriendManager.shared.loadAllFriends()
        friends.forEach { friend in
            if let topicName = friend.topicName {
                subscribeToMessages(with: topicName)
            }
        }
        listenToWebSocketMessagesGlobally()
    }

    func sendMessage(_ message: SendMessageRequest) {
        if !isConnected { return }
        
        if let token = TokenManager.shared.getAccessToken() {
            let stompMessage = """
            SEND
            destination:/app/send-message
            content-type:application/json
            Authorization: Bearer \(token)

            {
                "recipientId": "\(message.recipientId.uuidString)",
                "encryptedMessage": "\(message.encryptedMessage)"
            }
            \0
            """
            socket.write(string: stompMessage)
        }
    }

    func subscribeToMessages(with topicName: String) {
        if subscribedTopics.contains(topicName) { return }
        
        let subscribeMessage = """
        SUBSCRIBE
        id:sub-\(topicName)
        destination:/topic/messages/\(topicName)

        \0
        """
        socket.write(string: subscribeMessage)
        subscribedTopics.insert(topicName)
    }

      // WebSocket-Event-Handling
    func didReceive(event: WebSocketEvent, client: WebSocketClient) {
        switch event {
        case .connected(let headers):
            print("WebSocket verbunden mit Headern: \(headers)")
            isConnected = true
            for topicName in subscribedTopics {
                subscribeToMessages(with: topicName)
            }
        case .disconnected(let reason, let code):
            print("WebSocket getrennt: \(reason) mit Code \(code). Versuche erneut zu verbinden.")
            isConnected = false
            reconnectWebSocket()

        case .text(let string):
            print("Empfangene WebSocket Nachricht: \(string)") 

            if string.contains("MESSAGE") {
                let components = string.split(separator: "\n").map(String.init)
                if let jsonData = components.last?.data(using: .utf8) {
                    do {
                        let serverMessage = try JSONDecoder().decode(ServerMessage.self, from: jsonData)
                        let chatMessage = convertServerMessageToChatMessage(serverMessage)
                        onMessageReceived?(chatMessage)
                        print("Nachricht erfolgreich dekodiert und verarbeitet: \(chatMessage)")
                    } catch {
                        print("Fehler beim Dekodieren der Nachricht: \(error)")
                    }
                }
            } else {
                print("Keine gültige STOMP-Nachricht empfangen oder Nachricht nicht im erwarteten Format.")
            }

        case .binary(let data):
            print("Binary-Daten empfangen: \(data)")

        case .pong(let data):
            print("Pong empfangen: \(data?.count ?? 0) bytes")

        case .ping(let data):
            print("Ping empfangen: \(data?.count ?? 0) bytes")

        case .error(let error):
            print("WebSocket-Fehler: \(error?.localizedDescription ?? "Unbekannter Fehler")")
            isConnected = false

        case .viabilityChanged(let viable):
            print("Verbindungsfähigkeit geändert: \(viable)")

        case .reconnectSuggested(let suggested):
            print("Reconnect vorgeschlagen: \(suggested)")
            if suggested {
                reconnectWebSocket()
            }

        case .cancelled:
            print("WebSocket abgebrochen.")
            isConnected = false

        default:
            break
        }
    }
    
    private func convertServerMessageToChatMessage(_ serverMessage: ServerMessage) -> ChatMessage {
        return ChatMessage(id: serverMessage.messageId, text: serverMessage.encryptedMessage, isSentByCurrentUser: false, timestamp: serverMessage.timestamp, senderName: "TestUser")
    }
}

JavaScript WebSocket Client (Working Correctly):

Here’s the JavaScript code that successfully receives messages:


const Stomp = require('stompjs');
const WebSocket = require('ws'); // ws-Paket verwenden


const socket = new WebSocket('ws://192.168.0.80:9001/ws');
const stompClient = Stomp.over(socket);

socket.on('open', () => console.log("WebSocket-Verbindung geöffnet"));
socket.on('close', () => console.log("WebSocket-Verbindung geschlossen"));
socket.on('error', (error) => console.error("WebSocket-Fehler:", error));

const headers = {
    Authorization: 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIwNjQ3OGQxZC1hZTMxLTQ0MjktYWY4ZS02ZGMzMDVlMTYwOTciLCJpYXQiOjE3Mjk5NjUxNzMsImV4cCI6MTcyOTk2ODc3M30.xPdkLLv4LArvn00jAChKjsSE5RPt5zipgYGE0CNxe5BYsAvEomhIQohodNPTJat2pMVTgn2NkauZ8K3iiOt7DA'
};

stompClient.connect(headers, function (frame) {
    console.log('STOMP-Verbindung hergestellt:', frame);

    stompClient.subscribe('/topic/messages/1ed08549-ade6-4baa-b741-5634b75379f4', function (message) {
        console.log("Nachricht empfangen:", message.body);

        console.log("Header der Nachricht:", message.headers);
        console.log("Inhalt der Nachricht:", message.body);

        try {
            const parsedMessage = JSON.parse(message.body);
            console.log("JSON-Dekodierte Nachricht:", parsedMessage);
        } catch (error) {
            console.error("Fehler beim Parsen der Nachricht:", error);
        }
    });

    const messageBody = JSON.stringify({
        recipientId: "dd703e8e-8df1-4d27-9676-672d8c95d326",
        encryptedMessage: "Hallo von User A!"
    });

    console.log("Sende Nachricht:", messageBody);
    stompClient.send("/app/send-message", headers, messageBody);
}, 
function (error) {
    console.error("Verbindungsfehler mit STOMP:", error);
});

stompClient.debug = function (str) {
    console.log("STOMP-DEBUG:", str);
};

Received Message in JavaScript Client:

Here’s what the message looks like in the JS client, and it’s received successfully:

STOMP-DEBUG: <<< MESSAGE
destination:/topic/messages/1ed08549-ade6-4baa-b741-5634b75379f4
content-type:application/json
subscription:sub-0
message-id:1bbe8fc0-2a8e-3acf-5029-fd03ce44360f-25
content-length:832

{"messageId":"ee393914-db5a-4d29-b1ab-399c2a0dd726","encryptedMessage":"ddpdUSkCLaQupWQRR290LTeoX5NREazoLMcYM2JXmFQbiHpmv72+fMeI1FtDtg2DnRfhkpgzRi4048KklRYyUqpIvQUbH/U4Ow4MA06sVO6Xb4pKbQeyzme9Hhw8SHjFawQ9uzMvfR7Mt31aqL0T4ksJ0sefnS2EM84715l97WlYuOkilbnGkLkIM/OfUbggKRMvl1EDAnmxI3Ytkz7/donI2tzJol9TbQE0sLvZEQUUlZW8fF4PF+QyyNC1Zk21Yd5LbmwJg4uptmyZIi1NXGfzDKi/d1+WaatKxicPENgs1LV7XtM8pKfRpwvrkTeXG40y7x864Awh4g4qBbemOS9/2TO4xLRPFyx2DUx+wNW3KTgk9qu64eC+I10TsstJZa1WzOEDOR1ta+mnImHZcYJRdDOxvgAPWD7LPTurAScigepm8knh4e73YEPkEffPUbS1ORVotrAxO9ve31YsTC2D2OtAexPotDoJiEM7JdFHGrfRGF4Gje6zL8W1nMBN07rkIve7BN8lm2YTYkkSe0Vmq2Gso9flkimgT+g/TnHawyi/J5tsPexKgrFCkXgTANE/ZByFQRcfn/cTH9oJcV/WbYJl2ATi0TF7onqsacQvHVhCT1rPdScPU1XLdXY0einVA6VCGGZWUJBy0LydXc0sXlpn2XH/DZu6aSDdlRc=","timestamp":"2024-10-26T19:24:43.481808Z","status":"SENT","deleted":false}
Nachricht empfangen: {"messageId":"ee393914-db5a-4d29-b1ab-399c2a0dd726","encryptedMessage":"ddpdUSkCLaQupWQRR290LTeoX5NREazoLMcYM2JXmFQbiHpmv72+fMeI1FtDtg2DnRfhkpgzRi4048KklRYyUqpIvQUbH/U4Ow4MA06sVO6Xb4pKbQeyzme9Hhw8SHjFawQ9uzMvfR7Mt31aqL0T4ksJ0sefnS2EM84715l97WlYuOkilbnGkLkIM/OfUbggKRMvl1EDAnmxI3Ytkz7/donI2tzJol9TbQE0sLvZEQUUlZW8fF4PF+QyyNC1Zk21Yd5LbmwJg4uptmyZIi1NXGfzDKi/d1+WaatKxicPENgs1LV7XtM8pKfRpwvrkTeXG40y7x864Awh4g4qBbemOS9/2TO4xLRPFyx2DUx+wNW3KTgk9qu64eC+I10TsstJZa1WzOEDOR1ta+mnImHZcYJRdDOxvgAPWD7LPTurAScigepm8knh4e73YEPkEffPUbS1ORVotrAxO9ve31YsTC2D2OtAexPotDoJiEM7JdFHGrfRGF4Gje6zL8W1nMBN07rkIve7BN8lm2YTYkkSe0Vmq2Gso9flkimgT+g/TnHawyi/J5tsPexKgrFCkXgTANE/ZByFQRcfn/cTH9oJcV/WbYJl2ATi0TF7onqsacQvHVhCT1rPdScPU1XLdXY0einVA6VCGGZWUJBy0LydXc0sXlpn2XH/DZu6aSDdlRc=","timestamp":"2024-10-26T19:24:43.481808Z","status":"SENT","deleted":false}
Header der Nachricht: {
  'content-length': '832',
  'message-id': '1bbe8fc0-2a8e-3acf-5029-fd03ce44360f-25',
  subscription: 'sub-0',
  'content-type': 'application/json',
  destination: '/topic/messages/1ed08549-ade6-4baa-b741-5634b75379f4'
}
Inhalt der Nachricht: {"messageId":"ee393914-db5a-4d29-b1ab-399c2a0dd726","encryptedMessage":"ddpdUSkCLaQupWQRR290LTeoX5NREazoLMcYM2JXmFQbiHpmv72+fMeI1FtDtg2DnRfhkpgzRi4048KklRYyUqpIvQUbH/U4Ow4MA06sVO6Xb4pKbQeyzme9Hhw8SHjFawQ9uzMvfR7Mt31aqL0T4ksJ0sefnS2EM84715l97WlYuOkilbnGkLkIM/OfUbggKRMvl1EDAnmxI3Ytkz7/donI2tzJol9TbQE0sLvZEQUUlZW8fF4PF+QyyNC1Zk21Yd5LbmwJg4uptmyZIi1NXGfzDKi/d1+WaatKxicPENgs1LV7XtM8pKfRpwvrkTeXG40y7x864Awh4g4qBbemOS9/2TO4xLRPFyx2DUx+wNW3KTgk9qu64eC+I10TsstJZa1WzOEDOR1ta+mnImHZcYJRdDOxvgAPWD7LPTurAScigepm8knh4e73YEPkEffPUbS1ORVotrAxO9ve31YsTC2D2OtAexPotDoJiEM7JdFHGrfRGF4Gje6zL8W1nMBN07rkIve7BN8lm2YTYkkSe0Vmq2Gso9flkimgT+g/TnHawyi/J5tsPexKgrFCkXgTANE/ZByFQRcfn/cTH9oJcV/WbYJl2ATi0TF7onqsacQvHVhCT1rPdScPU1XLdXY0einVA6VCGGZWUJBy0LydXc0sXlpn2XH/DZu6aSDdlRc=","timestamp":"2024-10-26T19:24:43.481808Z","status":"SENT","deleted":false}
JSON-Dekodierte Nachricht: {
  messageId: 'ee393914-db5a-4d29-b1ab-399c2a0dd726',
  encryptedMessage: 'ddpdUSkCLaQupWQRR290LTeoX5NREazoLMcYM2JXmFQbiHpmv72+fMeI1FtDtg2DnRfhkpgzRi4048KklRYyUqpIvQUbH/U4Ow4MA06sVO6Xb4pKbQeyzme9Hhw8SHjFawQ9uzMvfR7Mt31aqL0T4ksJ0sefnS2EM84715l97WlYuOkilbnGkLkIM/OfUbggKRMvl1EDAnmxI3Ytkz7/donI2tzJol9TbQE0sLvZEQUUlZW8fF4PF+QyyNC1Zk21Yd5LbmwJg4uptmyZIi1NXGfzDKi/d1+WaatKxicPENgs1LV7XtM8pKfRpwvrkTeXG40y7x864Awh4g4qBbemOS9/2TO4xLRPFyx2DUx+wNW3KTgk9qu64eC+I10TsstJZa1WzOEDOR1ta+mnImHZcYJRdDOxvgAPWD7LPTurAScigepm8knh4e73YEPkEffPUbS1ORVotrAxO9ve31YsTC2D2OtAexPotDoJiEM7JdFHGrfRGF4Gje6zL8W1nMBN07rkIve7BN8lm2YTYkkSe0Vmq2Gso9flkimgT+g/TnHawyi/J5tsPexKgrFCkXgTANE/ZByFQRcfn/cTH9oJcV/WbYJl2ATi0TF7onqsacQvHVhCT1rPdScPU1XLdXY0einVA6VCGGZWUJBy0LydXc0sXlpn2XH/DZu6aSDdlRc=',
  timestamp: '2024-10-26T19:24:43.481808Z',
  status: 'SENT',
  deleted: false
}

Backend WebSocket Configuration (Java, Spring Boot):

Here’s the Java backend setup, which handles WebSocket connections and the /ws endpoint. The configuration includes the message broker setup and the controller that processes incoming messages.

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic"); 
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws").setAllowedOrigins("*");
    }
}

@Controller
public class WebSocketController {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    @MessageMapping("/send-message")
    public void sendMessage(SendMessageRequest request, SimpMessageHeaderAccessor headerAccessor) {
        String topic = "/topic/messages/" + friendService.getTopicName(request.getSenderId(), request.getRecipientId());
        messagingTemplate.convertAndSend(topic, request);
    }
}

Why is my Swift client not receiving messages, even though:

1.  It successfully connects to the backend (and includes the token in the header).
2.  Messages are successfully sent from the Swift client.
3.  The JavaScript client receives messages without any issues.

Are there any known issues or configuration requirements in Starscream or STOMP for iOS that might lead to this behavior? Any help would be greatly appreciated!

Upvotes: 0

Views: 40

Answers (0)

Related Questions