| 1 | import { createHash } from "node:crypto"; |
| 2 | import * as vscode from "vscode"; |
| 3 | import { compareVersions } from "./util"; |
| 4 | |
| 5 | const DAY_MS = 86_400_000; |
| 6 | const LAST_CHECK = "diffinery.lastUpdateCheck"; |
| 7 | |
| 8 | interface Latest { |
| 9 | version: string; |
| 10 | url: string; |
| 11 | sha256: string; |
| 12 | } |
| 13 | |
| 14 | /** |
| 15 | * The extension is installed from the Diffinery server rather than a marketplace, so it updates itself from there: |
| 16 | * once a day (or when asked) it reads /-/vscode/version.json and offers to install a newer build, checked against |
| 17 | * the digest the server published. |
| 18 | */ |
| 19 | export async function checkForUpdates(ctx: vscode.ExtensionContext, server: string, current: string, log: vscode.LogOutputChannel, manual: boolean): Promise<void> { |
| 20 | if (!manual) { |
| 21 | if (!vscode.workspace.getConfiguration("diffinery").get<boolean>("checkForUpdates", true)) return; |
| 22 | const last = ctx.globalState.get<number>(LAST_CHECK, 0); |
| 23 | if (Date.now() - last < DAY_MS) return; |
| 24 | } |
| 25 | await ctx.globalState.update(LAST_CHECK, Date.now()); |
| 26 | let latest: Latest; |
| 27 | try { |
| 28 | const res = await fetch(`${server}/-/vscode/version.json`, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(15_000) }); |
| 29 | if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); |
| 30 | latest = (await res.json()) as Latest; |
| 31 | } catch (e) { |
| 32 | log.warn("checking for a newer Diffinery extension failed", e); |
| 33 | if (manual) void vscode.window.showWarningMessage(`Couldn't check ${server} for a newer Diffinery extension: ${e instanceof Error ? e.message : e}`); |
| 34 | return; |
| 35 | } |
| 36 | if (compareVersions(latest.version, current) <= 0) { |
| 37 | if (manual) void vscode.window.showInformationMessage(`The Diffinery extension is up to date (${current}).`); |
| 38 | return; |
| 39 | } |
| 40 | const pick = await vscode.window.showInformationMessage(`Diffinery extension ${latest.version} is available (you have ${current}).`, "Install", "Later"); |
| 41 | if (pick !== "Install") return; |
| 42 | await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: `Installing Diffinery extension ${latest.version}…` }, async () => { |
| 43 | const res = await fetch(new URL(latest.url, server), { signal: AbortSignal.timeout(60_000) }); |
| 44 | if (!res.ok) throw new Error(`Downloading the extension failed: ${res.status} ${res.statusText}`); |
| 45 | const bytes = new Uint8Array(await res.arrayBuffer()); |
| 46 | if (createHash("sha256").update(bytes).digest("hex") !== latest.sha256) throw new Error("The downloaded extension didn't match the published digest; nothing was installed."); |
| 47 | const file = vscode.Uri.joinPath(ctx.globalStorageUri, `diffinery-${latest.version}.vsix`); |
| 48 | await vscode.workspace.fs.createDirectory(ctx.globalStorageUri); |
| 49 | await vscode.workspace.fs.writeFile(file, bytes); |
| 50 | await vscode.commands.executeCommand("workbench.extensions.installExtension", file); |
| 51 | }); |
| 52 | const reload = await vscode.window.showInformationMessage(`Diffinery extension ${latest.version} is installed. Reload the window to use it.`, "Reload Window"); |
| 53 | if (reload) void vscode.commands.executeCommand("workbench.action.reloadWindow"); |
| 54 | } |