sse_client_dart 3.0.0 copy "sse_client_dart: ^3.0.0" to clipboard
sse_client_dart: ^3.0.0 copied to clipboard

A Dart-IO based client implementation of Server-Sent Events.

sse_client_dart #

A Dart HTTP client for Server-Sent Events.

The package supports Dart IO and browser HTTP clients, configurable SSE reconnection, Last-Event-ID, cancellable retry delays, and deterministic connection cleanup.

Connect and listen #

import 'package:sse_client_dart/sse_client_dart.dart';

Future<void> main() async {
  final source = await EventSource.connect('https://example.com/events');
  final subscription = source.onMessage.listen((event) {
    print(event.data);
  });

  // Cancels this listener. With closeOnLastListener enabled, this Future also
  // waits for the current HTTP SSE connection to be cleaned up.
  await subscription.cancel();

  // Permanently disposes the EventSource and its internally owned HTTP client.
  await source.close();
}

An event without an explicit event: field is emitted with the standard message type and is available through onMessage. Named server events remain available through the main EventSource stream:

final alerts = source.where((event) => event.event == 'alert');

Connection notifications are separate from server messages:

source.onOpen.listen((_) => print('connected'));
source.onError.listen((event) => print(event.data));

Connection-level failures are emitted only through source.onError. source.listen(..., onError: ...) does not receive them. A server-sent event whose field is event: error remains an ordinary SSE event on the main stream; it is not a connection failure.

Listener-controlled connections #

To defer the HTTP request until the first listener and stop it after the final listener is cancelled:

final source = await EventSource.connect(
  'https://example.com/events',
  openOnlyOnFirstListener: true,
  closeOnLastListener: true,
);

final subscription = source.listen(print);
await subscription.cancel(); // Waits for current connection cleanup.

Stopping the last-listener connection does not permanently dispose the EventSource. Adding a later listener, or calling reopen(), creates a new connection. Calling close() is permanent.

If a new listener is added while final-listener cleanup is still running, the old connection is fully stopped before exactly one new connection is opened. A concurrent close() always wins and prevents that reopen.

closeOnLastListener remains false by default. In that mode, cancelling all listeners does not stop the connection; call source.close() when finished.

HTTP client ownership #

If no client is supplied, EventSource creates and owns an HTTP client. It closes that client when the connection is stopped and creates a fresh client if the reusable EventSource is opened again.

If a client is injected, EventSource never closes the shared client. It uses an AbortableRequest to abort only its current request:

import 'package:http/http.dart' as http;

final sharedClient = http.Client();
final source = await EventSource.connect(url, client: sharedClient);

await source.close();
// sharedClient is still caller-owned and may be reused.

Standard IOClient and BrowserClient implementations honor abortable requests. A custom BaseClient must honor AbortableRequest.abortTrigger to support cancellation while send() is still pending. Responses that arrive after cancellation are discarded and their streams are cancelled.

For browsers, pass a BrowserClient when you need to configure one explicitly:

import 'package:http/browser_client.dart';

final source = await EventSource.connect(url, client: BrowserClient());

Reconnection #

autoReconnect defaults to true, preserving standard EventSource behavior for GET requests. Normal response EOF and recoverable transport errors then schedule a reconnect. The server may update the base delay with an SSE retry: field. Consecutive request failures use bounded exponential backoff. Pending retry timers are cancelled by final-listener cleanup or close().

Disable automatic reconnection for POST or any request that may have side effects. This guarantees that normal EOF and transport errors do not resubmit the request body:

final source = await EventSource.connect(
  'https://example.com/chat',
  method: 'POST',
  body: requestBody,
  autoReconnect: false,
);

source.onError.listen((error) {
  // Observe connection EOF/failure without resubmitting the POST.
  print(error.data);
});

With autoReconnect: false, an accepted retry: field may update the stored delay but never starts another request. After EOF or a transport failure, readyState becomes CLOSED. The caller may still explicitly call reopen() if resubmission is intentional.

The most recently accepted SSE id: value is retained and sent in the next Last-Event-ID request header. An empty id: clears it.

HTTP responses must have status 200 and a text/event-stream content type. HTTP 204 and other invalid responses stop background reconnect attempts and are reported through onError. Initial connection failures are thrown by EventSource.connect() with their original HTTP status.

Ready state #

readyState uses the familiar values CONNECTING, OPEN, and CLOSED. A caller may supply a ready-state controller before connection starts:

final states = StreamController<EventSourceReadyState>.broadcast();
final source = await EventSource.connect(
  url,
  readyStateController: states,
);

Injected controllers remain caller-owned and are not closed by EventSource.

License #

MIT. See LICENSE.

0
likes
150
points
57
downloads

Documentation

API reference

Publisher

verified publisherdjbird.top

Weekly Downloads

A Dart-IO based client implementation of Server-Sent Events.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

http, http_parser

More

Packages that depend on sse_client_dart