Skip to content
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

feat: some public API improvements #74

Merged
merged 3 commits into from
Dec 2, 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
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ To stop listening to discontinuities, `unsubscribe` method on returned subscript
To send a message, simply call `send` on the `room.messages` property, with the message you want to send.

```kotlin
val message = room.messages.send(SendMessageParams(text = "text"))
val message = room.messages.send(text = "text")
```

### Unsubscribing from incoming messages
Expand All @@ -312,7 +312,7 @@ The messages object also exposes the `get` method which can be used to request h
to the given criteria. It returns a paginated response that can be used to request more messages.

```kotlin
var historicalMessages = room.messages.get(QueryParams(orderBy = NewestFirst, limit = 50))
var historicalMessages = room.messages.get(orderBy = NewestFirst, limit = 50)
println(historicalMessages.items.toString())

while (historicalMessages.hasNext()) {
Expand Down Expand Up @@ -551,17 +551,17 @@ To send room-level reactions, you must be [attached](#attaching-to-a-room) to th
To send a reaction such as `"like"`:

```kotlin
room.reactions.send(SendReactionParams(type = "like"))
room.reactions.send(type = "like")
```

You can also add any metadata and headers to reactions:

```kotlin
room.reactions.send(SendReactionParams(
room.reactions.send(
type ="like",
metadata = mapOf("effect" to "fireworks"),
metadata = JsonObject().apply { addProperty("effect", "fireworks") },
headers = mapOf("streamId" to "basketball-stream"),
))
)
```

### Subscribing to room reactions
Expand Down
12 changes: 6 additions & 6 deletions chat-android/src/main/java/com/ably/chat/ChatApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ internal class ChatApi(
roomId = it.requireString("roomId"),
text = it.requireString("text"),
createdAt = it.requireLong("createdAt"),
metadata = it.asJsonObject.get("metadata")?.toMap() ?: mapOf(),
metadata = it.asJsonObject.get("metadata"),
headers = it.asJsonObject.get("headers")?.toMap() ?: mapOf(),
latestAction = action,
)
Expand All @@ -69,7 +69,7 @@ internal class ChatApi(
}
// (CHA-M3b)
params.metadata?.let {
add("metadata", it.toJson())
add("metadata", it)
}
}

Expand All @@ -85,7 +85,7 @@ internal class ChatApi(
roomId = roomId,
text = params.text,
createdAt = it.requireLong("createdAt"),
metadata = params.metadata ?: mapOf(),
metadata = params.metadata,
headers = params.headers ?: mapOf(),
latestAction = MessageAction.MESSAGE_CREATE,
)
Expand All @@ -94,7 +94,7 @@ internal class ChatApi(

private fun validateSendMessageParams(params: SendMessageParams) {
// (CHA-M3c)
if (params.metadata?.containsKey(RESERVED_ABLY_CHAT_KEY) == true) {
if ((params.metadata as? JsonObject)?.has(RESERVED_ABLY_CHAT_KEY) == true) {
throw AblyException.fromErrorInfo(
ErrorInfo(
"Metadata contains reserved 'ably-chat' key",
Expand Down Expand Up @@ -204,8 +204,8 @@ private fun QueryOptions.toParams() = buildList {
Param(
"direction",
when (orderBy) {
QueryOptions.MessageOrder.NewestFirst -> "backwards"
QueryOptions.MessageOrder.OldestFirst -> "forwards"
QueryOptions.ResultOrder.NewestFirst -> "backwards"
QueryOptions.ResultOrder.OldestFirst -> "forwards"
},
),
)
Expand Down
3 changes: 1 addition & 2 deletions chat-android/src/main/java/com/ably/chat/Discontinuities.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import io.ably.lib.realtime.ChannelBase as AblyRealtimeChannel
*/
interface HandlesDiscontinuity {
/**
* A promise of the channel that this object is associated with. The promise
* is resolved when the feature has finished initializing.
* The channel that this object is associated with.
*/
val channel: AblyRealtimeChannel

Expand Down
6 changes: 3 additions & 3 deletions chat-android/src/main/java/com/ably/chat/Message.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ package com.ably.chat
import io.ably.lib.types.MessageAction

/**
* {@link Headers} type for chat messages.
* [Headers type for chat messages.
*/
typealias MessageHeaders = Headers

/**
* {@link Metadata} type for chat messages.
* [Metadata] type for chat messages.
*/
typealias MessageMetadata = Metadata

Expand Down Expand Up @@ -53,7 +53,7 @@ data class Message(
* Do not use metadata for authoritative information. There is no server-side
* validation. When reading the metadata treat it like user input.
*/
val metadata: MessageMetadata,
val metadata: MessageMetadata?,
ttypic marked this conversation as resolved.
Show resolved Hide resolved

/**
* The headers of a chat message. Headers enable attaching extra info to a message,
Expand Down
51 changes: 33 additions & 18 deletions chat-android/src/main/java/com/ably/chat/Messages.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

package com.ably.chat

import com.ably.chat.QueryOptions.MessageOrder.NewestFirst
import com.ably.chat.QueryOptions.ResultOrder.NewestFirst
import com.google.gson.JsonObject
import io.ably.lib.realtime.AblyRealtime
import io.ably.lib.realtime.ChannelState
Expand All @@ -19,7 +19,7 @@ typealias PubSubMessage = io.ably.lib.types.Message
* This interface is used to interact with messages in a chat room: subscribing
* to new messages, fetching history, or sending messages.
*
* Get an instance via {@link Room.messages}.
* Get an instance via [Room.messages].
*/
interface Messages : EmitsDiscontinuities {
/**
Expand All @@ -32,18 +32,26 @@ interface Messages : EmitsDiscontinuities {
/**
* Subscribe to new messages in this chat room.
* @param listener callback that will be called
* @returns A response object that allows you to control the subscription.
* @return A response object that allows you to control the subscription.
*/
fun subscribe(listener: Listener): MessagesSubscription

/**
* Get messages that have been previously sent to the chat room, based on the provided options.
*
* @param options Options for the query.
* @returns A promise that resolves with the paginated result of messages. This paginated result can
* be used to fetch more messages if available.
* @param start The start of the time window to query from. See [QueryOptions.start]
* @param end The end of the time window to query from. See [QueryOptions.end]
* @param limit The maximum number of messages to return in the response. See [QueryOptions.limit]
* @param orderBy The order of messages in the query result. See [QueryOptions.orderBy]
*
* @return Paginated result of messages. This paginated result can be used to fetch more messages if available.
*/
suspend fun get(options: QueryOptions): PaginatedResult<Message>
suspend fun get(
start: Long? = null,
ttypic marked this conversation as resolved.
Show resolved Hide resolved
end: Long? = null,
limit: Int = 100,
orderBy: QueryOptions.ResultOrder = NewestFirst,
): PaginatedResult<Message>
ttypic marked this conversation as resolved.
Show resolved Hide resolved

/**
* Send a message in the chat room.
Expand All @@ -54,13 +62,13 @@ interface Messages : EmitsDiscontinuities {
* from the realtime channel. This means you may see the message that was just
* sent in a callback to `subscribe` before the function resolves.
*
* TODO: Revisit this resolution policy during implementation (it will be much better for DX if this behavior is deterministic).
* @param text The text of the message. See [SendMessageParams.text]
* @param metadata Optional metadata of the message. See [SendMessageParams.metadata]
* @param headers Optional headers of the message. See [SendMessageParams.headers]
*
* @param params an object containing {text, headers, metadata} for the message
* to be sent. Text is required, metadata and headers are optional.
* @returns The message was published.
* @return The message was published.
*/
suspend fun send(params: SendMessageParams): Message
suspend fun send(text: String, metadata: MessageMetadata? = null, headers: MessageHeaders? = null): Message
ttypic marked this conversation as resolved.
Show resolved Hide resolved

/**
* An interface for listening to new messaging event
Expand Down Expand Up @@ -102,12 +110,12 @@ data class QueryOptions(
/**
* The order of messages in the query result.
*/
val orderBy: MessageOrder = NewestFirst,
val orderBy: ResultOrder = NewestFirst,
) {
/**
* Represents direction to query messages in.
*/
enum class MessageOrder {
enum class ResultOrder {
/**
* The response will include messages from the start of the time window to the end.
*/
Expand Down Expand Up @@ -292,9 +300,16 @@ internal class DefaultMessages(
)
}

override suspend fun get(options: QueryOptions): PaginatedResult<Message> = chatApi.getMessages(roomId, options)
override suspend fun get(start: Long?, end: Long?, limit: Int, orderBy: QueryOptions.ResultOrder): PaginatedResult<Message> =
chatApi.getMessages(
roomId,
QueryOptions(start, end, limit, orderBy),
)

override suspend fun send(params: SendMessageParams): Message = chatApi.sendMessage(roomId, params)
override suspend fun send(text: String, metadata: MessageMetadata?, headers: MessageHeaders?): Message = chatApi.sendMessage(
roomId,
SendMessageParams(text, metadata, headers),
)

/**
* Associate deferred channel serial value with the current channel's serial
Expand Down Expand Up @@ -370,7 +385,7 @@ internal class DefaultMessages(
/**
* Parsed data from the Pub/Sub channel's message data field
*/
private data class PubSubMessageData(val text: String, val metadata: MessageMetadata)
private data class PubSubMessageData(val text: String, val metadata: MessageMetadata?)

private fun parsePubSubMessageData(data: Any): PubSubMessageData {
if (data !is JsonObject) {
Expand All @@ -380,6 +395,6 @@ private fun parsePubSubMessageData(data: Any): PubSubMessageData {
}
return PubSubMessageData(
text = data.requireString("text"),
metadata = data.get("metadata")?.toMap() ?: mapOf(),
metadata = data.get("metadata"),
)
}
4 changes: 3 additions & 1 deletion chat-android/src/main/java/com/ably/chat/Metadata.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.ably.chat

import com.google.gson.JsonElement

/**
* Metadata is a map of extra information that can be attached to chat
* messages. It is not used by Ably and is sent as part of the realtime
Expand All @@ -13,4 +15,4 @@ package com.ably.chat
* The key `ably-chat` is reserved and cannot be used. Ably may populate
* this with different values in the future.
*/
typealias Metadata = Map<String, String>
typealias Metadata = JsonElement
2 changes: 1 addition & 1 deletion chat-android/src/main/java/com/ably/chat/Occupancy.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import kotlinx.coroutines.launch
* This interface is used to interact with occupancy in a chat room: subscribing to occupancy updates and
* fetching the current room occupancy metrics.
*
* Get an instance via {@link Room.occupancy}.
* Get an instance via [Room.occupancy].
*/
interface Occupancy : EmitsDiscontinuities {
/**
Expand Down
22 changes: 12 additions & 10 deletions chat-android/src/main/java/com/ably/chat/Presence.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ typealias PresenceData = JsonElement
* This interface is used to interact with presence in a chat room: subscribing to presence events,
* fetching presence members, or sending presence events (join,update,leave).
*
* Get an instance via {@link Room.presence}.
* Get an instance via [Room.presence].
*/
interface Presence : EmitsDiscontinuities {
/**
Expand All @@ -25,9 +25,11 @@ interface Presence : EmitsDiscontinuities {

/**
* Method to get list of the current online users and returns the latest presence messages associated to it.
* @param {Ably.RealtimePresenceParams} params - Parameters that control how the presence set is retrieved.
* @throws {@link io.ably.lib.types.AblyException} object which explains the error.
* @returns {List<PresenceMessage>}
* @param waitForSync when false, the current list of members is returned without waiting for a complete synchronization.
* @param clientId when provided, will filter array of members returned that match the provided `clientId` string.
* @param connectionId when provided, will filter array of members returned that match the provided `connectionId`.
* @throws [io.ably.lib.types.AblyException] object which explains the error.
* @return list of the current online users
*/
suspend fun get(waitForSync: Boolean = true, clientId: String? = null, connectionId: String? = null): List<PresenceMember>

Expand All @@ -40,22 +42,22 @@ interface Presence : EmitsDiscontinuities {

/**
* Method to join room presence, will emit an enter event to all subscribers. Repeat calls will trigger more enter events.
* @param {PresenceData} data - The users data, a JSON serializable object that will be sent to all subscribers.
* @throws {@link io.ably.lib.types.AblyException} object which explains the error.
* @param data The users data, a JSON serializable object that will be sent to all subscribers.
* @throws [io.ably.lib.types.AblyException] object which explains the error.
*/
suspend fun enter(data: PresenceData? = null)

/**
* Method to update room presence, will emit an update event to all subscribers. If the user is not present, it will be treated as a join event.
* @param {PresenceData} data - The users data, a JSON serializable object that will be sent to all subscribers.
* @throws {@link io.ably.lib.types.AblyException} object which explains the error.
* @param data The users data, a JSON serializable object that will be sent to all subscribers.
* @throws [io.ably.lib.types.AblyException] object which explains the error.
*/
suspend fun update(data: PresenceData? = null)

/**
* Method to leave room presence, will emit a leave event to all subscribers. If the user is not present, it will be treated as a no-op.
* @param {PresenceData} data - The users data, a JSON serializable object that will be sent to all subscribers.
* @throws {@link io.ably.lib.types.AblyException} object which explains the error.
* @param data The users data, a JSON serializable object that will be sent to all subscribers.
* @throws [io.ably.lib.types.AblyException] object which explains the error.
*/
suspend fun leave(data: PresenceData? = null)

Expand Down
6 changes: 3 additions & 3 deletions chat-android/src/main/java/com/ably/chat/Reaction.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.ably.chat

/**
* {@link Headers} type for chat messages.
* [Headers] type for chat messages.
*/
typealias ReactionHeaders = Headers

/**
* {@link Metadata} type for chat messages.
* [Metadata] type for chat messages.
*/
typealias ReactionMetadata = Metadata

Expand All @@ -22,7 +22,7 @@ data class Reaction(
/**
* Metadata of the reaction. If no metadata was set this is an empty object.
*/
val metadata: ReactionMetadata = mapOf(),
val metadata: ReactionMetadata?,
ttypic marked this conversation as resolved.
Show resolved Hide resolved

/**
* Headers of the reaction. If no headers were set this is an empty object.
Expand Down
12 changes: 6 additions & 6 deletions chat-android/src/main/java/com/ably/chat/Room.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,31 +30,31 @@ interface Room {
/**
* Allows you to subscribe to presence events in the room.
*
* @throws {@link ErrorInfo}} if presence is not enabled for the room.
* @throws [ErrorInfo] if presence is not enabled for the room.
* @returns The presence instance for the room.
*/
val presence: Presence

/**
* Allows you to interact with room-level reactions.
*
* @throws {@link ErrorInfo} if reactions are not enabled for the room.
* @throws [ErrorInfo] if reactions are not enabled for the room.
* @returns The room reactions instance for the room.
*/
val reactions: RoomReactions

/**
* Allows you to interact with typing events in the room.
*
* @throws {@link ErrorInfo} if typing is not enabled for the room.
* @throws [ErrorInfo] if typing is not enabled for the room.
* @returns The typing instance for the room.
*/
val typing: Typing

/**
* Allows you to interact with occupancy metrics for the room.
*
* @throws {@link ErrorInfo} if occupancy is not enabled for the room.
* @throws [ErrorInfo] if occupancy is not enabled for the room.
* @returns The occupancy instance for the room.
*/
val occupancy: Occupancy
Expand Down Expand Up @@ -94,11 +94,11 @@ interface Room {
/**
* Attaches to the room to receive events in realtime.
*
* If a room fails to attach, it will enter either the {@link RoomLifecycle.Suspended} or {@link RoomLifecycle.Failed} state.
* If a room fails to attach, it will enter either the [RoomLifecycle.Suspended] or [RoomLifecycle.Failed] state.
*
* If the room enters the failed state, then it will not automatically retry attaching and intervention is required.
*
* If the room enters the suspended state, then the call to attach will reject with the {@link ErrorInfo} that caused the suspension. However,
* If the room enters the suspended state, then the call to attach will reject with the [ErrorInfo] that caused the suspension. However,
* the room will automatically retry attaching after a delay.
*/
suspend fun attach()
Expand Down
Loading