// Copyright 2023 The Flutter Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. // Code needs to match API from VmService. // ignore_for_file: avoid-dynamic import 'dart:async'; import 'package:vm_service/utils.dart'; import 'package:vm_service/vm_service.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import '../sse/sse_shim.dart'; import '../utils/utils.dart'; Future _connectWithSse({ required Uri uri, required void Function(Object?) onError, required Completer finishedCompleter, required VmServiceFactory serviceFactory, }) { final serviceCompleter = Completer(); uri = uri.scheme == 'sse' ? uri.replace(scheme: 'http') : uri.replace(scheme: 'https'); final client = SseClient('$uri', debugKey: 'DevToolsService'); final stream = client.stream!.asBroadcastStream() as Stream; final service = serviceFactory( inStream: stream, writeMessage: client.sink!.add, wsUri: uri.toString(), ); unawaited( client.sink!.done.whenComplete(() async { finishedCompleter.complete(); await service.dispose(); }), ); serviceCompleter.complete(service); unawaited(stream.drain().catchError(onError)); return serviceCompleter.future; } Future _connectWithWebSocket({ required Uri uri, required void Function(Object?) onError, required Completer finishedCompleter, required VmServiceFactory serviceFactory, }) async { // Map the URI (which may be Observatory web app) to a WebSocket URI for // the VM service. uri = convertToWebSocketUrl(serviceProtocolUrl: uri); final ws = WebSocketChannel.connect(uri); final stream = ws.stream.handleError(onError); final service = serviceFactory( inStream: stream, writeMessage: (String message) { ws.sink.add(message); }, wsUri: uri.toString(), ); if (ws.closeCode != null) { onError(null); return service; } unawaited( ws.sink.done.then( (_) async { finishedCompleter.complete(); await service.dispose(); }, onError: onError, ), ); return service; } Future connect({ required Uri uri, required Completer finishedCompleter, required VmServiceFactory serviceFactory, }) { final connectedCompleter = Completer(); void onError(Object? error) => connectedCompleter.safeCompleteError(error!); // Connects to a VM Service but does not verify the connection was fully // successful. Future connectHelper() async { final useSse = uri.scheme == 'sse' || uri.scheme == 'sses'; final T service = useSse ? await _connectWithSse( uri: uri, onError: onError, finishedCompleter: finishedCompleter, serviceFactory: serviceFactory, ) : await _connectWithWebSocket( uri: uri, onError: onError, finishedCompleter: finishedCompleter, serviceFactory: serviceFactory, ); // Verify that the VM is alive enough to actually get the version before // considering it successfully connected. Otherwise, VMService instances // that failed part way through the connection may appear to be connected. await service.getVersion(); return service; } unawaited(connectHelper().then( (service) => connectedCompleter.safeComplete(service), onError: onError, )); unawaited(finishedCompleter.future.then((_) { // It is an error if we finish before we are connected. if (!connectedCompleter.isCompleted) { onError(null); } })); return connectedCompleter.future; }