mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-26 02:18:25 +08:00
Move from browser-request
to fetch
(#9345)
This commit is contained in:
parent
ae883bb94b
commit
8b54be6f48
@ -1,81 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const en = require("../src/i18n/strings/en_EN");
|
||||
const de = require("../src/i18n/strings/de_DE");
|
||||
const lv = {
|
||||
"Save": "Saglabāt",
|
||||
"Uploading %(filename)s and %(count)s others|one": "Качване на %(filename)s и %(count)s друг",
|
||||
};
|
||||
|
||||
function weblateToCounterpart(inTrs) {
|
||||
const outTrs = {};
|
||||
|
||||
for (const key of Object.keys(inTrs)) {
|
||||
const keyParts = key.split('|', 2);
|
||||
if (keyParts.length === 2) {
|
||||
let obj = outTrs[keyParts[0]];
|
||||
if (obj === undefined) {
|
||||
obj = outTrs[keyParts[0]] = {};
|
||||
} else if (typeof obj === "string") {
|
||||
// This is a transitional edge case if a string went from singular to pluralised and both still remain
|
||||
// in the translation json file. Use the singular translation as `other` and merge pluralisation atop.
|
||||
obj = outTrs[keyParts[0]] = {
|
||||
"other": inTrs[key],
|
||||
};
|
||||
console.warn("Found entry in i18n file in both singular and pluralised form", keyParts[0]);
|
||||
}
|
||||
obj[keyParts[1]] = inTrs[key];
|
||||
} else {
|
||||
outTrs[key] = inTrs[key];
|
||||
}
|
||||
}
|
||||
|
||||
return outTrs;
|
||||
}
|
||||
|
||||
// Mock the browser-request for the languageHandler tests to return
|
||||
// Fake languages.json containing references to en_EN, de_DE and lv
|
||||
// en_EN.json
|
||||
// de_DE.json
|
||||
// lv.json - mock version with few translations, used to test fallback translation
|
||||
module.exports = jest.fn((opts, cb) => {
|
||||
const url = opts.url || opts.uri;
|
||||
if (url && url.endsWith("languages.json")) {
|
||||
cb(undefined, { status: 200 }, JSON.stringify({
|
||||
"en": {
|
||||
"fileName": "en_EN.json",
|
||||
"label": "English",
|
||||
},
|
||||
"de": {
|
||||
"fileName": "de_DE.json",
|
||||
"label": "German",
|
||||
},
|
||||
"lv": {
|
||||
"fileName": "lv.json",
|
||||
"label": "Latvian",
|
||||
},
|
||||
}));
|
||||
} else if (url && url.endsWith("en_EN.json")) {
|
||||
cb(undefined, { status: 200 }, JSON.stringify(weblateToCounterpart(en)));
|
||||
} else if (url && url.endsWith("de_DE.json")) {
|
||||
cb(undefined, { status: 200 }, JSON.stringify(weblateToCounterpart(de)));
|
||||
} else if (url && url.endsWith("lv.json")) {
|
||||
cb(undefined, { status: 200 }, JSON.stringify(weblateToCounterpart(lv)));
|
||||
} else {
|
||||
cb(true, { status: 404 }, "");
|
||||
}
|
||||
});
|
@ -124,7 +124,8 @@ Cypress.Commands.add("startDM", (name: string) => {
|
||||
cy.get(".mx_BasicMessageComposer_input")
|
||||
.should("have.focus")
|
||||
.type("Hey!{enter}");
|
||||
cy.contains(".mx_EventTile_body", "Hey!");
|
||||
// The DM room is created at this point, this can take a little bit of time
|
||||
cy.contains(".mx_EventTile_body", "Hey!", { timeout: 30000 });
|
||||
cy.contains(".mx_RoomSublist[aria-label=People]", name);
|
||||
});
|
||||
|
||||
@ -217,7 +218,7 @@ describe("Spotlight", () => {
|
||||
it("should find joined rooms", () => {
|
||||
cy.openSpotlightDialog().within(() => {
|
||||
cy.spotlightSearch().clear().type(room1Name);
|
||||
cy.wait(1000); // wait for the dialog code to settle
|
||||
cy.wait(3000); // wait for the dialog code to settle
|
||||
cy.spotlightResults().should("have.length", 1);
|
||||
cy.spotlightResults().eq(0).should("contain", room1Name);
|
||||
cy.spotlightResults().eq(0).click();
|
||||
@ -231,7 +232,7 @@ describe("Spotlight", () => {
|
||||
cy.openSpotlightDialog().within(() => {
|
||||
cy.spotlightFilter(Filter.PublicRooms);
|
||||
cy.spotlightSearch().clear().type(room1Name);
|
||||
cy.wait(1000); // wait for the dialog code to settle
|
||||
cy.wait(3000); // wait for the dialog code to settle
|
||||
cy.spotlightResults().should("have.length", 1);
|
||||
cy.spotlightResults().eq(0).should("contain", room1Name);
|
||||
cy.spotlightResults().eq(0).should("contain", "View");
|
||||
@ -246,7 +247,7 @@ describe("Spotlight", () => {
|
||||
cy.openSpotlightDialog().within(() => {
|
||||
cy.spotlightFilter(Filter.PublicRooms);
|
||||
cy.spotlightSearch().clear().type(room2Name);
|
||||
cy.wait(1000); // wait for the dialog code to settle
|
||||
cy.wait(3000); // wait for the dialog code to settle
|
||||
cy.spotlightResults().should("have.length", 1);
|
||||
cy.spotlightResults().eq(0).should("contain", room2Name);
|
||||
cy.spotlightResults().eq(0).should("contain", "Join");
|
||||
@ -262,7 +263,7 @@ describe("Spotlight", () => {
|
||||
cy.openSpotlightDialog().within(() => {
|
||||
cy.spotlightFilter(Filter.PublicRooms);
|
||||
cy.spotlightSearch().clear().type(room3Name);
|
||||
cy.wait(1000); // wait for the dialog code to settle
|
||||
cy.wait(3000); // wait for the dialog code to settle
|
||||
cy.spotlightResults().should("have.length", 1);
|
||||
cy.spotlightResults().eq(0).should("contain", room3Name);
|
||||
cy.spotlightResults().eq(0).should("contain", "View");
|
||||
@ -301,7 +302,7 @@ describe("Spotlight", () => {
|
||||
cy.openSpotlightDialog().within(() => {
|
||||
cy.spotlightFilter(Filter.People);
|
||||
cy.spotlightSearch().clear().type(bot1Name);
|
||||
cy.wait(1000); // wait for the dialog code to settle
|
||||
cy.wait(3000); // wait for the dialog code to settle
|
||||
cy.spotlightResults().should("have.length", 1);
|
||||
cy.spotlightResults().eq(0).should("contain", bot1Name);
|
||||
cy.spotlightResults().eq(0).click();
|
||||
@ -314,7 +315,7 @@ describe("Spotlight", () => {
|
||||
cy.openSpotlightDialog().within(() => {
|
||||
cy.spotlightFilter(Filter.People);
|
||||
cy.spotlightSearch().clear().type(bot2Name);
|
||||
cy.wait(1000); // wait for the dialog code to settle
|
||||
cy.wait(3000); // wait for the dialog code to settle
|
||||
cy.spotlightResults().should("have.length", 1);
|
||||
cy.spotlightResults().eq(0).should("contain", bot2Name);
|
||||
cy.spotlightResults().eq(0).click();
|
||||
@ -331,7 +332,7 @@ describe("Spotlight", () => {
|
||||
cy.openSpotlightDialog().within(() => {
|
||||
cy.spotlightFilter(Filter.People);
|
||||
cy.spotlightSearch().clear().type(bot2Name);
|
||||
cy.wait(1000); // wait for the dialog code to settle
|
||||
cy.wait(3000); // wait for the dialog code to settle
|
||||
cy.spotlightResults().should("have.length", 1);
|
||||
cy.spotlightResults().eq(0).should("contain", bot2Name);
|
||||
cy.spotlightResults().eq(0).click();
|
||||
@ -345,7 +346,7 @@ describe("Spotlight", () => {
|
||||
.type("Hey!{enter}");
|
||||
|
||||
// Assert DM exists by checking for the first message and the room being in the room list
|
||||
cy.contains(".mx_EventTile_body", "Hey!");
|
||||
cy.contains(".mx_EventTile_body", "Hey!", { timeout: 30000 });
|
||||
cy.get(".mx_RoomSublist[aria-label=People]").should("contain", bot2Name);
|
||||
|
||||
// Invite BotBob into existing DM with ByteBot
|
||||
@ -409,7 +410,7 @@ describe("Spotlight", () => {
|
||||
cy.openSpotlightDialog().within(() => {
|
||||
cy.spotlightFilter(Filter.People);
|
||||
cy.spotlightSearch().clear().type(bot2Name);
|
||||
cy.wait(1000); // wait for the dialog code to settle
|
||||
cy.wait(3000); // wait for the dialog code to settle
|
||||
cy.spotlightResults().should("have.length", 1);
|
||||
cy.spotlightResults().eq(0).should("contain", bot2Name);
|
||||
cy.get(".mx_SpotlightDialog_startGroupChat").should("contain", "Start a group chat");
|
||||
@ -431,7 +432,7 @@ describe("Spotlight", () => {
|
||||
cy.openSpotlightDialog().within(() => {
|
||||
cy.spotlightFilter(Filter.People);
|
||||
cy.spotlightSearch().clear().type(bot1Name);
|
||||
cy.wait(1000); // wait for the dialog code to settle
|
||||
cy.wait(3000); // wait for the dialog code to settle
|
||||
cy.get(".mx_Spinner").should("not.exist");
|
||||
cy.spotlightResults().should("have.length", 1);
|
||||
});
|
||||
|
@ -91,11 +91,11 @@ describe("Timeline", () => {
|
||||
|
||||
describe("useOnlyCurrentProfiles", () => {
|
||||
beforeEach(() => {
|
||||
cy.uploadContent(OLD_AVATAR).then((url) => {
|
||||
cy.uploadContent(OLD_AVATAR).then(({ content_uri: url }) => {
|
||||
oldAvatarUrl = url;
|
||||
cy.setAvatarUrl(url);
|
||||
});
|
||||
cy.uploadContent(NEW_AVATAR).then((url) => {
|
||||
cy.uploadContent(NEW_AVATAR).then(({ content_uri: url }) => {
|
||||
newAvatarUrl = url;
|
||||
});
|
||||
});
|
||||
@ -271,7 +271,7 @@ describe("Timeline", () => {
|
||||
cy.get(".mx_RoomHeader_searchButton").click();
|
||||
cy.get(".mx_SearchBar_input input").type("Message{enter}");
|
||||
|
||||
cy.get(".mx_EventTile:not(.mx_EventTile_contextual)").find(".mx_EventTile_searchHighlight").should("exist");
|
||||
cy.get(".mx_EventTile:not(.mx_EventTile_contextual) .mx_EventTile_searchHighlight").should("exist");
|
||||
cy.get(".mx_RoomView_searchResultsPanel").percySnapshotElement("Highlighted search results");
|
||||
});
|
||||
|
||||
|
@ -16,8 +16,6 @@ limitations under the License.
|
||||
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import request from "browser-request";
|
||||
|
||||
import type { ISendEventResponse, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { SynapseInstance } from "../plugins/synapsedocker";
|
||||
import Chainable = Cypress.Chainable;
|
||||
@ -86,7 +84,6 @@ Cypress.Commands.add("getBot", (synapse: SynapseInstance, opts: CreateBotOpts):
|
||||
userId: credentials.userId,
|
||||
deviceId: credentials.deviceId,
|
||||
accessToken: credentials.accessToken,
|
||||
request,
|
||||
store: new win.matrixcs.MemoryStore(),
|
||||
scheduler: new win.matrixcs.MatrixScheduler(),
|
||||
cryptoStore: new win.matrixcs.MemoryCryptoStore(),
|
||||
|
@ -16,9 +16,8 @@ limitations under the License.
|
||||
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import type { FileType, UploadContentResponseType } from "matrix-js-sdk/src/http-api";
|
||||
import type { IAbortablePromise } from "matrix-js-sdk/src/@types/partials";
|
||||
import type { ICreateRoomOpts, ISendEventResponse, IUploadOpts } from "matrix-js-sdk/src/@types/requests";
|
||||
import type { FileType, Upload, UploadOpts } from "matrix-js-sdk/src/http-api";
|
||||
import type { ICreateRoomOpts, ISendEventResponse } from "matrix-js-sdk/src/@types/requests";
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import type { Room } from "matrix-js-sdk/src/models/room";
|
||||
import type { IContent } from "matrix-js-sdk/src/models/event";
|
||||
@ -90,10 +89,10 @@ declare global {
|
||||
* can be sent to XMLHttpRequest.send (typically a File). Under node.js,
|
||||
* a a Buffer, String or ReadStream.
|
||||
*/
|
||||
uploadContent<O extends IUploadOpts>(
|
||||
uploadContent(
|
||||
file: FileType,
|
||||
opts?: O,
|
||||
): IAbortablePromise<UploadContentResponseType<O>>;
|
||||
opts?: UploadOpts,
|
||||
): Chainable<Awaited<Upload["promise"]>>;
|
||||
/**
|
||||
* Turn an MXC URL into an HTTP one. <strong>This method is experimental and
|
||||
* may change.</strong>
|
||||
@ -203,9 +202,9 @@ Cypress.Commands.add("setDisplayName", (name: string): Chainable<{}> => {
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("uploadContent", (file: FileType): Chainable<{}> => {
|
||||
Cypress.Commands.add("uploadContent", (file: FileType, opts?: UploadOpts): Chainable<Awaited<Upload["promise"]>> => {
|
||||
return cy.getClient().then(async (cli: MatrixClient) => {
|
||||
return cli.uploadContent(file);
|
||||
return cli.uploadContent(file, opts);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -65,7 +65,6 @@
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"await-lock": "^2.1.0",
|
||||
"blurhash": "^1.1.3",
|
||||
"browser-request": "^0.3.3",
|
||||
"cheerio": "^1.0.0-rc.9",
|
||||
"classnames": "^2.2.6",
|
||||
"commonmark": "^0.29.3",
|
||||
@ -190,16 +189,16 @@
|
||||
"eslint-plugin-matrix-org": "^0.6.1",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"fetch-mock-jest": "^1.5.1",
|
||||
"fs-extra": "^10.0.1",
|
||||
"glob": "^7.1.6",
|
||||
"jest": "^27.4.0",
|
||||
"jest-canvas-mock": "^2.3.0",
|
||||
"jest-environment-jsdom": "^27.0.6",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"jest-mock": "^27.5.1",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"jest-sonar-reporter": "^2.0.0",
|
||||
"matrix-mock-request": "^2.0.0",
|
||||
"matrix-mock-request": "^2.5.0",
|
||||
"matrix-react-test-utils": "^0.2.3",
|
||||
"matrix-web-i18n": "^1.3.0",
|
||||
"postcss-scss": "^4.0.4",
|
||||
|
@ -85,7 +85,7 @@ export default class AddThreepid {
|
||||
const identityAccessToken = await authClient.getAccessToken();
|
||||
return MatrixClientPeg.get().requestEmailToken(
|
||||
emailAddress, this.clientSecret, 1,
|
||||
undefined, undefined, identityAccessToken,
|
||||
undefined, identityAccessToken,
|
||||
).then((res) => {
|
||||
this.sessionId = res.sid;
|
||||
return res;
|
||||
@ -142,7 +142,7 @@ export default class AddThreepid {
|
||||
const identityAccessToken = await authClient.getAccessToken();
|
||||
return MatrixClientPeg.get().requestMsisdnToken(
|
||||
phoneCountry, phoneNumber, this.clientSecret, 1,
|
||||
undefined, undefined, identityAccessToken,
|
||||
undefined, identityAccessToken,
|
||||
).then((res) => {
|
||||
this.sessionId = res.sid;
|
||||
return res;
|
||||
|
@ -17,16 +17,16 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { IUploadOpts } from "matrix-js-sdk/src/@types/requests";
|
||||
import { MsgType } from "matrix-js-sdk/src/@types/event";
|
||||
import encrypt from "matrix-encrypt-attachment";
|
||||
import extractPngChunks from "png-chunks-extract";
|
||||
import { IAbortablePromise, IImageInfo } from "matrix-js-sdk/src/@types/partials";
|
||||
import { IImageInfo } from "matrix-js-sdk/src/@types/partials";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { IEventRelation, ISendEventResponse, MatrixError, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { IEventRelation, ISendEventResponse, MatrixEvent, UploadOpts, UploadProgress } from "matrix-js-sdk/src/matrix";
|
||||
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
|
||||
import { removeElement } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { IEncryptedFile, IMediaEventInfo } from "./customisations/models/IMediaEventContent";
|
||||
import { IEncryptedFile, IMediaEventContent, IMediaEventInfo } from "./customisations/models/IMediaEventContent";
|
||||
import dis from './dispatcher/dispatcher';
|
||||
import { _t } from './languageHandler';
|
||||
import Modal from './Modal';
|
||||
@ -39,7 +39,7 @@ import {
|
||||
UploadProgressPayload,
|
||||
UploadStartedPayload,
|
||||
} from "./dispatcher/payloads/UploadPayload";
|
||||
import { IUpload } from "./models/IUpload";
|
||||
import { RoomUpload } from "./models/RoomUpload";
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
import { decorateStartSendingTime, sendRoundTripMetric } from "./sendTimePerformanceMetrics";
|
||||
import { TimelineRenderingType } from "./contexts/RoomContext";
|
||||
@ -62,14 +62,6 @@ interface IMediaConfig {
|
||||
"m.upload.size"?: number;
|
||||
}
|
||||
|
||||
interface IContent {
|
||||
body: string;
|
||||
msgtype: string;
|
||||
info: IMediaEventInfo;
|
||||
file?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a file into a newly created image element.
|
||||
*
|
||||
@ -78,7 +70,7 @@ interface IContent {
|
||||
*/
|
||||
async function loadImageElement(imageFile: File) {
|
||||
// Load the file into an html element
|
||||
const img = document.createElement("img");
|
||||
const img = new Image();
|
||||
const objectUrl = URL.createObjectURL(imageFile);
|
||||
const imgPromise = new Promise((resolve, reject) => {
|
||||
img.onload = function() {
|
||||
@ -93,7 +85,7 @@ async function loadImageElement(imageFile: File) {
|
||||
|
||||
// check for hi-dpi PNGs and fudge display resolution as needed.
|
||||
// this is mainly needed for macOS screencaps
|
||||
let parsePromise;
|
||||
let parsePromise: Promise<boolean>;
|
||||
if (imageFile.type === "image/png") {
|
||||
// in practice macOS happens to order the chunks so they fall in
|
||||
// the first 0x1000 bytes (thanks to a massive ICC header).
|
||||
@ -277,71 +269,58 @@ function readFileAsArrayBuffer(file: File | Blob): Promise<ArrayBuffer> {
|
||||
* @param {File} file The file to upload.
|
||||
* @param {Function?} progressHandler optional callback to be called when a chunk of
|
||||
* data is uploaded.
|
||||
* @param {AbortController?} controller optional abortController to use for this upload.
|
||||
* @return {Promise} A promise that resolves with an object.
|
||||
* If the file is unencrypted then the object will have a "url" key.
|
||||
* If the file is encrypted then the object will have a "file" key.
|
||||
*/
|
||||
export function uploadFile(
|
||||
export async function uploadFile(
|
||||
matrixClient: MatrixClient,
|
||||
roomId: string,
|
||||
file: File | Blob,
|
||||
progressHandler?: IUploadOpts["progressHandler"],
|
||||
): IAbortablePromise<{ url?: string, file?: IEncryptedFile }> {
|
||||
let canceled = false;
|
||||
progressHandler?: UploadOpts["progressHandler"],
|
||||
controller?: AbortController,
|
||||
): Promise<{ url?: string, file?: IEncryptedFile }> {
|
||||
const abortController = controller ?? new AbortController();
|
||||
|
||||
// If the room is encrypted then encrypt the file before uploading it.
|
||||
if (matrixClient.isRoomEncrypted(roomId)) {
|
||||
// If the room is encrypted then encrypt the file before uploading it.
|
||||
// First read the file into memory.
|
||||
let uploadPromise: IAbortablePromise<string>;
|
||||
const prom = readFileAsArrayBuffer(file).then(function(data) {
|
||||
if (canceled) throw new UploadCanceledError();
|
||||
// Then encrypt the file.
|
||||
return encrypt.encryptAttachment(data);
|
||||
}).then(function(encryptResult) {
|
||||
if (canceled) throw new UploadCanceledError();
|
||||
const data = await readFileAsArrayBuffer(file);
|
||||
if (abortController.signal.aborted) throw new UploadCanceledError();
|
||||
|
||||
// Pass the encrypted data as a Blob to the uploader.
|
||||
const blob = new Blob([encryptResult.data]);
|
||||
uploadPromise = matrixClient.uploadContent(blob, {
|
||||
progressHandler,
|
||||
includeFilename: false,
|
||||
});
|
||||
// Then encrypt the file.
|
||||
const encryptResult = await encrypt.encryptAttachment(data);
|
||||
if (abortController.signal.aborted) throw new UploadCanceledError();
|
||||
|
||||
return uploadPromise.then(url => {
|
||||
if (canceled) throw new UploadCanceledError();
|
||||
// Pass the encrypted data as a Blob to the uploader.
|
||||
const blob = new Blob([encryptResult.data]);
|
||||
|
||||
// If the attachment is encrypted then bundle the URL along
|
||||
// with the information needed to decrypt the attachment and
|
||||
// add it under a file key.
|
||||
return {
|
||||
file: {
|
||||
...encryptResult.info,
|
||||
url,
|
||||
},
|
||||
};
|
||||
});
|
||||
}) as IAbortablePromise<{ file: IEncryptedFile }>;
|
||||
prom.abort = () => {
|
||||
canceled = true;
|
||||
if (uploadPromise) matrixClient.cancelUpload(uploadPromise);
|
||||
const { content_uri: url } = await matrixClient.uploadContent(blob, {
|
||||
progressHandler,
|
||||
abortController,
|
||||
includeFilename: false,
|
||||
});
|
||||
if (abortController.signal.aborted) throw new UploadCanceledError();
|
||||
|
||||
// If the attachment is encrypted then bundle the URL along with the information
|
||||
// needed to decrypt the attachment and add it under a file key.
|
||||
return {
|
||||
file: {
|
||||
...encryptResult.info,
|
||||
url,
|
||||
} as IEncryptedFile,
|
||||
};
|
||||
return prom;
|
||||
} else {
|
||||
const basePromise = matrixClient.uploadContent(file, { progressHandler });
|
||||
const promise1 = basePromise.then(function(url) {
|
||||
if (canceled) throw new UploadCanceledError();
|
||||
// If the attachment isn't encrypted then include the URL directly.
|
||||
return { url };
|
||||
}) as IAbortablePromise<{ url: string }>;
|
||||
promise1.abort = () => {
|
||||
canceled = true;
|
||||
matrixClient.cancelUpload(basePromise);
|
||||
};
|
||||
return promise1;
|
||||
const { content_uri: url } = await matrixClient.uploadContent(file, { progressHandler, abortController });
|
||||
if (abortController.signal.aborted) throw new UploadCanceledError();
|
||||
// If the attachment isn't encrypted then include the URL directly.
|
||||
return { url };
|
||||
}
|
||||
}
|
||||
|
||||
export default class ContentMessages {
|
||||
private inprogress: IUpload[] = [];
|
||||
private inprogress: RoomUpload[] = [];
|
||||
private mediaConfig: IMediaConfig = null;
|
||||
|
||||
public sendStickerContentToRoom(
|
||||
@ -460,36 +439,33 @@ export default class ContentMessages {
|
||||
});
|
||||
}
|
||||
|
||||
public getCurrentUploads(relation?: IEventRelation): IUpload[] {
|
||||
return this.inprogress.filter(upload => {
|
||||
const noRelation = !relation && !upload.relation;
|
||||
const matchingRelation = relation && upload.relation
|
||||
&& relation.rel_type === upload.relation.rel_type
|
||||
&& relation.event_id === upload.relation.event_id;
|
||||
public getCurrentUploads(relation?: IEventRelation): RoomUpload[] {
|
||||
return this.inprogress.filter(roomUpload => {
|
||||
const noRelation = !relation && !roomUpload.relation;
|
||||
const matchingRelation = relation && roomUpload.relation
|
||||
&& relation.rel_type === roomUpload.relation.rel_type
|
||||
&& relation.event_id === roomUpload.relation.event_id;
|
||||
|
||||
return (noRelation || matchingRelation) && !upload.canceled;
|
||||
return (noRelation || matchingRelation) && !roomUpload.cancelled;
|
||||
});
|
||||
}
|
||||
|
||||
public cancelUpload(promise: IAbortablePromise<any>, matrixClient: MatrixClient): void {
|
||||
const upload = this.inprogress.find(item => item.promise === promise);
|
||||
if (upload) {
|
||||
upload.canceled = true;
|
||||
matrixClient.cancelUpload(upload.promise);
|
||||
dis.dispatch<UploadCanceledPayload>({ action: Action.UploadCanceled, upload });
|
||||
}
|
||||
public cancelUpload(upload: RoomUpload): void {
|
||||
upload.abort();
|
||||
dis.dispatch<UploadCanceledPayload>({ action: Action.UploadCanceled, upload });
|
||||
}
|
||||
|
||||
private sendContentToRoom(
|
||||
public async sendContentToRoom(
|
||||
file: File,
|
||||
roomId: string,
|
||||
relation: IEventRelation | undefined,
|
||||
matrixClient: MatrixClient,
|
||||
replyToEvent: MatrixEvent | undefined,
|
||||
promBefore: Promise<any>,
|
||||
promBefore?: Promise<any>,
|
||||
) {
|
||||
const content: Omit<IContent, "info"> & { info: Partial<IMediaEventInfo> } = {
|
||||
body: file.name || 'Attachment',
|
||||
const fileName = file.name || _t("Attachment");
|
||||
const content: Omit<IMediaEventContent, "info"> & { info: Partial<IMediaEventInfo> } = {
|
||||
body: fileName,
|
||||
info: {
|
||||
size: file.size,
|
||||
},
|
||||
@ -512,91 +488,72 @@ export default class ContentMessages {
|
||||
content.info.mimetype = file.type;
|
||||
}
|
||||
|
||||
const prom = new Promise<void>((resolve) => {
|
||||
if (file.type.indexOf('image/') === 0) {
|
||||
content.msgtype = MsgType.Image;
|
||||
infoForImageFile(matrixClient, roomId, file).then((imageInfo) => {
|
||||
Object.assign(content.info, imageInfo);
|
||||
resolve();
|
||||
}, (e) => {
|
||||
// Failed to thumbnail, fall back to uploading an m.file
|
||||
logger.error(e);
|
||||
content.msgtype = MsgType.File;
|
||||
resolve();
|
||||
});
|
||||
} else if (file.type.indexOf('audio/') === 0) {
|
||||
content.msgtype = MsgType.Audio;
|
||||
resolve();
|
||||
} else if (file.type.indexOf('video/') === 0) {
|
||||
content.msgtype = MsgType.Video;
|
||||
infoForVideoFile(matrixClient, roomId, file).then((videoInfo) => {
|
||||
Object.assign(content.info, videoInfo);
|
||||
resolve();
|
||||
}, (e) => {
|
||||
// Failed to thumbnail, fall back to uploading an m.file
|
||||
logger.error(e);
|
||||
content.msgtype = MsgType.File;
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
content.msgtype = MsgType.File;
|
||||
resolve();
|
||||
}
|
||||
}) as IAbortablePromise<void>;
|
||||
|
||||
// create temporary abort handler for before the actual upload gets passed off to js-sdk
|
||||
prom.abort = () => {
|
||||
upload.canceled = true;
|
||||
};
|
||||
|
||||
const upload: IUpload = {
|
||||
fileName: file.name || 'Attachment',
|
||||
roomId,
|
||||
relation,
|
||||
total: file.size,
|
||||
loaded: 0,
|
||||
promise: prom,
|
||||
};
|
||||
const upload = new RoomUpload(roomId, fileName, relation, file.size);
|
||||
this.inprogress.push(upload);
|
||||
dis.dispatch<UploadStartedPayload>({ action: Action.UploadStarted, upload });
|
||||
|
||||
function onProgress(ev) {
|
||||
upload.total = ev.total;
|
||||
upload.loaded = ev.loaded;
|
||||
function onProgress(progress: UploadProgress) {
|
||||
upload.onProgress(progress);
|
||||
dis.dispatch<UploadProgressPayload>({ action: Action.UploadProgress, upload });
|
||||
}
|
||||
|
||||
let error: MatrixError;
|
||||
return prom.then(() => {
|
||||
if (upload.canceled) throw new UploadCanceledError();
|
||||
// XXX: upload.promise must be the promise that
|
||||
// is returned by uploadFile as it has an abort()
|
||||
// method hacked onto it.
|
||||
upload.promise = uploadFile(matrixClient, roomId, file, onProgress);
|
||||
return upload.promise.then(function(result) {
|
||||
content.file = result.file;
|
||||
content.url = result.url;
|
||||
});
|
||||
}).then(() => {
|
||||
// Await previous message being sent into the room
|
||||
return promBefore;
|
||||
}).then(function() {
|
||||
if (upload.canceled) throw new UploadCanceledError();
|
||||
const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name
|
||||
? relation.event_id
|
||||
: null;
|
||||
const prom = matrixClient.sendMessage(roomId, threadId, content);
|
||||
if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
|
||||
prom.then(resp => {
|
||||
sendRoundTripMetric(matrixClient, roomId, resp.event_id);
|
||||
});
|
||||
try {
|
||||
if (file.type.startsWith('image/')) {
|
||||
content.msgtype = MsgType.Image;
|
||||
try {
|
||||
const imageInfo = await infoForImageFile(matrixClient, roomId, file);
|
||||
Object.assign(content.info, imageInfo);
|
||||
} catch (e) {
|
||||
// Failed to thumbnail, fall back to uploading an m.file
|
||||
logger.error(e);
|
||||
content.msgtype = MsgType.File;
|
||||
}
|
||||
} else if (file.type.indexOf('audio/') === 0) {
|
||||
content.msgtype = MsgType.Audio;
|
||||
} else if (file.type.indexOf('video/') === 0) {
|
||||
content.msgtype = MsgType.Video;
|
||||
try {
|
||||
const videoInfo = await infoForVideoFile(matrixClient, roomId, file);
|
||||
Object.assign(content.info, videoInfo);
|
||||
} catch (e) {
|
||||
// Failed to thumbnail, fall back to uploading an m.file
|
||||
logger.error(e);
|
||||
content.msgtype = MsgType.File;
|
||||
}
|
||||
} else {
|
||||
content.msgtype = MsgType.File;
|
||||
}
|
||||
return prom;
|
||||
}, function(err: MatrixError) {
|
||||
error = err;
|
||||
if (!upload.canceled) {
|
||||
|
||||
if (upload.cancelled) throw new UploadCanceledError();
|
||||
const result = await uploadFile(matrixClient, roomId, file, onProgress, upload.abortController);
|
||||
content.file = result.file;
|
||||
content.url = result.url;
|
||||
|
||||
if (upload.cancelled) throw new UploadCanceledError();
|
||||
// Await previous message being sent into the room
|
||||
if (promBefore) await promBefore;
|
||||
|
||||
if (upload.cancelled) throw new UploadCanceledError();
|
||||
const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name ? relation.event_id : null;
|
||||
|
||||
const response = await matrixClient.sendMessage(roomId, threadId, content);
|
||||
|
||||
if (SettingsStore.getValue("Performance.addSendMessageTimingMetadata")) {
|
||||
sendRoundTripMetric(matrixClient, roomId, response.event_id);
|
||||
}
|
||||
|
||||
dis.dispatch<UploadFinishedPayload>({ action: Action.UploadFinished, upload });
|
||||
dis.dispatch({ action: 'message_sent' });
|
||||
} catch (error) {
|
||||
// 413: File was too big or upset the server in some way:
|
||||
// clear the media size limit so we fetch it again next time we try to upload
|
||||
if (error?.httpStatus === 413) {
|
||||
this.mediaConfig = null;
|
||||
}
|
||||
|
||||
if (!upload.cancelled) {
|
||||
let desc = _t("The file '%(fileName)s' failed to upload.", { fileName: upload.fileName });
|
||||
if (err.httpStatus === 413) {
|
||||
if (error.httpStatus === 413) {
|
||||
desc = _t(
|
||||
"The file '%(fileName)s' exceeds this homeserver's size limit for uploads",
|
||||
{ fileName: upload.fileName },
|
||||
@ -606,27 +563,11 @@ export default class ContentMessages {
|
||||
title: _t('Upload Failed'),
|
||||
description: desc,
|
||||
});
|
||||
}
|
||||
}).finally(() => {
|
||||
for (let i = 0; i < this.inprogress.length; ++i) {
|
||||
if (this.inprogress[i].promise === upload.promise) {
|
||||
this.inprogress.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
// 413: File was too big or upset the server in some way:
|
||||
// clear the media size limit so we fetch it again next time
|
||||
// we try to upload
|
||||
if (error?.httpStatus === 413) {
|
||||
this.mediaConfig = null;
|
||||
}
|
||||
dis.dispatch<UploadErrorPayload>({ action: Action.UploadFailed, upload, error });
|
||||
} else {
|
||||
dis.dispatch<UploadFinishedPayload>({ action: Action.UploadFinished, upload });
|
||||
dis.dispatch({ action: 'message_sent' });
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
removeElement(this.inprogress, e => e.promise === upload.promise);
|
||||
}
|
||||
}
|
||||
|
||||
private isFileSizeAcceptable(file: File) {
|
||||
|
@ -739,7 +739,7 @@ export function logout(): void {
|
||||
_isLoggingOut = true;
|
||||
const client = MatrixClientPeg.get();
|
||||
PlatformPeg.get().destroyPickleKey(client.getUserId(), client.getDeviceId());
|
||||
client.logout(undefined, true).then(onLoggedOut, (err) => {
|
||||
client.logout(true).then(onLoggedOut, (err) => {
|
||||
// Just throwing an error here is going to be very unhelpful
|
||||
// if you're trying to log out because your server's down and
|
||||
// you want to log into a different server, so just forget the
|
||||
|
@ -169,7 +169,7 @@ export default class Login {
|
||||
* @param {string} loginType the type of login to do
|
||||
* @param {ILoginParams} loginParams the parameters for the login
|
||||
*
|
||||
* @returns {MatrixClientCreds}
|
||||
* @returns {IMatrixClientCreds}
|
||||
*/
|
||||
export async function sendLoginRequest(
|
||||
hsUrl: string,
|
||||
|
@ -15,10 +15,10 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import url from 'url';
|
||||
import request from "browser-request";
|
||||
import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { IOpenIDToken } from 'matrix-js-sdk/src/matrix';
|
||||
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
import { Service, startTermsFlow, TermsInteractionCallback, TermsNotSignedError } from './Terms';
|
||||
@ -103,29 +103,29 @@ export default class ScalarAuthClient {
|
||||
}
|
||||
}
|
||||
|
||||
private getAccountName(token: string): Promise<string> {
|
||||
const url = this.apiUrl + "/account";
|
||||
private async getAccountName(token: string): Promise<string> {
|
||||
const url = new URL(this.apiUrl + "/account");
|
||||
url.searchParams.set("scalar_token", token);
|
||||
url.searchParams.set("v", imApiVersion);
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
request({
|
||||
method: "GET",
|
||||
uri: url,
|
||||
qs: { scalar_token: token, v: imApiVersion },
|
||||
json: true,
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (body && body.errcode === 'M_TERMS_NOT_SIGNED') {
|
||||
reject(new TermsNotSignedError());
|
||||
} else if (response.statusCode / 100 !== 2) {
|
||||
reject(body);
|
||||
} else if (!body || !body.user_id) {
|
||||
reject(new Error("Missing user_id in response"));
|
||||
} else {
|
||||
resolve(body.user_id);
|
||||
}
|
||||
});
|
||||
const res = await fetch(url, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
const body = await res.json();
|
||||
if (body?.errcode === "M_TERMS_NOT_SIGNED") {
|
||||
throw new TermsNotSignedError();
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
throw body;
|
||||
}
|
||||
|
||||
if (!body?.user_id) {
|
||||
throw new Error("Missing user_id in response");
|
||||
}
|
||||
|
||||
return body.user_id;
|
||||
}
|
||||
|
||||
private checkToken(token: string): Promise<string> {
|
||||
@ -183,56 +183,41 @@ export default class ScalarAuthClient {
|
||||
});
|
||||
}
|
||||
|
||||
exchangeForScalarToken(openidTokenObject: any): Promise<string> {
|
||||
const scalarRestUrl = this.apiUrl;
|
||||
public async exchangeForScalarToken(openidTokenObject: IOpenIDToken): Promise<string> {
|
||||
const scalarRestUrl = new URL(this.apiUrl + "/register");
|
||||
scalarRestUrl.searchParams.set("v", imApiVersion);
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
request({
|
||||
method: 'POST',
|
||||
uri: scalarRestUrl + '/register',
|
||||
qs: { v: imApiVersion },
|
||||
body: openidTokenObject,
|
||||
json: true,
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (response.statusCode / 100 !== 2) {
|
||||
reject(new Error(`Scalar request failed: ${response.statusCode}`));
|
||||
} else if (!body || !body.scalar_token) {
|
||||
reject(new Error("Missing scalar_token in response"));
|
||||
} else {
|
||||
resolve(body.scalar_token);
|
||||
}
|
||||
});
|
||||
const res = await fetch(scalarRestUrl, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(openidTokenObject),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Scalar request failed: ${res.status}`);
|
||||
}
|
||||
|
||||
const body = await res.json();
|
||||
if (!body?.scalar_token) {
|
||||
throw new Error("Missing scalar_token in response");
|
||||
}
|
||||
|
||||
return body.scalar_token;
|
||||
}
|
||||
|
||||
getScalarPageTitle(url: string): Promise<string> {
|
||||
let scalarPageLookupUrl = this.apiUrl + '/widgets/title_lookup';
|
||||
scalarPageLookupUrl = this.getStarterLink(scalarPageLookupUrl);
|
||||
scalarPageLookupUrl += '&curl=' + encodeURIComponent(url);
|
||||
public async getScalarPageTitle(url: string): Promise<string> {
|
||||
const scalarPageLookupUrl = new URL(this.getStarterLink(this.apiUrl + '/widgets/title_lookup'));
|
||||
scalarPageLookupUrl.searchParams.set("curl", encodeURIComponent(url));
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
request({
|
||||
method: 'GET',
|
||||
uri: scalarPageLookupUrl,
|
||||
json: true,
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (response.statusCode / 100 !== 2) {
|
||||
reject(new Error(`Scalar request failed: ${response.statusCode}`));
|
||||
} else if (!body) {
|
||||
reject(new Error("Missing page title in response"));
|
||||
} else {
|
||||
let title = "";
|
||||
if (body.page_title_cache_item && body.page_title_cache_item.cached_title) {
|
||||
title = body.page_title_cache_item.cached_title;
|
||||
}
|
||||
resolve(title);
|
||||
}
|
||||
});
|
||||
const res = await fetch(scalarPageLookupUrl, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Scalar request failed: ${res.status}`);
|
||||
}
|
||||
|
||||
const body = await res.json();
|
||||
return body?.page_title_cache_item?.cached_title;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -243,31 +228,24 @@ export default class ScalarAuthClient {
|
||||
* @param {string} widgetId The widget ID to disable assets for
|
||||
* @return {Promise} Resolves on completion
|
||||
*/
|
||||
disableWidgetAssets(widgetType: WidgetType, widgetId: string): Promise<void> {
|
||||
let url = this.apiUrl + '/widgets/set_assets_state';
|
||||
url = this.getStarterLink(url);
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
request({
|
||||
method: 'GET', // XXX: Actions shouldn't be GET requests
|
||||
uri: url,
|
||||
json: true,
|
||||
qs: {
|
||||
'widget_type': widgetType.preferred,
|
||||
'widget_id': widgetId,
|
||||
'state': 'disable',
|
||||
},
|
||||
}, (err, response, body) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (response.statusCode / 100 !== 2) {
|
||||
reject(new Error(`Scalar request failed: ${response.statusCode}`));
|
||||
} else if (!body) {
|
||||
reject(new Error("Failed to set widget assets state"));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
public async disableWidgetAssets(widgetType: WidgetType, widgetId: string): Promise<void> {
|
||||
const url = new URL(this.getStarterLink(this.apiUrl + "/widgets/set_assets_state"));
|
||||
url.searchParams.set("widget_type", widgetType.preferred);
|
||||
url.searchParams.set("widget_id", widgetId);
|
||||
url.searchParams.set("state", "disable");
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: "GET", // XXX: Actions shouldn't be GET requests
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Scalar request failed: ${res.status}`);
|
||||
}
|
||||
|
||||
const body = await res.text();
|
||||
if (!body) {
|
||||
throw new Error("Failed to set widget assets state");
|
||||
}
|
||||
}
|
||||
|
||||
getScalarInterfaceUrlForRoom(room: Room, screen: string, id: string): string {
|
||||
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import request from 'browser-request';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import classnames from 'classnames';
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
@ -61,6 +60,37 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
|
||||
return sanitizeHtml(_t(s));
|
||||
}
|
||||
|
||||
private async fetchEmbed() {
|
||||
let res: Response;
|
||||
|
||||
try {
|
||||
res = await fetch(this.props.url, { method: "GET" });
|
||||
} catch (err) {
|
||||
if (this.unmounted) return;
|
||||
logger.warn(`Error loading page: ${err}`);
|
||||
this.setState({ page: _t("Couldn't load page") });
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.unmounted) return;
|
||||
|
||||
if (!res.ok) {
|
||||
logger.warn(`Error loading page: ${res.status}`);
|
||||
this.setState({ page: _t("Couldn't load page") });
|
||||
return;
|
||||
}
|
||||
|
||||
let body = (await res.text()).replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1) => this.translate(g1));
|
||||
|
||||
if (this.props.replaceMap) {
|
||||
Object.keys(this.props.replaceMap).forEach(key => {
|
||||
body = body.split(key).join(this.props.replaceMap[key]);
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ page: body });
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.unmounted = false;
|
||||
|
||||
@ -68,34 +98,10 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
|
||||
return;
|
||||
}
|
||||
|
||||
// we use request() to inline the page into the react component
|
||||
// We use fetch to inline the page into the react component
|
||||
// so that it can inherit CSS and theming easily rather than mess around
|
||||
// with iframes and trying to synchronise document.stylesheets.
|
||||
|
||||
request(
|
||||
{ method: "GET", url: this.props.url },
|
||||
(err, response, body) => {
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (err || response.status < 200 || response.status >= 300) {
|
||||
logger.warn(`Error loading page: ${err}`);
|
||||
this.setState({ page: _t("Couldn't load page") });
|
||||
return;
|
||||
}
|
||||
|
||||
body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1) => this.translate(g1));
|
||||
|
||||
if (this.props.replaceMap) {
|
||||
Object.keys(this.props.replaceMap).forEach(key => {
|
||||
body = body.split(key).join(this.props.replaceMap[key]);
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({ page: body });
|
||||
},
|
||||
);
|
||||
this.fetchEmbed();
|
||||
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
}
|
||||
|
@ -1362,7 +1362,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||
if (this.unmounted) return;
|
||||
|
||||
this.setState({ timelineLoading: false });
|
||||
logger.error(`Error loading timeline panel at ${this.props.timelineSet.room?.roomId}/${eventId}: ${error}`);
|
||||
logger.error(`Error loading timeline panel at ${this.props.timelineSet.room?.roomId}/${eventId}`, error);
|
||||
|
||||
let onFinished: () => void;
|
||||
|
||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||
import React from 'react';
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import filesize from "filesize";
|
||||
import { IAbortablePromise, IEventRelation } from 'matrix-js-sdk/src/matrix';
|
||||
import { IEventRelation } from 'matrix-js-sdk/src/matrix';
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
|
||||
import ContentMessages from '../../ContentMessages';
|
||||
@ -26,8 +26,7 @@ import { _t } from '../../languageHandler';
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import ProgressBar from "../views/elements/ProgressBar";
|
||||
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
|
||||
import { IUpload } from "../../models/IUpload";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import { RoomUpload } from "../../models/RoomUpload";
|
||||
import { ActionPayload } from '../../dispatcher/payloads';
|
||||
import { UploadPayload } from "../../dispatcher/payloads/UploadPayload";
|
||||
|
||||
@ -38,7 +37,7 @@ interface IProps {
|
||||
|
||||
interface IState {
|
||||
currentFile?: string;
|
||||
currentPromise?: IAbortablePromise<any>;
|
||||
currentUpload?: RoomUpload;
|
||||
currentLoaded?: number;
|
||||
currentTotal?: number;
|
||||
countFiles: number;
|
||||
@ -55,8 +54,6 @@ function isUploadPayload(payload: ActionPayload): payload is UploadPayload {
|
||||
}
|
||||
|
||||
export default class UploadBar extends React.PureComponent<IProps, IState> {
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
private dispatcherRef: Optional<string>;
|
||||
private mounted = false;
|
||||
|
||||
@ -78,7 +75,7 @@ export default class UploadBar extends React.PureComponent<IProps, IState> {
|
||||
dis.unregister(this.dispatcherRef!);
|
||||
}
|
||||
|
||||
private getUploadsInRoom(): IUpload[] {
|
||||
private getUploadsInRoom(): RoomUpload[] {
|
||||
const uploads = ContentMessages.sharedInstance().getCurrentUploads(this.props.relation);
|
||||
return uploads.filter(u => u.roomId === this.props.room.roomId);
|
||||
}
|
||||
@ -86,8 +83,8 @@ export default class UploadBar extends React.PureComponent<IProps, IState> {
|
||||
private calculateState(): IState {
|
||||
const [currentUpload, ...otherUploads] = this.getUploadsInRoom();
|
||||
return {
|
||||
currentUpload,
|
||||
currentFile: currentUpload?.fileName,
|
||||
currentPromise: currentUpload?.promise,
|
||||
currentLoaded: currentUpload?.loaded,
|
||||
currentTotal: currentUpload?.total,
|
||||
countFiles: otherUploads.length + 1,
|
||||
@ -103,7 +100,7 @@ export default class UploadBar extends React.PureComponent<IProps, IState> {
|
||||
|
||||
private onCancelClick = (ev: ButtonEvent) => {
|
||||
ev.preventDefault();
|
||||
ContentMessages.sharedInstance().cancelUpload(this.state.currentPromise!, this.context);
|
||||
ContentMessages.sharedInstance().cancelUpload(this.state.currentUpload!);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import { MatrixError } from "matrix-js-sdk/src/http-api";
|
||||
import { ConnectionError, MatrixError } from "matrix-js-sdk/src/http-api";
|
||||
import classNames from "classnames";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
@ -453,7 +453,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||
let errorText: ReactNode = _t("There was a problem communicating with the homeserver, " +
|
||||
"please try again later.") + (errCode ? " (" + errCode + ")" : "");
|
||||
|
||||
if (err["cors"] === 'rejected') { // browser-request specific error field
|
||||
if (err instanceof ConnectionError) {
|
||||
if (window.location.protocol === 'https:' &&
|
||||
(this.props.serverConfig.hsUrl.startsWith("http:") ||
|
||||
!this.props.serverConfig.hsUrl.startsWith("http"))
|
||||
|
@ -16,7 +16,6 @@ Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import request from 'browser-request';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import QuestionDialog from "./QuestionDialog";
|
||||
@ -37,22 +36,33 @@ export default class ChangelogDialog extends React.Component<IProps> {
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
private async fetchChanges(repo: string, oldVersion: string, newVersion: string): Promise<void> {
|
||||
const url = `https://riot.im/github/repos/${repo}/compare/${oldVersion}...${newVersion}`;
|
||||
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
|
||||
if (!res.ok) {
|
||||
this.setState({ [repo]: res.statusText });
|
||||
return;
|
||||
}
|
||||
|
||||
const body = await res.json();
|
||||
this.setState({ [repo]: body.commits });
|
||||
} catch (err) {
|
||||
this.setState({ [repo]: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const version = this.props.newVersion.split('-');
|
||||
const version2 = this.props.version.split('-');
|
||||
if (version == null || version2 == null) return;
|
||||
// parse versions of form: [vectorversion]-react-[react-sdk-version]-js-[js-sdk-version]
|
||||
for (let i=0; i<REPOS.length; i++) {
|
||||
for (let i = 0; i < REPOS.length; i++) {
|
||||
const oldVersion = version2[2*i];
|
||||
const newVersion = version[2*i];
|
||||
const url = `https://riot.im/github/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`;
|
||||
request(url, (err, response, body) => {
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
this.setState({ [REPOS[i]]: response.statusText });
|
||||
return;
|
||||
}
|
||||
this.setState({ [REPOS[i]]: JSON.parse(body).commits });
|
||||
});
|
||||
this.fetchChanges(REPOS[i], oldVersion, newVersion);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -654,12 +654,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||
const token = await authClient.getAccessToken();
|
||||
if (term !== this.state.filterText) return; // abandon hope
|
||||
|
||||
const lookup = await MatrixClientPeg.get().lookupThreePid(
|
||||
'email',
|
||||
term,
|
||||
undefined, // callback
|
||||
token,
|
||||
);
|
||||
const lookup = await MatrixClientPeg.get().lookupThreePid('email', term, token);
|
||||
if (term !== this.state.filterText) return; // abandon hope
|
||||
|
||||
if (!lookup || !lookup.mxid) {
|
||||
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { MatrixClient } from 'matrix-js-sdk/src/matrix';
|
||||
import { MatrixClient, Method } from 'matrix-js-sdk/src/matrix';
|
||||
import { logger } from 'matrix-js-sdk/src/logger';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
@ -33,17 +33,10 @@ import { SettingLevel } from "../../../settings/SettingLevel";
|
||||
* @throws if the proxy server is unreachable or not configured to the given homeserver
|
||||
*/
|
||||
async function syncHealthCheck(cli: MatrixClient): Promise<void> {
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), 10 * 1000); // 10s
|
||||
const url = cli.http.getUrl("/sync", {}, "/_matrix/client/unstable/org.matrix.msc3575");
|
||||
const res = await fetch(url, {
|
||||
signal: controller.signal,
|
||||
method: "POST",
|
||||
await cli.http.authedRequest(Method.Post, "/sync", undefined, undefined, {
|
||||
localTimeoutMs: 10 * 1000, // 10s
|
||||
prefix: "/_matrix/client/unstable/org.matrix.msc3575",
|
||||
});
|
||||
clearTimeout(id);
|
||||
if (res.status != 200) {
|
||||
throw new Error(`syncHealthCheck: server returned HTTP ${res.status}`);
|
||||
}
|
||||
logger.info("server natively support sliding sync OK");
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,7 @@ const MiniAvatarUploader: React.FC<IProps> = ({
|
||||
if (!ev.target.files?.length) return;
|
||||
setBusy(true);
|
||||
const file = ev.target.files[0];
|
||||
const uri = await cli.uploadContent(file);
|
||||
const { content_uri: uri } = await cli.uploadContent(file);
|
||||
await setAvatarUrl(uri);
|
||||
setBusy(false);
|
||||
}}
|
||||
|
@ -134,7 +134,7 @@ export default class RoomProfileSettings extends React.Component<IProps, IState>
|
||||
}
|
||||
|
||||
if (this.state.avatarFile) {
|
||||
const uri = await client.uploadContent(this.state.avatarFile);
|
||||
const { content_uri: uri } = await client.uploadContent(this.state.avatarFile);
|
||||
await client.sendStateEvent(this.props.roomId, 'm.room.avatar', { url: uri }, '');
|
||||
newState.avatarUrl = mediaFromMxc(uri).getSquareThumbnailHttp(96);
|
||||
newState.originalAvatarUrl = newState.avatarUrl;
|
||||
|
@ -148,7 +148,6 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||
const result = await MatrixClientPeg.get().lookupThreePid(
|
||||
'email',
|
||||
this.props.invitedEmail,
|
||||
undefined /* callback */,
|
||||
identityAccessToken,
|
||||
);
|
||||
this.setState({ invitedEmailMxid: result.mxid });
|
||||
|
@ -115,13 +115,13 @@ export default class ChangeAvatar extends React.Component<IProps, IState> {
|
||||
this.setState({
|
||||
phase: Phases.Uploading,
|
||||
});
|
||||
const httpPromise = MatrixClientPeg.get().uploadContent(file).then((url) => {
|
||||
const httpPromise = MatrixClientPeg.get().uploadContent(file).then(({ content_uri: url }) => {
|
||||
newUrl = url;
|
||||
if (this.props.room) {
|
||||
return MatrixClientPeg.get().sendStateEvent(
|
||||
this.props.room.roomId,
|
||||
'm.room.avatar',
|
||||
{ url: url },
|
||||
{ url },
|
||||
'',
|
||||
);
|
||||
} else {
|
||||
|
@ -111,7 +111,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
|
||||
logger.log(
|
||||
`Uploading new avatar, ${this.state.avatarFile.name} of type ${this.state.avatarFile.type},` +
|
||||
` (${this.state.avatarFile.size}) bytes`);
|
||||
const uri = await client.uploadContent(this.state.avatarFile);
|
||||
const { content_uri: uri } = await client.uploadContent(this.state.avatarFile);
|
||||
await client.setAvatarUrl(uri);
|
||||
newState.avatarUrl = mediaFromMxc(uri).getSquareThumbnailHttp(96);
|
||||
newState.originalAvatarUrl = newState.avatarUrl;
|
||||
|
@ -246,7 +246,7 @@ export default async function createRoom(opts: IOpts): Promise<string | null> {
|
||||
if (opts.avatar) {
|
||||
let url = opts.avatar;
|
||||
if (opts.avatar instanceof File) {
|
||||
url = await client.uploadContent(opts.avatar);
|
||||
({ content_uri: url } = await client.uploadContent(opts.avatar));
|
||||
}
|
||||
|
||||
createOpts.initial_state.push({
|
||||
|
@ -151,7 +151,7 @@ export class Media {
|
||||
* @param {MatrixClient} client? Optional client to use.
|
||||
* @returns {Media} The media object.
|
||||
*/
|
||||
export function mediaFromContent(content: IMediaEventContent, client?: MatrixClient): Media {
|
||||
export function mediaFromContent(content: Partial<IMediaEventContent>, client?: MatrixClient): Media {
|
||||
return new Media(prepEventContentAsMedia(content), client);
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,7 @@ export interface IMediaEventInfo {
|
||||
}
|
||||
|
||||
export interface IMediaEventContent {
|
||||
msgtype: string;
|
||||
body?: string;
|
||||
filename?: string; // `m.file` optional field
|
||||
url?: string; // required on unencrypted media
|
||||
@ -69,7 +70,7 @@ export interface IMediaObject {
|
||||
* @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 {
|
||||
export function prepEventContentAsMedia(content: Partial<IMediaEventContent>): IPreparedMedia {
|
||||
let thumbnail: IMediaObject = null;
|
||||
if (content?.info?.thumbnail_url) {
|
||||
thumbnail = {
|
||||
|
@ -16,13 +16,13 @@ limitations under the License.
|
||||
|
||||
import { ActionPayload } from "../payloads";
|
||||
import { Action } from "../actions";
|
||||
import { IUpload } from "../../models/IUpload";
|
||||
import { RoomUpload } from "../../models/RoomUpload";
|
||||
|
||||
export interface UploadPayload extends ActionPayload {
|
||||
/**
|
||||
* The upload with fields representing the new upload state.
|
||||
*/
|
||||
upload: IUpload;
|
||||
upload: RoomUpload;
|
||||
}
|
||||
|
||||
export interface UploadStartedPayload extends UploadPayload {
|
||||
|
@ -16,6 +16,7 @@
|
||||
"Error": "Error",
|
||||
"Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.",
|
||||
"Dismiss": "Dismiss",
|
||||
"Attachment": "Attachment",
|
||||
"The file '%(fileName)s' failed to upload.": "The file '%(fileName)s' failed to upload.",
|
||||
"The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "The file '%(fileName)s' exceeds this homeserver's size limit for uploads",
|
||||
"Upload Failed": "Upload Failed",
|
||||
@ -654,7 +655,6 @@
|
||||
"This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.",
|
||||
"Please <a>contact your service administrator</a> to continue using the service.": "Please <a>contact your service administrator</a> to continue using the service.",
|
||||
"Unable to connect to Homeserver. Retrying...": "Unable to connect to Homeserver. Retrying...",
|
||||
"Attachment": "Attachment",
|
||||
"%(items)s and %(count)s others|other": "%(items)s and %(count)s others",
|
||||
"%(items)s and %(count)s others|one": "%(items)s and one other",
|
||||
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
|
||||
|
@ -17,7 +17,6 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import request from 'browser-request';
|
||||
import counterpart from 'counterpart';
|
||||
import React from 'react';
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
@ -386,6 +385,11 @@ export function setMissingEntryGenerator(f: (value: string) => void) {
|
||||
counterpart.setMissingEntryGenerator(f);
|
||||
}
|
||||
|
||||
type Language = {
|
||||
fileName: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export function setLanguage(preferredLangs: string | string[]) {
|
||||
if (!Array.isArray(preferredLangs)) {
|
||||
preferredLangs = [preferredLangs];
|
||||
@ -396,8 +400,8 @@ export function setLanguage(preferredLangs: string | string[]) {
|
||||
plaf.setLanguage(preferredLangs);
|
||||
}
|
||||
|
||||
let langToUse;
|
||||
let availLangs;
|
||||
let langToUse: string;
|
||||
let availLangs: { [lang: string]: Language };
|
||||
return getLangsJson().then((result) => {
|
||||
availLangs = result;
|
||||
|
||||
@ -532,29 +536,21 @@ export function pickBestLanguage(langs: string[]): string {
|
||||
return langs[0];
|
||||
}
|
||||
|
||||
function getLangsJson(): Promise<object> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let url;
|
||||
if (typeof(webpackLangJsonUrl) === 'string') { // in Jest this 'url' isn't a URL, so just fall through
|
||||
url = webpackLangJsonUrl;
|
||||
} else {
|
||||
url = i18nFolder + 'languages.json';
|
||||
}
|
||||
request(
|
||||
{ method: "GET", url },
|
||||
(err, response, body) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
reject(new Error(`Failed to load ${url}, got ${response.status}`));
|
||||
return;
|
||||
}
|
||||
resolve(JSON.parse(body));
|
||||
},
|
||||
);
|
||||
});
|
||||
async function getLangsJson(): Promise<{ [lang: string]: Language }> {
|
||||
let url: string;
|
||||
if (typeof(webpackLangJsonUrl) === 'string') { // in Jest this 'url' isn't a URL, so just fall through
|
||||
url = webpackLangJsonUrl;
|
||||
} else {
|
||||
url = i18nFolder + 'languages.json';
|
||||
}
|
||||
|
||||
const res = await fetch(url, { method: "GET" });
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to load ${url}, got ${res.status}`);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
interface ICounterpartTranslation {
|
||||
@ -571,23 +567,14 @@ async function getLanguageRetry(langPath: string, num = 3): Promise<ICounterpart
|
||||
});
|
||||
}
|
||||
|
||||
function getLanguage(langPath: string): Promise<ICounterpartTranslation> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(
|
||||
{ method: "GET", url: langPath },
|
||||
(err, response, body) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
reject(new Error(`Failed to load ${langPath}, got ${response.status}`));
|
||||
return;
|
||||
}
|
||||
resolve(JSON.parse(body));
|
||||
},
|
||||
);
|
||||
});
|
||||
async function getLanguage(langPath: string): Promise<ICounterpartTranslation> {
|
||||
const res = await fetch(langPath, { method: "GET" });
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to load ${langPath}, got ${res.status}`);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export interface ICustomTranslations {
|
||||
|
@ -1,28 +0,0 @@
|
||||
/*
|
||||
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 { IEventRelation } from "matrix-js-sdk/src/matrix";
|
||||
import { IAbortablePromise } from "matrix-js-sdk/src/@types/partials";
|
||||
|
||||
export interface IUpload {
|
||||
fileName: string;
|
||||
roomId: string;
|
||||
relation?: IEventRelation;
|
||||
total: number;
|
||||
loaded: number;
|
||||
promise: IAbortablePromise<any>;
|
||||
canceled?: boolean;
|
||||
}
|
53
src/models/RoomUpload.ts
Normal file
53
src/models/RoomUpload.ts
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
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 { IEventRelation, UploadProgress } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { IEncryptedFile } from "../customisations/models/IMediaEventContent";
|
||||
|
||||
export class RoomUpload {
|
||||
public readonly abortController = new AbortController();
|
||||
public promise: Promise<{ url?: string, file?: IEncryptedFile }>;
|
||||
private uploaded = 0;
|
||||
|
||||
constructor(
|
||||
public readonly roomId: string,
|
||||
public readonly fileName: string,
|
||||
public readonly relation?: IEventRelation,
|
||||
public fileSize = 0,
|
||||
) {}
|
||||
|
||||
public onProgress(progress: UploadProgress) {
|
||||
this.uploaded = progress.loaded;
|
||||
this.fileSize = progress.total;
|
||||
}
|
||||
|
||||
public abort(): void {
|
||||
this.abortController.abort();
|
||||
}
|
||||
|
||||
public get cancelled(): boolean {
|
||||
return this.abortController.signal.aborted;
|
||||
}
|
||||
|
||||
public get total(): number {
|
||||
return this.fileSize;
|
||||
}
|
||||
|
||||
public get loaded(): number {
|
||||
return this.uploaded;
|
||||
}
|
||||
}
|
@ -184,7 +184,7 @@ export default class MultiInviter {
|
||||
}
|
||||
}
|
||||
|
||||
return this.matrixClient.invite(roomId, addr, undefined, this.reason);
|
||||
return this.matrixClient.invite(roomId, addr, this.reason);
|
||||
} else {
|
||||
throw new Error('Unsupported address');
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { IAbortablePromise, MatrixClient, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
import { TypedEventEmitter } from "matrix-js-sdk/src/models/typed-event-emitter";
|
||||
|
||||
import {
|
||||
@ -108,7 +108,7 @@ export class VoiceBroadcastRecording
|
||||
await this.sendVoiceMessage(chunk, url, file);
|
||||
};
|
||||
|
||||
private uploadFile(chunk: ChunkRecordedPayload): IAbortablePromise<{ url?: string, file?: IEncryptedFile }> {
|
||||
private uploadFile(chunk: ChunkRecordedPayload): ReturnType<typeof uploadFile> {
|
||||
return uploadFile(
|
||||
this.client,
|
||||
this.infoEvent.getRoomId(),
|
||||
|
@ -15,15 +15,31 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { IImageInfo, ISendEventResponse, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { IImageInfo, ISendEventResponse, MatrixClient, RelationType, UploadResponse } from "matrix-js-sdk/src/matrix";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
import encrypt, { IEncryptedFile } from "matrix-encrypt-attachment";
|
||||
|
||||
import ContentMessages from "../src/ContentMessages";
|
||||
import ContentMessages, { UploadCanceledError, uploadFile } from "../src/ContentMessages";
|
||||
import { doMaybeLocalRoomAction } from "../src/utils/local-room";
|
||||
import { createTestClient } from "./test-utils";
|
||||
import { BlurhashEncoder } from "../src/BlurhashEncoder";
|
||||
|
||||
jest.mock("matrix-encrypt-attachment", () => ({ encryptAttachment: jest.fn().mockResolvedValue({}) }));
|
||||
|
||||
jest.mock("../src/BlurhashEncoder", () => ({
|
||||
BlurhashEncoder: {
|
||||
instance: {
|
||||
getBlurhash: jest.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock("../src/utils/local-room", () => ({
|
||||
doMaybeLocalRoomAction: jest.fn(),
|
||||
}));
|
||||
|
||||
const createElement = document.createElement.bind(document);
|
||||
|
||||
describe("ContentMessages", () => {
|
||||
const stickerUrl = "https://example.com/sticker";
|
||||
const roomId = "!room:example.com";
|
||||
@ -36,6 +52,9 @@ describe("ContentMessages", () => {
|
||||
beforeEach(() => {
|
||||
client = {
|
||||
sendStickerMessage: jest.fn(),
|
||||
sendMessage: jest.fn(),
|
||||
isRoomEncrypted: jest.fn().mockReturnValue(false),
|
||||
uploadContent: jest.fn().mockResolvedValue({ content_uri: "mxc://server/file" }),
|
||||
} as unknown as MatrixClient;
|
||||
contentMessages = new ContentMessages();
|
||||
prom = Promise.resolve(null);
|
||||
@ -65,4 +84,226 @@ describe("ContentMessages", () => {
|
||||
expect(client.sendStickerMessage).toHaveBeenCalledWith(roomId, null, stickerUrl, imageInfo, text);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendContentToRoom", () => {
|
||||
const roomId = "!roomId:server";
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(global.Image.prototype, 'src', {
|
||||
// Define the property setter
|
||||
set(src) {
|
||||
setTimeout(() => this.onload());
|
||||
},
|
||||
});
|
||||
Object.defineProperty(global.Image.prototype, 'height', {
|
||||
get() { return 600; },
|
||||
});
|
||||
Object.defineProperty(global.Image.prototype, 'width', {
|
||||
get() { return 800; },
|
||||
});
|
||||
mocked(doMaybeLocalRoomAction).mockImplementation((
|
||||
roomId: string,
|
||||
fn: (actualRoomId: string) => Promise<ISendEventResponse>,
|
||||
) => fn(roomId));
|
||||
mocked(BlurhashEncoder.instance.getBlurhash).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it("should use m.image for image files", async () => {
|
||||
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
|
||||
const file = new File([], "fileName", { type: "image/jpeg" });
|
||||
await contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
|
||||
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, expect.objectContaining({
|
||||
url: "mxc://server/file",
|
||||
msgtype: "m.image",
|
||||
}));
|
||||
});
|
||||
|
||||
it("should fall back to m.file for invalid image files", async () => {
|
||||
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
|
||||
const file = new File([], "fileName", { type: "image/png" });
|
||||
await contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
|
||||
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, expect.objectContaining({
|
||||
url: "mxc://server/file",
|
||||
msgtype: "m.file",
|
||||
}));
|
||||
});
|
||||
|
||||
it("should use m.video for video files", async () => {
|
||||
jest.spyOn(document, "createElement").mockImplementation(tagName => {
|
||||
const element = createElement(tagName);
|
||||
if (tagName === "video") {
|
||||
element.load = jest.fn();
|
||||
element.play = () => element.onloadeddata(new Event("loadeddata"));
|
||||
element.pause = jest.fn();
|
||||
Object.defineProperty(element, 'videoHeight', {
|
||||
get() { return 600; },
|
||||
});
|
||||
Object.defineProperty(element, 'videoWidth', {
|
||||
get() { return 800; },
|
||||
});
|
||||
}
|
||||
return element;
|
||||
});
|
||||
|
||||
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
|
||||
const file = new File([], "fileName", { type: "video/mp4" });
|
||||
await contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
|
||||
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, expect.objectContaining({
|
||||
url: "mxc://server/file",
|
||||
msgtype: "m.video",
|
||||
}));
|
||||
});
|
||||
|
||||
it("should use m.audio for audio files", async () => {
|
||||
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
|
||||
const file = new File([], "fileName", { type: "audio/mp3" });
|
||||
await contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
|
||||
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, expect.objectContaining({
|
||||
url: "mxc://server/file",
|
||||
msgtype: "m.audio",
|
||||
}));
|
||||
});
|
||||
|
||||
it("should default to name 'Attachment' if file doesn't have a name", async () => {
|
||||
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
|
||||
const file = new File([], "", { type: "text/plain" });
|
||||
await contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
|
||||
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, expect.objectContaining({
|
||||
url: "mxc://server/file",
|
||||
msgtype: "m.file",
|
||||
body: "Attachment",
|
||||
}));
|
||||
});
|
||||
|
||||
it("should keep RoomUpload's total and loaded values up to date", async () => {
|
||||
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
|
||||
const file = new File([], "", { type: "text/plain" });
|
||||
const prom = contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
|
||||
const [upload] = contentMessages.getCurrentUploads();
|
||||
|
||||
expect(upload.loaded).toBe(0);
|
||||
expect(upload.total).toBe(file.size);
|
||||
const { progressHandler } = mocked(client.uploadContent).mock.calls[0][1];
|
||||
progressHandler({ loaded: 123, total: 1234 });
|
||||
expect(upload.loaded).toBe(123);
|
||||
expect(upload.total).toBe(1234);
|
||||
await prom;
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCurrentUploads", () => {
|
||||
const file1 = new File([], "file1");
|
||||
const file2 = new File([], "file2");
|
||||
const roomId = "!roomId:server";
|
||||
|
||||
beforeEach(() => {
|
||||
mocked(doMaybeLocalRoomAction).mockImplementation((
|
||||
roomId: string,
|
||||
fn: (actualRoomId: string) => Promise<ISendEventResponse>,
|
||||
) => fn(roomId));
|
||||
});
|
||||
|
||||
it("should return only uploads for the given relation", async () => {
|
||||
const relation = {
|
||||
rel_type: RelationType.Thread,
|
||||
event_id: "!threadId:server",
|
||||
};
|
||||
const p1 = contentMessages.sendContentToRoom(file1, roomId, relation, client, undefined);
|
||||
const p2 = contentMessages.sendContentToRoom(file2, roomId, undefined, client, undefined);
|
||||
|
||||
const uploads = contentMessages.getCurrentUploads(relation);
|
||||
expect(uploads).toHaveLength(1);
|
||||
expect(uploads[0].relation).toEqual(relation);
|
||||
expect(uploads[0].fileName).toEqual("file1");
|
||||
await Promise.all([p1, p2]);
|
||||
});
|
||||
|
||||
it("should return only uploads for no relation when not passed one", async () => {
|
||||
const relation = {
|
||||
rel_type: RelationType.Thread,
|
||||
event_id: "!threadId:server",
|
||||
};
|
||||
const p1 = contentMessages.sendContentToRoom(file1, roomId, relation, client, undefined);
|
||||
const p2 = contentMessages.sendContentToRoom(file2, roomId, undefined, client, undefined);
|
||||
|
||||
const uploads = contentMessages.getCurrentUploads();
|
||||
expect(uploads).toHaveLength(1);
|
||||
expect(uploads[0].relation).toEqual(undefined);
|
||||
expect(uploads[0].fileName).toEqual("file2");
|
||||
await Promise.all([p1, p2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("cancelUpload", () => {
|
||||
it("should cancel in-flight upload", async () => {
|
||||
const deferred = defer<UploadResponse>();
|
||||
mocked(client.uploadContent).mockReturnValue(deferred.promise);
|
||||
const file1 = new File([], "file1");
|
||||
const prom = contentMessages.sendContentToRoom(file1, roomId, undefined, client, undefined);
|
||||
const { abortController } = mocked(client.uploadContent).mock.calls[0][1];
|
||||
expect(abortController.signal.aborted).toBeFalsy();
|
||||
const [upload] = contentMessages.getCurrentUploads();
|
||||
contentMessages.cancelUpload(upload);
|
||||
expect(abortController.signal.aborted).toBeTruthy();
|
||||
deferred.resolve({} as UploadResponse);
|
||||
await prom;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("uploadFile", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const client = createTestClient();
|
||||
|
||||
it("should not encrypt the file if the room isn't encrypted", async () => {
|
||||
mocked(client.isRoomEncrypted).mockReturnValue(false);
|
||||
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
|
||||
const progressHandler = jest.fn();
|
||||
const file = new Blob([]);
|
||||
|
||||
const res = await uploadFile(client, "!roomId:server", file, progressHandler);
|
||||
|
||||
expect(res.url).toBe("mxc://server/file");
|
||||
expect(res.file).toBeFalsy();
|
||||
expect(encrypt.encryptAttachment).not.toHaveBeenCalled();
|
||||
expect(client.uploadContent).toHaveBeenCalledWith(file, expect.objectContaining({ progressHandler }));
|
||||
});
|
||||
|
||||
it("should encrypt the file if the room is encrypted", async () => {
|
||||
mocked(client.isRoomEncrypted).mockReturnValue(true);
|
||||
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
|
||||
mocked(encrypt.encryptAttachment).mockResolvedValue({
|
||||
data: new ArrayBuffer(123),
|
||||
info: {} as IEncryptedFile,
|
||||
});
|
||||
const progressHandler = jest.fn();
|
||||
const file = new Blob(["123"]);
|
||||
|
||||
const res = await uploadFile(client, "!roomId:server", file, progressHandler);
|
||||
|
||||
expect(res.url).toBeFalsy();
|
||||
expect(res.file).toEqual(expect.objectContaining({
|
||||
url: "mxc://server/file",
|
||||
}));
|
||||
expect(encrypt.encryptAttachment).toHaveBeenCalled();
|
||||
expect(client.uploadContent).toHaveBeenCalledWith(expect.any(Blob), expect.objectContaining({
|
||||
progressHandler,
|
||||
includeFilename: false,
|
||||
}));
|
||||
expect(mocked(client.uploadContent).mock.calls[0][0]).not.toBe(file);
|
||||
});
|
||||
|
||||
it("should throw UploadCanceledError upon aborting the upload", async () => {
|
||||
mocked(client.isRoomEncrypted).mockReturnValue(false);
|
||||
const deferred = defer<UploadResponse>();
|
||||
mocked(client.uploadContent).mockReturnValue(deferred.promise);
|
||||
const file = new Blob([]);
|
||||
|
||||
const prom = uploadFile(client, "!roomId:server", file);
|
||||
mocked(client.uploadContent).mock.calls[0][1].abortController.abort();
|
||||
deferred.resolve({ content_uri: "mxc://foo/bar" });
|
||||
await expect(prom).rejects.toThrowError(UploadCanceledError);
|
||||
});
|
||||
});
|
||||
|
@ -14,47 +14,199 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
|
||||
import ScalarAuthClient from '../src/ScalarAuthClient';
|
||||
import { MatrixClientPeg } from '../src/MatrixClientPeg';
|
||||
import { stubClient } from './test-utils';
|
||||
import SdkConfig from "../src/SdkConfig";
|
||||
import { WidgetType } from "../src/widgets/WidgetType";
|
||||
|
||||
describe('ScalarAuthClient', function() {
|
||||
const apiUrl = 'test.com/api';
|
||||
const uiUrl = 'test.com/app';
|
||||
const apiUrl = 'https://test.com/api';
|
||||
const uiUrl = 'https:/test.com/app';
|
||||
const tokenObject = {
|
||||
access_token: "token",
|
||||
token_type: "Bearer",
|
||||
matrix_server_name: "localhost",
|
||||
expires_in: 999,
|
||||
};
|
||||
|
||||
let client;
|
||||
beforeEach(function() {
|
||||
window.localStorage.getItem = jest.fn((arg) => {
|
||||
if (arg === "mx_scalar_token") return "brokentoken";
|
||||
});
|
||||
stubClient();
|
||||
jest.clearAllMocks();
|
||||
client = stubClient();
|
||||
});
|
||||
|
||||
it('should request a new token if the old one fails', async function() {
|
||||
const sac = new ScalarAuthClient(apiUrl, uiUrl);
|
||||
const sac = new ScalarAuthClient(apiUrl + 0, uiUrl);
|
||||
|
||||
// @ts-ignore unhappy with Promise calls
|
||||
jest.spyOn(sac, 'getAccountName').mockImplementation((arg: string) => {
|
||||
switch (arg) {
|
||||
case "brokentoken":
|
||||
return Promise.reject({
|
||||
message: "Invalid token",
|
||||
});
|
||||
case "wokentoken":
|
||||
default:
|
||||
return Promise.resolve(MatrixClientPeg.get().getUserId());
|
||||
}
|
||||
fetchMock.get("https://test.com/api0/account?scalar_token=brokentoken&v=1.1", {
|
||||
body: { message: "Invalid token" },
|
||||
});
|
||||
|
||||
MatrixClientPeg.get().getOpenIdToken = jest.fn().mockResolvedValue('this is your openid token');
|
||||
fetchMock.get("https://test.com/api0/account?scalar_token=wokentoken&v=1.1", {
|
||||
body: { user_id: client.getUserId() },
|
||||
});
|
||||
|
||||
client.getOpenIdToken = jest.fn().mockResolvedValue(tokenObject);
|
||||
|
||||
sac.exchangeForScalarToken = jest.fn((arg) => {
|
||||
if (arg === "this is your openid token") return Promise.resolve("wokentoken");
|
||||
if (arg === tokenObject) return Promise.resolve("wokentoken");
|
||||
});
|
||||
|
||||
await sac.connect();
|
||||
|
||||
expect(sac.exchangeForScalarToken).toBeCalledWith('this is your openid token');
|
||||
expect(sac.exchangeForScalarToken).toBeCalledWith(tokenObject);
|
||||
expect(sac.hasCredentials).toBeTruthy();
|
||||
// @ts-ignore private property
|
||||
expect(sac.scalarToken).toEqual('wokentoken');
|
||||
});
|
||||
|
||||
describe("exchangeForScalarToken", () => {
|
||||
it("should return `scalar_token` from API /register", async () => {
|
||||
const sac = new ScalarAuthClient(apiUrl + 1, uiUrl);
|
||||
|
||||
fetchMock.postOnce("https://test.com/api1/register?v=1.1", {
|
||||
body: { scalar_token: "stoken" },
|
||||
});
|
||||
|
||||
await expect(sac.exchangeForScalarToken(tokenObject)).resolves.toBe("stoken");
|
||||
});
|
||||
|
||||
it("should throw upon non-20x code", async () => {
|
||||
const sac = new ScalarAuthClient(apiUrl + 2, uiUrl);
|
||||
|
||||
fetchMock.postOnce("https://test.com/api2/register?v=1.1", {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
await expect(sac.exchangeForScalarToken(tokenObject)).rejects.toThrow("Scalar request failed: 500");
|
||||
});
|
||||
|
||||
it("should throw if scalar_token is missing in response", async () => {
|
||||
const sac = new ScalarAuthClient(apiUrl + 3, uiUrl);
|
||||
|
||||
fetchMock.postOnce("https://test.com/api3/register?v=1.1", {
|
||||
body: {},
|
||||
});
|
||||
|
||||
await expect(sac.exchangeForScalarToken(tokenObject)).rejects.toThrow("Missing scalar_token in response");
|
||||
});
|
||||
});
|
||||
|
||||
describe("registerForToken", () => {
|
||||
it("should call `termsInteractionCallback` upon M_TERMS_NOT_SIGNED error", async () => {
|
||||
const sac = new ScalarAuthClient(apiUrl + 4, uiUrl);
|
||||
const termsInteractionCallback = jest.fn();
|
||||
sac.setTermsInteractionCallback(termsInteractionCallback);
|
||||
fetchMock.get("https://test.com/api4/account?scalar_token=testtoken1&v=1.1", {
|
||||
body: { errcode: "M_TERMS_NOT_SIGNED" },
|
||||
});
|
||||
sac.exchangeForScalarToken = jest.fn(() => Promise.resolve("testtoken1"));
|
||||
mocked(client.getTerms).mockResolvedValue({ policies: [] });
|
||||
|
||||
await expect(sac.registerForToken()).resolves.toBe("testtoken1");
|
||||
});
|
||||
|
||||
it("should throw upon non-20x code", async () => {
|
||||
const sac = new ScalarAuthClient(apiUrl + 5, uiUrl);
|
||||
fetchMock.get("https://test.com/api5/account?scalar_token=testtoken2&v=1.1", {
|
||||
body: { errcode: "SERVER_IS_SAD" },
|
||||
status: 500,
|
||||
});
|
||||
sac.exchangeForScalarToken = jest.fn(() => Promise.resolve("testtoken2"));
|
||||
|
||||
await expect(sac.registerForToken()).rejects.toBeTruthy();
|
||||
});
|
||||
|
||||
it("should throw if user_id is missing from response", async () => {
|
||||
const sac = new ScalarAuthClient(apiUrl + 6, uiUrl);
|
||||
fetchMock.get("https://test.com/api6/account?scalar_token=testtoken3&v=1.1", {
|
||||
body: {},
|
||||
});
|
||||
sac.exchangeForScalarToken = jest.fn(() => Promise.resolve("testtoken3"));
|
||||
|
||||
await expect(sac.registerForToken()).rejects.toThrow("Missing user_id in response");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getScalarPageTitle", () => {
|
||||
let sac: ScalarAuthClient;
|
||||
|
||||
beforeEach(async () => {
|
||||
SdkConfig.put({
|
||||
integrations_rest_url: apiUrl + 7,
|
||||
integrations_ui_url: uiUrl,
|
||||
});
|
||||
|
||||
window.localStorage.setItem("mx_scalar_token_at_https://test.com/api7", "wokentoken1");
|
||||
fetchMock.get("https://test.com/api7/account?scalar_token=wokentoken1&v=1.1", {
|
||||
body: { user_id: client.getUserId() },
|
||||
});
|
||||
|
||||
sac = new ScalarAuthClient(apiUrl + 7, uiUrl);
|
||||
await sac.connect();
|
||||
});
|
||||
|
||||
it("should return `cached_title` from API /widgets/title_lookup", async () => {
|
||||
const url = "google.com";
|
||||
fetchMock.get("https://test.com/api7/widgets/title_lookup?scalar_token=wokentoken1&curl=" + url, {
|
||||
body: {
|
||||
page_title_cache_item: {
|
||||
cached_title: "Google",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(sac.getScalarPageTitle(url)).resolves.toBe("Google");
|
||||
});
|
||||
|
||||
it("should throw upon non-20x code", async () => {
|
||||
const url = "yahoo.com";
|
||||
fetchMock.get("https://test.com/api7/widgets/title_lookup?scalar_token=wokentoken1&curl=" + url, {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
await expect(sac.getScalarPageTitle(url)).rejects.toThrow("Scalar request failed: 500");
|
||||
});
|
||||
});
|
||||
|
||||
describe("disableWidgetAssets", () => {
|
||||
let sac: ScalarAuthClient;
|
||||
|
||||
beforeEach(async () => {
|
||||
SdkConfig.put({
|
||||
integrations_rest_url: apiUrl + 8,
|
||||
integrations_ui_url: uiUrl,
|
||||
});
|
||||
|
||||
window.localStorage.setItem("mx_scalar_token_at_https://test.com/api8", "wokentoken1");
|
||||
fetchMock.get("https://test.com/api8/account?scalar_token=wokentoken1&v=1.1", {
|
||||
body: { user_id: client.getUserId() },
|
||||
});
|
||||
|
||||
sac = new ScalarAuthClient(apiUrl + 8, uiUrl);
|
||||
await sac.connect();
|
||||
});
|
||||
|
||||
it("should send state=disable to API /widgets/set_assets_state", async () => {
|
||||
fetchMock.get("https://test.com/api8/widgets/set_assets_state?scalar_token=wokentoken1" +
|
||||
"&widget_type=m.custom&widget_id=id1&state=disable", {
|
||||
body: "OK",
|
||||
});
|
||||
|
||||
await expect(sac.disableWidgetAssets(WidgetType.CUSTOM, "id1")).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("should throw upon non-20x code", async () => {
|
||||
fetchMock.get("https://test.com/api8/widgets/set_assets_state?scalar_token=wokentoken1" +
|
||||
"&widget_type=m.custom&widget_id=id2&state=disable", {
|
||||
status: 500,
|
||||
});
|
||||
|
||||
await expect(sac.disableWidgetAssets(WidgetType.CUSTOM, "id2"))
|
||||
.rejects.toThrow("Scalar request failed: 500");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { IAbortablePromise, IEncryptedFile, IUploadOpts, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { IEncryptedFile, UploadOpts, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { createVoiceMessageRecording, VoiceMessageRecording } from "../../src/audio/VoiceMessageRecording";
|
||||
import { RecordingState, VoiceRecording } from "../../src/audio/VoiceRecording";
|
||||
@ -161,8 +161,8 @@ describe("VoiceMessageRecording", () => {
|
||||
matrixClient: MatrixClient,
|
||||
roomId: string,
|
||||
file: File | Blob,
|
||||
_progressHandler?: IUploadOpts["progressHandler"],
|
||||
): IAbortablePromise<{ url?: string, file?: IEncryptedFile }> => {
|
||||
_progressHandler?: UploadOpts["progressHandler"],
|
||||
): Promise<{ url?: string, file?: IEncryptedFile }> => {
|
||||
uploadFileClient = matrixClient;
|
||||
uploadFileRoomId = roomId;
|
||||
uploadBlob = file;
|
||||
|
58
test/components/views/context_menus/EmbeddedPage-test.tsx
Normal file
58
test/components/views/context_menus/EmbeddedPage-test.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import { _t } from "../../../../src/languageHandler";
|
||||
import EmbeddedPage from "../../../../src/components/structures/EmbeddedPage";
|
||||
|
||||
jest.mock("../../../../src/languageHandler", () => ({
|
||||
_t: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("<EmbeddedPage />", () => {
|
||||
it("should translate _t strings", async () => {
|
||||
mocked(_t).mockReturnValue("Przeglądaj pokoje");
|
||||
fetchMock.get("https://home.page", {
|
||||
body: '<h1>_t("Explore rooms")</h1>',
|
||||
});
|
||||
|
||||
const { asFragment } = render(<EmbeddedPage url="https://home.page" />);
|
||||
await screen.findByText("Przeglądaj pokoje");
|
||||
expect(_t).toHaveBeenCalledWith("Explore rooms");
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should show error if unable to load", async () => {
|
||||
mocked(_t).mockReturnValue("Couldn't load page");
|
||||
fetchMock.get("https://other.page", {
|
||||
status: 404,
|
||||
});
|
||||
|
||||
const { asFragment } = render(<EmbeddedPage url="https://other.page" />);
|
||||
await screen.findByText("Couldn't load page");
|
||||
expect(_t).toHaveBeenCalledWith("Couldn't load page");
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render nothing if no url given", () => {
|
||||
const { asFragment } = render(<EmbeddedPage />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -0,0 +1,43 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<EmbeddedPage /> should render nothing if no url given 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="undefined undefined_guest"
|
||||
>
|
||||
<div
|
||||
class="undefined_body"
|
||||
/>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<EmbeddedPage /> should show error if unable to load 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="undefined undefined_guest"
|
||||
>
|
||||
<div
|
||||
class="undefined_body"
|
||||
>
|
||||
Couldn't load page
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<EmbeddedPage /> should translate _t strings 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="undefined undefined_guest"
|
||||
>
|
||||
<div
|
||||
class="undefined_body"
|
||||
>
|
||||
<h1>
|
||||
Przeglądaj pokoje
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
104
test/components/views/dialogs/ChangelogDialog-test.tsx
Normal file
104
test/components/views/dialogs/ChangelogDialog-test.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
import { render, screen, waitForElementToBeRemoved } from "@testing-library/react";
|
||||
|
||||
import ChangelogDialog from "../../../../src/components/views/dialogs/ChangelogDialog";
|
||||
|
||||
describe("<ChangelogDialog />", () => {
|
||||
it("should fetch github proxy url for each repo with old and new version strings", async () => {
|
||||
const webUrl = "https://riot.im/github/repos/vector-im/element-web/compare/oldsha1...newsha1";
|
||||
fetchMock.get(webUrl, {
|
||||
url: "https://api.github.com/repos/vector-im/element-web/compare/master...develop",
|
||||
html_url: "https://github.com/vector-im/element-web/compare/master...develop",
|
||||
permalink_url: "https://github.com/vector-im/element-web/compare/vector-im:72ca95e...vector-im:8891698",
|
||||
diff_url: "https://github.com/vector-im/element-web/compare/master...develop.diff",
|
||||
patch_url: "https://github.com/vector-im/element-web/compare/master...develop.patch",
|
||||
base_commit: {},
|
||||
merge_base_commit: {},
|
||||
status: "ahead",
|
||||
ahead_by: 24,
|
||||
behind_by: 0,
|
||||
total_commits: 24,
|
||||
commits: [{
|
||||
sha: "commit-sha",
|
||||
html_url: "https://api.github.com/repos/vector-im/element-web/commit/commit-sha",
|
||||
commit: { message: "This is the first commit message" },
|
||||
}],
|
||||
files: [],
|
||||
});
|
||||
const reactUrl = "https://riot.im/github/repos/matrix-org/matrix-react-sdk/compare/oldsha2...newsha2";
|
||||
fetchMock.get(reactUrl, {
|
||||
url: "https://api.github.com/repos/matrix-org/matrix-react-sdk/compare/master...develop",
|
||||
html_url: "https://github.com/matrix-org/matrix-react-sdk/compare/master...develop",
|
||||
permalink_url: "https://github.com/matrix-org/matrix-react-sdk/compare/matrix-org:cdb00...matrix-org:4a926",
|
||||
diff_url: "https://github.com/matrix-org/matrix-react-sdk/compare/master...develop.diff",
|
||||
patch_url: "https://github.com/matrix-org/matrix-react-sdk/compare/master...develop.patch",
|
||||
base_commit: {},
|
||||
merge_base_commit: {},
|
||||
status: "ahead",
|
||||
ahead_by: 83,
|
||||
behind_by: 0,
|
||||
total_commits: 83,
|
||||
commits: [{
|
||||
sha: "commit-sha0",
|
||||
html_url: "https://api.github.com/repos/matrix-org/matrix-react-sdk/commit/commit-sha",
|
||||
commit: { message: "This is a commit message" },
|
||||
}],
|
||||
files: [],
|
||||
});
|
||||
const jsUrl = "https://riot.im/github/repos/matrix-org/matrix-js-sdk/compare/oldsha3...newsha3";
|
||||
fetchMock.get(jsUrl, {
|
||||
url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/compare/master...develop",
|
||||
html_url: "https://github.com/matrix-org/matrix-js-sdk/compare/master...develop",
|
||||
permalink_url: "https://github.com/matrix-org/matrix-js-sdk/compare/matrix-org:6166a8f...matrix-org:fec350",
|
||||
diff_url: "https://github.com/matrix-org/matrix-js-sdk/compare/master...develop.diff",
|
||||
patch_url: "https://github.com/matrix-org/matrix-js-sdk/compare/master...develop.patch",
|
||||
base_commit: {},
|
||||
merge_base_commit: {},
|
||||
status: "ahead",
|
||||
ahead_by: 48,
|
||||
behind_by: 0,
|
||||
total_commits: 48,
|
||||
commits: [{
|
||||
sha: "commit-sha1",
|
||||
html_url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha1",
|
||||
commit: { message: "This is a commit message" },
|
||||
}, {
|
||||
sha: "commit-sha2",
|
||||
html_url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha2",
|
||||
commit: { message: "This is another commit message" },
|
||||
}],
|
||||
files: [],
|
||||
});
|
||||
|
||||
const newVersion = "newsha1-react-newsha2-js-newsha3";
|
||||
const oldVersion = "oldsha1-react-oldsha2-js-oldsha3";
|
||||
const { asFragment } = render((
|
||||
<ChangelogDialog newVersion={newVersion} version={oldVersion} onFinished={jest.fn()} />
|
||||
));
|
||||
|
||||
// Wait for spinners to go away
|
||||
await waitForElementToBeRemoved(screen.getAllByRole("progressbar"));
|
||||
|
||||
expect(fetchMock).toHaveFetched(webUrl);
|
||||
expect(fetchMock).toHaveFetched(reactUrl);
|
||||
expect(fetchMock).toHaveFetched(jsUrl);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -26,6 +26,11 @@ import SdkConfig from "../../../../src/SdkConfig";
|
||||
import { ValidatedServerConfig } from "../../../../src/utils/ValidatedServerConfig";
|
||||
import { IConfigOptions } from "../../../../src/IConfigOptions";
|
||||
|
||||
const mockGetAccessToken = jest.fn().mockResolvedValue("getAccessToken");
|
||||
jest.mock("../../../../src/IdentityAuthClient", () => jest.fn().mockImplementation(() => ({
|
||||
getAccessToken: mockGetAccessToken,
|
||||
})));
|
||||
|
||||
describe("InviteDialog", () => {
|
||||
const roomId = "!111111111111111111:example.org";
|
||||
const aliceId = "@alice:example.org";
|
||||
@ -42,6 +47,14 @@ describe("InviteDialog", () => {
|
||||
getProfileInfo: jest.fn().mockRejectedValue({ errcode: "" }),
|
||||
getIdentityServerUrl: jest.fn(),
|
||||
searchUserDirectory: jest.fn().mockResolvedValue({}),
|
||||
lookupThreePid: jest.fn(),
|
||||
registerWithIdentityServer: jest.fn().mockResolvedValue({
|
||||
access_token: "access_token",
|
||||
token: "token",
|
||||
}),
|
||||
getOpenIdToken: jest.fn().mockResolvedValue({}),
|
||||
getIdentityAccount: jest.fn().mockResolvedValue({}),
|
||||
getTerms: jest.fn().mockResolvedValue({ policies: [] }),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@ -85,7 +98,7 @@ describe("InviteDialog", () => {
|
||||
expect(screen.queryByText("Invite to Room")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should suggest valid MXIDs even if unknown", () => {
|
||||
it("should suggest valid MXIDs even if unknown", async () => {
|
||||
render((
|
||||
<InviteDialog
|
||||
kind={KIND_INVITE}
|
||||
@ -95,7 +108,7 @@ describe("InviteDialog", () => {
|
||||
/>
|
||||
));
|
||||
|
||||
expect(screen.queryByText("@localpart:server.tld")).toBeFalsy();
|
||||
await screen.findAllByText("@localpart:server.tld"); // Using findAllByText as the MXID is used for name too
|
||||
});
|
||||
|
||||
it("should not suggest invalid MXIDs", () => {
|
||||
@ -110,4 +123,48 @@ describe("InviteDialog", () => {
|
||||
|
||||
expect(screen.queryByText("@localpart:server:tld")).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should lookup inputs which look like email addresses", async () => {
|
||||
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
|
||||
mockClient.lookupThreePid.mockResolvedValue({
|
||||
address: "foobar@email.com",
|
||||
medium: "email",
|
||||
mxid: "@foobar:server",
|
||||
});
|
||||
mockClient.getProfileInfo.mockResolvedValue({
|
||||
displayname: "Mr. Foo",
|
||||
avatar_url: "mxc://foo/bar",
|
||||
});
|
||||
|
||||
render((
|
||||
<InviteDialog
|
||||
kind={KIND_INVITE}
|
||||
roomId={roomId}
|
||||
onFinished={jest.fn()}
|
||||
initialText="foobar@email.com"
|
||||
/>
|
||||
));
|
||||
|
||||
await screen.findByText("Mr. Foo");
|
||||
await screen.findByText("@foobar:server");
|
||||
expect(mockClient.lookupThreePid).toHaveBeenCalledWith("email", "foobar@email.com", expect.anything());
|
||||
expect(mockClient.getProfileInfo).toHaveBeenCalledWith("@foobar:server");
|
||||
});
|
||||
|
||||
it("should suggest e-mail even if lookup fails", async () => {
|
||||
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
|
||||
mockClient.lookupThreePid.mockResolvedValue({});
|
||||
|
||||
render((
|
||||
<InviteDialog
|
||||
kind={KIND_INVITE}
|
||||
roomId={roomId}
|
||||
onFinished={jest.fn()}
|
||||
initialText="foobar@email.com"
|
||||
/>
|
||||
));
|
||||
|
||||
await screen.findByText("foobar@email.com");
|
||||
await screen.findByText("Invite by email");
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,135 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ChangelogDialog /> should fetch github proxy url for each repo with old and new version strings 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_QuestionDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header mx_Dialog_headerWithCancel"
|
||||
>
|
||||
<h2
|
||||
class="mx_Heading_h2 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Changelog
|
||||
</h2>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_ChangelogDialog_content"
|
||||
>
|
||||
<div>
|
||||
<h2>
|
||||
vector-im/element-web
|
||||
</h2>
|
||||
<ul>
|
||||
<li
|
||||
class="mx_ChangelogDialog_li"
|
||||
>
|
||||
<a
|
||||
href="https://api.github.com/repos/vector-im/element-web/commit/commit-sha"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
This is the first commit message
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h2>
|
||||
matrix-org/matrix-react-sdk
|
||||
</h2>
|
||||
<ul>
|
||||
<li
|
||||
class="mx_ChangelogDialog_li"
|
||||
>
|
||||
<a
|
||||
href="https://api.github.com/repos/matrix-org/matrix-react-sdk/commit/commit-sha"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
This is a commit message
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h2>
|
||||
matrix-org/matrix-js-sdk
|
||||
</h2>
|
||||
<ul>
|
||||
<li
|
||||
class="mx_ChangelogDialog_li"
|
||||
>
|
||||
<a
|
||||
href="https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha1"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
This is a commit message
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="mx_ChangelogDialog_li"
|
||||
>
|
||||
<a
|
||||
href="https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha2"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
This is another commit message
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<span
|
||||
class="mx_Dialog_buttons_row"
|
||||
>
|
||||
<button
|
||||
data-test-id="dialog-cancel-button"
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="mx_Dialog_primary"
|
||||
data-test-id="dialog-primary-button"
|
||||
type="button"
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -364,7 +364,7 @@ describe('<RoomPreviewBar />', () => {
|
||||
|
||||
expect(getMessage(component)).toMatchSnapshot();
|
||||
expect(MatrixClientPeg.get().lookupThreePid).toHaveBeenCalledWith(
|
||||
'email', invitedEmail, undefined, 'mock-token',
|
||||
'email', invitedEmail, 'mock-token',
|
||||
);
|
||||
await testJoinButton({ inviterName, invitedEmail })();
|
||||
});
|
||||
|
@ -130,6 +130,20 @@ describe("createRoom", () => {
|
||||
expect(callPower).toBe(100);
|
||||
expect(callMemberPower).toBe(100);
|
||||
});
|
||||
|
||||
it("should upload avatar if one is passed", async () => {
|
||||
client.uploadContent.mockResolvedValue({ content_uri: "mxc://foobar" });
|
||||
const avatar = new File([], "avatar.png");
|
||||
await createRoom({ avatar });
|
||||
expect(client.createRoom).toHaveBeenCalledWith(expect.objectContaining({
|
||||
initial_state: expect.arrayContaining([{
|
||||
content: {
|
||||
url: "mxc://foobar",
|
||||
},
|
||||
type: "m.room.avatar",
|
||||
}]),
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe("canEncryptToAllUsers", () => {
|
||||
|
@ -27,10 +27,7 @@ import {
|
||||
import { stubClient } from '../test-utils';
|
||||
|
||||
describe('languageHandler', function() {
|
||||
/*
|
||||
See /__mocks__/browser-request.js/ for how we are stubbing out translations
|
||||
to provide fixture data for these tests
|
||||
*/
|
||||
// See setupLanguage.ts for how we are stubbing out translations to provide fixture data for these tests
|
||||
const basicString = 'Rooms';
|
||||
const selfClosingTagSub = 'Accept <policyLink /> to continue:';
|
||||
const textInTagSub = '<a>Upgrade</a> to your own domain';
|
||||
|
@ -14,7 +14,70 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
|
||||
import * as languageHandler from "../../src/languageHandler";
|
||||
import en from "../../src/i18n/strings/en_EN.json";
|
||||
import de from "../../src/i18n/strings/de_DE.json";
|
||||
|
||||
fetchMock.config.overwriteRoutes = false;
|
||||
fetchMock.catch("");
|
||||
window.fetch = fetchMock.sandbox();
|
||||
|
||||
const lv = {
|
||||
"Save": "Saglabāt",
|
||||
"Uploading %(filename)s and %(count)s others|one": "Качване на %(filename)s и %(count)s друг",
|
||||
};
|
||||
|
||||
// Fake languages.json containing references to en_EN, de_DE and lv
|
||||
// en_EN.json
|
||||
// de_DE.json
|
||||
// lv.json - mock version with few translations, used to test fallback translation
|
||||
|
||||
function weblateToCounterpart(inTrs: object): object {
|
||||
const outTrs = {};
|
||||
|
||||
for (const key of Object.keys(inTrs)) {
|
||||
const keyParts = key.split('|', 2);
|
||||
if (keyParts.length === 2) {
|
||||
let obj = outTrs[keyParts[0]];
|
||||
if (obj === undefined) {
|
||||
obj = outTrs[keyParts[0]] = {};
|
||||
} else if (typeof obj === "string") {
|
||||
// This is a transitional edge case if a string went from singular to pluralised and both still remain
|
||||
// in the translation json file. Use the singular translation as `other` and merge pluralisation atop.
|
||||
obj = outTrs[keyParts[0]] = {
|
||||
"other": inTrs[key],
|
||||
};
|
||||
console.warn("Found entry in i18n file in both singular and pluralised form", keyParts[0]);
|
||||
}
|
||||
obj[keyParts[1]] = inTrs[key];
|
||||
} else {
|
||||
outTrs[key] = inTrs[key];
|
||||
}
|
||||
}
|
||||
|
||||
return outTrs;
|
||||
}
|
||||
|
||||
fetchMock
|
||||
.get("/i18n/languages.json", {
|
||||
"en": {
|
||||
"fileName": "en_EN.json",
|
||||
"label": "English",
|
||||
},
|
||||
"de": {
|
||||
"fileName": "de_DE.json",
|
||||
"label": "German",
|
||||
},
|
||||
"lv": {
|
||||
"fileName": "lv.json",
|
||||
"label": "Latvian",
|
||||
},
|
||||
})
|
||||
.get("end:en_EN.json", weblateToCounterpart(en))
|
||||
.get("end:de_DE.json", weblateToCounterpart(de))
|
||||
.get("end:lv.json", weblateToCounterpart(lv));
|
||||
|
||||
languageHandler.setLanguage('en');
|
||||
languageHandler.setMissingEntryGenerator(key => key.split("|", 2)[1]);
|
||||
|
@ -45,6 +45,7 @@ global.matchMedia = mockMatchMedia;
|
||||
|
||||
// maplibre requires a createObjectURL mock
|
||||
global.URL.createObjectURL = jest.fn();
|
||||
global.URL.revokeObjectURL = jest.fn();
|
||||
|
||||
// polyfilling TextEncoder as it is not available on JSDOM
|
||||
// view https://github.com/facebook/jest/issues/9983
|
||||
|
@ -20,8 +20,7 @@ import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
|
||||
import { configure } from "enzyme";
|
||||
import "blob-polyfill"; // https://github.com/jsdom/jsdom/issues/2555
|
||||
|
||||
// Enable the jest & enzyme mocks
|
||||
require('jest-fetch-mock').enableMocks();
|
||||
// Enable the enzyme mocks
|
||||
configure({ adapter: new Adapter() });
|
||||
|
||||
// Very carefully enable the mocks for everything else in
|
||||
|
@ -158,7 +158,7 @@ export function createTestClient(): MatrixClient {
|
||||
getOpenIdToken: jest.fn().mockResolvedValue(undefined),
|
||||
registerWithIdentityServer: jest.fn().mockResolvedValue({}),
|
||||
getIdentityAccount: jest.fn().mockResolvedValue({}),
|
||||
getTerms: jest.fn().mockResolvedValueOnce(undefined),
|
||||
getTerms: jest.fn().mockResolvedValue({ policies: [] }),
|
||||
doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(undefined),
|
||||
isVersionSupported: jest.fn().mockResolvedValue(undefined),
|
||||
getPushRules: jest.fn().mockResolvedValue(undefined),
|
||||
@ -182,6 +182,7 @@ export function createTestClient(): MatrixClient {
|
||||
setVideoInput: jest.fn(),
|
||||
setAudioInput: jest.fn(),
|
||||
} as unknown as MediaHandler),
|
||||
uploadContent: jest.fn(),
|
||||
} as unknown as MatrixClient;
|
||||
|
||||
client.reEmitter = new ReEmitter(client);
|
||||
|
@ -98,9 +98,9 @@ describe('MultiInviter', () => {
|
||||
const result = await inviter.invite([MXID1, MXID2, MXID3]);
|
||||
|
||||
expect(client.invite).toHaveBeenCalledTimes(3);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined, undefined);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, undefined, undefined);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, undefined, undefined);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, undefined);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, undefined);
|
||||
|
||||
expectAllInvitedResult(result);
|
||||
});
|
||||
@ -116,9 +116,9 @@ describe('MultiInviter', () => {
|
||||
const result = await inviter.invite([MXID1, MXID2, MXID3]);
|
||||
|
||||
expect(client.invite).toHaveBeenCalledTimes(3);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined, undefined);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, undefined, undefined);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, undefined, undefined);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, undefined);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, undefined);
|
||||
|
||||
expectAllInvitedResult(result);
|
||||
});
|
||||
@ -131,7 +131,7 @@ describe('MultiInviter', () => {
|
||||
const result = await inviter.invite([MXID1, MXID2, MXID3]);
|
||||
|
||||
expect(client.invite).toHaveBeenCalledTimes(1);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined, undefined);
|
||||
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined);
|
||||
|
||||
// The resolved state is 'invited' for all users.
|
||||
// With the above client expectations, the test ensures that only the first user is invited.
|
||||
|
219
yarn.lock
219
yarn.lock
@ -68,6 +68,32 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.13.tgz#6aff7b350a1e8c3e40b029e46cbe78e24a913483"
|
||||
integrity sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw==
|
||||
|
||||
"@babel/compat-data@^7.19.3":
|
||||
version "7.19.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.3.tgz#707b939793f867f5a73b2666e6d9a3396eb03151"
|
||||
integrity sha512-prBHMK4JYYK+wDjJF1q99KK4JLL+egWS4nmNqdlMUgCExMZ+iZW0hGhyC3VEbsPjvaN0TBhW//VIFwBrk8sEiw==
|
||||
|
||||
"@babel/core@^7.0.0":
|
||||
version "7.19.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.3.tgz#2519f62a51458f43b682d61583c3810e7dcee64c"
|
||||
integrity sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==
|
||||
dependencies:
|
||||
"@ampproject/remapping" "^2.1.0"
|
||||
"@babel/code-frame" "^7.18.6"
|
||||
"@babel/generator" "^7.19.3"
|
||||
"@babel/helper-compilation-targets" "^7.19.3"
|
||||
"@babel/helper-module-transforms" "^7.19.0"
|
||||
"@babel/helpers" "^7.19.0"
|
||||
"@babel/parser" "^7.19.3"
|
||||
"@babel/template" "^7.18.10"
|
||||
"@babel/traverse" "^7.19.3"
|
||||
"@babel/types" "^7.19.3"
|
||||
convert-source-map "^1.7.0"
|
||||
debug "^4.1.0"
|
||||
gensync "^1.0.0-beta.2"
|
||||
json5 "^2.2.1"
|
||||
semver "^6.3.0"
|
||||
|
||||
"@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0":
|
||||
version "7.18.13"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.13.tgz#9be8c44512751b05094a4d3ab05fc53a47ce00ac"
|
||||
@ -114,6 +140,15 @@
|
||||
"@jridgewell/gen-mapping" "^0.3.2"
|
||||
jsesc "^2.5.1"
|
||||
|
||||
"@babel/generator@^7.19.3":
|
||||
version "7.19.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.3.tgz#d7f4d1300485b4547cb6f94b27d10d237b42bf59"
|
||||
integrity sha512-fqVZnmp1ncvZU757UzDheKZpfPgatqY59XtW2/j/18H7u76akb8xqvjw82f+i2UKd/ksYsSick/BCLQUUtJ/qQ==
|
||||
dependencies:
|
||||
"@babel/types" "^7.19.3"
|
||||
"@jridgewell/gen-mapping" "^0.3.2"
|
||||
jsesc "^2.5.1"
|
||||
|
||||
"@babel/helper-annotate-as-pure@^7.18.6":
|
||||
version "7.18.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
|
||||
@ -139,6 +174,16 @@
|
||||
browserslist "^4.20.2"
|
||||
semver "^6.3.0"
|
||||
|
||||
"@babel/helper-compilation-targets@^7.19.3":
|
||||
version "7.19.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz#a10a04588125675d7c7ae299af86fa1b2ee038ca"
|
||||
integrity sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg==
|
||||
dependencies:
|
||||
"@babel/compat-data" "^7.19.3"
|
||||
"@babel/helper-validator-option" "^7.18.6"
|
||||
browserslist "^4.21.3"
|
||||
semver "^6.3.0"
|
||||
|
||||
"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.18.9":
|
||||
version "7.18.13"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.13.tgz#63e771187bd06d234f95fdf8bd5f8b6429de6298"
|
||||
@ -192,6 +237,14 @@
|
||||
"@babel/template" "^7.18.6"
|
||||
"@babel/types" "^7.18.9"
|
||||
|
||||
"@babel/helper-function-name@^7.19.0":
|
||||
version "7.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c"
|
||||
integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==
|
||||
dependencies:
|
||||
"@babel/template" "^7.18.10"
|
||||
"@babel/types" "^7.19.0"
|
||||
|
||||
"@babel/helper-hoist-variables@^7.18.6":
|
||||
version "7.18.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
|
||||
@ -227,6 +280,20 @@
|
||||
"@babel/traverse" "^7.18.9"
|
||||
"@babel/types" "^7.18.9"
|
||||
|
||||
"@babel/helper-module-transforms@^7.19.0":
|
||||
version "7.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30"
|
||||
integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==
|
||||
dependencies:
|
||||
"@babel/helper-environment-visitor" "^7.18.9"
|
||||
"@babel/helper-module-imports" "^7.18.6"
|
||||
"@babel/helper-simple-access" "^7.18.6"
|
||||
"@babel/helper-split-export-declaration" "^7.18.6"
|
||||
"@babel/helper-validator-identifier" "^7.18.6"
|
||||
"@babel/template" "^7.18.10"
|
||||
"@babel/traverse" "^7.19.0"
|
||||
"@babel/types" "^7.19.0"
|
||||
|
||||
"@babel/helper-optimise-call-expression@^7.18.6":
|
||||
version "7.18.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe"
|
||||
@ -291,6 +358,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
|
||||
integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.19.1":
|
||||
version "7.19.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
|
||||
integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==
|
||||
|
||||
"@babel/helper-validator-option@^7.18.6":
|
||||
version "7.18.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8"
|
||||
@ -315,6 +387,15 @@
|
||||
"@babel/traverse" "^7.18.9"
|
||||
"@babel/types" "^7.18.9"
|
||||
|
||||
"@babel/helpers@^7.19.0":
|
||||
version "7.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18"
|
||||
integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==
|
||||
dependencies:
|
||||
"@babel/template" "^7.18.10"
|
||||
"@babel/traverse" "^7.19.0"
|
||||
"@babel/types" "^7.19.0"
|
||||
|
||||
"@babel/highlight@^7.18.6":
|
||||
version "7.18.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
|
||||
@ -329,6 +410,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.13.tgz#5b2dd21cae4a2c5145f1fbd8ca103f9313d3b7e4"
|
||||
integrity sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==
|
||||
|
||||
"@babel/parser@^7.19.3":
|
||||
version "7.19.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.3.tgz#8dd36d17c53ff347f9e55c328710321b49479a9a"
|
||||
integrity sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ==
|
||||
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
|
||||
version "7.18.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
|
||||
@ -1092,6 +1178,22 @@
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
|
||||
"@babel/traverse@^7.19.0", "@babel/traverse@^7.19.3":
|
||||
version "7.19.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.3.tgz#3a3c5348d4988ba60884e8494b0592b2f15a04b4"
|
||||
integrity sha512-qh5yf6149zhq2sgIXmwjnsvmnNQC2iw70UFjp4olxucKrWd/dvlUsBI88VSLUsnMNF7/vnOiA+nk1+yLoCqROQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.18.6"
|
||||
"@babel/generator" "^7.19.3"
|
||||
"@babel/helper-environment-visitor" "^7.18.9"
|
||||
"@babel/helper-function-name" "^7.19.0"
|
||||
"@babel/helper-hoist-variables" "^7.18.6"
|
||||
"@babel/helper-split-export-declaration" "^7.18.6"
|
||||
"@babel/parser" "^7.19.3"
|
||||
"@babel/types" "^7.19.3"
|
||||
debug "^4.1.0"
|
||||
globals "^11.1.0"
|
||||
|
||||
"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.13", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
|
||||
version "7.18.13"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.13.tgz#30aeb9e514f4100f7c1cb6e5ba472b30e48f519a"
|
||||
@ -1101,6 +1203,15 @@
|
||||
"@babel/helper-validator-identifier" "^7.18.6"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.19.0", "@babel/types@^7.19.3":
|
||||
version "7.19.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.3.tgz#fc420e6bbe54880bce6779ffaf315f5e43ec9624"
|
||||
integrity sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw==
|
||||
dependencies:
|
||||
"@babel/helper-string-parser" "^7.18.10"
|
||||
"@babel/helper-validator-identifier" "^7.19.1"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@bcoe/v8-coverage@^0.2.3":
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
@ -3539,6 +3650,11 @@ core-js@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
|
||||
integrity sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA==
|
||||
|
||||
core-js@^3.0.0:
|
||||
version "3.25.5"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.5.tgz#e86f651a2ca8a0237a5f064c2fe56cef89646e27"
|
||||
integrity sha512-nbm6eZSjm+ZuBQxCUPQKQCoUEfFOXjUZ8dTTyikyKaWrTYmAVbykQfwsKE5dBK88u3QCkCrzsx/PPlKfhsvgpw==
|
||||
|
||||
core-util-is@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
@ -3576,13 +3692,6 @@ crc-32@^0.3.0:
|
||||
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-0.3.0.tgz#6a3d3687f5baec41f7e9b99fe1953a2e5d19775e"
|
||||
integrity sha512-kucVIjOmMc1f0tv53BJ/5WIX+MGLcKuoBhnGqQrgKJNqLByb/sVMWfW/Aw6hw0jgcqjJ2pi9E5y32zOIpaUlsA==
|
||||
|
||||
cross-fetch@^3.0.4:
|
||||
version "3.1.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
|
||||
integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
|
||||
dependencies:
|
||||
node-fetch "2.6.7"
|
||||
|
||||
cross-spawn@^6.0.0:
|
||||
version "6.0.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
||||
@ -4772,6 +4881,29 @@ fd-slicer@~1.1.0:
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
fetch-mock-jest@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/fetch-mock-jest/-/fetch-mock-jest-1.5.1.tgz#0e13df990d286d9239e284f12b279ed509bf53cd"
|
||||
integrity sha512-+utwzP8C+Pax1GSka3nFXILWMY3Er2L+s090FOgqVNrNCPp0fDqgXnAHAJf12PLHi0z4PhcTaZNTz8e7K3fjqQ==
|
||||
dependencies:
|
||||
fetch-mock "^9.11.0"
|
||||
|
||||
fetch-mock@^9.11.0:
|
||||
version "9.11.0"
|
||||
resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-9.11.0.tgz#371c6fb7d45584d2ae4a18ee6824e7ad4b637a3f"
|
||||
integrity sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q==
|
||||
dependencies:
|
||||
"@babel/core" "^7.0.0"
|
||||
"@babel/runtime" "^7.0.0"
|
||||
core-js "^3.0.0"
|
||||
debug "^4.1.1"
|
||||
glob-to-regexp "^0.4.0"
|
||||
is-subset "^0.1.1"
|
||||
lodash.isequal "^4.5.0"
|
||||
path-to-regexp "^2.2.1"
|
||||
querystring "^0.2.0"
|
||||
whatwg-url "^6.5.0"
|
||||
|
||||
fflate@^0.4.1:
|
||||
version "0.4.8"
|
||||
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
|
||||
@ -5074,7 +5206,7 @@ glob-parent@^6.0.1:
|
||||
dependencies:
|
||||
is-glob "^4.0.3"
|
||||
|
||||
glob-to-regexp@^0.4.1:
|
||||
glob-to-regexp@^0.4.0, glob-to-regexp@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
|
||||
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
|
||||
@ -6018,14 +6150,6 @@ jest-environment-node@^27.5.1:
|
||||
jest-mock "^27.5.1"
|
||||
jest-util "^27.5.1"
|
||||
|
||||
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"
|
||||
@ -6788,6 +6912,11 @@ lodash.once@^4.1.1:
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
|
||||
|
||||
lodash.sortby@^4.7.0:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||
integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==
|
||||
|
||||
lodash.truncate@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
|
||||
@ -6951,10 +7080,10 @@ matrix-events-sdk@^0.0.1-beta.7:
|
||||
request "^2.88.2"
|
||||
unhomoglyph "^1.0.6"
|
||||
|
||||
matrix-mock-request@^2.0.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/matrix-mock-request/-/matrix-mock-request-2.1.2.tgz#11e38ed1233dced88a6f2bfba1684d5c5b3aa2c2"
|
||||
integrity sha512-/OXCIzDGSLPJ3fs+uzDrtaOHI/Sqp4iEuniRn31U8S06mPXbvAnXknHqJ4c6A/KVwJj/nPFbGXpK4wPM038I6A==
|
||||
matrix-mock-request@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/matrix-mock-request/-/matrix-mock-request-2.5.0.tgz#78da2590e82be2e31edcf9814833af5e5f8d2f1a"
|
||||
integrity sha512-7T3gklpW+4rfHsTnp/FDML7aWoBrXhAh8+1ltinQfAh9TDj6y382z/RUMR7i03d1WDzt/ed1UTihqO5GDoOq9Q==
|
||||
dependencies:
|
||||
expect "^28.1.0"
|
||||
|
||||
@ -7189,13 +7318,6 @@ 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.7, node-fetch@^2.6.7:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-fetch@^1.0.1:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
|
||||
@ -7204,6 +7326,13 @@ node-fetch@^1.0.1:
|
||||
encoding "^0.1.11"
|
||||
is-stream "^1.0.1"
|
||||
|
||||
node-fetch@^2.6.7:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-int64@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
||||
@ -7549,6 +7678,11 @@ path-parse@^1.0.7:
|
||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
path-to-regexp@^2.2.1:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704"
|
||||
integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==
|
||||
|
||||
path-to-regexp@^6.2.0:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5"
|
||||
@ -7750,11 +7884,6 @@ process-nextick-args@~2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
||||
|
||||
promise-polyfill@^8.1.3:
|
||||
version "8.2.3"
|
||||
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.3.tgz#2edc7e4b81aff781c88a0d577e5fe9da822107c6"
|
||||
integrity sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg==
|
||||
|
||||
promise@^7.0.3, promise@^7.1.1:
|
||||
version "7.3.1"
|
||||
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
|
||||
@ -7854,6 +7983,11 @@ querystring@0.2.0:
|
||||
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
|
||||
integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==
|
||||
|
||||
querystring@^0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd"
|
||||
integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==
|
||||
|
||||
querystringify@^2.1.1:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||
@ -9075,6 +9209,13 @@ tough-cookie@~2.5.0:
|
||||
psl "^1.1.28"
|
||||
punycode "^2.1.1"
|
||||
|
||||
tr46@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
|
||||
integrity sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
tr46@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240"
|
||||
@ -9448,6 +9589,11 @@ webidl-conversions@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
|
||||
|
||||
webidl-conversions@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
||||
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
|
||||
|
||||
webidl-conversions@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
|
||||
@ -9493,6 +9639,15 @@ whatwg-url@^5.0.0:
|
||||
tr46 "~0.0.3"
|
||||
webidl-conversions "^3.0.0"
|
||||
|
||||
whatwg-url@^6.5.0:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"
|
||||
integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==
|
||||
dependencies:
|
||||
lodash.sortby "^4.7.0"
|
||||
tr46 "^1.0.1"
|
||||
webidl-conversions "^4.0.2"
|
||||
|
||||
whatwg-url@^8.0.0, whatwg-url@^8.5.0:
|
||||
version "8.7.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77"
|
||||
|
Loading…
Reference in New Issue
Block a user