15
15
package com.amplifyframework.aws.appsync.events
16
16
17
17
import com.amplifyframework.aws.appsync.core.AppSyncAuthorizer
18
- import com.amplifyframework.aws.appsync.core.AppSyncRequest
19
18
import com.amplifyframework.aws.appsync.events.data.ChannelAuthorizers
20
19
import com.amplifyframework.aws.appsync.events.data.ConnectionClosedException
21
20
import com.amplifyframework.aws.appsync.events.data.EventsException
@@ -34,7 +33,9 @@ import kotlinx.coroutines.flow.flow
34
33
import kotlinx.coroutines.flow.flowOn
35
34
import kotlinx.coroutines.flow.onCompletion
36
35
import kotlinx.coroutines.flow.onStart
36
+ import kotlinx.serialization.json.JsonArray
37
37
import kotlinx.serialization.json.JsonElement
38
+ import kotlinx.serialization.json.JsonPrimitive
38
39
39
40
/* *
40
41
* A class to manage channel subscriptions and publishes
@@ -45,7 +46,6 @@ import kotlinx.serialization.json.JsonElement
45
46
class EventsChannel internal constructor(
46
47
val name : String ,
47
48
val authorizers : ChannelAuthorizers ,
48
- private val endpoints : EventsEndpoints ,
49
49
private val eventsWebSocketProvider : EventsWebSocketProvider
50
50
) {
51
51
@@ -83,12 +83,12 @@ class EventsChannel internal constructor(
83
83
* @param authorizer for the publish call. If not provided, the EventChannel publish authorizer will be used.
84
84
* @return result of publish.
85
85
*/
86
- @Throws(EventsException ::class )
86
+ @Throws(Exception ::class )
87
87
suspend fun publish (
88
88
event : JsonElement ,
89
89
authorizer : AppSyncAuthorizer = this.authorizers.publishAuthorizer
90
90
): PublishResult {
91
- TODO ( " Need to implement " )
91
+ return publish( listOf (event), authorizer )
92
92
}
93
93
94
94
/* *
@@ -98,12 +98,43 @@ class EventsChannel internal constructor(
98
98
* @param authorizer for the publish call. If not provided, the EventChannel publish authorizer will be used.
99
99
* @return result of publish.
100
100
*/
101
- @Throws(EventsException ::class )
101
+ @Throws(Exception ::class )
102
102
suspend fun publish (
103
103
events : List <JsonElement >,
104
104
authorizer : AppSyncAuthorizer = this.authorizers.publishAuthorizer
105
- ): PublishResult {
106
- TODO (" Need to implement" )
105
+ ): PublishResult = coroutineScope {
106
+ val publishId = UUID .randomUUID().toString()
107
+ val publishMessage = WebSocketMessage .Send .Publish (
108
+ id = publishId,
109
+ channel = name,
110
+ events = JsonArray (events.map { JsonPrimitive (it.toString()) }),
111
+ )
112
+
113
+ val webSocket = eventsWebSocketProvider.getConnectedWebSocket()
114
+ val deferredResponse = async { getPublishResponse(webSocket, publishId) }
115
+
116
+ val queued = webSocket.sendWithAuthorizer(publishMessage, authorizer)
117
+ if (! queued) {
118
+ throw webSocket.disconnectReason?.toCloseException() ? : ConnectionClosedException ()
119
+ }
120
+
121
+ return @coroutineScope when (val response = deferredResponse.await()) {
122
+ is WebSocketMessage .Received .PublishSuccess -> {
123
+ PublishResult (response.successfulEvents, response.failedEvents)
124
+ }
125
+
126
+ is WebSocketMessage .ErrorContainer -> {
127
+ val fallbackMessage = " Failed to publish event(s)"
128
+ throw response.errors.firstOrNull()?.toEventsException(fallbackMessage)
129
+ ? : EventsException (fallbackMessage)
130
+ }
131
+
132
+ is WebSocketMessage .Closed -> {
133
+ throw response.reason.toCloseException()
134
+ }
135
+
136
+ else -> throw EventsException (" Received unexpected publish response of type: ${response::class } " )
137
+ }
107
138
}
108
139
109
140
private fun createSubscriptionEventDataFlow (subscriptionHolder : SubscriptionHolder ): Flow <EventsMessage > {
@@ -116,11 +147,12 @@ class EventsChannel internal constructor(
116
147
emit(EventsMessage (it.event))
117
148
}
118
149
it is WebSocketMessage .Closed -> {
119
- if (it.reason is DisconnectReason .UserInitiated ) {
120
- throw UserClosedConnectionException ()
121
- } else {
122
- throw ConnectionClosedException (it.reason.throwable)
123
- }
150
+ throw it.reason.toCloseException()
151
+ }
152
+ it is WebSocketMessage .ErrorContainer && it.id == subscriptionHolder.id -> {
153
+ val exceptionMessage = " Received error for subscription"
154
+ throw it.errors.firstOrNull()?.toEventsException(exceptionMessage)
155
+ ? : EventsException (exceptionMessage)
124
156
}
125
157
else -> Unit
126
158
}
@@ -133,24 +165,31 @@ class EventsChannel internal constructor(
133
165
subscriptionId : String ,
134
166
authorizer : AppSyncAuthorizer
135
167
): Boolean = coroutineScope {
168
+ // create a deferred holder for subscription response
136
169
val deferredSubscriptionResponse = async { getSubscriptionResponse(webSocket, subscriptionId) }
137
170
138
171
// Publish subscription to websocket
139
- publishSubscription(webSocket, subscriptionId, authorizer)
172
+ val queued = webSocket.sendWithAuthorizer(
173
+ webSocketMessage = WebSocketMessage .Send .Subscription .Subscribe (id = subscriptionId, channel = name),
174
+ authorizer = authorizer
175
+ )
176
+ if (! queued) {
177
+ throw webSocket.disconnectReason?.toCloseException() ? : ConnectionClosedException ()
178
+ }
140
179
141
180
// Wait for subscription result to return
142
181
when (val response = deferredSubscriptionResponse.await()) {
143
182
is WebSocketMessage .Received .Subscription .SubscribeSuccess -> {
144
183
return @coroutineScope true
145
184
}
146
- is WebSocketMessage .Received . Subscription . SubscribeError -> {
185
+ is WebSocketMessage .ErrorContainer -> {
147
186
val exceptionMessage = " Subscribe failed for channel: $name "
148
187
throw response.errors.firstOrNull()
149
188
?.toEventsException(exceptionMessage)
150
189
? : EventsException (exceptionMessage)
151
190
}
152
- is WebSocketMessage .Received . ConnectionClosed -> {
153
- throw ConnectionClosedException ()
191
+ is WebSocketMessage .Closed -> {
192
+ throw response.reason.toCloseException ()
154
193
}
155
194
else -> throw EventsException (" Received unexpected subscription response of type: ${response::class } " )
156
195
}
@@ -160,34 +199,22 @@ class EventsChannel internal constructor(
160
199
return webSocket.events.first {
161
200
when {
162
201
it is WebSocketMessage .Received .Subscription && it.id == subscriptionId -> true
163
- it is WebSocketMessage .Received .ConnectionClosed -> true
202
+ it is WebSocketMessage .ErrorContainer && it.id == subscriptionId -> true
203
+ it is WebSocketMessage .Closed -> true
164
204
else -> false
165
205
}
166
206
}
167
207
}
168
208
169
- private suspend fun publishSubscription (
170
- webSocket : EventsWebSocket ,
171
- subscriptionId : String ,
172
- authorizer : AppSyncAuthorizer
173
- ) {
174
- val subscribeMessage = WebSocketMessage .Send .Subscription .Subscribe (
175
- id = subscriptionId,
176
- channel = name,
177
- authorization = authorizer.getAuthorizationHeaders(
178
- object : AppSyncRequest {
179
- override val url: String
180
- get() = endpoints.restEndpoint.toString()
181
- override val body: String?
182
- get() = null
183
- override val headers: Map <String , String >
184
- get() = emptyMap()
185
- override val method: AppSyncRequest .HttpMethod
186
- get() = AppSyncRequest .HttpMethod .GET
187
- }
188
- )
189
- )
190
- webSocket.send(subscribeMessage)
209
+ private suspend fun getPublishResponse (webSocket : EventsWebSocket , publishId : String ): WebSocketMessage {
210
+ return webSocket.events.first {
211
+ when {
212
+ it is WebSocketMessage .Received .PublishSuccess && it.id == publishId -> true
213
+ it is WebSocketMessage .ErrorContainer && it.id == publishId -> true
214
+ it is WebSocketMessage .Closed -> true
215
+ else -> false
216
+ }
217
+ }
191
218
}
192
219
193
220
private fun completeSubscription (subscriptionHolder : SubscriptionHolder , throwable : Throwable ? ) {
@@ -198,7 +225,11 @@ class EventsChannel internal constructor(
198
225
199
226
if (currentWebSocket != null && isSubscribed && ! isDisconnected) {
200
227
// Unsubscribe from channel when flow is completed
201
- currentWebSocket.send(WebSocketMessage .Send .Subscription .Unsubscribe (subscriptionHolder.id))
228
+ try {
229
+ currentWebSocket.send(WebSocketMessage .Send .Subscription .Unsubscribe (subscriptionHolder.id))
230
+ } catch (e: Exception ) {
231
+ // do nothing with a failed unsubscribe post
232
+ }
202
233
}
203
234
}
204
235
}
0 commit comments