diff options
author | Matthias Einwag <[email protected]> | 2020-09-23 07:12:51 +0100 |
---|---|---|
committer | Matthias Einwag <[email protected]> | 2020-09-23 07:12:51 +0100 |
commit | b93ced6f633fab2733b40aef2541582b00e053fb (patch) | |
tree | d4f100e94e0f8cbece5fddc76385f42cdd181e45 | |
parent | bcdedbb3d5a45ea974cc5f8e9068e9604c43a757 (diff) |
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
-rw-r--r-- | editors/code/src/main.ts | 55 | ||||
-rw-r--r-- | editors/code/src/net.ts | 10 | ||||
-rw-r--r-- | editors/code/src/persistent_state.ts | 11 |
3 files changed, 72 insertions, 4 deletions
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index bd99d696a..8c1610570 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -173,7 +173,9 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi | |||
173 | if (!shouldCheckForNewNightly) return; | 173 | if (!shouldCheckForNewNightly) return; |
174 | } | 174 | } |
175 | 175 | ||
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) => { | ||
177 | log.error(e); | 179 | log.error(e); |
178 | if (state.releaseId === undefined) { // Show error only for the initial download | 180 | if (state.releaseId === undefined) { // Show error only for the initial download |
179 | vscode.window.showErrorMessage(`Failed to download rust-analyzer nightly ${e}`); | 181 | vscode.window.showErrorMessage(`Failed to download rust-analyzer nightly ${e}`); |
@@ -308,7 +310,10 @@ async function getServer(config: Config, state: PersistentState): Promise<string | |||
308 | if (userResponse !== "Download now") return dest; | 310 | if (userResponse !== "Download now") return dest; |
309 | } | 311 | } |
310 | 312 | ||
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); | ||
312 | const artifact = release.assets.find(artifact => artifact.name === `rust-analyzer-${platform}.gz`); | 317 | const artifact = release.assets.find(artifact => artifact.name === `rust-analyzer-${platform}.gz`); |
313 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); | 318 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); |
314 | 319 | ||
@@ -333,3 +338,49 @@ async function getServer(config: Config, state: PersistentState): Promise<string | |||
333 | await state.updateServerVersion(config.package.version); | 338 | await state.updateServerVersion(config.package.version); |
334 | return dest; | 339 | return dest; |
335 | } | 340 | } |
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 | } \ No newline at end of file | ||
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts index 5eba2728d..d6194b63e 100644 --- a/editors/code/src/net.ts +++ b/editors/code/src/net.ts | |||
@@ -18,7 +18,8 @@ const OWNER = "rust-analyzer"; | |||
18 | const REPO = "rust-analyzer"; | 18 | const REPO = "rust-analyzer"; |
19 | 19 | ||
20 | export async function fetchRelease( | 20 | export async function fetchRelease( |
21 | releaseTag: string | 21 | releaseTag: string, |
22 | githubToken: string | null | undefined, | ||
22 | ): Promise<GithubRelease> { | 23 | ): Promise<GithubRelease> { |
23 | 24 | ||
24 | const apiEndpointPath = `/repos/${OWNER}/${REPO}/releases/tags/${releaseTag}`; | 25 | const apiEndpointPath = `/repos/${OWNER}/${REPO}/releases/tags/${releaseTag}`; |
@@ -27,7 +28,12 @@ export async function fetchRelease( | |||
27 | 28 | ||
28 | log.debug("Issuing request for released artifacts metadata to", requestUrl); | 29 | log.debug("Issuing request for released artifacts metadata to", requestUrl); |
29 | 30 | ||
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 }); | ||
31 | 37 | ||
32 | if (!response.ok) { | 38 | if (!response.ok) { |
33 | log.error("Error fetching artifact release info", { | 39 | log.error("Error fetching artifact release info", { |
diff --git a/editors/code/src/persistent_state.ts b/editors/code/src/persistent_state.ts index 5705eed81..afb652589 100644 --- a/editors/code/src/persistent_state.ts +++ b/editors/code/src/persistent_state.ts | |||
@@ -38,4 +38,15 @@ export class PersistentState { | |||
38 | async updateServerVersion(value: string | undefined) { | 38 | async updateServerVersion(value: string | undefined) { |
39 | await this.globalState.update("serverVersion", value); | 39 | await this.globalState.update("serverVersion", value); |
40 | } | 40 | } |
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 | } | ||
41 | } | 52 | } |