Repository: ktorio/ktor
File: ktor-server/ktor-server-plugins/ktor-server-sse/common/src/io/ktor/server/sse/ServerSSESession.kt (Kotlin)

Code before:
public suspend inline fun <reified T : Any> ServerSSESessionWithSerialization.send(
    data: T? = null,
    event: String? = null,
    id: String? = null,
    retry: Long? = null,
    comments: String? = null
) {
    send(TypedServerSentEvent(data, event, id, retry, comments))
}

public suspend inline fun <reified T : Any> ServerSSESessionWithSerialization.send(data: T) {
    send(ServerSentEvent(serializer(typeInfo<T>(), data)))
}

/**
 * Starts a heartbeat for the ServerSSESession.
 *
 * The heartbeat will send the specified [Heartbeat.event] at the specified [Heartbeat.duration] interval
 * as long as the session is active.
 *
 * @param heartbeatConfig a lambda that configures the [Heartbeat] object used for the heartbeat.
 */
public fun ServerSSESession.heartbeat(heartbeatConfig: Heartbeat.() -> Unit) {
    val heartbeat = Heartbeat().apply(heartbeatConfig)
    val heartbeatJob = Job()
    launch(heartbeatJob) {
        while (true) {
            send(heartbeat.event)
            delay(heartbeat.duration)
        }
    }
    call.attributes.put(heartbeatJobKey, heartbeatJob)
}

internal val heartbeatJobKey = AttributeKey<Job>("HeartbeatJobAttributeKey")

/**
 * Represents a heartbeat configuration for a [ServerSSESession].
 *
 * @property duration the duration between heartbeat events, default is 30 seconds.
 * @property event the [ServerSentEvent] to be sent as the heartbeat, default is a [ServerSentEvent] with the comment "heartbeat".
 */
public class Heartbeat {
    public var duration: Duration = 30.seconds
    public var event: ServerSentEvent = ServerSentEvent(comments = "heartbeat")
}


Review comment (bug):
Just wondering why we don't add a `CoroutineName` to launched coroutines? In theory, this would simplify further debugging. Both, for us and for users.
@marychatte, @bjhham, WDYT?

Code after:
    id: String? = null,
    retry: Long? = null,
    comments: String? = null
) {
    send(TypedServerSentEvent(data, event, id, retry, comments))
}

public suspend inline fun <reified T : Any> ServerSSESessionWithSerialization.send(data: T) {
    send(ServerSentEvent(serializer(typeInfo<T>(), data)))
}

/**
 * Starts a heartbeat for the ServerSSESession.
 *
 * The heartbeat will send the specified [Heartbeat.event] at the specified [Heartbeat.duration] interval
 * as long as the session is active.
 *
 * @param heartbeatConfig a lambda that configures the [Heartbeat] object used for the heartbeat.
 */
public fun ServerSSESession.heartbeat(heartbeatConfig: Heartbeat.() -> Unit) {
    val heartbeat = Heartbeat().apply(heartbeatConfig)
    val heartbeatJob = Job(call.coroutineContext[Job])
    launch(heartbeatJob + CoroutineName("sse-heartbeat")) {
        while (true) {
            send(heartbeat.event)
            delay(heartbeat.duration)
        }
    }
    call.attributes.put(heartbeatJobKey, heartbeatJob)
}

internal val heartbeatJobKey = AttributeKey<Job>("HeartbeatJobAttributeKey")

/**
 * Represents a heartbeat configuration for a [ServerSSESession].
 *
 * @property duration the duration between heartbeat events, default is 30 seconds.
 * @property event the [ServerSentEvent] to be sent as the heartbeat, default is a [ServerSentEvent] with the comment "heartbeat".
 */
public class Heartbeat {
    public var duration: Duration = 30.seconds
    public var event: ServerSentEvent = ServerSentEvent(comments = "heartbeat")
}