Skip to content

Commit 45e6a16

Browse files
committed
fix: autologin flow can now also be canceled
- rest api is now suspendable giving a chance to the user to propagate the cancellation exception during the execution - reset the auto-login flag once the user hits back
1 parent 93dbc2a commit 45e6a16

File tree

7 files changed

+78
-60
lines changed

7 files changed

+78
-60
lines changed

src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,26 +74,35 @@ class CoderRemoteEnvironment(
7474
if (wsRawStatus.canStart()) {
7575
if (workspace.outdated) {
7676
actions.add(Action(context.i18n.ptrl("Update and start")) {
77-
val build = client.updateWorkspace(workspace)
78-
update(workspace.copy(latestBuild = build), agent)
77+
context.cs.launch {
78+
val build = client.updateWorkspace(workspace)
79+
update(workspace.copy(latestBuild = build), agent)
80+
}
7981
})
8082
} else {
8183
actions.add(Action(context.i18n.ptrl("Start")) {
82-
val build = client.startWorkspace(workspace)
83-
update(workspace.copy(latestBuild = build), agent)
84+
context.cs.launch {
85+
val build = client.startWorkspace(workspace)
86+
update(workspace.copy(latestBuild = build), agent)
87+
88+
}
8489
})
8590
}
8691
}
8792
if (wsRawStatus.canStop()) {
8893
if (workspace.outdated) {
8994
actions.add(Action(context.i18n.ptrl("Update and restart")) {
90-
val build = client.updateWorkspace(workspace)
91-
update(workspace.copy(latestBuild = build), agent)
95+
context.cs.launch {
96+
val build = client.updateWorkspace(workspace)
97+
update(workspace.copy(latestBuild = build), agent)
98+
}
9299
})
93100
} else {
94101
actions.add(Action(context.i18n.ptrl("Stop")) {
95-
val build = client.stopWorkspace(workspace)
96-
update(workspace.copy(latestBuild = build), agent)
102+
context.cs.launch {
103+
val build = client.stopWorkspace(workspace)
104+
update(workspace.copy(latestBuild = build), agent)
105+
}
97106
})
98107
}
99108
}

src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ class CoderRemoteProvider(
175175
private fun logout() {
176176
// Keep the URL and token to make it easy to log back in, but set
177177
// rememberMe to false so we do not try to automatically log in.
178-
context.secrets.rememberMe = "false"
178+
context.secrets.rememberMe = false
179179
close()
180180
}
181181

@@ -292,7 +292,7 @@ class CoderRemoteProvider(
292292
/**
293293
* Return the sign-in page if we do not have a valid client.
294294
295-
* Otherwise return null, which causes Toolbox to display the environment
295+
* Otherwise, return null, which causes Toolbox to display the environment
296296
* list.
297297
*/
298298
override fun getOverrideUiPage(): UiPage? {
@@ -327,14 +327,14 @@ class CoderRemoteProvider(
327327
return null
328328
}
329329

330-
private fun shouldDoAutoLogin(): Boolean = firstRun && context.secrets.rememberMe == "true"
330+
private fun shouldDoAutoLogin(): Boolean = firstRun && context.secrets.rememberMe == true
331331

332332
private fun onConnect(client: CoderRestClient, cli: CoderCLIManager) {
333333
// Store the URL and token for use next time.
334334
context.secrets.lastDeploymentURL = client.url.toString()
335335
context.secrets.lastToken = client.token ?: ""
336336
// Currently we always remember, but this could be made an option.
337-
context.secrets.rememberMe = "true"
337+
context.secrets.rememberMe = true
338338
this.client = client
339339
pollError = null
340340
pollJob?.cancel()

src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ open class CoderRestClient(
139139
*
140140
* @throws [APIResponseException].
141141
*/
142-
fun authenticate(): User {
142+
suspend fun authenticate(): User {
143143
me = me()
144144
buildVersion = buildInfo().version
145145
return me
@@ -149,8 +149,8 @@ open class CoderRestClient(
149149
* Retrieve the current user.
150150
* @throws [APIResponseException].
151151
*/
152-
fun me(): User {
153-
val userResponse = retroRestClient.me().execute()
152+
suspend fun me(): User {
153+
val userResponse = retroRestClient.me()
154154
if (!userResponse.isSuccessful) {
155155
throw APIResponseException("authenticate", url, userResponse)
156156
}
@@ -162,8 +162,8 @@ open class CoderRestClient(
162162
* Retrieves the available workspaces created by the user.
163163
* @throws [APIResponseException].
164164
*/
165-
fun workspaces(): List<Workspace> {
166-
val workspacesResponse = retroRestClient.workspaces("owner:me").execute()
165+
suspend fun workspaces(): List<Workspace> {
166+
val workspacesResponse = retroRestClient.workspaces("owner:me")
167167
if (!workspacesResponse.isSuccessful) {
168168
throw APIResponseException("retrieve workspaces", url, workspacesResponse)
169169
}
@@ -175,8 +175,8 @@ open class CoderRestClient(
175175
* Retrieves a workspace with the provided id.
176176
* @throws [APIResponseException].
177177
*/
178-
fun workspace(workspaceID: UUID): Workspace {
179-
val workspacesResponse = retroRestClient.workspace(workspaceID).execute()
178+
suspend fun workspace(workspaceID: UUID): Workspace {
179+
val workspacesResponse = retroRestClient.workspace(workspaceID)
180180
if (!workspacesResponse.isSuccessful) {
181181
throw APIResponseException("retrieve workspace", url, workspacesResponse)
182182
}
@@ -188,7 +188,7 @@ open class CoderRestClient(
188188
* Retrieves all the agent names for all workspaces, including those that
189189
* are off. Meant to be used when configuring SSH.
190190
*/
191-
fun agentNames(workspaces: List<Workspace>): Set<String> {
191+
suspend fun agentNames(workspaces: List<Workspace>): Set<String> {
192192
// It is possible for there to be resources with duplicate names so we
193193
// need to use a set.
194194
return workspaces.flatMap { ws ->
@@ -205,17 +205,17 @@ open class CoderRestClient(
205205
* removing hosts from the SSH config when they are off).
206206
* @throws [APIResponseException].
207207
*/
208-
fun resources(workspace: Workspace): List<WorkspaceResource> {
208+
suspend fun resources(workspace: Workspace): List<WorkspaceResource> {
209209
val resourcesResponse =
210-
retroRestClient.templateVersionResources(workspace.latestBuild.templateVersionID).execute()
210+
retroRestClient.templateVersionResources(workspace.latestBuild.templateVersionID)
211211
if (!resourcesResponse.isSuccessful) {
212212
throw APIResponseException("retrieve resources for ${workspace.name}", url, resourcesResponse)
213213
}
214214
return resourcesResponse.body()!!
215215
}
216216

217-
fun buildInfo(): BuildInfo {
218-
val buildInfoResponse = retroRestClient.buildInfo().execute()
217+
suspend fun buildInfo(): BuildInfo {
218+
val buildInfoResponse = retroRestClient.buildInfo()
219219
if (!buildInfoResponse.isSuccessful) {
220220
throw APIResponseException("retrieve build information", url, buildInfoResponse)
221221
}
@@ -225,8 +225,8 @@ open class CoderRestClient(
225225
/**
226226
* @throws [APIResponseException].
227227
*/
228-
private fun template(templateID: UUID): Template {
229-
val templateResponse = retroRestClient.template(templateID).execute()
228+
private suspend fun template(templateID: UUID): Template {
229+
val templateResponse = retroRestClient.template(templateID)
230230
if (!templateResponse.isSuccessful) {
231231
throw APIResponseException("retrieve template with ID $templateID", url, templateResponse)
232232
}
@@ -236,9 +236,9 @@ open class CoderRestClient(
236236
/**
237237
* @throws [APIResponseException].
238238
*/
239-
fun startWorkspace(workspace: Workspace): WorkspaceBuild {
239+
suspend fun startWorkspace(workspace: Workspace): WorkspaceBuild {
240240
val buildRequest = CreateWorkspaceBuildRequest(null, WorkspaceTransition.START)
241-
val buildResponse = retroRestClient.createWorkspaceBuild(workspace.id, buildRequest).execute()
241+
val buildResponse = retroRestClient.createWorkspaceBuild(workspace.id, buildRequest)
242242
if (buildResponse.code() != HttpURLConnection.HTTP_CREATED) {
243243
throw APIResponseException("start workspace ${workspace.name}", url, buildResponse)
244244
}
@@ -247,9 +247,9 @@ open class CoderRestClient(
247247

248248
/**
249249
*/
250-
fun stopWorkspace(workspace: Workspace): WorkspaceBuild {
250+
suspend fun stopWorkspace(workspace: Workspace): WorkspaceBuild {
251251
val buildRequest = CreateWorkspaceBuildRequest(null, WorkspaceTransition.STOP)
252-
val buildResponse = retroRestClient.createWorkspaceBuild(workspace.id, buildRequest).execute()
252+
val buildResponse = retroRestClient.createWorkspaceBuild(workspace.id, buildRequest)
253253
if (buildResponse.code() != HttpURLConnection.HTTP_CREATED) {
254254
throw APIResponseException("stop workspace ${workspace.name}", url, buildResponse)
255255
}
@@ -259,9 +259,9 @@ open class CoderRestClient(
259259
/**
260260
* @throws [APIResponseException] if issues are encountered during deletion
261261
*/
262-
fun removeWorkspace(workspace: Workspace) {
262+
suspend fun removeWorkspace(workspace: Workspace) {
263263
val buildRequest = CreateWorkspaceBuildRequest(null, WorkspaceTransition.DELETE, false)
264-
val buildResponse = retroRestClient.createWorkspaceBuild(workspace.id, buildRequest).execute()
264+
val buildResponse = retroRestClient.createWorkspaceBuild(workspace.id, buildRequest)
265265
if (buildResponse.code() != HttpURLConnection.HTTP_CREATED) {
266266
throw APIResponseException("delete workspace ${workspace.name}", url, buildResponse)
267267
}
@@ -277,11 +277,11 @@ open class CoderRestClient(
277277
* with this information when we do two START builds in a row.
278278
* @throws [APIResponseException].
279279
*/
280-
fun updateWorkspace(workspace: Workspace): WorkspaceBuild {
280+
suspend fun updateWorkspace(workspace: Workspace): WorkspaceBuild {
281281
val template = template(workspace.templateID)
282282
val buildRequest =
283283
CreateWorkspaceBuildRequest(template.activeVersionID, WorkspaceTransition.START)
284-
val buildResponse = retroRestClient.createWorkspaceBuild(workspace.id, buildRequest).execute()
284+
val buildResponse = retroRestClient.createWorkspaceBuild(workspace.id, buildRequest)
285285
if (buildResponse.code() != HttpURLConnection.HTTP_CREATED) {
286286
throw APIResponseException("update workspace ${workspace.name}", url, buildResponse)
287287
}

src/main/kotlin/com/coder/toolbox/sdk/v2/CoderV2RestFacade.kt

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import com.coder.toolbox.sdk.v2.models.Workspace
88
import com.coder.toolbox.sdk.v2.models.WorkspaceBuild
99
import com.coder.toolbox.sdk.v2.models.WorkspaceResource
1010
import com.coder.toolbox.sdk.v2.models.WorkspacesResponse
11-
import retrofit2.Call
11+
import retrofit2.Response
1212
import retrofit2.http.Body
1313
import retrofit2.http.GET
1414
import retrofit2.http.POST
@@ -21,43 +21,43 @@ interface CoderV2RestFacade {
2121
* Retrieves details about the authenticated user.
2222
*/
2323
@GET("api/v2/users/me")
24-
fun me(): Call<User>
24+
suspend fun me(): Response<User>
2525

2626
/**
2727
* Retrieves all workspaces the authenticated user has access to.
2828
*/
2929
@GET("api/v2/workspaces")
30-
fun workspaces(
30+
suspend fun workspaces(
3131
@Query("q") searchParams: String,
32-
): Call<WorkspacesResponse>
32+
): Response<WorkspacesResponse>
3333

3434
/**
3535
* Retrieves a workspace with the provided id.
3636
*/
3737
@GET("api/v2/workspaces/{workspaceID}")
38-
fun workspace(
38+
suspend fun workspace(
3939
@Path("workspaceID") workspaceID: UUID
40-
): Call<Workspace>
40+
): Response<Workspace>
4141

4242
@GET("api/v2/buildinfo")
43-
fun buildInfo(): Call<BuildInfo>
43+
suspend fun buildInfo(): Response<BuildInfo>
4444

4545
/**
4646
* Queues a new build to occur for a workspace.
4747
*/
4848
@POST("api/v2/workspaces/{workspaceID}/builds")
49-
fun createWorkspaceBuild(
49+
suspend fun createWorkspaceBuild(
5050
@Path("workspaceID") workspaceID: UUID,
5151
@Body createWorkspaceBuildRequest: CreateWorkspaceBuildRequest,
52-
): Call<WorkspaceBuild>
52+
): Response<WorkspaceBuild>
5353

5454
@GET("api/v2/templates/{templateID}")
55-
fun template(
55+
suspend fun template(
5656
@Path("templateID") templateID: UUID,
57-
): Call<Template>
57+
): Response<Template>
5858

5959
@GET("api/v2/templateversions/{templateID}/resources")
60-
fun templateVersionResources(
60+
suspend fun templateVersionResources(
6161
@Path("templateID") templateID: UUID,
62-
): Call<List<WorkspaceResource>>
62+
): Response<List<WorkspaceResource>>
6363
}

src/main/kotlin/com/coder/toolbox/store/CoderSecretsStore.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class CoderSecretsStore(private val store: PluginSecretStore) {
2323
var lastToken: String
2424
get() = get("last-token")
2525
set(value) = set("last-token", value)
26-
var rememberMe: String
27-
get() = get("remember-me")
28-
set(value) = set("remember-me", value)
26+
var rememberMe: Boolean
27+
get() = get("remember-me").toBoolean()
28+
set(value) = set("remember-me", value.toString())
2929
}

src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@ import kotlinx.coroutines.flow.update
1212

1313
class AuthWizardPage(
1414
private val context: CoderToolboxContext,
15-
private val isAutoLogin: Boolean = false,
15+
initialAutoLogin: Boolean = false,
1616
onConnect: (
1717
client: CoderRestClient,
1818
cli: CoderCLIManager,
1919
) -> Unit,
2020
) : CoderPage(context, context.i18n.ptrl("Authenticate to Coder")) {
21+
private val shouldAutoLogin = MutableStateFlow(initialAutoLogin)
22+
2123
private val signInStep = SignInStep(context)
2224
private val tokenStep = TokenStep(context)
23-
private val connectStep = ConnectStep(context, this::notify, onConnect)
25+
private val connectStep = ConnectStep(context, shouldAutoLogin, this::notify, onConnect)
2426

2527

2628
/**
@@ -78,10 +80,9 @@ class AuthWizardPage(
7880
actionButtons.update {
7981
listOf(
8082
Action(context.i18n.ptrl("Back"), closesPage = false, actionBlock = {
81-
if (isAutoLogin) {
82-
AuthWizardState.resetSteps()
83-
} else {
84-
connectStep.onBack()
83+
connectStep.onBack()
84+
shouldAutoLogin.update {
85+
false
8586
}
8687
displaySteps()
8788
})

src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.jetbrains.toolbox.api.ui.components.LabelField
1212
import com.jetbrains.toolbox.api.ui.components.RowGroup
1313
import com.jetbrains.toolbox.api.ui.components.ValidationErrorField
1414
import kotlinx.coroutines.Job
15+
import kotlinx.coroutines.flow.StateFlow
1516
import kotlinx.coroutines.flow.update
1617
import kotlinx.coroutines.launch
1718
import kotlinx.coroutines.yield
@@ -24,6 +25,7 @@ private const val USER_HIT_THE_BACK_BUTTON = "User hit the back button"
2425
*/
2526
class ConnectStep(
2627
private val context: CoderToolboxContext,
28+
private val shouldAutoLogin: StateFlow<Boolean>,
2729
private val notify: (String, Throwable) -> Unit,
2830
private val onConnect: (
2931
client: CoderRestClient,
@@ -70,6 +72,7 @@ class ConnectStep(
7072
signInJob?.cancel()
7173
signInJob = context.cs.launch {
7274
try {
75+
statusField.textState.update { (context.i18n.ptrl("Authenticating to ${url.host}...")) }
7376
// The http client Toolbox gives us is already set up with the
7477
// proxy config, so we do net need to explicitly add it.
7578
val client = CoderRestClient(
@@ -91,15 +94,15 @@ class ConnectStep(
9194
yield()
9295
cli.login(client.token)
9396
}
97+
statusField.textState.update { (context.i18n.ptrl("Successfully configured ${url.host}...")) }
9498
// allows interleaving with the back/cancel action
9599
yield()
96100
onConnect(client, cli)
97101
AuthWizardState.resetSteps()
98102
} catch (ex: CancellationException) {
99-
if (ex.message == USER_HIT_THE_BACK_BUTTON) {
100-
return@launch
103+
if (ex.message != USER_HIT_THE_BACK_BUTTON) {
104+
notify("Connection to ${url.host} was configured", ex)
101105
}
102-
notify("Connection to ${url.host} was configured", ex)
103106
} catch (ex: Exception) {
104107
notify("Failed to configure ${url.host}", ex)
105108
}
@@ -114,7 +117,12 @@ class ConnectStep(
114117
try {
115118
signInJob?.cancel(CancellationException(USER_HIT_THE_BACK_BUTTON))
116119
} finally {
117-
AuthWizardState.goToPreviousStep()
120+
if (shouldAutoLogin.value) {
121+
AuthWizardState.resetSteps()
122+
context.secrets.rememberMe = false
123+
} else {
124+
AuthWizardState.goToPreviousStep()
125+
}
118126
}
119127
}
120128
}

0 commit comments

Comments
 (0)