Using Socket.io not working in React Native Mobile App - node.js

I have tried to connect socket with React Native mobile App.
Node server socket.io is working i have use with react web, But when i use React Native Mobile App there was not working
Client (React Native
import io from 'socket.io-client';
const ENDPOINT = "http://192.168.1.3:7000";
const socket = io(ENDPOINT, {transports: ['websocket']});
const getMessage = () => {
socket.on('getMessage', (data) => {
console.log('socket data', data);
})
};
const sendMessage = () => {
var userid = log.userid
var message = "msg from app"
socket.emit('send msg', { userid, message });
}
Server (Node Js)
const app = express();
const server = app.listen(7000);
const io = require('socket.io')(server, {
cors : {
origin:'*'
}
});
io.on("connection",(socket)=>{
socket.on('sendMessage',({userid, message})=>{
io.emit('getMessage',{userid, message});
})
})

dev! I solved this just not using socket.io. I got that Socket.io is not a good choice in mobile apps. Use WebSocket API instead.
REACT COMPONENT
import React from 'react';
import { View, Text } from 'react-native';
class WebSocketConnection extends React.Component {
constructor(props) {
super(props);
this.webSocket = this.webSocket.bind(this);
}
webSocket() {
const ws = new WebSocket('ws:172.16.20.201:8080');
ws.onopen = (e) => {
console.log('connected on wsServer');
}
ws.addEventListener('open', function (event) {
ws.send('Hello from React Native!');
});
ws.addEventListener('message', function (event) {
this.message = event.data;
console.log('Message from server ', event.data);
});
ws.onerror = (e) => {
console.log(e);
}
}
render() {
return (
<View>
<Text>
Socket.io YES
</Text>
</View>
)
}
componentDidMount() {
this.webSocket();
}
}
export default WebSocketConnection;
SERVER
/**
* Create WebSocket server.
*/
const WebSocket = require('ws');
const serverWs = new WebSocket.Server({
port: 8080
});
let sockets = [];
serverWs.on('connection', function(socket) {
sockets.push(socket);
console.log(`connectet client`);
// When you receive a message, send that message to every socket.
socket.on('message', function(msg) {
console.log(msg);
sockets.forEach(s => s.send(msg));
});
// When a socket closes, or disconnects, remove it from the array.
socket.on('close', function() {
sockets = sockets.filter(s => s !== socket);
});
});
While using Express, I bind this server inside /bin/www and work fine to me.

I was able to work when i changed the react-native to use the ws protocol. My code was as below
useEffect(()=>{
const socket = io("ws://localhost:3000");
socket.on("connect", () => {
console.log(socket.connected); // true
});
},[])

Related

App Engine can't find default credentials to connect to Firestore in Google Cloud

I have a NextJS Typescript app running on Google App Engine. It fetches data from Firestore and everything works fine. In order to improve the speed of the app I'm experimenting new data fetching infrastructure in which the server listens to Firestore collections and updates all the data to JSON files in the tmp folder when changes are made in Firestore. This way all the data is up-to-date and available to the App Engine all the time. Locally this works like a charm.
There are some obvious things I need to improve, but the next step for me is to run a dev project in GCP and see if my memory usage is ok and if it works as quickly as I hope etc. But the problem is that when I change my NextJS infra to include a custom server, the connection between App Engine and Firestore vanishes.
The problem I'm seeing on GCP logs is:
Error: Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.
at GoogleAuth.getApplicationDefaultAsync (/workspace/node_modules/google-auth-library/build/src/auth/googleauth.js:180:19)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at runNextTicks (node:internal/process/task_queues:65:3)
at listOnTimeout (node:internal/timers:526:9)
at processTimers (node:internal/timers:500:7)
at async GoogleAuth.getClient (/workspace/node_modules/google-auth-library/build/src/auth/googleauth.js:558:17)
at async GrpcClient._getCredentials (/workspace/node_modules/google-gax/build/src/grpc.js:145:24)
at async GrpcClient.createStub (/workspace/node_modules/google-gax/build/src/grpc.js:308:23)
The actual error message in the client is "502 Bad Gateway – nginx".
Earlier I had a basic NextJS app which has frontend pages and backend API routes. The routes connect to Firestore and serve that data to correct users etc. The main difference is that I've added a custom server that initiates the listeners:
import { Firestore } from '#google-cloud/firestore';
import express, { Request, Response } from 'express';
import next from 'next';
import fs from 'fs';
import os from 'os';
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const port = process.env.PORT || 3000;
let firestoreListeners: { [collectionId: string]: () => void } = {};
const unsubscribeAllListeners = () => {
for (const collectionId of Object.keys(firestoreListeners)) {
console.log('unsubscribing from', collectionId);
firestoreListeners[collectionId]();
}
firestoreListeners = {};
};
const skippedCollections = ['analytics', 'pageRevisions', 'newsItemRevisions'];
app.prepare().then(() => {
const server = express();
unsubscribeAllListeners();
const firestoreSettings = {} as FirebaseFirestore.Settings;
if (process.env.GCP_KEYFILE_NAME) {
firestoreSettings.keyFilename = process.env.GCP_KEYFILE_NAME;
}
const firestoreData: {
[collectionId: string]: {
[id: string]: any;
};
} = {};
const firestore = new Firestore(firestoreSettings);
firestore.listCollections().then((collections) => {
for (const collection of collections) {
if (
!firestoreListeners[collection.id] &&
!skippedCollections.includes(collection.id)
) {
console.log('listening to', collection.id);
firestoreData[collection.id] = {};
const listener = firestore
.collection(collection.id)
.onSnapshot((snapshot) => {
firestoreData[collection.id] = {};
for (const doc of snapshot.docs) {
firestoreData[collection.id][doc.id] = {
_id: doc.id,
...doc.data(),
};
}
if (!fs.existsSync(os.tmpdir() + '/data')) {
fs.mkdirSync(os.tmpdir() + '/data');
}
fs.writeFileSync(
os.tmpdir() + `/data/${collection.id}.json`,
JSON.stringify(firestoreData[collection.id])
);
console.log(
'updated',
collection.id,
'with',
snapshot.docs.length,
'docs'
);
});
firestoreListeners[collection.id] = listener;
}
}
});
server.all('*', (req: Request, res: Response) => {
return handle(req, res);
});
server.listen(port, (err?: any) => {
if (err) throw err;
console.log(
`> Ready on localhost:${port} - env ${process.env.NODE_ENV}`
);
});
server.on('close', function () {
unsubscribeAllListeners();
});
process.on('beforeExit', () => {
unsubscribeAllListeners();
});
});
The build and deploy scripts are ok, it works if I take the listener logic out of the equation and just deploy the custom server.
What's the problem? Is it some nginx problem or do I have something else off?
The problem apparently is that I cannot initiate my Firestore connection before listen or even at the listen callback. I have to do it a bit later (to give GAE a possibility to authenticate for Firestore?).
When I moved my listeners to listen to all endpoints, it worked. Below is a solution that helped with the problem. I don't feel it's that beautiful, but gets the job done.
import { Firestore } from '#google-cloud/firestore';
import express, { Request, Response } from 'express';
import next from 'next';
import fs from 'fs';
import os from 'os';
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const port = process.env.PORT || 3000;
let firestoreListeners: { [collectionId: string]: () => void } = {};
const unsubscribeAllListeners = () => {
for (const collectionId of Object.keys(firestoreListeners)) {
console.log('unsubscribing from', collectionId);
firestoreListeners[collectionId]();
}
firestoreListeners = {};
};
const skippedCollections = ['analytics', 'pageRevisions', 'newsItemRevisions'];
export const firestoreData: {
[collectionId: string]: {
[id: string]: any;
};
} = {};
let listenersInitiated = false;
const initiateListeners = () => {
if (listenersInitiated) {
return;
}
const firestoreSettings = {} as FirebaseFirestore.Settings;
if (process.env.GCP_KEYFILE_NAME) {
firestoreSettings.keyFilename = process.env.GCP_KEYFILE_NAME;
}
const firestore = new Firestore(firestoreSettings);
firestore.listCollections().then((collections) => {
for (const collection of collections) {
if (
!firestoreListeners[collection.id] &&
!skippedCollections.includes(collection.id)
) {
console.log('listening to', collection.id);
firestoreData[collection.id] = {};
const listener = firestore
.collection(collection.id)
.onSnapshot((snapshot) => {
firestoreData[collection.id] = {};
for (const doc of snapshot.docs) {
firestoreData[collection.id][doc.id] = {
_id: doc.id,
...doc.data(),
};
}
if (!fs.existsSync(os.tmpdir() + '/data')) {
fs.mkdirSync(os.tmpdir() + '/data');
}
fs.writeFileSync(
os.tmpdir() + `/data/${collection.id}.json`,
JSON.stringify(firestoreData[collection.id])
);
console.log(
'updated',
collection.id,
'with',
snapshot.docs.length,
'docs'
);
});
firestoreListeners[collection.id] = listener;
}
}
});
listenersInitiated = true;
};
app.prepare().then(() => {
const server = express();
unsubscribeAllListeners();
server.all('*', (req: Request, res: Response) => {
initiateListeners();
return handle(req, res);
});
server.listen(port, (err?: any) => {
if (err) throw err;
console.log(
`> Ready on localhost:${port} - env ${process.env.NODE_ENV}`
);
});
server.on('close', function () {
console.log('Closing');
unsubscribeAllListeners();
});
process.on('beforeExit', () => {
console.log('Closing');
unsubscribeAllListeners();
});
});
According to my initial tests this works very nicely in GAE. When setting the app.yaml settings correctly, it provides nice speed with low costs.
This does not really handle listeners failing if a server instance lives for a long time and also, it might initiate too many listeners, but the initial results of my tests are promising!

react-native WebSocket & nodejs WebSocket

NodeJS:
const WebSocket = require('ws');
const ws = new WebSocket('wss://siteurl');
ws.on('open', function open() {
ws.send('');
});
ws.on('message', function incoming(message) {
console.log(message);
});
react-native
let connection = new WebSocket('wss://siteurl');
connection.onopen = () => {
connection.send('');
};
connection.onmessage = ((event) => {
console.log(event);
});
connection.onerror = (event) => {
console.log(event);
}
NodeJS works fine, but react-native return 'Unable to resolve host'.
Android Stuio Emulator, started with npm run android.
Any ideas why it could happens?
UPD:
const socket = new WebSocket('wss://siteurl');
socket.onopen = () => {
console.log('Socket connected!');
socket.send('');
}
socket.onmessage = (message) => {
console.log(`Message:\n`, message.data);
}
```
Browser works fine too
You can use socket.io-client
socket.io-client Document
Example
// ES6 import or TypeScript
import { io } from "socket.io-client";
// CommonJS
const io = require("socket.io-client");
// the following forms are similar
const socket = io("https://server-domain.com");
const socket = io("wss://server-domain.com");
const socket = io("server-domain.com"); // only in the browser when the page is served over https
socket.on('message', (event) => {
console.log(event);
});

Socket.IO Sever send to both clients

I have got a node server running locally and setting up a Socket.IO instance.
const http = require('http');
const socket = require('socket.io');
const path = require('path');
class Controller
{
constructor() {
this.localURL = path.resolve(process.cwd() + '/themes/');
this.theme = null;
const server = http.createServer();
this.io = new socket.Server(server, {
transports: ['websocket'],
});
this.io.on("connection", socket => {
// Wait for the client to send the website theme
socket.on('init', theme => {
// Inform current running client that the server is changing projects.
if (this.theme && this.theme !== theme) {
socket.emit(`message-${this.theme}`, {
type: 'message',
message: `Project changed to ${theme}, stopping ${this.theme}.`
});
return;
}
// Set the theme
this.theme = theme;
});
});
server.listen(8080);
}
}
new Controller();
Then on my website I have got a Vue component, but sometimes I could have 2 of the components, so I wanted to emit messages to BOTH of these component's from my server, I will handle accepting the messages in either Vue Instance myself.
This was working, all of a sudden it's not now, not too sure what I changed.
import { io } from 'socket.io-client';
export default {
props: [ 'code' ],
mounted: function () {
this.socket = io('ws://localhost:8080', {
forceNew: true,
timeout: 10000,
transports: ['websocket']
});
this.socket.on('connect', () => {
this.connected = true;
});
this.socket.on('disconnect', () => {
this.connected = false;
this.initiated = false;
});
this.socket.on(`stop-${this.code}`, () => {
this.started = '';
});
this.socket.on(`message-${this.code}`, message => {
console.log(message);
message.time = 'N/A';
this.messages.unshift(message);
})
this.socket.onAny((event, data) => {
if (event.indexOf(this.code) > -1) {
return;
}
event = event.replace(`-${this.code}`, '');
this[event] = data;
});
},
methods: {
initiate() {
this.messages = [];
this.socket.emit('init', this.code);
this.socket.on('started', code => {
if (code !== this.code) {
console.log('Themes don\'t match...');
this.initiated = false;
return;
}
So initially I would run initiate on one of the components, this sends some the theme name to the server and the server stores the theme in a property. Then I would run initiate on the second component, which would send a different theme, so this should hit the this.theme && this.theme !== theme if, and send a message back to the initial theme.
This message is being sent and the event names are as expected, but nothing comes through on the component.

React Native one to one conversation using socket.io

i currently have a react native app with nodejs express Sequelize as my backend and postgres as my database.
So, on my posts screen next to each post, i have a text input and a button where the current user can send the user of the post an initial message. Once the button is pressed, a conversation between these 2 users about this post is created in my database and stored in my conversation table and an entry of the message sent is also stored in my messages table.
I have implemented bidirectional communication between these 2 users. But my problem is i need to refresh the app in order to show the user current user the sent message and to show the receiving user the received message.
I have been researching for a while now and trying to understand how to implement this feature using socket.io but could not get anywhere.
Client Side
Here is my Chat Screen
function ChatScreen({route,navigation}) {
const message = route.params.message;
const [messages, setMessages] = useState(message.Messages);
const [text, setText] = useState('');
const { user } = useAuth();
const [socket, setSocket] = useState(null);
useEffect(() => {
const newsocket =io.connect(socketurl)
setMessages(messages);
newsocket.on('connection', msg => {
console.log('i have joined')
setMessages(messages=>messages.concat(msg))
setSocket(newsocket)
})
return()=>newsocket.close;
}, []);
const updateText=(text)=>{
setText(text);
}
const onSend = (ConversationId,senderId,receiverId,message) => {
console.log("sent")
messagesApi.sendMessage({ConversationId,senderId,receiverId,message});
setText("")
socket.emit('message', { to: (user.id===route.params.message.user1 ?
route.params.message.user2 : route.params.message.user1), from:
user.id, message,ConversationId });
};
return(
<Text>{user.id === message.Recipient.id ?
message.Creator.name:message.Recipient.name}</Text>
<KeyboardAvoidingView
style={{
display: "flex",
flex: 1,
}}
behavior={Platform.OS === "ios" ? "padding" : null}
keyboardVerticalOffset={Platform.OS === "ios" ? 25 : 0}
>
<FlatList
inverted
data={message.Messages}
keyExtractor={(message) => message.id.toString()}
renderItem={({item,index})=>(
<MessageBubble
text={item.message}
mine={item.senderId !== user.id}
/>
)}/>
<View style={styles.messageBoxContainer}>
<TextInput
style={styles.messageBox}
placeholder="Message..."
multiline
clearButtonMode="while-editing"
onChangeText={updateText}
value={text}
autoCorrect={false}
/>
<TouchableOpacity onPress={onSend}>
<Text style={styles.send}>Send</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
)
Server Side
index.js
const express = require("express");
const app = express();
const http = require("http");
const socketio = require("socket.io")
const server=http.createServer(app);
const io =socketio(server)
io.on("connection", socket => {
socket.on('message', (data) => {
socket.join(data.ConversationId);
io.sockets.in(data.to).emit('send_message', { message: data.message,
to: data.to });
});
});
const port = process.env.PORT || config.get("port");
server.listen(port, function () {
console.log(`Server started on port ${port}...`);
});
Currently when i send a message, the message gets stored in my database but my chat does not update instantly (ie. not live), i need to refresh my app and the messages appear.
Can someone please help me and check if the implementation of socket i currently have is correct and if so, how do i render my flatlist instantly?
UPDATE
i think something is wrong in my useEffect, because when i open the chat i am not getting "i have joined" in the console:
useEffect(() => {
setMessages(messages);
socket.on('connect', msg => {
console.log('i have joined')
setMessages(messages=>messages.concat(msg))
})
}, []);
Your currently creating a new connection on every state change.. const socket =io.connect(socketurl)
You have a useEffect callback and that would be the logical place to put your connection logic, currently your only listening once for a connection, but creating multiple connections, so your 'connection' event is never called on these new connections. But you only want to connect once anyway, so we just need to put the connection logic also inside the useEffect, not just the connection event.
Because connecting to a socket is async, you will want to wait for the connection before rendering. So what we could do is store the socket in state, and when we get a connection set socket state, this will fire a re-render with a now valid socket.
eg.
const [socket, setSocket] = useState(null);
...
useEffect(() => {
const socket = io.connect(socketurl)
setMessages(messages);
newsocket.on('connect', msg => { //connect not connection
console.log('i have joined')
setMessages(messages=>messages.concat(msg));
setSocket(newSocket);
});
//might make sense to close the socket too,
//otherwise a memory leak.
return () => newSocket.close();
}, [route, navigation]);
if (!socket) {
//we don't have a socket yet,
return "loading..";
} else {
// we have a socket,
const onSend = (ConversationId,senderId,receiverId,message) => {...
....
// now render..
return (
<Text>{.........
Socket Index.js
/** Socket.io server listens to our app **/
var io = require('socket.io').listen(app);
io.on('connection', function (socket) {
/** On User Log In **/
socket.on('login', function (data) {
console.log('user joined >>', data)
userList.addUser(data, socket);
});
// Example Event //
socket.on('get_online_friends', userId => {
//Get List Data And Then //
let data = [UserList];
socket.emit('send_online_friend', data);
}
)
// On Logout //
socket.on('disconnect', function (reason) {
var offlineId = userList.removeUser(socket)
);
}
user_list.js
var userList = {};
module.exports = userList;
var userData = [];
var userSocData = {};
userList.user = userData;
userList.userSoc = userSocData;
userList.getUserList = function () {
return userSocData;
};
userList.addUser = function (user, client) {
userSocData[user] = {
socketId: client.id
}
};
userList.setReceiverId = function (user, client) {
var index = userData.findIndex(x => x.user_id == user['user_id']);
if (index !== -1) {
userData[index]['receiver_id'] = user['receiver_id'];
}
userSocData[user['user_id']] = {
socket: client.id
};
};
userList.removeUser = function (client) {
for (const property in userSocData) {
if (client.id === userSocData[property].socketId) {
var userID = property;
delete userSocData[property]
}
}
return userID;
};
Front End
***
socket.emit("get_request", userData.user_id);
socket.on("get_request_data", function (data) {
if (data.status) {
self.setState({ onlineFriends: data.data });
}
});
***

NodeJS + WS access currently running WS server instance

I have implemented a simple REST API using NodeJS, ExpressJS and routing-controllers. I have also implemented a basic WebSocket server running alongside the REST API and using WS.
const app = express();
app.use(bodyParser.json({limit: "50mb"}));
app.use(bodyParser.urlencoded({limit: "50mb", extended: true}));
useExpressServer(app, {
controllers: [
UserController
]
});
const server = app.listen(21443, (err: Error) => {
console.log("listening on port 21443");
});
const wss = new WebSocket.Server({server});
wss.on("connection", (ws: WebSocket) => {
ws.on("message", (message: string) => {
console.log("received: %s", message);
ws.send(`Hello, you sent -> ${message}`);
});
ws.send("Hi there, I am a WebSocket server");
});
My question is how to I get access to the currently running WS instance so that I am able to send or broadcast from my controller methods. I have a number of POST methods that run long processes and so return a HTTP 200 to the client, I then would like to either send or broadcast to all connected WS clients.
What is the correct way to access the WebSocket.Server instance from within my controller classes?
You can create the websocket earlier and pass the instance around:
const notifier = new NotifierService();
notifier.connect(http.createServer(app));
app.get("/somethingHappened", () => {
notifier.broadcast("new notification!!");
});
app.use(routes(notifier))
Full code:
app.js
Pass the websocket to the other routes:
const express = require("express");
const http = require("http");
const NotifierService = require("../server/NotifierService.js");
const routes = require("./routes");
const app = express();
const server = http.createServer(app);
const notifier = new NotifierService();
notifier.connect(server);
app.get("/somethingHappened", () => {
notifier.broadcast("new notification!!");
});
// to demonstrate how the notifier instance can be
// passed around to different routes
app.use(routes(notifier));
server
.listen(4000)
.on("listening", () =>
console.log("info", `HTTP server listening on port 4000`)
);
NotifierService.js class that handles the websocket
const url = require("url");
const { Server } = require("ws");
class NotifierService {
constructor() {
this.connections = new Map();
}
connect(server) {
this.server = new Server({ noServer: true });
this.interval = setInterval(this.checkAll.bind(this), 10000);
this.server.on("close", this.close.bind(this));
this.server.on("connection", this.add.bind(this));
server.on("upgrade", (request, socket, head) => {
console.log("ws upgrade");
const id = url.parse(request.url, true).query.storeId;
if (id) {
this.server.handleUpgrade(request, socket, head, (ws) =>
this.server.emit("connection", id, ws)
);
} else {
socket.destroy();
}
});
}
add(id, socket) {
console.log("ws add");
socket.isAlive = true;
socket.on("pong", () => (socket.isAlive = true));
socket.on("close", this.remove.bind(this, id));
this.connections.set(id, socket);
}
send(id, message) {
console.log("ws sending message");
const connection = this.connections.get(id);
connection.send(JSON.stringify(message));
}
broadcast(message) {
console.log("ws broadcast");
this.connections.forEach((connection) =>
connection.send(JSON.stringify(message))
);
}
isAlive(id) {
return !!this.connections.get(id);
}
checkAll() {
this.connections.forEach((connection) => {
if (!connection.isAlive) {
return connection.terminate();
}
connection.isAlive = false;
connection.ping("");
});
}
remove(id) {
this.connections.delete(id);
}
close() {
clearInterval(this.interval);
}
}
module.exports = NotifierService;
routes.js
const express = require("express");
const router = express.Router();
module.exports = (webSocketNotifier) => {
router.post("/newPurchase/:id", (req, res, next) => {
webSocketNotifier.send(req.params.id, "purchase made");
res.status(200).send();
});
return router;
};
List of connected clients are stored inside wss object. You can receive and loop through them like this:
wss.clients.forEach((client) => {
if (client.userId === current_user_id && client.readyState === WebSocket.OPEN) {
// this is the socket of your current user
}
})
Now you need to somehow identify your client. You can do it by assigning some id to this client on connection:
wss.on('connection', async (ws, req) => {
// req.url is the url that user connected with
// use a query parameter on connection, or an authorization token by which you can identify the user
// so your connection url will look like
// http://example.com/socket?token=your_token
ws.userId = your_user_identifier
....
})
To broadcast use:
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
If your controller and socket will be in different files (and I am sure they will), you will have to export the wss object in your socket file and import it in controller.

Resources