From ae490841c6720c5257408a36bfd4816d1d0cfad0 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 27 Jan 2022 09:51:06 +0000 Subject: [PATCH] Look up tile server info in homeserver's .well-known area (#7623) --- .../views/location/LocationPicker.tsx | 39 ++++--- .../views/location/LocationViewDialog.tsx | 16 +++ .../views/messages/MLocationBody.tsx | 110 +++++++++++++----- src/utils/WellKnownUtils.ts | 22 ++++ 4 files changed, 142 insertions(+), 45 deletions(-) diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx index e02879017da..33f91ff7313 100644 --- a/src/components/views/location/LocationPicker.tsx +++ b/src/components/views/location/LocationPicker.tsx @@ -18,8 +18,8 @@ import React, { SyntheticEvent } from 'react'; import maplibregl from 'maplibre-gl'; import { logger } from "matrix-js-sdk/src/logger"; 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 { _t } from '../../../languageHandler'; import { replaceableComponent } from "../../../utils/replaceableComponent"; @@ -27,6 +27,8 @@ import MemberAvatar from '../avatars/MemberAvatar'; import MatrixClientContext from '../../../contexts/MatrixClientContext'; import Modal from '../../../Modal'; import ErrorDialog from '../dialogs/ErrorDialog'; +import { findMapStyleUrl } from '../messages/MLocationBody'; +import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils'; interface IProps { sender: RoomMember; @@ -51,9 +53,9 @@ interface IState { class LocationPicker extends React.Component { public static contextType = MatrixClientContext; public context!: React.ContextType; - private map: maplibregl.Map; - private geolocate: maplibregl.GeolocateControl; - private marker: maplibregl.Marker; + private map?: maplibregl.Map = null; + private geolocate?: maplibregl.GeolocateControl = null; + private marker?: maplibregl.Marker = null; constructor(props: IProps) { super(props); @@ -69,15 +71,16 @@ class LocationPicker extends React.Component { }; componentDidMount() { - const config = SdkConfig.get(); - this.map = new maplibregl.Map({ - container: 'mx_LocationPicker_map', - style: config.map_style_url, - center: [0, 0], - zoom: 1, - }); + this.context.on("WellKnown.client", this.updateStyleUrl); try { + this.map = new maplibregl.Map({ + container: 'mx_LocationPicker_map', + style: findMapStyleUrl(), + center: [0, 0], + zoom: 1, + }); + // Add geolocate control to the map. this.geolocate = new maplibregl.GeolocateControl({ positionOptions: { @@ -124,18 +127,26 @@ class LocationPicker extends React.Component { this.geolocate.on('geolocate', this.onGeolocate); } catch (e) { - logger.error("Failed to render map", e.error); - this.setState({ error: e.error }); + logger.error("Failed to render map", e); + this.setState({ error: e }); } } componentWillUnmount() { 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) => { this.setState({ position }); - this.marker.setLngLat( + this.marker?.setLngLat( new maplibregl.LngLat( position.coords.longitude, position.coords.latitude, diff --git a/src/components/views/location/LocationViewDialog.tsx b/src/components/views/location/LocationViewDialog.tsx index 438f6a5f16a..e09e73aded2 100644 --- a/src/components/views/location/LocationViewDialog.tsx +++ b/src/components/views/location/LocationViewDialog.tsx @@ -16,13 +16,16 @@ limitations under the License. import React from 'react'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; +import { IClientWellKnown, MatrixClient } from 'matrix-js-sdk/src/client'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import BaseDialog from "../dialogs/BaseDialog"; import { IDialogProps } from "../dialogs/IDialogProps"; import { createMap, LocationBodyContent, locationEventGeoUri, parseGeoUri } from '../messages/MLocationBody'; +import { tileServerFromWellKnown } from '../../../utils/WellKnownUtils'; interface IProps extends IDialogProps { + matrixClient: MatrixClient; mxEvent: MatrixEvent; } @@ -50,6 +53,8 @@ export default class LocationViewDialog extends React.Component return; } + this.props.matrixClient.on("WellKnown.client", this.updateStyleUrl); + this.map = createMap( this.coords, true, @@ -59,6 +64,17 @@ export default class LocationViewDialog extends React.Component ); } + 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 = () => { return `mx_LocationViewDialog_${this.props.mxEvent.getId()}`; }; diff --git a/src/components/views/messages/MLocationBody.tsx b/src/components/views/messages/MLocationBody.tsx index a2fcfe09824..9d4c2d13c3e 100644 --- a/src/components/views/messages/MLocationBody.tsx +++ b/src/components/views/messages/MLocationBody.tsx @@ -24,6 +24,7 @@ import { ILocationContent, LOCATION_EVENT_TYPE, } from 'matrix-js-sdk/src/@types/location'; +import { IClientWellKnown } from 'matrix-js-sdk/src/client'; import SdkConfig from '../../../SdkConfig'; import { replaceableComponent } from "../../../utils/replaceableComponent"; @@ -35,6 +36,8 @@ import LocationViewDialog from '../location/LocationViewDialog'; import TooltipTarget from '../elements/TooltipTarget'; import { Alignment } from '../elements/Tooltip'; import AccessibleButton from '../elements/AccessibleButton'; +import { getTileServerWellKnown, tileServerFromWellKnown } from '../../../utils/WellKnownUtils'; +import MatrixClientContext from '../../../contexts/MatrixClientContext'; interface IState { error: Error; @@ -42,9 +45,12 @@ interface IState { @replaceableComponent("views.messages.MLocationBody") export default class MLocationBody extends React.Component { + public static contextType = MatrixClientContext; + public context!: React.ContextType; private coords: GeolocationCoordinates; private bodyId: string; private markerId: string; + private map?: maplibregl.Map = null; constructor(props: IBodyProps) { super(props); @@ -65,7 +71,9 @@ export default class MLocationBody extends React.Component { return; } - createMap( + this.context.on("WellKnown.client", this.updateStyleUrl); + + this.map = createMap( this.coords, false, this.bodyId, @@ -74,6 +82,17 @@ export default class MLocationBody extends React.Component { ); } + 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 = ( event: React.MouseEvent, ) => { @@ -87,7 +106,10 @@ export default class MLocationBody extends React.Component { 'Location View', '', LocationViewDialog, - { mxEvent: this.props.mxEvent }, + { + matrixClient: this.context, + mxEvent: this.props.mxEvent, + }, "mx_LocationViewDialog_wrapper", false, // isPriority true, // isStatic @@ -206,6 +228,27 @@ function ZoomButtons(props: IZoomButtonsProps): React.ReactElement; } +/** + * 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( coords: GeolocationCoordinates, interactive: boolean, @@ -213,35 +256,40 @@ export function createMap( markerId: string, onError: (error: Error) => void, ): maplibregl.Map { - const styleUrl = SdkConfig.get().map_style_url; - const coordinates = new maplibregl.LngLat(coords.longitude, coords.latitude); - - const map = new maplibregl.Map({ - container: bodyId, - style: styleUrl, - center: coordinates, - zoom: 15, - interactive, - }); - - new maplibregl.Marker({ - element: document.getElementById(markerId), - anchor: 'bottom', - offset: [0, -1], - }) - .setLngLat(coordinates) - .addTo(map); - - map.on('error', (e) => { - logger.error( - "Failed to load map: check map_style_url in config.json has a " - + "valid URL and API key", - e.error, - ); - onError(e.error); - }); - - return map; + try { + const styleUrl = findMapStyleUrl(); + const coordinates = new maplibregl.LngLat(coords.longitude, coords.latitude); + + const map = new maplibregl.Map({ + container: bodyId, + style: styleUrl, + center: coordinates, + zoom: 15, + interactive, + }); + + new maplibregl.Marker({ + element: document.getElementById(markerId), + anchor: 'bottom', + offset: [0, -1], + }) + .setLngLat(coordinates) + .addTo(map); + + map.on('error', (e) => { + logger.error( + "Failed to load map: check map_style_url in config.json has a " + + "valid URL and API key", + e.error, + ); + onError(e.error); + }); + + return map; + } catch (e) { + logger.error("Failed to render map", e); + onError(e); + } } /** diff --git a/src/utils/WellKnownUtils.ts b/src/utils/WellKnownUtils.ts index 59307d642ed..cb3e92a7bde 100644 --- a/src/utils/WellKnownUtils.ts +++ b/src/utils/WellKnownUtils.ts @@ -14,11 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { IClientWellKnown } from 'matrix-js-sdk/src/client'; +import { UnstableValue } from 'matrix-js-sdk/src/NamespacedValue'; + import { MatrixClientPeg } from '../MatrixClientPeg'; const CALL_BEHAVIOUR_WK_KEY = "io.element.call_behaviour"; const E2EE_WK_KEY = "io.element.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 */ export interface ICallBehaviourWellKnown { @@ -30,6 +35,10 @@ export interface IE2EEWellKnown { secure_backup_required?: boolean; secure_backup_setup_methods?: SecureBackupSetupMethod[]; } + +export interface ITileServerWellKnown { + map_style_url?: string; +} /* eslint-enable camelcase */ export function getCallBehaviourWellKnown(): ICallBehaviourWellKnown { @@ -48,6 +57,19 @@ export function getE2EEWellKnown(): IE2EEWellKnown { 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 { const wellKnown = getE2EEWellKnown(); return wellKnown && wellKnown["secure_backup_required"] === true;