diff --git a/.eslintrc.js b/.eslintrc.js index a3c7eb4f8d..a1fe472db2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -92,6 +92,8 @@ module.exports = { "!matrix-js-sdk/src/crypto-api", "!matrix-js-sdk/src/types", "!matrix-js-sdk/src/testing", + "!matrix-js-sdk/src/utils/**", + "matrix-js-sdk/src/utils/internal/**", "matrix-js-sdk/lib", "matrix-js-sdk/lib/", "matrix-js-sdk/lib/**", @@ -119,7 +121,6 @@ module.exports = { "!matrix-js-sdk/src/extensible_events_v1/PollEndEvent", "!matrix-js-sdk/src/extensible_events_v1/InvalidEventError", "!matrix-js-sdk/src/crypto", - "!matrix-js-sdk/src/crypto/aes", "!matrix-js-sdk/src/crypto/keybackup", "!matrix-js-sdk/src/crypto/deviceinfo", "!matrix-js-sdk/src/crypto/dehydration", diff --git a/.github/labels.yml b/.github/labels.yml index d240f3750b..7c4b66d7f0 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -1,28 +1,28 @@ - name: "A-Timesheet-1" description: "Log any time spent on this into the A-Timesheet-1 project" - color: "#5319E7" + color: "5319E7" - name: "backport staging" description: "Label to automatically backport PR to staging branch" - color: "#B60205" + color: "B60205" - name: "Dependencies" description: "Pull requests that update a dependency file" - color: "#0366d6" + color: "0366d6" - name: "Sponsored" - color: "#b506d8" + color: "b506d8" - name: "T-Deprecation" description: "A pull request that makes something deprecated" - color: "#98e6ae" + color: "98e6ae" - name: "X-Blocked" description: "The PR cannot move forward in any capacity until an action is made" color: "ff7979" - name: "X-Breaking-Change" - color: "#ff7979" + color: "ff7979" - name: "X-Upcoming-Release-Blocker" description: "This does not affect the current release cycle but will affect the next one" - color: "#e99695" + color: "e99695" - name: "Z-Community-PR" description: "Issue is solved by a community member's PR" - color: "#ededed" + color: "ededed" - name: "Z-Experiment" description: "Experimental PR, primarily up for its Netlify build, high likelihood of never making it beyond here." - color: "#b60205" + color: "b60205" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 22a02779b3..3bf5700f03 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -89,13 +89,17 @@ jobs: coverage !coverage/lcov-report - skip_sonar: - name: Skip SonarCloud in merge queue - if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true' - runs-on: ubuntu-latest + complete: + name: jest-tests needs: jest + if: always() + runs-on: ubuntu-latest steps: - - name: Skip SonarCloud + - if: needs.jest.result != 'skipped' && needs.jest.result != 'success' + run: exit 1 + + - name: Skip SonarCloud in merge queue + if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true' uses: Sibz/github-status-action@faaa4d96fecf273bd762985e0e7f9f933c774918 # v1 with: authToken: ${{ secrets.GITHUB_TOKEN }} diff --git a/package.json b/package.json index be224ca61d..82aca86eb8 100644 --- a/package.json +++ b/package.json @@ -171,7 +171,7 @@ "@types/css-tree": "^2.3.8", "@types/diff-match-patch": "^1.0.32", "@types/escape-html": "^1.0.1", - "@types/express": "^4.17.21", + "@types/express": "^5.0.0", "@types/file-saver": "^2.0.3", "@types/fs-extra": "^11.0.0", "@types/glob-to-regexp": "^0.4.1", diff --git a/playwright/e2e/read-receipts/index.ts b/playwright/e2e/read-receipts/index.ts index 47bd5c2d5c..f097ec839d 100644 --- a/playwright/e2e/read-receipts/index.ts +++ b/playwright/e2e/read-receipts/index.ts @@ -395,6 +395,9 @@ class Helpers { */ async closeThreadsPanel() { await this.page.locator(".mx_RoomHeader").getByLabel("Threads").click(); + if (await this.page.locator("#thread-panel").isVisible()) { + await this.page.locator(".mx_RoomHeader").getByLabel("Threads").click(); + } await expect(this.page.locator(".mx_RightPanel")).not.toBeVisible(); } diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index c8ef0a780f..20e9e7add3 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -432,12 +432,8 @@ export default abstract class BasePlatform { redirectUris: [this.getOidcCallbackUrl().href], logoUri: config.oidc_metadata?.logo_uri ?? new URL("vector-icons/1024.png", this.baseUrl).href, applicationType: "web", - // XXX: We break the spec by not consistently supplying these required fields - // @ts-ignore contacts: config.oidc_metadata?.contacts, - // @ts-ignore tosUri: config.oidc_metadata?.tos_uri ?? config.terms_and_conditions_links?.[0]?.url, - // @ts-ignore policyUri: config.oidc_metadata?.policy_uri ?? config.privacy_policy_url, }; } diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index e83b8df20d..a8ddaf0a1d 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -11,7 +11,7 @@ Please see LICENSE files in the repository root for full details. import { ReactNode } from "react"; import { createClient, MatrixClient, SSOAction, OidcTokenRefresher, decodeBase64 } from "matrix-js-sdk/src/matrix"; -import { IEncryptedPayload } from "matrix-js-sdk/src/crypto/aes"; +import { AESEncryptedSecretStoragePayload } from "matrix-js-sdk/src/types"; import { QueryDict } from "matrix-js-sdk/src/utils"; import { logger } from "matrix-js-sdk/src/logger"; @@ -472,9 +472,9 @@ export interface IStoredSession { hsUrl: string; isUrl: string; hasAccessToken: boolean; - accessToken: string | IEncryptedPayload; + accessToken: string | AESEncryptedSecretStoragePayload; hasRefreshToken: boolean; - refreshToken?: string | IEncryptedPayload; + refreshToken?: string | AESEncryptedSecretStoragePayload; userId: string; deviceId: string; isGuest: boolean; diff --git a/src/Notifier.ts b/src/Notifier.ts index 6323343871..23d239de0a 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -513,12 +513,7 @@ class NotifierClass extends TypedEventEmitter m.sender === cli.getUserId()); - if ( - EventType.CallNotify === ev.getType() && - SettingsStore.getValue("feature_group_calls") && - (ev.getAge() ?? 0) < 10000 && - !thisUserHasConnectedDevice - ) { + if (EventType.CallNotify === ev.getType() && (ev.getAge() ?? 0) < 10000 && !thisUserHasConnectedDevice) { const content = ev.getContent(); const roomId = ev.getRoomId(); if (typeof content.call_id !== "string") { diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 87e8c3c307..b56172be42 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -614,10 +614,7 @@ export class RoomView extends React.Component { }; private getMainSplitContentType = (room: Room): MainSplitContentType => { - if ( - (SettingsStore.getValue("feature_group_calls") && this.context.roomViewStore.isViewingCall()) || - isVideoRoom(room) - ) { + if (this.context.roomViewStore.isViewingCall() || isVideoRoom(room)) { return MainSplitContentType.Call; } if (this.context.widgetLayoutStore.hasMaximisedWidget(room)) { @@ -2183,10 +2180,7 @@ export class RoomView extends React.Component { } const myMembership = this.state.room.getMyMembership(); - if ( - isVideoRoom(this.state.room) && - !(SettingsStore.getValue("feature_video_rooms") && myMembership === KnownMembership.Join) - ) { + if (isVideoRoom(this.state.room) && myMembership !== KnownMembership.Join) { return (
@@ -2521,9 +2515,15 @@ export class RoomView extends React.Component { mx_RoomView_timeline_rr_enabled: this.state.showReadReceipts, }); + let { mainSplitContentType } = this.state; + if (this.state.search) { + // When in the middle of a search force the main split content type to timeline + mainSplitContentType = MainSplitContentType.Timeline; + } + const mainClasses = classNames("mx_RoomView", { mx_RoomView_inCall: Boolean(activeCall), - mx_RoomView_immersive: this.state.mainSplitContentType !== MainSplitContentType.Timeline, + mx_RoomView_immersive: mainSplitContentType !== MainSplitContentType.Timeline, }); const showChatEffects = SettingsStore.getValue("showChatEffects"); @@ -2531,7 +2531,7 @@ export class RoomView extends React.Component { let mainSplitBody: JSX.Element | undefined; let mainSplitContentClassName: string | undefined; // Decide what to show in the main split - switch (this.state.mainSplitContentType) { + switch (mainSplitContentType) { case MainSplitContentType.Timeline: mainSplitContentClassName = "mx_MainSplit_timeline"; mainSplitBody = ( @@ -2595,7 +2595,7 @@ export class RoomView extends React.Component { let viewingCall = false; // Simplify the header for other main split types - switch (this.state.mainSplitContentType) { + switch (mainSplitContentType) { case MainSplitContentType.MaximisedWidget: excludedRightPanelPhaseButtons = []; onAppsClick = null; diff --git a/src/components/views/context_menus/RoomContextMenu.tsx b/src/components/views/context_menus/RoomContextMenu.tsx index 694aba0ef5..f431218b06 100644 --- a/src/components/views/context_menus/RoomContextMenu.tsx +++ b/src/components/views/context_menus/RoomContextMenu.tsx @@ -42,7 +42,7 @@ import { shouldShowComponent } from "../../../customisations/helpers/UIComponent import { UIComponent } from "../../../settings/UIFeature"; import { DeveloperToolsOption } from "./DeveloperToolsOption"; import { tagRoom } from "../../../utils/room/tagRoom"; -import { useIsVideoRoom } from "../../../utils/video-rooms"; +import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms"; import { usePinnedEvents } from "../../../hooks/usePinnedEvents"; interface IProps extends IContextMenuProps { @@ -105,7 +105,7 @@ const RoomContextMenu: React.FC = ({ room, onFinished, ...props }) => { } const isDm = DMRoomMap.shared().getUserIdForRoomId(room.roomId); - const isVideoRoom = useIsVideoRoom(room); + const isVideoRoom = calcIsVideoRoom(room); const canInvite = useEventEmitterState(cli, RoomMemberEvent.PowerLevel, () => room.canInvite(cli.getUserId()!)); let inviteOption: JSX.Element | undefined; if (canInvite && !isDm && shouldShowComponent(UIComponent.InviteUsers)) { diff --git a/src/components/views/dialogs/SlashCommandHelpDialog.tsx b/src/components/views/dialogs/SlashCommandHelpDialog.tsx index a83ec3917d..b5e01bf41e 100644 --- a/src/components/views/dialogs/SlashCommandHelpDialog.tsx +++ b/src/components/views/dialogs/SlashCommandHelpDialog.tsx @@ -45,7 +45,7 @@ const SlashCommandHelpDialog: React.FC = ({ onFinished }) => { {cmd.getCommand()} {cmd.args} - {cmd.description} + {_t(cmd.description)} , ); }); diff --git a/src/components/views/right_panel/RightPanelTabs.tsx b/src/components/views/right_panel/RightPanelTabs.tsx index 07a423dd05..4873f707b2 100644 --- a/src/components/views/right_panel/RightPanelTabs.tsx +++ b/src/components/views/right_panel/RightPanelTabs.tsx @@ -20,7 +20,7 @@ import { Action } from "../../../dispatcher/actions"; import SettingsStore from "../../../settings/SettingsStore"; import { UIComponent, UIFeature } from "../../../settings/UIFeature"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; -import { useIsVideoRoom } from "../../../utils/video-rooms"; +import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms"; function shouldShowTabsForPhase(phase?: RightPanelPhases): boolean { const tabs = [ @@ -48,7 +48,7 @@ export const RightPanelTabs: React.FC = ({ phase, room }): JSX.Element | } }); - const isVideoRoom = useIsVideoRoom(room); + const isVideoRoom = room !== undefined && calcIsVideoRoom(room); if (!shouldShowTabsForPhase(phase)) return null; diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 39575d94f5..e67d38a2ed 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -70,7 +70,7 @@ import { useDispatcher } from "../../../hooks/useDispatcher"; import { Action } from "../../../dispatcher/actions"; import { Key } from "../../../Keyboard"; import { useTransition } from "../../../hooks/useTransition"; -import { useIsVideoRoom } from "../../../utils/video-rooms"; +import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms"; import { usePinnedEvents } from "../../../hooks/usePinnedEvents"; import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx"; @@ -219,7 +219,7 @@ const RoomSummaryCard: React.FC = ({ const isRoomEncrypted = useIsEncrypted(cli, room); const roomContext = useContext(RoomContext); const e2eStatus = roomContext.e2eStatus; - const isVideoRoom = useIsVideoRoom(room); + const isVideoRoom = calcIsVideoRoom(room); const roomState = useRoomState(room); const directRoomsList = useAccountData>(room.client, EventType.Direct); diff --git a/src/components/views/rooms/LegacyRoomHeader.tsx b/src/components/views/rooms/LegacyRoomHeader.tsx index 3464aef30d..0e4cd28aa8 100644 --- a/src/components/views/rooms/LegacyRoomHeader.tsx +++ b/src/components/views/rooms/LegacyRoomHeader.tsx @@ -251,8 +251,7 @@ const CallButtons: FC = ({ room }) => { const [busy, setBusy] = useState(false); const showButtons = useSettingValue("showCallButtonsInComposer"); const groupCallsEnabled = useFeatureEnabled("feature_group_calls"); - const videoRoomsEnabled = useFeatureEnabled("feature_video_rooms"); - const isVideoRoom = useMemo(() => videoRoomsEnabled && calcIsVideoRoom(room), [videoRoomsEnabled, room]); + const isVideoRoom = useMemo(() => calcIsVideoRoom(room), [room]); const useElementCallExclusively = useMemo(() => { return SdkConfig.get("element_call").use_exclusively; }, []); @@ -290,53 +289,13 @@ const CallButtons: FC = ({ room }) => { if (isVideoRoom || !showButtons) { return null; - } else if (groupCallsEnabled) { - if (useElementCallExclusively) { - if (hasGroupCall) { - return makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_ongoing_call"))); - } else if (mayCreateElementCalls) { - return makeVideoCallButton("element"); - } else { - return makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_no_perms_start_video_call"))); - } - } else if (hasLegacyCall || hasJitsiWidget) { - return ( - <> - {makeVoiceCallButton(new DisabledWithReason(_t("voip|disabled_ongoing_call")))} - {makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_ongoing_call")))} - - ); - } else if (functionalMembers.length <= 1) { - return ( - <> - {makeVoiceCallButton(new DisabledWithReason(_t("voip|disabled_no_one_here")))} - {makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_no_one_here")))} - - ); - } else if (functionalMembers.length === 2) { - return ( - <> - {makeVoiceCallButton("legacy_or_jitsi")} - {makeVideoCallButton("legacy_or_element")} - - ); - } else if (mayEditWidgets) { - return ( - <> - {makeVoiceCallButton("legacy_or_jitsi")} - {makeVideoCallButton(mayCreateElementCalls ? "jitsi_or_element" : "legacy_or_jitsi")} - - ); + } else if (groupCallsEnabled && useElementCallExclusively) { + if (hasGroupCall) { + return makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_ongoing_call"))); + } else if (mayCreateElementCalls) { + return makeVideoCallButton("element"); } else { - const videoCallBehavior = mayCreateElementCalls - ? "element" - : new DisabledWithReason(_t("voip|disabled_no_perms_start_video_call")); - return ( - <> - {makeVoiceCallButton(new DisabledWithReason(_t("voip|disabled_no_perms_start_voice_call")))} - {makeVideoCallButton(videoCallBehavior)} - - ); + return makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_no_perms_start_video_call"))); } } else if (hasLegacyCall || hasJitsiWidget) { return ( @@ -352,18 +311,31 @@ const CallButtons: FC = ({ room }) => { {makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_no_one_here")))} ); - } else if (functionalMembers.length === 2 || mayEditWidgets) { + } else if (functionalMembers.length === 2) { + return ( + <> + {makeVoiceCallButton("legacy_or_jitsi")} + {makeVideoCallButton(groupCallsEnabled ? "legacy_or_element" : "legacy_or_jitsi")} + + ); + } else if (mayEditWidgets) { return ( <> {makeVoiceCallButton("legacy_or_jitsi")} - {makeVideoCallButton("legacy_or_jitsi")} + {makeVideoCallButton( + groupCallsEnabled && mayCreateElementCalls ? "jitsi_or_element" : "legacy_or_jitsi", + )} ); } else { + const videoCallBehavior = + groupCallsEnabled && mayCreateElementCalls + ? "element" + : new DisabledWithReason(_t("voip|disabled_no_perms_start_video_call")); return ( <> {makeVoiceCallButton(new DisabledWithReason(_t("voip|disabled_no_perms_start_voice_call")))} - {makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_no_perms_start_video_call")))} + {makeVideoCallButton(videoCallBehavior)} ); } @@ -745,7 +717,7 @@ export default class RoomHeader extends React.Component { } public render(): React.ReactNode { - const isVideoRoom = SettingsStore.getValue("feature_video_rooms") && calcIsVideoRoom(this.props.room); + const isVideoRoom = calcIsVideoRoom(this.props.room); let roomAvatar: JSX.Element | null = null; if (this.props.room) { diff --git a/src/components/views/rooms/RoomHeader.tsx b/src/components/views/rooms/RoomHeader.tsx index fae9e264ae..b09d4c70e2 100644 --- a/src/components/views/rooms/RoomHeader.tsx +++ b/src/components/views/rooms/RoomHeader.tsx @@ -42,7 +42,7 @@ import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; import PosthogTrackers from "../../../PosthogTrackers"; import { VideoRoomChatButton } from "./RoomHeader/VideoRoomChatButton"; import { RoomKnocksBar } from "./RoomKnocksBar"; -import { useIsVideoRoom } from "../../../utils/video-rooms"; +import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms"; import { notificationLevelToIndicator } from "../../../utils/notifications"; import { CallGuestLinkButton } from "./RoomHeader/CallGuestLinkButton"; import { ButtonEvent } from "../elements/AccessibleButton"; @@ -225,7 +225,7 @@ export default function RoomHeader({ } const roomContext = useContext(RoomContext); - const isVideoRoom = useIsVideoRoom(room); + const isVideoRoom = calcIsVideoRoom(room); const showChatButton = isVideoRoom || roomContext.mainSplitContentType === MainSplitContentType.MaximisedWidget || @@ -255,7 +255,7 @@ export default function RoomHeader({