Skip to content
Merged
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
18 changes: 17 additions & 1 deletion stytch/src/main/kotlin/com/stytch/java/b2b/StytchB2BClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,15 @@ import com.stytch.java.consumer.api.project.ProjectImpl
import com.stytch.java.http.HttpClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel

public class StytchB2BClient
@JvmOverloads
constructor(projectId: String, secret: String, clientConfig: OptionalClientConfig = OptionalClientConfig()) {
constructor(
projectId: String,
secret: String,
clientConfig: OptionalClientConfig = OptionalClientConfig(),
) : AutoCloseable {
private val coroutineScope = CoroutineScope(SupervisorJob())
private val baseUrl = getBaseUrl(projectId, clientConfig)
private val httpClient: HttpClient =
Expand Down Expand Up @@ -100,6 +105,17 @@ public class StytchB2BClient
public val sessions: Sessions = SessionsImpl(httpClient, coroutineScope, httpsJwks, jwtOptions, policyCache)
public val totps: TOTPs = TOTPsImpl(httpClient, coroutineScope)

override fun close() {
// Cancel the scope first so in-flight coroutines stop enqueuing new OkHttp calls.
// cancelBackgroundRefresh() is redundant once the scope is cancelled (child jobs
// are parented off this scope), but called first for explicit intent.
jwksCache.cancelBackgroundRefresh()
policyCache.cancelBackgroundRefresh()
coroutineScope.cancel()
httpClient.close()
fraudHttpClient.close()
}

/**
* Resolve the base URL for the Stytch API environment.
*/
Expand Down
2 changes: 1 addition & 1 deletion stytch/src/main/kotlin/com/stytch/java/common/Version.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package com.stytch.java.common

internal const val VERSION = "11.0.0"
internal const val VERSION = "11.1.0"
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,15 @@ import com.stytch.java.consumer.api.webauthn.WebAuthnImpl
import com.stytch.java.http.HttpClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel

public class StytchClient
@JvmOverloads
constructor(projectId: String, secret: String, clientConfig: OptionalClientConfig = OptionalClientConfig()) {
constructor(
projectId: String,
secret: String,
clientConfig: OptionalClientConfig = OptionalClientConfig(),
) : AutoCloseable {
private val coroutineScope = CoroutineScope(SupervisorJob())
private val baseUrl = getBaseUrl(projectId, clientConfig)
private val httpClient: HttpClient =
Expand Down Expand Up @@ -92,6 +97,16 @@ public class StytchClient
public val users: Users = UsersImpl(httpClient, coroutineScope)
public val webauthn: WebAuthn = WebAuthnImpl(httpClient, coroutineScope)

override fun close() {
// Cancel the scope first so in-flight coroutines stop enqueuing new OkHttp calls.
// cancelBackgroundRefresh() is redundant once the scope is cancelled (child jobs
// are parented off this scope), but called first for explicit intent.
jwksCache.cancelBackgroundRefresh()
coroutineScope.cancel()
httpClient.close()
fraudHttpClient.close()
}

/**
* Resolve the base URL for the Stytch API environment.
*/
Expand Down
8 changes: 7 additions & 1 deletion stytch/src/main/kotlin/com/stytch/java/http/HttpClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ internal class HttpClient(
projectId: String,
secret: String,
private val client: OkHttpClient = createHttpClient(projectId, secret),
) {
) : AutoCloseable {
private val moshi = Moshi.Builder().add(InstantAdapter()).build()

internal fun buildUrl(
Expand Down Expand Up @@ -222,4 +222,10 @@ internal class HttpClient(
StytchResult.Error(StytchException.Critical(e))
}
}

override fun close() {
client.dispatcher.cancelAll()
client.dispatcher.executorService.shutdown()
client.connectionPool.evictAll()
}
}
32 changes: 32 additions & 0 deletions stytch/src/test/kotlin/com/stytch/java/http/HttpClientTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import okhttp3.Call
import okhttp3.Callback
import okhttp3.ConnectionPool
import okhttp3.Dispatcher
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Protocol
Expand All @@ -27,6 +29,7 @@ import okhttp3.ResponseBody.Companion.toResponseBody
import okio.IOException
import org.junit.Before
import org.junit.Test
import java.util.concurrent.ExecutorService

@OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class)
internal class HttpClientTest {
Expand Down Expand Up @@ -174,6 +177,35 @@ internal class HttpClientTest {
assert(result is StytchResult.Error)
}

@Test
fun `close cancels in-flight calls, shuts down executor, and evicts connection pool`() {
val mockExecutorService: ExecutorService = mockk(relaxUnitFun = true)
val mockDispatcher: Dispatcher =
mockk {
every { cancelAll() } just runs
every { executorService } returns mockExecutorService
}
val mockConnectionPool: ConnectionPool = mockk(relaxUnitFun = true)
val mockClient: OkHttpClient =
mockk {
every { dispatcher } returns mockDispatcher
every { connectionPool } returns mockConnectionPool
}
val client =
HttpClient(
baseUrl = "http://something",
projectId = "project-id",
secret = "secret",
client = mockClient,
)

client.close()

verify(exactly = 1) { mockDispatcher.cancelAll() }
verify(exactly = 1) { mockExecutorService.shutdown() }
verify(exactly = 1) { mockConnectionPool.evictAll() }
}

@Test
fun `makeRequest returns StytchResult_Success for successful requests that are mappable`() =
runTest {
Expand Down
2 changes: 1 addition & 1 deletion version.gradle.kts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version = "11.0.0"
version = "11.1.0"
26 changes: 13 additions & 13 deletions workbench/src/main/kotlin/com/stytch/workbench/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import com.stytch.java.consumer.StytchClient
import com.stytch.java.consumer.models.magiclinksemail.LoginOrCreateRequest

suspend fun main() {
val stytchClient =
StytchClient(
projectId = "project-test-....",
secret = "secret-test-....",
)
val result =
stytchClient.magicLinks.email.loginOrCreate(
LoginOrCreateRequest(
email = "email@address.com",
signupExpirationMinutes = 30,
),
)
println(result)
StytchClient(
projectId = "project-test-....",
secret = "secret-test-....",
).use { stytchClient ->
val result =
stytchClient.magicLinks.email.loginOrCreate(
LoginOrCreateRequest(
email = "email@address.com",
signupExpirationMinutes = 30,
),
)
println(result)
}
}
Loading