Skip to content

Commit 7f74048

Browse files
committed
chore: simplify uri handling implementation (1)
Refactored code around uri, token, workspace and rest client resolving by encapsulating code in clearly named methods. Rest client resolving was overly-complicated (code inherited from Gateway), with token being a mandatory parameter. Removed a lot of code that asked the token from the user if it was missing. Also, I decided to use a snackbar to show errors because of attached regression in TBX. A bottom simple dialog is more pleasing to the eye than a dialog in the middle of a page. https://youtrack.jetbrains.com/issue/TBX-14944/Pop-up-dialogs-are-only-displayed-on-the-main-envs-page
1 parent 5e19a7d commit 7f74048

File tree

3 files changed

+68
-91
lines changed

3 files changed

+68
-91
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import com.jetbrains.toolbox.api.remoteDev.ui.EnvironmentUiPageManager
1414
import com.jetbrains.toolbox.api.ui.ToolboxUi
1515
import kotlinx.coroutines.CoroutineScope
1616
import java.net.URL
17+
import java.util.UUID
1718

1819
@Suppress("UnstableApiUsage")
1920
data class CoderToolboxContext(
@@ -47,4 +48,14 @@ data class CoderToolboxContext(
4748
}
4849
return this.settingsStore.defaultURL.toURL()
4950
}
51+
52+
suspend fun logAndShowError(title: String, error: String) {
53+
logger.error(error)
54+
ui.showSnackbar(
55+
UUID.randomUUID().toString(),
56+
i18n.pnotr(title),
57+
i18n.pnotr(error),
58+
i18n.ptrl("OK")
59+
)
60+
}
5061
}

src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt

Lines changed: 57 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import kotlin.time.Duration.Companion.minutes
2525
import kotlin.time.Duration.Companion.seconds
2626
import kotlin.time.toJavaDuration
2727

28+
private const val CAN_T_HANDLE_URI_TITLE = "Can't handle URI"
29+
2830
@Suppress("UnstableApiUsage")
2931
open class CoderProtocolHandler(
3032
private val context: CoderToolboxContext,
@@ -46,35 +48,18 @@ open class CoderProtocolHandler(
4648
reInitialize: suspend (CoderRestClient, CoderCLIManager) -> Unit
4749
) {
4850
context.popupPluginMainPage()
51+
context.logger.info("Handling $uri...")
4952
val params = uri.toQueryParameters()
5053
if (params.isEmpty()) {
5154
// probably a plugin installation scenario
5255
return
5356
}
5457

55-
val deploymentURL = params.url() ?: askUrl()
56-
if (deploymentURL.isNullOrBlank()) {
57-
context.logger.error("Query parameter \"$URL\" is missing from URI $uri")
58-
context.showErrorPopup(MissingArgumentException("Can't handle URI because query parameter \"$URL\" is missing"))
59-
return
60-
}
61-
62-
val queryToken = params.token()
63-
val restClient = try {
64-
authenticate(deploymentURL, queryToken)
65-
} catch (ex: Exception) {
66-
context.logger.error(ex, "Query parameter \"$TOKEN\" is missing from URI $uri")
67-
context.showErrorPopup(IllegalStateException(humanizeConnectionError(deploymentURL.toURL(), true, ex)))
68-
return
69-
}
70-
58+
val deploymentURL = resolveDeploymentUrl(params) ?: return
59+
val token = resolveToken(params) ?: return
7160
// TODO: Show a dropdown and ask for the workspace if missing. Right now it's not possible because dialogs are quite limited
72-
val workspaceName = params.workspace()
73-
if (workspaceName.isNullOrBlank()) {
74-
context.logger.error("Query parameter \"$WORKSPACE\" is missing from URI $uri")
75-
context.showErrorPopup(MissingArgumentException("Can't handle URI because query parameter \"$WORKSPACE\" is missing"))
76-
return
77-
}
61+
val workspaceName = resolveWorkspace(params) ?: return
62+
val restClient = buildRestClient(deploymentURL, token) ?: return
7863

7964
val workspaces = restClient.workspaces()
8065
val workspace = workspaces.firstOrNull { it.name == workspaceName }
@@ -244,6 +229,56 @@ open class CoderProtocolHandler(
244229
}
245230
}
246231

232+
private suspend fun resolveDeploymentUrl(params: Map<String, String>): String? {
233+
val deploymentURL = params.url() ?: askUrl()
234+
if (deploymentURL.isNullOrBlank()) {
235+
context.logAndShowError(CAN_T_HANDLE_URI_TITLE, "Query parameter \"$URL\" is missing from URI")
236+
return null
237+
}
238+
return deploymentURL
239+
}
240+
241+
private suspend fun resolveToken(params: Map<String, String>): String? {
242+
val token = params.token()
243+
if (token.isNullOrBlank()) {
244+
context.logAndShowError(CAN_T_HANDLE_URI_TITLE, "Query parameter \"$TOKEN\" is missing from URI")
245+
return null
246+
}
247+
return token
248+
}
249+
250+
private suspend fun resolveWorkspace(params: Map<String, String>): String? {
251+
val workspace = params.workspace()
252+
if (workspace.isNullOrBlank()) {
253+
context.logAndShowError(CAN_T_HANDLE_URI_TITLE, "Query parameter \"$WORKSPACE\" is missing from URI")
254+
return null
255+
}
256+
return workspace
257+
}
258+
259+
private suspend fun buildRestClient(deploymentURL: String, token: String): CoderRestClient? {
260+
try {
261+
return authenticate(deploymentURL, token)
262+
} catch (ex: Exception) {
263+
context.logAndShowError(CAN_T_HANDLE_URI_TITLE, humanizeConnectionError(deploymentURL.toURL(), true, ex))
264+
return null
265+
}
266+
}
267+
268+
/**
269+
* Returns an authenticated Coder CLI.
270+
*/
271+
private suspend fun authenticate(deploymentURL: String, token: String): CoderRestClient {
272+
val client = CoderRestClient(
273+
context,
274+
deploymentURL.toURL(),
275+
if (settings.requireTokenAuth) token else null,
276+
PluginManager.pluginInfo.version
277+
)
278+
client.authenticate()
279+
return client
280+
}
281+
247282
private suspend fun CoderRestClient.waitForReady(workspace: Workspace): Boolean {
248283
var status = workspace.latestBuild.status
249284
try {
@@ -285,43 +320,6 @@ open class CoderProtocolHandler(
285320
context.i18n.ptrl("Enter the full URL of your Coder deployment")
286321
)
287322
}
288-
289-
/**
290-
* Return an authenticated Coder CLI, asking for the token.
291-
* Throw MissingArgumentException if the user aborts. Any network or invalid
292-
* token error may also be thrown.
293-
*/
294-
private suspend fun authenticate(
295-
deploymentURL: String,
296-
tryToken: String?
297-
): CoderRestClient {
298-
val token =
299-
if (settings.requireTokenAuth) {
300-
// Try the provided token immediately on the first attempt.
301-
if (!tryToken.isNullOrBlank()) {
302-
tryToken
303-
} else {
304-
context.popupPluginMainPage()
305-
// Otherwise ask for a new token, showing the previous token.
306-
dialogUi.askToken(deploymentURL.toURL())
307-
}
308-
} else {
309-
null
310-
}
311-
312-
if (settings.requireTokenAuth && token == null) { // User aborted.
313-
throw MissingArgumentException("Token is required")
314-
}
315-
val client = CoderRestClient(
316-
context,
317-
deploymentURL.toURL(),
318-
token,
319-
PluginManager.pluginInfo.version
320-
)
321-
client.authenticate()
322-
return client
323-
}
324-
325323
}
326324

327325
/**
Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
package com.coder.toolbox.util
22

33
import com.coder.toolbox.CoderToolboxContext
4-
import com.coder.toolbox.browser.browse
54
import com.jetbrains.toolbox.api.localization.LocalizableString
65
import com.jetbrains.toolbox.api.ui.components.TextType
7-
import java.net.URL
86

97
/**
108
* Dialog implementation for standalone Gateway.
@@ -26,34 +24,4 @@ class DialogUi(private val context: CoderToolboxContext) {
2624
title, description, placeholder, TextType.General, context.i18n.ptrl("OK"), context.i18n.ptrl("Cancel")
2725
)
2826
}
29-
30-
suspend fun askPassword(
31-
title: LocalizableString,
32-
description: LocalizableString,
33-
placeholder: LocalizableString? = null,
34-
): String? {
35-
return context.ui.showTextInputPopup(
36-
title, description, placeholder, TextType.Password, context.i18n.ptrl("OK"), context.i18n.ptrl("Cancel")
37-
)
38-
}
39-
40-
private suspend fun openUrl(url: URL) {
41-
context.desktop.browse(url.toString()) {
42-
context.ui.showErrorInfoPopup(it)
43-
}
44-
}
45-
46-
/**
47-
* Open a dialog for providing the token.
48-
*/
49-
suspend fun askToken(
50-
url: URL,
51-
): String? {
52-
openUrl(url.withPath("/login?redirect=%2Fcli-auth"))
53-
return askPassword(
54-
title = context.i18n.ptrl("Session Token"),
55-
description = context.i18n.pnotr("Please paste the session token from the web-page"),
56-
placeholder = context.i18n.pnotr("")
57-
)
58-
}
5927
}

0 commit comments

Comments
 (0)