Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
a2764a0c1f
12 changed files with 297 additions and 210 deletions
119
src/Lifecycle.js
119
src/Lifecycle.js
|
@ -35,26 +35,20 @@ import { _t } from './languageHandler';
|
|||
* Called at startup, to attempt to build a logged-in Matrix session. It tries
|
||||
* a number of things:
|
||||
*
|
||||
* 1. if we have a loginToken in the (real) query params, it uses that to log
|
||||
* in.
|
||||
*
|
||||
* 2. if we have a guest access token in the fragment query params, it uses
|
||||
* 1. if we have a guest access token in the fragment query params, it uses
|
||||
* that.
|
||||
*
|
||||
* 3. if an access token is stored in local storage (from a previous session),
|
||||
* 2. if an access token is stored in local storage (from a previous session),
|
||||
* it uses that.
|
||||
*
|
||||
* 4. it attempts to auto-register as a guest user.
|
||||
* 3. it attempts to auto-register as a guest user.
|
||||
*
|
||||
* If any of steps 1-4 are successful, it will call {_doSetLoggedIn}, which in
|
||||
* turn will raise on_logged_in and will_start_client events.
|
||||
*
|
||||
* @param {object} opts
|
||||
*
|
||||
* @param {object} opts.realQueryParams: string->string map of the
|
||||
* query-parameters extracted from the real query-string of the starting
|
||||
* URI.
|
||||
*
|
||||
* @param {object} opts.fragmentQueryParams: string->string map of the
|
||||
* query-parameters extracted from the #-fragment of the starting URI.
|
||||
*
|
||||
|
@ -68,9 +62,10 @@ import { _t } from './languageHandler';
|
|||
* true; defines the IS to use.
|
||||
*
|
||||
* @returns {Promise} a promise which resolves when the above process completes.
|
||||
* Resolves to `true` if we ended up starting a session, or `false` if we
|
||||
* failed.
|
||||
*/
|
||||
export function loadSession(opts) {
|
||||
const realQueryParams = opts.realQueryParams || {};
|
||||
const fragmentQueryParams = opts.fragmentQueryParams || {};
|
||||
let enableGuest = opts.enableGuest || false;
|
||||
const guestHsUrl = opts.guestHsUrl;
|
||||
|
@ -82,14 +77,6 @@ export function loadSession(opts) {
|
|||
enableGuest = false;
|
||||
}
|
||||
|
||||
if (realQueryParams.loginToken) {
|
||||
if (!realQueryParams.homeserver) {
|
||||
console.warn("Cannot log in with token: can't determine HS URL to use");
|
||||
} else {
|
||||
return _loginWithToken(realQueryParams, defaultDeviceDisplayName);
|
||||
}
|
||||
}
|
||||
|
||||
if (enableGuest &&
|
||||
fragmentQueryParams.guest_user_id &&
|
||||
fragmentQueryParams.guest_access_token
|
||||
|
@ -101,12 +88,12 @@ export function loadSession(opts) {
|
|||
homeserverUrl: guestHsUrl,
|
||||
identityServerUrl: guestIsUrl,
|
||||
guest: true,
|
||||
}, true);
|
||||
}, true).then(() => true);
|
||||
}
|
||||
|
||||
return _restoreFromLocalStorage().then((success) => {
|
||||
if (success) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (enableGuest) {
|
||||
|
@ -114,10 +101,30 @@ export function loadSession(opts) {
|
|||
}
|
||||
|
||||
// fall back to login screen
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function _loginWithToken(queryParams, defaultDeviceDisplayName) {
|
||||
/**
|
||||
* @param {Object} queryParams string->string map of the
|
||||
* query-parameters extracted from the real query-string of the starting
|
||||
* URI.
|
||||
*
|
||||
* @param {String} defaultDeviceDisplayName
|
||||
*
|
||||
* @returns {Promise} promise which resolves to true if we completed the token
|
||||
* login, else false
|
||||
*/
|
||||
export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) {
|
||||
if (!queryParams.loginToken) {
|
||||
return q(false);
|
||||
}
|
||||
|
||||
if (!queryParams.homeserver) {
|
||||
console.warn("Cannot log in with token: can't determine HS URL to use");
|
||||
return q(false);
|
||||
}
|
||||
|
||||
// create a temporary MatrixClient to do the login
|
||||
const client = Matrix.createClient({
|
||||
baseUrl: queryParams.homeserver,
|
||||
|
@ -130,17 +137,21 @@ function _loginWithToken(queryParams, defaultDeviceDisplayName) {
|
|||
},
|
||||
).then(function(data) {
|
||||
console.log("Logged in with token");
|
||||
return _doSetLoggedIn({
|
||||
userId: data.user_id,
|
||||
deviceId: data.device_id,
|
||||
accessToken: data.access_token,
|
||||
homeserverUrl: queryParams.homeserver,
|
||||
identityServerUrl: queryParams.identityServer,
|
||||
guest: false,
|
||||
}, true);
|
||||
}, (err) => {
|
||||
return _clearStorage().then(() => {
|
||||
_persistCredentialsToLocalStorage({
|
||||
userId: data.user_id,
|
||||
deviceId: data.device_id,
|
||||
accessToken: data.access_token,
|
||||
homeserverUrl: queryParams.homeserver,
|
||||
identityServerUrl: queryParams.identityServer,
|
||||
guest: false,
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.error("Failed to log in with login token: " + err + " " +
|
||||
err.data);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -168,9 +179,10 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
|
|||
homeserverUrl: hsUrl,
|
||||
identityServerUrl: isUrl,
|
||||
guest: true,
|
||||
}, true);
|
||||
}, true).then(() => true);
|
||||
}, (err) => {
|
||||
console.error("Failed to register as guest: " + err + " " + err.data);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -282,10 +294,12 @@ export function initRtsClient(url) {
|
|||
* storage before starting the new client.
|
||||
*
|
||||
* @param {MatrixClientCreds} credentials The credentials to use
|
||||
*
|
||||
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
|
||||
*/
|
||||
export function setLoggedIn(credentials) {
|
||||
stopMatrixClient();
|
||||
_doSetLoggedIn(credentials, true);
|
||||
return _doSetLoggedIn(credentials, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -295,7 +309,7 @@ export function setLoggedIn(credentials) {
|
|||
* @param {MatrixClientCreds} credentials
|
||||
* @param {Boolean} clearStorage
|
||||
*
|
||||
* returns a Promise which resolves once the client has been started
|
||||
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
|
||||
*/
|
||||
async function _doSetLoggedIn(credentials, clearStorage) {
|
||||
credentials.guest = Boolean(credentials.guest);
|
||||
|
@ -322,23 +336,10 @@ async function _doSetLoggedIn(credentials, clearStorage) {
|
|||
// Resolves by default
|
||||
let teamPromise = Promise.resolve(null);
|
||||
|
||||
// persist the session
|
||||
|
||||
if (localStorage) {
|
||||
try {
|
||||
localStorage.setItem("mx_hs_url", credentials.homeserverUrl);
|
||||
localStorage.setItem("mx_is_url", credentials.identityServerUrl);
|
||||
localStorage.setItem("mx_user_id", credentials.userId);
|
||||
localStorage.setItem("mx_access_token", credentials.accessToken);
|
||||
localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest));
|
||||
|
||||
// if we didn't get a deviceId from the login, leave mx_device_id unset,
|
||||
// rather than setting it to "undefined".
|
||||
//
|
||||
// (in this case MatrixClient doesn't bother with the crypto stuff
|
||||
// - that's fine for us).
|
||||
if (credentials.deviceId) {
|
||||
localStorage.setItem("mx_device_id", credentials.deviceId);
|
||||
}
|
||||
_persistCredentialsToLocalStorage(credentials);
|
||||
|
||||
// The user registered as a PWLU (PassWord-Less User), the generated password
|
||||
// is cached here such that the user can change it at a later time.
|
||||
|
@ -349,8 +350,6 @@ async function _doSetLoggedIn(credentials, clearStorage) {
|
|||
cachedPassword: credentials.password,
|
||||
});
|
||||
}
|
||||
|
||||
console.log("Session persisted for %s", credentials.userId);
|
||||
} catch (e) {
|
||||
console.warn("Error using local storage: can't persist session!", e);
|
||||
}
|
||||
|
@ -377,6 +376,26 @@ async function _doSetLoggedIn(credentials, clearStorage) {
|
|||
});
|
||||
|
||||
startMatrixClient();
|
||||
return MatrixClientPeg.get();
|
||||
}
|
||||
|
||||
function _persistCredentialsToLocalStorage(credentials) {
|
||||
localStorage.setItem("mx_hs_url", credentials.homeserverUrl);
|
||||
localStorage.setItem("mx_is_url", credentials.identityServerUrl);
|
||||
localStorage.setItem("mx_user_id", credentials.userId);
|
||||
localStorage.setItem("mx_access_token", credentials.accessToken);
|
||||
localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest));
|
||||
|
||||
// if we didn't get a deviceId from the login, leave mx_device_id unset,
|
||||
// rather than setting it to "undefined".
|
||||
//
|
||||
// (in this case MatrixClient doesn't bother with the crypto stuff
|
||||
// - that's fine for us).
|
||||
if (credentials.deviceId) {
|
||||
localStorage.setItem("mx_device_id", credentials.deviceId);
|
||||
}
|
||||
|
||||
console.log("Session persisted for %s", credentials.userId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -43,7 +43,41 @@ import createRoom from "../../createRoom";
|
|||
import * as UDEHandler from '../../UnknownDeviceErrorHandler';
|
||||
import { _t, getCurrentLanguage } from '../../languageHandler';
|
||||
|
||||
/** constants for MatrixChat.state.view */
|
||||
const VIEWS = {
|
||||
// a special initial state which is only used at startup, while we are
|
||||
// trying to re-animate a matrix client or register as a guest.
|
||||
LOADING: 0,
|
||||
|
||||
// we are showing the login view
|
||||
LOGIN: 1,
|
||||
|
||||
// we are showing the registration view
|
||||
REGISTER: 2,
|
||||
|
||||
// completeing the registration flow
|
||||
POST_REGISTRATION: 3,
|
||||
|
||||
// showing the 'forgot password' view
|
||||
FORGOT_PASSWORD: 4,
|
||||
|
||||
// we have valid matrix credentials (either via an explicit login, via the
|
||||
// initial re-animation/guest registration, or via a registration), and are
|
||||
// now setting up a matrixclient to talk to it. This isn't an instant
|
||||
// process because (a) we need to clear out indexeddb, and (b) we need to
|
||||
// talk to the team server; while it is going on we show a big spinner.
|
||||
LOGGING_IN: 5,
|
||||
|
||||
// we are logged in with an active matrix client.
|
||||
LOGGED_IN: 6,
|
||||
};
|
||||
|
||||
module.exports = React.createClass({
|
||||
// we export this so that the integration tests can use it :-S
|
||||
statics: {
|
||||
VIEWS: VIEWS,
|
||||
},
|
||||
|
||||
displayName: 'MatrixChat',
|
||||
|
||||
propTypes: {
|
||||
|
@ -59,8 +93,8 @@ module.exports = React.createClass({
|
|||
// the initial queryParams extracted from the hash-fragment of the URI
|
||||
startingFragmentQueryParams: React.PropTypes.object,
|
||||
|
||||
// called when the session load completes
|
||||
onLoadCompleted: React.PropTypes.func,
|
||||
// called when we have completed a token login
|
||||
onTokenLoginCompleted: React.PropTypes.func,
|
||||
|
||||
// Represents the screen to display as a result of parsing the initial
|
||||
// window.location
|
||||
|
@ -93,8 +127,10 @@ module.exports = React.createClass({
|
|||
|
||||
getInitialState: function() {
|
||||
const s = {
|
||||
loading: true,
|
||||
screen: undefined,
|
||||
// the master view we are showing.
|
||||
view: VIEWS.LOADING,
|
||||
|
||||
// a thing to call showScreen with once login completes.
|
||||
screenAfterLogin: this.props.initialScreenAfterLogin,
|
||||
|
||||
// Stashed guest credentials if the user logs out
|
||||
|
@ -113,8 +149,6 @@ module.exports = React.createClass({
|
|||
// If we're trying to just view a user ID (i.e. /user URL), this is it
|
||||
viewUserId: null,
|
||||
|
||||
loggedIn: false,
|
||||
loggingIn: false,
|
||||
collapse_lhs: false,
|
||||
collapse_rhs: false,
|
||||
ready: false,
|
||||
|
@ -143,7 +177,7 @@ module.exports = React.createClass({
|
|||
realQueryParams: {},
|
||||
startingFragmentQueryParams: {},
|
||||
config: {},
|
||||
onLoadCompleted: () => {},
|
||||
onTokenLoginCompleted: () => {},
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -266,39 +300,49 @@ module.exports = React.createClass({
|
|||
const teamServerConfig = this.props.config.teamServerConfig || {};
|
||||
Lifecycle.initRtsClient(teamServerConfig.teamServerURL);
|
||||
|
||||
// if the user has followed a login or register link, don't reanimate
|
||||
// the old creds, but rather go straight to the relevant page
|
||||
// the first thing to do is to try the token params in the query-string
|
||||
Lifecycle.attemptTokenLogin(this.props.realQueryParams).then((loggedIn) => {
|
||||
if(loggedIn) {
|
||||
this.props.onTokenLoginCompleted();
|
||||
|
||||
const firstScreen = this.state.screenAfterLogin ?
|
||||
this.state.screenAfterLogin.screen : null;
|
||||
// don't do anything else until the page reloads - just stay in
|
||||
// the 'loading' state.
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstScreen === 'login' ||
|
||||
firstScreen === 'register' ||
|
||||
firstScreen === 'forgot_password') {
|
||||
this.props.onLoadCompleted();
|
||||
this.setState({loading: false});
|
||||
this._showScreenAfterLogin();
|
||||
return;
|
||||
}
|
||||
// if the user has followed a login or register link, don't reanimate
|
||||
// the old creds, but rather go straight to the relevant page
|
||||
const firstScreen = this.state.screenAfterLogin ?
|
||||
this.state.screenAfterLogin.screen : null;
|
||||
|
||||
// the extra q() ensures that synchronous exceptions hit the same codepath as
|
||||
// asynchronous ones.
|
||||
q().then(() => {
|
||||
return Lifecycle.loadSession({
|
||||
realQueryParams: this.props.realQueryParams,
|
||||
fragmentQueryParams: this.props.startingFragmentQueryParams,
|
||||
enableGuest: this.props.enableGuest,
|
||||
guestHsUrl: this.getCurrentHsUrl(),
|
||||
guestIsUrl: this.getCurrentIsUrl(),
|
||||
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
|
||||
if (firstScreen === 'login' ||
|
||||
firstScreen === 'register' ||
|
||||
firstScreen === 'forgot_password') {
|
||||
this.setState({loading: false});
|
||||
this._showScreenAfterLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
// the extra q() ensures that synchronous exceptions hit the same codepath as
|
||||
// asynchronous ones.
|
||||
return q().then(() => {
|
||||
return Lifecycle.loadSession({
|
||||
fragmentQueryParams: this.props.startingFragmentQueryParams,
|
||||
enableGuest: this.props.enableGuest,
|
||||
guestHsUrl: this.getCurrentHsUrl(),
|
||||
guestIsUrl: this.getCurrentIsUrl(),
|
||||
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.error("Unable to load session", e);
|
||||
return false;
|
||||
}).then((loadedSession) => {
|
||||
if (!loadedSession) {
|
||||
// fall back to showing the login screen
|
||||
dis.dispatch({action: "start_login"});
|
||||
}
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.error("Unable to load session", e);
|
||||
}).done(()=>{
|
||||
// stuff this through the dispatcher so that it happens
|
||||
// after the on_logged_in action.
|
||||
dis.dispatch({action: 'load_completed'});
|
||||
});
|
||||
}).done();
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
|
@ -317,18 +361,19 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
setStateForNewScreen: function(state) {
|
||||
setStateForNewView: function(state) {
|
||||
if (state.view === undefined) {
|
||||
throw new Error("setStateForNewView with no view!");
|
||||
}
|
||||
const newState = {
|
||||
screen: undefined,
|
||||
viewUserId: null,
|
||||
loggedIn: false,
|
||||
ready: false,
|
||||
};
|
||||
Object.assign(newState, state);
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
// console.log(`MatrixClientPeg.onAction: ${payload.action}`);
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
|
||||
|
@ -347,19 +392,19 @@ module.exports = React.createClass({
|
|||
guestCreds: MatrixClientPeg.getCredentials(),
|
||||
});
|
||||
}
|
||||
this.setStateForNewScreen({
|
||||
screen: 'login',
|
||||
this.setStateForNewView({
|
||||
view: VIEWS.LOGIN,
|
||||
});
|
||||
this.notifyNewScreen('login');
|
||||
break;
|
||||
case 'start_post_registration':
|
||||
this.setState({ // don't clobber loggedIn status
|
||||
screen: 'post_registration',
|
||||
this.setState({
|
||||
view: VIEWS.POST_REGISTRATION,
|
||||
});
|
||||
break;
|
||||
case 'start_password_recovery':
|
||||
this.setStateForNewScreen({
|
||||
screen: 'forgot_password',
|
||||
this.setStateForNewView({
|
||||
view: VIEWS.FORGOT_PASSWORD,
|
||||
});
|
||||
this.notifyNewScreen('forgot_password');
|
||||
break;
|
||||
|
@ -503,7 +548,10 @@ module.exports = React.createClass({
|
|||
// and also that we're not ready (we'll be marked as logged
|
||||
// in once the login completes, then ready once the sync
|
||||
// completes).
|
||||
this.setState({loggingIn: true, ready: false});
|
||||
this.setStateForNewView({
|
||||
view: VIEWS.LOGGING_IN,
|
||||
ready: false,
|
||||
});
|
||||
break;
|
||||
case 'on_logged_in':
|
||||
this._onLoggedIn(payload.teamToken);
|
||||
|
@ -514,15 +562,15 @@ module.exports = React.createClass({
|
|||
case 'will_start_client':
|
||||
this._onWillStartClient();
|
||||
break;
|
||||
case 'load_completed':
|
||||
this._onLoadCompleted();
|
||||
break;
|
||||
case 'new_version':
|
||||
this.onVersion(
|
||||
payload.currentVersion, payload.newVersion,
|
||||
payload.releaseNotes,
|
||||
);
|
||||
break;
|
||||
case 'send_event':
|
||||
this.onSendEvent(payload.room_id, payload.event);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -537,8 +585,8 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_startRegistration: function(params) {
|
||||
this.setStateForNewScreen({
|
||||
screen: 'register',
|
||||
this.setStateForNewView({
|
||||
view: VIEWS.REGISTER,
|
||||
// these params may be undefined, but if they are,
|
||||
// unset them from our state: we don't want to
|
||||
// resume a previous registration session if the
|
||||
|
@ -846,14 +894,6 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the sessionloader has finished
|
||||
*/
|
||||
_onLoadCompleted: function() {
|
||||
this.props.onLoadCompleted();
|
||||
this.setState({loading: false});
|
||||
},
|
||||
|
||||
/**
|
||||
* Called whenever someone changes the theme
|
||||
*
|
||||
|
@ -906,9 +946,8 @@ module.exports = React.createClass({
|
|||
*/
|
||||
_onLoggedIn: function(teamToken) {
|
||||
this.setState({
|
||||
view: VIEWS.LOGGED_IN,
|
||||
guestCreds: null,
|
||||
loggedIn: true,
|
||||
loggingIn: false,
|
||||
});
|
||||
|
||||
if (teamToken) {
|
||||
|
@ -969,8 +1008,8 @@ module.exports = React.createClass({
|
|||
*/
|
||||
_onLoggedOut: function() {
|
||||
this.notifyNewScreen('login');
|
||||
this.setStateForNewScreen({
|
||||
loggedIn: false,
|
||||
this.setStateForNewView({
|
||||
view: VIEWS.LOGIN,
|
||||
ready: false,
|
||||
collapse_lhs: false,
|
||||
collapse_rhs: false,
|
||||
|
@ -1133,7 +1172,7 @@ module.exports = React.createClass({
|
|||
|
||||
// we can't view a room unless we're logged in
|
||||
// (a guest account is fine)
|
||||
if (this.state.loggedIn) {
|
||||
if (this.state.view === VIEWS.LOGGED_IN) {
|
||||
dis.dispatch(payload);
|
||||
}
|
||||
} else if (screen.indexOf('user/') == 0) {
|
||||
|
@ -1241,19 +1280,20 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
// returns a promise which resolves to the new MatrixClient
|
||||
onRegistered: function(credentials, teamToken) {
|
||||
// XXX: These both should be in state or ideally store(s) because we risk not
|
||||
// rendering the most up-to-date view of state otherwise.
|
||||
// teamToken may not be truthy
|
||||
this._teamToken = teamToken;
|
||||
this._is_registered = true;
|
||||
Lifecycle.setLoggedIn(credentials);
|
||||
return Lifecycle.setLoggedIn(credentials);
|
||||
},
|
||||
|
||||
onFinishPostRegistration: function() {
|
||||
// Don't confuse this with "PageType" which is the middle window to show
|
||||
this.setState({
|
||||
screen: undefined,
|
||||
view: VIEWS.LOGGED_IN,
|
||||
});
|
||||
this.showScreen("settings");
|
||||
},
|
||||
|
@ -1267,6 +1307,27 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
onSendEvent: function(roomId, event) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (!cli) {
|
||||
dis.dispatch({action: 'message_send_failed'});
|
||||
return;
|
||||
}
|
||||
|
||||
cli.sendEvent(roomId, event.getType(), event.getContent()).done(() => {
|
||||
dis.dispatch({action: 'message_sent'});
|
||||
}, (err) => {
|
||||
if (err.name === 'UnknownDeviceError') {
|
||||
dis.dispatch({
|
||||
action: 'unknown_device_error',
|
||||
err: err,
|
||||
room: cli.getRoom(roomId),
|
||||
});
|
||||
}
|
||||
dis.dispatch({action: 'message_send_failed'});
|
||||
});
|
||||
},
|
||||
|
||||
updateStatusIndicator: function(state, prevState) {
|
||||
let notifCount = 0;
|
||||
|
||||
|
@ -1321,11 +1382,9 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
render: function() {
|
||||
// `loading` might be set to false before `loggedIn = true`, causing the default
|
||||
// (`<Login>`) to be visible for a few MS (say, whilst a request is in-flight to
|
||||
// the RTS). So in the meantime, use `loggingIn`, which is true between
|
||||
// actions `on_logging_in` and `on_logged_in`.
|
||||
if (this.state.loading || this.state.loggingIn) {
|
||||
// console.log(`Rendering MatrixChat with view ${this.state.view}`);
|
||||
|
||||
if (this.state.view === VIEWS.LOADING || this.state.view === VIEWS.LOGGING_IN) {
|
||||
const Spinner = sdk.getComponent('elements.Spinner');
|
||||
return (
|
||||
<div className="mx_MatrixChat_splash">
|
||||
|
@ -1335,7 +1394,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
// needs to be before normal PageTypes as you are logged in technically
|
||||
if (this.state.screen == 'post_registration') {
|
||||
if (this.state.view === VIEWS.POST_REGISTRATION) {
|
||||
const PostRegistration = sdk.getComponent('structures.login.PostRegistration');
|
||||
return (
|
||||
<PostRegistration
|
||||
|
@ -1343,38 +1402,42 @@ module.exports = React.createClass({
|
|||
);
|
||||
}
|
||||
|
||||
// `ready` and `loggedIn` may be set before `page_type` (because the
|
||||
// latter is set via the dispatcher). If we don't yet have a `page_type`,
|
||||
// keep showing the spinner for now.
|
||||
if (this.state.loggedIn && this.state.ready && this.state.page_type) {
|
||||
/* for now, we stuff the entirety of our props and state into the LoggedInView.
|
||||
* we should go through and figure out what we actually need to pass down, as well
|
||||
* as using something like redux to avoid having a billion bits of state kicking around.
|
||||
*/
|
||||
const LoggedInView = sdk.getComponent('structures.LoggedInView');
|
||||
return (
|
||||
<LoggedInView ref="loggedInView" matrixClient={MatrixClientPeg.get()}
|
||||
onRoomCreated={this.onRoomCreated}
|
||||
onUserSettingsClose={this.onUserSettingsClose}
|
||||
onRegistered={this.onRegistered}
|
||||
currentRoomId={this.state.currentRoomId}
|
||||
teamToken={this._teamToken}
|
||||
{...this.props}
|
||||
{...this.state}
|
||||
/>
|
||||
);
|
||||
} else if (this.state.loggedIn) {
|
||||
// we think we are logged in, but are still waiting for the /sync to complete
|
||||
const Spinner = sdk.getComponent('elements.Spinner');
|
||||
return (
|
||||
<div className="mx_MatrixChat_splash">
|
||||
<Spinner />
|
||||
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>
|
||||
{ _t('Logout') }
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.screen == 'register') {
|
||||
if (this.state.view === VIEWS.LOGGED_IN) {
|
||||
// `ready` and `view==LOGGED_IN` may be set before `page_type` (because the
|
||||
// latter is set via the dispatcher). If we don't yet have a `page_type`,
|
||||
// keep showing the spinner for now.
|
||||
if (this.state.ready && this.state.page_type) {
|
||||
/* for now, we stuff the entirety of our props and state into the LoggedInView.
|
||||
* we should go through and figure out what we actually need to pass down, as well
|
||||
* as using something like redux to avoid having a billion bits of state kicking around.
|
||||
*/
|
||||
const LoggedInView = sdk.getComponent('structures.LoggedInView');
|
||||
return (
|
||||
<LoggedInView ref="loggedInView" matrixClient={MatrixClientPeg.get()}
|
||||
onRoomCreated={this.onRoomCreated}
|
||||
onUserSettingsClose={this.onUserSettingsClose}
|
||||
onRegistered={this.onRegistered}
|
||||
currentRoomId={this.state.currentRoomId}
|
||||
teamToken={this._teamToken}
|
||||
{...this.props}
|
||||
{...this.state}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// we think we are logged in, but are still waiting for the /sync to complete
|
||||
const Spinner = sdk.getComponent('elements.Spinner');
|
||||
return (
|
||||
<div className="mx_MatrixChat_splash">
|
||||
<Spinner />
|
||||
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>
|
||||
{ _t('Logout') }
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.view === VIEWS.REGISTER) {
|
||||
const Registration = sdk.getComponent('structures.login.Registration');
|
||||
return (
|
||||
<Registration
|
||||
|
@ -1397,7 +1460,10 @@ module.exports = React.createClass({
|
|||
onCancelClick={this.state.guestCreds ? this.onReturnToGuestClick : null}
|
||||
/>
|
||||
);
|
||||
} else if (this.state.screen == 'forgot_password') {
|
||||
}
|
||||
|
||||
|
||||
if (this.state.view === VIEWS.FORGOT_PASSWORD) {
|
||||
const ForgotPassword = sdk.getComponent('structures.login.ForgotPassword');
|
||||
return (
|
||||
<ForgotPassword
|
||||
|
@ -1409,7 +1475,9 @@ module.exports = React.createClass({
|
|||
onRegisterClick={this.onRegisterClick}
|
||||
onLoginClick={this.onLoginClick} />
|
||||
);
|
||||
} else {
|
||||
}
|
||||
|
||||
if (this.state.view === VIEWS.LOGIN) {
|
||||
const Login = sdk.getComponent('structures.login.Login');
|
||||
return (
|
||||
<Login
|
||||
|
@ -1427,5 +1495,7 @@ module.exports = React.createClass({
|
|||
/>
|
||||
);
|
||||
}
|
||||
|
||||
console.error(`Unknown view ${this.state.view}`);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -169,6 +169,7 @@ module.exports = React.createClass({
|
|||
initialEventId: RoomViewStore.getInitialEventId(),
|
||||
initialEventPixelOffset: RoomViewStore.getInitialEventPixelOffset(),
|
||||
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
|
||||
forwardingEvent: RoomViewStore.getForwardingEvent(),
|
||||
shouldPeek: RoomViewStore.shouldPeek(),
|
||||
};
|
||||
|
||||
|
@ -457,11 +458,6 @@ module.exports = React.createClass({
|
|||
callState: callState
|
||||
});
|
||||
|
||||
break;
|
||||
case 'forward_event':
|
||||
this.setState({
|
||||
forwardingEvent: payload.content,
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
@ -1169,8 +1165,13 @@ module.exports = React.createClass({
|
|||
this.updateTint();
|
||||
this.setState({
|
||||
editingRoomSettings: false,
|
||||
forwardingEvent: null,
|
||||
});
|
||||
if (this.state.forwardingEvent) {
|
||||
dis.dispatch({
|
||||
action: 'forward_event',
|
||||
event: null,
|
||||
});
|
||||
}
|
||||
dis.dispatch({action: 'focus_composer'});
|
||||
},
|
||||
|
||||
|
@ -1581,7 +1582,7 @@ module.exports = React.createClass({
|
|||
} else if (this.state.uploadingRoomSettings) {
|
||||
aux = <Loader/>;
|
||||
} else if (this.state.forwardingEvent !== null) {
|
||||
aux = <ForwardMessage onCancelClick={this.onCancelClick} currentRoomId={this.state.room.roomId} mxEvent={this.state.forwardingEvent} />;
|
||||
aux = <ForwardMessage onCancelClick={this.onCancelClick} />;
|
||||
} else if (this.state.searching) {
|
||||
hideCancel = true; // has own cancel
|
||||
aux = <SearchBar ref="search_bar" searchInProgress={this.state.searchInProgress } onCancelClick={this.onCancelSearchClick} onSearch={this.onSearch}/>;
|
||||
|
|
|
@ -160,6 +160,10 @@ module.exports = React.createClass({
|
|||
this.checkFillState();
|
||||
},
|
||||
|
||||
componentWillUpdate: function(nextProps, nextState) {
|
||||
this._saveScrollState();
|
||||
},
|
||||
|
||||
componentDidUpdate: function() {
|
||||
// after adding event tiles, we may need to tweak the scroll (either to
|
||||
// keep at the bottom of the timeline, or to maintain the view after
|
||||
|
|
|
@ -218,29 +218,29 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
trackPromise.then((teamToken) => {
|
||||
this.props.onLoggedIn({
|
||||
return this.props.onLoggedIn({
|
||||
userId: response.user_id,
|
||||
deviceId: response.device_id,
|
||||
homeserverUrl: this._matrixClient.getHomeserverUrl(),
|
||||
identityServerUrl: this._matrixClient.getIdentityServerUrl(),
|
||||
accessToken: response.access_token
|
||||
}, teamToken);
|
||||
}).then(() => {
|
||||
return this._setupPushers();
|
||||
}).then((cli) => {
|
||||
return this._setupPushers(cli);
|
||||
});
|
||||
},
|
||||
|
||||
_setupPushers: function() {
|
||||
_setupPushers: function(matrixClient) {
|
||||
if (!this.props.brand) {
|
||||
return q();
|
||||
}
|
||||
return MatrixClientPeg.get().getPushers().then((resp)=>{
|
||||
return matrixClient.getPushers().then((resp)=>{
|
||||
const pushers = resp.pushers;
|
||||
for (let i = 0; i < pushers.length; ++i) {
|
||||
if (pushers[i].kind == 'email') {
|
||||
const emailPusher = pushers[i];
|
||||
emailPusher.data = { brand: this.props.brand };
|
||||
MatrixClientPeg.get().setPusher(emailPusher).done(() => {
|
||||
matrixClient.setPusher(emailPusher).done(() => {
|
||||
console.log("Set email branding to " + this.props.brand);
|
||||
}, (error) => {
|
||||
console.error("Couldn't set email branding: " + error);
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||
import dis from '../../../dispatcher';
|
||||
import KeyCode from '../../../KeyCode';
|
||||
|
||||
|
@ -26,11 +25,6 @@ module.exports = React.createClass({
|
|||
displayName: 'ForwardMessage',
|
||||
|
||||
propTypes: {
|
||||
currentRoomId: React.PropTypes.string.isRequired,
|
||||
|
||||
/* the MatrixEvent to be forwarded */
|
||||
mxEvent: React.PropTypes.object.isRequired,
|
||||
|
||||
onCancelClick: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
|
@ -44,7 +38,6 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
document.addEventListener('keydown', this._onKeyDown);
|
||||
},
|
||||
|
||||
|
@ -54,30 +47,9 @@ module.exports = React.createClass({
|
|||
sideOpacity: 1.0,
|
||||
middleOpacity: 1.0,
|
||||
});
|
||||
dis.unregister(this.dispatcherRef);
|
||||
document.removeEventListener('keydown', this._onKeyDown);
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
if (payload.action === 'view_room') {
|
||||
const event = this.props.mxEvent;
|
||||
const Client = MatrixClientPeg.get();
|
||||
Client.sendEvent(payload.room_id, event.getType(), event.getContent()).done(() => {
|
||||
dis.dispatch({action: 'message_sent'});
|
||||
}, (err) => {
|
||||
if (err.name === "UnknownDeviceError") {
|
||||
dis.dispatch({
|
||||
action: 'unknown_device_error',
|
||||
err: err,
|
||||
room: Client.getRoom(payload.room_id),
|
||||
});
|
||||
}
|
||||
dis.dispatch({action: 'message_send_failed'});
|
||||
});
|
||||
if (this.props.currentRoomId === payload.room_id) this.props.onCancelClick();
|
||||
}
|
||||
},
|
||||
|
||||
_onKeyDown: function(ev) {
|
||||
switch (ev.keyCode) {
|
||||
case KeyCode.ESCAPE:
|
||||
|
|
|
@ -839,13 +839,13 @@
|
|||
"Share message history with new users": "Διαμοιρασμός ιστορικού μηνυμάτων με τους νέους χρήστες",
|
||||
"numbullet": "απαρίθμηση",
|
||||
"%(severalUsers)sleft and rejoined %(repeats)s times": "%(severalUsers)s έφυγαν και ξανασυνδέθηκαν %(repeats)s φορές",
|
||||
"%(oneUser)sleft and rejoined %(repeats)s times": "%(severalUsers)s έφυγε και ξανασυνδέθηκε %(repeats)s φορές",
|
||||
"%(oneUser)sleft and rejoined %(repeats)s times": "%(oneUser)s έφυγε και ξανασυνδέθηκε %(repeats)s φορές",
|
||||
"%(severalUsers)sleft and rejoined": "%(severalUsers)s έφυγαν και ξανασυνδέθηκαν",
|
||||
"%(oneUser)sleft and rejoined": "%(severalUsers)s έφυγε και ξανασυνδέθηκε",
|
||||
"%(oneUser)sleft and rejoined": "%(oneUser)s έφυγε και ξανασυνδέθηκε",
|
||||
"%(severalUsers)shad their invitations withdrawn %(repeats)s times": "Οι %(severalUsers)s απέσυραν τις προσκλήσεις τους %(repeats)s φορές",
|
||||
"%(oneUser)shad their invitation withdrawn %(repeats)s times": "Ο %(severalUsers)s απέσυρε την πρόσκληση του %(repeats)s φορές",
|
||||
"%(oneUser)shad their invitation withdrawn %(repeats)s times": "Ο %(oneUser)s απέσυρε την πρόσκληση του %(repeats)s φορές",
|
||||
"%(severalUsers)shad their invitations withdrawn": "Οι %(severalUsers)s απέσυραν τις προσκλήσεις τους",
|
||||
"%(oneUser)shad their invitation withdrawn": "Ο %(severalUsers)s απέσυρε την πρόσκληση του",
|
||||
"%(oneUser)shad their invitation withdrawn": "Ο %(oneUser)s απέσυρε την πρόσκληση του",
|
||||
"You must join the room to see its files": "Πρέπει να συνδεθείτε στο δωμάτιο για να δείτε τα αρχεία του",
|
||||
"Reject all %(invitedRooms)s invites": "Απόρριψη όλων των προσκλήσεων %(invitedRooms)s",
|
||||
"Failed to invite the following users to the %(roomName)s room:": "Δεν ήταν δυνατή η πρόσκληση των χρηστών στο δωμάτιο %(roomName)s:",
|
||||
|
|
|
@ -204,7 +204,7 @@
|
|||
"Anyone who knows the room's link, apart from guests": "A vendégeken kívül bárki aki ismeri a szoba link-jét",
|
||||
"Anyone who knows the room's link, including guests": "Bárki aki tudja a szoba link-jét, még a vendégek is",
|
||||
"Are you sure?": "Biztos?",
|
||||
"Are you sure you want to leave the room '%(roomName)s'?": "Biztos elhagyod a szobát?",
|
||||
"Are you sure you want to leave the room '%(roomName)s'?": "Biztos elhagyod a szobát '%(roomName)s'?",
|
||||
"Are you sure you want to reject the invitation?": "Biztos elutasítod a meghívást?",
|
||||
"Are you sure you want to upload the following files?": "Biztos feltöltöd ezeket a fájlokat?",
|
||||
"Attachment": "Csatolmány",
|
||||
|
|
|
@ -333,7 +333,7 @@
|
|||
"Create an account": "Open een account",
|
||||
"Cryptography": "Cryptografie",
|
||||
"Current password": "Huidig wachtwoord",
|
||||
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName) heeft de naam van de kamer verwijderd.",
|
||||
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s heeft de naam van de kamer verwijderd.",
|
||||
"Create a new chat or reuse an existing one": "Maak een nieuwe chat aan of gebruik een reeds bestaande",
|
||||
"Create Room": "Maak een kamer",
|
||||
"Curve25519 identity key": "Curve25519 identiteitssleutel",
|
||||
|
|
|
@ -246,7 +246,7 @@
|
|||
"Failed to set up conference call": "Не удалось установить конференц-вызов",
|
||||
"Failed to verify email address: make sure you clicked the link in the email": "Не удалось подтвердить email-адрес: убедитесь что вы щелкнули по ссылке электронной почты",
|
||||
"Failure to create room": "Не удалось создать комнату",
|
||||
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId) изменил %(fromPowerLevel) на %(toPowerLevel)",
|
||||
"%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s изменил %(fromPowerLevel)s на %(toPowerLevel)s",
|
||||
"Guest users can't create new rooms. Please register to create room and start a chat.": "Гостевые пользователи не могут создавать новые комнаты. Зарегистрируйтесь для создания комнаты и чата.",
|
||||
"click to reveal": "нажать для открытия",
|
||||
"%(senderName)s invited %(targetName)s.": "%(senderName)s приглашает %(targetName)s.",
|
||||
|
@ -355,7 +355,7 @@
|
|||
"Friday": "Пятница",
|
||||
"Saturday": "Суббота",
|
||||
"Sunday": "Воскресенье",
|
||||
"%(weekDayName)s %(time)s": "%(weekDayName) %(time)",
|
||||
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
|
||||
"Upload an avatar:": "Загрузите аватар:",
|
||||
"You need to be logged in.": "Вы должны быть авторизованы.",
|
||||
"You need to be able to invite users to do that.": "Вам необходимо пригласить пользователей чтобы сделать это.",
|
||||
|
@ -524,7 +524,7 @@
|
|||
"OK": "ОК",
|
||||
"Only people who have been invited": "Только приглашённые люди",
|
||||
"Passwords can't be empty": "Поля паролей не могут быть пустыми",
|
||||
"%(senderName)s placed a %(callType)s call.": "%(senderName) выполнил %(callType) вызов.",
|
||||
"%(senderName)s placed a %(callType)s call.": "%(senderName)s выполнил %(callType)s вызов.",
|
||||
"Please check your email and click on the link it contains. Once this is done, click continue.": "Пожалуйста, проверьте вашу электронную почту и нажмите в ней ссылку. По завершении нажмите продолжить.",
|
||||
"Power level must be positive integer.": "Уровень силы должен быть положительным числом.",
|
||||
"Press": "Нажать",
|
||||
|
@ -946,8 +946,8 @@
|
|||
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Хотели бы вы <acceptText>подтвердить</acceptText> это приглашение или <declineText>отклонить</declineText>?",
|
||||
"(~%(count)s results).one": "(~%(count)s Результат)",
|
||||
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Не удается подключиться к домашнему серверу - проверьте подключение, убедитесь, что ваш сертификат SSL <a>homeserver's SSL certificate</a> действителен, и расширение браузера не блокирует запросы.",
|
||||
"You have been banned from %(roomName)s by %(userName)s.": "%(userName) забанил Вас в % (roomName).",
|
||||
"You have been kicked from %(roomName)s by %(userName)s.": "%(userName) выгнал Вас из %(roomName).",
|
||||
"You have been banned from %(roomName)s by %(userName)s.": "%(userName)s забанил Вас в %(roomName)s.",
|
||||
"You have been kicked from %(roomName)s by %(userName)s.": "%(userName)s выгнал Вас из %(roomName)s.",
|
||||
"You may wish to login with a different account, or add this email to this account.": "Вы можете войти в систему с другой учетной записью или добавить этот адрес email в эту учетную запись.",
|
||||
"Your home server does not support device management.": "Ваш домашний сервер не поддерживает управление устройствами.",
|
||||
"(could not connect media)": "(не удается подключиться к медиа)",
|
||||
|
|
|
@ -296,7 +296,7 @@
|
|||
"Active call (%(roomName)s)": "Aktiv samtal (%(roomName)s)",
|
||||
"Add": "Lägg till",
|
||||
"Admin tools": "Admin verktyg",
|
||||
"And %(count)s more...": "Och %(count) till...",
|
||||
"And %(count)s more...": "Och %(count)s till...",
|
||||
"Alias (optional)": "Alias (valfri)",
|
||||
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Det gick inte att ansluta till servern - kontrollera anslutningen, försäkra att din <a>hemservers TLS-certifikat</a> är betrott, och att inget webbläsartillägg blockerar förfrågningar.",
|
||||
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s ändrade maktnivån av %(powerLevelDiffText)s.",
|
||||
|
|
|
@ -55,6 +55,8 @@ const INITIAL_STATE = {
|
|||
// pixelOffset: the number of pixels the window is scrolled down
|
||||
// from the focussedEvent.
|
||||
scrollStateMap: {},
|
||||
|
||||
forwardingEvent: null,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -116,6 +118,11 @@ class RoomViewStore extends Store {
|
|||
case 'update_scroll_state':
|
||||
this._updateScrollState(payload);
|
||||
break;
|
||||
case 'forward_event':
|
||||
this._setState({
|
||||
forwardingEvent: payload.event,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,6 +134,7 @@ class RoomViewStore extends Store {
|
|||
initialEventId: payload.event_id,
|
||||
initialEventPixelOffset: undefined,
|
||||
isInitialEventHighlighted: payload.highlighted,
|
||||
forwardingEvent: null,
|
||||
roomLoading: false,
|
||||
roomLoadError: null,
|
||||
// should peek by default
|
||||
|
@ -143,6 +151,14 @@ class RoomViewStore extends Store {
|
|||
}
|
||||
}
|
||||
|
||||
if (this._state.forwardingEvent) {
|
||||
dis.dispatch({
|
||||
action: 'send_event',
|
||||
room_id: newState.roomId,
|
||||
event: this._state.forwardingEvent,
|
||||
});
|
||||
}
|
||||
|
||||
this._setState(newState);
|
||||
} else if (payload.room_alias) {
|
||||
// Resolve the alias and then do a second dispatch with the room ID acquired
|
||||
|
@ -279,6 +295,11 @@ class RoomViewStore extends Store {
|
|||
return this._state.joinError;
|
||||
}
|
||||
|
||||
// The mxEvent if one is about to be forwarded
|
||||
getForwardingEvent() {
|
||||
return this._state.forwardingEvent;
|
||||
}
|
||||
|
||||
shouldPeek() {
|
||||
return this._state.shouldPeek;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue