Skip to content

Commit 4dfc01f

Browse files
authored
Merge pull request #2 from itzcodex24/1-missing-recent-versions
(#1) wip - adds endpoint
2 parents 6a35bf2 + 2c58e2c commit 4dfc01f

File tree

4 files changed

+152
-54
lines changed

4 files changed

+152
-54
lines changed

app/api/versions/route.ts

Lines changed: 134 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,151 @@
1-
import { NextRequest } from 'next/server'
21
import { tryCatch } from '@/lib/try-catch'
3-
import { Platforms } from '@/lib/utils'
2+
import { PrimaryPlatforms, type TPrimaryPlatforms, type TSecondaryPlatforms } from '@/lib/utils'
3+
import { NextRequest } from 'next/server'
44

5-
interface Versions {
6-
[key: string]: {
5+
type PrimaryEndpoints = Record<TPrimaryPlatforms, {
6+
[version: string]: {
77
download_url: string
88
}
9+
}>
10+
11+
type SecondaryEndpoints = {
12+
versions: {
13+
version: string
14+
downloads: {
15+
chrome: {
16+
platform: TSecondaryPlatforms
17+
url: string
18+
}[]
19+
}
20+
}[]
921
}
1022

11-
const ENDPOINT = 'https://raw.githubusercontent.com/Bugazelle/chromium-all-old-stable-versions/master/chromium.stable.json'
23+
export type Result = { version: string, url: string }[]
1224

13-
export async function GET(request: NextRequest) {
14-
const os = request.nextUrl.searchParams.get('os') as typeof Platforms[number]
25+
const ENDPOINTS = {
26+
primary: 'https://raw.githubusercontent.com/Bugazelle/chromium-all-old-stable-versions/master/chromium.stable.json',
27+
secondary: 'https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json'
28+
} as const
1529

16-
if (!os) return new Response('Invalid request.', { status: 400 })
17-
if (!Platforms.includes(os)) return new Response('Unsupported Operating System.', { status: 400 })
30+
async function getPrimaryResponse(os: TPrimaryPlatforms): Promise<Result> {
31+
try {
32+
const response = await fetch(ENDPOINTS.primary, { method: "GET" })
33+
const json = await response.json() as PrimaryEndpoints
34+
35+
const versions = json[os]
36+
if (!versions) throw Error("No versions found.")
37+
38+
return Object.entries(versions)
39+
.map(([version, { download_url }]) => ({ version, url: download_url }))
40+
} catch {
41+
throw Error("Unable to fetch primary data.")
42+
}
43+
}
44+
45+
function mapSecondaryOperatingSystem(inputOs: TPrimaryPlatforms): TSecondaryPlatforms | null {
46+
switch (inputOs) {
47+
case 'mac':
48+
return 'mac-arm64'
49+
case 'win64':
50+
return 'win64'
51+
case 'win':
52+
return 'win32'
53+
case 'linux64':
54+
case 'linux':
55+
return 'linux64'
56+
case 'android':
57+
return null
58+
59+
default: {
60+
const exhaustive: never = inputOs
61+
console.error('Received exhaustive value ' + exhaustive)
62+
return null
63+
}
64+
}
65+
}
66+
67+
async function getSecondaryResponse(os: TPrimaryPlatforms, currentResults: Result): Promise<Result> {
68+
try {
69+
const response = await fetch(ENDPOINTS.secondary, { method: "GET" })
70+
const json = await response.json() as SecondaryEndpoints
71+
72+
const secondaryOs = mapSecondaryOperatingSystem(os)
73+
if (!secondaryOs) throw Error("Input OS Unsupported")
1874

19-
const { data: response, error } = await tryCatch(fetch(ENDPOINT, { method: 'GET' }))
20-
if (error) {
21-
console.log(error)
22-
return new Response('An error occured.', { status: 500 })
75+
const filteredVersions = json.versions.filter(v =>
76+
!currentResults.some(r => r.version === v.version)
77+
)
78+
79+
const secondaryDownloads = filteredVersions.flatMap(v => {
80+
const chromeDownloads = v.downloads.chrome
81+
const matchingDownloads = chromeDownloads.filter(d => d.platform === secondaryOs)
82+
return matchingDownloads.map(d => ({
83+
version: v.version,
84+
url: d.url
85+
}))
86+
})
87+
88+
return secondaryDownloads
89+
} catch {
90+
throw Error("Unable to fetch secondary data.")
91+
}
92+
}
93+
94+
function compareVersions(a: string, b: string): number {
95+
const aParts = a.split('.').map(Number)
96+
const bParts = b.split('.').map(Number)
97+
98+
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
99+
const aPart = aParts[i] || 0
100+
const bPart = bParts[i] || 0
101+
102+
if (aPart !== bPart) {
103+
return bPart - aPart
104+
}
23105
}
106+
107+
return 0
108+
}
24109

25-
if (!response.ok) return new Response('An error occured.', { status: 500 })
110+
async function getVersions(os: TPrimaryPlatforms): Promise<Result> {
111+
const result: Result = []
26112

27-
const { data, error: jsonError } = await tryCatch(response.json())
113+
const { data: primary, error: primaryError } = await tryCatch(getPrimaryResponse(os))
114+
if (primaryError) {
115+
console.error(primaryError)
116+
} else {
117+
result.push(...primary)
118+
}
28119

29-
if (jsonError) {
30-
console.log(error)
31-
return new Response('An error occured.', { status: 500 })
120+
const { data: secondary, error: secondaryError } = await tryCatch(getSecondaryResponse(os, result))
121+
if (secondaryError) {
122+
console.error(secondaryError)
123+
} else {
124+
result.push(...secondary)
32125
}
33126

34-
const versions = data[os] as Versions
35-
if (!versions) return new Response('Unsupported Operating System.', { status: 400 })
127+
const uniqueVersions = new Map<string, { version: string, url: string }>()
128+
for (const item of result) {
129+
if (!uniqueVersions.has(item.version)) {
130+
uniqueVersions.set(item.version, item)
131+
}
132+
}
36133

37-
const validVersions = Object.fromEntries(
38-
Object.entries(versions)
39-
.filter(([, { download_url }]) => !download_url.toLowerCase().includes('error:'))
40-
)
41-
42-
return Response.json(validVersions, {status: 200})
134+
const deduplicated = Array.from(uniqueVersions.values())
135+
deduplicated.sort((a, b) => compareVersions(a.version, b.version))
136+
137+
return deduplicated
138+
}
139+
140+
export async function GET(request: NextRequest) {
141+
const os = request.nextUrl.searchParams.get('os') as typeof PrimaryPlatforms[number]
142+
if (!os) return new Response('Invalid request.', { status: 400 })
143+
if (!PrimaryPlatforms.includes(os)) return new Response('Unsupported Operating System.', { status: 400 })
144+
145+
try {
146+
const versions = await getVersions(os)
147+
return Response.json(versions, { status: 200 })
148+
} catch {
149+
return new Response('Unable to fetch versions.', { status: 500 })
150+
}
43151
}

components/os-selector.tsx

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,14 @@ import {
2424
Search,
2525
Loader2,
2626
} from "lucide-react"
27-
import { Platforms } from "@/lib/utils"
2827

29-
type OperatingSystem = typeof Platforms[number]
30-
31-
type Version = {
32-
[key: string]: {download_url: string}
33-
}
34-
35-
interface ChromeVersion {
36-
version: string
37-
downloadUrl: string
38-
}
28+
import type { Result } from "@/app/api/versions/route"
29+
import type { TPrimaryPlatforms } from "@/lib/utils"
3930

4031
interface Platform {
4132
text: string
4233
icon: React.ReactNode
43-
os: OperatingSystem
34+
os: TPrimaryPlatforms
4435
}
4536

4637
const platforms: Platform[] = [
@@ -78,9 +69,9 @@ const platforms: Platform[] = [
7869

7970
export default function OperatingSystemSelector() {
8071
const [open, setOpen] = useState(false)
81-
const [selectedOS, setSelectedOS] = useState<OperatingSystem>("win64")
82-
const [chromeVersions, setChromeVersions] = useState<ChromeVersion[]>([])
83-
const [filteredVersions, setFilteredVersions] = useState<ChromeVersion[]>([])
72+
const [selectedOS, setSelectedOS] = useState<TPrimaryPlatforms>("win64")
73+
const [chromeVersions, setChromeVersions] = useState<Result>([])
74+
const [filteredVersions, setFilteredVersions] = useState<Result>([])
8475
const [searchQuery, setSearchQuery] = useState("")
8576
const [isLoading, setIsLoading] = useState(false)
8677
const [error, setError] = useState<string | null>(null)
@@ -102,14 +93,9 @@ export default function OperatingSystemSelector() {
10293
}
10394
return response.json()
10495
})
105-
.then((data: Version) => {
106-
const versions: ChromeVersion[] = Object.entries(data).map(([version, { download_url }]) => ({
107-
version,
108-
downloadUrl: download_url
109-
}))
110-
111-
setChromeVersions(versions)
112-
setFilteredVersions(versions)
96+
.then((data: Result) => {
97+
setChromeVersions(data)
98+
setFilteredVersions(data)
11399
})
114100
.catch(err => {
115101
console.error("Error fetching Chrome versions:", err)
@@ -133,7 +119,7 @@ export default function OperatingSystemSelector() {
133119
}
134120
}, [searchQuery, chromeVersions])
135121

136-
const handleOSSelect = (os: OperatingSystem) => {
122+
const handleOSSelect = (os: TPrimaryPlatforms) => {
137123
setSelectedOS(os)
138124
setSearchQuery("")
139125
setOpen(true)
@@ -216,7 +202,7 @@ export default function OperatingSystemSelector() {
216202
<td className="p-3">{version.version}</td>
217203
<td className="p-3 text-right">
218204
<Button size="sm" variant="outline" asChild>
219-
<a href={version.downloadUrl} target="_blank" rel="noopener noreferrer">
205+
<a href={version.url} target="_blank" rel="noopener noreferrer">
220206
<Download className="h-4 w-4 mr-2" />
221207
Download
222208
</a>

lib/utils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,8 @@ export function cn(...inputs: ClassValue[]) {
55
return twMerge(clsx(inputs))
66
}
77

8-
export const Platforms = ['android', 'mac', 'linux', 'linux64', 'win', 'win64'] as const
8+
export const PrimaryPlatforms = ['android', 'mac', 'linux', 'linux64', 'win', 'win64'] as const
9+
export const SecondaryPlatforms = ['linux64', 'mac-arm64', 'mac-x64', 'win32', 'win64'] as const
10+
11+
export type TPrimaryPlatforms = typeof PrimaryPlatforms[number]
12+
export type TSecondaryPlatforms = typeof SecondaryPlatforms[number]

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "google_downloader",
3-
"version": "0.1.0",
2+
"name": "rechrome",
3+
"version": "0.2.0",
44
"private": true,
55
"scripts": {
66
"dev": "next dev --turbopack",

0 commit comments

Comments
 (0)