Skip to content

Commit

Permalink
Merge pull request #194 from icerockdev/develop
Browse files Browse the repository at this point in the history
Release 0.20.0
  • Loading branch information
Alex009 authored Jan 15, 2023
2 parents 5dfac25 + fee2cbd commit a6f8f9d
Show file tree
Hide file tree
Showing 20 changed files with 233 additions and 74 deletions.
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

0 comments on commit a6f8f9d

Please sign in to comment.