Skip to content

Commit 9ee12ba

Browse files
authored
feat(data): Events WebSocket Connection Logic + Subscribe (#3018)
1 parent 880b91d commit 9ee12ba

File tree

8 files changed

+806
-18
lines changed

8 files changed

+806
-18
lines changed
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/*
2+
* Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package com.amplifyframework.aws.appsync.core.util
17+
18+
import java.util.function.Supplier
19+
20+
/**
21+
* A component which can emit logs.
22+
*/
23+
interface Logger {
24+
/**
25+
* Gets the minimum log-level, below which no logs are emitted.
26+
* This value is assigned when obtaining and instance of the logger.
27+
* @return The minimum permissible LogLevel for which logs will be emitted
28+
*/
29+
val thresholdLevel: LogLevel
30+
31+
/**
32+
* Gets the namespace of the logger.
33+
* @return namespace for logger
34+
*/
35+
val namespace: String
36+
37+
/**
38+
* Logs a message at the [LogLevel.ERROR] level.
39+
* @param message An error message
40+
*/
41+
fun error(message: String)
42+
43+
/**
44+
* Logs a message at the [LogLevel.ERROR] level. The supplier is only invoked if the log level threshold
45+
* is at ERROR or below.
46+
* @param messageSupplier A function that returns an error message
47+
*/
48+
fun error(messageSupplier: Supplier<String>) {
49+
if (!thresholdLevel.above(LogLevel.ERROR)) {
50+
error(messageSupplier.get())
51+
}
52+
}
53+
54+
/**
55+
* Logs a message and thrown error at [LogLevel.ERROR] level.
56+
* @param message An error message
57+
* @param error A thrown error
58+
*/
59+
fun error(message: String, error: Throwable?)
60+
61+
/**
62+
* Logs a message and thrown error at [LogLevel.ERROR] level. The supplier is only invoked if the log level
63+
* threshold is at ERROR or below.
64+
* @param error A thrown error
65+
* @param messageSupplier A function that returns an error message
66+
*/
67+
fun error(error: Throwable?, messageSupplier: Supplier<String>) {
68+
if (!thresholdLevel.above(LogLevel.ERROR)) {
69+
error(messageSupplier.get(), error)
70+
}
71+
}
72+
73+
/**
74+
* Log a message at the [LogLevel.WARN] level.
75+
* @param message A warning message
76+
*/
77+
fun warn(message: String)
78+
79+
/**
80+
* Log a message at the [LogLevel.WARN] level. The supplier is only invoked if the log level threshold
81+
* is at WARN or below.
82+
* @param messageSupplier A function that returns a warning message
83+
*/
84+
fun warn(messageSupplier: Supplier<String>) {
85+
if (!thresholdLevel.above(LogLevel.WARN)) {
86+
warn(messageSupplier.get())
87+
}
88+
}
89+
90+
/**
91+
* Log a message and a throwable issue at the [LogLevel.WARN] level.
92+
* @param message A warning message
93+
* @param issue An issue that caused this warning
94+
*/
95+
fun warn(message: String, issue: Throwable?)
96+
97+
/**
98+
* Log a message and a throwable issue at the [LogLevel.WARN] level. The supplier is only invoked if the
99+
* log level threshold is at WARN or below.
100+
* @param issue An issue that caused this warning
101+
* @param messageSupplier A function that returns a warning message
102+
*/
103+
fun warn(issue: Throwable?, messageSupplier: Supplier<String>) {
104+
if (!thresholdLevel.above(LogLevel.WARN)) {
105+
warn(messageSupplier.get(), issue)
106+
}
107+
}
108+
109+
/**
110+
* Logs a message at [LogLevel.INFO] level.
111+
* @param message An informational message
112+
*/
113+
fun info(message: String)
114+
115+
/**
116+
* Logs a message at [LogLevel.INFO] level. The supplier is only invoked if the log level threshold
117+
* is at INFO or below.
118+
* @param messageSupplier A function that returns an info message
119+
*/
120+
fun info(messageSupplier: Supplier<String>) {
121+
if (!thresholdLevel.above(LogLevel.INFO)) {
122+
info(messageSupplier.get())
123+
}
124+
}
125+
126+
/**
127+
* Logs a message at the [LogLevel.DEBUG] level.
128+
* @param message A debugging message.
129+
*/
130+
fun debug(message: String)
131+
132+
/**
133+
* Logs a message at the [LogLevel.DEBUG] level. The supplier is only invoked if the log level threshold
134+
* is at DEBUG or below.
135+
* @param messageSupplier A function that returns a debugging message
136+
*/
137+
fun debug(messageSupplier: Supplier<String>) {
138+
if (!thresholdLevel.above(LogLevel.DEBUG)) {
139+
debug(messageSupplier.get())
140+
}
141+
}
142+
143+
/**
144+
* Logs a message at the [LogLevel.VERBOSE] level.
145+
* @param message A verbose message
146+
*/
147+
fun verbose(message: String)
148+
149+
/**
150+
* Logs a message at the [LogLevel.VERBOSE] level. The supplier is only invoked if the log level threshold
151+
* is at VERBOSE.
152+
* @param messageSupplier A function that returns a verbose message
153+
*/
154+
fun verbose(messageSupplier: Supplier<String>) {
155+
if (!thresholdLevel.above(LogLevel.VERBOSE)) {
156+
verbose(messageSupplier.get())
157+
}
158+
}
159+
}
160+
161+
/**
162+
* An enumeration of the different levels of logging.
163+
* The levels are progressive, with lower-value items being lower priority
164+
* than higher-value items. For example, INFO is lower priority than WARNING
165+
* or ERROR.
166+
*/
167+
enum class LogLevel {
168+
/**
169+
* Verbose logs are used to study the behavior of particular components/flows
170+
* within a system, by developers. Verbose logs are not suitable for emission
171+
* in production, as they may contain sensitive information, and/or be emitted
172+
* so frequently that performance is impacted.
173+
*/
174+
VERBOSE,
175+
176+
/**
177+
* Debug logs are useful during development to understand the behavior of the system.
178+
* These logs may contain information that is inappropriate for emission in a production
179+
* environment.
180+
*/
181+
DEBUG,
182+
183+
/**
184+
* Informational logs may be emitted in production code, and provide
185+
* terse information about the general operation and flow of a piece of software.
186+
*/
187+
INFO,
188+
189+
/**
190+
* Warning logs indicate potential issues while running a piece of software.
191+
* For example, a network connection might retry, before succeeding with success -
192+
* the system is functioning without error, but not optimally.
193+
*/
194+
WARN,
195+
196+
/**
197+
* Errors should be logged when the system is not operating as expected.
198+
* For example, there might be no internet connection available to the system.
199+
* The application probably shouldn't need to crash, but anything that needs the
200+
* Internet will error out. Errors may logically be recoverable or fatal, but are
201+
* not distinguished here, and are logged at this same error level.
202+
*/
203+
ERROR,
204+
205+
/**
206+
* A log level above all other log levels. This log level may be used as a threshold
207+
* value, to prevent any logs from being emitted.
208+
*/
209+
NONE;
210+
211+
/**
212+
* Checks if a log level is above the current level.
213+
* For example, NONE is above ERROR, but ERROR is not above ERROR,
214+
* and WARN is not above ERROR.
215+
* @param threshold A threshold level to consider for evaluation
216+
* @return true if the current level is above the threshold level
217+
* @throws NullPointerException if threshold is null
218+
*/
219+
fun above(threshold: LogLevel): Boolean {
220+
return this.ordinal > threshold.ordinal
221+
}
222+
}

appsync/aws-appsync-events/src/main/java/com/amplifyframework/aws/appsync/events/Events.kt

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
package com.amplifyframework.aws.appsync.events
1616

1717
import com.amplifyframework.aws.appsync.core.AppSyncAuthorizer
18+
import com.amplifyframework.aws.appsync.core.util.Logger
1819
import com.amplifyframework.aws.appsync.events.data.ChannelAuthorizers
1920
import com.amplifyframework.aws.appsync.events.data.EventsException
2021
import com.amplifyframework.aws.appsync.events.data.PublishResult
22+
import kotlinx.coroutines.coroutineScope
2123
import kotlinx.serialization.json.Json
2224
import kotlinx.serialization.json.JsonElement
2325
import okhttp3.OkHttpClient
@@ -34,9 +36,14 @@ class Events @VisibleForTesting internal constructor(
3436
val endpoint: String,
3537
val connectAuthorizer: AppSyncAuthorizer,
3638
val defaultChannelAuthorizers: ChannelAuthorizers,
39+
options: Options,
3740
okHttpClient: OkHttpClient
3841
) {
3942

43+
data class Options(
44+
val logger: Logger? = null
45+
)
46+
4047
/**
4148
* The main class for interacting with AWS AppSync Events
4249
*
@@ -47,16 +54,29 @@ class Events @VisibleForTesting internal constructor(
4754
constructor(
4855
endpoint: String,
4956
connectAuthorizer: AppSyncAuthorizer,
50-
defaultChannelAuthorizers: ChannelAuthorizers
51-
) : this(endpoint, connectAuthorizer, defaultChannelAuthorizers, OkHttpClient.Builder().build())
57+
defaultChannelAuthorizers: ChannelAuthorizers,
58+
options: Options = Options()
59+
) : this(
60+
endpoint,
61+
connectAuthorizer,
62+
defaultChannelAuthorizers,
63+
options,
64+
OkHttpClient.Builder().build()
65+
)
5266

5367
private val json = Json {
5468
encodeDefaults = true
5569
ignoreUnknownKeys = true
5670
}
5771
private val endpoints = EventsEndpoints(endpoint)
5872
private val httpClient = RestClient(endpoints.restEndpoint, okHttpClient, json)
59-
private val eventsWebSocket = EventsWebSocket(endpoints, connectAuthorizer, okHttpClient, json)
73+
private val eventsWebSocketProvider = EventsWebSocketProvider(
74+
endpoints,
75+
connectAuthorizer,
76+
okHttpClient,
77+
json,
78+
options.logger
79+
)
6080

6181
/**
6282
* Publish a single event to a channel.
@@ -102,7 +122,7 @@ class Events @VisibleForTesting internal constructor(
102122
fun channel(
103123
channelName: String,
104124
authorizers: ChannelAuthorizers = this.defaultChannelAuthorizers,
105-
) = EventsChannel(channelName, authorizers, endpoints, eventsWebSocket)
125+
) = EventsChannel(channelName, authorizers, endpoints, eventsWebSocketProvider)
106126

107127
/**
108128
* Method to disconnect from all channels.
@@ -112,7 +132,7 @@ class Events @VisibleForTesting internal constructor(
112132
* @param authorizers for the channel to use for subscriptions and publishes.
113133
* @return a channel to manage subscriptions and publishes.
114134
*/
115-
suspend fun disconnect(flushEvents: Boolean = true) {
116-
eventsWebSocket.disconnect(flushEvents)
135+
suspend fun disconnect(flushEvents: Boolean = true): Unit = coroutineScope {
136+
eventsWebSocketProvider.getExistingWebSocket()?.disconnect(flushEvents)
117137
}
118138
}

0 commit comments

Comments
 (0)