Skip to content

Commit b93ced6

Browse files
committed
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
1 parent bcdedbb commit b93ced6

File tree

3 files changed

+72
-4
lines changed

3 files changed

+72
-4
lines changed

editors/code/src/main.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,9 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi
173173
if (!shouldCheckForNewNightly) return;
174174
}
175175

176-
const release = await fetchRelease("nightly").catch((e) => {
176+
const release = await performDownloadWithRetryDialog(async () => {
177+
return await fetchRelease("nightly", state.githubToken);
178+
}, state).catch((e) => {
177179
log.error(e);
178180
if (state.releaseId === undefined) { // Show error only for the initial download
179181
vscode.window.showErrorMessage(`Failed to download rust-analyzer nightly ${e}`);
@@ -308,7 +310,10 @@ async function getServer(config: Config, state: PersistentState): Promise<string
308310
if (userResponse !== "Download now") return dest;
309311
}
310312

311-
const release = await fetchRelease(config.package.releaseTag);
313+
const releaseTag = config.package.releaseTag;
314+
const release = await performDownloadWithRetryDialog(async () => {
315+
return await fetchRelease(releaseTag, state.githubToken);
316+
}, state);
312317
const artifact = release.assets.find(artifact => artifact.name === `rust-analyzer-${platform}.gz`);
313318
assert(!!artifact, `Bad release: ${JSON.stringify(release)}`);
314319

@@ -333,3 +338,49 @@ async function getServer(config: Config, state: PersistentState): Promise<string
333338
await state.updateServerVersion(config.package.version);
334339
return dest;
335340
}
341+
342+
async function performDownloadWithRetryDialog<T>(downloadFunc: () => Promise<T>, state: PersistentState): Promise<T> {
343+
while (true) {
344+
try {
345+
return await downloadFunc();
346+
} catch (e) {
347+
let selected = await vscode.window.showErrorMessage("Failed perform download: " + e.message, {}, {
348+
title: "Update Github Auth Token",
349+
updateToken: true,
350+
}, {
351+
title: "Retry download",
352+
retry: true,
353+
}, {
354+
title: "Dismiss",
355+
});
356+
357+
if (selected?.updateToken) {
358+
await queryForGithubToken(state);
359+
continue;
360+
} else if (selected?.retry) {
361+
continue;
362+
}
363+
throw e;
364+
};
365+
}
366+
367+
}
368+
369+
async function queryForGithubToken(state: PersistentState): Promise<void> {
370+
const githubTokenOptions: vscode.InputBoxOptions = {
371+
value: state.githubToken,
372+
password: true,
373+
prompt: `
374+
This dialog allows to store a Github authorization token.
375+
The usage of an authorization token allows will increase the rate
376+
limit on the use of Github APIs and can thereby prevent getting
377+
throttled.
378+
Auth tokens can be obtained at https://github.com/settings/tokens`,
379+
};
380+
381+
const newToken = await vscode.window.showInputBox(githubTokenOptions);
382+
if (newToken) {
383+
log.info("Storing new github token");
384+
await state.updateGithubToken(newToken);
385+
}
386+
}

editors/code/src/net.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ const OWNER = "rust-analyzer";
1818
const REPO = "rust-analyzer";
1919

2020
export async function fetchRelease(
21-
releaseTag: string
21+
releaseTag: string,
22+
githubToken: string | null | undefined,
2223
): Promise<GithubRelease> {
2324

2425
const apiEndpointPath = `/repos/${OWNER}/${REPO}/releases/tags/${releaseTag}`;
@@ -27,7 +28,12 @@ export async function fetchRelease(
2728

2829
log.debug("Issuing request for released artifacts metadata to", requestUrl);
2930

30-
const response = await fetch(requestUrl, { headers: { Accept: "application/vnd.github.v3+json" } });
31+
var headers: any = { Accept: "application/vnd.github.v3+json" };
32+
if (githubToken != null) {
33+
headers.Authorization = "token " + githubToken;
34+
}
35+
36+
const response = await fetch(requestUrl, { headers: headers });
3137

3238
if (!response.ok) {
3339
log.error("Error fetching artifact release info", {

editors/code/src/persistent_state.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,15 @@ export class PersistentState {
3838
async updateServerVersion(value: string | undefined) {
3939
await this.globalState.update("serverVersion", value);
4040
}
41+
42+
/**
43+
* Github authorization token.
44+
* This is used for API requests against the Github API.
45+
*/
46+
get githubToken(): string | undefined {
47+
return this.globalState.get("githubToken");
48+
}
49+
async updateGithubToken(value: string | undefined) {
50+
await this.globalState.update("githubToken", value);
51+
}
4152
}

0 commit comments

Comments
 (0)