From b4ee19e3826ae61c14669f390112cae066e10ad0 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 30 Apr 2024 16:44:08 -0600 Subject: [PATCH 1/2] Add helpers for authenticated media, and associated documentation --- README.md | 32 ++++++++++++++++++++++++++++++++ spec/unit/content-repo.spec.ts | 20 ++++++++++++++++++++ spec/unit/matrix-client.spec.ts | 3 +++ src/client.ts | 10 ++++++++-- src/content-repo.ts | 32 +++++++++++++++++++++++++++++--- 5 files changed, 92 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0466b88014e..0e48bdc353f 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,10 @@ endpoints from before Matrix 1.1, for example. # Quickstart +> [!IMPORTANT] +> Servers may require or use authenticated endpoints for media (images, files, avatars, etc). See the +> [Authenticated Media](#authenticated-media) section for information on how to enable support for this. + 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. @@ -89,6 +93,34 @@ Object.keys(client.store.rooms).forEach((roomId) => { }); ``` +## Authenticated media + +Servers supporting [MSC3916](https://github.com/matrix-org/matrix-spec-proposals/pull/3916) will require clients, like +yours, to include an `Authorization` header when `/download`ing or `/thumbnail`ing media. For NodeJS environments this +may be as easy as the following code snippet, though web browsers may need to use [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) +to append the header when using the endpoints in `` elements and similar. + +```javascript +const downloadUrl = client.mxcUrlToHttp( + /*mxcUrl=*/"mxc://example.org/abc123", // the MXC URI to download/thumbnail, typically from an event or profile + /*width=*/undefined, // part of the thumbnail API. Use as required. + /*height=*/undefined, // part of the thumbnail API. Use as required. + /*resizeMethod=*/undefined, // part of the thumbnail API. Use as required. + /*allowDirectLinks=*/false, // should generally be left `false`. + /*allowRedirects=*/true, // implied supported with authentication + /*useAuthentication=*/true, // the flag we're after in this example +); +const img = await fetch(downloadUrl, { + headers: { + 'Authorization': `Bearer ${client.getAccessToken()}`, + }, +}); +// Do something with `img`. +``` + +> [!WARNING] +> In future the js-sdk will *only* return authentication-required URLs, mandating population of the `Authorization` header. + ## What does this SDK do? This SDK provides a full object model around the Matrix Client-Server API and emits diff --git a/spec/unit/content-repo.spec.ts b/spec/unit/content-repo.spec.ts index 33eeab12d5e..63e8494c58e 100644 --- a/spec/unit/content-repo.spec.ts +++ b/spec/unit/content-repo.spec.ts @@ -76,5 +76,25 @@ describe("ContentRepo", function () { baseUrl + "/_matrix/media/v3/download/server.name/resourceid#automade", ); }); + + it("should return an authenticated URL when requested", function () { + const mxcUri = "mxc://server.name/resourceid"; + expect(getHttpUriForMxc(baseUrl, mxcUri, undefined, undefined, undefined, undefined, true, true)).toEqual( + baseUrl + "/_matrix/client/unstable/org.matrix.msc3916/media/download/server.name/resourceid?allow_redirect=true", + ); + expect(getHttpUriForMxc(baseUrl, mxcUri, 64, 64, 'scale', undefined, true, true)).toEqual( + baseUrl + "/_matrix/client/unstable/org.matrix.msc3916/media/thumbnail/server.name/resourceid?width=64&height=64&method=scale&allow_redirect=true", + ); + }); + + it("should force-enable allow_redirects when useAuthentication is set true", function () { + const mxcUri = "mxc://server.name/resourceid"; + expect(getHttpUriForMxc(baseUrl, mxcUri, undefined, undefined, undefined, undefined, false, true)).toEqual( + baseUrl + "/_matrix/client/unstable/org.matrix.msc3916/media/download/server.name/resourceid?allow_redirect=true", + ); + expect(getHttpUriForMxc(baseUrl, mxcUri, 64, 64, 'scale', undefined, false, true)).toEqual( + baseUrl + "/_matrix/client/unstable/org.matrix.msc3916/media/thumbnail/server.name/resourceid?width=64&height=64&method=scale&allow_redirect=true", + ); + }); }); }); diff --git a/spec/unit/matrix-client.spec.ts b/spec/unit/matrix-client.spec.ts index 62b479f89e4..b83e841fcf6 100644 --- a/spec/unit/matrix-client.spec.ts +++ b/spec/unit/matrix-client.spec.ts @@ -386,6 +386,9 @@ describe("MatrixClient", function () { expect(client.mxcUrlToHttp(mxc, 32, 46, "scale", false, true)).toBe( getHttpUriForMxc(client.baseUrl, mxc, 32, 46, "scale", false, true), ); + expect(client.mxcUrlToHttp(mxc, 32, 46, "scale", false, true, true)).toBe( + getHttpUriForMxc(client.baseUrl, mxc, 32, 46, "scale", false, true, true), + ); }); }); diff --git a/src/client.ts b/src/client.ts index 64e6855ea79..3f4757eeb42 100644 --- a/src/client.ts +++ b/src/client.ts @@ -5775,7 +5775,12 @@ export class MatrixClient extends TypedEventEmitter = {}; if (width) { @@ -68,7 +89,12 @@ export function getHttpUriForMxc( if (Object.keys(params).length > 0) { // these are thumbnailing params so they probably want the // thumbnailing API... - prefix = "/_matrix/media/v3/thumbnail/"; + if (useAuthentication) { + // TODO: Use stable once available (requires FCP on MSC3916). + prefix = "/_matrix/client/unstable/org.matrix.msc3916/media/thumbnail/"; + } else { + prefix = "/_matrix/media/v3/thumbnail/"; + } } if (typeof allowRedirects === "boolean") { From f33476729c970bb68d461424f2fc44ca06e5fbbd Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 30 Apr 2024 16:48:38 -0600 Subject: [PATCH 2/2] Appease the linter --- README.md | 18 +++++++++--------- spec/unit/content-repo.spec.ts | 16 ++++++++++------ src/client.ts | 11 ++++++++++- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 0e48bdc353f..f958333276d 100644 --- a/README.md +++ b/README.md @@ -102,24 +102,24 @@ to append the header when using the endpoints in `` elements and similar. ```javascript const downloadUrl = client.mxcUrlToHttp( - /*mxcUrl=*/"mxc://example.org/abc123", // the MXC URI to download/thumbnail, typically from an event or profile - /*width=*/undefined, // part of the thumbnail API. Use as required. - /*height=*/undefined, // part of the thumbnail API. Use as required. - /*resizeMethod=*/undefined, // part of the thumbnail API. Use as required. - /*allowDirectLinks=*/false, // should generally be left `false`. - /*allowRedirects=*/true, // implied supported with authentication - /*useAuthentication=*/true, // the flag we're after in this example + /*mxcUrl=*/ "mxc://example.org/abc123", // the MXC URI to download/thumbnail, typically from an event or profile + /*width=*/ undefined, // part of the thumbnail API. Use as required. + /*height=*/ undefined, // part of the thumbnail API. Use as required. + /*resizeMethod=*/ undefined, // part of the thumbnail API. Use as required. + /*allowDirectLinks=*/ false, // should generally be left `false`. + /*allowRedirects=*/ true, // implied supported with authentication + /*useAuthentication=*/ true, // the flag we're after in this example ); const img = await fetch(downloadUrl, { headers: { - 'Authorization': `Bearer ${client.getAccessToken()}`, + Authorization: `Bearer ${client.getAccessToken()}`, }, }); // Do something with `img`. ``` > [!WARNING] -> In future the js-sdk will *only* return authentication-required URLs, mandating population of the `Authorization` header. +> In future the js-sdk will _only_ return authentication-required URLs, mandating population of the `Authorization` header. ## What does this SDK do? diff --git a/spec/unit/content-repo.spec.ts b/spec/unit/content-repo.spec.ts index 63e8494c58e..e0f0b5e0c76 100644 --- a/spec/unit/content-repo.spec.ts +++ b/spec/unit/content-repo.spec.ts @@ -80,20 +80,24 @@ describe("ContentRepo", function () { it("should return an authenticated URL when requested", function () { const mxcUri = "mxc://server.name/resourceid"; expect(getHttpUriForMxc(baseUrl, mxcUri, undefined, undefined, undefined, undefined, true, true)).toEqual( - baseUrl + "/_matrix/client/unstable/org.matrix.msc3916/media/download/server.name/resourceid?allow_redirect=true", + baseUrl + + "/_matrix/client/unstable/org.matrix.msc3916/media/download/server.name/resourceid?allow_redirect=true", ); - expect(getHttpUriForMxc(baseUrl, mxcUri, 64, 64, 'scale', undefined, true, true)).toEqual( - baseUrl + "/_matrix/client/unstable/org.matrix.msc3916/media/thumbnail/server.name/resourceid?width=64&height=64&method=scale&allow_redirect=true", + expect(getHttpUriForMxc(baseUrl, mxcUri, 64, 64, "scale", undefined, true, true)).toEqual( + baseUrl + + "/_matrix/client/unstable/org.matrix.msc3916/media/thumbnail/server.name/resourceid?width=64&height=64&method=scale&allow_redirect=true", ); }); it("should force-enable allow_redirects when useAuthentication is set true", function () { const mxcUri = "mxc://server.name/resourceid"; expect(getHttpUriForMxc(baseUrl, mxcUri, undefined, undefined, undefined, undefined, false, true)).toEqual( - baseUrl + "/_matrix/client/unstable/org.matrix.msc3916/media/download/server.name/resourceid?allow_redirect=true", + baseUrl + + "/_matrix/client/unstable/org.matrix.msc3916/media/download/server.name/resourceid?allow_redirect=true", ); - expect(getHttpUriForMxc(baseUrl, mxcUri, 64, 64, 'scale', undefined, false, true)).toEqual( - baseUrl + "/_matrix/client/unstable/org.matrix.msc3916/media/thumbnail/server.name/resourceid?width=64&height=64&method=scale&allow_redirect=true", + expect(getHttpUriForMxc(baseUrl, mxcUri, 64, 64, "scale", undefined, false, true)).toEqual( + baseUrl + + "/_matrix/client/unstable/org.matrix.msc3916/media/thumbnail/server.name/resourceid?width=64&height=64&method=scale&allow_redirect=true", ); }); }); diff --git a/src/client.ts b/src/client.ts index 3f4757eeb42..bbaf8fd8f80 100644 --- a/src/client.ts +++ b/src/client.ts @@ -5792,7 +5792,16 @@ export class MatrixClient extends TypedEventEmitter