Skip to content

Commit 6ef32f7

Browse files
authored
Support authenticated media endpoints. (#49)
1 parent b3d2d64 commit 6ef32f7

File tree

3 files changed

+50
-26
lines changed

3 files changed

+50
-26
lines changed

src/MatrixClient.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,14 +1593,22 @@ export class MatrixClient extends EventEmitter {
15931593
await this.sendStateEvent(roomId, "m.room.power_levels", "", currentLevels);
15941594
}
15951595

1596+
private async getMediaEndpointPrefix() {
1597+
if (await this.doesServerSupportVersion('v1.11')) {
1598+
return `${this.homeserverUrl}/_matrix/client/v1/media`;
1599+
}
1600+
return `${this.homeserverUrl}/_matrix/media/v3`;
1601+
}
1602+
15961603
/**
15971604
* Converts a MXC URI to an HTTP URL.
15981605
* @param {string} mxc The MXC URI to convert
15991606
* @returns {string} The HTTP URL for the content.
16001607
*/
1601-
public mxcToHttp(mxc: string): string {
1608+
public async mxcToHttp(mxc: string): Promise<string> {
16021609
const { domain, mediaId } = MXCUrl.parse(mxc);
1603-
return `${this.homeserverUrl}/_matrix/media/v3/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`;
1610+
const endpoint = await this.getMediaEndpointPrefix();
1611+
return `${endpoint}/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`;
16041612
}
16051613

16061614
/**
@@ -1611,9 +1619,9 @@ export class MatrixClient extends EventEmitter {
16111619
* @param {"crop"|"scale"} method Whether to crop or scale (preserve aspect ratio) the content.
16121620
* @returns {string} The HTTP URL for the downsized content.
16131621
*/
1614-
public mxcToHttpThumbnail(mxc: string, width: number, height: number, method: "crop" | "scale"): string {
1615-
const downloadUri = this.mxcToHttp(mxc);
1616-
return downloadUri.replace("/_matrix/media/v3/download", "/_matrix/media/v3/thumbnail")
1622+
public async mxcToHttpThumbnail(mxc: string, width: number, height: number, method: "crop" | "scale"): Promise<string> {
1623+
const downloadUri = await this.mxcToHttp(mxc);
1624+
return downloadUri.replace("/download", "/thumbnail")
16171625
+ `?width=${width}&height=${height}&method=${encodeURIComponent(method)}`;
16181626
}
16191627

@@ -1626,9 +1634,10 @@ export class MatrixClient extends EventEmitter {
16261634
* @returns {Promise<string>} resolves to the MXC URI of the content
16271635
*/
16281636
@timedMatrixClientFunctionCall()
1629-
public uploadContent(data: Buffer, contentType = "application/octet-stream", filename: string = null): Promise<string> {
1637+
public async uploadContent(data: Buffer, contentType = "application/octet-stream", filename: string = null): Promise<string> {
16301638
// TODO: Make doRequest take an object for options
1631-
return this.doRequest("POST", "/_matrix/media/v3/upload", { filename: filename }, data, 60000, false, contentType)
1639+
const endpoint = await this.getMediaEndpointPrefix();
1640+
return this.doRequest("POST", `${endpoint}/upload`, { filename: filename }, data, 60000, false, contentType)
16321641
.then(response => response["content_uri"]);
16331642
}
16341643

@@ -1646,8 +1655,9 @@ export class MatrixClient extends EventEmitter {
16461655
if (this.contentScannerInstance) {
16471656
return this.contentScannerInstance.downloadContent(mxcUrl);
16481657
}
1658+
const endpoint = await this.getMediaEndpointPrefix();
16491659
const { domain, mediaId } = MXCUrl.parse(mxcUrl);
1650-
const path = `/_matrix/media/v3/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`;
1660+
const path = `${endpoint}/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`;
16511661
const res = await this.doRequest("GET", path, { allow_remote: allowRemote }, null, null, true, null, true);
16521662
return {
16531663
data: res.body,

test/MatrixClientTest.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ describe('MatrixClient', () => {
305305

306306
describe('getServerVersions', () => {
307307
it('should call the right endpoint', async () => {
308-
const { client, http } = createTestClient();
308+
const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: true, precacheVersions: false });
309309

310310
const versionsResponse: ServerVersions = {
311311
unstable_features: {
@@ -322,7 +322,7 @@ describe('MatrixClient', () => {
322322
});
323323

324324
it('should cache the response', async () => {
325-
const { client, http } = createTestClient();
325+
const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: true, precacheVersions: false });
326326

327327
const versionsResponse: ServerVersions = {
328328
unstable_features: {
@@ -358,7 +358,7 @@ describe('MatrixClient', () => {
358358
[{ "org.example.wrong": true }, "org.example.feature", false],
359359
[{ "org.example.wrong": false }, "org.example.feature", false],
360360
])("should find that %p has %p as %p", async (versions, flag, target) => {
361-
const { client, http } = createTestClient();
361+
const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: true, precacheVersions: false });
362362

363363
const versionsResponse: ServerVersions = {
364364
versions: ["v1.1"],
@@ -378,7 +378,7 @@ describe('MatrixClient', () => {
378378
[["v1.2"], "v1.1", false],
379379
[["v1.1", "v1.2", "v1.3"], "v1.2", true],
380380
])("should find that %p has %p as %p", async (versions, version, target) => {
381-
const { client, http } = createTestClient();
381+
const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: true, precacheVersions: false });
382382

383383
const versionsResponse: ServerVersions = {
384384
versions: versions,
@@ -397,7 +397,7 @@ describe('MatrixClient', () => {
397397
[["v1.3"], ["v1.1", "v1.2"], false],
398398
[["v1.1", "v1.2", "v1.3"], ["v1.2", "v1.3"], true],
399399
])("should find that %p has %p as %p", async (versions, searchVersions, target) => {
400-
const { client, http } = createTestClient();
400+
const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: true, precacheVersions: false });
401401

402402
const versionsResponse: ServerVersions = {
403403
versions: versions,
@@ -5646,8 +5646,8 @@ describe('MatrixClient', () => {
56465646
const mediaId = "testing/val";
56475647
const mxc = `mxc://${domain}/${mediaId}`;
56485648

5649-
const http = client.mxcToHttp(mxc);
5650-
expect(http).toBe(`${hsUrl}/_matrix/media/v3/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`);
5649+
const http = await client.mxcToHttp(mxc);
5650+
expect(http).toBe(`${hsUrl}/_matrix/client/v1/media/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`);
56515651
});
56525652

56535653
it('should error for non-MXC URIs', async () => {
@@ -5658,7 +5658,7 @@ describe('MatrixClient', () => {
56585658
const mxc = `https://${domain}/${mediaId}`;
56595659

56605660
try {
5661-
client.mxcToHttp(mxc);
5661+
await client.mxcToHttp(mxc);
56625662

56635663
// noinspection ExceptionCaughtLocallyJS
56645664
throw new Error("Expected an error and didn't get one");
@@ -5679,9 +5679,9 @@ describe('MatrixClient', () => {
56795679
const method = "scale";
56805680
const mxc = `mxc://${domain}/${mediaId}`;
56815681

5682-
const http = client.mxcToHttpThumbnail(mxc, width, height, method);
5682+
const http = await client.mxcToHttpThumbnail(mxc, width, height, method);
56835683
// eslint-disable-next-line max-len
5684-
expect(http).toBe(`${hsUrl}/_matrix/media/v3/thumbnail/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}?width=${width}&height=${height}&method=${encodeURIComponent(method)}`);
5684+
expect(http).toBe(`${hsUrl}/_matrix/client/v1/media/thumbnail/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}?width=${width}&height=${height}&method=${encodeURIComponent(method)}`);
56855685
});
56865686

56875687
it('should error for non-MXC URIs', async () => {
@@ -5695,7 +5695,7 @@ describe('MatrixClient', () => {
56955695
const mxc = `https://${domain}/${mediaId}`;
56965696

56975697
try {
5698-
client.mxcToHttpThumbnail(mxc, width, height, method);
5698+
await client.mxcToHttpThumbnail(mxc, width, height, method);
56995699

57005700
// noinspection ExceptionCaughtLocallyJS
57015701
throw new Error("Expected an error and didn't get one");
@@ -5717,7 +5717,7 @@ describe('MatrixClient', () => {
57175717
Buffer.isBuffer = <any>(i => i === data);
57185718

57195719
// noinspection TypeScriptValidateJSTypes
5720-
http.when("POST", "/_matrix/media/v3/upload").respond(200, (path, content, req) => {
5720+
http.when("POST", "/_matrix/client/v1/media/upload").respond(200, (path, content, req) => {
57215721
expect(content).toBeDefined();
57225722
expect(req.queryParams.filename).toEqual(filename);
57235723
expect(req.headers["Content-Type"]).toEqual(contentType);
@@ -5740,7 +5740,7 @@ describe('MatrixClient', () => {
57405740
Buffer.isBuffer = <any>(i => i === data);
57415741

57425742
// noinspection TypeScriptValidateJSTypes
5743-
http.when("POST", "/_matrix/media/v3/upload").respond(200, (path, content, req) => {
5743+
http.when("POST", "/_matrix/client/v1/media/upload").respond(200, (path, content, req) => {
57445744
expect(content).toBeDefined();
57455745
expect(req.queryParams.filename).toEqual(filename);
57465746
expect(req.headers["Content-Type"]).toEqual(contentType);
@@ -5761,8 +5761,8 @@ describe('MatrixClient', () => {
57615761
// const fileContents = Buffer.from("12345");
57625762

57635763
// noinspection TypeScriptValidateJSTypes
5764-
http.when("GET", "/_matrix/media/v3/download/").respond(200, (path, _, req) => {
5765-
expect(path).toContain("/_matrix/media/v3/download/" + urlPart);
5764+
http.when("GET", "/_matrix/client/v1/media/download/").respond(200, (path, _, req) => {
5765+
expect(path).toContain("/_matrix/client/v1/media/download/" + urlPart);
57665766
expect((req as any).opts.encoding).toEqual(null);
57675767
// TODO: Honestly, I have no idea how to coerce the mock library to return headers or buffers,
57685768
// so this is left as a fun activity.
@@ -5798,7 +5798,7 @@ describe('MatrixClient', () => {
57985798
});
57995799

58005800
// noinspection TypeScriptValidateJSTypes
5801-
http.when("POST", "/_matrix/media/v3/upload").respond(200, (path, content, req) => {
5801+
http.when("POST", "/_matrix/client/v1/media/upload").respond(200, (path, content, req) => {
58025802
expect(content).toBeDefined();
58035803
// HACK: We know the mock library will return JSON
58045804
expect(req.headers["Content-Type"]).toEqual("application/json");

test/TestUtils.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as tmp from "tmp";
22
import HttpBackend from "matrix-mock-request";
33
import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs";
44

5-
import { IStorageProvider, MatrixClient, OTKAlgorithm, RustSdkCryptoStorageProvider, UnpaddedBase64, setRequestFn } from "../src";
5+
import { IStorageProvider, MatrixClient, OTKAlgorithm, RustSdkCryptoStorageProvider, ServerVersions, UnpaddedBase64, setRequestFn } from "../src";
66

77
export const TEST_DEVICE_ID = "TEST_DEVICE";
88

@@ -31,20 +31,34 @@ export function createTestClient(
3131
storage: IStorageProvider = null,
3232
userId: string = null,
3333
cryptoStoreType?: StoreType,
34-
opts = { handleWhoAmI: true },
34+
opts?: Partial<{ handleWhoAmI: boolean, precacheVersions: boolean }>,
3535
): {
3636
client: MatrixClient;
3737
http: HttpBackend;
3838
hsUrl: string;
3939
accessToken: string;
4040
} {
41+
opts = {
42+
handleWhoAmI: true,
43+
precacheVersions: true,
44+
...opts,
45+
};
4146
const http = new HttpBackend();
4247
const hsUrl = "https://localhost";
4348
const accessToken = "s3cret";
4449
const client = new MatrixClient(hsUrl, accessToken, storage, (cryptoStoreType !== undefined) ? new RustSdkCryptoStorageProvider(tmp.dirSync().name, cryptoStoreType) : null);
4550
(<any>client).userId = userId; // private member access
4651
setRequestFn(http.requestFn);
4752

53+
// Force versions
54+
if (opts.precacheVersions) {
55+
(<any>client).cachedVersions = {
56+
unstable_features: { },
57+
versions: ["v1.11"],
58+
} as ServerVersions;
59+
(<any>client).versionsLastFetched = Date.now();
60+
}
61+
4862
if (opts.handleWhoAmI) {
4963
// Ensure we always respond to a whoami
5064
client.getWhoAmI = () => Promise.resolve({ user_id: userId, device_id: TEST_DEVICE_ID });

0 commit comments

Comments
 (0)