| // 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<T> _connectWithSse<T extends VmService>({ |
| required Uri uri, |
| required void Function(Object?) onError, |
| required Completer<void> finishedCompleter, |
| required VmServiceFactory<T> serviceFactory, |
| }) { |
| final serviceCompleter = Completer<T>(); |
| |
| 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<String>; |
| 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<void>().catchError(onError)); |
| return serviceCompleter.future; |
| } |
| |
| Future<T> _connectWithWebSocket<T extends VmService>({ |
| required Uri uri, |
| required void Function(Object?) onError, |
| required Completer<void> finishedCompleter, |
| required VmServiceFactory<T> 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<T> connect<T extends VmService>({ |
| required Uri uri, |
| required Completer<void> finishedCompleter, |
| required VmServiceFactory<T> serviceFactory, |
| }) { |
| final connectedCompleter = Completer<T>(); |
| |
| void onError(Object? error) => connectedCompleter.safeCompleteError(error!); |
| |
| // Connects to a VM Service but does not verify the connection was fully |
| // successful. |
| Future<T> connectHelper() async { |
| final useSse = uri.scheme == 'sse' || uri.scheme == 'sses'; |
| final T service = useSse |
| ? await _connectWithSse<T>( |
| uri: uri, |
| onError: onError, |
| finishedCompleter: finishedCompleter, |
| serviceFactory: serviceFactory, |
| ) |
| : await _connectWithWebSocket<T>( |
| 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; |
| } |