Cannot send message after handshake in SockJsClient (React) - reactjs

I am using Spring Boot-React JS and trying to send message through WebSocket. Handshake is fine, i can subscribe with no problem. When i try to send data to a topic, does not work. onMessage did not get triggered.
WebSocketConfig
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// prefix for server to client message destination
registry.enableSimpleBroker("/ws-push-notification");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// SockJS connection endpoint
registry.addEndpoint("/ws-push-notifications").setAllowedOrigins(allowedOrigin).withSockJS();
}
WebSocketDispatcher
public void pushNotification(String pushToken, PushNotificationDto notificationDto) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(pushToken);
headerAccessor.setLeaveMutable(true);
template.convertAndSendToUser(pushToken, "/ws-push-notification/item/" + pushToken,
notificationDto, headerAccessor.getMessageHeaders());
}
I checked data and token, it is matching.
React part:
const Client = ({
props,
autoReconnect = true
}) => {
const url = "http://localhost:8080/ws-push-notifications?access_token=" + localStorage.getItem(STORAGE_KEY_AT);
const topic = "ws-push-notification/item/" + props.pushToken;
const headers = {
"Content-Type": CONTENT_TYPE_JSON,
Authorization: "Bearer " + localStorage.getItem(STORAGE_KEY_AT)
};
const onConnect = () => {
props.changeSocketConnectionStatus(true);
console.info(`Subscribed to socket ${SUDate.toISOLocalDateTime(new Date())}`);
};
const onMessage = (data) => {
console.log("This part not getting triggered!!!")
};
return (
<SockJsClient
url={url}
topics={[topic]}
headers={headers}
subscribeHeaders={headers}
onConnect={onConnect}
onMessage={onMessage}
onDisconnect={onDisconnect}
onConnectFailure={onConnectFailure}
autoReconnect={autoReconnect}
/>
);
};
export default Client;
template.convertAndSendToUser(pushToken, "/ws-push-notification/item/" + pushToken,
notificationDto, headerAccessor.getMessageHeaders());
this send the dto to:
/ws-push-notification/item/771063a4-fcea-454f-9673-dedc842290bb905557640
and this part is the same here.
const topic = "ws-push-notification/item/" + props.pushToken;
Why is not working?

Problem was not receiving the data. It was sending it.
template.convertAndSend("/ws-push-notification/item/" + pushToken, notificationDto);
solved my problem.

Related

rsocketjs channel not established with spring-boot rsocket server

I have a simple rsocket backend with one message mapping defined :
#Controller
class MessageController {
#MessageMapping("test")
public Flux<String> test(Flux<String> stringFlux) {
return stringFlux.map(String::toUpperCase);
}
}
On the frontend side I try to connect to this channel from typescript + React + rsocketjs application.
Rsocket client :
class ChatServerClient {
private readonly host: String;
private readonly port: number;
private rsocket!: ReactiveSocket<any, any>;
private constructor(host: String = "localhost", port: number = 9090) {
this.host = host;
this.port = port
}
private async createClient() {
const client = new RSocketClient(
{
setup: {
dataMimeType: 'application/json',
keepAlive: 1000000, // avoid sending during test
lifetime: 100000,
metadataMimeType: MESSAGE_RSOCKET_COMPOSITE_METADATA.string,
},
transport: new RSocketWebsocketClient({
debug: true,
url: "ws://" + this.host + ":" + this.port,
wsCreator: (url) => {
return new WebSocket(url);
}
}, BufferEncoders),
errorHandler: (e) => {
console.log(e)
}
}
);
return await client.connect();
}
public static CreateAsync = async (host: String = "localhost", port: number = 9090) => {
const client = new ChatServerClient(host, port);
client.rsocket = await client.createClient()
return client;
};
test(pushMessage : Flowable<String>) {
const metadata = encodeCompositeMetadata(
[
[MESSAGE_RSOCKET_ROUTING.string, encodeRoute("test")]
]
)
const payloads = pushMessage.map(m => {return {
data : m,
metadata: metadata
}})
return this.rsocket.requestChannel(payloads)
}
}
export { ChatServerClient };
Simple component estabilishing connection :
interface ChatWindowProps { }
const ChatWindow: FC<ChatWindowProps> = (props: ChatWindowProps) => {
const [rsocket, setRsocket] = useState<ChatServerClient | null>(null)
useEffect(() => {
async function getClient() {
const client = await ChatServerClient.CreateAsync("localhost", 9090)
setRsocket(client)
}
getClient()
}, [setRsocket])
console.log("rerender")
const just : Flowable<String> = Flowable.just("a", "b")
const recivedStrings = rsocket?.test(just)
console.log("received strings " + recivedStrings)
recivedStrings?.subscribe({
onNext: m => console.log(m),
onSubscribe: s => {console.log("received strings on subscribe"); s.request(3)}
})
return (
<div className={styles.ChatWindow} data-testid="ChatWindow">
{ rsocket ? (
<div className="chat-container">
Connected
</div>
) : (
<div>Not Connected</div>
)}
</div>
)
};
export default ChatWindow;
I expect that on component rerender, when rsocket client is connected, and I establish the channel communication I would receive "A" and "B" in console but that is not the case, it seems that the channel is not created but client is connected to the server.
Other remarks :
this is just a simplification - other endpoints which use request-response style work so I do not think this is the problem with server setup
I can even see that the returned Flowable is subscribed to but nothing happens on the frontend side.
also on the server side I cannot see any frames which could indicate that any channel connection was established.
The question is - why is the channel not created and items are not emmited to the server and what is the correct way to use channel style communication from typescript rsocketjs client?

React Native websocket object not connected on first attempt

I'm new in react-native and websocket.
const client = new W3CWebSocket(
'url/api/ws/chat/1/' + logID + '/',
);
useEffect(() => {
getID();
getPrevMsgs();
client.onopen = (e) => {
console.log('WebSocket Client Connected',);
};
client.onmessage = message => {
const dataFromServer = JSON.parse(message.data);
console.log('got reply! ', dataFromServer);
if (dataFromServer) {
setMessages(messages => [
...messages,
{msg: dataFromServer.type, sender: dataFromServer.sender},
]);
}
};
scrollHandler();
return () => {
getPrevMsgs();
scrollHandler();
client.close();
};
Now as you see in the above code the issue is that when I come to my chat screen its not connected t WebSocket but when I press ctrl+s then it connects with the websocket, I also try to add this line *const client = newW3CWebSocket('url/api/ws/chat/1/' + logID + '/'); * in useEffect but the still the same thing happens. WHats the reason for that how I can solve this problem.
Problem: How to connect websocket automatically when I come to chatscreen?

How to display image from AWS s3 using spring boot and react?

How to display images from amazon s3 bucket in react?
Images are not being displayed using amazon s3,spring boot 2.5.3,react.
I have tested the end points in postman, it works.
React: Using Axios to connect to backend and MyDropZone
Using axios to make a post request, I can confirm that images are being saved in s3.
This is the reponse after making a post request, for the first 2 user.
[
{
"userId":"565c6cbe-7833-482d-b0d4-2edcd7fc6163",
"userName":"John",
"imageUrl":"pexels-photo-3147528.jpeg"
},
{
"userId":"3c776990-38e8-4de4-b7c6-7875c0ebb20f",
"userName":"Anthony",
"imageUrl":"pexels-photo-3147528.jpeg"
},
{
"userId":"bcac9cf2-5508-4996-953e-b18afe866581",
"userName":"Peter",
"imageUrl":null
}
]
React:
import './App.css';
import axios from 'axios';
import { useState, useEffect,useCallback } from 'react';
import {useDropzone} from 'react-dropzone'
//
const UserProfiles = () => {
const [userProfiles,setUserProfiles]=useState([])
const fetchUserProfiles=() => {
axios.get('http://localhost:5000/api/v1/users').then((response) => {
console.log(response.data)
setUserProfiles(response.data)
})
}
useEffect(() => {
fetchUserProfiles();
}, [])
return userProfiles.map((profile,index) => {
return (
<div key={index}>
<MyDropZone userId={profile.userId}></MyDropZone>
<h3>{profile.userId}</h3>
{
profile.userId ? (<img src={`http://localhost:5000/api/v1/users/${profile.userId}/image/download`} /> ) : <h5>No profile Image Uploaded</h5>
}
</div>
);
})
}
function MyDropZone({userId}) {
const onDrop = useCallback(acceptedFiles => {
// Do something with the files
console.log(acceptedFiles[0])
const file=acceptedFiles[0]
//Form-data
const formData = new FormData()
formData.append('file', file)
//Make a post req
axios.post(`http://localhost:5000/api/v1/users/${userId}/image/upload`, formData, {
headers: {
'Content-Type':'multipart/form-data'
}
}).then((response) => {
console.log(response)
console.log("Uploaded")
}).catch((error) => {
console.log(error)
})
}, [])
const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop})
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
{
isDragActive ?
<p>Drop the files here ...</p> :
<p>Drag 'n' drop some files here, or click to select files</p>
}
</div>
)
}
function App() {
return (
<div className="App">
<UserProfiles ></UserProfiles>
</div>
);
}
export default App;
The image is not being loaded in the UI.
{
profile.userId ? (<img src={`http://localhost:5000/api/v1/users/${profile.userId}/image/download`} /> ) : <h5>No profile Image Uploaded</h5>
}
When I go to this http://localhost:5000/api/v1/users/565c6cbe-7833-482d-b0d4-2edcd7fc6163/image/download URL via browser.
It has a response with
ÿØÿàJFIFHHÿâICC_PROFILElcmsmntrRGB XYZ Ü)9acspAPPLöÖÓ-lcms descü^cprt\wtpthbkpt|rXYZgXYZ¤bXYZ¸rTRCÌ#gTRCÌ#b
Update Added Backend code.
controller
#GetMapping(path = "{userId}/image/download")
public byte[] downloadUserProfileImage(#PathVariable("userId") UUID userId) {
return userProfileService.downloadUserProfileImage(userId);
}
Service:
private UserProfile getUserProfileOrThrow(UUID userId) {
UserProfile userProfile = userProfileRepository.getUserProfiles()
.stream()
.filter(profile -> profile.getUserId().equals(userId)).findFirst().orElseThrow(() -> new IllegalStateException("User does not exist" + userId)
);
return userProfile;
}
public byte[] downloadUserProfileImage(UUID userId) {
UserProfile userProfile=getUserProfileOrThrow(userId);
String path = String.format("%s/%s",
BucketName.PROFILE_IMAGE.getBucketName(),
userProfile.getUserId());
return userProfile.getImageUrl()
.map(key -> fileStore.download(path, key))
.orElse(new byte[0]);
}
FileStore:
#Service
public class FileStore {
private final AmazonS3 s3;
#Autowired
public FileStore(AmazonS3 s3) {
this.s3 = s3;
}
public void save(String path,
String fileName,
Optional<Map<String, String>> optionalMetadata,
InputStream inputStream) {
ObjectMetadata metadata = new ObjectMetadata();
optionalMetadata.ifPresent(map -> {
if (!map.isEmpty()) {
map.forEach(metadata::addUserMetadata);
}
});
try {
s3.putObject(path, fileName, inputStream, metadata);
} catch (AmazonServiceException e) {
throw new IllegalStateException("Failed to store file to s3", e);
}
}
public byte[] download(String path, String key) {
try {
S3Object object = s3.getObject(path, key);
return IOUtils.toByteArray(object.getObjectContent());
} catch (AmazonServiceException | IOException e) {
throw new IllegalStateException("Failed to download file to s3", e);
}
}
}
Amazon s3 config:
#Configuration
public class AmazonConfig {
#Bean
public AmazonS3 s3(){
AWSCredentials awsCredentials=new BasicAWSCredentials("my-credentials","my-secret-key");
return AmazonS3ClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.withRegion(Regions.AP_SOUTH_1)
.build();
}
}
UserProfile:
public class UserProfile {
private final UUID userId;
private final String userName;
private String imageUrl;
//This might be null
public Optional<String> getImageUrl() {
return Optional.ofNullable(imageUrl);
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
//getters & setters
}
When I was facing the same issue, I had to return the object.getObjectContent() image in a Base64 format.
Afterwards, when displaying the data in the front-end, you can try and to this:
<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA
AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />
You can try this Base64 decoder to see if your Base64 data is correct or not.
That implies that you make the GET call beforehand, save the result and then display the base64 string in the img src
UPDATE:
Depending on your approach, in order to download the images for each user profile, inside the .map, you can have a function that downloads the picture for each profile.
const fetchUserProfileImage = async (userProfileId) => {
return axios.get(`http://localhost:5000/api/v1/users/${profile.userId}/image/download`)
}
return userProfiles.map(async (profile,index) => {
const userProfileImageBase64 = await fetchUserProfileImage(profile.userId)
return (
<div key={index}>
<MyDropZone userId={profile.userId}></MyDropZone>
<h3>{profile.userId}</h3>
{
profile.userId ? (<img src={`data:image/png;base64, ${userProfileImageBase64}`}/> ) : <h5>No profile Image Uploaded</h5>
}
</div>
);
})
Or if you don't like to wait inside the .map, you can try to download all of the images before rendering the body and mapping them to the already existing user in the userProfiles state.
Or, the best approach imo is to add another profileImageSrc field to the User class and save it there whenever you upload an image in the backend. Then you don't have to make extra calls and just consume the data received when fetching the userProfiles
The simplest trick is to transform your byte arrays into an actual image
#GetMapping(path ="/download/{fileName}")
public ResponseEntity<ByteArrayResource> downloadFile(#PathVariable String fileName) {
byte[] data = service.downloadImage(fileName);
ByteArrayResource resource = new ByteArrayResource(data);
return ResponseEntity
.ok()
.contentLength(data.length)
.header("Content-type", "image/jpeg")
.header("Content-disposition", "attachment; filename=\"" + fileName + "\"")
.body(resource);
}
This will return an actual image whether the image was jpg or png it will not matter. You can actually view the image in postman and your frontend can pick it up directly.

WebSocket problem when trying to connect from client

I'm having trouble trying to connect with websocket server.
I need to subscribe to get the current price of EUR/USD but I keep reciving this object : {topic: "keepalive"}.
It should return the price and the latest timestamp;
This is my code :
import { w3cwebsocket as W3CWebSocket } from "websocket";
export default function LogIn() {
const client = new W3CWebSocket('ws://stream.tradingeconomics.com/?client=guest:guest');
useEffect(()=>{
client.onopen = function(e) {
console.log("Connection established!");
client.send(JSON.stringify({topic: "subscribe", to: "EURUSD:CUR"}))
client.onmessage = function(e) { console.log(e); };
};
},[])
I would appreciate the help !
The problem is that you are sending a Stringified Object instead of a String:
You are sending
client.send(JSON.stringify({topic: "subscribe", to: "EURUSD:CUR"}))
You need to send
client.send('{"topic": "subscribe", "to": "EURUSD:CUR"}')

SpringBoot + WebFlux + Reactjs Server Sent Events onmessage not fire up

I see EventStream on the network, but my clientSource.onmessage does not fireup on the client. I didn't find many examples in which they would use WebFlux Functional Endpoints for SSE. What am I doing wrong?
Router where /sseget is my SSE endpoint:
#Component
class PersonRouter {
#Bean
fun personRoutes(personRouteHandler: PersonRouteHandler) = coRouter {
"/person".nest {
GET("/", personRouteHandler::getTest)
// GET("findById", accept(MediaType.APPLICATION_JSON), personRouteHandler::)
GET("paramstest", accept(MediaType.APPLICATION_JSON), personRouteHandler::paramsTest)
POST("posttest", accept(MediaType.APPLICATION_JSON), personRouteHandler::postTest)
}
"/sse".nest {
GET("/sseget", personRouteHandler::sseGet)
}
}
}
Handler:
suspend fun sseGet(request: ServerRequest): ServerResponse {
val result = Flux.interval(Duration.ofSeconds(3))
.map{
ServerSentEvent.builder<String>()
.id(it.toString())
.event("periodic-event")
.data("SSE - " + LocalTime.now())
.comment("nejaky comment")
.retry(Duration.ofSeconds(10))
.build()
}
return ServerResponse
.ok()
.body(BodyInserters.fromServerSentEvents(result)).awaitSingle()
}
ReactJs client:
const ServerSideEventExample: React.FC<IProps> = (props) => {
const [listening, setListening] = useState(false);
useEffect(() => {
let eventSource: EventSource | undefined = undefined;
debugger;
if (!listening) {
debugger;
eventSource = new EventSource("http://localhost:8085/sse/sseget");
eventSource.onopen = (event) => {
debugger;
console.log(event);
};
eventSource.onmessage = (event) => {
debugger;
console.log(event);
};
eventSource.onerror = (err) => {
debugger;
console.error("EventSource failed:", err);
};
setListening(true);
}
return () => {
if (eventSource) {
eventSource.close();
console.log("event closed");
}
};
}, []);
return <div>a</div>;
};
Just put produce(MediaType.TEXT_EVENT_STREAM_VALUE) Your react application can't recognize your event.
Add the content-type to your server response as show below:
return ServerResponse
.ok()
.contentType(MediaType.TEXT_EVENT_STREAM)
.body(BodyInserters.fromServerSentEvents(result)).awaitSingle()
And remember to change the event parameter to "message" as follows
.id("Your ID")
.event("message") //<=== HERE
.data({your event here})
.comment("any comment")
.build();

Resources