/share?msg=foo endpoint using forward message dialog (#29874)

* basic implementation of an /share?msg=foo endpoint

* SharePayload

* add sharing html & md while we're at it

* remove whitespace from imports to appease linter

* lint

* Add unit test

* More tests

* Test for showScreen

* Use one of the typed strings

* Test nasty tags stripped out

* Add playwright test

* Fix flake

by not relying on the name being synced as soon as we load

---------

Co-authored-by: David Baker <dbkr@users.noreply.github.com>
This commit is contained in:
Matthew Hodgson
2025-05-28 19:48:32 +01:00
committed by GitHub
parent e92bf78289
commit 013f5a0c91
5 changed files with 265 additions and 4 deletions

View File

@@ -68,6 +68,7 @@ import AutoDiscoveryUtils from "../../../../src/utils/AutoDiscoveryUtils";
import { type ValidatedServerConfig } from "../../../../src/utils/ValidatedServerConfig";
import Modal from "../../../../src/Modal.tsx";
import { SetupEncryptionStore } from "../../../../src/stores/SetupEncryptionStore.ts";
import { ShareFormat } from "../../../../src/dispatcher/payloads/SharePayload.ts";
import { clearStorage } from "../../../../src/Lifecycle";
import RoomListStore from "../../../../src/stores/room-list/RoomListStore.ts";
@@ -813,6 +814,108 @@ describe("<MatrixChat />", () => {
});
});
});
it("should open forward dialog when text message shared", async () => {
await getComponentAndWaitForReady();
defaultDispatcher.dispatch({ action: Action.Share, format: ShareFormat.Text, msg: "Hello world" });
await waitFor(() => {
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
action: Action.OpenForwardDialog,
event: expect.any(MatrixEvent),
permalinkCreator: null,
});
});
const forwardCall = mocked(defaultDispatcher.dispatch).mock.calls.find(
([call]) => call.action === Action.OpenForwardDialog,
);
const payload = forwardCall?.[0];
expect(payload!.event.getContent()).toEqual({
msgtype: MatrixJs.MsgType.Text,
body: "Hello world",
});
});
it("should open forward dialog when html message shared", async () => {
await getComponentAndWaitForReady();
defaultDispatcher.dispatch({ action: Action.Share, format: ShareFormat.Html, msg: "Hello world" });
await waitFor(() => {
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
action: Action.OpenForwardDialog,
event: expect.any(MatrixEvent),
permalinkCreator: null,
});
});
const forwardCall = mocked(defaultDispatcher.dispatch).mock.calls.find(
([call]) => call.action === Action.OpenForwardDialog,
);
const payload = forwardCall?.[0];
expect(payload!.event.getContent()).toEqual({
msgtype: MatrixJs.MsgType.Text,
format: "org.matrix.custom.html",
body: expect.stringContaining("Hello world"),
formatted_body: expect.stringContaining("Hello world"),
});
});
it("should open forward dialog when markdown message shared", async () => {
await getComponentAndWaitForReady();
defaultDispatcher.dispatch({
action: Action.Share,
format: ShareFormat.Markdown,
msg: "Hello *world*",
});
await waitFor(() => {
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
action: Action.OpenForwardDialog,
event: expect.any(MatrixEvent),
permalinkCreator: null,
});
});
const forwardCall = mocked(defaultDispatcher.dispatch).mock.calls.find(
([call]) => call.action === Action.OpenForwardDialog,
);
const payload = forwardCall?.[0];
expect(payload!.event.getContent()).toEqual({
msgtype: MatrixJs.MsgType.Text,
format: "org.matrix.custom.html",
body: "Hello *world*",
formatted_body: "Hello <em>world</em>",
});
});
it("should strip malicious tags from shared html message", async () => {
await getComponentAndWaitForReady();
defaultDispatcher.dispatch({
action: Action.Share,
format: ShareFormat.Html,
msg: `evil<script src="http://evil.dummy/bad.js" />`,
});
await waitFor(() => {
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
action: Action.OpenForwardDialog,
event: expect.any(MatrixEvent),
permalinkCreator: null,
});
});
const forwardCall = mocked(defaultDispatcher.dispatch).mock.calls.find(
([call]) => call.action === Action.OpenForwardDialog,
);
const payload = forwardCall?.[0];
expect(payload!.event.getContent()).toEqual({
msgtype: MatrixJs.MsgType.Text,
format: "org.matrix.custom.html",
body: "evil",
formatted_body: "evil",
});
});
});
describe("logout", () => {
@@ -1004,6 +1107,22 @@ describe("<MatrixChat />", () => {
} as any;
}
});
describe("showScreen", () => {
it("should show the 'share' screen", async () => {
await getComponent({
initialScreenAfterLogin: { screen: "share", params: { msg: "Hello", format: ShareFormat.Text } },
});
await waitFor(() => {
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
action: "share",
msg: "Hello",
format: ShareFormat.Text,
});
});
});
});
});
describe("with a soft-logged-out session", () => {