531241d269
* Provide a link to transfer user to mobile App * show menu option only if appStoreLink is present and it is not running on mobile app already
302 lines
14 KiB
JavaScript
302 lines
14 KiB
JavaScript
import browserInfo from '/imports/utils/browserInfo';
|
|
import logger from '/imports/startup/client/logger';
|
|
import Auth from '/imports/ui/services/auth';
|
|
import { fetchStunTurnServers } from '/imports/utils/fetchStunTurnServers';
|
|
|
|
(function (){
|
|
// This function must be executed during the import time, that's why it's not exported to the caller component.
|
|
// It's needed because it changes some functions provided by browser, and these functions are verified during
|
|
// import time (like in ScreenshareBridgeService)
|
|
if(browserInfo.isTabletApp) {
|
|
logger.debug(`BBB-MOBILE - Mobile APP detected`);
|
|
|
|
const WEBRTC_CALL_TYPE_FULL_AUDIO = 'full_audio';
|
|
const WEBRTC_CALL_TYPE_SCREEN_SHARE = 'screen_share';
|
|
const WEBRTC_CALL_TYPE_STANDARD = 'standard';
|
|
|
|
// This function detects if the call happened to publish a screenshare
|
|
function detectWebRtcCallType(caller, peerConnection = null, args = null) {
|
|
// Keep track of how many webRTC evaluations was done
|
|
if(!peerConnection.detectWebRtcCallTypeEvaluations)
|
|
peerConnection.detectWebRtcCallTypeEvaluations = 0;
|
|
|
|
peerConnection.detectWebRtcCallTypeEvaluations ++;
|
|
|
|
// If already successfully evaluated, reuse
|
|
if(peerConnection && peerConnection.webRtcCallType !== undefined ) {
|
|
logger.info(`BBB-MOBILE - detectWebRtcCallType (already evaluated as ${peerConnection.webRtcCallType})`, {caller, peerConnection});
|
|
return peerConnection.webRtcCallType;
|
|
}
|
|
|
|
// Evaluate context otherwise
|
|
const e = new Error('dummy');
|
|
const stackTrace = e.stack;
|
|
logger.info(`BBB-MOBILE - detectWebRtcCallType (evaluating)`, {caller, peerConnection, stackTrace: stackTrace.split('\n'), detectWebRtcCallTypeEvaluations: peerConnection.detectWebRtcCallTypeEvaluations, args});
|
|
|
|
// addEventListener is the first call for screensharing and it has a startScreensharing in its stackTrace
|
|
if( peerConnection.detectWebRtcCallTypeEvaluations == 1) {
|
|
if(caller == 'addEventListener' && stackTrace.indexOf('startScreensharing') !== -1) {
|
|
peerConnection.webRtcCallType = WEBRTC_CALL_TYPE_SCREEN_SHARE; // this uses mobile app broadcast upload extension
|
|
} else if(caller == 'addEventListener' && stackTrace.indexOf('invite') !== -1) {
|
|
peerConnection.webRtcCallType = WEBRTC_CALL_TYPE_FULL_AUDIO; // this uses mobile app webRTC
|
|
} else {
|
|
peerConnection.webRtcCallType = WEBRTC_CALL_TYPE_STANDARD; // this uses the webview webRTC
|
|
}
|
|
|
|
return peerConnection.webRtcCallType;
|
|
}
|
|
|
|
}
|
|
// Store the method call sequential
|
|
const sequenceHolder = {sequence: 0};
|
|
|
|
// Store the promise for each method call
|
|
const promisesHolder = {};
|
|
|
|
// Call a method in the mobile application, returning a promise for its execution
|
|
function callNativeMethod(method, args=[]) {
|
|
try {
|
|
const sequence = ++sequenceHolder.sequence;
|
|
|
|
return new Promise ( (resolve, reject) => {
|
|
promisesHolder[sequence] = {
|
|
resolve, reject
|
|
};
|
|
|
|
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
sequence: sequenceHolder.sequence,
|
|
method: method,
|
|
arguments: args,
|
|
}));
|
|
} );
|
|
} catch(e) {
|
|
logger.error(`Error on callNativeMethod ${e.message}`, e);
|
|
}
|
|
}
|
|
|
|
// This method is called from the mobile app to notify us about a method invocation result
|
|
window.nativeMethodCallResult = (sequence, isResolve, resultOrException) => {
|
|
|
|
const promise = promisesHolder[sequence];
|
|
if(promise) {
|
|
if(isResolve) {
|
|
promise.resolve( resultOrException );
|
|
delete promisesHolder[sequence];
|
|
} else {
|
|
promise.reject( resultOrException );
|
|
delete promisesHolder[sequence];
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// WebRTC replacement functions
|
|
const buildVideoTrack = function () {}
|
|
const stream = {};
|
|
|
|
// Navigator
|
|
navigator.getDisplayMedia = function() {
|
|
logger.info(`BBB-MOBILE - getDisplayMedia called`, arguments);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
callNativeMethod('initializeScreenShare').then(
|
|
() => {
|
|
const fakeVideoTrack = {};
|
|
fakeVideoTrack.applyConstraints = function (constraints) {
|
|
return new Promise(
|
|
(resolve, reject) => {
|
|
resolve();
|
|
}
|
|
);
|
|
};
|
|
fakeVideoTrack.onended = null; // callbacks added from screenshare (we can use it later)
|
|
fakeVideoTrack.oninactive = null; // callbacks added from screenshare (we can use it later)
|
|
fakeVideoTrack.addEventListener = function() {}; // skip listeners
|
|
|
|
const videoTracks = [
|
|
fakeVideoTrack
|
|
];
|
|
stream.getTracks = stream.getVideoTracks = function () {
|
|
return videoTracks;
|
|
};
|
|
stream.active=true;
|
|
resolve(stream);
|
|
}
|
|
).catch(
|
|
(e) => {
|
|
logger.error(`Failure calling native initializeScreenShare`, e.message)
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
// RTCPeerConnection
|
|
const prototype = window.RTCPeerConnection.prototype;
|
|
|
|
prototype.originalCreateOffer = prototype.createOffer;
|
|
prototype.createOffer = async function (options) {
|
|
const webRtcCallType = detectWebRtcCallType('createOffer', this);
|
|
|
|
if(webRtcCallType === WEBRTC_CALL_TYPE_STANDARD){
|
|
return prototype.originalCreateOffer.call(this, ...arguments);
|
|
}
|
|
logger.info(`BBB-MOBILE - createOffer called`, {options});
|
|
|
|
const stunTurn = await fetchStunTurnServers(Auth._authToken);
|
|
|
|
const createOfferMethod = (webRtcCallType === WEBRTC_CALL_TYPE_SCREEN_SHARE) ? 'createScreenShareOffer' : 'createFullAudioOffer';
|
|
|
|
return await new Promise( (resolve, reject) => {
|
|
callNativeMethod(createOfferMethod, [stunTurn]).then ( sdp => {
|
|
logger.info(`BBB-MOBILE - createOffer resolved`, {sdp});
|
|
|
|
// send offer to BBB code
|
|
resolve({
|
|
type: 'offer',
|
|
sdp
|
|
});
|
|
});
|
|
} );
|
|
};
|
|
|
|
prototype.originalAddEventListener = prototype.addEventListener;
|
|
prototype.addEventListener = function (event, callback) {
|
|
if(WEBRTC_CALL_TYPE_STANDARD === detectWebRtcCallType('addEventListener', this, arguments)){
|
|
return prototype.originalAddEventListener.call(this, ...arguments);
|
|
}
|
|
|
|
logger.info(`BBB-MOBILE - addEventListener called`, {event, callback});
|
|
|
|
switch(event) {
|
|
case 'icecandidate':
|
|
window.bbbMobileScreenShareIceCandidateCallback = function () {
|
|
logger.info("Received a bbbMobileScreenShareIceCandidateCallback call with arguments", arguments);
|
|
if(callback){
|
|
callback.apply(this, arguments);
|
|
}
|
|
return true;
|
|
}
|
|
break;
|
|
case 'signalingstatechange':
|
|
window.bbbMobileScreenShareSignalingStateChangeCallback = function (newState) {
|
|
this.signalingState = newState;
|
|
callback();
|
|
};
|
|
break;
|
|
}
|
|
}
|
|
|
|
prototype.originalSetLocalDescription = prototype.setLocalDescription;
|
|
prototype.setLocalDescription = function (description, successCallback, failureCallback) {
|
|
if(WEBRTC_CALL_TYPE_STANDARD === detectWebRtcCallType('setLocalDescription', this)){
|
|
return prototype.originalSetLocalDescription.call(this, ...arguments);
|
|
}
|
|
logger.info(`BBB-MOBILE - setLocalDescription called`, {description, successCallback, failureCallback});
|
|
|
|
// store the value
|
|
this._localDescription = JSON.parse(JSON.stringify(description));
|
|
// replace getter of localDescription to return this value
|
|
Object.defineProperty(this, 'localDescription', {get: function() {return this._localDescription;},set: function(newValue) {}});
|
|
|
|
// return a promise that resolves immediately
|
|
return new Promise( (resolve, reject) => {
|
|
resolve();
|
|
})
|
|
}
|
|
|
|
prototype.originalSetRemoteDescription = prototype.setRemoteDescription;
|
|
prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
|
|
const webRtcCallType = detectWebRtcCallType('setRemoteDescription', this);
|
|
if(WEBRTC_CALL_TYPE_STANDARD === webRtcCallType){
|
|
return prototype.originalSetRemoteDescription.call(this, ...arguments);
|
|
}
|
|
|
|
logger.info(`BBB-MOBILE - setRemoteDescription called`, {description, successCallback, failureCallback});
|
|
|
|
this._remoteDescription = JSON.parse(JSON.stringify(description));
|
|
Object.defineProperty(this, 'remoteDescription', {get: function() {return this._remoteDescription;},set: function(newValue) {}});
|
|
|
|
const setRemoteDescriptionMethod = (webRtcCallType === WEBRTC_CALL_TYPE_SCREEN_SHARE) ? 'setScreenShareRemoteSDP' : 'setFullAudioRemoteSDP';
|
|
|
|
return new Promise( (resolve, reject) => {
|
|
callNativeMethod(setRemoteDescriptionMethod, [description]).then ( () => {
|
|
logger.info(`BBB-MOBILE - setRemoteDescription resolved`);
|
|
|
|
resolve();
|
|
|
|
if(webRtcCallType === WEBRTC_CALL_TYPE_FULL_AUDIO) {
|
|
Object.defineProperty(this, "iceGatheringState", {get: function() { return "complete" }, set: ()=>{} });
|
|
Object.defineProperty(this, "iceConnectionState", {get: function() { return "completed" }, set: ()=>{} });
|
|
this.onicegatheringstatechange && this.onicegatheringstatechange({target: this});
|
|
this.oniceconnectionstatechange && this.oniceconnectionstatechange({target: this});
|
|
}
|
|
});
|
|
} );
|
|
}
|
|
|
|
prototype.originalAddTrack = prototype.addTrack;
|
|
prototype.addTrack = function (description, successCallback, failureCallback) {
|
|
if(WEBRTC_CALL_TYPE_STANDARD === detectWebRtcCallType('addTrack', this)){
|
|
return prototype.originalAddTrack.call(this, ...arguments);
|
|
}
|
|
|
|
logger.info(`BBB-MOBILE - addTrack called`, {description, successCallback, failureCallback});
|
|
}
|
|
|
|
prototype.originalGetLocalStreams = prototype.getLocalStreams;
|
|
prototype.getLocalStreams = function() {
|
|
if(WEBRTC_CALL_TYPE_STANDARD === detectWebRtcCallType('getLocalStreams', this)){
|
|
return prototype.originalGetLocalStreams.call(this, ...arguments);
|
|
}
|
|
logger.info(`BBB-MOBILE - getLocalStreams called`, arguments);
|
|
|
|
//
|
|
return [
|
|
stream
|
|
];
|
|
}
|
|
|
|
prototype.originalAddTransceiver = prototype.addTransceiver;
|
|
prototype.addTransceiver = function() {
|
|
if(WEBRTC_CALL_TYPE_STANDARD === detectWebRtcCallType('addTransceiver', this)){
|
|
return prototype.originalAddTransceiver.call(this, ...arguments);
|
|
}
|
|
logger.info(`BBB-MOBILE - addTransceiver called`, arguments);
|
|
}
|
|
|
|
prototype.originalAddIceCandidate = prototype.addIceCandidate;
|
|
prototype.addIceCandidate = function (candidate) {
|
|
if(WEBRTC_CALL_TYPE_STANDARD === detectWebRtcCallType('addIceCandidate', this)){
|
|
return prototype.originalAddIceCandidate.call(this, ...arguments);
|
|
}
|
|
logger.info(`BBB-MOBILE - addIceCandidate called`, {candidate});
|
|
|
|
return new Promise( (resolve, reject) => {
|
|
callNativeMethod('addRemoteIceCandidate', [candidate]).then ( () => {
|
|
logger.info("BBB-MOBILE - addRemoteIceCandidate resolved");
|
|
|
|
resolve();
|
|
});
|
|
} );
|
|
}
|
|
|
|
// Handle screenshare stop
|
|
const KurentoScreenShareBridge = require('/imports/api/screenshare/client/bridge/index.js').default;
|
|
//Kurento Screen Share
|
|
var stopOriginal = KurentoScreenShareBridge.stop.bind(KurentoScreenShareBridge);
|
|
KurentoScreenShareBridge.stop = function(){
|
|
callNativeMethod('stopScreenShare')
|
|
logger.debug(`BBB-MOBILE - Click on stop screen share`);
|
|
stopOriginal()
|
|
}
|
|
|
|
// Handle screenshare stop requested by application (i.e. stopped the broadcast extension)
|
|
window.bbbMobileScreenShareBroadcastFinishedCallback = function () {
|
|
document.querySelector('[data-test="stopScreenShare"]')?.click();
|
|
}
|
|
|
|
}
|
|
})();
|
|
|
|
|