Merge branches 'develop' and 't3chguy/app_load4' of github.com:matrix-org/matrix-react-sdk into t3chguy/app_load4
Conflicts: src/components/structures/MatrixChat.tsx
This commit is contained in:
commit
9c916cc544
90 changed files with 2032 additions and 445 deletions
|
@ -1215,6 +1215,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
*/
|
||||
private async onLoggedIn() {
|
||||
ThemeController.isLogin = false;
|
||||
this.themeWatcher.recheck();
|
||||
this.setStateForNewView({ view: Views.LOGGED_IN });
|
||||
// If a specific screen is set to be shown after login, show that above
|
||||
// all else, as it probably means the user clicked on something already.
|
||||
|
|
|
@ -221,10 +221,27 @@ export default class RightPanel extends React.Component {
|
|||
case RIGHT_PANEL_PHASES.EncryptionPanel:
|
||||
if (SettingsStore.getValue("feature_cross_signing")) {
|
||||
const onClose = () => {
|
||||
dis.dispatch({
|
||||
action: "view_user",
|
||||
member: this.state.phase === RIGHT_PANEL_PHASES.EncryptionPanel ? this.state.member : null,
|
||||
});
|
||||
// XXX: There are three different ways of 'closing' this panel depending on what state
|
||||
// things are in... this knows far more than it should do about the state of the rest
|
||||
// of the app and is generally a bit silly.
|
||||
if (this.props.user) {
|
||||
// If we have a user prop then we're displaying a user from the 'user' page type
|
||||
// in LoggedInView, so need to change the page type to close the panel (we switch
|
||||
// to the home page which is not obviosuly the correct thing to do, but I'm not sure
|
||||
// anything else is - we could hide the close button altogether?)
|
||||
dis.dispatch({
|
||||
action: "view_home_page",
|
||||
});
|
||||
} else {
|
||||
// Otherwise we have got our user from RoomViewStore which means we're being shown
|
||||
// within a room, so go back to the member panel if we were in the encryption panel,
|
||||
// or the member list if we were in the member panel... phew.
|
||||
dis.dispatch({
|
||||
action: "view_user",
|
||||
member: this.state.phase === RIGHT_PANEL_PHASES.EncryptionPanel ?
|
||||
this.state.member : null,
|
||||
});
|
||||
}
|
||||
};
|
||||
panel = <UserInfo
|
||||
user={this.state.member}
|
||||
|
|
|
@ -32,6 +32,7 @@ import RoomTile from "../views/rooms/RoomTile";
|
|||
import LazyRenderList from "../views/elements/LazyRenderList";
|
||||
import {_t} from "../../languageHandler";
|
||||
import {RovingTabIndexWrapper} from "../../accessibility/RovingTabIndex";
|
||||
import toRem from "../../utils/rem";
|
||||
|
||||
// turn this on for drop & drag console debugging galore
|
||||
const debug = false;
|
||||
|
@ -383,7 +384,7 @@ export default class RoomSubList extends React.PureComponent {
|
|||
|
||||
setHeight = (height) => {
|
||||
if (this._subList.current) {
|
||||
this._subList.current.style.height = `${height}px`;
|
||||
this._subList.current.style.height = toRem(height);
|
||||
}
|
||||
this._updateLazyRenderHeight(height);
|
||||
};
|
||||
|
|
|
@ -1768,7 +1768,7 @@ export default createReactClass({
|
|||
const showRoomRecoveryReminder = (
|
||||
SettingsStore.getValue("showRoomRecoveryReminder") &&
|
||||
this.context.isRoomEncrypted(this.state.room.roomId) &&
|
||||
!this.context.getKeyBackupEnabled()
|
||||
this.context.getKeyBackupEnabled() === false
|
||||
);
|
||||
|
||||
const hiddenHighlightCount = this._getHiddenHighlightCount();
|
||||
|
|
|
@ -131,7 +131,7 @@ export default class SetupEncryptionBody extends React.Component {
|
|||
</div>
|
||||
|
||||
<div className="mx_CompleteSecurity_actionRow">
|
||||
<AccessibleButton kind="link" onClick={this.onSkipClick}>
|
||||
<AccessibleButton kind="link" onClick={this._onUsePassphraseClick}>
|
||||
{_t("Use Recovery Passphrase or Key")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton kind="danger" onClick={this.onSkipClick}>
|
||||
|
|
|
@ -24,6 +24,7 @@ import * as AvatarLogic from '../../../Avatar';
|
|||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import toRem from "../../../utils/rem";
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'BaseAvatar',
|
||||
|
@ -164,9 +165,11 @@ export default createReactClass({
|
|||
const initialLetter = AvatarLogic.getInitialLetter(name);
|
||||
const textNode = (
|
||||
<span className="mx_BaseAvatar_initial" aria-hidden="true"
|
||||
style={{ fontSize: (width * 0.65) + "px",
|
||||
width: width + "px",
|
||||
lineHeight: height + "px" }}
|
||||
style={{
|
||||
fontSize: toRem(width * 0.65),
|
||||
width: toRem(width),
|
||||
lineHeight: toRem(height),
|
||||
}}
|
||||
>
|
||||
{ initialLetter }
|
||||
</span>
|
||||
|
@ -174,7 +177,11 @@ export default createReactClass({
|
|||
const imgNode = (
|
||||
<img className="mx_BaseAvatar_image" src={imageUrl}
|
||||
alt="" title={title} onError={this.onError}
|
||||
width={width} height={height} aria-hidden="true" />
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
width: toRem(width),
|
||||
height: toRem(height)
|
||||
}} />
|
||||
);
|
||||
if (onClick != null) {
|
||||
return (
|
||||
|
@ -202,7 +209,10 @@ export default createReactClass({
|
|||
src={imageUrl}
|
||||
onClick={onClick}
|
||||
onError={this.onError}
|
||||
width={width} height={height}
|
||||
style={{
|
||||
width: toRem(width),
|
||||
height: toRem(height),
|
||||
}}
|
||||
title={title} alt=""
|
||||
inputRef={inputRef}
|
||||
{...otherProps} />
|
||||
|
@ -213,7 +223,10 @@ export default createReactClass({
|
|||
className="mx_BaseAvatar mx_BaseAvatar_image"
|
||||
src={imageUrl}
|
||||
onError={this.onError}
|
||||
width={width} height={height}
|
||||
style={{
|
||||
width: toRem(width),
|
||||
height: toRem(height),
|
||||
}}
|
||||
title={title} alt=""
|
||||
ref={inputRef}
|
||||
{...otherProps} />
|
||||
|
|
|
@ -577,10 +577,13 @@ export default class InviteDialog extends React.PureComponent {
|
|||
if (SettingsStore.getValue("feature_cross_signing")) {
|
||||
// Check whether all users have uploaded device keys before.
|
||||
// If so, enable encryption in the new room.
|
||||
const client = MatrixClientPeg.get();
|
||||
const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds);
|
||||
if (allHaveDeviceKeys) {
|
||||
createRoomOptions.encryption = true;
|
||||
const has3PidMembers = targets.some(t => t instanceof ThreepidMember);
|
||||
if (!has3PidMembers) {
|
||||
const client = MatrixClientPeg.get();
|
||||
const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds);
|
||||
if (allHaveDeviceKeys) {
|
||||
createRoomOptions.encryption = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,16 +14,52 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import SetupEncryptionBody from '../../structures/auth/SetupEncryptionBody';
|
||||
import BaseDialog from './BaseDialog';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { SetupEncryptionStore, PHASE_DONE } from '../../../stores/SetupEncryptionStore';
|
||||
|
||||
export default function SetupEncryptionDialog({onFinished}) {
|
||||
return <BaseDialog
|
||||
headerImage={require("../../../../res/img/e2e/warning.svg")}
|
||||
onFinished={onFinished}
|
||||
title={_t("Verify this session")}
|
||||
>
|
||||
<SetupEncryptionBody onFinished={onFinished} />
|
||||
</BaseDialog>;
|
||||
function iconFromPhase(phase) {
|
||||
if (phase === PHASE_DONE) {
|
||||
return require("../../../../res/img/e2e/verified.svg");
|
||||
} else {
|
||||
return require("../../../../res/img/e2e/warning.svg");
|
||||
}
|
||||
}
|
||||
|
||||
export default class SetupEncryptionDialog extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.store = SetupEncryptionStore.sharedInstance();
|
||||
this.state = {icon: iconFromPhase(this.store.phase)};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.store.on("update", this._onStoreUpdate);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.store.removeListener("update", this._onStoreUpdate);
|
||||
}
|
||||
|
||||
_onStoreUpdate = () => {
|
||||
this.setState({icon: iconFromPhase(this.store.phase)});
|
||||
};
|
||||
|
||||
render() {
|
||||
return <BaseDialog
|
||||
headerImage={this.state.icon}
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t("Verify this session")}
|
||||
>
|
||||
<SetupEncryptionBody onFinished={this.props.onFinished} />
|
||||
</BaseDialog>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ export default class UserSettingsDialog extends React.Component {
|
|||
tabs.push(new Tab(
|
||||
_td("Security & Privacy"),
|
||||
"mx_UserSettingsDialog_securityIcon",
|
||||
<SecurityUserSettingsTab />,
|
||||
<SecurityUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
||||
));
|
||||
if (SdkConfig.get()['showLabsSettings'] || SettingsStore.getLabsFeatures().length > 0) {
|
||||
tabs.push(new Tab(
|
||||
|
|
|
@ -59,6 +59,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
|||
forceRecoveryKey: false,
|
||||
passPhrase: '',
|
||||
restoreType: null,
|
||||
progress: { stage: "prefetch" },
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -80,6 +81,12 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
_progressCallback = (data) => {
|
||||
this.setState({
|
||||
progress: data,
|
||||
});
|
||||
}
|
||||
|
||||
_onResetRecoveryClick = () => {
|
||||
this.props.onFinished(false);
|
||||
Modal.createTrackedDialogAsync('Key Backup', 'Key Backup',
|
||||
|
@ -110,6 +117,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
|||
// is the right one and restoring it is currently the only way we can do this.
|
||||
const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithPassword(
|
||||
this.state.passPhrase, undefined, undefined, this.state.backupInfo,
|
||||
{ progressCallback: this._progressCallback },
|
||||
);
|
||||
if (this.props.keyCallback) {
|
||||
const key = await MatrixClientPeg.get().keyBackupKeyFromPassword(
|
||||
|
@ -146,6 +154,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
|||
try {
|
||||
const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithRecoveryKey(
|
||||
this.state.recoveryKey, undefined, undefined, this.state.backupInfo,
|
||||
{ progressCallback: this._progressCallback },
|
||||
);
|
||||
if (this.props.keyCallback) {
|
||||
const key = MatrixClientPeg.get().keyBackupKeyFromRecoveryKey(this.state.recoveryKey);
|
||||
|
@ -185,6 +194,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
|||
const recoverInfo = await accessSecretStorage(async () => {
|
||||
return MatrixClientPeg.get().restoreKeyBackupWithSecretStorage(
|
||||
this.state.backupInfo,
|
||||
{ progressCallback: this._progressCallback },
|
||||
);
|
||||
});
|
||||
this.setState({
|
||||
|
@ -207,6 +217,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
|||
undefined, /* targetRoomId */
|
||||
undefined, /* targetSessionId */
|
||||
backupInfo,
|
||||
{ progressCallback: this._progressCallback },
|
||||
);
|
||||
this.setState({
|
||||
recoverInfo,
|
||||
|
@ -272,8 +283,20 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
|||
let content;
|
||||
let title;
|
||||
if (this.state.loading) {
|
||||
title = _t("Loading...");
|
||||
content = <Spinner />;
|
||||
title = _t("Restoring keys from backup");
|
||||
let details;
|
||||
if (this.state.progress.stage === "fetch") {
|
||||
details = _t("Fetching keys from server...");
|
||||
} else if (this.state.progress.stage === "load_keys") {
|
||||
const { total, successes, failures } = this.state.progress;
|
||||
details = _t("%(completed)s of %(total)s keys restored", { total, completed: successes + failures });
|
||||
} else if (this.state.progress.stage === "prefetch") {
|
||||
details = _t("Fetching keys from server...");
|
||||
}
|
||||
content = <div>
|
||||
<div>{details}</div>
|
||||
<Spinner />
|
||||
</div>;
|
||||
} else if (this.state.loadError) {
|
||||
title = _t("Error");
|
||||
content = _t("Unable to load backup status");
|
||||
|
@ -305,7 +328,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
|||
content = _t("No backup found!");
|
||||
} else if (this.state.recoverInfo) {
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
title = _t("Backup restored");
|
||||
title = _t("Keys restored");
|
||||
let failedToDecrypt;
|
||||
if (this.state.recoverInfo.total > this.state.recoverInfo.imported) {
|
||||
failedToDecrypt = <p>{_t(
|
||||
|
@ -314,7 +337,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
|||
)}</p>;
|
||||
}
|
||||
content = <div>
|
||||
<p>{_t("Restored %(sessionCount)s session keys", {sessionCount: this.state.recoverInfo.imported})}</p>
|
||||
<p>{_t("Successfully restored %(sessionCount)s keys", {sessionCount: this.state.recoverInfo.imported})}</p>
|
||||
{failedToDecrypt}
|
||||
<DialogButtons primaryButton={_t('OK')}
|
||||
onPrimaryButtonClick={this._onDone}
|
||||
|
@ -435,7 +458,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
|||
onFinished={this.props.onFinished}
|
||||
title={title}
|
||||
>
|
||||
<div>
|
||||
<div className='mx_RestoreKeyBackupDialog_content'>
|
||||
{content}
|
||||
</div>
|
||||
</BaseDialog>
|
||||
|
|
|
@ -21,6 +21,7 @@ import * as sdk from '../../../../index';
|
|||
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
||||
|
||||
import { _t } from '../../../../languageHandler';
|
||||
import { accessSecretStorage } from '../../../../CrossSigningManager';
|
||||
|
||||
/*
|
||||
* Access Secure Secret Storage by requesting the user's passphrase.
|
||||
|
@ -55,8 +56,9 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
|||
}
|
||||
|
||||
_onResetRecoveryClick = () => {
|
||||
// Re-enter the access flow, but resetting storage this time around.
|
||||
this.props.onFinished(false);
|
||||
throw new Error("Resetting secret storage unimplemented");
|
||||
accessSecretStorage(() => {}, /* forceReset = */ true);
|
||||
}
|
||||
|
||||
_onRecoveryKeyChange = (e) => {
|
||||
|
|
|
@ -188,13 +188,14 @@ export default class AppTile extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Generify the name of this function. It's not just scalar tokens.
|
||||
/**
|
||||
* Adds a scalar token to the widget URL, if required
|
||||
* Component initialisation is only complete when this function has resolved
|
||||
*/
|
||||
setScalarToken() {
|
||||
if (!WidgetUtils.isScalarUrl(this.props.app.url)) {
|
||||
console.warn('Non-scalar widget, not setting scalar token!', url);
|
||||
console.warn('Widget does not match integration manager, refusing to set auth token', url);
|
||||
this.setState({
|
||||
error: null,
|
||||
widgetUrl: this._addWurlParams(this.props.app.url),
|
||||
|
@ -218,7 +219,7 @@ export default class AppTile extends React.Component {
|
|||
|
||||
const defaultManager = managers.getPrimaryManager();
|
||||
if (!WidgetUtils.isScalarUrl(defaultManager.apiUrl)) {
|
||||
console.warn('Non-scalar manager, not setting scalar token!', url);
|
||||
console.warn('Unknown integration manager, refusing to set auth token', url);
|
||||
this.setState({
|
||||
error: null,
|
||||
widgetUrl: this._addWurlParams(this.props.app.url),
|
||||
|
@ -423,8 +424,13 @@ export default class AppTile extends React.Component {
|
|||
_setupWidgetMessaging() {
|
||||
// FIXME: There's probably no reason to do this here: it should probably be done entirely
|
||||
// in ActiveWidgetStore.
|
||||
|
||||
// We use the app's URL over the rendered URL so that anything the widget does which could
|
||||
// lead to requesting a "security key" will pass accordingly. The only other thing this URL
|
||||
// is used for is to determine the origin we're talking to, and therefore we don't need the
|
||||
// fully templated URL.
|
||||
const widgetMessaging = new WidgetMessaging(
|
||||
this.props.app.id, this._getRenderedUrl(), this.props.userWidget, this._appFrame.current.contentWindow);
|
||||
this.props.app.id, this.props.app.url, this.props.userWidget, this._appFrame.current.contentWindow);
|
||||
ActiveWidgetStore.setWidgetMessaging(this.props.app.id, widgetMessaging);
|
||||
widgetMessaging.getCapabilities().then((requestedCapabilities) => {
|
||||
console.log(`Widget ${this.props.app.id} requested capabilities: ` + requestedCapabilities);
|
||||
|
@ -560,15 +566,18 @@ export default class AppTile extends React.Component {
|
|||
* Replace the widget template variables in a url with their values
|
||||
*
|
||||
* @param {string} u The URL with template variables
|
||||
* @param {string} widgetType The widget's type
|
||||
*
|
||||
* @returns {string} url with temlate variables replaced
|
||||
*/
|
||||
_templatedUrl(u) {
|
||||
_templatedUrl(u, widgetType: string) {
|
||||
const targetData = {};
|
||||
if (WidgetType.JITSI.matches(widgetType)) {
|
||||
targetData['domain'] = 'jitsi.riot.im'; // v1 jitsi widgets have this hardcoded
|
||||
}
|
||||
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
const myUser = MatrixClientPeg.get().getUser(myUserId);
|
||||
const vars = Object.assign({
|
||||
domain: "jitsi.riot.im", // v1 widgets have this hardcoded
|
||||
}, this.props.app.data, {
|
||||
const vars = Object.assign(targetData, this.props.app.data, {
|
||||
'matrix_user_id': myUserId,
|
||||
'matrix_room_id': this.props.room.roomId,
|
||||
'matrix_display_name': myUser ? myUser.displayName : myUserId,
|
||||
|
@ -605,18 +614,19 @@ export default class AppTile extends React.Component {
|
|||
} else {
|
||||
url = this._getSafeUrl(this.state.widgetUrl);
|
||||
}
|
||||
return this._templatedUrl(url);
|
||||
return this._templatedUrl(url, this.props.app.type);
|
||||
}
|
||||
|
||||
_getPopoutUrl() {
|
||||
if (WidgetType.JITSI.matches(this.props.app.type)) {
|
||||
return this._templatedUrl(
|
||||
WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: false}),
|
||||
this.props.app.type,
|
||||
);
|
||||
} else {
|
||||
// use app.url, not state.widgetUrl, because we want the one without
|
||||
// the wURL params for the popped-out version.
|
||||
return this._templatedUrl(this._getSafeUrl(this.props.app.url));
|
||||
return this._templatedUrl(this._getSafeUrl(this.props.app.url), this.props.app.type);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -630,7 +640,10 @@ export default class AppTile extends React.Component {
|
|||
if (ALLOWED_APP_URL_SCHEMES.includes(parsedWidgetUrl.protocol)) {
|
||||
safeWidgetUrl = url.format(parsedWidgetUrl);
|
||||
}
|
||||
return safeWidgetUrl;
|
||||
|
||||
// Replace all the dollar signs back to dollar signs as they don't affect HTTP at all.
|
||||
// We also need the dollar signs in-tact for variable substitution.
|
||||
return safeWidgetUrl.replace(/%24/g, '$');
|
||||
}
|
||||
|
||||
_getTileTitle() {
|
||||
|
|
|
@ -22,7 +22,6 @@ import VerificationPanel from "./VerificationPanel";
|
|||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import {ensureDMExists} from "../../../createRoom";
|
||||
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
||||
import {useAsyncMemo} from "../../../hooks/useAsyncMemo";
|
||||
import Modal from "../../../Modal";
|
||||
import {PHASE_REQUESTED, PHASE_UNSENT} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||
import * as sdk from "../../../index";
|
||||
|
@ -47,10 +46,7 @@ const EncryptionPanel = (props) => {
|
|||
}, [verificationRequest]);
|
||||
|
||||
const deviceId = request && request.channel.deviceId;
|
||||
const device = useAsyncMemo(() => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
return cli.getStoredDevice(cli.getUserId(), deviceId);
|
||||
}, [deviceId]);
|
||||
const device = MatrixClientPeg.get().getStoredDevice(MatrixClientPeg.get().getUserId(), deviceId);
|
||||
|
||||
useEffect(() => {
|
||||
async function awaitPromise() {
|
||||
|
|
|
@ -181,9 +181,7 @@ function DeviceItem({userId, device}) {
|
|||
});
|
||||
|
||||
const onDeviceClick = () => {
|
||||
if (!isVerified) {
|
||||
verifyDevice(cli.getUser(userId), device);
|
||||
}
|
||||
verifyDevice(cli.getUser(userId), device);
|
||||
};
|
||||
|
||||
const deviceName = device.ambiguous ?
|
||||
|
@ -191,17 +189,29 @@ function DeviceItem({userId, device}) {
|
|||
device.getDisplayName();
|
||||
let trustedLabel = null;
|
||||
if (userTrust.isVerified()) trustedLabel = isVerified ? _t("Trusted") : _t("Not trusted");
|
||||
return (
|
||||
<AccessibleButton
|
||||
className={classes}
|
||||
title={device.deviceId}
|
||||
onClick={onDeviceClick}
|
||||
>
|
||||
<div className={iconClasses} />
|
||||
<div className="mx_UserInfo_device_name">{deviceName}</div>
|
||||
<div className="mx_UserInfo_device_trusted">{trustedLabel}</div>
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
||||
|
||||
if (isVerified) {
|
||||
return (
|
||||
<div className={classes} title={device.deviceId} >
|
||||
<div className={iconClasses} />
|
||||
<div className="mx_UserInfo_device_name">{deviceName}</div>
|
||||
<div className="mx_UserInfo_device_trusted">{trustedLabel}</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<AccessibleButton
|
||||
className={classes}
|
||||
title={device.deviceId}
|
||||
onClick={onDeviceClick}
|
||||
>
|
||||
<div className={iconClasses} />
|
||||
<div className="mx_UserInfo_device_name">{deviceName}</div>
|
||||
<div className="mx_UserInfo_device_trusted">{trustedLabel}</div>
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function DevicesSection({devices, userId, loading}) {
|
||||
|
@ -1100,7 +1110,7 @@ export const useDevices = (userId) => {
|
|||
async function _downloadDeviceList() {
|
||||
try {
|
||||
await cli.downloadKeys([userId], true);
|
||||
const devices = await cli.getStoredDevicesForUser(userId);
|
||||
const devices = cli.getStoredDevicesForUser(userId);
|
||||
|
||||
if (cancelled) {
|
||||
// we got cancelled - presumably a different user now
|
||||
|
@ -1125,7 +1135,7 @@ export const useDevices = (userId) => {
|
|||
useEffect(() => {
|
||||
let cancel = false;
|
||||
const updateDevices = async () => {
|
||||
const newDevices = await cli.getStoredDevicesForUser(userId);
|
||||
const newDevices = cli.getStoredDevicesForUser(userId);
|
||||
if (cancel) return;
|
||||
setDevices(newDevices);
|
||||
};
|
||||
|
|
|
@ -34,6 +34,7 @@ import {ALL_RULE_TYPES} from "../../../mjolnir/BanList";
|
|||
import * as ObjectUtils from "../../../ObjectUtils";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import {E2E_STATE} from "./E2EIcon";
|
||||
import toRem from "../../../utils/rem";
|
||||
|
||||
const eventTileTypes = {
|
||||
'm.room.message': 'messages.MessageEvent',
|
||||
|
@ -473,7 +474,7 @@ export default createReactClass({
|
|||
if (remainder > 0) {
|
||||
remText = <span className="mx_EventTile_readAvatarRemainder"
|
||||
onClick={this.toggleAllReadAvatars}
|
||||
style={{ right: -(left - receiptOffset) }}>{ remainder }+
|
||||
style={{ right: "calc(" + toRem(-left) + " + " + receiptOffset + "px)" }}>{ remainder }+
|
||||
</span>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import * as sdk from '../../../index';
|
||||
import SettingsStore from '../../../settings/SettingsStore';
|
||||
|
||||
export default class InviteOnlyIcon extends React.Component {
|
||||
constructor() {
|
||||
|
@ -39,10 +38,6 @@ export default class InviteOnlyIcon extends React.Component {
|
|||
render() {
|
||||
const classes = this.props.collapsedPanel ? "mx_InviteOnlyIcon_small": "mx_InviteOnlyIcon_large";
|
||||
|
||||
if (!SettingsStore.isFeatureEnabled("feature_invite_only_padlocks")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||
let tooltip;
|
||||
if (this.state.hover) {
|
||||
|
|
|
@ -160,13 +160,10 @@ export default createReactClass({
|
|||
// no need to re-download the whole thing; just update our copy of
|
||||
// the list.
|
||||
|
||||
// Promise.resolve to handle transition from static result to promise; can be removed
|
||||
// in future
|
||||
Promise.resolve(this.context.getStoredDevicesForUser(userId)).then((devices) => {
|
||||
this.setState({
|
||||
devices: devices,
|
||||
e2eStatus: this._getE2EStatus(devices),
|
||||
});
|
||||
const devices = this.context.getStoredDevicesForUser(userId);
|
||||
this.setState({
|
||||
devices: devices,
|
||||
e2eStatus: this._getE2EStatus(devices),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
@ -129,7 +129,7 @@ export default createReactClass({
|
|||
return;
|
||||
}
|
||||
|
||||
const devices = await cli.getStoredDevicesForUser(userId);
|
||||
const devices = cli.getStoredDevicesForUser(userId);
|
||||
const anyDeviceUnverified = devices.some(device => {
|
||||
const { deviceId } = device;
|
||||
// For your own devices, we use the stricter check of cross-signing
|
||||
|
|
|
@ -23,6 +23,7 @@ import { _t } from '../../../languageHandler';
|
|||
import {formatDate} from '../../../DateUtils';
|
||||
import Velociraptor from "../../../Velociraptor";
|
||||
import * as sdk from "../../../index";
|
||||
import toRem from "../../../utils/rem";
|
||||
|
||||
let bounce = false;
|
||||
try {
|
||||
|
@ -148,7 +149,7 @@ export default createReactClass({
|
|||
// start at the old height and in the old h pos
|
||||
|
||||
startStyles.push({ top: startTopOffset+"px",
|
||||
left: oldInfo.left+"px" });
|
||||
left: toRem(oldInfo.left) });
|
||||
|
||||
const reorderTransitionOpts = {
|
||||
duration: 100,
|
||||
|
@ -181,7 +182,7 @@ export default createReactClass({
|
|||
}
|
||||
|
||||
const style = {
|
||||
left: this.props.leftOffset+'px',
|
||||
left: toRem(this.props.leftOffset),
|
||||
top: '0px',
|
||||
visibility: this.props.hidden ? 'hidden' : 'visible',
|
||||
};
|
||||
|
|
|
@ -150,7 +150,7 @@ export default class RoomRecoveryReminder extends React.PureComponent {
|
|||
)}</p>
|
||||
</div>
|
||||
<div className="mx_RoomRecoveryReminder_buttons">
|
||||
<AccessibleButton className="mx_RoomRecoveryReminder_button"
|
||||
<AccessibleButton kind="primary"
|
||||
onClick={this.onSetupClick}>
|
||||
{setupCaption}
|
||||
</AccessibleButton>
|
||||
|
|
|
@ -26,8 +26,7 @@ import PersistedElement from "../elements/PersistedElement";
|
|||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {ContextMenu} from "../../structures/ContextMenu";
|
||||
|
||||
const widgetType = 'm.stickerpicker';
|
||||
import {WidgetType} from "../../../widgets/WidgetType";
|
||||
|
||||
// This should be below the dialog level (4000), but above the rest of the UI (1000-2000).
|
||||
// We sit in a context menu, so this should be given to the context menu.
|
||||
|
@ -87,7 +86,7 @@ export default class Stickerpicker extends React.Component {
|
|||
console.log('Removing Stickerpicker widgets');
|
||||
if (this.state.widgetId) {
|
||||
if (scalarClient) {
|
||||
scalarClient.disableWidgetAssets(widgetType, this.state.widgetId).then(() => {
|
||||
scalarClient.disableWidgetAssets(WidgetType.STICKERPICKER, this.state.widgetId).then(() => {
|
||||
console.log('Assets disabled');
|
||||
}).catch((err) => {
|
||||
console.error('Failed to disable assets');
|
||||
|
@ -364,13 +363,13 @@ export default class Stickerpicker extends React.Component {
|
|||
if (SettingsStore.isFeatureEnabled("feature_many_integration_managers")) {
|
||||
IntegrationManagers.sharedInstance().openAll(
|
||||
this.props.room,
|
||||
`type_${widgetType}`,
|
||||
`type_${WidgetType.STICKERPICKER.preferred}`,
|
||||
this.state.widgetId,
|
||||
);
|
||||
} else {
|
||||
IntegrationManagers.sharedInstance().getPrimaryManager().open(
|
||||
this.props.room,
|
||||
`type_${widgetType}`,
|
||||
`type_${WidgetType.STICKERPICKER.preferred}`,
|
||||
this.state.widgetId,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -163,7 +163,7 @@ export default class EventIndexPanel extends React.Component {
|
|||
);
|
||||
|
||||
eventIndexingSettings = (
|
||||
<div>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{
|
||||
_t( "Riot is missing some components required for securely " +
|
||||
"caching encrypted messages locally. If you'd like to " +
|
||||
|
@ -180,7 +180,7 @@ export default class EventIndexPanel extends React.Component {
|
|||
);
|
||||
} else {
|
||||
eventIndexingSettings = (
|
||||
<div>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{
|
||||
_t( "Riot can't securely cache encrypted messages locally " +
|
||||
"while running in a web browser. Use <riotLink>Riot Desktop</riotLink> " +
|
||||
|
|
|
@ -52,6 +52,10 @@ export class IgnoredUser extends React.Component {
|
|||
}
|
||||
|
||||
export default class SecurityUserSettingsTab extends React.Component {
|
||||
static propTypes = {
|
||||
closeSettingsFn: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
|
@ -107,6 +111,11 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
);
|
||||
};
|
||||
|
||||
_onGoToUserProfileClick = () => {
|
||||
window.location.href = "#/user/" + MatrixClientPeg.get().getUserId();
|
||||
this.props.closeSettingsFn();
|
||||
}
|
||||
|
||||
_onUserUnignored = async (userId) => {
|
||||
const {ignoredUserIds, waitingUnignored} = this.state;
|
||||
const currentlyIgnoredUserIds = ignoredUserIds.filter(e => !waitingUnignored.includes(e));
|
||||
|
@ -282,15 +291,12 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
</div>
|
||||
);
|
||||
|
||||
let eventIndex;
|
||||
if (SettingsStore.isFeatureEnabled("feature_event_indexing")) {
|
||||
eventIndex = (
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Message search")}</span>
|
||||
<EventIndexPanel />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const eventIndex = (
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Message search")}</span>
|
||||
<EventIndexPanel />
|
||||
</div>
|
||||
);
|
||||
|
||||
// XXX: There's no such panel in the current cross-signing designs, but
|
||||
// it's useful to have for testing the feature. If there's no interest
|
||||
|
@ -315,7 +321,18 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
<div className="mx_SettingsTab mx_SecurityUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Security & Privacy")}</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Sessions")}</span>
|
||||
<span className="mx_SettingsTab_subheading">{_t("Where you’re logged in")}</span>
|
||||
<span>
|
||||
{_t(
|
||||
"Manage the names of and sign out of your sessions below or " +
|
||||
"<a>verify them in your User Profile</a>.", {},
|
||||
{
|
||||
a: sub => <AccessibleButton kind="link" onClick={this._onGoToUserProfileClick}>
|
||||
{sub}
|
||||
</AccessibleButton>,
|
||||
},
|
||||
)}
|
||||
</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_t("A session's public name is visible to people you communicate with")}
|
||||
<DevicesPanel />
|
||||
|
|
56
src/components/views/toasts/BulkUnverifiedSessionsToast.js
Normal file
56
src/components/views/toasts/BulkUnverifiedSessionsToast.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import dis from "../../../dispatcher";
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import DeviceListener from '../../../DeviceListener';
|
||||
import FormButton from '../elements/FormButton';
|
||||
import { replaceableComponent } from '../../../utils/replaceableComponent';
|
||||
|
||||
@replaceableComponent("views.toasts.BulkUnverifiedSessionsToast")
|
||||
export default class BulkUnverifiedSessionsToast extends React.PureComponent {
|
||||
static propTypes = {
|
||||
deviceIds: PropTypes.array,
|
||||
}
|
||||
|
||||
_onLaterClick = () => {
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions(this.props.deviceIds);
|
||||
};
|
||||
|
||||
_onReviewClick = async () => {
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions(this.props.deviceIds);
|
||||
|
||||
dis.dispatch({
|
||||
action: 'view_user_info',
|
||||
userId: MatrixClientPeg.get().getUserId(),
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (<div>
|
||||
<div className="mx_Toast_description">
|
||||
{_t("Verify all your sessions to ensure your account & messages are safe")}
|
||||
</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
<FormButton label={_t("Later")} kind="danger" onClick={this._onLaterClick} />
|
||||
<FormButton label={_t("Review")} onClick={this._onReviewClick} />
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
|
@ -90,6 +90,7 @@ export default class SetupEncryptionToast extends React.PureComponent {
|
|||
getSetupCaption() {
|
||||
switch (this.props.kind) {
|
||||
case 'set_up_encryption':
|
||||
return _t('Set up');
|
||||
case 'upgrade_encryption':
|
||||
case 'upgrade_ssss':
|
||||
return _t('Upgrade');
|
||||
|
|
|
@ -17,8 +17,8 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import Modal from "../../../Modal";
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import Modal from '../../../Modal';
|
||||
import DeviceListener from '../../../DeviceListener';
|
||||
import NewSessionReviewDialog from '../dialogs/NewSessionReviewDialog';
|
||||
import FormButton from '../elements/FormButton';
|
||||
|
@ -27,44 +27,39 @@ import { replaceableComponent } from '../../../utils/replaceableComponent';
|
|||
@replaceableComponent("views.toasts.UnverifiedSessionToast")
|
||||
export default class UnverifiedSessionToast extends React.PureComponent {
|
||||
static propTypes = {
|
||||
toastKey: PropTypes.string.isRequired,
|
||||
device: PropTypes.object.isRequired,
|
||||
};
|
||||
deviceId: PropTypes.string,
|
||||
}
|
||||
|
||||
_onLaterClick = () => {
|
||||
const { device } = this.props;
|
||||
DeviceListener.sharedInstance().dismissVerification(device.deviceId);
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions([this.props.deviceId]);
|
||||
};
|
||||
|
||||
_onReviewClick = async () => {
|
||||
const { device } = this.props;
|
||||
|
||||
const cli = MatrixClientPeg.get();
|
||||
Modal.createTrackedDialog('New Session Review', 'Starting dialog', NewSessionReviewDialog, {
|
||||
userId: MatrixClientPeg.get().getUserId(),
|
||||
device,
|
||||
userId: cli.getUserId(),
|
||||
device: cli.getStoredDevice(cli.getUserId(), this.props.deviceId),
|
||||
onFinished: (r) => {
|
||||
if (!r) {
|
||||
/* This'll come back false if the user clicks "this wasn't me" and saw a warning dialog */
|
||||
this._onLaterClick();
|
||||
DeviceListener.sharedInstance().dismissUnverifiedSessions([this.props.deviceId]);
|
||||
}
|
||||
},
|
||||
}, null, /* priority = */ false, /* static = */ true);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { device } = this.props;
|
||||
const cli = MatrixClientPeg.get();
|
||||
const device = cli.getStoredDevice(cli.getUserId(), this.props.deviceId);
|
||||
|
||||
return (<div>
|
||||
<div className="mx_Toast_description">
|
||||
<span className="mx_Toast_deviceName">
|
||||
{device.getDisplayName()}
|
||||
</span> <span className="mx_Toast_deviceID">
|
||||
({device.deviceId})
|
||||
</span>
|
||||
{_t(
|
||||
"Verify the new login accessing your account: %(name)s", { name: device.getDisplayName()})}
|
||||
</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
<FormButton label={_t("Later")} kind="danger" onClick={this._onLaterClick} />
|
||||
<FormButton label={_t("Review")} onClick={this._onReviewClick} />
|
||||
<FormButton label={_t("Verify")} onClick={this._onReviewClick} />
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ export default class VerificationRequestToast extends React.PureComponent {
|
|||
|
||||
if (request.isSelfVerification) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
this.setState({device: await cli.getStoredDevice(cli.getUserId(), request.channel.deviceId)});
|
||||
this.setState({device: cli.getStoredDevice(cli.getUserId(), request.channel.deviceId)});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue