{infoItems.map((item) => (
diff --git a/apps/web/src/lib/version-check.ts b/apps/web/src/lib/version-check.ts
new file mode 100644
index 00000000..b044e397
--- /dev/null
+++ b/apps/web/src/lib/version-check.ts
@@ -0,0 +1,59 @@
+const LATEST_RELEASE_URL = "https://api.github.com/repos/tianma-if/edgeever/releases/latest";
+const CACHE_KEY = "edgeever:latest-release";
+const CACHE_TTL_MS = 60 * 60 * 1000;
+
+export type LatestRelease = {
+ tagName: string;
+ url: string;
+};
+
+const parseVersion = (value: string) => {
+ const match = value.match(/v?(\d+)\.(\d+)\.(\d+)/i);
+ return match ? match.slice(1).map(Number) : null;
+};
+
+export const isVersionOutdated = (currentVersion: string, latestVersion: string) => {
+ const current = parseVersion(currentVersion);
+ const latest = parseVersion(latestVersion);
+ if (!current || !latest) return false;
+
+ for (let index = 0; index < 3; index += 1) {
+ if (current[index] !== latest[index]) return current[index] < latest[index];
+ }
+
+ return false;
+};
+
+const readCachedRelease = (): LatestRelease | null => {
+ try {
+ const cached = JSON.parse(window.sessionStorage.getItem(CACHE_KEY) ?? "null") as {
+ expiresAt?: number;
+ release?: LatestRelease;
+ } | null;
+ return cached?.expiresAt && cached.expiresAt > Date.now() && cached.release ? cached.release : null;
+ } catch {
+ return null;
+ }
+};
+
+export const fetchLatestRelease = async (signal?: AbortSignal): Promise => {
+ const cached = readCachedRelease();
+ if (cached) return cached;
+
+ const response = await fetch(LATEST_RELEASE_URL, {
+ headers: { Accept: "application/vnd.github+json" },
+ signal,
+ });
+ if (!response.ok) throw new Error(`Release lookup failed with ${response.status}`);
+
+ const payload = (await response.json()) as { html_url?: string; tag_name?: string };
+ if (!payload.tag_name || !payload.html_url) throw new Error("Release response is incomplete");
+
+ const release = { tagName: payload.tag_name, url: payload.html_url };
+ try {
+ window.sessionStorage.setItem(CACHE_KEY, JSON.stringify({ expiresAt: Date.now() + CACHE_TTL_MS, release }));
+ } catch {
+ // Storage can be unavailable in restricted browsing modes.
+ }
+ return release;
+};
diff --git a/package.json b/package.json
index 3e0242db..5d84aaf6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "edgeever",
- "version": "1.5.11",
+ "version": "1.5.12",
"private": true,
"type": "module",
"workspaces": [
diff --git a/packages/shared/src/i18n/en-US.ts b/packages/shared/src/i18n/en-US.ts
index 5c6bc979..1767661c 100644
--- a/packages/shared/src/i18n/en-US.ts
+++ b/packages/shared/src/i18n/en-US.ts
@@ -205,6 +205,10 @@ export const enUS = {
browserMode: "Browser",
unknown: "Unknown",
copy: "Copy info",
+ updateAvailableTitle: "New version available",
+ updateAvailableDescription: "This version is out of date. The latest version is {{version}}.",
+ viewRelease: "View update",
+ latestVersion: "You are running the latest version",
},
feedback: {
title: "Feedback",
diff --git a/packages/shared/src/i18n/zh-CN.ts b/packages/shared/src/i18n/zh-CN.ts
index a50983df..b9a638f3 100644
--- a/packages/shared/src/i18n/zh-CN.ts
+++ b/packages/shared/src/i18n/zh-CN.ts
@@ -205,6 +205,10 @@ export const zhCN = {
browserMode: "浏览器",
unknown: "未知",
copy: "复制信息",
+ updateAvailableTitle: "发现新版本",
+ updateAvailableDescription: "当前版本较旧,最新版本为 {{version}}。",
+ viewRelease: "查看更新",
+ latestVersion: "当前已是最新版本",
},
feedback: {
title: "意见反馈",