Add basic types

pull/21833/head
J. Ryan Stinnett 2021-04-06 12:26:50 +01:00
parent 0e92251f70
commit d7e6f4b4b5
29 changed files with 542 additions and 340 deletions

View File

@ -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.
@ -40,6 +40,8 @@ import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
import VoipUserMapper from "../VoipUserMapper";
import {SpaceStoreClass} from "../stores/SpaceStore";
import {VoiceRecording} from "../voice/VoiceRecording";
import TypingStore from "../stores/TypingStore";
import { EventIndexPeg } from "../indexing/EventIndexPeg";
declare global {
interface Window {
@ -72,11 +74,15 @@ declare global {
mxVoipUserMapper: VoipUserMapper;
mxSpaceStore: SpaceStoreClass;
mxVoiceRecorder: typeof VoiceRecording;
mxTypingStore: TypingStore;
mxEventIndexPeg: EventIndexPeg;
}
interface Document {
// https://developer.mozilla.org/en-US/docs/Web/API/Document/hasStorageAccess
hasStorageAccess?: () => Promise<boolean>;
// https://developer.mozilla.org/en-US/docs/Web/API/Document/requestStorageAccess
requestStorageAccess?: () => Promise<undefined>;
// Safari & IE11 only have this prefixed: we used prefixed versions
// previously so let's continue to support them for now

View File

@ -1,9 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2015-2021 The Matrix.org Foundation C.I.C.
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
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.
@ -59,7 +56,7 @@ export type LoginFlow = ISSOFlow | IPasswordFlow;
// TODO: Move this to JS SDK
/* eslint-disable camelcase */
interface ILoginParams {
identifier?: string;
identifier?: object;
password?: string;
token?: string;
device_id?: string;

View File

@ -1,6 +1,5 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2016, 2019, 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.
@ -17,7 +16,7 @@ limitations under the License.
import url from 'url';
import SettingsStore from "./settings/SettingsStore";
import { Service, startTermsFlow, TermsNotSignedError } from './Terms';
import { Service, startTermsFlow, TermsInteractionCallback, TermsNotSignedError } from './Terms';
import {MatrixClientPeg} from "./MatrixClientPeg";
import request from "browser-request";
@ -31,6 +30,12 @@ const imApiVersion = "1.1";
// TODO: Generify the name of this class and all components within - it's not just for Scalar.
export default class ScalarAuthClient {
private apiUrl: string;
private uiUrl: string;
private scalarToken: string;
private termsInteractionCallback: TermsInteractionCallback;
private isDefaultManager: boolean;
constructor(apiUrl, uiUrl) {
this.apiUrl = apiUrl;
this.uiUrl = uiUrl;
@ -154,7 +159,7 @@ export default class ScalarAuthClient {
parsedImRestUrl.pathname = '';
return startTermsFlow([new Service(
SERVICE_TYPES.IM,
parsedImRestUrl.format(),
url.format(parsedImRestUrl),
token,
)], this.termsInteractionCallback).then(() => {
return token;
@ -243,7 +248,7 @@ export default class ScalarAuthClient {
disableWidgetAssets(widgetType: WidgetType, widgetId) {
let url = this.apiUrl + '/widgets/set_assets_state';
url = this.getStarterLink(url);
return new Promise((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
request({
method: 'GET', // XXX: Actions shouldn't be GET requests
uri: url,

View File

@ -1,5 +1,5 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019, 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.
@ -17,7 +17,7 @@ limitations under the License.
import classNames from 'classnames';
import {MatrixClientPeg} from './MatrixClientPeg';
import * as sdk from './';
import * as sdk from '.';
import Modal from './Modal';
export class TermsNotSignedError extends Error {}
@ -27,6 +27,10 @@ export class TermsNotSignedError extends Error {}
* require agreement from the user before the user can use that service.
*/
export class Service {
public serviceType: string;
public baseUrl: string;
public accessToken: string;
/**
* @param {MatrixClient.SERVICE_TYPES} serviceType The type of service
* @param {string} baseUrl The Base URL of the service (ie. before '/_matrix')
@ -39,6 +43,26 @@ export class Service {
}
}
interface Policy {
// @ts-ignore: No great way to express indexed types together with other keys
version: string;
[lang: string]: {
url: string;
};
}
type Policies = {
[policy: string]: Policy,
};
export type TermsInteractionCallback = (
policiesAndServicePairs: {
service: Service,
policies: Policies,
}[],
agreedUrls: string[],
extraClassNames?: string,
) => Promise<string[]>;
/**
* Start a flow where the user is presented with terms & conditions for some services
*
@ -51,8 +75,8 @@ export class Service {
* if they cancel.
*/
export async function startTermsFlow(
services,
interactionCallback = dialogTermsInteractionCallback,
services: Service[],
interactionCallback: TermsInteractionCallback = dialogTermsInteractionCallback,
) {
const termsPromises = services.map(
(s) => MatrixClientPeg.get().getTerms(s.serviceType, s.baseUrl),
@ -77,7 +101,7 @@ export async function startTermsFlow(
* }
*/
const terms = await Promise.all(termsPromises);
const terms: { policies: Policies }[] = await Promise.all(termsPromises);
const policiesAndServicePairs = terms.map((t, i) => { return { 'service': services[i], 'policies': t.policies }; });
// fetch the set of agreed policy URLs from account data
@ -158,10 +182,13 @@ export async function startTermsFlow(
}
export function dialogTermsInteractionCallback(
policiesAndServicePairs,
agreedUrls,
extraClassNames,
) {
policiesAndServicePairs: {
service: Service,
policies: { [policy: string]: Policy },
}[],
agreedUrls: string[],
extraClassNames?: string,
): Promise<string[]> {
return new Promise((resolve, reject) => {
console.log("Terms that need agreement", policiesAndServicePairs);
const TermsDialog = sdk.getComponent("views.dialogs.TermsDialog");

View File

@ -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.
@ -16,7 +16,6 @@ limitations under the License.
import React from 'react';
import * as sdk from '../../../../index';
import PropTypes from 'prop-types';
import { _t } from '../../../../languageHandler';
import SdkConfig from '../../../../SdkConfig';
import SettingsStore from "../../../../settings/SettingsStore";
@ -26,14 +25,23 @@ import {formatBytes, formatCountLong} from "../../../../utils/FormattingUtils";
import EventIndexPeg from "../../../../indexing/EventIndexPeg";
import {SettingLevel} from "../../../../settings/SettingLevel";
interface IProps {
onFinished: (boolean) => {},
}
interface IState {
eventIndexSize: number;
eventCount: number;
crawlingRoomsCount: number;
roomCount: number;
currentRoom: string;
crawlerSleepTime: number;
}
/*
* 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,
};
export default class ManageEventIndexDialog extends React.Component<IProps, IState> {
constructor(props) {
super(props);
@ -84,7 +92,7 @@ export default class ManageEventIndexDialog extends React.Component {
}
}
async componentDidMount(): void {
async componentDidMount(): Promise<void> {
let eventIndexSize = 0;
let crawlingRoomsCount = 0;
let roomCount = 0;

View File

@ -1,5 +1,5 @@
/*
Copyright 2015, 2016, 2017, 2018, 2019 The Matrix.org Foundation C.I.C.
Copyright 2015-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.
@ -94,7 +94,7 @@ interface IState {
// be seeing.
serverIsAlive: boolean;
serverErrorIsFatal: boolean;
serverDeadError: string;
serverDeadError?: ReactNode;
}
/*

View File

@ -1,5 +1,5 @@
/*
Copyright 2015, 2016, 2017, 2018, 2019, 2020 The Matrix.org Foundation C.I.C.
Copyright 2015-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.
@ -95,7 +95,7 @@ interface IState {
// be seeing.
serverIsAlive: boolean;
serverErrorIsFatal: boolean;
serverDeadError: string;
serverDeadError?: ReactNode;
// Our matrix client - part of state because we can't render the UI auth
// component without it.

View File

@ -1,5 +1,5 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019-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,14 +15,13 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from '../../../languageHandler';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import * as Lifecycle from '../../../Lifecycle';
import Modal from '../../../Modal';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {sendLoginRequest} from "../../../Login";
import {ISSOFlow, LoginFlow, sendLoginRequest} from "../../../Login";
import AuthPage from "../../views/auth/AuthPage";
import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "../../../BasePlatform";
import SSOButtons from "../../views/elements/SSOButtons";
@ -42,26 +41,38 @@ const FLOWS_TO_VIEWS = {
"m.login.sso": LOGIN_VIEW.SSO,
};
@replaceableComponent("structures.auth.SoftLogout")
export default class SoftLogout extends React.Component {
static propTypes = {
// Query parameters from MatrixChat
realQueryParams: PropTypes.object, // {loginToken}
// Called when the SSO login completes
onTokenLoginCompleted: PropTypes.func,
interface IProps {
// Query parameters from MatrixChat
realQueryParams: {
loginToken?: string;
};
fragmentAfterLogin?: string;
constructor() {
super();
// Called when the SSO login completes
onTokenLoginCompleted: () => void,
}
interface IState {
loginView: number;
keyBackupNeeded: boolean;
busy: boolean;
password: string;
errorText: string;
flows: LoginFlow[];
}
@replaceableComponent("structures.auth.SoftLogout")
export default class SoftLogout extends React.Component<IProps, IState> {
constructor(props) {
super(props);
this.state = {
loginView: LOGIN_VIEW.LOADING,
keyBackupNeeded: true, // assume we do while we figure it out (see componentDidMount)
busy: false,
password: "",
errorText: "",
flows: [],
};
}
@ -247,7 +258,7 @@ export default class SoftLogout extends React.Component {
} // else we already have a message and should use it (key backup warning)
const loginType = this.state.loginView === LOGIN_VIEW.CAS ? "cas" : "sso";
const flow = this.state.flows.find(flow => flow.type === "m.login." + loginType);
const flow = this.state.flows.find(flow => flow.type === "m.login." + loginType) as ISSOFlow;
return (
<div>

View File

@ -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.
@ -110,7 +110,7 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
console.error(e);
const stateForError = AutoDiscoveryUtils.authComponentStateForError(e);
if (stateForError.isFatalError) {
if (stateForError.serverErrorIsFatal) {
let error = _t("Unable to validate homeserver");
if (e.translatedMessage) {
error = e.translatedMessage;
@ -168,7 +168,7 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
text = _t("Matrix.org is the biggest public homeserver in the world, so its a good place for many.");
}
let defaultServerName = this.defaultServer.hsName;
let defaultServerName: React.ReactNode = this.defaultServer.hsName;
if (this.defaultServer.hsNameIsDifferent) {
defaultServerName = (
<TextWithTooltip class="mx_Login_underlinedServerName" tooltip={this.defaultServer.hsUrl}>

View File

@ -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.
@ -67,7 +67,7 @@ const ServerPicker = ({ title, dialogTitle, serverConfig, onServerConfigChange }
</AccessibleButton>;
}
let serverName = serverConfig.isNameResolvable ? serverConfig.hsName : serverConfig.hsUrl;
let serverName: React.ReactNode = serverConfig.isNameResolvable ? serverConfig.hsName : serverConfig.hsUrl;
if (serverConfig.hsNameIsDifferent) {
serverName = <TextWithTooltip class="mx_Login_underlinedServerName" tooltip={serverConfig.hsUrl}>
{serverConfig.hsName}

View File

@ -1,6 +1,6 @@
/*
Copyright 2015-2021 The Matrix.org Foundation C.I.C.
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019 - 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,11 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import React from 'react';
import classNames from "classnames";
import {EventType} from "matrix-js-sdk/src/@types/event";
import {EventStatus} from 'matrix-js-sdk/src/models/event';
import { EventType } from "matrix-js-sdk/src/@types/event";
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Relations } from "matrix-js-sdk/src/models/relations";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import ReplyThread from "../elements/ReplyThread";
import { _t } from '../../../languageHandler';
@ -27,7 +29,7 @@ import * as TextForEvent from "../../../TextForEvent";
import * as sdk from "../../../index";
import dis from '../../../dispatcher/dispatcher';
import SettingsStore from "../../../settings/SettingsStore";
import {Layout, LayoutPropType} from "../../../settings/Layout";
import {Layout} from "../../../settings/Layout";
import {formatTime} from "../../../DateUtils";
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {ALL_RULE_TYPES} from "../../../mjolnir/BanList";
@ -40,6 +42,8 @@ import {WIDGET_LAYOUT_EVENT_TYPE} from "../../../stores/widgets/WidgetLayoutStor
import {objectHasDiff} from "../../../utils/objects";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import Tooltip from "../elements/Tooltip";
import { EditorStateTransfer } from "../../../utils/EditorStateTransfer";
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
const eventTileTypes = {
[EventType.RoomMessage]: 'messages.MessageEvent',
@ -169,101 +173,130 @@ const MAX_READ_AVATARS = 5;
// | '--------------------------------------' |
// '----------------------------------------------------------'
interface IReadReceiptProps {
userId: string;
roomMember: RoomMember;
ts: number;
}
interface IProps {
// the MatrixEvent to show
mxEvent: MatrixEvent;
// true if mxEvent is redacted. This is a prop because using mxEvent.isRedacted()
// might not be enough when deciding shouldComponentUpdate - prevProps.mxEvent
// references the same this.props.mxEvent.
isRedacted?: boolean;
// true if this is a continuation of the previous event (which has the
// effect of not showing another avatar/displayname
continuation?: boolean;
// true if this is the last event in the timeline (which has the effect
// of always showing the timestamp)
last?: boolean;
// true if the event is the last event in a section (adds a css class for
// targeting)
lastInSection?: boolean;
// True if the event is the last successful (sent) event.
lastSuccessful?: boolean;
// true if this is search context (which has the effect of greying out
// the text
contextual?: boolean;
// a list of words to highlight, ordered by longest first
highlights?: string[];
// link URL for the highlights
highlightLink?: string;
// should show URL previews for this event
showUrlPreview?: boolean;
// is this the focused event
isSelectedEvent?: boolean;
// callback called when dynamic content in events are loaded
onHeightChanged?: () => void,
// a list of read-receipts we should show. Each object has a 'roomMember' and 'ts'.
readReceipts?: IReadReceiptProps[],
// opaque readreceipt info for each userId; used by ReadReceiptMarker
// to manage its animations. Should be an empty object when the room
// first loads
readReceiptMap?: any,
// A function which is used to check if the parent panel is being
// unmounted, to avoid unnecessary work. Should return true if we
// are being unmounted.
checkUnmounting?: () => boolean,
// the status of this event - ie, mxEvent.status. Denormalised to here so
// that we can tell when it changes.
eventSendStatus?: string;
// the shape of the tile. by default, the layout is intended for the
// normal room timeline. alternative values are: "file_list", "file_grid"
// and "notif". This could be done by CSS, but it'd be horribly inefficient.
// It could also be done by subclassing EventTile, but that'd be quite
// boiilerplatey. So just make the necessary render decisions conditional
// for now.
tileShape?: string;
// show twelve hour timestamps
isTwelveHour?: boolean;
// helper function to access relations for this event
getRelationsForEvent?: (eventId: string, relationType: string, eventType: string) => Relations,
// whether to show reactions for this event
showReactions?: boolean;
// which layout to use
layout: Layout,
// whether or not to show flair at all
enableFlair?: boolean;
// whether or not to show read receipts
showReadReceipts?: boolean;
// Used while editing, to pass the event, and to preserve editor state
// from one editor instance to another when remounting the editor
// upon receiving the remote echo for an unsent event.
editState?: EditorStateTransfer;
// Event ID of the event replacing the content of this event, if any
replacingEventId?: string;
// Helper to build permalinks for the room
permalinkCreator?: RoomPermalinkCreator;
}
interface IState {
// Whether the action bar is focused.
actionBarFocused: boolean;
// Whether all read receipts are being displayed. If not, only display
// a truncation of them.
allReadAvatars: boolean;
// Whether the event's sender has been verified.
verified: string;
// Whether onRequestKeysClick has been called since mounting.
previouslyRequestedKeys: boolean;
// The Relations model from the JS SDK for reactions to `mxEvent`
reactions: Relations;
}
@replaceableComponent("views.rooms.EventTile")
export default class EventTile extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
/* true if mxEvent is redacted. This is a prop because using mxEvent.isRedacted()
* might not be enough when deciding shouldComponentUpdate - prevProps.mxEvent
* references the same this.props.mxEvent.
*/
isRedacted: PropTypes.bool,
/* true if this is a continuation of the previous event (which has the
* effect of not showing another avatar/displayname
*/
continuation: PropTypes.bool,
/* true if this is the last event in the timeline (which has the effect
* of always showing the timestamp)
*/
last: PropTypes.bool,
// true if the event is the last event in a section (adds a css class for
// targeting)
lastInSection: PropTypes.bool,
// True if the event is the last successful (sent) event.
isLastSuccessful: PropTypes.bool,
/* true if this is search context (which has the effect of greying out
* the text
*/
contextual: PropTypes.bool,
/* a list of words to highlight, ordered by longest first */
highlights: PropTypes.array,
/* link URL for the highlights */
highlightLink: PropTypes.string,
/* should show URL previews for this event */
showUrlPreview: PropTypes.bool,
/* is this the focused event */
isSelectedEvent: PropTypes.bool,
/* callback called when dynamic content in events are loaded */
onHeightChanged: PropTypes.func,
/* a list of read-receipts we should show. Each object has a 'roomMember' and 'ts'. */
readReceipts: PropTypes.arrayOf(PropTypes.object),
/* opaque readreceipt info for each userId; used by ReadReceiptMarker
* to manage its animations. Should be an empty object when the room
* first loads
*/
readReceiptMap: PropTypes.object,
/* A function which is used to check if the parent panel is being
* unmounted, to avoid unnecessary work. Should return true if we
* are being unmounted.
*/
checkUnmounting: PropTypes.func,
/* the status of this event - ie, mxEvent.status. Denormalised to here so
* that we can tell when it changes. */
eventSendStatus: PropTypes.string,
/* the shape of the tile. by default, the layout is intended for the
* normal room timeline. alternative values are: "file_list", "file_grid"
* and "notif". This could be done by CSS, but it'd be horribly inefficient.
* It could also be done by subclassing EventTile, but that'd be quite
* boiilerplatey. So just make the necessary render decisions conditional
* for now.
*/
tileShape: PropTypes.string,
// show twelve hour timestamps
isTwelveHour: PropTypes.bool,
// helper function to access relations for this event
getRelationsForEvent: PropTypes.func,
// whether to show reactions for this event
showReactions: PropTypes.bool,
// which layout to use
layout: LayoutPropType,
// whether or not to show flair at all
enableFlair: PropTypes.bool,
// whether or not to show read receipts
showReadReceipts: PropTypes.bool,
};
export default class EventTile extends React.Component<IProps, IState> {
private _suppressReadReceiptAnimation: boolean;
private _isListeningForReceipts: boolean;
private _tile = React.createRef();
private _replyThread = React.createRef();
static defaultProps = {
// no-op function because onHeightChanged is optional yet some sub-components assume its existence
@ -292,9 +325,6 @@ export default class EventTile extends React.Component {
// don't do RR animations until we are mounted
this._suppressReadReceiptAnimation = true;
this._tile = createRef();
this._replyThread = createRef();
// Throughout the component we manage a read receipt listener to see if our tile still
// qualifies for a "sent" or "sending" state (based on their relevant conditions). We
// don't want to over-subscribe to the read receipt events being fired, so we use a flag
@ -1190,14 +1220,18 @@ function E2ePadlockUnauthenticated(props) {
);
}
class E2ePadlock extends React.Component {
static propTypes = {
icon: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
};
interface IE2ePadlockProps {
icon: string;
title: string;
}
constructor() {
super();
interface IE2ePadlockState {
hover: boolean;
}
class E2ePadlock extends React.Component<IE2ePadlockProps, IE2ePadlockState> {
constructor(props) {
super(props);
this.state = {
hover: false,
@ -1215,14 +1249,13 @@ class E2ePadlock extends React.Component {
render() {
let tooltip = null;
if (this.state.hover) {
tooltip = <Tooltip className="mx_EventTile_e2eIcon_tooltip" label={this.props.title} dir="auto" />;
tooltip = <Tooltip className="mx_EventTile_e2eIcon_tooltip" label={this.props.title} />;
}
const classes = `mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${this.props.icon}`;
return (
<div
className={classes}
onClick={this.onClick}
onMouseEnter={this.onHoverStart}
onMouseLeave={this.onHoverEnd}
>{tooltip}</div>
@ -1239,8 +1272,8 @@ interface ISentReceiptState {
}
class SentReceipt extends React.PureComponent<ISentReceiptProps, ISentReceiptState> {
constructor() {
super();
constructor(props) {
super(props);
this.state = {
hover: false,

View File

@ -1,5 +1,5 @@
/*
Copyright 2015-2018, 2020, 2021 The Matrix.org Foundation C.I.C.
Copyright 2015-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.
@ -13,15 +13,17 @@ 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, {createRef} from 'react';
import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import * as sdk from '../../../index';
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import {Room} from "matrix-js-sdk/src/models/room";
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
import dis from '../../../dispatcher/dispatcher';
import Stickerpicker from './Stickerpicker';
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
import { makeRoomPermalink, RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import ContentMessages from '../../../ContentMessages';
import E2EIcon from './E2EIcon';
import SettingsStore from "../../../settings/SettingsStore";
@ -35,19 +37,26 @@ import VoiceRecordComposerTile from "./VoiceRecordComposerTile";
import {VoiceRecordingStore} from "../../../stores/VoiceRecordingStore";
import {RecordingState} from "../../../voice/VoiceRecording";
import Tooltip, {Alignment} from "../elements/Tooltip";
import ResizeNotifier from "../../../utils/ResizeNotifier";
import { E2EStatus } from '../../../utils/ShieldUtils';
import SendMessageComposer from "./SendMessageComposer";
function ComposerAvatar(props) {
interface IComposerAvatarProps {
me: object;
}
function ComposerAvatar(props: IComposerAvatarProps) {
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
return <div className="mx_MessageComposer_avatar">
<MemberStatusMessageAvatar member={props.me} width={24} height={24} />
</div>;
}
ComposerAvatar.propTypes = {
me: PropTypes.object.isRequired,
};
interface ISendButtonProps {
onClick: () => void;
}
function SendButton(props) {
function SendButton(props: ISendButtonProps) {
return (
<AccessibleTooltipButton
className="mx_MessageComposer_sendMessage"
@ -57,10 +66,6 @@ function SendButton(props) {
);
}
SendButton.propTypes = {
onClick: PropTypes.func.isRequired,
};
const EmojiButton = ({addEmoji}) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
@ -68,7 +73,7 @@ const EmojiButton = ({addEmoji}) => {
if (menuDisplayed) {
const buttonRect = button.current.getBoundingClientRect();
const EmojiPicker = sdk.getComponent('emojipicker.EmojiPicker');
contextMenu = <ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} catchTab={false}>
contextMenu = <ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu}>
<EmojiPicker onChoose={addEmoji} showQuickReactions={true} />
</ContextMenu>;
}
@ -98,17 +103,17 @@ const EmojiButton = ({addEmoji}) => {
</React.Fragment>;
};
class UploadButton extends React.Component {
static propTypes = {
roomId: PropTypes.string.isRequired,
}
interface IUploadButtonProps {
roomId: string;
}
class UploadButton extends React.Component<IUploadButtonProps> {
private _uploadInput = React.createRef<HTMLInputElement>();
private _dispatcherRef: string;
constructor(props) {
super(props);
this.onUploadClick = this.onUploadClick.bind(this);
this.onUploadFileInputChange = this.onUploadFileInputChange.bind(this);
this._uploadInput = createRef();
this._dispatcherRef = dis.register(this.onAction);
}
@ -116,13 +121,13 @@ class UploadButton extends React.Component {
dis.unregister(this._dispatcherRef);
}
onAction = payload => {
private onAction = payload => {
if (payload.action === "upload_file") {
this.onUploadClick();
}
};
onUploadClick(ev) {
private onUploadClick = () => {
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'require_registration'});
return;
@ -130,7 +135,7 @@ class UploadButton extends React.Component {
this._uploadInput.current.click();
}
onUploadFileInputChange(ev) {
private onUploadFileInputChange = (ev) => {
if (ev.target.files.length === 0) return;
// take a copy so we can safely reset the value of the form control
@ -171,19 +176,34 @@ class UploadButton extends React.Component {
}
}
interface IProps {
room: Room;
resizeNotifier: ResizeNotifier;
permalinkCreator: RoomPermalinkCreator;
replyToEvent?: MatrixEvent;
e2eStatus?: E2EStatus;
}
interface IState {
tombstone: MatrixEvent;
canSendMessages: boolean;
isComposerEmpty: boolean;
haveRecording: boolean;
recordingTimeLeftSeconds?: number;
me?: RoomMember;
}
@replaceableComponent("views.rooms.MessageComposer")
export default class MessageComposer extends React.Component {
export default class MessageComposer extends React.Component<IProps, IState> {
private dispatcherRef: string;
private messageComposerInput: SendMessageComposer;
constructor(props) {
super(props);
this.onInputStateChanged = this.onInputStateChanged.bind(this);
this._onRoomStateEvents = this._onRoomStateEvents.bind(this);
this._onTombstoneClick = this._onTombstoneClick.bind(this);
this.renderPlaceholderText = this.renderPlaceholderText.bind(this);
VoiceRecordingStore.instance.on(UPDATE_EVENT, this._onVoiceStoreUpdate);
this._dispatcherRef = null;
this.state = {
tombstone: this._getRoomTombstone(),
tombstone: this.getRoomTombstone(),
canSendMessages: this.props.room.maySendMessage(),
isComposerEmpty: true,
haveRecording: false,
@ -191,7 +211,13 @@ export default class MessageComposer extends React.Component {
};
}
onAction = (payload) => {
componentDidMount() {
this.dispatcherRef = dis.register(this.onAction);
MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents);
this.waitForOwnMember();
}
private onAction = (payload) => {
if (payload.action === 'reply_to_event') {
// add a timeout for the reply preview to be rendered, so
// that the ScrollPanel listening to the resizeNotifier can
@ -203,13 +229,7 @@ export default class MessageComposer extends React.Component {
}
};
componentDidMount() {
this.dispatcherRef = dis.register(this.onAction);
MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
this._waitForOwnMember();
}
_waitForOwnMember() {
private waitForOwnMember() {
// if we have the member already, do that
const me = this.props.room.getMember(MatrixClientPeg.get().getUserId());
if (me) {
@ -227,34 +247,28 @@ export default class MessageComposer extends React.Component {
componentWillUnmount() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("RoomState.events", this._onRoomStateEvents);
MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
}
VoiceRecordingStore.instance.off(UPDATE_EVENT, this._onVoiceStoreUpdate);
dis.unregister(this.dispatcherRef);
}
_onRoomStateEvents(ev, state) {
private onRoomStateEvents = (ev, state) => {
if (ev.getRoomId() !== this.props.room.roomId) return;
if (ev.getType() === 'm.room.tombstone') {
this.setState({tombstone: this._getRoomTombstone()});
this.setState({tombstone: this.getRoomTombstone()});
}
if (ev.getType() === 'm.room.power_levels') {
this.setState({canSendMessages: this.props.room.maySendMessage()});
}
}
_getRoomTombstone() {
private getRoomTombstone() {
return this.props.room.currentState.getStateEvents('m.room.tombstone', '');
}
onInputStateChanged(inputState) {
// Merge the new input state with old to support partial updates
inputState = Object.assign({}, this.state.inputState, inputState);
this.setState({inputState});
}
_onTombstoneClick(ev) {
private onTombstoneClick = (ev) => {
ev.preventDefault();
const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
@ -284,7 +298,7 @@ export default class MessageComposer extends React.Component {
});
}
renderPlaceholderText() {
private renderPlaceholderText = () => {
if (this.props.replyToEvent) {
if (this.props.e2eStatus) {
return _t('Send an encrypted reply…');
@ -386,7 +400,7 @@ export default class MessageComposer extends React.Component {
const continuesLink = replacementRoomId ? (
<a href={makeRoomPermalink(replacementRoomId)}
className="mx_MessageComposer_roomReplaced_link"
onClick={this._onTombstoneClick}
onClick={this.onTombstoneClick}
>
{_t("The conversation continues here.")}
</a>
@ -433,14 +447,3 @@ export default class MessageComposer extends React.Component {
);
}
}
MessageComposer.propTypes = {
// js-sdk Room object
room: PropTypes.object.isRequired,
// string representing the current voip call state
callState: PropTypes.string,
// string representing the current room app drawer state
showApps: PropTypes.bool,
};

View File

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd.
Copyright 2019-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,9 +15,9 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import {Room} from "matrix-js-sdk/src/models/room";
import {_t} from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher";
import * as sdk from "../../../index";
@ -27,11 +27,22 @@ import RoomAvatar from "../avatars/RoomAvatar";
import RoomName from "../elements/RoomName";
import {replaceableComponent} from "../../../utils/replaceableComponent";
interface IProps {
event: MatrixEvent;
}
interface IState {
stateKey: string;
roomId: string;
displayName: string;
invited: boolean;
canKick: boolean;
senderName: string;
}
@replaceableComponent("views.rooms.ThirdPartyMemberInfo")
export default class ThirdPartyMemberInfo extends React.Component {
static propTypes = {
event: PropTypes.instanceOf(MatrixEvent).isRequired,
};
export default class ThirdPartyMemberInfo extends React.Component<IProps, IState> {
private room: Room;
constructor(props) {
super(props);

View File

@ -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.
@ -28,10 +28,17 @@ import {SettingLevel} from "../../../settings/SettingLevel";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import SeshatResetDialog from '../dialogs/SeshatResetDialog';
interface IState {
enabling: boolean;
eventIndexSize: number;
roomCount: number;
eventIndexingEnabled: boolean;
}
@replaceableComponent("views.settings.EventIndexPanel")
export default class EventIndexPanel extends React.Component {
constructor() {
super();
export default class EventIndexPanel extends React.Component<{}, IState> {
constructor(props) {
super(props);
this.state = {
enabling: false,
@ -68,7 +75,7 @@ export default class EventIndexPanel extends React.Component {
}
}
async componentDidMount(): void {
componentDidMount(): void {
this.updateState();
}
@ -104,6 +111,8 @@ export default class EventIndexPanel extends React.Component {
_onManage = async () => {
Modal.createTrackedDialogAsync('Message search', 'Message search',
// @ts-ignore: TS doesn't seem to like the type of this now that it
// has also been converted to TS as well, but I can't figure out why...
import('../../../async-components/views/dialogs/eventindex/ManageEventIndexDialog'),
{
onFinished: () => {},

View File

@ -1,5 +1,5 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019-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.
@ -16,7 +16,6 @@ limitations under the License.
import url from 'url';
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../languageHandler";
import * as sdk from '../../../index';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
@ -59,16 +58,28 @@ async function checkIdentityServerUrl(u) {
}
}
@replaceableComponent("views.settings.SetIdServer")
export default class SetIdServer extends React.Component {
static propTypes = {
// Whether or not the ID server is missing terms. This affects the text
// shown to the user.
missingTerms: PropTypes.bool,
};
interface IProps {
// Whether or not the ID server is missing terms. This affects the text
// shown to the user.
missingTerms: boolean;
}
constructor() {
super();
interface IState {
defaultIdServer?: string;
currentClientIdServer: string;
idServer?: string;
error?: string;
busy: boolean;
disconnectBusy: boolean;
checking: boolean;
}
@replaceableComponent("views.settings.SetIdServer")
export default class SetIdServer extends React.Component<IProps, IState> {
private dispatcherRef: string;
constructor(props) {
super(props);
let defaultIdServer = '';
if (!MatrixClientPeg.get().getIdentityServerUrl() && getDefaultIdentityServerUrl()) {
@ -371,7 +382,7 @@ export default class SetIdServer extends React.Component {
let discoSection;
if (idServerUrl) {
let discoButtonContent = _t("Disconnect");
let discoButtonContent: React.ReactNode = _t("Disconnect");
let discoBodyText = _t(
"Disconnecting from your identity server will mean you " +
"won't be discoverable by other users and you won't be " +

View File

@ -1,5 +1,5 @@
/*
Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
Copyright 2019-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,7 +15,6 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {_t, _td} from "../../../../../languageHandler";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../..";
@ -23,6 +22,7 @@ import AccessibleButton from "../../../elements/AccessibleButton";
import Modal from "../../../../../Modal";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
import {EventType} from "matrix-js-sdk/src/@types/event";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
const plEventsToLabels = {
// These will be translated for us later.
@ -63,14 +63,14 @@ function parseIntWithDefault(val, def) {
return isNaN(res) ? def : res;
}
export class BannedUser extends React.Component {
static propTypes = {
canUnban: PropTypes.bool,
member: PropTypes.object.isRequired, // js-sdk RoomMember
by: PropTypes.string.isRequired,
reason: PropTypes.string,
};
interface IBannedUserProps {
canUnban: boolean;
member: RoomMember;
by: string;
reason: string;
}
export class BannedUser extends React.Component<IBannedUserProps> {
_onUnbanClick = (e) => {
MatrixClientPeg.get().unban(this.props.member.roomId, this.props.member.userId).catch((err) => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@ -107,12 +107,12 @@ export class BannedUser extends React.Component {
}
}
@replaceableComponent("views.settings.tabs.room.RolesRoomSettingsTab")
export default class RolesRoomSettingsTab extends React.Component {
static propTypes = {
roomId: PropTypes.string.isRequired,
};
interface IProps {
roomId: string;
}
@replaceableComponent("views.settings.tabs.room.RolesRoomSettingsTab")
export default class RolesRoomSettingsTab extends React.Component<IProps> {
componentDidMount(): void {
MatrixClientPeg.get().on("RoomState.members", this._onRoomMembership);
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019-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,7 +15,6 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../../../languageHandler";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../..";
@ -26,16 +25,28 @@ import StyledRadioGroup from '../../../elements/StyledRadioGroup';
import {SettingLevel} from "../../../../../settings/SettingLevel";
import SettingsStore from "../../../../../settings/SettingsStore";
import {UIFeature} from "../../../../../settings/UIFeature";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
type JoinRule = "public" | "knock" | "invite" | "private";
type GuestAccess = "can_join" | "forbidden";
type History = "invited" | "joined" | "shared" | "world_readable";
interface IProps {
roomId: string;
}
interface IState {
joinRule: JoinRule;
guestAccess: GuestAccess;
history: History;
hasAliases: boolean;
encrypted: boolean;
}
@replaceableComponent("views.settings.tabs.room.SecurityRoomSettingsTab")
export default class SecurityRoomSettingsTab extends React.Component {
static propTypes = {
roomId: PropTypes.string.isRequired,
};
constructor() {
super();
export default class SecurityRoomSettingsTab extends React.Component<IProps, IState> {
constructor(props) {
super(props);
this.state = {
joinRule: "invite",
@ -47,23 +58,23 @@ export default class SecurityRoomSettingsTab extends React.Component {
}
// TODO: [REACT-WARNING] Move this to constructor
async UNSAFE_componentWillMount(): void { // eslint-disable-line camelcase
async UNSAFE_componentWillMount(): Promise<void> { // eslint-disable-line camelcase
MatrixClientPeg.get().on("RoomState.events", this._onStateEvent);
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
const state = room.currentState;
const joinRule = this._pullContentPropertyFromEvent(
const joinRule: JoinRule = this._pullContentPropertyFromEvent(
state.getStateEvents("m.room.join_rules", ""),
'join_rule',
'invite',
);
const guestAccess = this._pullContentPropertyFromEvent(
const guestAccess: GuestAccess = this._pullContentPropertyFromEvent(
state.getStateEvents("m.room.guest_access", ""),
'guest_access',
'forbidden',
);
const history = this._pullContentPropertyFromEvent(
const history: History = this._pullContentPropertyFromEvent(
state.getStateEvents("m.room.history_visibility", ""),
'history_visibility',
'shared',
@ -163,8 +174,8 @@ export default class SecurityRoomSettingsTab extends React.Component {
// invite them, you clearly want them to join, whether they're a
// guest or not. In practice, guest_access should probably have
// been implemented as part of the join_rules enum.
let joinRule = "invite";
let guestAccess = "can_join";
let joinRule: JoinRule = "invite";
let guestAccess: GuestAccess = "can_join";
switch (roomAccess) {
case "invite_only":

View File

@ -1,6 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2019-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.
@ -16,7 +15,6 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {_t, getCurrentLanguage} from "../../../../../languageHandler";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import AccessibleButton from "../../../elements/AccessibleButton";
@ -27,16 +25,21 @@ import * as sdk from "../../../../..";
import PlatformPeg from "../../../../../PlatformPeg";
import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts";
import UpdateCheckButton from "../../UpdateCheckButton";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
interface IProps {
closeSettingsFn: () => {};
}
interface IState {
appVersion: string;
canUpdate: boolean;
}
@replaceableComponent("views.settings.tabs.user.HelpUserSettingsTab")
export default class HelpUserSettingsTab extends React.Component {
static propTypes = {
closeSettingsFn: PropTypes.func.isRequired,
};
constructor() {
super();
export default class HelpUserSettingsTab extends React.Component<IProps, IState> {
constructor(props) {
super(props);
this.state = {
appVersion: null,

View File

@ -1,5 +1,5 @@
/*
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Copyright 2019-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.
@ -25,10 +25,16 @@ import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../../index";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
interface IState {
busy: boolean;
newPersonalRule: string;
newList: string;
}
@replaceableComponent("views.settings.tabs.user.MjolnirUserSettingsTab")
export default class MjolnirUserSettingsTab extends React.Component {
constructor() {
super();
export default class MjolnirUserSettingsTab extends React.Component<{}, IState> {
constructor(props) {
super(props);
this.state = {
busy: false,

View File

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019-2021 The Matrix.org Foundation C.I.C.
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
@ -23,10 +23,24 @@ import Field from "../../../elements/Field";
import * as sdk from "../../../../..";
import PlatformPeg from "../../../../../PlatformPeg";
import {SettingLevel} from "../../../../../settings/SettingLevel";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
interface IState {
autoLaunch: boolean;
autoLaunchSupported: boolean;
warnBeforeExit: boolean;
warnBeforeExitSupported: boolean;
alwaysShowMenuBarSupported: boolean;
alwaysShowMenuBar: boolean;
minimizeToTraySupported: boolean;
minimizeToTray: boolean;
autocompleteDelay: string,
readMarkerInViewThresholdMs: string,
readMarkerOutOfViewThresholdMs: string,
}
@replaceableComponent("views.settings.tabs.user.PreferencesUserSettingsTab")
export default class PreferencesUserSettingsTab extends React.Component {
export default class PreferencesUserSettingsTab extends React.Component<{}, IState> {
static ROOM_LIST_SETTINGS = [
'breadcrumbs',
];
@ -68,8 +82,8 @@ export default class PreferencesUserSettingsTab extends React.Component {
// Autocomplete delay (niche text box)
];
constructor() {
super();
constructor(props) {
super(props);
this.state = {
autoLaunch: false,
@ -89,7 +103,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
};
}
async componentDidMount(): void {
async componentDidMount(): Promise<void> {
const platform = PlatformPeg.get();
const autoLaunchSupported = await platform.supportsAutoLaunch();

View File

@ -1,5 +1,5 @@
/*
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Copyright 2019-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.
@ -133,6 +133,10 @@ export default abstract class BaseEventIndexManager {
throw new Error("Unimplemented");
}
async isEventIndexEmpty(): Promise<boolean> {
throw new Error("Unimplemented");
}
/**
* Check if our event index is empty.
*/

View File

@ -1,5 +1,5 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019-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.
@ -27,11 +27,16 @@ import {SettingLevel} from "../settings/SettingLevel";
const INDEX_VERSION = 1;
class EventIndexPeg {
export class EventIndexPeg {
public index: EventIndex;
public error: Error;
private _supportIsInstalled: boolean;
constructor() {
this.index = null;
this._supportIsInstalled = false;
this.error = null;
this._supportIsInstalled = false;
}
/**
@ -181,7 +186,7 @@ class EventIndexPeg {
}
}
if (!global.mxEventIndexPeg) {
global.mxEventIndexPeg = new EventIndexPeg();
if (!window.mxEventIndexPeg) {
window.mxEventIndexPeg = new EventIndexPeg();
}
export default global.mxEventIndexPeg;
export default window.mxEventIndexPeg;

View File

@ -1,5 +1,5 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019, 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.
@ -25,15 +25,23 @@ const TYPING_SERVER_TIMEOUT = 30000;
* Tracks typing state for users.
*/
export default class TypingStore {
private _typingStates: {
[roomId: string]: {
isTyping: boolean,
userTimer: Timer,
serverTimer: Timer,
},
};
constructor() {
this.reset();
}
static sharedInstance(): TypingStore {
if (global.mxTypingStore === undefined) {
global.mxTypingStore = new TypingStore();
if (window.mxTypingStore === undefined) {
window.mxTypingStore = new TypingStore();
}
return global.mxTypingStore;
return window.mxTypingStore;
}
/**

View File

@ -1,6 +1,5 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2018-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.
@ -16,6 +15,7 @@ limitations under the License.
*/
import EventEmitter from 'events';
import { IWidget } from 'matrix-widget-api';
import {WidgetType} from "../widgets/WidgetType";
/**
@ -23,6 +23,12 @@ import {WidgetType} from "../widgets/WidgetType";
* proxying through state from the js-sdk.
*/
class WidgetEchoStore extends EventEmitter {
private _roomWidgetEcho: {
[roomId: string]: {
[widgetId: string]: IWidget,
},
};
constructor() {
super();
@ -65,7 +71,7 @@ class WidgetEchoStore extends EventEmitter {
return echoedWidgets;
}
roomHasPendingWidgetsOfType(roomId, currentRoomWidgets, type: WidgetType) {
roomHasPendingWidgetsOfType(roomId, currentRoomWidgets, type?: WidgetType) {
const roomEchoState = Object.assign({}, this._roomWidgetEcho[roomId]);
// any widget IDs that are already in the room are not pending, so
@ -89,7 +95,7 @@ class WidgetEchoStore extends EventEmitter {
return this.roomHasPendingWidgetsOfType(roomId, currentRoomWidgets);
}
setRoomWidgetEcho(roomId, widgetId, state) {
setRoomWidgetEcho(roomId: string, widgetId: string, state: IWidget) {
if (this._roomWidgetEcho[roomId] === undefined) this._roomWidgetEcho[roomId] = {};
this._roomWidgetEcho[roomId][widgetId] = state;

View File

@ -1,6 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Copyright 2019-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,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React, { ReactNode } from 'react';
import {AutoDiscovery} from "matrix-js-sdk/src/autodiscovery";
import {_t, _td, newTranslatableError} from "../languageHandler";
import {makeType} from "./TypeUtils";
import SdkConfig from '../SdkConfig';
const LIVELINESS_DISCOVERY_ERRORS = [
const LIVELINESS_DISCOVERY_ERRORS: string[] = [
AutoDiscovery.ERROR_INVALID_HOMESERVER,
AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
];
@ -40,17 +39,23 @@ export class ValidatedServerConfig {
warning: string;
}
export interface IAuthComponentState {
serverIsAlive: boolean;
serverErrorIsFatal: boolean;
serverDeadError?: ReactNode;
}
export default class AutoDiscoveryUtils {
/**
* Checks if a given error or error message is considered an error
* relating to the liveliness of the server. Must be an error returned
* from this AutoDiscoveryUtils class.
* @param {string|Error} error The error to check
* @param {string | Error} error The error to check
* @returns {boolean} True if the error is a liveliness error.
*/
static isLivelinessError(error: string|Error): boolean {
static isLivelinessError(error: string | Error): boolean {
if (!error) return false;
return !!LIVELINESS_DISCOVERY_ERRORS.find(e => e === error || e === error.message);
return !!LIVELINESS_DISCOVERY_ERRORS.find(e => typeof error === "string" ? e === error : e === error.message);
}
/**
@ -61,7 +66,7 @@ export default class AutoDiscoveryUtils {
* implementation for known values.
* @returns {*} The state for the component, given the error.
*/
static authComponentStateForError(err: string | Error | null, pageName = "login"): Object {
static authComponentStateForError(err: string | Error | null, pageName = "login"): IAuthComponentState {
if (!err) {
return {
serverIsAlive: true,
@ -70,7 +75,7 @@ export default class AutoDiscoveryUtils {
};
}
let title = _t("Cannot reach homeserver");
let body = _t("Ensure you have a stable internet connection, or get in touch with the server admin");
let body: ReactNode = _t("Ensure you have a stable internet connection, or get in touch with the server admin");
if (!AutoDiscoveryUtils.isLivelinessError(err)) {
const brand = SdkConfig.get().brand;
title = _t("Your %(brand)s is misconfigured", { brand });
@ -92,7 +97,7 @@ export default class AutoDiscoveryUtils {
}
let isFatalError = true;
const errorMessage = err.message ? err.message : err;
const errorMessage = typeof err === "string" ? err : err.message;
if (errorMessage === AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER) {
isFatalError = false;
title = _t("Cannot reach identity server");
@ -141,7 +146,10 @@ export default class AutoDiscoveryUtils {
* @returns {Promise<ValidatedServerConfig>} Resolves to the validated configuration.
*/
static async validateServerConfigWithStaticUrls(
homeserverUrl: string, identityUrl: string, syntaxOnly = false): ValidatedServerConfig {
homeserverUrl: string,
identityUrl?: string,
syntaxOnly = false,
): Promise<ValidatedServerConfig> {
if (!homeserverUrl) {
throw newTranslatableError(_td("No homeserver URL provided"));
}
@ -171,7 +179,7 @@ export default class AutoDiscoveryUtils {
* @param {string} serverName The homeserver domain name (eg: "matrix.org") to validate.
* @returns {Promise<ValidatedServerConfig>} Resolves to the validated configuration.
*/
static async validateServerName(serverName: string): ValidatedServerConfig {
static async validateServerName(serverName: string): Promise<ValidatedServerConfig> {
const result = await AutoDiscovery.findClientConfig(serverName);
return AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result);
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019-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.
@ -36,8 +36,8 @@ function log(msg) {
console.log(`StorageManager: ${msg}`);
}
function error(msg) {
console.error(`StorageManager: ${msg}`);
function error(msg, ...args) {
console.error(`StorageManager: ${msg}`, ...args);
}
function track(action) {
@ -73,7 +73,7 @@ export async function checkConsistency() {
dataInLocalStorage = localStorage.length > 0;
log(`Local storage contains data? ${dataInLocalStorage}`);
cryptoInited = localStorage.getItem("mx_crypto_initialised");
cryptoInited = !!localStorage.getItem("mx_crypto_initialised");
log(`Crypto initialised? ${cryptoInited}`);
} else {
healthy = false;

View File

@ -1,5 +1,5 @@
/*
Copyright 2018 New Vector Ltd
Copyright 2018, 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.
@ -26,6 +26,13 @@ Once a timer is finished or aborted, it can't be started again
a new one through `clone()` or `cloneIfRun()`.
*/
export default class Timer {
private _timeout: number;
private _timerHandle: NodeJS.Timeout;
private _startTs: number;
private _promise: Promise<void>;
private _resolve: () => void;
private _reject: (Error) => void;
constructor(timeout) {
this._timeout = timeout;
this._onTimeout = this._onTimeout.bind(this);
@ -35,7 +42,7 @@ export default class Timer {
_setNotStarted() {
this._timerHandle = null;
this._startTs = null;
this._promise = new Promise((resolve, reject) => {
this._promise = new Promise<void>((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
}).finally(() => {

View File

@ -20,7 +20,7 @@ import PermalinkConstructor, {PermalinkParts} from "./PermalinkConstructor";
* Generates permalinks that self-reference the running webapp
*/
export default class ElementPermalinkConstructor extends PermalinkConstructor {
_elementUrl: string;
private _elementUrl: string;
constructor(elementUrl: string) {
super();
@ -35,7 +35,7 @@ export default class ElementPermalinkConstructor extends PermalinkConstructor {
return `${this._elementUrl}/#/room/${roomId}/${eventId}${this.encodeServerCandidates(serverCandidates)}`;
}
forRoom(roomIdOrAlias: string, serverCandidates: string[]): string {
forRoom(roomIdOrAlias: string, serverCandidates?: string[]): string {
return `${this._elementUrl}/#/room/${roomIdOrAlias}${this.encodeServerCandidates(serverCandidates)}`;
}
@ -62,7 +62,7 @@ export default class ElementPermalinkConstructor extends PermalinkConstructor {
return testHost === (parsedUrl.host || parsedUrl.hostname); // one of the hosts should match
}
encodeServerCandidates(candidates: string[]) {
encodeServerCandidates(candidates?: string[]) {
if (!candidates || candidates.length === 0) return '';
return `?via=${candidates.map(c => encodeURIComponent(c)).join("&via=")}`;
}

View File

@ -74,10 +74,19 @@ const MAX_SERVER_CANDIDATES = 3;
// the list and magically have the link work.
export class RoomPermalinkCreator {
private _room: Room;
private _roomId: string;
private _highestPlUserId: string;
private _populationMap: { [serverName: string]: number };
private _bannedHostsRegexps: RegExp[];
private _allowedHostsRegexps: RegExp[];
private _serverCandidates: string[];
private _started: boolean;
// We support being given a roomId as a fallback in the event the `room` object
// doesn't exist or is not healthy for us to rely on. For example, loading a
// permalink to a room which the MatrixClient doesn't know about.
constructor(room, roomId = null) {
constructor(room: Room, roomId: string = null) {
this._room = room;
this._roomId = room ? room.roomId : roomId;
this._highestPlUserId = null;