From cbe308f5bb778c1a24f303632d9edb9af41fe60b Mon Sep 17 00:00:00 2001 From: Seydi Charyyev Date: Wed, 29 Jul 2026 09:23:33 +0500 Subject: [PATCH] fix(web): send mobile invitees to the app stores, not GitHub releases An invite opened on a phone offered github.com/block/buzz/releases - a list of desktop binaries. normalizeOperatingSystem folded every non-desktop user agent into `unknown`, assetPattern has no pattern for `unknown`, so the resolver fell back to the releases page. That fallback was the right answer while there was no mobile app. Both listings are live now, so iOS and Android become classified platforms and resolve straight to their store URL, ahead of the GitHub release lookup. ChromeOS and Fire tablets keep the releases fallback on purpose: neither is served by the Play Store listing. Desktop detection is untouched - macOS, Windows and Linux still resolve a release asset exactly as before. The existing non-desktop e2e case pinned the old fallback for all four devices. It now asserts the expected destination per device and covers a Fire tablet as well. Refs #3357. This is the store-links half of that issue; the blank mobile render it also reports is not addressed here. Signed-off-by: Seydi Charyyev --- web/src/shared/lib/buzz-download.ts | 44 ++++++++++++++++++++++++++++- web/tests/e2e/smoke.spec.ts | 29 ++++++++++++++++--- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/web/src/shared/lib/buzz-download.ts b/web/src/shared/lib/buzz-download.ts index 3c198382b4..73dc604003 100644 --- a/web/src/shared/lib/buzz-download.ts +++ b/web/src/shared/lib/buzz-download.ts @@ -1,14 +1,33 @@ export const BUZZ_RELEASES_URL = "https://github.com/block/buzz/releases"; +export const BUZZ_IOS_APP_STORE_URL = + "https://apps.apple.com/us/app/buzz-chat-with-your-hive/id6779728271"; +export const BUZZ_ANDROID_PLAY_STORE_URL = + "https://play.google.com/store/apps/details?id=xyz.block.buzz.mobile"; const BUZZ_RELEASES_API_URL = "https://api.github.com/repos/block/buzz/releases?per_page=10"; const CACHE_KEY = "buzz.latestDownload.v1"; const CACHE_TTL_MS = 60 * 60 * 1000; export type BuzzDownloadPlatform = { - operatingSystem: "linux" | "macos" | "windows" | "unknown"; + operatingSystem: + | "linux" + | "macos" + | "windows" + | "ios" + | "android" + | "unknown"; architecture: "arm64" | "x64" | "unknown"; }; +// iOS and Android are supported platforms that take a store listing instead of +// a GitHub release asset, so they resolve before the release lookup runs. +const STORE_URLS: Partial< + Record +> = { + ios: BUZZ_IOS_APP_STORE_URL, + android: BUZZ_ANDROID_PLAY_STORE_URL, +}; + type GitHubRelease = { draft: boolean; prerelease: boolean; @@ -39,6 +58,24 @@ function normalizeOperatingSystem( // Reject non-desktop devices before admitting desktop-looking signals. const isIPadDesktopMode = platform === "macintel" && navigatorValue.maxTouchPoints > 1; + + // Phones and tablets get a store listing, so classify the two platforms the + // mobile app actually ships on before the catch-all below folds every + // non-desktop device into `unknown`. Fire tablets (kindle/silk) and ChromeOS + // stay `unknown` on purpose: neither is served by the Play Store listing. + if (!/kindle|silk|cros/.test(userAgent)) { + if ( + isIPadDesktopMode || + platform === "ios" || + /iphone|ipad|ipod/.test(userAgent) + ) { + return "ios"; + } + if (platform === "android" || /android/.test(userAgent)) { + return "android"; + } + } + const isUnsupportedDevice = userAgentData?.mobile === true || isIPadDesktopMode || @@ -142,6 +179,11 @@ export function selectBuzzDownloadUrl( export async function resolveBuzzDownloadUrlForPlatform( platform: BuzzDownloadPlatform, ): Promise { + const storeUrl = STORE_URLS[platform.operatingSystem]; + if (storeUrl) { + return storeUrl; + } + try { const cached = JSON.parse(sessionStorage.getItem(CACHE_KEY) ?? "null") as { expiresAt: number; diff --git a/web/tests/e2e/smoke.spec.ts b/web/tests/e2e/smoke.spec.ts index 22e2628165..57c5f86fe3 100644 --- a/web/tests/e2e/smoke.spec.ts +++ b/web/tests/e2e/smoke.spec.ts @@ -260,16 +260,23 @@ 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 ({ +const APP_STORE_URL = + "https://apps.apple.com/us/app/buzz-chat-with-your-hive/id6779728271"; +const PLAY_STORE_URL = + "https://play.google.com/store/apps/details?id=xyz.block.buzz.mobile"; +const RELEASES_URL = "https://github.com/block/buzz/releases"; + +test("invite download sends each non-desktop device where its app lives", async ({ browser, }) => { - const unsupportedDevices = [ + const nonDesktopDevices = [ { name: "iPhone Safari", platform: "iPhone", userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15", maxTouchPoints: 5, + expected: APP_STORE_URL, }, { name: "iPadOS desktop mode", @@ -277,6 +284,7 @@ test("invite download falls back for mobile and non-desktop devices", async ({ userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15", maxTouchPoints: 5, + expected: APP_STORE_URL, }, { name: "Android phone", @@ -284,16 +292,29 @@ test("invite download falls back for mobile and non-desktop devices", async ({ userAgent: "Mozilla/5.0 (Linux; Android 15; Pixel 9 Pro) AppleWebKit/537.36 Mobile", maxTouchPoints: 5, + expected: PLAY_STORE_URL, }, + // No store listing serves these two, so they keep the releases-page + // fallback: ChromeOS is not a Play Store target here, and Fire tablets + // ship through Amazon's Appstore. { name: "ChromeOS", platform: "Linux x86_64", userAgent: "Mozilla/5.0 (X11; CrOS x86_64 16093.68.0) AppleWebKit/537.36", maxTouchPoints: 0, + expected: RELEASES_URL, + }, + { + name: "Fire tablet (Silk)", + 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, + expected: RELEASES_URL, }, ]; - for (const device of unsupportedDevices) { + for (const device of nonDesktopDevices) { const context = await browser.newContext({ userAgent: device.userAgent }); await context.addInitScript(({ platform, maxTouchPoints }) => { Object.defineProperties(navigator, { @@ -343,7 +364,7 @@ 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.expected); await context.close(); } });