Guide to Implementing Server-Sent Events (SSE) in JavaScript, Android, and iOS for Client-Side Developers

Guide to Implementing Server-Sent Events (SSE) in JavaScript, Android, and iOS for Client-Side Developers

Overview

  • Server-Sent Events(SSE) can provide effective and powerful Server Push Notifications when used in conjunction with the HTTP/2 protocol. Over the years, I have successfully and frequently used SSE in production-level scenarios for various business requirements. Recognizing the lack of examples on the internet that cater to each client's specific situations, I decided to write and introduce them myself. (All the example codes provided below have been validated at the production level.)

Using curl Command for SSE Connection and Event Reception

  • For testing and debugging purposes, it's possible to create an EventSource connection and receive events using the curl command as follows. The connection remains active until termination and receives new events.
curl -N --http2 \
    -H "Accept:text/event-stream" \
    -H "Authorization:Bearer {token}" \
  '{server-url}'

SSE Connection and Event Reception in Browser JavaScript

  • In the browser environment, it's possible to create the HTML5 standard EventSource object to receive SSE connection events. However, standard APIs cannot send custom request headers for authentication, etc. Using a third-party library as below makes this possible.
// Using the Yaffle EventSource Polyfill library to enable custom header requests not possible with vanilla EventSource
<script src="https://raw.githubusercontent.com/Yaffle/EventSource/master/src/eventsource.min.js"></script>
<script>
    // Creating the EventSource object
    const eventSource = new EventSourcePolyfill('{server-url}', {
        // Specifying custom request headers
        headers: {
            'Authorization': 'Bearer {token}'
        },
        // Specifying the name of the Last-Event-ID to be passed via QueryString
        lastEventIdQueryParameterName: 'Last-Event-ID',
        // Setting the maximum connection time in ms, longer than the maximum connection time set by the server
        heartbeatTimeout: 600000
    })

    // Writing a listener to handle Message arrival events for a specific Channel
    eventSource.addEventListener("{channel}", function(event) {
        console.log(event.data)
    })
</script>

SSE Connection and Event Reception in Android Kotlin

  • In Android and JVM environments, implementation can be done using third-party libraries as follows.
  • First, add the following to the project's root build.gradle.kts.
dependencies {
    // Using the OkHttp-based EventSource library by LaunchDarkly, known for SSE-based server push notifications in Silicon Valley
    implementation("com.launchdarkly:okhttp-eventsource:4.1.1")
}
  • Write a listener to handle Message arrival events for a specific Channel.
class SseEventHandler : BackgroundEventHandler {

    override fun onOpen() {
        // Write logic for handling successful SSE connection
    }

    override fun onClosed() {
        // Write logic for handling SSE connection closure
    }

    override fun onMessage(event: String, messageEvent: MessageEvent) {
        // Write logic for handling arrival of SSE events

        // event: String = Name of the channel or topic the event belongs to
        // messageEvent.lastEventId: String = ID of the arrived event
        // messageEvent.data: String = Data of the arrived event
    }

    override fun onComment(comment: String) {
    }

    override fun onError(t: Throwable) {
        // Write logic for handling errors before or after SSE connection

        // If the server responds with an error other than 2XX, com.launchdarkly.eventsource.StreamHttpErrorException: Server returned HTTP error 401 exception occurs
        // If the client sets a shorter connection time than the server, error=com.launchdarkly.eventsource.StreamIOException: java.net.SocketTimeoutException: timeout exception occurs
        // If the server terminates the connection due to exceeding the connection time, error=com.launchdarkly.eventsource.StreamClosedByServerException: Stream closed by server exception occurs
    }
}
  • Finally, implement the code to create the EventSource object as follows.
// Creating the EventSource object
val eventSource: BackgroundEventSource = BackgroundEventSource
    .Builder(
        SseEventHandler(),
        EventSource.Builder(
            ConnectStrategy
                .http(URL("{server-url}"))
                // Specifying custom request headers
                .header(
                    "Authorization",
                    "Bearer {token}"
                )
                .connectTimeout(3, TimeUnit.SECONDS)
                // Setting the maximum connection time, longer than the maximum connection time set by the server
                .readTimeout(600, TimeUnit.SECONDS)
        )
    )
    .threadPriority(Thread.MAX_PRIORITY)
    .build()

// Starting the EventSource connection
eventSource.start()

SSE Connection and Event Reception in iOS Swift

  • In the iOS Swift environment, implementation can be done using third-party libraries as follows.
  • First, add the library dependencies to the project environment as follows. (The same LaunchDarkly-produced EventSource library as in the Android example is used.)
// In CocoaPods environment, add the following to Podfile
pod 'LDSwiftEventSource', '~> 3.1'

// In Carthage environment, add the following to Cartfile
github "LaunchDarkly/swift-eventsource" ~> 3.1

// In Swift Package Manager environment, add the following to Package.swift
dependencies: [
    .package(url: "https://github.com/LaunchDarkly/swift-eventsource.git", .upToNextMajor(from: "3.1.1"))
]
  • Write a listener to handle Message arrival events for a specific Channel.
class SseEventHandler: EventHandler {

    func onOpened() {
        // Write logic for handling successful SSE connection
    }

    func onClosed() {
        // Write logic for handling SSE connection closure
    }

    func onMessage(eventType: String, messageEvent: MessageEvent) {
        // Write logic for handling arrival of SSE events

        // eventType: String = Name of the channel or topic the event belongs to
        // messageEvent.lastEventId: String = ID of the arrived event
        // messageEvent.data: String = Data of the arrived event
    }

    func onComment(comment: String) {
    }

    func onError(error: Error) {
        // Write logic for handling errors before or after SSE connection

        // error.responseCode: Int = Error response code
    }
}
  • Finally, implement the code to create the EventSource object as follows.
// Creating the EventSource object
var config = EventSource.Config(handler: SseEventHandler(), url: URL(string: "{server-url}")!)
// Specifying custom request headers
config.headers = ["Authorization": "Bearer {token}"]
// Setting the maximum connection time, longer than the maximum connection time set by the server
config.idelTimeout = 600.0
let eventSource = EventSource(config: config)

// Starting the EventSource connection
eventSource.start()