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
67 changes: 67 additions & 0 deletions website/src/download-handoff-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useEffect, useState } from "react";
import {
GITHUB_LATEST_RELEASE_URL,
latestMacDmgDownloadUrl,
} from "./download";

const DOWNLOAD_REDIRECT_DELAY_MS = 250;

export function DownloadHandoffPage({ title }: { title: string }) {
const [downloadUrl, setDownloadUrl] = useState(GITHUB_LATEST_RELEASE_URL);

useEffect(() => {
let isCurrent = true;
let redirectTimeout: ReturnType<typeof setTimeout> | undefined;

async function startDownload() {
try {
const url = await latestMacDmgDownloadUrl();
if (!isCurrent) return;
setDownloadUrl(url);
redirectTimeout = setTimeout(() => {
window.location.replace(url);
}, DOWNLOAD_REDIRECT_DELAY_MS);
} catch (error) {
console.error(error);
redirectTimeout = setTimeout(() => {
window.location.replace(GITHUB_LATEST_RELEASE_URL);
}, DOWNLOAD_REDIRECT_DELAY_MS);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unmount still triggers error redirect

Medium Severity

After leaving the download handoff page, a failed latestMacDmgDownloadUrl call can still schedule window.location.replace to the GitHub latest release URL. The success path checks isCurrent before redirecting, but the catch block does not, so cleanup cannot stop a redirect started after unmount.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9dcb472. Configure here.

}
}

startDownload();

return () => {
isCurrent = false;
if (redirectTimeout) {
clearTimeout(redirectTimeout);
}
};
}, []);

return (
<main className="flex min-h-screen items-center justify-center bg-paper px-[24px] text-center text-ink">
<div className="flex max-w-[520px] flex-col items-center gap-[18px]">
<a
href="/"
className="flex items-center gap-[9px] text-[19px] font-bold tracking-normal text-ink no-underline"
aria-label="Flashtype home"
>
<span>Flashtype</span>
</a>
<h1 className="m-0 text-[34px] font-bold leading-[1.1] tracking-normal">
{title}
</h1>
<p className="m-0 text-[17px] leading-[1.6] text-secondary">
If the download does not start automatically, use the link below.
</p>
<a
href={downloadUrl}
className="inline-flex items-center justify-center rounded-[14px] border border-transparent bg-[linear-gradient(180deg,#F97316_0%,#E8590C_100%)] px-[26px] py-[15px] font-bold leading-none text-white no-underline shadow-[0_12px_34px_rgba(232,89,12,0.4),inset_0_1px_0_rgba(255,255,255,0.25)]"
>
Download for Mac
</a>
</div>
</main>
);
}
1 change: 1 addition & 0 deletions website/src/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const GITHUB_RELEASES_URL = `${GITHUB_URL}/releases`;
export const GITHUB_LATEST_RELEASE_URL = `${GITHUB_RELEASES_URL}/latest`;
export const GITHUB_LATEST_RELEASE_API_URL =
"https://api.github.com/repos/opral/flashtype/releases/latest";
export const DOWNLOAD_URL = "/download";

type GitHubReleaseAsset = {
name: string;
Expand Down
10 changes: 10 additions & 0 deletions website/src/routes/download/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createFileRoute } from "@tanstack/react-router";
import { DownloadHandoffPage } from "../../download-handoff-page";

export const Route = createFileRoute("/download/")({
component: DownloadPage,
});

function DownloadPage() {
return <DownloadHandoffPage title="Starting your download" />;
}
18 changes: 3 additions & 15 deletions website/src/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { createFileRoute } from "@tanstack/react-router";
import type { MouseEvent, ReactNode } from "react";
import type { ReactNode } from "react";
import {
GITHUB_LATEST_RELEASE_URL,
DOWNLOAD_URL,
GITHUB_URL,
latestMacDmgDownloadUrl,
} from "../download";

export const Route = createFileRoute("/")({
Expand Down Expand Up @@ -77,20 +76,9 @@ const FEATURE_ROWS = [
badge?: string;
}>;

async function handleDownloadClick(event: MouseEvent<HTMLAnchorElement>) {
event.preventDefault();

try {
window.location.href = await latestMacDmgDownloadUrl();
} catch (error) {
console.error(error);
window.location.href = GITHUB_LATEST_RELEASE_URL;
}
}

function DownloadLink({ children, className }: { children: ReactNode; className: string }) {
return (
<a href={GITHUB_LATEST_RELEASE_URL} onClick={handleDownloadClick} className={className}>
<a href={DOWNLOAD_URL} className={className}>
{children}
</a>
);
Expand Down
Loading