Merge branches 'develop' and 't3chguy/cmds' of github.com:matrix-org/matrix-react-sdk into t3chguy/cmds
Conflicts: src/SlashCommands.tsx
This commit is contained in:
commit
6e61761012
21 changed files with 498 additions and 111 deletions
156
CHANGELOG.md
156
CHANGELOG.md
|
@ -1,3 +1,159 @@
|
|||
Changes in [2.3.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.3.0) (2020-03-30)
|
||||
===================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.3.0-rc.1...v2.3.0)
|
||||
|
||||
* Upgrade JS SDK to 5.2.0
|
||||
|
||||
Changes in [2.3.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.3.0-rc.1) (2020-03-26)
|
||||
=============================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.2.3...v2.3.0-rc.1)
|
||||
|
||||
* Upgrade JS SDK to 5.2.0-rc.1
|
||||
* Add a flag to control whether cross-signing signatures are trusted
|
||||
[\#4277](https://github.com/matrix-org/matrix-react-sdk/pull/4277)
|
||||
* Update from Weblate
|
||||
[\#4282](https://github.com/matrix-org/matrix-react-sdk/pull/4282)
|
||||
* Update copy on SSSS symmetric upgrade toast
|
||||
[\#4281](https://github.com/matrix-org/matrix-react-sdk/pull/4281)
|
||||
* Wait for SSSS upgrade to complete
|
||||
[\#4270](https://github.com/matrix-org/matrix-react-sdk/pull/4270)
|
||||
* Update cross-signing verification copy and fix i18n
|
||||
[\#4278](https://github.com/matrix-org/matrix-react-sdk/pull/4278)
|
||||
* Fix soft-crash on bad permalinks
|
||||
[\#4280](https://github.com/matrix-org/matrix-react-sdk/pull/4280)
|
||||
* Fix: make self-verification wait for incoming request
|
||||
[\#4267](https://github.com/matrix-org/matrix-react-sdk/pull/4267)
|
||||
* Fall back to non-standard persisted api for Safari
|
||||
[\#4272](https://github.com/matrix-org/matrix-react-sdk/pull/4272)
|
||||
* Respond to backup key sharing requests
|
||||
[\#4275](https://github.com/matrix-org/matrix-react-sdk/pull/4275)
|
||||
* Log and display secret sharing cache state
|
||||
[\#4268](https://github.com/matrix-org/matrix-react-sdk/pull/4268)
|
||||
* Support sending config and ready events to capable widgets (Jitsi)
|
||||
[\#4266](https://github.com/matrix-org/matrix-react-sdk/pull/4266)
|
||||
* If cached keys are present in the key backup dialog, use them
|
||||
[\#4273](https://github.com/matrix-org/matrix-react-sdk/pull/4273)
|
||||
* Fix formatbar not hidden on highlighted message sent
|
||||
[\#4265](https://github.com/matrix-org/matrix-react-sdk/pull/4265)
|
||||
* Support Jitsi conferences sent/received on Riot Mobile and older Riot Webs
|
||||
[\#4252](https://github.com/matrix-org/matrix-react-sdk/pull/4252)
|
||||
* Use unified function to check cross-signing is ready
|
||||
[\#4263](https://github.com/matrix-org/matrix-react-sdk/pull/4263)
|
||||
* Migrate SSSS to symmetric
|
||||
[\#4224](https://github.com/matrix-org/matrix-react-sdk/pull/4224)
|
||||
* Migration to symmetric SSSS
|
||||
[\#4242](https://github.com/matrix-org/matrix-react-sdk/pull/4242)
|
||||
* Always display verification request toasts on top
|
||||
[\#4262](https://github.com/matrix-org/matrix-react-sdk/pull/4262)
|
||||
* Fix: assume SAS is supported when starting request with .start
|
||||
[\#4249](https://github.com/matrix-org/matrix-react-sdk/pull/4249)
|
||||
* Fix logout when Olm failed to load.
|
||||
[\#4261](https://github.com/matrix-org/matrix-react-sdk/pull/4261)
|
||||
* Improve naming of Jitsi conferences
|
||||
[\#4251](https://github.com/matrix-org/matrix-react-sdk/pull/4251)
|
||||
* Handle matrix.to user permalink in-room rather than solo
|
||||
[\#4245](https://github.com/matrix-org/matrix-react-sdk/pull/4245)
|
||||
* Fix: filter room list (again) by canonical and alternative aliases
|
||||
[\#4260](https://github.com/matrix-org/matrix-react-sdk/pull/4260)
|
||||
* EventIndex: Add some logging to the file panel populating.
|
||||
[\#4250](https://github.com/matrix-org/matrix-react-sdk/pull/4250)
|
||||
* Update from Weblate
|
||||
[\#4259](https://github.com/matrix-org/matrix-react-sdk/pull/4259)
|
||||
* Migrate RoomView to React Contexts in the hope for better temporal stability
|
||||
[\#4258](https://github.com/matrix-org/matrix-react-sdk/pull/4258)
|
||||
* Update WidgetUtils.js fix Jitsi path
|
||||
[\#4256](https://github.com/matrix-org/matrix-react-sdk/pull/4256)
|
||||
* Fix local jitsi build url fail and missing argument
|
||||
[\#4255](https://github.com/matrix-org/matrix-react-sdk/pull/4255)
|
||||
* Add shortcut CmdOrCtrl+. to toggle right panel
|
||||
[\#4244](https://github.com/matrix-org/matrix-react-sdk/pull/4244)
|
||||
* Improve Keyboard Shortcuts. Add alt-arrows & alt-shift-arrows
|
||||
[\#4241](https://github.com/matrix-org/matrix-react-sdk/pull/4241)
|
||||
* Bring back legacy verification by comparing public device keys
|
||||
[\#4240](https://github.com/matrix-org/matrix-react-sdk/pull/4240)
|
||||
* Searching: Return an empty result if the search term is an empty string.
|
||||
[\#4248](https://github.com/matrix-org/matrix-react-sdk/pull/4248)
|
||||
* Break continuation on showHiddenEvents-rendered events
|
||||
[\#4247](https://github.com/matrix-org/matrix-react-sdk/pull/4247)
|
||||
* Watch for show-RR settings changes, use room-specific and fix margins
|
||||
[\#4246](https://github.com/matrix-org/matrix-react-sdk/pull/4246)
|
||||
* Register Mac electron specific Cmd+, shortcut to User Settings
|
||||
[\#4243](https://github.com/matrix-org/matrix-react-sdk/pull/4243)
|
||||
* Use a local wrapper for Jitsi calls
|
||||
[\#4234](https://github.com/matrix-org/matrix-react-sdk/pull/4234)
|
||||
* Invite Dialog fixes
|
||||
[\#4233](https://github.com/matrix-org/matrix-react-sdk/pull/4233)
|
||||
* RoomPreviewBar word-break the sender name too
|
||||
[\#4239](https://github.com/matrix-org/matrix-react-sdk/pull/4239)
|
||||
* Report to the user when a key signature upload fails
|
||||
[\#4229](https://github.com/matrix-org/matrix-react-sdk/pull/4229)
|
||||
* pre-send megolm keys when possible when a user starts typing
|
||||
[\#4235](https://github.com/matrix-org/matrix-react-sdk/pull/4235)
|
||||
* we don't do mx_fadable anymore so get rid of broken RightPanel disabling
|
||||
[\#4238](https://github.com/matrix-org/matrix-react-sdk/pull/4238)
|
||||
* Fix left left panel overflowing vertically
|
||||
[\#4237](https://github.com/matrix-org/matrix-react-sdk/pull/4237)
|
||||
* Fix custom tags causing left panel to over-expand
|
||||
[\#4236](https://github.com/matrix-org/matrix-react-sdk/pull/4236)
|
||||
* Add Keyboard shortcuts dialog
|
||||
[\#4231](https://github.com/matrix-org/matrix-react-sdk/pull/4231)
|
||||
* Don't use buildkite agent to upload logs
|
||||
[\#4232](https://github.com/matrix-org/matrix-react-sdk/pull/4232)
|
||||
* Remove Gemini Scrollbars
|
||||
[\#4217](https://github.com/matrix-org/matrix-react-sdk/pull/4217)
|
||||
* Room Directory Explore Servers redesign
|
||||
[\#4209](https://github.com/matrix-org/matrix-react-sdk/pull/4209)
|
||||
* Fix redo keyboard shortcut on macOS
|
||||
[\#4110](https://github.com/matrix-org/matrix-react-sdk/pull/4110)
|
||||
* Fix: ensure local state for aliases doesn't get garbled up
|
||||
[\#4230](https://github.com/matrix-org/matrix-react-sdk/pull/4230)
|
||||
* Rename 'jump to bottom' to avoid ublock block
|
||||
[\#4208](https://github.com/matrix-org/matrix-react-sdk/pull/4208)
|
||||
* Restore key backup in background after complete security
|
||||
[\#4225](https://github.com/matrix-org/matrix-react-sdk/pull/4225)
|
||||
* Fix key backup trust text for cross-signing
|
||||
[\#4223](https://github.com/matrix-org/matrix-react-sdk/pull/4223)
|
||||
* Add default on config setting to control call button in composer
|
||||
[\#4227](https://github.com/matrix-org/matrix-react-sdk/pull/4227)
|
||||
* Fix: make alternative addresses UX less confusing
|
||||
[\#4221](https://github.com/matrix-org/matrix-react-sdk/pull/4221)
|
||||
* Wait for verification request on login
|
||||
[\#4222](https://github.com/matrix-org/matrix-react-sdk/pull/4222)
|
||||
* EventIndex: Add support to delete events from the index.
|
||||
[\#4204](https://github.com/matrix-org/matrix-react-sdk/pull/4204)
|
||||
* EventIndex: Remove a checkpoint if the HTTP request returns a 403.
|
||||
[\#4214](https://github.com/matrix-org/matrix-react-sdk/pull/4214)
|
||||
* Move to composer when typing letters with Shift held
|
||||
[\#4216](https://github.com/matrix-org/matrix-react-sdk/pull/4216)
|
||||
* Wrap large room names when previewing them
|
||||
[\#4213](https://github.com/matrix-org/matrix-react-sdk/pull/4213)
|
||||
* Rename Review Devices to Review Sessions
|
||||
[\#4219](https://github.com/matrix-org/matrix-react-sdk/pull/4219)
|
||||
* Fix typo in tabIndex to make React happy
|
||||
[\#4215](https://github.com/matrix-org/matrix-react-sdk/pull/4215)
|
||||
* Proof of concept for custom theme adding
|
||||
[\#4148](https://github.com/matrix-org/matrix-react-sdk/pull/4148)
|
||||
* Remove stuff that yarn install doesn't think we need
|
||||
[\#4205](https://github.com/matrix-org/matrix-react-sdk/pull/4205)
|
||||
* Declare jsx in tsconfig for IDEs
|
||||
[\#4207](https://github.com/matrix-org/matrix-react-sdk/pull/4207)
|
||||
* Fix: best-effort to join room without canonical alias over federation from
|
||||
room directory
|
||||
[\#4210](https://github.com/matrix-org/matrix-react-sdk/pull/4210)
|
||||
* Test for cross-signing homeserver support during login, toasts
|
||||
[\#4206](https://github.com/matrix-org/matrix-react-sdk/pull/4206)
|
||||
* Send verification request to a single device in a way compatible with non-
|
||||
cross-signing
|
||||
[\#4202](https://github.com/matrix-org/matrix-react-sdk/pull/4202)
|
||||
* Fixes for removing local alias
|
||||
[\#4199](https://github.com/matrix-org/matrix-react-sdk/pull/4199)
|
||||
* yarn upgrade
|
||||
[\#4201](https://github.com/matrix-org/matrix-react-sdk/pull/4201)
|
||||
* Support TypeScript for React components
|
||||
[\#4203](https://github.com/matrix-org/matrix-react-sdk/pull/4203)
|
||||
* When room name is changed, show both the old and new name
|
||||
[\#4183](https://github.com/matrix-org/matrix-react-sdk/pull/4183)
|
||||
|
||||
Changes in [2.2.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.2.3) (2020-03-17)
|
||||
===================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.2.3-rc.1...v2.2.3)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "matrix-react-sdk",
|
||||
"version": "2.2.3",
|
||||
"version": "2.3.0",
|
||||
"description": "SDK for matrix.org using React",
|
||||
"author": "matrix.org",
|
||||
"repository": {
|
||||
|
|
|
@ -137,7 +137,7 @@ limitations under the License.
|
|||
top: -8px;
|
||||
border-radius: 8px;
|
||||
background-color: $neutral-badge-color;
|
||||
color: #ffffff;
|
||||
color: #000;
|
||||
font-weight: 600;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
|
|
|
@ -430,7 +430,7 @@ async function _startCallApp(roomId, type) {
|
|||
return;
|
||||
}
|
||||
|
||||
const confId = `JitsiConference_${generateHumanReadableId()}`;
|
||||
const confId = `JitsiConference${generateHumanReadableId()}`;
|
||||
const jitsiDomain = SdkConfig.get()['jitsi']['preferredDomain'];
|
||||
|
||||
let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl();
|
||||
|
|
|
@ -85,7 +85,7 @@ class Command {
|
|||
aliases = [],
|
||||
args = '',
|
||||
description,
|
||||
runFn=undefined,
|
||||
runFn = undefined,
|
||||
category = CommandCategories.other,
|
||||
hideCompletionAfterSpace = false,
|
||||
}: {
|
||||
|
@ -160,6 +160,15 @@ export const Commands = [
|
|||
},
|
||||
category: CommandCategories.messages,
|
||||
}),
|
||||
new Command({
|
||||
command: 'html',
|
||||
args: '<message>',
|
||||
description: _td('Sends a message as html, without interpreting it as markdown'),
|
||||
runFn: function(roomId, messages) {
|
||||
return success(MatrixClientPeg.get().sendHtmlMessage(roomId, messages, messages));
|
||||
},
|
||||
category: CommandCategories.messages,
|
||||
}),
|
||||
new Command({
|
||||
command: 'ddg',
|
||||
args: '<query>',
|
||||
|
|
|
@ -67,8 +67,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._keyInfo = null;
|
||||
this._encodedRecoveryKey = null;
|
||||
this._recoveryKey = null;
|
||||
this._recoveryKeyNode = null;
|
||||
this._setZxcvbnResultTimeout = null;
|
||||
|
||||
|
@ -180,7 +179,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
}
|
||||
|
||||
_onDownloadClick = () => {
|
||||
const blob = new Blob([this._encodedRecoveryKey], {
|
||||
const blob = new Blob([this._recoveryKey.encodedPrivateKey], {
|
||||
type: 'text/plain;charset=us-ascii',
|
||||
});
|
||||
FileSaver.saveAs(blob, 'recovery-key.txt');
|
||||
|
@ -234,14 +233,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
if (force) {
|
||||
await cli.bootstrapSecretStorage({
|
||||
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
||||
createSecretStorageKey: async () => this._keyInfo,
|
||||
createSecretStorageKey: async () => this._recoveryKey,
|
||||
setupNewKeyBackup: true,
|
||||
setupNewSecretStorage: true,
|
||||
});
|
||||
} else {
|
||||
await cli.bootstrapSecretStorage({
|
||||
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
||||
createSecretStorageKey: async () => this._keyInfo,
|
||||
createSecretStorageKey: async () => this._recoveryKey,
|
||||
keyBackupInfo: this.state.backupInfo,
|
||||
setupNewKeyBackup: !this.state.backupInfo && this.state.useKeyBackup,
|
||||
getKeyBackupPassphrase: promptForBackupPassphrase,
|
||||
|
@ -299,10 +298,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
}
|
||||
|
||||
_onSkipPassPhraseClick = async () => {
|
||||
const [keyInfo, encodedRecoveryKey] =
|
||||
this._recoveryKey =
|
||||
await MatrixClientPeg.get().createRecoveryKeyFromPassphrase();
|
||||
this._keyInfo = keyInfo;
|
||||
this._encodedRecoveryKey = encodedRecoveryKey;
|
||||
this.setState({
|
||||
copied: false,
|
||||
downloaded: false,
|
||||
|
@ -335,10 +332,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
|
||||
if (this.state.passPhrase !== this.state.passPhraseConfirm) return;
|
||||
|
||||
const [keyInfo, encodedRecoveryKey] =
|
||||
this._recoveryKey =
|
||||
await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(this.state.passPhrase);
|
||||
this._keyInfo = keyInfo;
|
||||
this._encodedRecoveryKey = encodedRecoveryKey;
|
||||
this.setState({
|
||||
copied: false,
|
||||
downloaded: false,
|
||||
|
@ -613,7 +608,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
|||
</div>
|
||||
<div className="mx_CreateSecretStorageDialog_recoveryKeyContainer">
|
||||
<div className="mx_CreateSecretStorageDialog_recoveryKey">
|
||||
<code ref={this._collectRecoveryKeyNode}>{this._encodedRecoveryKey}</code>
|
||||
<code ref={this._collectRecoveryKeyNode}>{this._recoveryKey.encodedPrivateKey}</code>
|
||||
</div>
|
||||
<div className="mx_CreateSecretStorageDialog_recoveryKeyButtons">
|
||||
<AccessibleButton kind='primary' className="mx_Dialog_primary" onClick={this._onCopyClick}>
|
||||
|
|
|
@ -37,6 +37,8 @@ export default class EmbeddedPage extends React.PureComponent {
|
|||
className: PropTypes.string,
|
||||
// Whether to wrap the page in a scrollbar
|
||||
scrollbar: PropTypes.bool,
|
||||
// Map of keys to replace with values, e.g {$placeholder: "value"}
|
||||
replaceMap: PropTypes.object,
|
||||
};
|
||||
|
||||
static contextType = MatrixClientContext;
|
||||
|
@ -81,6 +83,13 @@ export default class EmbeddedPage extends React.PureComponent {
|
|||
}
|
||||
|
||||
body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1));
|
||||
|
||||
if (this.props.replaceMap) {
|
||||
Object.keys(this.props.replaceMap).forEach(key => {
|
||||
body = body.split(key).join(this.props.replaceMap[key]);
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ page: body });
|
||||
},
|
||||
);
|
||||
|
|
|
@ -2022,7 +2022,7 @@ export default createReactClass({
|
|||
}
|
||||
} else if (this.state.view === VIEWS.WELCOME) {
|
||||
const Welcome = sdk.getComponent('auth.Welcome');
|
||||
view = <Welcome />;
|
||||
view = <Welcome {...this.getServerProperties()} />;
|
||||
} else if (this.state.view === VIEWS.REGISTER) {
|
||||
const Registration = sdk.getComponent('structures.auth.Registration');
|
||||
view = (
|
||||
|
|
|
@ -55,6 +55,7 @@ import RightPanelStore from "../../stores/RightPanelStore";
|
|||
import {haveTileForEvent} from "../views/rooms/EventTile";
|
||||
import RoomContext from "../../contexts/RoomContext";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import { shieldStatusForRoom } from '../../utils/ShieldUtils';
|
||||
|
||||
const DEBUG = false;
|
||||
let debuglog = function() {};
|
||||
|
@ -817,40 +818,9 @@ export default createReactClass({
|
|||
return;
|
||||
}
|
||||
|
||||
// Duplication between here and _updateE2eStatus in RoomTile
|
||||
/* At this point, the user has encryption on and cross-signing on */
|
||||
const e2eMembers = await room.getEncryptionTargetMembers();
|
||||
const verified = [];
|
||||
const unverified = [];
|
||||
e2eMembers.map(({userId}) => userId)
|
||||
.filter((userId) => userId !== this.context.getUserId())
|
||||
.forEach((userId) => {
|
||||
(this.context.checkUserTrust(userId).isCrossSigningVerified() ?
|
||||
verified : unverified).push(userId);
|
||||
});
|
||||
|
||||
debuglog("e2e verified", verified, "unverified", unverified);
|
||||
|
||||
/* Check all verified user devices. */
|
||||
/* Don't alarm if no other users are verified */
|
||||
const targets = (verified.length > 0) ? [...verified, this.context.getUserId()] : verified;
|
||||
for (const userId of targets) {
|
||||
const devices = await this.context.getStoredDevicesForUser(userId);
|
||||
const anyDeviceNotVerified = devices.some(({deviceId}) => {
|
||||
return !this.context.checkDeviceTrust(userId, deviceId).isVerified();
|
||||
});
|
||||
if (anyDeviceNotVerified) {
|
||||
this.setState({
|
||||
e2eStatus: "warning",
|
||||
});
|
||||
debuglog("e2e status set to warning as not all users trust all of their sessions." +
|
||||
" Aborted on user", userId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
e2eStatus: unverified.length === 0 ? "verified" : "normal",
|
||||
e2eStatus: await shieldStatusForRoom(this.context, room),
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -18,6 +18,12 @@ import React from 'react';
|
|||
import * as sdk from '../../../index';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import AuthPage from "./AuthPage";
|
||||
import * as Matrix from "matrix-js-sdk";
|
||||
import {_td} from "../../../languageHandler";
|
||||
import PlatformPeg from "../../../PlatformPeg";
|
||||
|
||||
// translatable strings for Welcome pages
|
||||
_td("Sign in with SSO");
|
||||
|
||||
export default class Welcome extends React.PureComponent {
|
||||
render() {
|
||||
|
@ -33,11 +39,24 @@ export default class Welcome extends React.PureComponent {
|
|||
pageUrl = 'welcome.html';
|
||||
}
|
||||
|
||||
const {hsUrl, isUrl} = this.props.serverConfig;
|
||||
const tmpClient = Matrix.createClient({
|
||||
baseUrl: hsUrl,
|
||||
idBaseUrl: isUrl,
|
||||
});
|
||||
const plaf = PlatformPeg.get();
|
||||
const callbackUrl = plaf.getSSOCallbackUrl(tmpClient.getHomeserverUrl(), tmpClient.getIdentityServerUrl());
|
||||
|
||||
return (
|
||||
<AuthPage>
|
||||
<div className="mx_Welcome">
|
||||
<EmbeddedPage className="mx_WelcomePage"
|
||||
<EmbeddedPage
|
||||
className="mx_WelcomePage"
|
||||
url={pageUrl}
|
||||
replaceMap={{
|
||||
"$riot:ssoUrl": tmpClient.getSsoLoginUrl(callbackUrl.toString(), "sso"),
|
||||
"$riot:casUrl": tmpClient.getSsoLoginUrl(callbackUrl.toString(), "cas"),
|
||||
}}
|
||||
/>
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
|
|
|
@ -20,6 +20,7 @@ import MemberAvatar from '../avatars/MemberAvatar';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import {MatrixEvent, RoomMember} from "matrix-js-sdk";
|
||||
import {useStateToggle} from "../../../hooks/useStateToggle";
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
|
||||
const EventListSummary = ({events, children, threshold=3, onToggle, startExpanded, summaryMembers=[], summaryText}) => {
|
||||
const [expanded, toggleExpanded] = useStateToggle(startExpanded);
|
||||
|
@ -42,24 +43,15 @@ const EventListSummary = ({events, children, threshold=3, onToggle, startExpande
|
|||
);
|
||||
}
|
||||
|
||||
let body;
|
||||
if (expanded) {
|
||||
return (
|
||||
<div className="mx_EventListSummary" data-scroll-tokens={eventIds}>
|
||||
<div className={"mx_EventListSummary_toggle"} onClick={toggleExpanded}>
|
||||
{ _t('collapse') }
|
||||
</div>
|
||||
<div className="mx_EventListSummary_line"> </div>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const avatars = summaryMembers.map((m) => <MemberAvatar key={m.userId} member={m} width={14} height={14} />);
|
||||
return (
|
||||
<div className="mx_EventListSummary" data-scroll-tokens={eventIds}>
|
||||
<div className={"mx_EventListSummary_toggle"} onClick={toggleExpanded}>
|
||||
{ _t('expand') }
|
||||
</div>
|
||||
body = <React.Fragment>
|
||||
<div className="mx_EventListSummary_line"> </div>
|
||||
{ children }
|
||||
</React.Fragment>;
|
||||
} else {
|
||||
const avatars = summaryMembers.map((m) => <MemberAvatar key={m.userId} member={m} width={14} height={14} />);
|
||||
body = (
|
||||
<div className="mx_EventTile_line">
|
||||
<div className="mx_EventTile_info">
|
||||
<span className="mx_EventListSummary_avatars" onClick={toggleExpanded}>
|
||||
|
@ -70,6 +62,15 @@ const EventListSummary = ({events, children, threshold=3, onToggle, startExpande
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_EventListSummary" data-scroll-tokens={eventIds}>
|
||||
<AccessibleButton className="mx_EventListSummary_toggle" onClick={toggleExpanded} aria-expanded={expanded}>
|
||||
{ expanded ? _t('collapse') : _t('expand') }
|
||||
</AccessibleButton>
|
||||
{ body }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -32,7 +32,7 @@ function getId() {
|
|||
export default class Field extends React.PureComponent {
|
||||
static propTypes = {
|
||||
// The field's ID, which binds the input and label together. Immutable.
|
||||
id: PropTypes.string.isRequired,
|
||||
id: PropTypes.string,
|
||||
// The element to create. Defaults to "input".
|
||||
// To define options for a select, use <Field><option ... /></Field>
|
||||
element: PropTypes.oneOf(["input", "select", "textarea"]),
|
||||
|
|
|
@ -68,8 +68,10 @@ export const getE2EStatus = (cli, userId, devices) => {
|
|||
return hasUnverifiedDevice ? "warning" : "verified";
|
||||
}
|
||||
const isMe = userId === cli.getUserId();
|
||||
const userVerified = cli.checkUserTrust(userId).isCrossSigningVerified();
|
||||
if (!userVerified) return "normal";
|
||||
const userTrust = cli.checkUserTrust(userId);
|
||||
if (!userTrust.isCrossSigningVerified()) {
|
||||
return userTrust.wasCrossSigningVerified() ? "warning" : "normal";
|
||||
}
|
||||
|
||||
const anyDeviceUnverified = devices.some(device => {
|
||||
const { deviceId } = device;
|
||||
|
|
|
@ -121,10 +121,10 @@ export default createReactClass({
|
|||
const cli = MatrixClientPeg.get();
|
||||
const { userId } = this.props.member;
|
||||
const isMe = userId === cli.getUserId();
|
||||
const userVerified = cli.checkUserTrust(userId).isCrossSigningVerified();
|
||||
if (!userVerified) {
|
||||
const userTrust = cli.checkUserTrust(userId);
|
||||
if (!userTrust.isCrossSigningVerified()) {
|
||||
this.setState({
|
||||
e2eStatus: "normal",
|
||||
e2eStatus: userTrust.wasCrossSigningVerified() ? "warning" : "normal",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -19,12 +19,13 @@ import PropTypes from 'prop-types';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import * as sdk from '../../../index';
|
||||
import classNames from 'classnames';
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
|
||||
export default class MessageComposerFormatBar extends React.PureComponent {
|
||||
static propTypes = {
|
||||
onAction: PropTypes.func.isRequired,
|
||||
shortcuts: PropTypes.object.isRequired,
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -64,7 +65,7 @@ class FormatButton extends React.PureComponent {
|
|||
icon: PropTypes.string.isRequired,
|
||||
shortcut: PropTypes.string,
|
||||
visible: PropTypes.bool,
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const InteractiveTooltip = sdk.getComponent('elements.InteractiveTooltip');
|
||||
|
@ -82,11 +83,12 @@ class FormatButton extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<InteractiveTooltip content={tooltipContent} forceHidden={!this.props.visible}>
|
||||
<span aria-label={this.props.label}
|
||||
role="button"
|
||||
onClick={this.props.onClick}
|
||||
className={className}>
|
||||
</span>
|
||||
<AccessibleButton
|
||||
as="span"
|
||||
role="button"
|
||||
onClick={this.props.onClick}
|
||||
aria-label={this.props.label}
|
||||
className={className} />
|
||||
</InteractiveTooltip>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import E2EIcon from './E2EIcon';
|
|||
import InviteOnlyIcon from './InviteOnlyIcon';
|
||||
// eslint-disable-next-line camelcase
|
||||
import rate_limited_func from '../../../ratelimitedfunc';
|
||||
import { shieldStatusForRoom } from '../../../utils/ShieldUtils';
|
||||
|
||||
export default createReactClass({
|
||||
displayName: 'RoomTile',
|
||||
|
@ -154,35 +155,9 @@ export default createReactClass({
|
|||
return;
|
||||
}
|
||||
|
||||
// Duplication between here and _updateE2eStatus in RoomView
|
||||
const e2eMembers = await this.props.room.getEncryptionTargetMembers();
|
||||
const verified = [];
|
||||
const unverified = [];
|
||||
e2eMembers.map(({userId}) => userId)
|
||||
.filter((userId) => userId !== cli.getUserId())
|
||||
.forEach((userId) => {
|
||||
(cli.checkUserTrust(userId).isCrossSigningVerified() ?
|
||||
verified : unverified).push(userId);
|
||||
});
|
||||
|
||||
/* Check all verified user devices. */
|
||||
/* Don't alarm if no other users are verified */
|
||||
const targets = (verified.length > 0) ? [...verified, cli.getUserId()] : verified;
|
||||
for (const userId of targets) {
|
||||
const devices = await cli.getStoredDevicesForUser(userId);
|
||||
const allDevicesVerified = devices.every(({deviceId}) => {
|
||||
return cli.checkDeviceTrust(userId, deviceId).isVerified();
|
||||
});
|
||||
if (!allDevicesVerified) {
|
||||
this.setState({
|
||||
e2eStatus: "warning",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* At this point, the user has encryption on and cross-signing on */
|
||||
this.setState({
|
||||
e2eStatus: unverified.length === 0 ? "verified" : "normal",
|
||||
e2eStatus: await shieldStatusForRoom(cli, this.props.room),
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -153,6 +153,7 @@
|
|||
"Usage": "Usage",
|
||||
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Prepends ¯\\_(ツ)_/¯ to a plain-text message",
|
||||
"Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown",
|
||||
"Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown",
|
||||
"Searches DuckDuckGo for results": "Searches DuckDuckGo for results",
|
||||
"/ddg is not a command": "/ddg is not a command",
|
||||
"To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.",
|
||||
|
@ -1880,6 +1881,7 @@
|
|||
"Find other public servers or use a custom server": "Find other public servers or use a custom server",
|
||||
"Sign in to your Matrix account on %(serverName)s": "Sign in to your Matrix account on %(serverName)s",
|
||||
"Sign in to your Matrix account on <underlinedServerName />": "Sign in to your Matrix account on <underlinedServerName />",
|
||||
"Sign in with SSO": "Sign in with SSO",
|
||||
"Sorry, your browser is <b>not</b> able to run Riot.": "Sorry, your browser is <b>not</b> able to run Riot.",
|
||||
"Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.",
|
||||
"Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, or <safariLink>Safari</safariLink> for the best experience.": "Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, or <safariLink>Safari</safariLink> for the best experience.",
|
||||
|
|
|
@ -469,6 +469,9 @@ export default class EventIndex extends EventEmitter {
|
|||
// decryption keys, do we want to retry this checkpoint at a later
|
||||
// stage?
|
||||
const filteredEvents = matrixEvents.filter(this.isValidEvent);
|
||||
const undecryptableEvents = matrixEvents.filter((ev) => {
|
||||
return ev.isDecryptionFailure();
|
||||
});
|
||||
|
||||
// Collect the redaction events so we can delete the redacted events
|
||||
// from the index.
|
||||
|
@ -503,7 +506,10 @@ export default class EventIndex extends EventEmitter {
|
|||
console.log(
|
||||
"EventIndex: Crawled room",
|
||||
client.getRoom(checkpoint.roomId).name,
|
||||
"and fetched", events.length, "events.",
|
||||
"and fetched total", matrixEvents.length, "events of which",
|
||||
events.length, "are being added,", redactionEvents.length,
|
||||
"are redacted,", matrixEvents.length - events.length,
|
||||
"are being skipped, undecryptable", undecryptableEvents.length,
|
||||
);
|
||||
|
||||
try {
|
||||
|
|
58
src/utils/ShieldUtils.ts
Normal file
58
src/utils/ShieldUtils.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import DMRoomMap from './DMRoomMap';
|
||||
|
||||
/* For now, a cut-down type spec for the client */
|
||||
interface Client {
|
||||
getUserId: () => string;
|
||||
checkUserTrust: (userId: string) => {
|
||||
isCrossSigningVerified: () => boolean
|
||||
wasCrossSigningVerified: () => boolean
|
||||
};
|
||||
getStoredDevicesForUser: (userId: string) => Promise<[{ deviceId: string }]>;
|
||||
checkDeviceTrust: (userId: string, deviceId: string) => {
|
||||
isVerified: () => boolean
|
||||
}
|
||||
}
|
||||
|
||||
interface Room {
|
||||
getEncryptionTargetMembers: () => Promise<[{userId: string}]>;
|
||||
roomId: string;
|
||||
}
|
||||
|
||||
export async function shieldStatusForRoom(client: Client, room: Room): Promise<string> {
|
||||
const members = (await room.getEncryptionTargetMembers()).map(({userId}) => userId);
|
||||
const inDMMap = !!DMRoomMap.shared().getUserIdForRoomId(room.roomId);
|
||||
|
||||
const verified: string[] = [];
|
||||
const unverified: string[] = [];
|
||||
members.filter((userId) => userId !== client.getUserId())
|
||||
.forEach((userId) => {
|
||||
(client.checkUserTrust(userId).isCrossSigningVerified() ?
|
||||
verified : unverified).push(userId);
|
||||
});
|
||||
|
||||
/* Alarm if any unverified users were verified before. */
|
||||
for (const userId of unverified) {
|
||||
if (client.checkUserTrust(userId).wasCrossSigningVerified()) {
|
||||
return "warning";
|
||||
}
|
||||
}
|
||||
|
||||
/* Check all verified user devices. */
|
||||
/* Don't alarm if no other users are verified */
|
||||
const includeUser = (verified.length > 0) && // Don't alarm for self in rooms where nobody else is verified
|
||||
!inDMMap && // Don't alarm for self in DMs with other users
|
||||
(members.length !== 2) || // Don't alarm for self in 1:1 chats with other users
|
||||
(members.length === 1); // Do alarm for self if we're alone in a room
|
||||
const targets = includeUser ? [...verified, client.getUserId()] : verified;
|
||||
for (const userId of targets) {
|
||||
const devices = await client.getStoredDevicesForUser(userId);
|
||||
const anyDeviceNotVerified = devices.some(({deviceId}) => {
|
||||
return !client.checkDeviceTrust(userId, deviceId).isVerified();
|
||||
});
|
||||
if (anyDeviceNotVerified) {
|
||||
return "warning";
|
||||
}
|
||||
}
|
||||
|
||||
return unverified.length === 0 ? "verified" : "normal";
|
||||
}
|
183
test/utils/ShieldUtils-test.js
Normal file
183
test/utils/ShieldUtils-test.js
Normal file
|
@ -0,0 +1,183 @@
|
|||
import { shieldStatusForRoom } from '../../src/utils/ShieldUtils';
|
||||
import DMRoomMap from '../../src/utils/DMRoomMap';
|
||||
|
||||
function mkClient(selfTrust) {
|
||||
return {
|
||||
getUserId: () => "@self:localhost",
|
||||
checkUserTrust: (userId) => ({
|
||||
isCrossSigningVerified: () => userId[1] == "T",
|
||||
wasCrossSigningVerified: () => userId[1] == "T" || userId[1] == "W",
|
||||
}),
|
||||
checkDeviceTrust: (userId, deviceId) => ({
|
||||
isVerified: () => userId === "@self:localhost" ? selfTrust : userId[2] == "T",
|
||||
}),
|
||||
getStoredDevicesForUser: async (userId) => ["DEVICE"],
|
||||
};
|
||||
}
|
||||
|
||||
describe("mkClient self-test", function() {
|
||||
test.each([true, false])("behaves well for self-trust=%s", (v) => {
|
||||
const client = mkClient(v);
|
||||
expect(client.checkDeviceTrust("@self:localhost", "DEVICE").isVerified()).toBe(v);
|
||||
});
|
||||
|
||||
test.each([
|
||||
["@TT:h", true],
|
||||
["@TF:h", true],
|
||||
["@FT:h", false],
|
||||
["@FF:h", false]],
|
||||
)("behaves well for user trust %s", (userId, trust) => {
|
||||
expect(mkClient().checkUserTrust(userId).isCrossSigningVerified()).toBe(trust);
|
||||
});
|
||||
|
||||
test.each([
|
||||
["@TT:h", true],
|
||||
["@TF:h", false],
|
||||
["@FT:h", true],
|
||||
["@FF:h", false]],
|
||||
)("behaves well for device trust %s", (userId, trust) => {
|
||||
expect(mkClient().checkDeviceTrust(userId, "device").isVerified()).toBe(trust);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shieldStatusForMembership self-trust behaviour", function() {
|
||||
beforeAll(() => {
|
||||
DMRoomMap._sharedInstance = {
|
||||
getUserIdForRoomId: (roomId) => roomId === "DM" ? "@any:h" : null,
|
||||
};
|
||||
});
|
||||
|
||||
it.each(
|
||||
[[true, true], [true, false],
|
||||
[false, true], [false, false]],
|
||||
)("2 unverified: returns 'normal', self-trust = %s, DM = %s", async (trusted, dm) => {
|
||||
const client = mkClient(trusted);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@FF1:h", "@FF2:h"].map((userId) => ({userId})),
|
||||
};
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual("normal");
|
||||
});
|
||||
|
||||
it.each(
|
||||
[["verified", true, true], ["verified", true, false],
|
||||
["verified", false, true], ["warning", false, false]],
|
||||
)("2 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||
const client = mkClient(trusted);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@TT1:h", "@TT2:h"].map((userId) => ({userId})),
|
||||
};
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
|
||||
it.each(
|
||||
[["normal", true, true], ["normal", true, false],
|
||||
["normal", false, true], ["warning", false, false]],
|
||||
)("2 mixed: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||
const client = mkClient(trusted);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@TT1:h", "@FF2:h"].map((userId) => ({userId})),
|
||||
};
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
|
||||
it.each(
|
||||
[["verified", true, true], ["verified", true, false],
|
||||
["warning", false, true], ["warning", false, false]],
|
||||
)("0 others: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||
const client = mkClient(trusted);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost"].map((userId) => ({userId})),
|
||||
};
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
|
||||
it.each(
|
||||
[["verified", true, true], ["verified", true, false],
|
||||
["verified", false, true], ["verified", false, false]],
|
||||
)("1 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||
const client = mkClient(trusted);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@TT:h"].map((userId) => ({userId})),
|
||||
};
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
|
||||
it.each(
|
||||
[["normal", true, true], ["normal", true, false],
|
||||
["normal", false, true], ["normal", false, false]],
|
||||
)("1 unverified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
|
||||
const client = mkClient(trusted);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@FF:h"].map((userId) => ({userId})),
|
||||
};
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shieldStatusForMembership other-trust behaviour", function() {
|
||||
beforeAll(() => {
|
||||
DMRoomMap._sharedInstance = {
|
||||
getUserIdForRoomId: (roomId) => roomId === "DM" ? "@any:h" : null,
|
||||
};
|
||||
});
|
||||
|
||||
it.each(
|
||||
[["warning", true], ["warning", false]],
|
||||
)("1 verified/untrusted: returns '%s', DM = %s", async (result, dm) => {
|
||||
const client = mkClient(true);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@TF:h"].map((userId) => ({userId})),
|
||||
};
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
|
||||
it.each(
|
||||
[["warning", true], ["warning", false]],
|
||||
)("2 verified/untrusted: returns '%s', DM = %s", async (result, dm) => {
|
||||
const client = mkClient(true);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@TF:h", "@TT:h"].map((userId) => ({userId})),
|
||||
};
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
|
||||
it.each(
|
||||
[["normal", true], ["normal", false]],
|
||||
)("2 unverified/untrusted: returns '%s', DM = %s", async (result, dm) => {
|
||||
const client = mkClient(true);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@FF:h", "@FT:h"].map((userId) => ({userId})),
|
||||
};
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
|
||||
it.each(
|
||||
[["warning", true], ["warning", false]],
|
||||
)("2 was verified: returns '%s', DM = %s", async (result, dm) => {
|
||||
const client = mkClient(true);
|
||||
const room = {
|
||||
roomId: dm ? "DM" : "other",
|
||||
getEncryptionTargetMembers: () => ["@self:localhost", "@WF:h", "@FT:h"].map((userId) => ({userId})),
|
||||
};
|
||||
const status = await shieldStatusForRoom(client, room);
|
||||
expect(status).toEqual(result);
|
||||
});
|
||||
});
|
|
@ -5690,8 +5690,8 @@ mathml-tag-names@^2.0.1:
|
|||
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
|
||||
|
||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
||||
version "5.1.1"
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b2e154377a4268441a3b27b183dd7f7018187035"
|
||||
version "5.2.0"
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/223d37ffce674a23ca73702f04b9ba31cfd84196"
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.8.3"
|
||||
another-json "^0.2.0"
|
||||
|
|
Loading…
Reference in a new issue