feat: add extension update check

This commit is contained in:
2026-05-19 18:50:03 +08:00
parent 703a095c08
commit 02d9063a11
22 changed files with 919 additions and 18 deletions
+94
View File
@@ -0,0 +1,94 @@
export interface UpdateManifest {
guideUrl: string;
latestVersion: string;
minSupportedVersion: string;
publishedAt: string;
releaseNotes: string[];
zipUrl: string;
}
export function compareExtensionVersions(left: string, right: string): number {
const leftParts = parseVersionParts(left);
const rightParts = parseVersionParts(right);
const maxLength = Math.max(leftParts.length, rightParts.length);
for (let index = 0; index < maxLength; index += 1) {
const leftValue = leftParts[index] ?? 0;
const rightValue = rightParts[index] ?? 0;
if (leftValue !== rightValue) {
return leftValue - rightValue;
}
}
return 0;
}
export function parseUpdateManifest(value: unknown): UpdateManifest | null {
if (!value || typeof value !== "object") {
return null;
}
const candidate = value as Partial<UpdateManifest>;
if (
!isVersionString(candidate.latestVersion) ||
!isVersionString(candidate.minSupportedVersion) ||
!isHttpsUrl(candidate.zipUrl) ||
!isHttpsUrl(candidate.guideUrl) ||
typeof candidate.publishedAt !== "string" ||
!Array.isArray(candidate.releaseNotes) ||
!candidate.releaseNotes.every((note) => typeof note === "string")
) {
return null;
}
return {
guideUrl: candidate.guideUrl,
latestVersion: candidate.latestVersion,
minSupportedVersion: candidate.minSupportedVersion,
publishedAt: candidate.publishedAt,
releaseNotes: candidate.releaseNotes,
zipUrl: candidate.zipUrl
};
}
export async function fetchUpdateManifest(
manifestUrl: string,
fetchImpl: typeof fetch = fetch
): Promise<UpdateManifest> {
const response = await fetchImpl(manifestUrl, {
cache: "no-store"
});
if (!response.ok) {
throw new Error(`update manifest request failed: ${response.status}`);
}
const manifest = parseUpdateManifest(await response.json());
if (!manifest) {
throw new Error("update manifest is invalid");
}
return manifest;
}
function parseVersionParts(value: string): number[] {
return value.split(".").map((part) => {
const parsed = Number.parseInt(part, 10);
return Number.isFinite(parsed) ? parsed : 0;
});
}
function isVersionString(value: unknown): value is string {
return typeof value === "string" && /^\d+(?:\.\d+)*$/.test(value);
}
function isHttpsUrl(value: unknown): value is string {
if (typeof value !== "string") {
return false;
}
try {
return new URL(value).protocol === "https:";
} catch {
return false;
}
}
+2
View File
@@ -0,0 +1,2 @@
export const UPDATE_MANIFEST_URL =
"https://example.com/star-chart-search-enhancer/latest.json";