diff --git a/functions/api/fetch_nightly_firmware.ts b/functions/api/fetch_nightly_firmware.ts index e765aa9..0b5709a 100644 --- a/functions/api/fetch_nightly_firmware.ts +++ b/functions/api/fetch_nightly_firmware.ts @@ -2,6 +2,7 @@ const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET,HEAD,POST,PUT,OPTIONS,DELETE", "Access-Control-Allow-Headers": "*", + "Access-Control-Expose-Headers": "*", "Access-Control-Max-Age": "86400", }; diff --git a/functions/api/fetch_stable_firmware.ts b/functions/api/fetch_stable_firmware.ts index 1f585f8..1961cfe 100644 --- a/functions/api/fetch_stable_firmware.ts +++ b/functions/api/fetch_stable_firmware.ts @@ -2,6 +2,7 @@ const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET,HEAD,POST,PUT,OPTIONS,DELETE", "Access-Control-Allow-Headers": "*", + "Access-Control-Expose-Headers": "*", "Access-Control-Max-Age": "86400", }; diff --git a/functions/api/get_versions.ts b/functions/api/get_versions.ts new file mode 100644 index 0000000..2f60b89 --- /dev/null +++ b/functions/api/get_versions.ts @@ -0,0 +1,63 @@ +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET,HEAD,POST,PUT,OPTIONS,DELETE", + "Access-Control-Allow-Headers": "*", + "Access-Control-Expose-Headers": "*", + "Access-Control-Max-Age": "86400", +}; + +export const onRequestGet: PagesFunction = async (context) => { + const convertNightlyString = (nightlyTag: string) => { + const parts = nightlyTag.split("-"); + + // We expect the input to be 'nightly-tag-YYYY-MM-DD' format + const year = parts[2].slice(2); // remove '20' prefix from the year + const month = parts[3]; + const day = parts[4]; + + return `n_${year}${month}${day}`; + }; + + let apiUrl = + "https://api.github.com/repos/portapack-mayhem/mayhem-firmware/releases"; + + let apiResponse = await fetch(apiUrl, { + method: "GET", + headers: { "User-Agent": "portapack-mayhem" }, + }); + + if (!apiResponse.ok) { + throw new Error(`HTTP error! status: ${apiResponse.status}`); + } + + let apiData: any = await apiResponse.json(); + + const latestNightlyObject = apiData.find( + (item: any) => item.prerelease === true + ); + const nightlyVersion = convertNightlyString(latestNightlyObject.tag_name); + + const latestStableObject = apiData.find( + (item: any) => item.prerelease === false + ); + + const data = { + stable: { + version: latestStableObject.tag_name, + published_at: latestStableObject.published_at, + }, + nightly: { + version: nightlyVersion, + published_at: latestNightlyObject.published_at, + }, + }; + + const json = JSON.stringify(data); + + return new Response(json, { + headers: { + ...corsHeaders, + "content-type": "application/json;charset=UTF-8", + }, + }); +}; diff --git a/src/app/models.tsx b/src/app/models.tsx new file mode 100644 index 0000000..4e9423a --- /dev/null +++ b/src/app/models.tsx @@ -0,0 +1,9 @@ +export interface LatestVersions { + stable: VersionDetails; + nightly: VersionDetails; +} + +export interface VersionDetails { + version: string; + published_at: string; +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 7fb2019..6fe5fb1 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,12 +1,12 @@ "use client"; import dynamic from "next/dynamic"; -import Controller from "./components/Controller/Controller"; -import { Loader } from "./components/Loader/Loader"; +import Controller from "@/components/Controller/Controller"; +import { Loader } from "@/components/Loader/Loader"; const Home = () => { const SerialLoader = dynamic( - async () => await import("./components/SerialLoader/SerialLoader"), + async () => await import("@/components/SerialLoader/SerialLoader"), { loading: () => , ssr: false, diff --git a/src/app/components/Controller/Controller.tsx b/src/components/Controller/Controller.tsx similarity index 78% rename from src/app/components/Controller/Controller.tsx rename to src/components/Controller/Controller.tsx index e4e6769..6d7df48 100644 --- a/src/app/components/Controller/Controller.tsx +++ b/src/components/Controller/Controller.tsx @@ -1,8 +1,14 @@ "use client"; import React, { ChangeEvent, useEffect, useRef, useState } from "react"; -import { parseDirectories } from "@/app/utils/fileUtils"; -import { downloadFileFromUrl, useWriteCommand } from "@/app/utils/serialUtils"; +import { LatestVersions } from "@/app/models"; +import { parseDirectories } from "@/utils/fileUtils"; +import { downloadFileFromUrl, useWriteCommand } from "@/utils/serialUtils"; +import { + getVersionLink, + getVersionType, + nightlyVersionFormat, +} from "@/utils/versionUtils"; import { DeviceButtons } from "../DeviceButtons/DeviceButtons"; import { FileBrowser, FileStructure } from "../FileBrowser/FileBrowser"; import HotkeyButton from "../HotkeyButton/HotkeyButton"; @@ -25,6 +31,7 @@ const Controller = () => { const [firmwarModalOpen, setFirmwarModalOpen] = useState(false); const [setupComplete, setSetupComplete] = useState(false); const [dirStructure, setDirStructure] = useState(); + const [latestVersion, setLatestVersion] = useState(); const canvasRef = useRef(null); const fileInputRef = useRef(null); const firmwareFileInputRef = useRef(null); @@ -73,6 +80,8 @@ const Controller = () => { setConsoleMessageList(""); setSetupComplete(true); + + setLatestVersion(await getLatestVersions()); }; const fetchFolderStructure = async () => { @@ -91,6 +100,16 @@ const Controller = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [serial]); + const getLatestVersions = async () => { + const apiResponse = await fetch("https://hackrf.app/api/get_versions"); + + if (!apiResponse.ok) { + console.error("Network response was not ok"); + } + + return await apiResponse.json(); + }; + const renderFrame = () => { const width = 241; const height = 321; @@ -209,7 +228,7 @@ const Controller = () => { } }; - const flashLatestFirmware = async () => { + const flashLatestNightlyFirmware = async () => { const fileBlob = await downloadFileFromUrl( "https://hackrf.app/api/fetch_nightly_firmware" ); @@ -333,6 +352,7 @@ const Controller = () => { { if (fileInputRef.current) { @@ -412,39 +432,76 @@ const Controller = () => { closeModal={() => setFirmwarModalOpen(false)} className="w-[40%]" > -
-

Select from the available options

- - - -