Skip to content

fix: Follow JetBrains IDE proxy settings #6531

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions extensions/intellij/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ repositories {
// Dependencies are managed with Gradle version catalog - read more:
// https://docs.gradle.org/current/userguide/platforms.html#sub:version-catalog
dependencies {
implementation("com.squareup.okhttp3:okhttp:4.12.0") {
exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
}
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.4.32")
implementation("com.posthog.java:posthog:1.+")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.continuedev.continueintellijextension.auth

import com.github.continuedev.continueintellijextension.error.ContinueErrorService
import com.github.continuedev.continueintellijextension.services.ContinueExtensionSettings
import com.github.continuedev.continueintellijextension.services.ContinuePluginService
import com.google.gson.Gson
Expand All @@ -10,24 +11,22 @@ import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.remoteServer.util.CloudConfigurationUtil.createCredentialAttributes
import com.intellij.util.io.HttpRequests
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.net.URL
import kotlin.time.Duration.Companion.minutes

@Service
class ContinueAuthService {
private val coroutineScope = CoroutineScope(Dispatchers.IO)
private val log = Logger.getInstance(ContinueAuthService::class.java)

companion object {
fun getInstance(): ContinueAuthService = service<ContinueAuthService>()
private const val CREDENTIALS_USER = "ContinueAuthUser"
private const val ACCESS_TOKEN_KEY = "ContinueAccessToken"
private const val REFRESH_TOKEN_KEY = "ContinueRefreshToken"
Expand Down Expand Up @@ -77,73 +76,48 @@ class ContinueAuthService {
}

private fun updateRefreshToken(token: String) {
// Launch a coroutine to call the suspend function
coroutineScope.launch {
try {
val response = refreshToken(token)
val accessToken = response["accessToken"] as? String
val refreshToken = response["refreshToken"] as? String
val user = response["user"] as? Map<*, *>
val firstName = user?.get("firstName") as? String
val lastName = user?.get("lastName") as? String
val label = "$firstName $lastName"
val id = user?.get("id") as? String
val email = user?.get("email") as? String

// Persist the session info
setRefreshToken(refreshToken!!)
val sessionInfo =
ControlPlaneSessionInfo(accessToken!!, ControlPlaneSessionInfo.Account(email!!, label))
setControlPlaneSessionInfo(sessionInfo)

// Notify listeners
ApplicationManager.getApplication().messageBus.syncPublisher(AuthListener.TOPIC)
.handleUpdatedSessionInfo(sessionInfo)

} catch (e: Exception) {
// Handle any exceptions
println("Exception while refreshing token: ${e.message}")
}
try {
val response = refreshToken(token)
val accountLabel = "${response.user.firstName} ${response.user.lastName}"

// Persist the session info
setRefreshToken(response.refreshToken)
val account = ControlPlaneSessionInfo.Account(response.user.email, accountLabel)
val sessionInfo = ControlPlaneSessionInfo(response.accessToken, account)
setControlPlaneSessionInfo(sessionInfo)

// Notify listeners
ApplicationManager.getApplication().messageBus.syncPublisher(AuthListener.TOPIC)
.handleUpdatedSessionInfo(sessionInfo)
} catch (e: Exception) {
service<ContinueErrorService>().report(e, "Exception while refreshing token ${e.message}")
}
}

private fun setupRefreshTokenInterval() {
// Launch a coroutine to refresh the token every 30 minutes
coroutineScope.launch {
while (true) {
val refreshToken = getRefreshToken()
if (refreshToken != null) {
updateRefreshToken(refreshToken)
}

kotlinx.coroutines.delay(15 * 60 * 1000) // 15 minutes in milliseconds
log.info("Token refreshed, retrying in 15 minutes")
delay(15.minutes)
}
}
}

private suspend fun refreshToken(refreshToken: String) = withContext(Dispatchers.IO) {
val client = OkHttpClient()
val url = URL(getControlPlaneUrl()).toURI().resolve("/auth/refresh").toURL()
val jsonBody = mapOf("refreshToken" to refreshToken)
val jsonString = Gson().toJson(jsonBody)
val requestBody = jsonString.toRequestBody("application/json".toMediaType())

val request = Request.Builder()
.url(url)
.post(requestBody)
.header("Content-Type", "application/json")
.build()

val response = client.newCall(request).execute()

val responseBody = response.body?.string()
private fun refreshToken(refreshToken: String): RefreshTokenResponse {
val gson = Gson()
val responseMap = gson.fromJson(responseBody, Map::class.java)

responseMap
val jsonBody = gson.toJson(mapOf("refreshToken" to refreshToken))
val url = getControlPlaneUrl() + "/auth/refresh"
// todo: test this manually before publishing PR
val response = HttpRequests.post(url, HttpRequests.JSON_CONTENT_TYPE)
.connect { connection -> connection.write(jsonBody.toByteArray()) }
.toString()
return gson.fromJson(response, RefreshTokenResponse::class.java)
}


private fun openSignInPage(project: Project, useOnboarding: Boolean): String? {
var authUrl: String? = null
val coreMessenger = project.service<ContinuePluginService>().coreMessenger
Expand Down Expand Up @@ -256,6 +230,17 @@ class ContinueAuthService {
setAccountLabel(info.account.label)
}

private data class RefreshTokenResponse(
val accessToken: String,
val refreshToken: String,
val user: User,
) {
data class User(
val firstName: String,
val lastName: String,
val email: String
)
}
}

// Data class to represent the ControlPlaneSessionInfo
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
package com.github.continuedev.continueintellijextension.services

import com.github.continuedev.continueintellijextension.constants.getConfigJsPath
import com.github.continuedev.continueintellijextension.constants.getConfigJsonPath
import com.intellij.execution.target.value.constant
import com.github.continuedev.continueintellijextension.error.ContinueErrorService
import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.*
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.components.service
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.util.SystemInfo
import com.intellij.util.concurrency.AppExecutorUtil
import com.intellij.util.io.HttpRequests
import com.intellij.util.messages.Topic
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import okhttp3.Request
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
import java.io.File
import java.io.IOException
import java.net.URL
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import javax.swing.*
Expand Down Expand Up @@ -72,11 +72,10 @@ class ContinueSettingsComponent : DumbAware {
}
}

@Serializable
class ContinueRemoteConfigSyncResponse {
var configJson: String? = null
var configJs: String? = null
}
data class ContinueRemoteConfigSyncResponse(
var configJson: String?,
var configJs: String?
)

@State(
name = "com.github.continuedev.continueintellijextension.services.ContinueExtensionSettings",
Expand Down Expand Up @@ -117,50 +116,24 @@ open class ContinueExtensionSettings : PersistentStateComponent<ContinueExtensio

// Sync remote config from server
private fun syncRemoteConfig() {
val state = instance.continueState

if (state.remoteConfigServerUrl != null && state.remoteConfigServerUrl!!.isNotEmpty()) {
// download remote config as json file

val client = OkHttpClient()
val baseUrl = state.remoteConfigServerUrl?.removeSuffix("/")

val requestBuilder = Request.Builder().url("${baseUrl}/sync")

if (state.userToken != null) {
requestBuilder.addHeader("Authorization", "Bearer ${state.userToken}")
}

val request = requestBuilder.build()
var configResponse: ContinueRemoteConfigSyncResponse? = null

// todo: test this feature manually before publishing PR (i don't have access)
val remoteServerUrl = state.remoteConfigServerUrl
val token = state.userToken
if (remoteServerUrl != null && remoteServerUrl.isNotEmpty()) {
val baseUrl = remoteServerUrl.removeSuffix("/")
try {
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")

response.body?.string()?.let { responseBody ->
try {
configResponse =
Json.decodeFromString<ContinueRemoteConfigSyncResponse>(responseBody)
} catch (e: Exception) {
e.printStackTrace()
return
}
}
}
val url = "$baseUrl/sync"
val responseBody = HttpRequests.post(url, HttpRequests.JSON_CONTENT_TYPE)
.tuner { connection ->
if (token != null)
connection.addRequestProperty("Authorization", "Bearer $token")
}.readString()
val response = Json.decodeFromString<ContinueRemoteConfigSyncResponse>(responseBody)
val file = File(getConfigJsonPath(URL(url).host))
response.configJs.let { file.writeText(it!!) }
response.configJson.let { file.writeText(it!!) }
} catch (e: IOException) {
e.printStackTrace()
return
}

if (configResponse?.configJson?.isNotEmpty()!!) {
val file = File(getConfigJsonPath(request.url.host))
file.writeText(configResponse!!.configJson!!)
}

if (configResponse?.configJs?.isNotEmpty()!!) {
val file = File(getConfigJsPath(request.url.host))
file.writeText(configResponse!!.configJs!!)
service<ContinueErrorService>().report(e, "Unexpected exception during remote config sync")
}
}
}
Expand All @@ -174,7 +147,7 @@ open class ContinueExtensionSettings : PersistentStateComponent<ContinueExtensio

instance.remoteSyncFuture = AppExecutorUtil.getAppScheduledExecutorService()
.scheduleWithFixedDelay(
{ syncRemoteConfig() },
::syncRemoteConfig,
0,
continueState.remoteConfigSyncPeriod.toLong(),
TimeUnit.MINUTES
Expand Down
1 change: 1 addition & 0 deletions packages/fetch/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function getProxyFromEnv(protocol: string): string | undefined {
}

// Note that request options proxy (per model) takes precedence over environment variables
// todo: this is the code i'm looking for?
export function getProxy(
protocol: string,
requestOptions?: RequestOptions,
Expand Down
Loading