@@ -8,6 +8,14 @@ import com.coder.toolbox.sdk.ex.APIResponseException
88import com.coder.toolbox.sdk.v2.models.WorkspaceStatus
99import com.coder.toolbox.util.CoderProtocolHandler
1010import com.coder.toolbox.util.DialogUi
11+ import com.coder.toolbox.util.TOKEN
12+ import com.coder.toolbox.util.URL
13+ import com.coder.toolbox.util.WebUrlValidationResult.Invalid
14+ import com.coder.toolbox.util.toQueryParameters
15+ import com.coder.toolbox.util.toURL
16+ import com.coder.toolbox.util.token
17+ import com.coder.toolbox.util.url
18+ import com.coder.toolbox.util.validateStrictWebUrl
1119import com.coder.toolbox.util.waitForTrue
1220import com.coder.toolbox.util.withPath
1321import com.coder.toolbox.views.Action
@@ -46,6 +54,7 @@ import com.jetbrains.toolbox.api.ui.components.AccountDropdownField as DropDownM
4654import com.jetbrains.toolbox.api.ui.components.AccountDropdownField as dropDownFactory
4755
4856private val POLL_INTERVAL = 5 .seconds
57+ private const val CAN_T_HANDLE_URI_TITLE = " Can't handle URI"
4958
5059@OptIn(ExperimentalCoroutinesApi ::class )
5160class CoderRemoteProvider (
@@ -63,6 +72,7 @@ class CoderRemoteProvider(
6372
6473 // The REST client, if we are signed in
6574 private var client: CoderRestClient ? = null
75+ private var cli: CoderCLIManager ? = null
6676
6777 // On the first load, automatically log in if we can.
6878 private var firstRun = true
@@ -84,7 +94,7 @@ class CoderRemoteProvider(
8494 providerVisible = false
8595 )
8696 )
87- private val linkHandler = CoderProtocolHandler (context, dialogUi, settingsPage, visibilityState, isInitialized )
97+ private val linkHandler = CoderProtocolHandler (context)
8898
8999 override val loadingEnvironmentsDescription: LocalizableString = context.i18n.ptrl(" Loading workspaces..." )
90100 override val environments: MutableStateFlow <LoadableState <List <CoderRemoteEnvironment >>> = MutableStateFlow (
@@ -258,6 +268,7 @@ class CoderRemoteProvider(
258268 override fun close () {
259269 softClose()
260270 client = null
271+ cli = null
261272 lastEnvironments.clear()
262273 environments.value = LoadableState .Value (emptyList())
263274 isInitialized.update { false }
@@ -337,13 +348,50 @@ class CoderRemoteProvider(
337348 */
338349 override suspend fun handleUri (uri : URI ) {
339350 try {
340- linkHandler.handle(
341- uri,
342- client?.url,
343- shouldDoAutoSetup(),
344- ::refreshSession,
345- ::performReLogin
346- )
351+ val params = uri.toQueryParameters()
352+ if (params.isEmpty()) {
353+ // probably a plugin installation scenario
354+ context.logAndShowInfo(" URI will not be handled" , " No query parameters were provided" )
355+ return
356+ }
357+ // this switches to the main plugin screen, even
358+ // if last opened provider was not Coder
359+ context.envPageManager.showPluginEnvironmentsPage()
360+ coderHeaderPage.isBusy.update { true }
361+ if (shouldDoAutoSetup()) {
362+ isInitialized.waitForTrue()
363+ }
364+ context.logger.info(" Handling $uri ..." )
365+ val newUrl = resolveDeploymentUrl(params)?.toURL() ? : return
366+ val newToken = if (context.settingsStore.requiresMTlsAuth) null else resolveToken(params) ? : return
367+ if (sameUrl(newUrl, client?.url)) {
368+ if (context.settingsStore.requiresTokenAuth) {
369+ newToken?.let {
370+ refreshSession(newUrl, it)
371+ }
372+ }
373+ } else {
374+ CoderCliSetupContext .apply {
375+ url = newUrl
376+ token = newToken
377+ }
378+ CoderCliSetupWizardState .goToStep(WizardStep .CONNECT )
379+ CoderCliSetupWizardPage (
380+ context, settingsPage, visibilityState,
381+ initialAutoSetup = true ,
382+ jumpToMainPageOnError = true ,
383+ connectSynchronously = true ,
384+ onConnect = ::onConnect
385+ ).apply {
386+ beforeShow()
387+ }
388+ }
389+ // TODO - do I really need these two lines? I'm anyway doing a workspace call
390+ triggerProviderVisible.send(true )
391+ isInitialized.waitForTrue()
392+
393+ linkHandler.handle(params, newUrl, this .client!! , this .cli!! )
394+ coderHeaderPage.isBusy.update { false }
347395 } catch (ex: Exception ) {
348396 val textError = if (ex is APIResponseException ) {
349397 if (! ex.reason.isNullOrBlank()) {
@@ -355,50 +403,61 @@ class CoderRemoteProvider(
355403 textError ? : " "
356404 )
357405 context.envPageManager.showPluginEnvironmentsPage()
406+ } finally {
407+ coderHeaderPage.isBusy.update { false }
358408 }
359409 }
360410
361- private suspend fun refreshSession (url : URL , token : String ): Pair <CoderRestClient , CoderCLIManager > {
362- coderHeaderPage.isBusyCreatingNewEnvironment.update { true }
363- try {
364- context.logger.info(" Stopping workspace polling and re-initializing the http client and cli with a new token" )
365- softClose()
366- val restClient = CoderRestClient (
367- context,
368- url,
369- token,
370- PluginManager .pluginInfo.version,
371- ).apply { initializeSession() }
372- val cli = CoderCLIManager (context, url).apply {
373- login(token)
374- }
375- this .client = restClient
376- pollJob = poll(restClient, cli)
377- triggerProviderVisible.send(true )
378- context.logger.info(" Workspace poll job with name ${pollJob.toString()} was created while handling URI" )
379- return restClient to cli
380- } finally {
381- coderHeaderPage.isBusyCreatingNewEnvironment.update { false }
411+ private suspend fun resolveDeploymentUrl (params : Map <String , String >): String? {
412+ val deploymentURL = params.url() ? : askUrl()
413+ if (deploymentURL.isNullOrBlank()) {
414+ context.logAndShowError(CAN_T_HANDLE_URI_TITLE , " Query parameter \" ${URL } \" is missing from URI" )
415+ return null
416+ }
417+ val validationResult = deploymentURL.validateStrictWebUrl()
418+ if (validationResult is Invalid ) {
419+ context.logAndShowError(CAN_T_HANDLE_URI_TITLE , " \" $URL \" is invalid: ${validationResult.reason} " )
420+ return null
382421 }
422+ return deploymentURL
383423 }
384424
385- private suspend fun performReLogin ( restClient : CoderRestClient , cli : CoderCLIManager ) {
386- context.logger.info( " Stopping workspace polling and de-initializing resources " )
387- close()
388- isInitialized.update {
389- false
425+ private suspend fun resolveToken ( params : Map < String , String >): String? {
426+ val token = params.token( )
427+ if (token.isNullOrBlank()) {
428+ context.logAndShowError( CAN_T_HANDLE_URI_TITLE , " Query parameter \" $TOKEN \" is missing from URI " )
429+ return null
390430 }
391- context.logger.info(" Starting initialization with the new settings" )
392- this @CoderRemoteProvider.client = restClient
393- if (context.settingsStore.useAppNameAsTitle) {
394- coderHeaderPage.setTitle(context.i18n.pnotr(restClient.appName))
395- } else {
396- coderHeaderPage.setTitle(context.i18n.pnotr(restClient.url.toString()))
431+ return token
432+ }
433+
434+ private fun sameUrl (first : URL , second : URL ? ): Boolean = first.toURI().normalize() == second?.toURI()?.normalize()
435+
436+ private suspend fun refreshSession (url : URL , token : String ): Pair <CoderRestClient , CoderCLIManager > {
437+ context.logger.info(" Stopping workspace polling and re-initializing the http client and cli with a new token" )
438+ softClose()
439+ val newRestClient = CoderRestClient (
440+ context,
441+ url,
442+ token,
443+ PluginManager .pluginInfo.version,
444+ ).apply { initializeSession() }
445+ val newCli = CoderCLIManager (context, url).apply {
446+ login(token)
397447 }
398- environments.showLoadingMessage()
399- pollJob = poll(restClient, cli)
448+ this .client = newRestClient
449+ this .cli = newCli
450+ pollJob = poll(newRestClient, newCli)
400451 context.logger.info(" Workspace poll job with name ${pollJob.toString()} was created while handling URI" )
401- isInitialized.waitForTrue()
452+ return newRestClient to newCli
453+ }
454+
455+ private suspend fun askUrl (): String? {
456+ context.popupPluginMainPage()
457+ return dialogUi.ask(
458+ context.i18n.ptrl(" Deployment URL" ),
459+ context.i18n.ptrl(" Enter the full URL of your Coder deployment" )
460+ )
402461 }
403462
404463 /* *
@@ -455,6 +514,7 @@ class CoderRemoteProvider(
455514
456515 private fun onConnect (client : CoderRestClient , cli : CoderCLIManager ) {
457516 // Store the URL and token for use next time.
517+ close()
458518 context.settingsStore.updateLastUsedUrl(client.url)
459519 if (context.settingsStore.requiresTokenAuth) {
460520 context.secrets.storeTokenFor(client.url, client.token ? : " " )
@@ -463,10 +523,7 @@ class CoderRemoteProvider(
463523 context.logger.info(" Deployment URL was stored and will be available for automatic connection" )
464524 }
465525 this .client = client
466- pollJob?.let {
467- it.cancel()
468- context.logger.info(" Cancelled workspace poll job ${pollJob.toString()} in order to start a new one" )
469- }
526+ this .cli = cli
470527 environments.showLoadingMessage()
471528 if (context.settingsStore.useAppNameAsTitle) {
472529 coderHeaderPage.setTitle(context.i18n.pnotr(client.appName))
0 commit comments