diff --git a/src/utils/download.mts b/src/utils/download.mts index f02be173..4b1c73a5 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -36,6 +36,7 @@ import { GITHUB_API_VERSION, HTTP_STATUS_UNAUTHORIZED, githubApiUnauthorized, + HTTP_STATUS_FORBIDDEN, } from "./githubREST.mjs"; import { unxzFile, unzipFile } from "./downloadHelpers.mjs"; import type { Writable } from "stream"; @@ -540,6 +541,8 @@ async function downloadAndInstallGithubAsset( maxRedirections: 0, // don't automatically follow redirects }; } + // keep track of retries + let retries = 0; return new Promise(resolve => { //, data: Dispatcher.StreamData) @@ -562,6 +565,18 @@ async function downloadAndInstallGithubAsset( if (code >= 400) { if (code === HTTP_STATUS_UNAUTHORIZED) { + retries++; + if (retries > 2) { + Logger.error( + LoggerSource.downloader, + `Downloading ${logName} failed. Multiple ` + STATUS_CODES[code] + ); + resolve(false); + + return; + } + // githubApiUnauthorized will take care of removing the + // token so it isn't used in retry attpemt githubApiUnauthorized() .then(() => { client.stream( @@ -573,6 +588,12 @@ async function downloadAndInstallGithubAsset( .catch(() => { resolve(false); }); + } else if (code === HTTP_STATUS_FORBIDDEN) { + Logger.error( + LoggerSource.downloader, + `Downloading ${logName} failed: ` + STATUS_CODES[code] + ); + resolve(false); } //return reject(new Error(STATUS_CODES[code])); Logger.error( diff --git a/src/utils/githubREST.mts b/src/utils/githubREST.mts index 74cd1f7f..204e12cd 100644 --- a/src/utils/githubREST.mts +++ b/src/utils/githubREST.mts @@ -5,12 +5,13 @@ import GithubApiCache, { } from "./githubApiCache.mjs"; import { type RequestOptions, request } from "https"; import { unknownErrorToString, unknownToError } from "./errorHelper.mjs"; -import { window } from "vscode"; +import { env, Uri, window } from "vscode"; // TODO: move into web consts export const HTTP_STATUS_OK = 200; export const HTTP_STATUS_NOT_MODIFIED = 304; export const HTTP_STATUS_UNAUTHORIZED = 401; +export const HTTP_STATUS_FORBIDDEN = 403; export const EXT_USER_AGENT = "Raspberry Pi Pico VS Code Extension"; export const GITHUB_API_BASE_URL = "https://api.github.com"; export const GITHUB_API_VERSION = "2022-11-28"; @@ -247,6 +248,12 @@ async function getReleases(repository: GithubRepository): Promise { await githubApiUnauthorized(); return getReleases(repository); + } else if (response.status === HTTP_STATUS_FORBIDDEN) { + githubApiForbidden(); + + // return the default response as without a PAT + // ther is no way a rerun will succeed in the near future + throw new Error("GitHub API Code 403 Forbidden"); } else if (response.status !== 200) { throw new Error("Error http status code: " + response.status); } @@ -359,6 +366,12 @@ export async function getGithubReleaseByTag( await githubApiUnauthorized(); return getGithubReleaseByTag(repository, tag); + } else if (response.status === HTTP_STATUS_FORBIDDEN) { + githubApiForbidden(); + + // return the default response as without a PAT + // ther is no way a rerun will succeed in the near future + throw new Error("GitHub API Code 403 Forbidden"); } else if (response.status !== 200) { throw new Error("Error http status code: " + response.status); } @@ -418,3 +431,32 @@ export async function githubApiUnauthorized(): Promise { ); await Settings.getInstance()?.updateGlobal(SettingsKey.githubToken, ""); } + +/** + * Show rate limit exceeded warning to the user. + */ +export function githubApiForbidden(): void { + Logger.warn( + LoggerSource.githubRestApi, + "GitHub API Code 403 Forbidden. Probably rate limit exceeded." + ); + // show a rate limit warning to the user + void window + .showWarningMessage( + "GitHub API rate limit exceeded. Please try again later." + + "Consider adding a GitHub Personal Access Token in the settings " + + "to increase your rate limit.", + "More Info" + ) + .then(selection => { + if (selection === "More Info") { + env.openExternal( + Uri.parse( + "https://github.com/raspberrypi/pico-vscode" + + "/?tab=readme-ov-file#github-api-rate-limit-error-" + + "while-retrieving-sdk-and-toolchain-versions" + ) + ); + } + }); +} diff --git a/src/webview/newProjectPanel.mts b/src/webview/newProjectPanel.mts index 28809712..6888403c 100644 --- a/src/webview/newProjectPanel.mts +++ b/src/webview/newProjectPanel.mts @@ -911,7 +911,7 @@ export class NewProjectPanel { increment: 100, }); await window.showErrorMessage( - "Failed to get ninja version for the selected Pico-SDK version." + "Failed to get ninja version for the selected Pico SDK version." ); return; @@ -1179,7 +1179,7 @@ export class NewProjectPanel { await this._updateTheme(); } else { void window.showErrorMessage( - "Failed to load available Pico-SDKs and/or supported toolchains. This may be due to an outdated personal access token for GitHub." + "Failed to load available Pico SDKs and/or supported toolchains. This may be due to an outdated personal access token for GitHub or a exceeded rate limit." ); this.dispose(); } @@ -1615,7 +1615,7 @@ export class NewProjectPanel {
- +