diff --git a/react-native-webrtc-app/client/App.js b/react-native-webrtc-app/client/App.js index bb7ebd0..445ba4c 100644 --- a/react-native-webrtc-app/client/App.js +++ b/react-native-webrtc-app/client/App.js @@ -8,11 +8,10 @@ import { Text, TouchableOpacity, StyleSheet, - DeviceEventEmitter, + TextInput, } from 'react-native'; import * as Animatable from 'react-native-animatable'; import TextInputContainer from './components/TextInputContainer'; -import SocketIOClient from 'socket.io-client'; import { mediaDevices, RTCPeerConnection, @@ -32,52 +31,74 @@ import IconContainer from './components/IconContainer'; import InCallManager from 'react-native-incall-manager'; import Logo from './asset/Logo'; import * as JsSIP from 'react-native-jssip'; -import EventEmitter from 'react-native/Libraries/vendor/emitter/EventEmitter'; export default function App({}) { - const eventoSip = new EventEmitter(); + const [session, setSession] = useState(null); + const [ua, setUa] = useState(null); + const [screen, setScreen] = useState('JOIN'); + const [incomingCaller, setIncomingCaller] = useState(null); + const [calle, setCalle] = useState(null); + const [myRamal, setMyRamal] = useState(null); + const [authData, setAuthData] = useState({ + protocolo: 'ws', + servidor: '192.168.115.179', + porta: '8088', + username: 'Matheo', + ramal: '1100', + senha: 'SIP1100', + }); + const [localMicOn, setlocalMicOn] = useState(true); + const [localWebcamOn, setlocalWebcamOn] = useState(false); + const [localStream, setLocalStream] = useState(null); + const [remoteStream, setRemoteStream] = useState(null); + const remoteAudio = useRef(null); + const localAudio = useRef(null); - function Autenticacao(PROTOCOLO, SERVIDOR, PORTA, NOME, RAMAL, SENHA) { - this.PROTOCOLO = 'ws'; - this.SERVIDOR = '192.168.115.179'; - this.PORTA = '8088'; - this.NOME = 'matheo'; - this.RAMAL = '1100'; - this.SENHA = 'SIP1100'; - } + class EventEmitter { + constructor() { + this.events = {}; + } - const _Autenticacao = new Autenticacao(); + on(eventName, listener) { + if (!this.events[eventName]) { + this.events[eventName] = []; + } + this.events[eventName].push(listener); + } - let phone; - let session; + emit(eventName, ...args) { + if (this.events[eventName]) { + this.events[eventName].forEach(listener => listener(...args)); + } + } - function iniciandoAutenticacaonoPBX() { + off(eventName, listener) { + if (this.events[eventName]) { + this.events[eventName] = this.events[eventName].filter( + fn => fn !== listener, + ); + } + } + } + const eventoSip = new EventEmitter(); + const {protocolo, servidor, porta, username, ramal, senha} = authData; + + useEffect(() => { + setMyRamal(ramal); const socket = new JsSIP.WebSocketInterface( - _Autenticacao.PROTOCOLO + - '://' + - _Autenticacao.SERVIDOR + - ':' + - _Autenticacao.PORTA + - '/ws', + protocolo + '://' + servidor + ':' + porta + '/ws', ); const configuration = { - uri: 'sip:' + _Autenticacao.RAMAL + '@' + _Autenticacao.SERVIDOR, - password: _Autenticacao.SENHA, + uri: 'sip:' + ramal + '@' + servidor, + password: senha, sockets: [socket], session_timers: false, no_answer_timeout: 180, hack_via_tcp: false, hack_via_ws: true, - display_name: - _Autenticacao.NOME !== null ? _Autenticacao.NOME : _Autenticacao.RAMAL, - user_agent: 'UASimpleSIP 1.2.0', - contact_uri: - 'sip:' + - _Autenticacao.RAMAL + - '@' + - _Autenticacao.SERVIDOR + - ';transport=' + - _Autenticacao.PROTOCOLO, + display_name: username !== null ? username : ramal, + user_agent: 'Softphone React Native', + contact_uri: 'sip:' + ramal + '@' + servidor + ';transport=' + protocolo, pcConfig: { iceServers: [ { @@ -92,532 +113,244 @@ export default function App({}) { ], }, }; - if (configuration.uri && configuration.password) { - phone = new JsSIP.UA(configuration); - phone.on('registrationFailed', function (ev) { - console.log('Registering on SIP server failed with error: ' + ev.cause); - configuration.uri = null; - configuration.password = null; - }); + const phone = new JsSIP.UA(configuration); - phone.on('newRTCSession', function (ev) { - const newSession = ev.session; - - session = newSession; - - eventoSip.on('endCall', () => { - if (session) { - eventoSip.emit('confirmedEnded'); - session.terminate(); - } - }); + phone.on('registrationFailed', function (ev) { + console.log('Registering on SIP server failed with error: ' + ev.cause); + configuration.uri = null; + configuration.password = null; + }); - eventoSip.on('evento', payload => { - console.log(payload); - }); - eventoSip.on('mute', () => { - session.mute(); - console.log(session?.isMuted()); - }); + phone.on('newRTCSession', ev => { + const newSession = ev.session; - eventoSip.on('unmute', () => { - session.unmute(); - console.log(session?.isMuted()); - }); + /////////////////////////////////////////////////// + eventoSip.on('mute', () => { + session.mute(); + console.log(session?.isMuted()); + }); - eventoSip.on('enviarDTMF', data => { - session.sendDTMF(data); - }); + eventoSip.on('unmute', () => { + session.unmute(); + console.log(session?.isMuted()); + }); - eventoSip.on('transferir', data => { - session.refer(data); - }); + eventoSip.on('enviarDTMF', data => { + session.sendDTMF(data); + }); - session.on('candidate', event => { - console.log(event); + eventoSip.on('transferir', data => { + session.refer(data); + }); + /////////////////////////////////////////////////// + + //RECEBENDO UMA CHAMADA + if (newSession.direction === 'incoming') { + setScreen('INCOMING_CALL'); + setIncomingCaller(newSession.remote_identity.uri.user); + setSession(newSession); + // Set InCallManager settings when a call starts + InCallManager.start({media: 'audio'}); + InCallManager.setForceSpeakerphoneOn(true); + + //Quando finalizar a chamada + newSession.on('ended', () => { + InCallManager.stop(); + setSession(null); + setIncomingCaller(null); + setScreen('JOIN'); }); - session.on('ended', () => { - endCallAudio.play(); - eventoSip.emit('confirmedEnded'); + //QUANDO UMA CHAMADA APRESENTAR PROBLEMAS + newSession.on('failed', () => { + InCallManager.stop(); + setSession(null); + setIncomingCaller(null); + setScreen('JOIN'); }); + } else { + setSession(newSession); - session.on('newDTMF', function (event) { - console.log('DTMF recebido:', event.dtmf.tone); + //Quando finalizar a chamada + newSession.on('ended', () => { + InCallManager.stop(); + setSession(null); }); - //QUANDO UMA CHAMADA É REJEITADA - session.on('failed', () => { - incomingCallAudio.pause(); - outgoingCallAudio.pause(); - eventoSip.emit('home'); + //QUANDO UMA CHAMADA APRESENTAR PROBLEMAS + newSession.on('failed', () => { + InCallManager.stop(); + setSession(null); }); + } - session.on('confirmed', function (confirmed) { - // Verifica se session.connection está definido - if ( - session.connection && - session.connection.getRemoteStreams().length > 0 - ) { - const remoteStreams = session.connection.getRemoteStreams()[0]; - remoteAudio.srcObject = remoteStreams; - remoteAudio.volume = 1; - } + newSession.on('peerconnection', e => { + const peerconnection = e.peerconnection; - // Verifica se session.connection está definido e se existem streams locais - if ( - session.connection && - session.connection.getLocalStreams().length > 0 - ) { - const localStreams = session.connection.getLocalStreams()[0]; - localAudio.srcObject = localStreams; - localAudio.volume = 0; - } - //PAUSA TODOS OS AUDIOS DE FEEDBACK - incomingCallAudio.pause(); - outgoingCallAudio.pause(); + //////////////Media/////////START////// + if (peerconnection) { + peerconnection.addEventListener('addstream', event => { + console.log('Remote stream added:', event.stream); + setRemoteStream(event.stream); + }); + } + //////////////Media/////////END//////// - eventoSip.emit('incall'); - }); - session.on('icecandidate', function (event) { - if ( - event.candidate.type === 'srflx' && - event.candidate.relatedAddress !== null && - event.candidate.relatedPort !== null - ) { - event.ready(); + peerconnection.addEventListener('icecandidate', event => { + if (event.candidate) { + console.log('Have NEW ICE Candidate: ', event.candidate); } }); - session.on('addstream', function (e) { - // Verifica se session.connection está definido - if (session.connection) { - remoteAudio.srcObject = e.stream; - remoteAudio.play(); - } + peerconnection.addEventListener('iceconnectionstatechange', () => { + console.log( + 'ICE connection state change: ', + peerconnection.iceConnectionState, + ); }); - - //RECEBENDO UMA CHAMADA - if (session.direction === 'incoming') { - incomingCallAudio.play(); - eventoSip.emit('incomingcall', session.remote_identity.uri.user); - - eventoSip.on('rejected', () => { - if (session?.isInProgress()) { - incomingCallAudio.pause(); - eventoSip.emit('home'); - session.terminate(); - session = null; - } else { - eventoSip.emit('home'); - } - }); - - eventoSip.on('accepted', () => { - if (session?.isInProgress()) { - session.answer(); - incomingCallAudio.pause(); - } - }); - } - //REALIZANDO UMA CHAMADA - if (session.direction === 'outgoing') { - eventoSip.emit('outgoingcall', session.remote_identity.uri.user); - if (session.isInProgress()) { - outgoingCallAudio.play(); - } - } - }); - phone.on('registered', function (e) { - eventoSip.emit('statusChange', 'registered'); - }); - - phone.on('unregistered', function (e) { - eventoSip.emit('statusChange', 'unregistered'); }); - phone.on('registrationFailed', function (e) { - eventoSip.emit('statusChange', 'registrationFailed'); + newSession.on('newDTMF', function (event) { + console.log('DTMF recebido:', event.dtmf.tone); }); - phone.on('connected', function (e) { - eventoSip.emit('statusChange', 'connected'); + newSession.on('confirmed', function (confirmed) { + setScreen('WEBRTC_ROOM'); }); - - phone.on('disconnected', function (e) { - eventoSip.emit('statusChange', 'disconnected'); + newSession.on('icecandidate', function (event) { + if ( + event.candidate.type === 'srflx' && + event.candidate.relatedAddress !== null && + event.candidate.relatedPort !== null + ) { + event.ready(); + } }); - phone.start(); - } - } - useEffect(() => { - iniciandoAutenticacaonoPBX(); - }, []); - - const [localStream, setlocalStream] = useState(null); - const [remoteStream, setRemoteStream] = useState(null); - const [isSpeakerOn, setIsSpeakerOn] = useState(false); - const [type, setType] = useState('JOIN'); - const [callerId] = useState( - Math.floor(100000 + Math.random() * 900000).toString(), - ); - const otherUserId = useRef(null); - const socket = SocketIOClient('http://129.148.58.190:8088', { - transports: ['websocket'], - query: { - callerId, - }, - reconnectionAttempts: 3, - reconnectionDelay: 1000, - }); - const [localMicOn, setlocalMicOn] = useState(true); - const [localWebcamOn, setlocalWebcamOn] = useState(true); - const [iniciarChamada, setIniciarChamada] = useState(false); - let remoteRTCMessage = useRef(null); - const peerConnection = useRef(null); - - const createPeerConnection = () => { - return new RTCPeerConnection({ - iceServers: [ - { - urls: 'stun:stun.l.google.com:19302', - }, - { - urls: 'stun:stun1.l.google.com:19302', - }, - {urls: 'stun:stun2.l.google.com:19302'}, - ], - }); - }; - - const initializePeerConnection = async () => { - try { - peerConnection.current = createPeerConnection(); - console.log('PeerConnection initialized successfully.'); - } catch (error) { - console.error('Error initializing PeerConnection:', error); - } - }; - - //Função para limpar todos os listeners do socket - const cleanUp = () => { - try { - if (peerConnection.current) { - peerConnection.current.close(); - } - // Fechar o socket e remover os listeners - socket.off('newCall'); - socket.off('callAnswered'); - socket.off('ICEcandidate'); - socket.close(); - - // Parar o InCallManager e fechar a conexão Peer - InCallManager.stop(); - - // Parar e remover os fluxos de mídia - if (localStream) { - localStream.getTracks().forEach(track => track.stop()); - } - - // Limpar o estado localStream e remoteRTCMessage - setlocalStream(null); - remoteRTCMessage.current = null; - - if (peerConnection.current) { - peerConnection.current.onconnectionstatechange = function () { - console.log('--->' + peerConnection.current.connectionState); - }; - } else { - console.error( - 'O objeto peerConnection.current não está definido ou é nulo.', - ); + //REALIZANDO UMA CHAMADA + if (newSession.direction === 'outgoing') { + setScreen('OUTGOING_CALL'); } - console.log('Limpeza concluída com sucesso.'); - } catch (error) { - console.error('Erro durante a limpeza:', error); - // Lidar com o erro de limpeza aqui, se necessário - } - }; - - const socketConfig = () => { - console.log('Iniciando a conexão com o socket'); - - socket.on('newCall', data => { - console.log('Received new call'); - remoteRTCMessage.current = data.rtcMessage; - otherUserId.current = data.callerId; - setType('INCOMING_CALL'); }); - - socket.on('callAnswered', data => { - console.log('Call answered'); - remoteRTCMessage.current = data.rtcMessage; - try { - peerConnection.current.setRemoteDescription( - new RTCSessionDescription(remoteRTCMessage.current), - ); - setType('WEBRTC_ROOM'); - } catch (error) { - console.log('Erro ao atender chamada: ', error); - } + phone.on('registered', function (e) { + eventoSip.emit('statusChange', 'registered'); }); - const closePeerConnection = () => { - if (peerConnection.current) { - console.log('Closing peer connection'); - peerConnection.current.close(); - peerConnection.current = null; - } - }; - - // No cliente, ouvir o evento "endCallAndLeaveRoom" do servidor - socket.on('endCallAndLeaveRoom', () => { - // Deixar a sala após encerrar a chamada - socket.emit('leaveRoom'); // Envia um evento para o servidor para deixar a sala - closePeerConnection(); - setlocalStream(null); - setType('JOIN'); + phone.on('unregistered', function (e) { + eventoSip.emit('statusChange', 'unregistered'); }); - // Lógica para sair da sala no cliente - socket.on('leaveRoom', () => { - socket.leave(socket.user); // Deixa a sala com o mesmo nome do usuário - console.log(`${socket.user} left the conference room.`); + phone.on('registrationFailed', function (e) { + eventoSip.emit('statusChange', 'registrationFailed'); }); - socket.on('ICEcandidate', data => { - let message = data.rtcMessage; - - if (peerConnection.current) { - peerConnection.current - .addIceCandidate( - new RTCIceCandidate({ - candidate: message.candidate, - sdpMid: message.id, - sdpMLineIndex: message.label, - }), - ) - .then(data => { - console.log('Added ICE candidate successfully:', message); - }) - .catch(err => { - console.log('Error adding ICE candidate:', err); - }); - } + phone.on('connected', function (e) { + eventoSip.emit('statusChange', 'connected'); }); - }; - - const mediaConfig = async () => { - try { - const devices = await mediaDevices.enumerateDevices(); - let isFront = true; - let videoSourceId = null; - - devices.forEach(device => { - if ( - device.kind === 'videoinput' && - device.facingMode === (isFront ? 'user' : 'environment') - ) { - videoSourceId = device.deviceId; - } - }); - - const constraints = { - audio: true, - video: false, - // video: { - // mandatory: { - // minWidth: 500, - // minHeight: 300, - // minFrameRate: 30, - // }, - // facingMode: isFront ? 'user' : 'environment', - // }, - }; - - if (videoSourceId) { - constraints.video.optional = [{sourceId: videoSourceId}]; - } - const stream = await mediaDevices.getUserMedia(constraints); - setlocalStream(stream); - - peerConnection.current.addStream(stream); - - peerConnection.current.onaddstream = event => { - setRemoteStream(event.stream); - }; - } catch (error) { - console.error('Error accessing media devices:', error); - } - }; + phone.on('disconnected', function (e) { + eventoSip.emit('statusChange', 'disconnected'); + }); + phone.start(); + setUa(phone); - const handleIniciarChamada = async () => { - setIniciarChamada(true); - await initializeApp(); // Aguarda a conclusão da inicialização - setType('OUTGOING_CALL'); - await processCall(); - }; + return () => { + InCallManager.stop(); + phone.stop(); + }; + }, []); const handleAceitarChamada = async () => { - setType('WEBRTC_ROOM'); - await processAccept(); - }; - - async function initializeApp() { - try { - await initializePeerConnection(); - await mediaConfig(); - socketConfig(); - - peerConnection.current.addEventListener('signalingstatechange', event => { - console.log( - 'Signaling state changed:', - event.type, - ':', - peerConnection.current.signalingState, - ); + if (session) { + session.answer({ + mediaConstraints: { + audio: true, + video: false, + }, }); - - setIniciarChamada(false); - } catch (error) { - console.error('Erro durante a inicialização:', error); - // Trate o erro conforme necessário - } - } - - useEffect(() => { - initializeApp(); - setIniciarChamada(false); - }, []); - - useEffect(() => { - if (iniciarChamada) { - initializeApp(); - setIniciarChamada(false); + setScreen('WEBRTC_ROOM'); } - }, [iniciarChamada]); - - //useEffect para iniciar o gerenciamento de chamadas - useEffect(() => { - const callOptions = { - media: 'audioVideo', - }; - InCallManager.start(callOptions); - InCallManager.setKeepScreenOn(true); - InCallManager.setForceSpeakerphoneOn(true); + }; - setIniciarChamada(false); - return () => { + const handleRejectCall = () => { + if (session) { + session.terminate(); + setSession(null); + setIncomingCaller(null); InCallManager.stop(); - }; - }, [iniciarChamada]); - - function sendICEcandidate(data) { - socket.emit('ICEcandidate', data); - } - - async function processCall() { - console.log('Processing call...'); - InCallManager.startRingback(); - const sessionDescription = await peerConnection.current.createOffer(); - await peerConnection.current.setLocalDescription(sessionDescription); - sendCall({ - calleeId: otherUserId.current, - rtcMessage: sessionDescription, - }); - } - - async function processAccept() { - console.log('Processing call acceptance...'); - try { - await peerConnection.current.setRemoteDescription( - new RTCSessionDescription(remoteRTCMessage.current), - ); - - const sessionDescription = await peerConnection.current.createAnswer(); - await peerConnection.current.setLocalDescription(sessionDescription); - - answerCall({ - callerId: otherUserId.current, - rtcMessage: sessionDescription, - }); + } + }; - // Adicionar evento onicecandidate após a criação da resposta local - peerConnection.current.onicecandidate = event => { - if (event.candidate) { - // Enviar ICE candidate apenas quando estiver disponível - sendICEcandidate({ - calleeId: otherUserId.current, - rtcMessage: { - label: event.candidate.sdpMLineIndex, - id: event.candidate.sdpMid, - candidate: event.candidate.candidate, - }, - }); - } else { - console.log('End of candidates.'); - } + const handleIniciarChamada = () => { + console.log('AQUI:::::::' + calle); + if (ua && calle) { + const eventHandlers = { + progress: () => { + console.log('Call is in progress'); + }, + failed: e => { + console.log('Call failed with cause: ' + e.cause); + InCallManager.stop(); + }, + ended: () => { + console.log('Call ended'); + InCallManager.stop(); + }, + confirmed: () => { + console.log('Call confirmed'); + InCallManager.setForceSpeakerphoneOn(true); + }, }; - } catch (error) { - console.error('Error processing call acceptance:', error); - // Tratar o erro conforme necessário - } - } - function answerCall(data) { - socket.emit('answerCall', data); - } + const options = { + eventHandlers: eventHandlers, + mediaConstraints: { + audio: true, + video: false, + }, + rtcOfferContraints: { + offerToReceiveAudio: true, + offerToReceiveVideo: false, + }, + }; - function sendCall(data) { - socket.emit('call', data); - } + const newSession = ua.call(`sip:${calle}@${servidor}`, options); + console.log('URI: ' + `sip:${calle}@${servidor}`); + setSession(newSession); - function endCall(data) { - socket.emit('endCall', data); - } + InCallManager.start({media: 'audio'}); + } + }; - function switchCamera() { - localStream.getVideoTracks().forEach(track => { - track._switchCamera(); + const toggleMic = () => { + console.log('Toggling microphone...'); + localMicOn ? setlocalMicOn(false) : setlocalMicOn(true); + localStream.getAudioTracks().forEach(track => { + localMicOn ? (track.enabled = false) : (track.enabled = true); }); - } + }; - function toggleCamera() { + const toggleCamera = () => { console.log('Toggling camera...'); localWebcamOn ? setlocalWebcamOn(false) : setlocalWebcamOn(true); localStream.getVideoTracks().forEach(track => { localWebcamOn ? (track.enabled = false) : (track.enabled = true); }); - } - - function toggleMic() { - console.log('Toggling microphone...'); - localMicOn ? setlocalMicOn(false) : setlocalMicOn(true); - localStream.getAudioTracks().forEach(track => { - localMicOn ? (track.enabled = false) : (track.enabled = true); - }); - } - - function toggleSpeaker() { - console.log('Toggling speaker...'); - setIsSpeakerOn(!isSpeakerOn); - InCallManager.setSpeakerphoneOn(!isSpeakerOn); - } + }; - function cancelCall() { - setType('JOIN'); - InCallManager.stopRingback(); - otherUserId.current = null; - } + // useEffect(() => { + // if (remoteStream && remoteAudio.current) { + // remoteAudio.current = remoteStream; + // remoteAudio.current.play(); + // } + // }, [remoteStream]); - function leave() { - // Emitir o evento 'endCall' para o servidor - endCall({otherPeer: otherUserId.current}); - setType('JOIN'); - } + // useEffect(() => { + // if (localAudio.current && localStream) { + // localAudio.current = localStream; + // } + // }, [localStream]); const styles = StyleSheet.create({ container: { @@ -684,7 +417,7 @@ export default function App({}) { Seu ID para ligação - {callerId} + {myRamal} @@ -693,10 +426,9 @@ export default function App({}) { Ligação { - otherUserId.current = text; - console.log('TEST', otherUserId.current); + setCalle(text); }} keyboardType={'number-pad'} /> @@ -744,7 +476,7 @@ export default function App({}) { color: '#ffff', letterSpacing: 6, }}> - {otherUserId.current} + {calle} - Recebendo ligação de {otherUserId.current} + Recebendo ligação de {incomingCaller} { - cancelCall(); + handleRejectCall(); }}> =6" } }, + "node_modules/react-native-sound": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/react-native-sound/-/react-native-sound-0.11.2.tgz", + "integrity": "sha512-LmGc8lgOK3qecYMVQpyHvww/C+wgT6sWeMpVbOe4NCRGC2yKd4fo4U0KBUo9PO7AqKESO3I/2GZg1/C0+bwiiA==", + "peerDependencies": { + "react-native": ">=0.8.0" + } + }, "node_modules/react-native-svg": { "version": "13.7.0", "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.7.0.tgz", diff --git a/react-native-webrtc-app/client/package.json b/react-native-webrtc-app/client/package.json index 2d1e753..81ea7d0 100644 --- a/react-native-webrtc-app/client/package.json +++ b/react-native-webrtc-app/client/package.json @@ -17,6 +17,7 @@ "react-native-animatable": "^1.4.0", "react-native-incall-manager": "^4.0.1", "react-native-jssip": "^3.7.6", + "react-native-sound": "^0.11.2", "react-native-svg": "^13.7.0", "react-native-webrtc": "^1.94.2", "socket.io-client": "^4.5.4" diff --git a/react-native-webrtc-app/client/yarn.lock b/react-native-webrtc-app/client/yarn.lock index d5098f4..8a13b52 100644 --- a/react-native-webrtc-app/client/yarn.lock +++ b/react-native-webrtc-app/client/yarn.lock @@ -7233,6 +7233,11 @@ react-native-jssip@^3.7.6: react-native-webrtc "^1.84.0" sdp-transform "^2.14.1" +react-native-sound@^0.11.2: + version "0.11.2" + resolved "https://registry.npmjs.org/react-native-sound/-/react-native-sound-0.11.2.tgz" + integrity sha512-LmGc8lgOK3qecYMVQpyHvww/C+wgT6sWeMpVbOe4NCRGC2yKd4fo4U0KBUo9PO7AqKESO3I/2GZg1/C0+bwiiA== + react-native-svg@^13.7.0: version "13.7.0" resolved "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.7.0.tgz" @@ -7251,7 +7256,7 @@ react-native-webrtc@^1.84.0, react-native-webrtc@^1.94.2: event-target-shim "6.0.2" tar "6.1.11" -react-native@*, react-native@>=0.40.0, react-native@>=0.60.0, react-native@0.68.2: +react-native@*, react-native@>=0.40.0, react-native@>=0.60.0, react-native@>=0.8.0, react-native@0.68.2: version "0.68.2" resolved "https://registry.npmjs.org/react-native/-/react-native-0.68.2.tgz" integrity sha512-qNMz+mdIirCEmlrhapAtAG+SWVx6MAiSfCbFNhfHqiqu1xw1OKXdzIrjaBEPihRC2pcORCoCHduHGQe/Pz9Yuw==