Merge branch 'develop' into luke/feature-user-settings-password-warning

This commit is contained in:
Luke Barnard 2017-06-20 13:40:34 +01:00 committed by GitHub
commit 4f9c1fe199
17 changed files with 663 additions and 87 deletions

View file

@ -41,6 +41,7 @@ import PageTypes from '../../PageTypes';
import createRoom from "../../createRoom";
import * as UDEHandler from '../../UnknownDeviceErrorHandler';
import KeyRequestHandler from '../../KeyRequestHandler';
import { _t, getCurrentLanguage } from '../../languageHandler';
/** constants for MatrixChat.state.view */
@ -133,11 +134,6 @@ module.exports = React.createClass({
// a thing to call showScreen with once login completes.
screenAfterLogin: this.props.initialScreenAfterLogin,
// Stashed guest credentials if the user logs out
// whilst logged in as a guest user (so they can change
// their mind & log back in)
guestCreds: null,
// What the LoggedInView would be showing if visible
page_type: null,
@ -385,13 +381,6 @@ module.exports = React.createClass({
this._startRegistration(payload.params || {});
break;
case 'start_login':
if (MatrixClientPeg.get() &&
MatrixClientPeg.get().isGuest()
) {
this.setState({
guestCreds: MatrixClientPeg.getCredentials(),
});
}
this.setStateForNewView({
view: VIEWS.LOGIN,
});
@ -545,12 +534,10 @@ module.exports = React.createClass({
break;
case 'on_logging_in':
// We are now logging in, so set the state to reflect that
// and also that we're not ready (we'll be marked as logged
// in once the login completes, then ready once the sync
// completes).
// NB. This does not touch 'ready' since if our dispatches
// are delayed, the sync could already have completed
this.setStateForNewView({
view: VIEWS.LOGGING_IN,
ready: false,
});
break;
case 'on_logged_in':
@ -947,7 +934,6 @@ module.exports = React.createClass({
_onLoggedIn: function(teamToken) {
this.setState({
view: VIEWS.LOGGED_IN,
guestCreds: null,
});
if (teamToken) {
@ -1025,6 +1011,10 @@ module.exports = React.createClass({
*/
_onWillStartClient() {
const self = this;
// if the client is about to start, we are, by definition, not ready.
// Set ready to false now, then it'll be set to true when the sync
// listener we set below fires.
this.setState({ready: false});
const cli = MatrixClientPeg.get();
// Allow the JS SDK to reap timeline events. This reduces the amount of
@ -1095,6 +1085,14 @@ module.exports = React.createClass({
}
}
});
const krh = new KeyRequestHandler(cli);
cli.on("crypto.roomKeyRequest", (req) => {
krh.handleKeyRequest(req);
});
cli.on("crypto.roomKeyRequestCancellation", (req) => {
krh.handleKeyRequestCancellation(req);
});
},
showScreen: function(screen, params) {
@ -1270,14 +1268,9 @@ module.exports = React.createClass({
this.showScreen("forgot_password");
},
onReturnToGuestClick: function() {
// reanimate our guest login
if (this.state.guestCreds) {
// TODO: this is probably a bit broken - we don't want to be
// clearing storage when we reanimate the guest creds.
Lifecycle.setLoggedIn(this.state.guestCreds);
this.setState({guestCreds: null});
}
onReturnToAppClick: function() {
// treat it the same as if the user had completed the login
this._onLoggedIn(null);
},
// returns a promise which resolves to the new MatrixClient
@ -1457,7 +1450,7 @@ module.exports = React.createClass({
onLoggedIn={this.onRegistered}
onLoginClick={this.onLoginClick}
onRegisterClick={this.onRegisterClick}
onCancelClick={this.state.guestCreds ? this.onReturnToGuestClick : null}
onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null}
/>
);
}
@ -1491,7 +1484,7 @@ module.exports = React.createClass({
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
onForgotPasswordClick={this.onForgotPasswordClick}
enableGuest={this.props.enableGuest}
onCancelClick={this.state.guestCreds ? this.onReturnToGuestClick : null}
onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null}
/>
);
}

View file

@ -0,0 +1,172 @@
/*
Copyright 2017 Vector Creations Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Modal from '../../../Modal';
import React from 'react';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
/**
* Dialog which asks the user whether they want to share their keys with
* an unverified device.
*
* onFinished is called with `true` if the key should be shared, `false` if it
* should not, and `undefined` if the dialog is cancelled. (In other words:
* truthy: do the key share. falsy: don't share the keys).
*/
export default React.createClass({
propTypes: {
matrixClient: React.PropTypes.object.isRequired,
userId: React.PropTypes.string.isRequired,
deviceId: React.PropTypes.string.isRequired,
onFinished: React.PropTypes.func.isRequired,
},
getInitialState: function() {
return {
deviceInfo: null,
wasNewDevice: false,
};
},
componentDidMount: function() {
this._unmounted = false;
const userId = this.props.userId;
const deviceId = this.props.deviceId;
// give the client a chance to refresh the device list
this.props.matrixClient.downloadKeys([userId], false).then((r) => {
if (this._unmounted) { return; }
const deviceInfo = r[userId][deviceId];
if(!deviceInfo) {
console.warn(`No details found for device ${userId}:${deviceId}`);
this.props.onFinished(false);
return;
}
const wasNewDevice = !deviceInfo.isKnown();
this.setState({
deviceInfo: deviceInfo,
wasNewDevice: wasNewDevice,
});
// if the device was new before, it's not any more.
if (wasNewDevice) {
this.props.matrixClient.setDeviceKnown(
userId,
deviceId,
true,
);
}
}).done();
},
componentWillUnmount: function() {
this._unmounted = true;
},
_onVerifyClicked: function() {
const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog');
console.log("KeyShareDialog: Starting verify dialog");
Modal.createDialog(DeviceVerifyDialog, {
userId: this.props.userId,
device: this.state.deviceInfo,
onFinished: (verified) => {
if (verified) {
// can automatically share the keys now.
this.props.onFinished(true);
}
},
});
},
_onShareClicked: function() {
console.log("KeyShareDialog: User clicked 'share'");
this.props.onFinished(true);
},
_onIgnoreClicked: function() {
console.log("KeyShareDialog: User clicked 'ignore'");
this.props.onFinished(false);
},
_renderContent: function() {
const displayName = this.state.deviceInfo.getDisplayName() ||
this.state.deviceInfo.deviceId;
let text;
if (this.state.wasNewDevice) {
text = "You added a new device '%(displayName)s', which is"
+ " requesting encryption keys.";
} else {
text = "Your unverified device '%(displayName)s' is requesting"
+ " encryption keys.";
}
text = _t(text, {displayName: displayName});
return (
<div>
<p>{text}</p>
<div className="mx_Dialog_buttons">
<button onClick={this._onVerifyClicked}>
{_t('Start verification')}
</button>
<button onClick={this._onShareClicked}>
{_t('Share without verifying')}
</button>
<button onClick={this._onIgnoreClicked}>
{_t('Ignore request')}
</button>
</div>
</div>
);
},
render: function() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const Spinner = sdk.getComponent('views.elements.Spinner');
let content;
if (this.state.deviceInfo) {
content = this._renderContent();
} else {
content = (
<div>
<p>{_t('Loading device info...')}</p>
<Spinner />
</div>
);
}
return (
<BaseDialog className='mx_KeyShareRequestDialog'
onFinished={this.props.onFinished}
title={_t('Encryption key request')}
>
{content}
</BaseDialog>
);
},
});

View file

@ -154,7 +154,7 @@ export default React.createClass({
/>
<input
type="submit"
value={_t("Cancel")}
value={_t("Skip")}
onClick={this.onCancelled}
/>
</div>

View file

@ -18,7 +18,7 @@ limitations under the License.
'use strict';
var React = require("react");
var ReactDOM = require("react-dom");
import { _t } from '../../../languageHandler';
import { _t, _tJsx } from '../../../languageHandler';
var GeminiScrollbar = require('react-gemini-scrollbar');
var MatrixClientPeg = require("../../../MatrixClientPeg");
var CallHandler = require('../../../CallHandler');
@ -33,11 +33,28 @@ var Receipt = require('../../../utils/Receipt');
const HIDE_CONFERENCE_CHANS = true;
const VERBS = {
'm.favourite': 'favourite',
'im.vector.fake.direct': 'tag direct chat',
'im.vector.fake.recent': 'restore',
'm.lowpriority': 'demote',
function phraseForSection(section) {
// These would probably be better as individual strings,
// but for some reason we have translations for these strings
// as-is, so keeping it like this for now.
let verb;
switch (section) {
case 'm.favourite':
verb = _t('to favourite');
break;
case 'im.vector.fake.direct':
verb = _t('to tag direct chat');
break;
case 'im.vector.fake.recent':
verb = _t('to restore');
break;
case 'm.lowpriority':
verb = _t('to demote');
break;
default:
return _t('Drop here to tag %(section)s', {section: section});
}
return _t('Drop here %(toAction)s', {toAction: verb});
};
module.exports = React.createClass({
@ -478,17 +495,25 @@ module.exports = React.createClass({
switch (section) {
case 'im.vector.fake.direct':
return <div className="mx_RoomList_emptySubListTip">
Press
<StartChatButton size="16" callout={true}/>
to start a chat with someone
{_tJsx(
"Press <StartChatButton> to start a chat with someone",
[/<StartChatButton>/],
[
(sub) => <StartChatButton size="16" callout={true}/>
]
)}
</div>;
case 'im.vector.fake.recent':
return <div className="mx_RoomList_emptySubListTip">
You're not in any rooms yet! Press
<CreateRoomButton size="16" callout={true}/>
to make a room or
<RoomDirectoryButton size="16" callout={true}/>
to browse the directory
{_tJsx(
"You're not in any rooms yet! Press <CreateRoomButton> to make a room or"+
" <RoomDirectoryButton> to browse the directory",
[/<CreateRoomButton>/, /<RoomDirectoryButton>/],
[
(sub) => <CreateRoomButton size="16" callout={true}/>,
(sub) => <RoomDirectoryButton size="16" callout={true}/>
]
)}
</div>;
}
@ -497,7 +522,7 @@ module.exports = React.createClass({
return null;
}
const labelText = 'Drop here to ' + (VERBS[section] || 'tag ' + section);
const labelText = phraseForSection(section);
return <RoomDropTarget label={labelText} />;
},