| // Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| @TestOn('vm') | |
| library; | |
| import 'dart:async'; | |
| import 'package:async/async.dart'; | |
| import 'package:shelf/shelf.dart' as shelf; | |
| import 'package:sse/server/sse_handler.dart'; | |
| import 'package:sse/src/server/sse_handler.dart' show closeSink; | |
| import 'package:stream_channel/stream_channel.dart'; | |
| import 'package:test/test.dart'; | |
| Future<void> sendRequest( | |
| String method, | |
| SseHandler handler, { | |
| Map<String, /* String | List<String> */ Object>? headers, | |
| Object? body, | |
| int? messageId, | |
| }) async { | |
| final streamController = StreamController<List<int>>(); | |
| void onHijack(void Function(StreamChannel<List<int>>) callback) { | |
| callback(StreamChannel(streamController.stream, streamController)); | |
| } | |
| final request = shelf.Request( | |
| method, | |
| Uri.http('localhost', 'test', { | |
| 'sseClientId': '14560f0c-c207-4245-9c1b-abe9faf8a1b4', | |
| if (messageId != null) 'messageId': messageId.toString(), | |
| }), | |
| headers: headers, | |
| body: body, | |
| onHijack: onHijack, | |
| ); | |
| try { | |
| await handler.handler(request); | |
| } on shelf.HijackException { | |
| // Swallow the expected HijackException. | |
| } | |
| } | |
| Future<void> createSseConnection(SseHandler handler) async { | |
| await sendRequest('GET', handler, headers: {'accept': 'text/event-stream'}); | |
| } | |
| Future<void> sendMessage( | |
| SseHandler handler, | |
| String message, | |
| int messageId, | |
| ) async { | |
| await sendRequest( | |
| 'POST', | |
| handler, | |
| headers: {'origin': 'http://localhost'}, | |
| body: '"$message"', | |
| messageId: messageId, | |
| ); | |
| } | |
| void main() { | |
| late SseHandler handler; | |
| group('SSE with server keep-alive', () { | |
| setUp(() async { | |
| handler = SseHandler( | |
| Uri(path: '/test'), | |
| keepAlive: const Duration(seconds: 5), | |
| ignoreDisconnect: const Duration(seconds: 5), | |
| ); | |
| }); | |
| tearDown(() async {}); | |
| test('handles out of order disconnect and reconnect', () async { | |
| var lastMessageId = -1; | |
| expect(handler.numberOfClients, 0); | |
| await createSseConnection(handler); | |
| var connection = await handler.connections.next; | |
| expect(handler.numberOfClients, 1); | |
| var queue = StreamQueue(connection.stream); | |
| await sendMessage(handler, 'baz', ++lastMessageId); | |
| expect(await queue.next, 'baz'); | |
| await sendMessage(handler, 'bar', ++lastMessageId); | |
| expect(await queue.next, 'bar'); | |
| // Simulate an out of order disconnect and reconnect by sending the create | |
| // connection request before the close event | |
| await createSseConnection(handler); | |
| closeSink(connection); | |
| await sendMessage(handler, 'foo', ++lastMessageId); | |
| expect(await queue.next, 'foo'); | |
| await sendMessage(handler, 'dart', ++lastMessageId); | |
| expect(await queue.next, 'dart'); | |
| expect(handler.numberOfClients, 1); | |
| }); | |
| }, timeout: const Timeout(Duration(seconds: 120))); | |
| } |