Abonnement /cards/stream pour sync co-parent en temps réel ; acquittement (arrêt maladie) en un clic, sans confirmation. Co-authored-by: Cursor <cursoragent@cursor.com>
61 lines
1.5 KiB
Dart
61 lines
1.5 KiB
Dart
// Connecteur web uniquement (import conditionnel).
|
|
// ignore_for_file: avoid_web_libraries_in_flutter, deprecated_member_use
|
|
|
|
import 'dart:html' as html;
|
|
|
|
/// Connecteur SSE navigateur (EventSource) — ticket #195.
|
|
/// Auth via `?access_token=` (EventSource ne permet pas Authorization).
|
|
class CardsStreamConnector {
|
|
html.EventSource? _source;
|
|
final Map<String, html.EventListener> _listeners = {};
|
|
|
|
Future<void> connect({
|
|
required String url,
|
|
String? bearerToken,
|
|
required void Function(String eventType, String data) onEvent,
|
|
required void Function(Object error) onError,
|
|
}) async {
|
|
close();
|
|
final source = html.EventSource(url);
|
|
_source = source;
|
|
|
|
const names = [
|
|
'card.created',
|
|
'card.updated',
|
|
'card.deleted',
|
|
'response.added',
|
|
'heartbeat',
|
|
];
|
|
for (final name in names) {
|
|
void listener(html.Event raw) {
|
|
final msg = raw as html.MessageEvent;
|
|
final data = msg.data?.toString() ?? '';
|
|
if (data.isEmpty) return;
|
|
onEvent(name, data);
|
|
}
|
|
|
|
_listeners[name] = listener;
|
|
source.addEventListener(name, listener);
|
|
}
|
|
|
|
void onErr(html.Event _) {
|
|
onError(StateError('SSE cards déconnecté'));
|
|
}
|
|
|
|
_listeners['error'] = onErr;
|
|
source.addEventListener('error', onErr);
|
|
}
|
|
|
|
void close() {
|
|
final source = _source;
|
|
if (source != null) {
|
|
_listeners.forEach((name, listener) {
|
|
source.removeEventListener(name, listener);
|
|
});
|
|
source.close();
|
|
}
|
|
_listeners.clear();
|
|
_source = null;
|
|
}
|
|
}
|