0

Hi guys I need some help with WebsocketSTOMP in React-Native Android app and Metro.

I can get the connection but no the STOMP handshake.

Frontend DevTool console:

Frontend DevTool console screenshot

Backend Terminal in Java SPring Boot: Backend Terminal in Java Spring Boot image nothing else no Stomp handshake. So the port is open and the connection is established.

On my Emulated Phone: On my Phone screenshot

Do someone know what is wrong or what settings are wrongly setup. On Expo it is working ok. I have no idea why not in Metro.

I created Backend:

@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") .addInterceptors(new HttpHandshakeInterceptor()) .setAllowedOriginPatterns("*"); } @Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(new WebSocketEventInterceptor()); } } 

@Controller public class MessageController { @MessageMapping("/sendMessage") // Maps to /app/sendMessage @SendTo("/topic/messages") // Broadcasts to /topic/messages public Message sendMessage(Message message) { System.out.println("Received message: " + message); return message; } } 

public class Message { private String sender; private String content; @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") private LocalDateTime timestamp; public Message() { this.timestamp = LocalDateTime.now(); } public Message(String sender, String content) { this(); this.sender = sender; this.content = content; } public String getSender() { return sender; } public void setSender(String sender) { this.sender = sender; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public LocalDateTime getTimestamp() { return timestamp; } public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; } @Override public String toString() { return "Message{sender='" + sender + "', content='" + content + "', timestamp=" + timestamp + "}"; } } 

public class HttpHandshakeInterceptor implements HandshakeInterceptor { private static final Logger logger = LoggerFactory.getLogger(HttpHandshakeInterceptor.class); @Override public boolean beforeHandshake( ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { if (request instanceof ServletServerHttpRequest servletRequest) { InetSocketAddress remoteAddr = servletRequest.getRemoteAddress(); String ip = (remoteAddr != null ? remoteAddr.getAddress().getHostAddress() : "unknown"); logger.info("WebSocket CONNECT attempt from IP={}", ip); // Store IP in session attributes so we can log it later in STOMP interceptor attributes.put("ip", ip); } return true; } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { logger.info("WebSocket handshake complete."); } } 

@Configuration public class WebSocketEventInterceptor implements ChannelInterceptor { private static final Logger logger = LoggerFactory.getLogger(WebSocketEventInterceptor.class); @Override public Message<?> preSend(Message<?> message, MessageChannel channel) { StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); if (accessor != null) { StompCommand command = accessor.getCommand(); if (StompCommand.CONNECT.equals(command)) { logger.info("STOMP CONNECT from sessionId={} user={} ip={}", accessor.getSessionId(), accessor.getUser(), accessor.getSessionAttributes() != null ? accessor.getSessionAttributes().get("ip") : "unknown"); } else if (StompCommand.SUBSCRIBE.equals(command)) { logger.info("STOMP SUBSCRIBE sessionId={} destination={}", accessor.getSessionId(), accessor.getDestination()); } else if (StompCommand.SEND.equals(command)) { logger.info("STOMP SEND sessionId={} destination={}", accessor.getSessionId(), accessor.getDestination()); } else if (StompCommand.DISCONNECT.equals(command)) { logger.info("STOMP DISCONNECT sessionId={}", accessor.getSessionId()); } } return message; } } 

And Frontend in React-Native for Android App runned via Metro on Emulated Pixel 6 on Android Studio:

import React, { useState, useEffect, useRef } from 'react'; import { View, Text, TextInput, Button, FlatList, StyleSheet, Alert, } from 'react-native'; import { Client } from '@stomp/stompjs'; import { EdgeInsets } from 'react-native-safe-area-context'; interface Message { sender: string; content: string; timestamp: string; } interface ChatScreenProps { safeAreaInsets: EdgeInsets; } const ChatScreen: React.FC<ChatScreenProps> = ({ safeAreaInsets }) => { const [client, setClient] = useState<Client | null>(null); const [messages, setMessages] = useState<Message[]>([]); const [inputText, setInputText] = useState<string>(''); const flatListRef = useRef<FlatList<Message>>(null); const isMounted = useRef<boolean>(true); const backendUrl = 'ws://10.0.2.2:8085'; useEffect(() => { if (!isMounted.current) return; console.log('Attempting to initialize WebSocket connection...'); const stompClient = new Client({ webSocketFactory: () => { console.log('Creating WebSocket connection to', `${backendUrl}/ws`); return new WebSocket(`${backendUrl}/ws`); }, reconnectDelay: 5000, heartbeatIncoming: 4000, heartbeatOutgoing: 4000, debug: (str) => console.log('STOMP Debug:', str), }); stompClient.onConnect = (frame) => { console.log('WebSocket Connected:', frame); if (isMounted.current) { setClient(stompClient); stompClient.subscribe('/topic/messages', (message) => { if (isMounted.current) { console.log('Received message:', message.body); const msg: Message = JSON.parse(message.body); setMessages((prev) => [...prev, msg]); flatListRef.current?.scrollToEnd({ animated: true }); } }); } }; stompClient.onStompError = (frame) => { console.error('STOMP Error:', frame); if (isMounted.current) { Alert.alert('Connection Error', `STOMP failed: ${frame.headers['message'] || 'Unknown error'}`); } }; stompClient.onWebSocketError = (error) => { console.error('WebSocket Error:', error); if (isMounted.current) { Alert.alert('WebSocket Error', `Failed to connect: ${error.message || 'Unknown error'}`); } }; stompClient.onWebSocketClose = (event) => { console.log('WebSocket Closed:', event); if (isMounted.current) { Alert.alert('WebSocket Closed', `Connection closed: ${event.reason || 'Unknown reason'}`); } }; console.log('Activating STOMP client...'); stompClient.activate(); const timeout = setTimeout(() => { if (isMounted.current && !stompClient.connected) { console.error('WebSocket connection timed out after 10 seconds'); Alert.alert('Connection Timeout', 'Failed to connect to WebSocket server'); } }, 10000); return () => { console.log('Cleaning up WebSocket connection...'); isMounted.current = false; clearTimeout(timeout); if (stompClient) { stompClient.deactivate(); } }; }, []); const sendMessage = () => { if (client && client.connected && inputText.trim()) { console.log('Sending message:', inputText); client.publish({ destination: '/app/sendMessage', body: JSON.stringify({ sender: 'Frontend User', content: inputText, timestamp: new Date().toISOString(), }), }); setInputText(''); } else { Alert.alert('Error', 'Not connected or empty message'); } }; const renderMessage = ({ item }: { item: Message }) => ( <View style={styles.messageItem}> <Text style={styles.sender}>{item.sender}: </Text> <Text>{item.content}</Text> <Text style={styles.timestamp}>{new Date(item.timestamp).toLocaleTimeString()}</Text> </View> ); 

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> <application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:theme="@style/AppTheme" android:supportsRtl="true" android:networkSecurityConfig="@xml/network_security_config"> <activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> 

\android\app\src\main\res\xml\network_security_config.xml

<?xml version="1.0" encoding="utf-8"?> <network-security-config> <!-- Allow cleartext traffic only for local dev server --> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">10.0.2.2</domain> </domain-config> </network-security-config> 

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.