EventSource
open class EventSource : NSObject, URLSessionDataDelegate, @unchecked Sendable
Implementation of the W3C Server-Sent Events (SSE) protocol for streaming data from Horizon.
This class implements a client for the Server-Sent Events protocol, which enables servers to push real-time updates to clients over HTTP. The implementation is used throughout the SDK to stream live data from Stellar Horizon servers, including ledgers, transactions, operations, effects, and account changes.
Protocol Overview
Server-Sent Events provide a unidirectional channel from server to client over HTTP. The connection remains open, and the server pushes updates as formatted text messages. Key features include:
- Automatic reconnection with configurable retry intervals
- Event identification for resuming from last received message
- Named events for routing different message types
- Long-lived connections with appropriate timeouts
Usage Example
// Stream ledger updates from Horizon
let url = "https://horizon.stellar.org/ledgers?cursor=now"
let eventSource = EventSource(url: url)
eventSource.onOpen { response in
print("Connection opened, status: \(response?.statusCode ?? 0)")
}
eventSource.onMessage { id, event, data in
print("Received message - ID: \(id ?? "none"), Event: \(event ?? "none")")
// Parse data as JSON ledger response
if let ledgerData = data?.data(using: .utf8) {
// Process ledger data
}
}
eventSource.onError { error in
print("Connection error: \(error?.localizedDescription ?? "unknown")")
}
// Close when done
eventSource.close()
Event Handlers
The EventSource supports three types of callbacks:
onOpen: Called when connection is establishedonMessage: Called for each message receivedonError: Called when errors occur
Additionally, named event listeners can be registered for specific event types:
eventSource.addEventListener("ledger-update") { id, event, data in
// Handle ledger-specific events
}
Automatic Reconnection
The implementation automatically reconnects on connection failures using an
exponential backoff strategy. The retry interval can be set by the server
using the retry: field in the event stream, defaulting to 3000 milliseconds.
Last Event ID
To support reliable streaming, the last received event ID is persisted and
sent with reconnection requests using the Last-Event-Id header. This allows
the server to resume streaming from where it left off.
See also:
- W3C Server-Sent Events Specification
- Stellar developer docs
- [StreamingHelper] for simplified Horizon streaming integration
-
Current connection state (connecting, open, or closed) of the Server-Sent Events stream.
Declaration
Swift
open internal(set) var readyState: EventSourceState { get } -
Milliseconds to wait before attempting reconnection after connection failure.
Declaration
Swift
open fileprivate(set) var retryTime: Int { get } -
Creates a new EventSource instance and establishes connection to the specified URL.
The initializer creates an EventSource that immediately begins connecting to the server. Custom headers can be provided for authentication or other purposes.
Note
The connection starts automatically after initialization. Use
onOpen,onMessage, andonErrorcallbacks to handle connection lifecycle events.Declaration
Swift
public init(url: String, headers: [String : String] = [:])Parameters
urlThe URL string of the Server-Sent Events endpoint. Must be a valid URL.
headersOptional dictionary of HTTP headers to include in the request. Useful for adding authentication tokens or custom headers.
-
Closes the Server-Sent Events connection and prevents automatic reconnection.
Declaration
Swift
open func close() -
Registers a callback to be invoked when the connection opens.
The callback is called on the main thread when the Server-Sent Events connection is successfully established and transitions to the
openstate.Example
eventSource.onOpen { response in if let statusCode = response?.statusCode { print("Connected with status: \(statusCode)") } }Declaration
Swift
open func onOpen(_ onOpenCallback: @escaping (HTTPURLResponse?) -> Void)Parameters
onOpenCallbackClosure called when connection opens.
-
Registers a callback to be invoked when an error occurs.
The callback is called on the main thread when connection errors occur, including network failures, timeouts, or server errors. If an error occurred before this callback was registered, it will be immediately invoked with that error.
Example
eventSource.onError { error in print("Connection error: \(error?.localizedDescription ?? "unknown")") // Handle reconnection or cleanup }Note
The EventSource automatically attempts to reconnect after errors unless explicitly closed.Declaration
Swift
open func onError(_ onErrorCallback: @escaping (NSError?) -> Void)Parameters
onErrorCallbackClosure called when errors occur.
-
Registers a callback to be invoked when a message is received.
The callback is called on the main thread for each Server-Sent Event message received from the server. Messages without an explicit event type are dispatched to this handler with event type “message”.
Example
eventSource.onMessage { id, event, data in guard let jsonData = data?.data(using: .utf8), let ledger = try? JSONDecoder().decode(LedgerResponse.self, from: jsonData) else { return } print("Received ledger: \(ledger.sequence)") }Declaration
Swift
open func onMessage(_ onMessageCallback: @escaping @Sendable (_ id: String?, _ event: String?, _ data: String?) -> Void)Parameters
onMessageCallbackClosure called when messages are received.
-
Registers a handler for messages with a specific event type.
Allows routing of different event types to specific handlers. The Server-Sent Events protocol supports named events, enabling the server to send different types of messages that can be handled separately.
Example
// Register handler for specific event types eventSource.addEventListener("create") { id, event, data in print("Create event received") } eventSource.addEventListener("update") { id, event, data in print("Update event received") }Note
Only one handler can be registered per event type. Registering a new handler for an event type will replace any existing handler for that type.Declaration
Swift
open func addEventListener(_ event: String, handler: @escaping @Sendable (_ id: String?, _ event: String?, _ data: String?) -> Void)Parameters
eventThe event type name that was matched.
handlerClosure called when a message with the specified event type is received.
-
Removes the handler for a specific event type.
After removal, messages with the specified event type will no longer be dispatched to a handler.
Declaration
Swift
open func removeEventListener(_ event: String)Parameters
eventThe event type name to stop listening for.
-
Returns an array of all registered event type names.
Declaration
Swift
open func events() -> Array<String>Return Value
Array of event type names that currently have registered handlers. Does not include the default “message” event type handled by
onMessage. -
URLSessionDataDelegate method called when data is received from the stream.
Declaration
Swift
open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) -
URLSessionDataDelegate method called when the initial response is received from the server.
Declaration
Swift
open func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) -
URLSessionDelegate method called when the connection completes or encounters an error.
Declaration
Swift
open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) -
Creates an AsyncStream for receiving Server-Sent Events.
This method provides a modern async/await alternative to the callback-based API. Use this when you prefer structured concurrency over callbacks.
Example:
let eventSource = EventSource(url: "https://horizon.stellar.org/ledgers?cursor=now") let stream = eventSource.eventStream() for await (id, event, data) in stream { print("Event: \(event ?? "message")") if let ledgerData = data?.data(using: .utf8) { // Process ledger data } }Note
The stream will continue until the EventSource is closed or an error occurs. Only one AsyncStream should be active per EventSource instance.Declaration
Swift
open func eventStream() -> AsyncStream<(id: String?, event: String?, data: String?)>Return Value
An AsyncStream that yields tuples of (id, event, data) for each message
-
Generates a Basic Authentication header value from username and password.
Declaration
Swift
open class func basicAuth(_ username: String, password: String) -> StringParameters
usernameThe username for authentication
passwordThe password for authentication
Return Value
A properly formatted “Basic {base64}” authentication header value
View on GitHub
Install in Dash