Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions desktop/src/shared/ui/link-preview-attachment.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { ResolvedLinkPreview } from "@/shared/lib/useResolvedLinkPreviews";
import { useLinkPreviewStyle } from "@/shared/lib/linkPreviewStylePreference";
import { rewriteRelayUrl } from "@/shared/lib/mediaUrl";
import { useMediaProxyPort } from "@/shared/lib/useMediaProxyPort";
import { CompactLinkPreviewAttachment } from "@/shared/ui/compact-link-preview-attachment";
import {
type LinkPreviewImageLightboxComponent,
Expand All @@ -21,6 +23,16 @@ export function LinkPreviewAttachment({
preview: ResolvedLinkPreview;
showControls?: boolean;
}) {
useMediaProxyPort();
const renderedPreview = {
...preview,
faviconDataUrl: preview.faviconDataUrl
? rewriteRelayUrl(preview.faviconDataUrl)
: null,
imageDataUrl: preview.imageDataUrl
? rewriteRelayUrl(preview.imageDataUrl)
: null,
};
const style = useLinkPreviewStyle();
if (style === "rich") {
return (
Expand All @@ -29,7 +41,7 @@ export function LinkPreviewAttachment({
ImageLightbox={ImageLightbox}
onOpen={onOpen}
onRemove={onRemove}
preview={preview}
preview={renderedPreview}
showControls={showControls}
/>
);
Expand All @@ -40,7 +52,7 @@ export function LinkPreviewAttachment({
className={className}
onOpen={onOpen}
onRemove={onRemove}
preview={preview}
preview={renderedPreview}
showControls={showControls}
/>
);
Expand Down
20 changes: 19 additions & 1 deletion desktop/src/testing/e2eBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ import type {
RawInstallRuntimeResult,
RuntimeFileConfigSubset,
} from "@/shared/api/tauri";
import {
ensureRelayOriginFetch,
resetMediaCaches,
} from "@/shared/lib/mediaUrl";
import { normalizePubkey } from "@/shared/lib/pubkey";
import {
isValidLinkPreviewSnapshotCanonicalUrl,
Expand Down Expand Up @@ -318,6 +322,8 @@ type E2eConfig = {
applyCommunityDelayMs?: number;
openDmDelayMs?: number;
sendMessageDelayMs?: number;
/** Hold the media proxy at port 0 until the E2E release seam is invoked. */
mediaProxyInitiallyUnavailable?: boolean;
/** Hold mock send live echoes until the E2E release seam is invoked. */
deferSendMessageLiveEcho?: boolean;
/** Close the first channel-window live REQ; its retry is accepted. */
Expand Down Expand Up @@ -1101,6 +1107,8 @@ declare global {
command: string;
payload: unknown;
}>;
/** Release a mock media proxy held at port 0 and return its ready port. */
__BUZZ_E2E_RELEASE_MEDIA_PROXY__?: () => number;
/** Release mock send events that were stored but withheld from live subscribers. */
__BUZZ_E2E_RELEASE_SEND_MESSAGE_LIVE_ECHO__?: () => number;
__BUZZ_E2E_EMIT_MEDIA_UPLOAD_PHASE__?: (input: {
Expand Down Expand Up @@ -1390,6 +1398,7 @@ const CHANNEL_WINDOW_AUX_DELETION_KINDS = new Set([
// in e2e (instead of the `buzz-media://` fallback). The reaction guard
// asserts against this exact port.
const MOCK_MEDIA_PROXY_PORT = 54321;
let mockMediaProxyPort = MOCK_MEDIA_PROXY_PORT;

// A relay-hosted custom emoji used by the reaction guard. Its URL matches
// `rewriteRelayUrl()`'s `/media/{64-hex}.{ext}` pattern on the relay origin, so
Expand Down Expand Up @@ -10131,6 +10140,15 @@ export function maybeInstallE2eTauriMocks() {
window.__BUZZ_E2E_COMMANDS__ = [];
window.__BUZZ_E2E_COMMAND_PAYLOADS__ = [];
window.__BUZZ_E2E_COMMAND_LOG__ = [];
mockMediaProxyPort = config.mock?.mediaProxyInitiallyUnavailable
? 0
: MOCK_MEDIA_PROXY_PORT;
window.__BUZZ_E2E_RELEASE_MEDIA_PROXY__ = () => {
mockMediaProxyPort = MOCK_MEDIA_PROXY_PORT;
resetMediaCaches();
ensureRelayOriginFetch();
return mockMediaProxyPort;
};
window.__BUZZ_E2E_EMIT_MOCK_HUDDLE_TTS_SPEAKER__ = (payload) =>
emit("huddle-tts-speaker-level", payload);
window.__BUZZ_E2E_SIGNED_EVENTS__ = [];
Expand Down Expand Up @@ -12691,7 +12709,7 @@ export function maybeInstallE2eTauriMocks() {
activeConfig,
);
case "get_media_proxy_port":
return MOCK_MEDIA_PROXY_PORT;
return mockMediaProxyPort;
case "pick_and_upload_media":
return await resolveMockUploadDescriptors(activeConfig);
case "pick_and_upload_image":
Expand Down
140 changes: 111 additions & 29 deletions desktop/tests/e2e/messaging.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,44 +280,63 @@ test.beforeEach(async ({ page }, testInfo) => {
"link-preview-image",
],
}
: testInfo.title.includes("link preview") ||
testInfo.title.includes("supported Compact")
: testInfo.title.includes(
"sent link preview media uses",
)
? {
mediaProxyInitiallyUnavailable: true,
linkPreviewMetadata: {
title: "Buzz pull request",
siteName: "GitHub",
description:
"A sender-authored preview snapshot.",
imageDataUrl: null,
imageDomain: null,
imageDataUrl:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=",
imageDomain: "opengraph.githubassets.com",
faviconDataUrl:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=",
},
linkPreviewMetadataDelayMs:
testInfo.title.includes(
"loading card before cold resolver work",
)
? 10_000
: testInfo.title.includes(
"send does not wait",
)
? 3_000
: testInfo.title.includes("draft auto-send")
? 500
}
: testInfo.title.includes("link preview") ||
testInfo.title.includes("supported Compact")
? {
linkPreviewMetadata: {
title: "Buzz pull request",
siteName: "GitHub",
description:
"A sender-authored preview snapshot.",
imageDataUrl: null,
imageDomain: null,
},
linkPreviewMetadataDelayMs:
testInfo.title.includes(
"loading card before cold resolver work",
)
? 10_000
: testInfo.title.includes(
"send does not wait",
)
? 3_000
: testInfo.title.includes(
"style defaults",
) ||
testInfo.title.includes(
"attachment-sized",
"draft auto-send",
)
? 1_500
: undefined,
linkPreviewMetadataStartBlockMs:
testInfo.title.includes(
"loading card before cold resolver work",
)
? 150
: undefined,
}
: undefined;
? 500
: testInfo.title.includes(
"style defaults",
) ||
testInfo.title.includes(
"attachment-sized",
)
? 1_500
: undefined,
linkPreviewMetadataStartBlockMs:
testInfo.title.includes(
"loading card before cold resolver work",
)
? 150
: undefined,
}
: undefined;
const mock = testInfo.title.includes("unresolvable preview")
? { linkPreviewMetadata: null, linkPreviewMetadataDelayMs: 800 }
: baseMock;
Expand Down Expand Up @@ -462,6 +481,69 @@ test("markdown tables overflow wide content and fill the message when narrow", a
.toBeLessThanOrEqual(1);
});

test("sent link preview media uses the authenticated proxy in compact and rich cards", async ({
page,
}) => {
const previewUrl = "https://github.com/block/buzz/pull/3246?proxy=1";
const fallbackMediaPattern =
/^buzz-media:\/\/localhost\/media\/[\da-f]{64}\.png$/;
const proxyMediaPattern =
/^http:\/\/127\.0\.0\.1:54321\/media\/[\da-f]{64}\.png$/;
await page.route("http://127.0.0.1:54321/media/**", (route) =>
route.fulfill({
body: '<svg xmlns="http://www.w3.org/2000/svg" width="40" height="20"><rect width="40" height="20" fill="#22c55e"/></svg>',
contentType: "image/svg+xml",
}),
);

await page.goto("/");
await page.getByTestId("channel-general").click();
await page.getByTestId("message-input").fill(previewUrl);
await waitForReadyComposerSnapshots(page);
await page.getByTestId("send-message").click();

const row = page.getByTestId("message-row").last();
const compactPreview = row.locator(
'[data-link-preview="github-pull-request"]',
);
const compactThumbnail = compactPreview
.locator("[data-link-preview-thumbnail] img")
.first();
const compactFavicon = compactPreview.locator(
"img[data-link-preview-hostname-favicon]",
);
await expect(compactThumbnail).toHaveAttribute("src", fallbackMediaPattern);
await expect(compactFavicon).toHaveAttribute("src", fallbackMediaPattern);

const releasedPort = await page.evaluate(() =>
window.__BUZZ_E2E_RELEASE_MEDIA_PROXY__?.(),
);
expect(releasedPort).toBe(54321);
await expect(compactThumbnail).toHaveAttribute("src", proxyMediaPattern);
await expect(compactFavicon).toHaveAttribute("src", proxyMediaPattern);
await expect
.poll(() => compactThumbnail.evaluate((image) => image.naturalWidth))
.toBe(40);

await openSettings(page, "appearance");
await page.getByTestId("link-preview-style-trigger").click();
await page.getByTestId("link-preview-style-rich").click();
await page.getByTestId("settings-back-to-app").click();

const richPreview = row.locator(
'[data-link-preview="github-pull-request"][data-link-preview-inline]',
);
const richThumbnail = richPreview
.locator("[data-link-preview-thumbnail] img")
.first();
const richFavicon = richPreview.locator("img[data-link-preview-favicon]");
await expect(richThumbnail).toHaveAttribute("src", proxyMediaPattern);
await expect(richFavicon).toHaveAttribute("src", proxyMediaPattern);
await expect
.poll(() => richThumbnail.evaluate((image) => image.naturalWidth))
.toBe(40);
});

test("link preview style defaults to compact and Rich unfurls descriptions", async ({
page,
}) => {
Expand Down
2 changes: 2 additions & 0 deletions desktop/tests/helpers/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ type MockBridgeOptions = {
applyCommunityDelayMs?: number;
openDmDelayMs?: number;
sendMessageDelayMs?: number;
/** Hold the media proxy at port 0 until the E2E release seam is invoked. */
mediaProxyInitiallyUnavailable?: boolean;
/** Hold mock send live echoes until the E2E release seam is invoked. */
deferSendMessageLiveEcho?: boolean;
/** Close the first channel-window live REQ; its retry is accepted. */
Expand Down
Loading