Merge branch 'develop' into gsouquet/message-bubbles-4635

This commit is contained in:
Germain Souquet
2021-07-07 13:07:43 +02:00
148 changed files with 2580 additions and 2149 deletions
@@ -21,8 +21,8 @@ interface IProps extends Omit<HTMLAttributes<HTMLDivElement>, "onScroll"> {
className?: string;
onScroll?: (event: Event) => void;
onWheel?: (event: WheelEvent) => void;
style?: React.CSSProperties
tabIndex?: number,
style?: React.CSSProperties;
tabIndex?: number;
wrappedRef?: (ref: HTMLDivElement) => void;
}
+8 -3
View File
@@ -19,6 +19,7 @@ import React from 'react';
import { Filter } from 'matrix-js-sdk/src/filter';
import { EventTimelineSet } from "matrix-js-sdk/src/models/event-timeline-set";
import { Direction } from "matrix-js-sdk/src/models/event-timeline";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from 'matrix-js-sdk/src/models/room';
import { TimelineWindow } from 'matrix-js-sdk/src/timeline-window';
@@ -37,7 +38,7 @@ import ResizeNotifier from '../../utils/ResizeNotifier';
interface IProps {
roomId: string;
onClose: () => void;
resizeNotifier: ResizeNotifier
resizeNotifier: ResizeNotifier;
}
interface IState {
@@ -129,7 +130,7 @@ class FilePanel extends React.Component<IProps, IState> {
}
}
public async fetchFileEventsServer(room: Room): Promise<void> {
public async fetchFileEventsServer(room: Room): Promise<EventTimelineSet> {
const client = MatrixClientPeg.get();
const filter = new Filter(client.credentials.userId);
@@ -153,7 +154,11 @@ class FilePanel extends React.Component<IProps, IState> {
return timelineSet;
}
private onPaginationRequest = (timelineWindow: TimelineWindow, direction: string, limit: number): void => {
private onPaginationRequest = (
timelineWindow: TimelineWindow,
direction: Direction,
limit: number,
): Promise<boolean> => {
const client = MatrixClientPeg.get();
const eventIndex = EventIndexPeg.get();
const roomId = this.props.roomId;
+1 -1
View File
@@ -36,7 +36,7 @@ import FlairStore from '../../stores/FlairStore';
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
import { makeGroupPermalink, makeUserPermalink } from "../../utils/permalinks/Permalinks";
import { Group } from "matrix-js-sdk/src/models/group";
import { sleep } from "../../utils/promise";
import { sleep } from "matrix-js-sdk/src/utils";
import RightPanelStore from "../../stores/RightPanelStore";
import AutoHideScrollbar from "./AutoHideScrollbar";
import { mediaFromMxc } from "../../customisations/Media";
+4 -4
View File
@@ -48,7 +48,7 @@ import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPay
import RoomListStore from "../../stores/room-list/RoomListStore";
import NonUrgentToastContainer from "./NonUrgentToastContainer";
import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload";
import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
import Modal from "../../Modal";
import { ICollapseConfig } from "../../resizer/distributors/collapse";
import HostSignupContainer from '../views/host_signup/HostSignupContainer';
@@ -81,14 +81,14 @@ interface IProps {
page_type: string;
autoJoin: boolean;
threepidInvite?: IThreepidInvite;
roomOobData?: object;
roomOobData?: IOOBData;
currentRoomId: string;
collapseLhs: boolean;
config: {
piwik: {
policyUrl: string;
},
[key: string]: any,
};
[key: string]: any;
};
currentUserId?: string;
currentGroupId?: string;
+3 -2
View File
@@ -19,6 +19,8 @@ import { createClient } from "matrix-js-sdk/src/matrix";
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { sleep, defer, IDeferred } from "matrix-js-sdk/src/utils";
// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by _AccessibleButton.scss
import 'focus-visible';
// what-input helps improve keyboard accessibility
@@ -55,7 +57,6 @@ import DMRoomMap from '../../utils/DMRoomMap';
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
import { FontWatcher } from '../../settings/watchers/FontWatcher';
import { storeRoomAliasInCache } from '../../RoomAliasCache';
import { defer, IDeferred, sleep } from "../../utils/promise";
import ToastStore from "../../stores/ToastStore";
import * as StorageManager from "../../utils/StorageManager";
import type LoggedInViewType from "./LoggedInView";
@@ -203,7 +204,7 @@ interface IState {
resizeNotifier: ResizeNotifier;
serverConfig?: ValidatedServerConfig;
ready: boolean;
threepidInvite?: IThreepidInvite,
threepidInvite?: IThreepidInvite;
roomOobData?: object;
pendingInitialSync?: boolean;
justRegistered?: boolean;
@@ -24,7 +24,7 @@ interface IProps {
}
interface IState {
toasts: ComponentClass[],
toasts: ComponentClass[];
}
@replaceableComponent("structures.NonUrgentToastContainer")
+5 -6
View File
@@ -23,7 +23,6 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import dis from '../../dispatcher/dispatcher';
import RateLimitedFunc from '../../ratelimitedfunc';
import GroupStore from '../../stores/GroupStore';
import {
RIGHT_PANEL_PHASES_NO_ARGS,
@@ -48,6 +47,7 @@ import FilePanel from "./FilePanel";
import NotificationPanel from "./NotificationPanel";
import ResizeNotifier from "../../utils/ResizeNotifier";
import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard";
import { throttle } from 'lodash';
interface IProps {
room?: Room; // if showing panels for a given room, this is set
@@ -73,7 +73,6 @@ interface IState {
export default class RightPanel extends React.Component<IProps, IState> {
static contextType = MatrixClientContext;
private readonly delayedUpdate: RateLimitedFunc;
private dispatcherRef: string;
constructor(props, context) {
@@ -84,12 +83,12 @@ export default class RightPanel extends React.Component<IProps, IState> {
isUserPrivilegedInGroup: null,
member: this.getUserForPanel(),
};
this.delayedUpdate = new RateLimitedFunc(() => {
this.forceUpdate();
}, 500);
}
private readonly delayedUpdate = throttle((): void => {
this.forceUpdate();
}, 500, { leading: true, trailing: true });
// Helper function to split out the logic for getPhaseFromProps() and the constructor
// as both are called at the same time in the constructor.
private getUserForPanel() {
+2 -2
View File
@@ -370,7 +370,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
private onFilterChange = (alias: string) => {
this.setState({
filterString: alias || null,
filterString: alias || "",
});
// don't send the request for a little bit,
@@ -389,7 +389,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
private onFilterClear = () => {
// update immediately
this.setState({
filterString: null,
filterString: "",
}, this.refreshRoomList);
if (this.filterTimeout) {
+12 -43
View File
@@ -37,13 +37,12 @@ import Modal from '../../Modal';
import * as sdk from '../../index';
import CallHandler, { PlaceCallType } from '../../CallHandler';
import dis from '../../dispatcher/dispatcher';
import rateLimitedFunc from '../../ratelimitedfunc';
import * as Rooms from '../../Rooms';
import eventSearch, { searchPagination } from '../../Searching';
import MainSplit from './MainSplit';
import RightPanel from './RightPanel';
import RoomViewStore from '../../stores/RoomViewStore';
import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
import RoomScrollStateStore, { ScrollState } from '../../stores/RoomScrollStateStore';
import WidgetEchoStore from '../../stores/WidgetEchoStore';
import SettingsStore from "../../settings/SettingsStore";
import { Layout } from "../../settings/Layout";
@@ -64,7 +63,7 @@ import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar";
import AuxPanel from "../views/rooms/AuxPanel";
import RoomHeader from "../views/rooms/RoomHeader";
import { XOR } from "../../@types/common";
import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
import EffectsOverlay from "../views/elements/EffectsOverlay";
import { containsEmoji } from '../../effects/utils';
import { CHAT_EFFECTS } from '../../effects';
@@ -82,6 +81,7 @@ import { IOpts } from "../../createRoom";
import { replaceableComponent } from "../../utils/replaceableComponent";
import UIStore from "../../stores/UIStore";
import EditorStateTransfer from "../../utils/EditorStateTransfer";
import { throttle } from "lodash";
const DEBUG = false;
let debuglog = function(msg: string) {};
@@ -94,22 +94,8 @@ if (DEBUG) {
}
interface IProps {
threepidInvite: IThreepidInvite,
// Any data about the room that would normally come from the homeserver
// but has been passed out-of-band, eg. the room name and avatar URL
// from an email invite (a workaround for the fact that we can't
// get this information from the HS using an email invite).
// Fields:
// * name (string) The room's name
// * avatarUrl (string) The mxc:// avatar URL for the room
// * inviterName (string) The display name of the person who
// * invited us to the room
oobData?: {
name?: string;
avatarUrl?: string;
inviterName?: string;
};
threepidInvite: IThreepidInvite;
oobData?: IOOBData;
resizeNotifier: ResizeNotifier;
justCreatedOpts?: IOpts;
@@ -675,8 +661,8 @@ export default class RoomView extends React.Component<IProps, IState> {
);
}
// cancel any pending calls to the rate_limited_funcs
this.updateRoomMembers.cancelPendingCall();
// cancel any pending calls to the throttled updated
this.updateRoomMembers.cancel();
for (const watcher of this.settingWatchers) {
SettingsStore.unwatchSetting(watcher);
@@ -1054,11 +1040,6 @@ export default class RoomView extends React.Component<IProps, IState> {
});
}
private updateTint() {
const room = this.state.room;
if (!room) return;
}
private onAccountData = (event: MatrixEvent) => {
const type = event.getType();
if ((type === "org.matrix.preview_urls" || type === "im.vector.web.settings") && this.state.room) {
@@ -1097,7 +1078,7 @@ export default class RoomView extends React.Component<IProps, IState> {
return;
}
this.updateRoomMembers(member);
this.updateRoomMembers();
};
private onMyMembership = (room: Room, membership: string, oldMembership: string) => {
@@ -1119,10 +1100,10 @@ export default class RoomView extends React.Component<IProps, IState> {
}
// rate limited because a power level change will emit an event for every member in the room.
private updateRoomMembers = rateLimitedFunc(() => {
private updateRoomMembers = throttle(() => {
this.updateDMState();
this.updateE2EStatus(this.state.room);
}, 500);
}, 500, { leading: true, trailing: true });
private checkDesktopNotifications() {
const memberCount = this.state.room.getJoinedMemberCount() + this.state.room.getInvitedMemberCount();
@@ -1266,7 +1247,7 @@ export default class RoomView extends React.Component<IProps, IState> {
});
};
private injectSticker(url, info, text) {
private injectSticker(url: string, info: object, text: string) {
if (this.context.isGuest()) {
dis.dispatch({ action: 'require_registration' });
return;
@@ -1465,13 +1446,6 @@ export default class RoomView extends React.Component<IProps, IState> {
});
};
private onLeaveClick = () => {
dis.dispatch({
action: 'leave_room',
room_id: this.state.room.roomId,
});
};
private onForgetClick = () => {
dis.dispatch({
action: 'forget_room',
@@ -1603,7 +1577,7 @@ export default class RoomView extends React.Component<IProps, IState> {
// get the current scroll position of the room, so that it can be
// restored when we switch back to it.
//
private getScrollState() {
private getScrollState(): ScrollState {
const messagePanel = this.messagePanel;
if (!messagePanel) return null;
@@ -1705,10 +1679,6 @@ export default class RoomView extends React.Component<IProps, IState> {
// otherwise react calls it with null on each update.
private gatherTimelinePanelRef = r => {
this.messagePanel = r;
if (r) {
console.log("updateTint from RoomView.gatherTimelinePanelRef");
this.updateTint();
}
};
private getOldRoom() {
@@ -2115,7 +2085,6 @@ export default class RoomView extends React.Component<IProps, IState> {
onSearchClick={this.onSearchClick}
onSettingsClick={this.onSettingsClick}
onForgetClick={(myMembership === "leave") ? this.onForgetClick : null}
onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
e2eStatus={this.state.e2eStatus}
onAppsClick={this.state.hasPinnedWidgets ? this.onAppsClick : null}
appsShown={this.state.showApps}
@@ -58,7 +58,7 @@ export interface ISpaceSummaryRoom {
avatar_url?: string;
guest_can_join: boolean;
name?: string;
num_joined_members: number
num_joined_members: number;
room_id: string;
topic?: string;
world_readable: boolean;
+16 -12
View File
@@ -16,11 +16,13 @@ limitations under the License.
import React, { createRef, ReactNode, SyntheticEvent } from 'react';
import ReactDOM from "react-dom";
import { Room } from "matrix-js-sdk/src/models/room";
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { TimelineSet } from "matrix-js-sdk/src/models/event-timeline-set";
import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
import { EventTimelineSet } from "matrix-js-sdk/src/models/event-timeline-set";
import { Direction, EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
import { TimelineWindow } from "matrix-js-sdk/src/timeline-window";
import { EventType, RelationType } from 'matrix-js-sdk/src/@types/event';
import { SyncState } from 'matrix-js-sdk/src/sync.api';
import SettingsStore from "../../settings/SettingsStore";
import { Layout } from "../../settings/Layout";
@@ -39,10 +41,8 @@ import { UIFeature } from "../../settings/UIFeature";
import { replaceableComponent } from "../../utils/replaceableComponent";
import { arrayFastClone } from "../../utils/arrays";
import MessagePanel from "./MessagePanel";
import { SyncState } from 'matrix-js-sdk/src/sync.api';
import { IScrollState } from "./ScrollPanel";
import { ActionPayload } from "../../dispatcher/payloads";
import { EventType } from 'matrix-js-sdk/src/@types/event';
import ResizeNotifier from "../../utils/ResizeNotifier";
import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
import Spinner from "../views/elements/Spinner";
@@ -65,7 +65,7 @@ interface IProps {
// representing. This may or may not have a room, depending on what it's
// a timeline representing. If it has a room, we maintain RRs etc for
// that room.
timelineSet: TimelineSet;
timelineSet: EventTimelineSet;
showReadReceipts?: boolean;
// Enable managing RRs and RMs. These require the timelineSet to have a room.
manageReadReceipts?: boolean;
@@ -125,7 +125,7 @@ interface IProps {
onReadMarkerUpdated?(): void;
// callback which is called when we wish to paginate the timeline window.
onPaginationRequest?(timelineWindow: TimelineWindow, direction: string, size: number): Promise<boolean>,
onPaginationRequest?(timelineWindow: TimelineWindow, direction: string, size: number): Promise<boolean>;
}
interface IState {
@@ -388,7 +388,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
private onPaginationRequest = (
timelineWindow: TimelineWindow,
direction: string,
direction: Direction,
size: number,
): Promise<boolean> => {
if (this.props.onPaginationRequest) {
@@ -579,7 +579,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
});
};
private onRoomTimelineReset = (room: Room, timelineSet: TimelineSet): void => {
private onRoomTimelineReset = (room: Room, timelineSet: EventTimelineSet): void => {
if (timelineSet !== this.props.timelineSet) return;
if (this.messagePanel.current && this.messagePanel.current.isAtBottom()) {
@@ -792,8 +792,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
// that sending an RR for the latest message will set our notif counter
// to zero: it may not do this if we send an RR for somewhere before the end.
if (this.isAtEndOfLiveTimeline()) {
this.props.timelineSet.room.setUnreadNotificationCount('total', 0);
this.props.timelineSet.room.setUnreadNotificationCount('highlight', 0);
this.props.timelineSet.room.setUnreadNotificationCount(NotificationCountType.Total, 0);
this.props.timelineSet.room.setUnreadNotificationCount(NotificationCountType.Highlight, 0);
dis.dispatch({
action: 'on_room_read',
roomId: this.props.timelineSet.room.roomId,
@@ -1416,7 +1416,11 @@ class TimelinePanel extends React.Component<IProps, IState> {
});
}
private getRelationsForEvent = (...args) => this.props.timelineSet.getRelationsForEvent(...args);
private getRelationsForEvent = (
eventId: string,
relationType: RelationType,
eventType: EventType | string,
) => this.props.timelineSet.getRelationsForEvent(eventId, relationType, eventType);
render() {
// just show a spinner while the timeline loads.
+4 -1
View File
@@ -26,6 +26,7 @@ import ProgressBar from "../views/elements/ProgressBar";
import AccessibleButton from "../views/elements/AccessibleButton";
import { IUpload } from "../../models/IUpload";
import { replaceableComponent } from "../../utils/replaceableComponent";
import MatrixClientContext from "../../contexts/MatrixClientContext";
interface IProps {
room: Room;
@@ -38,6 +39,8 @@ interface IState {
@replaceableComponent("structures.UploadBar")
export default class UploadBar extends React.Component<IProps, IState> {
static contextType = MatrixClientContext;
private dispatcherRef: string;
private mounted: boolean;
@@ -82,7 +85,7 @@ export default class UploadBar extends React.Component<IProps, IState> {
private onCancelClick = (ev) => {
ev.preventDefault();
ContentMessages.sharedInstance().cancelUpload(this.state.currentUpload.promise);
ContentMessages.sharedInstance().cancelUpload(this.state.currentUpload.promise, this.context);
};
render() {
@@ -15,39 +15,42 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
import SetupEncryptionBody from "./SetupEncryptionBody";
import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("structures.auth.CompleteSecurity")
export default class CompleteSecurity extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
};
interface IProps {
onFinished: () => void;
}
constructor() {
super();
interface IState {
phase: Phase;
}
@replaceableComponent("structures.auth.CompleteSecurity")
export default class CompleteSecurity extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
const store = SetupEncryptionStore.sharedInstance();
store.on("update", this._onStoreUpdate);
store.on("update", this.onStoreUpdate);
store.start();
this.state = { phase: store.phase };
}
_onStoreUpdate = () => {
private onStoreUpdate = (): void => {
const store = SetupEncryptionStore.sharedInstance();
this.setState({ phase: store.phase });
};
componentWillUnmount() {
public componentWillUnmount(): void {
const store = SetupEncryptionStore.sharedInstance();
store.off("update", this._onStoreUpdate);
store.off("update", this.onStoreUpdate);
store.stop();
}
render() {
public render() {
const AuthPage = sdk.getComponent("auth.AuthPage");
const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody");
const { phase } = this.state;
@@ -15,20 +15,19 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import AuthPage from '../../views/auth/AuthPage';
import CompleteSecurityBody from '../../views/auth/CompleteSecurityBody';
import CreateCrossSigningDialog from '../../views/dialogs/security/CreateCrossSigningDialog';
import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("structures.auth.E2eSetup")
export default class E2eSetup extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
accountPassword: PropTypes.string,
tokenLogin: PropTypes.bool,
};
interface IProps {
onFinished: () => void;
accountPassword?: string;
tokenLogin?: boolean;
}
@replaceableComponent("structures.auth.E2eSetup")
export default class E2eSetup extends React.Component<IProps> {
render() {
return (
<AuthPage>
@@ -17,7 +17,6 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { _t, _td } from '../../../languageHandler';
import * as sdk from '../../../index';
import Modal from "../../../Modal";
@@ -31,27 +30,50 @@ import PassphraseField from '../../views/auth/PassphraseField';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm';
// Phases
// Show the forgot password inputs
const PHASE_FORGOT = 1;
// Email is in the process of being sent
const PHASE_SENDING_EMAIL = 2;
// Email has been sent
const PHASE_EMAIL_SENT = 3;
// User has clicked the link in email and completed reset
const PHASE_DONE = 4;
import { IValidationResult } from "../../views/elements/Validation";
enum Phase {
// Show the forgot password inputs
Forgot = 1,
// Email is in the process of being sent
SendingEmail = 2,
// Email has been sent
EmailSent = 3,
// User has clicked the link in email and completed reset
Done = 4,
}
interface IProps {
serverConfig: ValidatedServerConfig;
onServerConfigChange: () => void;
onLoginClick?: () => void;
onComplete: () => void;
}
interface IState {
phase: Phase;
email: string;
password: string;
password2: string;
errorText: string;
// We perform liveliness checks later, but for now suppress the errors.
// We also track the server dead errors independently of the regular errors so
// that we can render it differently, and override any other error the user may
// be seeing.
serverIsAlive: boolean;
serverErrorIsFatal: boolean;
serverDeadError: string;
passwordFieldValid: boolean;
}
@replaceableComponent("structures.auth.ForgotPassword")
export default class ForgotPassword extends React.Component {
static propTypes = {
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
onServerConfigChange: PropTypes.func.isRequired,
onLoginClick: PropTypes.func,
onComplete: PropTypes.func.isRequired,
};
export default class ForgotPassword extends React.Component<IProps, IState> {
private reset: PasswordReset;
state = {
phase: PHASE_FORGOT,
phase: Phase.Forgot,
email: "",
password: "",
password2: "",
@@ -64,30 +86,31 @@ export default class ForgotPassword extends React.Component {
serverIsAlive: true,
serverErrorIsFatal: false,
serverDeadError: "",
passwordFieldValid: false,
};
constructor(props) {
constructor(props: IProps) {
super(props);
CountlyAnalytics.instance.track("onboarding_forgot_password_begin");
}
componentDidMount() {
public componentDidMount() {
this.reset = null;
this._checkServerLiveliness(this.props.serverConfig);
this.checkServerLiveliness(this.props.serverConfig);
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
// eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(newProps) {
public UNSAFE_componentWillReceiveProps(newProps: IProps): void {
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
// Do a liveliness check on the new URLs
this._checkServerLiveliness(newProps.serverConfig);
this.checkServerLiveliness(newProps.serverConfig);
}
async _checkServerLiveliness(serverConfig) {
private async checkServerLiveliness(serverConfig): Promise<void> {
try {
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(
serverConfig.hsUrl,
@@ -98,28 +121,28 @@ export default class ForgotPassword extends React.Component {
serverIsAlive: true,
});
} catch (e) {
this.setState(AutoDiscoveryUtils.authComponentStateForError(e, "forgot_password"));
this.setState(AutoDiscoveryUtils.authComponentStateForError(e, "forgot_password") as IState);
}
}
submitPasswordReset(email, password) {
public submitPasswordReset(email: string, password: string): void {
this.setState({
phase: PHASE_SENDING_EMAIL,
phase: Phase.SendingEmail,
});
this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl);
this.reset.resetPassword(email, password).then(() => {
this.setState({
phase: PHASE_EMAIL_SENT,
phase: Phase.EmailSent,
});
}, (err) => {
this.showErrorDialog(_t('Failed to send email') + ": " + err.message);
this.setState({
phase: PHASE_FORGOT,
phase: Phase.Forgot,
});
});
}
onVerify = async ev => {
private onVerify = async (ev: React.MouseEvent): Promise<void> => {
ev.preventDefault();
if (!this.reset) {
console.error("onVerify called before submitPasswordReset!");
@@ -127,17 +150,17 @@ export default class ForgotPassword extends React.Component {
}
try {
await this.reset.checkEmailLinkClicked();
this.setState({ phase: PHASE_DONE });
this.setState({ phase: Phase.Done });
} catch (err) {
this.showErrorDialog(err.message);
}
};
onSubmitForm = async ev => {
private onSubmitForm = async (ev: React.FormEvent): Promise<void> => {
ev.preventDefault();
// refresh the server errors, just in case the server came back online
await this._checkServerLiveliness(this.props.serverConfig);
await this.checkServerLiveliness(this.props.serverConfig);
await this['password_field'].validate({ allowEmpty: false });
@@ -172,27 +195,27 @@ export default class ForgotPassword extends React.Component {
}
};
onInputChanged = (stateKey, ev) => {
private onInputChanged = (stateKey: string, ev: React.FormEvent<HTMLInputElement>) => {
this.setState({
[stateKey]: ev.target.value,
});
[stateKey]: ev.currentTarget.value,
} as any);
};
onLoginClick = ev => {
private onLoginClick = (ev: React.MouseEvent): void => {
ev.preventDefault();
ev.stopPropagation();
this.props.onLoginClick();
};
showErrorDialog(body, title) {
public showErrorDialog(description: string, title?: string) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, {
title: title,
description: body,
title,
description,
});
}
onPasswordValidate(result) {
private onPasswordValidate(result: IValidationResult) {
this.setState({
passwordFieldValid: result.valid,
});
@@ -316,16 +339,16 @@ export default class ForgotPassword extends React.Component {
let resetPasswordJsx;
switch (this.state.phase) {
case PHASE_FORGOT:
case Phase.Forgot:
resetPasswordJsx = this.renderForgot();
break;
case PHASE_SENDING_EMAIL:
case Phase.SendingEmail:
resetPasswordJsx = this.renderSendingEmail();
break;
case PHASE_EMAIL_SENT:
case Phase.EmailSent:
resetPasswordJsx = this.renderEmailSent();
break;
case PHASE_DONE:
case Phase.Done:
resetPasswordJsx = this.renderDone();
break;
}
@@ -49,7 +49,7 @@ interface IProps {
// for operations like uploading cross-signing keys).
onLoggedIn(params: {
userId: string;
deviceId: string
deviceId: string;
homeserverUrl: string;
identityServerUrl?: string;
accessToken: string;
@@ -1,5 +1,5 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2020-2021 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,33 +15,43 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import Modal from '../../../Modal';
import VerificationRequestDialog from '../../views/dialogs/VerificationRequestDialog';
import * as sdk from '../../../index';
import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { ISecretStorageKeyInfo } from 'matrix-js-sdk';
import EncryptionPanel from "../../views/right_panel/EncryptionPanel";
import AccessibleButton from '../../views/elements/AccessibleButton';
import Spinner from '../../views/elements/Spinner';
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
function keyHasPassphrase(keyInfo) {
return (
function keyHasPassphrase(keyInfo: ISecretStorageKeyInfo): boolean {
return Boolean(
keyInfo.passphrase &&
keyInfo.passphrase.salt &&
keyInfo.passphrase.iterations
keyInfo.passphrase.iterations,
);
}
@replaceableComponent("structures.auth.SetupEncryptionBody")
export default class SetupEncryptionBody extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
};
interface IProps {
onFinished: (boolean) => void;
}
constructor() {
super();
interface IState {
phase: Phase;
verificationRequest: VerificationRequest;
backupInfo: IKeyBackupInfo;
}
@replaceableComponent("structures.auth.SetupEncryptionBody")
export default class SetupEncryptionBody extends React.Component<IProps, IState> {
constructor(props) {
super(props);
const store = SetupEncryptionStore.sharedInstance();
store.on("update", this._onStoreUpdate);
store.on("update", this.onStoreUpdate);
store.start();
this.state = {
phase: store.phase,
@@ -53,10 +63,10 @@ export default class SetupEncryptionBody extends React.Component {
};
}
_onStoreUpdate = () => {
private onStoreUpdate = () => {
const store = SetupEncryptionStore.sharedInstance();
if (store.phase === Phase.Finished) {
this.props.onFinished();
this.props.onFinished(true);
return;
}
this.setState({
@@ -66,18 +76,18 @@ export default class SetupEncryptionBody extends React.Component {
});
};
componentWillUnmount() {
public componentWillUnmount() {
const store = SetupEncryptionStore.sharedInstance();
store.off("update", this._onStoreUpdate);
store.off("update", this.onStoreUpdate);
store.stop();
}
_onUsePassphraseClick = async () => {
private onUsePassphraseClick = async () => {
const store = SetupEncryptionStore.sharedInstance();
store.usePassPhrase();
}
};
_onVerifyClick = () => {
private onVerifyClick = () => {
const cli = MatrixClientPeg.get();
const userId = cli.getUserId();
const requestPromise = cli.requestVerification(userId);
@@ -91,42 +101,44 @@ export default class SetupEncryptionBody extends React.Component {
request.cancel();
},
});
}
};
onSkipClick = () => {
private onSkipClick = () => {
const store = SetupEncryptionStore.sharedInstance();
store.skip();
}
};
onSkipConfirmClick = () => {
private onSkipConfirmClick = () => {
const store = SetupEncryptionStore.sharedInstance();
store.skipConfirm();
}
};
onSkipBackClick = () => {
private onSkipBackClick = () => {
const store = SetupEncryptionStore.sharedInstance();
store.returnAfterSkip();
}
};
onDoneClick = () => {
private onDoneClick = () => {
const store = SetupEncryptionStore.sharedInstance();
store.done();
}
};
render() {
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
private onEncryptionPanelClose = () => {
this.props.onFinished(false);
};
public render() {
const {
phase,
} = this.state;
if (this.state.verificationRequest) {
const EncryptionPanel = sdk.getComponent("views.right_panel.EncryptionPanel");
return <EncryptionPanel
layout="dialog"
verificationRequest={this.state.verificationRequest}
onClose={this.props.onFinished}
onClose={this.onEncryptionPanelClose}
member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)}
isRoomEncrypted={false}
/>;
} else if (phase === Phase.Intro) {
const store = SetupEncryptionStore.sharedInstance();
@@ -139,14 +151,14 @@ export default class SetupEncryptionBody extends React.Component {
let useRecoveryKeyButton;
if (recoveryKeyPrompt) {
useRecoveryKeyButton = <AccessibleButton kind="link" onClick={this._onUsePassphraseClick}>
useRecoveryKeyButton = <AccessibleButton kind="link" onClick={this.onUsePassphraseClick}>
{recoveryKeyPrompt}
</AccessibleButton>;
}
let verifyButton;
if (store.hasDevicesToVerifyAgainst) {
verifyButton = <AccessibleButton kind="primary" onClick={this._onVerifyClick}>
verifyButton = <AccessibleButton kind="primary" onClick={this.onVerifyClick}>
{ _t("Use another login") }
</AccessibleButton>;
}
@@ -217,7 +229,6 @@ export default class SetupEncryptionBody extends React.Component {
</div>
);
} else if (phase === Phase.Busy || phase === Phase.Loading) {
const Spinner = sdk.getComponent('views.elements.Spinner');
return <Spinner />;
} else {
console.log(`SetupEncryptionBody: Unknown phase ${phase}`);
@@ -49,7 +49,7 @@ interface IProps {
fragmentAfterLogin?: string;
// Called when the SSO login completes
onTokenLoginCompleted: () => void,
onTokenLoginCompleted: () => void;
}
interface IState {