Skip to content

Commit b5a8898

Browse files
authored
feat(codecatalyst): guide users to connect if not already connected (#3026)
1 parent 8f55e40 commit b5a8898

File tree

5 files changed

+66
-5
lines changed

5 files changed

+66
-5
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "Amazon CodeCatalyst: Connecting to AWS Builder ID when coming from the browser has been made easier"
4+
}

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/codecatalyst/auth.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@ import {
1313
SsoConnection,
1414
codecatalystScopes,
1515
hasScopes,
16+
createBuilderIdConnection,
1617
} from '../credentials/auth'
1718
import { getSecondaryAuth } from '../credentials/secondaryAuth'
1819
import { getLogger } from '../shared/logger'
20+
import * as localizedText from '../shared/localizedText'
21+
import { ToolkitError } from '../shared/errors'
22+
import { MetricName, MetricShapes, telemetry } from '../shared/telemetry/telemetry'
1923

2024
// Secrets stored on the macOS keychain appear as individual entries for each key
2125
// This is fine so long as the user has only a few accounts. Otherwise this should
@@ -90,6 +94,58 @@ export class CodeCatalystAuthenticationProvider {
9094
await this.secondaryAuth.restoreConnection()
9195
}
9296

97+
public async promptNotConnected(): Promise<SsoConnection> {
98+
type ConnectionFlowEvent = Partial<MetricShapes[MetricName]> & {
99+
readonly codecatalyst_connectionFlow: 'Create' | 'Switch' | 'Upgrade'
100+
}
101+
102+
const conn = (await this.auth.listConnections()).find(isBuilderIdConnection)
103+
const isNewUser = conn === undefined
104+
const okItem: vscode.MessageItem = { title: localizedText.ok }
105+
const cancelItem: vscode.MessageItem = { title: localizedText.cancel, isCloseAffordance: true }
106+
107+
if (isNewUser || !isValidCodeCatalystConnection(conn)) {
108+
// TODO: change to `satisfies` on TS 4.9
109+
telemetry.record({ codecatalyst_connectionFlow: isNewUser ? 'Create' : 'Upgrade' } as ConnectionFlowEvent)
110+
111+
const message = isNewUser
112+
? 'CodeCatalyst requires an AWS Builder ID connection. Creating a connection opens your browser to login.\n\n Create one now?'
113+
: 'Your AWS Builder ID connection does not have access to CodeCatalyst. Upgrading the connection requires another login.\n\n Upgrade now?'
114+
const resp = await vscode.window.showInformationMessage(message, { modal: true }, okItem, cancelItem)
115+
if (resp !== okItem) {
116+
throw new ToolkitError('Not connected to CodeCatalyst', { code: 'NoConnection', cancelled: true })
117+
}
118+
119+
const newConn = await createBuilderIdConnection(this.auth)
120+
if (this.auth.activeConnection?.id !== newConn.id) {
121+
await this.secondaryAuth.useNewConnection(newConn)
122+
}
123+
124+
return newConn
125+
}
126+
127+
if (this.auth.activeConnection?.id !== conn.id) {
128+
// TODO: change to `satisfies` on TS 4.9
129+
telemetry.record({ codecatalyst_connectionFlow: 'Switch' } as ConnectionFlowEvent)
130+
131+
const resp = await vscode.window.showInformationMessage(
132+
'CodeCatalyst requires an AWS Builder ID connection.\n\n Switch to it now?',
133+
{ modal: true },
134+
okItem,
135+
cancelItem
136+
)
137+
if (resp !== okItem) {
138+
throw new ToolkitError('Not connected to CodeCatalyst', { code: 'NoConnection', cancelled: true })
139+
}
140+
141+
await this.secondaryAuth.useNewConnection(conn)
142+
143+
return conn
144+
}
145+
146+
throw new ToolkitError('Not connected to CodeCatalyst', { code: 'NoConnectionBadState' })
147+
}
148+
93149
private static instance: CodeCatalystAuthenticationProvider
94150

95151
public static fromContext(ctx: Pick<vscode.ExtensionContext, 'secrets' | 'globalState'>) {

src/codecatalyst/commands.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,12 @@ function createClientInjector(
133133
clientFactory: () => Promise<CodeCatalystClient>
134134
): ClientInjector {
135135
return async (command, ...args) => {
136-
const client = await clientFactory()
136+
let client = await clientFactory()
137137

138138
try {
139139
if (!client.connected) {
140-
throw new ToolkitError('Not connected to CodeCatalyst', { code: 'NoConnection' })
140+
const conn = await authProvider.promptNotConnected()
141+
client = await client.setCredentials(async () => (await conn.getToken()).accessToken)
141142
}
142143

143144
return await command(client, ...args)

src/codecatalyst/reconnect.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ const MAX_RECONNECT_TIME = 10 * 60 * 1000
3030

3131
export function watchRestartingDevEnvs(ctx: ExtContext, authProvider: CodeCatalystAuthenticationProvider) {
3232
let restartHandled = false
33-
authProvider.onDidChangeActiveConnection(async () => {
34-
if (restartHandled) {
33+
authProvider.onDidChangeActiveConnection(async conn => {
34+
if (restartHandled || conn === undefined) {
3535
return
3636
}
3737

0 commit comments

Comments
 (0)