EventSource

A simple Swift client library for the Server Sent Events (SSE)

496
133
Swift

EventSource

EventSource

SSE Client written on Swift using NSURLSession.

Build Status codecov.io codecov.io

Abstract

This is an EventSource implementation written on Swift following the W3C EventSource document. If something is missing or not completely right open an issue and I’ll work on it!

If you like the library please leave us a ★. That helps us to stay engaged on the mantainence!

Changes from version 2.2.1 to 3.0

I took some time to review all the forks, pull requests and issues opened on github. The main changes and complains I found were related to the connection and the Last-Event-Id handling.

The changes on this version are:

  • EventSource doesn’t connect automatically anymore. It waits until connect(lastEventId: String? = nil) method is called. This method accepts a lastEventId which will be sent to the server upon connection.
  • EventSource lets you call disconnect() whenever you want.
  • EventSource doesn’t store the Last-Event-Id anymore and you will have to take care of storing the id and sending using it or not in the connect method.
  • EventSource doesn’t reconnect at all. If a network layer error occurs (disconnection, timeout, etc) or if the server closes the connection you will have to take care to reconnect with the server.
  • Modularization. This library has been around since Swift 1.0 and started just as a way to learn the language. With this new version the whole code has been improved, commented and fully tested to make it easier to track problems and extend in the future.

How to use it?

There is a simple working sample in the repository. Check the ViewController.swift to see how to use it.

Also in sse-server folder you will find an extremely simple node.js server to test the library. To run the server you just need to:

  • npm install
  • node sse.js

Install

Cocoapods

  1. Include EventSource in your Podfile: pod 'IKEventSource'

  2. Import the framework:

import IKEventSource

Carthage

  1. Include EventSource in your Cartfile: github "inaka/EventSource"

  2. Import the framework:

import IKEventSource

For further reference see Carthage’s documentation.

Swift Package Manager

  1. Include EventSource in your Package.swift: github "inaka/EventSource"
import PackageDescription

let package = Package(
dependencies: [
    .package(url: "https://github.com/inaka/EventSource.git", .branch("master"))
])
  1. Import the framework:
import IKEventSource

Swift API:

/// RetryTime: This can be changed remotly if the server sends an event `retry:`
var retryTime: Int { get }

/// URL where EventSource will listen for events.
var url: URL { get }

/// The last event id received from server. This id is neccesary to keep track of the last event-id received to avoid
/// receiving duplicate events after a reconnection.
var lastEventId: String? { get }

/// Current state of EventSource
var readyState: EventSourceState { get }

/// Method used to connect to server. It can receive an optional lastEventId indicating the Last-Event-ID
///
/// - Parameter lastEventId: optional value that is going to be added on the request header to server.
func connect(lastEventId: String?)

/// Method used to disconnect from server.
func disconnect()

/// Returns the list of event names that we are currently listening for.
///
/// - Returns: List of event names.
func events() -> [String]

/// Callback called when EventSource has successfully connected to the server.
///
/// - Parameter onOpenCallback: callback
func onOpen(_ onOpenCallback: @escaping (() -> Void))

/// Callback called once EventSource has disconnected from server. This can happen for multiple reasons.
/// The server could have requested the disconnection or maybe a network layer error, wrong URL or any other
/// error. The callback receives as parameters the status code of the disconnection, if we should reconnect or not
/// following event source rules and finally the network layer error if any. All this information is more than
/// enought for you to take a decition if you should reconnect or not.
/// - Parameter onOpenCallback: callback
func onComplete(_ onComplete: @escaping ((Int?, Bool?, NSError?) -> Void))

/// This callback is called everytime an event with name "message" or no name is received.
func onMessage(_ onMessageCallback: @escaping ((_ id: String?, _ event: String?, _ data: String?) -> Void))

/// Add an event handler for an specific event name.
///
/// - Parameters:
///   - event: name of the event to receive
///   - handler: this handler will be called everytime an event is received with this event-name
func addEventListener(_ event: String,
                      handler: @escaping ((_ id: String?, _ event: String?, _ data: String?) -> Void))

/// Remove an event handler for the event-name
///
/// - Parameter event: name of the listener to be remove from event source.
func removeEventListener(_ event: String)


Examples:


Event:

id: event-id
event: event-name
data: event-data

Calls

eventSource.addEventListener("event-name") { (id, event, data) in
  // Here you get an event 'event-name'
}

Event:

id: event-id
data: event-data
data: event-data

Calls

eventSource.onMessage { (id, event, data) in
  // Here you get an event without event name!
}

Event:

id: event-id
data: event-data-1
data: event-data-2
data: event-data-3

Calls

eventSource.onMessage { (id, event, data) in
  // Here you get an event without event name!
  // data: event-data-1\nevent-data-2\nevent-data-3
}

Event:

:heartbeat

Calls

nothing it's a comment

Live example

This is the example shipped with the app. If you run the server and run the app you will be able to see this example live. The moving box is just to show that everything works on background and the main thread performance shows no degradation. (The gif is pretty bad to see that, but if you click on the image you will be taken to the gfycat version of the gif which runs way smoother)

Sample

Contributors

Thanks to all the contributors for pointing out missing stuff or problems and fixing them or opening issues!!

Contact Us

If you find any bugs or have a problem while using this library, please open an issue in this repo (or a pull request 😃).

Please provide an example of the problem you are facing. If an event is not correctly parsed please provide a sample event.