Compare commits

...

5 Commits

Author SHA1 Message Date
paweldomas 4d60a4e3a6 ref(base/session): createSession on APP_WILL_NAVIGATE 2019-05-08 14:48:53 -05:00
paweldomas 160380dac0 ref(base/session): create tracks and connect on SET_ROOM 2019-05-08 14:46:18 -05:00
paweldomas 9dbba7b119 ref(base/session): move endAllSessions to APP_WILL_NAVIGATE 2019-05-08 14:39:18 -05:00
paweldomas 7101f90b6e feat: add base/session 2019-05-06 16:06:56 -05:00
paweldomas 621ee7b447 ref(base/connection/actions.native): JitsiConnection.connect returns void
Do not return anything from JitsiConnection.connect, because it's not
a promise and returns void. Doing so is confusing to the reader.
2019-05-06 16:06:56 -05:00
16 changed files with 537 additions and 144 deletions

View File

@ -5,7 +5,8 @@ import jitsiLocalStorage from './modules/util/JitsiLocalStorage';
import {
connectionEstablished,
connectionFailed
connectionFailed,
connectionWillConnect
} from './react/features/base/connection';
import {
isFatalJitsiConnectionError,
@ -74,6 +75,7 @@ function checkForAttachParametersAndConnect(id, password, connection) {
function connect(id, password, roomName) {
const connectionConfig = Object.assign({}, config);
const { issuer, jwt } = APP.store.getState()['features/base/jwt'];
const { locationURL } = APP.store.getState()['features/base/connection'];
connectionConfig.bosh += `?room=${roomName}`;
@ -83,6 +85,8 @@ function connect(id, password, roomName) {
jwt && issuer && issuer !== 'anonymous' ? jwt : undefined,
connectionConfig);
APP.store.dispatch(connectionWillConnect(connection, locationURL));
return new Promise((resolve, reject) => {
connection.addEventListener(
JitsiConnectionEvents.CONNECTION_ESTABLISHED,

View File

@ -2,6 +2,7 @@
import type { Dispatch } from 'redux';
import { appWillNavigate } from '../base/app';
import { setRoom } from '../base/conference';
import {
configWillLoad,
@ -58,6 +59,9 @@ export function appNavigate(uri: ?string) {
const { contextRoot, host, room } = location;
const locationURL = new URL(location.toString());
// XXX this looks like CONFIG_WILL_LOAD ?
dispatch(appWillNavigate(locationURL, room));
dispatch(configWillLoad(locationURL, room));
let protocol = location.protocol.toLowerCase();
@ -81,7 +85,7 @@ export function appNavigate(uri: ?string) {
config = restoreConfig(baseURL);
if (!config) {
dispatch(loadConfigError(error, locationURL));
dispatch(loadConfigError(error, locationURL, room));
return;
}
@ -92,7 +96,7 @@ export function appNavigate(uri: ?string) {
dispatch(setConfig(config));
dispatch(setRoom(room));
} else {
dispatch(loadConfigError(new Error('Config no longer needed!'), locationURL));
dispatch(loadConfigError(new Error('Config no longer needed!'), locationURL, room));
}
};
}

View File

@ -113,6 +113,7 @@ export function cancelWaitForOwner() {
// clients/consumers need an event.
const { authRequired } = getState()['features/base/conference'];
// FIXME remove when external-api ported to base/session
authRequired && dispatch(conferenceLeft(authRequired));
dispatch(appNavigate(undefined));

View File

@ -9,6 +9,13 @@
*/
export const APP_WILL_MOUNT = 'APP_WILL_MOUNT';
/**
* FIXME.
*
* @type {string}
*/
export const APP_WILL_NAVIGATE = 'APP_WILL_NAVIGATE';
/**
* The type of (redux) action which signals that a specific App will unmount (in
* React terms).

View File

@ -2,7 +2,7 @@
import type { Dispatch } from 'redux';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes';
import { APP_WILL_MOUNT, APP_WILL_NAVIGATE, APP_WILL_UNMOUNT } from './actionTypes';
declare var APP;
@ -33,6 +33,25 @@ export function appWillMount(app: Object) {
};
}
/**
* FIXME.
*
* @param {URL} locationURL - FIXME.
* @param {string} room - FIXME.
* @returns {{
* type: APP_WILL_NAVIGATE,
* locationURL: URL,
* room: ?string
* }}
*/
export function appWillNavigate(locationURL: URL, room: ?string) {
return {
type: APP_WILL_NAVIGATE,
locationURL,
room
};
}
/**
* Signals that a specific App will unmount (in the terms of React).
*

View File

@ -38,17 +38,20 @@ export function configWillLoad(locationURL: URL, room: string) {
* loading of a configuration.
* @param {URL} locationURL - The URL of the location which necessitated the
* loading of a configuration.
* @param {string} room - The name of the conference room.
* @returns {{
* type: LOAD_CONFIG_ERROR,
* error: Error,
* locationURL: URL
* locationURL: URL,
* room: string
* }}
*/
export function loadConfigError(error: Error, locationURL: URL) {
export function loadConfigError(error: Error, locationURL: URL, room: ?string) {
return {
type: LOAD_CONFIG_ERROR,
error,
locationURL
locationURL,
room
};
}

View File

@ -3,11 +3,6 @@
import _ from 'lodash';
import type { Dispatch } from 'redux';
import {
conferenceLeft,
conferenceWillLeave,
getCurrentConference
} from '../conference';
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
import { parseURIString } from '../util';
@ -20,7 +15,7 @@ import {
} from './actionTypes';
import { JITSI_CONNECTION_URL_KEY } from './constants';
const logger = require('jitsi-meet-logger').getLogger(__filename);
import type JitsiConnection from 'lib-jitsi-meet/JitsiConnection';
/**
* The error structure passed to the {@link connectionFailed} action.
@ -90,7 +85,7 @@ export function connect(id: ?string, password: ?string) {
connection[JITSI_CONNECTION_URL_KEY] = locationURL;
dispatch(_connectionWillConnect(connection));
dispatch(connectionWillConnect(connection, locationURL));
connection.addEventListener(
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
@ -102,7 +97,7 @@ export function connect(id: ?string, password: ?string) {
JitsiConnectionEvents.CONNECTION_FAILED,
_onConnectionFailed);
return connection.connect({
connection.connect({
id,
password
});
@ -258,16 +253,18 @@ export function connectionFailed(
*
* @param {JitsiConnection} connection - The {@code JitsiConnection} which will
* connect.
* @param {URL} locationURL - FIXME.
* @private
* @returns {{
* type: CONNECTION_WILL_CONNECT,
* connection: JitsiConnection
* }}
*/
function _connectionWillConnect(connection) {
export function connectionWillConnect(connection: JitsiConnection, locationURL: URL) {
return {
type: CONNECTION_WILL_CONNECT,
connection
connection,
locationURL
};
}
@ -319,67 +316,6 @@ function _constructOptions(state) {
return options;
}
/**
* Closes connection.
*
* @returns {Function}
*/
export function disconnect() {
return (dispatch: Dispatch<any>, getState: Function): 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_));
promise
= conference_.leave()
.catch(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 {
// FIXME: We have no connection! Fake a disconnect. Because of how the current disconnec is implemented
// (by doing the diconnect() in the Conference component unmount) we have lost the location URL already.
// Oh well, at least send the event.
promise.then(() => dispatch(_connectionDisconnected({}, '')));
}
return promise;
};
}
/**
* Sets the location URL of the application, connecton, conference, etc.
*

View File

@ -12,6 +12,7 @@ import { configureInitialDevices } from '../devices';
export {
connectionEstablished,
connectionFailed,
connectionWillConnect,
setLocationURL
} from './actions.native';

View File

@ -0,0 +1,86 @@
// @flow
import uuid from 'uuid';
import { toURLString } from '../util';
import type JitsiConference from 'lib-jitsi-meet/JitsiConference';
import type JitsiConnection from 'lib-jitsi-meet/JitsiConnection';
/**
* FIXME.
*/
export class Session {
_conference: ?JitsiConference;
_connection: ?JitsiConnection;
conferenceFailed: boolean;
id: string;
locationURL: URL;
room: string;
/**
* FIXME.
*
* @param {URL} locationURL - FIXME.
* @param {string} room - FIXME.
*/
constructor(locationURL: URL, room: string) {
this.locationURL = locationURL;
this.room = room;
this.id = uuid.v4().toUpperCase();
this.conferenceFailed = false;
}
/**
* FIXME.
*
* @param {JitsiConference} [conference] - FIXME.
*/
set conference(conference: ?JitsiConference) {
if (this._conference && conference && this._conference !== conference) {
throw new Error(`Attempt to reassign conference to ${this.toString()}`);
}
this._conference = conference;
}
/**
* FIXME.
*
* @returns {?JitsiConference}
*/
get conference(): ?JitsiConference {
return this._conference;
}
/**
* FIXME.
*
* @param {JitsiConnection} [connection] - FIXME.
*/
set connection(connection: ?JitsiConnection) {
if (this._connection && connection && this._connection !== connection) {
throw new Error(`Attempt to reassign connection to ${this.toString()}`);
}
this._connection = connection;
}
/**
* FIXME.
*
* @returns {?JitsiConnection}
*/
get connection() {
return this._connection;
}
/**
* FIXME.
*
* @returns {string}
*/
toString() {
return `Session[id=${this.id}, URL: ${toURLString(this.locationURL)} room: ${this.room}]`;
}
}

View File

@ -0,0 +1,8 @@
export const SESSION_CREATED = 'SESSION_CREATED';
export const SESSION_STARTED = 'SESSION_STARTED';
export const SESSION_TERMINATED = 'SESSION_TERMINATED';
export const SESSION_FAILED = 'SESSION_FAILED';

View File

@ -0,0 +1,135 @@
// @flow
import { conferenceLeft, conferenceWillLeave } from '../conference';
import { SESSION_CREATED, SESSION_FAILED, SESSION_STARTED, SESSION_TERMINATED } from './actionTypes';
import { Session } from './Session';
import type { Dispatch } from 'redux';
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* FIXME.
*
* @returns {Function}
*/
export function endAllSessions() {
return (dispatch: Dispatch<any>, getState: Function) => {
const sessions = getState()['features/base/session'];
for (const session of sessions.values()) {
dispatch(endSession(session));
}
};
}
/**
* FIXME.
*
* @param {Session} session - FIXME.
* @returns {function(*): *}
*/
export function endSession(session: Session) {
return (dispatch: Dispatch<any>) => {
// The conference we have already joined or are joining.
const conference_ = session.conference;
// 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_));
promise
= conference_.leave()
.catch(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();
}
const connection_ = session.connection;
if (connection_) {
promise = promise.then(() => connection_.disconnect());
}
return promise;
};
}
/**
* FIXME.
*
* @param {URL} locationURL - FIXME.
* @param {string} room - FIXME.
* @returns {{
* type: SESSION_CREATED,
* session: Session
* }}
*/
export function createSession(locationURL: URL, room: string) {
return {
type: SESSION_CREATED,
session: new Session(locationURL, room)
};
}
/**
* FIXME.
*
* @param {Session} session - FIXME.
* @returns {{
* type: SESSION_FAILED,
* session: Session
* }}
*/
export function sessionFailed(session: Session) {
return {
type: SESSION_FAILED,
session
};
}
/**
* FIXME.
*
* @param {Session} session - FIXME.
* @returns {{
* session: Session,
* type: string
* }}
*/
export function sessionStarted(session: Session) {
return {
type: SESSION_STARTED,
session
};
}
/**
* FIXME.
*
* @param {Session} session - FIXME.
* @returns {{
* type: SESSION_TERMINATED,
* session: Session
* }}
*/
export function sessionTerminated(session: Session) {
return {
type: SESSION_TERMINATED,
session
};
}

View File

@ -0,0 +1,6 @@
export * from './actions';
export * from './actionTypes';
export * from './selectors';
import './middleware';
import './reducer';

View File

@ -0,0 +1,164 @@
import { APP_WILL_NAVIGATE } from '../app';
import { CONFERENCE_FAILED, CONFERENCE_JOINED, CONFERENCE_LEFT, CONFERENCE_WILL_JOIN, SET_ROOM } from '../conference';
import { LOAD_CONFIG_ERROR } from '../config';
import { connect, CONNECTION_DISCONNECTED, CONNECTION_FAILED, CONNECTION_WILL_CONNECT } from '../connection';
import { MiddlewareRegistry } from '../redux';
import { createDesiredLocalTracks } from '../tracks';
import { toURLString } from '../util';
import { createSession, endAllSessions, endSession, sessionFailed, sessionStarted, sessionTerminated } from './actions';
import { SESSION_CREATED, SESSION_FAILED, SESSION_STARTED, SESSION_TERMINATED } from './actionTypes';
import { findSessionForConference, findSessionForConnection, findSessionForLocationURL } from './selectors';
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const result = next(action);
switch (action.type) {
case APP_WILL_NAVIGATE: {
const { locationURL, room } = action;
// Currently only one session is allowed at a time
dispatch(endAllSessions());
// Start a new session if there's a conference room name defined
room && room.length && dispatch(createSession(locationURL, room));
break;
}
case CONFERENCE_FAILED: {
const { conference, error } = action;
const { recoverable } = error;
const session = findSessionForConference(getState(), conference);
if (session) {
session.conference = null;
if (typeof recoverable === 'undefined' || recoverable === false) {
session.conferenceFailed = true;
if (session.connection) {
// The sessionFailed is expected to be dispatched from either CONNECTION_DISCONNECTED
// or CONNECTION_FAILED handler in this middleware. The purpose is to delay the SESSION_FAILED until
// the XMPP connection is properly disposed.
dispatch(endSession(session));
} else {
dispatch(sessionFailed(session));
}
}
} else {
console.error('CONFERENCE_FAILED - no session found');
}
break;
}
case CONFERENCE_LEFT: {
const { conference } = action;
const session = findSessionForConference(getState(), conference);
if (session) {
session.conference = null;
// If there's any existing connection wait for it to be closed first
session.connection || dispatch(sessionTerminated(session));
} else {
console.error('CONFERENCE_LEFT - no session found');
}
break;
}
case CONFERENCE_JOINED: {
const { conference } = action;
const session = findSessionForConference(getState(), conference);
if (session) {
dispatch(sessionStarted(session));
} else {
console.error('CONFERENCE_JOINED - no session found');
}
break;
}
case CONFERENCE_WILL_JOIN: {
const { conference } = action;
const { connection } = conference;
const session = findSessionForConnection(getState(), connection);
if (session) {
session.conference = conference;
} else {
console.error('CONFERENCE_WILL_JOIN - no session found');
}
break;
}
case LOAD_CONFIG_ERROR: {
const { locationURL, room } = action;
// There won't be a session if there's no room (it happen when the config is loaded for the welcome page).
if (room) {
const session = findSessionForLocationURL(getState(), locationURL);
if (session) {
dispatch(sessionFailed(session));
} else {
console.error(`LOAD_CONFIG_ERROR - no session found for: ${toURLString(locationURL)}`);
}
}
break;
}
case CONNECTION_DISCONNECTED: {
const { connection } = action;
const session = findSessionForConnection(getState(), connection);
if (session) {
session.connection = null;
dispatch(
session.conferenceFailed ? sessionFailed(session) : sessionTerminated(session));
} else {
console.error('CONNECTION_DISCONNECTED - no session found');
}
break;
}
case CONNECTION_FAILED: {
const { connection, error } = action;
const { recoverable } = error;
const session = findSessionForConnection(getState(), connection);
if (session) {
session.connection = null;
if (typeof recoverable === 'undefined' || recoverable === false) {
dispatch(sessionFailed(session));
}
} else {
console.error('CONNECTION_FAILED - no session found');
}
break;
}
case CONNECTION_WILL_CONNECT: {
const { connection, locationURL } = action;
const session = findSessionForLocationURL(getState(), locationURL);
if (session) {
session.connection = connection;
} else {
console.error(`CONNECTION_WILL_CONNECT - no session found for: ${toURLString(locationURL)}`);
}
break;
}
case SET_ROOM: {
const { locationURL } = getState()['features/base/connection'];
const session = findSessionForLocationURL(getState(), locationURL);
// Web has different logic for creating the local tracks and starting the connection
if (session && typeof APP === 'undefined') {
dispatch(createDesiredLocalTracks());
dispatch(connect());
}
break;
}
case SESSION_CREATED:
case SESSION_STARTED:
case SESSION_FAILED:
case SESSION_TERMINATED:
console.info(`DEBUG ${action.type} ${action.session}`);
break;
}
return result;
});

View File

@ -0,0 +1,27 @@
import { ReducerRegistry } from '../redux';
import { SESSION_CREATED, SESSION_FAILED, SESSION_TERMINATED } from './actionTypes';
ReducerRegistry.register('features/base/session', (state = new Map(), action) => {
switch (action.type) {
case SESSION_CREATED: {
const { session } = action;
const nextMap = new Map(state);
nextMap.set(session.id, session);
return nextMap;
}
case SESSION_TERMINATED:
case SESSION_FAILED: {
const { session } = action;
const nextMap = new Map(state);
nextMap.delete(session.id);
return nextMap;
}
}
return state;
});

View File

@ -0,0 +1,56 @@
/**
* FIXME.
*
* @param {Object} state - FIXME.
* @param {JitsiConnection} connection - FIXME.
* @returns {Session|null}
*/
export function findSessionForConnection(state, connection) {
const sessions = state['features/base/session'];
for (const session of sessions.values()) {
if (session.connection === connection) {
return session;
}
}
return null;
}
/**
* FIXME.
*
* @param {Object} state - FIXME.
* @param {JitsiConference} conference - FIXME.
* @returns {Session|null}
*/
export function findSessionForConference(state, conference) {
const sessions = state['features/base/session'];
for (const session of sessions.values()) {
if (session.conference === conference) {
return session;
}
}
return null;
}
/**
* FIXME.
*
* @param {Object} state - FIXME.
* @param {URL} locationURL - FIXME.
* @returns {Session|null}
*/
export function findSessionForLocationURL(state, locationURL) {
const sessions = state['features/base/session'];
for (const session of sessions.values()) {
if (session.locationURL === locationURL) {
return session;
}
}
return null;
}

View File

@ -5,7 +5,6 @@ import React from 'react';
import { BackHandler, SafeAreaView, StatusBar, View } from 'react-native';
import { appNavigate } from '../../../app';
import { connect, disconnect } from '../../../base/connection';
import { getParticipantCount } from '../../../base/participants';
import { Container, LoadingIndicator, TintedView } from '../../../base/react';
import { connect as reactReduxConnect } from '../../../base/redux';
@ -14,7 +13,6 @@ import {
makeAspectRatioAware
} from '../../../base/responsive-ui';
import { TestConnectionInfo } from '../../../base/testing';
import { createDesiredLocalTracks } from '../../../base/tracks';
import { ConferenceNotification } from '../../../calendar-sync';
import { Chat } from '../../../chat';
import { DisplayNameLabel } from '../../../display-name';
@ -66,21 +64,6 @@ type Props = AbstractProps & {
*/
_largeVideoParticipantId: string,
/**
* Current conference's full URL.
*
* @private
*/
_locationURL: URL,
/**
* The handler which dispatches the (redux) action connect.
*
* @private
* @returns {void}
*/
_onConnect: Function,
/**
* The handler which dispatches the (redux) action disconnect.
*
@ -166,8 +149,6 @@ class Conference extends AbstractConference<Props, *> {
* @returns {void}
*/
componentDidMount() {
this.props._onConnect();
BackHandler.addEventListener(
'hardwareBackPress',
this.props._onHardwareBackPress);
@ -186,24 +167,14 @@ class Conference extends AbstractConference<Props, *> {
*/
componentDidUpdate(pevProps: Props) {
const {
_locationURL: oldLocationURL,
_participantCount: oldParticipantCount,
_room: oldRoom
_participantCount: oldParticipantCount
} = pevProps;
const {
_locationURL: newLocationURL,
_participantCount: newParticipantCount,
_room: newRoom,
_setToolboxVisible,
_toolboxVisible
} = this.props;
// If the location URL changes we need to reconnect.
oldLocationURL !== newLocationURL && newRoom && this.props._onDisconnect();
// Start the connection process when there is a (valid) room.
oldRoom !== newRoom && newRoom && this.props._onConnect();
if (oldParticipantCount === 1
&& newParticipantCount > 1
&& _toolboxVisible) {
@ -228,8 +199,6 @@ class Conference extends AbstractConference<Props, *> {
BackHandler.removeEventListener(
'hardwareBackPress',
this.props._onHardwareBackPress);
this.props._onDisconnect();
}
/**
@ -396,36 +365,12 @@ class Conference extends AbstractConference<Props, *> {
* @param {Function} dispatch - Redux action dispatcher.
* @private
* @returns {{
* _onConnect: Function,
* _onDisconnect: Function,
* _onHardwareBackPress: Function,
* _setToolboxVisible: Function
* }}
*/
function _mapDispatchToProps(dispatch) {
return {
/**
* Dispatches actions to create the desired local tracks and for
* connecting to the conference.
*
* @private
* @returns {void}
*/
_onConnect() {
dispatch(createDesiredLocalTracks());
dispatch(connect());
},
/**
* Dispatches an action disconnecting from the conference.
*
* @private
* @returns {void}
*/
_onDisconnect() {
dispatch(disconnect());
},
/**
* Handles a hardware button press for back navigation. Leaves the
* associated {@code Conference}.
@ -462,8 +407,7 @@ function _mapDispatchToProps(dispatch) {
* @returns {Props}
*/
function _mapStateToProps(state) {
const { connecting, connection, locationURL }
= state['features/base/connection'];
const { connecting, connection } = state['features/base/connection'];
const {
conference,
joining,
@ -508,14 +452,6 @@ function _mapStateToProps(state) {
*/
_largeVideoParticipantId: state['features/large-video'].participantId,
/**
* Current conference's full URL.
*
* @private
* @type {URL}
*/
_locationURL: locationURL,
/**
* The number of participants in the conference.
*