Skip to content
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

Release 0.20.0 #194

Merged
merged 6 commits into from
Jan 15, 2023
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
2 changes: 1 addition & 1 deletion .github/workflows/compilation-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

jobs:
build:
runs-on: macOS-latest
runs-on: macOS-11

steps:
- uses: actions/checkout@v1
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
jobs:
publish:
name: Publish library at mavenCentral
runs-on: macOS-latest
runs-on: macOS-11
env:
OSSRH_USER: ${{ secrets.OSSRH_USER }}
OSSRH_KEY: ${{ secrets.OSSRH_KEY }}
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ buildscript {
}

dependencies {
classpath "dev.icerock.moko:network-generator:0.19.0"
classpath "dev.icerock.moko:network-generator:0.20.0"
}
}

Expand All @@ -53,9 +53,10 @@ project build.gradle
apply plugin: "dev.icerock.mobile.multiplatform-network-generator"

dependencies {
commonMainApi("dev.icerock.moko:network:0.19.0")
commonMainApi("dev.icerock.moko:network-bignum:0.19.0") // kbignum serializer
commonMainApi("dev.icerock.moko:network-errors:0.19.0") // moko-errors integration
commonMainApi("dev.icerock.moko:network:0.20.0")
commonMainApi("dev.icerock.moko:network-engine:0.20.0") // configured HttpClientEngine
commonMainApi("dev.icerock.moko:network-bignum:0.20.0") // kbignum serializer
commonMainApi("dev.icerock.moko:network-errors:0.20.0") // moko-errors integration
}
```

Expand Down
5 changes: 1 addition & 4 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ mokoResourcesVersion = "0.20.1"
mokoMvvmVersion = "0.12.0"
mokoErrorsVersion = "0.6.0"
mokoTestVersion = "0.6.1"
mokoNetworkVersion = "0.19.0"
mokoNetworkVersion = "0.20.0"

# tests
espressoCoreVersion = "3.2.0"
Expand Down Expand Up @@ -61,9 +61,6 @@ mokoResources = { module = "dev.icerock.moko:resources", version.ref = "mokoReso
mokoMvvmCore = { module = "dev.icerock.moko:mvvm-core", version.ref = "mokoMvvmVersion" }
mokoMvvmLiveData = { module = "dev.icerock.moko:mvvm-livedata", version.ref = "mokoMvvmVersion" }
mokoErrors = { module = "dev.icerock.moko:errors", version.ref = "mokoErrorsVersion" }
mokoNetwork = { module = "dev.icerock.moko:network", version.ref = "mokoNetworkVersion" }
mokoNetworkErrors = { module = "dev.icerock.moko:network-errors", version.ref = "mokoNetworkVersion" }
mokoNetworkBignum = { module = "dev.icerock.moko:network-bignum", version.ref = "mokoNetworkVersion" }

# tests
espressoCore = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCoreVersion" }
Expand Down
40 changes: 40 additions & 0 deletions network-engine/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2019 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
*/

plugins {
id("dev.icerock.moko.gradle.multiplatform.mobile")
id("dev.icerock.moko.gradle.detekt")
id("dev.icerock.moko.gradle.publication")
id("dev.icerock.moko.gradle.stub.javadoc")
id("dev.icerock.moko.gradle.tests")
}

kotlin {
jvm()

sourceSets {
val commonMain by getting

val commonJvmAndroid = create("commonJvmAndroid") {
dependsOn(commonMain)
dependencies {
api(libs.ktorClientOkHttp)
}
}

val androidMain by getting {
dependsOn(commonJvmAndroid)
}

val jvmMain by getting {
dependsOn(commonJvmAndroid)
}
}
}

dependencies {
commonMainImplementation(libs.coroutines)
commonMainApi(projects.network)
iosMainApi(libs.ktorClientIos)
}
2 changes: 2 additions & 0 deletions network-engine/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="dev.icerock.moko.network.engine" />
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ package dev.icerock.moko.network

import io.ktor.client.engine.HttpClientEngine
import io.ktor.client.engine.darwin.Darwin
import io.ktor.client.engine.darwin.DarwinHttpRequestException

actual fun createHttpClientEngine(block: HttpClientEngineConfig.() -> Unit): HttpClientEngine {
// configure darwin throwable mapper
ThrowableToNSErrorMapper.setup { (it as? DarwinHttpRequestException)?.origin }
// configure darwin engine
val config = HttpClientEngineConfig().also(block)
return Darwin.create {
this.configureSession {
Expand Down
5 changes: 0 additions & 5 deletions network/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ kotlin {

val commonJvmAndroid = create("commonJvmAndroid") {
dependsOn(commonMain)
dependencies {
api(libs.ktorClientOkHttp)
}
}

val androidMain by getting {
Expand All @@ -44,8 +41,6 @@ dependencies {
commonMainImplementation(libs.coroutines)
commonMainApi(libs.kotlinSerialization)
commonMainApi(libs.ktorClient)
androidMainApi(libs.ktorClientOkHttp)
iosMainApi(libs.ktorClientIos)

androidMainImplementation(libs.appCompat)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import io.ktor.client.statement.request
import io.ktor.http.HttpStatusCode
import io.ktor.util.AttributeKey
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

class RefreshTokenPlugin(
private val updateTokenHandler: suspend () -> Boolean,
Expand Down Expand Up @@ -49,30 +50,28 @@ class RefreshTokenPlugin(
return@intercept
}

refreshTokenHttpPluginMutex.lock()
refreshTokenHttpPluginMutex.withLock {

// If token of the request isn't actual, then token has already been updated and
// let's just to try repeat request
if (!plugin.isCredentialsActual(subject.request)) {
refreshTokenHttpPluginMutex.unlock()
val requestBuilder = HttpRequestBuilder().takeFrom(subject.request)
val result: HttpResponse = scope.request(requestBuilder)
proceedWith(result)
return@intercept
}
// If token of the request isn't actual, then token has already been updated and
// let's just to try repeat request
if (!plugin.isCredentialsActual(subject.request)) {
val requestBuilder = HttpRequestBuilder().takeFrom(subject.request)
val result: HttpResponse = scope.request(requestBuilder)
proceedWith(result)
return@intercept
}

// Else if token of the request is actual (same as in the storage), then need to send
// refresh request.
if (plugin.updateTokenHandler.invoke()) {
// If the request refresh was successful, then let's just to try repeat request
refreshTokenHttpPluginMutex.unlock()
val requestBuilder = HttpRequestBuilder().takeFrom(subject.request)
val result: HttpResponse = scope.request(requestBuilder)
proceedWith(result)
} else {
// If the request refresh was unsuccessful
refreshTokenHttpPluginMutex.unlock()
proceedWith(subject)
// Else if token of the request is actual (same as in the storage), then need to send
// refresh request.
if (plugin.updateTokenHandler.invoke()) {
// If the request refresh was successful, then let's just to try repeat request
val requestBuilder = HttpRequestBuilder().takeFrom(subject.request)
val result: HttpResponse = scope.request(requestBuilder)
proceedWith(result)
} else {
// If the request refresh was unsuccessful
proceedWith(subject)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class TokenPlugin private constructor(
}
}

interface TokenProvider {
fun interface TokenProvider {
fun getToken(): String?
}
}
98 changes: 97 additions & 1 deletion network/src/commonTest/kotlin/RefreshTokenPluginTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import io.ktor.client.engine.mock.respondOk
import io.ktor.client.request.get
import io.ktor.client.statement.request
import io.ktor.http.HttpStatusCode
import io.ktor.utils.io.errors.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
import kotlin.test.Test
import kotlin.test.assertEquals

Expand Down Expand Up @@ -48,6 +49,38 @@ class RefreshTokenPluginTest {
val invalidToken = "123"
val validToken = "124"
val tokenHolder = MutableStateFlow<String?>(invalidToken)
val client = createMockClient(
tokenProvider = { tokenHolder.value },
pluginConfig = {
this.updateTokenHandler = {
tokenHolder.value = validToken
true
}
this.isCredentialsActual = { request ->
request.headers[AUTH_HEADER_NAME] == tokenHolder.value
}
}
) { request ->
if (request.headers[AUTH_HEADER_NAME] == invalidToken) {
respondError(status = HttpStatusCode.Unauthorized)
} else respondOk()
}

val result = runBlocking {
client.get("localhost")
}

assertEquals(expected = HttpStatusCode.OK, actual = result.status)
assertEquals(expected = validToken, actual = result.request.headers[AUTH_HEADER_NAME])
}

@Test
fun `mutex not lock permanently when isCredentialsActual fail`() {
val invalidToken = "123"
val validToken = "124"
val tokenHolder = MutableStateFlow<String?>(invalidToken)
var isFirstTime = true

val client = createMockClient(
tokenProvider = object : TokenPlugin.TokenProvider {
override fun getToken(): String? {
Expand All @@ -59,6 +92,61 @@ class RefreshTokenPluginTest {
tokenHolder.value = validToken
true
}
this.isCredentialsActual = { request ->
with(request.headers[AUTH_HEADER_NAME] == tokenHolder.value) {
if (isFirstTime) {
isFirstTime = false
throw IOException("simulate io Error")
}
this
}
}
},
handler = { request ->
if (request.headers[AUTH_HEADER_NAME] == invalidToken) {
respondError(status = HttpStatusCode.Unauthorized)
} else respondOk()
}
)

runCatching {
runBlocking {
client.get("localhost")
}
}.onFailure {
println("simulate first request fail")
}

val result = runBlocking {
client.get("localhost")
}

assertEquals(expected = HttpStatusCode.OK, actual = result.status)
assertEquals(expected = validToken, actual = result.request.headers[AUTH_HEADER_NAME])
}

@Test
fun `mutex not lock permanently when updateTokenHandler fail`() {
val invalidToken = "123"
val validToken = "124"
val tokenHolder = MutableStateFlow<String?>(invalidToken)
var isFirstTime = true

val client = createMockClient(
tokenProvider = object : TokenPlugin.TokenProvider {
override fun getToken(): String? {
return tokenHolder.value
}
},
pluginConfig = {
this.updateTokenHandler = {
if (isFirstTime) {
isFirstTime = false
throw IOException("simulate io Error")
}
tokenHolder.value = validToken
true
}
this.isCredentialsActual = { request ->
request.headers[AUTH_HEADER_NAME] == tokenHolder.value
}
Expand All @@ -70,6 +158,14 @@ class RefreshTokenPluginTest {
}
)

runCatching {
runBlocking {
client.get("localhost")
}
}.onFailure {
println("simulate first request fail")
}

val result = runBlocking {
client.get("localhost")
}
Expand Down
30 changes: 10 additions & 20 deletions network/src/commonTest/kotlin/TokenFeatureTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,11 @@ class TokenFeatureTest {
@Test
fun `token added when exist`() {
val client = createMockClient(
tokenProvider = object : TokenPlugin.TokenProvider {
override fun getToken(): String {
return "mytoken"
}
},
handler = { request ->
if (request.headers[AUTH_HEADER_NAME] == "mytoken") respondOk()
else respondBadRequest()
}
)
tokenProvider = { "mytoken" }
) { request ->
if (request.headers[AUTH_HEADER_NAME] == "mytoken") respondOk()
else respondBadRequest()
}

val result = runBlocking {
client.get("localhost")
Expand All @@ -39,16 +34,11 @@ class TokenFeatureTest {
@Test
fun `token not added when not exist`() {
val client = createMockClient(
tokenProvider = object : TokenPlugin.TokenProvider {
override fun getToken(): String? {
return null
}
},
handler = { request ->
if (request.headers.contains(AUTH_HEADER_NAME).not()) respondOk()
else respondBadRequest()
}
)
tokenProvider = { null }
) { request ->
if (request.headers.contains(AUTH_HEADER_NAME).not()) respondOk()
else respondBadRequest()
}

val result = runBlocking {
client.get("localhost")
Expand Down
Loading