Skip to content

Commit

Permalink
Allow passing an optional ClientConfig when calling Hotwire.loadPathC…
Browse files Browse the repository at this point in the history
…onfiguration() to pass custom HTTP headers when loading the path configuration file.
  • Loading branch information
jayohms committed Feb 23, 2025
1 parent 228a0c8 commit 138384e
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 171 deletions.
12 changes: 10 additions & 2 deletions core/src/main/kotlin/dev/hotwire/core/config/Hotwire.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,16 @@ object Hotwire {
/**
* Loads the [PathConfiguration] JSON file(s) from the provided location to
* configure navigation rules.
* @param context The application or activity context.
* @param location Specifies local and/or remote location to retrieve path configuration files.
* @param clientConfig Optional HTTP client configuration options to use when fetching remote
* path configuration files from your server.
*/
fun loadPathConfiguration(context: Context, location: PathConfiguration.Location) {
config.pathConfiguration.load(context.applicationContext, location)
fun loadPathConfiguration(
context: Context,
location: PathConfiguration.Location,
clientConfig: PathConfiguration.ClientConfig = PathConfiguration.ClientConfig()
) {
config.pathConfiguration.load(context.applicationContext, location, clientConfig)
}
}
9 changes: 0 additions & 9 deletions core/src/main/kotlin/dev/hotwire/core/config/HotwireConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import dev.hotwire.core.bridge.BridgeComponent
import dev.hotwire.core.bridge.BridgeComponentFactory
import dev.hotwire.core.bridge.BridgeComponentJsonConverter
import dev.hotwire.core.turbo.config.PathConfiguration
import dev.hotwire.core.turbo.config.PathConfigurationClientConfig
import dev.hotwire.core.turbo.http.HotwireHttpClient
import dev.hotwire.core.turbo.offline.OfflineRequestHandler
import dev.hotwire.core.turbo.webview.HotwireWebView
Expand Down Expand Up @@ -101,12 +100,4 @@ class HotwireConfig internal constructor() {
fun userAgentWithWebViewDefault(context: Context): String {
return "$userAgent ${Hotwire.webViewInfo(context).defaultUserAgent}"
}

/**
* Sets custom headers for PathConfiguration requests.
* @param headers A map of header names to header values.
*/
fun pathConfigurationClientHeaders(headers: Map<String, String>) {
PathConfigurationClientConfig.setRequestHeaders(headers)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,31 @@ class PathConfiguration {
val remoteFileUrl: String? = null
)

/**
* HTTP client configuration options when fetching remote path configuration
* files from your server.
*/
data class ClientConfig(
/**
* Custom headers to send with each remote path configuration file request.
*/
val headers: Map<String, String> = emptyMap()
)

/**
* Loads and parses the specified configuration file(s) from their local
* and/or remote locations.
*/
fun load(context: Context, location: Location) {
fun load(
context: Context,
location: Location,
clientConfig: ClientConfig
) {
if (loader == null) {
loader = PathConfigurationLoader(context.applicationContext)
}

loader?.load(location) {
loader?.load(location, clientConfig) {
cachedProperties.clear()
rules = it.rules
settings = it.settings
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,30 @@ internal class PathConfigurationLoader(val context: Context) : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = dispatcherProvider.io + Job()

fun load(location: PathConfiguration.Location, onCompletion: (PathConfiguration) -> Unit) {
fun load(
location: PathConfiguration.Location,
clientConfig: PathConfiguration.ClientConfig,
onCompletion: (PathConfiguration) -> Unit
) {
location.assetFilePath?.let {
loadBundledAssetConfiguration(it, onCompletion)
}

location.remoteFileUrl?.let {
downloadRemoteConfiguration(it, onCompletion)
downloadRemoteConfiguration(it, clientConfig, onCompletion)
}
}

private fun downloadRemoteConfiguration(url: String, onCompletion: (PathConfiguration) -> Unit) {
private fun downloadRemoteConfiguration(
url: String,
clientConfig: PathConfiguration.ClientConfig,
onCompletion: (PathConfiguration) -> Unit
) {
// Always load the previously cached version first, if available
loadCachedConfigurationForUrl(url, onCompletion)

launch {
repository.getRemoteConfiguration(url)?.let { json ->
repository.getRemoteConfiguration(url, clientConfig)?.let { json ->
load(json)?.let {
logEvent("remotePathConfigurationLoaded", url)
onCompletion(it)
Expand All @@ -42,15 +50,21 @@ internal class PathConfigurationLoader(val context: Context) : CoroutineScope {
}
}

private fun loadBundledAssetConfiguration(filePath: String, onCompletion: (PathConfiguration) -> Unit) {
private fun loadBundledAssetConfiguration(
filePath: String,
onCompletion: (PathConfiguration) -> Unit
) {
val json = repository.getBundledConfiguration(context, filePath)
load(json)?.let {
logEvent("bundledPathConfigurationLoaded", filePath)
onCompletion(it)
}
}

private fun loadCachedConfigurationForUrl(url: String, onCompletion: (PathConfiguration) -> Unit) {
private fun loadCachedConfigurationForUrl(
url: String,
onCompletion: (PathConfiguration) -> Unit
) {
repository.getCachedConfigurationForUrl(context, url)?.let { json ->
load(json)?.let {
logEvent("cachedPathConfigurationLoaded", url)
Expand All @@ -59,7 +73,10 @@ internal class PathConfigurationLoader(val context: Context) : CoroutineScope {
}
}

private fun cacheConfigurationForUrl(url: String, pathConfiguration: PathConfiguration) {
private fun cacheConfigurationForUrl(
url: String,
pathConfiguration: PathConfiguration
) {
repository.cacheConfigurationForUrl(context, url, pathConfiguration)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ import okhttp3.Request
internal class PathConfigurationRepository {
private val cacheFile = "turbo"

suspend fun getRemoteConfiguration(url: String): String? {
suspend fun getRemoteConfiguration(
url: String,
clientConfig: PathConfiguration.ClientConfig
): String? {
val requestBuilder = Request.Builder().url(url)

PathConfigurationClientConfig.getHeaders()?.forEach { (key, value) ->
clientConfig.headers.forEach { (key, value) ->
requestBuilder.header(key, value)
}

Expand All @@ -27,15 +30,25 @@ internal class PathConfigurationRepository {
}
}

fun getBundledConfiguration(context: Context, filePath: String): String {
fun getBundledConfiguration(
context: Context,
filePath: String
): String {
return contentFromAsset(context, filePath)
}

fun getCachedConfigurationForUrl(context: Context, url: String): String? {
fun getCachedConfigurationForUrl(
context: Context,
url: String
): String? {
return prefs(context).getString(url, null)
}

fun cacheConfigurationForUrl(context: Context, url: String, pathConfiguration: PathConfiguration) {
fun cacheConfigurationForUrl(
context: Context,
url: String,
pathConfiguration: PathConfiguration
) {
prefs(context).edit {
putString(url, pathConfiguration.toJson())
}
Expand Down
40 changes: 0 additions & 40 deletions core/src/test/kotlin/dev/hotwire/core/config/HotwireConfigTest.kt

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.os.Build
import androidx.test.core.app.ApplicationProvider
import com.google.gson.reflect.TypeToken
import dev.hotwire.core.turbo.BaseRepositoryTest
import dev.hotwire.core.turbo.config.PathConfiguration.*
import dev.hotwire.core.turbo.util.toObject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand All @@ -29,19 +30,13 @@ class PathConfigurationRepositoryTest : BaseRepositoryTest() {
context = ApplicationProvider.getApplicationContext()
}

@After
fun tearDown() {
PathConfigurationClientConfig.setRequestHeaders(emptyMap())
}


@Test
fun getRemoteConfiguration() {
enqueueResponse("test-configuration.json")

runBlocking {
launch(Dispatchers.Main) {
val json = repository.getRemoteConfiguration(baseUrl())
val json = repository.getRemoteConfiguration(baseUrl(), ClientConfig())
assertThat(json).isNotNull()

val config = load(json)
Expand Down Expand Up @@ -73,33 +68,19 @@ class PathConfigurationRepositoryTest : BaseRepositoryTest() {
}

@Test
fun `getRemoteConfiguration should not include custom headers by default`() {
enqueueResponse("test-configuration.json")

runBlocking {
launch(Dispatchers.Main) {
repository.getRemoteConfiguration(baseUrl())

val request = server.takeRequest()
print(request.headers)
assertThat(request.headers["Custom-Header"]).isNull()
}
}
}

@Test
fun `getRemoteConfiguration should include custom headers when set`() {
fun `getRemoteConfiguration should include custom headers`() {
enqueueResponse("test-configuration.json")

val customHeaders = mapOf(
"Custom-Header" to "test-value",
"Accept" to "application/json"
val clientConfig = ClientConfig(
headers = mapOf(
"Accept" to "application/json",
"Custom-Header" to "test-value"
)
)
PathConfigurationClientConfig.setRequestHeaders(customHeaders)

runBlocking {
launch(Dispatchers.Main) {
repository.getRemoteConfiguration(baseUrl())
repository.getRemoteConfiguration(baseUrl(), clientConfig)

val request = server.takeRequest()
assertThat(request.headers["Custom-Header"]).isEqualTo("test-value")
Expand Down
Loading

0 comments on commit 138384e

Please sign in to comment.