about summary refs log tree commit diff
path: root/editors/code
diff options
context:
space:
mode:
authorMatthias Einwag <matthias.einwag@live.com>2020-09-22 23:12:51 -0700
committerMatthias Einwag <matthias.einwag@live.com>2020-09-22 23:12:51 -0700
commitb93ced6f633fab2733b40aef2541582b00e053fb (patch)
treed4f100e94e0f8cbece5fddc76385f42cdd181e45 /editors/code
parentbcdedbb3d5a45ea974cc5f8e9068e9604c43a757 (diff)
downloadrust-b93ced6f633fab2733b40aef2541582b00e053fb.tar.gz
rust-b93ced6f633fab2733b40aef2541582b00e053fb.zip
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
Diffstat (limited to 'editors/code')
-rw-r--r--editors/code/src/main.ts55
-rw-r--r--editors/code/src/net.ts10
-rw-r--r--editors/code/src/persistent_state.ts11
3 files changed, 72 insertions, 4 deletions
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index bd99d696ad8..8c161057063 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
         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);
+    }
+}
\ No newline at end of file
diff --git a/editors/code/src/net.ts b/editors/code/src/net.ts
index 5eba2728d28..d6194b63e4b 100644
--- a/editors/code/src/net.ts
+++ b/editors/code/src/net.ts
@@ -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", {
diff --git a/editors/code/src/persistent_state.ts b/editors/code/src/persistent_state.ts
index 5705eed812d..afb65258991 100644
--- a/editors/code/src/persistent_state.ts
+++ b/editors/code/src/persistent_state.ts
@@ -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);
+    }
 }