108 lines · 3.9 KB · TypeScript
RawDownload
import type { DesktopUpdateStatus } from "../shared/update"; export type UpdateCheckTrigger = "boot" | "interval" | "manual"; export interface UpdateCheckPlan {  readonly check: boolean;  readonly promptVersionAfterCheck: string | null;} export interface CompletedUpdateCheckInput {  readonly stagedVersion: string | null;  readonly trigger: UpdateCheckTrigger;  readonly updateAvailable: boolean;  readonly availableVersion: string | null;} export interface DownloadedUpdateInput {  readonly stagedVersion: string | null;  readonly declinedVersion: string | null;  readonly incomingVersion: string;  readonly trigger: UpdateCheckTrigger;} export interface DownloadedUpdatePlan {  readonly stagedVersion: string;  readonly declinedVersion: string | null;  readonly status: DesktopUpdateStatus;  readonly promptVersion: string | null;} const parseVersionParts = (version: string): readonly number[] | null => {  const core = version.trim().split(/[+-]/, 1)[0];  if (!core) return null;  const parts = core.split(".").map((part) => Number.parseInt(part, 10));  return parts.every((part) => Number.isInteger(part) && part >= 0) ? parts : null;}; export const compareUpdateVersions = (left: string, right: string): number | null => {  const leftParts = parseVersionParts(left);  const rightParts = parseVersionParts(right);  if (!leftParts || !rightParts) return null;  const length = Math.max(leftParts.length, rightParts.length);  for (let index = 0; index < length; index += 1) {    const l = leftParts[index] ?? 0;    const r = rightParts[index] ?? 0;    if (l !== r) return l > r ? 1 : -1;  }  return 0;}; const isNewerUpdateVersion = (incomingVersion: string, declinedVersion: string): boolean => {  const comparison = compareUpdateVersions(incomingVersion, declinedVersion);  return comparison === null ? incomingVersion !== declinedVersion : comparison > 0;}; export const planUpdateCheck = (input: {  readonly stagedVersion: string | null;  readonly trigger: UpdateCheckTrigger;}): UpdateCheckPlan => ({  check: true,  promptVersionAfterCheck: input.trigger === "manual" ? input.stagedVersion : null,}); export const planCompletedUpdateCheck = (  input: CompletedUpdateCheckInput,): { readonly promptVersion: string | null } => {  if (input.trigger !== "manual" || !input.stagedVersion) return { promptVersion: null };  if (!input.updateAvailable) return { promptVersion: input.stagedVersion };  if (!input.availableVersion) return { promptVersion: null };  const comparison = compareUpdateVersions(input.availableVersion, input.stagedVersion);  if (comparison === null) {    return {      promptVersion: input.availableVersion === input.stagedVersion ? input.stagedVersion : null,    };  }  return { promptVersion: comparison <= 0 ? input.stagedVersion : null };}; export const planDownloadedUpdate = (input: DownloadedUpdateInput): DownloadedUpdatePlan => {  const prompt =    input.trigger === "manual" ||    !input.declinedVersion ||    isNewerUpdateVersion(input.incomingVersion, input.declinedVersion);  return {    stagedVersion: input.incomingVersion,    declinedVersion: input.declinedVersion,    status: { state: "downloaded", version: input.incomingVersion },    promptVersion: prompt ? input.incomingVersion : null,  };}; export const statusAfterUpdateError = (  status: DesktopUpdateStatus,  message: string,): DesktopUpdateStatus => {  if (status.state === "available" || status.state === "downloading" || status.state === "error") {    return { state: "error", version: status.version, message };  }  return status;}; export const planFatalAutoInstallOnQuit = (input: {  readonly packaged: boolean;  readonly retryAfterReset: boolean;}): { readonly enableDuringFailure: boolean; readonly restoreAfterRecovery: boolean } => ({  enableDuringFailure: input.packaged,  restoreAfterRecovery: input.packaged && input.retryAfterReset,});