/// Browser WebSocket client library for the Angel framework. library angel3_websocket.browser; import 'dart:async'; import 'dart:html'; import 'package:angel3_client/angel3_client.dart'; import 'package:angel3_http_exception/angel3_http_exception.dart'; import 'package:http/browser_client.dart' as http; import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/html.dart'; import 'base_websocket_client.dart'; export 'angel3_websocket.dart'; final RegExp _straySlashes = RegExp(r'(^/)|(/+$)'); /// Queries an Angel server via WebSockets. class WebSockets extends BaseWebSocketClient { final List _services = []; WebSockets(baseUrl, {bool reconnectOnClose = true, Duration? reconnectInterval}) : super(http.BrowserClient(), baseUrl, reconnectOnClose: reconnectOnClose, reconnectInterval: reconnectInterval); @override Future close() { for (var service in _services) { service.close(); } return super.close(); } @override Stream authenticateViaPopup(String url, {String eventName = 'token', String? errorMessage}) { var ctrl = StreamController(); var wnd = window.open(url, 'angel_client_auth_popup'); Timer t; StreamSubscription? sub; t = Timer.periodic(Duration(milliseconds: 500), (timer) { if (!ctrl.isClosed) { if (wnd.closed!) { ctrl.addError(AngelHttpException.notAuthenticated( message: errorMessage ?? 'Authentication via popup window failed.')); ctrl.close(); timer.cancel(); sub?.cancel(); } } else { timer.cancel(); } }); sub = window.on[eventName].listen((e) { if (!ctrl.isClosed) { ctrl.add((e as CustomEvent).detail.toString()); t.cancel(); ctrl.close(); sub!.cancel(); } }); return ctrl.stream; } @override Future getConnectedWebSocket() { var url = websocketUri; if (authToken?.isNotEmpty == true) { url = url.replace( queryParameters: Map.from(url.queryParameters) ..['token'] = authToken); } var socket = WebSocket(url.toString()); var completer = Completer(); socket ..onOpen.listen((_) { if (!completer.isCompleted) { return completer.complete(HtmlWebSocketChannel(socket)); } }) ..onError.listen((e) { if (!completer.isCompleted) { return completer.completeError(e is ErrorEvent ? e.error! : e); } }); return completer.future; } @override Service service(String path, {Type? type, AngelDeserializer? deserializer}) { var uri = path.replaceAll(_straySlashes, ''); return BrowserWebSocketsService(socket, this, uri, deserializer: deserializer) as Service; } } class BrowserWebSocketsService extends WebSocketsService { final Type? type; BrowserWebSocketsService(WebSocketChannel? socket, WebSockets app, String uri, {this.type, AngelDeserializer? deserializer}) : super(socket, app, uri, deserializer: deserializer); }