Skip to content
This repository was archived by the owner on Mar 10, 2022. It is now read-only.

Commit 2a1cbd1

Browse files
committed
feat: prompt user to create an access token if it's missing
1 parent 1cd752d commit 2a1cbd1

7 files changed

Lines changed: 133 additions & 27 deletions

File tree

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,13 @@
198198
"default": "https://sourcegraph.com",
199199
"description": "The base URL of the Sourcegraph instance to use."
200200
},
201+
"sourcegraph.accessToken": {
202+
"type": [
203+
"string"
204+
],
205+
"default": "",
206+
"description": "The access token to query the Sourcegraph API. Create a new access token at https://sourcegraph.com/users/settings/tokens"
207+
},
201208
"sourcegraph.remoteUrlReplacements": {
202209
"type": [
203210
"object"

src/commands/searchSelectionCommand.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import open from 'open'
12
import * as vscode from 'vscode'
23
import { repoInfo } from '../git'
34
import { endpointSetting } from '../settings/endpointSetting'
@@ -22,7 +23,7 @@ export async function searchSelectionCommand(extensionVersion: string): Promise<
2223
}
2324

2425
// Search in browser.
25-
open(
26+
await open(
2627
`${endpointSetting()}/-/editor` +
2728
`?remote_url=${encodeURIComponent(remoteURL)}` +
2829
`&branch=${encodeURIComponent(branch)}` +

src/file-system/SourcegraphFileSystemProvider.ts

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,12 @@ export class SourcegraphFileSystemProvider implements vscode.FileSystemProvider
4040
private didChangeFile = new vscode.EventEmitter<vscode.FileChangeEvent[]>() // Never used.
4141
public readonly onDidChangeFile: vscode.Event<vscode.FileChangeEvent[]> = this.didChangeFile.event
4242
public async stat(uri: vscode.Uri): Promise<vscode.FileStat> {
43-
try {
44-
const blob = await this.fetchBlob(this.sourcegraphUri(uri))
45-
return {
46-
mtime: blob.time,
47-
ctime: blob.time,
48-
size: blob.byteSize,
49-
type: blob.type,
50-
}
51-
} catch {
52-
const time = new Date().getMilliseconds()
53-
return {
54-
mtime: time,
55-
ctime: time,
56-
size: 0,
57-
type: vscode.FileType.Directory,
58-
}
43+
const blob = await this.fetchBlob(this.sourcegraphUri(uri))
44+
return {
45+
mtime: blob.time,
46+
ctime: blob.time,
47+
size: blob.byteSize,
48+
type: blob.type,
5949
}
6050
}
6151

@@ -76,7 +66,7 @@ export class SourcegraphFileSystemProvider implements vscode.FileSystemProvider
7666
const children = tree.directChildren(uri.path || '')
7767
return children.map(childUri => {
7868
const child = SourcegraphUri.parse(childUri)
79-
const type = child.isDirectory() ? vscode.FileType.Directory : vscode.FileType.File
69+
const type = child.isDirectory ? vscode.FileType.Directory : vscode.FileType.File
8070
return [child.basename(), type]
8171
})
8272
}

src/file-system/SourcegraphUri.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export class SourcegraphUri {
1111
public readonly repositoryName: string,
1212
public readonly revision: string | undefined,
1313
public readonly path: string | undefined,
14-
public readonly position: Position | undefined
14+
public readonly position: Position | undefined,
15+
public readonly isDirectory: boolean
1516
) {}
1617

1718
public withRevision(newRevision: string | undefined): SourcegraphUri {
@@ -49,10 +50,6 @@ export class SourcegraphUri {
4950
return undefined
5051
}
5152

52-
public isDirectory(): boolean {
53-
return typeof this.path === 'string' && this.uri.includes('/-/tree/')
54-
}
55-
5653
public isFile(): boolean {
5754
return typeof this.path === 'string' && this.uri.includes('/-/blob/')
5855
}
@@ -68,15 +65,15 @@ export class SourcegraphUri {
6865
}
6966
): SourcegraphUri {
7067
const revisionPart = optional?.revision ? `@${optional.revision}` : ''
71-
const directoryPart = optional?.isDirectory ? 'tree' : 'blob'
72-
const pathPart = optional?.path ? `/-/${directoryPart}/${optional?.path}` : ''
68+
const pathPart = optional?.path ? `/-/blob/${optional?.path}` : ''
7369
return new SourcegraphUri(
7470
`sourcegraph://${host}/${repositoryName}${revisionPart}${pathPart}`,
7571
host,
7672
repositoryName,
7773
optional?.revision,
7874
optional?.path,
79-
optional?.position
75+
optional?.position,
76+
optional?.isDirectory || false
8077
)
8178
}
8279
public repositoryUri(): string {

src/queries/currentUserQuery.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { emptyCancelationToken } from '../file-system/emptyCancelationToken'
2+
import { graphqlQueryWithAccessToken } from './graphqlQuery'
3+
4+
export async function currentUserQuery(accessToken: string): Promise<string> {
5+
const result = await graphqlQueryWithAccessToken<CurrentUserParameters, CurrentUserResult>(
6+
'query { currentUser { username }}',
7+
{},
8+
emptyCancelationToken(),
9+
accessToken
10+
)
11+
const username = result?.data?.currentUser?.username
12+
if (username) {
13+
return username
14+
}
15+
throw new Error(`invalid access token ${accessToken}`)
16+
}
17+
18+
interface CurrentUserParameters {}
19+
interface CurrentUserResult {
20+
data?: {
21+
currentUser?: {
22+
username?: string
23+
}
24+
}
25+
}

src/queries/graphqlQuery.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,24 @@ import { CancellationToken } from 'vscode'
33
import { log } from '../log'
44
import { debugEnabledSetting } from '../settings/debugEnabledSetting'
55
import { endpointHostnameSetting } from '../settings/endpointSetting'
6+
import { accessTokenSetting } from '../settings/accessTokenSetting'
67

78
export function graphqlQuery<A, B>(query: string, variables: A, token: CancellationToken): Promise<B | undefined> {
9+
return accessTokenSetting().then(accessToken => graphqlQueryWithAccessToken(query, variables, token, accessToken))
10+
}
11+
12+
export function graphqlQueryWithAccessToken<A, B>(
13+
query: string,
14+
variables: A,
15+
token: CancellationToken,
16+
accessToken: string
17+
): Promise<B | undefined> {
18+
log.appendLine(`accessToken ${accessToken}`)
819
return new Promise<B | undefined>((resolve, reject) => {
920
const data = JSON.stringify({
1021
query,
1122
variables,
1223
})
13-
const accessToken = process.env.SRC_ACCESS_TOKEN || ''
1424
const options: RequestOptions = {
1525
hostname: endpointHostnameSetting(),
1626
port: 443,

src/settings/accessTokenSetting.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import open from 'open'
2+
import * as vscode from 'vscode'
3+
import { log } from '../log'
4+
import { currentUserQuery } from '../queries/currentUserQuery'
5+
import { endpointSetting } from './endpointSetting'
6+
import { readConfiguration } from './readConfiguration'
7+
8+
let cachedAccessToken: Promise<string> | undefined
9+
10+
export function accessTokenSetting(): Promise<string> {
11+
const environmentVariable = process.env.SRC_ACCESS_TOKEN
12+
if (environmentVariable) {
13+
return Promise.resolve(environmentVariable)
14+
}
15+
16+
const fromSettings = readConfiguration().get<string>('accessToken', '')
17+
if (fromSettings) {
18+
return Promise.resolve(fromSettings)
19+
}
20+
21+
if (!cachedAccessToken) {
22+
cachedAccessToken = askUserToCreateAccessToken()
23+
cachedAccessToken.then(
24+
() => {},
25+
error => {
26+
log.error('askUserToCreateAccessToken', error)
27+
cachedAccessToken = undefined
28+
}
29+
)
30+
}
31+
return cachedAccessToken
32+
}
33+
34+
async function askUserToCreateAccessToken(): Promise<string> {
35+
const openBrowserMessage = 'Open browser to create an access token'
36+
const learnMore = 'Learn more about access tokens'
37+
const userChoice = await vscode.window.showErrorMessage(
38+
'Missing Sourcegraph Access Token',
39+
{
40+
modal: true,
41+
detail: 'An access token is required to use the Sourcegraph extension. To fix this problem, create a new access token on the Sourcegraph website or set the $SRC_ACCESS_TOKEN environment variable and restart VS Code.',
42+
},
43+
openBrowserMessage,
44+
learnMore
45+
)
46+
const openUrl =
47+
userChoice === openBrowserMessage
48+
? `${endpointSetting()}/user/settings/tokens`
49+
: userChoice === learnMore
50+
? 'https://docs.sourcegraph.com/cli/how-tos/creating_an_access_token'
51+
: undefined
52+
if (openUrl) {
53+
await open(openUrl)
54+
const token = await vscode.window.showInputBox({
55+
title: 'Paste your Sourcegraph access token here',
56+
ignoreFocusOut: true,
57+
})
58+
if (token) {
59+
try {
60+
const currentUser = await currentUserQuery(token)
61+
log.appendLine(`Logged in successfully as '${currentUser}'`)
62+
await readConfiguration().update('accessToken', token, vscode.ConfigurationTarget.Global)
63+
return token
64+
} catch {
65+
await vscode.window.showErrorMessage(
66+
"Invalid Access Token. To fix this problem, update the 'sourcegraph.accessToken' setting and try again"
67+
)
68+
}
69+
} else {
70+
log.error('askUserToCreateAccessToken - The user provided an empty access token')
71+
}
72+
} else {
73+
log.error('askUserToCreateAccessToken - The user decided not to open the browser')
74+
}
75+
throw new Error('No access token')
76+
}

0 commit comments

Comments
 (0)