React Native websocket object not connected on first attempt - reactjs

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?

Related

Socket.io/React Chat App - Connection established

i have two components: where I set the username and room and then I have the component where is the send message and displays the conversation.
When I login on the component, I navigate to the component and trigger the connection with the socket:
const handleClick = (e) => {
e.preventDefault();
navigate("/chatroom", { state: { username, room } });
};
I have two issues regarding the connection with the socket:
-I´ve tried to start the connection inside an useEffect():
export default function ChatRoom() {
const [chatMessage, setChatMessage] = useState("");
const [showmsg, setShowMsg] = useState([]);
const [showObj, setShowObj] = useState([]);
const [submited, setSubmited] = useState(false);
const handleSubmit = (e) => {
e.preventDefault();
setShowMsg((oldmsg) => [...oldmsg, chatMessage]);
setChatMessage("");
setSubmited(true);
};
const { state } = useLocation(); //to get data from <Home/> component
useEffect(() => {
const socket = io("http://localhost:3000");
//Message from server
socket.on("message", (msg) => {
setShowObj((oldmsg) => [...oldmsg, msg]);
setSubmited(false);
console.log(showObj);
});
//Message to server
socket.emit("chatMessage", {
user: state.username,
text: showmsg[showmsg.length - 1],
}); //pass the last msg
}, [submited]);
ServerSide:
io.on("connection", (socket) => {
//Welcome current user
socket.emit("message", formatMessage("MiouriChat", "Welcome to the chat!"));
//Broadcast when user connects
socket.broadcast.emit(
"message",
formatMessage("MiouriChat", "A user has joined the chat")
);
//Run when clients disconects
socket.on("disconnect", () => {
io.emit("message", formatMessage("MiouriChat", "A user has left the chat"));
});
//Listen to chat message from client
socket.on("chatMessage", (msg) => {
io.emit("message", formatMessage(msg.user, msg.text));
});
});
This way, everytime I submit a msg ([submited] change), the connection is reseted for obvious reason and get the "welcome" messages everytime it reconects.
If I put the connection outside the useEffect(), the connection reseted everytyme I type a letter on the message input (becouse the state updates.)
What is the best solution for this?
For your initial "welcome" message, you want a useEffect that only runs after the first render of the page as explained in the docs - by specifying an empty dependency array. This is also a really nice place to define the "cleanup" function, where you want to send a "disconnect" message to your server (in fact the cleanup docs even use a chat API as an example!):
useEffect(() => {
const socket = io("http://localhost:3000");
//Message from server
socket.on("message", (msg) => {
setShowObj((oldmsg) => [...oldmsg, msg]);
console.log(showObj);
});
return () => {
socket.emit("disconnect"); // cleanly disconnect from server
};
}, []); // <- Empty dependency array === only runs after initial render
That solves the "welcome message" and "disconnect" problems, and you no longer need the submited state variable. I can't really help you with the rest, but again I commend the useEffect documentation to you, particularly the example which is so applicable to your use case!

How to start WebRTC call with AWS Kinesis as Master

I'm trying to start a WebRTC call with AWS Kinesis, but the demo on The AWS Kinesis Javascript docs only shows how to join the call as a VIEWER not the MASTER.
I can't find a clear example anywhere online, and I've spent hours on it with my teammate.
I can see and hear myself, so I know I'm getting the hardware working correctly, but we can't see or hear each other. I know it's going to be something simple, but I just can't figure out where I'm going wrong with the connection.
const startKinesisCall = async () => {
const coachingSession = new AWS.KinesisVideo({
region,
accessKeyId,
secretAccessKey,
correctClockSkew: true
});
// Get Signaling Channel Endpoints
// Each signaling channel is assigned an HTTPS and WSS endpoint to connect to for
// data-plane operations. These can be discovered using the GetSignalingChannelEndpoint API.
const getSignalingChannelEndpointResponse = await coachingSession.getSignalingChannelEndpoint({
ChannelARN: channelARN,
SingleMasterChannelEndpointConfiguration: {
Protocols: ['WSS', 'HTTPS'],
Role: Role.VIEWER
}
}).promise();
const endpointsByProtocol = getSignalingChannelEndpointResponse?.ResourceEndpointList?.reduce((endpoints, endpoint) => {
endpoints[endpoint.Protocol] = endpoint?.ResourceEndpoint;
return endpoints;
}, {});
// Create KVS Signaling Client
// The HTTPS endpoint from the GetSignalingChannelEndpoint response is used with this client.
// This client is just used for getting ICE servers, not for actual signaling.
const kinesisVideoSignalingChannelsClient = new AWS.KinesisVideoSignalingChannels({
region,
accessKeyId,
secretAccessKey,
endpoint: endpointsByProtocol.HTTPS,
correctClockSkew: true,
});
// Get ICE server configuration
// For best performance, we collect STUN and TURN ICE server configurations.
// The KVS STUN endpoint is always stun:stun.kinesisvideo.${region}.amazonaws.com:443.
// To get TURN servers, the GetIceServerConfig API is used.
const getIceServerConfigResponse = await kinesisVideoSignalingChannelsClient
.getIceServerConfig({
ChannelARN: channelARN,
}).promise();
const iceServers = [{ urls: `stun:stun.kinesisvideo.${region}.amazonaws.com:443` }];
getIceServerConfigResponse.IceServerList.forEach(iceServer =>
iceServers.push({
urls: iceServer.Uris,
username: iceServer.Username,
credential: iceServer.Password,
}),
);
console.log('ICE SERVERS: ', iceServers);
// Create RTCPeerConnection
// The RTCPeerConnection is the primary interface for WebRTC communications in the Web.
const peerConnection = new RTCPeerConnection({ iceServers });
// Create WebRTC Signaling Client
// This is the actual client that is used to send messages over the signaling channel.
const signalingClient = new SignalingClient({
channelARN,
channelEndpoint: endpointsByProtocol.WSS,
role: Role.MASTER,
region,
clientId,
credentials: {
accessKeyId,
secretAccessKey,
},
systemClockOffset: coachingSession.config.systemClockOffset
});
// GET THE USER MEDIA DEVICES
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).catch(e => {
console.log("COULD NOT FIND WEBCAM");
setShowErrorStartingVideoModal(true);
});
// *** AUDIO & VIDEO DEVICE COLLECTION ***
let audioInputDevices: MediaDeviceInfo[];
let audioOutputDevices: MediaDeviceInfo[];
let videoInputDevices: MediaDeviceInfo[];
try {
const mediaDevices = await navigator.mediaDevices.enumerateDevices();
audioInputDevices = mediaDevices.filter(device => device.kind === 'audioinput');
audioOutputDevices = mediaDevices.filter(device => device.kind === 'audiooutput');
videoInputDevices = mediaDevices.filter(device => device.kind === 'videoinput');
setMicrophoneList(audioInputDevices);
setSpeakerList(audioOutputDevices);
setCameraList(videoInputDevices);
} catch (e) {
console.log(e);
console.log("ERROR COLLECTING MEDIA DEVICE INFORMATION: MAKE SURE PERMISSIONS ARE ALLOWED AND TRY AGAIN");
};
// GRAB THE LOCAL PROVIDER AND PATIENT VIDEO TILES
const providerVideoTile: HTMLVideoElement = document.getElementById('provider-video-element') as HTMLVideoElement;
const patientVideoElement = document.getElementById('patient-video-element') as HTMLVideoElement;
// let dataChannel: RTCDataChannel
// Add Signaling Client Event Listeners
signalingClient.on('open', async () => {
if (!localStream || !peerConnection) return;
// Get a stream from the webcam, add it to the peer connection, and display it in the local view
try {
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
providerVideoTile.srcObject = localStream;
} catch (e) {
// Could not find webcam
console.log(e);
return;
};
// Create an SDP offer and send it to the master
const offer = await peerConnection.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true
});
console.log('CREATED OFFER: ', offer);
await peerConnection.setLocalDescription(offer);
if (peerConnection.localDescription) signalingClient.sendSdpOffer(peerConnection.localDescription, patient.patientID);
});
// When the SDP answer is received back from the master, add it to the peer connection.
signalingClient.on('sdpAnswer', async answer => {
console.log('RECEIVED ANSWER: ', answer);
if (!peerConnection) return;
await peerConnection.setRemoteDescription(answer).catch(e => console.log(e));
});
signalingClient.on('sdpOffer', async (offer, senderClientID) => {
console.log({ offer });
if (!peerConnection) return;
await peerConnection.setRemoteDescription(offer).catch(e => console.log(e));
console.log('REMOTE DESCRIPTION SET: ', peerConnection);
const answer = await peerConnection.createAnswer().catch(e => console.log(e));
console.log({ answer });
if (answer) signalingClient.sendSdpAnswer(answer, senderClientID);
// dataChannel = peerConnection.createDataChannel(`data-channel-of-${senderClientID}`);
// dataChannel.addEventListener("open", (event) => {
// console.log(event);
// dataChannel.send('******HI ALEC*******');
// });
});
// When an ICE candidate is received from the master, add it to the peer connection.
signalingClient.on('iceCandidate', async (candidate, senderClientID) => {
if (!peerConnection) return;
console.log('new iceCandidate received:', candidate);
await peerConnection.addIceCandidate(candidate).catch(e => console.log(e));
console.log("ICE CANDIDATE ADDED: ", candidate);
});
signalingClient.on('close', async () => {
if (!localStream) return;
// Handle client closures
console.log("ENDING THE CALL");
localStream.getTracks().forEach(track => track.stop());
peerConnection.close();
if ('srcObject' in providerVideoTile) providerVideoTile.srcObject = null;
});
signalingClient.on('error', error => {
// Handle client errors
console.log(error);
});
signalingClient.on('chat', (dataMessage: any) => {
const decodedMessage = UTF8Decoder.decode(new Uint8Array(dataMessage.data));
console.log("GOT TEST MESSAGE:", decodedMessage);
});
signalingClient.on('SeriesData', (dataMessage: any) => {
const seriesFromMobile = JSON.parse(UTF8Decoder.decode(new Uint8Array(dataMessage.data)));
console.log("SERIES FROM MOBILE:", seriesFromMobile);
kickOffSeriesCreation(seriesFromMobile);
});
signalingClient.on('EffortMarker', (dataMessage: any) => {
const effortMarker = UTF8Decoder.decode(new Uint8Array(dataMessage.data));
console.log("EFFORT MARKER:", effortMarker);
setEffortMarker(effortMarker);
});
signalingClient.on('CoachingMessage', async (dataMessage: any) => {
const coachingMessage = UTF8Decoder.decode(new Uint8Array(dataMessage.data));
console.log("COACHING MESSAGE FROM MOBILE:", coachingMessage);
if (coachingMessage === 'EndSeries') {
await handleForceEndEffort(signalingClient);
await handleEndSeries(signalingClient);
};
});
// Add Peer Connection Event Listeners
// Send any ICE candidates generated by the peer connection to the other peer
peerConnection.addEventListener('icecandidate', ({ candidate }) => {
if (candidate) {
console.log(candidate);
signalingClient.sendIceCandidate(candidate, patient.patientID);
} else {
// No more ICE candidates will be generated
console.log('NO MORE ICE CANDIDATES WILL BE GENERATED');
}
});
// As remote tracks are received, add them to the remote view
peerConnection.addEventListener('track', event => {
// if (patientVideoElement.srcObject) return;
setNoPatientConnected(false);
console.log({ event });
try {
peerConnection.addTrack(event.track, event.streams[0]);
if (event.track.kind === 'video') patientVideoElement.srcObject = event.streams[0];
} catch (e) {
console.log(e);
}
});
// Open Signaling Connection
signalingClient.open();
};
Try this this page, You can use master on one computer and viewer on other.
https://awslabs.github.io/amazon-kinesis-video-streams-webrtc-sdk-js/examples/index.html
For anyone else with the same issue, I managed to find the master example on this github repo and was able to get it working

ReactJS: STOMP subscription to multiple topics

My React code creates a WebSocket connection to our company's ActiveMQ 5.15.5 server, and then subscribes to the following two topics: salary and decoding. The problem is that the code is only able to subscribe to one of the topics. It cannot subscribe to both.
const client = window.Stomp.client(`ws://${ipAddress}:61614`, 'aj6.stomp');
const headers = { id: 'username' };
client.debug = null;
client.connect('user', 'pass', () => {
client.subscribe(
'/topic/salary', //BREAKPOINT was set here
message => {
const body = JSON.parse(message.body);
if (body && body.pcId) {
salaries[body.pcId] = body;
setState({ salaries});
}
},
headers,
);
client.subscribe(
'/topic/decoding', //BREAKPOINT was set here
message => {
const newBody = JSON.parse(message.body);
if (newBody && newBody.PcID) {
consoleMessage[newBody.PcID] = newBody;
setState({ consoleMessage });
}
},
headers,
);
});
So in the code above I put a break-point at client.subscribe('/topic/decoding... and client.subscribe('/topic/salary.... I saw that it only subscribes to /topic/decoding but not /topic/salary.
Does anyone know how I can fix this issue so that it subscribes to both topics?
From Stomp documentation:
Since a single connection can have multiple open subscriptions with a server, an id header MUST be included in the frame to uniquely identify the subscription. The id header allows the client and server to relate subsequent MESSAGE or UNSUBSCRIBE frames to the original subscription.
Within the same connection, different subscriptions MUST use different subscription identifiers.
Stomp API definition:
subscribe(destination, callback, headers = {})
So for my understanding, You can't have the same username id for both of your subscriptions
Try creating 2 clients, e.g.:
const salaryClient = window.Stomp.client(`ws://${ipAddress}:61614`, 'aj6.stomp');
const salaryHeaders = { id: 'salary' };
salaryClient.debug = null;
salaryClient.connect('user', 'pass', () => {
salaryClient.subscribe(
'/topic/salary',
message => {
const body = JSON.parse(message.body);
if (body && body.pcId) {
salaries[body.pcId] = body;
setState({ salaries});
}
},
salaryHeaders,
);
});
const decodingClient = window.Stomp.client(`ws://${ipAddress}:61614`, 'aj7.stomp');
const decodingHeaders = { id: 'decoding' };
decodingClient.debug = null;
decodingClient.connect('user', 'pass', () => {
decodingClient.subscribe(
'/topic/decoding',
message => {
const newBody = JSON.parse(message.body);
if (newBody && newBody.PcID) {
consoleMessage[newBody.PcID] = newBody;
setState({ consoleMessage });
}
},
decodingHeaders,
);
});

React/Socket.io - Client emits to all other clients only once and then only emits to itself after that

Running into an issue with React/Socket.io. I have two different socket emitters/listeners: one for a chat, and one for keeping track live changes to the application. I have two separate windows running localhost. The issue is when i emit a change on one window, the other window can receive that change the first time but never again (i.e. get first chat message but none that follow). After that first emit/receive, the sending client starts to receive its own emitters.
front end code:
`
socket = io("localhost:3002");
componentDidMount() {
//get id from url
const { id } = this.props.match.params;
//join specific room for project
this.socket.on("connect", () => {
this.socket.emit("room", this.projectId);
});
//listener for incoming messages
this.socket.on("RECEIVE_MESSAGE", (data) => {
this.props.addChat(this.projectId, data);
});
this.socket.on("UPDATE_PROJECT", () => {
console.log("update");
this.props.fetchProject(id);
});
}
emitTaskChange = () => {
this.socket.emit("TASK_CHANGE", { data: null });
};
onChatSubmit = (e) => {
e.preventDefault();
//create object with current user as author, message, and a timestamp
const chat = {
author: this.props.currentUser.name,
message: this.state.newChat,
createdAt: new Date().toLocaleString(),
};
//send message through socket
this.socket.emit("SEND_MESSAGE", chat);
//call action creator to add new chat
this.props.addChat(this.projectId, chat);
this.setState({ currentMessage: "" });
};
handleTaskEdit = (taskId, currentStatus) => {
const newStatus = currentStatus === "todo" ? "inprogress" : "completed";
this.props.editTask(this.projectId, taskId, newStatus);
this.emitTaskChange();
};
`
backend code:
`
const io = socket(server);
//create separate chat rooms using project id
io.on("connection", (socket) => {
socket.on("room", (room) => {
socket.join(room);
socket.in(room).on("SEND_MESSAGE", (message) => {
socket.emit("RECEIVE_MESSAGE", message);
});
socket.in(room).on("TASK_CHANGE", (data) => {
socket.emit("UPDATE_PROJECT", data);
});
});
`
found the error:
had to change the server-side code from socket.on and instead use the io object that was initialized such as io.sockets.on

JSSIP and React audio issue

So I am using jssip 3.2.10 to make calls on a React project.
The server is setup on Asterisk and CentOS.
I can make calls where the call receiver hears me well, but I can't hear their audio, nor the waiting (traditional) beep noises it should make until the call is picked up.
It does work with some sipml5/asterisk udp online tests so I feel it's on my clients side issue. I tested it on Chrome and Firefox (both latest, with the same results).
My setup
I have a helper to connect called sip.js:
const JsSIP = require('jssip')
const GLOBAL = require('../globals')
function register(user, pass, cb) {
console.log('Registering to SIP')
JsSIP.debug.disable('JsSIP:*')
const address = GLOBAL.jssip_server + ':' + GLOBAL.jssip_port
let socket = new JsSIP.WebSocketInterface('ws://' + address + '/ws')
const configuration = {
sockets: [socket],
uri: 'sip:' + user + '#' + GLOBAL.jssip_server,
authorization_user: user,
password: pass,
connection_recovery_min_interval: 3,
register: true
}
let ua = new JsSIP.UA(configuration)
ua.start()
cb(ua)
}
export {
register
}
Then on my main component I do the following:
componentDidMount() {
if(GLOBAL.jssip) {
this.props.dispatch(connecting(true))
register('***', '***', (ua) => {
this.setState({ua: ua}, () => {
this.state.ua.on("registered", () => {
this.props.dispatch(connecting(false))
this.setState({critical: false})
})
this.state.ua.on("registrationFailed", () => {
this.props.dispatch(connecting(false))
this.setState({critical: true})
})
})
})
}
}
And when I try to make a call I do the following:
doCall(number) {
this.props.dispatch(placeCall(call))
if(GLOBAL.jssip) {
let eventHandlers = {
'connecting': (e) => {
console.log('call is in progress')
this.setState({sipStatus: "connecting"})
},
'progress': (e) => {
console.log('call is in progress')
this.setState({sipStatus: "progress"})
},
'failed': (e) => {
console.log('call failed with cause: ', e)
this.setState({sipStatus: "failed"})
},
'ended': (e) => {
console.log('call ended with cause: ', e)
this.setState({sipStatus: "ended"})
},
'confirmed': (e) => {
this.setState({sipStatus: "confirmed"})
}
}
let options = {
eventHandlers: eventHandlers,
mediaConstraints: { 'audio': true, 'video': false }
}
let session = this.state.ua.call('sip:'+number+'#'+GLOBAL.jssip_server, options)
}
}
Anyone has a clue on how to fix this?
Thanks to the answer here:
How to handle audio stream in JsSIP?
I found the solution, I needed to add to the file rendering the call:
<audio ref={(audio) => {this.audioElement = audio}} id="audio-element"></audio>
And changed doCall last bit to this:
this.setState({session: this.state.ua.call('sip:'+number+'#'+GLOBAL.jssip_server, options)}, () =>{
this.state.session.connection.addEventListener('addstream', (event: any) => {
this.audioElement.srcObject = event.stream
this.audioElement.play()
})
})

Resources