You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-30 04:23:07 +03:00
Modernize http-api - move from browser-request
to fetch
(#2719)
This commit is contained in:
committed by
GitHub
parent
913660c818
commit
34c5598a3f
@ -33,10 +33,8 @@ In Node.js
|
|||||||
----------
|
----------
|
||||||
|
|
||||||
Ensure you have the latest LTS version of Node.js installed.
|
Ensure you have the latest LTS version of Node.js installed.
|
||||||
|
This library relies on `fetch` which is available in Node from v18.0.0 - it should work fine also with polyfills.
|
||||||
This SDK targets Node 12 for compatibility, which translates to ES6. If you're using
|
If you wish to use a ponyfill or adapter of some sort then pass it as `fetchFn` to the MatrixClient constructor options.
|
||||||
a bundler like webpack you'll likely have to transpile dependencies, including this
|
|
||||||
SDK, to match your target browsers.
|
|
||||||
|
|
||||||
Using `yarn` instead of `npm` is recommended. Please see the Yarn [install guide](https://classic.yarnpkg.com/en/docs/install)
|
Using `yarn` instead of `npm` is recommended. Please see the Yarn [install guide](https://classic.yarnpkg.com/en/docs/install)
|
||||||
if you do not have it already.
|
if you do not have it already.
|
||||||
|
12
package.json
12
package.json
@ -3,7 +3,7 @@
|
|||||||
"version": "20.1.0",
|
"version": "20.1.0",
|
||||||
"description": "Matrix Client-Server SDK for Javascript",
|
"description": "Matrix Client-Server SDK for Javascript",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.9.0"
|
"node": ">=16.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepublishOnly": "yarn build",
|
"prepublishOnly": "yarn build",
|
||||||
@ -55,14 +55,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"another-json": "^0.2.0",
|
"another-json": "^0.2.0",
|
||||||
"browser-request": "^0.3.3",
|
|
||||||
"bs58": "^5.0.0",
|
"bs58": "^5.0.0",
|
||||||
"content-type": "^1.0.4",
|
"content-type": "^1.0.4",
|
||||||
"loglevel": "^1.7.1",
|
"loglevel": "^1.7.1",
|
||||||
"matrix-events-sdk": "^0.0.1-beta.7",
|
"matrix-events-sdk": "^0.0.1-beta.7",
|
||||||
"p-retry": "4",
|
"p-retry": "4",
|
||||||
"qs": "^6.9.6",
|
"qs": "^6.9.6",
|
||||||
"request": "^2.88.2",
|
|
||||||
"unhomoglyph": "^1.0.6"
|
"unhomoglyph": "^1.0.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -81,9 +79,9 @@
|
|||||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.13.tgz",
|
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.13.tgz",
|
||||||
"@types/bs58": "^4.0.1",
|
"@types/bs58": "^4.0.1",
|
||||||
"@types/content-type": "^1.1.5",
|
"@types/content-type": "^1.1.5",
|
||||||
|
"@types/domexception": "^4.0.0",
|
||||||
"@types/jest": "^29.0.0",
|
"@types/jest": "^29.0.0",
|
||||||
"@types/node": "16",
|
"@types/node": "16",
|
||||||
"@types/request": "^2.48.5",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
||||||
"@typescript-eslint/parser": "^5.6.0",
|
"@typescript-eslint/parser": "^5.6.0",
|
||||||
"allchange": "^1.0.6",
|
"allchange": "^1.0.6",
|
||||||
@ -92,6 +90,7 @@
|
|||||||
"better-docs": "^2.4.0-beta.9",
|
"better-docs": "^2.4.0-beta.9",
|
||||||
"browserify": "^17.0.0",
|
"browserify": "^17.0.0",
|
||||||
"docdash": "^1.2.0",
|
"docdash": "^1.2.0",
|
||||||
|
"domexception": "^4.0.0",
|
||||||
"eslint": "8.24.0",
|
"eslint": "8.24.0",
|
||||||
"eslint-config-google": "^0.14.0",
|
"eslint-config-google": "^0.14.0",
|
||||||
"eslint-import-resolver-typescript": "^3.5.1",
|
"eslint-import-resolver-typescript": "^3.5.1",
|
||||||
@ -104,7 +103,7 @@
|
|||||||
"jest-mock": "^27.5.1",
|
"jest-mock": "^27.5.1",
|
||||||
"jest-sonar-reporter": "^2.0.0",
|
"jest-sonar-reporter": "^2.0.0",
|
||||||
"jsdoc": "^3.6.6",
|
"jsdoc": "^3.6.6",
|
||||||
"matrix-mock-request": "^2.1.2",
|
"matrix-mock-request": "^2.5.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"terser": "^5.5.1",
|
"terser": "^5.5.1",
|
||||||
"tsify": "^5.0.2",
|
"tsify": "^5.0.2",
|
||||||
@ -115,6 +114,9 @@
|
|||||||
"testMatch": [
|
"testMatch": [
|
||||||
"<rootDir>/spec/**/*.spec.{js,ts}"
|
"<rootDir>/spec/**/*.spec.{js,ts}"
|
||||||
],
|
],
|
||||||
|
"setupFilesAfterEnv": [
|
||||||
|
"<rootDir>/spec/setupTests.ts"
|
||||||
|
],
|
||||||
"collectCoverageFrom": [
|
"collectCoverageFrom": [
|
||||||
"<rootDir>/src/**/*.{js,ts}"
|
"<rootDir>/src/**/*.{js,ts}"
|
||||||
],
|
],
|
||||||
|
@ -30,7 +30,6 @@ import { MockStorageApi } from "./MockStorageApi";
|
|||||||
import { encodeUri } from "../src/utils";
|
import { encodeUri } from "../src/utils";
|
||||||
import { IDeviceKeys, IOneTimeKey } from "../src/crypto/dehydration";
|
import { IDeviceKeys, IOneTimeKey } from "../src/crypto/dehydration";
|
||||||
import { IKeyBackupSession } from "../src/crypto/keybackup";
|
import { IKeyBackupSession } from "../src/crypto/keybackup";
|
||||||
import { IHttpOpts } from "../src/http-api";
|
|
||||||
import { IKeysUploadResponse, IUploadKeysRequest } from '../src/client';
|
import { IKeysUploadResponse, IUploadKeysRequest } from '../src/client';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,11 +55,11 @@ export class TestClient {
|
|||||||
this.httpBackend = new MockHttpBackend();
|
this.httpBackend = new MockHttpBackend();
|
||||||
|
|
||||||
const fullOptions: ICreateClientOpts = {
|
const fullOptions: ICreateClientOpts = {
|
||||||
baseUrl: "http://" + userId + ".test.server",
|
baseUrl: "http://" + userId?.slice(1).replace(":", ".") + ".test.server",
|
||||||
userId: userId,
|
userId: userId,
|
||||||
accessToken: accessToken,
|
accessToken: accessToken,
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
request: this.httpBackend.requestFn as IHttpOpts["request"],
|
fetchFn: this.httpBackend.fetchFn as typeof global.fetch,
|
||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
if (!fullOptions.cryptoStore) {
|
if (!fullOptions.cryptoStore) {
|
||||||
|
@ -14,46 +14,66 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// load XmlHttpRequest mock
|
import HttpBackend from "matrix-mock-request";
|
||||||
|
|
||||||
import "./setupTests";
|
import "./setupTests";
|
||||||
import "../../dist/browser-matrix"; // uses browser-matrix instead of the src
|
import "../../dist/browser-matrix"; // uses browser-matrix instead of the src
|
||||||
import * as utils from "../test-utils/test-utils";
|
import type { MatrixClient, ClientEvent } from "../../src";
|
||||||
import { TestClient } from "../TestClient";
|
|
||||||
|
|
||||||
const USER_ID = "@user:test.server";
|
const USER_ID = "@user:test.server";
|
||||||
const DEVICE_ID = "device_id";
|
const DEVICE_ID = "device_id";
|
||||||
const ACCESS_TOKEN = "access_token";
|
const ACCESS_TOKEN = "access_token";
|
||||||
const ROOM_ID = "!room_id:server.test";
|
const ROOM_ID = "!room_id:server.test";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
|
namespace NodeJS {
|
||||||
|
interface Global {
|
||||||
|
matrixcs: {
|
||||||
|
MatrixClient: typeof MatrixClient;
|
||||||
|
ClientEvent: typeof ClientEvent;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe("Browserify Test", function() {
|
describe("Browserify Test", function() {
|
||||||
let client;
|
let client: MatrixClient;
|
||||||
let httpBackend;
|
let httpBackend: HttpBackend;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const testClient = new TestClient(USER_ID, DEVICE_ID, ACCESS_TOKEN);
|
httpBackend = new HttpBackend();
|
||||||
|
client = new global.matrixcs.MatrixClient({
|
||||||
client = testClient.client;
|
baseUrl: "http://test.server",
|
||||||
httpBackend = testClient.httpBackend;
|
userId: USER_ID,
|
||||||
|
accessToken: ACCESS_TOKEN,
|
||||||
|
deviceId: DEVICE_ID,
|
||||||
|
fetchFn: httpBackend.fetchFn as typeof global.fetch,
|
||||||
|
});
|
||||||
|
|
||||||
httpBackend.when("GET", "/versions").respond(200, {});
|
httpBackend.when("GET", "/versions").respond(200, {});
|
||||||
httpBackend.when("GET", "/pushrules").respond(200, {});
|
httpBackend.when("GET", "/pushrules").respond(200, {});
|
||||||
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
|
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
|
||||||
|
|
||||||
client.startClient();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
client.stopClient();
|
client.stopClient();
|
||||||
httpBackend.stop();
|
client.http.abort();
|
||||||
|
httpBackend.verifyNoOutstandingRequests();
|
||||||
|
httpBackend.verifyNoOutstandingExpectation();
|
||||||
|
await httpBackend.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Sync", function() {
|
it("Sync", async () => {
|
||||||
const event = utils.mkMembership({
|
const event = {
|
||||||
room: ROOM_ID,
|
type: "m.room.member",
|
||||||
mship: "join",
|
room_id: ROOM_ID,
|
||||||
user: "@other_user:server.test",
|
content: {
|
||||||
name: "Displayname",
|
membership: "join",
|
||||||
});
|
name: "Displayname",
|
||||||
|
},
|
||||||
|
event_id: "$foobar",
|
||||||
|
};
|
||||||
|
|
||||||
const syncData = {
|
const syncData = {
|
||||||
next_batch: "batch1",
|
next_batch: "batch1",
|
||||||
@ -71,11 +91,16 @@ describe("Browserify Test", function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
httpBackend.when("GET", "/sync").respond(200, syncData);
|
httpBackend.when("GET", "/sync").respond(200, syncData);
|
||||||
return Promise.race([
|
httpBackend.when("GET", "/sync").respond(200, syncData);
|
||||||
httpBackend.flushAllExpected(),
|
|
||||||
new Promise((_, reject) => {
|
const syncPromise = new Promise(r => client.once(global.matrixcs.ClientEvent.Sync, r));
|
||||||
client.once("sync.unexpectedError", reject);
|
const unexpectedErrorFn = jest.fn();
|
||||||
}),
|
client.once(global.matrixcs.ClientEvent.SyncUnexpectedError, unexpectedErrorFn);
|
||||||
]);
|
|
||||||
|
client.startClient();
|
||||||
|
|
||||||
|
await httpBackend.flushAllExpected();
|
||||||
|
await syncPromise;
|
||||||
|
expect(unexpectedErrorFn).not.toHaveBeenCalled();
|
||||||
}, 20000); // additional timeout as this test can take quite a while
|
}, 20000); // additional timeout as this test can take quite a while
|
||||||
});
|
});
|
||||||
|
@ -16,13 +16,12 @@ limitations under the License.
|
|||||||
import HttpBackend from "matrix-mock-request";
|
import HttpBackend from "matrix-mock-request";
|
||||||
|
|
||||||
import * as utils from "../test-utils/test-utils";
|
import * as utils from "../test-utils/test-utils";
|
||||||
import { CRYPTO_ENABLED, MatrixClient, IStoredClientOpts } from "../../src/client";
|
import { CRYPTO_ENABLED, IStoredClientOpts, MatrixClient } from "../../src/client";
|
||||||
import { MatrixEvent } from "../../src/models/event";
|
import { MatrixEvent } from "../../src/models/event";
|
||||||
import { Filter, MemoryStore, Room } from "../../src/matrix";
|
import { Filter, MemoryStore, Method, Room, SERVICE_TYPES } from "../../src/matrix";
|
||||||
import { TestClient } from "../TestClient";
|
import { TestClient } from "../TestClient";
|
||||||
import { THREAD_RELATION_TYPE } from "../../src/models/thread";
|
import { THREAD_RELATION_TYPE } from "../../src/models/thread";
|
||||||
import { IFilterDefinition } from "../../src/filter";
|
import { IFilterDefinition } from "../../src/filter";
|
||||||
import { FileType } from "../../src/http-api";
|
|
||||||
import { ISearchResults } from "../../src/@types/search";
|
import { ISearchResults } from "../../src/@types/search";
|
||||||
import { IStore } from "../../src/store";
|
import { IStore } from "../../src/store";
|
||||||
|
|
||||||
@ -65,28 +64,27 @@ describe("MatrixClient", function() {
|
|||||||
|
|
||||||
describe("uploadContent", function() {
|
describe("uploadContent", function() {
|
||||||
const buf = Buffer.from('hello world');
|
const buf = Buffer.from('hello world');
|
||||||
|
const file = buf;
|
||||||
|
const opts = {
|
||||||
|
type: "text/plain",
|
||||||
|
name: "hi.txt",
|
||||||
|
};
|
||||||
|
|
||||||
it("should upload the file", function() {
|
it("should upload the file", function() {
|
||||||
httpBackend!.when(
|
httpBackend!.when(
|
||||||
"POST", "/_matrix/media/r0/upload",
|
"POST", "/_matrix/media/r0/upload",
|
||||||
).check(function(req) {
|
).check(function(req) {
|
||||||
expect(req.rawData).toEqual(buf);
|
expect(req.rawData).toEqual(buf);
|
||||||
expect(req.queryParams?.filename).toEqual("hi.txt");
|
expect(req.queryParams?.filename).toEqual("hi.txt");
|
||||||
if (!(req.queryParams?.access_token == accessToken ||
|
expect(req.headers["Authorization"]).toBe("Bearer " + accessToken);
|
||||||
req.headers["Authorization"] == "Bearer " + accessToken)) {
|
|
||||||
expect(true).toBe(false);
|
|
||||||
}
|
|
||||||
expect(req.headers["Content-Type"]).toEqual("text/plain");
|
expect(req.headers["Content-Type"]).toEqual("text/plain");
|
||||||
// @ts-ignore private property
|
// @ts-ignore private property
|
||||||
expect(req.opts.json).toBeFalsy();
|
expect(req.opts.json).toBeFalsy();
|
||||||
// @ts-ignore private property
|
// @ts-ignore private property
|
||||||
expect(req.opts.timeout).toBe(undefined);
|
expect(req.opts.timeout).toBe(undefined);
|
||||||
}).respond(200, "content", true);
|
}).respond(200, '{"content_uri": "content"}', true);
|
||||||
|
|
||||||
const prom = client!.uploadContent({
|
const prom = client!.uploadContent(file, opts);
|
||||||
stream: buf,
|
|
||||||
name: "hi.txt",
|
|
||||||
type: "text/plain",
|
|
||||||
} as unknown as FileType);
|
|
||||||
|
|
||||||
expect(prom).toBeTruthy();
|
expect(prom).toBeTruthy();
|
||||||
|
|
||||||
@ -96,8 +94,7 @@ describe("MatrixClient", function() {
|
|||||||
expect(uploads[0].loaded).toEqual(0);
|
expect(uploads[0].loaded).toEqual(0);
|
||||||
|
|
||||||
const prom2 = prom.then(function(response) {
|
const prom2 = prom.then(function(response) {
|
||||||
// for backwards compatibility, we return the raw JSON
|
expect(response.content_uri).toEqual("content");
|
||||||
expect(response).toEqual("content");
|
|
||||||
|
|
||||||
const uploads = client!.getCurrentUploads();
|
const uploads = client!.getCurrentUploads();
|
||||||
expect(uploads.length).toEqual(0);
|
expect(uploads.length).toEqual(0);
|
||||||
@ -107,28 +104,6 @@ describe("MatrixClient", function() {
|
|||||||
return prom2;
|
return prom2;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should parse the response if rawResponse=false", function() {
|
|
||||||
httpBackend!.when(
|
|
||||||
"POST", "/_matrix/media/r0/upload",
|
|
||||||
).check(function(req) {
|
|
||||||
// @ts-ignore private property
|
|
||||||
expect(req.opts.json).toBeFalsy();
|
|
||||||
}).respond(200, { "content_uri": "uri" });
|
|
||||||
|
|
||||||
const prom = client!.uploadContent({
|
|
||||||
stream: buf,
|
|
||||||
name: "hi.txt",
|
|
||||||
type: "text/plain",
|
|
||||||
} as unknown as FileType, {
|
|
||||||
rawResponse: false,
|
|
||||||
}).then(function(response) {
|
|
||||||
expect(response.content_uri).toEqual("uri");
|
|
||||||
});
|
|
||||||
|
|
||||||
httpBackend!.flush('');
|
|
||||||
return prom;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should parse errors into a MatrixError", function() {
|
it("should parse errors into a MatrixError", function() {
|
||||||
httpBackend!.when(
|
httpBackend!.when(
|
||||||
"POST", "/_matrix/media/r0/upload",
|
"POST", "/_matrix/media/r0/upload",
|
||||||
@ -141,11 +116,7 @@ describe("MatrixClient", function() {
|
|||||||
"error": "broken",
|
"error": "broken",
|
||||||
});
|
});
|
||||||
|
|
||||||
const prom = client!.uploadContent({
|
const prom = client!.uploadContent(file, opts).then(function(response) {
|
||||||
stream: buf,
|
|
||||||
name: "hi.txt",
|
|
||||||
type: "text/plain",
|
|
||||||
} as unknown as FileType).then(function(response) {
|
|
||||||
throw Error("request not failed");
|
throw Error("request not failed");
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
expect(error.httpStatus).toEqual(400);
|
expect(error.httpStatus).toEqual(400);
|
||||||
@ -157,30 +128,18 @@ describe("MatrixClient", function() {
|
|||||||
return prom;
|
return prom;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return a promise which can be cancelled", function() {
|
it("should return a promise which can be cancelled", async () => {
|
||||||
const prom = client!.uploadContent({
|
const prom = client!.uploadContent(file, opts);
|
||||||
stream: buf,
|
|
||||||
name: "hi.txt",
|
|
||||||
type: "text/plain",
|
|
||||||
} as unknown as FileType);
|
|
||||||
|
|
||||||
const uploads = client!.getCurrentUploads();
|
const uploads = client!.getCurrentUploads();
|
||||||
expect(uploads.length).toEqual(1);
|
expect(uploads.length).toEqual(1);
|
||||||
expect(uploads[0].promise).toBe(prom);
|
expect(uploads[0].promise).toBe(prom);
|
||||||
expect(uploads[0].loaded).toEqual(0);
|
expect(uploads[0].loaded).toEqual(0);
|
||||||
|
|
||||||
const prom2 = prom.then(function(response) {
|
|
||||||
throw Error("request not aborted");
|
|
||||||
}, function(error) {
|
|
||||||
expect(error).toEqual("aborted");
|
|
||||||
|
|
||||||
const uploads = client!.getCurrentUploads();
|
|
||||||
expect(uploads.length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
const r = client!.cancelUpload(prom);
|
const r = client!.cancelUpload(prom);
|
||||||
expect(r).toBe(true);
|
expect(r).toBe(true);
|
||||||
return prom2;
|
await expect(prom).rejects.toThrow("Aborted");
|
||||||
|
expect(client.getCurrentUploads()).toHaveLength(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -202,6 +161,30 @@ describe("MatrixClient", function() {
|
|||||||
client!.joinRoom(roomId);
|
client!.joinRoom(roomId);
|
||||||
httpBackend!.verifyNoOutstandingRequests();
|
httpBackend!.verifyNoOutstandingRequests();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should send request to inviteSignUrl if specified", async () => {
|
||||||
|
const roomId = "!roomId:server";
|
||||||
|
const inviteSignUrl = "https://id.server/sign/this/for/me";
|
||||||
|
const viaServers = ["a", "b", "c"];
|
||||||
|
const signature = {
|
||||||
|
sender: "sender",
|
||||||
|
mxid: "@sender:foo",
|
||||||
|
token: "token",
|
||||||
|
signatures: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
httpBackend!.when("POST", inviteSignUrl).respond(200, signature);
|
||||||
|
httpBackend!.when("POST", "/join/" + encodeURIComponent(roomId)).check(request => {
|
||||||
|
expect(request.data.third_party_signed).toEqual(signature);
|
||||||
|
}).respond(200, { room_id: roomId });
|
||||||
|
|
||||||
|
const prom = client.joinRoom(roomId, {
|
||||||
|
inviteSignUrl,
|
||||||
|
viaServers,
|
||||||
|
});
|
||||||
|
await httpBackend!.flushAllExpected();
|
||||||
|
expect((await prom).roomId).toBe(roomId);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getFilter", function() {
|
describe("getFilter", function() {
|
||||||
@ -676,7 +659,7 @@ describe("MatrixClient", function() {
|
|||||||
// The vote event has been copied into the thread
|
// The vote event has been copied into the thread
|
||||||
const eventRefWithThreadId = withThreadId(
|
const eventRefWithThreadId = withThreadId(
|
||||||
eventPollResponseReference, eventPollStartThreadRoot.getId());
|
eventPollResponseReference, eventPollStartThreadRoot.getId());
|
||||||
expect(eventRefWithThreadId.threadId).toBeTruthy();
|
expect(eventRefWithThreadId.threadRootId).toBeTruthy();
|
||||||
|
|
||||||
expect(threaded).toEqual([
|
expect(threaded).toEqual([
|
||||||
eventPollStartThreadRoot,
|
eventPollStartThreadRoot,
|
||||||
@ -1178,15 +1161,150 @@ describe("MatrixClient", function() {
|
|||||||
expect(await prom).toStrictEqual(response);
|
expect(await prom).toStrictEqual(response);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("logout", () => {
|
||||||
|
it("should abort pending requests when called with stopClient=true", async () => {
|
||||||
|
httpBackend.when("POST", "/logout").respond(200, {});
|
||||||
|
const fn = jest.fn();
|
||||||
|
client.http.request(Method.Get, "/test").catch(fn);
|
||||||
|
client.logout(true);
|
||||||
|
await httpBackend.flush(undefined);
|
||||||
|
expect(fn).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("sendHtmlEmote", () => {
|
||||||
|
it("should send valid html emote", async () => {
|
||||||
|
httpBackend.when("PUT", "/send").check(req => {
|
||||||
|
expect(req.data).toStrictEqual({
|
||||||
|
"msgtype": "m.emote",
|
||||||
|
"body": "Body",
|
||||||
|
"formatted_body": "<h1>Body</h1>",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"org.matrix.msc1767.message": expect.anything(),
|
||||||
|
});
|
||||||
|
}).respond(200, { event_id: "$foobar" });
|
||||||
|
const prom = client.sendHtmlEmote("!room:server", "Body", "<h1>Body</h1>");
|
||||||
|
await httpBackend.flush(undefined);
|
||||||
|
await expect(prom).resolves.toStrictEqual({ event_id: "$foobar" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("sendHtmlMessage", () => {
|
||||||
|
it("should send valid html message", async () => {
|
||||||
|
httpBackend.when("PUT", "/send").check(req => {
|
||||||
|
expect(req.data).toStrictEqual({
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": "Body",
|
||||||
|
"formatted_body": "<h1>Body</h1>",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"org.matrix.msc1767.message": expect.anything(),
|
||||||
|
});
|
||||||
|
}).respond(200, { event_id: "$foobar" });
|
||||||
|
const prom = client.sendHtmlMessage("!room:server", "Body", "<h1>Body</h1>");
|
||||||
|
await httpBackend.flush(undefined);
|
||||||
|
await expect(prom).resolves.toStrictEqual({ event_id: "$foobar" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("forget", () => {
|
||||||
|
it("should remove from store by default", async () => {
|
||||||
|
const room = new Room("!roomId:server", client, userId);
|
||||||
|
client.store.storeRoom(room);
|
||||||
|
expect(client.store.getRooms()).toContain(room);
|
||||||
|
|
||||||
|
httpBackend.when("POST", "/forget").respond(200, {});
|
||||||
|
await Promise.all([
|
||||||
|
client.forget(room.roomId),
|
||||||
|
httpBackend.flushAllExpected(),
|
||||||
|
]);
|
||||||
|
expect(client.store.getRooms()).not.toContain(room);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getCapabilities", () => {
|
||||||
|
it("should cache by default", async () => {
|
||||||
|
httpBackend!.when("GET", "/capabilities").respond(200, {
|
||||||
|
capabilities: {
|
||||||
|
"m.change_password": false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const prom = httpBackend!.flushAllExpected();
|
||||||
|
const capabilities1 = await client!.getCapabilities();
|
||||||
|
const capabilities2 = await client!.getCapabilities();
|
||||||
|
await prom;
|
||||||
|
|
||||||
|
expect(capabilities1).toStrictEqual(capabilities2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getTerms", () => {
|
||||||
|
it("should return Identity Server terms", async () => {
|
||||||
|
httpBackend!.when("GET", "/terms").respond(200, { foo: "bar" });
|
||||||
|
const prom = client!.getTerms(SERVICE_TYPES.IS, "http://identity.server");
|
||||||
|
await httpBackend!.flushAllExpected();
|
||||||
|
await expect(prom).resolves.toEqual({ foo: "bar" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return Integrations Manager terms", async () => {
|
||||||
|
httpBackend!.when("GET", "/terms").respond(200, { foo: "bar" });
|
||||||
|
const prom = client!.getTerms(SERVICE_TYPES.IM, "http://im.server");
|
||||||
|
await httpBackend!.flushAllExpected();
|
||||||
|
await expect(prom).resolves.toEqual({ foo: "bar" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("publicRooms", () => {
|
||||||
|
it("should use GET request if no server or filter is specified", () => {
|
||||||
|
httpBackend!.when("GET", "/publicRooms").respond(200, {});
|
||||||
|
client!.publicRooms({});
|
||||||
|
return httpBackend!.flushAllExpected();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use GET request if only server is specified", () => {
|
||||||
|
httpBackend!.when("GET", "/publicRooms").check(request => {
|
||||||
|
expect(request.queryParams.server).toBe("server1");
|
||||||
|
}).respond(200, {});
|
||||||
|
client!.publicRooms({ server: "server1" });
|
||||||
|
return httpBackend!.flushAllExpected();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use POST request if filter is specified", () => {
|
||||||
|
httpBackend!.when("POST", "/publicRooms").check(request => {
|
||||||
|
expect(request.data.filter.generic_search_term).toBe("foobar");
|
||||||
|
}).respond(200, {});
|
||||||
|
client!.publicRooms({ filter: { generic_search_term: "foobar" } });
|
||||||
|
return httpBackend!.flushAllExpected();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("login", () => {
|
||||||
|
it("should persist values to the client opts", async () => {
|
||||||
|
const token = "!token&";
|
||||||
|
const userId = "@m:t";
|
||||||
|
|
||||||
|
httpBackend!.when("POST", "/login").respond(200, {
|
||||||
|
access_token: token,
|
||||||
|
user_id: userId,
|
||||||
|
});
|
||||||
|
const prom = client!.login("fake.login", {});
|
||||||
|
await httpBackend!.flushAllExpected();
|
||||||
|
const resp = await prom;
|
||||||
|
expect(resp.access_token).toBe(token);
|
||||||
|
expect(resp.user_id).toBe(userId);
|
||||||
|
expect(client.getUserId()).toBe(userId);
|
||||||
|
expect(client.http.opts.accessToken).toBe(token);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function withThreadId(event, newThreadId) {
|
function withThreadId(event: MatrixEvent, newThreadId: string): MatrixEvent {
|
||||||
const ret = event.toSnapshot();
|
const ret = event.toSnapshot();
|
||||||
ret.setThreadId(newThreadId);
|
ret.setThreadId(newThreadId);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildEventMessageInThread = (root) => new MatrixEvent({
|
const buildEventMessageInThread = (root: MatrixEvent) => new MatrixEvent({
|
||||||
"age": 80098509,
|
"age": 80098509,
|
||||||
"content": {
|
"content": {
|
||||||
"algorithm": "m.megolm.v1.aes-sha2",
|
"algorithm": "m.megolm.v1.aes-sha2",
|
||||||
@ -1233,7 +1351,7 @@ const buildEventPollResponseReference = () => new MatrixEvent({
|
|||||||
"user_id": "@andybalaam-test1:matrix.org",
|
"user_id": "@andybalaam-test1:matrix.org",
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildEventReaction = (event) => new MatrixEvent({
|
const buildEventReaction = (event: MatrixEvent) => new MatrixEvent({
|
||||||
"content": {
|
"content": {
|
||||||
"m.relates_to": {
|
"m.relates_to": {
|
||||||
"event_id": event.getId(),
|
"event_id": event.getId(),
|
||||||
@ -1252,7 +1370,7 @@ const buildEventReaction = (event) => new MatrixEvent({
|
|||||||
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
|
"room_id": "!STrMRsukXHtqQdSeHa:matrix.org",
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildEventRedaction = (event) => new MatrixEvent({
|
const buildEventRedaction = (event: MatrixEvent) => new MatrixEvent({
|
||||||
"content": {
|
"content": {
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -1286,7 +1404,7 @@ const buildEventPollStartThreadRoot = () => new MatrixEvent({
|
|||||||
"user_id": "@andybalaam-test1:matrix.org",
|
"user_id": "@andybalaam-test1:matrix.org",
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildEventReply = (target) => new MatrixEvent({
|
const buildEventReply = (target: MatrixEvent) => new MatrixEvent({
|
||||||
"age": 80098509,
|
"age": 80098509,
|
||||||
"content": {
|
"content": {
|
||||||
"algorithm": "m.megolm.v1.aes-sha2",
|
"algorithm": "m.megolm.v1.aes-sha2",
|
||||||
@ -1452,7 +1570,7 @@ const buildEventCreate = () => new MatrixEvent({
|
|||||||
"user_id": "@andybalaam-test1:matrix.org",
|
"user_id": "@andybalaam-test1:matrix.org",
|
||||||
});
|
});
|
||||||
|
|
||||||
function assertObjectContains(obj, expected) {
|
function assertObjectContains(obj: object, expected: any): void {
|
||||||
for (const k in expected) {
|
for (const k in expected) {
|
||||||
if (expected.hasOwnProperty(k)) {
|
if (expected.hasOwnProperty(k)) {
|
||||||
expect(obj[k]).toEqual(expected[k]);
|
expect(obj[k]).toEqual(expected[k]);
|
||||||
|
@ -5,7 +5,6 @@ import { MatrixClient } from "../../src/matrix";
|
|||||||
import { MatrixScheduler } from "../../src/scheduler";
|
import { MatrixScheduler } from "../../src/scheduler";
|
||||||
import { MemoryStore } from "../../src/store/memory";
|
import { MemoryStore } from "../../src/store/memory";
|
||||||
import { MatrixError } from "../../src/http-api";
|
import { MatrixError } from "../../src/http-api";
|
||||||
import { ICreateClientOpts } from "../../src/client";
|
|
||||||
import { IStore } from "../../src/store";
|
import { IStore } from "../../src/store";
|
||||||
|
|
||||||
describe("MatrixClient opts", function() {
|
describe("MatrixClient opts", function() {
|
||||||
@ -69,7 +68,7 @@ describe("MatrixClient opts", function() {
|
|||||||
let client;
|
let client;
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
client = new MatrixClient({
|
client = new MatrixClient({
|
||||||
request: httpBackend.requestFn as unknown as ICreateClientOpts['request'],
|
fetchFn: httpBackend.fetchFn as typeof global.fetch,
|
||||||
store: undefined,
|
store: undefined,
|
||||||
baseUrl: baseUrl,
|
baseUrl: baseUrl,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
@ -129,7 +128,7 @@ describe("MatrixClient opts", function() {
|
|||||||
let client;
|
let client;
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
client = new MatrixClient({
|
client = new MatrixClient({
|
||||||
request: httpBackend.requestFn as unknown as ICreateClientOpts['request'],
|
fetchFn: httpBackend.fetchFn as typeof global.fetch,
|
||||||
store: new MemoryStore() as IStore,
|
store: new MemoryStore() as IStore,
|
||||||
baseUrl: baseUrl,
|
baseUrl: baseUrl,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
@ -143,7 +142,7 @@ describe("MatrixClient opts", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("shouldn't retry sending events", function(done) {
|
it("shouldn't retry sending events", function(done) {
|
||||||
httpBackend.when("PUT", "/txn1").fail(500, new MatrixError({
|
httpBackend.when("PUT", "/txn1").respond(500, new MatrixError({
|
||||||
errcode: "M_SOMETHING",
|
errcode: "M_SOMETHING",
|
||||||
error: "Ruh roh",
|
error: "Ruh roh",
|
||||||
}));
|
}));
|
||||||
|
@ -18,7 +18,7 @@ import HttpBackend from "matrix-mock-request";
|
|||||||
|
|
||||||
import * as utils from "../test-utils/test-utils";
|
import * as utils from "../test-utils/test-utils";
|
||||||
import { EventStatus } from "../../src/models/event";
|
import { EventStatus } from "../../src/models/event";
|
||||||
import { ClientEvent, IEvent, MatrixClient, RoomEvent } from "../../src";
|
import { MatrixError, ClientEvent, IEvent, MatrixClient, RoomEvent } from "../../src";
|
||||||
import { TestClient } from "../TestClient";
|
import { TestClient } from "../TestClient";
|
||||||
|
|
||||||
describe("MatrixClient room timelines", function() {
|
describe("MatrixClient room timelines", function() {
|
||||||
@ -802,17 +802,14 @@ describe("MatrixClient room timelines", function() {
|
|||||||
it('Timeline recovers after `/context` request to generate new timeline fails', async () => {
|
it('Timeline recovers after `/context` request to generate new timeline fails', async () => {
|
||||||
// `/context` request for `refreshLiveTimeline()` -> `getEventTimeline()`
|
// `/context` request for `refreshLiveTimeline()` -> `getEventTimeline()`
|
||||||
// to construct a new timeline from.
|
// to construct a new timeline from.
|
||||||
httpBackend!.when("GET", contextUrl)
|
httpBackend!.when("GET", contextUrl).check(() => {
|
||||||
.respond(500, function() {
|
// The timeline should be cleared at this point in the refresh
|
||||||
// The timeline should be cleared at this point in the refresh
|
expect(room.timeline.length).toEqual(0);
|
||||||
expect(room.timeline.length).toEqual(0);
|
}).respond(500, new MatrixError({
|
||||||
|
errcode: 'TEST_FAKE_ERROR',
|
||||||
return {
|
error: 'We purposely intercepted this /context request to make it fail ' +
|
||||||
errcode: 'TEST_FAKE_ERROR',
|
'in order to test whether the refresh timeline code is resilient',
|
||||||
error: 'We purposely intercepted this /context request to make it fail ' +
|
}));
|
||||||
'in order to test whether the refresh timeline code is resilient',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Refresh the timeline and expect it to fail
|
// Refresh the timeline and expect it to fail
|
||||||
const settledFailedRefreshPromises = await Promise.allSettled([
|
const settledFailedRefreshPromises = await Promise.allSettled([
|
||||||
|
@ -1572,7 +1572,7 @@ describe("MatrixClient syncing (IndexedDB version)", () => {
|
|||||||
const idbHttpBackend = idbTestClient.httpBackend;
|
const idbHttpBackend = idbTestClient.httpBackend;
|
||||||
const idbClient = idbTestClient.client;
|
const idbClient = idbTestClient.client;
|
||||||
idbHttpBackend.when("GET", "/versions").respond(200, {});
|
idbHttpBackend.when("GET", "/versions").respond(200, {});
|
||||||
idbHttpBackend.when("GET", "/pushrules").respond(200, {});
|
idbHttpBackend.when("GET", "/pushrules/").respond(200, {});
|
||||||
idbHttpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
|
idbHttpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
|
||||||
|
|
||||||
await idbClient.initCrypto();
|
await idbClient.initCrypto();
|
||||||
|
@ -23,12 +23,13 @@ import { TestClient } from "../TestClient";
|
|||||||
import { IRoomEvent, IStateEvent } from "../../src/sync-accumulator";
|
import { IRoomEvent, IStateEvent } from "../../src/sync-accumulator";
|
||||||
import {
|
import {
|
||||||
MatrixClient, MatrixEvent, NotificationCountType, JoinRule, MatrixError,
|
MatrixClient, MatrixEvent, NotificationCountType, JoinRule, MatrixError,
|
||||||
EventType, IPushRules, PushRuleKind, TweakName, ClientEvent,
|
EventType, IPushRules, PushRuleKind, TweakName, ClientEvent, RoomMemberEvent,
|
||||||
} from "../../src";
|
} from "../../src";
|
||||||
import { SlidingSyncSdk } from "../../src/sliding-sync-sdk";
|
import { SlidingSyncSdk } from "../../src/sliding-sync-sdk";
|
||||||
import { SyncState } from "../../src/sync";
|
import { SyncState } from "../../src/sync";
|
||||||
import { IStoredClientOpts } from "../../src/client";
|
import { IStoredClientOpts } from "../../src/client";
|
||||||
import { logger } from "../../src/logger";
|
import { logger } from "../../src/logger";
|
||||||
|
import { emitPromise } from "../test-utils/test-utils";
|
||||||
|
|
||||||
describe("SlidingSyncSdk", () => {
|
describe("SlidingSyncSdk", () => {
|
||||||
let client: MatrixClient | undefined;
|
let client: MatrixClient | undefined;
|
||||||
@ -530,6 +531,7 @@ describe("SlidingSyncSdk", () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
await httpBackend!.flush("/profile", 1, 1000);
|
await httpBackend!.flush("/profile", 1, 1000);
|
||||||
|
await emitPromise(client!, RoomMemberEvent.Name);
|
||||||
const room = client!.getRoom(roomId)!;
|
const room = client!.getRoom(roomId)!;
|
||||||
expect(room).toBeDefined();
|
expect(room).toBeDefined();
|
||||||
const inviteeMember = room.getMember(invitee)!;
|
const inviteeMember = room.getMember(invitee)!;
|
||||||
|
19
spec/setupTests.ts
Normal file
19
spec/setupTests.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
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 DOMException from "domexception";
|
||||||
|
|
||||||
|
global.DOMException = DOMException;
|
@ -17,13 +17,12 @@ limitations under the License.
|
|||||||
|
|
||||||
import MockHttpBackend from "matrix-mock-request";
|
import MockHttpBackend from "matrix-mock-request";
|
||||||
|
|
||||||
import { request } from "../../src/matrix";
|
|
||||||
import { AutoDiscovery } from "../../src/autodiscovery";
|
import { AutoDiscovery } from "../../src/autodiscovery";
|
||||||
|
|
||||||
describe("AutoDiscovery", function() {
|
describe("AutoDiscovery", function() {
|
||||||
const getHttpBackend = (): MockHttpBackend => {
|
const getHttpBackend = (): MockHttpBackend => {
|
||||||
const httpBackend = new MockHttpBackend();
|
const httpBackend = new MockHttpBackend();
|
||||||
request(httpBackend.requestFn);
|
AutoDiscovery.setFetchFn(httpBackend.fetchFn as typeof global.fetch);
|
||||||
return httpBackend;
|
return httpBackend;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -176,8 +175,7 @@ describe("AutoDiscovery", function() {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return FAIL_PROMPT when .well-known does not have a base_url for " +
|
it("should return FAIL_PROMPT when .well-known does not have a base_url for m.homeserver (empty string)", () => {
|
||||||
"m.homeserver (empty string)", function() {
|
|
||||||
const httpBackend = getHttpBackend();
|
const httpBackend = getHttpBackend();
|
||||||
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
|
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
|
||||||
"m.homeserver": {
|
"m.homeserver": {
|
||||||
@ -205,8 +203,7 @@ describe("AutoDiscovery", function() {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return FAIL_PROMPT when .well-known does not have a base_url for " +
|
it("should return FAIL_PROMPT when .well-known does not have a base_url for m.homeserver (no property)", () => {
|
||||||
"m.homeserver (no property)", function() {
|
|
||||||
const httpBackend = getHttpBackend();
|
const httpBackend = getHttpBackend();
|
||||||
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
|
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
|
||||||
"m.homeserver": {},
|
"m.homeserver": {},
|
||||||
@ -232,8 +229,7 @@ describe("AutoDiscovery", function() {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return FAIL_ERROR when .well-known has an invalid base_url for " +
|
it("should return FAIL_ERROR when .well-known has an invalid base_url for m.homeserver (disallowed scheme)", () => {
|
||||||
"m.homeserver (disallowed scheme)", function() {
|
|
||||||
const httpBackend = getHttpBackend();
|
const httpBackend = getHttpBackend();
|
||||||
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
|
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
|
||||||
"m.homeserver": {
|
"m.homeserver": {
|
||||||
@ -679,4 +675,76 @@ describe("AutoDiscovery", function() {
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should return FAIL_PROMPT for connection errors", () => {
|
||||||
|
const httpBackend = getHttpBackend();
|
||||||
|
httpBackend.when("GET", "/.well-known/matrix/client").fail(0, undefined);
|
||||||
|
return Promise.all([
|
||||||
|
httpBackend.flushAllExpected(),
|
||||||
|
AutoDiscovery.findClientConfig("example.org").then((conf) => {
|
||||||
|
const expected = {
|
||||||
|
"m.homeserver": {
|
||||||
|
state: "FAIL_PROMPT",
|
||||||
|
error: AutoDiscovery.ERROR_INVALID,
|
||||||
|
base_url: null,
|
||||||
|
},
|
||||||
|
"m.identity_server": {
|
||||||
|
state: "PROMPT",
|
||||||
|
error: null,
|
||||||
|
base_url: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(conf).toEqual(expected);
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return FAIL_PROMPT for fetch errors", () => {
|
||||||
|
const httpBackend = getHttpBackend();
|
||||||
|
httpBackend.when("GET", "/.well-known/matrix/client").fail(0, new Error("CORS or something"));
|
||||||
|
return Promise.all([
|
||||||
|
httpBackend.flushAllExpected(),
|
||||||
|
AutoDiscovery.findClientConfig("example.org").then((conf) => {
|
||||||
|
const expected = {
|
||||||
|
"m.homeserver": {
|
||||||
|
state: "FAIL_PROMPT",
|
||||||
|
error: AutoDiscovery.ERROR_INVALID,
|
||||||
|
base_url: null,
|
||||||
|
},
|
||||||
|
"m.identity_server": {
|
||||||
|
state: "PROMPT",
|
||||||
|
error: null,
|
||||||
|
base_url: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(conf).toEqual(expected);
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return FAIL_PROMPT for invalid JSON", () => {
|
||||||
|
const httpBackend = getHttpBackend();
|
||||||
|
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, "<html>", true);
|
||||||
|
return Promise.all([
|
||||||
|
httpBackend.flushAllExpected(),
|
||||||
|
AutoDiscovery.findClientConfig("example.org").then((conf) => {
|
||||||
|
const expected = {
|
||||||
|
"m.homeserver": {
|
||||||
|
state: "FAIL_PROMPT",
|
||||||
|
error: AutoDiscovery.ERROR_INVALID,
|
||||||
|
base_url: null,
|
||||||
|
},
|
||||||
|
"m.identity_server": {
|
||||||
|
state: "PROMPT",
|
||||||
|
error: null,
|
||||||
|
base_url: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(conf).toEqual(expected);
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -30,7 +30,7 @@ import { Crypto } from "../../../src/crypto";
|
|||||||
import { resetCrossSigningKeys } from "./crypto-utils";
|
import { resetCrossSigningKeys } from "./crypto-utils";
|
||||||
import { BackupManager } from "../../../src/crypto/backup";
|
import { BackupManager } from "../../../src/crypto/backup";
|
||||||
import { StubStore } from "../../../src/store/stub";
|
import { StubStore } from "../../../src/store/stub";
|
||||||
import { IAbortablePromise, MatrixScheduler } from '../../../src';
|
import { MatrixScheduler } from '../../../src';
|
||||||
|
|
||||||
const Olm = global.Olm;
|
const Olm = global.Olm;
|
||||||
|
|
||||||
@ -131,7 +131,7 @@ function makeTestClient(cryptoStore) {
|
|||||||
baseUrl: "https://my.home.server",
|
baseUrl: "https://my.home.server",
|
||||||
idBaseUrl: "https://identity.server",
|
idBaseUrl: "https://identity.server",
|
||||||
accessToken: "my.access.token",
|
accessToken: "my.access.token",
|
||||||
request: jest.fn(), // NOP
|
fetchFn: jest.fn(), // NOP
|
||||||
store: store,
|
store: store,
|
||||||
scheduler: scheduler,
|
scheduler: scheduler,
|
||||||
userId: "@alice:bar",
|
userId: "@alice:bar",
|
||||||
@ -197,7 +197,7 @@ describe("MegolmBackup", function() {
|
|||||||
// to tick the clock between the first try and the retry.
|
// to tick the clock between the first try and the retry.
|
||||||
const realSetTimeout = global.setTimeout;
|
const realSetTimeout = global.setTimeout;
|
||||||
jest.spyOn(global, 'setTimeout').mockImplementation(function(f, n) {
|
jest.spyOn(global, 'setTimeout').mockImplementation(function(f, n) {
|
||||||
return realSetTimeout(f, n/100);
|
return realSetTimeout(f!, n/100);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -298,25 +298,25 @@ describe("MegolmBackup", function() {
|
|||||||
});
|
});
|
||||||
let numCalls = 0;
|
let numCalls = 0;
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
client.http.authedRequest = function(
|
client.http.authedRequest = function<T>(
|
||||||
callback, method, path, queryParams, data, opts,
|
method, path, queryParams, data, opts,
|
||||||
) {
|
): Promise<T> {
|
||||||
++numCalls;
|
++numCalls;
|
||||||
expect(numCalls).toBeLessThanOrEqual(1);
|
expect(numCalls).toBeLessThanOrEqual(1);
|
||||||
if (numCalls >= 2) {
|
if (numCalls >= 2) {
|
||||||
// exit out of retry loop if there's something wrong
|
// exit out of retry loop if there's something wrong
|
||||||
reject(new Error("authedRequest called too many timmes"));
|
reject(new Error("authedRequest called too many timmes"));
|
||||||
return Promise.resolve({}) as IAbortablePromise<any>;
|
return Promise.resolve({} as T);
|
||||||
}
|
}
|
||||||
expect(method).toBe("PUT");
|
expect(method).toBe("PUT");
|
||||||
expect(path).toBe("/room_keys/keys");
|
expect(path).toBe("/room_keys/keys");
|
||||||
expect(queryParams.version).toBe('1');
|
expect(queryParams.version).toBe('1');
|
||||||
expect(data.rooms[ROOM_ID].sessions).toBeDefined();
|
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toBeDefined();
|
||||||
expect(data.rooms[ROOM_ID].sessions).toHaveProperty(
|
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toHaveProperty(
|
||||||
groupSession.session_id(),
|
groupSession.session_id(),
|
||||||
);
|
);
|
||||||
resolve();
|
resolve();
|
||||||
return Promise.resolve({}) as IAbortablePromise<any>;
|
return Promise.resolve({} as T);
|
||||||
};
|
};
|
||||||
client.crypto.backupManager.backupGroupSession(
|
client.crypto.backupManager.backupGroupSession(
|
||||||
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
||||||
@ -381,25 +381,25 @@ describe("MegolmBackup", function() {
|
|||||||
});
|
});
|
||||||
let numCalls = 0;
|
let numCalls = 0;
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
client.http.authedRequest = function(
|
client.http.authedRequest = function<T>(
|
||||||
callback, method, path, queryParams, data, opts,
|
method, path, queryParams, data, opts,
|
||||||
) {
|
): Promise<T> {
|
||||||
++numCalls;
|
++numCalls;
|
||||||
expect(numCalls).toBeLessThanOrEqual(1);
|
expect(numCalls).toBeLessThanOrEqual(1);
|
||||||
if (numCalls >= 2) {
|
if (numCalls >= 2) {
|
||||||
// exit out of retry loop if there's something wrong
|
// exit out of retry loop if there's something wrong
|
||||||
reject(new Error("authedRequest called too many timmes"));
|
reject(new Error("authedRequest called too many timmes"));
|
||||||
return Promise.resolve({}) as IAbortablePromise<any>;
|
return Promise.resolve({} as T);
|
||||||
}
|
}
|
||||||
expect(method).toBe("PUT");
|
expect(method).toBe("PUT");
|
||||||
expect(path).toBe("/room_keys/keys");
|
expect(path).toBe("/room_keys/keys");
|
||||||
expect(queryParams.version).toBe('1');
|
expect(queryParams.version).toBe('1');
|
||||||
expect(data.rooms[ROOM_ID].sessions).toBeDefined();
|
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toBeDefined();
|
||||||
expect(data.rooms[ROOM_ID].sessions).toHaveProperty(
|
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toHaveProperty(
|
||||||
groupSession.session_id(),
|
groupSession.session_id(),
|
||||||
);
|
);
|
||||||
resolve();
|
resolve();
|
||||||
return Promise.resolve({}) as IAbortablePromise<any>;
|
return Promise.resolve({} as T);
|
||||||
};
|
};
|
||||||
client.crypto.backupManager.backupGroupSession(
|
client.crypto.backupManager.backupGroupSession(
|
||||||
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
||||||
@ -439,7 +439,7 @@ describe("MegolmBackup", function() {
|
|||||||
new Promise<void>((resolve, reject) => {
|
new Promise<void>((resolve, reject) => {
|
||||||
let backupInfo;
|
let backupInfo;
|
||||||
client.http.authedRequest = function(
|
client.http.authedRequest = function(
|
||||||
callback, method, path, queryParams, data, opts,
|
method, path, queryParams, data, opts,
|
||||||
) {
|
) {
|
||||||
++numCalls;
|
++numCalls;
|
||||||
expect(numCalls).toBeLessThanOrEqual(2);
|
expect(numCalls).toBeLessThanOrEqual(2);
|
||||||
@ -449,23 +449,23 @@ describe("MegolmBackup", function() {
|
|||||||
try {
|
try {
|
||||||
// make sure auth_data is signed by the master key
|
// make sure auth_data is signed by the master key
|
||||||
olmlib.pkVerify(
|
olmlib.pkVerify(
|
||||||
data.auth_data, client.getCrossSigningId(), "@alice:bar",
|
(data as Record<string, any>).auth_data, client.getCrossSigningId(), "@alice:bar",
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
return Promise.resolve({}) as IAbortablePromise<any>;
|
return Promise.resolve({});
|
||||||
}
|
}
|
||||||
backupInfo = data;
|
backupInfo = data;
|
||||||
return Promise.resolve({}) as IAbortablePromise<any>;
|
return Promise.resolve({});
|
||||||
} else if (numCalls === 2) {
|
} else if (numCalls === 2) {
|
||||||
expect(method).toBe("GET");
|
expect(method).toBe("GET");
|
||||||
expect(path).toBe("/room_keys/version");
|
expect(path).toBe("/room_keys/version");
|
||||||
resolve();
|
resolve();
|
||||||
return Promise.resolve(backupInfo) as IAbortablePromise<any>;
|
return Promise.resolve(backupInfo);
|
||||||
} else {
|
} else {
|
||||||
// exit out of retry loop if there's something wrong
|
// exit out of retry loop if there's something wrong
|
||||||
reject(new Error("authedRequest called too many times"));
|
reject(new Error("authedRequest called too many times"));
|
||||||
return Promise.resolve({}) as IAbortablePromise<any>;
|
return Promise.resolve({});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
@ -495,7 +495,7 @@ describe("MegolmBackup", function() {
|
|||||||
baseUrl: "https://my.home.server",
|
baseUrl: "https://my.home.server",
|
||||||
idBaseUrl: "https://identity.server",
|
idBaseUrl: "https://identity.server",
|
||||||
accessToken: "my.access.token",
|
accessToken: "my.access.token",
|
||||||
request: jest.fn(), // NOP
|
fetchFn: jest.fn(), // NOP
|
||||||
store: store,
|
store: store,
|
||||||
scheduler: scheduler,
|
scheduler: scheduler,
|
||||||
userId: "@alice:bar",
|
userId: "@alice:bar",
|
||||||
@ -542,30 +542,30 @@ describe("MegolmBackup", function() {
|
|||||||
let numCalls = 0;
|
let numCalls = 0;
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
client.http.authedRequest = function(
|
client.http.authedRequest = function<T>(
|
||||||
callback, method, path, queryParams, data, opts,
|
method, path, queryParams, data, opts,
|
||||||
) {
|
): Promise<T> {
|
||||||
++numCalls;
|
++numCalls;
|
||||||
expect(numCalls).toBeLessThanOrEqual(2);
|
expect(numCalls).toBeLessThanOrEqual(2);
|
||||||
if (numCalls >= 3) {
|
if (numCalls >= 3) {
|
||||||
// exit out of retry loop if there's something wrong
|
// exit out of retry loop if there's something wrong
|
||||||
reject(new Error("authedRequest called too many timmes"));
|
reject(new Error("authedRequest called too many timmes"));
|
||||||
return Promise.resolve({}) as IAbortablePromise<any>;
|
return Promise.resolve({} as T);
|
||||||
}
|
}
|
||||||
expect(method).toBe("PUT");
|
expect(method).toBe("PUT");
|
||||||
expect(path).toBe("/room_keys/keys");
|
expect(path).toBe("/room_keys/keys");
|
||||||
expect(queryParams.version).toBe('1');
|
expect(queryParams.version).toBe('1');
|
||||||
expect(data.rooms[ROOM_ID].sessions).toBeDefined();
|
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toBeDefined();
|
||||||
expect(data.rooms[ROOM_ID].sessions).toHaveProperty(
|
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toHaveProperty(
|
||||||
groupSession.session_id(),
|
groupSession.session_id(),
|
||||||
);
|
);
|
||||||
if (numCalls > 1) {
|
if (numCalls > 1) {
|
||||||
resolve();
|
resolve();
|
||||||
return Promise.resolve({}) as IAbortablePromise<any>;
|
return Promise.resolve({} as T);
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new Error("this is an expected failure"),
|
new Error("this is an expected failure"),
|
||||||
) as IAbortablePromise<any>;
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return client.crypto.backupManager.backupGroupSession(
|
return client.crypto.backupManager.backupGroupSession(
|
||||||
|
@ -141,7 +141,7 @@ describe("Cross Signing", function() {
|
|||||||
};
|
};
|
||||||
alice.uploadKeySignatures = async () => ({ failures: {} });
|
alice.uploadKeySignatures = async () => ({ failures: {} });
|
||||||
alice.setAccountData = async () => ({});
|
alice.setAccountData = async () => ({});
|
||||||
alice.getAccountDataFromServer = async <T extends {[k: string]: any}>(): Promise<T> => ({} as T);
|
alice.getAccountDataFromServer = async <T extends {[k: string]: any}>(): Promise<T | null> => ({} as T);
|
||||||
const authUploadDeviceSigningKeys = async func => await func({});
|
const authUploadDeviceSigningKeys = async func => await func({});
|
||||||
|
|
||||||
// Try bootstrap, expecting `authUploadDeviceSigningKeys` to pass
|
// Try bootstrap, expecting `authUploadDeviceSigningKeys` to pass
|
||||||
|
@ -109,16 +109,13 @@ describe("Secrets", function() {
|
|||||||
const secretStorage = alice.crypto.secretStorage;
|
const secretStorage = alice.crypto.secretStorage;
|
||||||
|
|
||||||
jest.spyOn(alice, 'setAccountData').mockImplementation(
|
jest.spyOn(alice, 'setAccountData').mockImplementation(
|
||||||
async function(eventType, contents, callback) {
|
async function(eventType, contents) {
|
||||||
alice.store.storeAccountDataEvents([
|
alice.store.storeAccountDataEvents([
|
||||||
new MatrixEvent({
|
new MatrixEvent({
|
||||||
type: eventType,
|
type: eventType,
|
||||||
content: contents,
|
content: contents,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
if (callback) {
|
|
||||||
callback(undefined, undefined);
|
|
||||||
}
|
|
||||||
return {};
|
return {};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -192,7 +189,7 @@ describe("Secrets", function() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
alice.setAccountData = async function(eventType, contents, callback) {
|
alice.setAccountData = async function(eventType, contents) {
|
||||||
alice.store.storeAccountDataEvents([
|
alice.store.storeAccountDataEvents([
|
||||||
new MatrixEvent({
|
new MatrixEvent({
|
||||||
type: eventType,
|
type: eventType,
|
||||||
@ -332,7 +329,7 @@ describe("Secrets", function() {
|
|||||||
);
|
);
|
||||||
bob.uploadDeviceSigningKeys = async () => ({});
|
bob.uploadDeviceSigningKeys = async () => ({});
|
||||||
bob.uploadKeySignatures = jest.fn().mockResolvedValue(undefined);
|
bob.uploadKeySignatures = jest.fn().mockResolvedValue(undefined);
|
||||||
bob.setAccountData = async function(eventType, contents, callback) {
|
bob.setAccountData = async function(eventType, contents) {
|
||||||
const event = new MatrixEvent({
|
const event = new MatrixEvent({
|
||||||
type: eventType,
|
type: eventType,
|
||||||
content: contents,
|
content: contents,
|
||||||
|
@ -29,7 +29,7 @@ describe("eventMapperFor", function() {
|
|||||||
client = new MatrixClient({
|
client = new MatrixClient({
|
||||||
baseUrl: "https://my.home.server",
|
baseUrl: "https://my.home.server",
|
||||||
accessToken: "my.access.token",
|
accessToken: "my.access.token",
|
||||||
request: function() {} as any, // NOP
|
fetchFn: function() {} as any, // NOP
|
||||||
store: {
|
store: {
|
||||||
getRoom(roomId: string): Room | null {
|
getRoom(roomId: string): Room | null {
|
||||||
return rooms.find(r => r.roomId === roomId);
|
return rooms.find(r => r.roomId === roomId);
|
||||||
|
11
spec/unit/http-api/__snapshots__/index.spec.ts.snap
Normal file
11
spec/unit/http-api/__snapshots__/index.spec.ts.snap
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`MatrixHttpApi should return expected object from \`getContentUri\` 1`] = `
|
||||||
|
{
|
||||||
|
"base": "http://baseUrl",
|
||||||
|
"params": {
|
||||||
|
"access_token": "token",
|
||||||
|
},
|
||||||
|
"path": "/_matrix/media/r0/upload",
|
||||||
|
}
|
||||||
|
`;
|
223
spec/unit/http-api/fetch.spec.ts
Normal file
223
spec/unit/http-api/fetch.spec.ts
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
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 { FetchHttpApi } from "../../../src/http-api/fetch";
|
||||||
|
import { TypedEventEmitter } from "../../../src/models/typed-event-emitter";
|
||||||
|
import { ClientPrefix, HttpApiEvent, HttpApiEventHandlerMap, IdentityPrefix, IHttpOpts, Method } from "../../../src";
|
||||||
|
import { emitPromise } from "../../test-utils/test-utils";
|
||||||
|
|
||||||
|
describe("FetchHttpApi", () => {
|
||||||
|
const baseUrl = "http://baseUrl";
|
||||||
|
const idBaseUrl = "http://idBaseUrl";
|
||||||
|
const prefix = ClientPrefix.V3;
|
||||||
|
|
||||||
|
it("should support aborting multiple times", () => {
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix, fetchFn });
|
||||||
|
|
||||||
|
api.request(Method.Get, "/foo");
|
||||||
|
api.request(Method.Get, "/baz");
|
||||||
|
expect(fetchFn.mock.calls[0][0].href.endsWith("/foo")).toBeTruthy();
|
||||||
|
expect(fetchFn.mock.calls[0][1].signal.aborted).toBeFalsy();
|
||||||
|
expect(fetchFn.mock.calls[1][0].href.endsWith("/baz")).toBeTruthy();
|
||||||
|
expect(fetchFn.mock.calls[1][1].signal.aborted).toBeFalsy();
|
||||||
|
|
||||||
|
api.abort();
|
||||||
|
expect(fetchFn.mock.calls[0][1].signal.aborted).toBeTruthy();
|
||||||
|
expect(fetchFn.mock.calls[1][1].signal.aborted).toBeTruthy();
|
||||||
|
|
||||||
|
api.request(Method.Get, "/bar");
|
||||||
|
expect(fetchFn.mock.calls[2][0].href.endsWith("/bar")).toBeTruthy();
|
||||||
|
expect(fetchFn.mock.calls[2][1].signal.aborted).toBeFalsy();
|
||||||
|
|
||||||
|
api.abort();
|
||||||
|
expect(fetchFn.mock.calls[2][1].signal.aborted).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fall back to global fetch if fetchFn not provided", () => {
|
||||||
|
global.fetch = jest.fn();
|
||||||
|
expect(global.fetch).not.toHaveBeenCalled();
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
|
||||||
|
api.fetch("test");
|
||||||
|
expect(global.fetch).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should update identity server base url", () => {
|
||||||
|
const api = new FetchHttpApi<IHttpOpts>(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
|
||||||
|
expect(api.opts.idBaseUrl).toBeUndefined();
|
||||||
|
api.setIdBaseUrl("https://id.foo.bar");
|
||||||
|
expect(api.opts.idBaseUrl).toBe("https://id.foo.bar");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("idServerRequest", () => {
|
||||||
|
it("should throw if no idBaseUrl", () => {
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
|
||||||
|
expect(() => api.idServerRequest(Method.Get, "/test", {}, IdentityPrefix.V2))
|
||||||
|
.toThrow("No identity server base URL set");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should send params as query string for GET requests", () => {
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, idBaseUrl, prefix, fetchFn });
|
||||||
|
api.idServerRequest(Method.Get, "/test", { foo: "bar", via: ["a", "b"] }, IdentityPrefix.V2);
|
||||||
|
expect(fetchFn.mock.calls[0][0].searchParams.get("foo")).toBe("bar");
|
||||||
|
expect(fetchFn.mock.calls[0][0].searchParams.getAll("via")).toEqual(["a", "b"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should send params as body for non-GET requests", () => {
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, idBaseUrl, prefix, fetchFn });
|
||||||
|
const params = { foo: "bar", via: ["a", "b"] };
|
||||||
|
api.idServerRequest(Method.Post, "/test", params, IdentityPrefix.V2);
|
||||||
|
expect(fetchFn.mock.calls[0][0].searchParams.get("foo")).not.toBe("bar");
|
||||||
|
expect(JSON.parse(fetchFn.mock.calls[0][1].body)).toStrictEqual(params);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add Authorization header if token provided", () => {
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, idBaseUrl, prefix, fetchFn });
|
||||||
|
api.idServerRequest(Method.Post, "/test", {}, IdentityPrefix.V2, "token");
|
||||||
|
expect(fetchFn.mock.calls[0][1].headers.Authorization).toBe("Bearer token");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return the Response object if onlyData=false", async () => {
|
||||||
|
const res = { ok: true };
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue(res);
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix, fetchFn, onlyData: false });
|
||||||
|
await expect(api.requestOtherUrl(Method.Get, "http://url")).resolves.toBe(res);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return text if json=false", async () => {
|
||||||
|
const text = "418 I'm a teapot";
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue({ ok: true, text: jest.fn().mockResolvedValue(text) });
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix, fetchFn, onlyData: true });
|
||||||
|
await expect(api.requestOtherUrl(Method.Get, "http://url", undefined, {
|
||||||
|
json: false,
|
||||||
|
})).resolves.toBe(text);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should send token via query params if useAuthorizationHeader=false", () => {
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), {
|
||||||
|
baseUrl,
|
||||||
|
prefix,
|
||||||
|
fetchFn,
|
||||||
|
accessToken: "token",
|
||||||
|
useAuthorizationHeader: false,
|
||||||
|
});
|
||||||
|
api.authedRequest(Method.Get, "/path");
|
||||||
|
expect(fetchFn.mock.calls[0][0].searchParams.get("access_token")).toBe("token");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should send token via headers by default", () => {
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), {
|
||||||
|
baseUrl,
|
||||||
|
prefix,
|
||||||
|
fetchFn,
|
||||||
|
accessToken: "token",
|
||||||
|
});
|
||||||
|
api.authedRequest(Method.Get, "/path");
|
||||||
|
expect(fetchFn.mock.calls[0][1].headers["Authorization"]).toBe("Bearer token");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not send a token if not calling `authedRequest`", () => {
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), {
|
||||||
|
baseUrl,
|
||||||
|
prefix,
|
||||||
|
fetchFn,
|
||||||
|
accessToken: "token",
|
||||||
|
});
|
||||||
|
api.request(Method.Get, "/path");
|
||||||
|
expect(fetchFn.mock.calls[0][0].searchParams.get("access_token")).toBeFalsy();
|
||||||
|
expect(fetchFn.mock.calls[0][1].headers["Authorization"]).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should ensure no token is leaked out via query params if sending via headers", () => {
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), {
|
||||||
|
baseUrl,
|
||||||
|
prefix,
|
||||||
|
fetchFn,
|
||||||
|
accessToken: "token",
|
||||||
|
useAuthorizationHeader: true,
|
||||||
|
});
|
||||||
|
api.authedRequest(Method.Get, "/path", { access_token: "123" });
|
||||||
|
expect(fetchFn.mock.calls[0][0].searchParams.get("access_token")).toBeFalsy();
|
||||||
|
expect(fetchFn.mock.calls[0][1].headers["Authorization"]).toBe("Bearer token");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not override manually specified access token via query params", () => {
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), {
|
||||||
|
baseUrl,
|
||||||
|
prefix,
|
||||||
|
fetchFn,
|
||||||
|
accessToken: "token",
|
||||||
|
useAuthorizationHeader: false,
|
||||||
|
});
|
||||||
|
api.authedRequest(Method.Get, "/path", { access_token: "RealToken" });
|
||||||
|
expect(fetchFn.mock.calls[0][0].searchParams.get("access_token")).toBe("RealToken");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not override manually specified access token via header", () => {
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), {
|
||||||
|
baseUrl,
|
||||||
|
prefix,
|
||||||
|
fetchFn,
|
||||||
|
accessToken: "token",
|
||||||
|
useAuthorizationHeader: true,
|
||||||
|
});
|
||||||
|
api.authedRequest(Method.Get, "/path", undefined, undefined, {
|
||||||
|
headers: { Authorization: "Bearer RealToken" },
|
||||||
|
});
|
||||||
|
expect(fetchFn.mock.calls[0][1].headers["Authorization"]).toBe("Bearer RealToken");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not override Accept header", () => {
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||||
|
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix, fetchFn });
|
||||||
|
api.authedRequest(Method.Get, "/path", undefined, undefined, {
|
||||||
|
headers: { Accept: "text/html" },
|
||||||
|
});
|
||||||
|
expect(fetchFn.mock.calls[0][1].headers["Accept"]).toBe("text/html");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should emit NoConsent when given errcode=M_CONTENT_NOT_GIVEN", async () => {
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue({
|
||||||
|
ok: false,
|
||||||
|
headers: {
|
||||||
|
get(name: string): string | null {
|
||||||
|
return name === "Content-Type" ? "application/json" : null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
text: jest.fn().mockResolvedValue(JSON.stringify({
|
||||||
|
errcode: "M_CONSENT_NOT_GIVEN",
|
||||||
|
error: "Ye shall ask for consent",
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
const emitter = new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>();
|
||||||
|
const api = new FetchHttpApi(emitter, { baseUrl, prefix, fetchFn });
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
emitPromise(emitter, HttpApiEvent.NoConsent),
|
||||||
|
expect(api.authedRequest(Method.Get, "/path")).rejects.toThrow("Ye shall ask for consent"),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
228
spec/unit/http-api/index.spec.ts
Normal file
228
spec/unit/http-api/index.spec.ts
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
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 DOMException from "domexception";
|
||||||
|
import { mocked } from "jest-mock";
|
||||||
|
|
||||||
|
import { ClientPrefix, MatrixHttpApi, Method, UploadResponse } from "../../../src";
|
||||||
|
import { TypedEventEmitter } from "../../../src/models/typed-event-emitter";
|
||||||
|
|
||||||
|
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
|
||||||
|
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
|
describe("MatrixHttpApi", () => {
|
||||||
|
const baseUrl = "http://baseUrl";
|
||||||
|
const prefix = ClientPrefix.V3;
|
||||||
|
|
||||||
|
let xhr: Partial<Writeable<XMLHttpRequest>>;
|
||||||
|
let upload: Promise<UploadResponse>;
|
||||||
|
|
||||||
|
const DONE = 0;
|
||||||
|
|
||||||
|
global.DOMException = DOMException;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
xhr = {
|
||||||
|
upload: {} as XMLHttpRequestUpload,
|
||||||
|
open: jest.fn(),
|
||||||
|
send: jest.fn(),
|
||||||
|
abort: jest.fn(),
|
||||||
|
setRequestHeader: jest.fn(),
|
||||||
|
onreadystatechange: undefined,
|
||||||
|
getResponseHeader: jest.fn(),
|
||||||
|
};
|
||||||
|
// We stub out XHR here as it is not available in JSDOM
|
||||||
|
// @ts-ignore
|
||||||
|
global.XMLHttpRequest = jest.fn().mockReturnValue(xhr);
|
||||||
|
// @ts-ignore
|
||||||
|
global.XMLHttpRequest.DONE = DONE;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
upload?.catch(() => {});
|
||||||
|
// Abort any remaining requests
|
||||||
|
xhr.readyState = DONE;
|
||||||
|
xhr.status = 0;
|
||||||
|
// @ts-ignore
|
||||||
|
xhr.onreadystatechange?.(new Event("test"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fall back to `fetch` where xhr is unavailable", () => {
|
||||||
|
global.XMLHttpRequest = undefined;
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue({ ok: true, json: jest.fn().mockResolvedValue({}) });
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix, fetchFn });
|
||||||
|
upload = api.uploadContent({} as File);
|
||||||
|
expect(fetchFn).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should prefer xhr where available", () => {
|
||||||
|
const fetchFn = jest.fn().mockResolvedValue({ ok: true });
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix, fetchFn });
|
||||||
|
upload = api.uploadContent({} as File);
|
||||||
|
expect(fetchFn).not.toHaveBeenCalled();
|
||||||
|
expect(xhr.open).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should send access token in query params if header disabled", () => {
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), {
|
||||||
|
baseUrl,
|
||||||
|
prefix,
|
||||||
|
accessToken: "token",
|
||||||
|
useAuthorizationHeader: false,
|
||||||
|
});
|
||||||
|
upload = api.uploadContent({} as File);
|
||||||
|
expect(xhr.open)
|
||||||
|
.toHaveBeenCalledWith(Method.Post, baseUrl.toLowerCase() + "/_matrix/media/r0/upload?access_token=token");
|
||||||
|
expect(xhr.setRequestHeader).not.toHaveBeenCalledWith("Authorization");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should send access token in header by default", () => {
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), {
|
||||||
|
baseUrl,
|
||||||
|
prefix,
|
||||||
|
accessToken: "token",
|
||||||
|
});
|
||||||
|
upload = api.uploadContent({} as File);
|
||||||
|
expect(xhr.open).toHaveBeenCalledWith(Method.Post, baseUrl.toLowerCase() + "/_matrix/media/r0/upload");
|
||||||
|
expect(xhr.setRequestHeader).toHaveBeenCalledWith("Authorization", "Bearer token");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should include filename by default", () => {
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
|
||||||
|
upload = api.uploadContent({} as File, { name: "name" });
|
||||||
|
expect(xhr.open)
|
||||||
|
.toHaveBeenCalledWith(Method.Post, baseUrl.toLowerCase() + "/_matrix/media/r0/upload?filename=name");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow not sending the filename", () => {
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
|
||||||
|
upload = api.uploadContent({} as File, { name: "name", includeFilename: false });
|
||||||
|
expect(xhr.open).toHaveBeenCalledWith(Method.Post, baseUrl.toLowerCase() + "/_matrix/media/r0/upload");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should abort xhr when the upload is aborted", () => {
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
|
||||||
|
upload = api.uploadContent({} as File);
|
||||||
|
api.cancelUpload(upload);
|
||||||
|
expect(xhr.abort).toHaveBeenCalled();
|
||||||
|
return expect(upload).rejects.toThrow("Aborted");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should timeout if no progress in 30s", () => {
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
|
||||||
|
upload = api.uploadContent({} as File);
|
||||||
|
jest.advanceTimersByTime(25000);
|
||||||
|
// @ts-ignore
|
||||||
|
xhr.upload.onprogress(new Event("progress", { loaded: 1, total: 100 }));
|
||||||
|
jest.advanceTimersByTime(25000);
|
||||||
|
expect(xhr.abort).not.toHaveBeenCalled();
|
||||||
|
jest.advanceTimersByTime(5000);
|
||||||
|
expect(xhr.abort).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call progressHandler", () => {
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
|
||||||
|
const progressHandler = jest.fn();
|
||||||
|
upload = api.uploadContent({} as File, { progressHandler });
|
||||||
|
const progressEvent = new Event("progress") as ProgressEvent;
|
||||||
|
Object.assign(progressEvent, { loaded: 1, total: 100 });
|
||||||
|
// @ts-ignore
|
||||||
|
xhr.upload.onprogress(progressEvent);
|
||||||
|
expect(progressHandler).toHaveBeenCalledWith({ loaded: 1, total: 100 });
|
||||||
|
|
||||||
|
Object.assign(progressEvent, { loaded: 95, total: 100 });
|
||||||
|
// @ts-ignore
|
||||||
|
xhr.upload.onprogress(progressEvent);
|
||||||
|
expect(progressHandler).toHaveBeenCalledWith({ loaded: 95, total: 100 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should error when no response body", () => {
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
|
||||||
|
upload = api.uploadContent({} as File);
|
||||||
|
|
||||||
|
xhr.readyState = DONE;
|
||||||
|
xhr.responseText = "";
|
||||||
|
xhr.status = 200;
|
||||||
|
// @ts-ignore
|
||||||
|
xhr.onreadystatechange?.(new Event("test"));
|
||||||
|
|
||||||
|
return expect(upload).rejects.toThrow("No response body.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should error on a 400-code", () => {
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
|
||||||
|
upload = api.uploadContent({} as File);
|
||||||
|
|
||||||
|
xhr.readyState = DONE;
|
||||||
|
xhr.responseText = '{"errcode": "M_NOT_FOUND", "error": "Not found"}';
|
||||||
|
xhr.status = 404;
|
||||||
|
mocked(xhr.getResponseHeader).mockReturnValue("application/json");
|
||||||
|
// @ts-ignore
|
||||||
|
xhr.onreadystatechange?.(new Event("test"));
|
||||||
|
|
||||||
|
return expect(upload).rejects.toThrow("Not found");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return response on successful upload", () => {
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
|
||||||
|
upload = api.uploadContent({} as File);
|
||||||
|
|
||||||
|
xhr.readyState = DONE;
|
||||||
|
xhr.responseText = '{"content_uri": "mxc://server/foobar"}';
|
||||||
|
xhr.status = 200;
|
||||||
|
mocked(xhr.getResponseHeader).mockReturnValue("application/json");
|
||||||
|
// @ts-ignore
|
||||||
|
xhr.onreadystatechange?.(new Event("test"));
|
||||||
|
|
||||||
|
return expect(upload).resolves.toStrictEqual({ content_uri: "mxc://server/foobar" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should abort xhr when calling `cancelUpload`", () => {
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
|
||||||
|
upload = api.uploadContent({} as File);
|
||||||
|
expect(api.cancelUpload(upload)).toBeTruthy();
|
||||||
|
expect(xhr.abort).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return false when `cancelUpload` is called but unsuccessful", async () => {
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
|
||||||
|
upload = api.uploadContent({} as File);
|
||||||
|
|
||||||
|
xhr.readyState = DONE;
|
||||||
|
xhr.status = 500;
|
||||||
|
mocked(xhr.getResponseHeader).mockReturnValue("application/json");
|
||||||
|
// @ts-ignore
|
||||||
|
xhr.onreadystatechange?.(new Event("test"));
|
||||||
|
await upload.catch(() => {});
|
||||||
|
|
||||||
|
expect(api.cancelUpload(upload)).toBeFalsy();
|
||||||
|
expect(xhr.abort).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return active uploads in `getCurrentUploads`", () => {
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
|
||||||
|
upload = api.uploadContent({} as File);
|
||||||
|
expect(api.getCurrentUploads().find(u => u.promise === upload)).toBeTruthy();
|
||||||
|
api.cancelUpload(upload);
|
||||||
|
expect(api.getCurrentUploads().find(u => u.promise === upload)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return expected object from `getContentUri`", () => {
|
||||||
|
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix, accessToken: "token" });
|
||||||
|
expect(api.getContentUri()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
183
spec/unit/http-api/utils.spec.ts
Normal file
183
spec/unit/http-api/utils.spec.ts
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
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 { mocked } from "jest-mock";
|
||||||
|
|
||||||
|
import {
|
||||||
|
anySignal,
|
||||||
|
ConnectionError,
|
||||||
|
MatrixError,
|
||||||
|
parseErrorResponse,
|
||||||
|
retryNetworkOperation,
|
||||||
|
timeoutSignal,
|
||||||
|
} from "../../../src";
|
||||||
|
import { sleep } from "../../../src/utils";
|
||||||
|
|
||||||
|
jest.mock("../../../src/utils");
|
||||||
|
|
||||||
|
describe("timeoutSignal", () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
|
it("should fire abort signal after specified timeout", () => {
|
||||||
|
const signal = timeoutSignal(3000);
|
||||||
|
const onabort = jest.fn();
|
||||||
|
signal.onabort = onabort;
|
||||||
|
expect(signal.aborted).toBeFalsy();
|
||||||
|
expect(onabort).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(3000);
|
||||||
|
expect(signal.aborted).toBeTruthy();
|
||||||
|
expect(onabort).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("anySignal", () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
|
it("should fire when any signal fires", () => {
|
||||||
|
const { signal } = anySignal([
|
||||||
|
timeoutSignal(3000),
|
||||||
|
timeoutSignal(2000),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const onabort = jest.fn();
|
||||||
|
signal.onabort = onabort;
|
||||||
|
expect(signal.aborted).toBeFalsy();
|
||||||
|
expect(onabort).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(2000);
|
||||||
|
expect(signal.aborted).toBeTruthy();
|
||||||
|
expect(onabort).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should cleanup when instructed", () => {
|
||||||
|
const { signal, cleanup } = anySignal([
|
||||||
|
timeoutSignal(3000),
|
||||||
|
timeoutSignal(2000),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const onabort = jest.fn();
|
||||||
|
signal.onabort = onabort;
|
||||||
|
expect(signal.aborted).toBeFalsy();
|
||||||
|
expect(onabort).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
jest.advanceTimersByTime(2000);
|
||||||
|
expect(signal.aborted).toBeFalsy();
|
||||||
|
expect(onabort).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should abort immediately if passed an aborted signal", () => {
|
||||||
|
const controller = new AbortController();
|
||||||
|
controller.abort();
|
||||||
|
const { signal } = anySignal([controller.signal]);
|
||||||
|
expect(signal.aborted).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("parseErrorResponse", () => {
|
||||||
|
it("should resolve Matrix Errors from XHR", () => {
|
||||||
|
expect(parseErrorResponse({
|
||||||
|
getResponseHeader(name: string): string | null {
|
||||||
|
return name === "Content-Type" ? "application/json" : null;
|
||||||
|
},
|
||||||
|
status: 500,
|
||||||
|
} as XMLHttpRequest, '{"errcode": "TEST"}')).toStrictEqual(new MatrixError({
|
||||||
|
errcode: "TEST",
|
||||||
|
}, 500));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should resolve Matrix Errors from fetch", () => {
|
||||||
|
expect(parseErrorResponse({
|
||||||
|
headers: {
|
||||||
|
get(name: string): string | null {
|
||||||
|
return name === "Content-Type" ? "application/json" : null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 500,
|
||||||
|
} as Response, '{"errcode": "TEST"}')).toStrictEqual(new MatrixError({
|
||||||
|
errcode: "TEST",
|
||||||
|
}, 500));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle no type gracefully", () => {
|
||||||
|
expect(parseErrorResponse({
|
||||||
|
headers: {
|
||||||
|
get(name: string): string | null {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 500,
|
||||||
|
} as Response, '{"errcode": "TEST"}')).toStrictEqual(new Error("Server returned 500 error"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle invalid type gracefully", () => {
|
||||||
|
expect(parseErrorResponse({
|
||||||
|
headers: {
|
||||||
|
get(name: string): string | null {
|
||||||
|
return name === "Content-Type" ? " " : null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 500,
|
||||||
|
} as Response, '{"errcode": "TEST"}'))
|
||||||
|
.toStrictEqual(new Error("Error parsing Content-Type ' ': TypeError: invalid media type"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle plaintext errors", () => {
|
||||||
|
expect(parseErrorResponse({
|
||||||
|
headers: {
|
||||||
|
get(name: string): string | null {
|
||||||
|
return name === "Content-Type" ? "text/plain" : null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 418,
|
||||||
|
} as Response, "I'm a teapot")).toStrictEqual(new Error("Server returned 418 error: I'm a teapot"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("retryNetworkOperation", () => {
|
||||||
|
it("should retry given number of times with exponential sleeps", async () => {
|
||||||
|
const err = new ConnectionError("test");
|
||||||
|
const fn = jest.fn().mockRejectedValue(err);
|
||||||
|
mocked(sleep).mockResolvedValue(undefined);
|
||||||
|
await expect(retryNetworkOperation(4, fn)).rejects.toThrow(err);
|
||||||
|
expect(fn).toHaveBeenCalledTimes(4);
|
||||||
|
expect(mocked(sleep)).toHaveBeenCalledTimes(3);
|
||||||
|
expect(mocked(sleep).mock.calls[0][0]).toBe(2000);
|
||||||
|
expect(mocked(sleep).mock.calls[1][0]).toBe(4000);
|
||||||
|
expect(mocked(sleep).mock.calls[2][0]).toBe(8000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should bail out on errors other than ConnectionError", async () => {
|
||||||
|
const err = new TypeError("invalid JSON");
|
||||||
|
const fn = jest.fn().mockRejectedValue(err);
|
||||||
|
mocked(sleep).mockResolvedValue(undefined);
|
||||||
|
await expect(retryNetworkOperation(3, fn)).rejects.toThrow(err);
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return newest ConnectionError when giving up", async () => {
|
||||||
|
const err1 = new ConnectionError("test1");
|
||||||
|
const err2 = new ConnectionError("test2");
|
||||||
|
const err3 = new ConnectionError("test3");
|
||||||
|
const errors = [err1, err2, err3];
|
||||||
|
const fn = jest.fn().mockImplementation(() => {
|
||||||
|
throw errors.shift();
|
||||||
|
});
|
||||||
|
mocked(sleep).mockResolvedValue(undefined);
|
||||||
|
await expect(retryNetworkOperation(3, fn)).rejects.toThrow(err3);
|
||||||
|
});
|
||||||
|
});
|
@ -103,7 +103,7 @@ describe("MatrixClient", function() {
|
|||||||
];
|
];
|
||||||
let acceptKeepalives: boolean;
|
let acceptKeepalives: boolean;
|
||||||
let pendingLookup = null;
|
let pendingLookup = null;
|
||||||
function httpReq(cb, method, path, qp, data, prefix) {
|
function httpReq(method, path, qp, data, prefix) {
|
||||||
if (path === KEEP_ALIVE_PATH && acceptKeepalives) {
|
if (path === KEEP_ALIVE_PATH && acceptKeepalives) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
unstable_features: {
|
unstable_features: {
|
||||||
@ -132,7 +132,6 @@ describe("MatrixClient", function() {
|
|||||||
method: method,
|
method: method,
|
||||||
path: path,
|
path: path,
|
||||||
};
|
};
|
||||||
pendingLookup.promise.abort = () => {}; // to make it a valid IAbortablePromise
|
|
||||||
return pendingLookup.promise;
|
return pendingLookup.promise;
|
||||||
}
|
}
|
||||||
if (next.path === path && next.method === method) {
|
if (next.path === path && next.method === method) {
|
||||||
@ -178,7 +177,7 @@ describe("MatrixClient", function() {
|
|||||||
baseUrl: "https://my.home.server",
|
baseUrl: "https://my.home.server",
|
||||||
idBaseUrl: identityServerUrl,
|
idBaseUrl: identityServerUrl,
|
||||||
accessToken: "my.access.token",
|
accessToken: "my.access.token",
|
||||||
request: function() {} as any, // NOP
|
fetchFn: function() {} as any, // NOP
|
||||||
store: store,
|
store: store,
|
||||||
scheduler: scheduler,
|
scheduler: scheduler,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
@ -1153,8 +1152,7 @@ describe("MatrixClient", function() {
|
|||||||
|
|
||||||
// event type combined
|
// event type combined
|
||||||
const expectedEventType = M_BEACON_INFO.name;
|
const expectedEventType = M_BEACON_INFO.name;
|
||||||
const [callback, method, path, queryParams, requestContent] = client.http.authedRequest.mock.calls[0];
|
const [method, path, queryParams, requestContent] = client.http.authedRequest.mock.calls[0];
|
||||||
expect(callback).toBeFalsy();
|
|
||||||
expect(method).toBe('PUT');
|
expect(method).toBe('PUT');
|
||||||
expect(path).toEqual(
|
expect(path).toEqual(
|
||||||
`/rooms/${encodeURIComponent(roomId)}/state/` +
|
`/rooms/${encodeURIComponent(roomId)}/state/` +
|
||||||
@ -1168,7 +1166,7 @@ describe("MatrixClient", function() {
|
|||||||
await client.unstable_setLiveBeacon(roomId, content);
|
await client.unstable_setLiveBeacon(roomId, content);
|
||||||
|
|
||||||
// event type combined
|
// event type combined
|
||||||
const [, , path, , requestContent] = client.http.authedRequest.mock.calls[0];
|
const [, path, , requestContent] = client.http.authedRequest.mock.calls[0];
|
||||||
expect(path).toEqual(
|
expect(path).toEqual(
|
||||||
`/rooms/${encodeURIComponent(roomId)}/state/` +
|
`/rooms/${encodeURIComponent(roomId)}/state/` +
|
||||||
`${encodeURIComponent(M_BEACON_INFO.name)}/${encodeURIComponent(userId)}`,
|
`${encodeURIComponent(M_BEACON_INFO.name)}/${encodeURIComponent(userId)}`,
|
||||||
@ -1229,7 +1227,7 @@ describe("MatrixClient", function() {
|
|||||||
it("is called with plain text topic and callback and sends state event", async () => {
|
it("is called with plain text topic and callback and sends state event", async () => {
|
||||||
const sendStateEvent = createSendStateEventMock("pizza");
|
const sendStateEvent = createSendStateEventMock("pizza");
|
||||||
client.sendStateEvent = sendStateEvent;
|
client.sendStateEvent = sendStateEvent;
|
||||||
await client.setRoomTopic(roomId, "pizza", () => {});
|
await client.setRoomTopic(roomId, "pizza");
|
||||||
expect(sendStateEvent).toHaveBeenCalledTimes(1);
|
expect(sendStateEvent).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1244,15 +1242,9 @@ describe("MatrixClient", function() {
|
|||||||
describe("setPassword", () => {
|
describe("setPassword", () => {
|
||||||
const auth = { session: 'abcdef', type: 'foo' };
|
const auth = { session: 'abcdef', type: 'foo' };
|
||||||
const newPassword = 'newpassword';
|
const newPassword = 'newpassword';
|
||||||
const callback = () => {};
|
|
||||||
|
|
||||||
const passwordTest = (expectedRequestContent: any, expectedCallback?: Function) => {
|
const passwordTest = (expectedRequestContent: any) => {
|
||||||
const [callback, method, path, queryParams, requestContent] = client.http.authedRequest.mock.calls[0];
|
const [method, path, queryParams, requestContent] = client.http.authedRequest.mock.calls[0];
|
||||||
if (expectedCallback) {
|
|
||||||
expect(callback).toBe(expectedCallback);
|
|
||||||
} else {
|
|
||||||
expect(callback).toBeFalsy();
|
|
||||||
}
|
|
||||||
expect(method).toBe('POST');
|
expect(method).toBe('POST');
|
||||||
expect(path).toEqual('/account/password');
|
expect(path).toEqual('/account/password');
|
||||||
expect(queryParams).toBeFalsy();
|
expect(queryParams).toBeFalsy();
|
||||||
@ -1269,8 +1261,8 @@ describe("MatrixClient", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("no logout_devices specified + callback", async () => {
|
it("no logout_devices specified + callback", async () => {
|
||||||
await client.setPassword(auth, newPassword, callback);
|
await client.setPassword(auth, newPassword);
|
||||||
passwordTest({ auth, new_password: newPassword }, callback);
|
passwordTest({ auth, new_password: newPassword });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("overload logoutDevices=true", async () => {
|
it("overload logoutDevices=true", async () => {
|
||||||
@ -1279,8 +1271,8 @@ describe("MatrixClient", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("overload logoutDevices=true + callback", async () => {
|
it("overload logoutDevices=true + callback", async () => {
|
||||||
await client.setPassword(auth, newPassword, true, callback);
|
await client.setPassword(auth, newPassword, true);
|
||||||
passwordTest({ auth, new_password: newPassword, logout_devices: true }, callback);
|
passwordTest({ auth, new_password: newPassword, logout_devices: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("overload logoutDevices=false", async () => {
|
it("overload logoutDevices=false", async () => {
|
||||||
@ -1289,8 +1281,8 @@ describe("MatrixClient", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("overload logoutDevices=false + callback", async () => {
|
it("overload logoutDevices=false + callback", async () => {
|
||||||
await client.setPassword(auth, newPassword, false, callback);
|
await client.setPassword(auth, newPassword, false);
|
||||||
passwordTest({ auth, new_password: newPassword, logout_devices: false }, callback);
|
passwordTest({ auth, new_password: newPassword, logout_devices: false });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1305,8 +1297,7 @@ describe("MatrixClient", function() {
|
|||||||
const result = await client.getLocalAliases(roomId);
|
const result = await client.getLocalAliases(roomId);
|
||||||
|
|
||||||
// Current version of the endpoint we support is v3
|
// Current version of the endpoint we support is v3
|
||||||
const [callback, method, path, queryParams, data, opts] = client.http.authedRequest.mock.calls[0];
|
const [method, path, queryParams, data, opts] = client.http.authedRequest.mock.calls[0];
|
||||||
expect(callback).toBeFalsy();
|
|
||||||
expect(data).toBeFalsy();
|
expect(data).toBeFalsy();
|
||||||
expect(method).toBe('GET');
|
expect(method).toBe('GET');
|
||||||
expect(path).toEqual(`/rooms/${encodeURIComponent(roomId)}/aliases`);
|
expect(path).toEqual(`/rooms/${encodeURIComponent(roomId)}/aliases`);
|
||||||
|
@ -890,9 +890,8 @@ describe("MSC3089TreeSpace", () => {
|
|||||||
expect(contents.length).toEqual(fileContents.length);
|
expect(contents.length).toEqual(fileContents.length);
|
||||||
expect(opts).toMatchObject({
|
expect(opts).toMatchObject({
|
||||||
includeFilename: false,
|
includeFilename: false,
|
||||||
onlyContentUri: true, // because the tests rely on this - we shouldn't really be testing for this.
|
|
||||||
});
|
});
|
||||||
return Promise.resolve(mxc);
|
return Promise.resolve({ content_uri: mxc });
|
||||||
});
|
});
|
||||||
client.uploadContent = uploadFn;
|
client.uploadContent = uploadFn;
|
||||||
|
|
||||||
@ -950,9 +949,8 @@ describe("MSC3089TreeSpace", () => {
|
|||||||
expect(contents.length).toEqual(fileContents.length);
|
expect(contents.length).toEqual(fileContents.length);
|
||||||
expect(opts).toMatchObject({
|
expect(opts).toMatchObject({
|
||||||
includeFilename: false,
|
includeFilename: false,
|
||||||
onlyContentUri: true, // because the tests rely on this - we shouldn't really be testing for this.
|
|
||||||
});
|
});
|
||||||
return Promise.resolve(mxc);
|
return Promise.resolve({ content_uri: mxc });
|
||||||
});
|
});
|
||||||
client.uploadContent = uploadFn;
|
client.uploadContent = uploadFn;
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import MockHttpBackend from 'matrix-mock-request';
|
import MockHttpBackend from 'matrix-mock-request';
|
||||||
|
|
||||||
import { IHttpOpts, MatrixClient, PUSHER_ENABLED } from "../../src/matrix";
|
import { MatrixClient, PUSHER_ENABLED } from "../../src/matrix";
|
||||||
import { mkPusher } from '../test-utils/test-utils';
|
import { mkPusher } from '../test-utils/test-utils';
|
||||||
|
|
||||||
const realSetTimeout = setTimeout;
|
const realSetTimeout = setTimeout;
|
||||||
@ -35,7 +35,7 @@ describe("Pushers", () => {
|
|||||||
client = new MatrixClient({
|
client = new MatrixClient({
|
||||||
baseUrl: "https://my.home.server",
|
baseUrl: "https://my.home.server",
|
||||||
accessToken: "my.access.token",
|
accessToken: "my.access.token",
|
||||||
request: httpBackend.requestFn as unknown as IHttpOpts["request"],
|
fetchFn: httpBackend.fetchFn as typeof global.fetch,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
import MockHttpBackend from 'matrix-mock-request';
|
import MockHttpBackend from 'matrix-mock-request';
|
||||||
import { indexedDB as fakeIndexedDB } from 'fake-indexeddb';
|
import { indexedDB as fakeIndexedDB } from 'fake-indexeddb';
|
||||||
|
|
||||||
import { IHttpOpts, IndexedDBStore, MatrixEvent, MemoryStore, Room } from "../../src";
|
import { IndexedDBStore, MatrixEvent, MemoryStore, Room } from "../../src";
|
||||||
import { MatrixClient } from "../../src/client";
|
import { MatrixClient } from "../../src/client";
|
||||||
import { ToDeviceBatch } from '../../src/models/ToDeviceMessage';
|
import { ToDeviceBatch } from '../../src/models/ToDeviceMessage';
|
||||||
import { logger } from '../../src/logger';
|
import { logger } from '../../src/logger';
|
||||||
@ -89,7 +89,7 @@ describe.each([
|
|||||||
client = new MatrixClient({
|
client = new MatrixClient({
|
||||||
baseUrl: "https://my.home.server",
|
baseUrl: "https://my.home.server",
|
||||||
accessToken: "my.access.token",
|
accessToken: "my.access.token",
|
||||||
request: httpBackend.requestFn as IHttpOpts["request"],
|
fetchFn: httpBackend.fetchFn as typeof global.fetch,
|
||||||
store,
|
store,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -18,7 +18,6 @@ import MockHttpBackend from 'matrix-mock-request';
|
|||||||
|
|
||||||
import { ReceiptType } from '../../src/@types/read_receipts';
|
import { ReceiptType } from '../../src/@types/read_receipts';
|
||||||
import { MatrixClient } from "../../src/client";
|
import { MatrixClient } from "../../src/client";
|
||||||
import { IHttpOpts } from '../../src/http-api';
|
|
||||||
import { EventType } from '../../src/matrix';
|
import { EventType } from '../../src/matrix';
|
||||||
import { MAIN_ROOM_TIMELINE } from '../../src/models/read-receipt';
|
import { MAIN_ROOM_TIMELINE } from '../../src/models/read-receipt';
|
||||||
import { encodeUri } from '../../src/utils';
|
import { encodeUri } from '../../src/utils';
|
||||||
@ -87,7 +86,7 @@ describe("Read receipt", () => {
|
|||||||
client = new MatrixClient({
|
client = new MatrixClient({
|
||||||
baseUrl: "https://my.home.server",
|
baseUrl: "https://my.home.server",
|
||||||
accessToken: "my.access.token",
|
accessToken: "my.access.token",
|
||||||
request: httpBackend.requestFn as unknown as IHttpOpts["request"],
|
fetchFn: httpBackend.fetchFn as typeof global.fetch,
|
||||||
});
|
});
|
||||||
client.isGuest = () => false;
|
client.isGuest = () => false;
|
||||||
});
|
});
|
||||||
@ -146,5 +145,23 @@ describe("Read receipt", () => {
|
|||||||
await httpBackend.flushAllExpected();
|
await httpBackend.flushAllExpected();
|
||||||
await flushPromises();
|
await flushPromises();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("sends a valid room read receipt even when body omitted", async () => {
|
||||||
|
httpBackend.when(
|
||||||
|
"POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
|
||||||
|
$roomId: ROOM_ID,
|
||||||
|
$receiptType: ReceiptType.Read,
|
||||||
|
$eventId: threadEvent.getId(),
|
||||||
|
}),
|
||||||
|
).check((request) => {
|
||||||
|
expect(request.data).toEqual({});
|
||||||
|
}).respond(200, {});
|
||||||
|
|
||||||
|
mockServerSideSupport(client, false);
|
||||||
|
client.sendReceipt(threadEvent, ReceiptType.Read, undefined);
|
||||||
|
|
||||||
|
await httpBackend.flushAllExpected();
|
||||||
|
await flushPromises();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -26,9 +26,7 @@ describe("utils", function() {
|
|||||||
foo: "bar",
|
foo: "bar",
|
||||||
baz: "beer@",
|
baz: "beer@",
|
||||||
};
|
};
|
||||||
expect(utils.encodeParams(params)).toEqual(
|
expect(utils.encodeParams(params).toString()).toEqual("foo=bar&baz=beer%40");
|
||||||
"foo=bar&baz=beer%40",
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle boolean and numeric values", function() {
|
it("should handle boolean and numeric values", function() {
|
||||||
@ -37,7 +35,24 @@ describe("utils", function() {
|
|||||||
number: 12345,
|
number: 12345,
|
||||||
boolean: false,
|
boolean: false,
|
||||||
};
|
};
|
||||||
expect(utils.encodeParams(params)).toEqual("string=foobar&number=12345&boolean=false");
|
expect(utils.encodeParams(params).toString()).toEqual("string=foobar&number=12345&boolean=false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle string arrays", () => {
|
||||||
|
const params = {
|
||||||
|
via: ["one", "two", "three"],
|
||||||
|
};
|
||||||
|
expect(utils.encodeParams(params).toString()).toEqual("via=one&via=two&via=three");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("decodeParams", () => {
|
||||||
|
it("should be able to decode multiple values into an array", () => {
|
||||||
|
const params = "foo=bar&via=a&via=b&via=c";
|
||||||
|
expect(utils.decodeParams(params)).toEqual({
|
||||||
|
foo: "bar",
|
||||||
|
via: ["a", "b", "c"],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
2
src/@types/global.d.ts
vendored
2
src/@types/global.d.ts
vendored
@ -30,6 +30,8 @@ declare global {
|
|||||||
namespace NodeJS {
|
namespace NodeJS {
|
||||||
interface Global {
|
interface Global {
|
||||||
localStorage: Storage;
|
localStorage: Storage;
|
||||||
|
// marker variable used to detect both the browser & node entrypoints being used at once
|
||||||
|
__js_sdk_entrypoint: unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,11 +40,6 @@ export enum Preset {
|
|||||||
|
|
||||||
export type ResizeMethod = "crop" | "scale";
|
export type ResizeMethod = "crop" | "scale";
|
||||||
|
|
||||||
// TODO move to http-api after TSification
|
|
||||||
export interface IAbortablePromise<T> extends Promise<T> {
|
|
||||||
abort(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type IdServerUnbindResult = "no-support" | "success";
|
export type IdServerUnbindResult = "no-support" | "success";
|
||||||
|
|
||||||
// Knock and private are reserved keywords which are not yet implemented.
|
// Knock and private are reserved keywords which are not yet implemented.
|
||||||
|
@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Callback } from "../client";
|
|
||||||
import { IContent, IEvent } from "../models/event";
|
import { IContent, IEvent } from "../models/event";
|
||||||
import { Preset, Visibility } from "./partials";
|
import { Preset, Visibility } from "./partials";
|
||||||
import { IEventWithRoomId, SearchKey } from "./search";
|
import { IEventWithRoomId, SearchKey } from "./search";
|
||||||
@ -130,16 +129,6 @@ export interface IRoomDirectoryOptions {
|
|||||||
third_party_instance_id?: string;
|
third_party_instance_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUploadOpts {
|
|
||||||
name?: string;
|
|
||||||
includeFilename?: boolean;
|
|
||||||
type?: string;
|
|
||||||
rawResponse?: boolean;
|
|
||||||
onlyContentUri?: boolean;
|
|
||||||
callback?: Callback;
|
|
||||||
progressHandler?: (state: {loaded: number, total: number}) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAddThreePidOnlyBody {
|
export interface IAddThreePidOnlyBody {
|
||||||
auth?: {
|
auth?: {
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -28,7 +28,7 @@ const MAX_BATCH_SIZE = 20;
|
|||||||
export class ToDeviceMessageQueue {
|
export class ToDeviceMessageQueue {
|
||||||
private sending = false;
|
private sending = false;
|
||||||
private running = true;
|
private running = true;
|
||||||
private retryTimeout: number = null;
|
private retryTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
private retryAttempts = 0;
|
private retryAttempts = 0;
|
||||||
|
|
||||||
constructor(private client: MatrixClient) {
|
constructor(private client: MatrixClient) {
|
||||||
@ -68,7 +68,7 @@ export class ToDeviceMessageQueue {
|
|||||||
logger.debug("Attempting to send queued to-device messages");
|
logger.debug("Attempting to send queued to-device messages");
|
||||||
|
|
||||||
this.sending = true;
|
this.sending = true;
|
||||||
let headBatch;
|
let headBatch: IndexedToDeviceBatch;
|
||||||
try {
|
try {
|
||||||
while (this.running) {
|
while (this.running) {
|
||||||
headBatch = await this.client.store.getOldestToDeviceBatch();
|
headBatch = await this.client.store.getOldestToDeviceBatch();
|
||||||
@ -92,7 +92,7 @@ export class ToDeviceMessageQueue {
|
|||||||
// bored and giving up for now
|
// bored and giving up for now
|
||||||
if (Math.floor(e.httpStatus / 100) === 4) {
|
if (Math.floor(e.httpStatus / 100) === 4) {
|
||||||
logger.error("Fatal error when sending to-device message - dropping to-device batch!", e);
|
logger.error("Fatal error when sending to-device message - dropping to-device batch!", e);
|
||||||
await this.client.store.removeToDeviceBatch(headBatch.id);
|
await this.client.store.removeToDeviceBatch(headBatch!.id);
|
||||||
} else {
|
} else {
|
||||||
logger.info("Automatic retry limit reached for to-device messages.");
|
logger.info("Automatic retry limit reached for to-device messages.");
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,9 @@ limitations under the License.
|
|||||||
|
|
||||||
/** @module auto-discovery */
|
/** @module auto-discovery */
|
||||||
|
|
||||||
import { ServerResponse } from "http";
|
|
||||||
|
|
||||||
import { IClientWellKnown, IWellKnownConfig } from "./client";
|
import { IClientWellKnown, IWellKnownConfig } from "./client";
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
|
import { MatrixError, Method, timeoutSignal } from "./http-api";
|
||||||
|
|
||||||
// Dev note: Auto discovery is part of the spec.
|
// Dev note: Auto discovery is part of the spec.
|
||||||
// See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
// See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||||
@ -395,6 +394,19 @@ export class AutoDiscovery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static fetch(resource: URL | string, options?: RequestInit): ReturnType<typeof global.fetch> {
|
||||||
|
if (this.fetchFn) {
|
||||||
|
return this.fetchFn(resource, options);
|
||||||
|
}
|
||||||
|
return global.fetch(resource, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static fetchFn?: typeof global.fetch;
|
||||||
|
|
||||||
|
public static setFetchFn(fetchFn: typeof global.fetch): void {
|
||||||
|
AutoDiscovery.fetchFn = fetchFn;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches a JSON object from a given URL, as expected by all .well-known
|
* Fetches a JSON object from a given URL, as expected by all .well-known
|
||||||
* related lookups. If the server gives a 404 then the `action` will be
|
* related lookups. If the server gives a 404 then the `action` will be
|
||||||
@ -411,45 +423,55 @@ export class AutoDiscovery {
|
|||||||
* @return {Promise<object>} Resolves to the returned state.
|
* @return {Promise<object>} Resolves to the returned state.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private static fetchWellKnownObject(uri: string): Promise<IWellKnownConfig> {
|
private static async fetchWellKnownObject(url: string): Promise<IWellKnownConfig> {
|
||||||
return new Promise((resolve) => {
|
let response: Response;
|
||||||
// eslint-disable-next-line
|
|
||||||
const request = require("./matrix").getRequest();
|
|
||||||
if (!request) throw new Error("No request library available");
|
|
||||||
request(
|
|
||||||
{ method: "GET", uri, timeout: 5000 },
|
|
||||||
(error: Error, response: ServerResponse, body: string) => {
|
|
||||||
if (error || response?.statusCode < 200 || response?.statusCode >= 300) {
|
|
||||||
const result = { error, raw: {} };
|
|
||||||
return resolve(response?.statusCode === 404
|
|
||||||
? {
|
|
||||||
...result,
|
|
||||||
action: AutoDiscoveryAction.IGNORE,
|
|
||||||
reason: AutoDiscovery.ERROR_MISSING_WELLKNOWN,
|
|
||||||
} : {
|
|
||||||
...result,
|
|
||||||
action: AutoDiscoveryAction.FAIL_PROMPT,
|
|
||||||
reason: error?.message || "General failure",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return resolve({
|
response = await AutoDiscovery.fetch(url, {
|
||||||
raw: JSON.parse(body),
|
method: Method.Get,
|
||||||
action: AutoDiscoveryAction.SUCCESS,
|
signal: timeoutSignal(5000),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
return resolve({
|
if (response.status === 404) {
|
||||||
error: err,
|
return {
|
||||||
raw: {},
|
raw: {},
|
||||||
action: AutoDiscoveryAction.FAIL_PROMPT,
|
action: AutoDiscoveryAction.IGNORE,
|
||||||
reason: err?.name === "SyntaxError"
|
reason: AutoDiscovery.ERROR_MISSING_WELLKNOWN,
|
||||||
? AutoDiscovery.ERROR_INVALID_JSON
|
};
|
||||||
: AutoDiscovery.ERROR_INVALID,
|
}
|
||||||
});
|
|
||||||
}
|
if (!response.ok) {
|
||||||
},
|
return {
|
||||||
);
|
raw: {},
|
||||||
});
|
action: AutoDiscoveryAction.FAIL_PROMPT,
|
||||||
|
reason: "General failure",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as Error | string | undefined;
|
||||||
|
return {
|
||||||
|
error,
|
||||||
|
raw: {},
|
||||||
|
action: AutoDiscoveryAction.FAIL_PROMPT,
|
||||||
|
reason: (<Error>error)?.message || "General failure",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
raw: await response.json(),
|
||||||
|
action: AutoDiscoveryAction.SUCCESS,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as Error | string | undefined;
|
||||||
|
return {
|
||||||
|
error,
|
||||||
|
raw: {},
|
||||||
|
action: AutoDiscoveryAction.FAIL_PROMPT,
|
||||||
|
reason: (error as MatrixError)?.name === "SyntaxError"
|
||||||
|
? AutoDiscovery.ERROR_INVALID_JSON
|
||||||
|
: AutoDiscovery.ERROR_INVALID,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,25 +14,12 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import request from "browser-request";
|
|
||||||
import queryString from "qs";
|
|
||||||
|
|
||||||
import * as matrixcs from "./matrix";
|
import * as matrixcs from "./matrix";
|
||||||
|
|
||||||
if (matrixcs.getRequest()) {
|
if (global.__js_sdk_entrypoint) {
|
||||||
throw new Error("Multiple matrix-js-sdk entrypoints detected!");
|
throw new Error("Multiple matrix-js-sdk entrypoints detected!");
|
||||||
}
|
}
|
||||||
|
global.__js_sdk_entrypoint = true;
|
||||||
matrixcs.request(function(opts, fn) {
|
|
||||||
// We manually fix the query string for browser-request because
|
|
||||||
// it doesn't correctly handle cases like ?via=one&via=two. Instead
|
|
||||||
// we mimic `request`'s query string interface to make it all work
|
|
||||||
// as expected.
|
|
||||||
// browser-request will happily take the constructed string as the
|
|
||||||
// query string without trying to modify it further.
|
|
||||||
opts.qs = queryString.stringify(opts.qs || {}, opts.qsStringifyOptions);
|
|
||||||
return request(opts, fn);
|
|
||||||
});
|
|
||||||
|
|
||||||
// just *accessing* indexedDB throws an exception in firefox with
|
// just *accessing* indexedDB throws an exception in firefox with
|
||||||
// indexeddb disabled.
|
// indexeddb disabled.
|
||||||
|
1039
src/client.ts
1039
src/client.ts
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,7 @@ import { logger } from "../logger";
|
|||||||
import { MatrixEvent } from "../models/event";
|
import { MatrixEvent } from "../models/event";
|
||||||
import { createCryptoStoreCacheCallbacks, ICacheCallbacks } from "./CrossSigning";
|
import { createCryptoStoreCacheCallbacks, ICacheCallbacks } from "./CrossSigning";
|
||||||
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
|
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
|
||||||
import { Method, PREFIX_V3 } from "../http-api";
|
import { Method, ClientPrefix } from "../http-api";
|
||||||
import { Crypto, IBootstrapCrossSigningOpts } from "./index";
|
import { Crypto, IBootstrapCrossSigningOpts } from "./index";
|
||||||
import {
|
import {
|
||||||
ClientEvent,
|
ClientEvent,
|
||||||
@ -241,19 +241,19 @@ export class EncryptionSetupOperation {
|
|||||||
// Sign the backup with the cross signing key so the key backup can
|
// Sign the backup with the cross signing key so the key backup can
|
||||||
// be trusted via cross-signing.
|
// be trusted via cross-signing.
|
||||||
await baseApis.http.authedRequest(
|
await baseApis.http.authedRequest(
|
||||||
undefined, Method.Put, "/room_keys/version/" + this.keyBackupInfo.version,
|
Method.Put, "/room_keys/version/" + this.keyBackupInfo.version,
|
||||||
undefined, {
|
undefined, {
|
||||||
algorithm: this.keyBackupInfo.algorithm,
|
algorithm: this.keyBackupInfo.algorithm,
|
||||||
auth_data: this.keyBackupInfo.auth_data,
|
auth_data: this.keyBackupInfo.auth_data,
|
||||||
},
|
},
|
||||||
{ prefix: PREFIX_V3 },
|
{ prefix: ClientPrefix.V3 },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// add new key backup
|
// add new key backup
|
||||||
await baseApis.http.authedRequest(
|
await baseApis.http.authedRequest(
|
||||||
undefined, Method.Post, "/room_keys/version",
|
Method.Post, "/room_keys/version",
|
||||||
undefined, this.keyBackupInfo,
|
undefined, this.keyBackupInfo,
|
||||||
{ prefix: PREFIX_V3 },
|
{ prefix: ClientPrefix.V3 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,7 +210,6 @@ export class DehydrationManager {
|
|||||||
logger.log("Uploading account to server");
|
logger.log("Uploading account to server");
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
const dehydrateResult = await this.crypto.baseApis.http.authedRequest<{ device_id: string }>(
|
const dehydrateResult = await this.crypto.baseApis.http.authedRequest<{ device_id: string }>(
|
||||||
undefined,
|
|
||||||
Method.Put,
|
Method.Put,
|
||||||
"/dehydrated_device",
|
"/dehydrated_device",
|
||||||
undefined,
|
undefined,
|
||||||
@ -273,7 +272,6 @@ export class DehydrationManager {
|
|||||||
|
|
||||||
logger.log("Uploading keys to server");
|
logger.log("Uploading keys to server");
|
||||||
await this.crypto.baseApis.http.authedRequest(
|
await this.crypto.baseApis.http.authedRequest(
|
||||||
undefined,
|
|
||||||
Method.Post,
|
Method.Post,
|
||||||
"/keys/upload/" + encodeURI(deviceId),
|
"/keys/upload/" + encodeURI(deviceId),
|
||||||
undefined,
|
undefined,
|
||||||
|
1140
src/http-api.ts
1140
src/http-api.ts
File diff suppressed because it is too large
Load Diff
64
src/http-api/errors.ts
Normal file
64
src/http-api/errors.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
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 { IUsageLimit } from "../@types/partials";
|
||||||
|
|
||||||
|
interface IErrorJson extends Partial<IUsageLimit> {
|
||||||
|
[key: string]: any; // extensible
|
||||||
|
errcode?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a Matrix error. This is a JavaScript Error with additional
|
||||||
|
* information specific to the standard Matrix error response.
|
||||||
|
* @constructor
|
||||||
|
* @param {Object} errorJson The Matrix error JSON returned from the homeserver.
|
||||||
|
* @prop {string} errcode The Matrix 'errcode' value, e.g. "M_FORBIDDEN".
|
||||||
|
* @prop {string} name Same as MatrixError.errcode but with a default unknown string.
|
||||||
|
* @prop {string} message The Matrix 'error' value, e.g. "Missing token."
|
||||||
|
* @prop {Object} data The raw Matrix error JSON used to construct this object.
|
||||||
|
* @prop {number} httpStatus The numeric HTTP status code given
|
||||||
|
*/
|
||||||
|
export class MatrixError extends Error {
|
||||||
|
public readonly errcode?: string;
|
||||||
|
public readonly data: IErrorJson;
|
||||||
|
|
||||||
|
constructor(errorJson: IErrorJson = {}, public httpStatus?: number) {
|
||||||
|
super(`MatrixError: ${errorJson.errcode}`);
|
||||||
|
this.errcode = errorJson.errcode;
|
||||||
|
this.name = errorJson.errcode || "Unknown error code";
|
||||||
|
this.message = errorJson.error || "Unknown message";
|
||||||
|
this.data = errorJson;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a ConnectionError. This is a JavaScript Error indicating
|
||||||
|
* that a request failed because of some error with the connection, either
|
||||||
|
* CORS was not correctly configured on the server, the server didn't response,
|
||||||
|
* the request timed out, or the internet connection on the client side went down.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
export class ConnectionError extends Error {
|
||||||
|
constructor(message: string, cause?: Error) {
|
||||||
|
super(message + (cause ? `: ${cause.message}` : ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return "ConnectionError";
|
||||||
|
}
|
||||||
|
}
|
327
src/http-api/fetch.ts
Normal file
327
src/http-api/fetch.ts
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an internal module. See {@link MatrixHttpApi} for the public class.
|
||||||
|
* @module http-api
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as utils from "../utils";
|
||||||
|
import { TypedEventEmitter } from "../models/typed-event-emitter";
|
||||||
|
import { Method } from "./method";
|
||||||
|
import { ConnectionError, MatrixError } from "./errors";
|
||||||
|
import { HttpApiEvent, HttpApiEventHandlerMap, IHttpOpts, IRequestOpts } from "./interface";
|
||||||
|
import { anySignal, parseErrorResponse, timeoutSignal } from "./utils";
|
||||||
|
import { QueryDict } from "../utils";
|
||||||
|
|
||||||
|
type Body = Record<string, any> | BodyInit;
|
||||||
|
|
||||||
|
interface TypedResponse<T> extends Response {
|
||||||
|
json(): Promise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ResponseType<T, O extends IHttpOpts> =
|
||||||
|
O extends undefined ? T :
|
||||||
|
O extends { onlyData: true } ? T :
|
||||||
|
TypedResponse<T>;
|
||||||
|
|
||||||
|
export class FetchHttpApi<O extends IHttpOpts> {
|
||||||
|
private abortController = new AbortController();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private eventEmitter: TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>,
|
||||||
|
public readonly opts: O,
|
||||||
|
) {
|
||||||
|
utils.checkObjectHasKeys(opts, ["baseUrl", "prefix"]);
|
||||||
|
opts.onlyData = !!opts.onlyData;
|
||||||
|
opts.useAuthorizationHeader = opts.useAuthorizationHeader ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abort(): void {
|
||||||
|
this.abortController.abort();
|
||||||
|
this.abortController = new AbortController();
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetch(resource: URL | string, options?: RequestInit): ReturnType<typeof global.fetch> {
|
||||||
|
if (this.opts.fetchFn) {
|
||||||
|
return this.opts.fetchFn(resource, options);
|
||||||
|
}
|
||||||
|
return global.fetch(resource, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the base URL for the identity server
|
||||||
|
* @param {string} url The new base url
|
||||||
|
*/
|
||||||
|
public setIdBaseUrl(url: string): void {
|
||||||
|
this.opts.idBaseUrl = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public idServerRequest<T extends {}>(
|
||||||
|
method: Method,
|
||||||
|
path: string,
|
||||||
|
params: Record<string, string | string[]>,
|
||||||
|
prefix: string,
|
||||||
|
accessToken?: string,
|
||||||
|
): Promise<ResponseType<T, O>> {
|
||||||
|
if (!this.opts.idBaseUrl) {
|
||||||
|
throw new Error("No identity server base URL set");
|
||||||
|
}
|
||||||
|
|
||||||
|
let queryParams: QueryDict | undefined = undefined;
|
||||||
|
let body: Record<string, string | string[]> | undefined = undefined;
|
||||||
|
if (method === Method.Get) {
|
||||||
|
queryParams = params;
|
||||||
|
} else {
|
||||||
|
body = params;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullUri = this.getUrl(path, queryParams, prefix, this.opts.idBaseUrl);
|
||||||
|
|
||||||
|
const opts: IRequestOpts = {
|
||||||
|
json: true,
|
||||||
|
headers: {},
|
||||||
|
};
|
||||||
|
if (accessToken) {
|
||||||
|
opts.headers.Authorization = `Bearer ${accessToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.requestOtherUrl(method, fullUri, body, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform an authorised request to the homeserver.
|
||||||
|
* @param {string} method The HTTP method e.g. "GET".
|
||||||
|
* @param {string} path The HTTP path <b>after</b> the supplied prefix e.g.
|
||||||
|
* "/createRoom".
|
||||||
|
*
|
||||||
|
* @param {Object=} queryParams A dict of query params (these will NOT be
|
||||||
|
* urlencoded). If unspecified, there will be no query params.
|
||||||
|
*
|
||||||
|
* @param {Object} [body] The HTTP JSON body.
|
||||||
|
*
|
||||||
|
* @param {Object|Number=} opts additional options. If a number is specified,
|
||||||
|
* this is treated as `opts.localTimeoutMs`.
|
||||||
|
*
|
||||||
|
* @param {Number=} opts.localTimeoutMs The maximum amount of time to wait before
|
||||||
|
* timing out the request. If not specified, there is no timeout.
|
||||||
|
*
|
||||||
|
* @param {string=} opts.prefix The full prefix to use e.g.
|
||||||
|
* "/_matrix/client/v2_alpha". If not specified, uses this.opts.prefix.
|
||||||
|
*
|
||||||
|
* @param {string=} opts.baseUrl The alternative base url to use.
|
||||||
|
* If not specified, uses this.opts.baseUrl
|
||||||
|
*
|
||||||
|
* @param {Object=} opts.headers map of additional request headers
|
||||||
|
*
|
||||||
|
* @return {Promise} Resolves to <code>{data: {Object},
|
||||||
|
* headers: {Object}, code: {Number}}</code>.
|
||||||
|
* If <code>onlyData</code> is set, this will resolve to the <code>data</code>
|
||||||
|
* object only.
|
||||||
|
* @return {module:http-api.MatrixError} Rejects with an error if a problem
|
||||||
|
* occurred. This includes network problems and Matrix-specific error JSON.
|
||||||
|
*/
|
||||||
|
public authedRequest<T>(
|
||||||
|
method: Method,
|
||||||
|
path: string,
|
||||||
|
queryParams?: QueryDict,
|
||||||
|
body?: Body,
|
||||||
|
opts: IRequestOpts = {},
|
||||||
|
): Promise<ResponseType<T, O>> {
|
||||||
|
if (!queryParams) queryParams = {};
|
||||||
|
|
||||||
|
if (this.opts.useAuthorizationHeader) {
|
||||||
|
if (!opts.headers) {
|
||||||
|
opts.headers = {};
|
||||||
|
}
|
||||||
|
if (!opts.headers.Authorization) {
|
||||||
|
opts.headers.Authorization = "Bearer " + this.opts.accessToken;
|
||||||
|
}
|
||||||
|
if (queryParams.access_token) {
|
||||||
|
delete queryParams.access_token;
|
||||||
|
}
|
||||||
|
} else if (!queryParams.access_token) {
|
||||||
|
queryParams.access_token = this.opts.accessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestPromise = this.request<T>(method, path, queryParams, body, opts);
|
||||||
|
|
||||||
|
requestPromise.catch((err: MatrixError) => {
|
||||||
|
if (err.errcode == 'M_UNKNOWN_TOKEN' && !opts?.inhibitLogoutEmit) {
|
||||||
|
this.eventEmitter.emit(HttpApiEvent.SessionLoggedOut, err);
|
||||||
|
} else if (err.errcode == 'M_CONSENT_NOT_GIVEN') {
|
||||||
|
this.eventEmitter.emit(HttpApiEvent.NoConsent, err.message, err.data.consent_uri);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// return the original promise, otherwise tests break due to it having to
|
||||||
|
// go around the event loop one more time to process the result of the request
|
||||||
|
return requestPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a request to the homeserver without any credentials.
|
||||||
|
* @param {string} method The HTTP method e.g. "GET".
|
||||||
|
* @param {string} path The HTTP path <b>after</b> the supplied prefix e.g.
|
||||||
|
* "/createRoom".
|
||||||
|
*
|
||||||
|
* @param {Object=} queryParams A dict of query params (these will NOT be
|
||||||
|
* urlencoded). If unspecified, there will be no query params.
|
||||||
|
*
|
||||||
|
* @param {Object} [body] The HTTP JSON body.
|
||||||
|
*
|
||||||
|
* @param {Object=} opts additional options
|
||||||
|
*
|
||||||
|
* @param {Number=} opts.localTimeoutMs The maximum amount of time to wait before
|
||||||
|
* timing out the request. If not specified, there is no timeout.
|
||||||
|
*
|
||||||
|
* @param {string=} opts.prefix The full prefix to use e.g.
|
||||||
|
* "/_matrix/client/v2_alpha". If not specified, uses this.opts.prefix.
|
||||||
|
*
|
||||||
|
* @param {Object=} opts.headers map of additional request headers
|
||||||
|
*
|
||||||
|
* @return {Promise} Resolves to <code>{data: {Object},
|
||||||
|
* headers: {Object}, code: {Number}}</code>.
|
||||||
|
* If <code>onlyData</code> is set, this will resolve to the <code>data</code>
|
||||||
|
* object only.
|
||||||
|
* @return {module:http-api.MatrixError} Rejects with an error if a problem
|
||||||
|
* occurred. This includes network problems and Matrix-specific error JSON.
|
||||||
|
*/
|
||||||
|
public request<T>(
|
||||||
|
method: Method,
|
||||||
|
path: string,
|
||||||
|
queryParams?: QueryDict,
|
||||||
|
body?: Body,
|
||||||
|
opts?: IRequestOpts,
|
||||||
|
): Promise<ResponseType<T, O>> {
|
||||||
|
const fullUri = this.getUrl(path, queryParams, opts?.prefix, opts?.baseUrl);
|
||||||
|
return this.requestOtherUrl<T>(method, fullUri, body, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a request to an arbitrary URL.
|
||||||
|
* @param {string} method The HTTP method e.g. "GET".
|
||||||
|
* @param {string} url The HTTP URL object.
|
||||||
|
*
|
||||||
|
* @param {Object} [body] The HTTP JSON body.
|
||||||
|
*
|
||||||
|
* @param {Object=} opts additional options
|
||||||
|
*
|
||||||
|
* @param {Number=} opts.localTimeoutMs The maximum amount of time to wait before
|
||||||
|
* timing out the request. If not specified, there is no timeout.
|
||||||
|
*
|
||||||
|
* @param {Object=} opts.headers map of additional request headers
|
||||||
|
*
|
||||||
|
* @return {Promise} Resolves to data unless `onlyData` is specified as false,
|
||||||
|
* where the resolved value will be a fetch Response object.
|
||||||
|
* @return {module:http-api.MatrixError} Rejects with an error if a problem
|
||||||
|
* occurred. This includes network problems and Matrix-specific error JSON.
|
||||||
|
*/
|
||||||
|
public async requestOtherUrl<T>(
|
||||||
|
method: Method,
|
||||||
|
url: URL | string,
|
||||||
|
body?: Body,
|
||||||
|
opts: Pick<IRequestOpts, "headers" | "json" | "localTimeoutMs" | "abortSignal"> = {},
|
||||||
|
): Promise<ResponseType<T, O>> {
|
||||||
|
const headers = Object.assign({}, opts.headers || {});
|
||||||
|
const json = opts.json ?? true;
|
||||||
|
// We can't use getPrototypeOf here as objects made in other contexts e.g. over postMessage won't have same ref
|
||||||
|
const jsonBody = json && body?.constructor?.name === Object.name;
|
||||||
|
|
||||||
|
if (json) {
|
||||||
|
if (jsonBody && !headers["Content-Type"]) {
|
||||||
|
headers["Content-Type"] = "application/json";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!headers["Accept"]) {
|
||||||
|
headers["Accept"] = "application/json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeout = opts.localTimeoutMs ?? this.opts.localTimeoutMs;
|
||||||
|
const signals = [
|
||||||
|
this.abortController.signal,
|
||||||
|
];
|
||||||
|
if (timeout !== undefined) {
|
||||||
|
signals.push(timeoutSignal(timeout));
|
||||||
|
}
|
||||||
|
if (opts.abortSignal) {
|
||||||
|
signals.push(opts.abortSignal);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: BodyInit;
|
||||||
|
if (jsonBody) {
|
||||||
|
data = JSON.stringify(body);
|
||||||
|
} else {
|
||||||
|
data = body as BodyInit;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { signal, cleanup } = anySignal(signals);
|
||||||
|
|
||||||
|
let res: Response;
|
||||||
|
try {
|
||||||
|
res = await this.fetch(url, {
|
||||||
|
signal,
|
||||||
|
method,
|
||||||
|
body: data,
|
||||||
|
headers,
|
||||||
|
mode: "cors",
|
||||||
|
redirect: "follow",
|
||||||
|
referrer: "",
|
||||||
|
referrerPolicy: "no-referrer",
|
||||||
|
cache: "no-cache",
|
||||||
|
credentials: "omit", // we send credentials via headers
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e.name === "AbortError") {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
throw new ConnectionError("fetch failed", e);
|
||||||
|
} finally {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw parseErrorResponse(res, await res.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.opts.onlyData) {
|
||||||
|
return json ? res.json() : res.text();
|
||||||
|
}
|
||||||
|
return res as ResponseType<T, O>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form and return a homeserver request URL based on the given path params and prefix.
|
||||||
|
* @param {string} path The HTTP path <b>after</b> the supplied prefix e.g. "/createRoom".
|
||||||
|
* @param {Object} queryParams A dict of query params (these will NOT be urlencoded).
|
||||||
|
* @param {string} prefix The full prefix to use e.g. "/_matrix/client/v2_alpha", defaulting to this.opts.prefix.
|
||||||
|
* @param {string} baseUrl The baseUrl to use e.g. "https://matrix.org/", defaulting to this.opts.baseUrl.
|
||||||
|
* @return {string} URL
|
||||||
|
*/
|
||||||
|
public getUrl(
|
||||||
|
path: string,
|
||||||
|
queryParams?: QueryDict,
|
||||||
|
prefix?: string,
|
||||||
|
baseUrl?: string,
|
||||||
|
): URL {
|
||||||
|
const url = new URL((baseUrl ?? this.opts.baseUrl) + (prefix ?? this.opts.prefix) + path);
|
||||||
|
if (queryParams) {
|
||||||
|
utils.encodeParams(queryParams, url.searchParams);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
216
src/http-api/index.ts
Normal file
216
src/http-api/index.ts
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
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 { FetchHttpApi } from "./fetch";
|
||||||
|
import { FileType, IContentUri, IHttpOpts, Upload, UploadOpts, UploadResponse } from "./interface";
|
||||||
|
import { MediaPrefix } from "./prefix";
|
||||||
|
import * as utils from "../utils";
|
||||||
|
import * as callbacks from "../realtime-callbacks";
|
||||||
|
import { Method } from "./method";
|
||||||
|
import { ConnectionError, MatrixError } from "./errors";
|
||||||
|
import { parseErrorResponse } from "./utils";
|
||||||
|
|
||||||
|
export * from "./interface";
|
||||||
|
export * from "./prefix";
|
||||||
|
export * from "./errors";
|
||||||
|
export * from "./method";
|
||||||
|
export * from "./utils";
|
||||||
|
|
||||||
|
export class MatrixHttpApi<O extends IHttpOpts> extends FetchHttpApi<O> {
|
||||||
|
private uploads: Upload[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload content to the homeserver
|
||||||
|
*
|
||||||
|
* @param {object} file The object to upload. On a browser, something that
|
||||||
|
* can be sent to XMLHttpRequest.send (typically a File). Under node.js,
|
||||||
|
* a Buffer, String or ReadStream.
|
||||||
|
*
|
||||||
|
* @param {object} opts options object
|
||||||
|
*
|
||||||
|
* @param {string=} opts.name Name to give the file on the server. Defaults
|
||||||
|
* to <tt>file.name</tt>.
|
||||||
|
*
|
||||||
|
* @param {boolean=} opts.includeFilename if false will not send the filename,
|
||||||
|
* e.g for encrypted file uploads where filename leaks are undesirable.
|
||||||
|
* Defaults to true.
|
||||||
|
*
|
||||||
|
* @param {string=} opts.type Content-type for the upload. Defaults to
|
||||||
|
* <tt>file.type</tt>, or <tt>application/octet-stream</tt>.
|
||||||
|
*
|
||||||
|
* @param {boolean=} opts.rawResponse Return the raw body, rather than
|
||||||
|
* parsing the JSON. Defaults to false (except on node.js, where it
|
||||||
|
* defaults to true for backwards compatibility).
|
||||||
|
*
|
||||||
|
* @param {boolean=} opts.onlyContentUri Just return the content URI,
|
||||||
|
* rather than the whole body. Defaults to false (except on browsers,
|
||||||
|
* where it defaults to true for backwards compatibility). Ignored if
|
||||||
|
* opts.rawResponse is true.
|
||||||
|
*
|
||||||
|
* @param {Function=} opts.progressHandler Optional. Called when a chunk of
|
||||||
|
* data has been uploaded, with an object containing the fields `loaded`
|
||||||
|
* (number of bytes transferred) and `total` (total size, if known).
|
||||||
|
*
|
||||||
|
* @return {Promise} Resolves to response object, as
|
||||||
|
* determined by this.opts.onlyData, opts.rawResponse, and
|
||||||
|
* opts.onlyContentUri. Rejects with an error (usually a MatrixError).
|
||||||
|
*/
|
||||||
|
public uploadContent(file: FileType, opts: UploadOpts = {}): Promise<UploadResponse> {
|
||||||
|
const includeFilename = opts.includeFilename ?? true;
|
||||||
|
const abortController = opts.abortController ?? new AbortController();
|
||||||
|
|
||||||
|
// If the file doesn't have a mime type, use a default since the HS errors if we don't supply one.
|
||||||
|
const contentType = opts.type ?? (file as File).type ?? 'application/octet-stream';
|
||||||
|
const fileName = opts.name ?? (file as File).name;
|
||||||
|
|
||||||
|
const upload = {
|
||||||
|
loaded: 0,
|
||||||
|
total: 0,
|
||||||
|
abortController,
|
||||||
|
} as Upload;
|
||||||
|
const defer = utils.defer<UploadResponse>();
|
||||||
|
|
||||||
|
if (global.XMLHttpRequest) {
|
||||||
|
const xhr = new global.XMLHttpRequest();
|
||||||
|
|
||||||
|
const timeoutFn = function() {
|
||||||
|
xhr.abort();
|
||||||
|
defer.reject(new Error("Timeout"));
|
||||||
|
};
|
||||||
|
|
||||||
|
// set an initial timeout of 30s; we'll advance it each time we get a progress notification
|
||||||
|
let timeoutTimer = callbacks.setTimeout(timeoutFn, 30000);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
switch (xhr.readyState) {
|
||||||
|
case global.XMLHttpRequest.DONE:
|
||||||
|
callbacks.clearTimeout(timeoutTimer);
|
||||||
|
try {
|
||||||
|
if (xhr.status === 0) {
|
||||||
|
throw new DOMException(xhr.statusText, "AbortError"); // mimic fetch API
|
||||||
|
}
|
||||||
|
if (!xhr.responseText) {
|
||||||
|
throw new Error('No response body.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xhr.status >= 400) {
|
||||||
|
defer.reject(parseErrorResponse(xhr, xhr.responseText));
|
||||||
|
} else {
|
||||||
|
defer.resolve(JSON.parse(xhr.responseText));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err.name === "AbortError") {
|
||||||
|
defer.reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(<MatrixError>err).httpStatus = xhr.status;
|
||||||
|
defer.reject(new ConnectionError("request failed", err));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.upload.onprogress = (ev: ProgressEvent) => {
|
||||||
|
callbacks.clearTimeout(timeoutTimer);
|
||||||
|
upload.loaded = ev.loaded;
|
||||||
|
upload.total = ev.total;
|
||||||
|
timeoutTimer = callbacks.setTimeout(timeoutFn, 30000);
|
||||||
|
opts.progressHandler?.({
|
||||||
|
loaded: ev.loaded,
|
||||||
|
total: ev.total,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = this.getUrl("/upload", undefined, MediaPrefix.R0);
|
||||||
|
|
||||||
|
if (includeFilename && fileName) {
|
||||||
|
url.searchParams.set("filename", encodeURIComponent(fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.opts.useAuthorizationHeader && this.opts.accessToken) {
|
||||||
|
url.searchParams.set("access_token", encodeURIComponent(this.opts.accessToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.open(Method.Post, url.href);
|
||||||
|
if (this.opts.useAuthorizationHeader && this.opts.accessToken) {
|
||||||
|
xhr.setRequestHeader("Authorization", "Bearer " + this.opts.accessToken);
|
||||||
|
}
|
||||||
|
xhr.setRequestHeader("Content-Type", contentType);
|
||||||
|
xhr.send(file);
|
||||||
|
|
||||||
|
abortController.signal.addEventListener("abort", () => {
|
||||||
|
xhr.abort();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const queryParams: utils.QueryDict = {};
|
||||||
|
if (includeFilename && fileName) {
|
||||||
|
queryParams.filename = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers: Record<string, string> = { "Content-Type": contentType };
|
||||||
|
|
||||||
|
this.authedRequest<UploadResponse>(
|
||||||
|
Method.Post, "/upload", queryParams, file, {
|
||||||
|
prefix: MediaPrefix.R0,
|
||||||
|
headers,
|
||||||
|
abortSignal: abortController.signal,
|
||||||
|
},
|
||||||
|
).then(response => {
|
||||||
|
return this.opts.onlyData ? <UploadResponse>response : response.json();
|
||||||
|
}).then(defer.resolve, defer.reject);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the upload from the list on completion
|
||||||
|
upload.promise = defer.promise.finally(() => {
|
||||||
|
utils.removeElement(this.uploads, elem => elem === upload);
|
||||||
|
});
|
||||||
|
abortController.signal.addEventListener("abort", () => {
|
||||||
|
utils.removeElement(this.uploads, elem => elem === upload);
|
||||||
|
defer.reject(new DOMException("Aborted", "AbortError"));
|
||||||
|
});
|
||||||
|
this.uploads.push(upload);
|
||||||
|
return upload.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public cancelUpload(promise: Promise<UploadResponse>): boolean {
|
||||||
|
const upload = this.uploads.find(u => u.promise === promise);
|
||||||
|
if (upload) {
|
||||||
|
upload.abortController.abort();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCurrentUploads(): Upload[] {
|
||||||
|
return this.uploads;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the content repository url with query parameters.
|
||||||
|
* @return {Object} An object with a 'base', 'path' and 'params' for base URL,
|
||||||
|
* path and query parameters respectively.
|
||||||
|
*/
|
||||||
|
public getContentUri(): IContentUri {
|
||||||
|
return {
|
||||||
|
base: this.opts.baseUrl,
|
||||||
|
path: MediaPrefix.R0 + "/upload",
|
||||||
|
params: {
|
||||||
|
access_token: this.opts.accessToken,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
93
src/http-api/interface.ts
Normal file
93
src/http-api/interface.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
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 { MatrixError } from "./errors";
|
||||||
|
|
||||||
|
export interface IHttpOpts {
|
||||||
|
fetchFn?: typeof global.fetch;
|
||||||
|
|
||||||
|
baseUrl: string;
|
||||||
|
idBaseUrl?: string;
|
||||||
|
prefix: string;
|
||||||
|
extraParams?: Record<string, string>;
|
||||||
|
|
||||||
|
accessToken?: string;
|
||||||
|
useAuthorizationHeader?: boolean; // defaults to true
|
||||||
|
|
||||||
|
onlyData?: boolean;
|
||||||
|
localTimeoutMs?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRequestOpts {
|
||||||
|
baseUrl?: string;
|
||||||
|
prefix?: string;
|
||||||
|
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
abortSignal?: AbortSignal;
|
||||||
|
localTimeoutMs?: number;
|
||||||
|
json?: boolean; // defaults to true
|
||||||
|
|
||||||
|
// Set to true to prevent the request function from emitting a Session.logged_out event.
|
||||||
|
// This is intended for use on endpoints where M_UNKNOWN_TOKEN is a valid/notable error response,
|
||||||
|
// such as with token refreshes.
|
||||||
|
inhibitLogoutEmit?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IContentUri {
|
||||||
|
base: string;
|
||||||
|
path: string;
|
||||||
|
params: {
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
access_token: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum HttpApiEvent {
|
||||||
|
SessionLoggedOut = "Session.logged_out",
|
||||||
|
NoConsent = "no_consent",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HttpApiEventHandlerMap = {
|
||||||
|
[HttpApiEvent.SessionLoggedOut]: (err: MatrixError) => void;
|
||||||
|
[HttpApiEvent.NoConsent]: (message: string, consentUri: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface UploadProgress {
|
||||||
|
loaded: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UploadOpts {
|
||||||
|
name?: string;
|
||||||
|
type?: string;
|
||||||
|
includeFilename?: boolean;
|
||||||
|
progressHandler?(progress: UploadProgress): void;
|
||||||
|
abortController?: AbortController;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Upload {
|
||||||
|
loaded: number;
|
||||||
|
total: number;
|
||||||
|
promise: Promise<UploadResponse>;
|
||||||
|
abortController: AbortController;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UploadResponse {
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
content_uri: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FileType = XMLHttpRequestBodyInit;
|
22
src/http-api/method.ts
Normal file
22
src/http-api/method.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export enum Method {
|
||||||
|
Get = "GET",
|
||||||
|
Put = "PUT",
|
||||||
|
Post = "POST",
|
||||||
|
Delete = "DELETE",
|
||||||
|
}
|
53
src/http-api/prefix.ts
Normal file
53
src/http-api/prefix.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export enum ClientPrefix {
|
||||||
|
/**
|
||||||
|
* A constant representing the URI path for release 0 of the Client-Server HTTP API.
|
||||||
|
*/
|
||||||
|
R0 = "/_matrix/client/r0",
|
||||||
|
/**
|
||||||
|
* A constant representing the URI path for the legacy release v1 of the Client-Server HTTP API.
|
||||||
|
*/
|
||||||
|
V1 = "/_matrix/client/v1",
|
||||||
|
/**
|
||||||
|
* A constant representing the URI path for Client-Server API endpoints versioned at v3.
|
||||||
|
*/
|
||||||
|
V3 = "/_matrix/client/v3",
|
||||||
|
/**
|
||||||
|
* A constant representing the URI path for as-yet unspecified Client-Server HTTP APIs.
|
||||||
|
*/
|
||||||
|
Unstable = "/_matrix/client/unstable",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum IdentityPrefix {
|
||||||
|
/**
|
||||||
|
* URI path for v1 of the identity API
|
||||||
|
* @deprecated Use v2.
|
||||||
|
*/
|
||||||
|
V1 = "/_matrix/identity/api/v1",
|
||||||
|
/**
|
||||||
|
* URI path for the v2 identity API
|
||||||
|
*/
|
||||||
|
V2 = "/_matrix/identity/api/v2",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MediaPrefix {
|
||||||
|
/**
|
||||||
|
* URI path for the media repo API
|
||||||
|
*/
|
||||||
|
R0 = "/_matrix/media/r0",
|
||||||
|
}
|
149
src/http-api/utils.ts
Normal file
149
src/http-api/utils.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
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 { parse as parseContentType, ParsedMediaType } from "content-type";
|
||||||
|
|
||||||
|
import { logger } from "../logger";
|
||||||
|
import { sleep } from "../utils";
|
||||||
|
import { ConnectionError, MatrixError } from "./errors";
|
||||||
|
|
||||||
|
// Ponyfill for https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout
|
||||||
|
export function timeoutSignal(ms: number): AbortSignal {
|
||||||
|
const controller = new AbortController();
|
||||||
|
setTimeout(() => {
|
||||||
|
controller.abort();
|
||||||
|
}, ms);
|
||||||
|
|
||||||
|
return controller.signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function anySignal(signals: AbortSignal[]): {
|
||||||
|
signal: AbortSignal;
|
||||||
|
cleanup(): void;
|
||||||
|
} {
|
||||||
|
const controller = new AbortController();
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
for (const signal of signals) {
|
||||||
|
signal.removeEventListener("abort", onAbort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAbort() {
|
||||||
|
controller.abort();
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const signal of signals) {
|
||||||
|
if (signal.aborted) {
|
||||||
|
onAbort();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
signal.addEventListener("abort", onAbort);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
signal: controller.signal,
|
||||||
|
cleanup,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to turn an HTTP error response into a Javascript Error.
|
||||||
|
*
|
||||||
|
* If it is a JSON response, we will parse it into a MatrixError. Otherwise
|
||||||
|
* we return a generic Error.
|
||||||
|
*
|
||||||
|
* @param {XMLHttpRequest|Response} response response object
|
||||||
|
* @param {String} body raw body of the response
|
||||||
|
* @returns {Error}
|
||||||
|
*/
|
||||||
|
export function parseErrorResponse(response: XMLHttpRequest | Response, body?: string): Error {
|
||||||
|
let contentType: ParsedMediaType;
|
||||||
|
try {
|
||||||
|
contentType = getResponseContentType(response);
|
||||||
|
} catch (e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentType?.type === "application/json" && body) {
|
||||||
|
return new MatrixError(JSON.parse(body), response.status);
|
||||||
|
}
|
||||||
|
if (contentType?.type === "text/plain") {
|
||||||
|
return new Error(`Server returned ${response.status} error: ${body}`);
|
||||||
|
}
|
||||||
|
return new Error(`Server returned ${response.status} error`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isXhr(response: XMLHttpRequest | Response): response is XMLHttpRequest {
|
||||||
|
return "getResponseHeader" in response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* extract the Content-Type header from the response object, and
|
||||||
|
* parse it to a `{type, parameters}` object.
|
||||||
|
*
|
||||||
|
* returns null if no content-type header could be found.
|
||||||
|
*
|
||||||
|
* @param {XMLHttpRequest|Response} response response object
|
||||||
|
* @returns {{type: String, parameters: Object}?} parsed content-type header, or null if not found
|
||||||
|
*/
|
||||||
|
function getResponseContentType(response: XMLHttpRequest | Response): ParsedMediaType | null {
|
||||||
|
let contentType: string | null;
|
||||||
|
if (isXhr(response)) {
|
||||||
|
contentType = response.getResponseHeader("Content-Type");
|
||||||
|
} else {
|
||||||
|
contentType = response.headers.get("Content-Type");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!contentType) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return parseContentType(contentType);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Error parsing Content-Type '${contentType}': ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retries a network operation run in a callback.
|
||||||
|
* @param {number} maxAttempts maximum attempts to try
|
||||||
|
* @param {Function} callback callback that returns a promise of the network operation. If rejected with ConnectionError, it will be retried by calling the callback again.
|
||||||
|
* @return {any} the result of the network operation
|
||||||
|
* @throws {ConnectionError} If after maxAttempts the callback still throws ConnectionError
|
||||||
|
*/
|
||||||
|
export async function retryNetworkOperation<T>(maxAttempts: number, callback: () => Promise<T>): Promise<T> {
|
||||||
|
let attempts = 0;
|
||||||
|
let lastConnectionError: ConnectionError | null = null;
|
||||||
|
while (attempts < maxAttempts) {
|
||||||
|
try {
|
||||||
|
if (attempts > 0) {
|
||||||
|
const timeout = 1000 * Math.pow(2, attempts);
|
||||||
|
logger.log(`network operation failed ${attempts} times, retrying in ${timeout}ms...`);
|
||||||
|
await sleep(timeout);
|
||||||
|
}
|
||||||
|
return await callback();
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ConnectionError) {
|
||||||
|
attempts += 1;
|
||||||
|
lastConnectionError = err;
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw lastConnectionError;
|
||||||
|
}
|
@ -14,17 +14,14 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as request from "request";
|
|
||||||
|
|
||||||
import * as matrixcs from "./matrix";
|
import * as matrixcs from "./matrix";
|
||||||
import * as utils from "./utils";
|
import * as utils from "./utils";
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
|
|
||||||
if (matrixcs.getRequest()) {
|
if (global.__js_sdk_entrypoint) {
|
||||||
throw new Error("Multiple matrix-js-sdk entrypoints detected!");
|
throw new Error("Multiple matrix-js-sdk entrypoints detected!");
|
||||||
}
|
}
|
||||||
|
global.__js_sdk_entrypoint = true;
|
||||||
matrixcs.request(request);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
@ -55,41 +55,6 @@ export {
|
|||||||
createNewMatrixCall,
|
createNewMatrixCall,
|
||||||
} from "./webrtc/call";
|
} from "./webrtc/call";
|
||||||
|
|
||||||
// expose the underlying request object so different environments can use
|
|
||||||
// different request libs (e.g. request or browser-request)
|
|
||||||
let requestInstance;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The function used to perform HTTP requests. Only use this if you want to
|
|
||||||
* use a different HTTP library, e.g. Angular's <code>$http</code>. This should
|
|
||||||
* be set prior to calling {@link createClient}.
|
|
||||||
* @param {requestFunction} r The request function to use.
|
|
||||||
*/
|
|
||||||
export function request(r) {
|
|
||||||
requestInstance = r;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the currently-set request function.
|
|
||||||
* @return {requestFunction} The current request function.
|
|
||||||
*/
|
|
||||||
export function getRequest() {
|
|
||||||
return requestInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply wrapping code around the request function. The wrapper function is
|
|
||||||
* installed as the new request handler, and when invoked it is passed the
|
|
||||||
* previous value, along with the options and callback arguments.
|
|
||||||
* @param {requestWrapperFunction} wrapper The wrapping function.
|
|
||||||
*/
|
|
||||||
export function wrapRequest(wrapper) {
|
|
||||||
const origRequest = requestInstance;
|
|
||||||
requestInstance = function(options, callback) {
|
|
||||||
return wrapper(origRequest, options, callback);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let cryptoStoreFactory = () => new MemoryCryptoStore;
|
let cryptoStoreFactory = () => new MemoryCryptoStore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -128,15 +93,13 @@ export interface ICryptoCallbacks {
|
|||||||
/**
|
/**
|
||||||
* Construct a Matrix Client. Similar to {@link module:client.MatrixClient}
|
* Construct a Matrix Client. Similar to {@link module:client.MatrixClient}
|
||||||
* except that the 'request', 'store' and 'scheduler' dependencies are satisfied.
|
* except that the 'request', 'store' and 'scheduler' dependencies are satisfied.
|
||||||
* @param {(Object|string)} opts The configuration options for this client. If
|
* @param {(Object)} opts The configuration options for this client. If
|
||||||
* this is a string, it is assumed to be the base URL. These configuration
|
* this is a string, it is assumed to be the base URL. These configuration
|
||||||
* options will be passed directly to {@link module:client.MatrixClient}.
|
* options will be passed directly to {@link module:client.MatrixClient}.
|
||||||
* @param {Object} opts.store If not set, defaults to
|
* @param {Object} opts.store If not set, defaults to
|
||||||
* {@link module:store/memory.MemoryStore}.
|
* {@link module:store/memory.MemoryStore}.
|
||||||
* @param {Object} opts.scheduler If not set, defaults to
|
* @param {Object} opts.scheduler If not set, defaults to
|
||||||
* {@link module:scheduler~MatrixScheduler}.
|
* {@link module:scheduler~MatrixScheduler}.
|
||||||
* @param {requestFunction} opts.request If not set, defaults to the function
|
|
||||||
* supplied to {@link request} which defaults to the request module from NPM.
|
|
||||||
*
|
*
|
||||||
* @param {module:crypto.store.base~CryptoStore=} opts.cryptoStore
|
* @param {module:crypto.store.base~CryptoStore=} opts.cryptoStore
|
||||||
* crypto store implementation. Calls the factory supplied to
|
* crypto store implementation. Calls the factory supplied to
|
||||||
@ -148,13 +111,7 @@ export interface ICryptoCallbacks {
|
|||||||
* @see {@link module:client.MatrixClient} for the full list of options for
|
* @see {@link module:client.MatrixClient} for the full list of options for
|
||||||
* <code>opts</code>.
|
* <code>opts</code>.
|
||||||
*/
|
*/
|
||||||
export function createClient(opts: ICreateClientOpts | string) {
|
export function createClient(opts: ICreateClientOpts) {
|
||||||
if (typeof opts === "string") {
|
|
||||||
opts = {
|
|
||||||
"baseUrl": opts,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
opts.request = opts.request || requestInstance;
|
|
||||||
opts.store = opts.store || new MemoryStore({
|
opts.store = opts.store || new MemoryStore({
|
||||||
localStorage: global.localStorage,
|
localStorage: global.localStorage,
|
||||||
});
|
});
|
||||||
@ -163,23 +120,6 @@ export function createClient(opts: ICreateClientOpts | string) {
|
|||||||
return new MatrixClient(opts);
|
return new MatrixClient(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The request function interface for performing HTTP requests. This matches the
|
|
||||||
* API for the {@link https://github.com/request/request#requestoptions-callback|
|
|
||||||
* request NPM module}. The SDK will attempt to call this function in order to
|
|
||||||
* perform an HTTP request.
|
|
||||||
* @callback requestFunction
|
|
||||||
* @param {Object} opts The options for this HTTP request.
|
|
||||||
* @param {string} opts.uri The complete URI.
|
|
||||||
* @param {string} opts.method The HTTP method.
|
|
||||||
* @param {Object} opts.qs The query parameters to append to the URI.
|
|
||||||
* @param {Object} opts.body The JSON-serializable object.
|
|
||||||
* @param {boolean} opts.json True if this is a JSON request.
|
|
||||||
* @param {Object} opts._matrix_opts The underlying options set for
|
|
||||||
* {@link MatrixHttpApi}.
|
|
||||||
* @param {requestCallback} callback The request callback.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper for the request function interface.
|
* A wrapper for the request function interface.
|
||||||
* @callback requestWrapperFunction
|
* @callback requestWrapperFunction
|
||||||
|
@ -476,10 +476,8 @@ export class MSC3089TreeSpace {
|
|||||||
info: Partial<IEncryptedFile>,
|
info: Partial<IEncryptedFile>,
|
||||||
additionalContent?: IContent,
|
additionalContent?: IContent,
|
||||||
): Promise<ISendEventResponse> {
|
): Promise<ISendEventResponse> {
|
||||||
const mxc = await this.client.uploadContent(encryptedContents, {
|
const { content_uri: mxc } = await this.client.uploadContent(encryptedContents, {
|
||||||
includeFilename: false,
|
includeFilename: false,
|
||||||
onlyContentUri: true,
|
|
||||||
rawResponse: false, // make this explicit otherwise behaviour is different on browser vs NodeJS
|
|
||||||
});
|
});
|
||||||
info.url = mxc;
|
info.url = mxc;
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import { logger } from './logger';
|
|||||||
import { MatrixEvent } from "./models/event";
|
import { MatrixEvent } from "./models/event";
|
||||||
import { EventType } from "./@types/event";
|
import { EventType } from "./@types/event";
|
||||||
import { IDeferred } from "./utils";
|
import { IDeferred } from "./utils";
|
||||||
import { MatrixError } from "./http-api";
|
import { ConnectionError, MatrixError } from "./http-api";
|
||||||
import { ISendEventResponse } from "./@types/requests";
|
import { ISendEventResponse } from "./@types/requests";
|
||||||
|
|
||||||
const DEBUG = false; // set true to enable console logging.
|
const DEBUG = false; // set true to enable console logging.
|
||||||
@ -68,9 +68,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
|
|||||||
// client error; no amount of retrying with save you now.
|
// client error; no amount of retrying with save you now.
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
// we ship with browser-request which returns { cors: rejected } when trying
|
if (err instanceof ConnectionError) {
|
||||||
// with no connection, so if we match that, give up since they have no conn.
|
|
||||||
if (err["cors"] === "rejected") {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -674,7 +674,7 @@ export class SlidingSyncSdk {
|
|||||||
member._requestedProfileInfo = true;
|
member._requestedProfileInfo = true;
|
||||||
// try to get a cached copy first.
|
// try to get a cached copy first.
|
||||||
const user = client.getUser(member.userId);
|
const user = client.getUser(member.userId);
|
||||||
let promise;
|
let promise: ReturnType<MatrixClient["getProfileInfo"]>;
|
||||||
if (user) {
|
if (user) {
|
||||||
promise = Promise.resolve({
|
promise = Promise.resolve({
|
||||||
avatar_url: user.avatarUrl,
|
avatar_url: user.avatarUrl,
|
||||||
|
@ -15,11 +15,11 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
import { IAbortablePromise } from "./@types/partials";
|
|
||||||
import { MatrixClient } from "./client";
|
import { MatrixClient } from "./client";
|
||||||
import { IRoomEvent, IStateEvent } from "./sync-accumulator";
|
import { IRoomEvent, IStateEvent } from "./sync-accumulator";
|
||||||
import { TypedEventEmitter } from "./models//typed-event-emitter";
|
import { TypedEventEmitter } from "./models/typed-event-emitter";
|
||||||
import { sleep, IDeferred, defer } from "./utils";
|
import { sleep, IDeferred, defer } from "./utils";
|
||||||
|
import { ConnectionError } from "./http-api";
|
||||||
|
|
||||||
// /sync requests allow you to set a timeout= but the request may continue
|
// /sync requests allow you to set a timeout= but the request may continue
|
||||||
// beyond that and wedge forever, so we need to track how long we are willing
|
// beyond that and wedge forever, so we need to track how long we are willing
|
||||||
@ -353,7 +353,8 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|||||||
private desiredRoomSubscriptions = new Set<string>(); // the *desired* room subscriptions
|
private desiredRoomSubscriptions = new Set<string>(); // the *desired* room subscriptions
|
||||||
private confirmedRoomSubscriptions = new Set<string>();
|
private confirmedRoomSubscriptions = new Set<string>();
|
||||||
|
|
||||||
private pendingReq?: IAbortablePromise<MSC3575SlidingSyncResponse>;
|
private pendingReq?: Promise<MSC3575SlidingSyncResponse>;
|
||||||
|
private abortController?: AbortController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new sliding sync instance
|
* Create a new sliding sync instance
|
||||||
@ -700,7 +701,8 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|||||||
...d,
|
...d,
|
||||||
txnId: this.txnId,
|
txnId: this.txnId,
|
||||||
});
|
});
|
||||||
this.pendingReq?.abort();
|
this.abortController?.abort();
|
||||||
|
this.abortController = new AbortController();
|
||||||
return d.promise;
|
return d.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -728,7 +730,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|||||||
this.txnIdDefers[i].reject(this.txnIdDefers[i].txnId);
|
this.txnIdDefers[i].reject(this.txnIdDefers[i].txnId);
|
||||||
}
|
}
|
||||||
this.txnIdDefers[txnIndex].resolve(txnId);
|
this.txnIdDefers[txnIndex].resolve(txnId);
|
||||||
// clear out settled promises, incuding the one we resolved.
|
// clear out settled promises, including the one we resolved.
|
||||||
this.txnIdDefers = this.txnIdDefers.slice(txnIndex+1);
|
this.txnIdDefers = this.txnIdDefers.slice(txnIndex+1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -737,7 +739,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|||||||
*/
|
*/
|
||||||
public stop(): void {
|
public stop(): void {
|
||||||
this.terminated = true;
|
this.terminated = true;
|
||||||
this.pendingReq?.abort();
|
this.abortController?.abort();
|
||||||
// remove all listeners so things can be GC'd
|
// remove all listeners so things can be GC'd
|
||||||
this.removeAllListeners(SlidingSyncEvent.Lifecycle);
|
this.removeAllListeners(SlidingSyncEvent.Lifecycle);
|
||||||
this.removeAllListeners(SlidingSyncEvent.List);
|
this.removeAllListeners(SlidingSyncEvent.List);
|
||||||
@ -748,6 +750,8 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|||||||
* Start syncing with the server. Blocks until stopped.
|
* Start syncing with the server. Blocks until stopped.
|
||||||
*/
|
*/
|
||||||
public async start() {
|
public async start() {
|
||||||
|
this.abortController = new AbortController();
|
||||||
|
|
||||||
let currentPos: string;
|
let currentPos: string;
|
||||||
while (!this.terminated) {
|
while (!this.terminated) {
|
||||||
this.needsResend = false;
|
this.needsResend = false;
|
||||||
@ -780,7 +784,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|||||||
reqBody.txn_id = this.txnId;
|
reqBody.txn_id = this.txnId;
|
||||||
this.txnId = null;
|
this.txnId = null;
|
||||||
}
|
}
|
||||||
this.pendingReq = this.client.slidingSync(reqBody, this.proxyBaseUrl);
|
this.pendingReq = this.client.slidingSync(reqBody, this.proxyBaseUrl, this.abortController.signal);
|
||||||
resp = await this.pendingReq;
|
resp = await this.pendingReq;
|
||||||
logger.debug(resp);
|
logger.debug(resp);
|
||||||
currentPos = resp.pos;
|
currentPos = resp.pos;
|
||||||
@ -821,10 +825,7 @@ export class SlidingSync extends TypedEventEmitter<SlidingSyncEvent, SlidingSync
|
|||||||
err,
|
err,
|
||||||
);
|
);
|
||||||
await sleep(3000);
|
await sleep(3000);
|
||||||
} else if (this.needsResend || err === "aborted") {
|
} else if (this.needsResend || err instanceof ConnectionError) {
|
||||||
// don't sleep as we caused this error by abort()ing the request.
|
|
||||||
// we check for 'aborted' because that's the error Jest returns and without it
|
|
||||||
// we get warnings about not exiting fast enough.
|
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
|
@ -247,7 +247,7 @@ export interface IStore {
|
|||||||
/**
|
/**
|
||||||
* Fetches the oldest batch of to-device messages in the queue
|
* Fetches the oldest batch of to-device messages in the queue
|
||||||
*/
|
*/
|
||||||
getOldestToDeviceBatch(): Promise<IndexedToDeviceBatch>;
|
getOldestToDeviceBatch(): Promise<IndexedToDeviceBatch | null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a specific batch of to-device messages from the queue
|
* Removes a specific batch of to-device messages from the queue
|
||||||
|
@ -33,7 +33,7 @@ export interface IIndexedDBBackend {
|
|||||||
getClientOptions(): Promise<IStartClientOpts>;
|
getClientOptions(): Promise<IStartClientOpts>;
|
||||||
storeClientOptions(options: IStartClientOpts): Promise<void>;
|
storeClientOptions(options: IStartClientOpts): Promise<void>;
|
||||||
saveToDeviceBatches(batches: ToDeviceBatchWithTxnId[]): Promise<void>;
|
saveToDeviceBatches(batches: ToDeviceBatchWithTxnId[]): Promise<void>;
|
||||||
getOldestToDeviceBatch(): Promise<IndexedToDeviceBatch>;
|
getOldestToDeviceBatch(): Promise<IndexedToDeviceBatch | null>;
|
||||||
removeToDeviceBatch(id: number): Promise<void>;
|
removeToDeviceBatch(id: number): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend {
|
|||||||
return this.doCmd('saveToDeviceBatches', [batches]);
|
return this.doCmd('saveToDeviceBatches', [batches]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getOldestToDeviceBatch(): Promise<IndexedToDeviceBatch> {
|
public async getOldestToDeviceBatch(): Promise<IndexedToDeviceBatch | null> {
|
||||||
return this.doCmd('getOldestToDeviceBatch');
|
return this.doCmd('getOldestToDeviceBatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,7 +357,7 @@ export class IndexedDBStore extends MemoryStore {
|
|||||||
return this.backend.saveToDeviceBatches(batches);
|
return this.backend.saveToDeviceBatches(batches);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getOldestToDeviceBatch(): Promise<IndexedToDeviceBatch> {
|
public getOldestToDeviceBatch(): Promise<IndexedToDeviceBatch | null> {
|
||||||
return this.backend.getOldestToDeviceBatch();
|
return this.backend.getOldestToDeviceBatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
28
src/sync.ts
28
src/sync.ts
@ -57,7 +57,6 @@ import { RoomStateEvent, IMarkerFoundOptions } from "./models/room-state";
|
|||||||
import { RoomMemberEvent } from "./models/room-member";
|
import { RoomMemberEvent } from "./models/room-member";
|
||||||
import { BeaconEvent } from "./models/beacon";
|
import { BeaconEvent } from "./models/beacon";
|
||||||
import { IEventsResponse } from "./@types/requests";
|
import { IEventsResponse } from "./@types/requests";
|
||||||
import { IAbortablePromise } from "./@types/partials";
|
|
||||||
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync";
|
import { UNREAD_THREAD_NOTIFICATIONS } from "./@types/sync";
|
||||||
import { Feature, ServerSupport } from "./feature";
|
import { Feature, ServerSupport } from "./feature";
|
||||||
|
|
||||||
@ -164,7 +163,8 @@ type WrappedRoom<T> = T & {
|
|||||||
*/
|
*/
|
||||||
export class SyncApi {
|
export class SyncApi {
|
||||||
private _peekRoom: Optional<Room> = null;
|
private _peekRoom: Optional<Room> = null;
|
||||||
private currentSyncRequest: Optional<IAbortablePromise<ISyncResponse>> = null;
|
private currentSyncRequest: Optional<Promise<ISyncResponse>> = null;
|
||||||
|
private abortController?: AbortController;
|
||||||
private syncState: Optional<SyncState> = null;
|
private syncState: Optional<SyncState> = null;
|
||||||
private syncStateData: Optional<ISyncStateData> = null; // additional data (eg. error object for failed sync)
|
private syncStateData: Optional<ISyncStateData> = null; // additional data (eg. error object for failed sync)
|
||||||
private catchingUp = false;
|
private catchingUp = false;
|
||||||
@ -298,9 +298,9 @@ export class SyncApi {
|
|||||||
getFilterName(client.credentials.userId, "LEFT_ROOMS"), filter,
|
getFilterName(client.credentials.userId, "LEFT_ROOMS"), filter,
|
||||||
).then(function(filterId) {
|
).then(function(filterId) {
|
||||||
qps.filter = filterId;
|
qps.filter = filterId;
|
||||||
return client.http.authedRequest<ISyncResponse>(
|
return client.http.authedRequest<ISyncResponse>(Method.Get, "/sync", qps as any, undefined, {
|
||||||
undefined, Method.Get, "/sync", qps as any, undefined, localTimeoutMs,
|
localTimeoutMs,
|
||||||
);
|
});
|
||||||
}).then(async (data) => {
|
}).then(async (data) => {
|
||||||
let leaveRooms = [];
|
let leaveRooms = [];
|
||||||
if (data.rooms?.leave) {
|
if (data.rooms?.leave) {
|
||||||
@ -433,11 +433,11 @@ export class SyncApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: gut wrenching; hard-coded timeout values
|
// FIXME: gut wrenching; hard-coded timeout values
|
||||||
this.client.http.authedRequest<IEventsResponse>(undefined, Method.Get, "/events", {
|
this.client.http.authedRequest<IEventsResponse>(Method.Get, "/events", {
|
||||||
room_id: peekRoom.roomId,
|
room_id: peekRoom.roomId,
|
||||||
timeout: String(30 * 1000),
|
timeout: String(30 * 1000),
|
||||||
from: token,
|
from: token,
|
||||||
}, undefined, 50 * 1000).then((res) => {
|
}, undefined, { localTimeoutMs: 50 * 1000 }).then((res) => {
|
||||||
if (this._peekRoom !== peekRoom) {
|
if (this._peekRoom !== peekRoom) {
|
||||||
debuglog("Stopped peeking in room %s", peekRoom.roomId);
|
debuglog("Stopped peeking in room %s", peekRoom.roomId);
|
||||||
return;
|
return;
|
||||||
@ -652,6 +652,7 @@ export class SyncApi {
|
|||||||
*/
|
*/
|
||||||
public async sync(): Promise<void> {
|
public async sync(): Promise<void> {
|
||||||
this.running = true;
|
this.running = true;
|
||||||
|
this.abortController = new AbortController();
|
||||||
|
|
||||||
global.window?.addEventListener?.("online", this.onOnline, false);
|
global.window?.addEventListener?.("online", this.onOnline, false);
|
||||||
|
|
||||||
@ -738,7 +739,7 @@ export class SyncApi {
|
|||||||
// but do not have global.window.removeEventListener.
|
// but do not have global.window.removeEventListener.
|
||||||
global.window?.removeEventListener?.("online", this.onOnline, false);
|
global.window?.removeEventListener?.("online", this.onOnline, false);
|
||||||
this.running = false;
|
this.running = false;
|
||||||
this.currentSyncRequest?.abort();
|
this.abortController?.abort();
|
||||||
if (this.keepAliveTimer) {
|
if (this.keepAliveTimer) {
|
||||||
clearTimeout(this.keepAliveTimer);
|
clearTimeout(this.keepAliveTimer);
|
||||||
this.keepAliveTimer = null;
|
this.keepAliveTimer = null;
|
||||||
@ -902,12 +903,12 @@ export class SyncApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private doSyncRequest(syncOptions: ISyncOptions, syncToken: string): IAbortablePromise<ISyncResponse> {
|
private doSyncRequest(syncOptions: ISyncOptions, syncToken: string): Promise<ISyncResponse> {
|
||||||
const qps = this.getSyncParams(syncOptions, syncToken);
|
const qps = this.getSyncParams(syncOptions, syncToken);
|
||||||
return this.client.http.authedRequest<ISyncResponse>(
|
return this.client.http.authedRequest<ISyncResponse>(Method.Get, "/sync", qps as any, undefined, {
|
||||||
undefined, Method.Get, "/sync", qps as any, undefined,
|
localTimeoutMs: qps.timeout + BUFFER_PERIOD_MS,
|
||||||
qps.timeout + BUFFER_PERIOD_MS,
|
abortSignal: this.abortController?.signal,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSyncParams(syncOptions: ISyncOptions, syncToken: string): ISyncParams {
|
private getSyncParams(syncOptions: ISyncOptions, syncToken: string): ISyncParams {
|
||||||
@ -1521,7 +1522,6 @@ export class SyncApi {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.client.http.request(
|
this.client.http.request(
|
||||||
undefined, // callback
|
|
||||||
Method.Get, "/_matrix/client/versions",
|
Method.Get, "/_matrix/client/versions",
|
||||||
undefined, // queryParams
|
undefined, // queryParams
|
||||||
undefined, // data
|
undefined, // data
|
||||||
|
20
src/utils.ts
20
src/utils.ts
@ -59,17 +59,23 @@ export function internaliseString(str: string): string {
|
|||||||
* {"foo": "bar", "baz": "taz"}
|
* {"foo": "bar", "baz": "taz"}
|
||||||
* @return {string} The encoded string e.g. foo=bar&baz=taz
|
* @return {string} The encoded string e.g. foo=bar&baz=taz
|
||||||
*/
|
*/
|
||||||
export function encodeParams(params: Record<string, string | number | boolean>): string {
|
export function encodeParams(params: QueryDict, urlSearchParams?: URLSearchParams): URLSearchParams {
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = urlSearchParams ?? new URLSearchParams();
|
||||||
for (const [key, val] of Object.entries(params)) {
|
for (const [key, val] of Object.entries(params)) {
|
||||||
if (val !== undefined && val !== null) {
|
if (val !== undefined && val !== null) {
|
||||||
searchParams.set(key, String(val));
|
if (Array.isArray(val)) {
|
||||||
|
val.forEach(v => {
|
||||||
|
searchParams.append(key, String(v));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
searchParams.append(key, String(val));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return searchParams.toString();
|
return searchParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type QueryDict = Record<string, string | string[]>;
|
export type QueryDict = Record<string, string[] | string | number | boolean | undefined>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode a query string in `application/x-www-form-urlencoded` format.
|
* Decode a query string in `application/x-www-form-urlencoded` format.
|
||||||
@ -80,8 +86,8 @@ export type QueryDict = Record<string, string | string[]>;
|
|||||||
* This behaviour matches Node's qs.parse but is built on URLSearchParams
|
* This behaviour matches Node's qs.parse but is built on URLSearchParams
|
||||||
* for native web compatibility
|
* for native web compatibility
|
||||||
*/
|
*/
|
||||||
export function decodeParams(query: string): QueryDict {
|
export function decodeParams(query: string): Record<string, string | string[]> {
|
||||||
const o: QueryDict = {};
|
const o: Record<string, string | string[]> = {};
|
||||||
const params = new URLSearchParams(query);
|
const params = new URLSearchParams(query);
|
||||||
for (const key of params.keys()) {
|
for (const key of params.keys()) {
|
||||||
const val = params.getAll(key);
|
const val = params.getAll(key);
|
||||||
|
@ -298,7 +298,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
// yet, null if we have but they didn't send a party ID.
|
// yet, null if we have but they didn't send a party ID.
|
||||||
private opponentPartyId: string;
|
private opponentPartyId: string;
|
||||||
private opponentCaps: CallCapabilities;
|
private opponentCaps: CallCapabilities;
|
||||||
private inviteTimeout: ReturnType<typeof setTimeout>;
|
private inviteTimeout?: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
// The logic of when & if a call is on hold is nontrivial and explained in is*OnHold
|
// The logic of when & if a call is on hold is nontrivial and explained in is*OnHold
|
||||||
// This flag represents whether we want the other party to be on hold
|
// This flag represents whether we want the other party to be on hold
|
||||||
@ -322,7 +322,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
|
|
||||||
private remoteSDPStreamMetadata: SDPStreamMetadata;
|
private remoteSDPStreamMetadata: SDPStreamMetadata;
|
||||||
|
|
||||||
private callLengthInterval: ReturnType<typeof setInterval>;
|
private callLengthInterval?: ReturnType<typeof setInterval>;
|
||||||
private callLength = 0;
|
private callLength = 0;
|
||||||
|
|
||||||
constructor(opts: CallOpts) {
|
constructor(opts: CallOpts) {
|
||||||
@ -1689,7 +1689,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
this.inviteOrAnswerSent = true;
|
this.inviteOrAnswerSent = true;
|
||||||
this.setState(CallState.InviteSent);
|
this.setState(CallState.InviteSent);
|
||||||
this.inviteTimeout = setTimeout(() => {
|
this.inviteTimeout = setTimeout(() => {
|
||||||
this.inviteTimeout = null;
|
this.inviteTimeout = undefined;
|
||||||
if (this.state === CallState.InviteSent) {
|
if (this.state === CallState.InviteSent) {
|
||||||
this.hangup(CallErrorCode.InviteTimeout, false);
|
this.hangup(CallErrorCode.InviteTimeout, false);
|
||||||
}
|
}
|
||||||
@ -2004,11 +2004,11 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
|||||||
|
|
||||||
if (this.inviteTimeout) {
|
if (this.inviteTimeout) {
|
||||||
clearTimeout(this.inviteTimeout);
|
clearTimeout(this.inviteTimeout);
|
||||||
this.inviteTimeout = null;
|
this.inviteTimeout = undefined;
|
||||||
}
|
}
|
||||||
if (this.callLengthInterval) {
|
if (this.callLengthInterval) {
|
||||||
clearInterval(this.callLengthInterval);
|
clearInterval(this.callLengthInterval);
|
||||||
this.callLengthInterval = null;
|
this.callLengthInterval = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Order is important here: first we stopAllMedia() and only then we can deleteAllFeeds()
|
// Order is important here: first we stopAllMedia() and only then we can deleteAllFeeds()
|
||||||
|
339
yarn.lock
339
yarn.lock
@ -1588,16 +1588,18 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
base-x "^3.0.6"
|
base-x "^3.0.6"
|
||||||
|
|
||||||
"@types/caseless@*":
|
|
||||||
version "0.12.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8"
|
|
||||||
integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==
|
|
||||||
|
|
||||||
"@types/content-type@^1.1.5":
|
"@types/content-type@^1.1.5":
|
||||||
version "1.1.5"
|
version "1.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/content-type/-/content-type-1.1.5.tgz#aa02dca40864749a9e2bf0161a6216da57e3ede5"
|
resolved "https://registry.yarnpkg.com/@types/content-type/-/content-type-1.1.5.tgz#aa02dca40864749a9e2bf0161a6216da57e3ede5"
|
||||||
integrity sha512-dgMN+syt1xb7Hk8LU6AODOfPlvz5z1CbXpPuJE5ZrX9STfBOIXF09pEB8N7a97WT9dbngt3ksDCm6GW6yMrxfQ==
|
integrity sha512-dgMN+syt1xb7Hk8LU6AODOfPlvz5z1CbXpPuJE5ZrX9STfBOIXF09pEB8N7a97WT9dbngt3ksDCm6GW6yMrxfQ==
|
||||||
|
|
||||||
|
"@types/domexception@^4.0.0":
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/domexception/-/domexception-4.0.0.tgz#bb19c920c81c3f1408b46d021fb79467ff2d32fd"
|
||||||
|
integrity sha512-vD9eLiVhgDrsVtUIiHBaToW/lfhCUYzmb81Sc0a0kJ4qEN2ZJyhz4wVyAmum4hAVDuBMUDE4yAeeF7fPHKCujw==
|
||||||
|
dependencies:
|
||||||
|
"@types/webidl-conversions" "*"
|
||||||
|
|
||||||
"@types/graceful-fs@^4.1.3":
|
"@types/graceful-fs@^4.1.3":
|
||||||
version "4.1.5"
|
version "4.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
|
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
|
||||||
@ -1675,16 +1677,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.1.tgz#dfd20e2dc35f027cdd6c1908e80a5ddc7499670e"
|
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.1.tgz#dfd20e2dc35f027cdd6c1908e80a5ddc7499670e"
|
||||||
integrity sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==
|
integrity sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==
|
||||||
|
|
||||||
"@types/request@^2.48.5":
|
|
||||||
version "2.48.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.8.tgz#0b90fde3b655ab50976cb8c5ac00faca22f5a82c"
|
|
||||||
integrity sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ==
|
|
||||||
dependencies:
|
|
||||||
"@types/caseless" "*"
|
|
||||||
"@types/node" "*"
|
|
||||||
"@types/tough-cookie" "*"
|
|
||||||
form-data "^2.5.0"
|
|
||||||
|
|
||||||
"@types/retry@0.12.0":
|
"@types/retry@0.12.0":
|
||||||
version "0.12.0"
|
version "0.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
|
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
|
||||||
@ -1695,10 +1687,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
||||||
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
|
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
|
||||||
|
|
||||||
"@types/tough-cookie@*":
|
"@types/webidl-conversions@*":
|
||||||
version "4.0.2"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397"
|
resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz#2b8e60e33906459219aa587e9d1a612ae994cfe7"
|
||||||
integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==
|
integrity sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog==
|
||||||
|
|
||||||
"@types/yargs-parser@*":
|
"@types/yargs-parser@*":
|
||||||
version "21.0.0"
|
version "21.0.0"
|
||||||
@ -1858,7 +1850,7 @@ acorn@^8.5.0, acorn@^8.8.0:
|
|||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
|
||||||
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
|
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
|
||||||
|
|
||||||
ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4:
|
ajv@^6.10.0, ajv@^6.12.4:
|
||||||
version "6.12.6"
|
version "6.12.6"
|
||||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||||
@ -1993,18 +1985,6 @@ asn1.js@^5.2.0:
|
|||||||
minimalistic-assert "^1.0.0"
|
minimalistic-assert "^1.0.0"
|
||||||
safer-buffer "^2.1.0"
|
safer-buffer "^2.1.0"
|
||||||
|
|
||||||
asn1@~0.2.3:
|
|
||||||
version "0.2.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d"
|
|
||||||
integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==
|
|
||||||
dependencies:
|
|
||||||
safer-buffer "~2.1.0"
|
|
||||||
|
|
||||||
assert-plus@1.0.0, assert-plus@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
|
||||||
integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==
|
|
||||||
|
|
||||||
assert@^1.4.0:
|
assert@^1.4.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb"
|
resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb"
|
||||||
@ -2025,26 +2005,11 @@ ast-types@^0.14.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.0.1"
|
tslib "^2.0.1"
|
||||||
|
|
||||||
asynckit@^0.4.0:
|
|
||||||
version "0.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
|
||||||
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
|
||||||
|
|
||||||
available-typed-arrays@^1.0.5:
|
available-typed-arrays@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
|
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
|
||||||
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
|
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
|
||||||
|
|
||||||
aws-sign2@~0.7.0:
|
|
||||||
version "0.7.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
|
||||||
integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==
|
|
||||||
|
|
||||||
aws4@^1.8.0:
|
|
||||||
version "1.11.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
|
|
||||||
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
|
||||||
|
|
||||||
babel-jest@^29.0.0, babel-jest@^29.1.2:
|
babel-jest@^29.0.0, babel-jest@^29.1.2:
|
||||||
version "29.1.2"
|
version "29.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.1.2.tgz#540d3241925c55240fb0c742e3ffc5f33a501978"
|
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.1.2.tgz#540d3241925c55240fb0c742e3ffc5f33a501978"
|
||||||
@ -2191,13 +2156,6 @@ base64-js@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||||
|
|
||||||
bcrypt-pbkdf@^1.0.0:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
|
||||||
integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==
|
|
||||||
dependencies:
|
|
||||||
tweetnacl "^0.14.3"
|
|
||||||
|
|
||||||
before-after-hook@^2.2.0:
|
before-after-hook@^2.2.0:
|
||||||
version "2.2.2"
|
version "2.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e"
|
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e"
|
||||||
@ -2274,11 +2232,6 @@ browser-pack@^6.0.1:
|
|||||||
through2 "^2.0.0"
|
through2 "^2.0.0"
|
||||||
umd "^3.0.0"
|
umd "^3.0.0"
|
||||||
|
|
||||||
browser-request@^0.3.3:
|
|
||||||
version "0.3.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/browser-request/-/browser-request-0.3.3.tgz#9ece5b5aca89a29932242e18bf933def9876cc17"
|
|
||||||
integrity sha512-YyNI4qJJ+piQG6MMEuo7J3Bzaqssufx04zpEKYfSrl/1Op59HWali9zMtBpXnkmqMcOuWJPZvudrm9wISmnCbg==
|
|
||||||
|
|
||||||
browser-resolve@^2.0.0:
|
browser-resolve@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-2.0.0.tgz#99b7304cb392f8d73dba741bb2d7da28c6d7842b"
|
resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-2.0.0.tgz#99b7304cb392f8d73dba741bb2d7da28c6d7842b"
|
||||||
@ -2504,11 +2457,6 @@ caniuse-lite@^1.0.30001400:
|
|||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001414.tgz#5f1715e506e71860b4b07c50060ea6462217611e"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001414.tgz#5f1715e506e71860b4b07c50060ea6462217611e"
|
||||||
integrity sha512-t55jfSaWjCdocnFdKQoO+d2ct9C59UZg4dY3OnUlSZ447r8pUtIKdp0hpAzrGFultmTC+Us+KpKi4GZl/LXlFg==
|
integrity sha512-t55jfSaWjCdocnFdKQoO+d2ct9C59UZg4dY3OnUlSZ447r8pUtIKdp0hpAzrGFultmTC+Us+KpKi4GZl/LXlFg==
|
||||||
|
|
||||||
caseless@~0.12.0:
|
|
||||||
version "0.12.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
|
||||||
integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==
|
|
||||||
|
|
||||||
catharsis@^0.9.0:
|
catharsis@^0.9.0:
|
||||||
version "0.9.0"
|
version "0.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.9.0.tgz#40382a168be0e6da308c277d3a2b3eb40c7d2121"
|
resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.9.0.tgz#40382a168be0e6da308c277d3a2b3eb40c7d2121"
|
||||||
@ -2684,13 +2632,6 @@ combine-source-map@^0.8.0, combine-source-map@~0.8.0:
|
|||||||
lodash.memoize "~3.0.3"
|
lodash.memoize "~3.0.3"
|
||||||
source-map "~0.5.3"
|
source-map "~0.5.3"
|
||||||
|
|
||||||
combined-stream@^1.0.6, combined-stream@~1.0.6:
|
|
||||||
version "1.0.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
|
||||||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
|
||||||
dependencies:
|
|
||||||
delayed-stream "~1.0.0"
|
|
||||||
|
|
||||||
commander@^2.19.0, commander@^2.20.0:
|
commander@^2.19.0, commander@^2.20.0:
|
||||||
version "2.20.3"
|
version "2.20.3"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||||
@ -2770,11 +2711,6 @@ core-js@^2.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
|
||||||
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
|
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
|
||||||
|
|
||||||
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"
|
|
||||||
integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==
|
|
||||||
|
|
||||||
core-util-is@~1.0.0:
|
core-util-is@~1.0.0:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
|
||||||
@ -2850,13 +2786,6 @@ dash-ast@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/dash-ast/-/dash-ast-1.0.0.tgz#12029ba5fb2f8aa6f0a861795b23c1b4b6c27d37"
|
resolved "https://registry.yarnpkg.com/dash-ast/-/dash-ast-1.0.0.tgz#12029ba5fb2f8aa6f0a861795b23c1b4b6c27d37"
|
||||||
integrity sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==
|
integrity sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==
|
||||||
|
|
||||||
dashdash@^1.12.0:
|
|
||||||
version "1.14.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
|
||||||
integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==
|
|
||||||
dependencies:
|
|
||||||
assert-plus "^1.0.0"
|
|
||||||
|
|
||||||
de-indent@^1.0.2:
|
de-indent@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
||||||
@ -2921,11 +2850,6 @@ defined@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
|
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
|
||||||
integrity sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==
|
integrity sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==
|
||||||
|
|
||||||
delayed-stream@~1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
|
||||||
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
|
|
||||||
|
|
||||||
deprecation@^2.0.0, deprecation@^2.3.1:
|
deprecation@^2.0.0, deprecation@^2.3.1:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
|
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
|
||||||
@ -3030,6 +2954,13 @@ domexception@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
webidl-conversions "^4.0.2"
|
webidl-conversions "^4.0.2"
|
||||||
|
|
||||||
|
domexception@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673"
|
||||||
|
integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==
|
||||||
|
dependencies:
|
||||||
|
webidl-conversions "^7.0.0"
|
||||||
|
|
||||||
duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2:
|
duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
|
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
|
||||||
@ -3037,14 +2968,6 @@ duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
readable-stream "^2.0.2"
|
readable-stream "^2.0.2"
|
||||||
|
|
||||||
ecc-jsbn@~0.1.1:
|
|
||||||
version "0.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
|
||||||
integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==
|
|
||||||
dependencies:
|
|
||||||
jsbn "~0.1.0"
|
|
||||||
safer-buffer "^2.1.0"
|
|
||||||
|
|
||||||
electron-to-chromium@^1.4.251:
|
electron-to-chromium@^1.4.251:
|
||||||
version "1.4.270"
|
version "1.4.270"
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.270.tgz#2c6ea409b45cdb5c3e0cb2c08cf6c0ba7e0f2c26"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.270.tgz#2c6ea409b45cdb5c3e0cb2c08cf6c0ba7e0f2c26"
|
||||||
@ -3467,21 +3390,6 @@ ext@^1.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
type "^2.7.2"
|
type "^2.7.2"
|
||||||
|
|
||||||
extend@~3.0.2:
|
|
||||||
version "3.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
|
||||||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
|
||||||
|
|
||||||
extsprintf@1.3.0:
|
|
||||||
version "1.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
|
||||||
integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==
|
|
||||||
|
|
||||||
extsprintf@^1.2.0:
|
|
||||||
version "1.4.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07"
|
|
||||||
integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==
|
|
||||||
|
|
||||||
fake-indexeddb@^4.0.0:
|
fake-indexeddb@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-4.0.0.tgz#1dfb2023a3be175e35a6d84975218b432041934d"
|
resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-4.0.0.tgz#1dfb2023a3be175e35a6d84975218b432041934d"
|
||||||
@ -3608,29 +3516,6 @@ foreground-child@^2.0.0:
|
|||||||
cross-spawn "^7.0.0"
|
cross-spawn "^7.0.0"
|
||||||
signal-exit "^3.0.2"
|
signal-exit "^3.0.2"
|
||||||
|
|
||||||
forever-agent@~0.6.1:
|
|
||||||
version "0.6.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
|
||||||
integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==
|
|
||||||
|
|
||||||
form-data@^2.5.0:
|
|
||||||
version "2.5.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
|
|
||||||
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
|
|
||||||
dependencies:
|
|
||||||
asynckit "^0.4.0"
|
|
||||||
combined-stream "^1.0.6"
|
|
||||||
mime-types "^2.1.12"
|
|
||||||
|
|
||||||
form-data@~2.3.2:
|
|
||||||
version "2.3.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
|
||||||
integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
|
|
||||||
dependencies:
|
|
||||||
asynckit "^0.4.0"
|
|
||||||
combined-stream "^1.0.6"
|
|
||||||
mime-types "^2.1.12"
|
|
||||||
|
|
||||||
fs-readdir-recursive@^1.1.0:
|
fs-readdir-recursive@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27"
|
resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27"
|
||||||
@ -3713,13 +3598,6 @@ get-tsconfig@^4.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.2.0.tgz#ff368dd7104dab47bf923404eb93838245c66543"
|
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.2.0.tgz#ff368dd7104dab47bf923404eb93838245c66543"
|
||||||
integrity sha512-X8u8fREiYOE6S8hLbq99PeykTDoLVnxvF4DjWKJmz9xy2nNRdUcV8ZN9tniJFeKyTU3qnC9lL8n4Chd6LmVKHg==
|
integrity sha512-X8u8fREiYOE6S8hLbq99PeykTDoLVnxvF4DjWKJmz9xy2nNRdUcV8ZN9tniJFeKyTU3qnC9lL8n4Chd6LmVKHg==
|
||||||
|
|
||||||
getpass@^0.1.1:
|
|
||||||
version "0.1.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
|
|
||||||
integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==
|
|
||||||
dependencies:
|
|
||||||
assert-plus "^1.0.0"
|
|
||||||
|
|
||||||
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||||
@ -3801,19 +3679,6 @@ grapheme-splitter@^1.0.4:
|
|||||||
resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
|
resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
|
||||||
integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
|
integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
|
||||||
|
|
||||||
har-schema@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
|
|
||||||
integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==
|
|
||||||
|
|
||||||
har-validator@~5.1.3:
|
|
||||||
version "5.1.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
|
|
||||||
integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==
|
|
||||||
dependencies:
|
|
||||||
ajv "^6.12.3"
|
|
||||||
har-schema "^2.0.0"
|
|
||||||
|
|
||||||
has-bigints@^1.0.1, has-bigints@^1.0.2:
|
has-bigints@^1.0.1, has-bigints@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
|
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
|
||||||
@ -3901,15 +3766,6 @@ htmlescape@^1.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351"
|
resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351"
|
||||||
integrity sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==
|
integrity sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==
|
||||||
|
|
||||||
http-signature@~1.2.0:
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
|
||||||
integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==
|
|
||||||
dependencies:
|
|
||||||
assert-plus "^1.0.0"
|
|
||||||
jsprim "^1.2.2"
|
|
||||||
sshpk "^1.7.0"
|
|
||||||
|
|
||||||
https-browserify@^1.0.0:
|
https-browserify@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||||
@ -4181,11 +4037,6 @@ is-typed-array@^1.1.3, is-typed-array@^1.1.9:
|
|||||||
for-each "^0.3.3"
|
for-each "^0.3.3"
|
||||||
has-tostringtag "^1.0.0"
|
has-tostringtag "^1.0.0"
|
||||||
|
|
||||||
is-typedarray@~1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
|
||||||
integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
|
|
||||||
|
|
||||||
is-utf8@^0.2.0:
|
is-utf8@^0.2.0:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
||||||
@ -4220,11 +4071,6 @@ isobject@^3.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||||
integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==
|
integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==
|
||||||
|
|
||||||
isstream@~0.1.2:
|
|
||||||
version "0.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
|
||||||
integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==
|
|
||||||
|
|
||||||
istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
|
istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
|
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
|
||||||
@ -4737,11 +4583,6 @@ js2xmlparser@^4.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
xmlcreate "^2.0.4"
|
xmlcreate "^2.0.4"
|
||||||
|
|
||||||
jsbn@~0.1.0:
|
|
||||||
version "0.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
|
||||||
integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==
|
|
||||||
|
|
||||||
jsdoc@^3.6.6:
|
jsdoc@^3.6.6:
|
||||||
version "3.6.11"
|
version "3.6.11"
|
||||||
resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.6.11.tgz#8bbb5747e6f579f141a5238cbad4e95e004458ce"
|
resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.6.11.tgz#8bbb5747e6f579f141a5238cbad4e95e004458ce"
|
||||||
@ -4783,21 +4624,11 @@ json-schema-traverse@^0.4.1:
|
|||||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||||
|
|
||||||
json-schema@0.4.0:
|
|
||||||
version "0.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5"
|
|
||||||
integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==
|
|
||||||
|
|
||||||
json-stable-stringify-without-jsonify@^1.0.1:
|
json-stable-stringify-without-jsonify@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
||||||
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
|
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
|
||||||
|
|
||||||
json-stringify-safe@~5.0.1:
|
|
||||||
version "5.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
|
||||||
integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
|
|
||||||
|
|
||||||
json5@^1.0.1:
|
json5@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
|
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
|
||||||
@ -4815,16 +4646,6 @@ jsonparse@^1.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
|
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
|
||||||
integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
|
integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
|
||||||
|
|
||||||
jsprim@^1.2.2:
|
|
||||||
version "1.4.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb"
|
|
||||||
integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==
|
|
||||||
dependencies:
|
|
||||||
assert-plus "1.0.0"
|
|
||||||
extsprintf "1.3.0"
|
|
||||||
json-schema "0.4.0"
|
|
||||||
verror "1.10.0"
|
|
||||||
|
|
||||||
jstransformer@1.0.0:
|
jstransformer@1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3"
|
resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3"
|
||||||
@ -5034,10 +4855,10 @@ matrix-events-sdk@^0.0.1-beta.7:
|
|||||||
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1-beta.7.tgz#5ffe45eba1f67cc8d7c2377736c728b322524934"
|
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1-beta.7.tgz#5ffe45eba1f67cc8d7c2377736c728b322524934"
|
||||||
integrity sha512-9jl4wtWanUFSy2sr2lCjErN/oC8KTAtaeaozJtrgot1JiQcEI4Rda9OLgQ7nLKaqb4Z/QUx/fR3XpDzm5Jy1JA==
|
integrity sha512-9jl4wtWanUFSy2sr2lCjErN/oC8KTAtaeaozJtrgot1JiQcEI4Rda9OLgQ7nLKaqb4Z/QUx/fR3XpDzm5Jy1JA==
|
||||||
|
|
||||||
matrix-mock-request@^2.1.2:
|
matrix-mock-request@^2.5.0:
|
||||||
version "2.4.1"
|
version "2.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/matrix-mock-request/-/matrix-mock-request-2.4.1.tgz#a9c7dbb6b466f582ba2ca21f17cf18ceb41c7657"
|
resolved "https://registry.yarnpkg.com/matrix-mock-request/-/matrix-mock-request-2.5.0.tgz#78da2590e82be2e31edcf9814833af5e5f8d2f1a"
|
||||||
integrity sha512-QMNpKUeHS2RHovSKybUySFTXTJ11EQPkp3bgvEXmNqAc3TYM23gKYqgI288BoBDYwQrK3WJFT0d4bvMiNIS/vA==
|
integrity sha512-7T3gklpW+4rfHsTnp/FDML7aWoBrXhAh8+1ltinQfAh9TDj6y382z/RUMR7i03d1WDzt/ed1UTihqO5GDoOq9Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
expect "^28.1.0"
|
expect "^28.1.0"
|
||||||
|
|
||||||
@ -5095,18 +4916,6 @@ miller-rabin@^4.0.0:
|
|||||||
bn.js "^4.0.0"
|
bn.js "^4.0.0"
|
||||||
brorand "^1.0.1"
|
brorand "^1.0.1"
|
||||||
|
|
||||||
mime-db@1.52.0:
|
|
||||||
version "1.52.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
|
||||||
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
|
||||||
|
|
||||||
mime-types@^2.1.12, mime-types@~2.1.19:
|
|
||||||
version "2.1.35"
|
|
||||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
|
||||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
|
||||||
dependencies:
|
|
||||||
mime-db "1.52.0"
|
|
||||||
|
|
||||||
mimic-fn@^2.1.0:
|
mimic-fn@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||||
@ -5244,11 +5053,6 @@ npm-run-path@^4.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
path-key "^3.0.0"
|
path-key "^3.0.0"
|
||||||
|
|
||||||
oauth-sign@~0.9.0:
|
|
||||||
version "0.9.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
|
||||||
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
|
|
||||||
|
|
||||||
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
@ -5469,11 +5273,6 @@ pbkdf2@^3.0.3:
|
|||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
sha.js "^2.4.8"
|
sha.js "^2.4.8"
|
||||||
|
|
||||||
performance-now@^2.1.0:
|
|
||||||
version "2.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
|
||||||
integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
|
|
||||||
|
|
||||||
picocolors@^1.0.0:
|
picocolors@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||||
@ -5576,11 +5375,6 @@ pseudomap@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
|
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
|
||||||
integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==
|
integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==
|
||||||
|
|
||||||
psl@^1.1.28:
|
|
||||||
version "1.9.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
|
|
||||||
integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
|
|
||||||
|
|
||||||
public-encrypt@^4.0.0:
|
public-encrypt@^4.0.0:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
|
resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
|
||||||
@ -5720,11 +5514,6 @@ qs@^6.9.6:
|
|||||||
dependencies:
|
dependencies:
|
||||||
side-channel "^1.0.4"
|
side-channel "^1.0.4"
|
||||||
|
|
||||||
qs@~6.5.2:
|
|
||||||
version "6.5.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
|
|
||||||
integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
|
|
||||||
|
|
||||||
querystring-es3@~0.2.0:
|
querystring-es3@~0.2.0:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
||||||
@ -5924,32 +5713,6 @@ repeat-string@^1.5.2:
|
|||||||
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
|
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
|
||||||
integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==
|
integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==
|
||||||
|
|
||||||
request@^2.88.2:
|
|
||||||
version "2.88.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
|
|
||||||
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
|
|
||||||
dependencies:
|
|
||||||
aws-sign2 "~0.7.0"
|
|
||||||
aws4 "^1.8.0"
|
|
||||||
caseless "~0.12.0"
|
|
||||||
combined-stream "~1.0.6"
|
|
||||||
extend "~3.0.2"
|
|
||||||
forever-agent "~0.6.1"
|
|
||||||
form-data "~2.3.2"
|
|
||||||
har-validator "~5.1.3"
|
|
||||||
http-signature "~1.2.0"
|
|
||||||
is-typedarray "~1.0.0"
|
|
||||||
isstream "~0.1.2"
|
|
||||||
json-stringify-safe "~5.0.1"
|
|
||||||
mime-types "~2.1.19"
|
|
||||||
oauth-sign "~0.9.0"
|
|
||||||
performance-now "^2.1.0"
|
|
||||||
qs "~6.5.2"
|
|
||||||
safe-buffer "^5.1.2"
|
|
||||||
tough-cookie "~2.5.0"
|
|
||||||
tunnel-agent "^0.6.0"
|
|
||||||
uuid "^3.3.2"
|
|
||||||
|
|
||||||
require-directory@^2.1.1:
|
require-directory@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
||||||
@ -6051,7 +5814,7 @@ safe-regex-test@^1.0.0:
|
|||||||
get-intrinsic "^1.1.3"
|
get-intrinsic "^1.1.3"
|
||||||
is-regex "^1.1.4"
|
is-regex "^1.1.4"
|
||||||
|
|
||||||
safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
safer-buffer@^2.1.0:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
@ -6189,21 +5952,6 @@ sprintf-js@~1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||||
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
|
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
|
||||||
|
|
||||||
sshpk@^1.7.0:
|
|
||||||
version "1.17.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.17.0.tgz#578082d92d4fe612b13007496e543fa0fbcbe4c5"
|
|
||||||
integrity sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==
|
|
||||||
dependencies:
|
|
||||||
asn1 "~0.2.3"
|
|
||||||
assert-plus "^1.0.0"
|
|
||||||
bcrypt-pbkdf "^1.0.0"
|
|
||||||
dashdash "^1.12.0"
|
|
||||||
ecc-jsbn "~0.1.1"
|
|
||||||
getpass "^0.1.1"
|
|
||||||
jsbn "~0.1.0"
|
|
||||||
safer-buffer "^2.0.2"
|
|
||||||
tweetnacl "~0.14.0"
|
|
||||||
|
|
||||||
stack-utils@^2.0.3:
|
stack-utils@^2.0.3:
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5"
|
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5"
|
||||||
@ -6506,14 +6254,6 @@ token-stream@0.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/token-stream/-/token-stream-0.0.1.tgz#ceeefc717a76c4316f126d0b9dbaa55d7e7df01a"
|
resolved "https://registry.yarnpkg.com/token-stream/-/token-stream-0.0.1.tgz#ceeefc717a76c4316f126d0b9dbaa55d7e7df01a"
|
||||||
integrity sha512-nfjOAu/zAWmX9tgwi5NRp7O7zTDUD1miHiB40klUnAh9qnL1iXdgzcz/i5dMaL5jahcBAaSfmNOBBJBLJW8TEg==
|
integrity sha512-nfjOAu/zAWmX9tgwi5NRp7O7zTDUD1miHiB40klUnAh9qnL1iXdgzcz/i5dMaL5jahcBAaSfmNOBBJBLJW8TEg==
|
||||||
|
|
||||||
tough-cookie@~2.5.0:
|
|
||||||
version "2.5.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
|
|
||||||
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
|
|
||||||
dependencies:
|
|
||||||
psl "^1.1.28"
|
|
||||||
punycode "^2.1.1"
|
|
||||||
|
|
||||||
tr46@^2.1.0:
|
tr46@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240"
|
resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240"
|
||||||
@ -6585,23 +6325,11 @@ tty-browserify@0.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811"
|
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811"
|
||||||
integrity sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==
|
integrity sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==
|
||||||
|
|
||||||
tunnel-agent@^0.6.0:
|
|
||||||
version "0.6.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
|
||||||
integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==
|
|
||||||
dependencies:
|
|
||||||
safe-buffer "^5.0.1"
|
|
||||||
|
|
||||||
tunnel@^0.0.6:
|
tunnel@^0.0.6:
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
|
||||||
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
|
||||||
|
|
||||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
|
||||||
version "0.14.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
|
||||||
integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==
|
|
||||||
|
|
||||||
type-check@^0.4.0, type-check@~0.4.0:
|
type-check@^0.4.0, type-check@~0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||||
@ -6794,11 +6522,6 @@ util@~0.12.0:
|
|||||||
safe-buffer "^5.1.2"
|
safe-buffer "^5.1.2"
|
||||||
which-typed-array "^1.1.2"
|
which-typed-array "^1.1.2"
|
||||||
|
|
||||||
uuid@^3.3.2:
|
|
||||||
version "3.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
|
||||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
|
||||||
|
|
||||||
uuid@^8.3.2:
|
uuid@^8.3.2:
|
||||||
version "8.3.2"
|
version "8.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||||
@ -6813,15 +6536,6 @@ v8-to-istanbul@^9.0.0, v8-to-istanbul@^9.0.1:
|
|||||||
"@types/istanbul-lib-coverage" "^2.0.1"
|
"@types/istanbul-lib-coverage" "^2.0.1"
|
||||||
convert-source-map "^1.6.0"
|
convert-source-map "^1.6.0"
|
||||||
|
|
||||||
verror@1.10.0:
|
|
||||||
version "1.10.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
|
||||||
integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==
|
|
||||||
dependencies:
|
|
||||||
assert-plus "^1.0.0"
|
|
||||||
core-util-is "1.0.2"
|
|
||||||
extsprintf "^1.2.0"
|
|
||||||
|
|
||||||
vm-browserify@^1.0.0:
|
vm-browserify@^1.0.0:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
||||||
@ -6885,6 +6599,11 @@ webidl-conversions@^6.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
|
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
|
||||||
integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
|
integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
|
||||||
|
|
||||||
|
webidl-conversions@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
|
||||||
|
integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
|
||||||
|
|
||||||
whatwg-url@^5.0.0:
|
whatwg-url@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
|
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
|
||||||
|
Reference in New Issue
Block a user