Skip to content

Commit

Permalink
Add custom event handling
Browse files Browse the repository at this point in the history
  • Loading branch information
wkal-pubnub committed Aug 22, 2024
1 parent 2f4ec6c commit 5220bc2
Show file tree
Hide file tree
Showing 12 changed files with 119 additions and 75 deletions.
23 changes: 4 additions & 19 deletions pubnub-chat-api/api/pubnub-chat-api.api
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ public final class com/pubnub/chat/config/LogLevel : java/lang/Enum {

public final class com/pubnub/chat/config/PushNotificationsConfig {
public fun <init> (ZLjava/lang/String;Lcom/pubnub/api/enums/PNPushType;Ljava/lang/String;Lcom/pubnub/api/enums/PNPushEnvironment;)V
public synthetic fun <init> (ZLjava/lang/String;Lcom/pubnub/api/enums/PNPushType;Ljava/lang/String;Lcom/pubnub/api/enums/PNPushEnvironment;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getApnsEnvironment ()Lcom/pubnub/api/enums/PNPushEnvironment;
public final fun getApnsTopic ()Ljava/lang/String;
public final fun getDeviceGateway ()Lcom/pubnub/api/enums/PNPushType;
Expand Down Expand Up @@ -439,28 +440,12 @@ public final class com/pubnub/chat/types/EventContent$Companion {
}

public final class com/pubnub/chat/types/EventContent$Custom : com/pubnub/chat/types/EventContent {
public static final field Companion Lcom/pubnub/chat/types/EventContent$Custom$Companion;
public fun <init> (Ljava/lang/Object;Lcom/pubnub/chat/types/EmitEventMethod;)V
public synthetic fun <init> (Ljava/lang/Object;Lcom/pubnub/chat/types/EmitEventMethod;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getData ()Ljava/lang/Object;
public fun <init> (Ljava/util/Map;Lcom/pubnub/chat/types/EmitEventMethod;)V
public synthetic fun <init> (Ljava/util/Map;Lcom/pubnub/chat/types/EmitEventMethod;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getData ()Ljava/util/Map;
public final fun getMethod ()Lcom/pubnub/chat/types/EmitEventMethod;
}

public synthetic class com/pubnub/chat/types/EventContent$Custom$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lcom/pubnub/chat/types/EventContent$Custom$$serializer;
public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/pubnub/chat/types/EventContent$Custom;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/pubnub/chat/types/EventContent$Custom;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}

public final class com/pubnub/chat/types/EventContent$Custom$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public final class com/pubnub/chat/types/EventContent$Invite : com/pubnub/chat/types/EventContent {
public static final field Companion Lcom/pubnub/chat/types/EventContent$Invite$Companion;
public fun <init> (Lcom/pubnub/chat/types/ChannelType;Ljava/lang/String;)V
Expand Down
4 changes: 2 additions & 2 deletions pubnub-chat-api/src/commonMain/kotlin/com/pubnub/chat/Chat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ interface Chat {
fun <T : EventContent> listenForEvents(
type: KClass<T>,
channelId: String,
customMethod: EmitEventMethod? = null,
customMethod: EmitEventMethod = EmitEventMethod.PUBLISH,
callback: (event: Event<T>) -> Unit
): AutoCloseable

Expand Down Expand Up @@ -194,7 +194,7 @@ interface Chat {

inline fun <reified T : EventContent> Chat.listenForEvents(
channelId: String,
customMethod: EmitEventMethod? = null,
customMethod: EmitEventMethod = EmitEventMethod.PUBLISH,
noinline callback: (event: Event<T>) -> Unit
): AutoCloseable {
return listenForEvents(T::class, channelId, customMethod, callback)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import com.pubnub.chat.types.ChannelType
import kotlin.time.Duration
import kotlin.time.Duration.Companion.ZERO
import kotlin.time.Duration.Companion.seconds
import kotlin.time.Duration.Companion.milliseconds

interface ChatConfiguration {
val logLevel: LogLevel
Expand Down Expand Up @@ -59,7 +58,3 @@ fun RateLimitPerChannel(
ChannelType.PUBLIC to public,
ChannelType.UNKNOWN to unknown,
)

fun main() {
ChatConfiguration(rateLimitPerChannel = RateLimitPerChannel(direct = 1.seconds, group = 2.seconds))
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.pubnub.chat.types

import com.pubnub.api.JsonElement
import com.pubnub.chat.restrictions.RestrictionType
import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
Expand Down Expand Up @@ -44,10 +43,8 @@ sealed class EventContent {
@SerialName("invite")
class Invite(val channelType: ChannelType, val channelId: String) : EventContent()

@Serializable
@SerialName("custom")
class Custom(
@Contextual val data: Any,
val data: Map<String, Any?>,
@Transient val method: EmitEventMethod = EmitEventMethod.PUBLISH
) : EventContent()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package com.pubnub.chat.internal
import com.benasher44.uuid.uuid4
import com.pubnub.api.PubNub
import com.pubnub.api.PubNubException
import com.pubnub.api.asMap
import com.pubnub.api.asString
import com.pubnub.api.decode
import com.pubnub.api.enums.PNPushType
import com.pubnub.api.models.consumer.PNBoundedPage
import com.pubnub.api.models.consumer.PNPublishResult
Expand Down Expand Up @@ -580,15 +583,23 @@ class ChatImpl(
override fun <T : EventContent> listenForEvents(
type: KClass<T>,
channelId: String,
customMethod: EmitEventMethod?,
customMethod: EmitEventMethod,
callback: (event: Event<T>) -> Unit
): AutoCloseable {
val handler = fun(_: PubNub, pnEvent: PNEvent) {
if (pnEvent.channel != channelId) {
return
}
val message = (pnEvent as? MessageResult)?.message ?: return
val eventContent: EventContent = PNDataEncoder.decode<EventContent>(message)
val eventContent: EventContent = try {
PNDataEncoder.decode<EventContent>(message)
} catch (e: Exception) {
if (message.asMap()?.get("type")?.asString() == "custom") {
EventContent.Custom((message.decode() as Map<String, Any?>) - "type")
} else {
throw e
}
}

@Suppress("UNCHECKED_CAST")
val payload = eventContent as? T ?: return
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.pubnub.chat.internal

import com.pubnub.api.asMap
import com.pubnub.api.asString
import com.pubnub.api.decode
import com.pubnub.api.models.consumer.history.PNFetchMessageItem
import com.pubnub.chat.Chat
import com.pubnub.chat.Event
Expand All @@ -19,10 +22,19 @@ class EventImpl<T : EventContent>(
channelId: String,
pnFetchMessageItem: PNFetchMessageItem
): Event<EventContent> {
val eventContent: EventContent = try {
PNDataEncoder.decode<EventContent>(pnFetchMessageItem.message)
} catch (e: Exception) {
if (pnFetchMessageItem.message.asMap()?.get("type")?.asString() == "custom") {
EventContent.Custom((pnFetchMessageItem.message.decode() as Map<String, Any?>) - "type")
} else {
throw e
}
}
return EventImpl(
chat = chat,
timetoken = pnFetchMessageItem.timetoken ?: 0,
payload = PNDataEncoder.decode<EventContent>(pnFetchMessageItem.message),
payload = eventContent,
channelId = channelId,
userId = pnFetchMessageItem.uuid ?: "unknown-user"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,14 @@ data class MessageImpl(

companion object {
internal fun fromDTO(chat: ChatInternal, pnMessageResult: PNMessageResult): Message {
val content = chat.config.customPayloads?.getMessageResponseBody?.invoke(pnMessageResult.message) ?: defaultGetMessageResponseBody(pnMessageResult.message)
val content =
chat.config.customPayloads?.getMessageResponseBody?.invoke(pnMessageResult.message)
?: defaultGetMessageResponseBody(pnMessageResult.message)
?: EventContent.UnknownMessageFormat(pnMessageResult.message)
return MessageImpl(
chat,
pnMessageResult.timetoken!!,
content!!, // todo handle malformed content (then this is null)
content,
pnMessageResult.channel,
pnMessageResult.publisher!!,
meta = pnMessageResult.userMetadata?.decode() as? Map<String, Any>,
Expand All @@ -54,12 +57,15 @@ data class MessageImpl(
}

internal fun fromDTO(chat: ChatInternal, messageItem: PNFetchMessageItem, channelId: String): Message {
val content = chat.config.customPayloads?.getMessageResponseBody?.invoke(messageItem.message) ?: defaultGetMessageResponseBody(messageItem.message)
val content =
chat.config.customPayloads?.getMessageResponseBody?.invoke(messageItem.message)
?: defaultGetMessageResponseBody(messageItem.message)
?: EventContent.UnknownMessageFormat(messageItem.message)

return MessageImpl(
chat,
messageItem.timetoken!!,
content!!,
content,
channelId,
messageItem.uuid!!,
messageItem.actions,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.pubnub.chat.internal.message

import com.pubnub.api.asString
import com.pubnub.api.decode
import com.pubnub.api.models.consumer.history.PNFetchMessageItem
import com.pubnub.api.models.consumer.history.PNFetchMessageItem.Action
Expand All @@ -10,6 +9,7 @@ import com.pubnub.chat.ThreadMessage
import com.pubnub.chat.internal.ChatImpl
import com.pubnub.chat.internal.ChatInternal
import com.pubnub.chat.internal.channel.ChannelImpl
import com.pubnub.chat.internal.defaultGetMessageResponseBody
import com.pubnub.chat.internal.error.PubNubErrorMessage.PARENT_CHANNEL_DOES_NOT_EXISTS
import com.pubnub.chat.internal.serialization.PNDataEncoder
import com.pubnub.chat.internal.util.pnError
Expand Down Expand Up @@ -53,11 +53,15 @@ data class ThreadMessageImpl(
private val log = logging()

internal fun fromDTO(chat: ChatImpl, pnMessageResult: PNMessageResult, parentChannelId: String): ThreadMessage {
val content =
chat.config.customPayloads?.getMessageResponseBody?.invoke(pnMessageResult.message)
?: defaultGetMessageResponseBody(pnMessageResult.message)
?: EventContent.UnknownMessageFormat(pnMessageResult.message)
return ThreadMessageImpl(
chat,
parentChannelId,
pnMessageResult.timetoken!!,
PNDataEncoder.decode<EventContent>(pnMessageResult.message) as EventContent.TextMessageContent,
content,
pnMessageResult.channel,
pnMessageResult.publisher!!,
meta = pnMessageResult.userMetadata?.decode() as? Map<String, Any>,
Expand All @@ -68,19 +72,16 @@ data class ThreadMessageImpl(
}

internal fun fromDTO(chat: ChatInternal, messageItem: PNFetchMessageItem, channelId: String, parentChannelId: String): ThreadMessage {
val eventContent = try {
messageItem.message.asString()?.let { text ->
EventContent.TextMessageContent(text, null)
} ?: PNDataEncoder.decode(messageItem.message)
} catch (e: Exception) {
EventContent.UnknownMessageFormat(messageItem.message)
}
val content =
chat.config.customPayloads?.getMessageResponseBody?.invoke(messageItem.message)
?: defaultGetMessageResponseBody(messageItem.message)
?: EventContent.UnknownMessageFormat(messageItem.message)

return ThreadMessageImpl(
chat = chat,
parentChannelId = parentChannelId,
timetoken = messageItem.timetoken!!,
content = eventContent,
content = content,
channelId = channelId,
userId = messageItem.uuid!!,
actions = messageItem.actions,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import com.pubnub.chat.internal.defaultGetMessagePublishBody
import com.pubnub.chat.internal.serialization.PNDataEncoder
import com.pubnub.chat.types.EventContent
import kotlinx.serialization.InternalSerializationApi

internal fun Any?.tryLong(): Long? {
return when (this) {
Expand Down Expand Up @@ -41,10 +42,19 @@ internal fun EventContent.TextMessageContent.encodeForSending(
return finalMessage
}

@OptIn(InternalSerializationApi::class)
internal fun EventContent.encodeForSending(
mergeMessageWith: Map<String, Any>? = null,
): Map<String, Any> {
var finalMessage = PNDataEncoder.encode(this) as Map<String, Any>
): Map<String, Any?> {
var finalMessage = if (this is EventContent.Custom) {
buildMap<String, Any?> {
putAll(data)
put("type", "custom")
}
} else {
PNDataEncoder.encode(this) as Map<String, Any?>
}

if (mergeMessageWith != null) {
finalMessage = buildMap {
putAll(finalMessage)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,37 @@ import kotlin.test.assertFalse
class ChatConfigurationIntegrationTest : BaseChatIntegrationTest() {
@Test
fun custom_payloads_send_receive_msgs() = runTest {
val chat = ChatImpl(ChatConfiguration(
customPayloads = CustomPayloads(
getMessagePublishBody = { content, channelId ->
mapOf(
val chat = ChatImpl(
ChatConfiguration(
customPayloads = CustomPayloads(
getMessagePublishBody = { content, channelId ->
mapOf(
"custom" to mapOf(
"payload" to mapOf(
"text" to content.text
)
"text" to content.text
)
,
"files" to content.files,
),
"files" to content.files,
// "type" to "text"
)
},
getMessageResponseBody = { json: JsonElement ->
EventContent.TextMessageContent(
json.asMap()?.get("custom")?.asMap()?.get("payload")?.asMap()?.get("text")?.asString()!!,
json.asList()?.map {
File(
it.asMap()?.get("name")?.asString()!!,
it.asMap()?.get("id")?.asString()!!,
it.asMap()?.get("url")?.asString()!!,
it.asMap()?.get("type")?.asString(),
)
},
getMessageResponseBody = { json: JsonElement ->
EventContent.TextMessageContent(
json.asMap()?.get("custom")?.asMap()?.get("payload")?.asMap()?.get("text")?.asString()!!,
json.asList()?.map {
File(
it.asMap()?.get("name")?.asString()!!,
it.asMap()?.get("id")?.asString()!!,
it.asMap()?.get("url")?.asString()!!,
it.asMap()?.get("type")?.asString(),
)
}
)
}
)
), pubnub).initialize().await()
}
)
}
)
),
pubnub
).initialize().await()
val channel = chat.createChannel(randomString()).await()
val messageText = randomString()
val message = CompletableDeferred<Message>()
Expand All @@ -67,4 +69,4 @@ class ChatConfigurationIntegrationTest : BaseChatIntegrationTest() {
unsubscribe?.close()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.pubnub.chat.membership.MembershipsResponse
import com.pubnub.chat.message.GetUnreadMessagesCounts
import com.pubnub.chat.message.MarkAllMessageAsReadResponse
import com.pubnub.chat.types.ChannelMentionData
import com.pubnub.chat.types.EmitEventMethod
import com.pubnub.chat.types.EventContent
import com.pubnub.chat.types.GetCurrentUserMentionsResult
import com.pubnub.chat.types.GetEventsHistoryResult
Expand All @@ -31,6 +32,7 @@ import com.pubnub.kmp.CustomObject
import com.pubnub.kmp.createCustomObject
import com.pubnub.test.await
import com.pubnub.test.randomString
import com.pubnub.test.test
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.test.runTest
import tryLong
Expand Down Expand Up @@ -470,6 +472,29 @@ class ChatIntegrationTest : BaseChatIntegrationTest() {
chat.pubNub.deleteMessages(listOf(channelId01, userId))
}

@Test
fun emitEvent_with_custom() = runTest {
val channel = chat.createChannel(randomString()).await()
val event = CompletableDeferred<Event<EventContent.Custom>>()
var tt: Long = 0
pubnub.test(backgroundScope, checkAllEvents = false) {
var unsubscribe: AutoCloseable? = null
pubnub.awaitSubscribe {
unsubscribe = chat.listenForEvents<EventContent.Custom>(channel.id) {
event.complete(it)
}
}
tt = chat.emitEvent(channel.id, EventContent.Custom(mapOf("abc" to "def"), EmitEventMethod.PUBLISH)).await().timetoken
assertEquals(mapOf("abc" to "def"), event.await().payload.data)
assertFalse(channel.getMembers().await().members.any { it.user.id == chat.currentUser.id })
unsubscribe?.close()
}
delayInMillis(1000)
val eventFromHistory = chat.getEventsHistory(channel.id, tt + 1, tt).await().events.first()
require(eventFromHistory.payload is EventContent.Custom)
assertEquals(mapOf("abc" to "def"), (eventFromHistory.payload as EventContent.Custom).data)
}

private suspend fun assertPushChannels(expectedNumberOfChannels: Int) {
val pushChannels = chat.getPushChannels().await()
assertEquals(expectedNumberOfChannels, pushChannels.size)
Expand Down
Loading

0 comments on commit 5220bc2

Please sign in to comment.