forked from mirror/Vesktop
Rewrite http utils; properly handle (& retry on) network errors
This commit is contained in:
parent
132adcb733
commit
97267ef89a
3 changed files with 54 additions and 40 deletions
|
@ -5,41 +5,54 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createWriteStream } from "fs";
|
import { createWriteStream } from "fs";
|
||||||
import type { IncomingMessage } from "http";
|
import { Readable } from "stream";
|
||||||
import { get, RequestOptions } from "https";
|
import { pipeline } from "stream/promises";
|
||||||
import { finished } from "stream/promises";
|
import { setTimeout } from "timers/promises";
|
||||||
|
|
||||||
export async function downloadFile(url: string, file: string, options: RequestOptions = {}) {
|
interface FetchieOptions {
|
||||||
const res = await simpleReq(url, options);
|
retryOnNetworkError?: boolean;
|
||||||
await finished(
|
}
|
||||||
res.pipe(
|
|
||||||
createWriteStream(file, {
|
export async function downloadFile(url: string, file: string, options: RequestInit = {}, fetchieOpts?: FetchieOptions) {
|
||||||
autoClose: true
|
const res = await fetchie(url, options, fetchieOpts);
|
||||||
})
|
await pipeline(
|
||||||
)
|
// @ts-expect-error odd type error
|
||||||
|
Readable.fromWeb(res.body!),
|
||||||
|
createWriteStream(file, {
|
||||||
|
autoClose: true
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function simpleReq(url: string, options: RequestOptions = {}) {
|
const ONE_MINUTE_MS = 1000 * 60;
|
||||||
return new Promise<IncomingMessage>((resolve, reject) => {
|
|
||||||
get(url, options, res => {
|
|
||||||
const { statusCode, statusMessage, headers } = res;
|
|
||||||
if (statusCode! >= 400) return void reject(`${statusCode}: ${statusMessage} - ${url}`);
|
|
||||||
if (statusCode! >= 300) return simpleReq(headers.location!, options).then(resolve).catch(reject);
|
|
||||||
|
|
||||||
resolve(res);
|
export async function fetchie(url: string, options?: RequestInit, { retryOnNetworkError }: FetchieOptions = {}) {
|
||||||
});
|
let res: Response | undefined;
|
||||||
});
|
|
||||||
}
|
try {
|
||||||
|
res = await fetch(url, options);
|
||||||
export async function simpleGet(url: string, options: RequestOptions = {}) {
|
} catch (err) {
|
||||||
const res = await simpleReq(url, options);
|
if (retryOnNetworkError) {
|
||||||
|
console.error("Failed to fetch", url + ".", "Gonna retry with backoff.");
|
||||||
return new Promise<Buffer>((resolve, reject) => {
|
|
||||||
const chunks = [] as Buffer[];
|
for (let tries = 0, delayMs = 500; tries < 20; tries++, delayMs = Math.min(2 * delayMs, ONE_MINUTE_MS)) {
|
||||||
|
await setTimeout(delayMs);
|
||||||
res.once("error", reject);
|
try {
|
||||||
res.on("data", chunk => chunks.push(chunk));
|
res = await fetch(url, options);
|
||||||
res.once("end", () => resolve(Buffer.concat(chunks)));
|
break;
|
||||||
});
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res) throw new Error(`Failed to fetch ${url}\n${err}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.ok) return res;
|
||||||
|
|
||||||
|
let msg = `Got non-OK response for ${url}: ${res.status} ${res.statusText}`;
|
||||||
|
|
||||||
|
const reason = await res.text().catch(() => "");
|
||||||
|
if (reason) msg += `\n${reason}`;
|
||||||
|
|
||||||
|
throw new Error(msg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { existsSync, mkdirSync } from "fs";
|
import { existsSync, mkdirSync } from "fs";
|
||||||
import type { RequestOptions } from "https";
|
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
|
import { USER_AGENT, VENCORD_FILES_DIR } from "../constants";
|
||||||
import { downloadFile, simpleGet } from "./http";
|
import { downloadFile, fetchie } from "./http";
|
||||||
|
|
||||||
const API_BASE = "https://api.github.com";
|
const API_BASE = "https://api.github.com";
|
||||||
|
|
||||||
|
@ -31,27 +30,29 @@ export interface ReleaseData {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function githubGet(endpoint: string) {
|
export async function githubGet(endpoint: string) {
|
||||||
const opts: RequestOptions = {
|
const opts: RequestInit = {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/vnd.github+json",
|
Accept: "application/vnd.github+json",
|
||||||
"User-Agent": USER_AGENT
|
"User-Agent": USER_AGENT
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.GITHUB_TOKEN) opts.headers!.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
if (process.env.GITHUB_TOKEN) (opts.headers! as any).Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
||||||
|
|
||||||
return simpleGet(API_BASE + endpoint, opts);
|
return fetchie(API_BASE + endpoint, opts, { retryOnNetworkError: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadVencordFiles() {
|
export async function downloadVencordFiles() {
|
||||||
const release = await githubGet("/repos/Vendicated/Vencord/releases/latest");
|
const release = await githubGet("/repos/Vendicated/Vencord/releases/latest");
|
||||||
|
|
||||||
const { assets } = JSON.parse(release.toString("utf-8")) as ReleaseData;
|
const { assets }: ReleaseData = await release.json();
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
assets
|
assets
|
||||||
.filter(({ name }) => FILES_TO_DOWNLOAD.some(f => name.startsWith(f)))
|
.filter(({ name }) => FILES_TO_DOWNLOAD.some(f => name.startsWith(f)))
|
||||||
.map(({ name, browser_download_url }) => downloadFile(browser_download_url, join(VENCORD_FILES_DIR, name)))
|
.map(({ name, browser_download_url }) =>
|
||||||
|
downloadFile(browser_download_url, join(VENCORD_FILES_DIR, name), {}, { retryOnNetworkError: true })
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ export async function checkUpdates() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const raw = await githubGet("/repos/Vencord/Vesktop/releases/latest");
|
const raw = await githubGet("/repos/Vencord/Vesktop/releases/latest");
|
||||||
const data = JSON.parse(raw.toString("utf-8")) as ReleaseData;
|
const data: ReleaseData = await raw.json();
|
||||||
|
|
||||||
const oldVersion = app.getVersion();
|
const oldVersion = app.getVersion();
|
||||||
const newVersion = data.tag_name.replace(/^v/, "");
|
const newVersion = data.tag_name.replace(/^v/, "");
|
||||||
|
|
Loading…
Add table
Reference in a new issue