Merge branches 'develop' and 't3chguy/render_html_entities_in_og-description' of github.com:matrix-org/matrix-react-sdk into t3chguy/render_html_entities_in_og-description
Conflicts: package.jsonpull/21833/head
						commit
						4bea87f00d
					
				| 
						 | 
				
			
			@ -82,6 +82,7 @@
 | 
			
		|||
    "glob-to-regexp": "^0.4.1",
 | 
			
		||||
    "highlight.js": "^9.15.8",
 | 
			
		||||
    "html-entities": "^1.2.1",
 | 
			
		||||
    "humanize": "^0.0.9",
 | 
			
		||||
    "is-ip": "^2.0.0",
 | 
			
		||||
    "isomorphic-fetch": "^2.2.1",
 | 
			
		||||
    "linkifyjs": "^2.1.6",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,6 +56,7 @@
 | 
			
		|||
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
 | 
			
		||||
@import "./views/dialogs/_CreateGroupDialog.scss";
 | 
			
		||||
@import "./views/dialogs/_CreateRoomDialog.scss";
 | 
			
		||||
@import "./views/dialogs/_DMInviteDialog.scss";
 | 
			
		||||
@import "./views/dialogs/_DeactivateAccountDialog.scss";
 | 
			
		||||
@import "./views/dialogs/_DeviceVerifyDialog.scss";
 | 
			
		||||
@import "./views/dialogs/_DevtoolsDialog.scss";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,81 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2019, 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.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
.mx_DMInviteDialog_addressBar {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
 | 
			
		||||
    .mx_DMInviteDialog_editor {
 | 
			
		||||
        flex: 1;
 | 
			
		||||
        width: 100%; // Needed to make the Field inside grow
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .mx_Field {
 | 
			
		||||
        margin: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .mx_DMInviteDialog_goButton {
 | 
			
		||||
        width: 48px;
 | 
			
		||||
        margin-left: 10px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_DMInviteDialog_section {
 | 
			
		||||
    padding-bottom: 10px;
 | 
			
		||||
 | 
			
		||||
    h3 {
 | 
			
		||||
        font-size: 12px;
 | 
			
		||||
        color: $muted-fg-color;
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        text-transform: uppercase;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mx_DMInviteDialog_roomTile {
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    padding: 5px 10px;
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
        background-color: $user-tile-hover-bg-color;
 | 
			
		||||
        border-radius: 4px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    * {
 | 
			
		||||
        vertical-align: middle;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .mx_DMInviteDialog_roomTile_name {
 | 
			
		||||
        font-weight: 600;
 | 
			
		||||
        font-size: 14px;
 | 
			
		||||
        color: $primary-fg-color;
 | 
			
		||||
        margin-left: 7px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .mx_DMInviteDialog_roomTile_userId {
 | 
			
		||||
        font-size: 12px;
 | 
			
		||||
        color: $muted-fg-color;
 | 
			
		||||
        margin-left: 7px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .mx_DMInviteDialog_roomTile_time {
 | 
			
		||||
        text-align: right;
 | 
			
		||||
        font-size: 12px;
 | 
			
		||||
        color: $muted-fg-color;
 | 
			
		||||
        float: right;
 | 
			
		||||
        line-height: 36px; // Height of the avatar to keep the time vertically aligned
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2015, 2016 OpenMarket Ltd
 | 
			
		||||
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.
 | 
			
		||||
| 
						 | 
				
			
			@ -353,7 +354,6 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody {
 | 
			
		|||
    left: 46px;
 | 
			
		||||
    width: 15px;
 | 
			
		||||
    height: 15px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    display: block;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    right: 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,4 +40,5 @@ limitations under the License.
 | 
			
		|||
 | 
			
		||||
.mx_RoomRecoveryReminder_secondary {
 | 
			
		||||
    font-size: 90%;
 | 
			
		||||
    margin-top: 1em;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,7 @@ $room-highlight-color: #343a46;
 | 
			
		|||
// typical text (dark-on-white in light skin)
 | 
			
		||||
$primary-fg-color: $text-primary-color;
 | 
			
		||||
$primary-bg-color: $bg-color;
 | 
			
		||||
$muted-fg-color: $header-panel-text-primary-color;
 | 
			
		||||
 | 
			
		||||
// used for dialog box text
 | 
			
		||||
$light-fg-color: $header-panel-text-secondary-color;
 | 
			
		||||
| 
						 | 
				
			
			@ -172,6 +173,8 @@ $interactive-tooltip-fg-color: #ffffff;
 | 
			
		|||
 | 
			
		||||
$breadcrumb-placeholder-bg-color: #272c35;
 | 
			
		||||
 | 
			
		||||
$user-tile-hover-bg-color: $header-panel-bg-color;
 | 
			
		||||
 | 
			
		||||
// ***** Mixins! *****
 | 
			
		||||
 | 
			
		||||
@define-mixin mx_DialogButton {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,7 @@ $header-panel-bg-color: #f3f8fd;
 | 
			
		|||
// typical text (dark-on-white in light skin)
 | 
			
		||||
$primary-fg-color: #2e2f32;
 | 
			
		||||
$primary-bg-color: #ffffff;
 | 
			
		||||
$muted-fg-color: #61708b; // Commonly used in headings and relevant alt text
 | 
			
		||||
 | 
			
		||||
// used for dialog box text
 | 
			
		||||
$light-fg-color: #747474;
 | 
			
		||||
| 
						 | 
				
			
			@ -293,6 +294,8 @@ $interactive-tooltip-fg-color: #ffffff;
 | 
			
		|||
 | 
			
		||||
$breadcrumb-placeholder-bg-color: #e8eef5;
 | 
			
		||||
 | 
			
		||||
$user-tile-hover-bg-color: $header-panel-bg-color;
 | 
			
		||||
 | 
			
		||||
// ***** Mixins! *****
 | 
			
		||||
 | 
			
		||||
@define-mixin mx_DialogButton {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,8 @@ echo "--- Install synapse & other dependencies"
 | 
			
		|||
./install.sh
 | 
			
		||||
# install static webserver to server symlinked local copy of riot
 | 
			
		||||
./riot/install-webserver.sh
 | 
			
		||||
mkdir logs || rm -r logs/*
 | 
			
		||||
rm -r logs || true
 | 
			
		||||
mkdir logs
 | 
			
		||||
echo "+++ Running end-to-end tests"
 | 
			
		||||
TESTS_STARTED=1
 | 
			
		||||
./run.sh --no-sandbox --log-directory logs/
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -97,7 +97,7 @@ export const crossSigningCallbacks = {
 | 
			
		|||
 *
 | 
			
		||||
 * Additionally, the secret storage keys are cached during the scope of this function
 | 
			
		||||
 * to ensure the user is prompted only once for their secret storage
 | 
			
		||||
 * passphrase. The cache is then
 | 
			
		||||
 * passphrase. The cache is then cleared once the provided function completes.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {Function} [func] An operation to perform once secret storage has been
 | 
			
		||||
 * bootstrapped. Optional.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -111,6 +111,12 @@ export default class KeyRequestHandler {
 | 
			
		|||
            this._currentUser = null;
 | 
			
		||||
            this._currentDevice = null;
 | 
			
		||||
 | 
			
		||||
            if (!this._pendingKeyRequests[userId] || !this._pendingKeyRequests[userId][deviceId]) {
 | 
			
		||||
                // request was removed in the time the dialog was displayed
 | 
			
		||||
                this._processNextRequest();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (r) {
 | 
			
		||||
                for (const req of this._pendingKeyRequests[userId][deviceId]) {
 | 
			
		||||
                    req.share();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,6 +25,7 @@ import sdk from './';
 | 
			
		|||
import dis from './dispatcher';
 | 
			
		||||
import DMRoomMap from './utils/DMRoomMap';
 | 
			
		||||
import { _t } from './languageHandler';
 | 
			
		||||
import SettingsStore from "./settings/SettingsStore";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Invites multiple addresses to a room
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +42,18 @@ function inviteMultipleToRoom(roomId, addrs) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
export function showStartChatInviteDialog() {
 | 
			
		||||
    if (SettingsStore.isFeatureEnabled("feature_ftue_dms")) {
 | 
			
		||||
        const DMInviteDialog = sdk.getComponent("dialogs.DMInviteDialog");
 | 
			
		||||
        Modal.createTrackedDialog('Start DM', '', DMInviteDialog, {
 | 
			
		||||
            onFinished: (inviteIds) => {
 | 
			
		||||
                // TODO: Replace _onStartDmFinished with less hacks
 | 
			
		||||
                if (inviteIds.length > 0) _onStartDmFinished(true, inviteIds.map(i => ({address: i})));
 | 
			
		||||
                // else ignore and just do nothing
 | 
			
		||||
            },
 | 
			
		||||
        }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
 | 
			
		||||
 | 
			
		||||
    Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, {
 | 
			
		||||
| 
						 | 
				
			
			@ -99,7 +112,7 @@ export function isValid3pidInvite(event) {
 | 
			
		|||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: Immutable DMs replaces this
 | 
			
		||||
// TODO: Canonical DMs replaces this
 | 
			
		||||
function _onStartDmFinished(shouldInvite, addrs) {
 | 
			
		||||
    if (!shouldInvite) return;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -780,54 +780,52 @@ export const CommandMap = {
 | 
			
		|||
                    const deviceId = matches[2];
 | 
			
		||||
                    const fingerprint = matches[3];
 | 
			
		||||
 | 
			
		||||
                    return success(
 | 
			
		||||
                        // Promise.resolve to handle transition from static result to promise; can be removed
 | 
			
		||||
                        // in future
 | 
			
		||||
                        Promise.resolve(cli.getStoredDevice(userId, deviceId)).then((device) => {
 | 
			
		||||
                            if (!device) {
 | 
			
		||||
                                throw new Error(_t('Unknown (user, device) pair:') + ` (${userId}, ${deviceId})`);
 | 
			
		||||
                            }
 | 
			
		||||
                    return success((async () => {
 | 
			
		||||
                        const device = await cli.getStoredDevice(userId, deviceId);
 | 
			
		||||
                        if (!device) {
 | 
			
		||||
                            throw new Error(_t('Unknown (user, device) pair:') + ` (${userId}, ${deviceId})`);
 | 
			
		||||
                        }
 | 
			
		||||
                        const deviceTrust = await cli.checkDeviceTrust(userId, deviceId);
 | 
			
		||||
 | 
			
		||||
                            if (device.isVerified()) {
 | 
			
		||||
                                if (device.getFingerprint() === fingerprint) {
 | 
			
		||||
                                    throw new Error(_t('Device already verified!'));
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    throw new Error(_t('WARNING: Device already verified, but keys do NOT MATCH!'));
 | 
			
		||||
                                }
 | 
			
		||||
                        if (deviceTrust.isVerified()) {
 | 
			
		||||
                            if (device.getFingerprint() === fingerprint) {
 | 
			
		||||
                                throw new Error(_t('Device already verified!'));
 | 
			
		||||
                            } else {
 | 
			
		||||
                                throw new Error(_t('WARNING: Device already verified, but keys do NOT MATCH!'));
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                            if (device.getFingerprint() !== fingerprint) {
 | 
			
		||||
                                const fprint = device.getFingerprint();
 | 
			
		||||
                                throw new Error(
 | 
			
		||||
                                    _t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device' +
 | 
			
		||||
                                        ' %(deviceId)s is "%(fprint)s" which does not match the provided key ' +
 | 
			
		||||
                                        '"%(fingerprint)s". This could mean your communications are being intercepted!',
 | 
			
		||||
                                        {
 | 
			
		||||
                                            fprint,
 | 
			
		||||
                                            userId,
 | 
			
		||||
                                            deviceId,
 | 
			
		||||
                                            fingerprint,
 | 
			
		||||
                                        }));
 | 
			
		||||
                            }
 | 
			
		||||
                        if (device.getFingerprint() !== fingerprint) {
 | 
			
		||||
                            const fprint = device.getFingerprint();
 | 
			
		||||
                            throw new Error(
 | 
			
		||||
                                _t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device' +
 | 
			
		||||
                                    ' %(deviceId)s is "%(fprint)s" which does not match the provided key ' +
 | 
			
		||||
                                    '"%(fingerprint)s". This could mean your communications are being intercepted!',
 | 
			
		||||
                                    {
 | 
			
		||||
                                        fprint,
 | 
			
		||||
                                        userId,
 | 
			
		||||
                                        deviceId,
 | 
			
		||||
                                        fingerprint,
 | 
			
		||||
                                    }));
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                            return cli.setDeviceVerified(userId, deviceId, true);
 | 
			
		||||
                        }).then(() => {
 | 
			
		||||
                            // Tell the user we verified everything
 | 
			
		||||
                            const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
 | 
			
		||||
                            Modal.createTrackedDialog('Slash Commands', 'Verified key', InfoDialog, {
 | 
			
		||||
                                title: _t('Verified key'),
 | 
			
		||||
                                description: <div>
 | 
			
		||||
                                    <p>
 | 
			
		||||
                                        {
 | 
			
		||||
                                            _t('The signing key you provided matches the signing key you received ' +
 | 
			
		||||
                                                'from %(userId)s\'s device %(deviceId)s. Device marked as verified.',
 | 
			
		||||
                                                {userId, deviceId})
 | 
			
		||||
                                        }
 | 
			
		||||
                                    </p>
 | 
			
		||||
                                </div>,
 | 
			
		||||
                            });
 | 
			
		||||
                        }),
 | 
			
		||||
                    );
 | 
			
		||||
                        await cli.setDeviceVerified(userId, deviceId, true);
 | 
			
		||||
 | 
			
		||||
                        // Tell the user we verified everything
 | 
			
		||||
                        const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
 | 
			
		||||
                        Modal.createTrackedDialog('Slash Commands', 'Verified key', InfoDialog, {
 | 
			
		||||
                            title: _t('Verified key'),
 | 
			
		||||
                            description: <div>
 | 
			
		||||
                                <p>
 | 
			
		||||
                                    {
 | 
			
		||||
                                        _t('The signing key you provided matches the signing key you received ' +
 | 
			
		||||
                                            'from %(userId)s\'s device %(deviceId)s. Device marked as verified.',
 | 
			
		||||
                                            {userId, deviceId})
 | 
			
		||||
                                    }
 | 
			
		||||
                                </p>
 | 
			
		||||
                            </div>,
 | 
			
		||||
                        });
 | 
			
		||||
                    })());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return reject(this.getUsage());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,9 @@ import { _t } from '../../../languageHandler';
 | 
			
		|||
const sdk = require('../../../index');
 | 
			
		||||
const MatrixClientPeg = require("../../../MatrixClientPeg");
 | 
			
		||||
 | 
			
		||||
// XXX: This component is not cross-signing aware.
 | 
			
		||||
// https://github.com/vector-im/riot-web/issues/11752 tracks either updating this
 | 
			
		||||
// component or taking it out to pasture.
 | 
			
		||||
module.exports = createReactClass({
 | 
			
		||||
    displayName: 'EncryptedEventDialog',
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2018, 2019 New Vector Ltd
 | 
			
		||||
Copyright 2019 The Matrix.org Foundation C.I.C.
 | 
			
		||||
Copyright 2019, 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -17,11 +17,14 @@ limitations under the License.
 | 
			
		|||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import FileSaver from 'file-saver';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
 | 
			
		||||
import sdk from '../../../../index';
 | 
			
		||||
import MatrixClientPeg from '../../../../MatrixClientPeg';
 | 
			
		||||
import { scorePassword } from '../../../../utils/PasswordScorer';
 | 
			
		||||
import { _t } from '../../../../languageHandler';
 | 
			
		||||
import { accessSecretStorage } from '../../../../CrossSigningManager';
 | 
			
		||||
import SettingsStore from '../../../../../lib/settings/SettingsStore';
 | 
			
		||||
 | 
			
		||||
const PHASE_PASSPHRASE = 0;
 | 
			
		||||
const PHASE_PASSPHRASE_CONFIRM = 1;
 | 
			
		||||
| 
						 | 
				
			
			@ -49,10 +52,20 @@ function selectText(target) {
 | 
			
		|||
 * on the server.
 | 
			
		||||
 */
 | 
			
		||||
export default class CreateKeyBackupDialog extends React.PureComponent {
 | 
			
		||||
    static propTypes = {
 | 
			
		||||
        secureSecretStorage: PropTypes.bool,
 | 
			
		||||
        onFinished: PropTypes.func.isRequired,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructor(props) {
 | 
			
		||||
        super(props);
 | 
			
		||||
 | 
			
		||||
        this._recoveryKeyNode = null;
 | 
			
		||||
        this._keyBackupInfo = null;
 | 
			
		||||
        this._setZxcvbnResultTimeout = null;
 | 
			
		||||
 | 
			
		||||
        this.state = {
 | 
			
		||||
            secureSecretStorage: props.secureSecretStorage,
 | 
			
		||||
            phase: PHASE_PASSPHRASE,
 | 
			
		||||
            passPhrase: '',
 | 
			
		||||
            passPhraseConfirm: '',
 | 
			
		||||
| 
						 | 
				
			
			@ -61,12 +74,25 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
 | 
			
		|||
            zxcvbnResult: null,
 | 
			
		||||
            setPassPhrase: false,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (this.state.secureSecretStorage === undefined) {
 | 
			
		||||
            this.state.secureSecretStorage =
 | 
			
		||||
                SettingsStore.isFeatureEnabled("feature_cross_signing");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If we're using secret storage, skip ahead to the backing up step, as
 | 
			
		||||
        // `accessSecretStorage` will handle passphrases as needed.
 | 
			
		||||
        if (this.state.secureSecretStorage) {
 | 
			
		||||
            this.state.phase = PHASE_BACKINGUP;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    componentWillMount() {
 | 
			
		||||
        this._recoveryKeyNode = null;
 | 
			
		||||
        this._keyBackupInfo = null;
 | 
			
		||||
        this._setZxcvbnResultTimeout = null;
 | 
			
		||||
    componentDidMount() {
 | 
			
		||||
        // If we're using secret storage, skip ahead to the backing up step, as
 | 
			
		||||
        // `accessSecretStorage` will handle passphrases as needed.
 | 
			
		||||
        if (this.state.secureSecretStorage) {
 | 
			
		||||
            this._createBackup();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    componentWillUnmount() {
 | 
			
		||||
| 
						 | 
				
			
			@ -103,15 +129,26 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    _createBackup = async () => {
 | 
			
		||||
        const { secureSecretStorage } = this.state;
 | 
			
		||||
        this.setState({
 | 
			
		||||
            phase: PHASE_BACKINGUP,
 | 
			
		||||
            error: null,
 | 
			
		||||
        });
 | 
			
		||||
        let info;
 | 
			
		||||
        try {
 | 
			
		||||
            info = await MatrixClientPeg.get().createKeyBackupVersion(
 | 
			
		||||
                this._keyBackupInfo,
 | 
			
		||||
            );
 | 
			
		||||
            if (secureSecretStorage) {
 | 
			
		||||
                await accessSecretStorage(async () => {
 | 
			
		||||
                    info = await MatrixClientPeg.get().prepareKeyBackupVersion(
 | 
			
		||||
                        null /* random key */,
 | 
			
		||||
                        { secureSecretStorage: true },
 | 
			
		||||
                    );
 | 
			
		||||
                    info = await MatrixClientPeg.get().createKeyBackupVersion(info);
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                info = await MatrixClientPeg.get().createKeyBackupVersion(
 | 
			
		||||
                    this._keyBackupInfo,
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            await MatrixClientPeg.get().scheduleAllGroupSessionsForBackup();
 | 
			
		||||
            this.setState({
 | 
			
		||||
                phase: PHASE_DONE,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2018-2019 New Vector Ltd
 | 
			
		||||
Copyright 2018, 2019 New Vector Ltd
 | 
			
		||||
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.
 | 
			
		||||
| 
						 | 
				
			
			@ -40,9 +41,11 @@ export default class NewRecoveryMethodDialog extends React.PureComponent {
 | 
			
		|||
 | 
			
		||||
    onSetupClick = async () => {
 | 
			
		||||
        const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
 | 
			
		||||
        Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
 | 
			
		||||
            onFinished: this.props.onFinished,
 | 
			
		||||
        });
 | 
			
		||||
        Modal.createTrackedDialog(
 | 
			
		||||
            'Restore Backup', '', RestoreKeyBackupDialog, {
 | 
			
		||||
                onFinished: this.props.onFinished,
 | 
			
		||||
            }, null, /* priority = */ false, /* static = */ true,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2019 New Vector Ltd
 | 
			
		||||
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.
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +36,7 @@ export default class RecoveryMethodRemovedDialog extends React.PureComponent {
 | 
			
		|||
        this.props.onFinished();
 | 
			
		||||
        Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
 | 
			
		||||
            import("./CreateKeyBackupDialog"),
 | 
			
		||||
            null, null, /* priority = */ false, /* static = */ true,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1569,9 +1569,17 @@ export default createReactClass({
 | 
			
		|||
                action: 'start_post_registration',
 | 
			
		||||
            });
 | 
			
		||||
        } else if (screen.indexOf('room/') == 0) {
 | 
			
		||||
            const segments = screen.substring(5).split('/');
 | 
			
		||||
            const roomString = segments[0];
 | 
			
		||||
            let eventId = segments.splice(1).join("/"); // empty string if no event id given
 | 
			
		||||
            // Rooms can have the following formats:
 | 
			
		||||
            // #room_alias:domain or !opaque_id:domain
 | 
			
		||||
            const room = screen.substring(5);
 | 
			
		||||
            const domainOffset = room.indexOf(':') + 1; // 0 in case room does not contain a :
 | 
			
		||||
            let eventOffset = room.length;
 | 
			
		||||
            // room aliases can contain slashes only look for slash after domain
 | 
			
		||||
            if (room.substring(domainOffset).indexOf('/') > -1) {
 | 
			
		||||
                eventOffset = domainOffset + room.substring(domainOffset).indexOf('/');
 | 
			
		||||
            }
 | 
			
		||||
            const roomString = room.substring(0, eventOffset);
 | 
			
		||||
            let eventId = room.substring(eventOffset + 1); // empty string if no event id given
 | 
			
		||||
 | 
			
		||||
            // Previously we pulled the eventID from the segments in such a way
 | 
			
		||||
            // where if there was no eventId then we'd get undefined. However, we
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,217 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2019, 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 sdk from "../../../index";
 | 
			
		||||
import MatrixClientPeg from "../../../MatrixClientPeg";
 | 
			
		||||
import {makeUserPermalink} from "../../../utils/permalinks/Permalinks";
 | 
			
		||||
import DMRoomMap from "../../../utils/DMRoomMap";
 | 
			
		||||
import {RoomMember} from "matrix-js-sdk/lib/matrix";
 | 
			
		||||
import * as humanize from "humanize";
 | 
			
		||||
 | 
			
		||||
// TODO: [TravisR] Make this generic for all kinds of invites
 | 
			
		||||
 | 
			
		||||
const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
 | 
			
		||||
const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked
 | 
			
		||||
 | 
			
		||||
class DMRoomTile extends React.PureComponent {
 | 
			
		||||
    static propTypes = {
 | 
			
		||||
        member: PropTypes.object.isRequired,
 | 
			
		||||
        lastActiveTs: PropTypes.number,
 | 
			
		||||
        onToggle: PropTypes.func.isRequired,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _onClick = (e) => {
 | 
			
		||||
        // Stop the browser from highlighting text
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        this.props.onToggle(this.props.member.userId);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
 | 
			
		||||
 | 
			
		||||
        let timestamp = null;
 | 
			
		||||
        if (this.props.lastActiveTs) {
 | 
			
		||||
            // TODO: [TravisR] Figure out how to i18n this
 | 
			
		||||
            // `humanize` wants seconds for a timestamp, so divide by 1000
 | 
			
		||||
            const humanTs = humanize.relativeTime(this.props.lastActiveTs / 1000);
 | 
			
		||||
            timestamp = <span className='mx_DMInviteDialog_roomTile_time'>{humanTs}</span>;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <div className='mx_DMInviteDialog_roomTile' onClick={this._onClick}>
 | 
			
		||||
                <MemberAvatar member={this.props.member} width={36} height={36} />
 | 
			
		||||
                <span className='mx_DMInviteDialog_roomTile_name'>{this.props.member.name}</span>
 | 
			
		||||
                <span className='mx_DMInviteDialog_roomTile_userId'>{this.props.member.userId}</span>
 | 
			
		||||
                {timestamp}
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class DMInviteDialog extends React.PureComponent {
 | 
			
		||||
    static propTypes = {
 | 
			
		||||
        // Takes an array of user IDs/emails to invite.
 | 
			
		||||
        onFinished: PropTypes.func.isRequired,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super();
 | 
			
		||||
 | 
			
		||||
        this.state = {
 | 
			
		||||
            targets: [], // string[] of mxids/email addresses
 | 
			
		||||
            filterText: "",
 | 
			
		||||
            recents: this._buildRecents(),
 | 
			
		||||
            numRecentsShown: INITIAL_ROOMS_SHOWN,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _buildRecents(): {userId: string, user: RoomMember, lastActive: number} {
 | 
			
		||||
        const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals();
 | 
			
		||||
        const recents = [];
 | 
			
		||||
        for (const userId in rooms) {
 | 
			
		||||
            const room = rooms[userId];
 | 
			
		||||
            const member = room.getMember(userId);
 | 
			
		||||
            if (!member) continue; // just skip people who don't have memberships for some reason
 | 
			
		||||
 | 
			
		||||
            const lastEventTs = room.timeline && room.timeline.length
 | 
			
		||||
                ? room.timeline[room.timeline.length - 1].getTs()
 | 
			
		||||
                : 0;
 | 
			
		||||
            if (!lastEventTs) continue; // something weird is going on with this room
 | 
			
		||||
 | 
			
		||||
            recents.push({userId, user: member, lastActive: lastEventTs});
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Sort the recents by last active to save us time later
 | 
			
		||||
        recents.sort((a, b) => b.lastActive - a.lastActive);
 | 
			
		||||
 | 
			
		||||
        return recents;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _startDm = () => {
 | 
			
		||||
        this.props.onFinished(this.state.targets);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    _cancel = () => {
 | 
			
		||||
        this.props.onFinished([]);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    _updateFilter = (e) => {
 | 
			
		||||
        this.setState({filterText: e.target.value});
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    _showMoreRecents = () => {
 | 
			
		||||
        this.setState({numRecentsShown: this.state.numRecentsShown + INCREMENT_ROOMS_SHOWN});
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    _toggleMember = (userId) => {
 | 
			
		||||
        const targets = this.state.targets.map(t => t); // cheap clone for mutation
 | 
			
		||||
        const idx = targets.indexOf(userId);
 | 
			
		||||
        if (idx >= 0) targets.splice(idx, 1);
 | 
			
		||||
        else targets.push(userId);
 | 
			
		||||
        this.setState({targets});
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    _renderRecents() {
 | 
			
		||||
        if (!this.state.recents || this.state.recents.length === 0) return null;
 | 
			
		||||
 | 
			
		||||
        // .slice() will return an incomplete array but won't error on us if we go too far
 | 
			
		||||
        const toRender = this.state.recents.slice(0, this.state.numRecentsShown);
 | 
			
		||||
        const hasMore = toRender.length < this.state.recents.length;
 | 
			
		||||
 | 
			
		||||
        const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
 | 
			
		||||
        let showMore = null;
 | 
			
		||||
        if (hasMore) {
 | 
			
		||||
            showMore = (
 | 
			
		||||
                <AccessibleButton onClick={this._showMoreRecents} kind="link">
 | 
			
		||||
                    {_t("Show more")}
 | 
			
		||||
                </AccessibleButton>
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const tiles = toRender.map(r => (
 | 
			
		||||
            <DMRoomTile member={r.user} lastActiveTs={r.lastActive} key={r.userId} onToggle={this._toggleMember} />
 | 
			
		||||
        ));
 | 
			
		||||
        return (
 | 
			
		||||
            <div className='mx_DMInviteDialog_section'>
 | 
			
		||||
                <h3>{_t("Recent Conversations")}</h3>
 | 
			
		||||
                {tiles}
 | 
			
		||||
                {showMore}
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
 | 
			
		||||
        const Field = sdk.getComponent("elements.Field");
 | 
			
		||||
        const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
 | 
			
		||||
 | 
			
		||||
        // Dev note: The use of Field is temporary/incomplete pending https://github.com/vector-im/riot-web/issues/11197
 | 
			
		||||
        // For now, we just list who the targets are.
 | 
			
		||||
        const editor = (
 | 
			
		||||
            <div className='mx_DMInviteDialog_editor'>
 | 
			
		||||
                <Field
 | 
			
		||||
                    id="inviteTargets"
 | 
			
		||||
                    value={this.state.filterText}
 | 
			
		||||
                    onChange={this._updateFilter}
 | 
			
		||||
                    placeholder="TODO: Implement filtering/searching (vector-im/riot-web#11199)"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
        const targets = this.state.targets.map(t => <div key={t}>{t}</div>);
 | 
			
		||||
 | 
			
		||||
        const userId = MatrixClientPeg.get().getUserId();
 | 
			
		||||
        return (
 | 
			
		||||
            <BaseDialog
 | 
			
		||||
                className='mx_DMInviteDialog'
 | 
			
		||||
                hasCancel={true}
 | 
			
		||||
                onFinished={this._cancel}
 | 
			
		||||
                title={_t("Direct Messages")}
 | 
			
		||||
            >
 | 
			
		||||
                <div className='mx_DMInviteDialog_content'>
 | 
			
		||||
                    <p>
 | 
			
		||||
                        {_t(
 | 
			
		||||
                            "If you can't find someone, ask them for their username, or share your " +
 | 
			
		||||
                            "username (%(userId)s) or <a>profile link</a>.",
 | 
			
		||||
                            {userId},
 | 
			
		||||
                            {a: (sub) => <a href={makeUserPermalink(userId)} rel="noopener" target="_blank">{sub}</a>},
 | 
			
		||||
                        )}
 | 
			
		||||
                    </p>
 | 
			
		||||
                    {targets}
 | 
			
		||||
                    <div className='mx_DMInviteDialog_addressBar'>
 | 
			
		||||
                        {editor}
 | 
			
		||||
                        <AccessibleButton
 | 
			
		||||
                            kind="primary"
 | 
			
		||||
                            onClick={this._startDm}
 | 
			
		||||
                            className='mx_DMInviteDialog_goButton'
 | 
			
		||||
                        >
 | 
			
		||||
                            {_t("Go")}
 | 
			
		||||
                        </AccessibleButton>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {this._renderRecents()}
 | 
			
		||||
                </div>
 | 
			
		||||
            </BaseDialog>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2018, 2019 New Vector Ltd
 | 
			
		||||
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.
 | 
			
		||||
| 
						 | 
				
			
			@ -94,10 +95,14 @@ export default class LogoutDialog extends React.Component {
 | 
			
		|||
            // verified, so restore the backup which will give us the keys from it and
 | 
			
		||||
            // allow us to trust it (ie. upload keys to it)
 | 
			
		||||
            const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
 | 
			
		||||
            Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {});
 | 
			
		||||
            Modal.createTrackedDialog(
 | 
			
		||||
                'Restore Backup', '', RestoreKeyBackupDialog, null, null,
 | 
			
		||||
                /* priority = */ false, /* static = */ true,
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
 | 
			
		||||
                import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"),
 | 
			
		||||
                null, null, /* priority = */ false, /* static = */ true,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2018, 2019 New Vector Ltd
 | 
			
		||||
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.
 | 
			
		||||
| 
						 | 
				
			
			@ -15,17 +16,18 @@ limitations under the License.
 | 
			
		|||
*/
 | 
			
		||||
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { MatrixClient } from 'matrix-js-sdk';
 | 
			
		||||
 | 
			
		||||
import sdk from '../../../../index';
 | 
			
		||||
import MatrixClientPeg from '../../../../MatrixClientPeg';
 | 
			
		||||
import Modal from '../../../../Modal';
 | 
			
		||||
 | 
			
		||||
import { MatrixClient } from 'matrix-js-sdk';
 | 
			
		||||
 | 
			
		||||
import { _t } from '../../../../languageHandler';
 | 
			
		||||
import {Key} from "../../../../Keyboard";
 | 
			
		||||
import { accessSecretStorage } from '../../../../CrossSigningManager';
 | 
			
		||||
 | 
			
		||||
const RESTORE_TYPE_PASSPHRASE = 0;
 | 
			
		||||
const RESTORE_TYPE_RECOVERYKEY = 1;
 | 
			
		||||
const RESTORE_TYPE_SECRET_STORAGE = 2;
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Dialog for restoring e2e keys from a backup and the user's recovery key
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +37,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
 | 
			
		|||
        super(props);
 | 
			
		||||
        this.state = {
 | 
			
		||||
            backupInfo: null,
 | 
			
		||||
            backupKeyStored: null,
 | 
			
		||||
            loading: false,
 | 
			
		||||
            loadError: null,
 | 
			
		||||
            restoreError: null,
 | 
			
		||||
| 
						 | 
				
			
			@ -73,7 +76,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
 | 
			
		|||
                onFinished: () => {
 | 
			
		||||
                    this._loadBackupStatus();
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            }, null, /* priority = */ false, /* static = */ true,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -148,6 +151,32 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async _restoreWithSecretStorage() {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            loading: true,
 | 
			
		||||
            restoreError: null,
 | 
			
		||||
            restoreType: RESTORE_TYPE_SECRET_STORAGE,
 | 
			
		||||
        });
 | 
			
		||||
        try {
 | 
			
		||||
            // `accessSecretStorage` may prompt for storage access as needed.
 | 
			
		||||
            const recoverInfo = await accessSecretStorage(async () => {
 | 
			
		||||
                return MatrixClientPeg.get().restoreKeyBackupWithSecretStorage(
 | 
			
		||||
                    this.state.backupInfo,
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
            this.setState({
 | 
			
		||||
                loading: false,
 | 
			
		||||
                recoverInfo,
 | 
			
		||||
            });
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            console.log("Error restoring backup", e);
 | 
			
		||||
            this.setState({
 | 
			
		||||
                restoreError: e,
 | 
			
		||||
                loading: false,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async _loadBackupStatus() {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            loading: true,
 | 
			
		||||
| 
						 | 
				
			
			@ -155,10 +184,20 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
 | 
			
		|||
        });
 | 
			
		||||
        try {
 | 
			
		||||
            const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
 | 
			
		||||
            const backupKeyStored = await MatrixClientPeg.get().isKeyBackupKeyStored();
 | 
			
		||||
            this.setState({
 | 
			
		||||
                backupInfo,
 | 
			
		||||
                backupKeyStored,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // If the backup key is stored, we can proceed directly to restore.
 | 
			
		||||
            if (backupKeyStored) {
 | 
			
		||||
                return this._restoreWithSecretStorage();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.setState({
 | 
			
		||||
                loadError: null,
 | 
			
		||||
                loading: false,
 | 
			
		||||
                backupInfo,
 | 
			
		||||
            });
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            console.log("Error loading backup status", e);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,8 @@ import sdk from '../../../index';
 | 
			
		|||
import Modal from '../../../Modal';
 | 
			
		||||
import { _t } from '../../../languageHandler';
 | 
			
		||||
 | 
			
		||||
// XXX: This component is *not* cross-signing aware. Once everything is
 | 
			
		||||
// cross-signing, this component should just go away.
 | 
			
		||||
export default createReactClass({
 | 
			
		||||
    displayName: 'DeviceVerifyButtons',
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -74,17 +74,6 @@ const _getE2EStatus = (cli, userId, devices) => {
 | 
			
		|||
    return "warning";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
async function unverifyUser(matrixClient, userId) {
 | 
			
		||||
    const devices = await matrixClient.getStoredDevicesForUser(userId);
 | 
			
		||||
    for (const device of devices) {
 | 
			
		||||
        if (device.isVerified()) {
 | 
			
		||||
            matrixClient.setDeviceVerified(
 | 
			
		||||
                userId, device.deviceId, false,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function openDMForUser(matrixClient, userId) {
 | 
			
		||||
    const dmRooms = DMRoomMap.shared().getDMRoomsForUserId(userId);
 | 
			
		||||
    const lastActiveRoom = dmRooms.reduce((lastActiveRoom, roomId) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -331,14 +320,6 @@ const UserOptionsSection = ({member, isIgnored, canInvite, devices}) => {
 | 
			
		|||
            </AccessibleButton>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    let unverifyButton;
 | 
			
		||||
    if (devices && devices.some(device => device.isVerified())) {
 | 
			
		||||
        unverifyButton = (
 | 
			
		||||
            <AccessibleButton onClick={() => unverifyUser(cli, member.userId)} className="mx_UserInfo_field mx_UserInfo_destructive">
 | 
			
		||||
                { _t('Unverify user') }
 | 
			
		||||
            </AccessibleButton>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="mx_UserInfo_container">
 | 
			
		||||
| 
						 | 
				
			
			@ -350,7 +331,6 @@ const UserOptionsSection = ({member, isIgnored, canInvite, devices}) => {
 | 
			
		|||
                { insertPillButton }
 | 
			
		||||
                { inviteUserButton }
 | 
			
		||||
                { ignoreButton }
 | 
			
		||||
                { unverifyButton }
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,8 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2015, 2016 OpenMarket Ltd
 | 
			
		||||
Copyright 2017 New Vector Ltd
 | 
			
		||||
Copyright 2019 The Matrix.org Foundation C.I.C.
 | 
			
		||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
 | 
			
		||||
Copyright 2019, 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +24,6 @@ import PropTypes from 'prop-types';
 | 
			
		|||
import createReactClass from 'create-react-class';
 | 
			
		||||
const classNames = require("classnames");
 | 
			
		||||
import { _t, _td } from '../../../languageHandler';
 | 
			
		||||
const Modal = require('../../../Modal');
 | 
			
		||||
 | 
			
		||||
const sdk = require('../../../index');
 | 
			
		||||
const TextForEvent = require('../../../TextForEvent');
 | 
			
		||||
| 
						 | 
				
			
			@ -443,15 +442,6 @@ module.exports = createReactClass({
 | 
			
		|||
        });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onCryptoClick: function(e) {
 | 
			
		||||
        const event = this.props.mxEvent;
 | 
			
		||||
 | 
			
		||||
        Modal.createTrackedDialogAsync('Encrypted Event Dialog', '',
 | 
			
		||||
            import('../../../async-components/views/dialogs/EncryptedEventDialog'),
 | 
			
		||||
            {event},
 | 
			
		||||
        );
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    onRequestKeysClick: function() {
 | 
			
		||||
        this.setState({
 | 
			
		||||
            // Indicate in the UI that the keys have been requested (this is expected to
 | 
			
		||||
| 
						 | 
				
			
			@ -479,11 +469,10 @@ module.exports = createReactClass({
 | 
			
		|||
 | 
			
		||||
    _renderE2EPadlock: function() {
 | 
			
		||||
        const ev = this.props.mxEvent;
 | 
			
		||||
        const props = {onClick: this.onCryptoClick};
 | 
			
		||||
 | 
			
		||||
        // event could not be decrypted
 | 
			
		||||
        if (ev.getContent().msgtype === 'm.bad.encrypted') {
 | 
			
		||||
            return <E2ePadlockUndecryptable {...props} />;
 | 
			
		||||
            return <E2ePadlockUndecryptable />;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // event is encrypted, display padlock corresponding to whether or not it is verified
 | 
			
		||||
| 
						 | 
				
			
			@ -491,7 +480,7 @@ module.exports = createReactClass({
 | 
			
		|||
            if (this.state.verified) {
 | 
			
		||||
                return; // no icon for verified
 | 
			
		||||
            } else {
 | 
			
		||||
                return (<E2ePadlockUnverified {...props} />);
 | 
			
		||||
                return (<E2ePadlockUnverified />);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -508,7 +497,7 @@ module.exports = createReactClass({
 | 
			
		|||
                return; // we expect this to be unencrypted
 | 
			
		||||
            }
 | 
			
		||||
            // if the event is not encrypted, but it's an e2e room, show the open padlock
 | 
			
		||||
            return <E2ePadlockUnencrypted {...props} />;
 | 
			
		||||
            return <E2ePadlockUnencrypted />;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // no padlock needed
 | 
			
		||||
| 
						 | 
				
			
			@ -920,7 +909,6 @@ class E2ePadlock extends React.Component {
 | 
			
		|||
    static propTypes = {
 | 
			
		||||
        icon: PropTypes.string.isRequired,
 | 
			
		||||
        title: PropTypes.string.isRequired,
 | 
			
		||||
        onClick: PropTypes.func,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
| 
						 | 
				
			
			@ -931,10 +919,6 @@ class E2ePadlock extends React.Component {
 | 
			
		|||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onClick = (e) => {
 | 
			
		||||
        if (this.props.onClick) this.props.onClick(e);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    onHoverStart = () => {
 | 
			
		||||
        this.setState({hover: true});
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,8 @@ import classNames from 'classnames';
 | 
			
		|||
export default class MemberDeviceInfo extends React.Component {
 | 
			
		||||
    render() {
 | 
			
		||||
        const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons');
 | 
			
		||||
        // XXX: These checks are not cross-signing aware but this component is only used
 | 
			
		||||
        // from the old, pre-cross-signing memberinfopanel
 | 
			
		||||
        const iconClasses = classNames({
 | 
			
		||||
            mx_MemberDeviceInfo_icon: true,
 | 
			
		||||
            mx_MemberDeviceInfo_icon_blacklisted: this.props.device.isBlocked(),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2018, 2019 New Vector Ltd
 | 
			
		||||
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.
 | 
			
		||||
| 
						 | 
				
			
			@ -70,10 +71,14 @@ export default class RoomRecoveryReminder extends React.PureComponent {
 | 
			
		|||
            // verified, so restore the backup which will give us the keys from it and
 | 
			
		||||
            // allow us to trust it (ie. upload keys to it)
 | 
			
		||||
            const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
 | 
			
		||||
            Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {});
 | 
			
		||||
            Modal.createTrackedDialog(
 | 
			
		||||
                'Restore Backup', '', RestoreKeyBackupDialog, null, null,
 | 
			
		||||
                /* priority = */ false, /* static = */ true,
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
 | 
			
		||||
                import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"),
 | 
			
		||||
                null, null, /* priority = */ false, /* static = */ true,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -150,14 +155,14 @@ export default class RoomRecoveryReminder extends React.PureComponent {
 | 
			
		|||
                        onClick={this.onSetupClick}>
 | 
			
		||||
                        {setupCaption}
 | 
			
		||||
                    </AccessibleButton>
 | 
			
		||||
                    <p><AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
 | 
			
		||||
                    <AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
 | 
			
		||||
                        onClick={this.onOnNotNowClick}>
 | 
			
		||||
                        { _t("Not now") }
 | 
			
		||||
                    </AccessibleButton></p>
 | 
			
		||||
                    <p><AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
 | 
			
		||||
                    </AccessibleButton>
 | 
			
		||||
                    <AccessibleButton className="mx_RoomRecoveryReminder_secondary mx_linkButton"
 | 
			
		||||
                        onClick={this.onDontAskAgainClick}>
 | 
			
		||||
                        { _t("Don't ask me again") }
 | 
			
		||||
                    </AccessibleButton></p>
 | 
			
		||||
                    </AccessibleButton>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2018 New Vector Ltd
 | 
			
		||||
Copyright 2019 The Matrix.org Foundation C.I.C.
 | 
			
		||||
Copyright 2019, 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -22,7 +22,6 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
 | 
			
		|||
import { _t } from '../../../languageHandler';
 | 
			
		||||
import Modal from '../../../Modal';
 | 
			
		||||
import SettingsStore from '../../../../lib/settings/SettingsStore';
 | 
			
		||||
import { accessSecretStorage } from '../../../CrossSigningManager';
 | 
			
		||||
 | 
			
		||||
export default class KeyBackupPanel extends React.PureComponent {
 | 
			
		||||
    constructor(props) {
 | 
			
		||||
| 
						 | 
				
			
			@ -128,36 +127,24 @@ export default class KeyBackupPanel extends React.PureComponent {
 | 
			
		|||
        Modal.createTrackedDialogAsync('Key Backup', 'Key Backup',
 | 
			
		||||
            import('../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'),
 | 
			
		||||
            {
 | 
			
		||||
                secureSecretStorage: false,
 | 
			
		||||
                onFinished: () => {
 | 
			
		||||
                    this._loadBackupStatus();
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            }, null, /* priority = */ false, /* static = */ true,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _startNewBackupWithSecureSecretStorage = async () => {
 | 
			
		||||
        const cli = MatrixClientPeg.get();
 | 
			
		||||
        let info;
 | 
			
		||||
        try {
 | 
			
		||||
            await accessSecretStorage(async () => {
 | 
			
		||||
                info = await cli.prepareKeyBackupVersion(
 | 
			
		||||
                    null /* random key */,
 | 
			
		||||
                    { secureSecretStorage: true },
 | 
			
		||||
                );
 | 
			
		||||
                info = await cli.createKeyBackupVersion(info);
 | 
			
		||||
            });
 | 
			
		||||
            await MatrixClientPeg.get().scheduleAllGroupSessionsForBackup();
 | 
			
		||||
            this._loadBackupStatus();
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            console.error("Error creating key backup", e);
 | 
			
		||||
            // TODO: If creating a version succeeds, but backup fails, should we
 | 
			
		||||
            // delete the version, disable backup, or do nothing?  If we just
 | 
			
		||||
            // disable without deleting, we'll enable on next app reload since
 | 
			
		||||
            // it is trusted.
 | 
			
		||||
            if (info && info.version) {
 | 
			
		||||
                MatrixClientPeg.get().deleteKeyBackupVersion(info.version);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Modal.createTrackedDialogAsync('Key Backup', 'Key Backup',
 | 
			
		||||
            import('../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'),
 | 
			
		||||
            {
 | 
			
		||||
                secureSecretStorage: true,
 | 
			
		||||
                onFinished: () => {
 | 
			
		||||
                    this._loadBackupStatus();
 | 
			
		||||
                },
 | 
			
		||||
            }, null, /* priority = */ false, /* static = */ true,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _deleteBackup = () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -181,22 +168,11 @@ export default class KeyBackupPanel extends React.PureComponent {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    _restoreBackup = async () => {
 | 
			
		||||
        // Use legacy path if backup key not stored in secret storage
 | 
			
		||||
        if (!this.state.backupKeyStored) {
 | 
			
		||||
            const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
 | 
			
		||||
            Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await accessSecretStorage(async () => {
 | 
			
		||||
                await MatrixClientPeg.get().restoreKeyBackupWithSecretStorage(
 | 
			
		||||
                    this.state.backupInfo,
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            console.log("Error restoring backup", e);
 | 
			
		||||
        }
 | 
			
		||||
        const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
 | 
			
		||||
        Modal.createTrackedDialog(
 | 
			
		||||
            'Restore Backup', '', RestoreKeyBackupDialog, null, null,
 | 
			
		||||
            /* priority = */ false, /* static = */ true,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
| 
						 | 
				
			
			@ -270,7 +246,7 @@ export default class KeyBackupPanel extends React.PureComponent {
 | 
			
		|||
                        {sub}
 | 
			
		||||
                    </span>;
 | 
			
		||||
                const verify = sub =>
 | 
			
		||||
                    <span className={sig.device && sig.device.isVerified() ? 'mx_KeyBackupPanel_deviceVerified' : 'mx_KeyBackupPanel_deviceNotVerified'}>
 | 
			
		||||
                    <span className={sig.device && sig.deviceTrust.isVerified() ? 'mx_KeyBackupPanel_deviceVerified' : 'mx_KeyBackupPanel_deviceNotVerified'}>
 | 
			
		||||
                        {sub}
 | 
			
		||||
                    </span>;
 | 
			
		||||
                const device = sub => <span className="mx_KeyBackupPanel_deviceName">{deviceName}</span>;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -358,6 +358,7 @@
 | 
			
		|||
    "Render simple counters in room header": "Render simple counters in room header",
 | 
			
		||||
    "Multiple integration managers": "Multiple integration managers",
 | 
			
		||||
    "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)",
 | 
			
		||||
    "New DM invite dialog (under development)": "New DM invite dialog (under development)",
 | 
			
		||||
    "Enable cross-signing to verify per-user instead of per-device (in development)": "Enable cross-signing to verify per-user instead of per-device (in development)",
 | 
			
		||||
    "Enable local event indexing and E2EE search (requires restart)": "Enable local event indexing and E2EE search (requires restart)",
 | 
			
		||||
    "Use the new, faster, composer for writing messages": "Use the new, faster, composer for writing messages",
 | 
			
		||||
| 
						 | 
				
			
			@ -1114,7 +1115,6 @@
 | 
			
		|||
    "%(count)s verified sessions|other": "%(count)s verified sessions",
 | 
			
		||||
    "%(count)s verified sessions|one": "1 verified session",
 | 
			
		||||
    "Direct message": "Direct message",
 | 
			
		||||
    "Unverify user": "Unverify user",
 | 
			
		||||
    "Remove from community": "Remove from community",
 | 
			
		||||
    "Disinvite this user from community?": "Disinvite this user from community?",
 | 
			
		||||
    "Remove this user from community?": "Remove this user from community?",
 | 
			
		||||
| 
						 | 
				
			
			@ -1431,6 +1431,11 @@
 | 
			
		|||
    "View Servers in Room": "View Servers in Room",
 | 
			
		||||
    "Toolbox": "Toolbox",
 | 
			
		||||
    "Developer Tools": "Developer Tools",
 | 
			
		||||
    "Show more": "Show more",
 | 
			
		||||
    "Recent Conversations": "Recent Conversations",
 | 
			
		||||
    "Direct Messages": "Direct Messages",
 | 
			
		||||
    "If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.": "If you can't find someone, ask them for their username, or share your username (%(userId)s) or <a>profile link</a>.",
 | 
			
		||||
    "Go": "Go",
 | 
			
		||||
    "An error has occurred.": "An error has occurred.",
 | 
			
		||||
    "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.",
 | 
			
		||||
    "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.": "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,6 +27,7 @@ import rageshake from './rageshake';
 | 
			
		|||
 | 
			
		||||
// polyfill textencoder if necessary
 | 
			
		||||
import * as TextEncodingUtf8 from 'text-encoding-utf-8';
 | 
			
		||||
import SettingsStore from "../settings/SettingsStore";
 | 
			
		||||
let TextEncoder = window.TextEncoder;
 | 
			
		||||
if (!TextEncoder) {
 | 
			
		||||
    TextEncoder = TextEncodingUtf8.TextEncoder;
 | 
			
		||||
| 
						 | 
				
			
			@ -85,6 +86,12 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
 | 
			
		|||
        body.append('label', opts.label);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // add labs options
 | 
			
		||||
    const enabledLabs = SettingsStore.getLabsFeatures().filter(SettingsStore.isFeatureEnabled);
 | 
			
		||||
    if (enabledLabs.length) {
 | 
			
		||||
        body.append('enabled_labs', enabledLabs.join(', '));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (opts.sendLogs) {
 | 
			
		||||
        progressCallback(_t("Collecting logs"));
 | 
			
		||||
        const logs = await rageshake.getLogsForReport();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -128,6 +128,12 @@ export const SETTINGS = {
 | 
			
		|||
        supportedLevels: LEVELS_FEATURE,
 | 
			
		||||
        default: false,
 | 
			
		||||
    },
 | 
			
		||||
    "feature_ftue_dms": {
 | 
			
		||||
        isFeature: true,
 | 
			
		||||
        displayName: _td("New DM invite dialog (under development)"),
 | 
			
		||||
        supportedLevels: LEVELS_FEATURE,
 | 
			
		||||
        default: false,
 | 
			
		||||
    },
 | 
			
		||||
    "mjolnirRooms": {
 | 
			
		||||
        supportedLevels: ['account'],
 | 
			
		||||
        default: [],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2016 OpenMarket Ltd
 | 
			
		||||
Copyright 2019, 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +17,7 @@ limitations under the License.
 | 
			
		|||
 | 
			
		||||
import MatrixClientPeg from '../MatrixClientPeg';
 | 
			
		||||
import _uniq from 'lodash/uniq';
 | 
			
		||||
import {Room} from "matrix-js-sdk/lib/matrix";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class that takes a Matrix Client and flips the m.direct map
 | 
			
		||||
| 
						 | 
				
			
			@ -144,6 +146,13 @@ export default class DMRoomMap {
 | 
			
		|||
        return this.roomToUser[roomId];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getUniqueRoomsWithIndividuals(): {[userId: string]: Room} {
 | 
			
		||||
        return Object.keys(this.roomToUser)
 | 
			
		||||
            .map(r => ({userId: this.getUserIdForRoomId(r), room: this.matrixClient.getRoom(r)}))
 | 
			
		||||
            .filter(r => r.userId && r.room && r.room.getInvitedAndJoinedMemberCount() === 2)
 | 
			
		||||
            .reduce((obj, r) => (obj[r.userId] = r.room) && obj, {});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    _getUserToRooms() {
 | 
			
		||||
        if (!this.userToRooms) {
 | 
			
		||||
            const userToRooms = this.mDirectEvent;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4094,6 +4094,11 @@ humanize-ms@^1.2.1:
 | 
			
		|||
  dependencies:
 | 
			
		||||
    ms "^2.0.0"
 | 
			
		||||
 | 
			
		||||
humanize@^0.0.9:
 | 
			
		||||
  version "0.0.9"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/humanize/-/humanize-0.0.9.tgz#1994ffaecdfe9c441ed2bdac7452b7bb4c9e41a4"
 | 
			
		||||
  integrity sha1-GZT/rs3+nEQe0r2sdFK3u0yeQaQ=
 | 
			
		||||
 | 
			
		||||
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
 | 
			
		||||
  version "0.4.24"
 | 
			
		||||
  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue