Look up tile server info in homeserver's .well-known area (#7623)

This commit is contained in:
Andy Balaam 2022-01-27 09:51:06 +00:00 committed by GitHub
parent 20819df60b
commit ae490841c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 138 additions and 41 deletions

View file

@ -18,8 +18,8 @@ import React, { SyntheticEvent } from 'react';
import maplibregl from 'maplibre-gl'; import maplibregl from 'maplibre-gl';
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { RoomMember } from 'matrix-js-sdk/src/models/room-member'; import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import { IClientWellKnown } from 'matrix-js-sdk/src/client';
import SdkConfig from '../../../SdkConfig';
import DialogButtons from "../elements/DialogButtons"; import DialogButtons from "../elements/DialogButtons";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
@ -27,6 +27,8 @@ import MemberAvatar from '../avatars/MemberAvatar';
import MatrixClientContext from '../../../contexts/MatrixClientContext'; import MatrixClientContext from '../../../contexts/MatrixClientContext';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import ErrorDialog from '../dialogs/ErrorDialog'; import ErrorDialog from '../dialogs/ErrorDialog';
import { findMapStyleUrl } from '../messages/MLocationBody';
import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils';
interface IProps { interface IProps {
sender: RoomMember; sender: RoomMember;
@ -51,9 +53,9 @@ interface IState {
class LocationPicker extends React.Component<IProps, IState> { class LocationPicker extends React.Component<IProps, IState> {
public static contextType = MatrixClientContext; public static contextType = MatrixClientContext;
public context!: React.ContextType<typeof MatrixClientContext>; public context!: React.ContextType<typeof MatrixClientContext>;
private map: maplibregl.Map; private map?: maplibregl.Map = null;
private geolocate: maplibregl.GeolocateControl; private geolocate?: maplibregl.GeolocateControl = null;
private marker: maplibregl.Marker; private marker?: maplibregl.Marker = null;
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
@ -69,15 +71,16 @@ class LocationPicker extends React.Component<IProps, IState> {
}; };
componentDidMount() { componentDidMount() {
const config = SdkConfig.get(); this.context.on("WellKnown.client", this.updateStyleUrl);
this.map = new maplibregl.Map({
container: 'mx_LocationPicker_map',
style: config.map_style_url,
center: [0, 0],
zoom: 1,
});
try { try {
this.map = new maplibregl.Map({
container: 'mx_LocationPicker_map',
style: findMapStyleUrl(),
center: [0, 0],
zoom: 1,
});
// Add geolocate control to the map. // Add geolocate control to the map.
this.geolocate = new maplibregl.GeolocateControl({ this.geolocate = new maplibregl.GeolocateControl({
positionOptions: { positionOptions: {
@ -124,18 +127,26 @@ class LocationPicker extends React.Component<IProps, IState> {
this.geolocate.on('geolocate', this.onGeolocate); this.geolocate.on('geolocate', this.onGeolocate);
} catch (e) { } catch (e) {
logger.error("Failed to render map", e.error); logger.error("Failed to render map", e);
this.setState({ error: e.error }); this.setState({ error: e });
} }
} }
componentWillUnmount() { componentWillUnmount() {
this.geolocate?.off('geolocate', this.onGeolocate); this.geolocate?.off('geolocate', this.onGeolocate);
this.context.off("WellKnown.client", this.updateStyleUrl);
} }
private updateStyleUrl = (clientWellKnown: IClientWellKnown) => {
const style = tileServerFromWellKnown(clientWellKnown)?.["map_style_url"];
if (style) {
this.map?.setStyle(style);
}
};
private onGeolocate = (position: GeolocationPosition) => { private onGeolocate = (position: GeolocationPosition) => {
this.setState({ position }); this.setState({ position });
this.marker.setLngLat( this.marker?.setLngLat(
new maplibregl.LngLat( new maplibregl.LngLat(
position.coords.longitude, position.coords.longitude,
position.coords.latitude, position.coords.latitude,

View file

@ -16,13 +16,16 @@ limitations under the License.
import React from 'react'; import React from 'react';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { IClientWellKnown, MatrixClient } from 'matrix-js-sdk/src/client';
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import BaseDialog from "../dialogs/BaseDialog"; import BaseDialog from "../dialogs/BaseDialog";
import { IDialogProps } from "../dialogs/IDialogProps"; import { IDialogProps } from "../dialogs/IDialogProps";
import { createMap, LocationBodyContent, locationEventGeoUri, parseGeoUri } from '../messages/MLocationBody'; import { createMap, LocationBodyContent, locationEventGeoUri, parseGeoUri } from '../messages/MLocationBody';
import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils';
interface IProps extends IDialogProps { interface IProps extends IDialogProps {
matrixClient: MatrixClient;
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
} }
@ -50,6 +53,8 @@ export default class LocationViewDialog extends React.Component<IProps, IState>
return; return;
} }
this.props.matrixClient.on("WellKnown.client", this.updateStyleUrl);
this.map = createMap( this.map = createMap(
this.coords, this.coords,
true, true,
@ -59,6 +64,17 @@ export default class LocationViewDialog extends React.Component<IProps, IState>
); );
} }
componentWillUnmount() {
this.props.matrixClient.off("WellKnown.client", this.updateStyleUrl);
}
private updateStyleUrl = (clientWellKnown: IClientWellKnown) => {
const style = tileServerFromWellKnown(clientWellKnown)?.["map_style_url"];
if (style) {
this.map?.setStyle(style);
}
};
private getBodyId = () => { private getBodyId = () => {
return `mx_LocationViewDialog_${this.props.mxEvent.getId()}`; return `mx_LocationViewDialog_${this.props.mxEvent.getId()}`;
}; };

View file

@ -24,6 +24,7 @@ import {
ILocationContent, ILocationContent,
LOCATION_EVENT_TYPE, LOCATION_EVENT_TYPE,
} from 'matrix-js-sdk/src/@types/location'; } from 'matrix-js-sdk/src/@types/location';
import { IClientWellKnown } from 'matrix-js-sdk/src/client';
import SdkConfig from '../../../SdkConfig'; import SdkConfig from '../../../SdkConfig';
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
@ -35,6 +36,8 @@ import LocationViewDialog from '../location/LocationViewDialog';
import TooltipTarget from '../elements/TooltipTarget'; import TooltipTarget from '../elements/TooltipTarget';
import { Alignment } from '../elements/Tooltip'; import { Alignment } from '../elements/Tooltip';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import { getTileServerWellKnown, tileServerFromWellKnown } from '../../../utils/WellKnownUtils';
import MatrixClientContext from '../../../contexts/MatrixClientContext';
interface IState { interface IState {
error: Error; error: Error;
@ -42,9 +45,12 @@ interface IState {
@replaceableComponent("views.messages.MLocationBody") @replaceableComponent("views.messages.MLocationBody")
export default class MLocationBody extends React.Component<IBodyProps, IState> { export default class MLocationBody extends React.Component<IBodyProps, IState> {
public static contextType = MatrixClientContext;
public context!: React.ContextType<typeof MatrixClientContext>;
private coords: GeolocationCoordinates; private coords: GeolocationCoordinates;
private bodyId: string; private bodyId: string;
private markerId: string; private markerId: string;
private map?: maplibregl.Map = null;
constructor(props: IBodyProps) { constructor(props: IBodyProps) {
super(props); super(props);
@ -65,7 +71,9 @@ export default class MLocationBody extends React.Component<IBodyProps, IState> {
return; return;
} }
createMap( this.context.on("WellKnown.client", this.updateStyleUrl);
this.map = createMap(
this.coords, this.coords,
false, false,
this.bodyId, this.bodyId,
@ -74,6 +82,17 @@ export default class MLocationBody extends React.Component<IBodyProps, IState> {
); );
} }
componentWillUnmount() {
this.context.off("WellKnown.client", this.updateStyleUrl);
}
private updateStyleUrl = (clientWellKnown: IClientWellKnown) => {
const style = tileServerFromWellKnown(clientWellKnown)?.["map_style_url"];
if (style) {
this.map?.setStyle(style);
}
};
private onClick = ( private onClick = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>, event: React.MouseEvent<HTMLDivElement, MouseEvent>,
) => { ) => {
@ -87,7 +106,10 @@ export default class MLocationBody extends React.Component<IBodyProps, IState> {
'Location View', 'Location View',
'', '',
LocationViewDialog, LocationViewDialog,
{ mxEvent: this.props.mxEvent }, {
matrixClient: this.context,
mxEvent: this.props.mxEvent,
},
"mx_LocationViewDialog_wrapper", "mx_LocationViewDialog_wrapper",
false, // isPriority false, // isPriority
true, // isStatic true, // isStatic
@ -206,6 +228,27 @@ function ZoomButtons(props: IZoomButtonsProps): React.ReactElement<HTMLDivElemen
</div>; </div>;
} }
/**
* Look up what map tile server style URL was provided in the homeserver's
* .well-known location, or, failing that, in our local config, or, failing
* that, defaults to the same tile server listed by matrix.org.
*/
export function findMapStyleUrl(): string {
const mapStyleUrl = (
getTileServerWellKnown()?.map_style_url ??
SdkConfig.get().map_style_url
);
if (!mapStyleUrl) {
throw new Error(
"'map_style_url' missing from homeserver .well-known area, and " +
"missing from from config.json.",
);
}
return mapStyleUrl;
}
export function createMap( export function createMap(
coords: GeolocationCoordinates, coords: GeolocationCoordinates,
interactive: boolean, interactive: boolean,
@ -213,35 +256,40 @@ export function createMap(
markerId: string, markerId: string,
onError: (error: Error) => void, onError: (error: Error) => void,
): maplibregl.Map { ): maplibregl.Map {
const styleUrl = SdkConfig.get().map_style_url; try {
const coordinates = new maplibregl.LngLat(coords.longitude, coords.latitude); const styleUrl = findMapStyleUrl();
const coordinates = new maplibregl.LngLat(coords.longitude, coords.latitude);
const map = new maplibregl.Map({ const map = new maplibregl.Map({
container: bodyId, container: bodyId,
style: styleUrl, style: styleUrl,
center: coordinates, center: coordinates,
zoom: 15, zoom: 15,
interactive, interactive,
}); });
new maplibregl.Marker({ new maplibregl.Marker({
element: document.getElementById(markerId), element: document.getElementById(markerId),
anchor: 'bottom', anchor: 'bottom',
offset: [0, -1], offset: [0, -1],
}) })
.setLngLat(coordinates) .setLngLat(coordinates)
.addTo(map); .addTo(map);
map.on('error', (e) => { map.on('error', (e) => {
logger.error( logger.error(
"Failed to load map: check map_style_url in config.json has a " "Failed to load map: check map_style_url in config.json has a "
+ "valid URL and API key", + "valid URL and API key",
e.error, e.error,
); );
onError(e.error); onError(e.error);
}); });
return map; return map;
} catch (e) {
logger.error("Failed to render map", e);
onError(e);
}
} }
/** /**

View file

@ -14,11 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { IClientWellKnown } from 'matrix-js-sdk/src/client';
import { UnstableValue } from 'matrix-js-sdk/src/NamespacedValue';
import { MatrixClientPeg } from '../MatrixClientPeg'; import { MatrixClientPeg } from '../MatrixClientPeg';
const CALL_BEHAVIOUR_WK_KEY = "io.element.call_behaviour"; const CALL_BEHAVIOUR_WK_KEY = "io.element.call_behaviour";
const E2EE_WK_KEY = "io.element.e2ee"; const E2EE_WK_KEY = "io.element.e2ee";
const E2EE_WK_KEY_DEPRECATED = "im.vector.riot.e2ee"; const E2EE_WK_KEY_DEPRECATED = "im.vector.riot.e2ee";
const TILE_SERVER_WK_KEY = new UnstableValue(
"m.tile_server", "org.matrix.msc3488.tile_server");
/* eslint-disable camelcase */ /* eslint-disable camelcase */
export interface ICallBehaviourWellKnown { export interface ICallBehaviourWellKnown {
@ -30,6 +35,10 @@ export interface IE2EEWellKnown {
secure_backup_required?: boolean; secure_backup_required?: boolean;
secure_backup_setup_methods?: SecureBackupSetupMethod[]; secure_backup_setup_methods?: SecureBackupSetupMethod[];
} }
export interface ITileServerWellKnown {
map_style_url?: string;
}
/* eslint-enable camelcase */ /* eslint-enable camelcase */
export function getCallBehaviourWellKnown(): ICallBehaviourWellKnown { export function getCallBehaviourWellKnown(): ICallBehaviourWellKnown {
@ -48,6 +57,19 @@ export function getE2EEWellKnown(): IE2EEWellKnown {
return null; return null;
} }
export function getTileServerWellKnown(): ITileServerWellKnown | undefined {
return tileServerFromWellKnown(MatrixClientPeg.get().getClientWellKnown());
}
export function tileServerFromWellKnown(
clientWellKnown?: IClientWellKnown | undefined,
): ITileServerWellKnown {
return (
clientWellKnown?.[TILE_SERVER_WK_KEY.name] ??
clientWellKnown?.[TILE_SERVER_WK_KEY.altName]
);
}
export function isSecureBackupRequired(): boolean { export function isSecureBackupRequired(): boolean {
const wellKnown = getE2EEWellKnown(); const wellKnown = getE2EEWellKnown();
return wellKnown && wellKnown["secure_backup_required"] === true; return wellKnown && wellKnown["secure_backup_required"] === true;