Skip to content

[ECO-4943] feat: basic sending and receiving messages in the Chat #16

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

Merged
merged 3 commits into from
Sep 18, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ publish.properties
/.idea/compiler.xml
/.idea/jarRepositories.xml
/.idea/misc.xml
/.idea/shelf

# general
**/.DS_Store
87 changes: 5 additions & 82 deletions chat-android/src/main/java/com/ably/chat/ChatApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ package com.ably.chat

import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import io.ably.lib.http.HttpCore
import io.ably.lib.http.HttpUtils
import io.ably.lib.types.AblyException
import io.ably.lib.types.AsyncHttpPaginatedResponse
import io.ably.lib.types.ErrorInfo
Expand All @@ -17,19 +14,20 @@ private const val API_PROTOCOL_VERSION = 3
private const val PROTOCOL_VERSION_PARAM_NAME = "v"
private val apiProtocolParam = Param(PROTOCOL_VERSION_PARAM_NAME, API_PROTOCOL_VERSION.toString())

// TODO make this class internal
class ChatApi(private val realtimeClient: RealtimeClient, private val clientId: String) {
internal class ChatApi(private val realtimeClient: RealtimeClient, private val clientId: String) {

/**
* Get messages from the Chat Backend
*
* @return paginated result with messages
*/
suspend fun getMessages(roomId: String, params: QueryOptions): PaginatedResult<Message> {
suspend fun getMessages(roomId: String, options: QueryOptions, fromSerial: String? = null): PaginatedResult<Message> {
val baseParams = options.toParams()
val params = fromSerial?.let { baseParams + Param("fromSerial", it) } ?: baseParams
return makeAuthorizedPaginatedRequest(
url = "/chat/v1/rooms/$roomId/messages",
method = "GET",
params = params.toParams(),
params = params,
) {
Message(
timeserial = it.requireString("timeserial"),
Expand Down Expand Up @@ -137,17 +135,6 @@ class ChatApi(private val realtimeClient: RealtimeClient, private val clientId:
}
}

private fun JsonElement?.toRequestBody(useBinaryProtocol: Boolean = false): HttpCore.RequestBody =
HttpUtils.requestBodyFromGson(this, useBinaryProtocol)

private fun Map<String, String>.toJson() = JsonObject().apply {
forEach { (key, value) -> addProperty(key, value) }
}

private fun JsonElement.toMap() = buildMap<String, String> {
requireJsonObject().entrySet().filter { (_, value) -> value.isJsonPrimitive }.forEach { (key, value) -> put(key, value.asString) }
}

private fun QueryOptions.toParams() = buildList {
start?.let { add(Param("start", it)) }
end?.let { add(Param("end", it)) }
Expand All @@ -162,67 +149,3 @@ private fun QueryOptions.toParams() = buildList {
),
)
}

private fun JsonElement.requireJsonObject(): JsonObject {
if (!isJsonObject) {
throw AblyException.fromErrorInfo(
ErrorInfo("Response value expected to be JsonObject, got primitive instead", HttpStatusCodes.InternalServerError),
)
}
return asJsonObject
}

private fun JsonElement.requireString(memberName: String): String {
val memberElement = requireField(memberName)
if (!memberElement.isJsonPrimitive) {
throw AblyException.fromErrorInfo(
ErrorInfo(
"Value for \"$memberName\" field expected to be JsonPrimitive, got object instead",
HttpStatusCodes.InternalServerError,
),
)
}
return memberElement.asString
}

private fun JsonElement.requireLong(memberName: String): Long {
val memberElement = requireJsonPrimitive(memberName)
try {
return memberElement.asLong
} catch (formatException: NumberFormatException) {
throw AblyException.fromErrorInfo(
formatException,
ErrorInfo("Required numeric field \"$memberName\" is not a valid long", HttpStatusCodes.InternalServerError),
)
}
}

private fun JsonElement.requireInt(memberName: String): Int {
val memberElement = requireJsonPrimitive(memberName)
try {
return memberElement.asInt
} catch (formatException: NumberFormatException) {
throw AblyException.fromErrorInfo(
formatException,
ErrorInfo("Required numeric field \"$memberName\" is not a valid int", HttpStatusCodes.InternalServerError),
)
}
}

private fun JsonElement.requireJsonPrimitive(memberName: String): JsonPrimitive {
val memberElement = requireField(memberName)
if (!memberElement.isJsonPrimitive) {
throw AblyException.fromErrorInfo(
ErrorInfo(
"Value for \"$memberName\" field expected to be JsonPrimitive, got object instead",
HttpStatusCodes.InternalServerError,
),
)
}
return memberElement.asJsonPrimitive
}

private fun JsonElement.requireField(memberName: String): JsonElement = requireJsonObject().get(memberName)
?: throw AblyException.fromErrorInfo(
ErrorInfo("Required field \"$memberName\" is missing", HttpStatusCodes.InternalServerError),
)
3 changes: 2 additions & 1 deletion chat-android/src/main/java/com/ably/chat/ChatClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ interface ChatClient {
val clientOptions: ClientOptions
}

fun ChatClient(realtimeClient: RealtimeClient, clientOptions: ClientOptions): ChatClient = DefaultChatClient(realtimeClient, clientOptions)
fun ChatClient(realtimeClient: RealtimeClient, clientOptions: ClientOptions = ClientOptions()): ChatClient =
DefaultChatClient(realtimeClient, clientOptions)

internal class DefaultChatClient(
override val realtime: RealtimeClient,
Expand Down
8 changes: 1 addition & 7 deletions chat-android/src/main/java/com/ably/chat/ConnectionStatus.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,7 @@ interface ConnectionStatus {
* Registers a listener that will be called whenever the connection status changes.
* @param listener The function to call when the status changes.
*/
fun on(listener: Listener)

/**
* Unregisters a listener
* @param listener The function to call when the status changes.
*/
fun off(listener: Listener)
fun on(listener: Listener): Subscription

/**
* An interface for listening to changes for the connection status
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,7 @@ interface EmitsDiscontinuities {
* Register a listener to be called when a discontinuity is detected.
* @param listener The listener to be called when a discontinuity is detected.
*/
fun onDiscontinuity(listener: Listener)

/**
* Unregister a listener to be called when a discontinuity is detected.
* @param listener The listener
*/
fun offDiscontinuity(listener: Listener)
fun onDiscontinuity(listener: Listener): Subscription

/**
* An interface for listening when discontinuity happens
Expand Down
84 changes: 84 additions & 0 deletions chat-android/src/main/java/com/ably/chat/JsonUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.ably.chat

import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import io.ably.lib.http.HttpCore
import io.ably.lib.http.HttpUtils
import io.ably.lib.types.AblyException
import io.ably.lib.types.ErrorInfo

internal fun JsonElement?.toRequestBody(useBinaryProtocol: Boolean = false): HttpCore.RequestBody =
HttpUtils.requestBodyFromGson(this, useBinaryProtocol)

internal fun Map<String, String>.toJson() = JsonObject().apply {
forEach { (key, value) -> addProperty(key, value) }
}

internal fun JsonElement.toMap() = buildMap<String, String> {
requireJsonObject().entrySet().filter { (_, value) -> value.isJsonPrimitive }.forEach { (key, value) -> put(key, value.asString) }
}

internal fun JsonElement.requireJsonObject(): JsonObject {
if (!isJsonObject) {
throw AblyException.fromErrorInfo(
ErrorInfo("Response value expected to be JsonObject, got primitive instead", HttpStatusCodes.InternalServerError),
)
}
return asJsonObject
}

internal fun JsonElement.requireString(memberName: String): String {
val memberElement = requireField(memberName)
if (!memberElement.isJsonPrimitive) {
throw AblyException.fromErrorInfo(
ErrorInfo(
"Value for \"$memberName\" field expected to be JsonPrimitive, got object instead",
HttpStatusCodes.InternalServerError,
),
)
}
return memberElement.asString
}

internal fun JsonElement.requireLong(memberName: String): Long {
val memberElement = requireJsonPrimitive(memberName)
try {
return memberElement.asLong
} catch (formatException: NumberFormatException) {
throw AblyException.fromErrorInfo(
formatException,
ErrorInfo("Required numeric field \"$memberName\" is not a valid long", HttpStatusCodes.InternalServerError),
)
}
}

internal fun JsonElement.requireInt(memberName: String): Int {
val memberElement = requireJsonPrimitive(memberName)
try {
return memberElement.asInt
} catch (formatException: NumberFormatException) {
throw AblyException.fromErrorInfo(
formatException,
ErrorInfo("Required numeric field \"$memberName\" is not a valid int", HttpStatusCodes.InternalServerError),
)
}
}

internal fun JsonElement.requireJsonPrimitive(memberName: String): JsonPrimitive {
val memberElement = requireField(memberName)
if (!memberElement.isJsonPrimitive) {
throw AblyException.fromErrorInfo(
ErrorInfo(
"Value for \"$memberName\" field expected to be JsonPrimitive, got object instead",
HttpStatusCodes.InternalServerError,
),
)
}
return memberElement.asJsonPrimitive
}

internal fun JsonElement.requireField(memberName: String): JsonElement = requireJsonObject().get(memberName)
?: throw AblyException.fromErrorInfo(
ErrorInfo("Required field \"$memberName\" is missing", HttpStatusCodes.InternalServerError),
)
Loading