diff --git a/.github/workflows/cypress.yaml b/.github/workflows/cypress.yaml index 57e6a7837e..cbb5347173 100644 --- a/.github/workflows/cypress.yaml +++ b/.github/workflows/cypress.yaml @@ -79,8 +79,8 @@ jobs: strategy: fail-fast: false matrix: - # Run 3 instances in Parallel - runner: [1, 2, 3] + # Run 4 instances in Parallel + runner: [1, 2, 3, 4] steps: - uses: actions/checkout@v2 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index edb088cd64..2765dbe450 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,53 @@ +Changes in [3.59.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.59.0) (2022-10-25) +===================================================================================================== + +## ✨ Features + * Include a file-safe room name and ISO date in chat exports ([\#9440](https://github.com/matrix-org/matrix-react-sdk/pull/9440)). Fixes vector-im/element-web#21812 and vector-im/element-web#19724. + * Room call banner ([\#9378](https://github.com/matrix-org/matrix-react-sdk/pull/9378)). Fixes vector-im/element-web#23453. Contributed by @toger5. + * Device manager - spinners while devices are signing out ([\#9433](https://github.com/matrix-org/matrix-react-sdk/pull/9433)). Fixes vector-im/element-web#15865. + * Device manager - silence call ringers when local notifications are silenced ([\#9420](https://github.com/matrix-org/matrix-react-sdk/pull/9420)). + * Pass the current language to Element Call ([\#9427](https://github.com/matrix-org/matrix-react-sdk/pull/9427)). + * Hide screen-sharing button in Element Call on desktop ([\#9423](https://github.com/matrix-org/matrix-react-sdk/pull/9423)). + * Add reply support to WysiwygComposer ([\#9422](https://github.com/matrix-org/matrix-react-sdk/pull/9422)). Contributed by @florianduros. + * Disconnect other connected devices (of the same user) when joining an Element call ([\#9379](https://github.com/matrix-org/matrix-react-sdk/pull/9379)). + * Device manager - device tile main click target ([\#9409](https://github.com/matrix-org/matrix-react-sdk/pull/9409)). + * Add formatting buttons to the rich text editor ([\#9410](https://github.com/matrix-org/matrix-react-sdk/pull/9410)). Contributed by @florianduros. + * Device manager - current session context menu ([\#9386](https://github.com/matrix-org/matrix-react-sdk/pull/9386)). + * Remove piwik config fallback for privacy policy URL ([\#9390](https://github.com/matrix-org/matrix-react-sdk/pull/9390)). + * Add the first step to integrate the matrix wysiwyg composer ([\#9374](https://github.com/matrix-org/matrix-react-sdk/pull/9374)). Contributed by @florianduros. + * Device manager - UA parsing tweaks ([\#9382](https://github.com/matrix-org/matrix-react-sdk/pull/9382)). + * Device manager - remove client information events when disabling setting ([\#9384](https://github.com/matrix-org/matrix-react-sdk/pull/9384)). + * Add Element Call participant limit ([\#9358](https://github.com/matrix-org/matrix-react-sdk/pull/9358)). + * Add Element Call room settings ([\#9347](https://github.com/matrix-org/matrix-react-sdk/pull/9347)). + * Device manager - render extended device information ([\#9360](https://github.com/matrix-org/matrix-react-sdk/pull/9360)). + * New group call experience: Room header and PiP designs ([\#9351](https://github.com/matrix-org/matrix-react-sdk/pull/9351)). + * Pass language to Jitsi Widget ([\#9346](https://github.com/matrix-org/matrix-react-sdk/pull/9346)). Contributed by @Fox32. + * Add notifications and toasts for Element Call calls ([\#9337](https://github.com/matrix-org/matrix-react-sdk/pull/9337)). + * Device manager - device type icon ([\#9355](https://github.com/matrix-org/matrix-react-sdk/pull/9355)). + * Delete the remainder of groups ([\#9357](https://github.com/matrix-org/matrix-react-sdk/pull/9357)). Fixes vector-im/element-web#22770. + * Device manager - display client information in device details ([\#9315](https://github.com/matrix-org/matrix-react-sdk/pull/9315)). + +## 🐛 Bug Fixes + * Send Content-Type: application/json header for integration manager /register API ([\#9490](https://github.com/matrix-org/matrix-react-sdk/pull/9490)). Fixes vector-im/element-web#23580. + * Device manager - put client/browser device metadata in correct section ([\#9447](https://github.com/matrix-org/matrix-react-sdk/pull/9447)). + * update the room unread notification counter when the server changes the value without any related read receipt ([\#9438](https://github.com/matrix-org/matrix-react-sdk/pull/9438)). + * Don't show call banners in video rooms ([\#9441](https://github.com/matrix-org/matrix-react-sdk/pull/9441)). + * Prevent useContextMenu isOpen from being true if the button ref goes away ([\#9418](https://github.com/matrix-org/matrix-react-sdk/pull/9418)). Fixes matrix-org/element-web-rageshakes#15637. + * Automatically focus the WYSIWYG composer when you enter a room ([\#9412](https://github.com/matrix-org/matrix-react-sdk/pull/9412)). + * Improve the tooltips on the call lobby join button ([\#9428](https://github.com/matrix-org/matrix-react-sdk/pull/9428)). + * Pass the homeserver's base URL to Element Call ([\#9429](https://github.com/matrix-org/matrix-react-sdk/pull/9429)). Fixes vector-im/element-web#23301. + * Better accommodate long room names in call toasts ([\#9426](https://github.com/matrix-org/matrix-react-sdk/pull/9426)). + * Hide virtual widgets from the room info panel ([\#9424](https://github.com/matrix-org/matrix-react-sdk/pull/9424)). Fixes vector-im/element-web#23494. + * Inhibit clicking on sender avatar in threads list ([\#9417](https://github.com/matrix-org/matrix-react-sdk/pull/9417)). Fixes vector-im/element-web#23482. + * Correct the dir parameter of MSC3715 ([\#9391](https://github.com/matrix-org/matrix-react-sdk/pull/9391)). Contributed by @dhenneke. + * Use a more correct subset of users in `/remakeolm` developer command ([\#9402](https://github.com/matrix-org/matrix-react-sdk/pull/9402)). + * use correct default for notification silencing ([\#9388](https://github.com/matrix-org/matrix-react-sdk/pull/9388)). Fixes vector-im/element-web#23456. + * Device manager - eagerly create `m.local_notification_settings` events ([\#9353](https://github.com/matrix-org/matrix-react-sdk/pull/9353)). + * Close incoming Element call toast when viewing the call lobby ([\#9375](https://github.com/matrix-org/matrix-react-sdk/pull/9375)). + * Always allow enabling sending read receipts ([\#9367](https://github.com/matrix-org/matrix-react-sdk/pull/9367)). Fixes vector-im/element-web#23433. + * Fixes (vector-im/element-web/issues/22609) where the white theme is not applied when `white -> dark -> white` sequence is done. ([\#9320](https://github.com/matrix-org/matrix-react-sdk/pull/9320)). Contributed by @florianduros. + * Fix applying programmatically set height for "top" room layout ([\#9339](https://github.com/matrix-org/matrix-react-sdk/pull/9339)). Contributed by @Fox32. + Changes in [3.58.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.58.1) (2022-10-11) ===================================================================================================== diff --git a/cypress/e2e/composer/composer.spec.ts b/cypress/e2e/composer/composer.spec.ts index f3fc374cf0..6fe562e12a 100644 --- a/cypress/e2e/composer/composer.spec.ts +++ b/cypress/e2e/composer/composer.spec.ts @@ -64,6 +64,21 @@ describe("Composer", () => { cy.contains('.mx_EventTile_body strong', 'bold message'); }); + it("should allow user to input emoji via graphical picker", () => { + cy.getComposer(false).within(() => { + cy.get('[aria-label="Emoji"]').click(); + }); + + cy.get('[data-testid="mx_EmojiPicker"]').within(() => { + cy.contains(".mx_EmojiPicker_item", "😇").click(); + }); + + cy.get(".mx_ContextualMenu_background").click(); // Close emoji picker + cy.get('div[contenteditable=true]').type("{enter}"); // Send message + + cy.contains(".mx_EventTile_body", "😇"); + }); + describe("when Ctrl+Enter is required to send", () => { beforeEach(() => { cy.setSettingValue("MessageComposerInput.ctrlEnterToSend", null, SettingLevel.ACCOUNT, true); diff --git a/cypress/e2e/sliding-sync/sliding-sync.ts b/cypress/e2e/sliding-sync/sliding-sync.ts index ebc90443f3..e0e7c974a7 100644 --- a/cypress/e2e/sliding-sync/sliding-sync.ts +++ b/cypress/e2e/sliding-sync/sliding-sync.ts @@ -235,7 +235,7 @@ describe("Sliding Sync", () => { "Test Room", "Dummy", ]); - cy.contains(".mx_RoomTile", "Test Room").get(".mx_NotificationBadge").should("not.be.visible"); + cy.contains(".mx_RoomTile", "Test Room").get(".mx_NotificationBadge").should("not.exist"); }); it("should update user settings promptly", () => { diff --git a/cypress/plugins/sliding-sync/index.ts b/cypress/plugins/sliding-sync/index.ts index 61a62aad13..608ada8dbf 100644 --- a/cypress/plugins/sliding-sync/index.ts +++ b/cypress/plugins/sliding-sync/index.ts @@ -77,7 +77,7 @@ async function proxyStart(synapse: SynapseInstance): Promise { const port = await getFreePort(); console.log(new Date(), "starting proxy container..."); const containerId = await dockerRun({ - image: "ghcr.io/matrix-org/sliding-sync-proxy:v0.4.0", + image: "ghcr.io/matrix-org/sliding-sync-proxy:v0.6.0", containerName: "react-sdk-cypress-sliding-sync-proxy", params: [ "--rm", diff --git a/package.json b/package.json index 3973d22fb8..9d7d7baa65 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.58.1", + "version": "3.59.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 4417382b20..cc7c6a2e2a 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -4,7 +4,6 @@ @import "./_font-sizes.pcss"; @import "./_font-weights.pcss"; @import "./_spacing.pcss"; -@import "./compound/_Icon.pcss"; @import "./components/views/beacon/_BeaconListItem.pcss"; @import "./components/views/beacon/_BeaconStatus.pcss"; @import "./components/views/beacon/_BeaconStatusTooltip.pcss"; @@ -19,6 +18,7 @@ @import "./components/views/beacon/_StyledLiveBeaconIcon.pcss"; @import "./components/views/context_menus/_KebabContextMenu.pcss"; @import "./components/views/elements/_FilterDropdown.pcss"; +@import "./components/views/elements/_LearnMore.pcss"; @import "./components/views/location/_EnableLiveShare.pcss"; @import "./components/views/location/_LiveDurationDropdown.pcss"; @import "./components/views/location/_LocationShareMenu.pcss"; @@ -44,6 +44,7 @@ @import "./components/views/settings/shared/_SettingsSubsectionHeading.pcss"; @import "./components/views/spaces/_QuickThemeSwitcher.pcss"; @import "./components/views/typography/_Caption.pcss"; +@import "./compound/_Icon.pcss"; @import "./structures/_AutoHideScrollbar.pcss"; @import "./structures/_BackdropPanel.pcss"; @import "./structures/_CompatibilityPage.pcss"; @@ -299,10 +300,10 @@ @import "./views/rooms/_TopUnreadMessagesBar.pcss"; @import "./views/rooms/_VoiceRecordComposerTile.pcss"; @import "./views/rooms/_WhoIsTypingTile.pcss"; +@import "./views/rooms/wysiwyg_composer/_EditWysiwygComposer.pcss"; +@import "./views/rooms/wysiwyg_composer/_SendWysiwygComposer.pcss"; @import "./views/rooms/wysiwyg_composer/components/_Editor.pcss"; @import "./views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss"; -@import "./views/rooms/wysiwyg_composer/_SendWysiwygComposer.pcss"; -@import "./views/rooms/wysiwyg_composer/_EditWysiwygComposer.pcss"; @import "./views/settings/_AvatarSetting.pcss"; @import "./views/settings/_CrossSigningPanel.pcss"; @import "./views/settings/_CryptographyPanel.pcss"; @@ -373,6 +374,4 @@ @import "./voice-broadcast/atoms/_PlaybackControlButton.pcss"; @import "./voice-broadcast/atoms/_VoiceBroadcastControl.pcss"; @import "./voice-broadcast/atoms/_VoiceBroadcastHeader.pcss"; -@import "./voice-broadcast/molecules/_VoiceBroadcastPlaybackBody.pcss"; -@import "./voice-broadcast/molecules/_VoiceBroadcastRecordingBody.pcss"; -@import "./voice-broadcast/molecules/_VoiceBroadcastRecordingPip.pcss"; +@import "./voice-broadcast/molecules/_VoiceBroadcastBody.pcss"; diff --git a/res/css/voice-broadcast/molecules/_VoiceBroadcastPlaybackBody.pcss b/res/css/components/views/elements/_LearnMore.pcss similarity index 71% rename from res/css/voice-broadcast/molecules/_VoiceBroadcastPlaybackBody.pcss rename to res/css/components/views/elements/_LearnMore.pcss index 11921e1f95..97f3b4c527 100644 --- a/res/css/voice-broadcast/molecules/_VoiceBroadcastPlaybackBody.pcss +++ b/res/css/components/views/elements/_LearnMore.pcss @@ -14,14 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_VoiceBroadcastPlaybackBody { - background-color: $quinary-content; - border-radius: 8px; - display: inline-block; - padding: 12px; -} - -.mx_VoiceBroadcastPlaybackBody_controls { - display: flex; - justify-content: center; +.mx_LearnMore_button { + margin-left: $spacing-4; } diff --git a/res/css/views/dialogs/_SpotlightDialog.pcss b/res/css/views/dialogs/_SpotlightDialog.pcss index cf6917a475..8602ca9a43 100644 --- a/res/css/views/dialogs/_SpotlightDialog.pcss +++ b/res/css/views/dialogs/_SpotlightDialog.pcss @@ -247,7 +247,7 @@ limitations under the License. } &.mx_SpotlightDialog_result_multiline { - align-items: start; + align-items: flex-start; .mx_AccessibleButton { padding: $spacing-4 $spacing-20; diff --git a/res/css/views/elements/_UseCaseSelection.pcss b/res/css/views/elements/_UseCaseSelection.pcss index 3daf15772f..2b907e7b67 100644 --- a/res/css/views/elements/_UseCaseSelection.pcss +++ b/res/css/views/elements/_UseCaseSelection.pcss @@ -65,7 +65,7 @@ limitations under the License. .mx_UseCaseSelection_skip { display: flex; flex-direction: column; - align-self: start; + align-self: flex-start; } } diff --git a/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss b/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss index 499b2b457b..cd0ac38e0e 100644 --- a/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss +++ b/res/css/views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss @@ -16,7 +16,7 @@ limitations under the License. .mx_FormattingButtons { display: flex; - justify-content: start; + justify-content: flex-start; .mx_FormattingButtons_Button { --size: 28px; diff --git a/res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingPip.pcss b/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss similarity index 84% rename from res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingPip.pcss rename to res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss index 11534a4797..37606f993c 100644 --- a/res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingPip.pcss +++ b/res/css/voice-broadcast/molecules/_VoiceBroadcastBody.pcss @@ -14,22 +14,26 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_VoiceBroadcastRecordingPip { - background-color: $system; +.mx_VoiceBroadcastBody { + background-color: $quinary-content; border-radius: 8px; - box-shadow: 0 2px 8px 0 #0000004a; display: inline-block; padding: $spacing-12; } -.mx_VoiceBroadcastRecordingPip_divider { +.mx_VoiceBroadcastBody--pip { + background-color: $system; + box-shadow: 0 2px 8px 0 #0000004a; +} + +.mx_VoiceBroadcastBody_divider { background-color: $quinary-content; border: 0; height: 1px; margin: $spacing-12 0; } -.mx_VoiceBroadcastRecordingPip_controls { +.mx_VoiceBroadcastBody_controls { display: flex; justify-content: space-around; } diff --git a/src/Login.ts b/src/Login.ts index c36f5770b9..4dc96bc17d 100644 --- a/src/Login.ts +++ b/src/Login.ts @@ -24,13 +24,6 @@ import { ILoginParams, LoginFlow } from "matrix-js-sdk/src/@types/auth"; import { IMatrixClientCreds } from "./MatrixClientPeg"; import SecurityCustomisations from "./customisations/Security"; -export { - IdentityProviderBrand, - IIdentityProvider, - ISSOFlow, - LoginFlow, -} from "matrix-js-sdk/src/@types/auth"; - interface ILoginOptions { defaultDeviceDisplayName?: string; } diff --git a/src/Notifier.ts b/src/Notifier.ts index cc84acb2fa..b75b821ae8 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -435,7 +435,16 @@ export const Notifier = { if (actions?.notify) { this._performCustomEventHandling(ev); - if (SdkContextClass.instance.roomViewStore.getRoomId() === room.roomId && + const store = SdkContextClass.instance.roomViewStore; + const isViewingRoom = store.getRoomId() === room.roomId; + const threadId: string | undefined = ev.getId() !== ev.threadRootId + ? ev.threadRootId + : undefined; + const isViewingThread = store.getThreadId() === threadId; + + const isViewingEventTimeline = isViewingRoom && (!threadId || isViewingThread); + + if (isViewingEventTimeline && UserActivity.sharedInstance().userActiveRecently() && !Modal.hasDialogs() ) { diff --git a/src/SlidingSyncManager.ts b/src/SlidingSyncManager.ts index 0e5736465e..c41e6a78e3 100644 --- a/src/SlidingSyncManager.ts +++ b/src/SlidingSyncManager.ts @@ -63,6 +63,15 @@ const DEFAULT_ROOM_SUBSCRIPTION_INFO = { required_state: [ ["*", "*"], // all events ], + include_old_rooms: { + timeline_limit: 0, + required_state: [ // state needed to handle space navigation and tombstone chains + [EventType.RoomCreate, ""], + [EventType.RoomTombstone, ""], + [EventType.SpaceChild, "*"], + [EventType.SpaceParent, "*"], + ], + }, }; export type PartialSlidingSyncRequest = { @@ -121,6 +130,16 @@ export class SlidingSyncManager { [EventType.SpaceParent, "*"], // all space parents [EventType.RoomMember, this.client.getUserId()!], // lets the client calculate that we are in fact in the room ], + include_old_rooms: { + timeline_limit: 0, + required_state: [ + [EventType.RoomCreate, ""], + [EventType.RoomTombstone, ""], // lets JS SDK hide rooms which are dead + [EventType.SpaceChild, "*"], // all space children + [EventType.SpaceParent, "*"], // all space parents + [EventType.RoomMember, this.client.getUserId()!], // lets the client calculate that we are in fact in the room + ], + }, filters: { room_types: ["m.space"], }, @@ -176,7 +195,7 @@ export class SlidingSyncManager { list = { ranges: [[0, 20]], sort: [ - "by_highlight_count", "by_notification_count", "by_recency", + "by_notification_level", "by_recency", ], timeline_limit: 1, // most recent message display: though this seems to only be needed for favourites? required_state: [ @@ -187,6 +206,16 @@ export class SlidingSyncManager { [EventType.RoomCreate, ""], // for isSpaceRoom checks [EventType.RoomMember, this.client.getUserId()], // lets the client calculate that we are in fact in the room ], + include_old_rooms: { + timeline_limit: 0, + required_state: [ + [EventType.RoomCreate, ""], + [EventType.RoomTombstone, ""], // lets JS SDK hide rooms which are dead + [EventType.SpaceChild, "*"], // all space children + [EventType.SpaceParent, "*"], // all space parents + [EventType.RoomMember, this.client.getUserId()!], // lets the client calculate that we are in fact in the room + ], + }, }; list = Object.assign(list, updateArgs); } else { diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 223eb0a6db..56c396750f 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -835,6 +835,13 @@ export default class MessagePanel extends React.Component { : room; const receipts: IReadReceiptProps[] = []; + + if (!receiptDestination) { + logger.debug("Discarding request, could not find the receiptDestination for event: " + + this.context.threadId); + return receipts; + } + receiptDestination.getReceiptsForEvent(event).forEach((r) => { if ( !r.userId || diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 8350b5e734..cb72e22d9b 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -55,6 +55,7 @@ import Spinner from "../views/elements/Spinner"; import { ComposerInsertPayload, ComposerType } from "../../dispatcher/payloads/ComposerInsertPayload"; import Heading from '../views/typography/Heading'; import { SdkContextClass } from '../../contexts/SDKContext'; +import { ThreadPayload } from '../../dispatcher/payloads/ThreadPayload'; interface IProps { room: Room; @@ -132,6 +133,11 @@ export default class ThreadView extends React.Component { metricsTrigger: undefined, // room doesn't change }); } + + dis.dispatch({ + action: Action.ViewThread, + thread_id: null, + }); } public componentDidUpdate(prevProps) { @@ -225,6 +231,10 @@ export default class ThreadView extends React.Component { }; private async postThreadUpdate(thread: Thread): Promise { + dis.dispatch({ + action: Action.ViewThread, + thread_id: thread.id, + }); thread.emit(ThreadEvent.ViewThread); await thread.fetchInitialEvents(); this.updateThreadRelation(); diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index c9fc7e001d..7c1564c9d9 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -18,9 +18,10 @@ import React, { ReactNode } from 'react'; import { ConnectionError, MatrixError } from "matrix-js-sdk/src/http-api"; import classNames from "classnames"; import { logger } from "matrix-js-sdk/src/logger"; +import { ISSOFlow, LoginFlow } from "matrix-js-sdk/src/@types/auth"; import { _t, _td } from '../../../languageHandler'; -import Login, { ISSOFlow, LoginFlow } from '../../../Login'; +import Login from '../../../Login'; import SdkConfig from '../../../SdkConfig'; import { messageForResourceLimitError } from '../../../utils/ErrorUtils'; import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils"; diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index b5770110f6..c155b5acc2 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -19,6 +19,7 @@ import React, { Fragment, ReactNode } from 'react'; import { MatrixClient } from "matrix-js-sdk/src/client"; import classNames from "classnames"; import { logger } from "matrix-js-sdk/src/logger"; +import { ISSOFlow } from "matrix-js-sdk/src/@types/auth"; import { _t, _td } from '../../../languageHandler'; import { messageForResourceLimitError } from '../../../utils/ErrorUtils'; @@ -26,7 +27,7 @@ import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils"; import * as Lifecycle from '../../../Lifecycle'; import { IMatrixClientCreds, MatrixClientPeg } from "../../../MatrixClientPeg"; import AuthPage from "../../views/auth/AuthPage"; -import Login, { ISSOFlow } from "../../../Login"; +import Login from "../../../Login"; import dis from "../../../dispatcher/dispatcher"; import SSOButtons from "../../views/elements/SSOButtons"; import ServerPicker from '../../views/elements/ServerPicker'; diff --git a/src/components/structures/auth/SoftLogout.tsx b/src/components/structures/auth/SoftLogout.tsx index 64dcdce645..7b946070ca 100644 --- a/src/components/structures/auth/SoftLogout.tsx +++ b/src/components/structures/auth/SoftLogout.tsx @@ -17,13 +17,14 @@ limitations under the License. import React from 'react'; import { logger } from "matrix-js-sdk/src/logger"; import { Optional } from "matrix-events-sdk"; +import { ISSOFlow, LoginFlow } from "matrix-js-sdk/src/@types/auth"; import { _t } from '../../../languageHandler'; import dis from '../../../dispatcher/dispatcher'; import * as Lifecycle from '../../../Lifecycle'; import Modal from '../../../Modal'; import { MatrixClientPeg } from "../../../MatrixClientPeg"; -import { ISSOFlow, LoginFlow, sendLoginRequest } from "../../../Login"; +import { sendLoginRequest } from "../../../Login"; import AuthPage from "../../views/auth/AuthPage"; import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY } from "../../../BasePlatform"; import SSOButtons from "../../views/elements/SSOButtons"; diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx index 7036575cd1..6f11fa12bd 100644 --- a/src/components/views/elements/AccessibleButton.tsx +++ b/src/components/views/elements/AccessibleButton.tsx @@ -75,7 +75,7 @@ type IProps = DynamicHtmlElementProps onClick: ((e: ButtonEvent) => void | Promise) | null; }; -interface IAccessibleButtonProps extends React.InputHTMLAttributes { +export interface IAccessibleButtonProps extends React.InputHTMLAttributes { ref?: React.Ref; } diff --git a/src/components/views/elements/LearnMore.tsx b/src/components/views/elements/LearnMore.tsx new file mode 100644 index 0000000000..1a96e3d8f4 --- /dev/null +++ b/src/components/views/elements/LearnMore.tsx @@ -0,0 +1,56 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; + +import { _t } from '../../../languageHandler'; +import Modal from '../../../Modal'; +import InfoDialog from '../dialogs/InfoDialog'; +import AccessibleButton, { IAccessibleButtonProps } from './AccessibleButton'; + +interface Props extends IAccessibleButtonProps { + title: string; + description: string | React.ReactNode; +} + +const LearnMore: React.FC = ({ + title, + description, + ...rest +}) => { + const onClick = () => { + Modal.createDialog( + InfoDialog, + { + title, + description, + button: _t('Got it'), + hasCloseButton: true, + }, + ); + }; + + return + { _t('Learn more') } + ; +}; + +export default LearnMore; diff --git a/src/components/views/elements/SSOButtons.tsx b/src/components/views/elements/SSOButtons.tsx index 666e55eab4..4332c914e7 100644 --- a/src/components/views/elements/SSOButtons.tsx +++ b/src/components/views/elements/SSOButtons.tsx @@ -19,11 +19,11 @@ import { chunk } from "lodash"; import classNames from "classnames"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { Signup } from "@matrix-org/analytics-events/types/typescript/Signup"; +import { IdentityProviderBrand, IIdentityProvider, ISSOFlow } from "matrix-js-sdk/src/@types/auth"; import PlatformPeg from "../../../PlatformPeg"; import AccessibleButton from "./AccessibleButton"; import { _t } from "../../../languageHandler"; -import { IdentityProviderBrand, IIdentityProvider, ISSOFlow } from "../../../Login"; import AccessibleTooltipButton from "./AccessibleTooltipButton"; import { mediaFromMxc } from "../../../customisations/Media"; import { PosthogAnalytics } from "../../../PosthogAnalytics"; diff --git a/src/components/views/rooms/NotificationBadge.tsx b/src/components/views/rooms/NotificationBadge.tsx index 3555582298..dccf8b1190 100644 --- a/src/components/views/rooms/NotificationBadge.tsx +++ b/src/components/views/rooms/NotificationBadge.tsx @@ -111,7 +111,12 @@ export default class NotificationBadge extends React.PureComponent { const slidingList = SlidingSyncManager.instance.slidingSync.getList(slidingSyncIndex); isAlphabetical = slidingList.sort[0] === "by_name"; isUnreadFirst = ( - slidingList.sort[0] === "by_highlight_count" || - slidingList.sort[0] === "by_notification_count" + slidingList.sort[0] === "by_notification_level" ); } diff --git a/src/components/views/settings/devices/FilteredDeviceList.tsx b/src/components/views/settings/devices/FilteredDeviceList.tsx index 9bc216a086..a2afcc22f6 100644 --- a/src/components/views/settings/devices/FilteredDeviceList.tsx +++ b/src/components/views/settings/devices/FilteredDeviceList.tsx @@ -38,6 +38,7 @@ import { import { DevicesState } from './useOwnDevices'; import FilteredDeviceListHeader from './FilteredDeviceListHeader'; import Spinner from '../../elements/Spinner'; +import LearnMore from '../../elements/LearnMore'; interface Props { devices: DevicesDictionary; @@ -73,48 +74,88 @@ const getFilteredSortedDevices = (devices: DevicesDictionary, filter?: DeviceSec const ALL_FILTER_ID = 'ALL'; type DeviceFilterKey = DeviceSecurityVariation | typeof ALL_FILTER_ID; +const securityCardContent: Record = { + [DeviceSecurityVariation.Verified]: { + title: _t('Verified sessions'), + description: _t('For best security, sign out from any session that you don\'t recognize or use anymore.'), + learnMoreDescription: <> +

{ _t('Verified sessions have logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.') } +

+

+ { _t( + `This means they hold encryption keys for your previous messages, ` + + `and confirm to other users you are communicating with that these sessions are really you.`, + ) + } +

+ , + }, + [DeviceSecurityVariation.Unverified]: { + title: _t('Unverified sessions'), + description: _t( + `Verify your sessions for enhanced secure messaging or ` + + `sign out from those you don't recognize or use anymore.`, + ), + learnMoreDescription: <> +

{ _t('Unverified sessions are sessions that have logged in with your credentials but have not been cross-verified.') } +

+

+ { _t( + `You should make especially certain that you recognise these sessions ` + + `as they could represent an unauthorised use of your account.`, + ) + } +

+ , + }, + [DeviceSecurityVariation.Inactive]: { + title: _t('Inactive sessions'), + description: _t( + `Consider signing out from old sessions ` + + `(%(inactiveAgeDays)s days or older) you don't use anymore.`, + { inactiveAgeDays: INACTIVE_DEVICE_AGE_DAYS }, + ), + learnMoreDescription: <> +

{ _t('Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.') } +

+

+ { _t( + `Removing inactive sessions improves security and performance, ` + + `and makes it easier for you to identify if a new session is suspicious.`, + ) + } +

+ , + }, + }; + +const isSecurityVariation = (filter?: DeviceFilterKey): filter is DeviceSecurityVariation => + Object.values(DeviceSecurityVariation).includes(filter); + const FilterSecurityCard: React.FC<{ filter?: DeviceFilterKey }> = ({ filter }) => { - switch (filter) { - case DeviceSecurityVariation.Verified: - return
- -
- ; - case DeviceSecurityVariation.Unverified: - return
- -
- ; - case DeviceSecurityVariation.Inactive: - return
- -
- ; - default: - return null; + if (isSecurityVariation(filter)) { + const { title, description, learnMoreDescription } = securityCardContent[filter]; + return
+ + { description } + + } + /> +
+ ; } + + return null; }; const getNoResultsMessage = (filter?: DeviceSecurityVariation): string => { diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 7d2d935f70..5a19e225de 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -116,6 +116,11 @@ export enum Action { */ ViewRoom = "view_room", + /** + * Changes thread based on payload parameters. Should be used with ThreadPayload. + */ + ViewThread = "view_thread", + /** * Changes room based on room list order and payload parameters. Should be used with ViewRoomDeltaPayload. */ diff --git a/res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingBody.pcss b/src/dispatcher/payloads/ThreadPayload.ts similarity index 66% rename from res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingBody.pcss rename to src/dispatcher/payloads/ThreadPayload.ts index 13e3104c9a..653bbba3ae 100644 --- a/res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingBody.pcss +++ b/src/dispatcher/payloads/ThreadPayload.ts @@ -14,16 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_VoiceBroadcastRecordingBody { - align-items: flex-start; - background-color: $quinary-content; - border-radius: 8px; - display: inline-flex; - gap: $spacing-8; - padding: 12px; -} +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; -.mx_VoiceBroadcastRecordingBody_title { - font-size: $font-12px; - font-weight: $font-semi-bold; +/* eslint-disable camelcase */ +export interface ThreadPayload extends Pick { + action: Action.ViewThread; + + thread_id: string | null; } +/* eslint-enable camelcase */ diff --git a/src/hooks/useSlidingSyncRoomSearch.ts b/src/hooks/useSlidingSyncRoomSearch.ts index 6ba08dc1a7..97e43d8b28 100644 --- a/src/hooks/useSlidingSyncRoomSearch.ts +++ b/src/hooks/useSlidingSyncRoomSearch.ts @@ -52,7 +52,6 @@ export const useSlidingSyncRoomSearch = () => { ranges: [[0, limit]], filters: { room_name_like: term, - is_tombstoned: false, }, }); const rooms = []; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 54a8445520..b892c67bf9 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1778,10 +1778,16 @@ "Verify session": "Verify session", "Verified sessions": "Verified sessions", "For best security, sign out from any session that you don't recognize or use anymore.": "For best security, sign out from any session that you don't recognize or use anymore.", + "Verified sessions have logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.": "Verified sessions have logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.", + "This means they hold encryption keys for your previous messages, and confirm to other users you are communicating with that these sessions are really you.": "This means they hold encryption keys for your previous messages, and confirm to other users you are communicating with that these sessions are really you.", "Unverified sessions": "Unverified sessions", "Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore.": "Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore.", + "Unverified sessions are sessions that have logged in with your credentials but have not been cross-verified.": "Unverified sessions are sessions that have logged in with your credentials but have not been cross-verified.", + "You should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account.": "You should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account.", "Inactive sessions": "Inactive sessions", - "Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore": "Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore", + "Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore.": "Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore.", + "Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.": "Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.", + "Removing inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.": "Removing inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.", "No verified sessions found.": "No verified sessions found.", "No unverified sessions found.": "No unverified sessions found.", "No inactive sessions found.": "No inactive sessions found.", @@ -1801,6 +1807,7 @@ "Security recommendations": "Security recommendations", "Improve your account security by following these recommendations": "Improve your account security by following these recommendations", "View all": "View all", + "Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore": "Consider signing out from old sessions (%(inactiveAgeDays)s days or older) you don't use anymore", "Failed to set pusher state": "Failed to set pusher state", "Unable to remove contact information": "Unable to remove contact information", "Remove %(email)s?": "Remove %(email)s?", diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index b3814f7a32..13c1e09c76 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -50,6 +50,7 @@ import { awaitRoomDownSync } from "../utils/RoomUpgrade"; import { UPDATE_EVENT } from "./AsyncStore"; import { SdkContextClass } from "../contexts/SDKContext"; import { CallStore } from "./CallStore"; +import { ThreadPayload } from "../dispatcher/payloads/ThreadPayload"; const NUM_JOIN_RETRY = 5; @@ -66,6 +67,10 @@ interface State { * The ID of the room currently being viewed */ roomId: string | null; + /** + * The ID of the thread currently being viewed + */ + threadId: string | null; /** * The ID of the room being subscribed to (in Sliding Sync) */ @@ -109,6 +114,7 @@ const INITIAL_STATE: State = { joining: false, joinError: null, roomId: null, + threadId: null, subscribingRoomId: null, initialEventId: null, initialEventPixelOffset: null, @@ -200,6 +206,9 @@ export class RoomViewStore extends EventEmitter { case Action.ViewRoom: this.viewRoom(payload); break; + case Action.ViewThread: + this.viewThread(payload); + break; // for these events blank out the roomId as we are no longer in the RoomView case 'view_welcome_page': case Action.ViewHomePage: @@ -430,6 +439,12 @@ export class RoomViewStore extends EventEmitter { } } + private viewThread(payload: ThreadPayload): void { + this.setState({ + threadId: payload.thread_id, + }); + } + private viewRoomError(payload: ViewRoomErrorPayload): void { this.setState({ roomId: payload.room_id, @@ -550,6 +565,10 @@ export class RoomViewStore extends EventEmitter { return this.state.roomId; } + public getThreadId(): Optional { + return this.state.threadId; + } + // The event to scroll to when the room is first viewed public getInitialEventId(): Optional { return this.state.initialEventId; diff --git a/src/stores/notifications/RoomNotificationState.ts b/src/stores/notifications/RoomNotificationState.ts index 49e76bedf8..92dc4e6a53 100644 --- a/src/stores/notifications/RoomNotificationState.ts +++ b/src/stores/notifications/RoomNotificationState.ts @@ -37,6 +37,9 @@ export class RoomNotificationState extends NotificationState implements IDestroy this.room.on(RoomEvent.Receipt, this.handleReadReceipt); this.room.on(RoomEvent.MyMembership, this.handleMembershipUpdate); this.room.on(RoomEvent.LocalEchoUpdated, this.handleLocalEchoUpdated); + this.room.on(RoomEvent.Timeline, this.handleRoomEventUpdate); + this.room.on(RoomEvent.Redaction, this.handleRoomEventUpdate); + this.room.on(RoomEvent.UnreadNotifications, this.handleNotificationCountUpdate); // for server-sent counts if (cli.canSupport.get(Feature.ThreadUnreadNotifications) === ServerSupport.Unsupported) { this.threadsState?.on(NotificationStateEvents.Update, this.handleThreadsUpdate); @@ -56,6 +59,8 @@ export class RoomNotificationState extends NotificationState implements IDestroy this.room.removeListener(RoomEvent.Receipt, this.handleReadReceipt); this.room.removeListener(RoomEvent.MyMembership, this.handleMembershipUpdate); this.room.removeListener(RoomEvent.LocalEchoUpdated, this.handleLocalEchoUpdated); + this.room.removeListener(RoomEvent.Timeline, this.handleRoomEventUpdate); + this.room.removeListener(RoomEvent.Redaction, this.handleRoomEventUpdate); if (cli.canSupport.get(Feature.ThreadUnreadNotifications) === ServerSupport.Unsupported) { this.room.removeListener(RoomEvent.UnreadNotifications, this.handleNotificationCountUpdate); } else if (this.threadsState) { @@ -93,6 +98,11 @@ export class RoomNotificationState extends NotificationState implements IDestroy this.updateNotificationState(); }; + private handleRoomEventUpdate = (event: MatrixEvent, room: Room | null) => { + if (room?.roomId !== this.room.roomId) return; // ignore - not for us or notifications timeline + this.updateNotificationState(); + }; + private handleAccountDataUpdate = (ev: MatrixEvent) => { if (ev.getType() === "m.push_rules") { this.updateNotificationState(); diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts index d6f9de79c3..73d6bdbd51 100644 --- a/src/stores/room-list/RoomListStore.ts +++ b/src/stores/room-list/RoomListStore.ts @@ -24,7 +24,7 @@ import SettingsStore from "../../settings/SettingsStore"; import { DefaultTagID, OrderedDefaultTagIDs, RoomUpdateCause, TagID } from "./models"; import { IListOrderingMap, ITagMap, ITagSortingMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models"; import { ActionPayload } from "../../dispatcher/payloads"; -import defaultDispatcher from "../../dispatcher/dispatcher"; +import defaultDispatcher, { MatrixDispatcher } from "../../dispatcher/dispatcher"; import { readReceiptChangeIsFor } from "../../utils/read-receipts"; import { FILTER_CHANGED, IFilterCondition } from "./filters/IFilterCondition"; import { Algorithm, LIST_UPDATED_EVENT } from "./algorithms/Algorithm"; @@ -65,8 +65,8 @@ export class RoomListStoreClass extends AsyncStoreWithClient implements this.emit(LISTS_UPDATE_EVENT); }); - constructor() { - super(defaultDispatcher); + constructor(dis: MatrixDispatcher) { + super(dis); this.setMaxListeners(20); // RoomList + LeftPanel + 8xRoomSubList + spares this.algorithm.start(); } @@ -613,11 +613,11 @@ export default class RoomListStore { if (!RoomListStore.internalInstance) { if (SettingsStore.getValue("feature_sliding_sync")) { logger.info("using SlidingRoomListStoreClass"); - const instance = new SlidingRoomListStoreClass(); + const instance = new SlidingRoomListStoreClass(defaultDispatcher, SdkContextClass.instance); instance.start(); RoomListStore.internalInstance = instance; } else { - const instance = new RoomListStoreClass(); + const instance = new RoomListStoreClass(defaultDispatcher); instance.start(); RoomListStore.internalInstance = instance; } diff --git a/src/stores/room-list/SlidingRoomListStore.ts b/src/stores/room-list/SlidingRoomListStore.ts index 35550d04f1..1c5fd1adea 100644 --- a/src/stores/room-list/SlidingRoomListStore.ts +++ b/src/stores/room-list/SlidingRoomListStore.ts @@ -21,12 +21,10 @@ import { MSC3575Filter, SlidingSyncEvent } from "matrix-js-sdk/src/sliding-sync" import { RoomUpdateCause, TagID, OrderedDefaultTagIDs, DefaultTagID } from "./models"; import { ITagMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models"; import { ActionPayload } from "../../dispatcher/payloads"; -import defaultDispatcher from "../../dispatcher/dispatcher"; +import { MatrixDispatcher } from "../../dispatcher/dispatcher"; import { IFilterCondition } from "./filters/IFilterCondition"; import { AsyncStoreWithClient } from "../AsyncStoreWithClient"; import { RoomListStore as Interface, RoomListStoreEvent } from "./Interface"; -import { SlidingSyncManager } from "../../SlidingSyncManager"; -import SpaceStore from "../spaces/SpaceStore"; import { MetaSpace, SpaceKey, UPDATE_SELECTED_SPACE } from "../spaces"; import { LISTS_LOADING_EVENT } from "./RoomListStore"; import { UPDATE_EVENT } from "../AsyncStore"; @@ -38,7 +36,7 @@ interface IState { export const SlidingSyncSortToFilter: Record = { [SortAlgorithm.Alphabetic]: ["by_name", "by_recency"], - [SortAlgorithm.Recent]: ["by_highlight_count", "by_notification_count", "by_recency"], + [SortAlgorithm.Recent]: ["by_notification_level", "by_recency"], [SortAlgorithm.Manual]: ["by_recency"], }; @@ -48,21 +46,18 @@ const filterConditions: Record = { }, [DefaultTagID.Favourite]: { tags: ["m.favourite"], - is_tombstoned: false, }, // TODO https://github.com/vector-im/element-web/issues/23207 // DefaultTagID.SavedItems, [DefaultTagID.DM]: { is_dm: true, is_invite: false, - is_tombstoned: false, // If a DM has a Favourite & Low Prio tag then it'll be shown in those lists instead not_tags: ["m.favourite", "m.lowpriority"], }, [DefaultTagID.Untagged]: { is_dm: false, is_invite: false, - is_tombstoned: false, not_room_types: ["m.space"], not_tags: ["m.favourite", "m.lowpriority"], // spaces filter added dynamically @@ -71,7 +66,6 @@ const filterConditions: Record = { tags: ["m.lowpriority"], // If a room has both Favourite & Low Prio tags then it'll be shown under Favourites not_tags: ["m.favourite"], - is_tombstoned: false, }, // TODO https://github.com/vector-im/element-web/issues/23207 // DefaultTagID.ServerNotice, @@ -87,25 +81,25 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient impl private counts: Record = {}; private stickyRoomId: string | null; - public constructor() { - super(defaultDispatcher); + public constructor(dis: MatrixDispatcher, private readonly context: SdkContextClass) { + super(dis); this.setMaxListeners(20); // RoomList + LeftPanel + 8xRoomSubList + spares } public async setTagSorting(tagId: TagID, sort: SortAlgorithm) { logger.info("SlidingRoomListStore.setTagSorting ", tagId, sort); this.tagIdToSortAlgo[tagId] = sort; - const slidingSyncIndex = SlidingSyncManager.instance.getOrAllocateListIndex(tagId); + const slidingSyncIndex = this.context.slidingSyncManager.getOrAllocateListIndex(tagId); switch (sort) { case SortAlgorithm.Alphabetic: - await SlidingSyncManager.instance.ensureListRegistered( + await this.context.slidingSyncManager.ensureListRegistered( slidingSyncIndex, { sort: SlidingSyncSortToFilter[SortAlgorithm.Alphabetic], }, ); break; case SortAlgorithm.Recent: - await SlidingSyncManager.instance.ensureListRegistered( + await this.context.slidingSyncManager.ensureListRegistered( slidingSyncIndex, { sort: SlidingSyncSortToFilter[SortAlgorithm.Recent], }, @@ -174,10 +168,13 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient impl // check all lists for each tag we know about and see if the room is there const tags: TagID[] = []; for (const tagId in this.tagIdToSortAlgo) { - const index = SlidingSyncManager.instance.getOrAllocateListIndex(tagId); - const { roomIndexToRoomId } = SlidingSyncManager.instance.slidingSync.getListData(index); - for (const roomIndex in roomIndexToRoomId) { - const roomId = roomIndexToRoomId[roomIndex]; + const index = this.context.slidingSyncManager.getOrAllocateListIndex(tagId); + const listData = this.context.slidingSyncManager.slidingSync.getListData(index); + if (!listData) { + continue; + } + for (const roomIndex in listData.roomIndexToRoomId) { + const roomId = listData.roomIndexToRoomId[roomIndex]; if (roomId === room.roomId) { tags.push(tagId); break; @@ -207,7 +204,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient impl // this room will not move due to it being viewed: it is sticky. This can be null to indicate // no sticky room if you aren't viewing a room. - this.stickyRoomId = SdkContextClass.instance.roomViewStore.getRoomId(); + this.stickyRoomId = this.context.roomViewStore.getRoomId(); let stickyRoomNewIndex = -1; const stickyRoomOldIndex = (tagMap[tagId] || []).findIndex((room) => { return room.roomId === this.stickyRoomId; @@ -264,7 +261,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient impl } private onSlidingSyncListUpdate(listIndex: number, joinCount: number, roomIndexToRoomId: Record) { - const tagId = SlidingSyncManager.instance.listIdForIndex(listIndex); + const tagId = this.context.slidingSyncManager.listIdForIndex(listIndex); this.counts[tagId]= joinCount; this.refreshOrderedLists(tagId, roomIndexToRoomId); // let the UI update @@ -273,7 +270,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient impl private onRoomViewStoreUpdated() { // we only care about this to know when the user has clicked on a room to set the stickiness value - if (SdkContextClass.instance.roomViewStore.getRoomId() === this.stickyRoomId) { + if (this.context.roomViewStore.getRoomId() === this.stickyRoomId) { return; } @@ -296,14 +293,17 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient impl if (room) { // resort it based on the slidingSync view of the list. This may cause this old sticky // room to cease to exist. - const index = SlidingSyncManager.instance.getOrAllocateListIndex(tagId); - const { roomIndexToRoomId } = SlidingSyncManager.instance.slidingSync.getListData(index); - this.refreshOrderedLists(tagId, roomIndexToRoomId); + const index = this.context.slidingSyncManager.getOrAllocateListIndex(tagId); + const listData = this.context.slidingSyncManager.slidingSync.getListData(index); + if (!listData) { + continue; + } + this.refreshOrderedLists(tagId, listData.roomIndexToRoomId); hasUpdatedAnyList = true; } } // in the event we didn't call refreshOrderedLists, it helps to still remember the sticky room ID. - this.stickyRoomId = SdkContextClass.instance.roomViewStore.getRoomId(); + this.stickyRoomId = this.context.roomViewStore.getRoomId(); if (hasUpdatedAnyList) { this.emit(LISTS_UPDATE_EVENT); @@ -313,11 +313,11 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient impl protected async onReady(): Promise { logger.info("SlidingRoomListStore.onReady"); // permanent listeners: never get destroyed. Could be an issue if we want to test this in isolation. - SlidingSyncManager.instance.slidingSync.on(SlidingSyncEvent.List, this.onSlidingSyncListUpdate.bind(this)); - SdkContextClass.instance.roomViewStore.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdated.bind(this)); - SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated.bind(this)); - if (SpaceStore.instance.activeSpace) { - this.onSelectedSpaceUpdated(SpaceStore.instance.activeSpace, false); + this.context.slidingSyncManager.slidingSync.on(SlidingSyncEvent.List, this.onSlidingSyncListUpdate.bind(this)); + this.context.roomViewStore.addListener(UPDATE_EVENT, this.onRoomViewStoreUpdated.bind(this)); + this.context.spaceStore.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated.bind(this)); + if (this.context.spaceStore.activeSpace) { + this.onSelectedSpaceUpdated(this.context.spaceStore.activeSpace, false); } // sliding sync has an initial response for spaces. Now request all the lists. @@ -332,8 +332,8 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient impl const sort = SortAlgorithm.Recent; // default to recency sort, TODO: read from config this.tagIdToSortAlgo[tagId] = sort; this.emit(LISTS_LOADING_EVENT, tagId, true); - const index = SlidingSyncManager.instance.getOrAllocateListIndex(tagId); - SlidingSyncManager.instance.ensureListRegistered(index, { + const index = this.context.slidingSyncManager.getOrAllocateListIndex(tagId); + this.context.slidingSyncManager.ensureListRegistered(index, { filters: filter, sort: SlidingSyncSortToFilter[sort], }).then(() => { @@ -350,9 +350,18 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient impl const oldSpace = filters.spaces?.[0]; filters.spaces = (activeSpace && activeSpace != MetaSpace.Home) ? [activeSpace] : undefined; if (oldSpace !== activeSpace) { + // include subspaces in this list + this.context.spaceStore.traverseSpace(activeSpace, (roomId: string) => { + if (roomId === activeSpace) { + return; + } + filters.spaces.push(roomId); // add subspace + }, false); + this.emit(LISTS_LOADING_EVENT, tagId, true); - SlidingSyncManager.instance.ensureListRegistered( - SlidingSyncManager.instance.getOrAllocateListIndex(tagId), + const index = this.context.slidingSyncManager.getOrAllocateListIndex(tagId); + this.context.slidingSyncManager.ensureListRegistered( + index, { filters: filters, }, diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index aa1ad2c393..59653bf384 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -18,6 +18,7 @@ import { Room } from "matrix-js-sdk/src/models/room"; import { ClientWidgetApi, IModalWidgetOpenRequest, + IRoomEvent, IStickerActionRequest, IStickyActionRequest, ITemplateParams, @@ -465,7 +466,7 @@ export class StopGapWidget extends EventEmitter { private onToDeviceEvent = async (ev: MatrixEvent) => { await this.client.decryptEventIfNeeded(ev); if (ev.isDecryptionFailure()) return; - await this.messaging.feedToDevice(ev.getEffectiveEvent(), ev.isEncrypted()); + await this.messaging.feedToDevice(ev.getEffectiveEvent() as IRoomEvent, ev.isEncrypted()); }; private feedEvent(ev: MatrixEvent) { @@ -509,7 +510,7 @@ export class StopGapWidget extends EventEmitter { this.readUpToMap[ev.getRoomId()] = ev.getId(); const raw = ev.getEffectiveEvent(); - this.messaging.feedEvent(raw, this.eventListenerRoomId).catch(e => { + this.messaging.feedEvent(raw as IRoomEvent, this.eventListenerRoomId).catch(e => { logger.error("Error sending event to widget: ", e); }); } diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts index e1fb1d6729..3a72193c3f 100644 --- a/src/stores/widgets/StopGapWidgetDriver.ts +++ b/src/stores/widgets/StopGapWidgetDriver.ts @@ -34,7 +34,7 @@ import { } from "matrix-widget-api"; import { ClientEvent, ITurnServer as IClientTurnServer } from "matrix-js-sdk/src/client"; import { EventType } from "matrix-js-sdk/src/@types/event"; -import { IContent, IEvent, MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { IContent, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { Room } from "matrix-js-sdk/src/models/room"; import { logger } from "matrix-js-sdk/src/logger"; import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread"; @@ -310,7 +310,7 @@ export class StopGapWidgetDriver extends WidgetDriver { limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary const rooms = this.pickRooms(roomIds); - const allResults: IEvent[] = []; + const allResults: IRoomEvent[] = []; for (const room of rooms) { const results: MatrixEvent[] = []; const events = room.getLiveTimeline().getEvents(); // timelines are most recent last @@ -323,7 +323,7 @@ export class StopGapWidgetDriver extends WidgetDriver { results.push(ev); } - results.forEach(e => allResults.push(e.getEffectiveEvent())); + results.forEach(e => allResults.push(e.getEffectiveEvent() as IRoomEvent)); } return allResults; } @@ -337,7 +337,7 @@ export class StopGapWidgetDriver extends WidgetDriver { limitPerRoom = limitPerRoom > 0 ? Math.min(limitPerRoom, Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER; // relatively arbitrary const rooms = this.pickRooms(roomIds); - const allResults: IEvent[] = []; + const allResults: IRoomEvent[] = []; for (const room of rooms) { const results: MatrixEvent[] = []; const state: Map = room.currentState.events.get(eventType); @@ -350,7 +350,7 @@ export class StopGapWidgetDriver extends WidgetDriver { } } - results.slice(0, limitPerRoom).forEach(e => allResults.push(e.getEffectiveEvent())); + results.slice(0, limitPerRoom).forEach(e => allResults.push(e.getEffectiveEvent() as IRoomEvent)); } return allResults; } @@ -459,7 +459,7 @@ export class StopGapWidgetDriver extends WidgetDriver { }); return { - chunk: events.map(e => e.getEffectiveEvent()), + chunk: events.map(e => e.getEffectiveEvent() as IRoomEvent), nextBatch, prevBatch, }; diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx index e0634636a7..e6f2e343cb 100644 --- a/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx +++ b/src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody.tsx @@ -74,14 +74,14 @@ export const VoiceBroadcastPlaybackBody: React.FC +
-
+
{ control }
diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody.tsx index b9721170eb..1b13377da9 100644 --- a/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody.tsx +++ b/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody.tsx @@ -27,7 +27,7 @@ export const VoiceBroadcastRecordingBody: React.FC +
; return
-
-
+
+
{ toggleControl } { if (this.state !== VoiceBroadcastInfoState.Paused) return; - this.setState(VoiceBroadcastInfoState.Running); + this.setState(VoiceBroadcastInfoState.Resumed); await this.getRecorder().start(); - await this.sendInfoStateEvent(VoiceBroadcastInfoState.Running); + await this.sendInfoStateEvent(VoiceBroadcastInfoState.Resumed); } public toggle = async (): Promise => { if (this.getState() === VoiceBroadcastInfoState.Paused) return this.resume(); - if ([VoiceBroadcastInfoState.Started, VoiceBroadcastInfoState.Running].includes(this.getState())) { + if ([VoiceBroadcastInfoState.Started, VoiceBroadcastInfoState.Resumed].includes(this.getState())) { return this.pause(); } }; diff --git a/test/Notifier-test.ts b/test/Notifier-test.ts index 224c1fec77..30a1691c2e 100644 --- a/test/Notifier-test.ts +++ b/test/Notifier-test.ts @@ -19,6 +19,7 @@ import { ClientEvent, MatrixClient } from "matrix-js-sdk/src/client"; import { Room } from "matrix-js-sdk/src/models/room"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { SyncState } from "matrix-js-sdk/src/sync"; +import { waitFor } from "@testing-library/react"; import BasePlatform from "../src/BasePlatform"; import { ElementCall } from "../src/models/Call"; @@ -29,8 +30,15 @@ import { createLocalNotificationSettingsIfNeeded, getLocalNotificationAccountDataEventType, } from "../src/utils/notifications"; -import { getMockClientWithEventEmitter, mkEvent, mkRoom, mockClientMethodsUser, mockPlatformPeg } from "./test-utils"; +import { getMockClientWithEventEmitter, mkEvent, mockClientMethodsUser, mockPlatformPeg } from "./test-utils"; import { IncomingCallToast } from "../src/toasts/IncomingCallToast"; +import { SdkContextClass } from "../src/contexts/SDKContext"; +import UserActivity from "../src/UserActivity"; +import Modal from "../src/Modal"; +import { mkThread } from "./test-utils/threads"; +import dis from "../src/dispatcher/dispatcher"; +import { ThreadPayload } from "../src/dispatcher/payloads/ThreadPayload"; +import { Action } from "../src/dispatcher/actions"; jest.mock("../src/utils/notifications", () => ({ // @ts-ignore @@ -50,10 +58,12 @@ describe("Notifier", () => { let MockPlatform: MockedObject; let mockClient: MockedObject; - let testRoom: MockedObject; + let testRoom: Room; let accountDataEventKey: string; let accountDataStore = {}; + let mockSettings: Record = {}; + const userId = "@bob:example.org"; beforeEach(() => { @@ -78,7 +88,7 @@ describe("Notifier", () => { }; accountDataEventKey = getLocalNotificationAccountDataEventType(mockClient.deviceId); - testRoom = mkRoom(mockClient, roomId); + testRoom = new Room(roomId, mockClient, mockClient.getUserId()); MockPlatform = mockPlatformPeg({ supportsNotifications: jest.fn().mockReturnValue(true), @@ -89,7 +99,9 @@ describe("Notifier", () => { Notifier.isBodyEnabled = jest.fn().mockReturnValue(true); - mockClient.getRoom.mockReturnValue(testRoom); + mockClient.getRoom.mockImplementation(id => { + return id === roomId ? testRoom : new Room(id, mockClient, mockClient.getUserId()); + }); }); describe('triggering notification from events', () => { @@ -121,13 +133,14 @@ describe("Notifier", () => { }, }); - const enabledSettings = [ - 'notificationsEnabled', - 'audioNotificationsEnabled', - ]; + mockSettings = { + 'notificationsEnabled': true, + 'audioNotificationsEnabled': true, + }; + // enable notifications by default - jest.spyOn(SettingsStore, "getValue").mockImplementation( - settingName => enabledSettings.includes(settingName), + jest.spyOn(SettingsStore, "getValue").mockReset().mockImplementation( + settingName => mockSettings[settingName] ?? false, ); }); @@ -253,16 +266,13 @@ describe("Notifier", () => { }); const callOnEvent = (type?: string) => { - const callEvent = { - getContent: () => { }, - getRoomId: () => roomId, - isBeingDecrypted: () => false, - isDecryptionFailure: () => false, - getSender: () => "@alice:foo", - getType: () => type ?? ElementCall.CALL_EVENT_TYPE.name, - getStateKey: () => "state_key", - } as unknown as MatrixEvent; - + const callEvent = mkEvent({ + type: type ?? ElementCall.CALL_EVENT_TYPE.name, + user: "@alice:foo", + room: roomId, + content: {}, + event: true, + }); Notifier.onEvent(callEvent); return callEvent; }; @@ -345,4 +355,72 @@ describe("Notifier", () => { expect(createLocalNotificationSettingsIfNeededMock).toHaveBeenCalled(); }); }); + + describe('_evaluateEvent', () => { + beforeEach(() => { + jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId") + .mockReturnValue(testRoom.roomId); + + jest.spyOn(UserActivity.sharedInstance(), "userActiveRecently") + .mockReturnValue(true); + + jest.spyOn(Modal, "hasDialogs").mockReturnValue(false); + + jest.spyOn(Notifier, "_displayPopupNotification").mockReset(); + jest.spyOn(Notifier, "isEnabled").mockReturnValue(true); + + mockClient.getPushActionsForEvent.mockReturnValue({ + notify: true, + tweaks: { + sound: true, + }, + }); + }); + + it("should show a pop-up", () => { + expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(0); + Notifier._evaluateEvent(testEvent); + expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(0); + + const eventFromOtherRoom = mkEvent({ + event: true, + type: "m.room.message", + user: "@user1:server", + room: "!otherroom:example.org", + content: {}, + }); + + Notifier._evaluateEvent(eventFromOtherRoom); + expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1); + }); + + it("should a pop-up for thread event", async () => { + const { events, rootEvent } = mkThread({ + room: testRoom, + client: mockClient, + authorId: "@bob:example.org", + participantUserIds: ["@bob:example.org"], + }); + + expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(0); + + Notifier._evaluateEvent(rootEvent); + expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(0); + + Notifier._evaluateEvent(events[1]); + expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1); + + dis.dispatch({ + action: Action.ViewThread, + thread_id: rootEvent.getId(), + }); + + await waitFor(() => + expect(SdkContextClass.instance.roomViewStore.getThreadId()).toBe(rootEvent.getId()), + ); + + Notifier._evaluateEvent(events[1]); + expect(Notifier._displayPopupNotification).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/test/components/structures/ThreadView-test.tsx b/test/components/structures/ThreadView-test.tsx index 2516aad082..2893958a8f 100644 --- a/test/components/structures/ThreadView-test.tsx +++ b/test/components/structures/ThreadView-test.tsx @@ -28,6 +28,7 @@ import { act } from "react-dom/test-utils"; import ThreadView from "../../../src/components/structures/ThreadView"; import MatrixClientContext from "../../../src/contexts/MatrixClientContext"; import RoomContext from "../../../src/contexts/RoomContext"; +import { SdkContextClass } from "../../../src/contexts/SDKContext"; import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; import DMRoomMap from "../../../src/utils/DMRoomMap"; import ResizeNotifier from "../../../src/utils/ResizeNotifier"; @@ -155,4 +156,13 @@ describe("ThreadView", () => { ROOM_ID, rootEvent2.getId(), expectedMessageBody(rootEvent2, "yolo"), ); }); + + it("sets the correct thread in the room view store", async () => { + // expect(SdkContextClass.instance.roomViewStore.getThreadId()).toBeNull(); + const { unmount } = await getComponent(); + expect(SdkContextClass.instance.roomViewStore.getThreadId()).toBe(rootEvent.getId()); + + unmount(); + await waitFor(() => expect(SdkContextClass.instance.roomViewStore.getThreadId()).toBeNull()); + }); }); diff --git a/test/components/views/elements/LearnMore-test.tsx b/test/components/views/elements/LearnMore-test.tsx new file mode 100644 index 0000000000..6ae577543c --- /dev/null +++ b/test/components/views/elements/LearnMore-test.tsx @@ -0,0 +1,57 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; + +import LearnMore from '../../../../src/components/views/elements/LearnMore'; +import Modal from '../../../../src/Modal'; +import InfoDialog from '../../../../src/components/views/dialogs/InfoDialog'; + +describe('', () => { + const defaultProps = { + title: 'Test', + description: 'test test test', + ['data-testid']: 'testid', + }; + const getComponent = (props = {}) => + (); + + const modalSpy = jest.spyOn(Modal, 'createDialog').mockReturnValue(undefined); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders button', () => { + const { container } = render(getComponent()); + expect(container).toMatchSnapshot(); + }); + + it('opens modal on click', async () => { + const { getByTestId } = render(getComponent()); + fireEvent.click(getByTestId('testid')); + + expect(modalSpy).toHaveBeenCalledWith( + InfoDialog, + { + button: 'Got it', + description: defaultProps.description, + hasCloseButton: true, + title: defaultProps.title, + }); + }); +}); diff --git a/test/components/views/elements/__snapshots__/LearnMore-test.tsx.snap b/test/components/views/elements/__snapshots__/LearnMore-test.tsx.snap new file mode 100644 index 0000000000..41904877c8 --- /dev/null +++ b/test/components/views/elements/__snapshots__/LearnMore-test.tsx.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders button 1`] = ` +
+ +
+`; diff --git a/test/components/views/settings/devices/__snapshots__/FilteredDeviceList-test.tsx.snap b/test/components/views/settings/devices/__snapshots__/FilteredDeviceList-test.tsx.snap index c0f5b9af98..62a6cd94d1 100644 --- a/test/components/views/settings/devices/__snapshots__/FilteredDeviceList-test.tsx.snap +++ b/test/components/views/settings/devices/__snapshots__/FilteredDeviceList-test.tsx.snap @@ -37,7 +37,16 @@ HTMLCollection [

- Consider signing out from old sessions (90 days or older) you don't use anymore + + Consider signing out from old sessions (90 days or older) you don't use anymore. +

+

@@ -72,7 +81,16 @@ HTMLCollection [

- Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore. + + Verify your sessions for enhanced secure messaging or sign out from those you don't recognize or use anymore. +

+

@@ -107,7 +125,16 @@ HTMLCollection [

- For best security, sign out from any session that you don't recognize or use anymore. + + For best security, sign out from any session that you don't recognize or use anymore. +

+

diff --git a/test/setup/setupLanguage.ts b/test/setup/setupLanguage.ts index 5efd8786cd..bd07616ab3 100644 --- a/test/setup/setupLanguage.ts +++ b/test/setup/setupLanguage.ts @@ -20,10 +20,6 @@ import * as languageHandler from "../../src/languageHandler"; import en from "../../src/i18n/strings/en_EN.json"; import de from "../../src/i18n/strings/de_DE.json"; -fetchMock.config.overwriteRoutes = false; -fetchMock.catch(""); -window.fetch = fetchMock.sandbox(); - const lv = { "Save": "Saglabāt", "Uploading %(filename)s and %(count)s others|one": "Качване на %(filename)s и %(count)s друг", diff --git a/test/setup/setupManualMocks.ts b/test/setup/setupManualMocks.ts index 31c716e375..2adda89e0f 100644 --- a/test/setup/setupManualMocks.ts +++ b/test/setup/setupManualMocks.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import fetchMock from "fetch-mock-jest"; import { TextDecoder, TextEncoder } from "util"; // jest 27 removes setImmediate from jsdom @@ -54,3 +55,9 @@ global.TextDecoder = TextDecoder; // prevent errors whenever a component tries to manually scroll. window.HTMLElement.prototype.scrollIntoView = jest.fn(); + +// set up fetch API mock +fetchMock.config.overwriteRoutes = false; +fetchMock.catch(""); +fetchMock.get("/image-file-stub", "image file stub"); +window.fetch = fetchMock.sandbox(); diff --git a/test/stores/room-list/SlidingRoomListStore-test.ts b/test/stores/room-list/SlidingRoomListStore-test.ts new file mode 100644 index 0000000000..488c92396a --- /dev/null +++ b/test/stores/room-list/SlidingRoomListStore-test.ts @@ -0,0 +1,319 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +import { mocked } from 'jest-mock'; +import { SlidingSync, SlidingSyncEvent } from 'matrix-js-sdk/src/sliding-sync'; +import { Room } from 'matrix-js-sdk/src/matrix'; + +import { + LISTS_UPDATE_EVENT, + SlidingRoomListStoreClass, + SlidingSyncSortToFilter, +} from "../../../src/stores/room-list/SlidingRoomListStore"; +import { SpaceStoreClass } from "../../../src/stores/spaces/SpaceStore"; +import { MockEventEmitter, stubClient, untilEmission } from "../../test-utils"; +import { TestSdkContext } from '../../TestSdkContext'; +import { SlidingSyncManager } from '../../../src/SlidingSyncManager'; +import { RoomViewStore } from '../../../src/stores/RoomViewStore'; +import { MatrixDispatcher } from '../../../src/dispatcher/dispatcher'; +import { SortAlgorithm } from '../../../src/stores/room-list/algorithms/models'; +import { DefaultTagID, TagID } from '../../../src/stores/room-list/models'; +import { UPDATE_SELECTED_SPACE } from '../../../src/stores/spaces'; +import { LISTS_LOADING_EVENT } from '../../../src/stores/room-list/RoomListStore'; +import { UPDATE_EVENT } from '../../../src/stores/AsyncStore'; + +jest.mock('../../../src/SlidingSyncManager'); +const MockSlidingSyncManager = >SlidingSyncManager; + +describe("SlidingRoomListStore", () => { + let store: SlidingRoomListStoreClass; + let context: TestSdkContext; + let dis: MatrixDispatcher; + let activeSpace: string; + let tagIdToIndex = {}; + + beforeEach(async () => { + context = new TestSdkContext(); + context.client = stubClient(); + context._SpaceStore = new MockEventEmitter({ + traverseSpace: jest.fn(), + get activeSpace() { + return activeSpace; + }, + }) as SpaceStoreClass; + context._SlidingSyncManager = new MockSlidingSyncManager(); + context._SlidingSyncManager.slidingSync = mocked(new MockEventEmitter({ + getListData: jest.fn(), + }) as unknown as SlidingSync); + context._RoomViewStore = mocked(new MockEventEmitter({ + getRoomId: jest.fn(), + }) as unknown as RoomViewStore); + + // mock implementations to allow the store to map tag IDs to sliding sync list indexes and vice versa + let index = 0; + tagIdToIndex = {}; + mocked(context._SlidingSyncManager.getOrAllocateListIndex).mockImplementation((listId: string): number => { + if (tagIdToIndex[listId] != null) { + return tagIdToIndex[listId]; + } + tagIdToIndex[listId] = index; + index++; + return index; + }); + mocked(context.slidingSyncManager.listIdForIndex).mockImplementation((i) => { + for (const tagId in tagIdToIndex) { + const j = tagIdToIndex[tagId]; + if (i === j) { + return tagId; + } + } + return null; + }); + mocked(context._SlidingSyncManager.ensureListRegistered).mockResolvedValue({ + ranges: [[0, 10]], + }); + + dis = new MatrixDispatcher(); + store = new SlidingRoomListStoreClass(dis, context); + }); + + describe("spaces", () => { + it("alters 'filters.spaces' on the DefaultTagID.Untagged list when the selected space changes", async () => { + await store.start(); // call onReady + const spaceRoomId = "!foo:bar"; + + const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => { + return listName === DefaultTagID.Untagged && !isLoading; + }); + + // change the active space + activeSpace = spaceRoomId; + context._SpaceStore.emit(UPDATE_SELECTED_SPACE, spaceRoomId, false); + await p; + + expect(context._SlidingSyncManager.ensureListRegistered).toHaveBeenCalledWith( + tagIdToIndex[DefaultTagID.Untagged], + { + filters: expect.objectContaining({ + spaces: [spaceRoomId], + }), + }, + ); + }); + + it("alters 'filters.spaces' on the DefaultTagID.Untagged list if it loads with an active space", async () => { + // change the active space before we are ready + const spaceRoomId = "!foo2:bar"; + activeSpace = spaceRoomId; + const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => { + return listName === DefaultTagID.Untagged && !isLoading; + }); + await store.start(); // call onReady + await p; + expect(context._SlidingSyncManager.ensureListRegistered).toHaveBeenCalledWith( + tagIdToIndex[DefaultTagID.Untagged], + expect.objectContaining({ + filters: expect.objectContaining({ + spaces: [spaceRoomId], + }), + }), + ); + }); + + it("includes subspaces in 'filters.spaces' when the selected space has subspaces", async () => { + await store.start(); // call onReady + const spaceRoomId = "!foo:bar"; + const subSpace1 = "!ss1:bar"; + const subSpace2 = "!ss2:bar"; + + const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => { + return listName === DefaultTagID.Untagged && !isLoading; + }); + + mocked(context._SpaceStore.traverseSpace).mockImplementation( + (spaceId: string, fn: (roomId: string) => void) => { + if (spaceId === spaceRoomId) { + fn(subSpace1); + fn(subSpace2); + } + }, + ); + + // change the active space + activeSpace = spaceRoomId; + context._SpaceStore.emit(UPDATE_SELECTED_SPACE, spaceRoomId, false); + await p; + + expect(context._SlidingSyncManager.ensureListRegistered).toHaveBeenCalledWith( + tagIdToIndex[DefaultTagID.Untagged], + { + filters: expect.objectContaining({ + spaces: [spaceRoomId, subSpace1, subSpace2], + }), + }, + ); + }); + }); + + it("setTagSorting alters the 'sort' option in the list", async () => { + mocked(context._SlidingSyncManager.getOrAllocateListIndex).mockReturnValue(0); + const tagId: TagID = "foo"; + await store.setTagSorting(tagId, SortAlgorithm.Alphabetic); + expect(context._SlidingSyncManager.ensureListRegistered).toBeCalledWith(0, { + sort: SlidingSyncSortToFilter[SortAlgorithm.Alphabetic], + }); + expect(store.getTagSorting(tagId)).toEqual(SortAlgorithm.Alphabetic); + + await store.setTagSorting(tagId, SortAlgorithm.Recent); + expect(context._SlidingSyncManager.ensureListRegistered).toBeCalledWith(0, { + sort: SlidingSyncSortToFilter[SortAlgorithm.Recent], + }); + expect(store.getTagSorting(tagId)).toEqual(SortAlgorithm.Recent); + }); + + it("getTagsForRoom gets the tags for the room", async () => { + await store.start(); + const untaggedIndex = context._SlidingSyncManager.getOrAllocateListIndex(DefaultTagID.Untagged); + const favIndex = context._SlidingSyncManager.getOrAllocateListIndex(DefaultTagID.Favourite); + const roomA = "!a:localhost"; + const roomB = "!b:localhost"; + const indexToListData = { + [untaggedIndex]: { + joinedCount: 10, + roomIndexToRoomId: { + 0: roomA, + 1: roomB, + }, + }, + [favIndex]: { + joinedCount: 2, + roomIndexToRoomId: { + 0: roomB, + }, + }, + }; + mocked(context._SlidingSyncManager.slidingSync.getListData).mockImplementation((i: number) => { + return indexToListData[i] || null; + }); + + expect(store.getTagsForRoom(new Room(roomA, context.client, context.client.getUserId()))).toEqual( + [DefaultTagID.Untagged], + ); + expect(store.getTagsForRoom(new Room(roomB, context.client, context.client.getUserId()))).toEqual( + [DefaultTagID.Favourite, DefaultTagID.Untagged], + ); + }); + + it("emits LISTS_UPDATE_EVENT when slidingSync lists update", async () => { + await store.start(); + const roomA = "!a:localhost"; + const roomB = "!b:localhost"; + const roomC = "!c:localhost"; + const tagId = DefaultTagID.Favourite; + const listIndex = context.slidingSyncManager.getOrAllocateListIndex(tagId); + const joinCount = 10; + const roomIndexToRoomId = { // mixed to ensure we sort + 1: roomB, + 2: roomC, + 0: roomA, + }; + const rooms = [ + new Room(roomA, context.client, context.client.getUserId()), + new Room(roomB, context.client, context.client.getUserId()), + new Room(roomC, context.client, context.client.getUserId()), + ]; + mocked(context.client.getRoom).mockImplementation((roomId: string) => { + switch (roomId) { + case roomA: + return rooms[0]; + case roomB: + return rooms[1]; + case roomC: + return rooms[2]; + } + return null; + }); + const p = untilEmission(store, LISTS_UPDATE_EVENT); + context.slidingSyncManager.slidingSync.emit(SlidingSyncEvent.List, listIndex, joinCount, roomIndexToRoomId); + await p; + expect(store.getCount(tagId)).toEqual(joinCount); + expect(store.orderedLists[tagId]).toEqual(rooms); + }); + + it("sets the sticky room on the basis of the viewed room in RoomViewStore", async () => { + await store.start(); + // seed the store with 3 rooms + const roomIdA = "!a:localhost"; + const roomIdB = "!b:localhost"; + const roomIdC = "!c:localhost"; + const tagId = DefaultTagID.Favourite; + const listIndex = context.slidingSyncManager.getOrAllocateListIndex(tagId); + const joinCount = 10; + const roomIndexToRoomId = { // mixed to ensure we sort + 1: roomIdB, + 2: roomIdC, + 0: roomIdA, + }; + const roomA = new Room(roomIdA, context.client, context.client.getUserId()); + const roomB = new Room(roomIdB, context.client, context.client.getUserId()); + const roomC = new Room(roomIdC, context.client, context.client.getUserId()); + mocked(context.client.getRoom).mockImplementation((roomId: string) => { + switch (roomId) { + case roomIdA: + return roomA; + case roomIdB: + return roomB; + case roomIdC: + return roomC; + } + return null; + }); + mocked(context._SlidingSyncManager.slidingSync.getListData).mockImplementation((i: number) => { + if (i !== listIndex) { + return null; + } + return { + roomIndexToRoomId: roomIndexToRoomId, + joinedCount: joinCount, + }; + }); + let p = untilEmission(store, LISTS_UPDATE_EVENT); + context.slidingSyncManager.slidingSync.emit(SlidingSyncEvent.List, listIndex, joinCount, roomIndexToRoomId); + await p; + expect(store.orderedLists[tagId]).toEqual([roomA, roomB, roomC]); + + // make roomB sticky and inform the store + mocked(context.roomViewStore.getRoomId).mockReturnValue(roomIdB); + context.roomViewStore.emit(UPDATE_EVENT); + + // bump room C to the top, room B should not move from i=1 despite the list update saying to + roomIndexToRoomId[0] = roomIdC; + roomIndexToRoomId[1] = roomIdA; + roomIndexToRoomId[2] = roomIdB; + p = untilEmission(store, LISTS_UPDATE_EVENT); + context.slidingSyncManager.slidingSync.emit(SlidingSyncEvent.List, listIndex, joinCount, roomIndexToRoomId); + await p; + + // check that B didn't move and that A was put below B + expect(store.orderedLists[tagId]).toEqual([roomC, roomB, roomA]); + + // make room C sticky: rooms should move as a result, without needing an additional list update + mocked(context.roomViewStore.getRoomId).mockReturnValue(roomIdC); + p = untilEmission(store, LISTS_UPDATE_EVENT); + context.roomViewStore.emit(UPDATE_EVENT); + await p; + expect(store.orderedLists[tagId].map((r) => r.roomId)).toEqual([roomC, roomA, roomB].map((r) => r.roomId)); + }); +}); diff --git a/test/stores/widgets/__snapshots__/StopGapWidgetDriver-test.ts.snap b/test/stores/widgets/__snapshots__/StopGapWidgetDriver-test.ts.snap index 5f19dbb793..4c67524015 100644 --- a/test/stores/widgets/__snapshots__/StopGapWidgetDriver-test.ts.snap +++ b/test/stores/widgets/__snapshots__/StopGapWidgetDriver-test.ts.snap @@ -6,7 +6,7 @@ Array [ Array [ Object { "deviceInfo": DeviceInfo { - "algorithms": undefined, + "algorithms": Array [], "deviceId": "aliceWeb", "keys": Object {}, "known": false, @@ -18,7 +18,7 @@ Array [ }, Object { "deviceInfo": DeviceInfo { - "algorithms": undefined, + "algorithms": Array [], "deviceId": "aliceMobile", "keys": Object {}, "known": false, @@ -37,7 +37,7 @@ Array [ Array [ Object { "deviceInfo": DeviceInfo { - "algorithms": undefined, + "algorithms": Array [], "deviceId": "bobDesktop", "keys": Object {}, "known": false, diff --git a/test/test-utils/client.ts b/test/test-utils/client.ts index e0c532c021..e155dd17c4 100644 --- a/test/test-utils/client.ts +++ b/test/test-utils/client.ts @@ -21,6 +21,26 @@ import { MatrixClient, User } from "matrix-js-sdk/src/matrix"; import { MatrixClientPeg } from "../../src/MatrixClientPeg"; +/** + * Mocked generic class with a real EventEmitter. + * Useful for mocks which need event emitters. + */ +export class MockEventEmitter extends EventEmitter { + /** + * Construct a new event emitter with additional properties/functions. The event emitter functions + * like .emit and .on will be real. + * @param mockProperties An object with the mock property or function implementations. 'getters' + * are correctly cloned to this event emitter. + */ + constructor(mockProperties: Partial|PropertyKeysOf, unknown>> = {}) { + super(); + // We must use defineProperties and not assign as the former clones getters correctly, + // whereas the latter invokes the getter and sets the return value permanently on the + // destination object. + Object.defineProperties(this, Object.getOwnPropertyDescriptors(mockProperties)); + } +} + /** * Mock client with real event emitter * useful for testing code that listens diff --git a/test/utils/location/isSelfLocation-test.ts b/test/utils/location/isSelfLocation-test.ts index 6fafc5e467..cd1b3452a9 100644 --- a/test/utils/location/isSelfLocation-test.ts +++ b/test/utils/location/isSelfLocation-test.ts @@ -28,7 +28,7 @@ import { isSelfLocation } from "../../../src/utils/location"; describe("isSelfLocation", () => { it("Returns true for a full m.asset event", () => { - const content = makeLocationContent("", '0'); + const content = makeLocationContent("", '0', Date.now()); expect(isSelfLocation(content)).toBe(true); }); diff --git a/test/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody-test.tsx b/test/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody-test.tsx index 25e1f7c215..36b2b4c5a7 100644 --- a/test/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody-test.tsx +++ b/test/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody-test.tsx @@ -50,7 +50,7 @@ describe("VoiceBroadcastRecordingBody", () => { room: roomId, user: userId, }); - recording = new VoiceBroadcastRecording(infoEvent, client, VoiceBroadcastInfoState.Running); + recording = new VoiceBroadcastRecording(infoEvent, client, VoiceBroadcastInfoState.Resumed); }); describe("when rendering a live broadcast", () => { diff --git a/test/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip-test.tsx b/test/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip-test.tsx index 4cd85d37ef..f07b7dd0bd 100644 --- a/test/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip-test.tsx +++ b/test/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip-test.tsx @@ -118,7 +118,7 @@ describe("VoiceBroadcastRecordingPip", () => { }); it("should resume the recording", () => { - expect(recording.getState()).toBe(VoiceBroadcastInfoState.Running); + expect(recording.getState()).toBe(VoiceBroadcastInfoState.Resumed); }); }); }); diff --git a/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap b/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap index 4dba41de67..1eace91e48 100644 --- a/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap +++ b/test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastPlaybackBody-test.tsx.snap @@ -3,7 +3,7 @@ exports[`VoiceBroadcastPlaybackBody when rendering a 0 broadcast should render as expected 1`] = `


{ onStateChanged = jest.fn(); }); - describe("when there is a running broadcast without chunks yet", () => { + describe(`when there is a ${VoiceBroadcastInfoState.Resumed} broadcast without chunks yet`, () => { beforeEach(() => { - infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Running); + infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Resumed); playback = mkPlayback(); setUpChunkEvents([]); }); @@ -236,9 +236,9 @@ describe("VoiceBroadcastPlayback", () => { }); }); - describe("when there is a running voice broadcast with some chunks", () => { + describe(`when there is a ${VoiceBroadcastInfoState.Resumed} voice broadcast with some chunks`, () => { beforeEach(() => { - infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Running); + infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Resumed); playback = mkPlayback(); setUpChunkEvents([chunk2Event, chunk0Event, chunk1Event]); }); diff --git a/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts b/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts index b3076d72c0..b8b8008c13 100644 --- a/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts +++ b/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts @@ -423,15 +423,15 @@ describe("VoiceBroadcastRecording", () => { await action(); }); - itShouldBeInState(VoiceBroadcastInfoState.Running); - itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Running); + itShouldBeInState(VoiceBroadcastInfoState.Resumed); + itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Resumed); it("should start the recorder", () => { expect(mocked(voiceBroadcastRecorder.start)).toHaveBeenCalled(); }); - it("should emit a running state changed event", () => { - expect(onStateChanged).toHaveBeenCalledWith(VoiceBroadcastInfoState.Running); + it(`should emit a ${VoiceBroadcastInfoState.Resumed} state changed event`, () => { + expect(onStateChanged).toHaveBeenCalledWith(VoiceBroadcastInfoState.Resumed); }); }); }); diff --git a/test/voice-broadcast/utils/hasRoomLiveVoiceBroadcast-test.ts b/test/voice-broadcast/utils/hasRoomLiveVoiceBroadcast-test.ts index 40b50ec883..a984ed5fd6 100644 --- a/test/voice-broadcast/utils/hasRoomLiveVoiceBroadcast-test.ts +++ b/test/voice-broadcast/utils/hasRoomLiveVoiceBroadcast-test.ts @@ -121,7 +121,7 @@ describe("hasRoomLiveVoiceBroadcast", () => { // all there are kind of live states VoiceBroadcastInfoState.Started, VoiceBroadcastInfoState.Paused, - VoiceBroadcastInfoState.Running, + VoiceBroadcastInfoState.Resumed, ])("when there is a live broadcast (%s) from the current user", (state: VoiceBroadcastInfoState) => { beforeEach(() => { addVoiceBroadcastInfoEvent(state, client.getUserId()); @@ -132,7 +132,7 @@ describe("hasRoomLiveVoiceBroadcast", () => { describe("when there was a live broadcast, that has been stopped", () => { beforeEach(() => { - addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Running, client.getUserId()); + addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Resumed, client.getUserId()); addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped, client.getUserId()); }); @@ -141,7 +141,7 @@ describe("hasRoomLiveVoiceBroadcast", () => { describe("when there is a live broadcast from another user", () => { beforeEach(() => { - addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Running, otherUserId); + addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Resumed, otherUserId); }); itShouldReturnTrueFalse(); diff --git a/test/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile-test.ts b/test/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile-test.ts index 7673dc4d7b..fc4ec2c04b 100644 --- a/test/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile-test.ts +++ b/test/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastRecordingTile-test.ts @@ -40,7 +40,7 @@ const testCases = [ [ "@user1:example.com", "@user1:example.com", - VoiceBroadcastInfoState.Running, + VoiceBroadcastInfoState.Resumed, true, ], [ diff --git a/test/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile-test.ts b/test/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile-test.ts index 3d9e87ee3b..394b8c4c11 100644 --- a/test/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile-test.ts +++ b/test/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile-test.ts @@ -128,7 +128,7 @@ describe("shouldDisplayAsVoiceBroadcastTile", () => { describe.each( [ VoiceBroadcastInfoState.Paused, - VoiceBroadcastInfoState.Running, + VoiceBroadcastInfoState.Resumed, VoiceBroadcastInfoState.Stopped, ], )("when a voice broadcast info event in state %s occurs", (state: VoiceBroadcastInfoState) => { diff --git a/test/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts b/test/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts index b19ea3c691..dc72868c83 100644 --- a/test/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts +++ b/test/voice-broadcast/utils/startNewVoiceBroadcastRecording-test.ts @@ -161,7 +161,7 @@ describe("startNewVoiceBroadcastRecording", () => { room.currentState.setStateEvents([ mkVoiceBroadcastInfoStateEvent( roomId, - VoiceBroadcastInfoState.Running, + VoiceBroadcastInfoState.Resumed, client.getUserId(), client.getDeviceId(), ), @@ -184,7 +184,7 @@ describe("startNewVoiceBroadcastRecording", () => { room.currentState.setStateEvents([ mkVoiceBroadcastInfoStateEvent( roomId, - VoiceBroadcastInfoState.Running, + VoiceBroadcastInfoState.Resumed, otherUserId, "ASD123", ), diff --git a/yarn.lock b/yarn.lock index d1e87319af..6cfe174374 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2674,7 +2674,7 @@ ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -3198,11 +3198,6 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browser-request@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/browser-request/-/browser-request-0.3.3.tgz#9ece5b5aca89a29932242e18bf933def9876cc17" - integrity sha512-YyNI4qJJ+piQG6MMEuo7J3Bzaqssufx04zpEKYfSrl/1Op59HWali9zMtBpXnkmqMcOuWJPZvudrm9wISmnCbg== - browserslist@^4.20.2, browserslist@^4.21.3: version "4.21.3" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a" @@ -5285,19 +5280,6 @@ grid-index@^1.1.0: resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7" integrity sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA== -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== - -har-validator@~5.1.3: - version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" @@ -5458,15 +5440,6 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - http-signature@~1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" @@ -6695,16 +6668,6 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsprim@^1.2.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" - integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.4.0" - verror "1.10.0" - jsprim@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" @@ -7065,19 +7028,17 @@ matrix-events-sdk@^0.0.1-beta.7: integrity sha512-9jl4wtWanUFSy2sr2lCjErN/oC8KTAtaeaozJtrgot1JiQcEI4Rda9OLgQ7nLKaqb4Z/QUx/fR3XpDzm5Jy1JA== "matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "20.1.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8eed354e17001cd25e3cafe81f74dab499a9882e" + version "21.0.0" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/4b3e6939d6dbfb72c9637d18d6346796bc6f997f" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" - browser-request "^0.3.3" bs58 "^5.0.0" content-type "^1.0.4" loglevel "^1.7.1" matrix-events-sdk "^0.0.1-beta.7" p-retry "4" qs "^6.9.6" - request "^2.88.2" unhomoglyph "^1.0.6" matrix-mock-request@^2.5.0: @@ -7396,11 +7357,6 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.1.tgz#10a9f268fbf4c461249ebcfe38e359aa36e2577c" integrity sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg== -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -8303,32 +8259,6 @@ request-progress@^3.0.0: dependencies: throttleit "^1.0.0" -request@^2.88.2: - version "2.88.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -8795,7 +8725,7 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -sshpk@^1.14.1, sshpk@^1.7.0: +sshpk@^1.14.1: version "1.17.0" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5" integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ== @@ -9509,11 +9439,6 @@ util-deprecate@^1.0.2, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"