From c25c1e6a2f83bd50676bd040f9ddb7199f47dbae Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Wed, 19 Apr 2017 17:34:11 -0300 Subject: [PATCH 01/11] convert webrtc still need to add the coments --- .../{ => client}/WebRTCClass.coffee | 4 +- .../rocketchat-webrtc/client/WebRTCClass.js | 1006 +++++++++++++++++ .../rocketchat-webrtc/{ => client}/adapter.js | 0 .../{ => client}/screenShare.coffee | 0 .../rocketchat-webrtc/client/screenShare.js | 31 + packages/rocketchat-webrtc/package.js | 14 +- packages/rocketchat-webrtc/server/settings.js | 22 + 7 files changed, 1069 insertions(+), 8 deletions(-) rename packages/rocketchat-webrtc/{ => client}/WebRTCClass.coffee (99%) create mode 100644 packages/rocketchat-webrtc/client/WebRTCClass.js rename packages/rocketchat-webrtc/{ => client}/adapter.js (100%) rename packages/rocketchat-webrtc/{ => client}/screenShare.coffee (100%) create mode 100644 packages/rocketchat-webrtc/client/screenShare.js create mode 100644 packages/rocketchat-webrtc/server/settings.js diff --git a/packages/rocketchat-webrtc/WebRTCClass.coffee b/packages/rocketchat-webrtc/client/WebRTCClass.coffee similarity index 99% rename from packages/rocketchat-webrtc/WebRTCClass.coffee rename to packages/rocketchat-webrtc/client/WebRTCClass.coffee index be46fdb70b83..aefe207996ae 100644 --- a/packages/rocketchat-webrtc/WebRTCClass.coffee +++ b/packages/rocketchat-webrtc/client/WebRTCClass.coffee @@ -195,8 +195,10 @@ class WebRTCClass itemsById = {} for id, peerConnection of @peerConnections + console.log peerConnection for remoteStream in peerConnection.getRemoteStreams() - item = + console.log peerConnection.getRemoteStreams() + item = peerConnection.getRemoteStreams() id: id url: URL.createObjectURL(remoteStream) state: peerConnection.iceConnectionState diff --git a/packages/rocketchat-webrtc/client/WebRTCClass.js b/packages/rocketchat-webrtc/client/WebRTCClass.js new file mode 100644 index 000000000000..57ef29c0943d --- /dev/null +++ b/packages/rocketchat-webrtc/client/WebRTCClass.js @@ -0,0 +1,1006 @@ +/* globals chrome, ChromeScreenShare */ +class WebRTCTransportClass { + constructor(webrtcInstance) { + this.debug = false; + this.webrtcInstance = webrtcInstance; + this.callbacks = {}; + RocketChat.Notifications.onRoom(this.webrtcInstance.room, 'webrtc', (type, data) => { + const onRemoteStatus = this.callbacks['onRemoteStatus']; + this.log('WebRTCTransportClass - onRoom', type, data); + switch (type) { + case 'status': + if (onRemoteStatus && onRemoteStatus.length) { + onRemoteStatus.forEach((fn) => fn(data)); + } + } + }); + } + + log() { + if (this.debug === true) { + return console.log.apply(console, arguments); + } + } + + onUserStream(type, data) { + if (data.room !== this.webrtcInstance.room) { + return; + } + this.log('WebRTCTransportClass - onUser', type, data); + console.log(this.callbacks); + const onRemoteCall = this.callbacks['onRemoteCall']; + const onRemoteJoin = this.callbacks['onRemoteJoin']; + const onRemoteCandidate = this.callbacks['onRemoteCandidate']; + const onRemoteDescription = this.callbacks['onRemoteDescription']; + + switch (type) { + case 'call': + if (onRemoteCall && onRemoteCall.length) { + onRemoteCall.forEach((fn) => { + fn(data); + }); + } + break; + case 'join': + if (onRemoteJoin && onRemoteJoin.length) { + onRemoteJoin.forEach((fn) => fn(data)); + } + break; + case 'candidate': + if (onRemoteCandidate && onRemoteCandidate.length) { + onRemoteCandidate.forEach((fn) => fn(data)); + } + break; + case 'description': + if (onRemoteDescription && onRemoteDescription.length) { + onRemoteDescription.forEach((fn) => fn(data)); + } + } + } + + startCall(data) { + this.log('WebRTCTransportClass - startCall', this.webrtcInstance.room, this.webrtcInstance.selfId); + return RocketChat.Notifications.notifyUsersOfRoom(this.webrtcInstance.room, 'webrtc', 'call', { + from: this.webrtcInstance.selfId, + room: this.webrtcInstance.room, + media: data.media, + monitor: data.monitor + }); + } + + joinCall(data) { + this.log('WebRTCTransportClass - joinCall', this.webrtcInstance.room, this.webrtcInstance.selfId); + if (data.monitor === true) { + return RocketChat.Notifications.notifyUser(data.to, 'webrtc', 'join', { + from: this.webrtcInstance.selfId, + room: this.webrtcInstance.room, + media: data.media, + monitor: data.monitor + }); + } else { + return RocketChat.Notifications.notifyUsersOfRoom(this.webrtcInstance.room, 'webrtc', 'join', { + from: this.webrtcInstance.selfId, + room: this.webrtcInstance.room, + media: data.media, + monitor: data.monitor + }); + } + } + + sendCandidate(data) { + data.from = this.webrtcInstance.selfId; + data.room = this.webrtcInstance.room; + this.log('WebRTCTransportClass - sendCandidate', data); + return RocketChat.Notifications.notifyUser(data.to, 'webrtc', 'candidate', data); + } + + sendDescription(data) { + data.from = this.webrtcInstance.selfId; + data.room = this.webrtcInstance.room; + this.log('WebRTCTransportClass - sendDescription', data); + return RocketChat.Notifications.notifyUser(data.to, 'webrtc', 'description', data); + } + + sendStatus(data) { + this.log('WebRTCTransportClass - sendStatus', data, this.webrtcInstance.room); + data.from = this.webrtcInstance.selfId; + return RocketChat.Notifications.notifyRoom(this.webrtcInstance.room, 'webrtc', 'status', data); + } + + onRemoteCall(fn) { + const callbacks = this.callbacks; + if (callbacks['onRemoteCall'] == null) { + callbacks['onRemoteCall'] = []; + } + return callbacks['onRemoteCall'].push(fn); + } + + onRemoteJoin(fn) { + const callbacks = this.callbacks; + if (callbacks['onRemoteJoin'] == null) { + callbacks['onRemoteJoin'] = []; + } + return callbacks['onRemoteJoin'].push(fn); + } + + onRemoteCandidate(fn) { + const callbacks = this.callbacks; + if (callbacks['onRemoteCandidate'] == null) { + callbacks['onRemoteCandidate'] = []; + } + return callbacks['onRemoteCandidate'].push(fn); + } + + onRemoteDescription(fn) { + const callbacks = this.callbacks; + if (callbacks['onRemoteDescription'] == null) { + callbacks['onRemoteDescription'] = []; + } + return callbacks['onRemoteDescription'].push(fn); + } + + onRemoteStatus(fn) { + const callbacks = this.callbacks; + if (callbacks['onRemoteStatus'] == null) { + callbacks['onRemoteStatus'] = []; + } + return callbacks['onRemoteStatus'].push(fn); + } + + + +} + +class WebRTCClass { + /* + @param seldId {String} + @param room {String} + */ + + constructor(selfId, room) { + this.config = { + iceServers: [] + }; + this.debug = false; + this.TransportClass = WebRTCTransportClass; + this.selfId = selfId; + this.room = room; + this.config.iceServers = []; + let servers = RocketChat.settings.get('WebRTC_Servers'); + if (servers && servers.trim() !== '') { + servers = servers.replace(/\s/g, ''); + servers = servers.split(','); + + servers.forEach((server) => { + server = server.split('@'); + const serverConfig = { + urls: server.pop() + }; + if (server.length === 1) { + server = server[0].split(':'); + serverConfig.username = decodeURIComponent(server[0]); + serverConfig.credential = decodeURIComponent(server[1]); + } + this.config.iceServers.push(serverConfig); + }); + } + this.peerConnections = {}; + this.remoteItems = new ReactiveVar([]); + this.remoteItemsById = new ReactiveVar({}); + this.callInProgress = new ReactiveVar(false); + this.audioEnabled = new ReactiveVar(true); + this.videoEnabled = new ReactiveVar(true); + this.overlayEnabled = new ReactiveVar(false); + this.screenShareEnabled = new ReactiveVar(false); + this.localUrl = new ReactiveVar; + this.active = false; + this.remoteMonitoring = false; + this.monitor = false; + this.autoAccept = false; + this.navigator = undefined; + const userAgent = navigator.userAgent.toLocaleLowerCase(); + + if (userAgent.indexOf('electron') !== -1) { + this.navigator = 'electron'; + } else if (userAgent.indexOf('chrome') !== -1) { + this.navigator = 'chrome'; + } else if (userAgent.indexOf('firefox') !== -1) { + this.navigator = 'firefox'; + } else if (userAgent.indexOf('safari') !== -1) { + this.navigator = 'safari'; + } + const nav = this.navigator; + this.screenShareAvailable = nav === 'chrome' || nav === 'firefox' || nav === 'electron'; + this.media = { + video: false, + audio: true + }; + this.transport = new this.TransportClass(this); + this.transport.onRemoteCall(this.onRemoteCall.bind(this)); + this.transport.onRemoteJoin(this.onRemoteJoin.bind(this)); + this.transport.onRemoteCandidate(this.onRemoteCandidate.bind(this)); + this.transport.onRemoteDescription(this.onRemoteDescription.bind(this)); + this.transport.onRemoteStatus(this.onRemoteStatus.bind(this)); + Meteor.setInterval(this.checkPeerConnections.bind(this), 1000); + } + + log() { + if (this.debug === true) { + return console.log.apply(console, arguments); + } + } + + onError() { + return console.error.apply(console, arguments); + } + + checkPeerConnections() { + const peerConnections = this.peerConnections; + Object.keys(peerConnections).forEach((id) => { + const peerConnection = peerConnections[id]; + if (peerConnection.iceConnectionState !== 'connected' && peerConnection.iceConnectionState !== 'completed' && peerConnection.createdAt + 5000 < Date.now()) { + this.stopPeerConnection(id); + } + }); + } + + updateRemoteItems() { + const items = []; + const itemsById = {}; + const peerConnections = this.peerConnections; + + Object.keys(peerConnections).forEach((id) => { + const peerConnection = peerConnections[id]; + console.log(peerConnection); + // const remoteStreams = peerConnection.getRemoteStreams(); + + peerConnection.getRemoteStreams().forEach((remoteStream) => { + const item = { + id, + url: URL.createObjectURL(remoteStream), + state: peerConnection.iceConnectionState + }; + console.log(item); + switch (peerConnection.iceConnectionState) { + case 'checking': + item.stateText = 'Connecting...'; + break; + case 'connected': + case 'completed': + item.stateText = 'Connected'; + item.connected = true; + break; + case 'disconnected': + item.stateText = 'Disconnected'; + break; + case 'failed': + item.stateText = 'Failed'; + break; + case 'closed': + item.stateText = 'Closed'; + } + items.push(item); + itemsById[id] = item; + }); + }); + this.remoteItems.set(items); + this.remoteItemsById.set(itemsById); + } + + resetCallInProgress() { + return this.callInProgress.set(false); + } + + broadcastStatus() { + if (this.active !== true || this.monitor === true || this.remoteMonitoring === true) { + return; + } + const remoteConnections = []; + const peerConnections = this.peerConnections; + Object.keys(peerConnections).forEach((id) => { + const peerConnection = peerConnections[id]; + remoteConnections.push({ + id, + media: peerConnection.remoteMedia + }); + }); + + return this.transport.sendStatus({ + media: this.media, + remoteConnections + }); + } + + + /* + @param data {Object} + from {String} + media {Object} + remoteConnections {Array[Object]} + id {String} + media {Object} + */ + + onRemoteStatus(data) { + this.callInProgress.set(true); + Meteor.clearTimeout(this.callInProgressTimeout); + this.callInProgressTimeout = Meteor.setTimeout(this.resetCallInProgress.bind(this), 2000); + if (this.active !== true) { + return; + } + const remoteConnections = [{ + id: data.from, + media: data.media + }, + ...data.remoteConnections]; + + remoteConnections.forEach((remoteConnection) => { + if (remoteConnection.id !== this.selfId && (this.peerConnections[remoteConnection.id] == null)) { + this.log('reconnecting with', remoteConnection.id); + this.onRemoteJoin({ + from: remoteConnection.id, + media: remoteConnection.media + }); + } + }); + } + + + /* + @param id {String} + */ + + getPeerConnection(id) { + if (this.peerConnections[id] != null) { + return this.peerConnections[id]; + } + const peerConnection = new RTCPeerConnection(this.config); + + peerConnection.createdAt = Date.now(); + peerConnection.remoteMedia = {}; + this.peerConnections[id] = peerConnection; + const eventNames = ['icecandidate', 'addstream', 'removestream', 'iceconnectionstatechange', 'datachannel', 'identityresult', 'idpassertionerror', 'idpvalidationerror', 'negotiationneeded', 'peeridentity', 'signalingstatechange']; + + eventNames.forEach((eventName) => { + peerConnection.addEventListener(eventName, (e) => { + this.log(id, e.type, e); + }); + }); + + peerConnection.addEventListener('icecandidate', (e) => { + if (e.candidate == null) { + return; + } + this.transport.sendCandidate({ + to: id, + candidate: { + candidate: e.candidate.candidate, + sdpMLineIndex: e.candidate.sdpMLineIndex, + sdpMid: e.candidate.sdpMid + } + }); + }); + peerConnection.addEventListener('addstream', () => { + this.updateRemoteItems(); + }); + peerConnection.addEventListener('removestream', () => { + this.updateRemoteItems(); + }); + peerConnection.addEventListener('iceconnectionstatechange', () => { + let ref; + if ((peerConnection.iceConnectionState === 'disconnected' || ref === 'closed') && peerConnection === this.peerConnections[id]) { + this.stopPeerConnection(id); + Meteor.setTimeout(() => { + if (Object.keys(this.peerConnections).length === 0) { + this.stop(); + } + }, 3000); + } + this.updateRemoteItems(); + }); + return peerConnection; + } + + _getUserMedia(media, onSuccess, onError) { + const onSuccessLocal = function(stream) { + if (AudioContext && stream.getAudioTracks().length > 0) { + const audioContext = new AudioContext; + const source = audioContext.createMediaStreamSource(stream); + const volume = audioContext.createGain(); + source.connect(volume); + const peer = audioContext.createMediaStreamDestination(); + volume.connect(peer); + volume.gain.value = 0.6; + stream.removeTrack(stream.getAudioTracks()[0]); + stream.addTrack(peer.stream.getAudioTracks()[0]); + stream.volume = volume; + this.audioContext = audioContext; + } + return onSuccess(stream); + }; + return navigator.getUserMedia(media, onSuccessLocal, onError); + } + + getUserMedia(media, onSuccess, onError) { + if (onError == null) { + onError = this.onError; + } + if (media.desktop !== true) { + this._getUserMedia(media, onSuccess, onError); + return; + } + if (this.screenShareAvailable !== true) { + console.log('Screen share is not avaliable'); + return; + } + const getScreen = (audioStream) => { + if (document.cookie.indexOf('rocketchatscreenshare=chrome') === -1 && (window.rocketchatscreenshare == null) && this.navigator !== 'electron') { + const refresh = function() { + swal({ + type: 'warning', + title: TAPi18n.__('Refresh_your_page_after_install_to_enable_screen_sharing') + }); + }; + swal({ + type: 'warning', + title: TAPi18n.__('Screen_Share'), + text: TAPi18n.__('You_need_install_an_extension_to_allow_screen_sharing'), + html: true, + showCancelButton: true, + confirmButtonText: TAPi18n.__('Install_Extension'), + cancelButtonText: TAPi18n.__('Cancel') + }, (isConfirm) => { + if (isConfirm) { + if (this.navigator === 'chrome') { + chrome.webstore.install(undefined, refresh, function() { + window.open('https://chrome.google.com/webstore/detail/rocketchat-screen-share/nocfbnnmjnndkbipkabodnheejiegccf'); + refresh(); + }); + } else if (this.navigator === 'firefox') { + window.open('https://addons.mozilla.org/en-GB/firefox/addon/rocketchat-screen-share/'); + refresh(); + } + } + }); + return onError(false); + } + const getScreenSuccess = (stream) => { + if (audioStream != null) { + stream.addTrack(audioStream.getAudioTracks()[0]); + } + onSuccess(stream); + }; + if (this.navigator === 'firefox') { + media = { + audio: media.audio, + video: { + mozMediaSource: 'window', + mediaSource: 'window' + } + }; + this._getUserMedia(media, getScreenSuccess, onError); + } else { + ChromeScreenShare.getSourceId(this.navigator, (id) => { + media = { + audio: false, + video: { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: id, + maxWidth: 1280, + maxHeight: 720 + } + } + }; + this._getUserMedia(media, getScreenSuccess, onError); + }); + } + }; + if (this.navigator === 'firefox' || (media.audio == null) || media.audio === false) { + return getScreen(); + } else { + const getAudioSuccess = (audioStream) => { + return getScreen(audioStream); + }; + const getAudioError = () => { + getScreen(); + }; + + this._getUserMedia({ + audio: media.audio + }, getAudioSuccess, getAudioError); + } + } + + + /* + @param callback {Function} + */ + + getLocalUserMedia(callback) { + this.log('getLocalUserMedia', arguments); + if (this.localStream != null) { + return callback(null, this.localStream); + } + const onSuccess = (stream) => { + this.localStream = stream; + this.localUrl.set(URL.createObjectURL(stream)); + this.videoEnabled.set(this.media.video === true); + this.audioEnabled.set(this.media.audio === true); + const peerConnections = this.peerConnections; + Object.keys(peerConnections).forEach((id) => { + const peerConnection = peerConnections[id]; + peerConnection.addStream(stream); + }); + return callback(null, this.localStream); + }; + const onError = (error) => { + callback(false); + return this.onError(error); + }; + return this.getUserMedia(this.media, onSuccess, onError); + } + + + /* + @param id {String} + */ + + stopPeerConnection(id) { + const peerConnection = this.peerConnections[id]; + if (peerConnection == null) { + return; + } + delete this.peerConnections[id]; + peerConnection.close(); + this.updateRemoteItems(); + } + + stopAllPeerConnections() { + const peerConnections = this.peerConnections; + + Object.keys(peerConnections).forEach((id) => { + this.stopPeerConnection(id); + }); + + window.audioContext && window.audioContext.close(); + } + + setAudioEnabled(enabled) { + if (enabled == null) { + enabled = true; + } + if (this.localStream != null) { + if (enabled === true && this.media.audio !== true) { + delete this.localStream; + this.media.audio = true; + return this.getLocalUserMedia(() => { + this.stopAllPeerConnections(); + return this.joinCall(); + }); + } else { + this.localStream.getAudioTracks().forEach(function(audio) { + return audio.enabled = enabled; + }); + return this.audioEnabled.set(enabled); + } + } + } + + disableAudio() { + return this.setAudioEnabled(false); + } + + enableAudio() { + return this.setAudioEnabled(true); + } + + setVideoEnabled(enabled) { + if (enabled == null) { + enabled = true; + } + if (this.localStream != null) { + if (enabled === true && this.media.video !== true) { + delete this.localStream; + this.media.video = true; + return this.getLocalUserMedia(() => { + this.stopAllPeerConnections(); + return this.joinCall(); + }); + } else { + this.localStream.getVideoTracks().forEach(function(video) { + return video.enabled = enabled; + }); + return this.videoEnabled.set(enabled); + } + } + } + + disableScreenShare() { + return this.setScreenShareEnabled(false); + } + + enableScreenShare() { + return this.setScreenShareEnabled(true); + } + + setScreenShareEnabled(enabled) { + if (enabled == null) { + enabled = true; + } + if (this.localStream != null) { + this.media.desktop = enabled; + delete this.localStream; + return this.getLocalUserMedia((err) => { + if (err != null) { + return; + } + this.screenShareEnabled.set(enabled); + this.stopAllPeerConnections(); + return this.joinCall(); + }); + } + } + + disableVideo() { + return this.setVideoEnabled(false); + } + + enableVideo() { + return this.setVideoEnabled(true); + } + + stop() { + this.active = false; + this.monitor = false; + this.remoteMonitoring = false; + if ((this.localStream != null) && typeof this.localStream !== 'undefined') { + this.localStream.getTracks().forEach(function(track) { + return track.stop(); + }); + } + this.localUrl.set(undefined); + delete this.localStream; + return this.stopAllPeerConnections(); + } + + + /* + @param media {Object} + audio {Boolean} + video {Boolean} + */ + + startCall(media) { + if (media == null) { + media = {}; + } + this.log('startCall', arguments); + this.media = media; + return this.getLocalUserMedia(() => { + this.active = true; + return this.transport.startCall({ + media: this.media + }); + }); + } + + startCallAsMonitor(media) { + if (media == null) { + media = {}; + } + this.log('startCallAsMonitor', arguments); + this.media = media; + this.active = true; + this.monitor = true; + return this.transport.startCall({ + media: this.media, + monitor: true + }); + } + + + /* + @param data {Object} + from {String} + monitor {Boolean} + media {Object} + audio {Boolean} + video {Boolean} + */ + + onRemoteCall(data) { + if (this.autoAccept === true) { + FlowRouter.goToRoomById(data.room); + Meteor.defer(() => { + return this.joinCall({ + to: data.from, + monitor: data.monitor, + media: data.media + }); + }); + return; + } + + const user = Meteor.users.findOne(data.from); + let fromUsername = undefined; + if (user && user.username) { + fromUsername = user.username; + } + const subscription = ChatSubscription.findOne({ + rid: data.room + }); + + let icon; + let title; + if (data.monitor === true) { + icon = 'eye'; + title = `Monitor call from ${ fromUsername }`; + } else if (subscription && subscription.t === 'd') { + if (data.media && data.media.video) { + icon = 'videocam'; + title = `Direct video call from ${ fromUsername }`; + } else { + icon = 'phone'; + title = `Direct audio call from ${ fromUsername }`; + } + } else if (data.media && data.media.video) { + icon = 'videocam'; + title = `Group video call from ${ subscription.name }`; + } else { + icon = 'phone'; + title = `Group audio call from ${ subscription.name }`; + } + return swal({ + title: `${ title }`, + text: 'Do you want to accept?', + html: true, + showCancelButton: true, + confirmButtonText: 'Yes', + cancelButtonText: 'No' + }, (isConfirm) => { + if (isConfirm) { + FlowRouter.goToRoomById(data.room); + return Meteor.defer(() => { + return this.joinCall({ + to: data.from, + monitor: data.monitor, + media: data.media + }); + }); + } else { + return this.stop(); + } + }); + } + + + /* + @param data {Object} + to {String} + monitor {Boolean} + media {Object} + audio {Boolean} + video {Boolean} + desktop {Boolean} + */ + + joinCall(data) { + if (data == null) { + data = {}; + } + if (data.media && data.media.audio) { + this.media.audio = data.media.audio; + } + if (data.media && data.media.video) { + this.media.video = data.media.video; + } + data.media = this.media; + this.log('joinCall', arguments); + this.getLocalUserMedia(() => { + this.remoteMonitoring = data.monitor; + this.active = true; + this.transport.joinCall(data); + }); + } + + + onRemoteJoin(data) { + if (this.active !== true) { + return; + } + this.log('onRemoteJoin', arguments); + let peerConnection = this.getPeerConnection(data.from); + if (peerConnection.signalingState !== 'checking') { + this.stopPeerConnection(data.from); + peerConnection = this.getPeerConnection(data.from); + } + if (peerConnection.iceConnectionState !== 'new') { + return; + } + peerConnection.remoteMedia = data.media; + if (this.localStream) { + peerConnection.addStream(this.localStream); + } + const onOffer = offer => { + const onLocalDescription = () => { + return this.transport.sendDescription({ + to: data.from, + type: 'offer', + ts: peerConnection.createdAt, + media: this.media, + description: { + sdp: offer.sdp, + type: offer.type + } + }); + }; + + return peerConnection.setLocalDescription(new RTCSessionDescription(offer), onLocalDescription, this.onError); + }; + + if (data.monitor === true) { + return peerConnection.createOffer(onOffer, this.onError, { + mandatory: { + OfferToReceiveAudio: data.media.audio, + OfferToReceiveVideo: data.media.video + } + }); + } else { + return peerConnection.createOffer(onOffer, this.onError); + } + } + + + + onRemoteOffer(data) { + if (this.active !== true) { return; } + + this.log('onRemoteOffer', arguments); + let peerConnection = this.getPeerConnection(data.from); + + if (['have-local-offer', 'stable'].includes(peerConnection.signalingState) && (peerConnection.createdAt < data.ts)) { + this.stopPeerConnection(data.from); + peerConnection = this.getPeerConnection(data.from); + } + + if (peerConnection.iceConnectionState !== 'new') { + return; + } + + peerConnection.setRemoteDescription(new RTCSessionDescription(data.description)); + + try { + if (this.localStream) { + peerConnection.addStream(this.localStream); + } + } catch (error) { + console.log(error); + } + + const onAnswer = answer => { + const onLocalDescription = () => { + return this.transport.sendDescription({ + to: data.from, + type: 'answer', + ts: peerConnection.createdAt, + description: { + sdp: answer.sdp, + type: answer.type + } + }); + }; + + return peerConnection.setLocalDescription(new RTCSessionDescription(answer), onLocalDescription, this.onError); + }; + + return peerConnection.createAnswer(onAnswer, this.onError); + } + + + /* + @param data {Object} + to {String} + from {String} + candidate {RTCIceCandidate JSON encoded} + */ + + onRemoteCandidate(data) { + if (this.active !== true) { + return; + } + if (data.to !== this.selfId) { + return; + } + this.log('onRemoteCandidate', arguments); + const peerConnection = this.getPeerConnection(data.from); + if (peerConnection.iceConnectionState !== 'closed' && peerConnection.iceConnectionState !== 'failed' && peerConnection.iceConnectionState !== 'disconnected' && peerConnection.iceConnectionState !== 'completed') { + return peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate)); + } + } + + + /* + @param data {Object} + to {String} + from {String} + type {String} [offer, answer] + description {RTCSessionDescription JSON encoded} + ts {Integer} + media {Object} + audio {Boolean} + video {Boolean} + desktop {Boolean} + */ + + onRemoteDescription(data) { + if (this.active !== true) { + return; + } + if (data.to !== this.selfId) { + return; + } + this.log('onRemoteDescription', arguments); + const peerConnection = this.getPeerConnection(data.from); + if (data.type === 'offer') { + peerConnection.remoteMedia = data.media; + return this.onRemoteOffer({ + from: data.from, + ts: data.ts, + description: data.description + }); + } else { + return peerConnection.setRemoteDescription(new RTCSessionDescription(data.description)); + } + } + +} + +const WebRTC = new class { + constructor() { + this.instancesByRoomId = {}; + } + + getInstanceByRoomId(roomId) { + const subscription = ChatSubscription.findOne({ + rid: roomId + }); + if (!subscription) { + return; + } + let enabled = false; + switch (subscription.t) { + case 'd': + enabled = RocketChat.settings.get('WebRTC_Enable_Direct'); + break; + case 'p': + enabled = RocketChat.settings.get('WebRTC_Enable_Private'); + break; + case 'c': + enabled = RocketChat.settings.get('WebRTC_Enable_Channel'); + } + if (enabled === false) { + return; + } + if (this.instancesByRoomId[roomId] == null) { + this.instancesByRoomId[roomId] = new WebRTCClass(Meteor.userId(), roomId); + } + return this.instancesByRoomId[roomId]; + } +}; + +Meteor.startup(function() { + return Tracker.autorun(function() { + if (Meteor.userId()) { + return RocketChat.Notifications.onUser('webrtc', (type, data) => { + if (data.room == null) { + return; + } + const webrtc = WebRTC.getInstanceByRoomId(data.room); + return webrtc.transport.onUserStream(type, data); + }); + } + }); +}); + +export {WebRTC}; diff --git a/packages/rocketchat-webrtc/adapter.js b/packages/rocketchat-webrtc/client/adapter.js similarity index 100% rename from packages/rocketchat-webrtc/adapter.js rename to packages/rocketchat-webrtc/client/adapter.js diff --git a/packages/rocketchat-webrtc/screenShare.coffee b/packages/rocketchat-webrtc/client/screenShare.coffee similarity index 100% rename from packages/rocketchat-webrtc/screenShare.coffee rename to packages/rocketchat-webrtc/client/screenShare.coffee diff --git a/packages/rocketchat-webrtc/client/screenShare.js b/packages/rocketchat-webrtc/client/screenShare.js new file mode 100644 index 000000000000..f4ff0eef4d76 --- /dev/null +++ b/packages/rocketchat-webrtc/client/screenShare.js @@ -0,0 +1,31 @@ +/* globals ChromeScreenShare, fireGlobalEvent */ +this.ChromeScreenShare = { + screenCallback: undefined, + getSourceId(navigator, callback) { + if (callback == null) { + throw '"callback" parameter is mandatory.'; + } + ChromeScreenShare.screenCallback = callback; + if (navigator === 'electron') { + return fireGlobalEvent('get-sourceId', '*'); + } else { + return window.postMessage('get-sourceId', '*'); + } + } +}; + +window.addEventListener('message', function(e) { + if (e.origin !== window.location.origin) { + return; + } + if (e.data === 'PermissionDeniedError') { + if (ChromeScreenShare.screenCallback != null) { + return ChromeScreenShare.screenCallback('PermissionDeniedError'); + } else { + throw new Error('PermissionDeniedError'); + } + } + if (e.data.sourceId != null) { + return typeof ChromeScreenShare.screenCallback === 'function' ? ChromeScreenShare.screenCallback(e.data.sourceId) : void 0; + } +}); diff --git a/packages/rocketchat-webrtc/package.js b/packages/rocketchat-webrtc/package.js index 53d56d1cde70..66c3258e0e0e 100644 --- a/packages/rocketchat-webrtc/package.js +++ b/packages/rocketchat-webrtc/package.js @@ -7,16 +7,16 @@ Package.describe({ Package.onUse(function(api) { api.use('rocketchat:lib'); - api.use('coffeescript'); api.use('ecmascript'); + api.use('coffeescript'); api.use('templating', 'client'); + api.mainModule('client/WebRTCClass.js', 'client'); + api.addFiles('client/adapter.js', 'client'); + // api.addFiles('); + api.addFiles('client/screenShare.js', 'client'); - api.addFiles('adapter.js', 'client'); - api.addFiles('WebRTCClass.coffee', 'client'); - api.addFiles('screenShare.coffee', 'client'); - - api.addFiles('server/settings.coffee', 'server'); + api.addFiles('server/settings.js', 'server'); - api.export('WebRTC'); + api.export('WebRTC', 'client'); }); diff --git a/packages/rocketchat-webrtc/server/settings.js b/packages/rocketchat-webrtc/server/settings.js new file mode 100644 index 000000000000..c32f3103acaf --- /dev/null +++ b/packages/rocketchat-webrtc/server/settings.js @@ -0,0 +1,22 @@ +RocketChat.settings.addGroup('WebRTC', function() { + this.add('WebRTC_Enable_Channel', false, { + type: 'boolean', + group: 'WebRTC', + 'public': true + }); + this.add('WebRTC_Enable_Private', true, { + type: 'boolean', + group: 'WebRTC', + 'public': true + }); + this.add('WebRTC_Enable_Direct', true, { + type: 'boolean', + group: 'WebRTC', + 'public': true + }); + return this.add('WebRTC_Servers', 'stun:stun.l.google.com:19302, stun:23.21.150.121, team%40rocket.chat:demo@turn:numb.viagenie.ca:3478', { + type: 'string', + group: 'WebRTC', + 'public': true + }); +}); From 481e0fa5638bad0daa24f6764287f5191d574ad0 Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Mon, 24 Apr 2017 11:03:52 -0300 Subject: [PATCH 02/11] Remove Console logs --- .../client/WebRTCClass.coffee | 3 +-- .../rocketchat-webrtc/client/WebRTCClass.js | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/rocketchat-webrtc/client/WebRTCClass.coffee b/packages/rocketchat-webrtc/client/WebRTCClass.coffee index aefe207996ae..ffefcc915413 100644 --- a/packages/rocketchat-webrtc/client/WebRTCClass.coffee +++ b/packages/rocketchat-webrtc/client/WebRTCClass.coffee @@ -197,8 +197,7 @@ class WebRTCClass for id, peerConnection of @peerConnections console.log peerConnection for remoteStream in peerConnection.getRemoteStreams() - console.log peerConnection.getRemoteStreams() - item = peerConnection.getRemoteStreams() + item = id: id url: URL.createObjectURL(remoteStream) state: peerConnection.iceConnectionState diff --git a/packages/rocketchat-webrtc/client/WebRTCClass.js b/packages/rocketchat-webrtc/client/WebRTCClass.js index 57ef29c0943d..decfab47d5e5 100644 --- a/packages/rocketchat-webrtc/client/WebRTCClass.js +++ b/packages/rocketchat-webrtc/client/WebRTCClass.js @@ -27,7 +27,6 @@ class WebRTCTransportClass { return; } this.log('WebRTCTransportClass - onUser', type, data); - console.log(this.callbacks); const onRemoteCall = this.callbacks['onRemoteCall']; const onRemoteJoin = this.callbacks['onRemoteJoin']; const onRemoteCandidate = this.callbacks['onRemoteCandidate']; @@ -222,6 +221,8 @@ class WebRTCClass { this.transport.onRemoteDescription(this.onRemoteDescription.bind(this)); this.transport.onRemoteStatus(this.onRemoteStatus.bind(this)); Meteor.setInterval(this.checkPeerConnections.bind(this), 1000); + + //Meteor.setInterval(this.broadcastStatus.bind(@), 1000); } log() { @@ -251,8 +252,6 @@ class WebRTCClass { Object.keys(peerConnections).forEach((id) => { const peerConnection = peerConnections[id]; - console.log(peerConnection); - // const remoteStreams = peerConnection.getRemoteStreams(); peerConnection.getRemoteStreams().forEach((remoteStream) => { const item = { @@ -322,6 +321,7 @@ class WebRTCClass { */ onRemoteStatus(data) { + //this.log(onRemoteStatus, arguments); this.callInProgress.set(true); Meteor.clearTimeout(this.callInProgressTimeout); this.callInProgressTimeout = Meteor.setTimeout(this.resetCallInProgress.bind(this), 2000); @@ -811,6 +811,15 @@ class WebRTCClass { } this.log('onRemoteJoin', arguments); let peerConnection = this.getPeerConnection(data.from); + + // needsRefresh = false + // if peerConnection.iceConnectionState isnt 'new' + // needsAudio = data.media.audio is true and peerConnection.remoteMedia.audio isnt true + // needsVideo = data.media.video is true and peerConnection.remoteMedia.video isnt true + // needsRefresh = needsAudio or needsVideo or data.media.desktop isnt peerConnection.remoteMedia.desktop + + // # if peerConnection.signalingState is "have-local-offer" or needsRefresh + if (peerConnection.signalingState !== 'checking') { this.stopPeerConnection(data.from); peerConnection = this.getPeerConnection(data.from); @@ -854,7 +863,9 @@ class WebRTCClass { onRemoteOffer(data) { - if (this.active !== true) { return; } + if (this.active !== true) { + return; + } this.log('onRemoteOffer', arguments); let peerConnection = this.getPeerConnection(data.from); From 7ef71c6ea05f45973f131ff58d35afc82ab68af0 Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Mon, 24 Apr 2017 11:09:42 -0300 Subject: [PATCH 03/11] Remove Coffee Files --- .../client/WebRTCClass.coffee | 819 ------------------ .../client/screenShare.coffee | 27 - packages/rocketchat-webrtc/package.js | 1 - .../rocketchat-webrtc/server/settings.coffee | 5 - 4 files changed, 852 deletions(-) delete mode 100644 packages/rocketchat-webrtc/client/WebRTCClass.coffee delete mode 100644 packages/rocketchat-webrtc/client/screenShare.coffee delete mode 100644 packages/rocketchat-webrtc/server/settings.coffee diff --git a/packages/rocketchat-webrtc/client/WebRTCClass.coffee b/packages/rocketchat-webrtc/client/WebRTCClass.coffee deleted file mode 100644 index ffefcc915413..000000000000 --- a/packages/rocketchat-webrtc/client/WebRTCClass.coffee +++ /dev/null @@ -1,819 +0,0 @@ -emptyFn = -> - # empty - -class WebRTCTransportClass - debug: false - - log: -> - if @debug is true - console.log.apply(console, arguments) - - constructor: (@webrtcInstance) -> - @callbacks = {} - - RocketChat.Notifications.onRoom @webrtcInstance.room, 'webrtc', (type, data) => - @log 'WebRTCTransportClass - onRoom', type, data - - switch type - when 'status' - if @callbacks['onRemoteStatus']?.length > 0 - fn(data) for fn in @callbacks['onRemoteStatus'] - - onUserStream: (type, data) -> - if data.room isnt @webrtcInstance.room then return - @log 'WebRTCTransportClass - onUser', type, data - - switch type - when 'call' - if @callbacks['onRemoteCall']?.length > 0 - fn(data) for fn in @callbacks['onRemoteCall'] - - when 'join' - if @callbacks['onRemoteJoin']?.length > 0 - fn(data) for fn in @callbacks['onRemoteJoin'] - - when 'candidate' - if @callbacks['onRemoteCandidate']?.length > 0 - fn(data) for fn in @callbacks['onRemoteCandidate'] - - when 'description' - if @callbacks['onRemoteDescription']?.length > 0 - fn(data) for fn in @callbacks['onRemoteDescription'] - - startCall: (data) -> - @log 'WebRTCTransportClass - startCall', @webrtcInstance.room, @webrtcInstance.selfId - RocketChat.Notifications.notifyUsersOfRoom @webrtcInstance.room, 'webrtc', 'call', - from: @webrtcInstance.selfId - room: @webrtcInstance.room - media: data.media - monitor: data.monitor - - joinCall: (data) -> - @log 'WebRTCTransportClass - joinCall', @webrtcInstance.room, @webrtcInstance.selfId - if data.monitor is true - RocketChat.Notifications.notifyUser data.to, 'webrtc', 'join', - from: @webrtcInstance.selfId - room: @webrtcInstance.room - media: data.media - monitor: data.monitor - else - RocketChat.Notifications.notifyUsersOfRoom @webrtcInstance.room, 'webrtc', 'join', - from: @webrtcInstance.selfId - room: @webrtcInstance.room - media: data.media - monitor: data.monitor - - sendCandidate: (data) -> - data.from = @webrtcInstance.selfId - data.room = @webrtcInstance.room - @log 'WebRTCTransportClass - sendCandidate', data - RocketChat.Notifications.notifyUser data.to, 'webrtc', 'candidate', data - - sendDescription: (data) -> - data.from = @webrtcInstance.selfId - data.room = @webrtcInstance.room - @log 'WebRTCTransportClass - sendDescription', data - RocketChat.Notifications.notifyUser data.to, 'webrtc', 'description', data - - sendStatus: (data) -> - @log 'WebRTCTransportClass - sendStatus', data, @webrtcInstance.room - data.from = @webrtcInstance.selfId - RocketChat.Notifications.notifyRoom @webrtcInstance.room, 'webrtc', 'status', data - - onRemoteCall: (fn) -> - @callbacks['onRemoteCall'] ?= [] - @callbacks['onRemoteCall'].push fn - - onRemoteJoin: (fn) -> - @callbacks['onRemoteJoin'] ?= [] - @callbacks['onRemoteJoin'].push fn - - onRemoteCandidate: (fn) -> - @callbacks['onRemoteCandidate'] ?= [] - @callbacks['onRemoteCandidate'].push fn - - onRemoteDescription: (fn) -> - @callbacks['onRemoteDescription'] ?= [] - @callbacks['onRemoteDescription'].push fn - - onRemoteStatus: (fn) -> - @callbacks['onRemoteStatus'] ?= [] - @callbacks['onRemoteStatus'].push fn - - -class WebRTCClass - config: - iceServers: [] - - debug: false - - transportClass: WebRTCTransportClass - - - ### - @param seldId {String} - @param room {String} - ### - constructor: (@selfId, @room) -> - @config.iceServers = [] - - servers = RocketChat.settings.get("WebRTC_Servers") - if servers?.trim() isnt '' - servers = servers.replace /\s/g, '' - servers = servers.split ',' - for server in servers - server = server.split '@' - serverConfig = - urls: server.pop() - - if server.length is 1 - server = server[0].split ':' - serverConfig.username = decodeURIComponent(server[0]) - serverConfig.credential = decodeURIComponent(server[1]) - - @config.iceServers.push serverConfig - - @peerConnections = {} - - @remoteItems = new ReactiveVar [] - @remoteItemsById = new ReactiveVar {} - @callInProgress = new ReactiveVar false - @audioEnabled = new ReactiveVar true - @videoEnabled = new ReactiveVar true - @overlayEnabled = new ReactiveVar false - @screenShareEnabled = new ReactiveVar false - @localUrl = new ReactiveVar - - @active = false - @remoteMonitoring = false - @monitor = false - @autoAccept = false - - @navigator = undefined - userAgent = navigator.userAgent.toLocaleLowerCase(); - if userAgent.indexOf('electron') isnt -1 - @navigator = 'electron' - else if userAgent.indexOf('chrome') isnt -1 - @navigator = 'chrome' - else if userAgent.indexOf('firefox') isnt -1 - @navigator = 'firefox' - else if userAgent.indexOf('safari') isnt -1 - @navigator = 'safari' - - @screenShareAvailable = @navigator in ['chrome', 'firefox', 'electron'] - - @media = - video: false - audio: true - - @transport = new @transportClass @ - - @transport.onRemoteCall @onRemoteCall.bind @ - @transport.onRemoteJoin @onRemoteJoin.bind @ - @transport.onRemoteCandidate @onRemoteCandidate.bind @ - @transport.onRemoteDescription @onRemoteDescription.bind @ - @transport.onRemoteStatus @onRemoteStatus.bind @ - - Meteor.setInterval @checkPeerConnections.bind(@), 1000 - - # Meteor.setInterval @broadcastStatus.bind(@), 1000 - - log: -> - if @debug is true - console.log.apply(console, arguments) - - onError: -> - console.error.apply(console, arguments) - - checkPeerConnections: -> - for id, peerConnection of @peerConnections - if peerConnection.iceConnectionState not in ['connected', 'completed'] and peerConnection.createdAt + 5000 < Date.now() - @stopPeerConnection id - - updateRemoteItems: -> - items = [] - itemsById = {} - - for id, peerConnection of @peerConnections - console.log peerConnection - for remoteStream in peerConnection.getRemoteStreams() - item = - id: id - url: URL.createObjectURL(remoteStream) - state: peerConnection.iceConnectionState - - switch peerConnection.iceConnectionState - when 'checking' - item.stateText = 'Connecting...' - - when 'connected', 'completed' - item.stateText = 'Connected' - item.connected = true - - when 'disconnected' - item.stateText = 'Disconnected' - - when 'failed' - item.stateText = 'Failed' - - when 'closed' - item.stateText = 'Closed' - - items.push item - itemsById[id] = item - - @remoteItems.set items - @remoteItemsById.set itemsById - - resetCallInProgress: -> - @callInProgress.set false - - broadcastStatus: -> - if @active isnt true or @monitor is true or @remoteMonitoring is true then return - - remoteConnections = [] - for id, peerConnection of @peerConnections - remoteConnections.push - id: id - media: peerConnection.remoteMedia - - @transport.sendStatus - media: @media - remoteConnections: remoteConnections - - ### - @param data {Object} - from {String} - media {Object} - remoteConnections {Array[Object]} - id {String} - media {Object} - ### - onRemoteStatus: (data) -> - # @log 'onRemoteStatus', arguments - - @callInProgress.set true - - Meteor.clearTimeout @callInProgressTimeout - @callInProgressTimeout = Meteor.setTimeout @resetCallInProgress.bind(@), 2000 - - if @active isnt true then return - - remoteConnections = [{id: data.from, media: data.media}].concat data.remoteConnections - - for remoteConnection in remoteConnections - if remoteConnection.id isnt @selfId and not @peerConnections[remoteConnection.id]? - @log 'reconnecting with', remoteConnection.id - @onRemoteJoin - from: remoteConnection.id - media: remoteConnection.media - - ### - @param id {String} - ### - getPeerConnection: (id) -> - return @peerConnections[id] if @peerConnections[id]? - - peerConnection = new RTCPeerConnection @config - - peerConnection.createdAt = Date.now() - peerConnection.remoteMedia = {} - - @peerConnections[id] = peerConnection - - eventNames = [ - 'icecandidate' - 'addstream' - 'removestream' - 'iceconnectionstatechange' - 'datachannel' - 'identityresult' - 'idpassertionerror' - 'idpvalidationerror' - 'negotiationneeded' - 'peeridentity' - 'signalingstatechange' - ] - - for eventName in eventNames - peerConnection.addEventListener eventName, (e) => - @log id, e.type, e - - peerConnection.addEventListener 'icecandidate', (e) => - if not e.candidate? - return - - @transport.sendCandidate - to: id - candidate: - candidate: e.candidate.candidate - sdpMLineIndex: e.candidate.sdpMLineIndex - sdpMid: e.candidate.sdpMid - - peerConnection.addEventListener 'addstream', (e) => - @updateRemoteItems() - - peerConnection.addEventListener 'removestream', (e) => - @updateRemoteItems() - - peerConnection.addEventListener 'iceconnectionstatechange', (e) => - if peerConnection.iceConnectionState in ['disconnected', 'closed'] and peerConnection is @peerConnections[id] - @stopPeerConnection id - Meteor.setTimeout => - if Object.keys(@peerConnections).length is 0 - @stop() - , 3000 - - @updateRemoteItems() - - return peerConnection - - _getUserMedia: (media, onSuccess, onError) -> - onSuccessLocal = (stream) -> - if AudioContext? and stream.getAudioTracks().length > 0 - audioContext = new AudioContext - source = audioContext.createMediaStreamSource(stream) - - volume = audioContext.createGain() - source.connect(volume) - peer = audioContext.createMediaStreamDestination() - volume.connect(peer) - volume.gain.value = 0.6 - - stream.removeTrack(stream.getAudioTracks()[0]) - stream.addTrack(peer.stream.getAudioTracks()[0]) - stream.volume = volume - - this.audioContext = audioContext - - onSuccess(stream) - - navigator.getUserMedia media, onSuccessLocal, onError - - - getUserMedia: (media, onSuccess, onError=@onError) -> - if media.desktop isnt true - @_getUserMedia media, onSuccess, onError - return - - if @screenShareAvailable isnt true - console.log 'Screen share is not avaliable' - return - - getScreen = (audioStream) => - if document.cookie.indexOf("rocketchatscreenshare=chrome") is -1 and not window.rocketchatscreenshare? and @navigator isnt 'electron' - refresh = -> - swal - type: "warning" - title: TAPi18n.__ "Refresh_your_page_after_install_to_enable_screen_sharing" - - swal - type: "warning" - title: TAPi18n.__ "Screen_Share" - text: TAPi18n.__ "You_need_install_an_extension_to_allow_screen_sharing" - html: true - showCancelButton: true - confirmButtonText: TAPi18n.__ "Install_Extension" - cancelButtonText: TAPi18n.__ "Cancel" - , (isConfirm) => - if isConfirm - if @navigator is 'chrome' - chrome.webstore.install undefined, refresh, -> - window.open('https://chrome.google.com/webstore/detail/rocketchat-screen-share/nocfbnnmjnndkbipkabodnheejiegccf') - refresh() - else if @navigator is 'firefox' - window.open('https://addons.mozilla.org/en-GB/firefox/addon/rocketchat-screen-share/') - refresh() - - return onError(false) - - getScreenSuccess = (stream) => - if audioStream? - stream.addTrack(audioStream.getAudioTracks()[0]) - onSuccess(stream) - - if @navigator is 'firefox' - media = - audio: media.audio - video: - mozMediaSource: 'window' - mediaSource: 'window' - @_getUserMedia media, getScreenSuccess, onError - else - ChromeScreenShare.getSourceId @navigator, (id) => - media = - audio: false - video: - mandatory: - chromeMediaSource: 'desktop' - chromeMediaSourceId: id - maxWidth: 1280 - maxHeight: 720 - - @_getUserMedia media, getScreenSuccess, onError - - if @navigator is 'firefox' or not media.audio? or media.audio is false - getScreen() - else - getAudioSuccess = (audioStream) => - getScreen(audioStream) - - getAudioError = => - getScreen() - - @_getUserMedia {audio: media.audio}, getAudioSuccess, getAudioError - - - ### - @param callback {Function} - ### - getLocalUserMedia: (callback) -> - @log 'getLocalUserMedia', arguments - - if @localStream? - return callback null, @localStream - - onSuccess = (stream) => - @localStream = stream - @localUrl.set URL.createObjectURL(stream) - - @videoEnabled.set @media.video is true - @audioEnabled.set @media.audio is true - - for id, peerConnection of @peerConnections - peerConnection.addStream stream - - callback null, @localStream - - onError = (error) => - callback false - @onError error - - @getUserMedia @media, onSuccess, onError - - - ### - @param id {String} - ### - stopPeerConnection: (id) -> - peerConnection = @peerConnections[id] - if not peerConnection? then return - - delete @peerConnections[id] - peerConnection.close() - - @updateRemoteItems() - - stopAllPeerConnections: -> - for id, peerConnection of @peerConnections - @stopPeerConnection id - window.audioContext?.close() - - setAudioEnabled: (enabled=true) -> - if @localStream? - if enabled is true and @media.audio isnt true - delete @localStream - @media.audio = true - @getLocalUserMedia => - @stopAllPeerConnections() - @joinCall() - else - @localStream.getAudioTracks().forEach (audio) -> audio.enabled = enabled - @audioEnabled.set enabled - - disableAudio: -> - @setAudioEnabled false - - enableAudio: -> - @setAudioEnabled true - - setVideoEnabled: (enabled=true) -> - if @localStream? - if enabled is true and @media.video isnt true - delete @localStream - @media.video = true - @getLocalUserMedia => - @stopAllPeerConnections() - @joinCall() - else - @localStream.getVideoTracks().forEach (video) -> video.enabled = enabled - @videoEnabled.set enabled - - disableScreenShare: -> - @setScreenShareEnabled false - - enableScreenShare: -> - @setScreenShareEnabled true - - setScreenShareEnabled: (enabled=true) -> - if @localStream? - @media.desktop = enabled - delete @localStream - @getLocalUserMedia (err) => - if err? - return - @screenShareEnabled.set enabled - @stopAllPeerConnections() - @joinCall() - - disableVideo: -> - @setVideoEnabled false - - enableVideo: -> - @setVideoEnabled true - - stop: -> - @active = false - @monitor = false - @remoteMonitoring = false - if @localStream? and typeof @localStream isnt 'undefined' - @localStream.getTracks().forEach (track) -> - track.stop() - @localUrl.set undefined - delete @localStream - - @stopAllPeerConnections() - - - ### - @param media {Object} - audio {Boolean} - video {Boolean} - ### - startCall: (media={}) -> - @log 'startCall', arguments - @media = media - @getLocalUserMedia => - @active = true - @transport.startCall - media: @media - - startCallAsMonitor: (media={}) -> - @log 'startCallAsMonitor', arguments - @media = media - @active = true - @monitor = true - @transport.startCall - media: @media - monitor: true - - - ### - @param data {Object} - from {String} - monitor {Boolean} - media {Object} - audio {Boolean} - video {Boolean} - ### - onRemoteCall: (data) -> - if @autoAccept is true - FlowRouter.goToRoomById data.room - Meteor.defer => - @joinCall - to: data.from - monitor: data.monitor - media: data.media - return - - fromUsername = Meteor.users.findOne(data.from)?.username - subscription = ChatSubscription.findOne({rid: data.room}) - - if data.monitor is true - icon = 'eye' - title = "Monitor call from #{fromUsername}" - else if subscription?.t is 'd' - if data.media?.video - icon = 'videocam' - title = "Direct video call from #{fromUsername}" - else - icon = 'phone' - title = "Direct audio call from #{fromUsername}" - else - if data.media?.video - icon = 'videocam' - title = "Group video call from #{subscription.name}" - else - icon = 'phone' - title = "Group audio call from #{subscription.name}" - - swal - title: "#{title}" - text: "Do you want to accept?" - html: true - showCancelButton: true - confirmButtonText: "Yes" - cancelButtonText: "No" - , (isConfirm) => - if isConfirm - FlowRouter.goToRoomById data.room - Meteor.defer => - @joinCall - to: data.from - monitor: data.monitor - media: data.media - else - @stop() - - - ### - @param data {Object} - to {String} - monitor {Boolean} - media {Object} - audio {Boolean} - video {Boolean} - desktop {Boolean} - ### - joinCall: (data={}) -> - if data.media?.audio? - @media.audio = data.media.audio - - if data.media?.video? - @media.video = data.media.video - - data.media = @media - - @log 'joinCall', arguments - @getLocalUserMedia => - @remoteMonitoring = data.monitor - @active = true - @transport.joinCall(data) - - - ### - @param data {Object} - from {String} - monitor {Boolean} - media {Object} - audio {Boolean} - video {Boolean} - desktop {Boolean} - ### - onRemoteJoin: (data) -> - if @active isnt true then return - - @log 'onRemoteJoin', arguments - - peerConnection = @getPeerConnection data.from - - # needsRefresh = false - # if peerConnection.iceConnectionState isnt 'new' - # needsAudio = data.media.audio is true and peerConnection.remoteMedia.audio isnt true - # needsVideo = data.media.video is true and peerConnection.remoteMedia.video isnt true - # needsRefresh = needsAudio or needsVideo or data.media.desktop isnt peerConnection.remoteMedia.desktop - - # if peerConnection.signalingState is "have-local-offer" or needsRefresh - if peerConnection.signalingState isnt "checking" - @stopPeerConnection data.from - peerConnection = @getPeerConnection data.from - - if peerConnection.iceConnectionState isnt 'new' - return - - peerConnection.remoteMedia = data.media - - peerConnection.addStream @localStream if @localStream - - onOffer = (offer) => - onLocalDescription = => - @transport.sendDescription - to: data.from - type: 'offer' - ts: peerConnection.createdAt - media: @media - description: - sdp: offer.sdp - type: offer.type - - peerConnection.setLocalDescription(new RTCSessionDescription(offer), onLocalDescription, @onError) - - if data.monitor is true - peerConnection.createOffer onOffer, @onError, - mandatory: - OfferToReceiveAudio: data.media.audio - OfferToReceiveVideo: data.media.video - else - peerConnection.createOffer(onOffer, @onError) - - - ### - @param data {Object} - from {String} - ts {Integer} - description {String} - ### - onRemoteOffer: (data) -> - if @active isnt true then return - - @log 'onRemoteOffer', arguments - peerConnection = @getPeerConnection data.from - - if peerConnection.signalingState in ["have-local-offer", "stable"] and peerConnection.createdAt < data.ts - @stopPeerConnection data.from - peerConnection = @getPeerConnection data.from - - if peerConnection.iceConnectionState isnt 'new' - return - - peerConnection.setRemoteDescription new RTCSessionDescription(data.description) - - try peerConnection.addStream @localStream if @localStream - - onAnswer = (answer) => - onLocalDescription = => - @transport.sendDescription - to: data.from - type: 'answer' - ts: peerConnection.createdAt - description: - sdp: answer.sdp - type: answer.type - - peerConnection.setLocalDescription(new RTCSessionDescription(answer), onLocalDescription, @onError) - - peerConnection.createAnswer(onAnswer, @onError) - - - ### - @param data {Object} - to {String} - from {String} - candidate {RTCIceCandidate JSON encoded} - ### - onRemoteCandidate: (data) -> - if @active isnt true then return - if data.to isnt @selfId then return - - @log 'onRemoteCandidate', arguments - peerConnection = @getPeerConnection data.from - - if peerConnection.iceConnectionState not in ["closed", "failed", "disconnected", "completed"] - peerConnection.addIceCandidate new RTCIceCandidate(data.candidate) - - - ### - @param data {Object} - to {String} - from {String} - type {String} [offer, answer] - description {RTCSessionDescription JSON encoded} - ts {Integer} - media {Object} - audio {Boolean} - video {Boolean} - desktop {Boolean} - ### - onRemoteDescription: (data) -> - if @active isnt true then return - if data.to isnt @selfId then return - - @log 'onRemoteDescription', arguments - peerConnection = @getPeerConnection data.from - - if data.type is 'offer' - peerConnection.remoteMedia = data.media - @onRemoteOffer - from: data.from - ts: data.ts - description: data.description - else - peerConnection.setRemoteDescription new RTCSessionDescription(data.description) - - -WebRTC = new class - constructor: -> - @instancesByRoomId = {} - - getInstanceByRoomId: (roomId) -> - subscription = ChatSubscription.findOne({rid: roomId}) - if not subscription - return - - enabled = false - switch subscription.t - when 'd' - enabled = RocketChat.settings.get('WebRTC_Enable_Direct') - when 'p' - enabled = RocketChat.settings.get('WebRTC_Enable_Private') - when 'c' - enabled = RocketChat.settings.get('WebRTC_Enable_Channel') - - if enabled is false - return - - if not @instancesByRoomId[roomId]? - @instancesByRoomId[roomId] = new WebRTCClass Meteor.userId(), roomId - - return @instancesByRoomId[roomId] - - -Meteor.startup -> - Tracker.autorun -> - if Meteor.userId() - RocketChat.Notifications.onUser 'webrtc', (type, data) => - if not data.room? then return - - webrtc = WebRTC.getInstanceByRoomId(data.room) - - webrtc.transport.onUserStream type, data diff --git a/packages/rocketchat-webrtc/client/screenShare.coffee b/packages/rocketchat-webrtc/client/screenShare.coffee deleted file mode 100644 index c157e7c045b3..000000000000 --- a/packages/rocketchat-webrtc/client/screenShare.coffee +++ /dev/null @@ -1,27 +0,0 @@ -@ChromeScreenShare = - screenCallback: undefined - - getSourceId: (navigator, callback) -> - if not callback? then throw '"callback" parameter is mandatory.' - - ChromeScreenShare.screenCallback = callback - - if navigator is 'electron' - fireGlobalEvent('get-sourceId', '*') - else - window.postMessage('get-sourceId', '*') - -window.addEventListener 'message', (e) -> - if e.origin isnt window.location.origin - return - - # "cancel" button was clicked - if e.data is 'PermissionDeniedError' - if ChromeScreenShare.screenCallback? - return ChromeScreenShare.screenCallback('PermissionDeniedError') - else - throw new Error('PermissionDeniedError') - - # extension shared temp sourceId - if e.data.sourceId? - ChromeScreenShare.screenCallback?(e.data.sourceId) diff --git a/packages/rocketchat-webrtc/package.js b/packages/rocketchat-webrtc/package.js index 66c3258e0e0e..608f3d91768e 100644 --- a/packages/rocketchat-webrtc/package.js +++ b/packages/rocketchat-webrtc/package.js @@ -8,7 +8,6 @@ Package.describe({ Package.onUse(function(api) { api.use('rocketchat:lib'); api.use('ecmascript'); - api.use('coffeescript'); api.use('templating', 'client'); api.mainModule('client/WebRTCClass.js', 'client'); diff --git a/packages/rocketchat-webrtc/server/settings.coffee b/packages/rocketchat-webrtc/server/settings.coffee deleted file mode 100644 index 84337ffbc49c..000000000000 --- a/packages/rocketchat-webrtc/server/settings.coffee +++ /dev/null @@ -1,5 +0,0 @@ -RocketChat.settings.addGroup 'WebRTC', -> - @add 'WebRTC_Enable_Channel', false, { type: 'boolean', group: 'WebRTC', public: true} - @add 'WebRTC_Enable_Private', true , { type: 'boolean', group: 'WebRTC', public: true} - @add 'WebRTC_Enable_Direct' , true , { type: 'boolean', group: 'WebRTC', public: true} - @add 'WebRTC_Servers', 'stun:stun.l.google.com:19302, stun:23.21.150.121, team%40rocket.chat:demo@turn:numb.viagenie.ca:3478', { type: 'string', group: 'WebRTC', public: true} From c9eb3a250553cb1987d06e598c95ea1aa6a420a3 Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Mon, 24 Apr 2017 11:18:25 -0300 Subject: [PATCH 04/11] fix EsLint --- packages/rocketchat-webrtc/client/screenShare.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rocketchat-webrtc/client/screenShare.js b/packages/rocketchat-webrtc/client/screenShare.js index f4ff0eef4d76..aab43e8cebfb 100644 --- a/packages/rocketchat-webrtc/client/screenShare.js +++ b/packages/rocketchat-webrtc/client/screenShare.js @@ -26,6 +26,6 @@ window.addEventListener('message', function(e) { } } if (e.data.sourceId != null) { - return typeof ChromeScreenShare.screenCallback === 'function' ? ChromeScreenShare.screenCallback(e.data.sourceId) : void 0; + return typeof ChromeScreenShare.screenCallback === 'function' && ChromeScreenShare.screenCallback(e.data.sourceId); } }); From 2540f1525334bd1b47dc6d78651b04c1a7797e69 Mon Sep 17 00:00:00 2001 From: Aaron Ogle Date: Wed, 10 May 2017 13:38:46 -0500 Subject: [PATCH 05/11] dont try to show file preview on upload if unknown file type closes 6922 closes 6917 --- packages/rocketchat-ui/client/lib/fileUpload.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/rocketchat-ui/client/lib/fileUpload.coffee b/packages/rocketchat-ui/client/lib/fileUpload.coffee index 55507fe06189..6d5600628fb6 100644 --- a/packages/rocketchat-ui/client/lib/fileUpload.coffee +++ b/packages/rocketchat-ui/client/lib/fileUpload.coffee @@ -9,6 +9,8 @@ getUploadPreview = (file, callback) -> # If greater then 10MB don't try and show a preview if file.file.size > 10 * 1000000 callback(file, null) + else if file.file.type is undefined + callback(file, null) else if file.file.type.indexOf('audio') > -1 or file.file.type.indexOf('video') > -1 or file.file.type.indexOf('image') > -1 file.type = file.file.type.split('/')[0] From 559e535ebc160a64e6651812a828ab996c07c91b Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Thu, 11 May 2017 13:49:41 -0300 Subject: [PATCH 06/11] fix conflicts --- packages/rocketchat-webrtc/WebRTCClass.coffee | 823 ------------------ .../rocketchat-webrtc/client/WebRTCClass.js | 13 +- 2 files changed, 9 insertions(+), 827 deletions(-) delete mode 100644 packages/rocketchat-webrtc/WebRTCClass.coffee diff --git a/packages/rocketchat-webrtc/WebRTCClass.coffee b/packages/rocketchat-webrtc/WebRTCClass.coffee deleted file mode 100644 index a957e411d7cc..000000000000 --- a/packages/rocketchat-webrtc/WebRTCClass.coffee +++ /dev/null @@ -1,823 +0,0 @@ -emptyFn = -> - # empty - -class WebRTCTransportClass - debug: false - - log: -> - if @debug is true - console.log.apply(console, arguments) - - constructor: (@webrtcInstance) -> - @callbacks = {} - - RocketChat.Notifications.onRoom @webrtcInstance.room, 'webrtc', (type, data) => - @log 'WebRTCTransportClass - onRoom', type, data - - switch type - when 'status' - if @callbacks['onRemoteStatus']?.length > 0 - fn(data) for fn in @callbacks['onRemoteStatus'] - - onUserStream: (type, data) -> - if data.room isnt @webrtcInstance.room then return - @log 'WebRTCTransportClass - onUser', type, data - - switch type - when 'call' - if @callbacks['onRemoteCall']?.length > 0 - fn(data) for fn in @callbacks['onRemoteCall'] - - when 'join' - if @callbacks['onRemoteJoin']?.length > 0 - fn(data) for fn in @callbacks['onRemoteJoin'] - - when 'candidate' - if @callbacks['onRemoteCandidate']?.length > 0 - fn(data) for fn in @callbacks['onRemoteCandidate'] - - when 'description' - if @callbacks['onRemoteDescription']?.length > 0 - fn(data) for fn in @callbacks['onRemoteDescription'] - - startCall: (data) -> - @log 'WebRTCTransportClass - startCall', @webrtcInstance.room, @webrtcInstance.selfId - RocketChat.Notifications.notifyUsersOfRoom @webrtcInstance.room, 'webrtc', 'call', - from: @webrtcInstance.selfId - room: @webrtcInstance.room - media: data.media - monitor: data.monitor - - joinCall: (data) -> - @log 'WebRTCTransportClass - joinCall', @webrtcInstance.room, @webrtcInstance.selfId - if data.monitor is true - RocketChat.Notifications.notifyUser data.to, 'webrtc', 'join', - from: @webrtcInstance.selfId - room: @webrtcInstance.room - media: data.media - monitor: data.monitor - else - RocketChat.Notifications.notifyUsersOfRoom @webrtcInstance.room, 'webrtc', 'join', - from: @webrtcInstance.selfId - room: @webrtcInstance.room - media: data.media - monitor: data.monitor - - sendCandidate: (data) -> - data.from = @webrtcInstance.selfId - data.room = @webrtcInstance.room - @log 'WebRTCTransportClass - sendCandidate', data - RocketChat.Notifications.notifyUser data.to, 'webrtc', 'candidate', data - - sendDescription: (data) -> - data.from = @webrtcInstance.selfId - data.room = @webrtcInstance.room - @log 'WebRTCTransportClass - sendDescription', data - RocketChat.Notifications.notifyUser data.to, 'webrtc', 'description', data - - sendStatus: (data) -> - @log 'WebRTCTransportClass - sendStatus', data, @webrtcInstance.room - data.from = @webrtcInstance.selfId - RocketChat.Notifications.notifyRoom @webrtcInstance.room, 'webrtc', 'status', data - - onRemoteCall: (fn) -> - @callbacks['onRemoteCall'] ?= [] - @callbacks['onRemoteCall'].push fn - - onRemoteJoin: (fn) -> - @callbacks['onRemoteJoin'] ?= [] - @callbacks['onRemoteJoin'].push fn - - onRemoteCandidate: (fn) -> - @callbacks['onRemoteCandidate'] ?= [] - @callbacks['onRemoteCandidate'].push fn - - onRemoteDescription: (fn) -> - @callbacks['onRemoteDescription'] ?= [] - @callbacks['onRemoteDescription'].push fn - - onRemoteStatus: (fn) -> - @callbacks['onRemoteStatus'] ?= [] - @callbacks['onRemoteStatus'].push fn - - -class WebRTCClass - config: - iceServers: [] - - debug: false - - transportClass: WebRTCTransportClass - - - ### - @param seldId {String} - @param room {String} - ### - constructor: (@selfId, @room) -> - @config.iceServers = [] - - servers = RocketChat.settings.get("WebRTC_Servers") - if servers?.trim() isnt '' - servers = servers.replace /\s/g, '' - servers = servers.split ',' - for server in servers - server = server.split '@' - serverConfig = - urls: server.pop() - - if server.length is 1 - server = server[0].split ':' - serverConfig.username = decodeURIComponent(server[0]) - serverConfig.credential = decodeURIComponent(server[1]) - - @config.iceServers.push serverConfig - - @peerConnections = {} - - @remoteItems = new ReactiveVar [] - @remoteItemsById = new ReactiveVar {} - @callInProgress = new ReactiveVar false - @audioEnabled = new ReactiveVar true - @videoEnabled = new ReactiveVar true - @overlayEnabled = new ReactiveVar false - @screenShareEnabled = new ReactiveVar false - @localUrl = new ReactiveVar - - @active = false - @remoteMonitoring = false - @monitor = false - @autoAccept = false - - @navigator = undefined - userAgent = navigator.userAgent.toLocaleLowerCase(); - if userAgent.indexOf('electron') isnt -1 - @navigator = 'electron' - else if userAgent.indexOf('chrome') isnt -1 - @navigator = 'chrome' - else if userAgent.indexOf('firefox') isnt -1 - @navigator = 'firefox' - else if userAgent.indexOf('safari') isnt -1 - @navigator = 'safari' - - @screenShareAvailable = @navigator in ['chrome', 'firefox', 'electron'] - - @media = - video: false - audio: true - - @transport = new @transportClass @ - - @transport.onRemoteCall @onRemoteCall.bind @ - @transport.onRemoteJoin @onRemoteJoin.bind @ - @transport.onRemoteCandidate @onRemoteCandidate.bind @ - @transport.onRemoteDescription @onRemoteDescription.bind @ - @transport.onRemoteStatus @onRemoteStatus.bind @ - - Meteor.setInterval @checkPeerConnections.bind(@), 1000 - - # Meteor.setInterval @broadcastStatus.bind(@), 1000 - - log: -> - if @debug is true - console.log.apply(console, arguments) - - onError: -> - console.error.apply(console, arguments) - - checkPeerConnections: -> - for id, peerConnection of @peerConnections - if peerConnection.iceConnectionState not in ['connected', 'completed'] and peerConnection.createdAt + 5000 < Date.now() - @stopPeerConnection id - - updateRemoteItems: -> - items = [] - itemsById = {} - - for id, peerConnection of @peerConnections - for remoteStream in peerConnection.getRemoteStreams() - item = - id: id - url: URL.createObjectURL(remoteStream) - state: peerConnection.iceConnectionState - - switch peerConnection.iceConnectionState - when 'checking' - item.stateText = 'Connecting...' - - when 'connected', 'completed' - item.stateText = 'Connected' - item.connected = true - - when 'disconnected' - item.stateText = 'Disconnected' - - when 'failed' - item.stateText = 'Failed' - - when 'closed' - item.stateText = 'Closed' - - items.push item - itemsById[id] = item - - @remoteItems.set items - @remoteItemsById.set itemsById - - resetCallInProgress: -> - @callInProgress.set false - - broadcastStatus: -> - if @active isnt true or @monitor is true or @remoteMonitoring is true then return - - remoteConnections = [] - for id, peerConnection of @peerConnections - remoteConnections.push - id: id - media: peerConnection.remoteMedia - - @transport.sendStatus - media: @media - remoteConnections: remoteConnections - - ### - @param data {Object} - from {String} - media {Object} - remoteConnections {Array[Object]} - id {String} - media {Object} - ### - onRemoteStatus: (data) -> - # @log 'onRemoteStatus', arguments - - @callInProgress.set true - - Meteor.clearTimeout @callInProgressTimeout - @callInProgressTimeout = Meteor.setTimeout @resetCallInProgress.bind(@), 2000 - - if @active isnt true then return - - remoteConnections = [{id: data.from, media: data.media}].concat data.remoteConnections - - for remoteConnection in remoteConnections - if remoteConnection.id isnt @selfId and not @peerConnections[remoteConnection.id]? - @log 'reconnecting with', remoteConnection.id - @onRemoteJoin - from: remoteConnection.id - media: remoteConnection.media - - ### - @param id {String} - ### - getPeerConnection: (id) -> - return @peerConnections[id] if @peerConnections[id]? - - peerConnection = new RTCPeerConnection @config - - peerConnection.createdAt = Date.now() - peerConnection.remoteMedia = {} - - @peerConnections[id] = peerConnection - - eventNames = [ - 'icecandidate' - 'addstream' - 'removestream' - 'iceconnectionstatechange' - 'datachannel' - 'identityresult' - 'idpassertionerror' - 'idpvalidationerror' - 'negotiationneeded' - 'peeridentity' - 'signalingstatechange' - ] - - for eventName in eventNames - peerConnection.addEventListener eventName, (e) => - @log id, e.type, e - - peerConnection.addEventListener 'icecandidate', (e) => - if not e.candidate? - return - - @transport.sendCandidate - to: id - candidate: - candidate: e.candidate.candidate - sdpMLineIndex: e.candidate.sdpMLineIndex - sdpMid: e.candidate.sdpMid - - peerConnection.addEventListener 'addstream', (e) => - @updateRemoteItems() - - peerConnection.addEventListener 'removestream', (e) => - @updateRemoteItems() - - peerConnection.addEventListener 'iceconnectionstatechange', (e) => - if peerConnection.iceConnectionState in ['disconnected', 'closed'] and peerConnection is @peerConnections[id] - @stopPeerConnection id - Meteor.setTimeout => - if Object.keys(@peerConnections).length is 0 - @stop() - , 3000 - - @updateRemoteItems() - - return peerConnection - - _getUserMedia: (media, onSuccess, onError) -> - onSuccessLocal = (stream) -> - if AudioContext? and stream.getAudioTracks().length > 0 - audioContext = new AudioContext - source = audioContext.createMediaStreamSource(stream) - - volume = audioContext.createGain() - source.connect(volume) - peer = audioContext.createMediaStreamDestination() - volume.connect(peer) - volume.gain.value = 0.6 - - stream.removeTrack(stream.getAudioTracks()[0]) - stream.addTrack(peer.stream.getAudioTracks()[0]) - stream.volume = volume - - this.audioContext = audioContext - - onSuccess(stream) - - navigator.getUserMedia media, onSuccessLocal, onError - - - getUserMedia: (media, onSuccess, onError=@onError) -> - if media.desktop isnt true - @_getUserMedia media, onSuccess, onError - return - - if @screenShareAvailable isnt true - console.log 'Screen share is not avaliable' - return - - getScreen = (audioStream) => - if document.cookie.indexOf("rocketchatscreenshare=chrome") is -1 and not window.rocketchatscreenshare? and @navigator isnt 'electron' - refresh = -> - swal - type: "warning" - title: TAPi18n.__ "Refresh_your_page_after_install_to_enable_screen_sharing" - - swal - type: "warning" - title: TAPi18n.__ "Screen_Share" - text: TAPi18n.__ "You_need_install_an_extension_to_allow_screen_sharing" - html: true - showCancelButton: true - confirmButtonText: TAPi18n.__ "Install_Extension" - cancelButtonText: TAPi18n.__ "Cancel" - , (isConfirm) => - if isConfirm - if @navigator is 'chrome' - url = 'https://chrome.google.com/webstore/detail/rocketchat-screen-share/nocfbnnmjnndkbipkabodnheejiegccf' - try - chrome.webstore.install url, refresh, -> - window.open(url) - refresh() - catch e - window.open(url) - refresh() - else if @navigator is 'firefox' - window.open('https://addons.mozilla.org/en-GB/firefox/addon/rocketchat-screen-share/') - refresh() - - return onError(false) - - getScreenSuccess = (stream) => - if audioStream? - stream.addTrack(audioStream.getAudioTracks()[0]) - onSuccess(stream) - - if @navigator is 'firefox' - media = - audio: media.audio - video: - mozMediaSource: 'window' - mediaSource: 'window' - @_getUserMedia media, getScreenSuccess, onError - else - ChromeScreenShare.getSourceId @navigator, (id) => - media = - audio: false - video: - mandatory: - chromeMediaSource: 'desktop' - chromeMediaSourceId: id - maxWidth: 1280 - maxHeight: 720 - - @_getUserMedia media, getScreenSuccess, onError - - if @navigator is 'firefox' or not media.audio? or media.audio is false - getScreen() - else - getAudioSuccess = (audioStream) => - getScreen(audioStream) - - getAudioError = => - getScreen() - - @_getUserMedia {audio: media.audio}, getAudioSuccess, getAudioError - - - ### - @param callback {Function} - ### - getLocalUserMedia: (callback) -> - @log 'getLocalUserMedia', arguments - - if @localStream? - return callback null, @localStream - - onSuccess = (stream) => - @localStream = stream - @localUrl.set URL.createObjectURL(stream) - - @videoEnabled.set @media.video is true - @audioEnabled.set @media.audio is true - - for id, peerConnection of @peerConnections - peerConnection.addStream stream - - callback null, @localStream - - onError = (error) => - callback false - @onError error - - @getUserMedia @media, onSuccess, onError - - - ### - @param id {String} - ### - stopPeerConnection: (id) -> - peerConnection = @peerConnections[id] - if not peerConnection? then return - - delete @peerConnections[id] - peerConnection.close() - - @updateRemoteItems() - - stopAllPeerConnections: -> - for id, peerConnection of @peerConnections - @stopPeerConnection id - window.audioContext?.close() - - setAudioEnabled: (enabled=true) -> - if @localStream? - if enabled is true and @media.audio isnt true - delete @localStream - @media.audio = true - @getLocalUserMedia => - @stopAllPeerConnections() - @joinCall() - else - @localStream.getAudioTracks().forEach (audio) -> audio.enabled = enabled - @audioEnabled.set enabled - - disableAudio: -> - @setAudioEnabled false - - enableAudio: -> - @setAudioEnabled true - - setVideoEnabled: (enabled=true) -> - if @localStream? - if enabled is true and @media.video isnt true - delete @localStream - @media.video = true - @getLocalUserMedia => - @stopAllPeerConnections() - @joinCall() - else - @localStream.getVideoTracks().forEach (video) -> video.enabled = enabled - @videoEnabled.set enabled - - disableScreenShare: -> - @setScreenShareEnabled false - - enableScreenShare: -> - @setScreenShareEnabled true - - setScreenShareEnabled: (enabled=true) -> - if @localStream? - @media.desktop = enabled - delete @localStream - @getLocalUserMedia (err) => - if err? - return - @screenShareEnabled.set enabled - @stopAllPeerConnections() - @joinCall() - - disableVideo: -> - @setVideoEnabled false - - enableVideo: -> - @setVideoEnabled true - - stop: -> - @active = false - @monitor = false - @remoteMonitoring = false - if @localStream? and typeof @localStream isnt 'undefined' - @localStream.getTracks().forEach (track) -> - track.stop() - @localUrl.set undefined - delete @localStream - - @stopAllPeerConnections() - - - ### - @param media {Object} - audio {Boolean} - video {Boolean} - ### - startCall: (media={}) -> - @log 'startCall', arguments - @media = media - @getLocalUserMedia => - @active = true - @transport.startCall - media: @media - - startCallAsMonitor: (media={}) -> - @log 'startCallAsMonitor', arguments - @media = media - @active = true - @monitor = true - @transport.startCall - media: @media - monitor: true - - - ### - @param data {Object} - from {String} - monitor {Boolean} - media {Object} - audio {Boolean} - video {Boolean} - ### - onRemoteCall: (data) -> - if @autoAccept is true - FlowRouter.goToRoomById data.room - Meteor.defer => - @joinCall - to: data.from - monitor: data.monitor - media: data.media - return - - fromUsername = Meteor.users.findOne(data.from)?.username - subscription = ChatSubscription.findOne({rid: data.room}) - - if data.monitor is true - icon = 'eye' - title = "Monitor call from #{fromUsername}" - else if subscription?.t is 'd' - if data.media?.video - icon = 'videocam' - title = "Direct video call from #{fromUsername}" - else - icon = 'phone' - title = "Direct audio call from #{fromUsername}" - else - if data.media?.video - icon = 'videocam' - title = "Group video call from #{subscription.name}" - else - icon = 'phone' - title = "Group audio call from #{subscription.name}" - - swal - title: "#{title}" - text: "Do you want to accept?" - html: true - showCancelButton: true - confirmButtonText: "Yes" - cancelButtonText: "No" - , (isConfirm) => - if isConfirm - FlowRouter.goToRoomById data.room - Meteor.defer => - @joinCall - to: data.from - monitor: data.monitor - media: data.media - else - @stop() - - - ### - @param data {Object} - to {String} - monitor {Boolean} - media {Object} - audio {Boolean} - video {Boolean} - desktop {Boolean} - ### - joinCall: (data={}) -> - if data.media?.audio? - @media.audio = data.media.audio - - if data.media?.video? - @media.video = data.media.video - - data.media = @media - - @log 'joinCall', arguments - @getLocalUserMedia => - @remoteMonitoring = data.monitor - @active = true - @transport.joinCall(data) - - - ### - @param data {Object} - from {String} - monitor {Boolean} - media {Object} - audio {Boolean} - video {Boolean} - desktop {Boolean} - ### - onRemoteJoin: (data) -> - if @active isnt true then return - - @log 'onRemoteJoin', arguments - - peerConnection = @getPeerConnection data.from - - # needsRefresh = false - # if peerConnection.iceConnectionState isnt 'new' - # needsAudio = data.media.audio is true and peerConnection.remoteMedia.audio isnt true - # needsVideo = data.media.video is true and peerConnection.remoteMedia.video isnt true - # needsRefresh = needsAudio or needsVideo or data.media.desktop isnt peerConnection.remoteMedia.desktop - - # if peerConnection.signalingState is "have-local-offer" or needsRefresh - if peerConnection.signalingState isnt "checking" - @stopPeerConnection data.from - peerConnection = @getPeerConnection data.from - - if peerConnection.iceConnectionState isnt 'new' - return - - peerConnection.remoteMedia = data.media - - peerConnection.addStream @localStream if @localStream - - onOffer = (offer) => - onLocalDescription = => - @transport.sendDescription - to: data.from - type: 'offer' - ts: peerConnection.createdAt - media: @media - description: - sdp: offer.sdp - type: offer.type - - peerConnection.setLocalDescription(new RTCSessionDescription(offer), onLocalDescription, @onError) - - if data.monitor is true - peerConnection.createOffer onOffer, @onError, - mandatory: - OfferToReceiveAudio: data.media.audio - OfferToReceiveVideo: data.media.video - else - peerConnection.createOffer(onOffer, @onError) - - - ### - @param data {Object} - from {String} - ts {Integer} - description {String} - ### - onRemoteOffer: (data) -> - if @active isnt true then return - - @log 'onRemoteOffer', arguments - peerConnection = @getPeerConnection data.from - - if peerConnection.signalingState in ["have-local-offer", "stable"] and peerConnection.createdAt < data.ts - @stopPeerConnection data.from - peerConnection = @getPeerConnection data.from - - if peerConnection.iceConnectionState isnt 'new' - return - - peerConnection.setRemoteDescription new RTCSessionDescription(data.description) - - try peerConnection.addStream @localStream if @localStream - - onAnswer = (answer) => - onLocalDescription = => - @transport.sendDescription - to: data.from - type: 'answer' - ts: peerConnection.createdAt - description: - sdp: answer.sdp - type: answer.type - - peerConnection.setLocalDescription(new RTCSessionDescription(answer), onLocalDescription, @onError) - - peerConnection.createAnswer(onAnswer, @onError) - - - ### - @param data {Object} - to {String} - from {String} - candidate {RTCIceCandidate JSON encoded} - ### - onRemoteCandidate: (data) -> - if @active isnt true then return - if data.to isnt @selfId then return - - @log 'onRemoteCandidate', arguments - peerConnection = @getPeerConnection data.from - - if peerConnection.iceConnectionState not in ["closed", "failed", "disconnected", "completed"] - peerConnection.addIceCandidate new RTCIceCandidate(data.candidate) - - - ### - @param data {Object} - to {String} - from {String} - type {String} [offer, answer] - description {RTCSessionDescription JSON encoded} - ts {Integer} - media {Object} - audio {Boolean} - video {Boolean} - desktop {Boolean} - ### - onRemoteDescription: (data) -> - if @active isnt true then return - if data.to isnt @selfId then return - - @log 'onRemoteDescription', arguments - peerConnection = @getPeerConnection data.from - - if data.type is 'offer' - peerConnection.remoteMedia = data.media - @onRemoteOffer - from: data.from - ts: data.ts - description: data.description - else - peerConnection.setRemoteDescription new RTCSessionDescription(data.description) - - -WebRTC = new class - constructor: -> - @instancesByRoomId = {} - - getInstanceByRoomId: (roomId) -> - subscription = ChatSubscription.findOne({rid: roomId}) - if not subscription - return - - enabled = false - switch subscription.t - when 'd' - enabled = RocketChat.settings.get('WebRTC_Enable_Direct') - when 'p' - enabled = RocketChat.settings.get('WebRTC_Enable_Private') - when 'c' - enabled = RocketChat.settings.get('WebRTC_Enable_Channel') - - if enabled is false - return - - if not @instancesByRoomId[roomId]? - @instancesByRoomId[roomId] = new WebRTCClass Meteor.userId(), roomId - - return @instancesByRoomId[roomId] - - -Meteor.startup -> - Tracker.autorun -> - if Meteor.userId() - RocketChat.Notifications.onUser 'webrtc', (type, data) => - if not data.room? then return - - webrtc = WebRTC.getInstanceByRoomId(data.room) - - webrtc.transport.onUserStream type, data diff --git a/packages/rocketchat-webrtc/client/WebRTCClass.js b/packages/rocketchat-webrtc/client/WebRTCClass.js index decfab47d5e5..8ac4847f9c6a 100644 --- a/packages/rocketchat-webrtc/client/WebRTCClass.js +++ b/packages/rocketchat-webrtc/client/WebRTCClass.js @@ -452,10 +452,15 @@ class WebRTCClass { }, (isConfirm) => { if (isConfirm) { if (this.navigator === 'chrome') { - chrome.webstore.install(undefined, refresh, function() { - window.open('https://chrome.google.com/webstore/detail/rocketchat-screen-share/nocfbnnmjnndkbipkabodnheejiegccf'); - refresh(); - }); + const url = 'https://chrome.google.com/webstore/detail/rocketchat-screen-share/nocfbnnmjnndkbipkabodnheejiegccf'; + try { + chrome.webstore.install(url, refresh, function() { + window.open(url); + return refresh(); + }); + } catch (_error) { + console.log(_error); + } } else if (this.navigator === 'firefox') { window.open('https://addons.mozilla.org/en-GB/firefox/addon/rocketchat-screen-share/'); refresh(); From b427d4ba33d8f13575c2e78c0fa95ed4285fdf3c Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Thu, 11 May 2017 14:17:36 -0300 Subject: [PATCH 07/11] fix reviews --- .../rocketchat-webrtc/client/WebRTCClass.js | 43 ++++--------------- .../rocketchat-webrtc/client/screenShare.js | 6 +-- 2 files changed, 11 insertions(+), 38 deletions(-) diff --git a/packages/rocketchat-webrtc/client/WebRTCClass.js b/packages/rocketchat-webrtc/client/WebRTCClass.js index 8ac4847f9c6a..dad5ba81367e 100644 --- a/packages/rocketchat-webrtc/client/WebRTCClass.js +++ b/packages/rocketchat-webrtc/client/WebRTCClass.js @@ -164,7 +164,6 @@ class WebRTCClass { this.TransportClass = WebRTCTransportClass; this.selfId = selfId; this.room = room; - this.config.iceServers = []; let servers = RocketChat.settings.get('WebRTC_Servers'); if (servers && servers.trim() !== '') { servers = servers.replace(/\s/g, ''); @@ -259,7 +258,6 @@ class WebRTCClass { url: URL.createObjectURL(remoteStream), state: peerConnection.iceConnectionState }; - console.log(item); switch (peerConnection.iceConnectionState) { case 'checking': item.stateText = 'Connecting...'; @@ -421,10 +419,7 @@ class WebRTCClass { return navigator.getUserMedia(media, onSuccessLocal, onError); } - getUserMedia(media, onSuccess, onError) { - if (onError == null) { - onError = this.onError; - } + getUserMedia(media, onSuccess, onError = this.onError) { if (media.desktop !== true) { this._getUserMedia(media, onSuccess, onError); return; @@ -571,10 +566,7 @@ class WebRTCClass { window.audioContext && window.audioContext.close(); } - setAudioEnabled(enabled) { - if (enabled == null) { - enabled = true; - } + setAudioEnabled(enabled = true) { if (this.localStream != null) { if (enabled === true && this.media.audio !== true) { delete this.localStream; @@ -600,10 +592,7 @@ class WebRTCClass { return this.setAudioEnabled(true); } - setVideoEnabled(enabled) { - if (enabled == null) { - enabled = true; - } + setVideoEnabled(enabled = true) { if (this.localStream != null) { if (enabled === true && this.media.video !== true) { delete this.localStream; @@ -629,10 +618,7 @@ class WebRTCClass { return this.setScreenShareEnabled(true); } - setScreenShareEnabled(enabled) { - if (enabled == null) { - enabled = true; - } + setScreenShareEnabled(enabled = true) { if (this.localStream != null) { this.media.desktop = enabled; delete this.localStream; @@ -659,10 +645,8 @@ class WebRTCClass { this.active = false; this.monitor = false; this.remoteMonitoring = false; - if ((this.localStream != null) && typeof this.localStream !== 'undefined') { - this.localStream.getTracks().forEach(function(track) { - return track.stop(); - }); + if (this.localStream != null && typeof this.localStream !== 'undefined') { + this.localStream.getTracks().forEach(track => track.stop()); } this.localUrl.set(undefined); delete this.localStream; @@ -676,10 +660,7 @@ class WebRTCClass { video {Boolean} */ - startCall(media) { - if (media == null) { - media = {}; - } + startCall(media = {}) { this.log('startCall', arguments); this.media = media; return this.getLocalUserMedia(() => { @@ -690,10 +671,7 @@ class WebRTCClass { }); } - startCallAsMonitor(media) { - if (media == null) { - media = {}; - } + startCallAsMonitor(media = {}) { this.log('startCallAsMonitor', arguments); this.media = media; this.active = true; @@ -790,10 +768,7 @@ class WebRTCClass { desktop {Boolean} */ - joinCall(data) { - if (data == null) { - data = {}; - } + joinCall(data = {}) { if (data.media && data.media.audio) { this.media.audio = data.media.audio; } diff --git a/packages/rocketchat-webrtc/client/screenShare.js b/packages/rocketchat-webrtc/client/screenShare.js index aab43e8cebfb..22f9ba2dc951 100644 --- a/packages/rocketchat-webrtc/client/screenShare.js +++ b/packages/rocketchat-webrtc/client/screenShare.js @@ -8,9 +8,8 @@ this.ChromeScreenShare = { ChromeScreenShare.screenCallback = callback; if (navigator === 'electron') { return fireGlobalEvent('get-sourceId', '*'); - } else { - return window.postMessage('get-sourceId', '*'); } + return window.postMessage('get-sourceId', '*'); } }; @@ -21,9 +20,8 @@ window.addEventListener('message', function(e) { if (e.data === 'PermissionDeniedError') { if (ChromeScreenShare.screenCallback != null) { return ChromeScreenShare.screenCallback('PermissionDeniedError'); - } else { - throw new Error('PermissionDeniedError'); } + throw new Error('PermissionDeniedError'); } if (e.data.sourceId != null) { return typeof ChromeScreenShare.screenCallback === 'function' && ChromeScreenShare.screenCallback(e.data.sourceId); From c816950bded20f6c5f41d77bb1a3c6ff7c9d6a49 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Thu, 11 May 2017 14:25:23 -0300 Subject: [PATCH 08/11] [NEW] Show info about multiple instances at admin page --- packages/rocketchat-i18n/i18n/en.i18n.json | 7 ++- .../client/adminInfo.coffee | 11 ++++- .../rocketchat-ui-admin/client/adminInfo.html | 44 +++++++++++++++++++ server/stream/streamBroadcast.js | 15 +++++++ 4 files changed, 75 insertions(+), 2 deletions(-) diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 1d18d31ef923..6500fda913b1 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -267,6 +267,7 @@ "BotHelpers_userFields": "User Fields", "BotHelpers_userFields_Description": "CSV of user fields that can be accessed by bots helper methods.", "Branch": "Branch", + "Broadcast_Connected_Instances": "Broadcast Connected Instances", "Bugsnag_api_key": "Bugsnag API Key", "busy": "busy", "Busy": "Busy", @@ -362,6 +363,7 @@ "CROWD_Reject_Unauthorized": "Reject Unauthorized", "CRM_Integration": "CRM Integration", "Current_Chats": "Current Chats", + "Current_Status": "Current Status", "Custom": "Custom", "Custom_Emoji": "Custom Emoji", "Custom_Emoji_Add": "Add New Emoji", @@ -706,6 +708,7 @@ "Install_FxOs_follow_instructions": "Please confirm the app installation on your device (press \"Install\" when prompted).", "Installation": "Installation", "Installed_at": "Installed at", + "Instance_Record": "Instance Record", "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "Instructions to your visitor fill the form to send a message", "Impersonate_user": "Impersonate User", "Impersonate_user_description": "When enabled, integration posts as the user that triggered integration", @@ -1256,6 +1259,7 @@ "Reset_password": "Reset password", "Restart": "Restart", "Restart_the_server": "Restart the server", + "Retry_Count": "Retry Count", "Role": "Role", "Role_Editing": "Role Editing", "Role_removed": "Role removed", @@ -1560,6 +1564,7 @@ "Unread_Rooms": "Unread Rooms", "Unread_Rooms_Mode": "Unread Rooms Mode", "Unstar_Message": "Remove Star", + "Updated_at": "Updated at", "Upload_file_description": "File description", "Upload_file_name": "File name", "Upload_file_question": "Upload file?", @@ -1719,4 +1724,4 @@ "your_message_optional": "your message (optional)", "Your_password_is_wrong": "Your password is wrong!", "Your_push_was_sent_to_s_devices": "Your push was sent to %s devices" -} \ No newline at end of file +} diff --git a/packages/rocketchat-ui-admin/client/adminInfo.coffee b/packages/rocketchat-ui-admin/client/adminInfo.coffee index 51cea0151143..1bfaedf4c899 100644 --- a/packages/rocketchat-ui-admin/client/adminInfo.coffee +++ b/packages/rocketchat-ui-admin/client/adminInfo.coffee @@ -5,6 +5,8 @@ Template.adminInfo.helpers return Template.instance().ready.get() statistics: -> return Template.instance().statistics.get() + instances: -> + return Template.instance().instances.get() inGB: (size) -> if size > 1073741824 return _.numberFormat(size / 1024 / 1024 / 1024, 2) + ' GB' @@ -52,13 +54,20 @@ Template.adminInfo.onRendered -> Template.adminInfo.onCreated -> instance = @ @statistics = new ReactiveVar {} + @instances = new ReactiveVar [] @ready = new ReactiveVar false if RocketChat.authz.hasAllPermission('view-statistics') Meteor.call 'getStatistics', (error, statistics) -> - instance.ready.set true if error handleError(error) else instance.statistics.set statistics + Meteor.call 'instances/get', (error, instances) -> + instance.ready.set true + if error + handleError(error) + else + instance.instances.set instances + diff --git a/packages/rocketchat-ui-admin/client/adminInfo.html b/packages/rocketchat-ui-admin/client/adminInfo.html index e3d5358d08eb..c900a7c6dac6 100644 --- a/packages/rocketchat-ui-admin/client/adminInfo.html +++ b/packages/rocketchat-ui-admin/client/adminInfo.html @@ -226,6 +226,50 @@

{{_ "Usage"}}

+ {{#if instances}} +

{{_ "Broadcast_Connected_Instances"}}

+ {{#each instances}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{_ "Address"}}{{address}}
{{_ "Auth"}}{{broadcastAuth}}
{{_ "Current_Status"}} > {{_ "Connected"}}{{currentStatus.connected}}
{{_ "Current_Status"}} > {{_ "Retry_Count"}}{{currentStatus.retryCount}}
{{_ "Current_Status"}} > {{_ "Status"}}{{currentStatus.status}}
{{_ "Instance_Record"}} > {{_ "ID"}}{{instanceRecord._id}}
{{_ "Instance_Record"}} > {{_ "PID"}}{{instanceRecord.pid}}
{{_ "Instance_Record"}} > {{_ "Created_at"}}{{formatDate instanceRecord._createdAt}}
{{_ "Instance_Record"}} > {{_ "Updated_at"}}{{formatDate instanceRecord._updatedAt}}
+ {{/each}} + {{/if}} + {{else}} {{_ "Loading..."}} diff --git a/server/stream/streamBroadcast.js b/server/stream/streamBroadcast.js index 6de334e34b95..b9b43639eae8 100644 --- a/server/stream/streamBroadcast.js +++ b/server/stream/streamBroadcast.js @@ -257,3 +257,18 @@ function startStreamBroadcast() { Meteor.startup(function() { return startStreamBroadcast(); }); + +Meteor.methods({ + 'instances/get'() { + if (!RocketChat.authz.hasPermission(Meteor.userId(), 'view-statistics')) { + throw new Meteor.Error('error-action-not-allowed', 'List instances is not allowed', { + method: 'instances/get' + }); + } + + return Object.keys(connections).map(address => { + const conn = connections[address]; + return Object.assign({ address, currentStatus: conn._stream.currentStatus }, _.pick(conn, 'instanceRecord', 'broadcastAuth')); + }); + } +}); From 879f169fd5a3743fe9f47cd28eeb5b96076a765b Mon Sep 17 00:00:00 2001 From: Martin Schoeler Date: Thu, 11 May 2017 16:15:25 -0300 Subject: [PATCH 09/11] remove useless returns --- .../rocketchat-webrtc/client/WebRTCClass.js | 161 +++++++++--------- 1 file changed, 78 insertions(+), 83 deletions(-) diff --git a/packages/rocketchat-webrtc/client/WebRTCClass.js b/packages/rocketchat-webrtc/client/WebRTCClass.js index dad5ba81367e..dfbccbd4391f 100644 --- a/packages/rocketchat-webrtc/client/WebRTCClass.js +++ b/packages/rocketchat-webrtc/client/WebRTCClass.js @@ -10,7 +10,7 @@ class WebRTCTransportClass { switch (type) { case 'status': if (onRemoteStatus && onRemoteStatus.length) { - onRemoteStatus.forEach((fn) => fn(data)); + onRemoteStatus.forEach(fn => fn(data)); } } }); @@ -18,7 +18,7 @@ class WebRTCTransportClass { log() { if (this.debug === true) { - return console.log.apply(console, arguments); + console.log.apply(console, arguments); } } @@ -35,31 +35,29 @@ class WebRTCTransportClass { switch (type) { case 'call': if (onRemoteCall && onRemoteCall.length) { - onRemoteCall.forEach((fn) => { - fn(data); - }); + onRemoteCall.forEach(fn => fn(data)); } break; case 'join': if (onRemoteJoin && onRemoteJoin.length) { - onRemoteJoin.forEach((fn) => fn(data)); + onRemoteJoin.forEach(fn => fn(data)); } break; case 'candidate': if (onRemoteCandidate && onRemoteCandidate.length) { - onRemoteCandidate.forEach((fn) => fn(data)); + onRemoteCandidate.forEach(fn => fn(data)); } break; case 'description': if (onRemoteDescription && onRemoteDescription.length) { - onRemoteDescription.forEach((fn) => fn(data)); + onRemoteDescription.forEach(fn => fn(data)); } } } startCall(data) { this.log('WebRTCTransportClass - startCall', this.webrtcInstance.room, this.webrtcInstance.selfId); - return RocketChat.Notifications.notifyUsersOfRoom(this.webrtcInstance.room, 'webrtc', 'call', { + RocketChat.Notifications.notifyUsersOfRoom(this.webrtcInstance.room, 'webrtc', 'call', { from: this.webrtcInstance.selfId, room: this.webrtcInstance.room, media: data.media, @@ -70,14 +68,14 @@ class WebRTCTransportClass { joinCall(data) { this.log('WebRTCTransportClass - joinCall', this.webrtcInstance.room, this.webrtcInstance.selfId); if (data.monitor === true) { - return RocketChat.Notifications.notifyUser(data.to, 'webrtc', 'join', { + RocketChat.Notifications.notifyUser(data.to, 'webrtc', 'join', { from: this.webrtcInstance.selfId, room: this.webrtcInstance.room, media: data.media, monitor: data.monitor }); } else { - return RocketChat.Notifications.notifyUsersOfRoom(this.webrtcInstance.room, 'webrtc', 'join', { + RocketChat.Notifications.notifyUsersOfRoom(this.webrtcInstance.room, 'webrtc', 'join', { from: this.webrtcInstance.selfId, room: this.webrtcInstance.room, media: data.media, @@ -90,20 +88,20 @@ class WebRTCTransportClass { data.from = this.webrtcInstance.selfId; data.room = this.webrtcInstance.room; this.log('WebRTCTransportClass - sendCandidate', data); - return RocketChat.Notifications.notifyUser(data.to, 'webrtc', 'candidate', data); + RocketChat.Notifications.notifyUser(data.to, 'webrtc', 'candidate', data); } sendDescription(data) { data.from = this.webrtcInstance.selfId; data.room = this.webrtcInstance.room; this.log('WebRTCTransportClass - sendDescription', data); - return RocketChat.Notifications.notifyUser(data.to, 'webrtc', 'description', data); + RocketChat.Notifications.notifyUser(data.to, 'webrtc', 'description', data); } sendStatus(data) { this.log('WebRTCTransportClass - sendStatus', data, this.webrtcInstance.room); data.from = this.webrtcInstance.selfId; - return RocketChat.Notifications.notifyRoom(this.webrtcInstance.room, 'webrtc', 'status', data); + RocketChat.Notifications.notifyRoom(this.webrtcInstance.room, 'webrtc', 'status', data); } onRemoteCall(fn) { @@ -111,7 +109,7 @@ class WebRTCTransportClass { if (callbacks['onRemoteCall'] == null) { callbacks['onRemoteCall'] = []; } - return callbacks['onRemoteCall'].push(fn); + callbacks['onRemoteCall'].push(fn); } onRemoteJoin(fn) { @@ -119,7 +117,7 @@ class WebRTCTransportClass { if (callbacks['onRemoteJoin'] == null) { callbacks['onRemoteJoin'] = []; } - return callbacks['onRemoteJoin'].push(fn); + callbacks['onRemoteJoin'].push(fn); } onRemoteCandidate(fn) { @@ -127,7 +125,7 @@ class WebRTCTransportClass { if (callbacks['onRemoteCandidate'] == null) { callbacks['onRemoteCandidate'] = []; } - return callbacks['onRemoteCandidate'].push(fn); + callbacks['onRemoteCandidate'].push(fn); } onRemoteDescription(fn) { @@ -135,7 +133,7 @@ class WebRTCTransportClass { if (callbacks['onRemoteDescription'] == null) { callbacks['onRemoteDescription'] = []; } - return callbacks['onRemoteDescription'].push(fn); + callbacks['onRemoteDescription'].push(fn); } onRemoteStatus(fn) { @@ -143,7 +141,7 @@ class WebRTCTransportClass { if (callbacks['onRemoteStatus'] == null) { callbacks['onRemoteStatus'] = []; } - return callbacks['onRemoteStatus'].push(fn); + callbacks['onRemoteStatus'].push(fn); } @@ -169,7 +167,7 @@ class WebRTCClass { servers = servers.replace(/\s/g, ''); servers = servers.split(','); - servers.forEach((server) => { + servers.forEach(server => { server = server.split('@'); const serverConfig = { urls: server.pop() @@ -226,17 +224,17 @@ class WebRTCClass { log() { if (this.debug === true) { - return console.log.apply(console, arguments); + console.log.apply(console, arguments); } } onError() { - return console.error.apply(console, arguments); + console.error.apply(console, arguments); } checkPeerConnections() { const peerConnections = this.peerConnections; - Object.keys(peerConnections).forEach((id) => { + Object.keys(peerConnections).forEach(id => { const peerConnection = peerConnections[id]; if (peerConnection.iceConnectionState !== 'connected' && peerConnection.iceConnectionState !== 'completed' && peerConnection.createdAt + 5000 < Date.now()) { this.stopPeerConnection(id); @@ -249,10 +247,10 @@ class WebRTCClass { const itemsById = {}; const peerConnections = this.peerConnections; - Object.keys(peerConnections).forEach((id) => { + Object.keys(peerConnections).forEach(id => { const peerConnection = peerConnections[id]; - peerConnection.getRemoteStreams().forEach((remoteStream) => { + peerConnection.getRemoteStreams().forEach(remoteStream => { const item = { id, url: URL.createObjectURL(remoteStream), @@ -285,7 +283,7 @@ class WebRTCClass { } resetCallInProgress() { - return this.callInProgress.set(false); + this.callInProgress.set(false); } broadcastStatus() { @@ -294,7 +292,7 @@ class WebRTCClass { } const remoteConnections = []; const peerConnections = this.peerConnections; - Object.keys(peerConnections).forEach((id) => { + Object.keys(peerConnections).forEach(id => { const peerConnection = peerConnections[id]; remoteConnections.push({ id, @@ -302,7 +300,7 @@ class WebRTCClass { }); }); - return this.transport.sendStatus({ + this.transport.sendStatus({ media: this.media, remoteConnections }); @@ -332,7 +330,7 @@ class WebRTCClass { }, ...data.remoteConnections]; - remoteConnections.forEach((remoteConnection) => { + remoteConnections.forEach(remoteConnection => { if (remoteConnection.id !== this.selfId && (this.peerConnections[remoteConnection.id] == null)) { this.log('reconnecting with', remoteConnection.id); this.onRemoteJoin({ @@ -359,7 +357,7 @@ class WebRTCClass { this.peerConnections[id] = peerConnection; const eventNames = ['icecandidate', 'addstream', 'removestream', 'iceconnectionstatechange', 'datachannel', 'identityresult', 'idpassertionerror', 'idpvalidationerror', 'negotiationneeded', 'peeridentity', 'signalingstatechange']; - eventNames.forEach((eventName) => { + eventNames.forEach(eventName => { peerConnection.addEventListener(eventName, (e) => { this.log(id, e.type, e); }); @@ -385,8 +383,7 @@ class WebRTCClass { this.updateRemoteItems(); }); peerConnection.addEventListener('iceconnectionstatechange', () => { - let ref; - if ((peerConnection.iceConnectionState === 'disconnected' || ref === 'closed') && peerConnection === this.peerConnections[id]) { + if ((peerConnection.iceConnectionState === 'disconnected' || peerConnection.iceConnectionState === 'closed') && peerConnection === this.peerConnections[id]) { this.stopPeerConnection(id); Meteor.setTimeout(() => { if (Object.keys(this.peerConnections).length === 0) { @@ -414,9 +411,9 @@ class WebRTCClass { stream.volume = volume; this.audioContext = audioContext; } - return onSuccess(stream); + onSuccess(stream); }; - return navigator.getUserMedia(media, onSuccessLocal, onError); + navigator.getUserMedia(media, onSuccessLocal, onError); } getUserMedia(media, onSuccess, onError = this.onError) { @@ -451,7 +448,7 @@ class WebRTCClass { try { chrome.webstore.install(url, refresh, function() { window.open(url); - return refresh(); + refresh(); }); } catch (_error) { console.log(_error); @@ -497,10 +494,10 @@ class WebRTCClass { } }; if (this.navigator === 'firefox' || (media.audio == null) || media.audio === false) { - return getScreen(); + getScreen(); } else { const getAudioSuccess = (audioStream) => { - return getScreen(audioStream); + getScreen(audioStream); }; const getAudioError = () => { getScreen(); @@ -528,17 +525,17 @@ class WebRTCClass { this.videoEnabled.set(this.media.video === true); this.audioEnabled.set(this.media.audio === true); const peerConnections = this.peerConnections; - Object.keys(peerConnections).forEach((id) => { + Object.keys(peerConnections).forEach(id => { const peerConnection = peerConnections[id]; peerConnection.addStream(stream); }); - return callback(null, this.localStream); + callback(null, this.localStream); }; const onError = (error) => { callback(false); - return this.onError(error); + this.onError(error); }; - return this.getUserMedia(this.media, onSuccess, onError); + this.getUserMedia(this.media, onSuccess, onError); } @@ -559,7 +556,7 @@ class WebRTCClass { stopAllPeerConnections() { const peerConnections = this.peerConnections; - Object.keys(peerConnections).forEach((id) => { + Object.keys(peerConnections).forEach(id => { this.stopPeerConnection(id); }); @@ -571,25 +568,25 @@ class WebRTCClass { if (enabled === true && this.media.audio !== true) { delete this.localStream; this.media.audio = true; - return this.getLocalUserMedia(() => { + this.getLocalUserMedia(() => { this.stopAllPeerConnections(); - return this.joinCall(); + this.joinCall(); }); } else { this.localStream.getAudioTracks().forEach(function(audio) { - return audio.enabled = enabled; + audio.enabled = enabled; }); - return this.audioEnabled.set(enabled); + this.audioEnabled.set(enabled); } } } disableAudio() { - return this.setAudioEnabled(false); + this.setAudioEnabled(false); } enableAudio() { - return this.setAudioEnabled(true); + this.setAudioEnabled(true); } setVideoEnabled(enabled = true) { @@ -597,48 +594,48 @@ class WebRTCClass { if (enabled === true && this.media.video !== true) { delete this.localStream; this.media.video = true; - return this.getLocalUserMedia(() => { + this.getLocalUserMedia(() => { this.stopAllPeerConnections(); - return this.joinCall(); + this.joinCall(); }); } else { this.localStream.getVideoTracks().forEach(function(video) { - return video.enabled = enabled; + video.enabled = enabled; }); - return this.videoEnabled.set(enabled); + this.videoEnabled.set(enabled); } } } disableScreenShare() { - return this.setScreenShareEnabled(false); + this.setScreenShareEnabled(false); } enableScreenShare() { - return this.setScreenShareEnabled(true); + this.setScreenShareEnabled(true); } setScreenShareEnabled(enabled = true) { if (this.localStream != null) { this.media.desktop = enabled; delete this.localStream; - return this.getLocalUserMedia((err) => { + this.getLocalUserMedia(err => { if (err != null) { return; } this.screenShareEnabled.set(enabled); this.stopAllPeerConnections(); - return this.joinCall(); + this.joinCall(); }); } } disableVideo() { - return this.setVideoEnabled(false); + this.setVideoEnabled(false); } enableVideo() { - return this.setVideoEnabled(true); + this.setVideoEnabled(true); } stop() { @@ -650,7 +647,7 @@ class WebRTCClass { } this.localUrl.set(undefined); delete this.localStream; - return this.stopAllPeerConnections(); + this.stopAllPeerConnections(); } @@ -663,9 +660,9 @@ class WebRTCClass { startCall(media = {}) { this.log('startCall', arguments); this.media = media; - return this.getLocalUserMedia(() => { + this.getLocalUserMedia(() => { this.active = true; - return this.transport.startCall({ + this.transport.startCall({ media: this.media }); }); @@ -676,7 +673,7 @@ class WebRTCClass { this.media = media; this.active = true; this.monitor = true; - return this.transport.startCall({ + this.transport.startCall({ media: this.media, monitor: true }); @@ -696,7 +693,7 @@ class WebRTCClass { if (this.autoAccept === true) { FlowRouter.goToRoomById(data.room); Meteor.defer(() => { - return this.joinCall({ + this.joinCall({ to: data.from, monitor: data.monitor, media: data.media @@ -734,7 +731,7 @@ class WebRTCClass { icon = 'phone'; title = `Group audio call from ${ subscription.name }`; } - return swal({ + swal({ title: `${ title }`, text: 'Do you want to accept?', html: true, @@ -744,15 +741,15 @@ class WebRTCClass { }, (isConfirm) => { if (isConfirm) { FlowRouter.goToRoomById(data.room); - return Meteor.defer(() => { - return this.joinCall({ + Meteor.defer(() => { + this.joinCall({ to: data.from, monitor: data.monitor, media: data.media }); }); } else { - return this.stop(); + this.stop(); } }); } @@ -813,7 +810,7 @@ class WebRTCClass { } const onOffer = offer => { const onLocalDescription = () => { - return this.transport.sendDescription({ + this.transport.sendDescription({ to: data.from, type: 'offer', ts: peerConnection.createdAt, @@ -825,18 +822,18 @@ class WebRTCClass { }); }; - return peerConnection.setLocalDescription(new RTCSessionDescription(offer), onLocalDescription, this.onError); + peerConnection.setLocalDescription(new RTCSessionDescription(offer), onLocalDescription, this.onError); }; if (data.monitor === true) { - return peerConnection.createOffer(onOffer, this.onError, { + peerConnection.createOffer(onOffer, this.onError, { mandatory: { OfferToReceiveAudio: data.media.audio, OfferToReceiveVideo: data.media.video } }); } else { - return peerConnection.createOffer(onOffer, this.onError); + peerConnection.createOffer(onOffer, this.onError); } } @@ -871,7 +868,7 @@ class WebRTCClass { const onAnswer = answer => { const onLocalDescription = () => { - return this.transport.sendDescription({ + this.transport.sendDescription({ to: data.from, type: 'answer', ts: peerConnection.createdAt, @@ -882,10 +879,10 @@ class WebRTCClass { }); }; - return peerConnection.setLocalDescription(new RTCSessionDescription(answer), onLocalDescription, this.onError); + peerConnection.setLocalDescription(new RTCSessionDescription(answer), onLocalDescription, this.onError); }; - return peerConnection.createAnswer(onAnswer, this.onError); + peerConnection.createAnswer(onAnswer, this.onError); } @@ -906,7 +903,7 @@ class WebRTCClass { this.log('onRemoteCandidate', arguments); const peerConnection = this.getPeerConnection(data.from); if (peerConnection.iceConnectionState !== 'closed' && peerConnection.iceConnectionState !== 'failed' && peerConnection.iceConnectionState !== 'disconnected' && peerConnection.iceConnectionState !== 'completed') { - return peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate)); + peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate)); } } @@ -935,13 +932,13 @@ class WebRTCClass { const peerConnection = this.getPeerConnection(data.from); if (data.type === 'offer') { peerConnection.remoteMedia = data.media; - return this.onRemoteOffer({ + this.onRemoteOffer({ from: data.from, ts: data.ts, description: data.description }); } else { - return peerConnection.setRemoteDescription(new RTCSessionDescription(data.description)); + peerConnection.setRemoteDescription(new RTCSessionDescription(data.description)); } } @@ -953,9 +950,7 @@ const WebRTC = new class { } getInstanceByRoomId(roomId) { - const subscription = ChatSubscription.findOne({ - rid: roomId - }); + const subscription = ChatSubscription.findOne({ rid: roomId }); if (!subscription) { return; } @@ -981,14 +976,14 @@ const WebRTC = new class { }; Meteor.startup(function() { - return Tracker.autorun(function() { + Tracker.autorun(function() { if (Meteor.userId()) { - return RocketChat.Notifications.onUser('webrtc', (type, data) => { + RocketChat.Notifications.onUser('webrtc', (type, data) => { if (data.room == null) { return; } const webrtc = WebRTC.getInstanceByRoomId(data.room); - return webrtc.transport.onUserStream(type, data); + webrtc.transport.onUserStream(type, data); }); } }); From 066bae49b6f7f1f2527b852aabd297a8f17fe5ea Mon Sep 17 00:00:00 2001 From: Aaron Ogle Date: Thu, 11 May 2017 14:18:11 -0500 Subject: [PATCH 10/11] check falsy --- packages/rocketchat-ui/client/lib/fileUpload.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rocketchat-ui/client/lib/fileUpload.coffee b/packages/rocketchat-ui/client/lib/fileUpload.coffee index 6d5600628fb6..e4494b7d7868 100644 --- a/packages/rocketchat-ui/client/lib/fileUpload.coffee +++ b/packages/rocketchat-ui/client/lib/fileUpload.coffee @@ -9,7 +9,7 @@ getUploadPreview = (file, callback) -> # If greater then 10MB don't try and show a preview if file.file.size > 10 * 1000000 callback(file, null) - else if file.file.type is undefined + else if not file.file.type? callback(file, null) else if file.file.type.indexOf('audio') > -1 or file.file.type.indexOf('video') > -1 or file.file.type.indexOf('image') > -1 From 079b7ff7c44b2978a353eef828e9818661bef110 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Thu, 11 May 2017 17:49:53 -0300 Subject: [PATCH 11/11] Send tag builds to github releases page --- .travis.yml | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3192c2d4b139..80a8b6cc8d64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -72,16 +72,24 @@ before_deploy: - ".travis/namefiles.sh" - echo ".travis/sandstorm.sh" deploy: - provider: s3 - access_key_id: AKIAIKIA7H7D47KUHYCA - secret_access_key: "$ACCESSKEY" - bucket: download.rocket.chat - skip_cleanup: true - upload_dir: build - local_dir: "$ROCKET_DEPLOY_DIR" - on: - condition: "$TRAVIS_PULL_REQUEST=false" - all_branches: true + - provider: s3 + access_key_id: AKIAIKIA7H7D47KUHYCA + secret_access_key: "$ACCESSKEY" + bucket: download.rocket.chat + skip_cleanup: true + upload_dir: build + local_dir: "$ROCKET_DEPLOY_DIR" + on: + condition: "$TRAVIS_PULL_REQUEST=false" + all_branches: true + - provider: releases + api-key: "$GITHUB_TOKEN" + file_glob: true + file: build/* + skip_cleanup: true + on: + tags: true + after_deploy: - ".travis/docker.sh" - ".travis/update-releases.sh"