All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.com.caesarealabs.rpc4k.runtime.api.EventClient.kt Maven / Gradle / Ivy

package com.caesarealabs.rpc4k.runtime.api

import com.caesarealabs.logging.PrintLogging
import com.caesarealabs.rpc4k.runtime.platform.ConcurrentMutableMap
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch

/**
 * Defines how to send messages to the server, and listen to responses.
 * Extending [AbstractEventClient] is usually good enough
 */
public interface EventClient {
    /**
     * Sends arbitrary information to the event accepting server. This is used to set up subscription and unsubscription.
     */
    public suspend fun send(message: ByteArray)

    /**
     * Creates a _cold_ `Flow` that listens to new events
     */
    public fun createFlow(subscribeMessage: ByteArray, unsubscribeMessage: ByteArray, listenerId: String): Flow
}

/**
 * Utility that handles most of the implementation of a usual [EventClient]
 */
public abstract class AbstractEventClient: EventClient {
    /**
     * Used in conjunction with [EventClient.createFlowUtil] to manage subscription flows.
     * Usually, Values are pushed into the flows in the websocket implementation.
     */
    private val activeFlows: MutableMap Unit> = ConcurrentMutableMap()

    internal fun handleMessage(message: S2CEventMessage) {
        when (message) {
            is S2CEventMessage.Emitted -> {
                val listener = activeFlows[message.listenerId]
                if (listener != null) {
                    listener(message.payload)
                } else {
                    PrintLogging.logWarn { "Could not find listener for id '${message.listenerId}', the subscription may still open on the server" }
                }
            }

            is S2CEventMessage.SubscriptionError -> {
                error("Failed to subscribe to event: ${message.error}")
            }
        }
    }

    final override fun createFlow(subscribeMessage: ByteArray, unsubscribeMessage: ByteArray, listenerId: String): Flow {
        return callbackFlow {
            // Register event for self
            activeFlows[listenerId] = {
                trySend(it)
                    .onFailure { e ->
                        if(e is CancellationException) {
                            cancel(e)
                        } else {
                            if (e != null) throw e
                            else PrintLogging.logWarn { "Failed to update event listener" }
                        }
                    }
            }
            // Tell the server to start sending events
            [email protected](subscribeMessage)
            awaitClose {
                launch {
                    // Tell the server to stop sending events
                    [email protected](unsubscribeMessage)
                    // Remove event reference from self
                    activeFlows.remove(listenerId)
                }
            }
        }
    }
}







© 2015 - 2024 Weber Informatics LLC | Privacy Policy