An implementation of Server-Sent Events (SSE) for Swift using async sequences. Events are parsed from any AsyncSequence of bytes and exposed as an AsyncSequence of ServerSentEvent values, with an optional EventSource-style reconnecting client built on URLSession.
EventSource mirrors the specification's EventSource interface: it sends the Accept: text/event-stream header, automatically reconnects when the server closes the stream or the network drops, honors the server's retry interval, and resumes with the Last-Event-ID header.
let url = URL(string: "https://example.com/sse")!
for try await event in EventSource(url: url) {
print(event.type, event.data)
}Iteration only ends when the server responds with HTTP 204 (the spec's "stop reconnecting" signal), an unrecoverable response is received (thrown as SSEError), or the consuming task is cancelled. To resume a previous session, pass the last event ID you stored:
let source = EventSource(
request: URLRequest(url: url),
configuration: .init(retryInterval: 3000, lastEventId: storedId)
)The URLSession helpers open one connection, send the required headers, and validate that the response is a 200 with a text/event-stream content type (throwing SSEError otherwise):
let (sse, response) = try await URLSession.shared.serverSentEvents(from: url)
for try await event in sse {
print(event.data)
}
// For manual reconnection:
let lastEventId = await sse.state.lastEventId
let retryInterval = await sse.state.retryIntervalYou can also parse an existing byte stream directly:
let (bytes, response) = try await URLSession.shared.bytes(from: url)
for try await event in bytes.sse() {
print(event)
}The parser is generic over AsyncSequence with UInt8 elements, so it isn't tied to URLSession. Any byte sequence can be wrapped directly or via the .sse() convenience:
let events = AsyncServerSentEvents(bytes: someByteSequence)
// or equivalently:
for try await event in someByteSequence.sse() { ... }Parsing is lazy and driven by iteration: bytes are only consumed as you request events, errors from the byte stream are rethrown to the consumer, and cancelling the consuming task stops the parse.
SSEParser is the synchronous core the async APIs are built on. Use it directly when bytes arrive through something other than an AsyncSequence — a delegate callback, a WebSocket frame — or when you already have the whole payload:
// Incremental: feed chunks as they arrive.
var parser = SSEParser()
for event in parser.consume(chunk) {
print(event.type, event.data)
}
// At end of stream: discards an incomplete trailing block (per spec) and
// resets for a new stream, keeping lastEventId/retryInterval for reconnection.
parser.finish()
// Buffered: parse a complete payload in one call.
let events = SSEParser.parse(data)SSEParser is a value type, never throws (malformed input is handled by the spec's own rules), and exposes lastEventId and retryInterval for implementing reconnection.
Each ServerSentEvent carries:
data— the event payload, with multipledatafields joined by newlines per the specname— the raweventfield, if present;typeapplies the spec's"message"defaultid— theidfield explicitly present on this event's block, if anylastEventId— the last event ID in effect when the event was dispatched (persists across events, per the spec); this is the value to resume from
- Synchronous incremental parsing (
SSEParser) usable without async sequences - WHATWG-compliant line parsing (
CR,LF,CRLF) - UTF-8 decoding with replacement characters and leading BOM removal
- Strict field parsing (
data,id,event,retry) with single-space value trim - Comment lines ignored per spec
- Last event ID persistence across events, committed at dispatch time
-
retryfield handling - Errors from the transport are rethrown to the consumer
-
Accept: text/event-streamrequest header and response validation (status and content type) - Automatic reconnection with
retryinterval andLast-Event-IDheader (EventSource) - HTTP 204 handled as "stop reconnecting"
The library works on Apple platforms and Linux. Instead of URLSession.AsyncBytes (which is unavailable on Linux), networking is built on SSEByteStream, a small delegate-based byte stream over URLSessionDataTask, so the parser, the URLSession helpers, and EventSource all behave identically everywhere. On Apple platforms, URLSession.AsyncBytes.sse() remains available as a convenience.