fix(desktop): derive MAS review state from OTA versions

This commit is contained in:
DIYgod 2026-04-15 15:57:51 +08:00
parent 34fd039ff9
commit 5aba30eb4f
5 changed files with 170 additions and 13 deletions

View File

@ -4,6 +4,7 @@ import PKG from "@pkg"
import { atomWithStorage } from "jotai/utils"
import { createAtomHooks } from "~/lib/jotai"
import { isLocalMASVersionInReview } from "~/lib/mas-review"
export const [, , useServerConfigs, , getServerConfigs, setServerConfigs] = createAtomHooks(
atomWithStorage<Nullable<ExtractResponseData<GetStatusConfigsResponse>>>(
@ -16,26 +17,32 @@ export const [, , useServerConfigs, , getServerConfigs, setServerConfigs] = crea
),
)
export const [, , useMASStoreVersion, , getMASStoreVersion, setMASStoreVersion] = createAtomHooks(
atomWithStorage<null | string>(getStorageNS("mas-store-version"), null, undefined, {
getOnInit: true,
}),
)
export type ServerConfigs = ExtractResponseData<GetStatusConfigsResponse>
export type PaymentPlan = ServerConfigs["PAYMENT_PLAN_LIST"][number]
export type PaymentFeature = PaymentPlan["limit"]
export const useIsInMASReview = () => {
const serverConfigs = useServerConfigs()
return (
typeof process !== "undefined" &&
process.mas &&
serverConfigs?.MAS_IN_REVIEW_VERSION === PKG.version
)
const masStoreVersion = useMASStoreVersion()
return isLocalMASVersionInReview({
isMASBuild: typeof process !== "undefined" && !!process.mas,
localVersion: PKG.version,
storeVersion: masStoreVersion,
})
}
export const getIsInMASReview = () => {
const serverConfigs = getServerConfigs()
return (
typeof process !== "undefined" &&
process.mas &&
serverConfigs?.MAS_IN_REVIEW_VERSION === PKG.version
)
const masStoreVersion = getMASStoreVersion()
return isLocalMASVersionInReview({
isMASBuild: typeof process !== "undefined" && !!process.mas,
localVersion: PKG.version,
storeVersion: masStoreVersion,
})
}
export const useIsPaymentEnabled = () => {

View File

@ -0,0 +1,73 @@
import { describe, expect, it } from "vitest"
import { getMASStoreVersionFromOTAVersions, isLocalMASVersionInReview } from "../mas-review"
describe("getMASStoreVersionFromOTAVersions", () => {
it("reads the MAS version from the OTA payload", () => {
expect(
getMASStoreVersionFromOTAVersions({
store: {
desktop: {
mas: {
version: "1.6.0",
},
},
},
}),
).toBe("1.6.0")
})
it("returns null when the OTA payload has no MAS version", () => {
expect(getMASStoreVersionFromOTAVersions({})).toBeNull()
})
})
describe("isLocalMASVersionInReview", () => {
it("returns true when the local MAS build is newer than the store version", () => {
expect(
isLocalMASVersionInReview({
isMASBuild: true,
localVersion: "1.6.1",
storeVersion: "1.6.0",
}),
).toBe(true)
})
it("returns false when the local version matches the store version", () => {
expect(
isLocalMASVersionInReview({
isMASBuild: true,
localVersion: "1.6.1",
storeVersion: "1.6.1",
}),
).toBe(false)
})
it("returns false when the build is not a MAS build", () => {
expect(
isLocalMASVersionInReview({
isMASBuild: false,
localVersion: "1.6.1",
storeVersion: "1.6.0",
}),
).toBe(false)
})
it("returns false when the store version is missing or invalid", () => {
expect(
isLocalMASVersionInReview({
isMASBuild: true,
localVersion: "1.6.1",
storeVersion: null,
}),
).toBe(false)
expect(
isLocalMASVersionInReview({
isMASBuild: true,
localVersion: "1.6.1",
storeVersion: "latest",
}),
).toBe(false)
})
})

View File

@ -0,0 +1,45 @@
import { gt, valid } from "semver"
export interface OTAVersionsResponse {
store?: {
desktop?: {
mas?: {
version?: null | string
}
}
}
}
const normalizeVersion = (version?: null | string) => {
if (!version) {
return null
}
return valid(version.trim())
}
export const getMASStoreVersionFromOTAVersions = (payload: OTAVersionsResponse) =>
payload.store?.desktop?.mas?.version ?? null
export const isLocalMASVersionInReview = ({
isMASBuild,
localVersion,
storeVersion,
}: {
isMASBuild: boolean
localVersion: string
storeVersion?: null | string
}) => {
if (!isMASBuild) {
return false
}
const normalizedLocalVersion = normalizeVersion(localVersion)
const normalizedStoreVersion = normalizeVersion(storeVersion)
if (!normalizedLocalVersion || !normalizedStoreVersion) {
return false
}
return gt(normalizedLocalVersion, normalizedStoreVersion)
}

View File

@ -1,11 +1,13 @@
import { useEffect } from "react"
import { setServerConfigs } from "~/atoms/server-configs"
import { setMASStoreVersion, setServerConfigs } from "~/atoms/server-configs"
import { syncServerShortcuts } from "~/atoms/settings/ai"
import { useMASStoreVersionQuery } from "~/queries/ota-versions"
import { useServerConfigsQuery } from "~/queries/server-configs"
export const ServerConfigsProvider = () => {
const serverConfigs = useServerConfigsQuery()
const masStoreVersion = useMASStoreVersionQuery()
useEffect(() => {
if (!serverConfigs) return
@ -13,5 +15,10 @@ export const ServerConfigsProvider = () => {
syncServerShortcuts(serverConfigs.AI_SHORTCUTS)
}, [serverConfigs])
useEffect(() => {
if (masStoreVersion === undefined) return
setMASStoreVersion(masStoreVersion)
}, [masStoreVersion])
return null
}

View File

@ -0,0 +1,25 @@
import { useQuery } from "@tanstack/react-query"
import { ofetch } from "ofetch"
import type { OTAVersionsResponse } from "~/lib/mas-review"
import { getMASStoreVersionFromOTAVersions } from "~/lib/mas-review"
const OTA_VERSIONS_URL = "https://ota.folo.is/versions"
const isMASBuild = () => typeof process !== "undefined" && !!process.mas
export const useMASStoreVersionQuery = () => {
const { data } = useQuery({
queryKey: ["ota-versions", "store", "desktop", "mas"],
queryFn: async () => {
const response = await ofetch<OTAVersionsResponse>(OTA_VERSIONS_URL, {
cache: "no-store",
})
return getMASStoreVersionFromOTAVersions(response)
},
enabled: isMASBuild(),
})
return data
}