[RN] Simplify

There were getDomain, setDomain, SET_DOMAIN, setRoomURL, SET_ROOM_URL
which together were repeating one and the same information and in the
case of the 'room URL' abstraction was not 100% accurate because it
would exist even when there was no room. Replace them all with a
'location URL' abstraction which exists with or without a room.

Then the 'room URL' abstraction was not used in (mobile) feature
share-room. Use the 'location URL' there now.

Finally, removes source code duplication in supporting the Web
application context root.
This commit is contained in:
Lyubo Marinov 2017-05-09 00:15:43 -05:00
parent e6f6884c36
commit 2f3706bd37
19 changed files with 474 additions and 467 deletions

View File

@ -19,8 +19,7 @@ import analytics from './modules/analytics/analytics';
import EventEmitter from "events";
import { showDesktopSharingButton } from './react/features/toolbox';
import { getLocationContextRoot } from './react/features/app';
import {
AVATAR_ID_COMMAND,
AVATAR_URL_COMMAND,
@ -50,6 +49,7 @@ import {
mediaPermissionPromptVisibilityChanged,
suspendDetected
} from './react/features/overlay';
import { showDesktopSharingButton } from './react/features/toolbox';
const ConnectionEvents = JitsiMeetJS.events.connection;
const ConnectionErrors = JitsiMeetJS.errors.connection;
@ -311,18 +311,11 @@ function assignWindowLocationPathname(pathname) {
const windowLocation = window.location;
if (!pathname.startsWith('/')) {
// XXX To support a deployment in a sub-directory, assume that the room
// (name) is the last non-directory component of the path (name).
let contextRoot = windowLocation.pathname;
contextRoot
= contextRoot.substring(0, contextRoot.lastIndexOf('/') + 1);
// A pathname equal to ./ specifies the current directory. It will be
// fine but pointless to include it because contextRoot is the current
// directory.
pathname.startsWith('./') && (pathname = pathname.substring(2));
pathname = contextRoot + pathname;
pathname = getLocationContextRoot(windowLocation) + pathname;
}
windowLocation.pathname = pathname;

View File

@ -1,6 +1,6 @@
import { setRoom, setRoomURL } from '../base/conference';
import { setRoom } from '../base/conference';
import { setLocationURL } from '../base/connection';
import { setConfig } from '../base/config';
import { getDomain, setDomain } from '../base/connection';
import { loadConfig } from '../base/lib-jitsi-meet';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes';
@ -23,100 +23,151 @@ export function appInit() {
}
/**
* Triggers an in-app navigation to a different route. Allows navigation to be
* abstracted between the mobile and web versions.
* Triggers an in-app navigation to a specific route. Allows navigation to be
* abstracted between the mobile/React Native and Web/React applications.
*
* @param {(string|undefined)} uri - The URI to which to navigate. It may be a
* full URL with an http(s) scheme, a full or partial URI with the app-specific
* full URL with an HTTP(S) scheme, a full or partial URI with the app-specific
* sheme, or a mere room name.
* @returns {Function}
*/
export function appNavigate(uri) {
return (dispatch: Dispatch<*>, getState: Function) => {
const state = getState();
const oldDomain = getDomain(state);
const defaultURL = state['features/app'].app._getDefaultURL();
let urlObject;
export function appNavigate(uri: ?string) {
return (dispatch: Dispatch<*>, getState: Function) =>
_appNavigateToOptionalLocation(
dispatch, getState,
_parseURIString(uri));
}
// eslint-disable-next-line prefer-const
let { domain, room } = _parseURIString(uri);
/**
* Triggers an in-app navigation to a specific location URI.
*
* @param {Dispatch} dispatch - The redux {@code dispatch} function.
* @param {Function} getState - The redux function that gets/retrieves the redux
* state.
* @param {Object} newLocation - The location URI to navigate to. The value
* cannot be undefined and is assumed to have all properties such as
* {@code host} and {@code room} defined values.
* @private
* @returns {void}
*/
function _appNavigateToMandatoryLocation(
dispatch: Dispatch<*>, getState: Function,
newLocation: Object) {
// TODO Kostiantyn Tsaregradskyi: We should probably detect if user is
// currently in a conference and ask her if she wants to close the
// current conference and start a new one with the new room name or
// domain.
// If the specified URI does not identify a domain, use the app's
// default.
if (typeof domain === 'undefined') {
domain = _parseURIString(defaultURL).domain;
const oldLocationURL = getState()['features/base/connection'].locationURL;
const oldHost = oldLocationURL ? oldLocationURL.host : undefined;
const newHost = newLocation.host;
if (oldHost === newHost) {
dispatchSetLocationURL();
dispatchSetRoomAndNavigate();
} else {
// If the host has changed, we need to load the config of the new host
// and set it, and only after that we can navigate to a different route.
_loadConfig(newLocation)
.then(
config => configLoaded(/* err */ undefined, config),
err => configLoaded(err, /* config */ undefined))
.then(dispatchSetRoomAndNavigate);
}
/**
* Notifies that an attempt to load the config(uration) of domain has
* completed.
*
* @param {string|undefined} err - If the loading has failed, the error
* detailing the cause of the failure.
* @param {Object|undefined} config - If the loading has succeeded, the
* loaded config(uration).
* @returns {void}
*/
function configLoaded(err, config) {
if (err) {
// XXX The failure could be, for example, because of a
// certificate-related error. In which case the connection will
// fail later in Strophe anyway even if we use the default
// config here.
// The function loadConfig will log the err.
return;
}
if (room) {
const splitURL = uri.split(domain);
const urlWithoutDomain = splitURL[splitURL.length - 1];
dispatchSetLocationURL();
dispatch(setConfig(config));
}
urlObject = new URL(urlWithoutDomain, `https://${domain}`);
/**
* Dispatches {@link setLocationURL} in the redux store.
*
* @returns {void}
*/
function dispatchSetLocationURL() {
dispatch(
setLocationURL(
new URL(
(newLocation.protocol || 'https:')
// TODO userinfo
+ newLocation.host
+ (newLocation.pathname || '/')
+ newLocation.search
+ newLocation.hash)));
}
/**
* Dispatches {@link _setRoomAndNavigate} in the redux store.
*
* @returns {void}
*/
function dispatchSetRoomAndNavigate() {
dispatch(_setRoomAndNavigate(newLocation.room));
}
}
/**
* Triggers an in-app navigation to a specific or undefined location (URI).
*
* @param {Dispatch} dispatch - The redux {@code dispatch} function.
* @param {Function} getState - The redux function that gets/retrieves the redux
* state.
* @param {Object} location - The location (URI) to navigate to. The value may
* be undefined.
* @private
* @returns {void}
*/
function _appNavigateToOptionalLocation(
dispatch: Dispatch<*>, getState: Function,
location: Object) {
// If the specified location (URI) does not identify a host, use the app's
// default.
if (!location || !location.host) {
const defaultLocation
= _parseURIString(getState()['features/app'].app._getDefaultURL());
if (location) {
location.host = defaultLocation.host;
// FIXME Turn location's host, hostname, and port properties into
// setters in order to reduce the risks of inconsistent state.
location.hostname = defaultLocation.hostname;
location.port = defaultLocation.port;
location.protocol = defaultLocation.protocol;
} else {
// eslint-disable-next-line no-param-reassign
location = defaultLocation;
}
}
dispatch(setRoomURL(urlObject));
if (!location.protocol) {
location.protocol = 'https:';
}
// TODO Kostiantyn Tsaregradskyi: We should probably detect if user is
// currently in a conference and ask her if she wants to close the
// current conference and start a new one with the new room name or
// domain.
if (typeof domain === 'undefined' || oldDomain === domain) {
dispatchSetRoomAndNavigate();
} else if (oldDomain !== domain) {
// Update domain without waiting for config to be loaded to prevent
// race conditions when we will start to load config multiple times.
dispatch(setDomain(domain));
// If domain has changed, we need to load the config of the new
// domain and set it, and only after that we can navigate to a
// different route.
loadConfig(`https://${domain}`)
.then(
config => configLoaded(/* err */ undefined, config),
err => configLoaded(err, /* config */ undefined))
.then(dispatchSetRoomAndNavigate);
}
/**
* Notifies that an attempt to load the config(uration) of domain has
* completed.
*
* @param {string|undefined} err - If the loading has failed, the error
* detailing the cause of the failure.
* @param {Object|undefined} config - If the loading has succeeded, the
* loaded config(uration).
* @returns {void}
*/
function configLoaded(err, config) {
if (err) {
// XXX The failure could be, for example, because of a
// certificate-related error. In which case the connection will
// fail later in Strophe anyway even if we use the default
// config here.
// The function loadConfig will log the err.
return;
}
dispatch(setConfig(config));
}
/**
* Dispatches _setRoomAndNavigate in the Redux store.
*
* @returns {void}
*/
function dispatchSetRoomAndNavigate() {
// If both domain and room vars became undefined, that means we're
// actually dealing with just room name and not with URL.
dispatch(
_setRoomAndNavigate(
typeof room === 'undefined' && typeof domain === 'undefined'
? uri
: room));
}
};
_appNavigateToMandatoryLocation(dispatch, getState, location);
}
/**
@ -151,6 +202,27 @@ export function appWillUnmount(app) {
};
}
/**
* Loads config.js from a specific host.
*
* @param {Object} location - The loction URI which specifies the host to load
* the config.js from.
* @returns {Promise<Object>}
*/
function _loadConfig(location: Object) {
let protocol = location.protocol.toLowerCase();
// The React Native app supports an app-specific scheme which is sure to not
// be supported by fetch (or whatever loadConfig utilizes).
if (protocol !== 'http:' && protocol !== 'https:') {
protocol = 'https:';
}
// TDOO userinfo
return loadConfig(protocol + location.host + (location.contextRoot || '/'));
}
/**
* Navigates to a route in accord with a specific Redux state.
*

View File

@ -268,13 +268,13 @@ export class AbstractApp extends Component {
// By default, open the domain configured in the configuration file
// which may be the domain at which the whole server infrastructure is
// deployed.
const config = this.props.config;
const { config } = this.props;
if (typeof config === 'object') {
const hosts = config.hosts;
const { hosts } = config;
if (typeof hosts === 'object') {
const domain = hosts.domain;
const { domain } = hosts;
if (domain) {
return `https://${domain}`;

View File

@ -1,5 +1,6 @@
import { appInit } from '../actions';
import { AbstractApp } from './AbstractApp';
import { getLocationContextRoot } from '../functions';
import '../../room-lock';
@ -65,13 +66,7 @@ export class App extends AbstractApp {
* @returns {string} The context root of window.location i.e. this Web App.
*/
_getWindowLocationContextRoot() {
const pathname = this.getWindowLocation().pathname;
const contextRootEndIndex = pathname.lastIndexOf('/');
return (
contextRootEndIndex === -1
? '/'
: pathname.substring(0, contextRootEndIndex + 1));
return getLocationContextRoot(this.getWindowLocation());
}
/**

View File

@ -112,39 +112,21 @@ function _fixURIStringScheme(uri) {
}
/**
* Gets room name and domain from URL object.
* Gets the (Web application) context root defined by a specific location (URI).
*
* @param {URL} url - URL object.
* @private
* @returns {{
* domain: (string|undefined),
* room: (string|undefined)
* }}
* @param {Object} location - The location (URI) which defines the (Web
* application) context root.
* @returns {string} - The (Web application) context root defined by the
* specified {@code location} (URI).
*/
function _getRoomAndDomainFromURLObject(url) {
let domain;
let room;
export function getLocationContextRoot(location: Object) {
const pathname = location.pathname;
const contextRootEndIndex = pathname.lastIndexOf('/');
if (url) {
domain = url.host;
// The room (name) is the last component of pathname.
room = url.pathname;
room = room.substring(room.lastIndexOf('/') + 1);
// Convert empty string to undefined to simplify checks.
if (room === '') {
room = undefined;
}
if (domain === '') {
domain = undefined;
}
}
return {
domain,
room
};
return (
contextRootEndIndex === -1
? '/'
: pathname.substring(0, contextRootEndIndex + 1));
}
/**
@ -160,12 +142,113 @@ export function _getRouteToRender(stateOrGetState) {
= typeof stateOrGetState === 'function'
? stateOrGetState()
: stateOrGetState;
const room = state['features/base/conference'].room;
const { room } = state['features/base/conference'];
const component = isRoomValid(room) ? Conference : WelcomePage;
return RouteRegistry.getRouteByComponent(component);
}
/**
* Parses a specific URI string into an object with the well-known properties of
* the {@link Location} and/or {@link URL} interfaces implemented by Web
* browsers. The parsing attempts to be in accord with IETF's RFC 3986.
*
* @param {string} str - The URI string to parse.
* @returns {{
* hash: string,
* host: (string|undefined),
* hostname: (string|undefined),
* pathname: string,
* port: (string|undefined),
* protocol: (string|undefined),
* search: string
* }}
*/
function _parseStandardURIString(str: string) {
/* eslint-disable no-param-reassign */
const obj = {};
let regex;
let match;
// protocol
regex = new RegExp(`^${_URI_PROTOCOL_PATTERN}`, 'gi');
match = regex.exec(str);
if (match) {
obj.protocol = match[1].toLowerCase();
str = str.substring(regex.lastIndex);
}
// authority
regex = new RegExp(`^${_URI_AUTHORITY_PATTERN}`, 'gi');
match = regex.exec(str);
if (match) {
let authority = match[1].substring(/* // */ 2);
str = str.substring(regex.lastIndex);
// userinfo
const userinfoEndIndex = authority.indexOf('@');
if (userinfoEndIndex !== -1) {
authority = authority.substring(userinfoEndIndex + 1);
}
obj.host = authority;
// port
const portBeginIndex = authority.lastIndexOf(':');
if (portBeginIndex !== -1) {
obj.port = authority.substring(portBeginIndex + 1);
authority = authority.substring(0, portBeginIndex);
}
// hostname
obj.hostname = authority;
}
// pathname
regex = new RegExp(`^${_URI_PATH_PATTERN}`, 'gi');
match = regex.exec(str);
let pathname;
if (match) {
pathname = match[1];
str = str.substring(regex.lastIndex);
}
if (pathname) {
if (!pathname.startsWith('/')) {
pathname = `/${pathname}`;
}
} else {
pathname = '/';
}
obj.pathname = pathname;
// query
if (str.startsWith('?')) {
let hashBeginIndex = str.indexOf('#', 1);
if (hashBeginIndex === -1) {
hashBeginIndex = str.length;
}
obj.search = str.substring(0, hashBeginIndex);
str = str.substring(hashBeginIndex);
} else {
obj.search = ''; // Google Chrome
}
// fragment
obj.hash = str.startsWith('#') ? str : '';
/* eslint-enable no-param-reassign */
return obj;
}
/**
* Parses a specific URI which (supposedly) references a Jitsi Meet resource
* (location).
@ -173,68 +256,28 @@ export function _getRouteToRender(stateOrGetState) {
* @param {(string|undefined)} uri - The URI to parse which (supposedly)
* references a Jitsi Meet resource (location).
* @returns {{
* domain: (string|undefined),
* room: (string|undefined)
* }}
*/
export function _parseURIString(uri) {
let obj;
if (typeof uri === 'string') {
let str = uri;
str = _fixURIStringScheme(str);
str = _fixURIStringHierPart(str);
obj = {};
let regex;
let match;
// protocol
regex = new RegExp(`^${_URI_PROTOCOL_PATTERN}`, 'gi');
match = regex.exec(str);
if (match) {
obj.protocol = match[1].toLowerCase();
str = str.substring(regex.lastIndex);
}
// authority
regex = new RegExp(`^${_URI_AUTHORITY_PATTERN}`, 'gi');
match = regex.exec(str);
if (match) {
let authority = match[1].substring(/* // */ 2);
str = str.substring(regex.lastIndex);
// userinfo
const userinfoEndIndex = authority.indexOf('@');
if (userinfoEndIndex !== -1) {
authority = authority.substring(userinfoEndIndex + 1);
}
obj.host = authority;
// port
const portBeginIndex = authority.lastIndexOf(':');
if (portBeginIndex !== -1) {
obj.port = authority.substring(portBeginIndex + 1);
authority = authority.substring(0, portBeginIndex);
}
obj.hostname = authority;
}
// pathname
regex = new RegExp(`^${_URI_PATH_PATTERN}`, 'gi');
match = regex.exec(str);
if (match) {
obj.pathname = match[1] || '/';
str = str.substring(regex.lastIndex);
}
export function _parseURIString(uri: ?string) {
if (typeof uri !== 'string') {
return undefined;
}
return _getRoomAndDomainFromURLObject(obj);
const obj
= _parseStandardURIString(
_fixURIStringHierPart(_fixURIStringScheme(uri)));
// Add the properties that are specific to a Jitsi Meet resource (location)
// such as contextRoot, room:
// contextRoot
obj.contextRoot = getLocationContextRoot(obj);
// The room (name) is the last component of pathname.
const { pathname } = obj;
obj.room = pathname.substring(pathname.lastIndexOf('/') + 1) || undefined;
return obj;
}

View File

@ -78,7 +78,7 @@ const _INTERCEPT_COMPONENT_RULES = [
}
];
export { _parseURIString } from './functions.native';
export { getLocationContextRoot, _parseURIString } from './functions.native';
/**
* Determines which route is to be rendered in order to depict a specific Redux

View File

@ -1,8 +1,7 @@
import { Symbol } from '../react';
/**
* The type of the Redux action which signals that a specific conference has
* failed.
* The type of (redux) action which signals that a specific conference failed.
*
* {
* type: CONFERENCE_FAILED,
@ -13,8 +12,8 @@ import { Symbol } from '../react';
export const CONFERENCE_FAILED = Symbol('CONFERENCE_FAILED');
/**
* The type of the Redux action which signals that a specific conference has
* been joined.
* The type of (redux) action which signals that a specific conference was
* joined.
*
* {
* type: CONFERENCE_JOINED,
@ -24,8 +23,7 @@ export const CONFERENCE_FAILED = Symbol('CONFERENCE_FAILED');
export const CONFERENCE_JOINED = Symbol('CONFERENCE_JOINED');
/**
* The type of the Redux action which signals that a specific conference has
* been left.
* The type of (redux) action which signals that a specific conference was left.
*
* {
* type: CONFERENCE_LEFT,
@ -35,7 +33,7 @@ export const CONFERENCE_JOINED = Symbol('CONFERENCE_JOINED');
export const CONFERENCE_LEFT = Symbol('CONFERENCE_LEFT');
/**
* The type of the Redux action which signals that a specific conference will be
* The type of (redux) action which signals that a specific conference will be
* joined.
*
* {
@ -46,7 +44,7 @@ export const CONFERENCE_LEFT = Symbol('CONFERENCE_LEFT');
export const CONFERENCE_WILL_JOIN = Symbol('CONFERENCE_WILL_JOIN');
/**
* The type of the Redux action which signals that a specific conference will be
* The type of (redux) action which signals that a specific conference will be
* left.
*
* {
@ -57,8 +55,8 @@ export const CONFERENCE_WILL_JOIN = Symbol('CONFERENCE_WILL_JOIN');
export const CONFERENCE_WILL_LEAVE = Symbol('CONFERENCE_WILL_LEAVE');
/**
* The type of the Redux action which signals that the lock state of a specific
* <tt>JitsiConference</tt> changed.
* The type of (redux) action which signals that the lock state of a specific
* {@code JitsiConference} changed.
*
* {
* type: LOCK_STATE_CHANGED,
@ -69,7 +67,7 @@ export const CONFERENCE_WILL_LEAVE = Symbol('CONFERENCE_WILL_LEAVE');
export const LOCK_STATE_CHANGED = Symbol('LOCK_STATE_CHANGED');
/**
* The type of the Redux action which sets the audio-only flag for the current
* The type of (redux) action which sets the audio-only flag for the current
* conference.
*
* {
@ -80,8 +78,8 @@ export const LOCK_STATE_CHANGED = Symbol('LOCK_STATE_CHANGED');
export const SET_AUDIO_ONLY = Symbol('SET_AUDIO_ONLY');
/**
* The type of redux action which signals that video will be muted because the
* audio-only mode was enabled / disabled.
* The type of (redux) action which signals that video will be muted because the
* audio-only mode was enabled/disabled.
*
* {
* type: _SET_AUDIO_ONLY_VIDEO_MUTED,
@ -105,7 +103,7 @@ export const _SET_AUDIO_ONLY_VIDEO_MUTED
export const SET_LARGE_VIDEO_HD_STATUS = Symbol('SET_LARGE_VIDEO_HD_STATUS');
/**
* The type of redux action which sets the video channel's lastN (value).
* The type of (redux) action which sets the video channel's lastN (value).
*
* {
* type: SET_LASTN,
@ -115,8 +113,8 @@ export const SET_LARGE_VIDEO_HD_STATUS = Symbol('SET_LARGE_VIDEO_HD_STATUS');
export const SET_LASTN = Symbol('SET_LASTN');
/**
* The type of the Redux action which sets the password to join or lock a
* specific JitsiConference.
* The type of (redux) action which sets the password to join or lock a specific
* {@code JitsiConference}.
*
* {
* type: SET_PASSWORD,
@ -128,8 +126,8 @@ export const SET_LASTN = Symbol('SET_LASTN');
export const SET_PASSWORD = Symbol('SET_PASSWORD');
/**
* The type of Redux action which signals that setting a password on a
* JitsiConference failed (with an error).
* The type of (redux) action which signals that setting a password on a
* {@code JitsiConference} failed (with an error).
*
* {
* type: SET_PASSWORD_FAILED,
@ -139,7 +137,7 @@ export const SET_PASSWORD = Symbol('SET_PASSWORD');
export const SET_PASSWORD_FAILED = Symbol('SET_PASSWORD_FAILED');
/**
* The type of the Redux action which sets the name of the room of the
* The type of (redux) action which sets the name of the room of the
* conference to be joined.
*
* {
@ -148,13 +146,3 @@ export const SET_PASSWORD_FAILED = Symbol('SET_PASSWORD_FAILED');
* }
*/
export const SET_ROOM = Symbol('SET_ROOM');
/**
* The type of (Redux) action which sets the room URL.
*
* {
* type: SET_ROOM_URL,
* roomURL: URL
* }
*/
export const SET_ROOM_URL = Symbol('SET_ROOM_URL');

View File

@ -24,8 +24,7 @@ import {
SET_LASTN,
SET_PASSWORD,
SET_PASSWORD_FAILED,
SET_ROOM,
SET_ROOM_URL
SET_ROOM
} from './actionTypes';
import {
AVATAR_ID_COMMAND,
@ -491,22 +490,6 @@ export function setRoom(room) {
};
}
/**
* Sets the room URL.
*
* @param {string} roomURL - Room url.
* @returns {{
* type: SET_ROOM_URL,
* roomURL: URL
* }}
*/
export function setRoomURL(roomURL) {
return {
type: SET_ROOM_URL,
roomURL
};
}
/**
* Toggles the audio-only flag for the current JitsiConference.
*

View File

@ -13,8 +13,7 @@ import {
_SET_AUDIO_ONLY_VIDEO_MUTED,
SET_LARGE_VIDEO_HD_STATUS,
SET_PASSWORD,
SET_ROOM,
SET_ROOM_URL
SET_ROOM
} from './actionTypes';
import { isRoomValid } from './functions';
@ -53,9 +52,6 @@ ReducerRegistry.register('features/base/conference', (state = {}, action) => {
case SET_ROOM:
return _setRoom(state, action);
case SET_ROOM_URL:
return _setRoomURL(state, action);
}
return state;
@ -348,23 +344,3 @@ function _setRoom(state, action) {
*/
return set(state, 'room', room);
}
/**
* Reduces a specific Redux action SET_ROOM_URL of the feature base/conference.
*
* @param {Object} state - The Redux state of the feature base/conference.
* @param {Action} action - The Redux action SET_ROOM_URL to reduce.
* @private
* @returns {Object} The new state of the feature base/conference after the
* reduction of the specified action.
*/
function _setRoomURL(state, action) {
const { roomURL } = action;
/**
* Room URL of the conference (to be) joined.
*
* @type {string}
*/
return set(state, 'roomURL', roomURL);
}

View File

@ -1,26 +1,46 @@
import { Symbol } from '../react';
/**
* Action type to signal that connection has disconnected.
* The type of (redux) action which signals that a connection disconnected.
*
* {
* type: CONNECTION_DISCONNECTED,
* connection: JitsiConnection,
* message: string
* }
*/
export const CONNECTION_DISCONNECTED = Symbol('CONNECTION_DISCONNECTED');
/**
* Action type to signal that have successfully established a connection.
* The type of (redux) action which signals that a connection was successfully
* established.
*
* {
* type: CONNECTION_ESTABLISHED,
* connection: JitsiConnection
* }
*/
export const CONNECTION_ESTABLISHED = Symbol('CONNECTION_ESTABLISHED');
/**
* Action type to signal a connection failed.
* The type of (redux) action which signals that a connection failed.
*
* {
* type: CONNECTION_FAILED,
* connection: JitsiConnection,
* error: string,
* message: string
* }
*/
export const CONNECTION_FAILED = Symbol('CONNECTION_FAILED');
/**
* Action to signal to change connection domain.
* The type of (redux) action which sets the location URL of the application,
* connection, conference, etc.
*
* {
* type: SET_DOMAIN,
* domain: string
* type: SET_LOCATION_URL,
* locationURL: ?URL
* }
*/
export const SET_DOMAIN = Symbol('SET_DOMAIN');
export const SET_LOCATION_URL = Symbol('SET_LOCATION_URL');

View File

@ -9,7 +9,7 @@ import {
CONNECTION_DISCONNECTED,
CONNECTION_ESTABLISHED,
CONNECTION_FAILED,
SET_DOMAIN
SET_LOCATION_URL
} from './actionTypes';
/**
@ -88,7 +88,7 @@ export function connect() {
function _onConnectionFailed(err) {
unsubscribe();
console.error('CONNECTION FAILED:', err);
dispatch(connectionFailed(connection, err, ''));
dispatch(connectionFailed(connection, err));
}
/**
@ -108,6 +108,70 @@ export function connect() {
};
}
/**
* Create an action for when the signaling connection has been lost.
*
* @param {JitsiConnection} connection - The JitsiConnection which disconnected.
* @param {string} message - Error message.
* @private
* @returns {{
* type: CONNECTION_DISCONNECTED,
* connection: JitsiConnection,
* message: string
* }}
*/
function _connectionDisconnected(connection: Object, message: string) {
return {
type: CONNECTION_DISCONNECTED,
connection,
message
};
}
/**
* Create an action for when the signaling connection has been established.
*
* @param {JitsiConnection} connection - The JitsiConnection which was
* established.
* @public
* @returns {{
* type: CONNECTION_ESTABLISHED,
* connection: JitsiConnection
* }}
*/
export function connectionEstablished(connection: Object) {
return {
type: CONNECTION_ESTABLISHED,
connection
};
}
/**
* Create an action for when the signaling connection could not be created.
*
* @param {JitsiConnection} connection - The JitsiConnection which failed.
* @param {string} error - Error.
* @param {string} message - Error message.
* @public
* @returns {{
* type: CONNECTION_FAILED,
* connection: JitsiConnection,
* error: string,
* message: string
* }}
*/
export function connectionFailed(
connection: Object,
error: string,
message: ?string) {
return {
type: CONNECTION_FAILED,
connection,
error,
message
};
}
/**
* Closes connection.
*
@ -116,8 +180,8 @@ export function connect() {
export function disconnect() {
return (dispatch: Dispatch<*>, getState: Function) => {
const state = getState();
const conference = state['features/base/conference'].conference;
const connection = state['features/base/connection'].connection;
const { conference } = state['features/base/conference'];
const { connection } = state['features/base/connection'];
let promise;
@ -144,79 +208,18 @@ export function disconnect() {
}
/**
* Sets connection domain.
* Sets the location URL of the application, connecton, conference, etc.
*
* @param {string} domain - Domain name.
* @param {URL} [locationURL] - The location URL of the application,
* connection, conference, etc.
* @returns {{
* type: SET_DOMAIN,
* domain: string
* }}
*/
export function setDomain(domain: string) {
return {
type: SET_DOMAIN,
domain
};
}
/**
* Create an action for when the signaling connection has been lost.
*
* @param {JitsiConnection} connection - The JitsiConnection which disconnected.
* @param {string} message - Error message.
* @private
* @returns {{
* type: CONNECTION_DISCONNECTED,
* connection: JitsiConnection,
* message: string
* type: SET_LOCATION_URL,
* locationURL: URL
* }}
*/
function _connectionDisconnected(connection, message: string) {
export function setLocationURL(locationURL: ?URL) {
return {
type: CONNECTION_DISCONNECTED,
connection,
message
};
}
/**
* Create an action for when the signaling connection has been established.
*
* @param {JitsiConnection} connection - The JitsiConnection which was
* established.
* @returns {{
* type: CONNECTION_ESTABLISHED,
* connection: JitsiConnection
* }}
* @public
*/
export function connectionEstablished(connection: Object) {
return {
type: CONNECTION_ESTABLISHED,
connection
};
}
/**
* Create an action for when the signaling connection could not be created.
*
* @param {JitsiConnection} connection - The JitsiConnection which failed.
* @param {string} error - Error.
* @param {string} errorMessage - Error message.
* @returns {{
* type: CONNECTION_FAILED,
* connection: JitsiConnection,
* error: string,
* errorMessage: string
* }}
* @public
*/
export function connectionFailed(
connection: Object, error: string, errorMessage: string) {
return {
type: CONNECTION_FAILED,
connection,
error,
errorMessage
type: SET_LOCATION_URL,
locationURL
};
}

View File

@ -8,11 +8,8 @@ import {
WEBRTC_NOT_READY,
WEBRTC_NOT_SUPPORTED
} from '../lib-jitsi-meet';
import UIEvents from '../../../../service/UI/UIEvents';
import { SET_DOMAIN } from './actionTypes';
declare var APP: Object;
declare var config: Object;
@ -20,7 +17,8 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
export {
connectionEstablished,
connectionFailed
connectionFailed,
setLocationURL
} from './actions.native.js';
/**
@ -106,19 +104,3 @@ export function disconnect() {
// app.
return () => APP.conference.hangup();
}
/**
* Sets connection domain.
*
* @param {string} domain - Domain name.
* @returns {{
* type: SET_DOMAIN,
* domain: string
* }}
*/
export function setDomain(domain: string) {
return {
type: SET_DOMAIN,
domain
};
}

View File

@ -1,28 +0,0 @@
/* @flow */
/**
* Returns current domain.
*
* @param {(Function|Object)} stateOrGetState - Redux getState() method or Redux
* state.
* @returns {(string|undefined)}
*/
export function getDomain(stateOrGetState: Function | Object) {
const state
= typeof stateOrGetState === 'function'
? stateOrGetState()
: stateOrGetState;
const { options } = state['features/base/connection'];
let domain;
try {
domain = options.hosts.domain;
} catch (e) {
// XXX The value of options or any of the properties descending from it
// may be undefined at some point in the execution (e.g. on start).
// Instead of multiple checks for the undefined value, we just wrap it
// in a try-catch block.
}
return domain;
}

View File

@ -1,5 +1,4 @@
export * from './actions';
export * from './actionTypes';
export * from './functions';
import './reducer';

View File

@ -1,11 +1,11 @@
/* @flow */
import { ReducerRegistry, set } from '../redux';
import { assign, ReducerRegistry, set } from '../redux';
import {
CONNECTION_DISCONNECTED,
CONNECTION_ESTABLISHED,
SET_DOMAIN
SET_LOCATION_URL
} from './actionTypes';
/**
@ -21,8 +21,8 @@ ReducerRegistry.register(
case CONNECTION_ESTABLISHED:
return _connectionEstablished(state, action);
case SET_DOMAIN:
return _setDomain(state, action);
case SET_LOCATION_URL:
return _setLocationURL(state, action);
}
return state;
@ -38,8 +38,10 @@ ReducerRegistry.register(
* @returns {Object} The new state of the feature base/connection after the
* reduction of the specified action.
*/
function _connectionDisconnected(state: Object, action: Object) {
if (state.connection === action.connection) {
function _connectionDisconnected(
state: Object,
{ connection }: { connection: Object }) {
if (state.connection === connection) {
return set(state, 'connection', undefined);
}
@ -56,13 +58,15 @@ function _connectionDisconnected(state: Object, action: Object) {
* @returns {Object} The new state of the feature base/connection after the
* reduction of the specified action.
*/
function _connectionEstablished(state: Object, action: Object) {
return set(state, 'connection', action.connection);
function _connectionEstablished(
state: Object,
{ connection }: { connection: Object }) {
return set(state, 'connection', connection);
}
/**
* Constructs options to be passed to the constructor of JitsiConnection based
* on a specific domain.
* Constructs options to be passed to the constructor of {@code JitsiConnection}
* based on a specific domain.
*
* @param {string} domain - The domain with which the returned options are to be
* populated.
@ -105,20 +109,20 @@ function _constructOptions(domain: string) {
}
/**
* Reduces a specific Redux action SET_DOMAIN of the feature base/connection.
* Reduces a specific redux action {@link SET_LOCATION_URL} of the feature
* base/connection.
*
* @param {Object} state - The Redux state of the feature base/connection.
* @param {Action} action - The Redux action SET_DOMAIN to reduce.
* @param {Object} state - The redux state of the feature base/connection.
* @param {Action} action - The redux action {@code SET_LOCATION_URL} to reduce.
* @private
* @returns {Object} The new state of the feature base/connection after the
* reduction of the specified action.
*/
function _setDomain(state: Object, action: Object) {
return {
...state,
options: {
...state.options,
..._constructOptions(action.domain)
}
};
function _setLocationURL(
state: Object,
{ locationURL }: { locationURL: ?URL }) {
return assign(state, {
locationURL,
options: locationURL ? _constructOptions(locationURL.host) : undefined
});
}

View File

@ -52,13 +52,13 @@ export function isFatalJitsiConnectionError(error: string) {
}
/**
* Loads config.js file from remote server.
* Loads config.js from a specific remote server.
*
* @param {string} host - Host where config.js is hosted.
* @param {string} path='/config.js' - Relative pah to config.js file.
* @param {string} path='config.js' - Relative pah to config.js file.
* @returns {Promise<Object>}
*/
export function loadConfig(host: string, path: string = '/config.js') {
export function loadConfig(host: string, path: string = 'config.js') {
let promise;
if (typeof APP === 'undefined') {

View File

@ -1,7 +1,7 @@
import jwtDecode from 'jwt-decode';
import { SET_ROOM_URL } from '../base/conference';
import { parseURLParams, SET_CONFIG } from '../base/config';
import { SET_LOCATION_URL } from '../base/connection';
import { MiddlewareRegistry } from '../base/redux';
import { setJWT } from './actions';
@ -17,13 +17,13 @@ import { SET_JWT } from './actionTypes';
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case SET_CONFIG:
case SET_ROOM_URL:
case SET_LOCATION_URL:
// XXX The JSON Web Token (JWT) is not the only piece of state that we
// have decided to store in the feature jwt, there is isGuest as well
// which depends on the states of the features base/config and jwt. So
// the JSON Web Token comes from the room's URL and isGuest needs a
// recalculation upon SET_CONFIG as well.
return _setConfigOrRoomURL(store, next, action);
// the JSON Web Token comes from the conference/room's URL and isGuest
// needs a recalculation upon SET_CONFIG as well.
return _setConfigOrLocationURL(store, next, action);
case SET_JWT:
return _setJWT(store, next, action);
@ -34,7 +34,7 @@ MiddlewareRegistry.register(store => next => action => {
/**
* Notifies the feature jwt that the action {@link SET_CONFIG} or
* {@link SET_ROOM_URL} is being dispatched within a specific Redux
* {@link SET_LOCATION_URL} is being dispatched within a specific Redux
* {@code store}.
*
* @param {Store} store - The Redux store in which the specified {@code action}
@ -42,20 +42,20 @@ MiddlewareRegistry.register(store => next => action => {
* @param {Dispatch} next - The Redux dispatch function to dispatch the
* specified {@code action} to the specified {@code store}.
* @param {Action} action - The Redux action {@code SET_CONFIG} or
* {@code SET_ROOM_NAME} which is being dispatched in the specified
* {@code SET_LOCATION_URL} which is being dispatched in the specified
* {@code store}.
* @private
* @returns {Object} The new state that is the result of the reduction of the
* specified {@code action}.
*/
function _setConfigOrRoomURL({ dispatch, getState }, next, action) {
function _setConfigOrLocationURL({ dispatch, getState }, next, action) {
const result = next(action);
const { roomURL } = getState()['features/base/conference'];
const { locationURL } = getState()['features/base/connection'];
let jwt;
if (roomURL) {
jwt = parseURLParams(roomURL, true, 'search').jwt;
if (locationURL) {
jwt = parseURLParams(locationURL, true, 'search').jwt;
}
dispatch(setJWT(jwt));

View File

@ -47,15 +47,13 @@ ReducerRegistry.register('features/overlay', (state = {}, action) => {
* the specified action.
* @private
*/
function _conferenceFailed(state, action) {
const error = action.error;
function _conferenceFailed(state, { error, message }) {
if (error === JitsiConferenceErrors.FOCUS_LEFT
|| error === JitsiConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE) {
return assign(state, {
haveToReload: true,
isNetworkFailure: false,
reason: action.errorMessage
reason: message
});
}
@ -84,13 +82,9 @@ function _connectionEstablished(state) {
* the specified action.
* @private
*/
function _connectionFailed(state, action) {
const error = action.error;
function _connectionFailed(state, { error, message }) {
if (isFatalJitsiConnectionError(error)) {
const errorMessage = action.errorMessage;
logger.error(`XMPP connection error: ${errorMessage}`);
logger.error(`XMPP connection error: ${message}`);
return assign(state, {
haveToReload: true,
@ -99,7 +93,7 @@ function _connectionFailed(state, action) {
// considered a network type of failure.
isNetworkFailure:
error === JitsiConnectionErrors.CONNECTION_DROPPED_ERROR,
reason: `xmpp-conn-dropped: ${errorMessage}`
reason: `xmpp-conn-dropped: ${message}`
});
}

View File

@ -12,18 +12,17 @@ import { BEGIN_SHARE_ROOM, END_SHARE_ROOM } from './actionTypes';
export function beginShareRoom(roomURL: ?string): Function {
return (dispatch, getState) => {
if (!roomURL) {
const { conference, room } = getState()['features/base/conference'];
const { locationURL } = getState()['features/base/connection'];
// eslint-disable-next-line no-param-reassign
roomURL = _getRoomURL(conference, room);
}
if (roomURL) {
dispatch({
type: BEGIN_SHARE_ROOM,
roomURL
});
if (locationURL) {
// eslint-disable-next-line no-param-reassign
roomURL = locationURL.toString();
}
}
roomURL && dispatch({
type: BEGIN_SHARE_ROOM,
roomURL
});
};
}
@ -47,19 +46,3 @@ export function endShareRoom(roomURL: string, shared: boolean): Object {
shared
};
}
/**
* Gets the public URL of a conference/room.
*
* @param {JitsiConference} conference - The JitsiConference to share the URL
* of.
* @param {string} room - The name of the room to share the URL of.
* @private
* @returns {string|null}
*/
function _getRoomURL(conference, room) {
return (
conference
&& room
&& `https://${conference.connection.options.hosts.domain}/${room}`);
}