Merge branches 'develop' and 't3chguy/cs_verification_decoration' of https://github.com/matrix-org/matrix-react-sdk into t3chguy/cs_verification_decoration
This commit is contained in:
commit
76e61b9948
38 changed files with 1195 additions and 254 deletions
|
@ -2,19 +2,16 @@ module.exports = {
|
|||
"sourceMaps": "inline",
|
||||
"presets": [
|
||||
["@babel/preset-env", {
|
||||
"targets": {
|
||||
"browsers": [
|
||||
"last 2 versions"
|
||||
]
|
||||
},
|
||||
"modules": "commonjs"
|
||||
"targets": [
|
||||
"last 2 Chrome versions", "last 2 Firefox versions", "last 2 Safari versions"
|
||||
],
|
||||
}],
|
||||
"@babel/preset-typescript",
|
||||
"@babel/preset-flow",
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": [
|
||||
["@babel/plugin-proposal-decorators", { "legacy": true }],
|
||||
["@babel/plugin-proposal-decorators", {legacy: true}],
|
||||
"@babel/plugin-proposal-export-default-from",
|
||||
"@babel/plugin-proposal-numeric-separator",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
"is-ip": "^2.0.0",
|
||||
"linkifyjs": "^2.1.6",
|
||||
"lodash": "^4.17.14",
|
||||
"matrix-js-sdk": "4.0.0",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||
"pako": "^1.0.5",
|
||||
"png-chunks-extract": "^1.0.0",
|
||||
"prop-types": "^15.5.8",
|
||||
|
|
|
@ -428,6 +428,11 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
|||
color: $accent-fg-color;
|
||||
}
|
||||
|
||||
.mx_Dialog button.warning, .mx_Dialog input[type="submit"].warning {
|
||||
border: solid 1px $warning-color;
|
||||
color: $warning-color;
|
||||
}
|
||||
|
||||
.mx_Dialog button:disabled, .mx_Dialog input[type="submit"]:disabled, .mx_Dialog_buttons button:disabled, .mx_Dialog_buttons input[type="submit"]:disabled {
|
||||
background-color: $light-fg-color;
|
||||
border: solid 1px $light-fg-color;
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
@import "./views/auth/_AuthHeader.scss";
|
||||
@import "./views/auth/_AuthHeaderLogo.scss";
|
||||
@import "./views/auth/_AuthPage.scss";
|
||||
@import "./views/auth/_CompleteSecurityBody.scss";
|
||||
@import "./views/auth/_CountryDropdown.scss";
|
||||
@import "./views/auth/_InteractiveAuthEntryComponents.scss";
|
||||
@import "./views/auth/_LanguageSelector.scss";
|
||||
|
@ -65,7 +66,9 @@
|
|||
@import "./views/dialogs/_IncomingSasDialog.scss";
|
||||
@import "./views/dialogs/_InviteDialog.scss";
|
||||
@import "./views/dialogs/_MessageEditHistoryDialog.scss";
|
||||
@import "./views/dialogs/_NewSessionReviewDialog.scss";
|
||||
@import "./views/dialogs/_RoomSettingsDialog.scss";
|
||||
@import "./views/dialogs/_RoomSettingsDialogBridges.scss";
|
||||
@import "./views/dialogs/_RoomUpgradeDialog.scss";
|
||||
@import "./views/dialogs/_RoomUpgradeWarningDialog.scss";
|
||||
@import "./views/dialogs/_SetEmailDialog.scss";
|
||||
|
@ -150,10 +153,10 @@
|
|||
@import "./views/rooms/_AuxPanel.scss";
|
||||
@import "./views/rooms/_BasicMessageComposer.scss";
|
||||
@import "./views/rooms/_E2EIcon.scss";
|
||||
@import "./views/rooms/_InviteOnlyIcon.scss";
|
||||
@import "./views/rooms/_EditMessageComposer.scss";
|
||||
@import "./views/rooms/_EntityTile.scss";
|
||||
@import "./views/rooms/_EventTile.scss";
|
||||
@import "./views/rooms/_InviteOnlyIcon.scss";
|
||||
@import "./views/rooms/_JumpToBottomButton.scss";
|
||||
@import "./views/rooms/_LinkPreviewWidget.scss";
|
||||
@import "./views/rooms/_MemberDeviceInfo.scss";
|
||||
|
|
|
@ -22,7 +22,7 @@ limitations under the License.
|
|||
.mx_CompleteSecurity_headerIcon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 0 4px;
|
||||
margin-right: 4px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -15,6 +16,9 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
.mx_AuthBody {
|
||||
width: 500px;
|
||||
font-size: 12px;
|
||||
color: $authpage-secondary-color;
|
||||
background-color: $authpage-body-bg-color;
|
||||
border-radius: 0 4px 4px 0;
|
||||
padding: 25px 60px;
|
||||
|
@ -92,16 +96,6 @@ limitations under the License.
|
|||
}
|
||||
}
|
||||
|
||||
.mx_AuthBody_noHeader {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mx_AuthBody_loginRegister {
|
||||
width: 500px;
|
||||
font-size: 12px;
|
||||
color: $authpage-secondary-color;
|
||||
}
|
||||
|
||||
.mx_AuthBody_editServerDetails {
|
||||
padding-left: 1em;
|
||||
font-size: 12px;
|
||||
|
|
42
res/css/views/auth/_CompleteSecurityBody.scss
Normal file
42
res/css/views/auth/_CompleteSecurityBody.scss
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
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.
|
||||
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_CompleteSecurityBody {
|
||||
width: 600px;
|
||||
color: $authpage-primary-color;
|
||||
background-color: $authpage-body-bg-color;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
a:link,
|
||||
a:hover,
|
||||
a:visited {
|
||||
@mixin mx_Dialog_link;
|
||||
}
|
||||
}
|
37
res/css/views/dialogs/_NewSessionReviewDialog.scss
Normal file
37
res/css/views/dialogs/_NewSessionReviewDialog.scss
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
.mx_NewSessionReviewDialog_header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mx_NewSessionReviewDialog_headerIcon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 4px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mx_NewSessionReviewDialog_deviceName {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.mx_NewSessionReviewDialog_deviceID {
|
||||
font-size: 12px;
|
||||
color: $notice-secondary-color;
|
||||
}
|
|
@ -56,16 +56,3 @@ limitations under the License.
|
|||
mask-position: center;
|
||||
}
|
||||
|
||||
.mx_RoomSettingsDialog_BridgeList {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mx_RoomSettingsDialog_BridgeList li {
|
||||
list-style-type: none;
|
||||
padding: 5px;
|
||||
margin-bottom: 5px;
|
||||
border-width: 1px 0px;
|
||||
border-color: #dee1f3;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
|
|
112
res/css/views/dialogs/_RoomSettingsDialogBridges.scss
Normal file
112
res/css/views/dialogs/_RoomSettingsDialogBridges.scss
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
.mx_RoomSettingsDialog_BridgeList {
|
||||
padding: 0;
|
||||
|
||||
.mx_AccessibleButton {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomSettingsDialog_BridgeList li {
|
||||
list-style-type: none;
|
||||
padding: 5px;
|
||||
margin-bottom: 8px;
|
||||
border-width: 1px 1px;
|
||||
border-color: $primary-hairline-color;
|
||||
border-style: solid;
|
||||
border-radius: 5px;
|
||||
|
||||
.column-icon {
|
||||
float: left;
|
||||
padding-right: 10px;
|
||||
|
||||
* {
|
||||
border-radius: 5px;
|
||||
border: 1px solid $input-darker-bg-color;
|
||||
}
|
||||
|
||||
.noProtocolIcon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: $input-darker-bg-color;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.protocol-icon {
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
img {
|
||||
border-radius: 5px;
|
||||
border-width: 1px 1px;
|
||||
border-color: $primary-hairline-color;
|
||||
}
|
||||
span {
|
||||
/* Correct letter placement */
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column-data {
|
||||
display: inline-block;
|
||||
width: 85%;
|
||||
|
||||
> h3 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
font-size: 16pt;
|
||||
color: $primary-fg-color;
|
||||
}
|
||||
|
||||
> * {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.workspace-channel-details {
|
||||
color: $primary-fg-color;
|
||||
font-weight: 600;
|
||||
|
||||
.channel {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_showMore {
|
||||
display: block;
|
||||
text-align: left;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.metadata {
|
||||
color: $muted-fg-color;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.metadata.visible {
|
||||
overflow-y: visible;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ class Skinner {
|
|||
}
|
||||
|
||||
getComponent(name) {
|
||||
if (!name) throw new Error(`Invalid component name: ${name}`);
|
||||
if (this.components === null) {
|
||||
throw new Error(
|
||||
"Attempted to get a component before a skin has been loaded."+
|
||||
|
@ -41,13 +42,7 @@ class Skinner {
|
|||
};
|
||||
|
||||
// Check the skin first
|
||||
let comp = doLookup(this.components);
|
||||
|
||||
// If that failed, check against our own components
|
||||
if (!comp) {
|
||||
// Lazily load our own components because they might end up calling .getComponent()
|
||||
comp = doLookup(require("./component-index").components);
|
||||
}
|
||||
const comp = doLookup(this.components);
|
||||
|
||||
// Just return nothing instead of erroring - the consumer should be smart enough to
|
||||
// handle this at this point.
|
||||
|
@ -75,6 +70,13 @@ class Skinner {
|
|||
const comp = skinObject.components[compKeys[i]];
|
||||
this.addComponent(compKeys[i], comp);
|
||||
}
|
||||
|
||||
// Now that we have a skin, load our components too
|
||||
const idx = require("./component-index");
|
||||
if (!idx || !idx.components) throw new Error("Invalid react-sdk component index");
|
||||
for (const c in idx.components) {
|
||||
if (!this.components[c]) this.components[c] = idx.components[c];
|
||||
}
|
||||
}
|
||||
|
||||
addComponent(name, comp) {
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
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 * as sdk from '../../../../index';
|
||||
import PropTypes from 'prop-types';
|
||||
import dis from "../../../../dispatcher";
|
||||
import { _t } from '../../../../languageHandler';
|
||||
|
||||
import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore";
|
||||
import EventIndexPeg from "../../../../indexing/EventIndexPeg";
|
||||
|
||||
/*
|
||||
* Allows the user to disable the Event Index.
|
||||
*/
|
||||
export default class DisableEventIndexDialog extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
disabling: false,
|
||||
};
|
||||
}
|
||||
|
||||
_onDisable = async () => {
|
||||
this.setState({
|
||||
disabling: true,
|
||||
});
|
||||
|
||||
await SettingsStore.setValue('enableEventIndexing', null, SettingLevel.DEVICE, false);
|
||||
await EventIndexPeg.deleteEventIndex();
|
||||
this.props.onFinished();
|
||||
dis.dispatch({ action: 'view_user_settings' });
|
||||
}
|
||||
|
||||
render() {
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const Spinner = sdk.getComponent('elements.Spinner');
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
|
||||
return (
|
||||
<BaseDialog onFinished={this.props.onFinished} title={_t("Are you sure?")}>
|
||||
{_t("If disabled, messages from encrypted rooms won't appear in search results.")}
|
||||
{this.state.disabling ? <Spinner /> : <div />}
|
||||
<DialogButtons
|
||||
primaryButton={_t('Disable')}
|
||||
onPrimaryButtonClick={this._onDisable}
|
||||
primaryButtonClass="danger"
|
||||
cancelButtonClass="warning"
|
||||
onCancel={this.props.onFinished}
|
||||
disabled={this.state.disabling}
|
||||
/>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
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 * as sdk from '../../../../index';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../../languageHandler';
|
||||
|
||||
import Modal from '../../../../Modal';
|
||||
import {formatBytes, formatCountLong} from "../../../../utils/FormattingUtils";
|
||||
import EventIndexPeg from "../../../../indexing/EventIndexPeg";
|
||||
|
||||
/*
|
||||
* Allows the user to introspect the event index state and disable it.
|
||||
*/
|
||||
export default class ManageEventIndexDialog extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
eventIndexSize: 0,
|
||||
eventCount: 0,
|
||||
roomCount: 0,
|
||||
currentRoom: null,
|
||||
};
|
||||
}
|
||||
|
||||
async updateCurrentRoom(room) {
|
||||
const eventIndex = EventIndexPeg.get();
|
||||
const stats = await eventIndex.getStats();
|
||||
let currentRoom = null;
|
||||
|
||||
if (room) currentRoom = room.name;
|
||||
|
||||
this.setState({
|
||||
eventIndexSize: stats.size,
|
||||
roomCount: stats.roomCount,
|
||||
eventCount: stats.eventCount,
|
||||
currentRoom: currentRoom,
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
const eventIndex = EventIndexPeg.get();
|
||||
|
||||
if (eventIndex !== null) {
|
||||
eventIndex.removeListener("changedCheckpoint", this.updateCurrentRoom.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
async componentWillMount(): void {
|
||||
let eventIndexSize = 0;
|
||||
let roomCount = 0;
|
||||
let eventCount = 0;
|
||||
let currentRoom = null;
|
||||
|
||||
const eventIndex = EventIndexPeg.get();
|
||||
|
||||
if (eventIndex !== null) {
|
||||
eventIndex.on("changedCheckpoint", this.updateCurrentRoom.bind(this));
|
||||
|
||||
const stats = await eventIndex.getStats();
|
||||
eventIndexSize = stats.size;
|
||||
roomCount = stats.roomCount;
|
||||
eventCount = stats.eventCount;
|
||||
|
||||
const room = eventIndex.currentRoom();
|
||||
if (room) currentRoom = room.name;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
eventIndexSize,
|
||||
eventCount,
|
||||
roomCount,
|
||||
currentRoom,
|
||||
});
|
||||
}
|
||||
|
||||
_onDisable = async () => {
|
||||
Modal.createTrackedDialogAsync("Disable message search", "Disable message search",
|
||||
import("./DisableEventIndexDialog"),
|
||||
null, null, /* priority = */ false, /* static = */ true,
|
||||
);
|
||||
}
|
||||
|
||||
_onDone = () => {
|
||||
this.props.onFinished(true);
|
||||
}
|
||||
|
||||
render() {
|
||||
let crawlerState;
|
||||
|
||||
if (this.state.currentRoom === null) {
|
||||
crawlerState = _t("Not currently downloading messages for any room.");
|
||||
} else {
|
||||
crawlerState = (
|
||||
_t("Downloading mesages for %(currentRoom)s.", { currentRoom: this.state.currentRoom })
|
||||
);
|
||||
}
|
||||
|
||||
const eventIndexingSettings = (
|
||||
<div>
|
||||
{
|
||||
_t( "Riot is securely caching encrypted messages locally for them " +
|
||||
"to appear in search results:",
|
||||
)
|
||||
}
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_t("Space used:")} {formatBytes(this.state.eventIndexSize, 0)}<br />
|
||||
{_t("Indexed messages:")} {formatCountLong(this.state.eventCount)}<br />
|
||||
{_t("Number of rooms:")} {formatCountLong(this.state.roomCount)}<br />
|
||||
{crawlerState}<br />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
|
||||
return (
|
||||
<BaseDialog className='mx_ManageEventIndexDialog'
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t("Message search")}
|
||||
>
|
||||
{eventIndexingSettings}
|
||||
<DialogButtons
|
||||
primaryButton={_t("Done")}
|
||||
onPrimaryButtonClick={this.props.onFinished}
|
||||
primaryButtonClass="primary"
|
||||
cancelButton={_t("Disable")}
|
||||
onCancel={this._onDisable}
|
||||
cancelButtonClass="danger"
|
||||
/>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1834,6 +1834,7 @@ export default createReactClass({
|
|||
this._accountPassword = null;
|
||||
this._accountPasswordTimer = null;
|
||||
}, 60 * 5 * 1000);
|
||||
|
||||
// Wait for the client to be logged in (but not started)
|
||||
// which is enough to ask the server about account data.
|
||||
const loggedIn = new Promise(resolve => {
|
||||
|
@ -1867,6 +1868,9 @@ export default createReactClass({
|
|||
}
|
||||
|
||||
if (masterKeyInStorage) {
|
||||
// Auto-enable cross-signing for the new session when key found in
|
||||
// secret storage.
|
||||
SettingsStore.setFeatureEnabled("feature_cross_signing", true);
|
||||
this.setStateForNewView({ view: VIEWS.COMPLETE_SECURITY });
|
||||
} else if (SettingsStore.isFeatureEnabled("feature_cross_signing")) {
|
||||
// This will only work if the feature is set to 'enable' in the config,
|
||||
|
|
|
@ -811,7 +811,7 @@ export default createReactClass({
|
|||
debuglog("e2e verified", verified, "unverified", unverified);
|
||||
|
||||
/* Check all verified user devices. */
|
||||
for (const userId of verified) {
|
||||
for (const userId of [...verified, cli.getUserId()]) {
|
||||
const devices = await cli.getStoredDevicesForUser(userId);
|
||||
const anyDeviceNotVerified = devices.some(({deviceId}) => {
|
||||
return !cli.checkDeviceTrust(userId, deviceId).isVerified();
|
||||
|
|
|
@ -112,7 +112,7 @@ export default class CompleteSecurity extends React.Component {
|
|||
|
||||
render() {
|
||||
const AuthPage = sdk.getComponent("auth.AuthPage");
|
||||
const AuthBody = sdk.getComponent("auth.AuthBody");
|
||||
const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody");
|
||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||
|
||||
const {
|
||||
|
@ -204,7 +204,7 @@ export default class CompleteSecurity extends React.Component {
|
|||
|
||||
return (
|
||||
<AuthPage>
|
||||
<AuthBody header={false}>
|
||||
<CompleteSecurityBody>
|
||||
<h2 className="mx_CompleteSecurity_header">
|
||||
{icon}
|
||||
{title}
|
||||
|
@ -212,7 +212,7 @@ export default class CompleteSecurity extends React.Component {
|
|||
<div className="mx_CompleteSecurity_body">
|
||||
{body}
|
||||
</div>
|
||||
</AuthBody>
|
||||
</CompleteSecurityBody>
|
||||
</AuthPage>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -34,16 +34,16 @@ export default class E2eSetup extends React.Component {
|
|||
|
||||
render() {
|
||||
const AuthPage = sdk.getComponent("auth.AuthPage");
|
||||
const AuthBody = sdk.getComponent("auth.AuthBody");
|
||||
const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody");
|
||||
return (
|
||||
<AuthPage>
|
||||
<AuthBody header={false}>
|
||||
<CompleteSecurityBody>
|
||||
<AsyncWrapper prom={this._createStorageDialogPromise}
|
||||
hasCancel={false}
|
||||
onFinished={this.props.onFinished}
|
||||
accountPassword={this.props.accountPassword}
|
||||
/>
|
||||
</AuthBody>
|
||||
</CompleteSecurityBody>
|
||||
</AuthPage>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,29 +17,10 @@ limitations under the License.
|
|||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
export default class AuthBody extends React.PureComponent {
|
||||
static PropTypes = {
|
||||
header: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
header: true,
|
||||
};
|
||||
|
||||
render() {
|
||||
const classes = {
|
||||
'mx_AuthBody': true,
|
||||
'mx_AuthBody_noHeader': !this.props.header,
|
||||
// XXX The login pages all use a smaller fonts size but we don't want this
|
||||
// for subsequent auth screens like the e2e setup. Doing this a terrible way
|
||||
// for now.
|
||||
'mx_AuthBody_loginRegister': this.props.header,
|
||||
};
|
||||
|
||||
return <div className={classnames(classes)}>
|
||||
return <div className="mx_AuthBody">
|
||||
{ this.props.children }
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ export default createReactClass({
|
|||
console.log("Loading recaptcha script...");
|
||||
window.mx_on_recaptcha_loaded = () => {this._onCaptchaLoaded();};
|
||||
let protocol = global.location.protocol;
|
||||
if (protocol === "vector:") {
|
||||
if (protocol !== "http:") {
|
||||
protocol = "https:";
|
||||
}
|
||||
const scriptTag = document.createElement('script');
|
||||
|
|
27
src/components/views/auth/CompleteSecurityBody.js
Normal file
27
src/components/views/auth/CompleteSecurityBody.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default class CompleteSecurityBody extends React.PureComponent {
|
||||
render() {
|
||||
return <div className="mx_CompleteSecurityBody">
|
||||
{ this.props.children }
|
||||
</div>;
|
||||
}
|
||||
}
|
91
src/components/views/dialogs/NewSessionReviewDialog.js
Normal file
91
src/components/views/dialogs/NewSessionReviewDialog.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
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 Modal from '../../../Modal';
|
||||
import { replaceableComponent } from '../../../utils/replaceableComponent';
|
||||
import DeviceVerifyDialog from './DeviceVerifyDialog';
|
||||
import BaseDialog from './BaseDialog';
|
||||
import DialogButtons from '../elements/DialogButtons';
|
||||
|
||||
@replaceableComponent("views.dialogs.NewSessionReviewDialog")
|
||||
export default class NewSessionReviewDialog extends React.PureComponent {
|
||||
static propTypes = {
|
||||
userId: PropTypes.string.isRequired,
|
||||
device: PropTypes.object.isRequired,
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
onCancelClick = () => {
|
||||
this.props.onFinished(false);
|
||||
}
|
||||
|
||||
onContinueClick = () => {
|
||||
const { userId, device } = this.props;
|
||||
Modal.createTrackedDialog('New Session Verification', 'Starting dialog', DeviceVerifyDialog, {
|
||||
userId,
|
||||
device,
|
||||
}, null, /* priority = */ false, /* static = */ true);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { device } = this.props;
|
||||
|
||||
const icon = <span className="mx_NewSessionReviewDialog_headerIcon mx_E2EIcon_warning"></span>;
|
||||
const titleText = _t("New session");
|
||||
|
||||
const title = <h2 className="mx_NewSessionReviewDialog_header">
|
||||
{icon}
|
||||
{titleText}
|
||||
</h2>;
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
title={title}
|
||||
onFinished={this.props.onFinished}
|
||||
>
|
||||
<div className="mx_NewSessionReviewDialog_body">
|
||||
<p>{_t(
|
||||
"Use this session to verify your new one, " +
|
||||
"granting it access to encrypted messages:",
|
||||
)}</p>
|
||||
<div className="mx_NewSessionReviewDialog_deviceInfo">
|
||||
<div>
|
||||
<span className="mx_NewSessionReviewDialog_deviceName">
|
||||
{device.getDisplayName()}
|
||||
</span> <span className="mx_NewSessionReviewDialog_deviceID">
|
||||
({device.deviceId})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p>{_t(
|
||||
"If you didn’t sign in to this session, " +
|
||||
"your account may be compromised.",
|
||||
)}</p>
|
||||
<DialogButtons
|
||||
cancelButton={_t("This wasn't me")}
|
||||
cancelButtonClass="danger"
|
||||
primaryButton={_t("Continue")}
|
||||
onCancel={this.onCancelClick}
|
||||
onPrimaryButtonClick={this.onContinueClick}
|
||||
/>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -54,9 +54,6 @@ export default class RoomSettingsDialog extends React.Component {
|
|||
|
||||
_getTabs() {
|
||||
const tabs = [];
|
||||
const featureFlag = SettingsStore.isFeatureEnabled("feature_bridge_state");
|
||||
const shouldShowBridgeIcon = featureFlag &&
|
||||
BridgeSettingsTab.getBridgeStateEvents(this.props.roomId).length > 0;
|
||||
|
||||
tabs.push(new Tab(
|
||||
_td("General"),
|
||||
|
@ -79,9 +76,9 @@ export default class RoomSettingsDialog extends React.Component {
|
|||
<NotificationSettingsTab roomId={this.props.roomId} />,
|
||||
));
|
||||
|
||||
if (shouldShowBridgeIcon) {
|
||||
if (SettingsStore.isFeatureEnabled("feature_bridge_state")) {
|
||||
tabs.push(new Tab(
|
||||
_td("Bridge Info"),
|
||||
_td("Bridges"),
|
||||
"mx_RoomSettingsDialog_bridgesIcon",
|
||||
<BridgeSettingsTab roomId={this.props.roomId} />,
|
||||
));
|
||||
|
|
|
@ -43,6 +43,10 @@ export default createReactClass({
|
|||
// should there be a cancel button? default: true
|
||||
hasCancel: PropTypes.bool,
|
||||
|
||||
// The class of the cancel button, only used if a cancel button is
|
||||
// enabled
|
||||
cancelButtonClass: PropTypes.node,
|
||||
|
||||
// onClick handler for the cancel button.
|
||||
onCancel: PropTypes.func,
|
||||
|
||||
|
@ -72,12 +76,14 @@ export default createReactClass({
|
|||
primaryButtonClassName += " " + this.props.primaryButtonClass;
|
||||
}
|
||||
let cancelButton;
|
||||
|
||||
if (this.props.cancelButton || this.props.hasCancel) {
|
||||
cancelButton = <button
|
||||
// important: the default type is 'submit' and this button comes before the
|
||||
// primary in the DOM so will get form submissions unless we make it not a submit.
|
||||
type="button"
|
||||
onClick={this._onCancelClick}
|
||||
className={this.props.cancelButtonClass}
|
||||
disabled={this.props.disabled}
|
||||
>
|
||||
{ this.props.cancelButton || _t("Cancel") }
|
||||
|
|
|
@ -166,7 +166,7 @@ export default createReactClass({
|
|||
});
|
||||
|
||||
/* Check all verified user devices. */
|
||||
for (const userId of verified) {
|
||||
for (const userId of [...verified, cli.getUserId()]) {
|
||||
const devices = await cli.getStoredDevicesForUser(userId);
|
||||
const allDevicesVerified = devices.every(({deviceId}) => {
|
||||
return cli.checkDeviceTrust(userId, deviceId).isVerified();
|
||||
|
|
114
src/components/views/settings/BridgeTile.js
Normal file
114
src/components/views/settings/BridgeTile.js
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
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 {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
||||
import {_t} from "../../../languageHandler";
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import Pill from "../elements/Pill";
|
||||
import {makeUserPermalink} from "../../../utils/permalinks/Permalinks";
|
||||
import BaseAvatar from "../avatars/BaseAvatar";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.settings.BridgeTile")
|
||||
export default class BridgeTile extends React.PureComponent {
|
||||
static propTypes = {
|
||||
ev: PropTypes.object.isRequired,
|
||||
room: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
state = {
|
||||
visible: false,
|
||||
}
|
||||
|
||||
_toggleVisible() {
|
||||
this.setState({
|
||||
visible: !this.state.visible,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const content = this.props.ev.getContent();
|
||||
const { channel, network, protocol } = content;
|
||||
const protocolName = protocol.displayname || protocol.id;
|
||||
const channelName = channel.displayname || channel.id;
|
||||
const networkName = network ? network.displayname || network.id : protocolName;
|
||||
|
||||
let creator = null;
|
||||
if (content.creator) {
|
||||
creator = _t("This bridge was provisioned by <user />.", {}, {
|
||||
user: <Pill
|
||||
type={Pill.TYPE_USER_MENTION}
|
||||
room={this.props.room}
|
||||
url={makeUserPermalink(content.creator)}
|
||||
shouldShowPillAvatar={true}
|
||||
/>,
|
||||
});
|
||||
}
|
||||
|
||||
const bot = _t("This bridge is managed by <user />.", {}, {
|
||||
user: <Pill
|
||||
type={Pill.TYPE_USER_MENTION}
|
||||
room={this.props.room}
|
||||
url={makeUserPermalink(this.props.ev.getSender())}
|
||||
shouldShowPillAvatar={true}
|
||||
/>,
|
||||
});
|
||||
|
||||
let networkIcon;
|
||||
|
||||
if (protocol.avatar) {
|
||||
const avatarUrl = getHttpUriForMxc(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
protocol.avatar, 64, 64, "crop",
|
||||
);
|
||||
|
||||
networkIcon = <BaseAvatar className="protocol-icon"
|
||||
width={48}
|
||||
height={48}
|
||||
resizeMethod='crop'
|
||||
name={ protocolName }
|
||||
idName={ protocolName }
|
||||
url={ avatarUrl }
|
||||
/>;
|
||||
} else {
|
||||
networkIcon = <div class="noProtocolIcon"></div>;
|
||||
}
|
||||
|
||||
const id = this.props.ev.getId();
|
||||
const metadataClassname = "metadata" + (this.state.visible ? " visible" : "");
|
||||
return (<li key={id}>
|
||||
<div className="column-icon">
|
||||
{networkIcon}
|
||||
</div>
|
||||
<div className="column-data">
|
||||
<h3>{protocolName}</h3>
|
||||
<p className="workspace-channel-details">
|
||||
<span>{_t("Workspace: %(networkName)s", {networkName})}</span>
|
||||
<span className="channel">{_t("Channel: %(channelName)s", {channelName})}</span>
|
||||
</p>
|
||||
<p className={metadataClassname}>
|
||||
{creator} {bot}
|
||||
</p>
|
||||
<AccessibleButton className="mx_showMore" kind="secondary" onClick={this._toggleVisible.bind(this)}>
|
||||
{ this.state.visible ? _t("Show less") : _t("Show more") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</li>);
|
||||
}
|
||||
}
|
187
src/components/views/settings/EventIndexPanel.js
Normal file
187
src/components/views/settings/EventIndexPanel.js
Normal file
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
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 { _t } from '../../../languageHandler';
|
||||
import * as sdk from '../../../index';
|
||||
import Modal from '../../../Modal';
|
||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import {formatBytes, formatCountLong} from "../../../utils/FormattingUtils";
|
||||
import EventIndexPeg from "../../../indexing/EventIndexPeg";
|
||||
|
||||
export default class EventIndexPanel extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
enabling: false,
|
||||
eventIndexSize: 0,
|
||||
roomCount: 0,
|
||||
eventIndexingEnabled:
|
||||
SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing'),
|
||||
};
|
||||
}
|
||||
|
||||
async updateCurrentRoom(room) {
|
||||
const eventIndex = EventIndexPeg.get();
|
||||
const stats = await eventIndex.getStats();
|
||||
|
||||
this.setState({
|
||||
eventIndexSize: stats.size,
|
||||
roomCount: stats.roomCount,
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
const eventIndex = EventIndexPeg.get();
|
||||
|
||||
if (eventIndex !== null) {
|
||||
eventIndex.removeListener("changedCheckpoint", this.updateCurrentRoom.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
async componentWillMount(): void {
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
async updateState() {
|
||||
const eventIndex = EventIndexPeg.get();
|
||||
const eventIndexingEnabled = SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing');
|
||||
const enabling = false;
|
||||
|
||||
let eventIndexSize = 0;
|
||||
let roomCount = 0;
|
||||
|
||||
if (eventIndex !== null) {
|
||||
eventIndex.on("changedCheckpoint", this.updateCurrentRoom.bind(this));
|
||||
|
||||
const stats = await eventIndex.getStats();
|
||||
eventIndexSize = stats.size;
|
||||
roomCount = stats.roomCount;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
enabling,
|
||||
eventIndexSize,
|
||||
roomCount,
|
||||
eventIndexingEnabled,
|
||||
});
|
||||
}
|
||||
|
||||
_onManage = async () => {
|
||||
Modal.createTrackedDialogAsync('Message search', 'Message search',
|
||||
import('../../../async-components/views/dialogs/eventindex/ManageEventIndexDialog'),
|
||||
{
|
||||
onFinished: () => {},
|
||||
}, null, /* priority = */ false, /* static = */ true,
|
||||
);
|
||||
}
|
||||
|
||||
_onEnable = async () => {
|
||||
this.setState({
|
||||
enabling: true,
|
||||
});
|
||||
|
||||
await EventIndexPeg.initEventIndex();
|
||||
await EventIndexPeg.get().addInitialCheckpoints();
|
||||
await EventIndexPeg.get().startCrawler();
|
||||
await SettingsStore.setValue('enableEventIndexing', null, SettingLevel.DEVICE, true);
|
||||
await this.updateState();
|
||||
}
|
||||
|
||||
render() {
|
||||
let eventIndexingSettings = null;
|
||||
const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
|
||||
|
||||
if (EventIndexPeg.get() !== null) {
|
||||
eventIndexingSettings = (
|
||||
<div>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_t( "Securely cache encrypted messages locally for them " +
|
||||
"to appear in search results, using ")
|
||||
} {formatBytes(this.state.eventIndexSize, 0)}
|
||||
{_t( " to store messages from ")}
|
||||
{formatCountLong(this.state.roomCount)} {_t("rooms.")}
|
||||
</div>
|
||||
<div>
|
||||
<AccessibleButton kind="primary" onClick={this._onManage}>
|
||||
{_t("Manage")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (!this.state.eventIndexingEnabled && EventIndexPeg.supportIsInstalled()) {
|
||||
eventIndexingSettings = (
|
||||
<div>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_t( "Securely cache encrypted messages locally for them to " +
|
||||
"appear in search results.")}
|
||||
</div>
|
||||
<div>
|
||||
<AccessibleButton kind="primary" disabled={this.state.enabling}
|
||||
onClick={this._onEnable}>
|
||||
{_t("Enable")}
|
||||
</AccessibleButton>
|
||||
{this.state.enabling ? <InlineSpinner /> : <div />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (EventIndexPeg.platformHasSupport() && !EventIndexPeg.supportIsInstalled()) {
|
||||
const nativeLink = (
|
||||
"https://github.com/vector-im/riot-web/blob/develop/" +
|
||||
"docs/native-node-modules.md#" +
|
||||
"adding-seshat-for-search-in-e2e-encrypted-rooms"
|
||||
);
|
||||
|
||||
eventIndexingSettings = (
|
||||
<div>
|
||||
{
|
||||
_t( "Riot is missing some components required for securely " +
|
||||
"caching encrypted messages locally. If you'd like to " +
|
||||
"experiment with this feature, build a custom Riot Desktop " +
|
||||
"with <nativeLink>search components added</nativeLink>.",
|
||||
{},
|
||||
{
|
||||
'nativeLink': (sub) => <a href={nativeLink} target="_blank"
|
||||
rel="noopener">{sub}</a>,
|
||||
},
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
eventIndexingSettings = (
|
||||
<div>
|
||||
{
|
||||
_t( "Riot can't securely cache encrypted messages locally " +
|
||||
"while running in a web browser. Use <riotLink>Riot Desktop</riotLink> " +
|
||||
"for encrypted messages to appear in search results.",
|
||||
{},
|
||||
{
|
||||
'riotLink': (sub) => <a href="https://riot.im/download/desktop"
|
||||
target="_blank" rel="noopener">{sub}</a>,
|
||||
},
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return eventIndexingSettings;
|
||||
}
|
||||
}
|
|
@ -18,16 +18,15 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
|
||||
import Pill from "../../../elements/Pill";
|
||||
import {makeUserPermalink} from "../../../../../utils/permalinks/Permalinks";
|
||||
import BaseAvatar from "../../../avatars/BaseAvatar";
|
||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
||||
import BridgeTile from "../../BridgeTile";
|
||||
|
||||
const BRIDGE_EVENT_TYPES = [
|
||||
"uk.half-shot.bridge",
|
||||
// m.bridge
|
||||
];
|
||||
|
||||
const BRIDGES_LINK = "https://matrix.org/bridges/";
|
||||
|
||||
export default class BridgeSettingsTab extends React.Component {
|
||||
static propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
|
@ -38,99 +37,7 @@ export default class BridgeSettingsTab extends React.Component {
|
|||
if (!content || !content.channel || !content.protocol) {
|
||||
return null;
|
||||
}
|
||||
const { channel, network } = content;
|
||||
const protocolName = content.protocol.displayname || content.protocol.id;
|
||||
const channelName = channel.displayname || channel.id;
|
||||
const networkName = network ? network.displayname || network.id : protocolName;
|
||||
|
||||
let creator = null;
|
||||
if (content.creator) {
|
||||
creator = <p> { _t("This bridge was provisioned by <user />", {}, {
|
||||
user: <Pill
|
||||
type={Pill.TYPE_USER_MENTION}
|
||||
room={room}
|
||||
url={makeUserPermalink(content.creator)}
|
||||
shouldShowPillAvatar={true}
|
||||
/>,
|
||||
})}</p>;
|
||||
}
|
||||
|
||||
const bot = (<p> {_t("This bridge is managed by <user />.", {}, {
|
||||
user: <Pill
|
||||
type={Pill.TYPE_USER_MENTION}
|
||||
room={room}
|
||||
url={makeUserPermalink(event.getSender())}
|
||||
shouldShowPillAvatar={true}
|
||||
/>,
|
||||
})} </p>);
|
||||
let channelLink = channelName;
|
||||
if (channel.external_url) {
|
||||
channelLink = <a target="_blank" href={channel.external_url} rel="noopener">{channelName}</a>;
|
||||
}
|
||||
|
||||
let networkLink = networkName;
|
||||
if (network && network.external_url) {
|
||||
networkLink = <a target="_blank" href={network.external_url} rel="noopener">{networkName}</a>;
|
||||
}
|
||||
|
||||
const chanAndNetworkInfo = (
|
||||
_t("Bridged into <channelLink /> <networkLink />, on <protocolName />", {}, {
|
||||
channelLink,
|
||||
networkLink,
|
||||
protocolName,
|
||||
})
|
||||
);
|
||||
|
||||
let networkIcon = null;
|
||||
if (networkName && network.avatar) {
|
||||
const avatarUrl = getHttpUriForMxc(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
network.avatar, 32, 32, "crop",
|
||||
);
|
||||
networkIcon = <BaseAvatar
|
||||
width={32}
|
||||
height={32}
|
||||
resizeMethod='crop'
|
||||
name={ networkName }
|
||||
idName={ networkName }
|
||||
url={ avatarUrl }
|
||||
/>;
|
||||
}
|
||||
|
||||
let channelIcon = null;
|
||||
if (channel.avatar) {
|
||||
const avatarUrl = getHttpUriForMxc(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
channel.avatar, 32, 32, "crop",
|
||||
);
|
||||
channelIcon = <BaseAvatar
|
||||
width={32}
|
||||
height={32}
|
||||
resizeMethod='crop'
|
||||
name={ networkName }
|
||||
idName={ networkName }
|
||||
url={ avatarUrl }
|
||||
/>;
|
||||
}
|
||||
|
||||
const heading = _t("Connected to <channelIcon /> <channelName /> on <networkIcon /> <networkName />", { }, {
|
||||
channelIcon,
|
||||
channelName,
|
||||
networkName,
|
||||
networkIcon,
|
||||
});
|
||||
|
||||
return (<li key={event.stateKey}>
|
||||
<div>
|
||||
<h3>{heading}</h3>
|
||||
<p>{_t("Connected via %(protocolName)s", { protocolName })}</p>
|
||||
<details>
|
||||
{creator}
|
||||
{bot}
|
||||
<p>{chanAndNetworkInfo}</p>
|
||||
</details>
|
||||
</div>
|
||||
</li>);
|
||||
return <BridgeTile room={room} ev={event}></BridgeTile>;
|
||||
}
|
||||
|
||||
static getBridgeStateEvents(roomId) {
|
||||
|
@ -151,14 +58,40 @@ export default class BridgeSettingsTab extends React.Component {
|
|||
const client = MatrixClientPeg.get();
|
||||
const room = client.getRoom(this.props.roomId);
|
||||
|
||||
let content = null;
|
||||
|
||||
if (bridgeEvents.length > 0) {
|
||||
content = <div>
|
||||
<p>{_t(
|
||||
"This room is bridging messages to the following platforms. " +
|
||||
"<a>Learn more.</a>", {},
|
||||
{
|
||||
// TODO: We don't have this link yet: this will prevent the translators
|
||||
// having to re-translate the string when we do.
|
||||
a: sub => <a href={BRIDGES_LINK} target="_blank" rel="noopener">{sub}</a>,
|
||||
},
|
||||
)}</p>
|
||||
<ul className="mx_RoomSettingsDialog_BridgeList">
|
||||
{ bridgeEvents.map((event) => this._renderBridgeCard(event, room)) }
|
||||
</ul>
|
||||
</div>;
|
||||
} else {
|
||||
content = <p>{_t(
|
||||
"This room isn’t bridging messages to any platforms. " +
|
||||
"<a>Learn more.</a>", {},
|
||||
{
|
||||
// TODO: We don't have this link yet: this will prevent the translators
|
||||
// having to re-translate the string when we do.
|
||||
a: sub => <a href={BRIDGES_LINK} target="_blank" rel="noopener">{sub}</a>,
|
||||
},
|
||||
)}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Bridge Info")}</div>
|
||||
<div className="mx_SettingsTab_heading">{_t("Bridges")}</div>
|
||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
<p>{ _t("Below is a list of bridges connected to this room.") }</p>
|
||||
<ul className="mx_RoomSettingsDialog_BridgeList">
|
||||
{ bridgeEvents.map((event) => this._renderBridgeCard(event, room)) }
|
||||
</ul>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -170,6 +170,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
|
|||
return (
|
||||
<div className="mx_SettingsTab mx_PreferencesUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Preferences")}</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Composer")}</span>
|
||||
{this._renderGroup(PreferencesUserSettingsTab.COMPOSER_SETTINGS)}
|
||||
|
|
|
@ -242,6 +242,7 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
render() {
|
||||
const DevicesPanel = sdk.getComponent('views.settings.DevicesPanel');
|
||||
const SettingsFlag = sdk.getComponent('views.elements.SettingsFlag');
|
||||
const EventIndexPanel = sdk.getComponent('views.settings.EventIndexPanel');
|
||||
|
||||
const KeyBackupPanel = sdk.getComponent('views.settings.KeyBackupPanel');
|
||||
const keyBackup = (
|
||||
|
@ -253,6 +254,16 @@ 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>
|
||||
);
|
||||
}
|
||||
|
||||
// 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
|
||||
// in having advanced details here once all flows are implemented, we
|
||||
|
@ -281,6 +292,7 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
</div>
|
||||
</div>
|
||||
{keyBackup}
|
||||
{eventIndex}
|
||||
{crossSigning}
|
||||
{this._renderCurrentDeviceInfo()}
|
||||
<div className='mx_SettingsTab_section'>
|
||||
|
|
|
@ -16,12 +16,15 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as sdk from "../../../index";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import Modal from "../../../Modal";
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import DeviceListener from '../../../DeviceListener';
|
||||
import NewSessionReviewDialog from '../dialogs/NewSessionReviewDialog';
|
||||
import FormButton from '../elements/FormButton';
|
||||
import { replaceableComponent } from '../../../utils/replaceableComponent';
|
||||
|
||||
@replaceableComponent("views.toasts.VerifySessionToast")
|
||||
export default class VerifySessionToast extends React.PureComponent {
|
||||
static propTypes = {
|
||||
toastKey: PropTypes.string.isRequired,
|
||||
|
@ -34,18 +37,16 @@ export default class VerifySessionToast extends React.PureComponent {
|
|||
|
||||
_onReviewClick = async () => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog');
|
||||
|
||||
const device = await cli.getStoredDevice(cli.getUserId(), this.props.deviceId);
|
||||
|
||||
Modal.createTrackedDialog('New Session Verify', 'Starting dialog', DeviceVerifyDialog, {
|
||||
Modal.createTrackedDialog('New Session Review', 'Starting dialog', NewSessionReviewDialog, {
|
||||
userId: MatrixClientPeg.get().getUserId(),
|
||||
device,
|
||||
}, null, /* priority = */ false, /* static = */ true);
|
||||
};
|
||||
|
||||
render() {
|
||||
const FormButton = sdk.getComponent("elements.FormButton");
|
||||
return (<div>
|
||||
<div className="mx_Toast_description">{_t("Review & verify your new session")}</div>
|
||||
<div className="mx_Toast_buttons" aria-live="off">
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)",
|
||||
"Send read receipts for messages (requires compatible homeserver to disable)": "Send read receipts for messages (requires compatible homeserver to disable)",
|
||||
"Show previews/thumbnails for images": "Show previews/thumbnails for images",
|
||||
"Enable message search in encrypted rooms": "Enable message search in encrypted rooms",
|
||||
"Collecting app version information": "Collecting app version information",
|
||||
"Collecting logs": "Collecting logs",
|
||||
"Uploading report": "Uploading report",
|
||||
|
@ -526,6 +527,12 @@
|
|||
"Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:",
|
||||
"Upload": "Upload",
|
||||
"Remove": "Remove",
|
||||
"This bridge was provisioned by <user />.": "This bridge was provisioned by <user />.",
|
||||
"This bridge is managed by <user />.": "This bridge is managed by <user />.",
|
||||
"Workspace: %(networkName)s": "Workspace: %(networkName)s",
|
||||
"Channel: %(channelName)s": "Channel: %(channelName)s",
|
||||
"Show less": "Show less",
|
||||
"Show more": "Show more",
|
||||
"Failed to upload profile picture!": "Failed to upload profile picture!",
|
||||
"Upload new:": "Upload new:",
|
||||
"No display name": "No display name",
|
||||
|
@ -562,6 +569,14 @@
|
|||
"Failed to set display name": "Failed to set display name",
|
||||
"Disable Notifications": "Disable Notifications",
|
||||
"Enable Notifications": "Enable Notifications",
|
||||
"Securely cache encrypted messages locally for them to appear in search results, using ": "Securely cache encrypted messages locally for them to appear in search results, using ",
|
||||
" to store messages from ": " to store messages from ",
|
||||
"rooms.": "rooms.",
|
||||
"Manage": "Manage",
|
||||
"Securely cache encrypted messages locally for them to appear in search results.": "Securely cache encrypted messages locally for them to appear in search results.",
|
||||
"Enable": "Enable",
|
||||
"Riot is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom Riot Desktop with <nativeLink>search components added</nativeLink>.": "Riot is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom Riot Desktop with <nativeLink>search components added</nativeLink>.",
|
||||
"Riot can't securely cache encrypted messages locally while running in a web browser. Use <riotLink>Riot Desktop</riotLink> for encrypted messages to appear in search results.": "Riot can't securely cache encrypted messages locally while running in a web browser. Use <riotLink>Riot Desktop</riotLink> for encrypted messages to appear in search results.",
|
||||
"Connecting to integration manager...": "Connecting to integration manager...",
|
||||
"Cannot connect to integration manager": "Cannot connect to integration manager",
|
||||
"The integration manager is offline or it cannot reach your homeserver.": "The integration manager is offline or it cannot reach your homeserver.",
|
||||
|
@ -755,6 +770,7 @@
|
|||
"Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites",
|
||||
"Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
|
||||
"Key backup": "Key backup",
|
||||
"Message search": "Message search",
|
||||
"Cross-signing": "Cross-signing",
|
||||
"Security & Privacy": "Security & Privacy",
|
||||
"Devices": "Devices",
|
||||
|
@ -785,13 +801,9 @@
|
|||
"Room version:": "Room version:",
|
||||
"Developer options": "Developer options",
|
||||
"Open Devtools": "Open Devtools",
|
||||
"This bridge was provisioned by <user />": "This bridge was provisioned by <user />",
|
||||
"This bridge is managed by <user />.": "This bridge is managed by <user />.",
|
||||
"Bridged into <channelLink /> <networkLink />, on <protocolName />": "Bridged into <channelLink /> <networkLink />, on <protocolName />",
|
||||
"Connected to <channelIcon /> <channelName /> on <networkIcon /> <networkName />": "Connected to <channelIcon /> <channelName /> on <networkIcon /> <networkName />",
|
||||
"Connected via %(protocolName)s": "Connected via %(protocolName)s",
|
||||
"Bridge Info": "Bridge Info",
|
||||
"Below is a list of bridges connected to this room.": "Below is a list of bridges connected to this room.",
|
||||
"This room is bridging messages to the following platforms. <a>Learn more.</a>": "This room is bridging messages to the following platforms. <a>Learn more.</a>",
|
||||
"This room isn’t bridging messages to any platforms. <a>Learn more.</a>": "This room isn’t bridging messages to any platforms. <a>Learn more.</a>",
|
||||
"Bridges": "Bridges",
|
||||
"Room Addresses": "Room Addresses",
|
||||
"Publish this room to the public in %(domain)s's room directory?": "Publish this room to the public in %(domain)s's room directory?",
|
||||
"URL Previews": "URL Previews",
|
||||
|
@ -1496,7 +1508,6 @@
|
|||
"Recent Conversations": "Recent Conversations",
|
||||
"Suggestions": "Suggestions",
|
||||
"Recently Direct Messaged": "Recently Direct Messaged",
|
||||
"Show more": "Show more",
|
||||
"If you can't find someone, ask them for their username, share your username (%(userId)s) or <a>profile link</a>.": "If you can't find someone, ask them for their username, share your username (%(userId)s) or <a>profile link</a>.",
|
||||
"Go": "Go",
|
||||
"If you can't find someone, ask them for their username (e.g. @user:server.com) or <a>share this room</a>.": "If you can't find someone, ask them for their username (e.g. @user:server.com) or <a>share this room</a>.",
|
||||
|
@ -1519,6 +1530,10 @@
|
|||
"Are you sure you want to sign out?": "Are you sure you want to sign out?",
|
||||
"Your homeserver doesn't seem to support this feature.": "Your homeserver doesn't seem to support this feature.",
|
||||
"Message edits": "Message edits",
|
||||
"New session": "New session",
|
||||
"Use this session to verify your new one, granting it access to encrypted messages:": "Use this session to verify your new one, granting it access to encrypted messages:",
|
||||
"If you didn’t sign in to this session, your account may be compromised.": "If you didn’t sign in to this session, your account may be compromised.",
|
||||
"This wasn't me": "This wasn't me",
|
||||
"If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.",
|
||||
"To help avoid duplicate issues, please <existingIssuesLink>view existing issues</existingIssuesLink> first (and add a +1) or <newIssueLink>create a new issue</newIssueLink> if you can't find it.": "To help avoid duplicate issues, please <existingIssuesLink>view existing issues</existingIssuesLink> first (and add a +1) or <newIssueLink>create a new issue</newIssueLink> if you can't find it.",
|
||||
"Report bugs & give feedback": "Report bugs & give feedback",
|
||||
|
@ -2072,6 +2087,14 @@
|
|||
"This device has detected that your recovery passphrase and key for Secure Messages have been removed.": "This device has detected that your recovery passphrase and key for Secure Messages have been removed.",
|
||||
"If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.": "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.",
|
||||
"If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.",
|
||||
"If disabled, messages from encrypted rooms won't appear in search results.": "If disabled, messages from encrypted rooms won't appear in search results.",
|
||||
"Disable": "Disable",
|
||||
"Not currently downloading messages for any room.": "Not currently downloading messages for any room.",
|
||||
"Downloading mesages for %(currentRoom)s.": "Downloading mesages for %(currentRoom)s.",
|
||||
"Riot is securely caching encrypted messages locally for them to appear in search results:": "Riot is securely caching encrypted messages locally for them to appear in search results:",
|
||||
"Space used:": "Space used:",
|
||||
"Indexed messages:": "Indexed messages:",
|
||||
"Number of rooms:": "Number of rooms:",
|
||||
"Failed to set direct chat tag": "Failed to set direct chat tag",
|
||||
"Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room",
|
||||
"Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room"
|
||||
|
|
|
@ -74,6 +74,12 @@ export interface LoadArgs {
|
|||
direction: string;
|
||||
}
|
||||
|
||||
export interface IndexStats {
|
||||
size: number;
|
||||
event_count: number;
|
||||
room_count: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for classes that provide platform-specific event indexing.
|
||||
*
|
||||
|
@ -124,6 +130,16 @@ export default class BaseEventIndexManager {
|
|||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistical information of the index.
|
||||
*
|
||||
* @return {Promise<IndexStats>} A promise that will resolve to the index
|
||||
* statistics.
|
||||
*/
|
||||
async getStats(): Promise<IndexStats> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the previously queued up events to the index.
|
||||
*
|
||||
|
|
|
@ -17,20 +17,25 @@ limitations under the License.
|
|||
import PlatformPeg from "../PlatformPeg";
|
||||
import {MatrixClientPeg} from "../MatrixClientPeg";
|
||||
import {EventTimeline, RoomMember} from 'matrix-js-sdk';
|
||||
import {sleep} from "../utils/promise";
|
||||
import {EventEmitter} from "events";
|
||||
|
||||
/*
|
||||
* Event indexing class that wraps the platform specific event indexing.
|
||||
*/
|
||||
export default class EventIndex {
|
||||
export default class EventIndex extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.crawlerCheckpoints = [];
|
||||
// The time that the crawler will wait between /rooms/{room_id}/messages
|
||||
// requests
|
||||
this._crawlerTimeout = 3000;
|
||||
// The time in ms that the crawler will wait loop iterations if there
|
||||
// have not been any checkpoints to consume in the last iteration.
|
||||
this._crawlerIdleTime = 5000;
|
||||
this._crawlerSleepTime = 3000;
|
||||
// The maximum number of events our crawler should fetch in a single
|
||||
// crawl.
|
||||
this._eventsPerCrawl = 100;
|
||||
this._crawler = null;
|
||||
this._currentCheckpoint = null;
|
||||
this.liveEventsForIndex = new Set();
|
||||
}
|
||||
|
||||
|
@ -65,59 +70,62 @@ export default class EventIndex {
|
|||
client.removeListener('Room.timelineReset', this.onTimelineReset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get crawler checkpoints for the encrypted rooms and store them in the index.
|
||||
*/
|
||||
async addInitialCheckpoints() {
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
const client = MatrixClientPeg.get();
|
||||
const rooms = client.getRooms();
|
||||
|
||||
const isRoomEncrypted = (room) => {
|
||||
return client.isRoomEncrypted(room.roomId);
|
||||
};
|
||||
|
||||
// We only care to crawl the encrypted rooms, non-encrypted
|
||||
// rooms can use the search provided by the homeserver.
|
||||
const encryptedRooms = rooms.filter(isRoomEncrypted);
|
||||
|
||||
console.log("EventIndex: Adding initial crawler checkpoints");
|
||||
|
||||
// Gather the prev_batch tokens and create checkpoints for
|
||||
// our message crawler.
|
||||
await Promise.all(encryptedRooms.map(async (room) => {
|
||||
const timeline = room.getLiveTimeline();
|
||||
const token = timeline.getPaginationToken("b");
|
||||
|
||||
console.log("EventIndex: Got token for indexer",
|
||||
room.roomId, token);
|
||||
|
||||
const backCheckpoint = {
|
||||
roomId: room.roomId,
|
||||
token: token,
|
||||
direction: "b",
|
||||
};
|
||||
|
||||
const forwardCheckpoint = {
|
||||
roomId: room.roomId,
|
||||
token: token,
|
||||
direction: "f",
|
||||
};
|
||||
|
||||
await indexManager.addCrawlerCheckpoint(backCheckpoint);
|
||||
await indexManager.addCrawlerCheckpoint(forwardCheckpoint);
|
||||
this.crawlerCheckpoints.push(backCheckpoint);
|
||||
this.crawlerCheckpoints.push(forwardCheckpoint);
|
||||
}));
|
||||
}
|
||||
|
||||
onSync = async (state, prevState, data) => {
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
|
||||
if (prevState === "PREPARED" && state === "SYNCING") {
|
||||
const addInitialCheckpoints = async () => {
|
||||
const client = MatrixClientPeg.get();
|
||||
const rooms = client.getRooms();
|
||||
|
||||
const isRoomEncrypted = (room) => {
|
||||
return client.isRoomEncrypted(room.roomId);
|
||||
};
|
||||
|
||||
// We only care to crawl the encrypted rooms, non-encrypted.
|
||||
// rooms can use the search provided by the homeserver.
|
||||
const encryptedRooms = rooms.filter(isRoomEncrypted);
|
||||
|
||||
console.log("EventIndex: Adding initial crawler checkpoints");
|
||||
|
||||
// Gather the prev_batch tokens and create checkpoints for
|
||||
// our message crawler.
|
||||
await Promise.all(encryptedRooms.map(async (room) => {
|
||||
const timeline = room.getLiveTimeline();
|
||||
const token = timeline.getPaginationToken("b");
|
||||
|
||||
console.log("EventIndex: Got token for indexer",
|
||||
room.roomId, token);
|
||||
|
||||
const backCheckpoint = {
|
||||
roomId: room.roomId,
|
||||
token: token,
|
||||
direction: "b",
|
||||
};
|
||||
|
||||
const forwardCheckpoint = {
|
||||
roomId: room.roomId,
|
||||
token: token,
|
||||
direction: "f",
|
||||
};
|
||||
|
||||
await indexManager.addCrawlerCheckpoint(backCheckpoint);
|
||||
await indexManager.addCrawlerCheckpoint(forwardCheckpoint);
|
||||
this.crawlerCheckpoints.push(backCheckpoint);
|
||||
this.crawlerCheckpoints.push(forwardCheckpoint);
|
||||
}));
|
||||
};
|
||||
|
||||
// If our indexer is empty we're most likely running Riot the
|
||||
// first time with indexing support or running it with an
|
||||
// initial sync. Add checkpoints to crawl our encrypted rooms.
|
||||
const eventIndexWasEmpty = await indexManager.isEventIndexEmpty();
|
||||
if (eventIndexWasEmpty) await addInitialCheckpoints();
|
||||
if (eventIndexWasEmpty) await this.addInitialCheckpoints();
|
||||
|
||||
// Start our crawler.
|
||||
this.startCrawler();
|
||||
return;
|
||||
}
|
||||
|
@ -182,13 +190,11 @@ export default class EventIndex {
|
|||
indexManager.addEventToIndex(e, profile);
|
||||
}
|
||||
|
||||
async crawlerFunc() {
|
||||
// TODO either put this in a better place or find a library provided
|
||||
// method that does this.
|
||||
const sleep = async (ms) => {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
};
|
||||
emitNewCheckpoint() {
|
||||
this.emit("changedCheckpoint", this.currentRoom());
|
||||
}
|
||||
|
||||
async crawlerFunc() {
|
||||
let cancelled = false;
|
||||
|
||||
console.log("EventIndex: Started crawler function");
|
||||
|
@ -202,11 +208,27 @@ export default class EventIndex {
|
|||
cancelled = true;
|
||||
};
|
||||
|
||||
let idle = false;
|
||||
|
||||
while (!cancelled) {
|
||||
// This is a low priority task and we don't want to spam our
|
||||
// homeserver with /messages requests so we set a hefty timeout
|
||||
// here.
|
||||
await sleep(this._crawlerTimeout);
|
||||
let sleepTime = this._crawlerSleepTime;
|
||||
|
||||
// Don't let the user configure a lower sleep time than 100 ms.
|
||||
sleepTime = Math.max(sleepTime, 100);
|
||||
|
||||
if (idle) {
|
||||
sleepTime = this._crawlerIdleTime;
|
||||
}
|
||||
|
||||
if (this._currentCheckpoint !== null) {
|
||||
this._currentCheckpoint = null;
|
||||
this.emitNewCheckpoint();
|
||||
}
|
||||
|
||||
await sleep(sleepTime);
|
||||
|
||||
console.log("EventIndex: Running the crawler loop.");
|
||||
|
||||
|
@ -219,9 +241,15 @@ export default class EventIndex {
|
|||
/// There is no checkpoint available currently, one may appear if
|
||||
// a sync with limited room timelines happens, so go back to sleep.
|
||||
if (checkpoint === undefined) {
|
||||
idle = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
this._currentCheckpoint = checkpoint;
|
||||
this.emitNewCheckpoint();
|
||||
|
||||
idle = false;
|
||||
|
||||
console.log("EventIndex: crawling using checkpoint", checkpoint);
|
||||
|
||||
// We have a checkpoint, let us fetch some messages, again, very
|
||||
|
@ -241,6 +269,11 @@ export default class EventIndex {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (cancelled) {
|
||||
this.crawlerCheckpoints.push(checkpoint);
|
||||
break;
|
||||
}
|
||||
|
||||
if (res.chunk.length === 0) {
|
||||
console.log("EventIndex: Done with the checkpoint", checkpoint);
|
||||
// We got to the start/end of our timeline, lets just
|
||||
|
@ -600,4 +633,29 @@ export default class EventIndex {
|
|||
|
||||
return paginationPromise;
|
||||
}
|
||||
|
||||
async getStats() {
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
return indexManager.getStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the room that we are currently crawling.
|
||||
*
|
||||
* @returns {Room} A MatrixRoom that is being currently crawled, null
|
||||
* if no room is currently being crawled.
|
||||
*/
|
||||
currentRoom() {
|
||||
if (this._currentCheckpoint === null && this.crawlerCheckpoints.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
if (this._currentCheckpoint !== null) {
|
||||
return client.getRoom(this._currentCheckpoint.roomId);
|
||||
} else {
|
||||
return client.getRoom(this.crawlerCheckpoints[0].roomId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,17 +21,19 @@ limitations under the License.
|
|||
|
||||
import PlatformPeg from "../PlatformPeg";
|
||||
import EventIndex from "../indexing/EventIndex";
|
||||
import SettingsStore from '../settings/SettingsStore';
|
||||
import SettingsStore, {SettingLevel} from '../settings/SettingsStore';
|
||||
|
||||
class EventIndexPeg {
|
||||
constructor() {
|
||||
this.index = null;
|
||||
this._supportIsInstalled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new EventIndex and initialize it if the platform supports it.
|
||||
* Initialize the EventIndexPeg and if event indexing is enabled initialize
|
||||
* the event index.
|
||||
*
|
||||
* @return {Promise<bool>} A promise that will resolve to true if an
|
||||
* @return {Promise<boolean>} A promise that will resolve to true if an
|
||||
* EventIndex was successfully initialized, false otherwise.
|
||||
*/
|
||||
async init() {
|
||||
|
@ -40,12 +42,33 @@ class EventIndexPeg {
|
|||
}
|
||||
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
if (!indexManager || await indexManager.supportsEventIndexing() !== true) {
|
||||
console.log("EventIndex: Platform doesn't support event indexing,",
|
||||
"not initializing.");
|
||||
if (!indexManager) {
|
||||
console.log("EventIndex: Platform doesn't support event indexing, not initializing.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this._supportIsInstalled = await indexManager.supportsEventIndexing();
|
||||
|
||||
if (!this.supportIsInstalled()) {
|
||||
console.log("EventIndex: Event indexing isn't installed for the platform, not initializing.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableEventIndexing')) {
|
||||
console.log("EventIndex: Event indexing is disabled, not initializing");
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.initEventIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the event index.
|
||||
*
|
||||
* @returns {boolean} True if the event index was succesfully initialized,
|
||||
* false otherwise.
|
||||
*/
|
||||
async initEventIndex() {
|
||||
const index = new EventIndex();
|
||||
|
||||
try {
|
||||
|
@ -60,6 +83,29 @@ class EventIndexPeg {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current platform has support for event indexing.
|
||||
*
|
||||
* @return {boolean} True if it has support, false otherwise. Note that this
|
||||
* does not mean that support is installed.
|
||||
*/
|
||||
platformHasSupport(): boolean {
|
||||
return PlatformPeg.get().getEventIndexingManager() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if event indexing support is installed for the platfrom.
|
||||
*
|
||||
* Event indexing might require additional optional modules to be installed,
|
||||
* this tells us if those are installed. Note that this should only be
|
||||
* called after the init() method was called.
|
||||
*
|
||||
* @return {boolean} True if support is installed, false otherwise.
|
||||
*/
|
||||
supportIsInstalled(): boolean {
|
||||
return this._supportIsInstalled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current event index.
|
||||
*
|
||||
|
@ -69,6 +115,11 @@ class EventIndexPeg {
|
|||
return this.index;
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.index === null) return;
|
||||
this.index.startCrawler();
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.index === null) return;
|
||||
this.index.stopCrawler();
|
||||
|
|
|
@ -480,4 +480,9 @@ export const SETTINGS = {
|
|||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||
default: RIGHT_PANEL_PHASES.GroupMemberList,
|
||||
},
|
||||
"enableEventIndexing": {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||
displayName: _td("Enable message search in encrypted rooms"),
|
||||
default: true,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -145,7 +145,7 @@ export default class SettingsStore {
|
|||
callbackFn(originalSettingName, changedInRoomId, atLevel, newValAtLevel, newValue);
|
||||
};
|
||||
|
||||
console.log(`Starting watcher for ${settingName}@${roomId || '<null room>'}`);
|
||||
console.log(`Starting watcher for ${settingName}@${roomId || '<null room>'} as ID ${watcherId}`);
|
||||
SettingsStore._watchers[watcherId] = localizedCallback;
|
||||
defaultWatchManager.watchSetting(settingName, roomId, localizedCallback);
|
||||
|
||||
|
@ -159,8 +159,12 @@ export default class SettingsStore {
|
|||
* to cancel.
|
||||
*/
|
||||
static unwatchSetting(watcherReference) {
|
||||
if (!SettingsStore._watchers[watcherReference]) return;
|
||||
if (!SettingsStore._watchers[watcherReference]) {
|
||||
console.warn(`Ending non-existent watcher ID ${watcherReference}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Ending watcher ID ${watcherReference}`);
|
||||
defaultWatchManager.unwatchSetting(SettingsStore._watchers[watcherReference]);
|
||||
delete SettingsStore._watchers[watcherReference];
|
||||
}
|
||||
|
|
|
@ -30,6 +30,31 @@ export function formatCount(count) {
|
|||
return (count / 1000000000).toFixed(1) + "B"; // 10B is enough for anyone, right? :S
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a count showing the whole number but making it a bit more readable.
|
||||
* e.g: 1000 => 1,000
|
||||
*/
|
||||
export function formatCountLong(count) {
|
||||
const formatter = new Intl.NumberFormat();
|
||||
return formatter.format(count)
|
||||
}
|
||||
|
||||
/**
|
||||
* format a size in bytes into a human readable form
|
||||
* e.g: 1024 -> 1.00 KB
|
||||
*/
|
||||
export function formatBytes(bytes, decimals = 2) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* format a key into groups of 4 characters, for easier visual inspection
|
||||
*
|
||||
|
|
|
@ -5760,10 +5760,9 @@ mathml-tag-names@^2.0.1:
|
|||
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz#6dff66c99d55ecf739ca53c492e626f1d12a33cc"
|
||||
integrity sha512-pWB896KPGSGkp1XtyzRBftpTzwSOL0Gfk0wLvxt4f2mgzjY19o0LxJ3U25vNWTzsh7da+KTbuXQoQ3lOJZ8WHw==
|
||||
|
||||
matrix-js-sdk@4.0.0:
|
||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-4.0.0.tgz#c81bdc905af2ab1634527e5f542f2f15977d31cf"
|
||||
integrity sha512-Xbe36xL443qtEBH4xk0k39JabolqZfloK7fwYGMb/PgWO26VOzvw94XWahnIr5w83oxBAF9nFmP+7EnPG6IHnA==
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/21e4c597d9633aef606871cf9ffffaf039142be3"
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.8.3"
|
||||
another-json "^0.2.0"
|
||||
|
|
Loading…
Reference in a new issue