fix(web): route mobile invite downloads to app stores (#7702)

Route the invite page’s existing download link to the App Store on
iOS/iPadOS and Google Play on Android. Appearance and app-opening
behavior stay unchanged.

The browser test checks platform routing using shared URL constants. Web
build, lint, and all six smoke tests pass. Rebased onto main to pick up
the relay test compilation fix.

Refs #3357. Alternative to #3464 with a smaller implementation and no
duplicated store URLs in tests.

---------

Signed-off-by: Tom Brow <tomb@block.xyz>
This commit is contained in:
Tom Brow
2026-09-16 21:48:05 -07:00
committed by GitHub
parent b36600fc44
commit 4ab4f78608
2 changed files with 59 additions and 9 deletions

View File

@@ -1,5 +1,10 @@
import { createHash } from "node:crypto";
import { expect, test } from "@playwright/test";
import {
BUZZ_ANDROID_PLAY_STORE_URL,
BUZZ_IOS_APP_STORE_URL,
BUZZ_RELEASES_URL,
} from "../../src/shared/lib/buzz-download";
test("home page loads with Buzz branding", async ({ page }) => {
await page.goto("/");
@@ -260,12 +265,13 @@ test("invite asks Safari users to choose their Mac download", async ({
await context.close();
});
test("invite download falls back for mobile and non-desktop devices", async ({
test("invite download links to the appropriate platform destination", async ({
browser,
}) => {
const unsupportedDevices = [
const devices = [
{
name: "iPhone Safari",
expectedUrl: BUZZ_IOS_APP_STORE_URL,
platform: "iPhone",
userAgent:
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15",
@@ -273,6 +279,7 @@ test("invite download falls back for mobile and non-desktop devices", async ({
},
{
name: "iPadOS desktop mode",
expectedUrl: BUZZ_IOS_APP_STORE_URL,
platform: "MacIntel",
userAgent:
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15",
@@ -280,20 +287,30 @@ test("invite download falls back for mobile and non-desktop devices", async ({
},
{
name: "Android phone",
expectedUrl: BUZZ_ANDROID_PLAY_STORE_URL,
platform: "Linux armv8l",
userAgent:
"Mozilla/5.0 (Linux; Android 15; Pixel 9 Pro) AppleWebKit/537.36 Mobile",
maxTouchPoints: 5,
},
{
name: "Fire tablet",
expectedUrl: BUZZ_RELEASES_URL,
platform: "Linux armv8l",
userAgent:
"Mozilla/5.0 (Linux; Android 9; KFMAWI) AppleWebKit/537.36 Silk/126.0 like Chrome/126.0.0.0 Safari/537.36",
maxTouchPoints: 5,
},
{
name: "ChromeOS",
expectedUrl: BUZZ_RELEASES_URL,
platform: "Linux x86_64",
userAgent: "Mozilla/5.0 (X11; CrOS x86_64 16093.68.0) AppleWebKit/537.36",
maxTouchPoints: 0,
},
];
for (const device of unsupportedDevices) {
for (const device of devices) {
const context = await browser.newContext({ userAgent: device.userAgent });
await context.addInitScript(({ platform, maxTouchPoints }) => {
Object.defineProperties(navigator, {
@@ -301,7 +318,12 @@ test("invite download falls back for mobile and non-desktop devices", async ({
maxTouchPoints: { configurable: true, value: maxTouchPoints },
userAgentData: {
configurable: true,
value: { platform, mobile: maxTouchPoints > 0 },
value: {
platform,
mobile: maxTouchPoints > 0,
// Store routing must not wait for desktop architecture hints.
getHighEntropyValues: () => new Promise(() => {}),
},
},
});
}, device);
@@ -343,7 +365,13 @@ test("invite download falls back for mobile and non-desktop devices", async ({
await expect(
page.getByRole("link", { name: "Download it now" }),
device.name,
).toHaveAttribute("href", "https://github.com/block/buzz/releases");
).toHaveAttribute("href", device.expectedUrl);
await context.route(device.expectedUrl, (route) =>
route.fulfill({ contentType: "text/html", body: "Store destination" }),
);
const destination = context.waitForEvent("page");
await page.getByRole("link", { name: "Download it now" }).click();
await expect(await destination).toHaveURL(device.expectedUrl);
await context.close();
}
});