mirror of https://github.com/vector-im/riot-web
Merge branch 'develop' into travis/update-qr-code
commit
a8cfde72e1
|
@ -264,6 +264,9 @@ limitations under the License.
|
|||
display: block;
|
||||
margin: 16px 0;
|
||||
}
|
||||
button.mx_UserInfo_verify {
|
||||
width: 100%; // FIXME get rid of this once we get rid of DialogButtons here
|
||||
}
|
||||
}
|
||||
|
||||
.mx_UserInfo.mx_UserInfo_smallAvatar {
|
||||
|
|
|
@ -22,8 +22,9 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
|||
import { accessSecretStorage } from '../../../CrossSigningManager';
|
||||
|
||||
const PHASE_INTRO = 0;
|
||||
const PHASE_DONE = 1;
|
||||
const PHASE_CONFIRM_SKIP = 2;
|
||||
const PHASE_BUSY = 1;
|
||||
const PHASE_DONE = 2;
|
||||
const PHASE_CONFIRM_SKIP = 3;
|
||||
|
||||
export default class CompleteSecurity extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -39,6 +40,7 @@ export default class CompleteSecurity extends React.Component {
|
|||
// the presence of it insidicating that we're in 'verify mode'.
|
||||
// Because of the latter, it lives in the state.
|
||||
verificationRequest: null,
|
||||
backupInfo: null,
|
||||
};
|
||||
MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequest);
|
||||
}
|
||||
|
@ -53,10 +55,16 @@ export default class CompleteSecurity extends React.Component {
|
|||
}
|
||||
|
||||
onStartClick = async () => {
|
||||
this.setState({
|
||||
phase: PHASE_BUSY,
|
||||
});
|
||||
const cli = MatrixClientPeg.get();
|
||||
const backupInfo = await cli.getKeyBackupVersion();
|
||||
this.setState({backupInfo});
|
||||
try {
|
||||
await accessSecretStorage(async () => {
|
||||
await cli.checkOwnCrossSigningTrust();
|
||||
if (backupInfo) await cli.restoreKeyBackupWithSecretStorage(backupInfo);
|
||||
});
|
||||
|
||||
if (cli.getCrossSigningId()) {
|
||||
|
@ -66,6 +74,9 @@ export default class CompleteSecurity extends React.Component {
|
|||
}
|
||||
} catch (e) {
|
||||
// this will throw if the user hits cancel, so ignore
|
||||
this.setState({
|
||||
phase: PHASE_INTRO,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,13 +166,21 @@ export default class CompleteSecurity extends React.Component {
|
|||
} else if (phase === PHASE_DONE) {
|
||||
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_verified"></span>;
|
||||
title = _t("Session verified");
|
||||
let message;
|
||||
if (this.state.backupInfo) {
|
||||
message = <p>{_t(
|
||||
"Your new session is now verified. It has access to your " +
|
||||
"encrypted messages, and other users will see it as trusted.",
|
||||
)}</p>;
|
||||
} else {
|
||||
message = <p>{_t(
|
||||
"Your new session is now verified. Other users will see it as trusted.",
|
||||
)}</p>;
|
||||
}
|
||||
body = (
|
||||
<div>
|
||||
<div className="mx_CompleteSecurity_heroIcon mx_E2EIcon_verified"></div>
|
||||
<p>{_t(
|
||||
"Your new session is now verified. It has access to your " +
|
||||
"encrypted messages, and other users will see it as trusted.",
|
||||
)}</p>
|
||||
{message}
|
||||
<div className="mx_CompleteSecurity_actionRow">
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
|
@ -198,6 +217,11 @@ export default class CompleteSecurity extends React.Component {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (phase === PHASE_BUSY) {
|
||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
||||
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning"></span>;
|
||||
title = '';
|
||||
body = <Spinner />;
|
||||
} else {
|
||||
throw new Error(`Unknown phase ${phase}`);
|
||||
}
|
||||
|
|
|
@ -351,9 +351,20 @@ export default class InviteDialog extends React.PureComponent {
|
|||
continue;
|
||||
}
|
||||
|
||||
const lastEventTs = room.timeline && room.timeline.length
|
||||
? room.timeline[room.timeline.length - 1].getTs()
|
||||
: 0;
|
||||
// Find the last timestamp for a message event
|
||||
const searchTypes = ["m.room.message", "m.room.encrypted", "m.sticker"];
|
||||
const maxSearchEvents = 20; // to prevent traversing history
|
||||
let lastEventTs = 0;
|
||||
if (room.timeline && room.timeline.length) {
|
||||
for (let i = room.timeline.length - 1; i >= 0; i--) {
|
||||
const ev = room.timeline[i];
|
||||
if (searchTypes.includes(ev.getType())) {
|
||||
lastEventTs = ev.getTs();
|
||||
break;
|
||||
}
|
||||
if (room.timeline.length - i > maxSearchEvents) break;
|
||||
}
|
||||
}
|
||||
if (!lastEventTs) {
|
||||
// something weird is going on with this room
|
||||
console.warn(`[Invite:Recents] ${userId} (${room.roomId}) has a weird last timestamp: ${lastEventTs}`);
|
||||
|
@ -747,6 +758,12 @@ export default class InviteDialog extends React.PureComponent {
|
|||
};
|
||||
|
||||
_onPaste = async (e) => {
|
||||
if (this.state.filterText) {
|
||||
// if the user has already typed something, just let them
|
||||
// paste normally.
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent the text being pasted into the textarea
|
||||
e.preventDefault();
|
||||
|
||||
|
@ -937,6 +954,7 @@ export default class InviteDialog extends React.PureComponent {
|
|||
value={this.state.filterText}
|
||||
ref={this._editorRef}
|
||||
onPaste={this._onPaste}
|
||||
autoFocus={true}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
|
|
|
@ -36,7 +36,7 @@ const EncryptionPanel = ({verificationRequest, member, onClose}) => {
|
|||
setRequest(verificationRequest);
|
||||
}, [verificationRequest]);
|
||||
|
||||
const [phase, setPhase] = useState(false);
|
||||
const [phase, setPhase] = useState(undefined);
|
||||
const changeHandler = useCallback(() => {
|
||||
// handle transitions -> cancelled for mismatches which fire a modal instead of showing a card
|
||||
if (request && request.cancelled && MISMATCHES.includes(request.cancellationCode)) {
|
||||
|
@ -71,7 +71,7 @@ const EncryptionPanel = ({verificationRequest, member, onClose}) => {
|
|||
setRequest(verificationRequest);
|
||||
}, [member.userId]);
|
||||
|
||||
const requested = request && phase === PHASE_REQUESTED;
|
||||
const requested = request && (phase === PHASE_REQUESTED || phase === undefined);
|
||||
if (!request || requested) {
|
||||
return <EncryptionInfo onStartVerification={onStartVerification} member={member} pending={requested} />;
|
||||
} else {
|
||||
|
|
|
@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import * as sdk from '../../../index';
|
||||
import {verificationMethods} from 'matrix-js-sdk/src/crypto';
|
||||
|
@ -23,6 +24,8 @@ import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
|||
import {_t} from "../../../languageHandler";
|
||||
import E2EIcon from "../rooms/E2EIcon";
|
||||
import {
|
||||
PHASE_UNSENT,
|
||||
PHASE_REQUESTED,
|
||||
PHASE_READY,
|
||||
PHASE_DONE,
|
||||
PHASE_STARTED,
|
||||
|
@ -31,6 +34,20 @@ import {
|
|||
import Spinner from "../elements/Spinner";
|
||||
|
||||
export default class VerificationPanel extends React.PureComponent {
|
||||
static propTypes = {
|
||||
request: PropTypes.object.isRequired,
|
||||
member: PropTypes.object.isRequired,
|
||||
phase: PropTypes.oneOf([
|
||||
PHASE_UNSENT,
|
||||
PHASE_REQUESTED,
|
||||
PHASE_READY,
|
||||
PHASE_STARTED,
|
||||
PHASE_CANCELLED,
|
||||
PHASE_DONE,
|
||||
]).isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
|
@ -147,11 +164,11 @@ export default class VerificationPanel extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {member} = this.props;
|
||||
const {member, phase} = this.props;
|
||||
|
||||
const displayName = member.displayName || member.name || member.userId;
|
||||
|
||||
switch (this.props.phase) {
|
||||
switch (phase) {
|
||||
case PHASE_READY:
|
||||
return this.renderQRPhase();
|
||||
case PHASE_STARTED:
|
||||
|
@ -174,6 +191,7 @@ export default class VerificationPanel extends React.PureComponent {
|
|||
case PHASE_CANCELLED:
|
||||
return this.renderCancelledPhase();
|
||||
}
|
||||
console.error("VerificationPanel unhandled phase:", phase);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -1113,7 +1113,8 @@ export default createReactClass({
|
|||
}
|
||||
}
|
||||
|
||||
const avatarUrl = this.props.member.getMxcAvatarUrl();
|
||||
const {member} = this.props;
|
||||
const avatarUrl = member.avatarUrl || (member.getMxcAvatarUrl && member.getMxcAvatarUrl());
|
||||
let avatarElement;
|
||||
if (avatarUrl) {
|
||||
const httpUrl = this.context.mxcUrlToHttp(avatarUrl, 800, 800);
|
||||
|
|
|
@ -27,7 +27,8 @@ function capFirst(s) {
|
|||
|
||||
export default class VerificationShowSas extends React.Component {
|
||||
static propTypes = {
|
||||
displayName: PropTypes.string.isRequired,
|
||||
pending: PropTypes.bool,
|
||||
displayName: PropTypes.string, // required if pending is true
|
||||
onDone: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
sas: PropTypes.object.isRequired,
|
||||
|
@ -95,7 +96,7 @@ export default class VerificationShowSas extends React.Component {
|
|||
confirm = <DialogButtons
|
||||
primaryButton={_t("They match")}
|
||||
onPrimaryButtonClick={this.onMatchClick}
|
||||
primaryButtonClassName="mx_UserInfo_verify"
|
||||
primaryButtonClass="mx_UserInfo_verify"
|
||||
cancelButton={_t("They don't match")}
|
||||
onCancel={this.props.onCancel}
|
||||
cancelButtonClass="mx_UserInfo_verify"
|
||||
|
|
|
@ -1912,6 +1912,7 @@
|
|||
"Start": "Start",
|
||||
"Session verified": "Session verified",
|
||||
"Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.",
|
||||
"Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.",
|
||||
"Done": "Done",
|
||||
"Without completing security on this device, it won’t have access to encrypted messages.": "Without completing security on this device, it won’t have access to encrypted messages.",
|
||||
"Go Back": "Go Back",
|
||||
|
|
|
@ -105,7 +105,7 @@ export default class BaseEventIndexManager {
|
|||
* @return {Promise} A promise that will resolve when the event index is
|
||||
* initialized.
|
||||
*/
|
||||
async initEventIndex(): Promise<> {
|
||||
async initEventIndex(): Promise<void> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
|
@ -146,15 +146,15 @@ export default class BaseEventIndexManager {
|
|||
* @return {Promise} A promise that will resolve once the queued up events
|
||||
* were added to the index.
|
||||
*/
|
||||
async commitLiveEvents(): Promise<> {
|
||||
async commitLiveEvents(): Promise<void> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the event index using the given term for matching events.
|
||||
*
|
||||
* @param {SearchArgs} searchArgs The search configuration sets what should
|
||||
* be searched for and what should be contained in the search result.
|
||||
* @param {SearchArgs} searchArgs The search configuration for the search,
|
||||
* sets the search term and determines the search result contents.
|
||||
*
|
||||
* @return {Promise<[SearchResult]>} A promise that will resolve to an array
|
||||
* of search results once the search is done.
|
||||
|
@ -197,7 +197,7 @@ export default class BaseEventIndexManager {
|
|||
* @return {Promise} A promise that will resolve once the checkpoint has
|
||||
* been stored.
|
||||
*/
|
||||
async addCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<> {
|
||||
async addCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<void> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
|
@ -210,7 +210,7 @@ export default class BaseEventIndexManager {
|
|||
* @return {Promise} A promise that will resolve once the checkpoint has
|
||||
* been removed.
|
||||
*/
|
||||
async removeCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<> {
|
||||
async removeCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<void> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
|
@ -250,7 +250,7 @@ export default class BaseEventIndexManager {
|
|||
* @return {Promise} A promise that will resolve once the event index has
|
||||
* been closed.
|
||||
*/
|
||||
async closeEventIndex(): Promise<> {
|
||||
async closeEventIndex(): Promise<void> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
|
||||
|
@ -260,7 +260,7 @@ export default class BaseEventIndexManager {
|
|||
* @return {Promise} A promise that will resolve once the event index has
|
||||
* been deleted.
|
||||
*/
|
||||
async deleteEventIndex(): Promise<> {
|
||||
async deleteEventIndex(): Promise<void> {
|
||||
throw new Error("Unimplemented");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,9 @@ export default class EventIndex extends EventEmitter {
|
|||
this.registerListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register event listeners that are necessary for the event index to work.
|
||||
*/
|
||||
registerListeners() {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
|
@ -60,6 +63,9 @@ export default class EventIndex extends EventEmitter {
|
|||
client.on('Room.timelineReset', this.onTimelineReset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the event index specific event listeners.
|
||||
*/
|
||||
removeListeners() {
|
||||
const client = MatrixClientPeg.get();
|
||||
if (client === null) return;
|
||||
|
@ -116,6 +122,15 @@ export default class EventIndex extends EventEmitter {
|
|||
}));
|
||||
}
|
||||
|
||||
/*
|
||||
* The sync event listener.
|
||||
*
|
||||
* The listener has two cases:
|
||||
* - First sync after start up, check if the index is empty, add
|
||||
* initial checkpoints, if so. Start the crawler background task.
|
||||
* - Every other sync, tell the event index to commit all the queued up
|
||||
* live events
|
||||
*/
|
||||
onSync = async (state, prevState, data) => {
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
|
||||
|
@ -139,6 +154,14 @@ export default class EventIndex extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The Room.timeline listener.
|
||||
*
|
||||
* This listener waits for live events in encrypted rooms, if they are
|
||||
* decrypted or unencrypted we queue them to be added to the index,
|
||||
* otherwise we save their event id and wait for them in the Event.decrypted
|
||||
* listener.
|
||||
*/
|
||||
onRoomTimeline = async (ev, room, toStartOfTimeline, removed, data) => {
|
||||
// We only index encrypted rooms locally.
|
||||
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;
|
||||
|
@ -162,6 +185,12 @@ export default class EventIndex extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The Event.decrypted listener.
|
||||
*
|
||||
* Checks if the event was marked for addition in the Room.timeline
|
||||
* listener, if so queues it up to be added to the index.
|
||||
*/
|
||||
onEventDecrypted = async (ev, err) => {
|
||||
const eventId = ev.getId();
|
||||
|
||||
|
@ -171,6 +200,41 @@ export default class EventIndex extends EventEmitter {
|
|||
await this.addLiveEventToIndex(ev);
|
||||
}
|
||||
|
||||
/*
|
||||
* The Room.timelineReset listener.
|
||||
*
|
||||
* Listens for timeline resets that are caused by a limited timeline to
|
||||
* re-add checkpoints for rooms that need to be crawled again.
|
||||
*/
|
||||
onTimelineReset = async (room, timelineSet, resetAllTimelines) => {
|
||||
if (room === null) return;
|
||||
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;
|
||||
|
||||
const timeline = room.getLiveTimeline();
|
||||
const token = timeline.getPaginationToken("b");
|
||||
|
||||
const backwardsCheckpoint = {
|
||||
roomId: room.roomId,
|
||||
token: token,
|
||||
fullCrawl: false,
|
||||
direction: "b",
|
||||
};
|
||||
|
||||
console.log("EventIndex: Added checkpoint because of a limited timeline",
|
||||
backwardsCheckpoint);
|
||||
|
||||
await indexManager.addCrawlerCheckpoint(backwardsCheckpoint);
|
||||
|
||||
this.crawlerCheckpoints.push(backwardsCheckpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue up live events to be added to the event index.
|
||||
*
|
||||
* @param {MatrixEvent} ev The event that should be added to the index.
|
||||
*/
|
||||
async addLiveEventToIndex(ev) {
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
|
||||
|
@ -190,10 +254,24 @@ export default class EventIndex extends EventEmitter {
|
|||
indexManager.addEventToIndex(e, profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emmit that the crawler has changed the checkpoint that it's currently
|
||||
* handling.
|
||||
*/
|
||||
emitNewCheckpoint() {
|
||||
this.emit("changedCheckpoint", this.currentRoom());
|
||||
}
|
||||
|
||||
/**
|
||||
* The main crawler loop.
|
||||
*
|
||||
* Goes through crawlerCheckpoints and fetches events from the server to be
|
||||
* added to the EventIndex.
|
||||
*
|
||||
* If a /room/{roomId}/messages request doesn't contain any events, stop the
|
||||
* crawl, otherwise create a new checkpoint and push it to the
|
||||
* crawlerCheckpoints queue so we go through them in a round-robin way.
|
||||
*/
|
||||
async crawlerFunc() {
|
||||
let cancelled = false;
|
||||
|
||||
|
@ -328,8 +406,6 @@ export default class EventIndex extends EventEmitter {
|
|||
].indexOf(value.getType()) >= 0
|
||||
&& !value.isRedacted() && !value.isDecryptionFailure()
|
||||
);
|
||||
// TODO do we need to check if the event has all the valid
|
||||
// attributes?
|
||||
};
|
||||
|
||||
// TODO if there are no events at this point we're missing a lot
|
||||
|
@ -394,40 +470,28 @@ export default class EventIndex extends EventEmitter {
|
|||
console.log("EventIndex: Stopping crawler function");
|
||||
}
|
||||
|
||||
onTimelineReset = async (room, timelineSet, resetAllTimelines) => {
|
||||
if (room === null) return;
|
||||
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;
|
||||
|
||||
const timeline = room.getLiveTimeline();
|
||||
const token = timeline.getPaginationToken("b");
|
||||
|
||||
const backwardsCheckpoint = {
|
||||
roomId: room.roomId,
|
||||
token: token,
|
||||
fullCrawl: false,
|
||||
direction: "b",
|
||||
};
|
||||
|
||||
console.log("EventIndex: Added checkpoint because of a limited timeline",
|
||||
backwardsCheckpoint);
|
||||
|
||||
await indexManager.addCrawlerCheckpoint(backwardsCheckpoint);
|
||||
|
||||
this.crawlerCheckpoints.push(backwardsCheckpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the crawler background task.
|
||||
*/
|
||||
startCrawler() {
|
||||
if (this._crawler !== null) return;
|
||||
this.crawlerFunc();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the crawler background task.
|
||||
*/
|
||||
stopCrawler() {
|
||||
if (this._crawler === null) return;
|
||||
this._crawler.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the event index.
|
||||
*
|
||||
* This removes all the MatrixClient event listeners, stops the crawler
|
||||
* task, and closes the index.
|
||||
*/
|
||||
async close() {
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
this.removeListeners();
|
||||
|
@ -435,6 +499,15 @@ export default class EventIndex extends EventEmitter {
|
|||
return indexManager.closeEventIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the event index using the given term for matching events.
|
||||
*
|
||||
* @param {SearchArgs} searchArgs The search configuration for the search,
|
||||
* sets the search term and determines the search result contents.
|
||||
*
|
||||
* @return {Promise<[SearchResult]>} A promise that will resolve to an array
|
||||
* of search results once the search is done.
|
||||
*/
|
||||
async search(searchArgs) {
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
return indexManager.searchEventIndex(searchArgs);
|
||||
|
@ -634,6 +707,12 @@ export default class EventIndex extends EventEmitter {
|
|||
return paginationPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistical information of the index.
|
||||
*
|
||||
* @return {Promise<IndexStats>} A promise that will resolve to the index
|
||||
* statistics.
|
||||
*/
|
||||
async getStats() {
|
||||
const indexManager = PlatformPeg.get().getEventIndexingManager();
|
||||
return indexManager.getStats();
|
||||
|
|
Loading…
Reference in New Issue