Skip to content

Commit 7d1c2bf

Browse files
committed
Merge branch 'main' into store-session-token-into-os-keyring
2 parents 28e1834 + 8783eb8 commit 7d1c2bf

4 files changed

Lines changed: 59 additions & 50 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Unreleased
44

5+
### Fixed
6+
7+
- snackbar dismissal no longer cancels the calling coroutine, so error popups during URI handling don't leave the page
8+
stuck in a busy state
9+
510
### Changed
611

712
- skip the Coder TLS alternate hostname when fetching IDE metadata from JetBrains

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

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -360,15 +360,18 @@ class CoderRemoteProvider(
360360
context.logger.info("Handling $uri...")
361361
val newUrl = resolveDeploymentUrl(params)?.toURL() ?: return
362362
val newToken = if (context.settingsStore.requiresMTlsAuth) null else resolveToken(params) ?: return
363-
coderHeaderPage.isBusy.update { true }
364363
if (sameUrl(newUrl, client?.url)) {
365-
if (context.settingsStore.requiresTokenAuth) {
366-
newToken?.let {
367-
refreshSession(newUrl, it)
364+
coderHeaderPage.isBusy.update { true }
365+
try {
366+
if (context.settingsStore.requiresTokenAuth) {
367+
newToken?.let {
368+
refreshSession(newUrl, it)
369+
}
368370
}
371+
linkHandler.handle(params, newUrl, this.client!!, this.cli!!)
372+
} finally {
373+
coderHeaderPage.isBusy.update { false }
369374
}
370-
linkHandler.handle(params, newUrl, this.client!!, this.cli!!)
371-
coderHeaderPage.isBusy.update { false }
372375
} else {
373376
// Different URL - we need a new connection. Tear down any
374377
// in-flight wizard, install a fresh one on the router, and let
@@ -378,10 +381,7 @@ class CoderRemoteProvider(
378381
context, settingsPage, visibilityState,
379382
url = newUrl,
380383
credentials = credentials,
381-
onConnect = onConnect.andThen(deferredLinkHandler(params, newUrl))
382-
.andThen { _, _ ->
383-
coderHeaderPage.isBusy.update { false }
384-
},
384+
onConnect = onConnect.andThen(deferredLinkHandler(params, newUrl)),
385385
onTokenRefreshed = ::onTokenRefreshed,
386386
)
387387
router.navigate(wizard)
@@ -397,7 +397,6 @@ class CoderRemoteProvider(
397397
"Error encountered while handling Coder URI",
398398
textError ?: ""
399399
)
400-
coderHeaderPage.isBusy.update { false }
401400
} finally {
402401
firstRun = false
403402
}
@@ -654,13 +653,16 @@ class CoderRemoteProvider(
654653
deploymentUrl: URL,
655654
): SuspendBiConsumer<CoderRestClient, CoderCLIManager> = SuspendBiConsumer { client, cli ->
656655
context.cs.launch(CoroutineName("Deferred Link Handler")) {
656+
coderHeaderPage.isBusy.update { true }
657657
try {
658658
linkHandler.handle(params, deploymentUrl, client, cli)
659659
} catch (ex: Exception) {
660660
context.logAndShowError(
661661
"Error handling deferred link",
662662
ex.message ?: ""
663663
)
664+
} finally {
665+
coderHeaderPage.isBusy.update { false }
664666
}
665667
}
666668
}

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

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import com.jetbrains.toolbox.api.remoteDev.connection.ToolboxProxySettings
1313
import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentStateColorPalette
1414
import com.jetbrains.toolbox.api.remoteDev.ui.EnvironmentUiPageManager
1515
import com.jetbrains.toolbox.api.ui.ToolboxUi
16+
import kotlinx.coroutines.CancellationException
17+
import kotlinx.coroutines.CoroutineName
1618
import kotlinx.coroutines.CoroutineScope
19+
import kotlinx.coroutines.launch
1720
import java.net.URL
1821
import java.util.UUID
1922

@@ -49,44 +52,53 @@ data class CoderToolboxContext(
4952
?: settingsStore.defaultURL.toURL()
5053
}
5154

52-
suspend fun logAndShowError(title: String, error: String) {
55+
fun logAndShowError(title: String, error: String) {
5356
logger.error(error)
54-
ui.showSnackbar(
55-
UUID.randomUUID().toString(),
56-
i18n.pnotr(title),
57-
i18n.pnotr(error),
58-
i18n.ptrl("OK")
59-
)
57+
showSnackbar(title, error)
6058
}
6159

62-
suspend fun logAndShowError(title: String, error: String, exception: Exception) {
60+
fun logAndShowError(title: String, error: String, exception: Exception) {
6361
logger.error(exception, error)
64-
ui.showSnackbar(
65-
UUID.randomUUID().toString(),
66-
i18n.pnotr(title),
67-
i18n.pnotr(error),
68-
i18n.ptrl("OK")
69-
)
62+
showSnackbar(title, error)
7063
}
7164

72-
suspend fun logAndShowWarning(title: String, warning: String) {
65+
fun logAndShowWarning(title: String, warning: String) {
7366
logger.warn(warning)
74-
ui.showSnackbar(
75-
UUID.randomUUID().toString(),
76-
i18n.pnotr(title),
77-
i18n.pnotr(warning),
78-
i18n.ptrl("OK")
79-
)
67+
showSnackbar(title, warning)
8068
}
8169

82-
suspend fun logAndShowInfo(title: String, info: String) {
70+
fun logAndShowInfo(title: String, info: String) {
8371
logger.info(info)
84-
ui.showSnackbar(
85-
UUID.randomUUID().toString(),
86-
i18n.pnotr(title),
87-
i18n.pnotr(info),
88-
i18n.ptrl("OK")
89-
)
72+
showSnackbar(title, info)
73+
}
74+
75+
/**
76+
* Displays a snackbar on a child of the plugin coroutine scope rather than on the
77+
* caller's coroutine, without waiting for it.
78+
*
79+
* Toolbox keeps the [ToolboxUi.showSnackbar] coroutine suspended for the entire lifetime
80+
* of the snackbar and cancels it (rather than resuming it) when the snackbar goes away.
81+
* Calling it directly on the caller's coroutine would therefore either block the caller
82+
* until the snackbar is gone or, on dismissal, abruptly cancel the caller (e.g. the URI
83+
* handler) - skipping any code that runs after the error is shown, such as resetting the
84+
* busy state. Launching it fire-and-forget on the plugin scope lets the caller continue
85+
* immediately while the snackbar lives independently.
86+
*/
87+
fun showSnackbar(title: String, text: String) {
88+
cs.launch(CoroutineName("snackbar")) {
89+
try {
90+
ui.showSnackbar(
91+
UUID.randomUUID().toString(),
92+
i18n.pnotr(title),
93+
i18n.pnotr(text),
94+
i18n.ptrl("OK")
95+
)
96+
} catch (_: CancellationException) {
97+
// Expected when the snackbar is dismissed or the plugin scope shuts down.
98+
} catch (ex: Exception) {
99+
logger.error(ex, "Failed to display snackbar with title '$title'")
100+
}
101+
}
90102
}
91103

92104
fun popupPluginMainPage() {

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

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import com.coder.toolbox.CoderToolboxContext
44
import com.coder.toolbox.util.prettify
55
import com.jetbrains.toolbox.api.remoteDev.ProviderVisibilityState
66
import kotlinx.coroutines.flow.StateFlow
7-
import kotlinx.coroutines.launch
8-
import java.util.UUID
97

108
sealed class ErrorReporter {
119

@@ -47,15 +45,7 @@ private class ErrorReporterImpl(
4745

4846
private fun showError(ex: Throwable) {
4947
val textError = ex.prettify()
50-
51-
context.cs.launch {
52-
context.ui.showSnackbar(
53-
UUID.randomUUID().toString(),
54-
context.i18n.ptrl("Error encountered while setting up Coder"),
55-
context.i18n.pnotr(textError),
56-
context.i18n.ptrl("Dismiss")
57-
)
58-
}
48+
context.showSnackbar("Error encountered while setting up Coder", textError)
5949
}
6050

6151
override fun flush() {

0 commit comments

Comments
 (0)