95 lines
2.5 KiB
TypeScript
95 lines
2.5 KiB
TypeScript
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;
|
|
}
|
|
}
|