mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-30 03:12:29 +00:00
On web we detect if we run on an iframe, and on mobile we detect if the app is one of ours.
443 lines
14 KiB
TypeScript
443 lines
14 KiB
TypeScript
import { cloneDeep } from 'lodash-es';
|
|
|
|
import { IReduxState, IStore } from '../../app/types';
|
|
import { conferenceLeft, conferenceWillLeave, redirect } from '../conference/actions';
|
|
import { getCurrentConference } from '../conference/functions';
|
|
import { IConfigState } from '../config/reducer';
|
|
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
|
|
import { isEmbedded } from '../util/embedUtils';
|
|
import { parseURLParams } from '../util/parseURLParams';
|
|
import {
|
|
appendURLParam,
|
|
getBackendSafeRoomName
|
|
} from '../util/uri';
|
|
|
|
import {
|
|
CONNECTION_DISCONNECTED,
|
|
CONNECTION_ESTABLISHED,
|
|
CONNECTION_FAILED,
|
|
CONNECTION_PROPERTIES_UPDATED,
|
|
CONNECTION_WILL_CONNECT,
|
|
SET_LOCATION_URL,
|
|
SET_PREFER_VISITOR
|
|
} from './actionTypes';
|
|
import { JITSI_CONNECTION_URL_KEY } from './constants';
|
|
import logger from './logger';
|
|
import { ConnectionFailedError, IIceServers } from './types';
|
|
|
|
/**
|
|
* The options that will be passed to the JitsiConnection instance.
|
|
*/
|
|
interface IOptions extends IConfigState {
|
|
iceServersOverride?: IIceServers;
|
|
preferVisitor?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Create an action for when the signaling connection has been lost.
|
|
*
|
|
* @param {JitsiConnection} connection - The {@code JitsiConnection} which
|
|
* disconnected.
|
|
* @private
|
|
* @returns {{
|
|
* type: CONNECTION_DISCONNECTED,
|
|
* connection: JitsiConnection
|
|
* }}
|
|
*/
|
|
export function connectionDisconnected(connection?: Object) {
|
|
return {
|
|
type: CONNECTION_DISCONNECTED,
|
|
connection
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create an action for when the signaling connection has been established.
|
|
*
|
|
* @param {JitsiConnection} connection - The {@code JitsiConnection} which was
|
|
* established.
|
|
* @param {number} timeEstablished - The time at which the
|
|
* {@code JitsiConnection} which was established.
|
|
* @public
|
|
* @returns {{
|
|
* type: CONNECTION_ESTABLISHED,
|
|
* connection: JitsiConnection,
|
|
* timeEstablished: number
|
|
* }}
|
|
*/
|
|
export function connectionEstablished(
|
|
connection: Object, timeEstablished: number) {
|
|
return {
|
|
type: CONNECTION_ESTABLISHED,
|
|
connection,
|
|
timeEstablished
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create an action for when the signaling connection could not be created.
|
|
*
|
|
* @param {JitsiConnection} connection - The {@code JitsiConnection} which
|
|
* failed.
|
|
* @param {ConnectionFailedError} error - Error.
|
|
* @public
|
|
* @returns {{
|
|
* type: CONNECTION_FAILED,
|
|
* connection: JitsiConnection,
|
|
* error: ConnectionFailedError
|
|
* }}
|
|
*/
|
|
export function connectionFailed(
|
|
connection: Object,
|
|
error: ConnectionFailedError) {
|
|
const { credentials } = error;
|
|
|
|
if (credentials && !Object.keys(credentials).length) {
|
|
error.credentials = undefined;
|
|
}
|
|
|
|
return {
|
|
type: CONNECTION_FAILED,
|
|
connection,
|
|
error
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Constructs options to be passed to the constructor of {@code JitsiConnection}
|
|
* based on the redux state.
|
|
*
|
|
* @param {Object} state - The redux state.
|
|
* @returns {Object} The options to be passed to the constructor of
|
|
* {@code JitsiConnection}.
|
|
*/
|
|
export function constructOptions(state: IReduxState) {
|
|
// Deep clone the options to make sure we don't modify the object in the
|
|
// redux store.
|
|
const options: IOptions = cloneDeep(state['features/base/config']);
|
|
|
|
const { locationURL, preferVisitor } = state['features/base/connection'];
|
|
const params = parseURLParams(locationURL || '');
|
|
const iceServersOverride = params['iceServers.replace'];
|
|
|
|
// Allow iceServersOverride only when jitsi-meet is in an iframe.
|
|
if (isEmbedded() && iceServersOverride) {
|
|
options.iceServersOverride = iceServersOverride;
|
|
}
|
|
|
|
const { bosh, preferBosh, flags } = options;
|
|
let { websocket } = options;
|
|
|
|
if (preferBosh) {
|
|
websocket = undefined;
|
|
}
|
|
|
|
// WebSocket is preferred over BOSH.
|
|
const serviceUrl = websocket || bosh;
|
|
|
|
logger.log(`Using service URL ${serviceUrl}`);
|
|
|
|
// Append room to the URL's search.
|
|
const { room } = state['features/base/conference'];
|
|
|
|
if (serviceUrl && room) {
|
|
const roomName = getBackendSafeRoomName(room);
|
|
|
|
options.serviceUrl = appendURLParam(serviceUrl, 'room', roomName ?? '');
|
|
|
|
if (options.websocketKeepAliveUrl) {
|
|
options.websocketKeepAliveUrl = appendURLParam(options.websocketKeepAliveUrl, 'room', roomName ?? '');
|
|
}
|
|
if (options.conferenceRequestUrl) {
|
|
options.conferenceRequestUrl = appendURLParam(options.conferenceRequestUrl, 'room', roomName ?? '');
|
|
}
|
|
}
|
|
|
|
if (preferVisitor) {
|
|
options.preferVisitor = true;
|
|
}
|
|
|
|
// Enable ssrc-rewriting by default.
|
|
if (typeof flags?.ssrcRewritingEnabled === 'undefined') {
|
|
const { ...otherFlags } = flags ?? {};
|
|
|
|
options.flags = {
|
|
...otherFlags,
|
|
ssrcRewritingEnabled: true
|
|
};
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
/**
|
|
* Sets the location URL of the application, connection, conference, etc.
|
|
*
|
|
* @param {URL} [locationURL] - The location URL of the application,
|
|
* connection, conference, etc.
|
|
* @returns {{
|
|
* type: SET_LOCATION_URL,
|
|
* locationURL: URL
|
|
* }}
|
|
*/
|
|
export function setLocationURL(locationURL?: URL) {
|
|
return {
|
|
type: SET_LOCATION_URL,
|
|
locationURL
|
|
};
|
|
}
|
|
|
|
/**
|
|
* To change prefer visitor in the store. Used later to decide what to request from jicofo on connection.
|
|
*
|
|
* @param {boolean} preferVisitor - The value to set.
|
|
* @returns {{
|
|
* type: SET_PREFER_VISITOR,
|
|
* preferVisitor: boolean
|
|
* }}
|
|
*/
|
|
export function setPreferVisitor(preferVisitor: boolean) {
|
|
return {
|
|
type: SET_PREFER_VISITOR,
|
|
preferVisitor
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Opens new connection.
|
|
*
|
|
* @param {string} [id] - The XMPP user's ID (e.g. {@code user@server.com}).
|
|
* @param {string} [password] - The XMPP user's password.
|
|
* @returns {Function}
|
|
*/
|
|
export function _connectInternal(id?: string, password?: string) {
|
|
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
|
const state = getState();
|
|
const options = constructOptions(state);
|
|
const { locationURL } = state['features/base/connection'];
|
|
const { jwt } = state['features/base/jwt'];
|
|
|
|
const connection = new JitsiMeetJS.JitsiConnection(options.appId, jwt, options);
|
|
|
|
connection[JITSI_CONNECTION_URL_KEY] = locationURL;
|
|
|
|
dispatch(_connectionWillConnect(connection));
|
|
|
|
return new Promise((resolve, reject) => {
|
|
connection.addEventListener(
|
|
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
|
|
_onConnectionDisconnected);
|
|
connection.addEventListener(
|
|
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
|
_onConnectionEstablished);
|
|
connection.addEventListener(
|
|
JitsiConnectionEvents.CONNECTION_FAILED,
|
|
_onConnectionFailed);
|
|
connection.addEventListener(
|
|
JitsiConnectionEvents.CONNECTION_REDIRECTED,
|
|
_onConnectionRedirected);
|
|
connection.addEventListener(
|
|
JitsiConnectionEvents.PROPERTIES_UPDATED,
|
|
_onPropertiesUpdate);
|
|
|
|
/**
|
|
* Unsubscribe the connection instance from
|
|
* {@code CONNECTION_DISCONNECTED} and {@code CONNECTION_FAILED} events.
|
|
*
|
|
* @returns {void}
|
|
*/
|
|
function unsubscribe() {
|
|
connection.removeEventListener(
|
|
JitsiConnectionEvents.CONNECTION_DISCONNECTED, _onConnectionDisconnected);
|
|
connection.removeEventListener(JitsiConnectionEvents.CONNECTION_FAILED, _onConnectionFailed);
|
|
connection.removeEventListener(JitsiConnectionEvents.PROPERTIES_UPDATED, _onPropertiesUpdate);
|
|
}
|
|
|
|
/**
|
|
* Dispatches {@code CONNECTION_DISCONNECTED} action when connection is
|
|
* disconnected.
|
|
*
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
function _onConnectionDisconnected() {
|
|
unsubscribe();
|
|
dispatch(connectionDisconnected(connection));
|
|
resolve(connection);
|
|
}
|
|
|
|
/**
|
|
* Rejects external promise when connection fails.
|
|
*
|
|
* @param {JitsiConnectionErrors} err - Connection error.
|
|
* @param {string} [message] - Error message supplied by lib-jitsi-meet.
|
|
* @param {Object} [credentials] - The invalid credentials that were
|
|
* used to authenticate and the authentication failed.
|
|
* @param {string} [credentials.jid] - The XMPP user's ID.
|
|
* @param {string} [credentials.password] - The XMPP user's password.
|
|
* @param {Object} details - Additional information about the error.
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
function _onConnectionFailed( // eslint-disable-line max-params
|
|
err: string,
|
|
message: string,
|
|
credentials: any,
|
|
details: Object) {
|
|
unsubscribe();
|
|
|
|
dispatch(connectionFailed(connection, {
|
|
credentials,
|
|
details,
|
|
name: err,
|
|
message
|
|
}));
|
|
|
|
reject(err);
|
|
}
|
|
|
|
/**
|
|
* Resolves external promise when connection is established.
|
|
*
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
function _onConnectionEstablished() {
|
|
connection.removeEventListener(JitsiConnectionEvents.CONNECTION_ESTABLISHED, _onConnectionEstablished);
|
|
dispatch(connectionEstablished(connection, Date.now()));
|
|
resolve(connection);
|
|
}
|
|
|
|
/**
|
|
* Connection was redirected.
|
|
*
|
|
* @param {string|undefined} vnode - The vnode to connect to.
|
|
* @param {string} focusJid - The focus jid to use.
|
|
* @param {string|undefined} username - The username to use when joining. This is after promotion from
|
|
* visitor to main participant.
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
function _onConnectionRedirected(vnode: string, focusJid: string, username: string) {
|
|
connection.removeEventListener(JitsiConnectionEvents.CONNECTION_REDIRECTED, _onConnectionRedirected);
|
|
dispatch(redirect(vnode, focusJid, username));
|
|
}
|
|
|
|
/**
|
|
* Connection properties were updated.
|
|
*
|
|
* @param {Object} properties - The properties which were updated.
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
function _onPropertiesUpdate(properties: object) {
|
|
dispatch(_propertiesUpdate(properties));
|
|
}
|
|
|
|
// in case of configured http url for conference request we need the room name
|
|
const name = getBackendSafeRoomName(state['features/base/conference'].room);
|
|
|
|
connection.connect({
|
|
id,
|
|
password,
|
|
name
|
|
});
|
|
});
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create an action for when a connection will connect.
|
|
*
|
|
* @param {JitsiConnection} connection - The {@code JitsiConnection} which will
|
|
* connect.
|
|
* @private
|
|
* @returns {{
|
|
* type: CONNECTION_WILL_CONNECT,
|
|
* connection: JitsiConnection
|
|
* }}
|
|
*/
|
|
function _connectionWillConnect(connection: Object) {
|
|
return {
|
|
type: CONNECTION_WILL_CONNECT,
|
|
connection
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create an action for when connection properties are updated.
|
|
*
|
|
* @param {Object} properties - The properties which were updated.
|
|
* @private
|
|
* @returns {{
|
|
* type: CONNECTION_PROPERTIES_UPDATED,
|
|
* properties: Object
|
|
* }}
|
|
*/
|
|
function _propertiesUpdate(properties: object) {
|
|
return {
|
|
type: CONNECTION_PROPERTIES_UPDATED,
|
|
properties
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Closes connection.
|
|
*
|
|
* @param {boolean} isRedirect - Indicates if the action has been dispatched as part of visitor promotion.
|
|
*
|
|
* @returns {Function}
|
|
*/
|
|
export function disconnect(isRedirect?: boolean) {
|
|
return (dispatch: IStore['dispatch'], getState: IStore['getState']): Promise<void> => {
|
|
const state = getState();
|
|
|
|
// The conference we have already joined or are joining.
|
|
const conference_ = getCurrentConference(state);
|
|
|
|
// Promise which completes when the conference has been left and the
|
|
// connection has been disconnected.
|
|
let promise;
|
|
|
|
// Leave the conference.
|
|
if (conference_) {
|
|
// In a fashion similar to JitsiConference's CONFERENCE_LEFT event
|
|
// (and the respective Redux action) which is fired after the
|
|
// conference has been left, notify the application about the
|
|
// intention to leave the conference.
|
|
dispatch(conferenceWillLeave(conference_, isRedirect));
|
|
|
|
promise
|
|
= conference_.leave()
|
|
.catch((error: Error) => {
|
|
logger.warn(
|
|
'JitsiConference.leave() rejected with:',
|
|
error);
|
|
|
|
// The library lib-jitsi-meet failed to make the
|
|
// JitsiConference leave. Which may be because
|
|
// JitsiConference thinks it has already left.
|
|
// Regardless of the failure reason, continue in
|
|
// jitsi-meet as if the leave has succeeded.
|
|
dispatch(conferenceLeft(conference_));
|
|
});
|
|
} else {
|
|
promise = Promise.resolve();
|
|
}
|
|
|
|
// Disconnect the connection.
|
|
const { connecting, connection } = state['features/base/connection'];
|
|
|
|
// The connection we have already connected or are connecting.
|
|
const connection_ = connection || connecting;
|
|
|
|
if (connection_) {
|
|
promise = promise.then(() => connection_.disconnect());
|
|
} else {
|
|
logger.info('No connection found while disconnecting.');
|
|
}
|
|
|
|
return promise;
|
|
};
|
|
}
|