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/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/settings/BridgeTile.tsx b/src/components/views/settings/BridgeTile.tsx
index b33219ad4a..3565d1ba2e 100644
--- a/src/components/views/settings/BridgeTile.tsx
+++ b/src/components/views/settings/BridgeTile.tsx
@@ -16,9 +16,7 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
-import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
import {_t} from "../../../languageHandler";
-import {MatrixClientPeg} from "../../../MatrixClientPeg";
import Pill from "../elements/Pill";
import {makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import BaseAvatar from "../avatars/BaseAvatar";
@@ -27,6 +25,7 @@ import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { isUrlPermitted } from '../../../HtmlUtils';
import {replaceableComponent} from "../../../utils/replaceableComponent";
+import {mediaFromMxc} from "../../../customisations/Media";
interface IProps {
ev: MatrixEvent;
@@ -114,10 +113,7 @@ export default class BridgeTile extends React.PureComponent {
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 = } 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/stores/OwnProfileStore.ts b/src/stores/OwnProfileStore.ts
index 8983380fec..5e722877e2 100644
--- a/src/stores/OwnProfileStore.ts
+++ b/src/stores/OwnProfileStore.ts
@@ -22,6 +22,7 @@ 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;
@@ -72,8 +73,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() {
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/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/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..89ad76638f 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"
@@ -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"
|