Socket.io with React, server-to-client emit call not being received - reactjs

I'm using React with Socket.io and trying to make my component update in real time, so one user can create a new event and it immediately shows up for all users. I've done this before outside of React, and it seems so simple, but I can't get it to work.
Desired behavior: When a user adds a new event, the server sends the new event to the client, where the client sets the new event into the redux store.
Actual behavior: The server emits the event, but the client never receives it. In the network tab, two websocket connections have status 'pending'.
This is my code:
server:
io.on('connection', (socket) => {
socket.on('createEvent', async (event, acknowledge) => {
let err;
let result;
// add event to DB
result = await db.createEvent(event);
if(!result) err = "An error occured during event creation.";
acknowledge(err, result);
console.log('result', result);
if (result) {
socket.emit('eventCreated', result);
console.log('emitted eventCreated');
}
});
});
Client:
componentDidMount () {
this.getEventsFromDB();
//listen for new events
socket.on ('eventCreated', (event) => {
console.log('hello,', event);
this.props.dispatch(addEvent({ event }));
});
};

I found the answer - I was using const socket = io() at the start of each file on the client side where I was using websockets. So each page was getting its own separate socket, which worked just fine until I needed two different pages to have access to the same socket.
I fixed it by instantiating one socket in my main file with my router, and passing it down to each component as a prop or via Redux.

Related

Communicating a successful workbox-background-sync replay to open clients

I'm using React 17, Workbox 5, and react-scripts 4.
I created a react app with PWA template using:
npx create-react-app my-app --template cra-template-pwa
I use BackgroundSyncPlugin from workbox-background-sync for my offline requests, so when the app is online again, request will be sent automatically.
The problem is I don't know when the request is sent in my React code, so I can update some states, and display a message to the user.
How can I communicate from the service worker to my React code that the request is sent and React should update the state?
Thanks in advance.
You can accomplish this by using a custom onSync callback when you configure BackgroundSyncPlugin. This code is then executed instead of Workbox's built-in replayRequests() logic whenever the criteria to retry the requests are met.
You can include whatever logic you'd like in this callback; this.shiftRequest() and this.unshiftRequest(entry) can be used to remove queued requests in order to retry them, and then re-add them if the retry fails. Here's an adaption of the default replayRequests() that will use postMessage() to communicate to all controlled window clients when a retry succeeds.
async function postSuccessMessage(response) {
const clients = await self.clients.matchAll();
for (const client of clients) {
// Customize this message format as you see fit.
client.postMessage({
type: 'REPLAY_SUCCESS',
url: response.url,
});
}
}
async function customReplay() {
let entry;
while ((entry = await this.shiftRequest())) {
try {
const response = await fetch(entry.request.clone());
// Optional: check response.ok and throw if it's false if you
// want to treat HTTP 4xx and 5xx responses as retriable errors.
postSuccessMessage(response);
} catch (error) {
await this.unshiftRequest(entry);
// Throwing an error tells the Background Sync API
// that a retry is needed.
throw new Error('Replaying failed.');
}
}
}
const bgSync = new BackgroundSyncPlugin('api-queue', {
onSync: customReplay,
});
// Now add bgSync to a Strategy that's associated with
// a route you want to retry:
registerRoute(
({url}) => url.pathname === '/api_endpoint',
new NetworkOnly({plugins: [bgSync]}),
'POST'
);
Within your client page, you can use navigator.seviceWorker.addEventListener('message', ...) to listen for incoming messages from the service worker and take appropriate action.

How to use Meteor.settings in a React Component

I have a React component which is making an API call on init on the client side. I don't want to hard-code my API key (god forbid in the repo), and it's not much better to put it in Meteor.settings.public since that can just be looked up in the console. I want to keep it in Meteor.settings, but then it's invisible to the client. I've tried using a method, but although it appears to work on the server the method call returns undefined on the client.
On the server:
Meteor.methods({
getFileStackAPIKey: function () {
if (Meteor.settings.fileStackAPIKey) {
console.log(Meteor.settings.fileStackAPIKey) // returns: [fileStackAPIKey] correctly
return Meteor.settings.fileStackAPIKey
}
else {
return {message: "Configure Meteor.settings.fileStackAPIKey to connect to FileStack."}
}
}});
On the client:
console.log(Meteor.call('getFileStackAPIKey')); // returns: undefined
I tried to use ReactiveVar and again it set it on the server but was inaccessible on the client. I have the feeling that I'm missing something obvious. Specifically, what I'm trying to make work is FileStack. Their example code shows the API key hard-coded inline. As does the official FileStack React package. This just doesn't seem like a good idea.
It has to do with callbacks. The method result will be in the callback, so what I needed to do on the client was more like this:
Meteor.call('getFileStackAPIKey', (err, res) => {
console.log("FileStack API Key: " + res);
});
But because what I really wanted to do was pass it into the FileStack init (again, on the client side), so I needed to put the following in the constructor for the FileStack object:
// "this" is the FileStack object we're constructing
const fileStackObj = this;
Meteor.call('getFileStackAPIKey', (err, apiKey) => {
// here we're inside the callback, so we have the resulting API key
const client = filestack.init(apiKey, clientOptions);
// these are synchronous actions dependent on the existence of "client"
// that we could not do outside of the callback
fileStackObj.state = {
client,
picker: action === 'pick' ? client.picker({ ...actionOptions, onUploadDone: fileStackObj.onFinished }) : null,
};
fileStackObj.onFinished = fileStackObj.onFinished.bind(fileStackObj);
fileStackObj.onFail = fileStackObj.onFail.bind(fileStackObj);
});

Incorporating Socket.IO in React Application

I am trying to incorporate socket.io in a React application. When the user clicks a button, I want the program to display a modal notifying all other users that the button has been clicked. In my current implementation, I set up the socket.io connection in my server.js file and use socket.io-client in one of the component files to send / listen for information from the server.
Server.js file:
io.on("connection", function(socket) {
console.log("Socket.io connection established");
socket.emit("saved article", function(article){
console.log("article saved");
io.emit("saved article", article);
});
});
Component file:
const socket = io();
class Search extends Component {
state = {
topic: "",
start: "",
end: "",
results: [],
savedModalTriggered: false,
articlesSaved: []
};
componentDidMount(){
socket.on("saved article", article => {
let articlesSavedCopy = this.state.articlesSaved;
articlesSavedCopy.push(article.title);
this.setState({savedModalTriggered: true, articlesSaved: articlesSavedCopy});
});
};
saveOrUnsave = (index) => {
API.saveArticle(this.state.results[index]).then(response => {
const article = {
title: response.data.title
};
socket.emit("saved article", article);
this.reverseSaved(index, response.data);
});
};
};
The following problems arise when I run my code:
1) When the Search component mounts, the program triggers socket.on("saved article"), causing the notification modal to pop up even though the saveOrUnsave function was not called.
2) After some period of time, I get the following error in my console: "WebSocket connection to localhost:3000... failed: Connection closed before receiving a handshake response"
3) I also get the following error in my console: "WebSocket connection to localhost:3000... failed: WebSocket opening handshake timed out"
The problem is that you are emitting a saved article event upon connection. When the client opens a new connection in the componentDidMount callback the server emits an event, thus triggering the callback you have registered.
If that is not what you want you should remove the emit logic from your connection callback in the server code.

Can't use "this" in stomp client subscribe - React

I have my Spring-Boot service setup so I can send messages through websocket to my browser and it works.
//#MessageMapping
#RequestMapping(value = "/notify")
#SubscribeMapping("/notification")
#SendTo("/topic/notification")
public String sendNotification() throws Exception {
sendMessage();
return "Request to update Tanks has been sent!";
}
public void sendMessage() {
this.messagingTemplate.convertAndSend("/topic/notification", "IT WORKS");
}
Here's the console log from chrome:
<<< MESSAGE
destination:/topic/notification
content-type:text/plain;charset=UTF-8
subscription:sub-1519225601109-13
message-id:f2qodiqn-8
content-length:8
IT WORKS
I want to be able to receive a message from the service and update the state in react, so, that it refetches from the backend. This is what my client looks like:
var socket = new SockJS("http://localhost:6667/refresh");
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('connected: ' + frame);
stompClient.subscribe('/topic/notification', function(notification){
console.log(notification.body);
//this.showNotification(JSON.parse(notification.body).content);
//this.showNotification(notification.body);
})
}, function(err) {
console.log('err', err);
});
And the fetch in componentDidMount()
fetch(`http://localhost:6666/front/objects`)
.then(result=>result.json())
.then(fuelTanks=>this.setState({fuelTanks}))
.catch(function(err) {
console.log('Could not fetch: ' + err.message);
}
)
I can't use this.showNotification(notification.body), hence I can't set the state to be able to refetch my objects. I tried making methods outside the class but then I can't use anything from the main class.
Is there a way to make react run componentDidMount again, or better, just access the fetch method in my class when I get a message from spring through the websocket?
Like this:
componentDidMount(){
var socket = new SockJS("http://192.168.1.139:8610/refresh");
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('connected: ' + frame);
stompClient.subscribe('/topic/notification', function(notification){
refetchTanks(); // call fetch tanks -> can't use "this"
})
}, function(err) {
console.log('err', err);
});
Thanks!
I know, it is a bit old question, but since it pops every time when you search for stomp issue, i thought of answering it. The way to access this in callbacks is to bind callbacks with this first, then the whole of object can be accessed in the callback.
Example:
connectCallBack(){
this.setState({loading:false})
}
errorCallback=()=>{
}
componentDidMount() {
axios.post('http://localhost:8080/subscribe', null, { params: {
deviceId
}})
.then(response => response.status)
.catch(err => console.warn(err));
const socket = new SockJS('http://localhost:8080/test');
const stompClient = Stomp.over(socket);
//stompClient.connect();
stompClient.connect( {}, this.connectCallBack, this.errorCallback);
If see above code both callbacks can access this.
I tried everything to be able to use my class methods and the state in stompClient's .subscribe method. I was able to connect and reconnect if the service died, nevertheless it wasn't working.
I decided to use react-stomp, which worked. I could use a class method in onMessage=.... This is what my code looks like:
<SockJsClient
url = 'http://localhost:8610/refresh/'
topics={['/topic/notification']}
onConnect={console.log("Connection established!")}
onDisconnect={console.log("Disconnected!")}
onMessage={() => this.update()} <------ this method performs a new GET
request
debug= {true}
/>
I also had to send the message in a specific way on the server side, since I was getting a JSON error when sending a string.
this.messagingTemplate.send("/topic/notification", "{"text":"text"}");
<<< MESSAGE
destination:/topic/notification
content-type:text/plain;charset=UTF-8
subscription:sub-0
message-id:aaylfxl4-1
content-length:49
{
"text": "text"
}
It currently works, but I am curious if there are other, better solutions to this issue.
EDIT: a much better solution here! Use the code from the first post and create a variable before connect to be able to access this like this var self = this;, then just access is as self.update() after subscribe!

AngularJS - POST request reload

I am searching solution for this question more than 3 day and can't find anything..
I have ionic3 App and working width Http POST requests. I am sending requests to my php server and geting data..
My data-api.ts (provider)
public getNotifications(token){
return this.http.post(this.sharedVars.getApi(),"action=messages/notification&token="+token, this.options
).map(res => res.json());
}
profilePage.ts
notifications() {
this.api.getNotifications(this.user.token).subscribe(
data => {
if(data.err == 0){
this.notifications = data.data;
}
},
err => {
console.log(err);
}
);
}
This is working functions and I am getting right output (1) when click this function. but on x action on my server notification count will changed to 2, 3, 4 etc.. and I want load this function not on click, but on page load. so If this.notifications have new value I want change value live (like as firebase)
Example 2:
I have send message action in my data-api.ts (provider)
public sendMessage(token, to, message, attachment){
return this.http.post(this.sharedVars.getApi(),"action=messages/send&token="+token+"&to="+to+"&message="+message+"&attachment="+attachment, this.options
).map(res => res.json());
}
and also have function to get this messages.
public getActivity(token){
return this.http.post(this.sharedVars.getApi(),"action=messages/getActivity&token="+token, this.options
).map(res => res.json());
}
so if I am making post request to sendMessage then I want listen live getActivity action and load new message in my page but not reload.. like as firebase..
I hope this question is clear. because I am not english speaker and tryng to find solution. Tanks
Listening actively to live events is not possible with a single HTTP request in angular.
However you might look into eventSources.
Look at this question for using with angular 2+ :
Creating an RxJS Observable from a (server sent) EventSource

Resources