Allow to use a Github Auth token for fetching releases

This change allows to use a authorization token provided by Github in
order to fetch metadata for a RA release. Using an authorization token
prevents to get rate-limited in environments where lots of RA users use
a shared client IP (e.g. behind a company NAT).

The auth token is stored in `ExtensionContext.globalState`.
As far as I could observe through testing with a local WSL2 environment
that state is synced between an extension installed locally and a remote
version.

The change provides no explicit command to query for an auth token.
However in case a download fails it will provide a retry option as well
as an option to enter the auth token. This should be more discoverable
for most users.

Closes #3688
This commit is contained in:
Matthias Einwag 2020-09-22 23:12:51 -07:00
parent bcdedbb3d5
commit b93ced6f63
3 changed files with 72 additions and 4 deletions

View file

@ -173,7 +173,9 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi
if (!shouldCheckForNewNightly) return;
}
const release = await fetchRelease("nightly").catch((e) => {
const release = await performDownloadWithRetryDialog(async () => {
return await fetchRelease("nightly", state.githubToken);
}, state).catch((e) => {
log.error(e);
if (state.releaseId === undefined) { // Show error only for the initial download
vscode.window.showErrorMessage(`Failed to download rust-analyzer nightly ${e}`);
@ -308,7 +310,10 @@ async function getServer(config: Config, state: PersistentState): Promise<string
if (userResponse !== "Download now") return dest;
}
const release = await fetchRelease(config.package.releaseTag);
const releaseTag = config.package.releaseTag;
const release = await performDownloadWithRetryDialog(async () => {
return await fetchRelease(releaseTag, state.githubToken);
}, state);
const artifact = release.assets.find(artifact => artifact.name === `rust-analyzer-${platform}.gz`);
assert(!!artifact, `Bad release: ${JSON.stringify(release)}`);
@ -333,3 +338,49 @@ async function getServer(config: Config, state: PersistentState): Promise<string
await state.updateServerVersion(config.package.version);
return dest;
}
async function performDownloadWithRetryDialog<T>(downloadFunc: () => Promise<T>, state: PersistentState): Promise<T> {
while (true) {
try {
return await downloadFunc();
} catch (e) {
let selected = await vscode.window.showErrorMessage("Failed perform download: " + e.message, {}, {
title: "Update Github Auth Token",
updateToken: true,
}, {
title: "Retry download",
retry: true,
}, {
title: "Dismiss",
});
if (selected?.updateToken) {
await queryForGithubToken(state);
continue;
} else if (selected?.retry) {
continue;
}
throw e;
};
}
}
async function queryForGithubToken(state: PersistentState): Promise<void> {
const githubTokenOptions: vscode.InputBoxOptions = {
value: state.githubToken,
password: true,
prompt: `
This dialog allows to store a Github authorization token.
The usage of an authorization token allows will increase the rate
limit on the use of Github APIs and can thereby prevent getting
throttled.
Auth tokens can be obtained at https://github.com/settings/tokens`,
};
const newToken = await vscode.window.showInputBox(githubTokenOptions);
if (newToken) {
log.info("Storing new github token");
await state.updateGithubToken(newToken);
}
}

View file

@ -18,7 +18,8 @@ const OWNER = "rust-analyzer";
const REPO = "rust-analyzer";
export async function fetchRelease(
releaseTag: string
releaseTag: string,
githubToken: string | null | undefined,
): Promise<GithubRelease> {
const apiEndpointPath = `/repos/${OWNER}/${REPO}/releases/tags/${releaseTag}`;
@ -27,7 +28,12 @@ export async function fetchRelease(
log.debug("Issuing request for released artifacts metadata to", requestUrl);
const response = await fetch(requestUrl, { headers: { Accept: "application/vnd.github.v3+json" } });
var headers: any = { Accept: "application/vnd.github.v3+json" };
if (githubToken != null) {
headers.Authorization = "token " + githubToken;
}
const response = await fetch(requestUrl, { headers: headers });
if (!response.ok) {
log.error("Error fetching artifact release info", {

View file

@ -38,4 +38,15 @@ export class PersistentState {
async updateServerVersion(value: string | undefined) {
await this.globalState.update("serverVersion", value);
}
/**
* Github authorization token.
* This is used for API requests against the Github API.
*/
get githubToken(): string | undefined {
return this.globalState.get("githubToken");
}
async updateGithubToken(value: string | undefined) {
await this.globalState.update("githubToken", value);
}
}