How to recover SIP js Invitation Object or Session Object in React Js on page refresh - reactjs

I am implementing Audio/Video call with SIP js and Astrisk server in React JS.I was successful on creating the WebRTC Audio/Video calling. But I am facing an issue with storing the Invitation or Session Object for SIP js. Because Circular JSON data can't be stringed to store.
Assume someone has started calling and the other end got notification of calling and in that case if the page refreshed or reloaded I am unable to recover the call session to take any action(answer/ decline)
/**
* The following code is inside useState and the dependency are handled properly.
* For making it simple and sort I have just copied the required parts. */
const simpleUserDelegate = {
onCallAnswered: (session) => {
console.log(` Call answered`);
if (simpleUser) {
let remoteVideoTrack = simpleUser.getRemoteVideoTrack(session);
if (remoteVideoTrack) {
} else {
setIsAudioCall(true);
}
}
setIsCallAnswered(true);
setIsCallRecieved(false);
localStorage.setItem('isCallRecieved',null);
localStorage.setItem('callerName',null);
localStorage.setItem('callerImage',null);
setIsCallling(false);
},
onCallCreated: (session) => {
setCallSession(session);
console.log(session,` Call created`);
//console.log('session====>',JSON.stringify(session))
// localStorage.setItem('callerUserAgent',JSON.stringify(session._userAgent));
setIsCallling(true);
localStorage.getItem('callerUserAgent')
},
onCallReceived: (invitation) => {
console.log('invitation',invitation);
console.log('invitationSession',invitation.session);
setCallerActiveRoom(invitation._userAgent.options.displayRoomId);
setCallerName(invitation._userAgent.options.displayName);
setCallerImage(invitation._userAgent.options.displayImage);
localStorage.setItem('callerUserAgent',JSON.stringify(invitation.request));
console.log(` Call received`);
// dispatch(setActiveRoomId(invitation._userAgent.options.displayRoomId));
setIsCallRecieved(true);
localStorage.setItem('isCallRecieved',true);
localStorage.setItem('callerName',invitation._userAgent.options.displayName);
localStorage.setItem('callerImage',invitation._userAgent.options.displayImage);
},
onCallHangup: () => {
console.log(` Call hangup`);
setIsCallling(false);
setIsCallRecieved(false);
localStorage.setItem('isCallRecieved',null);
localStorage.setItem('callerName',null);
localStorage.setItem('callerImage',null);
setIsCallAnswered(false);
},
onCallHold: () => {
console.log(` Call hold`);
},
onRegistered: () => {
//console.log('session',session);
console.log(` Call registered`);
},
onUnregistered: () => {
console.log(` Call unregistered`);
},
onServerConnect: () => {
console.log(` server connect`);
},
onServerDisconnect: () => {
console.log(` server dis connect`);
}
};
let simpleUserOptions = {
// traceSip: false,
// logBuiltinEnabled: false,
delegate: simpleUserDelegate,
media: {
constraints: {
audio: true,
video: true
},
local: {
video: document.getElementById('localMedia')
},
remote: {
video: document.getElementById('remoteMedia'),
//audio: remoteAudioRef.current
}
},
userAgentOptions: {
logBuiltinEnabled: true,
logLevel: "debug",
authorizationPassword: password,
authorizationUsername: username,
uri: urI,
noAnswerTimeout : 30,
displayName: name,
displayImage: profileImage,
displayRoomId: `hi${displayRoomId}`
},
};
const simpleUserObj = new Web.SessionManager('wss://pbx.scinner.com:8089/ws', simpleUserOptions);
if(!simpleUserObj.isConnected()){
simpleUserObj
.connect()
.then(() => {
console.log(`${user.username} connected`);
simpleUserObj.register().then(() => {
console.log(`${user.username} registerd`);
}).catch((error) => {
alert("Failed to register.\n" + error);
});
})
.catch((error) => {
alert("Failed to connect.\n" + error);
});
setIsSARegistered(true);
setSimpleUser(simpleUserObj);
setCallerUserAgent
}else{
console.log('isconnected');
setIsSARegistered(true);
}
/**
Set calling
*/
const setCalling = (name, target) => {
simpleUser
.call(target, {
sessionDescriptionHandlerOptions: {
constraints: {
audio: true,
video: true
}
},
inviteWithoutSdp: false
}).then(() => {
console.log(`anon placed a call`);
}).catch((error) => {
console.error(`[${simpleUser.id}] failed to place call`);
console.error(error);
alert("Failed to place call.\n" + error);
});
//setIsCallling(true);
// console.log('isCallling', isCallling)
}
}
const answerCall = () => {
//callSession stored in local state
if (callSession) {
simpleUser.answer(callSession).then(() => {
console.log(`call answered`);
}).catch((error) => {
console.error(`call answered failed`);
console.error(error);
// alert("Failed to place call.\n" + error);
});
}
};

Related

Axios Spy not being called correct number of times in Jest

I have a React context I am testing that runs a single function to check for an application update. The checkForUpdate function looks like this:
async function checkForUpdate() {
if (isPlatform('capacitor')) {
const maintanenceURL =
'https://example.com/maintenance.json';
const updateURL =
'https://example.com/update.json';
try {
const maintanenceFetch: AxiosResponse<MaintanenceDataInterface> =
await axios.get(maintanenceURL);
console.log('maintain', maintanenceFetch);
if (maintanenceFetch.data.enabled) {
setUpdateMessage(maintanenceFetch.data.msg);
return;
}
const updateFetch: AxiosResponse<UpdateDataInterface> = await axios.get(
updateURL
);
console.log('updateFetch', updateFetch);
if (updateFetch.data.enabled) {
const capApp = await App.getInfo();
const capAppVersion = capApp.version;
console.log('Thi is a thinkg', capAppVersion);
if (isPlatform('android')) {
console.log('hi');
const { currentAndroid, majorMsg, minorMsg } = updateFetch.data;
const idealVersionArr = currentAndroid.split('.');
const actualVersionArr = capAppVersion.split('.');
if (idealVersionArr[0] !== actualVersionArr[0]) {
setUpdateMessage(majorMsg);
setUpdateAvailable(true);
return;
}
if (idealVersionArr[1] !== actualVersionArr[1]) {
setUpdateMessage(minorMsg);
setUpdateAvailable(true);
return;
}
} else {
const { currentIos, majorMsg, minorMsg } = updateFetch.data;
const idealVersionArr = currentIos.split('.');
const actualVersionArr = capAppVersion.split('.');
if (idealVersionArr[0] !== actualVersionArr[0]) {
setUpdateMessage(majorMsg);
setUpdateAvailable(true);
return;
}
if (idealVersionArr[1] !== actualVersionArr[1]) {
setUpdateMessage(minorMsg);
setUpdateAvailable(true);
return;
}
}
}
} catch (err) {
console.log('Error in checkForUpdate', err);
}
}
}
For some reason, in my test I wrote to test this, my axiosSpy only shows that it has been called 1 time instead of the expected 2 times. The console logs I posted for both get requests run as well. I cannot figure out what I am doing wrong.
Here is the test:
it.only('should render the update page if the fetch call to update bucket is enabled and returns a different major version', async () => {
const isPlatformSpy = jest.spyOn(ionicReact, 'isPlatform');
isPlatformSpy.mockReturnValueOnce(true).mockReturnValueOnce(true);
const appSpy = jest.spyOn(App, 'getInfo');
appSpy.mockResolvedValueOnce({
version: '0.8.0',
name: 'test',
build: '123',
id: 'r132-132',
});
const axiosSpy = jest.spyOn(axios, 'get');
axiosSpy
.mockResolvedValueOnce({
data: {
enabled: false,
msg: {
title: 'App maintenance',
msg: 'We are currently solving an issue where users cannot open the app. This should be solved by end of day 12/31/2022! Thank you for your patience 😁',
btn: 'Ok',
type: 'maintenance',
},
},
})
.mockResolvedValueOnce({
data: {
current: '1.0.0',
currentAndroid: '1.0.0',
currentIos: '2.0.0',
enabled: true,
majorMsg: {
title: 'Important App update',
msg: 'Please update your app to the latest version to continue using it. If you are on iPhone, go to the app store and search MO Gas Tax Back to update your app. The button below does not work but will in the current update!',
btn: 'Download',
type: 'major',
},
minorMsg: {
title: 'App update available',
msg: "There's a new version available, would you like to get it now?",
btn: 'Download',
type: 'minor',
},
},
});
customRender(<UpdateChild />);
expect(axiosSpy).toHaveBeenCalledTimes(2);
});

local audio is not getting stream on remote side

I am trying to make an web app with audio, video call using WebRTC.
Problem is that local audio/video working properly in my web app, but remote audio/video is not getting stream on remote side. in console there is no error. you can join room but you can't hear others audio or see video.
here's code:
useEffect(() => {
const initRoom = async () => {
socket.current = socketInit();
//Get User Audio
await captureLocalMedia();
socket.current.emit(ACTIONS.JOIN, {roomId, user});
socket.current.on(ACTIONS.ADD_PEER, handleNewPeerConnection);
async function captureLocalMedia() {
localMediaStream.current =
await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
}
async function handleNewPeerConnection({peerId, createOffer, user: newUser}) {
if(peerId in connections.current) {
return console.warn(`You are already joined with ${user.username}`)
}
var configuration = {
offerToReceiveAudio: true
}
connections.current[peerId] = new RTCPeerConnection({
iceServers: [
{
urls: "stun:stun.l.google.com:19302"
},
{
urls: "stun:stun1.l.google.com:19302"
},
{
urls: "stun:stun2.l.google.com:19302"
},
{
urls: "stun:stun3.l.google.com:19302"
},
{
urls: "stun:stun4.l.google.com:19302"
}
],
configuration: configuration
})
connections.current[peerId].ontrack = (event) => {
addNewClients(newUser, () => {
if(audioElements.current[newUser.id]) {
audioElements.current[newUser.id].srcObject = event.streams[0];
} else {
let settled = false;
const interval = setInterval(() => {
if(audioElements.current[newUser.id]) {
const [remoteStream] = event.streams;
audioElements.current[newUser.id].srcObject=remoteStream
settled = true;
}
if (settled) {
clearInterval(interval)
}
}, 600)
}
})
}
localMediaStream.current.getTracks().forEach((track) => {
connections.current[peerId].addTrack(
track,
localMediaStream.current
)
});
if(createOffer) {
const offer = await connections.current[peerId].createOffer()
await connections.current[peerId].setLocalDescription(offer)
socket.current.emit(ACTIONS.RELAY_SDP, {
peerId,
sessionDescription: offer
})
}
}
}
initRoom();
return () => {
localMediaStream.current
.getTracks()
.forEach((track) => track.stop());
socket.current.emit(ACTIONS.LEAVE, { roomId });
for (let peerId in connections.current) {
connections.current[peerId].close();
delete connections.current[peerId];
delete audioElements.current[peerId];
}
socket.current.off(ACTIONS.ADD_PEER);
}
}, [])
this is socketInit function:
import {io} from 'socket.io-client';
const socketInit = () => {
const options = {
'force new connection': true,
reconnectionAttempts: 'Infinity',
timeout: 10000,
transports: ['websocket'],
};
return io('http://localhost:5500', options)
};
export default socketInit;
You should check whether the offer's SDP contains information about media tracks. For example:
sdp v=0
o=- 4748410946812024893 2 IN IP4 127.0.0.1
............
a=sendrecv
**a=msid:Eei3sKzfsiJybxa4TYhANjGsFMuWe2lAxadS f798f673-566e-4a8e-9760-8d657d031acf**
............
a=rtpmap:126 telephone-event/8000
a=ssrc:3563088629 cname:0j/yv49mmBxgcAbW
a=ssrc:3563088629 msid:Eei3sKzfsiJybxa4TYhANjGsFMuWe2lAxadS f798f673-566e-4a8e-9760-8d657d031acf
a=ssrc:3563088629 mslabel:Eei3sKzfsiJybxa4TYhANjGsFMuWe2lAxadS
a=ssrc:3563088629 label:f798f673-566e-4a8e-9760-8d657d031acf
............
a=max-message-size:262144
If remote peer got information about media tracks and it doesn't work, then the problem is probably with the playing of HTMLMediaElement. Try to add the line:
audioElements.current[newUser.id].autoplay = true

agora.io screen-sharing web sdk getDisplayMedia

I'm using Agora to create screen-sharing action and I'm stuck not being able to get it to work on Safari/MacOS. The code below is working for Chrome and Firefox in both Mac/Windows. Every time I click on this IconButton, the error message is in the screenshot below.
What could be the issue? I've compared this to Agora's sample code, but cannot figure what caused this bug.
<IconButton color='primary' onClick={shareScreenClient ? clearScreenStream : startScreenStream}>
{shareScreenClient ? <StopScreenShareOutlinedIcon fontSize='large' /> : <ScreenShareOutlinedIcon fontSize='large' />}</IconButton>
//here's the def of startScreenStream.
const startScreenStream = (e) => {
e.preventDefault();
screenClient.current = AgoraRTC.createClient(agoraClientObj);
const channel = classroom;
const token = null;
screenClient.current.init(appID, () => {
screenClient.current.join(
token,
channel,
null,
(screenUid) => {
// Create the screen-sharing stream, screenStream.
setScreenStreamObj((x) => ({
...x,
streamId: screenUid,
}));
let screenStreamObj = {
streamID: screenUid,
audio: false,
video: false,
screen: true,
screenAudio: true,
};
if (audioDeviceId) screenStreamObj.microphoneId = audioDeviceId;
if (videoDeviceId) screenStreamObj.cameraId = videoDeviceId;
if (isFirefox) screenStreamObj.mediaSource = 'screen';
screenStream.current = AgoraRTC.createStream(screenStreamObj);
screenStream.current.setScreenProfile('480p_1');
screenStream.current.init(
() => {
screenStream.current.play('screen-share', { fit: 'cover' }, () => {
console.log('sharing screen');
setShareScreenClient(true);
});
screenClient.current.publish(screenStream.current, (err) => {
console.error('publish failed', err);
//clearScreenStream();
});
},
(err) => {
screenStream.current = null;
setScreenStreamObj({});
console.error('could not init stream', err);
}
);
},
(err) => {
screenClient.current = null;
console.error('Could not join channel', err);
}
);
});
}

React-Apollo: Recommended way of subscribing to multiple events that doesn't require UI updates

So i want to subscribe to multiple events for the current logged user.
I've extracted the subscriptions to a separate function that update my logged user state from inside and returns an array of subscriptions.
Now i wanted to know is there a different / better way of doing this ?
Is this the correct / recommended way of approaching this problem ?
Current implementation
export const subscribeToCurrentUserUpdates = (setLoggedUser) => {
const friendRequestObserver$ = apolloClient.subscribe(
{ query: queries.NEW_FRIEND_REQUEST },
);
const followersUpdatesObserver$ = apolloClient.subscribe(
{ query: queries.FOLLOWERS_UPDATES },
);
const acceptedFriendRequestObserver$ = apolloClient.subscribe(
{ query: queries.ACCEPTED_FRIEND_REQUEST },
);
const friendRequestSubscription = friendRequestObserver$.subscribe({
next: ({ data: { newFriendRequest } }) => {
Alert.success(`${newFriendRequest.username} just sent you a friend request`);
setLoggedUser((loggedUser) => {
loggedUser.incomingFriendRequests.unshift(newFriendRequest._id);
});
},
error: err => console.error(err),
});
const followersUpdatesSubscription = followersUpdatesObserver$.subscribe({
next: ({ data: { followersUpdates: { follower, isFollow } } }) => {
if (isFollow) {
Alert.success(`${follower.username} is now following you`);
}
setLoggedUser((loggedUser) => {
isFollow
? loggedUser.followers.unshift(follower._id)
: loggedUser.followers.splice(loggedUser.followers.indexOf(follower._id), 1);
});
},
error: err => console.error(err),
});
const acceptedFriendRequestSubscription = acceptedFriendRequestObserver$.subscribe({
next: ({ data: { acceptedFriendRequest: newFriend } }) => {
Alert.success(`${newFriend.username} just accepted your friend request!`);
setLoggedUser((loggedUser) => {
loggedUser.friends.push(newFriend._id);
loggedUser.sentFriendRequests.splice(
loggedUser.sentFriendRequests.indexOf(newFriend._id), 1,
);
});
},
error: err => console.error(err),
});
return [
friendRequestSubscription,
followersUpdatesSubscription,
acceptedFriendRequestSubscription,
];
};
The way i subscribe from my component
const App = () => {
const currentUserSubscriptionRef = useRef();
useEffect(() => {
if (loggedUser && !currentUserSubscriptionRef.current) {
currentUserSubscriptionRef.current = subscribeToCurrentUserUpdates(
setLoggedUser,
);
}
if (!loggedUser && currentUserSubscriptionRef.current) {
currentUserSubscriptionRef.current.forEach((subscription) => {
subscription.unsubscribe();
});
currentUserSubscriptionRef.current = null;
}
}, [loggedUser, setLoggedUser]);
}

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