From 38a77291cb3e0808f3069398e200c3af2bab0cdf Mon Sep 17 00:00:00 2001 From: marcin-cebo <102806110+marcin-cebo@users.noreply.github.com> Date: Tue, 7 Jan 2025 18:04:34 +0100 Subject: [PATCH] Added status and type to Membership. (#151) * Refactor. Use non-deprecated Memberships and ChannelMembers methods. * Increased timeout for JS test to passed. In new version of PubNub core js sdk verbose logging has been added that slows down test execution. * Refactor. Use non-deprecated Memberships and ChannelMembers method. * PubNub kotlin 0.10.0 release. --------- Co-authored-by: PubNub Release Bot <120067856+pubnub-release-bot@users.noreply.github.com> --- .pubnub.yml | 11 ++- Package.swift | 4 +- README.md | 2 +- .../gradle/PubNubKotlinMultiplatformPlugin.kt | 2 +- gradle.properties | 2 +- gradle/libs.versions.toml | 4 +- kotlin-js-store/yarn.lock | 8 +- pubnub-chat-api/api/pubnub-chat-api.api | 2 + .../kotlin/com/pubnub/chat/Membership.kt | 10 +++ .../com/pubnub/chat/internal/ChatImpl.kt | 58 ++++++++++---- .../pubnub/chat/internal/MembershipImpl.kt | 58 +++++++++++--- .../com/pubnub/chat/internal/UserImpl.kt | 42 +++++----- .../chat/internal/channel/BaseChannel.kt | 78 +++++++++++++------ .../pubnub/integration/ChatIntegrationTest.kt | 12 +-- .../kotlin/com/pubnub/kmp/ChannelTest.kt | 28 +++---- .../kotlin/com/pubnub/kmp/ChatTest.kt | 50 +++++++++--- .../kotlin/com/pubnub/kmp/MembershipTest.kt | 6 ++ .../kotlin/com/pubnub/kmp/UserTest.kt | 66 ++++++++-------- .../src/jsMain/kotlin/MembershipJs.kt | 4 + src/jsMain/resources/index.d.ts | 4 +- 20 files changed, 296 insertions(+), 155 deletions(-) diff --git a/.pubnub.yml b/.pubnub.yml index 106bc70d..4345b52d 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,5 +1,5 @@ name: kmp-chat -version: 0.9.4 +version: 0.10.0 schema: 1 scm: github.com/pubnub/kmp-chat sdks: @@ -21,8 +21,8 @@ sdks: - distribution-type: library distribution-repository: maven - package-name: pubnub-chat-0.9.4 - location: https://repo.maven.apache.org/maven2/com/pubnub/pubnub-chat/0.9.4/ + package-name: pubnub-chat-0.10.0 + location: https://repo.maven.apache.org/maven2/com/pubnub/pubnub-chat/0.10.0/ supported-platforms: supported-operating-systems: Android: @@ -77,6 +77,11 @@ sdks: license-url: https://github.com/pubnub/kotlin/blob/master/LICENSE is-required: Required changelog: + - date: 2025-01-07 + version: 0.10.0 + changes: + - type: feature + text: "Added status and type to Membership." - date: 2024-12-20 version: 0.9.4 changes: diff --git a/Package.swift b/Package.swift index 8c8bbebf..01afa6c3 100644 --- a/Package.swift +++ b/Package.swift @@ -18,8 +18,8 @@ let package = Package( targets: [ .binaryTarget( name: "PubNubChatRemoteBinaryPackage", - url: "https://github.com/pubnub/kmp-chat/releases/download/kotlin-0.9.4/PubNubChat.xcframework.zip", - checksum: "b2971bcc09a9e0107c4bcbf3795fd53ab80608f4dc3dbb7c67ee668009e4b1fe" + url: "https://github.com/pubnub/kmp-chat/releases/download/kotlin-0.10.0/PubNubChat.xcframework.zip", + checksum: "bc2c024e9b83a67d90e078778e140c0858651f96d0909d9a14143d36fa5efdf3" ) ] ) diff --git a/README.md b/README.md index 4c2f04eb..fc751079 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ You will need the publish and subscribe keys to authenticate your app. Get your com.pubnub pubnub-chat - 0.9.4 + 0.10.0 ``` diff --git a/build-logic/gradle-plugins/src/main/kotlin/com/pubnub/gradle/PubNubKotlinMultiplatformPlugin.kt b/build-logic/gradle-plugins/src/main/kotlin/com/pubnub/gradle/PubNubKotlinMultiplatformPlugin.kt index 8b95d4ed..c8ecb29b 100644 --- a/build-logic/gradle-plugins/src/main/kotlin/com/pubnub/gradle/PubNubKotlinMultiplatformPlugin.kt +++ b/build-logic/gradle-plugins/src/main/kotlin/com/pubnub/gradle/PubNubKotlinMultiplatformPlugin.kt @@ -78,7 +78,7 @@ class PubNubBaseKotlinMultiplatformPlugin : Plugin { testTask { it.environment("MOCHA_OPTIONS", "--exit") it.useMocha { - timeout = "20s" + timeout = "30s" } } } diff --git a/gradle.properties b/gradle.properties index c78d4d29..80c872e2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ SONATYPE_HOST=DEFAULT SONATYPE_AUTOMATIC_RELEASE=false GROUP=com.pubnub POM_PACKAGING=jar -VERSION_NAME=0.9.6 +VERSION_NAME=0.10.0 POM_NAME=PubNub Chat SDK POM_DESCRIPTION=This SDK offers a set of handy methods to create your own feature-rich chat or add a chat to your existing application. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6b1ee2cc..30e78fb6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,8 +6,8 @@ ktlint = "12.1.0" dokka = "1.9.20" kotlinx_serialization = "1.7.3" kotlinx_coroutines = "1.9.0" -pubnub = "10.3.2" -pubnub_swift = "8.2.2" +pubnub = "10.3.3" +pubnub_swift = "8.2.3" [libraries] pubnub-kotlin-api = { module = "com.pubnub:pubnub-kotlin-api", version.ref = "pubnub" } diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index f36679b5..83c32f93 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -659,10 +659,10 @@ proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -pubnub@8.2.8: - version "8.2.8" - resolved "https://registry.yarnpkg.com/pubnub/-/pubnub-8.2.8.tgz#68d3b84d07e128b29f74c6c46495f0c5bedd1a3f" - integrity sha512-OREW+YDWWocOaz16jbrrybD0AyqEFuskCGi+EMgOb9FUJkuYPnInjlMSAUBBrlx7lMTARMQxx3Sk0EIvbB3hig== +pubnub@8.4.1: + version "8.4.1" + resolved "https://registry.yarnpkg.com/pubnub/-/pubnub-8.4.1.tgz#5f6f19e84d3187dc8aee0a458bd6b05e90d43e6a" + integrity sha512-mPlwVoHJDWPasZx52UfSMiPX5TATm5A+ficSogyqDqTQ4w5EQnwxH+PJdsWc0mPnlT051jM1vIISMeM0fQ30CQ== dependencies: agentkeepalive "^3.5.2" buffer "^6.0.3" diff --git a/pubnub-chat-api/api/pubnub-chat-api.api b/pubnub-chat-api/api/pubnub-chat-api.api index 7e3582ec..409db660 100644 --- a/pubnub-chat-api/api/pubnub-chat-api.api +++ b/pubnub-chat-api/api/pubnub-chat-api.api @@ -128,6 +128,8 @@ public abstract interface class com/pubnub/chat/Membership { public abstract fun getCustom ()Ljava/util/Map; public abstract fun getETag ()Ljava/lang/String; public abstract fun getLastReadMessageTimetoken ()Ljava/lang/Long; + public abstract fun getStatus ()Ljava/lang/String; + public abstract fun getType ()Ljava/lang/String; public abstract fun getUnreadMessagesCount ()Lcom/pubnub/kmp/PNFuture; public abstract fun getUpdated ()Ljava/lang/String; public abstract fun getUser ()Lcom/pubnub/chat/User; diff --git a/pubnub-chat-api/src/commonMain/kotlin/com/pubnub/chat/Membership.kt b/pubnub-chat-api/src/commonMain/kotlin/com/pubnub/chat/Membership.kt index 8b3b6bd8..9262a99e 100644 --- a/pubnub-chat-api/src/commonMain/kotlin/com/pubnub/chat/Membership.kt +++ b/pubnub-chat-api/src/commonMain/kotlin/com/pubnub/chat/Membership.kt @@ -39,6 +39,16 @@ interface Membership { */ val eTag: String? + /** + * Status of a Membership + */ + val status: String? + + /** + * Type of a Membership + */ + val type: String? + /** * Timetoken of the last message a user read on a given channel. */ diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/ChatImpl.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/ChatImpl.kt index 15c3cf38..4cb881a7 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/ChatImpl.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/ChatImpl.kt @@ -20,7 +20,7 @@ import com.pubnub.api.models.consumer.objects.PNSortKey import com.pubnub.api.models.consumer.objects.channel.PNChannelMetadataResult import com.pubnub.api.models.consumer.objects.member.PNMember import com.pubnub.api.models.consumer.objects.member.PNMemberArrayResult -import com.pubnub.api.models.consumer.objects.membership.PNChannelDetailsLevel +import com.pubnub.api.models.consumer.objects.membership.MembershipInclude import com.pubnub.api.models.consumer.objects.membership.PNChannelMembership import com.pubnub.api.models.consumer.objects.membership.PNChannelMembershipArrayResult import com.pubnub.api.models.consumer.objects.uuid.PNUUIDMetadataArrayResult @@ -63,6 +63,7 @@ import com.pubnub.chat.internal.error.PubNubErrorMessage.FAILED_TO_FORWARD_MESSA import com.pubnub.chat.internal.error.PubNubErrorMessage.FAILED_TO_RETRIEVE_WHO_IS_PRESENT_DATA import com.pubnub.chat.internal.error.PubNubErrorMessage.FAILED_TO_SOFT_DELETE_CHANNEL import com.pubnub.chat.internal.error.PubNubErrorMessage.ID_IS_REQUIRED +import com.pubnub.chat.internal.error.PubNubErrorMessage.MODERATION_CAN_BE_SET_ONLY_BY_CLIENT_HAVING_SECRET_KEY import com.pubnub.chat.internal.error.PubNubErrorMessage.ONLY_ONE_LEVEL_OF_THREAD_NESTING_IS_ALLOWED import com.pubnub.chat.internal.error.PubNubErrorMessage.STORE_USER_ACTIVITY_INTERVAL_SHOULD_BE_AT_LEAST_1_MIN import com.pubnub.chat.internal.error.PubNubErrorMessage.THERE_IS_NO_ACTION_TIMETOKEN_CORRESPONDING_TO_THE_THREAD @@ -556,10 +557,16 @@ class ChatImpl( val hostMembershipFuture = pubNub.setMemberships( listOf(PNChannelMembership.Partial(channel.id, membershipCustom)), filter = "channel.id == '${channel.id}'", - includeCustom = true, - includeChannelDetails = PNChannelDetailsLevel.CHANNEL_WITH_CUSTOM, - includeCount = true, - includeType = true, + include = MembershipInclude( + includeCustom = true, + includeStatus = false, + includeType = false, + includeTotalCount = true, + includeChannel = true, + includeChannelCustom = true, + includeChannelType = true, + includeChannelStatus = false + ), ) awaitAll( hostMembershipFuture, @@ -602,10 +609,16 @@ class ChatImpl( val hostMembershipFuture = pubNub.setMemberships( listOf(PNChannelMembership.Partial(channel.id, membershipCustom)), filter = "channel.id == '${channel.id}'", - includeCustom = true, - includeChannelDetails = PNChannelDetailsLevel.CHANNEL_WITH_CUSTOM, - includeCount = true, - includeType = true, + include = MembershipInclude( + includeCustom = true, + includeStatus = false, + includeType = false, + includeTotalCount = true, + includeChannel = true, + includeChannelCustom = true, + includeChannelType = true, + includeChannelStatus = false + ), ) awaitAll( hostMembershipFuture, @@ -699,6 +712,9 @@ class ChatImpl( override fun setRestrictions( restriction: Restriction ): PNFuture { + if (this.pubNub.configuration.secretKey.isEmpty()) { + return log.logErrorAndReturnException(MODERATION_CAN_BE_SET_ONLY_BY_CLIENT_HAVING_SECRET_KEY).asFuture() + } val channel: String = INTERNAL_MODERATION_PREFIX + restriction.channelId val userId = restriction.userId return createChannel(channel).catch { exception -> @@ -730,7 +746,7 @@ class ChatImpl( ) ) val uuids = listOf(PNMember.Partial(uuidId = userId, custom = custom, null)) - pubNub.setChannelMembers(channel = channel, uuids = uuids) + pubNub.setChannelMembers(channel = channel, users = uuids) .alsoAsync { _ -> emitEvent( channelId = INTERNAL_USER_MODERATION_CHANNEL_PREFIX + userId, @@ -876,11 +892,17 @@ class ChatImpl( pubNub.setMemberships( channels = channelMembershipInputs, filter = filterExpression, - uuid = currentUser.id, - includeCount = true, - includeCustom = true, - includeChannelDetails = PNChannelDetailsLevel.CHANNEL_WITH_CUSTOM, - includeType = true + userId = currentUser.id, + include = MembershipInclude( + includeCustom = true, + includeStatus = false, + includeType = false, + includeTotalCount = true, + includeChannel = true, + includeChannelCustom = true, + includeChannelType = true, + includeChannelStatus = false + ), ).alsoAsync { _: PNChannelMembershipArrayResult -> val emitEventFutures: List> = relevantChannelIds.map { channelId: String -> @@ -1162,7 +1184,11 @@ class ChatImpl( customMetadataToSet[PINNED_MESSAGE_TIMETOKEN] = message.timetoken.toString() customMetadataToSet[PINNED_MESSAGE_CHANNEL_ID] = message.channelId } - return pubNub.setChannelMetadata(channel.id, includeCustom = true, custom = createCustomObject(customMetadataToSet)) + return pubNub.setChannelMetadata( + channel = channel.id, + includeCustom = true, + custom = createCustomObject(customMetadataToSet) + ) } internal fun getThreadId(channelId: String, messageTimetoken: Long): String { diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/MembershipImpl.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/MembershipImpl.kt index aa3be150..813a4ee6 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/MembershipImpl.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/MembershipImpl.kt @@ -2,7 +2,7 @@ package com.pubnub.chat.internal import co.touchlab.kermit.Logger import com.pubnub.api.models.consumer.objects.member.PNMember -import com.pubnub.api.models.consumer.objects.membership.PNChannelDetailsLevel +import com.pubnub.api.models.consumer.objects.membership.MembershipInclude import com.pubnub.api.models.consumer.objects.membership.PNChannelMembership import com.pubnub.api.models.consumer.pubsub.objects.PNDeleteMembershipEventMessage import com.pubnub.api.models.consumer.pubsub.objects.PNSetMembershipEvent @@ -35,6 +35,8 @@ data class MembershipImpl( override val custom: Map?, override val updated: String?, override val eTag: String?, + override val status: String?, + override val type: String?, ) : Membership { override val lastReadMessageTimetoken: Long? get() { @@ -51,12 +53,18 @@ data class MembershipImpl( log.pnError(NO_SUCH_MEMBERSHIP_EXISTS) } chat.pubNub.setMemberships( - uuid = user.id, + userId = user.id, channels = listOf(PNChannelMembership.Partial(channel.id, custom)), - includeCustom = true, - includeCount = true, - includeType = true, - includeChannelDetails = PNChannelDetailsLevel.CHANNEL_WITH_CUSTOM, + include = MembershipInclude( + includeCustom = true, + includeStatus = true, + includeType = true, + includeTotalCount = true, + includeChannel = true, + includeChannelCustom = true, + includeChannelType = true, + includeChannelStatus = true + ), filter = filterThisChannel() ).then { pnChannelMembershipArrayResult -> fromMembershipDTO(chat, pnChannelMembershipArrayResult.data.first(), user) @@ -108,14 +116,34 @@ data class MembershipImpl( chat, channel, user, - update.custom?.value ?: custom, + update.custom.let { newCustom -> + if (newCustom != null) { + newCustom.value + } else { + custom + } + }, update.updated, - update.eTag + update.eTag, + update.status.let { newStatus -> + if (newStatus != null) { + newStatus.value + } else { + status + } + }, + update.type.let { newType -> + if (newType != null) { + newType.value + } else { + type + } + } ) } private fun exists(): PNFuture = - chat.pubNub.getMemberships(uuid = user.id, filter = filterThisChannel()).then { + chat.pubNub.getMemberships(userId = user.id, filter = filterThisChannel()).then { it.data.isNotEmpty() } @@ -147,11 +175,13 @@ data class MembershipImpl( previousMembership?.let { it + message.data } ?: MembershipImpl( chat, - user = membership.user, channel = membership.channel, + user = membership.user, custom = message.data.custom?.value, updated = message.data.updated, - eTag = message.data.eTag + eTag = message.data.eTag, + status = message.data.status?.value, + type = message.data.type?.value, ) } is PNDeleteMembershipEventMessage -> null @@ -184,7 +214,9 @@ data class MembershipImpl( user, channelMembership.custom?.value, channelMembership.updated, - channelMembership.eTag + channelMembership.eTag, + channelMembership.status?.value, + channelMembership.type?.value, ) internal fun fromChannelMemberDTO(chat: ChatInternal, userMembership: PNMember, channel: Channel) = @@ -195,6 +227,8 @@ data class MembershipImpl( userMembership.custom?.value, userMembership.updated, userMembership.eTag, + userMembership.status?.value, + userMembership.type?.value, ) } } diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/UserImpl.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/UserImpl.kt index 75d5ec8d..bd305b2e 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/UserImpl.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/UserImpl.kt @@ -5,7 +5,7 @@ import com.pubnub.api.PubNubException import com.pubnub.api.models.consumer.objects.PNMembershipKey import com.pubnub.api.models.consumer.objects.PNPage import com.pubnub.api.models.consumer.objects.PNSortKey -import com.pubnub.api.models.consumer.objects.membership.PNChannelDetailsLevel +import com.pubnub.api.models.consumer.objects.membership.MembershipInclude import com.pubnub.api.models.consumer.objects.membership.PNChannelMembership import com.pubnub.api.models.consumer.objects.membership.PNChannelMembershipArrayResult import com.pubnub.api.models.consumer.objects.uuid.PNUUIDMetadata @@ -98,15 +98,21 @@ data class UserImpl( val effectiveFilter: String = filter?.let { "$internalModerationFilter && ($filter)" } ?: internalModerationFilter return chat.pubNub.getMemberships( - uuid = id, + userId = id, limit = limit, page = page, filter = effectiveFilter, sort = sort, - includeCount = true, - includeCustom = true, - includeType = true, - includeChannelDetails = getChannelDetailsType(true) + include = MembershipInclude( + includeCustom = true, + includeStatus = true, + includeType = true, + includeTotalCount = true, + includeChannel = true, + includeChannelCustom = true, + includeChannelType = true, + includeChannelStatus = true + ) ).then { pnChannelMembershipArrayResult -> MembershipsResponse( next = pnChannelMembershipArrayResult.next, @@ -196,26 +202,24 @@ data class UserImpl( } return chat.pubNub.getMemberships( - uuid = id, + userId = id, limit = limit, page = page, filter = filter, sort = sort, - includeCount = true, - includeCustom = true, - includeChannelDetails = PNChannelDetailsLevel.CHANNEL_WITH_CUSTOM, - includeType = true + include = MembershipInclude( + includeCustom = true, + includeStatus = true, + includeType = true, + includeTotalCount = true, + includeChannel = true, + includeChannelCustom = true, + includeChannelType = true, + includeChannelStatus = true + ) ) } - private fun getChannelDetailsType(includeChannelWithCustom: Boolean): PNChannelDetailsLevel { - return if (includeChannelWithCustom) { - PNChannelDetailsLevel.CHANNEL_WITH_CUSTOM - } else { - PNChannelDetailsLevel.CHANNEL - } - } - private fun getMembershipsFromResult( pnChannelMembershipArrayResult: PNChannelMembershipArrayResult, user: User, diff --git a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/channel/BaseChannel.kt b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/channel/BaseChannel.kt index bc8aa0bd..848d547c 100644 --- a/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/channel/BaseChannel.kt +++ b/pubnub-chat-impl/src/commonMain/kotlin/com/pubnub/chat/internal/channel/BaseChannel.kt @@ -14,10 +14,10 @@ import com.pubnub.api.models.consumer.objects.PNMemberKey import com.pubnub.api.models.consumer.objects.PNPage import com.pubnub.api.models.consumer.objects.PNSortKey import com.pubnub.api.models.consumer.objects.channel.PNChannelMetadata +import com.pubnub.api.models.consumer.objects.member.MemberInclude import com.pubnub.api.models.consumer.objects.member.PNMember import com.pubnub.api.models.consumer.objects.member.PNMemberArrayResult -import com.pubnub.api.models.consumer.objects.member.PNUUIDDetailsLevel -import com.pubnub.api.models.consumer.objects.membership.PNChannelDetailsLevel +import com.pubnub.api.models.consumer.objects.membership.MembershipInclude import com.pubnub.api.models.consumer.objects.membership.PNChannelMembership import com.pubnub.api.models.consumer.objects.membership.PNChannelMembershipArrayResult import com.pubnub.api.models.consumer.pubsub.objects.PNDeleteChannelMetadataEventMessage @@ -371,12 +371,18 @@ abstract class BaseChannel( } else { chat.pubNub.setMemberships( channels = listOf(PNChannelMembership.Partial(this.id)), - uuid = user.id, - includeChannelDetails = PNChannelDetailsLevel.CHANNEL_WITH_CUSTOM, - includeCustom = true, - includeCount = true, - includeType = true, + userId = user.id, filter = channelFilterString, + include = MembershipInclude( + includeCustom = true, + includeStatus = false, + includeType = false, + includeTotalCount = true, + includeChannel = true, + includeChannelCustom = true, + includeChannelType = true, + includeChannelStatus = false + ) ).then { setMembershipsResult -> MembershipImpl.fromMembershipDTO(chat, setMembershipsResult.data.first(), user) }.thenAsync { membership -> @@ -397,10 +403,16 @@ abstract class BaseChannel( return chat.pubNub.setChannelMembers( this.id, users.map { PNMember.Partial(it.id) }, - includeCustom = true, - includeCount = true, - includeType = true, - includeUUIDDetails = PNUUIDDetailsLevel.UUID_WITH_CUSTOM, + include = MemberInclude( + includeCustom = true, + includeStatus = false, + includeType = false, + includeTotalCount = true, + includeUser = true, + includeUserCustom = true, + includeUserType = true, + includeUserStatus = false + ), filter = users.joinToString(" || ") { it.uuidFilterString } ).thenAsync { memberArrayResult: PNMemberArrayResult -> chat.pubNub.time().thenAsync { time: PNTimeResult -> @@ -428,10 +440,16 @@ abstract class BaseChannel( page = page, filter = filter, sort = sort, - includeCustom = true, - includeCount = true, - includeType = true, - includeUUIDDetails = PNUUIDDetailsLevel.UUID_WITH_CUSTOM, + include = MemberInclude( + includeCustom = true, + includeStatus = false, + includeType = false, + includeTotalCount = true, + includeUser = true, + includeUserCustom = true, + includeUserType = true, + includeUserStatus = false + ), ).then { it: PNMemberArrayResult -> MembersResponse( it.next, @@ -484,11 +502,17 @@ abstract class BaseChannel( custom ) ), // todo should null overwrite? Waiting for optionals? - includeChannelDetails = PNChannelDetailsLevel.CHANNEL_WITH_CUSTOM, - includeCustom = true, - includeCount = true, - includeType = true, filter = channelFilterString, + include = MembershipInclude( + includeCustom = true, + includeStatus = false, + includeType = false, + includeTotalCount = true, + includeChannel = true, + includeChannelCustom = true, + includeChannelType = true, + includeChannelStatus = false + ) ).thenAsync { membershipArray: PNChannelMembershipArrayResult -> val resultDisconnect = callback?.let { connect(it) } @@ -505,7 +529,7 @@ abstract class BaseChannel( } // there is a discrepancy between KMP and JS. There is no unsubscribe here. This is agreed and will be changed in JS Chat - override fun leave(): PNFuture = chat.pubNub.removeMemberships(channels = listOf(id), includeType = false).then { Unit } + override fun leave(): PNFuture = chat.pubNub.removeMemberships(channels = listOf(id), include = MembershipInclude()).then { Unit } override fun getPinnedMessage(): PNFuture { val pinnedMessageTimetoken = this.custom?.get(PINNED_MESSAGE_TIMETOKEN).tryLong() ?: return null.asFuture() @@ -721,10 +745,16 @@ abstract class BaseChannel( page = page, filter = user?.let { "uuid.id == '${user.id}'" }, sort = sort, - includeCount = true, - includeCustom = true, - includeUUIDDetails = PNUUIDDetailsLevel.UUID_WITH_CUSTOM, - includeType = true + include = MemberInclude( + includeCustom = true, + includeStatus = false, + includeType = false, + includeTotalCount = true, + includeUser = true, + includeUserCustom = true, + includeUserType = true, + includeUserStatus = false + ), ) } diff --git a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/integration/ChatIntegrationTest.kt b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/integration/ChatIntegrationTest.kt index 8530bcea..7d9ed19e 100644 --- a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/integration/ChatIntegrationTest.kt +++ b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/integration/ChatIntegrationTest.kt @@ -196,7 +196,7 @@ class ChatIntegrationTest : BaseChatIntegrationTest() { val channelId02 = channel02.id val membership01: ChannelMembershipInput = PNChannelMembership.Partial(channelId01, custom) val membership02: ChannelMembershipInput = PNChannelMembership.Partial(channelId02) - chat.pubNub.setMemberships(channels = listOf(membership01, membership02), uuid = chat.currentUser.id, includeType = false).await() + chat.pubNub.setMemberships(channels = listOf(membership01, membership02), userId = chat.currentUser.id).await() // to each channel add two messages(we want to check if last message will be taken by fetchMessages with limit = 1) channel01.sendText("message01In$channelId01").await() @@ -627,10 +627,10 @@ class ChatIntegrationTest : BaseChatIntegrationTest() { val unbanned = CompletableDeferred() val restrictionBan = Restriction(userId = userId, channelId = channelId, ban = true, reason = "rude") val restrictionUnban = Restriction(userId = userId, channelId = channelId, ban = false, mute = false, reason = "ok") - pubnub.test(backgroundScope, checkAllEvents = false) { + pubnubPamServer.test(backgroundScope, checkAllEvents = false) { var removeListenerAndUnsubscribe: AutoCloseable? = null - pubnub.awaitSubscribe(channels = listOf(INTERNAL_USER_MODERATION_CHANNEL_PREFIX + userId)) { - removeListenerAndUnsubscribe = chat.listenForEvents( + pubnubPamServer.awaitSubscribe(channels = listOf(INTERNAL_USER_MODERATION_CHANNEL_PREFIX + userId)) { + removeListenerAndUnsubscribe = chatPamServer.listenForEvents( type = EventContent.Moderation::class, channelId = INTERNAL_USER_MODERATION_CHANNEL_PREFIX + userId ) { event: Event -> @@ -643,9 +643,9 @@ class ChatIntegrationTest : BaseChatIntegrationTest() { } } - chat.setRestrictions(restrictionBan).await() + chatPamServer.setRestrictions(restrictionBan).await() banned.await() - chat.setRestrictions(restrictionUnban).await() + chatPamServer.setRestrictions(restrictionUnban).await() unbanned.await() removeListenerAndUnsubscribe?.close() diff --git a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/ChannelTest.kt b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/ChannelTest.kt index f843756a..9f3d38ff 100644 --- a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/ChannelTest.kt +++ b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/ChannelTest.kt @@ -22,7 +22,7 @@ import com.pubnub.api.models.consumer.objects.PNPage import com.pubnub.api.models.consumer.objects.PNSortKey import com.pubnub.api.models.consumer.objects.channel.PNChannelMetadata import com.pubnub.api.models.consumer.objects.channel.PNChannelMetadataResult -import com.pubnub.api.models.consumer.objects.member.PNUUIDDetailsLevel +import com.pubnub.api.models.consumer.objects.member.MemberInclude import com.pubnub.api.utils.Clock import com.pubnub.api.utils.Instant import com.pubnub.api.utils.PatchValue @@ -732,10 +732,7 @@ class ChannelTest : BaseTest() { any(), any(), any(), - any(), - any(), - any(), - any() + include = any(), ) } returns fetChannelMembers @@ -748,10 +745,10 @@ class ChannelTest : BaseTest() { page = page, filter = null, sort = sort, - includeCount = true, - includeCustom = true, - includeUUIDDetails = PNUUIDDetailsLevel.UUID_WITH_CUSTOM, - includeType = true + include = matching { + it.includeCustom && !it.includeStatus && !it.includeType && it.includeTotalCount && + it.includeUser && it.includeUserCustom && it.includeUserType && !it.includeUserStatus + }, ) } } @@ -771,10 +768,7 @@ class ChannelTest : BaseTest() { any(), any(), any(), - any(), - any(), - any(), - any() + include = any(), ) } returns getChannelMembers @@ -787,10 +781,10 @@ class ChannelTest : BaseTest() { page = page, filter = "uuid.id == 'userId'", sort = sort, - includeCount = true, - includeCustom = true, - includeUUIDDetails = PNUUIDDetailsLevel.UUID_WITH_CUSTOM, - includeType = true + include = matching { + it.includeCustom && !it.includeStatus && !it.includeType && it.includeTotalCount && + it.includeUser && it.includeUserCustom && it.includeUserType && !it.includeUserStatus + }, ) } } diff --git a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/ChatTest.kt b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/ChatTest.kt index eb116eb1..cf40a161 100644 --- a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/ChatTest.kt +++ b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/ChatTest.kt @@ -134,12 +134,22 @@ class ChatTest : BaseTest() { @BeforeTest fun setUp() { - pnConfiguration = createPNConfiguration(UserId(userId), subscribeKey, publishKey, authToken = null) + objectUnderTest = ChatImpl(setupConfiguration(withSecretKey = false), pubnub, timerManager = timerManager) + } + + private fun setupConfiguration(withSecretKey: Boolean): ChatConfiguration { + pnConfiguration = if (withSecretKey) { + createPNConfiguration(UserId(userId), subscribeKey, publishKey, "secretKey", authToken = null) + } else { + createPNConfiguration(UserId(userId), subscribeKey, publishKey, authToken = null) + } + every { pubnub.configuration } returns pnConfiguration chatConfig = ChatConfiguration( typingTimeout = 2000.milliseconds ) - objectUnderTest = ChatImpl(chatConfig, pubnub, timerManager = timerManager) + + return chatConfig } @Test @@ -986,10 +996,7 @@ class ChatTest : BaseTest() { any(), any(), any(), - any(), - any(), - any(), - any() + include = any(), ) } returns getMembershipsEndpoint every { getMembershipsEndpoint.async(any()) } calls { (callback: Consumer>) -> @@ -1298,10 +1305,7 @@ class ChatTest : BaseTest() { any(), any(), any(), - any(), - any(), - any(), - any() + include = any(), ) } returns getMembershipsEndpoint every { getMembershipsEndpoint.async(any()) } calls { (callback: Consumer>) -> @@ -1325,6 +1329,8 @@ class ChatTest : BaseTest() { @Test fun shouldRemoveRestrictionWhenBanAndMuteIsFalse() { + objectUnderTest = ChatImpl(setupConfiguration(withSecretKey = true), pubnub, timerManager = timerManager) + val restrictedUserId = userId val restrictedChannelId = channelId val ban = false @@ -1386,8 +1392,29 @@ class ChatTest : BaseTest() { assertEquals(INTERNAL_USER_MODERATION_CHANNEL_PREFIX + restrictedUserId, actualModerationEventChannelId) } + @Test + fun shouldThrowExceptionWhenSecretKeyIsNotSet() { + val restriction = Restriction( + userId = "userId", + channelId = "channelId", + ban = true, + mute = true, + reason = "rude" + ) + + objectUnderTest.setRestrictions(restriction).async { result: Result -> + assertTrue(result.isFailure) + assertEquals( + "Moderation restrictions can only be set by clients initialized with a Secret Key.", + result.exceptionOrNull()?.message + ) + } + } + @Test fun shouldAddRestrictionWhenBanIsTrue() { + objectUnderTest = ChatImpl(setupConfiguration(withSecretKey = true), pubnub, timerManager = timerManager) + val restrictedUserId = userId val restrictedChannelId = channelId val ban = true @@ -1420,7 +1447,8 @@ class ChatTest : BaseTest() { every { pubnub.setChannelMembers( channel = capture(channelIdSlot), - uuids = capture(userIdsSlot) + users = capture(userIdsSlot), + include = any() ) } returns manageChannelMembersEndpoint every { manageChannelMembersEndpoint.async(any()) } calls { (callback: Consumer>) -> diff --git a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/MembershipTest.kt b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/MembershipTest.kt index d68ec14a..3f4dfeae 100644 --- a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/MembershipTest.kt +++ b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/MembershipTest.kt @@ -40,6 +40,8 @@ class MembershipTest { user, mapOf("lastReadMessageTimetoken" to lastMessageTimetoken, "other_stuff" to "some string"), null, + null, + null, null ) @@ -55,6 +57,8 @@ class MembershipTest { user, mapOf(), null, + null, + null, null ) assertNull(membership.getUnreadMessagesCount().await()) @@ -69,6 +73,8 @@ class MembershipTest { user, mapOf("lastReadMessageTimetoken" to lastMessageTimetoken), null, + null, + null, null ) diff --git a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/UserTest.kt b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/UserTest.kt index ab111a28..d8106922 100644 --- a/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/UserTest.kt +++ b/pubnub-chat-impl/src/commonTest/kotlin/com/pubnub/kmp/UserTest.kt @@ -6,7 +6,7 @@ import com.pubnub.api.models.consumer.objects.PNMembershipKey import com.pubnub.api.models.consumer.objects.PNPage import com.pubnub.api.models.consumer.objects.PNSortKey import com.pubnub.api.models.consumer.objects.channel.PNChannelMetadata -import com.pubnub.api.models.consumer.objects.membership.PNChannelDetailsLevel +import com.pubnub.api.models.consumer.objects.membership.MembershipInclude import com.pubnub.api.models.consumer.objects.membership.PNChannelMembership import com.pubnub.api.models.consumer.objects.membership.PNChannelMembershipArrayResult import com.pubnub.api.models.consumer.objects.uuid.PNUUIDMetadata @@ -27,6 +27,7 @@ import dev.mokkery.answering.calls import dev.mokkery.answering.returns import dev.mokkery.every import dev.mokkery.matcher.any +import dev.mokkery.matcher.matching import dev.mokkery.mock import dev.mokkery.verify import kotlin.test.BeforeTest @@ -190,15 +191,12 @@ class UserTest { every { chat.pubNub } returns pubNub every { pubNub.getMemberships( - uuid = any(), + userId = any(), limit = any(), page = any(), filter = any(), sort = any(), - includeCount = any(), - includeCustom = any(), - includeChannelDetails = any(), - includeType = any() + include = any() ) } returns getMembershipsEndpoint every { getMembershipsEndpoint.async(any()) } calls { (callback1: Consumer>) -> @@ -225,15 +223,12 @@ class UserTest { every { chat.pubNub } returns pubNub every { pubNub.getMemberships( - uuid = any(), + userId = any(), limit = any(), page = any(), filter = any(), sort = any(), - includeCount = any(), - includeCustom = any(), - includeChannelDetails = any(), - includeType = any() + include = any(), ) } returns getMembershipsEndpoint every { getMembershipsEndpoint.async(any()) } calls { (callback1: Consumer>) -> @@ -251,15 +246,16 @@ class UserTest { // then verify { pubNub.getMemberships( - uuid = id, + userId = id, limit = limit, page = page, filter = expectedFilter, sort = sort, - includeCount = true, - includeCustom = true, - includeChannelDetails = PNChannelDetailsLevel.CHANNEL_WITH_CUSTOM, - includeType = true + include = matching { + it.includeCustom && it.includeStatus && it.includeType && it.includeTotalCount && + it.includeChannel && it.includeChannelCustom && it.includeChannelType && + it.includeChannelStatus + } ) } } @@ -279,10 +275,7 @@ class UserTest { any(), any(), any(), - any(), - any(), - any(), - any() + include = any(), ) } returns getMemberships @@ -296,15 +289,16 @@ class UserTest { val expectedFilter = "channel.id LIKE 'PUBNUB_INTERNAL_MODERATION_*'" verify { pubNub.getMemberships( - uuid = id, + userId = id, limit = limit, page = page, filter = expectedFilter, sort = sort, - includeCount = true, - includeCustom = true, - includeChannelDetails = PNChannelDetailsLevel.CHANNEL_WITH_CUSTOM, - includeType = true + include = matching { + it.includeCustom && it.includeStatus && it.includeType && it.includeTotalCount && + it.includeChannel && it.includeChannelCustom && it.includeChannelType && + it.includeChannelStatus + } ) } } @@ -325,10 +319,7 @@ class UserTest { any(), any(), any(), - any(), - any(), - any(), - any() + include = any(), ) } returns getMemberships @@ -337,15 +328,16 @@ class UserTest { val expectedFilter = "channel.id == 'PUBNUB_INTERNAL_MODERATION_channelId'" verify { pubNub.getMemberships( - uuid = id, + userId = id, limit = limit, page = page, filter = expectedFilter, sort = sort, - includeCount = true, - includeCustom = true, - includeChannelDetails = PNChannelDetailsLevel.CHANNEL_WITH_CUSTOM, - includeType = true + include = matching { + it.includeCustom && it.includeStatus && it.includeType && it.includeTotalCount && + it.includeChannel && it.includeChannelCustom && + it.includeChannelType && it.includeChannelStatus + } ) } } @@ -367,7 +359,11 @@ class UserTest { val user = createUser(chat) val expectedUser = user.copy(name = randomString(), email = randomString()) - val newUser = user + PNUUIDMetadata(expectedUser.id, name = PatchValue.of(expectedUser.name), email = PatchValue.of(expectedUser.email)) + val newUser = user + PNUUIDMetadata( + expectedUser.id, + name = PatchValue.of(expectedUser.name), + email = PatchValue.of(expectedUser.email) + ) assertEquals(expectedUser, newUser) } diff --git a/pubnub-chat-impl/src/jsMain/kotlin/MembershipJs.kt b/pubnub-chat-impl/src/jsMain/kotlin/MembershipJs.kt index 154c9f3f..363aba89 100644 --- a/pubnub-chat-impl/src/jsMain/kotlin/MembershipJs.kt +++ b/pubnub-chat-impl/src/jsMain/kotlin/MembershipJs.kt @@ -16,6 +16,8 @@ class MembershipJs internal constructor(internal val membership: Membership, int val custom get() = membership.custom?.toJsMap() val updated by membership::updated val eTag by membership::eTag + val status by membership::status + val type by membership::type val lastReadMessageTimetoken: String? get() = membership.lastReadMessageTimetoken?.toString() @@ -56,6 +58,8 @@ class MembershipJs internal constructor(internal val membership: Membership, int "custom" to custom, "updated" to updated, "eTag" to eTag, + "status" to status, + "type" to type, "lastReadMessageTimetoken" to lastReadMessageTimetoken ) } diff --git a/src/jsMain/resources/index.d.ts b/src/jsMain/resources/index.d.ts index 27ea9eb1..af39f0f9 100644 --- a/src/jsMain/resources/index.d.ts +++ b/src/jsMain/resources/index.d.ts @@ -1,7 +1,7 @@ /// import PubNub from "pubnub"; import { GetMembershipsParametersv2, GetChannelMembersParameters, ObjectCustom, SetMembershipsParameters, ChannelMetadataObject, PublishParameters, SendFileParameters } from "pubnub"; -type MembershipFields = Pick; +type MembershipFields = Pick; declare class Membership { private chat; readonly channel: Channel; @@ -9,6 +9,8 @@ declare class Membership { readonly custom: ObjectCustom | null | undefined; readonly updated: string; readonly eTag: string; + readonly status?: string; + readonly type?: string; update({ custom }: { custom: ObjectCustom; }): Promise;