mirror of
https://github.com/element-hq/element-web
synced 2024-11-27 11:47:23 +03:00
Extract view/join room logic to room helper (#8329)
This commit is contained in:
parent
d5e911d876
commit
5d6143aaa7
6 changed files with 226 additions and 122 deletions
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { IFieldType, IInstance, IProtocol, IPublicRoomsChunkRoom } from "matrix-js-sdk/src/client";
|
||||
import { IFieldType, IPublicRoomsChunkRoom } from "matrix-js-sdk/src/client";
|
||||
import { Visibility } from "matrix-js-sdk/src/@types/partials";
|
||||
import { IRoomDirectoryOptions } from "matrix-js-sdk/src/@types/requests";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
@ -28,7 +28,7 @@ import { _t } from '../../languageHandler';
|
|||
import SdkConfig from '../../SdkConfig';
|
||||
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
||||
import Analytics from '../../Analytics';
|
||||
import NetworkDropdown, { ALL_ROOMS, Protocols } from "../views/directory/NetworkDropdown";
|
||||
import NetworkDropdown from "../views/directory/NetworkDropdown";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import { IDialogProps } from "../views/dialogs/IDialogProps";
|
||||
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
|
||||
|
@ -39,10 +39,11 @@ import DirectorySearchBox from "../views/elements/DirectorySearchBox";
|
|||
import ScrollPanel from "./ScrollPanel";
|
||||
import Spinner from "../views/elements/Spinner";
|
||||
import { getDisplayAliasForAliasSet } from "../../Rooms";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import PosthogTrackers from "../../PosthogTrackers";
|
||||
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { PublicRoomTile } from "../views/rooms/PublicRoomTile";
|
||||
import { getFieldsForThirdPartyLocation, joinRoomByAlias, showRoom } from "../../utils/rooms";
|
||||
import { GenericError } from "../../utils/error";
|
||||
import { ALL_ROOMS, Protocols } from "../../utils/DirectoryUtils";
|
||||
|
||||
const LAST_SERVER_KEY = "mx_last_room_directory_server";
|
||||
const LAST_INSTANCE_KEY = "mx_last_room_directory_instance";
|
||||
|
@ -350,44 +351,23 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private onJoinFromSearchClick = (alias: string) => {
|
||||
// If we don't have a particular instance id selected, just show that rooms alias
|
||||
if (!this.state.instanceId || this.state.instanceId === ALL_ROOMS) {
|
||||
// If the user specified an alias without a domain, add on whichever server is selected
|
||||
// in the dropdown
|
||||
if (alias.indexOf(':') == -1) {
|
||||
alias = alias + ':' + this.state.roomServer;
|
||||
}
|
||||
this.showRoomAlias(alias, true);
|
||||
} else {
|
||||
// This is a 3rd party protocol. Let's see if we can join it
|
||||
const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
|
||||
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
|
||||
const fields = protocolName
|
||||
? this.getFieldsForThirdPartyLocation(alias, this.protocols[protocolName], instance)
|
||||
: null;
|
||||
if (!fields) {
|
||||
const brand = SdkConfig.get().brand;
|
||||
Modal.createTrackedDialog('Unable to join network', '', ErrorDialog, {
|
||||
title: _t('Unable to join network'),
|
||||
description: _t('%(brand)s does not know how to join a room on this network', { brand }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).then((resp) => {
|
||||
if (resp.length > 0 && resp[0].alias) {
|
||||
this.showRoomAlias(resp[0].alias, true);
|
||||
} else {
|
||||
Modal.createTrackedDialog('Room not found', '', ErrorDialog, {
|
||||
title: _t('Room not found'),
|
||||
description: _t('Couldn\'t find a matching Matrix room'),
|
||||
});
|
||||
}
|
||||
}, (e) => {
|
||||
Modal.createTrackedDialog('Fetching third party location failed', '', ErrorDialog, {
|
||||
title: _t('Fetching third party location failed'),
|
||||
description: _t('Unable to look up room ID from server'),
|
||||
});
|
||||
const cli = MatrixClientPeg.get();
|
||||
try {
|
||||
joinRoomByAlias(cli, alias, {
|
||||
instanceId: this.state.instanceId,
|
||||
roomServer: this.state.roomServer,
|
||||
protocols: this.protocols,
|
||||
metricsTrigger: "RoomDirectory",
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof GenericError) {
|
||||
Modal.createTrackedDialog(e.message, '', ErrorDialog, {
|
||||
title: e.message,
|
||||
description: e.description,
|
||||
});
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -401,55 +381,18 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
PosthogTrackers.trackInteraction("WebRoomDirectoryCreateRoomButton", ev);
|
||||
};
|
||||
|
||||
private showRoomAlias(alias: string, autoJoin = false) {
|
||||
this.showRoom(null, alias, autoJoin);
|
||||
}
|
||||
|
||||
private showRoom = (room: IPublicRoomsChunkRoom, roomAlias?: string, autoJoin = false, shouldPeek = false) => {
|
||||
private onRoomClick = (room: IPublicRoomsChunkRoom, roomAlias?: string, autoJoin = false, shouldPeek = false) => {
|
||||
this.onFinished();
|
||||
const payload: ViewRoomPayload = {
|
||||
action: Action.ViewRoom,
|
||||
auto_join: autoJoin,
|
||||
should_peek: shouldPeek,
|
||||
const cli = MatrixClientPeg.get();
|
||||
showRoom(cli, room, {
|
||||
roomAlias,
|
||||
autoJoin,
|
||||
shouldPeek,
|
||||
roomServer: this.state.roomServer,
|
||||
metricsTrigger: "RoomDirectory",
|
||||
};
|
||||
if (room) {
|
||||
// Don't let the user view a room they won't be able to either
|
||||
// peek or join: fail earlier so they don't have to click back
|
||||
// to the directory.
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
if (!room.world_readable && !room.guest_can_join) {
|
||||
dis.dispatch({ action: 'require_registration' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!roomAlias) {
|
||||
roomAlias = getDisplayAliasForRoom(room);
|
||||
}
|
||||
|
||||
payload.oob_data = {
|
||||
avatarUrl: room.avatar_url,
|
||||
// XXX: This logic is duplicated from the JS SDK which
|
||||
// would normally decide what the name is.
|
||||
name: room.name || roomAlias || _t('Unnamed room'),
|
||||
};
|
||||
|
||||
if (this.state.roomServer) {
|
||||
payload.via_servers = [this.state.roomServer];
|
||||
}
|
||||
}
|
||||
// It's not really possible to join Matrix rooms by ID because the HS has no way to know
|
||||
// which servers to start querying. However, there's no other way to join rooms in
|
||||
// this list without aliases at present, so if roomAlias isn't set here we have no
|
||||
// choice but to supply the ID.
|
||||
if (roomAlias) {
|
||||
payload.room_alias = roomAlias;
|
||||
} else {
|
||||
payload.room_id = room.room_id;
|
||||
}
|
||||
dis.dispatch(payload);
|
||||
});
|
||||
};
|
||||
|
||||
private stringLooksLikeId(s: string, fieldType: IFieldType) {
|
||||
let pat = /^#[^\s]+:[^\s]/;
|
||||
if (fieldType && fieldType.regexp) {
|
||||
|
@ -459,27 +402,11 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
return pat.test(s);
|
||||
}
|
||||
|
||||
private getFieldsForThirdPartyLocation(userInput: string, protocol: IProtocol, instance: IInstance) {
|
||||
// make an object with the fields specified by that protocol. We
|
||||
// require that the values of all but the last field come from the
|
||||
// instance. The last is the user input.
|
||||
const requiredFields = protocol.location_fields;
|
||||
if (!requiredFields) return null;
|
||||
const fields = {};
|
||||
for (let i = 0; i < requiredFields.length - 1; ++i) {
|
||||
const thisField = requiredFields[i];
|
||||
if (instance.fields[thisField] === undefined) return null;
|
||||
fields[thisField] = instance.fields[thisField];
|
||||
}
|
||||
fields[requiredFields[requiredFields.length - 1]] = userInput;
|
||||
return fields;
|
||||
}
|
||||
|
||||
private onFinished = () => {
|
||||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
let content;
|
||||
if (this.state.error) {
|
||||
content = this.state.error;
|
||||
|
@ -491,7 +418,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
<PublicRoomTile
|
||||
key={room.room_id}
|
||||
room={room}
|
||||
showRoom={this.showRoom}
|
||||
showRoom={this.onRoomClick}
|
||||
removeFromDirectory={this.removeFromDirectory}
|
||||
/>,
|
||||
);
|
||||
|
@ -571,7 +498,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
let showJoinButton = this.stringLooksLikeId(this.state.filterString, instanceExpectedFieldType);
|
||||
if (protocolName) {
|
||||
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
|
||||
if (this.getFieldsForThirdPartyLocation(
|
||||
if (getFieldsForThirdPartyLocation(
|
||||
this.state.filterString,
|
||||
this.protocols[protocolName],
|
||||
instance,
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { MatrixError } from "matrix-js-sdk/src/http-api";
|
||||
import { IProtocol } from "matrix-js-sdk/src/client";
|
||||
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { instanceForInstanceId } from '../../../utils/DirectoryUtils';
|
||||
|
@ -42,9 +41,7 @@ import UIStore from "../../../stores/UIStore";
|
|||
import { compare } from "../../../utils/strings";
|
||||
import { SnakedObject } from "../../../utils/SnakedObject";
|
||||
import { IConfigOptions } from "../../../IConfigOptions";
|
||||
|
||||
// XXX: We would ideally use a symbol here but we can't since we save this value to localStorage
|
||||
export const ALL_ROOMS = "ALL_ROOMS";
|
||||
import { ALL_ROOMS, Protocols } from "../../../utils/DirectoryUtils";
|
||||
|
||||
const SETTING_NAME = "room_directory_servers";
|
||||
|
||||
|
@ -85,8 +82,6 @@ const validServer = withValidation<undefined, { error?: MatrixError }>({
|
|||
],
|
||||
});
|
||||
|
||||
export type Protocols = Record<string, IProtocol>;
|
||||
|
||||
interface IProps {
|
||||
protocols: Protocols;
|
||||
selectedServerName: string;
|
||||
|
|
|
@ -733,6 +733,13 @@
|
|||
"Common names and surnames are easy to guess": "Common names and surnames are easy to guess",
|
||||
"Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess",
|
||||
"Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess",
|
||||
"Unnamed room": "Unnamed room",
|
||||
"Unable to join network": "Unable to join network",
|
||||
"%(brand)s does not know how to join a room on this network": "%(brand)s does not know how to join a room on this network",
|
||||
"Room not found": "Room not found",
|
||||
"Couldn't find a matching Matrix room": "Couldn't find a matching Matrix room",
|
||||
"Fetching third party location failed": "Fetching third party location failed",
|
||||
"Unable to look up room ID from server": "Unable to look up room ID from server",
|
||||
"Error upgrading room": "Error upgrading room",
|
||||
"Double check that your server supports the room version chosen and try again.": "Double check that your server supports the room version chosen and try again.",
|
||||
"Invite to %(spaceName)s": "Invite to %(spaceName)s",
|
||||
|
@ -1753,7 +1760,6 @@
|
|||
"Idle": "Idle",
|
||||
"Offline": "Offline",
|
||||
"Unknown": "Unknown",
|
||||
"Unnamed room": "Unnamed room",
|
||||
"Preview": "Preview",
|
||||
"View": "View",
|
||||
"Join": "Join",
|
||||
|
@ -3054,12 +3060,6 @@
|
|||
"remove %(name)s from the directory.": "remove %(name)s from the directory.",
|
||||
"delete the address.": "delete the address.",
|
||||
"The server may be unavailable or overloaded": "The server may be unavailable or overloaded",
|
||||
"Unable to join network": "Unable to join network",
|
||||
"%(brand)s does not know how to join a room on this network": "%(brand)s does not know how to join a room on this network",
|
||||
"Room not found": "Room not found",
|
||||
"Couldn't find a matching Matrix room": "Couldn't find a matching Matrix room",
|
||||
"Fetching third party location failed": "Fetching third party location failed",
|
||||
"Unable to look up room ID from server": "Unable to look up room ID from server",
|
||||
"Create new room": "Create new room",
|
||||
"No results for \"%(query)s\"": "No results for \"%(query)s\"",
|
||||
"Try different words or check for typos. Some results may not be visible as they're private and you need an invite to join them.": "Try different words or check for typos. Some results may not be visible as they're private and you need an invite to join them.",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2018 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2018, 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.
|
||||
|
@ -14,9 +14,12 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { IInstance } from "matrix-js-sdk/src/client";
|
||||
import { IInstance, IProtocol } from "matrix-js-sdk/src/client";
|
||||
|
||||
import { Protocols } from "../components/views/directory/NetworkDropdown";
|
||||
// XXX: We would ideally use a symbol here but we can't since we save this value to localStorage
|
||||
export const ALL_ROOMS = "ALL_ROOMS";
|
||||
|
||||
export type Protocols = Record<string, IProtocol>;
|
||||
|
||||
// Find a protocol 'instance' with a given instance_id
|
||||
// in the supplied protocols dict
|
||||
|
|
24
src/utils/error.ts
Normal file
24
src/utils/error.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
export class GenericError extends Error {
|
||||
constructor(
|
||||
public readonly message: string,
|
||||
public readonly description?: string | undefined,
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -14,7 +14,19 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { IInstance, IProtocol, IPublicRoomsChunkRoom, MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { ViewRoom as ViewRoomEvent } from "matrix-analytics-events/types/typescript/ViewRoom";
|
||||
|
||||
import { Action } from "../dispatcher/actions";
|
||||
import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload";
|
||||
import { getE2EEWellKnown } from "./WellKnownUtils";
|
||||
import dis from "../dispatcher/dispatcher";
|
||||
import { getDisplayAliasForAliasSet } from "../Rooms";
|
||||
import { _t } from "../languageHandler";
|
||||
import { instanceForInstanceId, protocolNameForInstanceId } from "./DirectoryUtils";
|
||||
import SdkConfig from "../SdkConfig";
|
||||
import { GenericError } from "./error";
|
||||
import { ALL_ROOMS, Protocols } from "./DirectoryUtils";
|
||||
|
||||
export function privateShouldBeEncrypted(): boolean {
|
||||
const e2eeWellKnown = getE2EEWellKnown();
|
||||
|
@ -24,3 +36,146 @@ export function privateShouldBeEncrypted(): boolean {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
interface IShowRoomOpts {
|
||||
roomAlias?: string;
|
||||
autoJoin?: boolean;
|
||||
shouldPeek?: boolean;
|
||||
roomServer?: string;
|
||||
metricsTrigger: ViewRoomEvent["trigger"];
|
||||
}
|
||||
|
||||
export const showRoom = (
|
||||
client: MatrixClient,
|
||||
room: IPublicRoomsChunkRoom | null,
|
||||
{
|
||||
roomAlias,
|
||||
autoJoin = false,
|
||||
shouldPeek = false,
|
||||
roomServer,
|
||||
}: IShowRoomOpts,
|
||||
): void => {
|
||||
const payload: ViewRoomPayload = {
|
||||
action: Action.ViewRoom,
|
||||
auto_join: autoJoin,
|
||||
should_peek: shouldPeek,
|
||||
metricsTrigger: "RoomDirectory",
|
||||
};
|
||||
if (room) {
|
||||
// Don't let the user view a room they won't be able to either
|
||||
// peek or join: fail earlier so they don't have to click back
|
||||
// to the directory.
|
||||
if (client.isGuest()) {
|
||||
if (!room.world_readable && !room.guest_can_join) {
|
||||
dis.dispatch({ action: 'require_registration' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!roomAlias) {
|
||||
roomAlias = getDisplayAliasForAliasSet(room.canonical_alias, room.aliases);
|
||||
}
|
||||
|
||||
payload.oob_data = {
|
||||
avatarUrl: room.avatar_url,
|
||||
// XXX: This logic is duplicated from the JS SDK which
|
||||
// would normally decide what the name is.
|
||||
name: room.name || roomAlias || _t('Unnamed room'),
|
||||
};
|
||||
|
||||
if (roomServer) {
|
||||
payload.via_servers = [roomServer];
|
||||
}
|
||||
}
|
||||
// It's not really possible to join Matrix rooms by ID because the HS has no way to know
|
||||
// which servers to start querying. However, there's no other way to join rooms in
|
||||
// this list without aliases at present, so if roomAlias isn't set here we have no
|
||||
// choice but to supply the ID.
|
||||
if (roomAlias) {
|
||||
payload.room_alias = roomAlias;
|
||||
} else {
|
||||
payload.room_id = room.room_id;
|
||||
}
|
||||
dis.dispatch(payload);
|
||||
};
|
||||
|
||||
interface IJoinRoomByAliasOpts {
|
||||
instanceId?: string;
|
||||
roomServer?: string;
|
||||
protocols: Protocols;
|
||||
metricsTrigger: ViewRoomEvent["trigger"];
|
||||
}
|
||||
|
||||
export function joinRoomByAlias(cli: MatrixClient, alias: string, {
|
||||
instanceId,
|
||||
roomServer,
|
||||
protocols,
|
||||
metricsTrigger,
|
||||
}: IJoinRoomByAliasOpts): void {
|
||||
// If we don't have a particular instance id selected, just show that rooms alias
|
||||
if (!instanceId || instanceId === ALL_ROOMS) {
|
||||
// If the user specified an alias without a domain, add on whichever server is selected
|
||||
// in the dropdown
|
||||
if (!alias.includes(':')) {
|
||||
alias = alias + ':' + roomServer;
|
||||
}
|
||||
showRoom(cli, null, {
|
||||
roomAlias: alias,
|
||||
autoJoin: true,
|
||||
metricsTrigger,
|
||||
});
|
||||
} else {
|
||||
// This is a 3rd party protocol. Let's see if we can join it
|
||||
const protocolName = protocolNameForInstanceId(protocols, instanceId);
|
||||
const instance = instanceForInstanceId(protocols, instanceId);
|
||||
const fields = protocolName
|
||||
? getFieldsForThirdPartyLocation(alias, protocols[protocolName], instance)
|
||||
: null;
|
||||
if (!fields) {
|
||||
const brand = SdkConfig.get().brand;
|
||||
throw new GenericError(
|
||||
_t('Unable to join network'),
|
||||
_t('%(brand)s does not know how to join a room on this network', { brand }),
|
||||
);
|
||||
}
|
||||
cli.getThirdpartyLocation(protocolName, fields).then((resp) => {
|
||||
if (resp.length > 0 && resp[0].alias) {
|
||||
showRoom(cli, null, {
|
||||
roomAlias: resp[0].alias,
|
||||
autoJoin: true,
|
||||
metricsTrigger,
|
||||
});
|
||||
} else {
|
||||
throw new GenericError(
|
||||
_t('Room not found'),
|
||||
_t('Couldn\'t find a matching Matrix room'),
|
||||
);
|
||||
}
|
||||
}, (e) => {
|
||||
throw new GenericError(
|
||||
_t('Fetching third party location failed'),
|
||||
_t('Unable to look up room ID from server'),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getFieldsForThirdPartyLocation(
|
||||
userInput: string,
|
||||
protocol: IProtocol,
|
||||
instance: IInstance,
|
||||
): { searchFields?: string[] } | null {
|
||||
// make an object with the fields specified by that protocol. We
|
||||
// require that the values of all but the last field come from the
|
||||
// instance. The last is the user input.
|
||||
const requiredFields = protocol.location_fields;
|
||||
if (!requiredFields) return null;
|
||||
const fields = {};
|
||||
for (let i = 0; i < requiredFields.length - 1; ++i) {
|
||||
const thisField = requiredFields[i];
|
||||
if (instance.fields[thisField] === undefined) return null;
|
||||
fields[thisField] = instance.fields[thisField];
|
||||
}
|
||||
fields[requiredFields[requiredFields.length - 1]] = userInput;
|
||||
return fields;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue