Skip to content

Commit a05813a

Browse files
authored
Merge pull request #181 from icerockdev/develop
Release 0.18.0
2 parents 0f8459f + d6a3e50 commit a05813a

File tree

30 files changed

+627
-38
lines changed

30 files changed

+627
-38
lines changed

.idea/copyright/IceRock.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/copyright/profiles_settings.xml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ root build.gradle
3232
```groovy
3333
buildscript {
3434
repositories {
35-
mavenCentral()
35+
gradlePluginPortal()
3636
}
3737
3838
dependencies {
39-
classpath "dev.icerock.moko:network-generator:0.17.0"
39+
classpath "dev.icerock.moko:network-generator:0.18.0"
4040
}
4141
}
4242
@@ -53,9 +53,9 @@ project build.gradle
5353
apply plugin: "dev.icerock.mobile.multiplatform-network-generator"
5454
5555
dependencies {
56-
commonMainApi("dev.icerock.moko:network:0.17.0")
57-
commonMainApi("dev.icerock.moko:network-bignum:0.17.0") // kbignum serializer
58-
commonMainApi("dev.icerock.moko:network-errors:0.17.0") // moko-errors integration
56+
commonMainApi("dev.icerock.moko:network:0.18.0")
57+
commonMainApi("dev.icerock.moko:network-bignum:0.18.0") // kbignum serializer
58+
commonMainApi("dev.icerock.moko:network-errors:0.18.0") // moko-errors integration
5959
}
6060
```
6161

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ mokoResourcesVersion = "0.18.0"
1919
mokoMvvmVersion = "0.12.0"
2020
mokoErrorsVersion = "0.6.0"
2121
mokoTestVersion = "0.6.1"
22-
mokoNetworkVersion = "0.17.0"
22+
mokoNetworkVersion = "0.18.0"
2323

2424
# tests
2525
espressoCoreVersion = "3.2.0"

network-generator/src/main/resources/kotlin-ktor-client/api.mustache

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ import io.ktor.http.content.TextContent
120120
{{/hasFormParams}}
121121
with(builder.headers) {
122122
append("Accept", "application/json")
123+
{{#headerParams}}
124+
if ({{paramName}} != null)
125+
append("{{baseName}}", {{paramName}})
126+
{{/headerParams}}
123127
}
124128

125129
try {

network-generator/src/main/resources/kotlin-ktor-client/property_serializer.mustache

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
}
55
{{/isMap}}
66
{{#isArray}}
7-
{{#items}}{{>property_serializer}}{{/items}}.let {
8-
{{#uniqueItems}}SetSerializer(it){{/uniqueItems}}
9-
{{^uniqueItems}}ListSerializer(it){{/uniqueItems}}
10-
}
7+
{{#items}}{{>property_serializer}}{{/items}}.let {
8+
{{#uniqueItems}}SetSerializer(it){{/uniqueItems}}
9+
{{^uniqueItems}}ListSerializer(it){{/uniqueItems}}
10+
}
1111
{{/isArray}}
1212
{{^containerType}}
1313
{{dataType}}.serializer()
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2022 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.icerock.moko.network.features
6+
7+
import io.ktor.client.HttpClient
8+
import io.ktor.client.features.HttpClientFeature
9+
import io.ktor.client.request.HttpRequestPipeline
10+
import io.ktor.client.request.header
11+
import io.ktor.http.HttpHeaders
12+
import io.ktor.util.AttributeKey
13+
14+
class DynamicUserAgent(
15+
val agentProvider: () -> String?
16+
) {
17+
class Config(var agentProvider: () -> String? = { null })
18+
19+
companion object Feature : HttpClientFeature<Config, DynamicUserAgent> {
20+
override val key: AttributeKey<DynamicUserAgent> = AttributeKey("DynamicUserAgent")
21+
22+
override fun prepare(block: Config.() -> Unit): DynamicUserAgent =
23+
DynamicUserAgent(Config().apply(block).agentProvider)
24+
25+
override fun install(feature: DynamicUserAgent, scope: HttpClient) {
26+
scope.requestPipeline.intercept(HttpRequestPipeline.State) {
27+
feature.agentProvider()?.let { context.header(HttpHeaders.UserAgent, it) }
28+
}
29+
}
30+
}
31+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.icerock.moko.network
6+
7+
import io.ktor.client.features.websocket.WebSocketException
8+
import io.ktor.http.cio.websocket.CloseReason
9+
import io.ktor.http.cio.websocket.DefaultWebSocketSession
10+
import io.ktor.http.cio.websocket.ExperimentalWebSocketExtensionApi
11+
import io.ktor.http.cio.websocket.Frame
12+
import io.ktor.http.cio.websocket.WebSocketExtension
13+
import io.ktor.http.cio.websocket.readText
14+
import io.ktor.util.InternalAPI
15+
import kotlinx.coroutines.CompletableDeferred
16+
import kotlinx.coroutines.CoroutineScope
17+
import kotlinx.coroutines.Deferred
18+
import kotlinx.coroutines.cancel
19+
import kotlinx.coroutines.channels.Channel
20+
import kotlinx.coroutines.channels.ReceiveChannel
21+
import kotlinx.coroutines.channels.SendChannel
22+
import kotlinx.coroutines.channels.consumeEach
23+
import kotlinx.coroutines.launch
24+
import platform.Foundation.NSData
25+
import platform.Foundation.NSError
26+
import platform.Foundation.NSOperationQueue
27+
import platform.Foundation.NSPOSIXErrorDomain
28+
import platform.Foundation.NSURL
29+
import platform.Foundation.NSURLSession
30+
import platform.Foundation.NSURLSessionConfiguration
31+
import platform.Foundation.NSURLSessionWebSocketCloseCode
32+
import platform.Foundation.NSURLSessionWebSocketDelegateProtocol
33+
import platform.Foundation.NSURLSessionWebSocketMessage
34+
import platform.Foundation.NSURLSessionWebSocketTask
35+
import platform.darwin.NSObject
36+
import kotlin.coroutines.CoroutineContext
37+
38+
internal class IosWebSocket(
39+
socketEndpoint: NSURL,
40+
override val coroutineContext: CoroutineContext
41+
) : DefaultWebSocketSession {
42+
internal val originResponse: CompletableDeferred<String?> = CompletableDeferred()
43+
44+
private val webSocket: NSURLSessionWebSocketTask
45+
46+
private val _incoming = Channel<Frame>()
47+
private val _outgoing = Channel<Frame>()
48+
private val _closeReason = CompletableDeferred<CloseReason?>()
49+
50+
override val incoming: ReceiveChannel<Frame> = _incoming
51+
override val outgoing: SendChannel<Frame> = _outgoing
52+
override val closeReason: Deferred<CloseReason?> = _closeReason
53+
54+
@ExperimentalWebSocketExtensionApi
55+
override val extensions: List<WebSocketExtension<*>>
56+
get() = emptyList()
57+
58+
override var maxFrameSize: Long
59+
get() = throw WebSocketException("websocket doesn't support max frame size.")
60+
set(_) = throw WebSocketException("websocket doesn't support max frame size.")
61+
62+
override suspend fun flush() = Unit
63+
64+
@OptIn(ExperimentalWebSocketExtensionApi::class)
65+
@InternalAPI
66+
override fun start(negotiatedExtensions: List<WebSocketExtension<*>>) {
67+
require(negotiatedExtensions.isEmpty()) { "Extensions are not supported." }
68+
}
69+
70+
init {
71+
val urlSession = NSURLSession.sessionWithConfiguration(
72+
configuration = NSURLSessionConfiguration.defaultSessionConfiguration(),
73+
delegate = object : NSObject(), NSURLSessionWebSocketDelegateProtocol {
74+
override fun URLSession(
75+
session: NSURLSession,
76+
webSocketTask: NSURLSessionWebSocketTask,
77+
didOpenWithProtocol: String?
78+
) {
79+
originResponse.complete(didOpenWithProtocol)
80+
}
81+
82+
override fun URLSession(
83+
session: NSURLSession,
84+
webSocketTask: NSURLSessionWebSocketTask,
85+
didCloseWithCode: NSURLSessionWebSocketCloseCode,
86+
reason: NSData?
87+
) {
88+
val closeReason = CloseReason(
89+
code = CloseReason.Codes.PROTOCOL_ERROR,
90+
message = "$didCloseWithCode : ${reason.toString()}"
91+
)
92+
_closeReason.complete(closeReason)
93+
}
94+
},
95+
delegateQueue = NSOperationQueue.currentQueue()
96+
)
97+
webSocket = urlSession.webSocketTaskWithURL(socketEndpoint)
98+
99+
CoroutineScope(coroutineContext).launch {
100+
_outgoing.consumeEach { frame ->
101+
if (frame is Frame.Text) {
102+
val message = NSURLSessionWebSocketMessage(frame.readText())
103+
webSocket.sendMessage(message) { nsError ->
104+
if (nsError == null) return@sendMessage
105+
106+
nsError.closeSocketOrThrow {
107+
throw SendMessageException(nsError.description ?: nsError.toString())
108+
}
109+
}
110+
}
111+
}
112+
}
113+
114+
listenMessages()
115+
}
116+
117+
fun start() {
118+
webSocket.resume()
119+
}
120+
121+
private fun listenMessages() {
122+
webSocket.receiveMessageWithCompletionHandler { message, nsError ->
123+
when {
124+
nsError != null -> {
125+
nsError.closeSocketOrThrow {
126+
throw ReceiveMessageException(nsError.description ?: nsError.toString())
127+
}
128+
}
129+
message != null -> {
130+
message.string?.let { _incoming.trySend(Frame.Text(it)) }
131+
}
132+
}
133+
listenMessages()
134+
}
135+
}
136+
137+
private fun NSError.closeSocketOrThrow(throwBlock: () -> Unit) {
138+
if (domain !in listOf("kNWErrorDomainPOSIX", NSPOSIXErrorDomain)) return throwBlock()
139+
if (code != 57L) return throwBlock()
140+
141+
val closeReason = CloseReason(
142+
code = CloseReason.Codes.NORMAL,
143+
message = description ?: toString()
144+
)
145+
_closeReason.complete(closeReason)
146+
webSocket.cancel()
147+
}
148+
149+
override fun terminate() {
150+
coroutineContext.cancel()
151+
}
152+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.icerock.moko.network
6+
7+
import io.ktor.utils.io.errors.IOException
8+
9+
class ReceiveMessageException(message: String) : IOException(message)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
* Copyright 2021 IceRock MAG Inc. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package dev.icerock.moko.network
6+
7+
import io.ktor.utils.io.errors.IOException
8+
9+
class SendMessageException(message: String) : IOException(message)

0 commit comments

Comments
 (0)