Add basic types

This commit is contained in:
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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 VoipUserMapper from "../VoipUserMapper";
import {SpaceStoreClass} from "../stores/SpaceStore"; import {SpaceStoreClass} from "../stores/SpaceStore";
import {VoiceRecording} from "../voice/VoiceRecording"; import {VoiceRecording} from "../voice/VoiceRecording";
import TypingStore from "../stores/TypingStore";
import { EventIndexPeg } from "../indexing/EventIndexPeg";
declare global { declare global {
interface Window { interface Window {
@ -72,11 +74,15 @@ declare global {
mxVoipUserMapper: VoipUserMapper; mxVoipUserMapper: VoipUserMapper;
mxSpaceStore: SpaceStoreClass; mxSpaceStore: SpaceStoreClass;
mxVoiceRecorder: typeof VoiceRecording; mxVoiceRecorder: typeof VoiceRecording;
mxTypingStore: TypingStore;
mxEventIndexPeg: EventIndexPeg;
} }
interface Document { interface Document {
// https://developer.mozilla.org/en-US/docs/Web/API/Document/hasStorageAccess // https://developer.mozilla.org/en-US/docs/Web/API/Document/hasStorageAccess
hasStorageAccess?: () => Promise<boolean>; 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 // Safari & IE11 only have this prefixed: we used prefixed versions
// previously so let's continue to support them for now // previously so let's continue to support them for now

View File

@ -1,9 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015-2021 The Matrix.org Foundation C.I.C.
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 // TODO: Move this to JS SDK
/* eslint-disable camelcase */ /* eslint-disable camelcase */
interface ILoginParams { interface ILoginParams {
identifier?: string; identifier?: object;
password?: string; password?: string;
token?: string; token?: string;
device_id?: string; device_id?: string;

View File

@ -1,6 +1,5 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016, 2019, 2021 The Matrix.org Foundation C.I.C.
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 url from 'url';
import SettingsStore from "./settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
import { Service, startTermsFlow, TermsNotSignedError } from './Terms'; import { Service, startTermsFlow, TermsInteractionCallback, TermsNotSignedError } from './Terms';
import {MatrixClientPeg} from "./MatrixClientPeg"; import {MatrixClientPeg} from "./MatrixClientPeg";
import request from "browser-request"; 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. // TODO: Generify the name of this class and all components within - it's not just for Scalar.
export default class ScalarAuthClient { export default class ScalarAuthClient {
private apiUrl: string;
private uiUrl: string;
private scalarToken: string;
private termsInteractionCallback: TermsInteractionCallback;
private isDefaultManager: boolean;
constructor(apiUrl, uiUrl) { constructor(apiUrl, uiUrl) {
this.apiUrl = apiUrl; this.apiUrl = apiUrl;
this.uiUrl = uiUrl; this.uiUrl = uiUrl;
@ -154,7 +159,7 @@ export default class ScalarAuthClient {
parsedImRestUrl.pathname = ''; parsedImRestUrl.pathname = '';
return startTermsFlow([new Service( return startTermsFlow([new Service(
SERVICE_TYPES.IM, SERVICE_TYPES.IM,
parsedImRestUrl.format(), url.format(parsedImRestUrl),
token, token,
)], this.termsInteractionCallback).then(() => { )], this.termsInteractionCallback).then(() => {
return token; return token;
@ -243,7 +248,7 @@ export default class ScalarAuthClient {
disableWidgetAssets(widgetType: WidgetType, widgetId) { disableWidgetAssets(widgetType: WidgetType, widgetId) {
let url = this.apiUrl + '/widgets/set_assets_state'; let url = this.apiUrl + '/widgets/set_assets_state';
url = this.getStarterLink(url); url = this.getStarterLink(url);
return new Promise((resolve, reject) => { return new Promise<void>((resolve, reject) => {
request({ request({
method: 'GET', // XXX: Actions shouldn't be GET requests method: 'GET', // XXX: Actions shouldn't be GET requests
uri: url, 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 classNames from 'classnames';
import {MatrixClientPeg} from './MatrixClientPeg'; import {MatrixClientPeg} from './MatrixClientPeg';
import * as sdk from './'; import * as sdk from '.';
import Modal from './Modal'; import Modal from './Modal';
export class TermsNotSignedError extends Error {} 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. * require agreement from the user before the user can use that service.
*/ */
export class Service { export class Service {
public serviceType: string;
public baseUrl: string;
public accessToken: string;
/** /**
* @param {MatrixClient.SERVICE_TYPES} serviceType The type of service * @param {MatrixClient.SERVICE_TYPES} serviceType The type of service
* @param {string} baseUrl The Base URL of the service (ie. before '/_matrix') * @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 * Start a flow where the user is presented with terms & conditions for some services
* *
@ -51,8 +75,8 @@ export class Service {
* if they cancel. * if they cancel.
*/ */
export async function startTermsFlow( export async function startTermsFlow(
services, services: Service[],
interactionCallback = dialogTermsInteractionCallback, interactionCallback: TermsInteractionCallback = dialogTermsInteractionCallback,
) { ) {
const termsPromises = services.map( const termsPromises = services.map(
(s) => MatrixClientPeg.get().getTerms(s.serviceType, s.baseUrl), (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 }; }); const policiesAndServicePairs = terms.map((t, i) => { return { 'service': services[i], 'policies': t.policies }; });
// fetch the set of agreed policy URLs from account data // fetch the set of agreed policy URLs from account data
@ -158,10 +182,13 @@ export async function startTermsFlow(
} }
export function dialogTermsInteractionCallback( export function dialogTermsInteractionCallback(
policiesAndServicePairs, policiesAndServicePairs: {
agreedUrls, service: Service,
extraClassNames, policies: { [policy: string]: Policy },
) { }[],
agreedUrls: string[],
extraClassNames?: string,
): Promise<string[]> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
console.log("Terms that need agreement", policiesAndServicePairs); console.log("Terms that need agreement", policiesAndServicePairs);
const TermsDialog = sdk.getComponent("views.dialogs.TermsDialog"); 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 React from 'react';
import * as sdk from '../../../../index'; import * as sdk from '../../../../index';
import PropTypes from 'prop-types';
import { _t } from '../../../../languageHandler'; import { _t } from '../../../../languageHandler';
import SdkConfig from '../../../../SdkConfig'; import SdkConfig from '../../../../SdkConfig';
import SettingsStore from "../../../../settings/SettingsStore"; import SettingsStore from "../../../../settings/SettingsStore";
@ -26,14 +25,23 @@ import {formatBytes, formatCountLong} from "../../../../utils/FormattingUtils";
import EventIndexPeg from "../../../../indexing/EventIndexPeg"; import EventIndexPeg from "../../../../indexing/EventIndexPeg";
import {SettingLevel} from "../../../../settings/SettingLevel"; 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. * Allows the user to introspect the event index state and disable it.
*/ */
export default class ManageEventIndexDialog extends React.Component { export default class ManageEventIndexDialog extends React.Component<IProps, IState> {
static propTypes = {
onFinished: PropTypes.func.isRequired,
};
constructor(props) { constructor(props) {
super(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 eventIndexSize = 0;
let crawlingRoomsCount = 0; let crawlingRoomsCount = 0;
let roomCount = 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -94,7 +94,7 @@ interface IState {
// be seeing. // be seeing.
serverIsAlive: boolean; serverIsAlive: boolean;
serverErrorIsFatal: 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -95,7 +95,7 @@ interface IState {
// be seeing. // be seeing.
serverIsAlive: boolean; serverIsAlive: boolean;
serverErrorIsFatal: boolean; serverErrorIsFatal: boolean;
serverDeadError: string; serverDeadError?: ReactNode;
// Our matrix client - part of state because we can't render the UI auth // Our matrix client - part of state because we can't render the UI auth
// component without it. // 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 React from 'react';
import PropTypes from 'prop-types';
import {_t} from '../../../languageHandler'; import {_t} from '../../../languageHandler';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import * as Lifecycle from '../../../Lifecycle'; import * as Lifecycle from '../../../Lifecycle';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {sendLoginRequest} from "../../../Login"; import {ISSOFlow, LoginFlow, sendLoginRequest} from "../../../Login";
import AuthPage from "../../views/auth/AuthPage"; import AuthPage from "../../views/auth/AuthPage";
import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "../../../BasePlatform"; import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "../../../BasePlatform";
import SSOButtons from "../../views/elements/SSOButtons"; import SSOButtons from "../../views/elements/SSOButtons";
@ -42,26 +41,38 @@ const FLOWS_TO_VIEWS = {
"m.login.sso": LOGIN_VIEW.SSO, "m.login.sso": LOGIN_VIEW.SSO,
}; };
@replaceableComponent("structures.auth.SoftLogout") interface IProps {
export default class SoftLogout extends React.Component { // Query parameters from MatrixChat
static propTypes = { realQueryParams: {
// Query parameters from MatrixChat loginToken?: string;
realQueryParams: PropTypes.object, // {loginToken}
// Called when the SSO login completes
onTokenLoginCompleted: PropTypes.func,
}; };
fragmentAfterLogin?: string;
constructor() { // Called when the SSO login completes
super(); 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 = { this.state = {
loginView: LOGIN_VIEW.LOADING, loginView: LOGIN_VIEW.LOADING,
keyBackupNeeded: true, // assume we do while we figure it out (see componentDidMount) keyBackupNeeded: true, // assume we do while we figure it out (see componentDidMount)
busy: false, busy: false,
password: "", password: "",
errorText: "", 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) } // else we already have a message and should use it (key backup warning)
const loginType = this.state.loginView === LOGIN_VIEW.CAS ? "cas" : "sso"; 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 ( return (
<div> <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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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); console.error(e);
const stateForError = AutoDiscoveryUtils.authComponentStateForError(e); const stateForError = AutoDiscoveryUtils.authComponentStateForError(e);
if (stateForError.isFatalError) { if (stateForError.serverErrorIsFatal) {
let error = _t("Unable to validate homeserver"); let error = _t("Unable to validate homeserver");
if (e.translatedMessage) { if (e.translatedMessage) {
error = 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."); 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) { if (this.defaultServer.hsNameIsDifferent) {
defaultServerName = ( defaultServerName = (
<TextWithTooltip class="mx_Login_underlinedServerName" tooltip={this.defaultServer.hsUrl}> <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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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>; </AccessibleButton>;
} }
let serverName = serverConfig.isNameResolvable ? serverConfig.hsName : serverConfig.hsUrl; let serverName: React.ReactNode = serverConfig.isNameResolvable ? serverConfig.hsName : serverConfig.hsUrl;
if (serverConfig.hsNameIsDifferent) { if (serverConfig.hsNameIsDifferent) {
serverName = <TextWithTooltip class="mx_Login_underlinedServerName" tooltip={serverConfig.hsUrl}> serverName = <TextWithTooltip class="mx_Login_underlinedServerName" tooltip={serverConfig.hsUrl}>
{serverConfig.hsName} {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 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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. limitations under the License.
*/ */
import React, {createRef} from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import classNames from "classnames"; 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 ReplyThread from "../elements/ReplyThread";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
@ -27,7 +29,7 @@ import * as TextForEvent from "../../../TextForEvent";
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import {Layout, LayoutPropType} from "../../../settings/Layout"; import {Layout} from "../../../settings/Layout";
import {formatTime} from "../../../DateUtils"; import {formatTime} from "../../../DateUtils";
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {ALL_RULE_TYPES} from "../../../mjolnir/BanList"; 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 {objectHasDiff} from "../../../utils/objects";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import Tooltip from "../elements/Tooltip"; import Tooltip from "../elements/Tooltip";
import { EditorStateTransfer } from "../../../utils/EditorStateTransfer";
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
const eventTileTypes = { const eventTileTypes = {
[EventType.RoomMessage]: 'messages.MessageEvent', [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") @replaceableComponent("views.rooms.EventTile")
export default class EventTile extends React.Component { export default class EventTile extends React.Component<IProps, IState> {
static propTypes = { private _suppressReadReceiptAnimation: boolean;
/* the MatrixEvent to show */ private _isListeningForReceipts: boolean;
mxEvent: PropTypes.object.isRequired, private _tile = React.createRef();
private _replyThread = React.createRef();
/* 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,
};
static defaultProps = { static defaultProps = {
// no-op function because onHeightChanged is optional yet some sub-components assume its existence // 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 // don't do RR animations until we are mounted
this._suppressReadReceiptAnimation = true; this._suppressReadReceiptAnimation = true;
this._tile = createRef();
this._replyThread = createRef();
// Throughout the component we manage a read receipt listener to see if our tile still // 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 // 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 // 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 { interface IE2ePadlockProps {
static propTypes = { icon: string;
icon: PropTypes.string.isRequired, title: string;
title: PropTypes.string.isRequired, }
};
constructor() { interface IE2ePadlockState {
super(); hover: boolean;
}
class E2ePadlock extends React.Component<IE2ePadlockProps, IE2ePadlockState> {
constructor(props) {
super(props);
this.state = { this.state = {
hover: false, hover: false,
@ -1215,14 +1249,13 @@ class E2ePadlock extends React.Component {
render() { render() {
let tooltip = null; let tooltip = null;
if (this.state.hover) { 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}`; const classes = `mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${this.props.icon}`;
return ( return (
<div <div
className={classes} className={classes}
onClick={this.onClick}
onMouseEnter={this.onHoverStart} onMouseEnter={this.onHoverStart}
onMouseLeave={this.onHoverEnd} onMouseLeave={this.onHoverEnd}
>{tooltip}</div> >{tooltip}</div>
@ -1239,8 +1272,8 @@ interface ISentReceiptState {
} }
class SentReceipt extends React.PureComponent<ISentReceiptProps, ISentReceiptState> { class SentReceipt extends React.PureComponent<ISentReceiptProps, ISentReceiptState> {
constructor() { constructor(props) {
super(); super(props);
this.state = { this.state = {
hover: false, 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {createRef} from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import * as sdk from '../../../index'; 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 dis from '../../../dispatcher/dispatcher';
import Stickerpicker from './Stickerpicker'; import Stickerpicker from './Stickerpicker';
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks'; import { makeRoomPermalink, RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import ContentMessages from '../../../ContentMessages'; import ContentMessages from '../../../ContentMessages';
import E2EIcon from './E2EIcon'; import E2EIcon from './E2EIcon';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
@ -35,19 +37,26 @@ import VoiceRecordComposerTile from "./VoiceRecordComposerTile";
import {VoiceRecordingStore} from "../../../stores/VoiceRecordingStore"; import {VoiceRecordingStore} from "../../../stores/VoiceRecordingStore";
import {RecordingState} from "../../../voice/VoiceRecording"; import {RecordingState} from "../../../voice/VoiceRecording";
import Tooltip, {Alignment} from "../elements/Tooltip"; 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'); const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
return <div className="mx_MessageComposer_avatar"> return <div className="mx_MessageComposer_avatar">
<MemberStatusMessageAvatar member={props.me} width={24} height={24} /> <MemberStatusMessageAvatar member={props.me} width={24} height={24} />
</div>; </div>;
} }
ComposerAvatar.propTypes = { interface ISendButtonProps {
me: PropTypes.object.isRequired, onClick: () => void;
}; }
function SendButton(props) { function SendButton(props: ISendButtonProps) {
return ( return (
<AccessibleTooltipButton <AccessibleTooltipButton
className="mx_MessageComposer_sendMessage" className="mx_MessageComposer_sendMessage"
@ -57,10 +66,6 @@ function SendButton(props) {
); );
} }
SendButton.propTypes = {
onClick: PropTypes.func.isRequired,
};
const EmojiButton = ({addEmoji}) => { const EmojiButton = ({addEmoji}) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
@ -68,7 +73,7 @@ const EmojiButton = ({addEmoji}) => {
if (menuDisplayed) { if (menuDisplayed) {
const buttonRect = button.current.getBoundingClientRect(); const buttonRect = button.current.getBoundingClientRect();
const EmojiPicker = sdk.getComponent('emojipicker.EmojiPicker'); 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} /> <EmojiPicker onChoose={addEmoji} showQuickReactions={true} />
</ContextMenu>; </ContextMenu>;
} }
@ -98,17 +103,17 @@ const EmojiButton = ({addEmoji}) => {
</React.Fragment>; </React.Fragment>;
}; };
class UploadButton extends React.Component { interface IUploadButtonProps {
static propTypes = { roomId: string;
roomId: PropTypes.string.isRequired, }
}
class UploadButton extends React.Component<IUploadButtonProps> {
private _uploadInput = React.createRef<HTMLInputElement>();
private _dispatcherRef: string;
constructor(props) { constructor(props) {
super(props); super(props);
this.onUploadClick = this.onUploadClick.bind(this);
this.onUploadFileInputChange = this.onUploadFileInputChange.bind(this);
this._uploadInput = createRef();
this._dispatcherRef = dis.register(this.onAction); this._dispatcherRef = dis.register(this.onAction);
} }
@ -116,13 +121,13 @@ class UploadButton extends React.Component {
dis.unregister(this._dispatcherRef); dis.unregister(this._dispatcherRef);
} }
onAction = payload => { private onAction = payload => {
if (payload.action === "upload_file") { if (payload.action === "upload_file") {
this.onUploadClick(); this.onUploadClick();
} }
}; };
onUploadClick(ev) { private onUploadClick = () => {
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'require_registration'}); dis.dispatch({action: 'require_registration'});
return; return;
@ -130,7 +135,7 @@ class UploadButton extends React.Component {
this._uploadInput.current.click(); this._uploadInput.current.click();
} }
onUploadFileInputChange(ev) { private onUploadFileInputChange = (ev) => {
if (ev.target.files.length === 0) return; if (ev.target.files.length === 0) return;
// take a copy so we can safely reset the value of the form control // 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") @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) { constructor(props) {
super(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); VoiceRecordingStore.instance.on(UPDATE_EVENT, this._onVoiceStoreUpdate);
this._dispatcherRef = null;
this.state = { this.state = {
tombstone: this._getRoomTombstone(), tombstone: this.getRoomTombstone(),
canSendMessages: this.props.room.maySendMessage(), canSendMessages: this.props.room.maySendMessage(),
isComposerEmpty: true, isComposerEmpty: true,
haveRecording: false, 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') { if (payload.action === 'reply_to_event') {
// add a timeout for the reply preview to be rendered, so // add a timeout for the reply preview to be rendered, so
// that the ScrollPanel listening to the resizeNotifier can // that the ScrollPanel listening to the resizeNotifier can
@ -203,13 +229,7 @@ export default class MessageComposer extends React.Component {
} }
}; };
componentDidMount() { private waitForOwnMember() {
this.dispatcherRef = dis.register(this.onAction);
MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
this._waitForOwnMember();
}
_waitForOwnMember() {
// if we have the member already, do that // if we have the member already, do that
const me = this.props.room.getMember(MatrixClientPeg.get().getUserId()); const me = this.props.room.getMember(MatrixClientPeg.get().getUserId());
if (me) { if (me) {
@ -227,34 +247,28 @@ export default class MessageComposer extends React.Component {
componentWillUnmount() { componentWillUnmount() {
if (MatrixClientPeg.get()) { if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("RoomState.events", this._onRoomStateEvents); MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
} }
VoiceRecordingStore.instance.off(UPDATE_EVENT, this._onVoiceStoreUpdate); VoiceRecordingStore.instance.off(UPDATE_EVENT, this._onVoiceStoreUpdate);
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
} }
_onRoomStateEvents(ev, state) { private onRoomStateEvents = (ev, state) => {
if (ev.getRoomId() !== this.props.room.roomId) return; if (ev.getRoomId() !== this.props.room.roomId) return;
if (ev.getType() === 'm.room.tombstone') { if (ev.getType() === 'm.room.tombstone') {
this.setState({tombstone: this._getRoomTombstone()}); this.setState({tombstone: this.getRoomTombstone()});
} }
if (ev.getType() === 'm.room.power_levels') { if (ev.getType() === 'm.room.power_levels') {
this.setState({canSendMessages: this.props.room.maySendMessage()}); this.setState({canSendMessages: this.props.room.maySendMessage()});
} }
} }
_getRoomTombstone() { private getRoomTombstone() {
return this.props.room.currentState.getStateEvents('m.room.tombstone', ''); return this.props.room.currentState.getStateEvents('m.room.tombstone', '');
} }
onInputStateChanged(inputState) { private onTombstoneClick = (ev) => {
// Merge the new input state with old to support partial updates
inputState = Object.assign({}, this.state.inputState, inputState);
this.setState({inputState});
}
_onTombstoneClick(ev) {
ev.preventDefault(); ev.preventDefault();
const replacementRoomId = this.state.tombstone.getContent()['replacement_room']; 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.replyToEvent) {
if (this.props.e2eStatus) { if (this.props.e2eStatus) {
return _t('Send an encrypted reply…'); return _t('Send an encrypted reply…');
@ -386,7 +400,7 @@ export default class MessageComposer extends React.Component {
const continuesLink = replacementRoomId ? ( const continuesLink = replacementRoomId ? (
<a href={makeRoomPermalink(replacementRoomId)} <a href={makeRoomPermalink(replacementRoomId)}
className="mx_MessageComposer_roomReplaced_link" className="mx_MessageComposer_roomReplaced_link"
onClick={this._onTombstoneClick} onClick={this.onTombstoneClick}
> >
{_t("The conversation continues here.")} {_t("The conversation continues here.")}
</a> </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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 React from 'react';
import PropTypes from 'prop-types';
import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {MatrixEvent} from "matrix-js-sdk/src/models/event"; import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import {Room} from "matrix-js-sdk/src/models/room";
import {_t} from "../../../languageHandler"; import {_t} from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import * as sdk from "../../../index"; import * as sdk from "../../../index";
@ -27,11 +27,22 @@ import RoomAvatar from "../avatars/RoomAvatar";
import RoomName from "../elements/RoomName"; import RoomName from "../elements/RoomName";
import {replaceableComponent} from "../../../utils/replaceableComponent"; 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") @replaceableComponent("views.rooms.ThirdPartyMemberInfo")
export default class ThirdPartyMemberInfo extends React.Component { export default class ThirdPartyMemberInfo extends React.Component<IProps, IState> {
static propTypes = { private room: Room;
event: PropTypes.instanceOf(MatrixEvent).isRequired,
};
constructor(props) { constructor(props) {
super(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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 {replaceableComponent} from "../../../utils/replaceableComponent";
import SeshatResetDialog from '../dialogs/SeshatResetDialog'; import SeshatResetDialog from '../dialogs/SeshatResetDialog';
interface IState {
enabling: boolean;
eventIndexSize: number;
roomCount: number;
eventIndexingEnabled: boolean;
}
@replaceableComponent("views.settings.EventIndexPanel") @replaceableComponent("views.settings.EventIndexPanel")
export default class EventIndexPanel extends React.Component { export default class EventIndexPanel extends React.Component<{}, IState> {
constructor() { constructor(props) {
super(); super(props);
this.state = { this.state = {
enabling: false, enabling: false,
@ -68,7 +75,7 @@ export default class EventIndexPanel extends React.Component {
} }
} }
async componentDidMount(): void { componentDidMount(): void {
this.updateState(); this.updateState();
} }
@ -104,6 +111,8 @@ export default class EventIndexPanel extends React.Component {
_onManage = async () => { _onManage = async () => {
Modal.createTrackedDialogAsync('Message search', 'Message search', 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'), import('../../../async-components/views/dialogs/eventindex/ManageEventIndexDialog'),
{ {
onFinished: () => {}, 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 url from 'url';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../languageHandler"; import {_t} from "../../../languageHandler";
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../MatrixClientPeg";
@ -59,16 +58,28 @@ async function checkIdentityServerUrl(u) {
} }
} }
@replaceableComponent("views.settings.SetIdServer") interface IProps {
export default class SetIdServer extends React.Component { // Whether or not the ID server is missing terms. This affects the text
static propTypes = { // shown to the user.
// Whether or not the ID server is missing terms. This affects the text missingTerms: boolean;
// shown to the user. }
missingTerms: PropTypes.bool,
};
constructor() { interface IState {
super(); 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 = ''; let defaultIdServer = '';
if (!MatrixClientPeg.get().getIdentityServerUrl() && getDefaultIdentityServerUrl()) { if (!MatrixClientPeg.get().getIdentityServerUrl() && getDefaultIdentityServerUrl()) {
@ -371,7 +382,7 @@ export default class SetIdServer extends React.Component {
let discoSection; let discoSection;
if (idServerUrl) { if (idServerUrl) {
let discoButtonContent = _t("Disconnect"); let discoButtonContent: React.ReactNode = _t("Disconnect");
let discoBodyText = _t( let discoBodyText = _t(
"Disconnecting from your identity server will mean you " + "Disconnecting from your identity server will mean you " +
"won't be discoverable by other users and you won't be " + "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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 React from 'react';
import PropTypes from 'prop-types';
import {_t, _td} from "../../../../../languageHandler"; import {_t, _td} from "../../../../../languageHandler";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../.."; import * as sdk from "../../../../..";
@ -23,6 +22,7 @@ import AccessibleButton from "../../../elements/AccessibleButton";
import Modal from "../../../../../Modal"; import Modal from "../../../../../Modal";
import {replaceableComponent} from "../../../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../../../utils/replaceableComponent";
import {EventType} from "matrix-js-sdk/src/@types/event"; import {EventType} from "matrix-js-sdk/src/@types/event";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
const plEventsToLabels = { const plEventsToLabels = {
// These will be translated for us later. // These will be translated for us later.
@ -63,14 +63,14 @@ function parseIntWithDefault(val, def) {
return isNaN(res) ? def : res; return isNaN(res) ? def : res;
} }
export class BannedUser extends React.Component { interface IBannedUserProps {
static propTypes = { canUnban: boolean;
canUnban: PropTypes.bool, member: RoomMember;
member: PropTypes.object.isRequired, // js-sdk RoomMember by: string;
by: PropTypes.string.isRequired, reason: string;
reason: PropTypes.string, }
};
export class BannedUser extends React.Component<IBannedUserProps> {
_onUnbanClick = (e) => { _onUnbanClick = (e) => {
MatrixClientPeg.get().unban(this.props.member.roomId, this.props.member.userId).catch((err) => { MatrixClientPeg.get().unban(this.props.member.roomId, this.props.member.userId).catch((err) => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@ -107,12 +107,12 @@ export class BannedUser extends React.Component {
} }
} }
@replaceableComponent("views.settings.tabs.room.RolesRoomSettingsTab") interface IProps {
export default class RolesRoomSettingsTab extends React.Component { roomId: string;
static propTypes = { }
roomId: PropTypes.string.isRequired,
};
@replaceableComponent("views.settings.tabs.room.RolesRoomSettingsTab")
export default class RolesRoomSettingsTab extends React.Component<IProps> {
componentDidMount(): void { componentDidMount(): void {
MatrixClientPeg.get().on("RoomState.members", this._onRoomMembership); 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../../../languageHandler"; import {_t} from "../../../../../languageHandler";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../.."; import * as sdk from "../../../../..";
@ -26,16 +25,28 @@ import StyledRadioGroup from '../../../elements/StyledRadioGroup';
import {SettingLevel} from "../../../../../settings/SettingLevel"; import {SettingLevel} from "../../../../../settings/SettingLevel";
import SettingsStore from "../../../../../settings/SettingsStore"; import SettingsStore from "../../../../../settings/SettingsStore";
import {UIFeature} from "../../../../../settings/UIFeature"; 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") @replaceableComponent("views.settings.tabs.room.SecurityRoomSettingsTab")
export default class SecurityRoomSettingsTab extends React.Component { export default class SecurityRoomSettingsTab extends React.Component<IProps, IState> {
static propTypes = { constructor(props) {
roomId: PropTypes.string.isRequired, super(props);
};
constructor() {
super();
this.state = { this.state = {
joinRule: "invite", joinRule: "invite",
@ -47,23 +58,23 @@ export default class SecurityRoomSettingsTab extends React.Component {
} }
// TODO: [REACT-WARNING] Move this to constructor // 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); MatrixClientPeg.get().on("RoomState.events", this._onStateEvent);
const room = MatrixClientPeg.get().getRoom(this.props.roomId); const room = MatrixClientPeg.get().getRoom(this.props.roomId);
const state = room.currentState; const state = room.currentState;
const joinRule = this._pullContentPropertyFromEvent( const joinRule: JoinRule = this._pullContentPropertyFromEvent(
state.getStateEvents("m.room.join_rules", ""), state.getStateEvents("m.room.join_rules", ""),
'join_rule', 'join_rule',
'invite', 'invite',
); );
const guestAccess = this._pullContentPropertyFromEvent( const guestAccess: GuestAccess = this._pullContentPropertyFromEvent(
state.getStateEvents("m.room.guest_access", ""), state.getStateEvents("m.room.guest_access", ""),
'guest_access', 'guest_access',
'forbidden', 'forbidden',
); );
const history = this._pullContentPropertyFromEvent( const history: History = this._pullContentPropertyFromEvent(
state.getStateEvents("m.room.history_visibility", ""), state.getStateEvents("m.room.history_visibility", ""),
'history_visibility', 'history_visibility',
'shared', 'shared',
@ -163,8 +174,8 @@ export default class SecurityRoomSettingsTab extends React.Component {
// invite them, you clearly want them to join, whether they're a // invite them, you clearly want them to join, whether they're a
// guest or not. In practice, guest_access should probably have // guest or not. In practice, guest_access should probably have
// been implemented as part of the join_rules enum. // been implemented as part of the join_rules enum.
let joinRule = "invite"; let joinRule: JoinRule = "invite";
let guestAccess = "can_join"; let guestAccess: GuestAccess = "can_join";
switch (roomAccess) { switch (roomAccess) {
case "invite_only": case "invite_only":

View File

@ -1,6 +1,5 @@
/* /*
Copyright 2019 New Vector Ltd Copyright 2019-2021 The Matrix.org Foundation C.I.C.
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 React from 'react';
import PropTypes from 'prop-types';
import {_t, getCurrentLanguage} from "../../../../../languageHandler"; import {_t, getCurrentLanguage} from "../../../../../languageHandler";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import AccessibleButton from "../../../elements/AccessibleButton"; import AccessibleButton from "../../../elements/AccessibleButton";
@ -27,16 +25,21 @@ import * as sdk from "../../../../..";
import PlatformPeg from "../../../../../PlatformPeg"; import PlatformPeg from "../../../../../PlatformPeg";
import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts"; import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts";
import UpdateCheckButton from "../../UpdateCheckButton"; 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") @replaceableComponent("views.settings.tabs.user.HelpUserSettingsTab")
export default class HelpUserSettingsTab extends React.Component { export default class HelpUserSettingsTab extends React.Component<IProps, IState> {
static propTypes = { constructor(props) {
closeSettingsFn: PropTypes.func.isRequired, super(props);
};
constructor() {
super();
this.state = { this.state = {
appVersion: null, 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 * as sdk from "../../../../../index";
import {replaceableComponent} from "../../../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../../../utils/replaceableComponent";
interface IState {
busy: boolean;
newPersonalRule: string;
newList: string;
}
@replaceableComponent("views.settings.tabs.user.MjolnirUserSettingsTab") @replaceableComponent("views.settings.tabs.user.MjolnirUserSettingsTab")
export default class MjolnirUserSettingsTab extends React.Component { export default class MjolnirUserSettingsTab extends React.Component<{}, IState> {
constructor() { constructor(props) {
super(); super(props);
this.state = { this.state = {
busy: false, 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> Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -23,10 +23,24 @@ import Field from "../../../elements/Field";
import * as sdk from "../../../../.."; import * as sdk from "../../../../..";
import PlatformPeg from "../../../../../PlatformPeg"; import PlatformPeg from "../../../../../PlatformPeg";
import {SettingLevel} from "../../../../../settings/SettingLevel"; 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") @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 = [ static ROOM_LIST_SETTINGS = [
'breadcrumbs', 'breadcrumbs',
]; ];
@ -68,8 +82,8 @@ export default class PreferencesUserSettingsTab extends React.Component {
// Autocomplete delay (niche text box) // Autocomplete delay (niche text box)
]; ];
constructor() { constructor(props) {
super(); super(props);
this.state = { this.state = {
autoLaunch: false, 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 platform = PlatformPeg.get();
const autoLaunchSupported = await platform.supportsAutoLaunch(); 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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"); throw new Error("Unimplemented");
} }
async isEventIndexEmpty(): Promise<boolean> {
throw new Error("Unimplemented");
}
/** /**
* Check if our event index is empty. * 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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; const INDEX_VERSION = 1;
class EventIndexPeg { export class EventIndexPeg {
public index: EventIndex;
public error: Error;
private _supportIsInstalled: boolean;
constructor() { constructor() {
this.index = null; this.index = null;
this._supportIsInstalled = false;
this.error = null; this.error = null;
this._supportIsInstalled = false;
} }
/** /**
@ -181,7 +186,7 @@ class EventIndexPeg {
} }
} }
if (!global.mxEventIndexPeg) { if (!window.mxEventIndexPeg) {
global.mxEventIndexPeg = new EventIndexPeg(); 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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. * Tracks typing state for users.
*/ */
export default class TypingStore { export default class TypingStore {
private _typingStates: {
[roomId: string]: {
isTyping: boolean,
userTimer: Timer,
serverTimer: Timer,
},
};
constructor() { constructor() {
this.reset(); this.reset();
} }
static sharedInstance(): TypingStore { static sharedInstance(): TypingStore {
if (global.mxTypingStore === undefined) { if (window.mxTypingStore === undefined) {
global.mxTypingStore = new TypingStore(); window.mxTypingStore = new TypingStore();
} }
return global.mxTypingStore; return window.mxTypingStore;
} }
/** /**

View File

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

View File

@ -1,6 +1,5 @@
/* /*
Copyright 2019 New Vector Ltd Copyright 2019-2021 The Matrix.org Foundation C.I.C.
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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. limitations under the License.
*/ */
import React from 'react'; import React, { ReactNode } from 'react';
import {AutoDiscovery} from "matrix-js-sdk/src/autodiscovery"; import {AutoDiscovery} from "matrix-js-sdk/src/autodiscovery";
import {_t, _td, newTranslatableError} from "../languageHandler"; import {_t, _td, newTranslatableError} from "../languageHandler";
import {makeType} from "./TypeUtils"; import {makeType} from "./TypeUtils";
import SdkConfig from '../SdkConfig'; import SdkConfig from '../SdkConfig';
const LIVELINESS_DISCOVERY_ERRORS = [ const LIVELINESS_DISCOVERY_ERRORS: string[] = [
AutoDiscovery.ERROR_INVALID_HOMESERVER, AutoDiscovery.ERROR_INVALID_HOMESERVER,
AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER, AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER,
]; ];
@ -40,17 +39,23 @@ export class ValidatedServerConfig {
warning: string; warning: string;
} }
export interface IAuthComponentState {
serverIsAlive: boolean;
serverErrorIsFatal: boolean;
serverDeadError?: ReactNode;
}
export default class AutoDiscoveryUtils { export default class AutoDiscoveryUtils {
/** /**
* Checks if a given error or error message is considered an error * Checks if a given error or error message is considered an error
* relating to the liveliness of the server. Must be an error returned * relating to the liveliness of the server. Must be an error returned
* from this AutoDiscoveryUtils class. * 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. * @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; 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. * implementation for known values.
* @returns {*} The state for the component, given the error. * @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) { if (!err) {
return { return {
serverIsAlive: true, serverIsAlive: true,
@ -70,7 +75,7 @@ export default class AutoDiscoveryUtils {
}; };
} }
let title = _t("Cannot reach homeserver"); 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)) { if (!AutoDiscoveryUtils.isLivelinessError(err)) {
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
title = _t("Your %(brand)s is misconfigured", { brand }); title = _t("Your %(brand)s is misconfigured", { brand });
@ -92,7 +97,7 @@ export default class AutoDiscoveryUtils {
} }
let isFatalError = true; 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) { if (errorMessage === AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER) {
isFatalError = false; isFatalError = false;
title = _t("Cannot reach identity server"); title = _t("Cannot reach identity server");
@ -141,7 +146,10 @@ export default class AutoDiscoveryUtils {
* @returns {Promise<ValidatedServerConfig>} Resolves to the validated configuration. * @returns {Promise<ValidatedServerConfig>} Resolves to the validated configuration.
*/ */
static async validateServerConfigWithStaticUrls( static async validateServerConfigWithStaticUrls(
homeserverUrl: string, identityUrl: string, syntaxOnly = false): ValidatedServerConfig { homeserverUrl: string,
identityUrl?: string,
syntaxOnly = false,
): Promise<ValidatedServerConfig> {
if (!homeserverUrl) { if (!homeserverUrl) {
throw newTranslatableError(_td("No homeserver URL provided")); 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. * @param {string} serverName The homeserver domain name (eg: "matrix.org") to validate.
* @returns {Promise<ValidatedServerConfig>} Resolves to the validated configuration. * @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); const result = await AutoDiscovery.findClientConfig(serverName);
return AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result); 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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}`); console.log(`StorageManager: ${msg}`);
} }
function error(msg) { function error(msg, ...args) {
console.error(`StorageManager: ${msg}`); console.error(`StorageManager: ${msg}`, ...args);
} }
function track(action) { function track(action) {
@ -73,7 +73,7 @@ export async function checkConsistency() {
dataInLocalStorage = localStorage.length > 0; dataInLocalStorage = localStorage.length > 0;
log(`Local storage contains data? ${dataInLocalStorage}`); log(`Local storage contains data? ${dataInLocalStorage}`);
cryptoInited = localStorage.getItem("mx_crypto_initialised"); cryptoInited = !!localStorage.getItem("mx_crypto_initialised");
log(`Crypto initialised? ${cryptoInited}`); log(`Crypto initialised? ${cryptoInited}`);
} else { } else {
healthy = false; 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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()`. a new one through `clone()` or `cloneIfRun()`.
*/ */
export default class Timer { 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) { constructor(timeout) {
this._timeout = timeout; this._timeout = timeout;
this._onTimeout = this._onTimeout.bind(this); this._onTimeout = this._onTimeout.bind(this);
@ -35,7 +42,7 @@ export default class Timer {
_setNotStarted() { _setNotStarted() {
this._timerHandle = null; this._timerHandle = null;
this._startTs = null; this._startTs = null;
this._promise = new Promise((resolve, reject) => { this._promise = new Promise<void>((resolve, reject) => {
this._resolve = resolve; this._resolve = resolve;
this._reject = reject; this._reject = reject;
}).finally(() => { }).finally(() => {

View File

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