feat: add extension update check
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export const UPDATE_MANIFEST_URL =
|
||||
"https://example.com/star-chart-search-enhancer/latest.json";
|
||||
Reference in New Issue
Block a user