From 680bd209c5ce8a441ae50323a9185be03582e640 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 10 Feb 2025 23:11:40 +0000 Subject: [PATCH 1/3] feat: kotlin wrapper for `ably-java` and `ably-android` This is the initial version of the Kotlin wrapper for `ably-java` and `ably-android`. The main goal is to provide an extendable public interface for the Ably PubSub SDK (the core SDK for Chat and other products). This wrapper SDK is the first step toward modernizing our libraries. It introduces a set of interfaces that can be modified, for example, to inject agent information or other data when used within new product SDKs. Note: In this PR we are not going to provide fully updated and idiomatic public API for `ably-java` and `ably-android`, this is the initial step of public API modernization, we will continue working on this. --- build.gradle.kts | 1 + gradle/libs.versions.toml | 4 +- pubsub-adapter/build.gradle.kts | 10 + pubsub-adapter/gradle.properties | 4 + .../src/main/kotlin/com/ably/Subscription.kt | 12 ++ .../main/kotlin/com/ably/http/HttpMethod.kt | 12 ++ .../main/kotlin/com/ably/pubsub/Channel.kt | 65 +++++++ .../main/kotlin/com/ably/pubsub/Channels.kt | 48 +++++ .../src/main/kotlin/com/ably/pubsub/Client.kt | 174 ++++++++++++++++++ .../main/kotlin/com/ably/pubsub/Presence.kt | 55 ++++++ .../kotlin/com/ably/pubsub/RealtimeChannel.kt | 147 +++++++++++++++ .../kotlin/com/ably/pubsub/RealtimeClient.kt | 24 +++ .../com/ably/pubsub/RealtimePresence.kt | 155 ++++++++++++++++ .../kotlin/com/ably/pubsub/RestChannel.kt | 44 +++++ .../main/kotlin/com/ably/pubsub/RestClient.kt | 9 + .../kotlin/com/ably/pubsub/RestPresence.kt | 36 ++++ .../src/main/kotlin/com/ably/query/OrderBy.kt | 18 ++ .../main/kotlin/com/ably/query/TimeUnit.kt | 16 ++ settings.gradle.kts | 1 + 19 files changed, 834 insertions(+), 1 deletion(-) create mode 100644 pubsub-adapter/build.gradle.kts create mode 100644 pubsub-adapter/gradle.properties create mode 100644 pubsub-adapter/src/main/kotlin/com/ably/Subscription.kt create mode 100644 pubsub-adapter/src/main/kotlin/com/ably/http/HttpMethod.kt create mode 100644 pubsub-adapter/src/main/kotlin/com/ably/pubsub/Channel.kt create mode 100644 pubsub-adapter/src/main/kotlin/com/ably/pubsub/Channels.kt create mode 100644 pubsub-adapter/src/main/kotlin/com/ably/pubsub/Client.kt create mode 100644 pubsub-adapter/src/main/kotlin/com/ably/pubsub/Presence.kt create mode 100644 pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimeChannel.kt create mode 100644 pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimeClient.kt create mode 100644 pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimePresence.kt create mode 100644 pubsub-adapter/src/main/kotlin/com/ably/pubsub/RestChannel.kt create mode 100644 pubsub-adapter/src/main/kotlin/com/ably/pubsub/RestClient.kt create mode 100644 pubsub-adapter/src/main/kotlin/com/ably/pubsub/RestPresence.kt create mode 100644 pubsub-adapter/src/main/kotlin/com/ably/query/OrderBy.kt create mode 100644 pubsub-adapter/src/main/kotlin/com/ably/query/TimeUnit.kt diff --git a/build.gradle.kts b/build.gradle.kts index c20fc7ead..6deb0b770 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,7 @@ import com.vanniktech.maven.publish.SonatypeHost plugins { alias(libs.plugins.android.library) apply false + alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.maven.publish) apply false alias(libs.plugins.lombok) apply false alias(libs.plugins.test.retry) apply false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index baa16e88f..554cce7ae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,10 +15,11 @@ firebase-messaging = "22.0.0" android-test = "1.0.2" dexmaker = "1.4" android-retrostreams = "1.7.4" -maven-publish = "0.29.0" +maven-publish = "0.30.0" lombok = "8.10" okhttp = "4.12.0" test-retry = "1.6.0" +kotlin = "2.1.10" [libraries] gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } @@ -48,6 +49,7 @@ tests = ["junit","hamcrest-all", "nanohttpd", "nanohttpd-nanolets", "nanohttpd-w instrumental-android = ["android-test-runner", "android-test-rules", "dexmaker", "dexmaker-dx", "dexmaker-mockito", "android-retrostreams"] [plugins] +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } android-library = { id = "com.android.library", version.ref = "agp" } build-config = { id = "com.github.gmazzo.buildconfig", version.ref = "build-config" } maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "maven-publish" } diff --git a/pubsub-adapter/build.gradle.kts b/pubsub-adapter/build.gradle.kts new file mode 100644 index 000000000..a0959e9db --- /dev/null +++ b/pubsub-adapter/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + `java-library` + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.maven.publish) +} + +dependencies { + compileOnly(project(":java")) + testImplementation(project(":java")) +} diff --git a/pubsub-adapter/gradle.properties b/pubsub-adapter/gradle.properties new file mode 100644 index 000000000..48d9d1d46 --- /dev/null +++ b/pubsub-adapter/gradle.properties @@ -0,0 +1,4 @@ +POM_ARTIFACT_ID=pubsub-adapter +POM_NAME=Internal Ably PubSub adapter +POM_DESCRIPTION=Internal adapter for using Ably PubSub in Kotlin +POM_PACKAGING=jar diff --git a/pubsub-adapter/src/main/kotlin/com/ably/Subscription.kt b/pubsub-adapter/src/main/kotlin/com/ably/Subscription.kt new file mode 100644 index 000000000..489502e7f --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/com/ably/Subscription.kt @@ -0,0 +1,12 @@ +package com.ably + +/** + * An unsubscription handle, returned by various functions (mostly subscriptions) + * where unsubscription is required. + */ +fun interface Subscription { + /** + * Handle unsubscription (unsubscribe listeners, clean up) + */ + fun unsubscribe() +} diff --git a/pubsub-adapter/src/main/kotlin/com/ably/http/HttpMethod.kt b/pubsub-adapter/src/main/kotlin/com/ably/http/HttpMethod.kt new file mode 100644 index 000000000..482f62a83 --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/com/ably/http/HttpMethod.kt @@ -0,0 +1,12 @@ +package com.ably.http + +enum class HttpMethod(private val method: String) { + Get("GET"), + Post("POST"), + Put("PUT"), + Delete("DELETE"), + Patch("PATCH"), + ; + + override fun toString() = method +} diff --git a/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Channel.kt b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Channel.kt new file mode 100644 index 000000000..173b19903 --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Channel.kt @@ -0,0 +1,65 @@ +package com.ably.pubsub + +import com.ably.query.OrderBy +import io.ably.lib.types.* + +/** + * An interface representing a Channel in the Ably API. + */ +interface Channel { + + /** + * The channel name. + */ + val name: String + + /** + * A [Presence] object. + * + * + * Spec: RTL9 + */ + val presence: Presence + + /** + * Obtain recent history for this channel using the REST API. + * The history provided relates to all clients of this application, + * not just this instance. + * + * @param start The start of the query interval as a time in milliseconds since the epoch. + * A message qualifies as a member of the result set if it was received at or after this time. (default: beginning of time) + * @param end The end of the query interval as a time in milliseconds since the epoch. + * A message qualifies as a member of the result set if it was received at or before this time. (default: now) + * @param limit The maximum number of records to return. A limit greater than 1,000 is invalid. + * @param orderBy The direction of this query. + * + * @return Paginated result of Messages for this Channel. + */ + fun history( + start: Long? = null, + end: Long? = null, + limit: Int = 100, + orderBy: OrderBy = OrderBy.NewestFirst, + ): PaginatedResult + + /** + * Asynchronously obtain recent history for this channel using the REST API. + * + * @param start The start of the query interval as a time in milliseconds since the epoch. + * A message qualifies as a member of the result set if it was received at or after this time. (default: beginning of time) + * @param end The end of the query interval as a time in milliseconds since the epoch. + * A message qualifies as a member of the result set if it was received at or before this time. (default: now) + * @param limit The maximum number of records to return. A limit greater than 1,000 is invalid. + * @param orderBy The direction of this query. + * @param callback A Callback returning [AsyncPaginatedResult] object containing an array of [Message] objects. + * Note: This callback is invoked on a background thread. + */ + fun historyAsync( + callback: Callback>, + start: Long? = null, + end: Long? = null, + limit: Int = 100, + orderBy: OrderBy = OrderBy.NewestFirst, + ) + +} diff --git a/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Channels.kt b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Channels.kt new file mode 100644 index 000000000..67cc7c6f5 --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Channels.kt @@ -0,0 +1,48 @@ +package com.ably.pubsub + +import io.ably.lib.realtime.ChannelState +import io.ably.lib.types.ChannelOptions + +/** + * Represents collection of managed Channel instances + */ +interface Channels : Iterable { + + /** + * Checks if channel with specified name exists + *

+ * Spec: RSN2, RTS2 + * @param name The channel name. + * @return `true` if it contains the specified [name]. + */ + fun contains(name: String): Boolean + + /** + * Creates a new [Channel] object, or returns the existing channel object. + *

+ * Spec: RSN3a, RTS3a + * @param name The channel name. + * @return A [Channel] object. + */ + fun get(name: String): ChannelType + + /** + * Creates a new [Channel] object, with the specified [ChannelOptions], or returns the existing channel object. + *

+ * Spec: RSN3c, RTS3c + * @param name The channel name. + * @param options A [ChannelOptions] object. + * @return A [Channel] object. + */ + fun get(name: String, options: ChannelOptions): ChannelType + + /** + * Releases a [Channel] object, deleting it, and enabling it to be garbage collected. + * It also removes any listeners associated with the channel. + * To release a channel, the [ChannelState] must be `INITIALIZED`, `DETACHED`, or `FAILED`. + *

+ * Spec: RSN4, RTS4 + * @param name The channel name. + */ + fun release(name: String) +} diff --git a/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Client.kt b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Client.kt new file mode 100644 index 000000000..83539b3f3 --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Client.kt @@ -0,0 +1,174 @@ +package com.ably.pubsub + +import com.ably.query.OrderBy +import com.ably.query.TimeUnit +import com.ably.http.HttpMethod +import io.ably.lib.http.HttpCore +import io.ably.lib.push.Push +import io.ably.lib.rest.Auth +import io.ably.lib.types.* + +/** + * A client that offers a base interface to interact with Ably's API. + * + * This class implements {@link AutoCloseable} so you can use it in + * try-with-resources constructs and have the JDK close it for you. + */ +interface Client : AutoCloseable { + + /** + * An [Auth] object. + * + * Spec: RSC5 + */ + val auth: Auth + + /** + * A [Channels] object. + * + * Spec: RTC3, RTS1 + */ + val channels: Channels + + /** + * Client options + */ + val options: ClientOptions + + /** + * An [Push] object. + * + * Spec: RSH7 + */ + val push: Push + + /** + * Retrieves the time from the Ably service as milliseconds + * since the Unix epoch. Clients that do not have access + * to a sufficiently well maintained time source and wish + * to issue Ably [Auth.TokenRequest] with + * a more accurate timestamp should use the + * [ClientOptions.queryTime] property instead of this method. + *

+ * Spec: RSC16 + * @return The time as milliseconds since the Unix epoch. + */ + fun time(): Long + + /** + * Asynchronously retrieves the time from the Ably service as milliseconds + * since the Unix epoch. Clients that do not have access + * to a sufficiently well maintained time source and wish + * to issue Ably [Auth.TokenRequest] with + * a more accurate timestamp should use the + * [ClientOptions.queryTime] property instead of this method. + * + * Spec: RSC16 + * + * @param callback Listener with the time as milliseconds since the Unix epoch. + * This callback is invoked on a background thread + */ + fun timeAsync(callback: Callback) + + /** + * Queries the REST /stats API and retrieves your application's usage statistics. + * @param start (RSC6b1) - The time from which stats are retrieved, specified as milliseconds since the Unix epoch. + * @param end (RSC6b1) - The time until stats are retrieved, specified as milliseconds since the Unix epoch. + * @param orderBy (RSC6b2) - The order for which stats are returned in. + * @param limit (RSC6b3) - An upper limit on the number of stats returned. The default is 100, and the maximum is 1000. + * @param unit (RSC6b4) - minute, hour, day or month. Based on the unit selected, the given start or end times are rounded down to the start of the relevant interval depending on the unit granularity of the query. + * + * Spec: RSC6a + * + * @return A [PaginatedResult] object containing an array of [Stats] objects. + * @throws AblyException + */ + fun stats( + start: Long? = null, + end: Long? = null, + limit: Int = 100, + orderBy: OrderBy = OrderBy.NewestFirst, + unit: TimeUnit = TimeUnit.Minute, + ): PaginatedResult + + /** + * Asynchronously queries the REST /stats API and retrieves your application's usage statistics. + * + * @param start (RSC6b1) - The time from which stats are retrieved, specified as milliseconds since the Unix epoch. + * @param end (RSC6b1) - The time until stats are retrieved, specified as milliseconds since the Unix epoch. + * @param orderBy (RSC6b2) - The order for which stats are returned in. + * @param limit (RSC6b3) - An upper limit on the number of stats returned. The default is 100, and the maximum is 1000. + * @param unit (RSC6b4) - minute, hour, day or month. Based on the unit selected, the given start or end times are rounded down to the start of the relevant interval depending on the unit granularity of the query. + * + * Spec: RSC6a + * + * @param callback Listener which returns a [AsyncPaginatedResult] object containing an array of [Stats] objects. + * This callback is invoked on a background thread + */ + fun statsAsync( + callback: Callback>, + start: Long? = null, + end: Long? = null, + limit: Int = 100, + orderBy: OrderBy = OrderBy.NewestFirst, + unit: TimeUnit = TimeUnit.Minute, + ) + + /** + * Makes a REST request to a provided path. This is provided as a convenience + * for developers who wish to use REST API functionality that is either not + * documented or is not yet included in the public API, without having to + * directly handle features such as authentication, paging, fallback hosts, + * MsgPack and JSON support. + * + * Spec: RSC19 + * + * @param method The request method to use, such as GET, POST. + * @param path The request path. + * @param params The parameters to include in the URL query of the request. + * The parameters depend on the endpoint being queried. + * See the [REST API reference](https://ably.com/docs/api/rest-api) + * for the available parameters of each endpoint. + * @param body The RequestBody of the request. + * @param headers Additional HTTP headers to include in the request. + * @return An [HttpPaginatedResponse] object returned by the HTTP request, containing an empty or JSON-encodable object. + */ + fun request( + path: String, + method: HttpMethod = HttpMethod.Get, + params: List = emptyList(), + body: HttpCore.RequestBody? = null, + headers: List = emptyList(), + ): HttpPaginatedResponse + + /** + * Makes a async REST request to a provided path. This is provided as a convenience + * for developers who wish to use REST API functionality that is either not + * documented or is not yet included in the public API, without having to + * directly handle features such as authentication, paging, fallback hosts, + * MsgPack and JSON support. + * + * Spec: RSC19 + * + * @param method The request method to use, such as GET, POST. + * @param path The request path. + * @param params The parameters to include in the URL query of the request. + * The parameters depend on the endpoint being queried. + * See the [REST API reference](https://ably.com/docs/api/rest-api) + * for the available parameters of each endpoint. + * @param body The RequestBody of the request. + * @param headers Additional HTTP headers to include in the request. + * @param callback called with the asynchronous result, + * returns an [AsyncHttpPaginatedResponse] object returned by the HTTP request, + * containing an empty or JSON-encodable object. + * This callback is invoked on a background thread + */ + fun requestAsync( + path: String, + callback: AsyncHttpPaginatedResponse.Callback, + method: HttpMethod = HttpMethod.Get, + params: List = emptyList(), + body: HttpCore.RequestBody? = null, + headers: List = emptyList(), + ) +} diff --git a/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Presence.kt b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Presence.kt new file mode 100644 index 000000000..22f86561d --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Presence.kt @@ -0,0 +1,55 @@ +package com.ably.pubsub + +import com.ably.query.OrderBy +import io.ably.lib.types.* + +/** + * Enables get historic presence set for a channel. + */ +interface Presence { + + /** + * Retrieves a [PaginatedResult] object, containing an array of historical [PresenceMessage] objects for the channel. + * If the channel is configured to persist messages, + * then presence messages can be retrieved from history for up to 72 hours in the past. + * If not, presence messages can only be retrieved from history for up to two minutes in the past. + * + * Spec: RSP4a + * + * @param start (RSP4b1) - The time from which messages are retrieved, specified as milliseconds since the Unix epoch. + * @param end (RSP4b1) - The time until messages are retrieved, specified as milliseconds since the Unix epoch. + * @param orderBy (RSP4b2) - The order for which messages are returned in. + * @param limit (RSP4b3) - An upper limit on the number of messages returned. The default is 100, and the maximum is 1000. + * + * @return A [PaginatedResult] object containing an array of [PresenceMessage] objects. + */ + fun history( + start: Long? = null, + end: Long? = null, + limit: Int = 100, + orderBy: OrderBy = OrderBy.NewestFirst, + ): PaginatedResult + + /** + * Asynchronously retrieves a [PaginatedResult] object, containing an array of historical [PresenceMessage] objects for the channel. + * If the channel is configured to persist messages, + * then presence messages can be retrieved from history for up to 72 hours in the past. + * If not, presence messages can only be retrieved from history for up to two minutes in the past. + * + * Spec: RSP4a + * + * @param start (RSP4b1) - The time from which messages are retrieved, specified as milliseconds since the Unix epoch. + * @param end (RSP4b1) - The time until messages are retrieved, specified as milliseconds since the Unix epoch. + * @param orderBy (RSP4b2) - The order for which messages are returned in. + * @param limit (RSP4b3) - An upper limit on the number of messages returned. The default is 100, and the maximum is 1000. + * @param callback A Callback returning [AsyncPaginatedResult] object containing an array of [PresenceMessage] objects. + * Note: This callback is invoked on a background thread. + */ + fun historyAsync( + callback: Callback>, + start: Long? = null, + end: Long? = null, + limit: Int = 100, + orderBy: OrderBy = OrderBy.NewestFirst, + ) +} diff --git a/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimeChannel.kt b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimeChannel.kt new file mode 100644 index 000000000..5a06b10d0 --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimeChannel.kt @@ -0,0 +1,147 @@ +package com.ably.pubsub + +import com.ably.Subscription +import io.ably.lib.realtime.ChannelBase.MessageListener +import io.ably.lib.realtime.ChannelState +import io.ably.lib.realtime.CompletionListener +import io.ably.lib.types.ChannelOptions +import io.ably.lib.types.ChannelProperties +import io.ably.lib.types.ErrorInfo +import io.ably.lib.types.Message + + +/** + * An interface representing a Realtime Channel. + */ +interface RealtimeChannel : Channel { + /** + * Presence set for a channel. + */ + override val presence: RealtimePresence + + /** + * The current [ChannelState] of the channel. + * + * Spec: RTL2b + */ + val state: ChannelState + + /** + * An [ErrorInfo] object describing the last error which occurred on the channel, if any. + * + * Spec: RTL4e + */ + val reason: ErrorInfo + + /** + * A [ChannelProperties] object. + * + * Spec: CP1, RTL15 + */ + val properties: ChannelProperties + + /** + * Attach to this channel ensuring the channel is created in the Ably system and all messages published + * on the channel are received by any channel listeners registered using [subscribe]. + * Any resulting channel state change will be emitted to any listeners registered using the + * [io.ably.lib.util.EventEmitter.on] or [io.ably.lib.util.EventEmitter.once] methods. + * As a convenience, `attach()` is called implicitly if [subscribe] for the channel is called, + * or [RealtimePresence.enter] or [RealtimePresence.subscribe] are called on the [RealtimePresence] object for this channel. + * + * Spec: RTL4d + */ + fun attach(listener: CompletionListener? = null) + + /** + * Detach from this channel. + * Any resulting channel state change is emitted to any listeners registered using the + * [io.ably.lib.util.EventEmitter.on] or [io.ably.lib.util.EventEmitter.once] methods. + * Once all clients globally have detached from the channel, the channel will be released in the Ably service within two minutes. + * + * Spec: RTL5e + */ + fun detach(listener: CompletionListener? = null) + + /** + * Registers a listener for messages on this channel. + * The caller supplies a listener function, which is called each time one or more messages arrives on the channel. + * + * Spec: RTL7a + * + * @param listener A listener may optionally be passed in to this call to be notified of success or failure + * of the channel [RealtimeChannel.attach] operation. This listener is invoked on a background thread. + */ + fun subscribe(listener: MessageListener): Subscription + + /** + * Registers a listener for messages with a given event name on this channel. + * The caller supplies a listener function, which is called each time one or more matching messages arrives at the channel. + * + * Spec: RTL7b + * + * @param eventName The event name. + * @param listener A listener may optionally be passed in to this call to be notified of success or failure + * of the channel [RealtimeChannel.attach] operation. This listener is invoked on a background thread. + */ + fun subscribe(eventName: String, listener: MessageListener): Subscription + + /** + * Registers a listener for messages on this channel for multiple event name values. + * The caller supplies a listener function, which is called each time one or more matching messages arrives on the channel. + * + * Spec: RTL7a + * + * @param eventNames A list of event names. + * @param listener A listener may optionally be passed in to this call to be notified of success or failure + * of the channel [RealtimeChannel.attach] operation. This listener is invoked on a background thread. + */ + fun subscribe(eventNames: List, listener: MessageListener): Subscription + + /** + * Publishes a single message to the channel with the given event name and payload. + * When publish is called with this client library, it won't attempt to implicitly attach to the channel, + * so long as [transient publishing](https://ably.com/docs/realtime/channels#transient-publish) is available in the library. + * Otherwise, the client will implicitly attach. + * + * Spec: RTL6i + * + * @param name the event name + * @param data the message payload + * @param listener A listener may optionally be passed in to this call to be notified of success or failure of the operation. + * This listener is invoked on a background thread. + */ + fun publish(name: String? = null, data: Any? = null, listener: CompletionListener? = null) + + /** + * Publishes a message to the channel. + * When publish is called with this client library, it won't attempt to implicitly attach to the channel. + * + * Spec: RTL6i + * + * @param message A [Message] object. + * @param listener A listener may optionally be passed in to this call to be notified of success or failure of the operation. + * This listener is invoked on a background thread. + */ + fun publish(message: Message, listener: CompletionListener? = null) + + /** + * Publishes an array of messages to the channel. + * When publish is called with this client library, it won't attempt to implicitly attach to the channel. + * + * Spec: RTL6i + * + * @param messages A list of [Message] objects. + * @param listener A listener may optionally be passed in to this call to be notified of success or failure of the operation. + * This listener is invoked on a background thread. + */ + fun publish(messages: List, listener: CompletionListener? = null) + + /** + * Sets the [ChannelOptions] for the channel. + * + * Spec: RTL16 + * + * @param options A {@link ChannelOptions} object. + */ + fun setOptions(options: ChannelOptions) +} diff --git a/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimeClient.kt b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimeClient.kt new file mode 100644 index 000000000..d3a29be87 --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimeClient.kt @@ -0,0 +1,24 @@ +package com.ably.pubsub + +import io.ably.lib.realtime.Connection + +/** + * A client that extends the functionality of the {@link Client} and provides additional realtime-specific features. + * + * This class implements {@link AutoCloseable} so you can use it in + * try-with-resources constructs and have the JDK close it for you. + */ +interface RealtimeClient : Client { + + /** + * The {@link Connection} object for this instance. + *

+ * Spec: RTC2 + */ + val connection: Connection + + /** + * Collection of [RealtimeChannel] instances currently managed by Realtime client + */ + override val channels: Channels +} diff --git a/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimePresence.kt b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimePresence.kt new file mode 100644 index 000000000..63ee25c84 --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimePresence.kt @@ -0,0 +1,155 @@ +package com.ably.pubsub + +import com.ably.Subscription +import io.ably.lib.realtime.Channel +import io.ably.lib.realtime.ChannelState +import io.ably.lib.realtime.CompletionListener +import io.ably.lib.realtime.Presence.PresenceListener +import io.ably.lib.types.AblyException +import io.ably.lib.types.PresenceMessage +import java.util.* + + +/** + * Presence for a Realtime channel + */ +interface RealtimePresence : Presence { + + /** + * Retrieves the current members present on the channel and the metadata for each member, + * such as their [io.ably.lib.types.PresenceMessage.Action] and ID. + * Returns an array of [PresenceMessage] objects. + * + * Spec: RTP11 + * + * @param waitForSync (RTP11c1) - Sets whether to wait for a full presence set synchronization between Ably and the clients on + * the channel to complete before returning the results. + * Synchronization begins as soon as the channel is [ChannelState.attached]. + * When set to true the results will be returned as soon as the sync is complete. + * When set to false the current list of members will be returned without the sync completing. + * The default is true. + * @param clientId (RTP11c2) - Filters the array of returned presence members by a specific client using its ID. + * @param connectionId (RTP11c3) - Filters the array of returned presence members by a specific connection using its ID. + * @return A list of [PresenceMessage] objects. + */ + fun get(clientId: String? = null, connectionId: String? = null, waitForSync: Boolean = true): List + + /** + * Registers a listener that is called each time a [PresenceMessage] matching a given [PresenceMessage.Action], + * or an action within an array of [PresenceMessage.Action], is received on the channel, + * such as a new member entering the presence set. + * + * Spec: RTP6a + * + * @param listener An event listener function. + * The listener is invoked on a background thread. + */ + fun subscribe(listener: PresenceListener): Subscription + + /** + * Registers a listener that is called each time a [PresenceMessage] matching a given [PresenceMessage.Action], + * or an action within an array of [PresenceMessage.Action], is received on the channel, + * such as a new member entering the presence set. + * + * Spec: RTP6b + * + * @param action A [PresenceMessage.Action] to register the listener for. + * @param listener An event listener function. + * The listener is invoked on a background thread. + */ + fun subscribe(action: PresenceMessage.Action, listener: PresenceListener): Subscription + + /** + * Registers a listener that is called each time a [PresenceMessage] matching a given [PresenceMessage.Action], + * or an action within an array of [PresenceMessage.Action], is received on the channel, + * such as a new member entering the presence set. + * + * Spec: RTP6b + * + * @param actions An array of [PresenceMessage.Action] to register the listener for. + * @param listener An event listener function. + * The listener is invoked on a background thread. + */ + fun subscribe(actions: EnumSet, listener: PresenceListener): Subscription + + /** + * Enters the presence set for the channel, optionally passing a data payload. + * A clientId is required to be present on a channel. + * An optional callback may be provided to notify of the success or failure of the operation. + * + * Spec: RTP8 + * + * @param data The payload associated with the presence member. + * @param listener A callback to notify of the success or failure of the operation. + * This listener is invoked on a background thread. + */ + fun enter(data: Any? = null, listener: CompletionListener? = null) + + /** + * Updates the data payload for a presence member. + * If called before entering the presence set, this is treated as an [PresenceMessage.Action.enter] event. + * An optional callback may be provided to notify of the success or failure of the operation. + * + * Spec: RTP9 + * + * @param data The payload associated with the presence member. + * @param listener A callback to notify of the success or failure of the operation. + * This listener is invoked on a background thread. + */ + fun update(data: Any? = null, listener: CompletionListener? = null) + + /** + * Leaves the presence set for the channel. + * A client must have previously entered the presence set before they can leave it. + * + * Spec: RTP10 + * + * @param data The payload associated with the presence member. + * @param listener a listener to notify of the success or failure of the operation. + * This listener is invoked on a background thread. + */ + fun leave(data: Any? = null, listener: CompletionListener? = null) + + /** + * Enters the presence set of the channel for a given clientId. + * Enables a single client to update presence on behalf of any number of clients using a single connection. + * The library must have been instantiated with an API key or a token bound to a wildcard clientId. + * + * Spec: RTP4, RTP14, RTP15 + * + * @param clientId The ID of the client to enter into the presence set. + * @param data The payload associated with the presence member. + * @param listener A callback to notify of the success or failure of the operation. + * This listener is invoked on a background thread. + */ + fun enterClient(clientId: String, data: Any? = null, listener: CompletionListener? = null) + + /** + * Updates the data payload for a presence member using a given clientId. + * Enables a single client to update presence on behalf of any number of clients using a single connection. + * The library must have been instantiated with an API key or a token bound to a wildcard clientId. + * An optional callback may be provided to notify of the success or failure of the operation. + * + * Spec: RTP15 + * + * @param clientId The ID of the client to update in the presence set. + * @param data The payload to update for the presence member. + * @param listener A callback to notify of the success or failure of the operation. + * This listener is invoked on a background thread. + */ + fun updateClient(clientId: String, data: Any? = null, listener: CompletionListener? = null) + + /** + * Leaves the presence set of the channel for a given clientId. + * Enables a single client to update presence on behalf of any number of clients using a single connection. + * The library must have been instantiated with an API key or a token bound to a wildcard clientId. + * + * Spec: RTP15 + * + * @param clientId The ID of the client to leave the presence set for. + * @param data The payload associated with the presence member. + * @param listener A callback to notify of the success or failure of the operation. + * This listener is invoked on a background thread. + */ + fun leaveClient(clientId: String?, data: Any? = null, listener: CompletionListener? = null) +} diff --git a/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RestChannel.kt b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RestChannel.kt new file mode 100644 index 000000000..ff5acc210 --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RestChannel.kt @@ -0,0 +1,44 @@ +package com.ably.pubsub + +import io.ably.lib.realtime.CompletionListener +import io.ably.lib.types.Message + +interface RestChannel : Channel { + + /** + * Presence set for a channel. + */ + override val presence: RestPresence + + /** + * Publish a message on this channel + * + * @param name the event name + * @param data the message payload; see [io.ably.types.Data] for details of supported data types. + */ + fun publish(name: String? = null, data: Any? = null) + + /** + * Publish list of messages on this channel. When there are + * multiple messages to be sent, it is more efficient to use this + * method to publish them in a single request, as compared with + * publishing via multiple independent requests. + * + * @param messages list of messages to publish. + */ + fun publish(messages: List) + + /** + * Publish a message on this channel asynchronously + * + * @see [publish] + */ + fun publishAsync(name: String? = null, data: Any? = null, listener: CompletionListener) + + /** + * Publish list of messages on this channel asynchronously + * + * @see [publish] + */ + fun publishAsync(messages: List, listener: CompletionListener) +} diff --git a/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RestClient.kt b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RestClient.kt new file mode 100644 index 000000000..8ea6b8f4b --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RestClient.kt @@ -0,0 +1,9 @@ +package com.ably.pubsub + +interface RestClient : Client { + + /** + * Collection of [RestChannel] instances currently managed by the client + */ + override val channels: Channels +} diff --git a/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RestPresence.kt b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RestPresence.kt new file mode 100644 index 000000000..96cca617a --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RestPresence.kt @@ -0,0 +1,36 @@ +package com.ably.pubsub + +import io.ably.lib.types.* + +interface RestPresence : Presence { + + /** + * Retrieves the current members present on the channel and the metadata for each member, + * such as their [io.ably.lib.types.PresenceMessage.Action] and ID. Returns a [PaginatedResult] object, + * containing an array of [PresenceMessage] objects. + * + * Spec: RSPa + * + * @param limit (RSP3a) - An upper limit on the number of messages returned. The default is 100, and the maximum is 1000. + * @param clientId (RSP3a2) - Filters the list of returned presence members by a specific client using its ID. + * @param connectionId (RSP3a3) - Filters the list of returned presence members by a specific connection using its ID. + * @return A [PaginatedResult] object containing an array of [PresenceMessage] objects. + */ + fun get(limit: Int = 100, clientId: String? = null, connectionId: String? = null): PaginatedResult + + /** + * Asynchronously retrieves the current members present on the channel and the metadata for each member, + * such as their [io.ably.lib.types.PresenceMessage.Action] and ID. Returns a [PaginatedResult] object, + * containing an array of [PresenceMessage] objects. + * + * Spec: RSPa + * + * @param limit (RSP3a) - An upper limit on the number of messages returned. The default is 100, and the maximum is 1000. + * @param clientId (RSP3a2) - Filters the list of returned presence members by a specific client using its ID. + * @param connectionId (RSP3a3) - Filters the list of returned presence members by a specific connection using its ID. + * @param callback A Callback returning [AsyncPaginatedResult] object containing an array of [PresenceMessage] objects. + * This callback is invoked on a background thread. + */ + fun getAsync(callback: Callback>, limit: Int = 100, clientId: String? = null, connectionId: String? = null) + +} diff --git a/pubsub-adapter/src/main/kotlin/com/ably/query/OrderBy.kt b/pubsub-adapter/src/main/kotlin/com/ably/query/OrderBy.kt new file mode 100644 index 000000000..21945927e --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/com/ably/query/OrderBy.kt @@ -0,0 +1,18 @@ +package com.ably.query + +/** + * Represents direction to query messages in. + */ +enum class OrderBy(val direction: String) { + + /** + * The response will include messages from the end of the time window to the start. + */ + NewestFirst("backwards"), + + /** + * The response will include messages from the start of the time window to the end. + */ + OldestFirst("forwards"), + ; +} diff --git a/pubsub-adapter/src/main/kotlin/com/ably/query/TimeUnit.kt b/pubsub-adapter/src/main/kotlin/com/ably/query/TimeUnit.kt new file mode 100644 index 000000000..437557c1b --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/com/ably/query/TimeUnit.kt @@ -0,0 +1,16 @@ +package com.ably.query + +/** + * The period for which the stats query will be aggregated by, + * values supported are minute, hour, day or month; if omitted the unit defaults + * to the REST API default (minute) + */ +enum class TimeUnit(private val unit: String) { + Minute("minute"), + Hour("hour"), + Day("day"), + Month("month"), + ; + + override fun toString() = unit +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 136b798ca..7ccfd6f3f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,3 +14,4 @@ include("gradle-lint") include("network-client-core") include("network-client-default") include("network-client-okhttp") +include("pubsub-adapter") From 18a502071a5d00acf5096b808a353807b0254e5f Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 11 Feb 2025 16:14:43 +0000 Subject: [PATCH 2/3] feat: Rest and Realtime clients adapters Adapters for the Kotlin API for `ably-java` and `ably-android` --- .../src/main/kotlin/io/ably/lib/Utils.kt | 38 ++++++++++ .../lib/realtime/RealtimeChannelAdapter.kt | 68 +++++++++++++++++ .../lib/realtime/RealtimeChannelsAdapter.kt | 20 +++++ .../lib/realtime/RealtimeClientAdapter.kt | 71 ++++++++++++++++++ .../lib/realtime/RealtimePresenceAdapter.kt | 73 +++++++++++++++++++ .../io/ably/lib/rest/RestChannelAdapter.kt | 38 ++++++++++ .../io/ably/lib/rest/RestChannelsAdapter.kt | 20 +++++ .../io/ably/lib/rest/RestClientAdapter.kt | 68 +++++++++++++++++ .../io/ably/lib/rest/RestPresenceAdapter.kt | 39 ++++++++++ 9 files changed, 435 insertions(+) create mode 100644 pubsub-adapter/src/main/kotlin/io/ably/lib/Utils.kt create mode 100644 pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeChannelAdapter.kt create mode 100644 pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeChannelsAdapter.kt create mode 100644 pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeClientAdapter.kt create mode 100644 pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimePresenceAdapter.kt create mode 100644 pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestChannelAdapter.kt create mode 100644 pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestChannelsAdapter.kt create mode 100644 pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestClientAdapter.kt create mode 100644 pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestPresenceAdapter.kt diff --git a/pubsub-adapter/src/main/kotlin/io/ably/lib/Utils.kt b/pubsub-adapter/src/main/kotlin/io/ably/lib/Utils.kt new file mode 100644 index 000000000..2fc0af773 --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/io/ably/lib/Utils.kt @@ -0,0 +1,38 @@ +package io.ably.lib + +import com.ably.query.OrderBy +import com.ably.query.TimeUnit +import io.ably.lib.types.Param + +fun buildStatsParams( + start: Long?, + end: Long?, + limit: Int, + orderBy: OrderBy, + unit: TimeUnit, +) = buildList { + addAll(buildHistoryParams(start, end, limit, orderBy)) + add(Param("unit", unit.toString())) +} + +fun buildHistoryParams( + start: Long?, + end: Long?, + limit: Int, + orderBy: OrderBy, +) = buildList { + start?.let { add(Param("start", it)) } + end?.let { add(Param("end", it)) } + add(Param("limit", limit)) + add(Param("direction", orderBy.direction)) +} + +fun buildRestPresenceParams( + limit: Int, + clientId: String?, + connectionId: String?, +) = buildList { + add(Param("limit", limit)) + clientId?.let { add(Param("clientId", it)) } + connectionId?.let { add(Param("connectionId", it)) } +} diff --git a/pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeChannelAdapter.kt b/pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeChannelAdapter.kt new file mode 100644 index 000000000..f746d6bbc --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeChannelAdapter.kt @@ -0,0 +1,68 @@ +package io.ably.lib.realtime + +import com.ably.Subscription +import com.ably.pubsub.RealtimeChannel +import com.ably.pubsub.RealtimePresence +import com.ably.query.OrderBy +import io.ably.lib.buildHistoryParams +import io.ably.lib.types.* + +internal class RealtimeChannelAdapter(private val javaChannel: Channel) : RealtimeChannel { + override val name: String + get() = javaChannel.name + override val presence: RealtimePresence + get() = RealtimePresenceAdapter(javaChannel.presence) + override val state: ChannelState + get() = javaChannel.state + override val reason: ErrorInfo + get() = javaChannel.reason + override val properties: ChannelProperties + get() = javaChannel.properties + + override fun attach(listener: CompletionListener?) = javaChannel.attach(listener) + + override fun detach(listener: CompletionListener?) = javaChannel.detach(listener) + + override fun subscribe(listener: ChannelBase.MessageListener): Subscription { + javaChannel.subscribe(listener) + return Subscription { + javaChannel.unsubscribe(listener) + } + } + + override fun subscribe(eventName: String, listener: ChannelBase.MessageListener): Subscription { + javaChannel.subscribe(eventName, listener) + return Subscription { + javaChannel.unsubscribe(eventName, listener) + } + } + + override fun subscribe(eventNames: List, listener: ChannelBase.MessageListener): Subscription { + javaChannel.subscribe(eventNames.toTypedArray(), listener) + return Subscription { + javaChannel.unsubscribe(eventNames.toTypedArray(), listener) + } + } + + override fun publish(name: String?, data: Any?, listener: CompletionListener?) = + javaChannel.publish(name, data, listener) + + override fun publish(message: Message, listener: CompletionListener?) = javaChannel.publish(message, listener) + + override fun publish(messages: List, listener: CompletionListener?) = + javaChannel.publish(messages.toTypedArray(), listener) + + override fun setOptions(options: ChannelOptions) = javaChannel.setOptions(options) + + override fun history(start: Long?, end: Long?, limit: Int, orderBy: OrderBy): PaginatedResult = + javaChannel.history(buildHistoryParams(start, end, limit, orderBy).toTypedArray()) + + override fun historyAsync( + callback: Callback>, + start: Long?, + end: Long?, + limit: Int, + orderBy: OrderBy, + ) = + javaChannel.historyAsync(buildHistoryParams(start, end, limit, orderBy).toTypedArray(), callback) +} diff --git a/pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeChannelsAdapter.kt b/pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeChannelsAdapter.kt new file mode 100644 index 000000000..0fe955b30 --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeChannelsAdapter.kt @@ -0,0 +1,20 @@ +package io.ably.lib.realtime + +import com.ably.pubsub.Channels +import com.ably.pubsub.RealtimeChannel +import io.ably.lib.types.ChannelOptions + +internal class RealtimeChannelsAdapter(private val javaChannels: AblyRealtime.Channels) : Channels { + override fun contains(name: String): Boolean = javaChannels.containsKey(name) + + override fun get(name: String): RealtimeChannel = RealtimeChannelAdapter(javaChannels.get(name)) + + override fun get(name: String, options: ChannelOptions): RealtimeChannel = + RealtimeChannelAdapter(javaChannels.get(name, options)) + + override fun release(name: String) = javaChannels.release(name) + + override fun iterator(): Iterator = iterator { + javaChannels.entrySet().forEach { yield(RealtimeChannelAdapter(it.value)) } + } +} diff --git a/pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeClientAdapter.kt b/pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeClientAdapter.kt new file mode 100644 index 000000000..a4e69dc48 --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimeClientAdapter.kt @@ -0,0 +1,71 @@ +package io.ably.lib.realtime + +import com.ably.http.HttpMethod +import com.ably.pubsub.Channels +import com.ably.pubsub.RealtimeChannel +import com.ably.pubsub.RealtimeClient +import com.ably.query.OrderBy +import com.ably.query.TimeUnit +import io.ably.lib.buildStatsParams +import io.ably.lib.http.HttpCore +import io.ably.lib.push.Push +import io.ably.lib.rest.Auth +import io.ably.lib.types.* + +/** + * Wrapper for Realtime client + */ +fun RealtimeClient(javaClient: AblyRealtime): RealtimeClient = RealtimeClientAdapter(javaClient) + +internal class RealtimeClientAdapter(private val javaClient: AblyRealtime) : RealtimeClient { + override val channels: Channels + get() = RealtimeChannelsAdapter(javaClient.channels) + override val connection: Connection + get() = javaClient.connection + override val auth: Auth + get() = javaClient.auth + override val options: ClientOptions + get() = javaClient.options + override val push: Push + get() = javaClient.push + + override fun time(): Long = javaClient.time() + + override fun timeAsync(callback: Callback) = javaClient.timeAsync(callback) + + override fun stats( + start: Long?, + end: Long?, + limit: Int, + orderBy: OrderBy, + unit: TimeUnit + ): PaginatedResult = javaClient.stats(buildStatsParams(start, end, limit, orderBy, unit).toTypedArray()) + + override fun statsAsync( + callback: Callback>, + start: Long?, + end: Long?, + limit: Int, + orderBy: OrderBy, + unit: TimeUnit + ) = javaClient.statsAsync(buildStatsParams(start, end, limit, orderBy, unit).toTypedArray(), callback) + + override fun request( + path: String, + method: HttpMethod, + params: List, + body: HttpCore.RequestBody?, + headers: List, + ) = javaClient.request(method.toString(), path, params.toTypedArray(), body, headers.toTypedArray())!! + + override fun requestAsync( + path: String, + callback: AsyncHttpPaginatedResponse.Callback, + method: HttpMethod, + params: List, + body: HttpCore.RequestBody?, + headers: List, + ) = javaClient.requestAsync(method.toString(), path, params.toTypedArray(), body, headers.toTypedArray(), callback) + + override fun close() = javaClient.close() +} diff --git a/pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimePresenceAdapter.kt b/pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimePresenceAdapter.kt new file mode 100644 index 000000000..441c3dace --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/io/ably/lib/realtime/RealtimePresenceAdapter.kt @@ -0,0 +1,73 @@ +package io.ably.lib.realtime + +import com.ably.Subscription +import com.ably.pubsub.RealtimePresence +import com.ably.query.OrderBy +import io.ably.lib.buildHistoryParams +import io.ably.lib.types.* +import java.util.* + +internal class RealtimePresenceAdapter(private val javaPresence: Presence) : RealtimePresence { + override fun get(clientId: String?, connectionId: String?, waitForSync: Boolean): List { + val params = buildList { + clientId?.let { add(Param(Presence.GET_CLIENTID, it)) } + connectionId?.let { add(Param(Presence.GET_CONNECTIONID, it)) } + add(Param(Presence.GET_WAITFORSYNC, waitForSync)) + } + return javaPresence.get(*params.toTypedArray()).toList() + } + + override fun subscribe(listener: Presence.PresenceListener): Subscription { + javaPresence.subscribe(listener) + return Subscription { + javaPresence.unsubscribe(listener) + } + } + + override fun subscribe( + action: PresenceMessage.Action, + listener: Presence.PresenceListener, + ): Subscription { + javaPresence.subscribe(action, listener) + return Subscription { + javaPresence.unsubscribe(action, listener) + } + } + + override fun subscribe( + actions: EnumSet, + listener: Presence.PresenceListener, + ): Subscription { + javaPresence.subscribe(actions, listener) + return Subscription { + javaPresence.unsubscribe(actions, listener) + } + } + + override fun enter(data: Any?, listener: CompletionListener?) = javaPresence.enter(data, listener) + + override fun update(data: Any?, listener: CompletionListener?) = javaPresence.update(data, listener) + + override fun leave(data: Any?, listener: CompletionListener?) = javaPresence.leave(data, listener) + + override fun enterClient(clientId: String, data: Any?, listener: CompletionListener?) = + javaPresence.enterClient(clientId, data, listener) + + override fun updateClient(clientId: String, data: Any?, listener: CompletionListener?) = + javaPresence.updateClient(clientId, data, listener) + + override fun leaveClient(clientId: String?, data: Any?, listener: CompletionListener?) = + javaPresence.leaveClient(clientId, data, listener) + + override fun history(start: Long?, end: Long?, limit: Int, orderBy: OrderBy): PaginatedResult = + javaPresence.history(buildHistoryParams(start, end, limit, orderBy).toTypedArray()) + + override fun historyAsync( + callback: Callback>, + start: Long?, + end: Long?, + limit: Int, + orderBy: OrderBy, + ) = + javaPresence.historyAsync(buildHistoryParams(start, end, limit, orderBy).toTypedArray(), callback) +} diff --git a/pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestChannelAdapter.kt b/pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestChannelAdapter.kt new file mode 100644 index 000000000..29a00d1c9 --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestChannelAdapter.kt @@ -0,0 +1,38 @@ +package io.ably.lib.rest + +import com.ably.pubsub.RestChannel +import com.ably.pubsub.RestPresence +import com.ably.query.OrderBy +import io.ably.lib.buildHistoryParams +import io.ably.lib.realtime.CompletionListener +import io.ably.lib.types.* + +internal class RestChannelAdapter(private val javaChannel: Channel) : RestChannel { + override val name: String + get() = javaChannel.name + + override val presence: RestPresence + get() = RestPresenceAdapter(javaChannel.presence) + + override fun publish(name: String?, data: Any?) = javaChannel.publish(name, data) + + override fun publish(messages: List) = javaChannel.publish(messages.toTypedArray()) + + override fun publishAsync(name: String?, data: Any?, listener: CompletionListener) = + javaChannel.publishAsync(name, data, listener) + + override fun publishAsync(messages: List, listener: CompletionListener) = + javaChannel.publishAsync(messages.toTypedArray(), listener) + + override fun history(start: Long?, end: Long?, limit: Int, orderBy: OrderBy): PaginatedResult = + javaChannel.history(buildHistoryParams(start, end, limit, orderBy).toTypedArray()) + + override fun historyAsync( + callback: Callback>, + start: Long?, + end: Long?, + limit: Int, + orderBy: OrderBy, + ) = + javaChannel.historyAsync(buildHistoryParams(start, end, limit, orderBy).toTypedArray(), callback) +} diff --git a/pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestChannelsAdapter.kt b/pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestChannelsAdapter.kt new file mode 100644 index 000000000..083a2ce2a --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestChannelsAdapter.kt @@ -0,0 +1,20 @@ +package io.ably.lib.rest + +import com.ably.pubsub.Channels +import com.ably.pubsub.RestChannel +import io.ably.lib.types.ChannelOptions + +internal class RestChannelsAdapter(private val javaChannels: AblyBase.Channels) : Channels { + override fun contains(name: String): Boolean = javaChannels.containsKey(name) + + override fun get(name: String): RestChannel = RestChannelAdapter(javaChannels.get(name)) + + override fun get(name: String, options: ChannelOptions): RestChannel = + RestChannelAdapter(javaChannels.get(name, options)) + + override fun release(name: String) = javaChannels.release(name) + + override fun iterator(): Iterator = iterator { + javaChannels.entrySet().forEach { yield(RestChannelAdapter(it.value)) } + } +} diff --git a/pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestClientAdapter.kt b/pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestClientAdapter.kt new file mode 100644 index 000000000..c7a163554 --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestClientAdapter.kt @@ -0,0 +1,68 @@ +package io.ably.lib.rest + +import com.ably.http.HttpMethod +import com.ably.pubsub.Channels +import com.ably.pubsub.RestChannel +import com.ably.pubsub.RestClient +import com.ably.query.OrderBy +import com.ably.query.TimeUnit +import io.ably.lib.buildStatsParams +import io.ably.lib.http.HttpCore +import io.ably.lib.push.Push +import io.ably.lib.types.* + +/** + * Wrapper for Rest client + */ +fun RestClient(javaClient: AblyRest): RestClient = RestClientAdapter(javaClient) + +internal class RestClientAdapter(private val javaClient: AblyRest) : RestClient { + override val channels: Channels + get() = RestChannelsAdapter(javaClient.channels) + override val auth: Auth + get() = javaClient.auth + override val options: ClientOptions + get() = javaClient.options + override val push: Push + get() = javaClient.push + + override fun time(): Long = javaClient.time() + + override fun timeAsync(callback: Callback) = javaClient.timeAsync(callback) + + override fun stats( + start: Long?, + end: Long?, + limit: Int, + orderBy: OrderBy, + unit: TimeUnit + ): PaginatedResult = javaClient.stats(buildStatsParams(start, end, limit, orderBy, unit).toTypedArray()) + + override fun statsAsync( + callback: Callback>, + start: Long?, + end: Long?, + limit: Int, + orderBy: OrderBy, + unit: TimeUnit + ) = javaClient.statsAsync(buildStatsParams(start, end, limit, orderBy, unit).toTypedArray(), callback) + + override fun request( + path: String, + method: HttpMethod, + params: List, + body: HttpCore.RequestBody?, + headers: List, + ) = javaClient.request(method.toString(), path, params.toTypedArray(), body, headers.toTypedArray())!! + + override fun requestAsync( + path: String, + callback: AsyncHttpPaginatedResponse.Callback, + method: HttpMethod, + params: List, + body: HttpCore.RequestBody?, + headers: List, + ) = javaClient.requestAsync(method.toString(), path, params.toTypedArray(), body, headers.toTypedArray(), callback) + + override fun close() = javaClient.close() +} diff --git a/pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestPresenceAdapter.kt b/pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestPresenceAdapter.kt new file mode 100644 index 000000000..1b4267b09 --- /dev/null +++ b/pubsub-adapter/src/main/kotlin/io/ably/lib/rest/RestPresenceAdapter.kt @@ -0,0 +1,39 @@ +package io.ably.lib.rest + +import com.ably.pubsub.RestPresence +import com.ably.query.OrderBy +import io.ably.lib.buildHistoryParams +import io.ably.lib.buildRestPresenceParams +import io.ably.lib.rest.ChannelBase.Presence +import io.ably.lib.types.AsyncPaginatedResult +import io.ably.lib.types.Callback +import io.ably.lib.types.PaginatedResult +import io.ably.lib.types.PresenceMessage + +internal class RestPresenceAdapter(private val javaPresence: Presence) : RestPresence { + override fun get( + limit: Int, + clientId: String?, + connectionId: String?, + ): PaginatedResult = + javaPresence.get(buildRestPresenceParams(limit, clientId, connectionId).toTypedArray()) + + override fun getAsync( + callback: Callback>, limit: Int, + clientId: String?, + connectionId: String?, + ) = + javaPresence.getAsync(buildRestPresenceParams(limit, clientId, connectionId).toTypedArray(), callback) + + override fun history(start: Long?, end: Long?, limit: Int, orderBy: OrderBy): PaginatedResult = + javaPresence.history(buildHistoryParams(start, end, limit, orderBy).toTypedArray()) + + override fun historyAsync( + callback: Callback>, + start: Long?, + end: Long?, + limit: Int, + orderBy: OrderBy, + ) = + javaPresence.historyAsync(buildHistoryParams(start, end, limit, orderBy).toTypedArray(), callback) +} From 2f7661d6da65377cc2e682b4bf1fd8321175ab39 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 12 Feb 2025 14:33:14 +0000 Subject: [PATCH 3/3] chore: interface update - make ErrorReason on the channel nullable - improve docstrings --- .../main/kotlin/com/ably/pubsub/Channel.kt | 24 ++++++++++++++++++- .../kotlin/com/ably/pubsub/RealtimeChannel.kt | 2 +- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Channel.kt b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Channel.kt index 173b19903..cc9c30873 100644 --- a/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Channel.kt +++ b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/Channel.kt @@ -4,18 +4,40 @@ import com.ably.query.OrderBy import io.ably.lib.types.* /** - * An interface representing a Channel in the Ably API. + * An interface representing a Channel in the Ably API. This serves as the base interface + * for both [RealtimeChannel] and [RestChannel], providing common channel functionality + * such as history retrieval and presence management. + * + * A channel is the medium through which messages are distributed. Channels can represent + * different topics, rooms, or contexts in your application. + * + * @see Ably Channels Documentation */ interface Channel { /** * The channel name. + * + * Channel names: + * - Can contain any Unicode characters except colon (':') + * - Are limited to 250 characters + * - Are case-sensitive + * + * @see Channel Naming Rules */ val name: String /** * A [Presence] object. * + * The Presence object enables clients to be notified when other clients enter or leave + * the channel (presence events) and get the set of current members on the channel + * (presence state). + * + * Common use cases include: + * - Online status indicators + * - Typing indicators + * - User activity tracking * * Spec: RTL9 */ diff --git a/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimeChannel.kt b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimeChannel.kt index 5a06b10d0..65a1eed6b 100644 --- a/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimeChannel.kt +++ b/pubsub-adapter/src/main/kotlin/com/ably/pubsub/RealtimeChannel.kt @@ -31,7 +31,7 @@ interface RealtimeChannel : Channel { * * Spec: RTL4e */ - val reason: ErrorInfo + val reason: ErrorInfo? /** * A [ChannelProperties] object.