diff --git a/src/components/views/room_settings/RelatedGroupSettings.js b/src/components/views/room_settings/RelatedGroupSettings.js
index f82e238722..272ecd1228 100644
--- a/src/components/views/room_settings/RelatedGroupSettings.js
+++ b/src/components/views/room_settings/RelatedGroupSettings.js
@@ -16,7 +16,7 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
-import {MatrixEvent} from 'matrix-js-sdk';
+import {MatrixEvent} from 'matrix-js-sdk/src/models/event';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';
diff --git a/src/components/views/room_settings/RoomProfileSettings.js b/src/components/views/room_settings/RoomProfileSettings.js
index 563368384b..3dbe2b2b7f 100644
--- a/src/components/views/room_settings/RoomProfileSettings.js
+++ b/src/components/views/room_settings/RoomProfileSettings.js
@@ -21,6 +21,7 @@ import {MatrixClientPeg} from "../../../MatrixClientPeg";
import Field from "../elements/Field";
import * as sdk from "../../../index";
import {replaceableComponent} from "../../../utils/replaceableComponent";
+import {mediaFromMxc} from "../../../customisations/Media";
// TODO: Merge with ProfileSettings?
@replaceableComponent("views.room_settings.RoomProfileSettings")
@@ -38,7 +39,7 @@ export default class RoomProfileSettings extends React.Component {
const avatarEvent = room.currentState.getStateEvents("m.room.avatar", "");
let avatarUrl = avatarEvent && avatarEvent.getContent() ? avatarEvent.getContent()["url"] : null;
- if (avatarUrl) avatarUrl = client.mxcUrlToHttp(avatarUrl, 96, 96, 'crop', false);
+ if (avatarUrl) avatarUrl = mediaFromMxc(avatarUrl).getSquareThumbnailHttp(96);
const topicEvent = room.currentState.getStateEvents("m.room.topic", "");
const topic = topicEvent && topicEvent.getContent() ? topicEvent.getContent()['topic'] : '';
@@ -112,7 +113,7 @@ export default class RoomProfileSettings extends React.Component {
if (this.state.avatarFile) {
const uri = await client.uploadContent(this.state.avatarFile);
await client.sendStateEvent(this.props.roomId, 'm.room.avatar', {url: uri}, '');
- newState.avatarUrl = client.mxcUrlToHttp(uri, 96, 96, 'crop', false);
+ newState.avatarUrl = mediaFromMxc(uri).getSquareThumbnailHttp(96);
newState.originalAvatarUrl = newState.avatarUrl;
newState.avatarFile = null;
} else if (this.state.originalAvatarUrl !== this.state.avatarUrl) {
diff --git a/src/components/views/rooms/EditMessageComposer.js b/src/components/views/rooms/EditMessageComposer.js
index 6ecb2bd549..be04a50798 100644
--- a/src/components/views/rooms/EditMessageComposer.js
+++ b/src/components/views/rooms/EditMessageComposer.js
@@ -27,7 +27,7 @@ import {parseEvent} from '../../../editor/deserialize';
import {PartCreator} from '../../../editor/parts';
import EditorStateTransfer from '../../../utils/EditorStateTransfer';
import classNames from 'classnames';
-import {EventStatus} from 'matrix-js-sdk';
+import {EventStatus} from 'matrix-js-sdk/src/models/event';
import BasicMessageComposer from "./BasicMessageComposer";
import {Key, isOnlyCtrlOrCmdKeyEvent} from "../../../Keyboard";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js
index cbe3252c2b..644d64d322 100644
--- a/src/components/views/rooms/EventTile.js
+++ b/src/components/views/rooms/EventTile.js
@@ -28,7 +28,7 @@ import * as sdk from "../../../index";
import dis from '../../../dispatcher/dispatcher';
import SettingsStore from "../../../settings/SettingsStore";
import {Layout, LayoutPropType} from "../../../settings/Layout";
-import {EventStatus} from 'matrix-js-sdk';
+import {EventStatus} from 'matrix-js-sdk/src/models/event';
import {formatTime} from "../../../DateUtils";
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {ALL_RULE_TYPES} from "../../../mjolnir/BanList";
diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js
index 39c9f0bcf7..536abf57fc 100644
--- a/src/components/views/rooms/LinkPreviewWidget.js
+++ b/src/components/views/rooms/LinkPreviewWidget.js
@@ -26,6 +26,7 @@ import Modal from "../../../Modal";
import * as ImageUtils from "../../../ImageUtils";
import { _t } from "../../../languageHandler";
import {replaceableComponent} from "../../../utils/replaceableComponent";
+import {mediaFromMxc} from "../../../customisations/Media";
@replaceableComponent("views.rooms.LinkPreviewWidget")
export default class LinkPreviewWidget extends React.Component {
@@ -83,7 +84,7 @@ export default class LinkPreviewWidget extends React.Component {
let src = p["og:image"];
if (src && src.startsWith("mxc://")) {
- src = MatrixClientPeg.get().mxcUrlToHttp(src);
+ src = mediaFromMxc(src).srcHttp;
}
const params = {
@@ -109,9 +110,11 @@ export default class LinkPreviewWidget extends React.Component {
if (!SettingsStore.getValue("showImages")) {
image = null; // Don't render a button to show the image, just hide it outright
}
- const imageMaxWidth = 100; const imageMaxHeight = 100;
+ const imageMaxWidth = 100;
+ const imageMaxHeight = 100;
if (image && image.startsWith("mxc://")) {
- image = MatrixClientPeg.get().mxcUrlToHttp(image, imageMaxWidth, imageMaxHeight);
+ // We deliberately don't want a square here, so use the source HTTP thumbnail function
+ image = mediaFromMxc(image).getThumbnailOfSourceHttp(imageMaxWidth, imageMaxHeight, 'scale');
}
let thumbHeight = imageMaxHeight;
diff --git a/src/components/views/rooms/RoomDetailRow.js b/src/components/views/rooms/RoomDetailRow.js
index e7c259cd98..62960930f2 100644
--- a/src/components/views/rooms/RoomDetailRow.js
+++ b/src/components/views/rooms/RoomDetailRow.js
@@ -18,10 +18,9 @@ import * as sdk from '../../../index';
import React, {createRef} from 'react';
import { _t } from '../../../languageHandler';
import { linkifyElement } from '../../../HtmlUtils';
-import {MatrixClientPeg} from '../../../MatrixClientPeg';
import PropTypes from 'prop-types';
-import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
import {replaceableComponent} from "../../../utils/replaceableComponent";
+import {mediaFromMxc} from "../../../customisations/Media";
export function getDisplayAliasForRoom(room) {
return room.canonicalAlias || (room.aliases ? room.aliases[0] : "");
@@ -100,13 +99,14 @@ export default class RoomDetailRow extends React.Component {
{ guestJoin }
) :
+ url={avatarUrl} />
|
{ name }
diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx
index 07de70fe45..79db460275 100644
--- a/src/components/views/rooms/RoomTile.tsx
+++ b/src/components/views/rooms/RoomTile.tsx
@@ -333,6 +333,17 @@ export default class RoomTile extends React.PureComponent {
this.setState({generalMenuPosition: null}); // hide the menu
};
+ private onInviteClick = (ev: ButtonEvent) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ dis.dispatch({
+ action: 'view_invite',
+ roomId: this.props.room.roomId,
+ });
+ this.setState({generalMenuPosition: null}); // hide the menu
+ };
+
private async saveNotifState(ev: ButtonEvent, newState: Volume) {
ev.preventDefault();
ev.stopPropagation();
@@ -453,6 +464,8 @@ export default class RoomTile extends React.PureComponent {
const isLowPriority = roomTags.includes(DefaultTagID.LowPriority);
const lowPriorityLabel = _t("Low Priority");
+ const userId = MatrixClientPeg.get().getUserId();
+ const canInvite = this.props.room.canInvite(userId);
contextMenu = {
label={lowPriorityLabel}
iconClassName="mx_RoomTile_iconArrowDown"
/>
-
+ {canInvite ? (
+
+ ) : null}
{
let networkIcon;
if (protocol.avatar_url) {
- const avatarUrl = getHttpUriForMxc(
- MatrixClientPeg.get().getHomeserverUrl(),
- protocol.avatar_url, 64, 64, "crop",
- );
+ const avatarUrl = mediaFromMxc(protocol.avatar_url).getSquareThumbnailHttp(64);
networkIcon = ;
}
interface IItemState {
@@ -299,7 +300,8 @@ export class SpaceItem extends React.PureComponent {
const isNarrow = this.props.isPanelCollapsed;
const collapsed = this.state.collapsed || forceCollapsed;
- const childSpaces = SpaceStore.instance.getChildSpaces(space.roomId);
+ const childSpaces = SpaceStore.instance.getChildSpaces(space.roomId)
+ .filter(s => !this.props.parents?.has(s.roomId));
const isActive = activeSpaces.includes(space);
const itemClasses = classNames({
"mx_SpaceItem": true,
@@ -312,11 +314,17 @@ export class SpaceItem extends React.PureComponent {
mx_SpaceButton_narrow: isNarrow,
});
const notificationState = SpaceStore.instance.getNotificationState(space.roomId);
- const childItems = childSpaces && !collapsed ? : null;
+
+ let childItems;
+ if (childSpaces && !collapsed) {
+ childItems = ;
+ }
+
let notifBadge;
if (notificationState) {
notifBadge =
@@ -383,12 +391,14 @@ interface ITreeLevelProps {
spaces: Room[];
activeSpaces: Room[];
isNested?: boolean;
+ parents: Set ;
}
const SpaceTreeLevel: React.FC = ({
spaces,
activeSpaces,
isNested,
+ parents,
}) => {
return
{spaces.map(s => {
@@ -397,6 +407,7 @@ const SpaceTreeLevel: React.FC = ({
activeSpaces={activeSpaces}
space={s}
isNested={isNested}
+ parents={parents}
/>);
})}
;
diff --git a/src/customisations/Media.ts b/src/customisations/Media.ts
new file mode 100644
index 0000000000..f262179f3d
--- /dev/null
+++ b/src/customisations/Media.ts
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2021 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.
+ */
+
+import {MatrixClientPeg} from "../MatrixClientPeg";
+import {IMediaEventContent, IPreparedMedia, prepEventContentAsMedia} from "./models/IMediaEventContent";
+import {ResizeMethod} from "../Avatar";
+
+// Populate this class with the details of your customisations when copying it.
+
+// Implementation note: The Media class must complete the contract as shown here, though
+// the constructor can be whatever is relevant to your implementation. The mediaForX
+// functions below create an instance of the Media class and are used throughout the
+// project.
+
+/**
+ * A media object is a representation of a "source media" and an optional
+ * "thumbnail media", derived from event contents or external sources.
+ */
+export class Media {
+ // Per above, this constructor signature can be whatever is helpful for you.
+ constructor(private prepared: IPreparedMedia) {
+ }
+
+ /**
+ * True if the media appears to be encrypted. Actual file contents may vary.
+ */
+ public get isEncrypted(): boolean {
+ return !!this.prepared.file;
+ }
+
+ /**
+ * The MXC URI of the source media.
+ */
+ public get srcMxc(): string {
+ return this.prepared.mxc;
+ }
+
+ /**
+ * The MXC URI of the thumbnail media, if a thumbnail is recorded. Null/undefined
+ * otherwise.
+ */
+ public get thumbnailMxc(): string | undefined | null {
+ return this.prepared.thumbnail?.mxc;
+ }
+
+ /**
+ * Whether or not a thumbnail is recorded for this media.
+ */
+ public get hasThumbnail(): boolean {
+ return !!this.thumbnailMxc;
+ }
+
+ /**
+ * The HTTP URL for the source media.
+ */
+ public get srcHttp(): string {
+ return MatrixClientPeg.get().mxcUrlToHttp(this.srcMxc);
+ }
+
+ /**
+ * The HTTP URL for the thumbnail media (without any specified width, height, etc). Null/undefined
+ * if no thumbnail media recorded.
+ */
+ public get thumbnailHttp(): string | undefined | null {
+ if (!this.hasThumbnail) return null;
+ return MatrixClientPeg.get().mxcUrlToHttp(this.thumbnailMxc);
+ }
+
+ /**
+ * Gets the HTTP URL for the thumbnail media with the requested characteristics, if a thumbnail
+ * is recorded for this media. Returns null/undefined otherwise.
+ * @param {number} width The desired width of the thumbnail.
+ * @param {number} height The desired height of the thumbnail.
+ * @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale.
+ * @returns {string} The HTTP URL which points to the thumbnail.
+ */
+ public getThumbnailHttp(width: number, height: number, mode: ResizeMethod = "scale"): string | null | undefined {
+ if (!this.hasThumbnail) return null;
+ return MatrixClientPeg.get().mxcUrlToHttp(this.thumbnailMxc, width, height, mode);
+ }
+
+ /**
+ * Gets the HTTP URL for a thumbnail of the source media with the requested characteristics.
+ * @param {number} width The desired width of the thumbnail.
+ * @param {number} height The desired height of the thumbnail.
+ * @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale.
+ * @returns {string} The HTTP URL which points to the thumbnail.
+ */
+ public getThumbnailOfSourceHttp(width: number, height: number, mode: ResizeMethod = "scale"): string {
+ return MatrixClientPeg.get().mxcUrlToHttp(this.srcMxc, width, height, mode);
+ }
+
+ /**
+ * Creates a square thumbnail of the media. If the media has a thumbnail recorded, that MXC will
+ * be used, otherwise the source media will be used.
+ * @param {number} dim The desired width and height.
+ * @returns {string} An HTTP URL for the thumbnail.
+ */
+ public getSquareThumbnailHttp(dim: number): string {
+ if (this.hasThumbnail) {
+ return this.getThumbnailHttp(dim, dim, 'crop');
+ }
+ return this.getThumbnailOfSourceHttp(dim, dim, 'crop');
+ }
+
+ /**
+ * Downloads the source media.
+ * @returns {Promise} Resolves to the server's response for chaining.
+ */
+ public downloadSource(): Promise {
+ return fetch(this.srcHttp);
+ }
+}
+
+/**
+ * Creates a media object from event content.
+ * @param {IMediaEventContent} content The event content.
+ * @returns {Media} The media object.
+ */
+export function mediaFromContent(content: IMediaEventContent): Media {
+ return new Media(prepEventContentAsMedia(content));
+}
+
+/**
+ * Creates a media object from an MXC URI.
+ * @param {string} mxc The MXC URI.
+ * @returns {Media} The media object.
+ */
+export function mediaFromMxc(mxc: string): Media {
+ return mediaFromContent({url: mxc});
+}
diff --git a/src/customisations/models/IMediaEventContent.ts b/src/customisations/models/IMediaEventContent.ts
new file mode 100644
index 0000000000..fb05d76a4d
--- /dev/null
+++ b/src/customisations/models/IMediaEventContent.ts
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2021 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.
+ */
+
+// TODO: These types should be elsewhere.
+
+export interface IEncryptedFile {
+ url: string;
+ mimetype?: string;
+ key: {
+ alg: string;
+ key_ops: string[]; // eslint-disable-line camelcase
+ kty: string;
+ k: string;
+ ext: boolean;
+ };
+ iv: string;
+ hashes: {[alg: string]: string};
+ v: string;
+}
+
+export interface IMediaEventContent {
+ url?: string; // required on unencrypted media
+ file?: IEncryptedFile; // required for *encrypted* media
+ info?: {
+ thumbnail_url?: string; // eslint-disable-line camelcase
+ thumbnail_file?: IEncryptedFile; // eslint-disable-line camelcase
+ };
+}
+
+export interface IPreparedMedia extends IMediaObject {
+ thumbnail?: IMediaObject;
+}
+
+export interface IMediaObject {
+ mxc: string;
+ file?: IEncryptedFile;
+}
+
+/**
+ * Parses an event content body into a prepared media object. This prepared media object
+ * can be used with other functions to manipulate the media.
+ * @param {IMediaEventContent} content Unredacted media event content. See interface.
+ * @returns {IPreparedMedia} A prepared media object.
+ * @throws Throws if the given content cannot be packaged into a prepared media object.
+ */
+export function prepEventContentAsMedia(content: IMediaEventContent): IPreparedMedia {
+ let thumbnail: IMediaObject = null;
+ if (content?.info?.thumbnail_url) {
+ thumbnail = {
+ mxc: content.info.thumbnail_url,
+ file: content.info.thumbnail_file,
+ };
+ } else if (content?.info?.thumbnail_file?.url) {
+ thumbnail = {
+ mxc: content.info.thumbnail_file.url,
+ file: content.info.thumbnail_file,
+ };
+ }
+
+ if (content?.url) {
+ return {
+ thumbnail,
+ mxc: content.url,
+ file: content.file,
+ };
+ } else if (content?.file?.url) {
+ return {
+ thumbnail,
+ mxc: content.file.url,
+ file: content.file,
+ };
+ }
+
+ throw new Error("Invalid file provided: cannot determine MXC URI. Has it been redacted?");
+}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 63449eb99f..63b19831bb 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1601,6 +1601,7 @@
"Favourited": "Favourited",
"Favourite": "Favourite",
"Low Priority": "Low Priority",
+ "Invite People": "Invite People",
"Leave Room": "Leave Room",
"Room options": "Room options",
"%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.",
@@ -2150,10 +2151,10 @@
"Add comment": "Add comment",
"Comment": "Comment",
"There are two ways you can provide feedback and help us improve %(brand)s.": "There are two ways you can provide feedback and help us improve %(brand)s.",
+ "PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.": "PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.",
"Feedback": "Feedback",
"Report a bug": "Report a bug",
"Please view existing bugs on Github first. No match? Start a new one.": "Please view existing bugs on Github first. No match? Start a new one.",
- "PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.": "PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.",
"Send feedback": "Send feedback",
"Confirm abort of host creation": "Confirm abort of host creation",
"Are you sure you wish to abort creation of the host? The process cannot be continued.": "Are you sure you wish to abort creation of the host? The process cannot be continued.",
@@ -2201,6 +2202,7 @@
"Go": "Go",
"Invite to %(spaceName)s": "Invite to %(spaceName)s",
"Unnamed Space": "Unnamed Space",
+ "Invite to %(roomName)s": "Invite to %(roomName)s",
"Invite someone using their name, email address, username (like ) or share this space.": "Invite someone using their name, email address, username (like ) or share this space.",
"Invite someone using their name, username (like ) or share this space.": "Invite someone using their name, username (like ) or share this space.",
"Invite someone using their name, email address, username (like ) or share this room.": "Invite someone using their name, email address, username (like ) or share this room.",
@@ -2269,8 +2271,9 @@
"Automatically invite users": "Automatically invite users",
"Upgrade private room": "Upgrade private room",
"Upgrade public room": "Upgrade public room",
- "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.",
+ "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.": "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.",
"This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.": "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.",
+ "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.": "Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.",
"You'll upgrade this room from to .": "You'll upgrade this room from to .",
"Resend": "Resend",
"You're all caught up.": "You're all caught up.",
@@ -2550,6 +2553,8 @@
"Logout": "Logout",
"%(creator)s created this DM.": "%(creator)s created this DM.",
"%(creator)s created and configured the room.": "%(creator)s created and configured the room.",
+ "%(count)s messages deleted.|other": "%(count)s messages deleted.",
+ "%(count)s messages deleted.|one": "%(count)s message deleted.",
"Your Communities": "Your Communities",
"Did you know: you can use communities to filter your %(brand)s experience!": "Did you know: you can use communities to filter your %(brand)s experience!",
"To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.",
diff --git a/src/index.js b/src/index.js
index 008e15ad90..e360c04f4f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -28,3 +28,7 @@ export function resetSkin() {
export function getComponent(componentName) {
return Skinner.getComponent(componentName);
}
+
+// Import the js-sdk so the proper `request` object can be set. This does some
+// magic with the browser injection to make all subsequent imports work fine.
+import "matrix-js-sdk";
diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js
index fa263a2a55..2dcdb9e3a3 100644
--- a/src/indexing/EventIndex.js
+++ b/src/indexing/EventIndex.js
@@ -16,7 +16,8 @@ limitations under the License.
import PlatformPeg from "../PlatformPeg";
import {MatrixClientPeg} from "../MatrixClientPeg";
-import {EventTimeline, RoomMember} from 'matrix-js-sdk';
+import {RoomMember} from 'matrix-js-sdk/src/models/room-member';
+import {EventTimeline} from 'matrix-js-sdk/src/models/event-timeline';
import {sleep} from "../utils/promise";
import SettingsStore from "../settings/SettingsStore";
import {EventEmitter} from "events";
diff --git a/src/rageshake/rageshake.js b/src/rageshake/rageshake.js
index 8eb77bb3ae..b886f369df 100644
--- a/src/rageshake/rageshake.js
+++ b/src/rageshake/rageshake.js
@@ -434,15 +434,37 @@ function selectQuery(store, keyRange, resultMapper) {
/**
* Configure rage shaking support for sending bug reports.
* Modifies globals.
+ * @param {boolean} setUpPersistence When true (default), the persistence will
+ * be set up immediately for the logs.
* @return {Promise} Resolves when set up.
*/
-export function init() {
+export function init(setUpPersistence = true) {
if (global.mx_rage_initPromise) {
return global.mx_rage_initPromise;
}
global.mx_rage_logger = new ConsoleLogger();
global.mx_rage_logger.monkeyPatch(window.console);
+ if (setUpPersistence) {
+ return tryInitStorage();
+ }
+
+ global.mx_rage_initPromise = Promise.resolve();
+ return global.mx_rage_initPromise;
+}
+
+/**
+ * Try to start up the rageshake storage for logs. If not possible (client unsupported)
+ * then this no-ops.
+ * @return {Promise} Resolves when complete.
+ */
+export function tryInitStorage() {
+ if (global.mx_rage_initStoragePromise) {
+ return global.mx_rage_initStoragePromise;
+ }
+
+ console.log("Configuring rageshake persistence...");
+
// just *accessing* indexedDB throws an exception in firefox with
// indexeddb disabled.
let indexedDB;
@@ -452,11 +474,11 @@ export function init() {
if (indexedDB) {
global.mx_rage_store = new IndexedDBLogStore(indexedDB, global.mx_rage_logger);
- global.mx_rage_initPromise = global.mx_rage_store.connect();
- return global.mx_rage_initPromise;
+ global.mx_rage_initStoragePromise = global.mx_rage_store.connect();
+ return global.mx_rage_initStoragePromise;
}
- global.mx_rage_initPromise = Promise.resolve();
- return global.mx_rage_initPromise;
+ global.mx_rage_initStoragePromise = Promise.resolve();
+ return global.mx_rage_initStoragePromise;
}
export function flush() {
diff --git a/src/stores/OwnProfileStore.ts b/src/stores/OwnProfileStore.ts
index 8983380fec..bb45456f1e 100644
--- a/src/stores/OwnProfileStore.ts
+++ b/src/stores/OwnProfileStore.ts
@@ -22,19 +22,29 @@ import { User } from "matrix-js-sdk/src/models/user";
import { throttle } from "lodash";
import { MatrixClientPeg } from "../MatrixClientPeg";
import { _t } from "../languageHandler";
+import {mediaFromMxc} from "../customisations/Media";
interface IState {
displayName?: string;
avatarUrl?: string;
}
+const KEY_DISPLAY_NAME = "mx_profile_displayname";
+const KEY_AVATAR_URL = "mx_profile_avatar_url";
+
export class OwnProfileStore extends AsyncStoreWithClient {
private static internalInstance = new OwnProfileStore();
private monitoredUser: User;
private constructor() {
- super(defaultDispatcher, {});
+ // seed from localstorage because otherwise we won't get these values until a whole network
+ // round-trip after the client is ready, and we often load widgets in that time, and we'd
+ // and up passing them an incorrect display name
+ super(defaultDispatcher, {
+ displayName: window.localStorage.getItem(KEY_DISPLAY_NAME),
+ avatarUrl: window.localStorage.getItem(KEY_AVATAR_URL),
+ });
}
public static get instance(): OwnProfileStore {
@@ -72,8 +82,12 @@ export class OwnProfileStore extends AsyncStoreWithClient {
*/
public getHttpAvatarUrl(size = 0): string {
if (!this.avatarMxc) return null;
- const adjustedSize = size > 1 ? size : undefined; // don't let negatives or zero through
- return this.matrixClient.mxcUrlToHttp(this.avatarMxc, adjustedSize, adjustedSize);
+ const media = mediaFromMxc(this.avatarMxc);
+ if (!size || size <= 0) {
+ return media.srcHttp;
+ } else {
+ return media.getSquareThumbnailHttp(size);
+ }
}
protected async onNotReady() {
@@ -110,6 +124,16 @@ export class OwnProfileStore extends AsyncStoreWithClient {
// We specifically do not use the User object we stored for profile info as it
// could easily be wrong (such as per-room instead of global profile).
const profileInfo = await this.matrixClient.getProfileInfo(this.matrixClient.getUserId());
+ if (profileInfo.displayname) {
+ window.localStorage.setItem(KEY_DISPLAY_NAME, profileInfo.displayname);
+ } else {
+ window.localStorage.removeItem(KEY_DISPLAY_NAME);
+ }
+ if (profileInfo.avatar_url) {
+ window.localStorage.setItem(KEY_AVATAR_URL, profileInfo.avatar_url);
+ } else {
+ window.localStorage.removeItem(KEY_AVATAR_URL);
+ }
await this.updateState({displayName: profileInfo.displayname, avatarUrl: profileInfo.avatar_url});
};
diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts
index 3f415f946d..074c2e569d 100644
--- a/src/stores/room-list/RoomListStore.ts
+++ b/src/stores/room-list/RoomListStore.ts
@@ -655,6 +655,18 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
if (!algorithmTags) return [DefaultTagID.Untagged];
return algorithmTags;
}
+
+ /**
+ * Manually update a room with a given cause. This should only be used if the
+ * room list store would otherwise be incapable of doing the update itself. Note
+ * that this may race with the room list's regular operation.
+ * @param {Room} room The room to update.
+ * @param {RoomUpdateCause} cause The cause to update for.
+ */
+ public async manualRoomUpdate(room: Room, cause: RoomUpdateCause) {
+ await this.handleRoomUpdate(room, cause);
+ this.updateFn.trigger();
+ }
}
export default class RoomListStore {
diff --git a/src/usercontent/index.js b/src/usercontent/index.js
index 6ecd17dcd7..13f38cc31a 100644
--- a/src/usercontent/index.js
+++ b/src/usercontent/index.js
@@ -1,10 +1,8 @@
function remoteRender(event) {
const data = event.data;
- const img = document.createElement("img");
+ const img = document.createElement("span"); // we'll mask it as an image
img.id = "img";
- img.src = data.imgSrc;
- img.style = data.imgStyle;
const a = document.createElement("a");
a.id = "a";
@@ -16,6 +14,23 @@ function remoteRender(event) {
a.appendChild(img);
a.appendChild(document.createTextNode(data.textContent));
+ // Apply image style after so we can steal the anchor's colour.
+ // Style copied from a rendered version of mx_MFileBody_download_icon
+ img.style = (data.imgStyle || "" +
+ "width: 12px; height: 12px;" +
+ "-webkit-mask-size: 12px;" +
+ "mask-size: 12px;" +
+ "-webkit-mask-position: center;" +
+ "mask-position: center;" +
+ "-webkit-mask-repeat: no-repeat;" +
+ "mask-repeat: no-repeat;" +
+ "display: inline-block;") + "" +
+
+ // Always add these styles
+ `-webkit-mask-image: url('${data.imgSrc}');` +
+ `mask-image: url('${data.imgSrc}');` +
+ `background-color: ${a.style.color};`;
+
const body = document.body;
// Don't display scrollbars if the link takes more than one line to display.
body.style = "margin: 0px; overflow: hidden";
@@ -26,20 +41,8 @@ function remoteRender(event) {
}
}
-function remoteSetTint(event) {
- const data = event.data;
-
- const img = document.getElementById("img");
- img.src = data.imgSrc;
- img.style = data.imgStyle;
-
- const a = document.getElementById("a");
- a.style = data.style;
-}
-
window.onmessage = function(e) {
if (e.origin === window.location.origin) {
if (e.data.blob) remoteRender(e);
- else remoteSetTint(e);
}
};
diff --git a/src/utils/AutoDiscoveryUtils.js b/src/utils/AutoDiscoveryUtils.js
index 18b6451d3e..614aa4cea8 100644
--- a/src/utils/AutoDiscoveryUtils.js
+++ b/src/utils/AutoDiscoveryUtils.js
@@ -16,7 +16,7 @@ limitations under the License.
*/
import React from 'react';
-import {AutoDiscovery} from "matrix-js-sdk";
+import {AutoDiscovery} from "matrix-js-sdk/src/autodiscovery";
import {_t, _td, newTranslatableError} from "../languageHandler";
import {makeType} from "./TypeUtils";
import SdkConfig from '../SdkConfig';
diff --git a/src/utils/DecryptFile.js b/src/utils/DecryptFile.ts
similarity index 74%
rename from src/utils/DecryptFile.js
rename to src/utils/DecryptFile.ts
index d3625d614a..93cedbc707 100644
--- a/src/utils/DecryptFile.js
+++ b/src/utils/DecryptFile.ts
@@ -1,6 +1,5 @@
/*
-Copyright 2016 OpenMarket Ltd
-Copyright 2018 New Vector Ltd
+Copyright 2016, 2018, 2021 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.
@@ -17,8 +16,8 @@ limitations under the License.
// Pull in the encryption lib so that we can decrypt attachments.
import encrypt from 'browser-encrypt-attachment';
-// Grab the client so that we can turn mxc:// URLs into https:// URLS.
-import {MatrixClientPeg} from '../MatrixClientPeg';
+import {mediaFromContent} from "../customisations/Media";
+import {IEncryptedFile} from "../customisations/models/IMediaEventContent";
// WARNING: We have to be very careful about what mime-types we allow into blobs,
// as for performance reasons these are now rendered via URL.createObjectURL()
@@ -54,48 +53,46 @@ import {MatrixClientPeg} from '../MatrixClientPeg';
// For the record, mime-types which must NEVER enter this list below include:
// text/html, text/xhtml, image/svg, image/svg+xml, image/pdf, and similar.
-const ALLOWED_BLOB_MIMETYPES = {
- 'image/jpeg': true,
- 'image/gif': true,
- 'image/png': true,
+const ALLOWED_BLOB_MIMETYPES = [
+ 'image/jpeg',
+ 'image/gif',
+ 'image/png',
- 'video/mp4': true,
- 'video/webm': true,
- 'video/ogg': true,
+ 'video/mp4',
+ 'video/webm',
+ 'video/ogg',
- 'audio/mp4': true,
- 'audio/webm': true,
- 'audio/aac': true,
- 'audio/mpeg': true,
- 'audio/ogg': true,
- 'audio/wave': true,
- 'audio/wav': true,
- 'audio/x-wav': true,
- 'audio/x-pn-wav': true,
- 'audio/flac': true,
- 'audio/x-flac': true,
-};
+ 'audio/mp4',
+ 'audio/webm',
+ 'audio/aac',
+ 'audio/mpeg',
+ 'audio/ogg',
+ 'audio/wave',
+ 'audio/wav',
+ 'audio/x-wav',
+ 'audio/x-pn-wav',
+ 'audio/flac',
+ 'audio/x-flac',
+];
/**
* Decrypt a file attached to a matrix event.
- * @param {Object} file The json taken from the matrix event.
+ * @param {IEncryptedFile} file The json taken from the matrix event.
* This passed to [link]{@link https://github.com/matrix-org/browser-encrypt-attachments}
* as the encryption info object, so will also have the those keys in addition to
* the keys below.
- * @param {string} file.url An mxc:// URL for the encrypted file.
- * @param {string} file.mimetype The MIME-type of the plaintext file.
- * @returns {Promise}
+ * @returns {Promise} Resolves to a Blob of the file.
*/
-export function decryptFile(file) {
- const url = MatrixClientPeg.get().mxcUrlToHttp(file.url);
+export function decryptFile(file: IEncryptedFile): Promise {
+ const media = mediaFromContent({file});
// Download the encrypted file as an array buffer.
- return Promise.resolve(fetch(url)).then(function(response) {
+ return media.downloadSource().then((response) => {
return response.arrayBuffer();
- }).then(function(responseData) {
+ }).then((responseData) => {
// Decrypt the array buffer using the information taken from
// the event content.
return encrypt.decryptAttachment(responseData, file);
- }).then(function(dataArray) {
+ }).then((dataArray) => {
// Turn the array into a Blob and give it the correct MIME-type.
// IMPORTANT: we must not allow scriptable mime-types into Blobs otherwise
@@ -103,11 +100,10 @@ export function decryptFile(file) {
// browser (e.g. by copying the URI into a new tab or window.)
// See warning at top of file.
let mimetype = file.mimetype ? file.mimetype.split(";")[0].trim() : '';
- if (!ALLOWED_BLOB_MIMETYPES[mimetype]) {
+ if (!ALLOWED_BLOB_MIMETYPES.includes(mimetype)) {
mimetype = 'application/octet-stream';
}
- const blob = new Blob([dataArray], {type: mimetype});
- return blob;
+ return new Blob([dataArray], {type: mimetype});
});
}
diff --git a/src/utils/EventUtils.js b/src/utils/EventUtils.js
index 6558a11ed4..be21896417 100644
--- a/src/utils/EventUtils.js
+++ b/src/utils/EventUtils.js
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { EventStatus } from 'matrix-js-sdk';
+import { EventStatus } from 'matrix-js-sdk/src/models/event';
import {MatrixClientPeg} from '../MatrixClientPeg';
import shouldHideEvent from "../shouldHideEvent";
/**
diff --git a/src/utils/IdentityServerUtils.js b/src/utils/IdentityServerUtils.js
index 093d4eeabf..5ece308954 100644
--- a/src/utils/IdentityServerUtils.js
+++ b/src/utils/IdentityServerUtils.js
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { SERVICE_TYPES } from 'matrix-js-sdk';
+import { SERVICE_TYPES } from 'matrix-js-sdk/src/service-types';
import SdkConfig from '../SdkConfig';
import {MatrixClientPeg} from '../MatrixClientPeg';
diff --git a/src/utils/StorageManager.js b/src/utils/StorageManager.js
index c90281bacf..23c27a2d1c 100644
--- a/src/utils/StorageManager.js
+++ b/src/utils/StorageManager.js
@@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import Matrix from 'matrix-js-sdk';
import {LocalStorageCryptoStore} from 'matrix-js-sdk/src/crypto/store/localStorage-crypto-store';
import Analytics from '../Analytics';
+import {IndexedDBStore} from "matrix-js-sdk/src/store/indexeddb";
+import {IndexedDBCryptoStore} from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store";
const localStorage = window.localStorage;
@@ -132,7 +133,7 @@ export async function checkConsistency() {
async function checkSyncStore() {
let exists = false;
try {
- exists = await Matrix.IndexedDBStore.exists(
+ exists = await IndexedDBStore.exists(
indexedDB, SYNC_STORE_NAME,
);
log(`Sync store using IndexedDB contains data? ${exists}`);
@@ -148,7 +149,7 @@ async function checkSyncStore() {
async function checkCryptoStore() {
let exists = false;
try {
- exists = await Matrix.IndexedDBCryptoStore.exists(
+ exists = await IndexedDBCryptoStore.exists(
indexedDB, CRYPTO_STORE_NAME,
);
log(`Crypto store using IndexedDB contains data? ${exists}`);
diff --git a/src/utils/createMatrixClient.js b/src/utils/createMatrixClient.js
index c8ff35a584..f5e196d846 100644
--- a/src/utils/createMatrixClient.js
+++ b/src/utils/createMatrixClient.js
@@ -14,7 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import * as Matrix from 'matrix-js-sdk';
+import {createClient} from "matrix-js-sdk/src/matrix";
+import {IndexedDBCryptoStore} from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store";
+import {WebStorageSessionStore} from "matrix-js-sdk/src/store/session/webstorage";
+import {IndexedDBStore} from "matrix-js-sdk/src/store/indexeddb";
const localStorage = window.localStorage;
@@ -44,7 +47,7 @@ export default function createMatrixClient(opts) {
};
if (indexedDB && localStorage) {
- storeOpts.store = new Matrix.IndexedDBStore({
+ storeOpts.store = new IndexedDBStore({
indexedDB: indexedDB,
dbName: "riot-web-sync",
localStorage: localStorage,
@@ -53,18 +56,18 @@ export default function createMatrixClient(opts) {
}
if (localStorage) {
- storeOpts.sessionStore = new Matrix.WebStorageSessionStore(localStorage);
+ storeOpts.sessionStore = new WebStorageSessionStore(localStorage);
}
if (indexedDB) {
- storeOpts.cryptoStore = new Matrix.IndexedDBCryptoStore(
+ storeOpts.cryptoStore = new IndexedDBCryptoStore(
indexedDB, "matrix-js-sdk:crypto",
);
}
opts = Object.assign(storeOpts, opts);
- return Matrix.createClient(opts);
+ return createClient(opts);
}
createMatrixClient.indexedDbWorkerScript = null;
diff --git a/src/utils/pages.js b/src/utils/pages.ts
similarity index 68%
rename from src/utils/pages.js
rename to src/utils/pages.ts
index d63ca3f2c7..bae76be29d 100644
--- a/src/utils/pages.js
+++ b/src/utils/pages.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019, 2021 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,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-export function getHomePageUrl(appConfig) {
+import { ConfigOptions } from "../SdkConfig";
+
+export function getHomePageUrl(appConfig: ConfigOptions): string | null {
const pagesConfig = appConfig.embeddedPages;
- let pageUrl = null;
- if (pagesConfig) {
- pageUrl = pagesConfig.homeUrl;
- }
+ let pageUrl = pagesConfig?.homeUrl;
+
if (!pageUrl) {
// This is a deprecated config option for the home page
// (despite the name, given we also now have a welcome
@@ -29,3 +29,8 @@ export function getHomePageUrl(appConfig) {
return pageUrl;
}
+
+export function shouldUseLoginForWelcome(appConfig: ConfigOptions): boolean {
+ const pagesConfig = appConfig.embeddedPages;
+ return pagesConfig?.loginForWelcome === true;
+}
diff --git a/test/components/structures/GroupView-test.js b/test/components/structures/GroupView-test.js
index fb942d2f7c..ee5d1b6912 100644
--- a/test/components/structures/GroupView-test.js
+++ b/test/components/structures/GroupView-test.js
@@ -262,7 +262,8 @@ describe('GroupView', function() {
expect(longDescElement.innerHTML).toContain('');
expect(longDescElement.innerHTML).toContain('- And lists!
');
- const imgSrc = "https://my.home.server/_matrix/media/r0/thumbnail/someimageurl?width=800&height=600";
+ const imgSrc = "https://my.home.server/_matrix/media/r0/thumbnail/someimageurl" +
+ "?width=800&height=600&method=scale";
expect(longDescElement.innerHTML).toContain(' ');
});
diff --git a/test/components/structures/MessagePanel-test.js b/test/components/structures/MessagePanel-test.js
index 2fd5bd6ad1..7347ff2658 100644
--- a/test/components/structures/MessagePanel-test.js
+++ b/test/components/structures/MessagePanel-test.js
@@ -116,6 +116,7 @@ describe('MessagePanel', function() {
getAvatarUrl: () => {
return "avatar.jpeg";
},
+ getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
},
ts: ts0 + i*1000,
mship: 'join',
@@ -148,6 +149,7 @@ describe('MessagePanel', function() {
getAvatarUrl: () => {
return "avatar.jpeg";
},
+ getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
},
ts: ts0 + i*1000,
mship: 'join',
@@ -193,6 +195,7 @@ describe('MessagePanel', function() {
getAvatarUrl: () => {
return "avatar.jpeg";
},
+ getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
},
ts: ts0 + 1,
mship: 'join',
@@ -239,6 +242,7 @@ describe('MessagePanel', function() {
getAvatarUrl: () => {
return "avatar.jpeg";
},
+ getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
},
ts: ts0 + 5,
mship: 'invite',
diff --git a/test/components/views/elements/MemberEventListSummary-test.js b/test/components/views/elements/MemberEventListSummary-test.js
index 6d26fa36e9..dd6febc7d7 100644
--- a/test/components/views/elements/MemberEventListSummary-test.js
+++ b/test/components/views/elements/MemberEventListSummary-test.js
@@ -50,6 +50,7 @@ describe('MemberEventListSummary', function() {
getAvatarUrl: () => {
return "avatar.jpeg";
},
+ getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
},
});
// Override random event ID to allow for equality tests against tiles from
diff --git a/test/components/views/messages/TextualBody-test.js b/test/components/views/messages/TextualBody-test.js
index a596825c09..0a6d47a72b 100644
--- a/test/components/views/messages/TextualBody-test.js
+++ b/test/components/views/messages/TextualBody-test.js
@@ -37,6 +37,7 @@ describe("", () => {
getRoom: () => mkStubRoom("room_id"),
getAccountData: () => undefined,
isGuest: () => false,
+ mxcUrlToHttp: (s) => s,
};
const ev = mkEvent({
@@ -61,6 +62,7 @@ describe("", () => {
getRoom: () => mkStubRoom("room_id"),
getAccountData: () => undefined,
isGuest: () => false,
+ mxcUrlToHttp: (s) => s,
};
const ev = mkEvent({
@@ -86,6 +88,7 @@ describe("", () => {
getRoom: () => mkStubRoom("room_id"),
getAccountData: () => undefined,
isGuest: () => false,
+ mxcUrlToHttp: (s) => s,
};
});
@@ -139,6 +142,7 @@ describe("", () => {
on: () => undefined,
removeListener: () => undefined,
isGuest: () => false,
+ mxcUrlToHttp: (s) => s,
};
});
@@ -284,6 +288,7 @@ describe("", () => {
getAccountData: () => undefined,
getUrlPreview: (url) => new Promise(() => {}),
isGuest: () => false,
+ mxcUrlToHttp: (s) => s,
};
const ev = mkEvent({
diff --git a/test/setupTests.js b/test/setupTests.js
index 9c2d16a8df..6d37d48987 100644
--- a/test/setupTests.js
+++ b/test/setupTests.js
@@ -2,3 +2,5 @@ import * as languageHandler from "../src/languageHandler";
languageHandler.setLanguage('en');
languageHandler.setMissingEntryGenerator(key => key.split("|", 2)[1]);
+
+require('jest-fetch-mock').enableMocks();
diff --git a/test/test-utils.js b/test/test-utils.js
index b6e0468d86..d259fcb95f 100644
--- a/test/test-utils.js
+++ b/test/test-utils.js
@@ -213,6 +213,7 @@ export function mkStubRoom(roomId = null) {
rawDisplayName: 'Member',
roomId: roomId,
getAvatarUrl: () => 'mxc://avatar.url/image.png',
+ getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
}),
getMembersWithMembership: jest.fn().mockReturnValue([]),
getJoinedMembers: jest.fn().mockReturnValue([]),
@@ -242,6 +243,7 @@ export function mkStubRoom(roomId = null) {
removeListener: jest.fn(),
getDMInviter: jest.fn(),
getAvatarUrl: () => 'mxc://avatar.url/room.png',
+ getMxcAvatarUrl: () => 'mxc://avatar.url/room.png',
};
}
diff --git a/yarn.lock b/yarn.lock
index f99ea5900d..58686248f7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2589,6 +2589,13 @@ crc-32@^0.3.0:
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-0.3.0.tgz#6a3d3687f5baec41f7e9b99fe1953a2e5d19775e"
integrity sha1-aj02h/W67EH36bmf4ZU6Ll0Zd14=
+cross-fetch@^3.0.4:
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c"
+ integrity sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ==
+ dependencies:
+ node-fetch "2.6.1"
+
cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -4918,6 +4925,14 @@ jest-environment-node@^26.6.2:
jest-mock "^26.6.2"
jest-util "^26.6.2"
+jest-fetch-mock@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b"
+ integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==
+ dependencies:
+ cross-fetch "^3.0.4"
+ promise-polyfill "^8.1.3"
+
jest-get-type@^26.3.0:
version "26.3.0"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0"
@@ -5573,8 +5588,8 @@ mathml-tag-names@^2.1.3:
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
- version "9.8.0"
- resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/fb73ab687826e4d05fb8b424ab013a771213f84f"
+ version "9.9.0"
+ resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/cd38fb9b4c349eb31feac14e806e710bf6431b72"
dependencies:
"@babel/runtime" "^7.12.5"
another-json "^0.2.0"
@@ -5835,6 +5850,11 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
+node-fetch@2.6.1:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
+ integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
+
node-fetch@^1.0.1:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
@@ -6448,6 +6468,11 @@ progress@^2.0.0:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
+promise-polyfill@^8.1.3:
+ version "8.2.0"
+ resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0"
+ integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g==
+
promise@^7.0.3, promise@^7.1.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
|