diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index bd084d09..0535abdd 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -1593,14 +1593,22 @@ export class MatrixClient extends EventEmitter { await this.sendStateEvent(roomId, "m.room.power_levels", "", currentLevels); } + private async getMediaEndpointPrefix() { + if (await this.doesServerSupportVersion('v1.11')) { + return `${this.homeserverUrl}/_matrix/client/v1/media`; + } + return `${this.homeserverUrl}/_matrix/media/v3`; + } + /** * Converts a MXC URI to an HTTP URL. * @param {string} mxc The MXC URI to convert * @returns {string} The HTTP URL for the content. */ - public mxcToHttp(mxc: string): string { + public async mxcToHttp(mxc: string): Promise { const { domain, mediaId } = MXCUrl.parse(mxc); - return `${this.homeserverUrl}/_matrix/media/v3/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`; + const endpoint = await this.getMediaEndpointPrefix(); + return `${endpoint}/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`; } /** @@ -1611,9 +1619,9 @@ export class MatrixClient extends EventEmitter { * @param {"crop"|"scale"} method Whether to crop or scale (preserve aspect ratio) the content. * @returns {string} The HTTP URL for the downsized content. */ - public mxcToHttpThumbnail(mxc: string, width: number, height: number, method: "crop" | "scale"): string { - const downloadUri = this.mxcToHttp(mxc); - return downloadUri.replace("/_matrix/media/v3/download", "/_matrix/media/v3/thumbnail") + public async mxcToHttpThumbnail(mxc: string, width: number, height: number, method: "crop" | "scale"): Promise { + const downloadUri = await this.mxcToHttp(mxc); + return downloadUri.replace("/download", "/thumbnail") + `?width=${width}&height=${height}&method=${encodeURIComponent(method)}`; } @@ -1626,9 +1634,10 @@ export class MatrixClient extends EventEmitter { * @returns {Promise} resolves to the MXC URI of the content */ @timedMatrixClientFunctionCall() - public uploadContent(data: Buffer, contentType = "application/octet-stream", filename: string = null): Promise { + public async uploadContent(data: Buffer, contentType = "application/octet-stream", filename: string = null): Promise { // TODO: Make doRequest take an object for options - return this.doRequest("POST", "/_matrix/media/v3/upload", { filename: filename }, data, 60000, false, contentType) + const endpoint = await this.getMediaEndpointPrefix(); + return this.doRequest("POST", `${endpoint}/upload`, { filename: filename }, data, 60000, false, contentType) .then(response => response["content_uri"]); } @@ -1646,8 +1655,9 @@ export class MatrixClient extends EventEmitter { if (this.contentScannerInstance) { return this.contentScannerInstance.downloadContent(mxcUrl); } + const endpoint = await this.getMediaEndpointPrefix(); const { domain, mediaId } = MXCUrl.parse(mxcUrl); - const path = `/_matrix/media/v3/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`; + const path = `${endpoint}/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`; const res = await this.doRequest("GET", path, { allow_remote: allowRemote }, null, null, true, null, true); return { data: res.body, diff --git a/test/MatrixClientTest.ts b/test/MatrixClientTest.ts index 99c3c404..0edd5e3a 100644 --- a/test/MatrixClientTest.ts +++ b/test/MatrixClientTest.ts @@ -305,7 +305,7 @@ describe('MatrixClient', () => { describe('getServerVersions', () => { it('should call the right endpoint', async () => { - const { client, http } = createTestClient(); + const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: true, precacheVersions: false }); const versionsResponse: ServerVersions = { unstable_features: { @@ -322,7 +322,7 @@ describe('MatrixClient', () => { }); it('should cache the response', async () => { - const { client, http } = createTestClient(); + const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: true, precacheVersions: false }); const versionsResponse: ServerVersions = { unstable_features: { @@ -358,7 +358,7 @@ describe('MatrixClient', () => { [{ "org.example.wrong": true }, "org.example.feature", false], [{ "org.example.wrong": false }, "org.example.feature", false], ])("should find that %p has %p as %p", async (versions, flag, target) => { - const { client, http } = createTestClient(); + const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: true, precacheVersions: false }); const versionsResponse: ServerVersions = { versions: ["v1.1"], @@ -378,7 +378,7 @@ describe('MatrixClient', () => { [["v1.2"], "v1.1", false], [["v1.1", "v1.2", "v1.3"], "v1.2", true], ])("should find that %p has %p as %p", async (versions, version, target) => { - const { client, http } = createTestClient(); + const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: true, precacheVersions: false }); const versionsResponse: ServerVersions = { versions: versions, @@ -397,7 +397,7 @@ describe('MatrixClient', () => { [["v1.3"], ["v1.1", "v1.2"], false], [["v1.1", "v1.2", "v1.3"], ["v1.2", "v1.3"], true], ])("should find that %p has %p as %p", async (versions, searchVersions, target) => { - const { client, http } = createTestClient(); + const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: true, precacheVersions: false }); const versionsResponse: ServerVersions = { versions: versions, @@ -5646,8 +5646,8 @@ describe('MatrixClient', () => { const mediaId = "testing/val"; const mxc = `mxc://${domain}/${mediaId}`; - const http = client.mxcToHttp(mxc); - expect(http).toBe(`${hsUrl}/_matrix/media/v3/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`); + const http = await client.mxcToHttp(mxc); + expect(http).toBe(`${hsUrl}/_matrix/client/v1/media/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`); }); it('should error for non-MXC URIs', async () => { @@ -5658,7 +5658,7 @@ describe('MatrixClient', () => { const mxc = `https://${domain}/${mediaId}`; try { - client.mxcToHttp(mxc); + await client.mxcToHttp(mxc); // noinspection ExceptionCaughtLocallyJS throw new Error("Expected an error and didn't get one"); @@ -5679,9 +5679,9 @@ describe('MatrixClient', () => { const method = "scale"; const mxc = `mxc://${domain}/${mediaId}`; - const http = client.mxcToHttpThumbnail(mxc, width, height, method); + const http = await client.mxcToHttpThumbnail(mxc, width, height, method); // eslint-disable-next-line max-len - expect(http).toBe(`${hsUrl}/_matrix/media/v3/thumbnail/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}?width=${width}&height=${height}&method=${encodeURIComponent(method)}`); + expect(http).toBe(`${hsUrl}/_matrix/client/v1/media/thumbnail/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}?width=${width}&height=${height}&method=${encodeURIComponent(method)}`); }); it('should error for non-MXC URIs', async () => { @@ -5695,7 +5695,7 @@ describe('MatrixClient', () => { const mxc = `https://${domain}/${mediaId}`; try { - client.mxcToHttpThumbnail(mxc, width, height, method); + await client.mxcToHttpThumbnail(mxc, width, height, method); // noinspection ExceptionCaughtLocallyJS throw new Error("Expected an error and didn't get one"); @@ -5717,7 +5717,7 @@ describe('MatrixClient', () => { Buffer.isBuffer = (i => i === data); // noinspection TypeScriptValidateJSTypes - http.when("POST", "/_matrix/media/v3/upload").respond(200, (path, content, req) => { + http.when("POST", "/_matrix/client/v1/media/upload").respond(200, (path, content, req) => { expect(content).toBeDefined(); expect(req.queryParams.filename).toEqual(filename); expect(req.headers["Content-Type"]).toEqual(contentType); @@ -5740,7 +5740,7 @@ describe('MatrixClient', () => { Buffer.isBuffer = (i => i === data); // noinspection TypeScriptValidateJSTypes - http.when("POST", "/_matrix/media/v3/upload").respond(200, (path, content, req) => { + http.when("POST", "/_matrix/client/v1/media/upload").respond(200, (path, content, req) => { expect(content).toBeDefined(); expect(req.queryParams.filename).toEqual(filename); expect(req.headers["Content-Type"]).toEqual(contentType); @@ -5761,8 +5761,8 @@ describe('MatrixClient', () => { // const fileContents = Buffer.from("12345"); // noinspection TypeScriptValidateJSTypes - http.when("GET", "/_matrix/media/v3/download/").respond(200, (path, _, req) => { - expect(path).toContain("/_matrix/media/v3/download/" + urlPart); + http.when("GET", "/_matrix/client/v1/media/download/").respond(200, (path, _, req) => { + expect(path).toContain("/_matrix/client/v1/media/download/" + urlPart); expect((req as any).opts.encoding).toEqual(null); // TODO: Honestly, I have no idea how to coerce the mock library to return headers or buffers, // so this is left as a fun activity. @@ -5798,7 +5798,7 @@ describe('MatrixClient', () => { }); // noinspection TypeScriptValidateJSTypes - http.when("POST", "/_matrix/media/v3/upload").respond(200, (path, content, req) => { + http.when("POST", "/_matrix/client/v1/media/upload").respond(200, (path, content, req) => { expect(content).toBeDefined(); // HACK: We know the mock library will return JSON expect(req.headers["Content-Type"]).toEqual("application/json"); diff --git a/test/TestUtils.ts b/test/TestUtils.ts index df748d64..833f0305 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -2,7 +2,7 @@ import * as tmp from "tmp"; import HttpBackend from "matrix-mock-request"; import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; -import { IStorageProvider, MatrixClient, OTKAlgorithm, RustSdkCryptoStorageProvider, UnpaddedBase64, setRequestFn } from "../src"; +import { IStorageProvider, MatrixClient, OTKAlgorithm, RustSdkCryptoStorageProvider, ServerVersions, UnpaddedBase64, setRequestFn } from "../src"; export const TEST_DEVICE_ID = "TEST_DEVICE"; @@ -31,13 +31,18 @@ export function createTestClient( storage: IStorageProvider = null, userId: string = null, cryptoStoreType?: StoreType, - opts = { handleWhoAmI: true }, + opts?: Partial<{ handleWhoAmI: boolean, precacheVersions: boolean }>, ): { client: MatrixClient; http: HttpBackend; hsUrl: string; accessToken: string; } { + opts = { + handleWhoAmI: true, + precacheVersions: true, + ...opts, + }; const http = new HttpBackend(); const hsUrl = "https://localhost"; const accessToken = "s3cret"; @@ -45,6 +50,15 @@ export function createTestClient( (client).userId = userId; // private member access setRequestFn(http.requestFn); + // Force versions + if (opts.precacheVersions) { + (client).cachedVersions = { + unstable_features: { }, + versions: ["v1.11"], + } as ServerVersions; + (client).versionsLastFetched = Date.now(); + } + if (opts.handleWhoAmI) { // Ensure we always respond to a whoami client.getWhoAmI = () => Promise.resolve({ user_id: userId, device_id: TEST_DEVICE_ID });