Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 32 additions & 105 deletions src/components/structures/RoomDirectory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -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 }),
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,
});
Comment on lines +364 to 367
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe utils/error.ts should offer this as showErrorModal()? I assume this could become a common pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we can do this as the .ts files should not have anything to do with UI according to our new style guide.
So i guess we should probably have some UI helpers?

return;
} else {
throw e;
}
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'),
});
});
}
};

Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -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}
/>,
);
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 1 addition & 6 deletions src/components/views/directory/NetworkDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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";

Expand Down Expand Up @@ -85,8 +82,6 @@ const validServer = withValidation<undefined, { error?: MatrixError }>({
],
});

export type Protocols = Record<string, IProtocol>;

interface IProps {
protocols: Protocols;
selectedServerName: string;
Expand Down
14 changes: 7 additions & 7 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -1742,7 +1749,6 @@
"Idle": "Idle",
"Offline": "Offline",
"Unknown": "Unknown",
"Unnamed room": "Unnamed room",
"Preview": "Preview",
"View": "View",
"Join": "Join",
Expand Down Expand Up @@ -3033,12 +3039,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.",
Expand Down
9 changes: 6 additions & 3 deletions src/utils/DirectoryUtils.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand Down
24 changes: 24 additions & 0 deletions src/utils/error.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading