mirror of
https://github.com/Novaloop-AG/healthchat-web.git
synced 2026-11-07 06:58:25 +00:00
Fix invite-specific join errors not being shown (#32621)
* Fix invite-specific join errors not being shown * remove horrible non-jest function of getAccountData * Use an Error * Always dispatch an Error * Throw a UserFriendlyError
This commit is contained in:
@@ -6,16 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
|||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type MatrixError } from "matrix-js-sdk/src/matrix";
|
import type { ActionPayload } from "../payloads";
|
||||||
|
import type { Action } from "../actions";
|
||||||
import { type ActionPayload } from "../payloads";
|
|
||||||
import { type Action } from "../actions";
|
|
||||||
|
|
||||||
export interface JoinRoomErrorPayload extends Pick<ActionPayload, "action"> {
|
export interface JoinRoomErrorPayload extends Pick<ActionPayload, "action"> {
|
||||||
action: Action.JoinRoomError;
|
action: Action.JoinRoomError;
|
||||||
|
|
||||||
roomId: string;
|
roomId: string | null;
|
||||||
err: MatrixError;
|
err: Error;
|
||||||
|
|
||||||
canAskToJoin?: boolean;
|
canAskToJoin?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1990,6 +1990,7 @@
|
|||||||
"error_join_incompatible_version_1": "Sorry, your homeserver is too old to participate here.",
|
"error_join_incompatible_version_1": "Sorry, your homeserver is too old to participate here.",
|
||||||
"error_join_incompatible_version_2": "Please contact your homeserver administrator.",
|
"error_join_incompatible_version_2": "Please contact your homeserver administrator.",
|
||||||
"error_join_title": "Failed to join",
|
"error_join_title": "Failed to join",
|
||||||
|
"error_join_unknown": "An unknonw error occured.",
|
||||||
"error_jump_to_date": "Server returned %(statusCode)s with error code %(errorCode)s",
|
"error_jump_to_date": "Server returned %(statusCode)s with error code %(errorCode)s",
|
||||||
"error_jump_to_date_connection": "A network error occurred while trying to find and jump to the given date. Your homeserver might be down or there was just a temporary problem with your internet connection. Please try again. If this continues, please contact your homeserver administrator.",
|
"error_jump_to_date_connection": "A network error occurred while trying to find and jump to the given date. Your homeserver might be down or there was just a temporary problem with your internet connection. Please try again. If this continues, please contact your homeserver administrator.",
|
||||||
"error_jump_to_date_details": "Error details",
|
"error_jump_to_date_details": "Error details",
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
import { type MatrixDispatcher } from "../dispatcher/dispatcher";
|
import { type MatrixDispatcher } from "../dispatcher/dispatcher";
|
||||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||||
import Modal from "../Modal";
|
import Modal from "../Modal";
|
||||||
import { _t } from "../languageHandler";
|
import { _t, UserFriendlyError } from "../languageHandler";
|
||||||
import { getCachedRoomIdForAlias, storeRoomAliasInCache } from "../RoomAliasCache";
|
import { getCachedRoomIdForAlias, storeRoomAliasInCache } from "../RoomAliasCache";
|
||||||
import { Action } from "../dispatcher/actions";
|
import { Action } from "../dispatcher/actions";
|
||||||
import { retry } from "../utils/promise";
|
import { retry } from "../utils/promise";
|
||||||
@@ -569,10 +569,11 @@ export class RoomViewStore extends EventEmitter {
|
|||||||
metricsTrigger: payload.metricsTrigger,
|
metricsTrigger: payload.metricsTrigger,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.dis?.dispatch({
|
logger.error("Error thrown while handling joinRoom", err);
|
||||||
|
this.dis?.dispatch<JoinRoomErrorPayload>({
|
||||||
action: Action.JoinRoomError,
|
action: Action.JoinRoomError,
|
||||||
roomId,
|
roomId,
|
||||||
err,
|
err: err instanceof Error ? err : new UserFriendlyError("room|error_join_unknown", { cause: err }),
|
||||||
canAskToJoin: payload.canAskToJoin,
|
canAskToJoin: payload.canAskToJoin,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -592,11 +593,11 @@ export class RoomViewStore extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public showJoinRoomError(err: MatrixError, roomId: string): void {
|
public showJoinRoomError(err: unknown, roomId: string | null): void {
|
||||||
let description: ReactNode = err.message ? err.message : JSON.stringify(err);
|
let description: ReactNode = err instanceof Error && err.message ? err.message : JSON.stringify(err);
|
||||||
logger.log("Failed to join room:", description);
|
if (err instanceof MatrixError === false) {
|
||||||
|
// This isn't a MatrixError so just show the error verbatim.
|
||||||
if (err.name === "ConnectionError") {
|
} else if (err.name === "ConnectionError") {
|
||||||
description = _t("room|error_join_connection");
|
description = _t("room|error_join_connection");
|
||||||
} else if (err.errcode === "M_INCOMPATIBLE_ROOM_VERSION") {
|
} else if (err.errcode === "M_INCOMPATIBLE_ROOM_VERSION") {
|
||||||
description = (
|
description = (
|
||||||
@@ -607,7 +608,7 @@ export class RoomViewStore extends EventEmitter {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (err.httpStatus === 404) {
|
} else if (err.httpStatus === 404) {
|
||||||
const invitingUserId = this.getInvitingUserId(roomId);
|
const invitingUserId = roomId && this.getInvitingUserId(roomId);
|
||||||
// provide a better error message for invites
|
// provide a better error message for invites
|
||||||
if (invitingUserId) {
|
if (invitingUserId) {
|
||||||
// if the inviting user is on the same HS, there can only be one cause: they left.
|
// if the inviting user is on the same HS, there can only be one cause: they left.
|
||||||
@@ -617,10 +618,9 @@ export class RoomViewStore extends EventEmitter {
|
|||||||
description = _t("room|error_join_404_invite");
|
description = _t("room|error_join_404_invite");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// provide a more detailed error than "No known servers" when attempting to
|
// provide a more detailed error than "No known servers" when attempting to
|
||||||
// join using a room ID and no via servers
|
// join using a room ID and no via servers
|
||||||
if (roomId === this.state.roomId && this.state.viaServers.length === 0) {
|
else if (roomId === this.state.roomId && this.state.viaServers.length === 0) {
|
||||||
description = (
|
description = (
|
||||||
<div>
|
<div>
|
||||||
{_t("room|error_join_404_1")}
|
{_t("room|error_join_404_1")}
|
||||||
@@ -631,6 +631,7 @@ export class RoomViewStore extends EventEmitter {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
logger.log("Failed to join room:", description);
|
||||||
|
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("room|error_join_title"),
|
title: _t("room|error_join_title"),
|
||||||
@@ -641,7 +642,7 @@ export class RoomViewStore extends EventEmitter {
|
|||||||
private joinRoomError(payload: JoinRoomErrorPayload): void {
|
private joinRoomError(payload: JoinRoomErrorPayload): void {
|
||||||
this.setState({
|
this.setState({
|
||||||
joining: false,
|
joining: false,
|
||||||
joinError: payload.err,
|
joinError: payload.err instanceof Error ? payload.err : null,
|
||||||
});
|
});
|
||||||
if (payload.err && !payload.canAskToJoin) {
|
if (payload.err && !payload.canAskToJoin) {
|
||||||
this.showJoinRoomError(payload.err, payload.roomId);
|
this.showJoinRoomError(payload.err, payload.roomId);
|
||||||
|
|||||||
@@ -688,7 +688,6 @@ export function mkStubRoom(
|
|||||||
fetchRoomThreads: jest.fn().mockReturnValue(Promise.resolve()),
|
fetchRoomThreads: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
findEventById: jest.fn().mockReturnValue(undefined),
|
findEventById: jest.fn().mockReturnValue(undefined),
|
||||||
findPredecessor: jest.fn().mockReturnValue({ roomId: "", eventId: null }),
|
findPredecessor: jest.fn().mockReturnValue({ roomId: "", eventId: null }),
|
||||||
getAccountData: (_: EventType | string) => undefined as MatrixEvent | undefined,
|
|
||||||
getAltAliases: jest.fn().mockReturnValue([]),
|
getAltAliases: jest.fn().mockReturnValue([]),
|
||||||
getAvatarUrl: () => "mxc://avatar.url/room.png",
|
getAvatarUrl: () => "mxc://avatar.url/room.png",
|
||||||
getCanonicalAlias: jest.fn(),
|
getCanonicalAlias: jest.fn(),
|
||||||
@@ -725,6 +724,7 @@ export function mkStubRoom(
|
|||||||
getRoomUnreadNotificationCount: jest.fn().mockReturnValue(0),
|
getRoomUnreadNotificationCount: jest.fn().mockReturnValue(0),
|
||||||
getVersion: jest.fn().mockReturnValue("1"),
|
getVersion: jest.fn().mockReturnValue("1"),
|
||||||
getBumpStamp: jest.fn().mockReturnValue(0),
|
getBumpStamp: jest.fn().mockReturnValue(0),
|
||||||
|
getAccountData: jest.fn(),
|
||||||
hasMembershipState: () => false,
|
hasMembershipState: () => false,
|
||||||
isElementVideoRoom: jest.fn().mockReturnValue(false),
|
isElementVideoRoom: jest.fn().mockReturnValue(false),
|
||||||
isSpaceRoom: jest.fn().mockReturnValue(false),
|
isSpaceRoom: jest.fn().mockReturnValue(false),
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
import { MatrixError, Room } from "matrix-js-sdk/src/matrix";
|
import { KnownMembership, MatrixError, Room } from "matrix-js-sdk/src/matrix";
|
||||||
import { sleep } from "matrix-js-sdk/src/utils";
|
import { sleep } from "matrix-js-sdk/src/utils";
|
||||||
import {
|
import {
|
||||||
RoomViewLifecycle,
|
RoomViewLifecycle,
|
||||||
@@ -20,6 +20,9 @@ import { Action } from "../../../src/dispatcher/actions";
|
|||||||
import {
|
import {
|
||||||
flushPromises,
|
flushPromises,
|
||||||
getMockClientWithEventEmitter,
|
getMockClientWithEventEmitter,
|
||||||
|
mkEvent,
|
||||||
|
mkRoom,
|
||||||
|
mkRoomMember,
|
||||||
setupAsyncStoreWithClient,
|
setupAsyncStoreWithClient,
|
||||||
untilDispatch,
|
untilDispatch,
|
||||||
untilEmission,
|
untilEmission,
|
||||||
@@ -126,6 +129,7 @@ describe("RoomViewStore", function () {
|
|||||||
getUserId: jest.fn().mockReturnValue(userId),
|
getUserId: jest.fn().mockReturnValue(userId),
|
||||||
getSafeUserId: jest.fn().mockReturnValue(userId),
|
getSafeUserId: jest.fn().mockReturnValue(userId),
|
||||||
getDeviceId: jest.fn().mockReturnValue("ABC123"),
|
getDeviceId: jest.fn().mockReturnValue("ABC123"),
|
||||||
|
getDomain: jest.fn().mockReturnValue("server"),
|
||||||
sendStateEvent: jest.fn().mockResolvedValue({}),
|
sendStateEvent: jest.fn().mockResolvedValue({}),
|
||||||
supportsThreads: jest.fn(),
|
supportsThreads: jest.fn(),
|
||||||
isInitialSyncComplete: jest.fn().mockResolvedValue(false),
|
isInitialSyncComplete: jest.fn().mockResolvedValue(false),
|
||||||
@@ -144,7 +148,7 @@ describe("RoomViewStore", function () {
|
|||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
});
|
});
|
||||||
const room = new Room(roomId, mockClient, userId);
|
const room = mkRoom(mockClient, roomId);
|
||||||
const room2 = new Room(roomId2, mockClient, userId);
|
const room2 = new Room(roomId2, mockClient, userId);
|
||||||
getRooms.mockReturnValue([room, room2]);
|
getRooms.mockReturnValue([room, room2]);
|
||||||
|
|
||||||
@@ -439,6 +443,31 @@ describe("RoomViewStore", function () {
|
|||||||
expect(mocked(Modal).createDialog.mock.calls[0][1]).toMatchSnapshot();
|
expect(mocked(Modal).createDialog.mock.calls[0][1]).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The server bob is on will affect the message we send.
|
||||||
|
it.each(["server", "another-server"])(
|
||||||
|
"should display an invite-specific error message when the room is unreachable",
|
||||||
|
async (bobsServer) => {
|
||||||
|
room.getMyMembership.mockReturnValue(KnownMembership.Invite);
|
||||||
|
room.getMember.mockImplementationOnce((memberUserId) => {
|
||||||
|
if (userId === memberUserId) {
|
||||||
|
const member = mkRoomMember(roomId, userId, KnownMembership.Invite);
|
||||||
|
member.events.member!.getSender = () => `@bob:${bobsServer}`;
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
|
||||||
|
await untilDispatch(Action.ActiveRoomChanged, dis);
|
||||||
|
|
||||||
|
// Generate error to display the expected error message
|
||||||
|
const error = new MatrixError(undefined, 404);
|
||||||
|
roomViewStore.showJoinRoomError(error, roomId);
|
||||||
|
|
||||||
|
// Check the modal props
|
||||||
|
expect(mocked(Modal).createDialog.mock.calls[0][1]).toMatchSnapshot();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
it("should display the generic error message when the roomId doesnt match", async () => {
|
it("should display the generic error message when the roomId doesnt match", async () => {
|
||||||
// When
|
// When
|
||||||
// Generate error to display the expected error message
|
// Generate error to display the expected error message
|
||||||
@@ -450,9 +479,9 @@ describe("RoomViewStore", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("clears the unread flag when viewing a room", async () => {
|
it("clears the unread flag when viewing a room", async () => {
|
||||||
room.getAccountData = jest.fn().mockReturnValue({
|
room.getAccountData.mockReturnValue(
|
||||||
getContent: jest.fn().mockReturnValue({ unread: true }),
|
mkEvent({ type: "m.marked_unread", user: "@anyone:example.org", content: { unread: true }, event: true }),
|
||||||
});
|
);
|
||||||
dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
|
dis.dispatch({ action: Action.ViewRoom, room_id: roomId });
|
||||||
await untilDispatch(Action.ActiveRoomChanged, dis);
|
await untilDispatch(Action.ActiveRoomChanged, dis);
|
||||||
expect(mockClient.setRoomAccountData).toHaveBeenCalledWith(roomId, "m.marked_unread", {
|
expect(mockClient.setRoomAccountData).toHaveBeenCalledWith(roomId, "m.marked_unread", {
|
||||||
|
|||||||
@@ -12,6 +12,20 @@ exports[`RoomViewStore should display an error message when the room is unreacha
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`RoomViewStore should display an invite-specific error message when the room is unreachable 1`] = `
|
||||||
|
{
|
||||||
|
"description": "The person who invited you has already left.",
|
||||||
|
"title": "Failed to join",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`RoomViewStore should display an invite-specific error message when the room is unreachable 2`] = `
|
||||||
|
{
|
||||||
|
"description": "The person who invited you has already left, or their server is offline.",
|
||||||
|
"title": "Failed to join",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`RoomViewStore should display the generic error message when the roomId doesnt match 1`] = `
|
exports[`RoomViewStore should display the generic error message when the roomId doesnt match 1`] = `
|
||||||
{
|
{
|
||||||
"description": "MatrixError: [404] my 404 error",
|
"description": "MatrixError: [404] my 404 error",
|
||||||
|
|||||||
Reference in New Issue
Block a user